~magentoerpconnect-core-editors/magentoerpconnect/trunk_version

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
# -*- coding: utf-8 -*-
#########################################################################
#                                                                       #
#########################################################################
#                                                                       #
# Copyright (C) 2011  Sharoon Thomas                                    #
# Copyright (C) 2009  Raphaël Valyi                                     #
# Copyright (C) 2011 Akretion Sébastien BEAU sebastien.beau@akretion.com#
# Copyright (C) 2011-2012 Camptocamp Guewen Baconnier                   #
#                                                                       #
#This program is free software: you can redistribute it and/or modify   #
#it under the terms of the GNU General Public License as published by   #
#the Free Software Foundation, either version 3 of the License, or      #
#(at your option) any later version.                                    #
#                                                                       #
#This program is distributed in the hope that it will be useful,        #
#but WITHOUT ANY WARRANTY; without even the implied warranty of         #
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          #
#GNU General Public License for more details.                           #
#                                                                       #
#You should have received a copy of the GNU General Public License      #
#along with this program.  If not, see <http://www.gnu.org/licenses/>.  #
#########################################################################

from osv import osv, fields
import pooler
import magerp_osv
import netsvc
import logging
from tools.translate import _
import string
#from datetime import datetime
from contextlib import closing
import tools
import time
from tools import DEFAULT_SERVER_DATETIME_FORMAT

DEBUG = True
NOTRY = False

#TODO, may be move that on out CSV mapping, but not sure we can easily
#see OpenERP sale/sale.py and Magento app/code/core/Mage/Sales/Model/Order.php for details
ORDER_STATUS_MAPPING = {
    'manual': 'processing',
    'progress': 'processing',
    'shipping_except': 'complete',
    'invoice_except': 'complete',
    'done': 'complete',
    'cancel': 'canceled',
    'waiting_date': 'holded'}
SALE_ORDER_IMPORT_STEP = 200
PARTNER_IMPORT_STEP = 400


class external_shop_group(magerp_osv.magerp_osv):
    _inherit = 'external.shop.group'

    @staticmethod
    def _get_magento_partners(connection, filters, delta=PARTNER_IMPORT_STEP):
        """
        Get customer using pagination, we get all ids
        then ask customer info per chunk of ids
        this to avoid memory and timing issues on Magento server

        :param connection: connection data to magento
        :param filters: filter terms for partner search
        :param delta: size of groups to import
        """
        customer_ids = connection.call('ol_customer.search', filters)

        data = []
        for i in xrange(0, len(customer_ids), delta):
            filters = [{'customer_id': {'in': customer_ids[i:i+delta]}}]
            data += connection.call('customer.list', filters)
        return data

    @staticmethod
    def _get_magento_partners_update(connection, from_date=False, website_id=False):
        """
        Get data from magento of new and updated customers since a specific_date

        :param connection: connection data to magento
        :param from_data: date from which we get the changes
        """
        filters = []
        if from_date:
            filters = [{'updated_at': {'gt': from_date}}]
        if website_id:
            if filters:
                filters[0].update(website_id={'eq': website_id})
            else:
                filters = [{'website_id': {'eq': website_id}}]

        return external_shop_group._get_magento_partners(connection, filters)

    def _import_partners(self, cr, uid, group, context=None):
        """
        Import partners for a single shop group

        :param group: browse record of an external.shop.group
        """
        if context is None: context = {}

        referential_id = group.referential_id.id
        from_date = group.import_partners_from_date
        website_id = self.oeid_to_extid(cr, uid, group.id, referential_id)
        partner_obj = self.pool.get('res.partner')
        result = super(external_shop_group, self)._import_partners(
                cr, uid, group,
                context=context)

        ref_obj = self.pool.get('external.referential')
        connection = context.get('conn_obj') or \
                        ref_obj.external_connection(
                            cr, uid, referential_id, context=context)

        [result.setdefault(key, []) for key in ['create_ids', 'write_ids', 'unchanged_ids']]

        # Get partners from magento which where created or updated
        # since last import
        data = self._get_magento_partners_update(connection, from_date, website_id)

        data.sort(key=lambda customer: customer['updated_at'] or customer['created_at'])

        imp_ctx = dict(context, import_no_new_cr=True)
        with closing(pooler.get_db(cr.dbname).cursor()) as local_cr:
            # here we commit each partner updated or created as it can
            # be an long processing and we want to be able to restart
            # from the state when it failed
            for customer in data:
                ext_id = customer['customer_id']
                change_date = customer['updated_at'] or customer['created_at']
                try:
                    current_result = partner_obj._import_partner_from_external(
                            local_cr, uid, ext_id,
                            connection, referential_id,
                            context=imp_ctx)
                except Exception as e:
                    local_cr.rollback()
                    if "res_partner_emailid_uniq" in e.message:
                        message = _("Cannot import a partner: "
                                    "A partner with email %s already exists ")\
                                    % customer['email']
                        raise osv.except_osv(_("Import Error"), message)
                    else:
                        raise
                else:
                    self.write(
                            local_cr, uid,
                            group.id,
                            {'import_partners_from_date': change_date},
                            context=context)
                    local_cr.commit()
                    result['create_ids'] += current_result.get('create_ids')
                    result['write_ids'] += current_result.get('write_ids')

        return result

    def run_import_partners_scheduler(self, cr, uid, context=None):
        """
        Methode to launch the import on all external shop groups
        to be used by a cron job
        """
        group_ids = self.search(cr, uid, [], context=context)

        if group_ids:
            shop_groups = self.browse(cr, uid, ids, context=context)

            for group in shop_groups:
                self._import_partners(cr, uid, group, context=context)
        return True


external_shop_group()

class sale_shop(magerp_osv.magerp_osv):
    _inherit = "sale.shop"

    def _get_exportable_product_ids(self, cr, uid, ids, name, args, context=None):
        res = super(sale_shop, self)._get_exportable_product_ids(cr, uid, ids, name, args, context=None)
        for shop_id in res:
            website_id =  self.read(cr, uid, shop_id, ['shop_group_id'])
            if website_id.get('shop_group_id', False):
                res[shop_id] = self.pool.get('product.product').search(cr, uid, [('magento_exportable', '=', True), ('id', 'in', res[shop_id]), "|", ('websites_ids', 'in', [website_id['shop_group_id'][0]]) , ('websites_ids', '=', False)])
            else:
                res[shop_id] = []
        return res

    def _get_default_storeview_id(self, cr, uid, ids, prop, unknow_none, context=None):
        res = {}
        for shop in self.browse(cr, uid, ids, context):
            if shop.default_storeview_integer_id:
                rid = self.pool.get('magerp.storeviews').extid_to_oeid(cr, uid, shop.default_storeview_integer_id, shop.referential_id.id)
                res[shop.id] = rid
            else:
                res[shop.id] = False
        return res

    def export_images(self, cr, uid, ids, context=None):
        if context is None: context = {}
        logger = logging.getLogger('ext synchro')
        start_date = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
        image_obj = self.pool.get('product.images')
        for shop in self.browse(cr, uid, ids):
            context['shop_id'] = shop.id
            context['external_referential_id'] = shop.referential_id.id
            context['conn_obj'] = shop.referential_id.external_connection()
            context['last_images_export_date'] = shop.last_images_export_date
            exportable_product_ids = self.read(cr, uid, shop.id, ['exportable_product_ids'], context=context)['exportable_product_ids']
            res = self.pool.get('product.product').get_exportable_images(cr, uid, exportable_product_ids, context=context)
            if res:
                logger.info("Creating %s images", len(res['to_create']))
                logger.info("Updating %s images", len(res['to_update']))
                image_obj.update_remote_images(cr, uid, res['to_update']+res['to_create'], context)
            self.write(cr,uid,context['shop_id'],{'last_images_export_date': start_date})
        return True


    def _get_rootcategory(self, cr, uid, ids, prop, unknow_none, context=None):
        res = {}
        for shop in self.browse(cr, uid, ids, context):
            if shop.root_category_id and shop.shop_group_id.referential_id:
                rid = self.pool.get('product.category').extid_to_oeid(cr, uid, shop.root_category_id, shop.shop_group_id.referential_id.id)
                res[shop.id] = rid
            else:
                res[shop.id] = False
        return res

    def _set_rootcategory(self, cr, uid, id, name, value, fnct_inv_arg, context=None):
        ir_model_data_obj = self.pool.get('ir.model.data')
        shop = self.browse(cr, uid, id, context=context)
        if shop.root_category_id:
            model_data_id = self.pool.get('product.category').\
            extid_to_existing_oeid(cr, uid, shop.root_category_id, shop.referential_id.id, context=context)
            if model_data_id:
                ir_model_data_obj.write(cr, uid, model_data_id, {'res_id' : value}, context=context)
            else:
                raise osv.except_osv(_('Warning!'), _('No external id found, are you sure that the referential are syncronized? Please contact your administrator. (more information in magentoerpconnect/sale.py)'))
        return True

    def _get_exportable_root_category_ids(self, cr, uid, ids, prop, unknow_none, context=None):
        res = {}
        res1 = self._get_rootcategory(cr, uid, ids, prop, unknow_none, context)
        for shop in self.browse(cr, uid, ids, context):
            res[shop.id] = res1[shop.id] and [res1[shop.id]] or []
        return res

    _columns = {
        'default_storeview_integer_id':fields.integer('Magento default Storeview ID'), #This field can't be a many2one because store field will be mapped before creating storeviews
        'default_storeview_id':fields.function(_get_default_storeview_id, type="many2one", relation="magerp.storeviews", method=True, string="Default Storeview"),
        'root_category_id':fields.integer('Root product Category'), #This field can't be a many2one because store field will be mapped before creating category
        'magento_root_category':fields.function(_get_rootcategory, fnct_inv = _set_rootcategory, type="many2one", relation="product.category", method=True, string="Root Category", store=True),
        'exportable_root_category_ids': fields.function(_get_exportable_root_category_ids, type="many2many", relation="product.category", method=True, string="Root Category"), #fields.function(_get_exportable_root_category_ids, type="many2one", relation="product.category", method=True, 'Exportable Root Categories'),
        'storeview_ids': fields.one2many('magerp.storeviews', 'shop_id', 'Store Views'),
        'exportable_product_ids': fields.function(_get_exportable_product_ids, method=True, type='one2many', relation="product.product", string='Exportable Products'),
        'magento_shop': fields.boolean('Magento Shop', readonly=True),
        'allow_magento_order_status_push': fields.boolean('Allow Magento Order Status push', help='Allow to send back order status to Magento if order status changed in OpenERP first?'),
        'allow_magento_notification': fields.boolean('Allow Magento Notification', help='Allow Magento to notify customer with an e-mail when OpenERP change an order status, create an invoice or a delivery order on Magento.'),
    }

    _defaults = {
        'allow_magento_order_status_push': lambda * a: False,
        'allow_magento_notification': lambda * a: False,
    }


    def import_shop_orders(self, cr, uid, shop, defaults, context=None):
        if context is None: context = {}
        result = super(sale_shop, self).import_shop_orders(cr, uid, shop, defaults=defaults, context=context)
        [result.setdefault(key, []) for key in ['create_ids', 'write_ids', 'unchanged_ids']]
        if shop.magento_shop:
            self.check_need_to_update(cr, uid, [shop.id], context=context)
            for storeview in shop.storeview_ids:
                magento_storeview_id = self.pool.get('magerp.storeviews').oeid_to_extid(cr, uid, storeview.id, shop.referential_id.id, context={})
                ids_or_filter = {'store_id': {'eq': magento_storeview_id}, 'state': {'neq': 'canceled'}}
                if shop.import_orders_from_date:
                    ids_or_filter.update({'created_at' : {'gt': shop.import_orders_from_date}})
                nb_last_created_ids = SALE_ORDER_IMPORT_STEP
                while nb_last_created_ids:
                    defaults['magento_storeview_id'] = storeview.id
                    ctx = context.copy()
                    ctx['ids_or_filter'] = ids_or_filter
                    resp = self.pool.get('sale.order').mage_import_base(cr, uid, context.get('conn_obj', False),
                                                                        shop.referential_id.id, defaults=defaults,
                                                                        context=ctx)
                    result['create_ids'] += resp.get('create_ids', [])
                    result['write_ids'] += resp.get('write_ids', [])
                    result['unchanged_ids'] += resp.get('unchanged_ids', [])
                    nb_last_created_ids = len(resp.get('create_ids', []) + resp.get('write_ids', []) + resp.get('unchanged_ids', []))
                    print nb_last_created_ids
        return result

    def check_need_to_update(self, cr, uid, ids, context=None):
        """ This function will update the order status in OpenERP for
        the order which are in the state 'need to update' """
        so_obj = self.pool.get('sale.order')

        for shop in self.browse(cr, uid, ids):
            conn = shop.referential_id.external_connection()
            # Update the state of orders in OERP that are in "need_to_update":True
            # from the Magento's corresponding orders

            # Get all need_to_update orders in OERP
            orders_to_update = so_obj.search(
                cr, uid,
                [('need_to_update', '=', True),
                 ('shop_id', '=', shop.id)],
                context=context)
            so_obj.check_need_to_update(
                cr, uid, orders_to_update, conn, context=context)
        return False

    def update_shop_orders(self, cr, uid, order, ext_id, context=None):
        if context is None: context = {}
        result = {}

        if order.shop_id.allow_magento_order_status_push:
            sale_obj = self.pool.get('sale.order')
            #status update:
            conn = context.get('conn_obj', False)
            status = ORDER_STATUS_MAPPING.get(order.state, False)
            if status:
                result['status_change'] = conn.call(
                    'sales_order.addComment',
                    [ext_id, status, '',
                     order.shop_id.allow_magento_notification])
                # If status has changed into OERP and the order need_to_update,
                # then we consider the update is done
                # remove the 'need_to_update': True
                if order.need_to_update:
                    sale_obj.write(
                        cr, uid, order.id, {'need_to_update': False})

            sale_obj.export_invoice(
                cr, uid, order, conn, ext_id, context=context)
        return result

    def _sale_shop(self, cr, uid, callback, context=None):
        if context is None:
            context = {}
        proxy = self.pool.get('sale.shop')
        domain = [ ('magento_shop', '=', True), ('auto_import', '=', True) ]

        ids = proxy.search(cr, uid, domain, context=context)
        if ids:
            callback(cr, uid, ids, context=context)

        # tools.debug(callback)
        # tools.debug(ids)
        return True

    # Schedules functions ============ #
    def run_import_orders_scheduler(self, cr, uid, context=None):
        self._sale_shop(cr, uid, self.import_orders, context=context)

    def run_update_orders_scheduler(self, cr, uid, context=None):
        self._sale_shop(cr, uid, self.update_orders, context=context)

    def run_export_catalog_scheduler(self, cr, uid, context=None):
        self._sale_shop(cr, uid, self.export_catalog, context=context)

    def run_export_stock_levels_scheduler(self, cr, uid, context=None):
        self._sale_shop(cr, uid, self.export_inventory, context=context)

    def run_update_images_scheduler(self, cr, uid, context=None):
        self._sale_shop(cr, uid, self.export_images, context=context)

    def run_export_shipping_scheduler(self, cr, uid, context=None):
        self._sale_shop(cr, uid, self.export_shipping, context=context)

sale_shop()


class sale_order(magerp_osv.magerp_osv):
    _inherit = "sale.order"

    _columns = {
        'magento_incrementid': fields.char('Magento Increment ID', size=32),
        'magento_storeview_id': fields.many2one('magerp.storeviews', 'Magento Store View'),
        'is_magento': fields.related(
            'shop_id', 'referential_id', 'magento_referential',
            type='boolean',
            string='Is a Magento Sale Order')
    }

    def _auto_init(self, cr, context=None):
        tools.drop_view_if_exists(cr, 'sale_report')
        cr.execute("ALTER TABLE sale_order_line ALTER COLUMN discount TYPE numeric(16,6);")
        cr.execute("ALTER TABLE account_invoice_line ALTER COLUMN discount TYPE numeric(16,6);")
        self.pool.get('sale.report').init(cr)
        super(sale_order, self)._auto_init(cr, context)

    def get_order_addresses(self, cr, uid, referential_id, data_record, context=None):
        partner_obj = self.pool.get('res.partner')
        partner_address_obj = self.pool.get('res.partner.address')
        res = {}

        # Magento allows to create a sale order
        # without register as a user
        is_guest_order = bool(int(data_record.get('customer_is_guest', 0)))

        # for a guest order or when magento
        # does not provide customer_id on a normal order
        # (it happens magento inconsistencies are common)
        if (is_guest_order or
           (data_record.get('website_id') and
           not data_record.get('customer_id'))):

            website_obj = self.pool.get('external.shop.group')
            oerp_website_id = website_obj.extid_to_oeid(
                cr, uid, data_record['website_id'], referential_id, context=context)

            # first, try to find an already bound partner
            # with same mail for same website
            partner_id = partner_obj.search_magento_partner(
                cr, uid,
                data_record['customer_email'],
                oerp_website_id,
                is_bound=True,  # search only for partners already bound
                context=context)

            # if we have found one, we "fix" the data_record
            # with the magento customer id
            if partner_id:
                ext_customer_id = partner_obj.oeid_to_extid(
                    cr, uid, partner_id, referential_id, context=context)
                data_record['customer_id'] = ext_customer_id

            # no bound partner matching
            # means that we have to consider it as a guest order
            else:
                is_guest_order = True

        # if we do not have a customer_id, the sale order
        # is a guest sale order, so we cannot link the customer
        if is_guest_order:
            # when we import a guest sale order, we create the partner
            # on the fly and we do not bind it ir.model.data

            # as we do not have a customer data record in the sale order
            # data record, we manually populate one from the magento
            # sale order record and import it with the standard mappings
            address = data_record['billing_address']

            customer_record = {
                'firstname': address['firstname'],
                'middlename': address['middlename'],
                'lastname': address['lastname'],
                'prefix': address['prefix'],
                'suffix': address.get('suffix', False),
                'email': data_record.get('customer_email', False),
                'taxvat': data_record.get('customer_taxvat', False),
                'group_id': data_record.get('customer_group_id', False),
                'gender': data_record.get('customer_gender', False),
                'store_id': data_record['store_id'],
                'created_at': data_record['created_at'],
                'updated_at': False,
                'created_in': False,
                'dob': data_record.get('customer_dob', False),
                'website_id': data_record.get('website_id', False),
            }
            partner_defaults = {'is_magento_guest': True}
            partner_id = partner_obj.ext_import_unbound(
                cr, uid, customer_record, referential_id,
                defaults=partner_defaults, context=context)
        else:
            ext_id = data_record['customer_id']
            connection = context.get('conn_obj')
            website_id = data_record.get('website_id')
            if website_id:
                website_id = int(data_record['website_id'])
            # check if another customer exists
            existing_id = partner_obj.search_magento_partner(
                    cr, uid,
                    data_record['customer_email'],
                    website_id,
                    context=context)
            if existing_id:
                # check if the ext_id is the same
                # to make sure it isn't some outdated data
                # This could happen when a customer change his email
                # from A to B on a old account and create
                # a new account with A address.
                # in such case we want to make sure the old account is
                # updated
                existing_ext_id = partner_obj.oeid_to_extid(cr, uid, existing_id, referential_id)
                if existing_ext_id != ext_id:
                    partner_obj._import_partner_from_external(
                            cr, uid, existing_ext_id,
                            connection, referential_id,
                            context=context)

            # always update the customer when importing an order
            result = partner_obj._import_partner_from_external(
                    cr, uid, ext_id,
                    connection, referential_id,
                    context=context)
            partner_id = (result['create_ids'] and result['create_ids'][0]
                          or result['write_ids'] and result['write_ids'][0])

        # The addresses of the sale order are imported as active=false
        # so they are linked with the sale order but they are not displayed
        # in the customer form and the searches.

        # We import the addresses of the sale order as Active = False
        # so they will be available in the documents generated as the
        # sale order or the picking, but they won't be available on
        # the partner form or the searches. Too many adresses would
        # be displayed.
        # They are never synchronized,
        # we keep the revision of the sale order
        # For the orders which are from guests, we let the addresses
        # as active because they doesn't have an address book.
        addresses_defaults = {'partner_id': partner_id,
                              'email': data_record.get('customer_email', False),
                              'active': is_guest_order,
                              'is_magento_order_address': True}
        # We import the addresses as unbound becauses Magento is not able to
        # give us an ID when the address is created during the order.
        # It gives an empty id, a 0 or worse, a wrong id.
        billing_address = data_record['billing_address'].copy()
        # remove customer_id because we force it in the defaults
        # it avoid to try to import the partner by customer_id
        billing_address.pop('customer_id', False)
        billing_id = partner_address_obj.ext_import_unbound(
            cr, uid, billing_address,
            referential_id, defaults=addresses_defaults,
            context=context)

        shipping_id = False
        if data_record['shipping_address']:
            shipping_address = data_record['shipping_address'].copy()
            # remove customer_id because we force it in the defaults
            # it avoid to try to import the partner by customer_id
            shipping_address.pop('customer_id', False)
            shipping_id = partner_address_obj.ext_import_unbound(
                cr, uid, shipping_address,
                referential_id, defaults=addresses_defaults,
                context=context)

        # default addresses ids for the sale order
        res['partner_id'] = partner_id
        res['partner_order_id'] = res['partner_invoice_id'] = billing_id
        res['partner_shipping_id'] = shipping_id or billing_id

        if data_record.get('customer_taxvat'):
            partner_obj.add_magento_vat_number(
                cr, uid, partner_id,
                data_record['customer_taxvat'],
                data_record['billing_address'].get('country_id'),
                context=context)
        return res

    def add_order_extra_line(self, cr, uid, res, data_record, ext_field, product_ref, defaults, context=None):
        """ Add or substract amount on order as a separate line item with single quantity for each type of amounts like :
        shipping, cash on delivery, discount, gift certificates...

        @param res: dict of the order to create
        @param data_record: full data dict of the order
        @param ext_field: name of the field in data_record where the amount of the extra lineis stored
        @param product_ref: tuple with module and xml_id (module, xml_id) of the product to use for the extra line
            or id as int/long of the product

        Optional arguments in context:
        sign: multiply the amount with the sign to add or substract it from the sale order
        ext_tax_field: name of the field in data_record where the tax amount is stored
        ext_code_field: name of the field in data_record containing a code (for coupons and gift certificates) which will be printed on the product name
        """
        if context is None: context = {}
        model_data_obj = self.pool.get('ir.model.data')
        sign = 'sign' in context and context['sign'] or 1
        ext_tax_field = 'ext_tax_field' in context and context['ext_tax_field'] or None
        ext_code_field = 'ext_code_field' in context and context['ext_code_field'] or None

        if isinstance(product_ref, tuple):
            dummy, product_id = model_data_obj.get_object_reference(cr, uid, *product_ref)
        else:
            product_id = product_ref
        product = self.pool.get('product.product').browse(cr, uid, product_id, context)
        is_tax_included = context.get('price_is_tax_included', False)
        amount = float(data_record[ext_field]) * sign
        name = product.name
        if ext_code_field and data_record.get(ext_code_field, False):
            name = "%s [%s]" % (name, data_record[ext_code_field])

        if is_tax_included:
            price_unit = float(amount) + float(data_record[ext_tax_field])
        else:
            price_unit = float(amount)

        extra_line = {
                        'product_id': product.id,
                        'name': name,
                        'product_uom': product.uom_id.id,
                        'product_uom_qty': 1,
                        'price_unit': price_unit,
                    }

        if not res.get('order_line'):
            res['order_line'] = []

        if context.get('use_external_tax'):
            # get the tax computed by the external system
            tax_vat = abs(float(data_record[ext_tax_field]) / amount)
            line_tax_id = self.pool.get('account.tax').get_tax_from_rate(cr, uid, tax_vat, context.get('is_tax_included'), context=context)
            extra_line['tax_id'] = [(6, 0, line_tax_id)]
        else:
            # compute the taxes, apply fiscal positions, default values and so on
            extra_line = self.pool.get('sale.order.line').play_sale_order_line_onchange(cr, uid, extra_line, res, res['order_line'], defaults, context=context)
        res['order_line'].append((0, 0, extra_line))

        return res

    def add_order_shipping(self, cr, uid, res, external_referential_id, data_record, defaults, context=None):
        if context is None: context = {}
        if data_record.get('shipping_amount', False) and float(data_record.get('shipping_amount', False)) > 0:
            ctx = context.copy()
            ctx.update({
                'ext_tax_field': 'shipping_tax_amount',
            })
            product = False
            if res.get('carrier_id'):
                carrier = self.pool.get('delivery.carrier').browse(cr, uid, res['carrier_id'], context=context)
                product = carrier.product_id.id
            else:
                product = ('base_sale_multichannels', 'product_product_shipping')
            res = self.add_order_extra_line(cr, uid, res, data_record, 'shipping_amount', product, defaults, ctx)
        return res

    def add_gift_certificates(self, cr, uid, res, external_referential_id, data_record, defaults, context=None):
        if context is None: context = {}
        if data_record.get('giftcert_amount', False) and float(data_record.get('giftcert_amount', False)) > 0:
            ctx = context.copy()
            ctx.update({
                'ext_code_field': 'giftcert_code',
                'sign': -1,
            })
            product_ref = ('magentoerpconnect', 'product_product_gift')
            res = self.add_order_extra_line(cr, uid, res, data_record, 'giftcert_amount', product_ref, defaults, ctx)
        return res

    def add_discount(self, cr, uid, res, external_referential_id, data_record, defaults, context=None):
        #TODO fix me rev 476
        #if data_record.get('discount_amount', False) and float(data_record.get('discount_amount', False)) < 0:
        #    ctx = context.copy()
        #    ctx.update({
        #        'ext_code_field': 'coupon_code',
        #    })
        #    product_ref = ('magentoerpconnect', 'product_product_discount')
        #    res = self.add_order_extra_line(cr, uid, res, data_record, 'discount_amount', product_ref, defaults, ctx)
        return res

    def add_cash_on_delivery(self, cr, uid, res, external_referential_id, data_record, defaults, context=None):
        if context is None: context = {}
        if data_record.get('cod_fee', False) and float(data_record.get('cod_fee', False)) > 0:
            ctx = context.copy()
            ctx.update({
                'ext_tax_field': 'cod_tax_amount',
            })
            product_ref = ('magentoerpconnect', 'product_product_cash_on_delivery')
            res = self.add_order_extra_line(cr, uid, res, data_record, 'cod_fee', product_ref, defaults, ctx)
        return res

    def convert_extdata_into_oedata(self, cr, uid, external_data, external_referential_id, parent_data=None, defaults=None, context=None):
        res = super(sale_order, self).convert_extdata_into_oedata(cr, uid, external_data, external_referential_id, parent_data=parent_data, defaults=defaults, context=context)
        res=res[0]
        external_data = external_data[0]
        res = self.add_order_shipping(cr, uid, res, external_referential_id, external_data, defaults, context)
        res = self.add_gift_certificates(cr, uid, res, external_referential_id, external_data, defaults, context)
        res = self.add_discount(cr, uid, res, external_referential_id, external_data, defaults, context)
        res = self.add_cash_on_delivery(cr, uid, res, external_referential_id, external_data, defaults, context)
        return [res]

    def _merge_sub_items(self, cr, uid, product_type, top_item, child_items, context=None):
        """
        Manage the sub items of the magento sale order lines. A top item contains one
        or many child_items. For some product types, we want to merge them in the main
        item, or keep them as order line.

        A list may be returned to add many items (ie to keep all child_items as items.

        :param top_item: main item (bundle, configurable)
        :param child_items: list of childs of the top item
        :return: item or list of items
        """
        if product_type == 'configurable':
            item = top_item.copy()
            # For configurable product all information regarding the price is in the configurable item
            # In the child a lot of information is empty, but contains the right sku and product_id
            # So the real product_id and the sku and the name have to be extracted from the child
            for field in ['sku', 'product_id', 'name']:
                item[field] = child_items[0][field]
            return item
        return top_item

    def data_record_filter(self, cr, uid, data_record, context=None):
        child_items = {}  # key is the parent item id
        top_items = []

        # Group the childs with their parent
        for item in data_record['items']:
            if item.get('parent_item_id'):
                child_items.setdefault(item['parent_item_id'], []).append(item)
            else:
                top_items.append(item)

        all_items = []
        for top_item in top_items:
            if top_item['item_id'] in child_items:
                item_modified = self._merge_sub_items(cr, uid,
                                                      top_item['product_type'],
                                                      top_item,
                                                      child_items[top_item['item_id']],
                                                      context=context)
                if not isinstance(item_modified, list):
                    item_modified = [item_modified]
                all_items.extend(item_modified)
            else:
                all_items.append(top_item)

        data_record['items'] = all_items
        return data_record

    def oevals_from_extdata(self, cr, uid, external_referential_id, data_record, key_field, mapping_lines, parent_data=None, previous_lines=None, defaults=None, context=None):
        if context is None: context = {}
        if data_record.get('items', False):
            data_record = self.data_record_filter(cr, uid, data_record, context=context)
        #TODO refactor this code regarding the new feature of sub-mapping in base_external_referential
        if not context.get('one_by_one', False):
            # we fix the website_id when it is empty because we need it to
            # find the correct partner (based on email + website_id)
            # website is based on the store_id which is:
            # website m2o -> store
            if not data_record.get('website_id') and data_record.get('store_id'):
                store_obj = self.pool.get('magerp.storeviews')
                store_id = store_obj.extid_to_oeid(
                    cr, uid, data_record['store_id'], external_referential_id)
                store = store_obj.browse(cr, uid, store_id, context=context)
                data_record['website_id'] = store.website_id.oeid_to_extid(
                    external_referential_id=external_referential_id, context=context)

            defaults.update(self.get_order_addresses(
                cr, uid, external_referential_id, data_record, context=context))
        res = super(magerp_osv.magerp_osv, self).oevals_from_extdata(cr, uid, external_referential_id, data_record, key_field, mapping_lines, parent_data, previous_lines, defaults, context)

        #Move me in a mapping
        if not context.get('one_by_one', False):
            if data_record.get('status_history', False) and len(data_record['status_history']) > 0:
                res['date_order'] = data_record['status_history'][len(data_record['status_history'])-1]['created_at']
        return res

    def _parse_external_payment(self, cr, uid, order_data, context=None):
        """
        Parse the external order data and return if the sale order
        has been paid and the amount to pay or to be paid

        :param dict order_data: payment information of the magento sale
            order
        :return: tuple where :
            - first item indicates if the payment has been done (True or False)
            - second item represents the amount paid or to be paid
        """
        paid = amount = False
        payment_info = order_data.get('payment')
        if payment_info:
            amount = False
            if payment_info.get('amount_paid', False):
                amount =  payment_info.get('amount_paid', False)
                paid = True
            elif payment_info.get('amount_ordered', False):
                amount = payment_info.get('amount_ordered', False)
        return paid, amount

    def _get_payment_amounts(self, cr, uid, order, total_amount, context=None):
        """
        Compute the payments to create for an order based on an amount
        which represents the total paid.

        Uses the account.payment.term if there is one on the order so it will
        correctly divide the payment between the billing deadlines.

        :param browse_record order: browsable order
        :param float total_amount: total amount of the payment
        :return: list of tuple of payment [(payment date, payment amount), ...]
        """
        if order.payment_term:
            return self.pool.get('account.payment.term').compute(
                cr, uid, order.payment_term.id, total_amount,
                date_ref=order.date_order, context=context)
        else:
            # full payment should be generated
            return [(order.date_order, total_amount)]

    def create_payments(self, cr, uid, order_id, data_record, context=None):
        if context is None:
            context = {}

        if 'Magento' in context.get('external_referential_type', ''):
            payment_info = data_record.get('payment')
            paid, amount = self._parse_external_payment(
                cr, uid, data_record, context=context)
            if paid:
                # external amount is a str
                amount = float(amount)
                order = self.pool.get('sale.order').browse( cr, uid, order_id, context)
                payments = self._get_payment_amounts(cr, uid, order, amount, context=context)
                for pay_date, pay_amount in payments:
                    self.generate_payment_with_pay_code(
                        cr, uid,
                        payment_info['method'],
                        order.partner_id.id,
                        pay_amount,
                        "mag_" + payment_info['payment_id'],
                        "mag_" + data_record['increment_id'],
                        pay_date,
                        paid,
                        context=context)
        else:
            paid = super(sale_order, self).create_payments(
                cr, uid, order_id, data_record, context=context)
        return paid

    def _chain_cancel_orders(self, cr, uid, external_id, external_referential_id, defaults=None, context=None):
        """ Get all the chain of edited orders (an edited order is canceled on Magento)
         and cancel them on OpenERP. If an order cannot be canceled (confirmed for example)
         A request is created to inform the user.
        """
        if context is None:
            context = {}
        logger = logging.getLogger('ext synchro')
        conn = context.get('conn_obj', False)
        parent_list = []
        # get all parents orders (to cancel) of the sale orders
        parent = conn.call('sales_order.get_parent', [external_id])
        while parent:
            parent_list.append(parent)
            parent = conn.call('sales_order.get_parent', [parent])

        wf_service = netsvc.LocalService("workflow")
        for parent_incr_id in parent_list:
            canceled_order_id = self.extid_to_existing_oeid(cr, uid, parent_incr_id, external_referential_id)
            if canceled_order_id:
                try:
                    wf_service.trg_validate(uid, 'sale.order', canceled_order_id, 'cancel', cr)
                    self.log(cr, uid, canceled_order_id, "order %s canceled when updated from external system" % (canceled_order_id,))
                    logger.info("Order %s canceled when updated from external system because it has been replaced by a new one", canceled_order_id)
                except osv.except_osv, e:
                    #TODO: generic reporting of errors in magentoerpconnect
                    # except if the sale order has been confirmed for example, we cannot cancel the order
                    to_cancel_order_name = self.read(cr, uid, canceled_order_id, ['name'])['name']
                    request = self.pool.get('res.request')
                    summary = _(("The sale order %s has been replaced by the sale order %s on Magento.\n"
                                 "The sale order %s has to be canceled on OpenERP but it is currently impossible.\n\n"
                                 "Error:\n"
                                 "%s\n"
                                 "%s")) % (parent_incr_id,
                                          external_id,
                                          to_cancel_order_name,
                                          e.name,
                                          e.value)
                    request.create(cr, uid,
                                   {'name': _("Could not cancel sale order %s during Magento's sale orders import") % (to_cancel_order_name,),
                                    'act_from': uid,
                                    'act_to': uid,
                                    'body': summary,
                                    'priority': '2'
                                    })

    def _ext_import_one(self, cr, uid, external_id, vals, external_data, referential_id, defaults=None, context=None):
        """
        Inherit the method to flag the order to "Imported" on Magento right after the importation
        Before the import, check if the order is already imported and in a such case, skip the import
         and flag "imported" on Magento.
        """
        if context is None: context = {}
        if not (context.get('external_referential_type', False) and 'Magento' in context['external_referential_type']):
            return super(sale_order, self)._ext_import_one(
            cr, uid, external_id, vals, external_data, referential_id, defaults=defaults, context=context)

        res = False, False
        if not self.extid_to_existing_oeid(cr, uid, external_id, referential_id, context=context):
            res = super(sale_order, self)._ext_import_one(
                cr, uid, external_id, vals, external_data, referential_id, defaults=defaults, context=context)

        if any(res):
            # if a created order has a relation_parent_real_id,
            # the new one replaces the original, so we have to cancel the old ones
            if external_data.get('relation_parent_real_id', False):
                self._chain_cancel_orders(cr, uid, external_id, referential_id, defaults=defaults, context=context)

        return res

    def _ext_import_one_cr(self, cr, uid, external_data, referential_id, defaults=None, context=None):
        """ Import one external resource, with cursor management, open a new cursor
        which is commited on each import

        This method can be inherited to do an action which have to be done after
        that the imported resource is commited in database.

        @param dict external_data: vals of the external resource before conversion
        @param external_referential_id: external referential id from where we import the resource
        @param defaults: defaults value for fields which are not in vals
        @return: tuple created id / updated id
        """

        cid, wid = super(sale_order, self)._ext_import_one_cr(
            cr, uid, external_data, referential_id, defaults=defaults, context=context)

        if (cid or wid and
           (context.get('external_referential_type') and
               'Magento' in context['external_referential_type'])):
            ext_id = self.oeid_to_extid(cr, uid, cid or wid, referential_id,
                context=context)
            # set the "imported" flag to true on Magento
            self.ext_set_order_imported(cr, uid, ext_id, referential_id, context=context)
        return cid, wid

    def ext_set_order_imported(self, cr, uid, external_id, external_referential_id, context=None):
        if context is None:
            context = {}
        logger = logging.getLogger('ext synchro')
        conn = context.get('conn_obj', False)
        conn.call('sales_order.done', [external_id])
        logger.info("Successfully set the imported flag on Magento on sale order %s", external_id)
        return True

    def mage_import_base(self, cr, uid, conn, external_referential_id, defaults=None, context=None):
        """ Inherited method for Sales orders in order to import only order not flagged as "imported" on Magento
        """
        if context is None:
            context = {}
        if not 'ids_or_filter' in context.keys():
            context['ids_or_filter'] = []
        result = {'create_ids': [], 'write_ids': []}

        # returns the non already imported order (limit returns the n first orders)
        order_retrieve_params = {
            'imported': False,
            'limit': SALE_ORDER_IMPORT_STEP,
            'filters': context['ids_or_filter'],
        }
        ext_order_ids = conn.call('sales_order.search', [order_retrieve_params])
        result = self._import_orders(
            cr, uid, conn, ext_order_ids, external_referential_id, defaults=defaults, context=context)
        return result

    def _import_orders(self, cr, uid, conn, external_ids, referential_id, defaults=None, context=None):
        if context is None:
            context = {}

        logger = logging.getLogger('ext synchro')
        mapping_id = self.pool.get('external.mapping').search(
            cr, uid,
            [('model', '=', self._name),
             ('referential_id', '=', referential_id)],
            context=context)

        context = dict(context)
        # we will need the connection to set the flag to "imported" on magento after each order import
        context['conn_obj'] = conn
        # use the external log for the orders import
        context['use_external_log'] = True
        order_ids_filtred = []
        unchanged_ids = []
        for ext_order_id in external_ids:
            existing_id = self.extid_to_existing_oeid(cr, uid, ext_order_id, referential_id, context=context)
            if existing_id:
                unchanged_ids.append(existing_id)
                logger.info("the order %s already exist in OpenERP", ext_order_id)
                self.ext_set_order_imported(cr, uid, ext_order_id, referential_id, context=context)
            else:
                order_ids_filtred.append({'increment_id' : ext_order_id})
        result = self.mage_import_one_by_one(
            cr, uid, conn, referential_id, mapping_id[0], order_ids_filtred, defaults, context)
        result['unchanged_ids'] = unchanged_ids
        return result


    def _update_single(self, cr, uid, order, conn, update_data, context=None):
        """ Update a sale order using its updated data from Magento.

        Check on Magento if it has been paid since last
        check. If so, it will launch the defined flow based on the
        payment type (validate order, invoice, ...)

        Called from the need_to_update flow
        (:meth:`_check_need_to_update_single`)

        :param browse_record order: sale order being updated
        :param Connection conn: connection object for Magento
        :param dict update_data: updated data of the magento sale order
        """
        if update_data['status'] == 'canceled':
            wf_service = netsvc.LocalService("workflow")
            wf_service.trg_validate(uid, 'sale.order', order.id, 'cancel', cr)
            updated = True
            self.log(cr, uid, order.id, "order %s canceled when updated from external system" % (order.id,))
        # If the order isn't canceled and was waiting for a payment,
        # so we follow the standard flow according to ext_payment_method:
        else:
            paid, __ = self._parse_external_payment(
                cr, uid, update_data, context=context)
            self.oe_status(cr, uid, order.id, paid, context)
            # create_payments has to be done after oe_status
            # because oe_status creates the invoice
            # and create_payment could reconcile the payment
            # with the invoice

            updated = self.create_payments(
                cr, uid, order.id, update_data, context)
            if updated:
                self.log(
                    cr, uid, order.id,
                    "order %s paid when updated from external system" %
                    (order.id,))
        # Untick the need_to_update if updated (if so was canceled in magento
        # or if it has been paid through magento)
        if updated:
            self.write(cr, uid, order.id, {'need_to_update': False})
        cr.commit()
        return True

    def _check_need_to_update_single(self, cr, uid, order, conn, context=None):
        """ Find the Magento ID of a sale order, get its latest data and update
        it on OpenERP.

        :param browse_record order: browseable sale.order
        :param Connection conn: connection with Magento
        :return: True
        """
        model_data_obj = self.pool.get('ir.model.data')
        # check if the status has changed in Magento
        # We don't use oeid_to_extid function cause it only handles integer ids
        # Magento can have something like '100000077-2'
        model_data_ids = model_data_obj.search(
            cr, uid,
            [('model', '=', self._name),
             ('res_id', '=', order.id),
             ('external_referential_id', '=', order.shop_id.referential_id.id)],
            context=context)

        if model_data_ids:
            prefixed_id = model_data_obj.read(
                cr, uid, model_data_ids[0], ['name'], context=context)['name']
            ext_id = self.id_from_prefixed_id(prefixed_id)
        else:
            return False

        update_data = conn.call('sales_order.info', [ext_id])

        self._update_single(cr, uid, order, conn, update_data, context=context)
        return True

    def check_need_to_update(self, cr, uid, ids, conn, context=None):
        """
        For each order, check on Magento if it has been paid since last
        check. If so, it will launch the defined flow based on the
        payment type (validate order, invoice, ...)

        :param Connection conn: connection with Magento
        :return: True
        """
        for order in self.browse(cr, uid, ids, context=context):
            self._check_need_to_update_single(
                cr, uid, order, conn, context=context)
        return True

    def _create_external_invoice(self, cr, uid, order, conn, ext_id,
                                context=None):
        """ Creation of an invoice on Magento."""
        magento_invoice_ref = conn.call(
            'sales_order_invoice.create',
            [order.magento_incrementid,
            [],
             _("Invoice Created"),
             order.shop_id.allow_magento_notification,
             True])
        return magento_invoice_ref

    # TODO Move in base_sale_multichannels?
    def export_invoice(self, cr, uid, order, conn, ext_id, context=None):
        """ Export an invoice on external referential """
        cr.execute("select account_invoice.id "
                   "from account_invoice "
                   "inner join sale_order_invoice_rel "
                   "on invoice_id = account_invoice.id "
                   "where order_id = %s" % order.id)
        resultset = cr.fetchone()
        created = False
        if resultset and len(resultset) == 1:
            invoice = self.pool.get("account.invoice").browse(
                cr, uid, resultset[0], context=context)
            if (invoice.amount_total == order.amount_total and
                not invoice.magento_ref):
                magento_invoice_ref = False
                try:
                    magento_invoice_ref = self._create_external_invoice(
                        cr, uid, order, conn, ext_id, context=context)
                except Exception, e:
                    self.log(cr, uid, order.id,
                             "failed to create Magento invoice for order %s" %
                             (order.id,))
                    # TODO make sure that's because Magento invoice already
                    # exists and then re-attach it!
                if magento_invoice_ref:
                    self.pool.get("account.invoice").write(
                        cr, uid,
                        invoice.id,
                        {'magento_ref': magento_invoice_ref},
                        context=context)
                    created = True

        return created

    def retry_import(self, cr, uid, id, ext_id, external_referential_id, defaults=None, context=None):
        """ When we import again a previously failed import"""
        conn = self.pool.get('external.referential').external_connection(
            cr, uid, external_referential_id)
        res =  self._import_orders(
            cr, uid, conn, [ext_id], external_referential_id, defaults=defaults, context=context)
        if any(res.values()):
            return True
        return False

sale_order()


class sale_order_line(osv.osv):

    _inherit = 'sale.order.line'

    _columns = {
        # Rised the precision of the sale.order.line discount field
        # from 2 to 3 digits in order to be able to have the same amount as Magento.
        # Example: Magento has a sale line of 299€ and 150€ of discount, so a line at 149€.
        # We translate it to a percent in the openerp sale order
        # With a 2 digits precision, we can have 50.17 % => 148.99 or 50.16% => 149.02.
        # Rise the digits to 3 allows to have 50.167% => 149€
        'discount': fields.float('Discount (%)', digits=(16, 3), readonly=True, states={'draft': [('readonly', False)]}),
    }

sale_order_line()