~allison-miller/openerp-connector/7.0-magentoerpconnect-invoice-export-state

« back to all changes in this revision

Viewing changes to magentoerpconnect/old_stuff.py

  • Committer: Guewen Baconnier
  • Date: 2013-07-05 09:43:07 UTC
  • Revision ID: guewen.baconnier@camptocamp.com-20130705094307-2a0not6twwsb2jdi
[DEL] remove old stuff

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: utf-8 -*-
2
 
#########################################################################
3
 
#This module intergrates Open ERP with the magento core                 #
4
 
#Core settings are stored here                                          #
5
 
#########################################################################
6
 
#                                                                       #
7
 
# Copyright (C) 2009  Sharoon Thomas, Raphaël Valyi                     #
8
 
# Copyright (C) 2011-2012 Akretion Sébastien BEAU sebastien.beau@akretion.com#
9
 
# Copyright (C) 2011-2012 Camptocamp Guewen Baconnier                   #
10
 
#                                                                       #
11
 
#This program is free software: you can redistribute it and/or modify   #
12
 
#it under the terms of the GNU General Public License as published by   #
13
 
#the Free Software Foundation, either version 3 of the License, or      #
14
 
#(at your option) any later version.                                    #
15
 
#                                                                       #
16
 
#This program is distributed in the hope that it will be useful,        #
17
 
#but WITHOUT ANY WARRANTY; without even the implied warranty of         #
18
 
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          #
19
 
#GNU General Public License for more details.                           #
20
 
#                                                                       #
21
 
#You should have received a copy of the GNU General Public License      #
22
 
#along with this program.  If not, see <http://www.gnu.org/licenses/>.  #
23
 
#########################################################################
24
 
 
25
 
import logging
26
 
 
27
 
from openerp.osv import fields
28
 
from openerp.osv.osv import except_osv
29
 
from openerp import pooler
30
 
from openerp import tools
31
 
from openerp.tools.translate import _
32
 
 
33
 
from .magerp_osv import MagerpModel, Connection
34
 
from openerp.addons.connector.decorator import only_for_referential
35
 
from openerp.addons.connector.external_osv import ExternalSession
36
 
 
37
 
import openerp.addons.connector as connector
38
 
 
39
 
 
40
 
_logger = logging.getLogger(__name__)
41
 
 
42
 
 
43
 
 
44
 
DEBUG = True
45
 
TIMEOUT = 2
46
 
 
47
 
 
48
 
# Don't go below this point unless you're not afraid of spiderwebs ################
49
 
 
50
 
# TODO: move all the stuff related to Magento in magento.backend
51
 
class external_referential(MagerpModel):
52
 
    #This class stores instances of magento to which the ERP will
53
 
    #connect, so you can connect OpenERP to multiple Magento
54
 
    #installations (eg different Magento databases)
55
 
    _inherit = "external.referential"
56
 
 
57
 
    SYNC_PRODUCT_FILTERS = {'status': {'=': 1}}
58
 
    SYNC_PARTNER_FILTERS = {}
59
 
    SYNC_PARTNER_STEP = 500
60
 
 
61
 
    def _is_magento_referential(self, cr, uid, ids, field_name, arg, context=None):
62
 
        """If at least one shop is magento, we consider that the external
63
 
        referential is a magento referential
64
 
        """
65
 
        res = {}
66
 
        for referential in self.browse(cr, uid, ids, context):
67
 
            if referential.type_id.name == 'magento':
68
 
                res[referential.id] = True
69
 
            else:
70
 
                res[referential.id] = False
71
 
        return res
72
 
 
73
 
    _columns = {
74
 
        'attribute_sets':fields.one2many('magerp.product_attribute_set', 'referential_id', 'Attribute Sets'),
75
 
        'default_pro_cat':fields.many2one('product.category','Default Product Category', help="Products imported from magento may have many categories.\nOpenERP requires a specific category for a product to facilitate invoicing etc."),
76
 
        'default_lang_id':fields.many2one('res.lang', 'Default Language', help="Choose the language which will be used for the Default Value in Magento"),
77
 
        'active': fields.boolean('Active'),
78
 
        'magento_referential': fields.function(_is_magento_referential, type="boolean", method=True, string="Magento Referential"),
79
 
        'last_imported_product_id': fields.integer('Last Imported Product Id', help="Product are imported one by one. This is the magento id of the last product imported. If you clear it all product will be imported"),
80
 
        'last_imported_partner_id': fields.integer('Last Imported Partner Id', help="Partners are imported one by one. This is the magento id of the last partner imported. If you clear it all partners will be imported"),
81
 
        'import_all_attributs': fields.boolean('Import all attributs', help="If the option is uncheck only the attributs that doesn't exist in OpenERP will be imported"),
82
 
        'import_image_with_product': fields.boolean('With image', help="If the option is check the product's image and the product will be imported at the same time and so the step '7-import images' is not needed"),
83
 
        'import_links_with_product': fields.boolean('With links', help="If the option is check the product's links (Up-Sell, Cross-Sell, Related) and the product will be imported at the same time and so the step '8-import links' is not needed"),
84
 
    }
85
 
 
86
 
    _defaults = {
87
 
        'active': lambda *a: 1,
88
 
    }
89
 
 
90
 
    @only_for_referential('magento')
91
 
    def external_connection(self, cr, uid, id, debug=False, logger=False, context=None):
92
 
        if isinstance(id, list):
93
 
            id=id[0]
94
 
        referential = self.browse(cr, uid, id, context=context)
95
 
        attr_conn = Connection(referential.location, referential.apiusername, referential.apipass, debug, logger)
96
 
        return attr_conn.connect() and attr_conn or False
97
 
 
98
 
    @only_for_referential('magento')
99
 
    def import_referentials(self, cr, uid, ids, context=None):
100
 
        self.import_resources(cr, uid, ids, 'external.shop.group', method='search_read_no_loop', context=context)
101
 
        self.import_resources(cr, uid, ids, 'sale.shop', method='search_read_no_loop', context=context)
102
 
        self.import_resources(cr, uid, ids, 'magerp.storeviews', method='search_read_no_loop', context=context)
103
 
        return True
104
 
 
105
 
    @only_for_referential('magento')
106
 
    def import_product_categories(self, cr, uid, ids, context=None):
107
 
        self.import_resources(cr, uid, ids, 'product.category', method='search_then_read_no_loop', context=context)
108
 
        return True
109
 
 
110
 
 
111
 
    #This function will be refactored latter, need to improve Magento API before
112
 
    def sync_attribs(self, cr, uid, ids, context=None):
113
 
        attr_obj = self.pool.get('magerp.product_attributes')
114
 
        attr_set_obj = self.pool.get('magerp.product_attribute_set')
115
 
        for referential in self.browse(cr, uid, ids, context=context):
116
 
            external_session = ExternalSession(referential, referential)
117
 
            attr_conn = external_session.connection
118
 
            attrib_set_ids = attr_set_obj.search(cr, uid, [('referential_id', '=', referential.id)])
119
 
            attrib_sets = attr_set_obj.read(cr, uid, attrib_set_ids, ['magento_id'])
120
 
            #Get all attribute set ids to get all attributes in one go
121
 
            all_attr_set_ids = attr_set_obj.get_all_extid_from_referential(cr, uid, referential.id, context=context)
122
 
            #Call magento for all attributes
123
 
            if referential.import_all_attributs:
124
 
                attributes_imported=[]
125
 
            else:
126
 
                attributes_imported = attr_obj.get_all_extid_from_referential(cr, uid, referential.id, context=context)
127
 
            import_cr = pooler.get_db(cr.dbname).cursor()
128
 
 
129
 
            mapping = {'magerp.product_attributes' : attr_obj._get_mapping(cr, uid, referential.id, context=context)}
130
 
            try:
131
 
                for attr_set_id in all_attr_set_ids:
132
 
                    mage_inp = attr_conn.call('ol_catalog_product_attribute.list', [attr_set_id])             #Get the tree
133
 
                    attribut_to_import = []
134
 
                    for attribut in mage_inp:
135
 
                        ext_id = attribut['attribute_id']
136
 
                        if not ext_id in attributes_imported:
137
 
                            attributes_imported.append(ext_id)
138
 
                            attr_obj._record_one_external_resource(import_cr, uid, external_session, attribut,
139
 
                                                            defaults={'referential_id':referential.id},
140
 
                                                            mapping=mapping,
141
 
                                                            context=context,
142
 
                                                        )
143
 
                            import_cr.commit()
144
 
                    _logger.info("All attributs for the attributs set id %s was succesfully imported", attr_set_id)
145
 
                #Relate attribute sets & attributes
146
 
                mage_inp = {}
147
 
                #Pass in {attribute_set_id:{attributes},attribute_set_id2:{attributes}}
148
 
                #print "Attribute sets are:", attrib_sets
149
 
                #TODO find a solution in order to import the relation in a incremental way (maybe splitting this function in two)
150
 
                for each in attrib_sets:
151
 
                    mage_inp[each['magento_id']] = attr_conn.call('ol_catalog_product_attribute.relations', [each['magento_id']])
152
 
                if mage_inp:
153
 
                    attr_set_obj.relate(import_cr, uid, mage_inp, referential.id, DEBUG)
154
 
                import_cr.commit()
155
 
            finally:
156
 
                import_cr.close()
157
 
        return True
158
 
 
159
 
    def sync_attrib_sets(self, cr, uid, ids, context=None):
160
 
        return self.import_resources(cr, uid, ids, 'magerp.product_attribute_set', method='search_read_no_loop', context=context)
161
 
 
162
 
    def sync_attrib_groups(self, cr, uid, ids, context=None):
163
 
        return self.import_resources(cr, uid, ids, 'magerp.product_attribute_groups', method='search_read_no_loop', context=context)
164
 
 
165
 
    @only_for_referential('magento')
166
 
    def import_customer_groups(self, cr, uid, ids, context=None):
167
 
        return self.import_resources(cr, uid, ids, 'res.partner.category', method='search_read_no_loop', context=context)
168
 
 
169
 
    def sync_customer_addresses(self, cr, uid, ids, context=None):
170
 
        for referential_id in ids:
171
 
            attr_conn = self.external_connection(cr, uid, referential_id, DEBUG, context=context)
172
 
            filter = []
173
 
            #self.pool.get('res.partner').mage_import(cr, uid, filter, attr_conn, referential_id, DEBUG)
174
 
            #TODO fix by retrieving customer list first
175
 
            self.pool.get('res.partner.address').mage_import_base(cr, uid, attr_conn, referential_id, {}, {'ids_or_filter':filter})
176
 
        return True
177
 
 
178
 
    def _sync_product_storeview(self, cr, uid, external_session, referential_id, ext_product_id, storeview, mapping=None, context=None):
179
 
        if context is None: context = {}
180
 
        #we really need to clean all magento call and give the posibility to force everythere to use the id as identifier
181
 
        product_info = external_session.connection.call('catalog_product.info', [ext_product_id, storeview.code, False, 'id'])
182
 
        ctx = context.copy()
183
 
        ctx.update({'magento_sku': product_info['sku']})
184
 
        defaults={'magento_exportable': True}
185
 
        return self.pool.get('product.product')._record_one_external_resource(cr, uid, external_session, product_info,
186
 
                                                            defaults=defaults,
187
 
                                                            mapping=mapping,
188
 
                                                            context=ctx,
189
 
                                                        )
190
 
    # This function will be refactored later, maybe it will be better
191
 
    # to improve the magento API before
192
 
    def sync_products(self, cr, uid, ids, context=None):
193
 
        if context is None:
194
 
            context = {}
195
 
        prod_obj = self.pool.get('product.product')
196
 
#        context.update({'dont_raise_error': True})
197
 
        for referential in self.browse(cr, uid, ids, context):
198
 
            external_session = ExternalSession(referential, referential)
199
 
            attr_conn = external_session.connection
200
 
            mapping = {'product.product' : prod_obj._get_mapping(cr, uid,
201
 
                                                                 referential.id,
202
 
                                                                 context=context)
203
 
                       }
204
 
            filter = []
205
 
            if referential.last_imported_product_id:
206
 
                filters = {'product_id': {'gt': referential.last_imported_product_id}}
207
 
                filters.update(self.SYNC_PRODUCT_FILTERS)
208
 
                filter = [filters]
209
 
            #TODO call method should be not harcoded. Need refactoring
210
 
            ext_product_ids = attr_conn.call('ol_catalog_product.search', filter)
211
 
            storeview_obj = self.pool.get('magerp.storeviews')
212
 
 
213
 
            #get all instance storeviews
214
 
            storeview_ids = []
215
 
            for website in referential.shop_group_ids:
216
 
                for shop in website.shop_ids:
217
 
                    for storeview in shop.storeview_ids:
218
 
                        storeview_ids += [storeview.id]
219
 
 
220
 
            lang_2_storeview={}
221
 
            for storeview in storeview_obj.browse(cr, uid, storeview_ids, context):
222
 
                #get lang of the storeview
223
 
                lang_id = storeview.lang_id
224
 
                if lang_id:
225
 
                    lang = lang_id.code
226
 
                else:
227
 
                    except_osv(_('Warning!'), _('The storeviews have no language defined')) #TODO needed?
228
 
                    lang = referential.default_lang_id.code
229
 
                if not lang_2_storeview.get(lang, False):
230
 
                    lang_2_storeview[lang] = storeview
231
 
 
232
 
            if referential.import_links_with_product:
233
 
                link_types = self.get_magento_product_link_types(cr, uid, referential.id, attr_conn, context=context)
234
 
 
235
 
            import_cr = pooler.get_db(cr.dbname).cursor()
236
 
            try:
237
 
                for ext_product_id in ext_product_ids:
238
 
                    for lang, storeview in lang_2_storeview.iteritems():
239
 
                        ctx = context.copy()
240
 
                        ctx.update({'lang': lang})
241
 
                        res = self._sync_product_storeview(import_cr, uid, external_session, referential.id, ext_product_id, storeview, mapping=mapping, context=ctx)
242
 
                    product_id = (res.get('create_id') or res.get('write_id'))
243
 
                    if referential.import_image_with_product:
244
 
                        prod_obj.import_product_image(import_cr, uid, product_id, referential.id, attr_conn, ext_id=ext_product_id, context=context)
245
 
                    if referential.import_links_with_product:
246
 
                        prod_obj.mag_import_product_links_types(import_cr, uid, product_id, link_types,  external_session, context=context)
247
 
                    self.write(import_cr, uid, referential.id, {'last_imported_product_id': int(ext_product_id)}, context=context)
248
 
                    import_cr.commit()
249
 
            finally:
250
 
                import_cr.close()
251
 
        return True
252
 
 
253
 
    def get_magento_product_link_types(self, cr, uid, ids, conn=None, context=None):
254
 
        if not conn:
255
 
            conn = self.external_connection(cr, uid, ids, DEBUG, context=context)
256
 
        return conn.call('catalog_product_link.types')
257
 
 
258
 
    #TODO refactore me base on connector
259
 
    def sync_images(self, cr, uid, ids, context=None):
260
 
        product_obj = self.pool.get('product.product')
261
 
        image_obj = self.pool.get('product.images')
262
 
        import_cr = pooler.get_db(cr.dbname).cursor()
263
 
        try:
264
 
            for referential in self.browse(cr, uid, ids, context=context):
265
 
                external_session = ExternalSession(referential, referential)
266
 
                conn = external_session.connection
267
 
                product_ids = product_obj.get_all_oeid_from_referential(cr, uid, referential.id, context=context)
268
 
                for product_id in product_ids:
269
 
                    product_obj.import_product_image(import_cr, uid, product_id, referential.id, conn, context=context)
270
 
                    import_cr.commit()
271
 
        finally:
272
 
            import_cr.close()
273
 
        return True
274
 
 
275
 
    #TODO refactore me base on connector
276
 
    def sync_product_links(self, cr, uid, ids, context=None):
277
 
        if context is None: context = {}
278
 
        for referential in self.browse(cr, uid, ids, context):
279
 
            external_session = ExternalSession(referential, referential)
280
 
            conn = external_session.connection
281
 
            exportable_product_ids= []
282
 
            for shop_group in referential.shop_group_ids:
283
 
                for shop in shop_group.shop_ids:
284
 
                    exportable_product_ids.extend([product.id for product in shop.exportable_product_ids])
285
 
            exportable_product_ids = list(set(exportable_product_ids))
286
 
            self.pool.get('product.product').mag_import_product_links(cr, uid, exportable_product_ids, external_session, context=context)
287
 
        return True
288
 
 
289
 
    def export_products(self, cr, uid, ids, context=None):
290
 
        if context is None: context = {}
291
 
        shop_ids = self.pool.get('sale.shop').search(cr, uid, [])
292
 
        for referential_id in ids:
293
 
            for shop in self.pool.get('sale.shop').browse(cr, uid, shop_ids, context):
294
 
                context['conn_obj'] = self.external_connection(cr, uid, referential_id, context=context)
295
 
                #shop.export_catalog
296
 
                tools.debug((cr, uid, shop, context,))
297
 
                shop.export_products(cr, uid, shop, context)
298
 
        return True
299
 
 
300
 
    #TODO refactor me
301
 
    def sync_partner(self, cr, uid, ids, context=None):
302
 
        def next_partners(connection, start, step):
303
 
            filters = {'customer_id': {'in': range(start, start + step)}}
304
 
            filters.update(self.SYNC_PARTNER_FILTERS)
305
 
            filter = [filters]
306
 
            return attr_conn.call('ol_customer.search', filter)
307
 
 
308
 
        for referential in self.browse(cr, uid, ids, context):
309
 
            attr_conn = referential.external_connection(DEBUG)
310
 
            last_imported_id = 0
311
 
            if referential.last_imported_partner_id:
312
 
                last_imported_id = referential.last_imported_partner_id
313
 
 
314
 
            ext_customer_ids = next_partners(attr_conn, last_imported_id + 1, self.SYNC_PARTNER_STEP)
315
 
            import_cr = pooler.get_db(cr.dbname).cursor()
316
 
            try:
317
 
                while ext_customer_ids:
318
 
                    for ext_customer_id in ext_customer_ids:
319
 
                        customer_info = attr_conn.call('customer.info', [ext_customer_id])
320
 
                        customer_address_info = attr_conn.call('customer_address.list', [ext_customer_id])
321
 
 
322
 
                        address_info = False
323
 
                        if customer_address_info:
324
 
                            address_info = customer_address_info[0]
325
 
                            address_info['customer_id'] = ext_customer_id
326
 
                            address_info['email'] = customer_info['email']
327
 
                        external_session = ExternalSession(referential, referential)
328
 
                        self.pool.get('res.partner')._record_one_external_resource(import_cr, uid, external_session, customer_info, context=context)
329
 
                        if address_info:
330
 
                            self.pool.get('res.partner.address')._record_one_external_resource(import_cr, uid, external_session, address_info, context=context)
331
 
                        last_imported_id = int(ext_customer_id)
332
 
                        self.write(import_cr, uid, referential.id, {'last_imported_partner_id': last_imported_id}, context=context)
333
 
                        import_cr.commit()
334
 
                    ext_customer_ids = next_partners(attr_conn, last_imported_id + 1, self.SYNC_PARTNER_STEP)
335
 
            finally:
336
 
                import_cr.close()
337
 
        return True
338
 
 
339
 
    def sync_newsletter(self, cr, uid, ids, context=None):
340
 
        #update first all customer
341
 
        self.sync_partner(cr, uid, ids, context)
342
 
 
343
 
        partner_obj = self.pool.get('res.partner')
344
 
 
345
 
        for referential_id in ids:
346
 
            attr_conn = self.external_connection(cr, uid, referential_id, DEBUG, context=context)
347
 
            filter = []
348
 
            list_subscribers = attr_conn.call('ol_customer_subscriber.list')
349
 
            result = []
350
 
            for each in list_subscribers:
351
 
                each_subscribers_info = attr_conn.call('ol_customer_subscriber.info', [each])
352
 
 
353
 
                # search this customer. If exist, update your newsletter subscription
354
 
                partner_ids = partner_obj.search(cr, uid, [('emailid', '=', each_subscribers_info[0]['subscriber_email'])])
355
 
                if partner_ids:
356
 
                    #unsubscriber magento value: 3
357
 
                    if int(each_subscribers_info[0]['subscriber_status']) == 1:
358
 
                        subscriber_status = 1
359
 
                    else:
360
 
                        subscriber_status = 0
361
 
                    partner_obj.write(cr, uid, partner_ids[0], {'mag_newsletter': subscriber_status})
362
 
        return True
363
 
 
364
 
    def sync_newsletter_unsubscriber(self, cr, uid, ids, context=None):
365
 
        partner_obj = self.pool.get('res.partner')
366
 
 
367
 
        for referential_id in ids:
368
 
            attr_conn = self.external_connection(cr, uid, referential_id, DEBUG, context=context)
369
 
            partner_ids  = partner_obj.search(cr, uid, [('mag_newsletter', '!=', 1), ('emailid', '!=', '')])
370
 
 
371
 
            for partner in partner_obj.browse(cr, uid, partner_ids):
372
 
                if partner.emailid:
373
 
                    attr_conn.call('ol_customer_subscriber.delete', [partner.emailid])
374
 
 
375
 
        return True
376
 
 
377
 
    # Schedules functions ============ #
378
 
    def run_import_newsletter_scheduler(self, cr, uid, context=None):
379
 
        if context is None:
380
 
            context = {}
381
 
 
382
 
        referential_ids  = self.search(cr, uid, [('active', '=', 1)])
383
 
 
384
 
        if referential_ids:
385
 
            self.sync_newsletter(cr, uid, referential_ids, context)
386
 
        if DEBUG:
387
 
            print "run_import_newsletter_scheduler: %s" % referential_ids
388
 
 
389
 
    def run_import_newsletter_unsubscriber_scheduler(self, cr, uid, context=None):
390
 
        if context is None:
391
 
            context = {}
392
 
 
393
 
        referential_ids  = self.search(cr, uid, [('active', '=', 1)])
394
 
 
395
 
        if referential_ids:
396
 
            self.sync_newsletter_unsubscriber(cr, uid, referential_ids, context)
397
 
        if DEBUG:
398
 
            print "run_import_newsletter_unsubscriber_scheduler: %s" % referential_ids
399
 
 
400
 
 
401
 
# TODO: remove
402
 
class external_shop_group(MagerpModel):
403
 
    _inherit = "external.shop.group"
404
 
    #Return format of API:{'code': 'base', 'name': 'Main', 'website_id': '1', 'is_default': '1', 'sort_order': '0', 'default_group_id': '1'}
405
 
    # default_group_id is the default shop of the external_shop_group (external_shop_group = website)
406
 
 
407
 
    def _get_default_shop_id(self, cr, uid, ids, prop, unknow_none, context=None):
408
 
        res = {}
409
 
        for shop_group in self.browse(cr, uid, ids, context):
410
 
            if shop_group.default_shop_integer_id:
411
 
                rid = self.pool.get('sale.shop').extid_to_existing_oeid(cr, uid, shop_group.default_shop_integer_id, shop_group.referential_id.id)
412
 
                res[shop_group.id] = rid
413
 
            else:
414
 
                res[shop_group.id] = False
415
 
        return res
416
 
 
417
 
    _columns = {
418
 
        'code':fields.char('Code', size=100),
419
 
        'is_default':fields.boolean('Is Default?'),
420
 
        'sort_order':fields.integer('Sort Order'),
421
 
        'default_shop_integer_id':fields.integer('Default Store'), #This field can't be a many2one because shop_group field will be mapped before creating Shop (Shop = Store, shop_group = website)
422
 
        'default_shop_id':fields.function(_get_default_shop_id, type="many2one", relation="sale.shop", method=True, string="Default Store"),
423
 
        'referential_type' : fields.related('referential_id', 'type_id', type='many2one', relation='external.referential.type', string='External Referential Type'),
424
 
        }
425
 
 
426
 
 
427
 
# TODO: remove
428
 
class magerp_storeviews(MagerpModel):
429
 
    _name = "magerp.storeviews"
430
 
    _description = "The magento store views information"
431
 
 
432
 
    _columns = {
433
 
        'name':fields.char('Store View Name', size=100),
434
 
        'code':fields.char('Code', size=100),
435
 
        'website_id':fields.many2one('external.shop.group', 'Website', select=True, ondelete='cascade'),
436
 
        'is_active':fields.boolean('Default ?'),
437
 
        'sort_order':fields.integer('Sort Order'),
438
 
        'shop_id':fields.many2one('sale.shop', 'Shop', select=True, ondelete='cascade'),
439
 
        'lang_id':fields.many2one('res.lang', 'Language'),
440
 
    }
441
 
 
442
 
 
443
 
# transfered from sale.py ###############################
444
 
 
445
 
if False:
446
 
 
447
 
 
448
 
    
449
 
    class sale_shop(Model):
450
 
        _inherit = "sale.shop"
451
 
 
452
 
        @only_for_referential('magento')
453
 
        def init_context_before_exporting_resource(self, cr, uid, external_session, object_id, resource_name, context=None):
454
 
            context = super(sale_shop, self).init_context_before_exporting_resource(cr, uid, external_session, object_id, resource_name, context=context)
455
 
            shop = self.browse(cr, uid, object_id, context=context)
456
 
            context['main_lang'] = shop.referential_id.default_lang_id.code
457
 
            context['lang_to_export'] = [shop.referential_id.default_lang_id.code]
458
 
            context['storeview_to_lang'] = {}
459
 
            for storeview in shop.storeview_ids:
460
 
                if storeview.lang_id and storeview.lang_id.code != shop.referential_id.default_lang_id.code:
461
 
                    context['storeview_to_lang'][storeview.code] = storeview.lang_id.code
462
 
                    if not storeview.lang_id.code in context['lang_to_export']:
463
 
                        context['lang_to_export'].append(storeview.lang_id.code)
464
 
            return context
465
 
 
466
 
        def _get_exportable_product_ids(self, cr, uid, ids, name, args, context=None):
467
 
            res = super(sale_shop, self)._get_exportable_product_ids(cr, uid, ids, name, args, context=None)
468
 
            for shop_id in res:
469
 
                website_id =  self.read(cr, uid, shop_id, ['shop_group_id'])
470
 
                if website_id.get('shop_group_id', False):
471
 
                    res[shop_id] = self.pool.get('product.product').search(cr, uid,
472
 
                                                                           [('magento_exportable', '=', True),
473
 
                                                                            ('id', 'in', res[shop_id]),
474
 
                                                                            "|", ('websites_ids', 'in', [website_id['shop_group_id'][0]]),
475
 
                                                                                 ('websites_ids', '=', False)])
476
 
                else:
477
 
                    res[shop_id] = []
478
 
            return res
479
 
 
480
 
        def _get_default_storeview_id(self, cr, uid, ids, prop, unknow_none, context=None):
481
 
            res = {}
482
 
            for shop in self.browse(cr, uid, ids, context):
483
 
                if shop.default_storeview_integer_id:
484
 
                    rid = self.pool.get('magerp.storeviews').extid_to_oeid(cr, uid, shop.default_storeview_integer_id, shop.referential_id.id)
485
 
                    res[shop.id] = rid
486
 
                else:
487
 
                    res[shop.id] = False
488
 
            return res
489
 
 
490
 
        def export_images(self, cr, uid, ids, context=None):
491
 
            if context is None: context = {}
492
 
            start_date = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
493
 
            image_obj = self.pool.get('product.images')
494
 
            for shop in self.browse(cr, uid, ids):
495
 
                external_session = ExternalSession(shop.referential_id, shop)
496
 
                exportable_product_ids = self.read(cr, uid, shop.id, ['exportable_product_ids'], context=context)['exportable_product_ids']
497
 
                res = self.pool.get('product.product').get_exportable_images(cr, uid, external_session, exportable_product_ids, context=context)
498
 
                if res:
499
 
                    _logger.info("Creating %s images", len(res['to_create']))
500
 
                    _logger.info("Updating %s images", len(res['to_update']))
501
 
                    image_obj.update_remote_images(cr, uid, external_session, res['to_update']+res['to_create'], context)
502
 
                self.pool.get('product.images')._set_last_exported_date(cr, uid, external_session, start_date, context=context)
503
 
            return True
504
 
 
505
 
        #TODO refactor the ay to export images
506
 
        #No time for doing it now so I just overwrite the generic function
507
 
        #In order to use the actual implementation
508
 
 
509
 
        def export_resources(self, cr, uid, ids, resource_name, context=None):
510
 
            if resource_name == 'product.images':
511
 
                return self.export_images(cr, uid, ids, context=context)
512
 
            else:
513
 
                return super(sale_shop, self).export_resources(cr, uid, ids, resource_name, context=context)
514
 
 
515
 
        def _get_rootcategory(self, cr, uid, ids, name, value, context=None):
516
 
            res = {}
517
 
            for shop in self.browse(cr, uid, ids, context):
518
 
                if shop.root_category_id and shop.shop_group_id.referential_id:
519
 
                    rid = self.pool.get('product.category').extid_to_existing_oeid(
520
 
                        cr, uid, shop.shop_group_id.referential_id.id, shop.root_category_id)
521
 
                    res[shop.id] = rid
522
 
                else:
523
 
                    res[shop.id] = False
524
 
            return res
525
 
 
526
 
        def _set_rootcategory(self, cr, uid, id, name, value, fnct_inv_arg, context=None):
527
 
            ir_model_data_obj = self.pool.get('ir.model.data')
528
 
            shop = self.browse(cr, uid, id, context=context)
529
 
            if shop.root_category_id:
530
 
                model_data_id = self.pool.get('product.category').\
531
 
                extid_to_existing_oeid(cr, uid, shop.root_category_id, shop.referential_id.id, context=context)
532
 
                if model_data_id:
533
 
                    ir_model_data_obj.write(cr, uid, model_data_id, {'res_id' : value}, context=context)
534
 
                else:
535
 
                    raise except_osv(_('Warning!'),
536
 
                                     _('No external id found, are you sure that the referential are syncronized? '
537
 
                                       'Please contact your administrator. '
538
 
                                       '(more information in magentoerpconnect/sale.py)'))
539
 
            return True
540
 
 
541
 
        def _get_exportable_root_category_ids(self, cr, uid, ids, prop, unknow_none, context=None):
542
 
            res = {}
543
 
            res1 = self._get_rootcategory(cr, uid, ids, prop, unknow_none, context)
544
 
            for shop in self.browse(cr, uid, ids, context):
545
 
                res[shop.id] = res1[shop.id] and [res1[shop.id]] or []
546
 
            return res
547
 
 
548
 
        # xxx move to MagentoConnector._get_import_defaults_sale_shop
549
 
        @only_for_referential('magento')
550
 
        def _get_default_import_values(self, cr, uid, external_session, **kwargs):
551
 
            defaults = super(sale_shop, self)._get_default_import_values(cr, uid, external_session, **kwargs)
552
 
            if not defaults: defaults={}
553
 
            defaults.update({'magento_shop' : True})
554
 
            return defaults
555
 
 
556
 
        @only_for_referential('magento')
557
 
        @open_report
558
 
        def _export_inventory(self, *args, **kwargs):
559
 
            return super(sale_shop, self)._export_inventory(*args, **kwargs)
560
 
 
561
 
        _columns = {
562
 
            '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
563
 
            'default_storeview_id':fields.function(_get_default_storeview_id, type="many2one", relation="magerp.storeviews", method=True, string="Default Storeview"),
564
 
            'root_category_id':fields.integer('Root product Category'), #This field can't be a many2one because store field will be mapped before creating category
565
 
            'magento_root_category':fields.function(_get_rootcategory, fnct_inv = _set_rootcategory, type="many2one", relation="product.category", string="Root Category"),
566
 
            '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'),
567
 
            'storeview_ids': fields.one2many('magerp.storeviews', 'shop_id', 'Store Views'),
568
 
            'exportable_product_ids': fields.function(_get_exportable_product_ids, method=True, type='one2many', relation="product.product", string='Exportable Products'),
569
 
            #TODO fix me, it's look like related field on a function fielf doesn't work.
570
 
            #'magento_shop': fields.related('referential_id', 'magento_referential',type="boolean", string='Magento Shop', readonly=True),
571
 
            'magento_shop': fields.boolean('Magento Shop', readonly=True),
572
 
            '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?'),
573
 
            '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.'),
574
 
        }
575
 
 
576
 
        _defaults = {
577
 
            'allow_magento_order_status_push': lambda * a: False,
578
 
            'allow_magento_notification': lambda * a: False,
579
 
        }
580
 
 
581
 
        def _get_magento_status(self, cr, uid, order, context=None):
582
 
            return ORDER_STATUS_MAPPING.get(order.state)
583
 
 
584
 
        def update_shop_orders(self, cr, uid, external_session, order, ext_id, context=None):
585
 
            if context is None: context = {}
586
 
            result = False
587
 
            if order.shop_id.allow_magento_order_status_push:
588
 
                sale_obj = self.pool.get('sale.order')
589
 
                #status update:
590
 
                status = self._get_magento_status(cr, uid, order, context=context)
591
 
                if status:
592
 
                    result = external_session.connection.call(
593
 
                        'sales_order.addComment',
594
 
                        [ext_id, status, '',
595
 
                         order.shop_id.allow_magento_notification])
596
 
                    #TODO REMOVE ME
597
 
                    # If status has changed into OERP and the order need_to_update,
598
 
                    # then we consider the update is done
599
 
                    # remove the 'need_to_update': True
600
 
                    if order.need_to_update:
601
 
                        sale_obj.write(
602
 
                            cr, uid, order.id, {'need_to_update': False})
603
 
            return result
604
 
 
605
 
        def _sale_shop(self, cr, uid, callback, context=None):
606
 
            if context is None:
607
 
                context = {}
608
 
            proxy = self.pool.get('sale.shop')
609
 
            domain = [ ('magento_shop', '=', True), ('auto_import', '=', True) ]
610
 
 
611
 
            ids = proxy.search(cr, uid, domain, context=context)
612
 
            if ids:
613
 
                callback(cr, uid, ids, context=context)
614
 
 
615
 
            # tools.debug(callback)
616
 
            # tools.debug(ids)
617
 
            return True
618
 
 
619
 
        # Schedules functions ============ #
620
 
        def run_import_orders_scheduler(self, cr, uid, context=None):
621
 
            self._sale_shop(cr, uid, self.import_orders, context=context)
622
 
 
623
 
        def run_update_orders_scheduler(self, cr, uid, context=None):
624
 
            self._sale_shop(cr, uid, self.update_orders, context=context)
625
 
 
626
 
        def run_export_catalog_scheduler(self, cr, uid, context=None):
627
 
            self._sale_shop(cr, uid, self.export_catalog, context=context)
628
 
 
629
 
        def run_export_stock_levels_scheduler(self, cr, uid, context=None):
630
 
            self._sale_shop(cr, uid, self.export_inventory, context=context)
631
 
 
632
 
        def run_update_images_scheduler(self, cr, uid, context=None):
633
 
            self._sale_shop(cr, uid, self.export_images, context=context)
634
 
 
635
 
        def run_export_shipping_scheduler(self, cr, uid, context=None):
636
 
            self._sale_shop(cr, uid, self.export_shipping, context=context)
637
 
 
638
 
        def run_import_check_need_to_update(self, cr, uid, context=None):
639
 
            self._sale_shop(cr, uid, self.check_need_to_update, context=context)
640
 
 
641
 
    class sale_order(Model):
642
 
        _inherit = "sale.order"
643
 
        _columns = {
644
 
            'magento_incrementid': fields.char('Magento Increment ID', size=32),
645
 
            'magento_storeview_id': fields.many2one('magerp.storeviews', 'Magento Store View'),
646
 
            'is_magento': fields.related(
647
 
                'shop_id', 'referential_id', 'magento_referential',
648
 
                type='boolean',
649
 
                string='Is a Magento Sale Order')
650
 
            }
651
 
 
652
 
        def _auto_init(self, cr, context=None):
653
 
            tools.drop_view_if_exists(cr, 'sale_report')
654
 
            cr.execute("ALTER TABLE sale_order_line ALTER COLUMN discount TYPE numeric(16,6);")
655
 
            cr.execute("ALTER TABLE account_invoice_line ALTER COLUMN discount TYPE numeric(16,6);")
656
 
            self.pool.get('sale.report').init(cr)
657
 
            super(sale_order, self)._auto_init(cr, context)
658
 
 
659
 
 
660
 
        def _get_payment_information(self, cr, uid, external_session, order_id, resource, context=None):
661
 
            """
662
 
            Parse the external resource and return a dict of data converted
663
 
            """
664
 
            vals = super(sale_order, self)._get_payment_information(cr, uid, external_session, order_id, resource, context=context)
665
 
            payment_info = resource.get('payment')
666
 
            if payment_info and payment_info.get('amount_paid'):
667
 
                vals['paid'] = True
668
 
                vals['amount'] = float(payment_info['amount_paid'])
669
 
            return vals
670
 
 
671
 
        def _chain_cancel_orders(self, cr, uid, external_id, external_referential_id, defaults=None, context=None):
672
 
            """ Get all the chain of edited orders (an edited order is canceled on Magento)
673
 
             and cancel them on OpenERP. If an order cannot be canceled (confirmed for example)
674
 
             A request is created to inform the user.
675
 
            """
676
 
            if context is None:
677
 
                context = {}
678
 
            conn = context.get('conn_obj', False)
679
 
            parent_list = []
680
 
            # get all parents orders (to cancel) of the sale orders
681
 
            parent = conn.call('sales_order.get_parent', [external_id])
682
 
            while parent:
683
 
                parent_list.append(parent)
684
 
                parent = conn.call('sales_order.get_parent', [parent])
685
 
 
686
 
            wf_service = netsvc.LocalService("workflow")
687
 
            for parent_incr_id in parent_list:
688
 
                canceled_order_id = self.extid_to_existing_oeid(cr, uid, parent_incr_id, external_referential_id)
689
 
                if canceled_order_id:
690
 
                    try:
691
 
                        wf_service.trg_validate(uid, 'sale.order', canceled_order_id, 'cancel', cr)
692
 
                        self.log(cr, uid, canceled_order_id, "order %s canceled when updated from external system" % (canceled_order_id,))
693
 
                        _logger.info("Order %s canceled when updated from external system because it has been replaced by a new one", canceled_order_id)
694
 
                    except except_osv, e:
695
 
                        #TODO: generic reporting of errors in magentoerpconnect
696
 
                        # except if the sale order has been confirmed for example, we cannot cancel the order
697
 
                        to_cancel_order_name = self.read(cr, uid, canceled_order_id, ['name'])['name']
698
 
                        request = self.pool.get('res.request')
699
 
                        summary = _(("The sale order %s has been replaced by the sale order %s on Magento.\n"
700
 
                                     "The sale order %s has to be canceled on OpenERP but it is currently impossible.\n\n"
701
 
                                     "Error:\n"
702
 
                                     "%s\n"
703
 
                                     "%s")) % (parent_incr_id,
704
 
                                              external_id,
705
 
                                              to_cancel_order_name,
706
 
                                              e.name,
707
 
                                              e.value)
708
 
                        request.create(cr, uid,
709
 
                                       {'name': _("Could not cancel sale order %s during Magento's sale orders import") % (to_cancel_order_name,),
710
 
                                        'act_from': uid,
711
 
                                        'act_to': uid,
712
 
                                        'body': summary,
713
 
                                        'priority': '2'
714
 
                                        })
715
 
 
716
 
    #NEW FEATURE
717
 
 
718
 
    #TODO reimplement chain cancel orders
719
 
    #                # if a created order has a relation_parent_real_id, the new one replaces the original, so we have to cancel the old one
720
 
    #                if data[0].get('relation_parent_real_id', False): # data[0] because orders are imported one by one so data always has 1 element
721
 
    #                    self._chain_cancel_orders(order_cr, uid, ext_order_id, external_referential_id, defaults=defaults, context=context)
722
 
 
723
 
        # XXX a deplacer dans MagentoConnector
724
 
        def _get_filter(self, cr, uid, external_session, step, previous_filter=None, context=None):
725
 
            magento_storeview_ids=[]
726
 
            shop = external_session.sync_from_object
727
 
            for storeview in shop.storeview_ids:
728
 
                magento_storeview_id = self.pool.get('magerp.storeviews').get_extid(cr, uid, storeview.id, shop.referential_id.id, context={})
729
 
                if magento_storeview_id:
730
 
                    magento_storeview_ids.append(magento_storeview_id)
731
 
 
732
 
            mag_filter = {
733
 
                'state': {'neq': 'canceled'},
734
 
                'store_id': {'in': magento_storeview_ids},
735
 
                }
736
 
 
737
 
            if shop.import_orders_from_date:
738
 
                mag_filter.update({'created_at' : {'gt': shop.import_orders_from_date}})
739
 
            return {
740
 
                'imported': False,
741
 
                'limit': step,
742
 
                'filters': mag_filter,
743
 
            }
744
 
 
745
 
        def create_onfly_partner(self, cr, uid, external_session, resource, mapping, defaults, context=None):
746
 
            """
747
 
            As magento allow guest order we have to create an on fly partner without any external id
748
 
            """
749
 
            if not defaults: defaults={}
750
 
            local_defaults = defaults.copy()
751
 
 
752
 
            resource['firstname'] = resource['customer_firstname']
753
 
            resource['lastname'] = resource['customer_lastname']
754
 
            resource['email'] = resource['customer_email']
755
 
 
756
 
            shop = external_session.sync_from_object
757
 
            partner_defaults = {'website_id': shop.shop_group_id.id}
758
 
            res = self.pool.get('res.partner')._record_one_external_resource(cr, uid, external_session, resource,\
759
 
                                    mapping=mapping, defaults=partner_defaults, context=context)
760
 
            partner_id = res.get('create_id') or res.get('write_id')
761
 
 
762
 
            local_defaults['partner_id'] = partner_id
763
 
            for address_key in ['partner_invoice_id', 'partner_shipping_id']:
764
 
                if not defaults.get(address_key): local_defaults[address_key] = {}
765
 
                local_defaults[address_key]['partner_id'] = partner_id
766
 
            return local_defaults
767
 
 
768
 
        @only_for_referential('magento')
769
 
        def _transform_one_resource(self, cr, uid, external_session, convertion_type, resource, mapping, mapping_id, \
770
 
                         mapping_line_filter_ids=None, parent_data=None, previous_result=None, defaults=None, context=None):
771
 
            resource = self.clean_magento_resource(cr, uid, resource, context=context)
772
 
            resource = self.clean_magento_items(cr, uid, resource, context=context)
773
 
            for line in mapping[mapping_id]['mapping_lines']:
774
 
                if line['name'] == 'customer_id' and not resource.get('customer_id'):
775
 
                    #If there is not partner it's a guest order
776
 
                    #So we remove the useless information
777
 
                    #And create a partner on fly and set the data in the default value
778
 
                    #We only do this if the customer_id is in the mapping line
779
 
                    #Indeed when we check if a sale order exist only the name is asked for convertion
780
 
                    resource.pop('customer_id', None)
781
 
                    resource['billing_address'].pop('customer_id', None)
782
 
                    resource['shipping_address'].pop('customer_id', None)
783
 
                    defaults = self.create_onfly_partner(cr, uid, external_session, resource, mapping, defaults, context=context)
784
 
 
785
 
            return super(sale_order, self)._transform_one_resource(cr, uid, external_session, convertion_type, resource,\
786
 
                     mapping, mapping_id,  mapping_line_filter_ids=mapping_line_filter_ids, parent_data=parent_data,\
787
 
                     previous_result=previous_result, defaults=defaults, context=context)
788
 
 
789
 
        # XXX move to MagentoConnector _ext_search_sale_order
790
 
        @only_for_referential('magento')
791
 
        def _get_external_resource_ids(self, cr, uid, external_session, resource_filter=None, mapping=None, mapping_id=None, context=None):
792
 
            res = super(sale_order, self)._get_external_resource_ids(cr, uid, external_session, resource_filter=resource_filter, mapping=mapping, mapping_id=mapping_id, context=context)
793
 
            order_ids_to_import=[]
794
 
            for external_id in res:
795
 
                existing_id = self.get_oeid(cr, uid, external_id, external_session.referential_id.id, context=context)
796
 
                if existing_id:
797
 
                    external_session.logger.info(_("the order %s already exist in OpenERP") % (external_id,))
798
 
                    self.ext_set_resource_as_imported(cr, uid, external_session, external_id, mapping=mapping, mapping_id=mapping_id, context=context)
799
 
                else:
800
 
                    order_ids_to_import.append(external_id)
801
 
            return order_ids_to_import
802
 
 
803
 
        # xxx a deplacer dans MagentoConnector _record_one_sale_order
804
 
        @only_for_referential('magento')
805
 
        def _record_one_external_resource(self, cr, uid, external_session, resource, defaults=None, mapping=None, mapping_id=None, context=None):
806
 
            res = super(sale_order, self)._record_one_external_resource(cr, uid, external_session, resource, defaults=defaults, mapping=mapping, mapping_id=mapping_id, context=context)
807
 
            external_id = resource['increment_id'] # TODO it will be better to not hardcode this parameter
808
 
            self.ext_set_resource_as_imported(cr, uid, external_session, external_id, mapping=mapping, mapping_id=mapping_id, context=context)
809
 
            return res
810
 
 
811
 
        @only_for_referential('magento')
812
 
        def _check_need_to_update_single(self, cr, uid, external_session, order, context=None):
813
 
            """
814
 
            For one order, check on Magento if it has been paid since last
815
 
            check. If so, it will launch the defined flow based on the
816
 
            payment type (validate order, invoice, ...)
817
 
 
818
 
            :param browse_record order: browseable sale.order
819
 
            :param Connection conn: connection with Magento
820
 
            :return: True
821
 
            """
822
 
 
823
 
            #TODO improve me and replace me by a generic function in connector_ecommerce
824
 
            #Only the call to magento should be here
825
 
 
826
 
            model_data_obj = self.pool.get('ir.model.data')
827
 
            # check if the status has changed in Magento
828
 
            # We don't use oeid_to_extid function cause it only handles integer ids
829
 
            # Magento can have something like '100000077-2'
830
 
            model_data_ids = model_data_obj.search(
831
 
                cr, uid,
832
 
                [('model', '=', self._name),
833
 
                 ('res_id', '=', order.id),
834
 
                 ('referential_id', '=', order.shop_id.referential_id.id)],
835
 
                context=context)
836
 
 
837
 
            if model_data_ids:
838
 
                prefixed_id = model_data_obj.read(
839
 
                    cr, uid, model_data_ids[0], ['name'], context=context)['name']
840
 
                ext_id = self.id_from_prefixed_id(prefixed_id)
841
 
            else:
842
 
                return False
843
 
 
844
 
            resource = external_session.connection.call('sales_order.info', [ext_id])
845
 
 
846
 
            if resource['status'] == 'canceled':
847
 
                wf_service = netsvc.LocalService("workflow")
848
 
                wf_service.trg_validate(uid, 'sale.order', order.id, 'cancel', cr)
849
 
                updated = True
850
 
                self.log(cr, uid, order.id, "order %s canceled when updated from external system" % (order.id,))
851
 
            # If the order isn't canceled and was waiting for a payment,
852
 
            # so we follow the standard flow according to ext_payment_method:
853
 
            else:
854
 
                updated = self.paid_and_update(cr, uid, external_session, order.id, resource, context=context)
855
 
                if updated:
856
 
                    self.log(
857
 
                        cr, uid, order.id,
858
 
                        "order %s paid when updated from external system" %
859
 
                        (order.id,))
860
 
            # Untick the need_to_update if updated (if so was canceled in magento
861
 
            # or if it has been paid through magento)
862
 
            if updated:
863
 
                self.write(cr, uid, order.id, {'need_to_update': False})
864
 
            cr.commit() #Ugly we should not commit in the current cursor
865
 
            return True
866
 
 
867
 
    ########################################################################################################################
868
 
    #
869
 
    #           CODE THAT CLEAN MAGENTO DATA BEFORE IMPORTING IT THE BEST WILL BE TO REFACTOR MAGENTO API
870
 
    #
871
 
    ########################################################################################################################
872
 
 
873
 
 
874
 
        def _merge_sub_items(self, cr, uid, product_type, top_item, child_items, context=None):
875
 
            """
876
 
            Manage the sub items of the magento sale order lines. A top item contains one
877
 
            or many child_items. For some product types, we want to merge them in the main
878
 
            item, or keep them as order line.
879
 
 
880
 
            This method has to stay because it allow to customize the behavior of the sale
881
 
            order according to the product type.
882
 
 
883
 
            A list may be returned to add many items (ie to keep all child_items as items.
884
 
 
885
 
            :param top_item: main item (bundle, configurable)
886
 
            :param child_items: list of childs of the top item
887
 
            :return: item or list of items
888
 
            """
889
 
            if product_type == 'configurable':
890
 
                item = top_item.copy()
891
 
                # For configurable product all information regarding the price is in the configurable item
892
 
                # In the child a lot of information is empty, but contains the right sku and product_id
893
 
                # So the real product_id and the sku and the name have to be extracted from the child
894
 
                for field in ['sku', 'product_id', 'name']:
895
 
                    item[field] = child_items[0][field]
896
 
                return item
897
 
            return top_item
898
 
 
899
 
        def clean_magento_items(self, cr, uid, resource, context=None):
900
 
            """
901
 
            Method that clean the sale order line given by magento before importing it
902
 
 
903
 
            This method has to stay here because it allow to customize the behavior of the sale
904
 
            order.
905
 
 
906
 
            """
907
 
            child_items = {}  # key is the parent item id
908
 
            top_items = []
909
 
 
910
 
            # Group the childs with their parent
911
 
            for item in resource['items']:
912
 
                if item.get('parent_item_id'):
913
 
                    child_items.setdefault(item['parent_item_id'], []).append(item)
914
 
                else:
915
 
                    top_items.append(item)
916
 
 
917
 
            all_items = []
918
 
            for top_item in top_items:
919
 
                if top_item['item_id'] in child_items:
920
 
                    item_modified = self._merge_sub_items(cr, uid,
921
 
                                                          top_item['product_type'],
922
 
                                                          top_item,
923
 
                                                          child_items[top_item['item_id']],
924
 
                                                          context=context)
925
 
                    if not isinstance(item_modified, list):
926
 
                        item_modified = [item_modified]
927
 
                    all_items.extend(item_modified)
928
 
                else:
929
 
                    all_items.append(top_item)
930
 
 
931
 
            resource['items'] = all_items
932
 
            return resource
933
 
 
934
 
        def clean_magento_resource(self, cr, uid, resource, context=None):
935
 
            """
936
 
            Magento copy each address in a address sale table.
937
 
            Keeping the extid of this table 'address_id' is useless because we don't need it later
938
 
            And it's dangerous because we will have various external id for the same resource and the same referential
939
 
            Getting the ext_id of the table customer address is also not posible because Magento LIE
940
 
            Indeed if a customer create a new address on fly magento will give us the default id instead of False
941
 
            So it better to NOT trust magento and not based the address on external_id
942
 
            To avoid any erreur we remove the key
943
 
            """
944
 
            for remove_key in ['customer_address_id', 'address_id']:
945
 
                for key in ['billing_address', 'shipping_address']:
946
 
                    if remove_key in resource[key]: del resource[key][remove_key]
947
 
 
948
 
            # For really strange and unknow reason magento want to play with me and make me some joke.
949
 
            # Depending of the customer installation some time the field customer_id is equal to NONE
950
 
            # in the sale order and sometime it's equal to NONE in the address but at least the
951
 
            # the information is correct in one of this field
952
 
            # So I make this ugly code to try to fix it.
953
 
            if not resource.get('customer_id'):
954
 
                if resource['billing_address'].get('customer_id'):
955
 
                    resource['customer_id'] = resource['billing_address']['customer_id']
956
 
            else:
957
 
                if not resource['billing_address'].get('customer_id'):
958
 
                    resource['billing_address']['customer_id'] = resource['customer_id']
959
 
                if not resource['shipping_address'].get('customer_id'):
960
 
                    resource['shipping_address']['customer_id'] = resource['customer_id']
961
 
            return resource
962
 
 
963
 
 
964
 
    class sale_order_line(Model):
965
 
        _inherit = 'sale.order.line'
966
 
        _columns = {
967
 
            # Rised the precision of the sale.order.line discount field
968
 
            # from 2 to 3 digits in order to be able to have the same amount as Magento.
969
 
            # Example: Magento has a sale line of 299€ and 150€ of discount, so a line at 149€.
970
 
            # We translate it to a percent in the openerp sale order
971
 
            # With a 2 digits precision, we can have 50.17 % => 148.99 or 50.16% => 149.02.
972
 
            # Rise the digits to 3 allows to have 50.167% => 149€
973
 
            'discount': fields.float('Discount (%)', digits=(16, 3), readonly=True, states={'draft': [('readonly', False)]}),
974
 
            }
975
 
 
976
 
 
977
 
# transfered from invoice.py ###############################
978
 
 
979
 
 
980
 
class account_invoice(orm.Model):
981
 
    _inherit = 'account.invoice'
982
 
 
983
 
    #TODO instead of calling again the sale order information
984
 
    # it will be better to store the ext_id of each sale order line
985
 
    #Moreover some code should be share between the partial export of picking and invoice
986
 
    def add_invoice_line(self, cr, uid, lines, line, context=None):
987
 
        """ A line to add in the invoice is a dict with : product_id and product_qty keys."""
988
 
        line_info = {'product_id': line.product_id.id,
989
 
                     'product_qty': line.quantity,
990
 
                     }
991
 
        lines.append(line_info)
992
 
        return lines
993
 
 
994
 
    def get_invoice_items(self, cr, uid, external_session, invoice_id, order_increment_id, context=None):
995
 
        invoice = self.browse(cr, uid, invoice_id, context=context)
996
 
        balance = invoice.sale_ids[0].amount_total - invoice.amount_total
997
 
        precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
998
 
        item_qty = {}
999
 
        if round(balance, precision):
1000
 
            order_items = external_session.connection.call('sales_order.info', [order_increment_id])['items']
1001
 
            product_2_item = {}
1002
 
            for item in order_items:
1003
 
                product_2_item.update({self.pool.get('product.product').get_oeid(cr, uid, item['product_id'],
1004
 
                                        external_session.referential_id.id, context={}): item['item_id']})
1005
 
 
1006
 
            lines = []
1007
 
            # get product and quantities to invoice from the invoice
1008
 
            for line in invoice.invoice_line:
1009
 
                lines = self.add_invoice_line(cr, uid, lines, line, context)
1010
 
 
1011
 
            for line in lines:
1012
 
                #Only export product that exist in the original sale order
1013
 
                if product_2_item.get(line['product_id']):
1014
 
                    if item_qty.get(product_2_item[line['product_id']], False):
1015
 
                        item_qty[product_2_item[line['product_id']]] += line['product_qty']
1016
 
                    else:
1017
 
                        item_qty.update({product_2_item[line['product_id']]: line['product_qty']})
1018
 
        return item_qty
1019
 
 
1020
 
    def map_magento_order(self, cr, uid, external_session, invoice_id, order_increment_id, context=None):
1021
 
        #TODO Error should be catch by the external report system (need some improvement before)
1022
 
        #For now error are just logged into the OpenERP log file
1023
 
        try:
1024
 
            external_session.logger.warning('Try to map the invoice with an existing order')
1025
 
            invoice_ids = external_session.connection.call('sales_order.get_invoice_ids', [order_increment_id])
1026
 
            #TODO support mapping for partiel invoice if needed
1027
 
            if len(invoice_ids) == 1:
1028
 
                external_session.logger.info(
1029
 
                    'Success to map the invoice %s with an existing order for the order %s.'
1030
 
                    %(invoice_ids[0], order_increment_id))
1031
 
                return invoice_ids[0]
1032
 
            else:
1033
 
                external_session.logger.error(
1034
 
                    'Failed to map the invoice %s with an existing order for the order %s. Too many invoice found'
1035
 
                    %(invoice_ids[0], order_increment_id))
1036
 
                return False
1037
 
        except Exception, e:
1038
 
            external_session.logger.error(
1039
 
                'Failed to map the invoice with an existing order for the order %s. Error : %s'
1040
 
                %(order_increment_id, e))
1041
 
        return False
1042
 
 
1043
 
    def create_magento_invoice(self, cr, uid, external_session, invoice_id, order_increment_id, context=None):
1044
 
        item_qty = self.get_invoice_items(cr, uid, external_session, invoice_id, order_increment_id, context=context)
1045
 
        try:
1046
 
            return external_session.connection.call('sales_order_invoice.create', [order_increment_id,
1047
 
                                                     item_qty, _('Invoice Created'), False, False])
1048
 
        except Exception, e:
1049
 
            external_session.logger.warning(
1050
 
                'Can not create the invoice for the order %s in the external system. Error : %s'
1051
 
                %(order_increment_id, e))
1052
 
            invoice_id = self.map_magento_order(cr, uid, external_session, invoice_id, order_increment_id, context=context)
1053
 
            if invoice_id:
1054
 
                return invoice_id
1055
 
            else:
1056
 
                raise except_osv(_('Magento Error'), _('Failed to synchronize Magento invoice with OpenERP invoice'))
1057
 
 
1058
 
    def ext_create(self, cr, uid, external_session, resources, mapping=None, mapping_id=None, context=None):
1059
 
        ext_create_ids={}
1060
 
        for resource_id, resource in resources.items():
1061
 
            res = self.ext_create_one_invoice(cr, uid, external_session, resource_id, resource, context=context)
1062
 
            if res:
1063
 
                ext_create_ids[resource_id] = res
1064
 
        return ext_create_ids
1065
 
 
1066
 
    def ext_create_one_invoice(self, cr, uid, external_session, resource_id, resource, context=None):
1067
 
        resource = resource[resource.keys()[0]]
1068
 
        if resource['type'] == 'out_invoice':
1069
 
            return self.create_magento_invoice(cr, uid, external_session,
1070
 
                                resource_id, resource['order_increment_id'], context=context)
1071
 
        return False
1072
 
 
1073
 
    def _export_one_invoice(self, cr, uid, invoice, context=None):
1074
 
        if invoice.sale_ids:
1075
 
            sale = invoice.sale_ids[0]
1076
 
            referential = sale.shop_id.referential_id
1077
 
            if referential and referential.type_name == 'Magento':
1078
 
                ext_id = invoice.get_extid(referential.id)
1079
 
                if ext_id:
1080
 
                    return ext_id
1081
 
                else:
1082
 
                    external_session = ExternalSession(referential, sale.shop_id)
1083
 
                    return self._export_one_resource(cr, uid, external_session, invoice.id,
1084
 
                                                     context=context)
1085
 
 
1086
 
    def export_invoice(self, cr, uid, ids, context=None):
1087
 
        for invoice in self .browse(cr, uid, ids, context=context):
1088
 
            self._export_one_invoice(cr, uid, invoice, context=context)
1089
 
        return True
1090
 
 
1091
 
 
1092
 
# transfered from product.py ###############################
1093
 
 
1094
 
 
1095
 
#Enabling this to True will put all custom attributes into One page in
1096
 
#the products view
1097
 
GROUP_CUSTOM_ATTRS_TOGETHER = False
1098
 
 
1099
 
 
1100
 
#TODO find a good method to replace all of the special caracter allowed by magento as name for product fields
1101
 
special_character_to_replace = [
1102
 
    (u"\xf8", u"diam"),
1103
 
    (u'\xb5', u'micro'),
1104
 
    (u'\xb2', u'2'),
1105
 
    (u'\u0153', u'oe'),
1106
 
    (u'\uff92', u'_'),
1107
 
    (u'\ufffd', u'_'),
1108
 
]
1109
 
 
1110
 
def convert_to_ascii(my_unicode):
1111
 
    '''Convert to ascii, with clever management of accents (é -> e, è -> e)'''
1112
 
    if isinstance(my_unicode, unicode):
1113
 
        my_unicode_with_ascii_chars_only = ''.join((char for char in unicodedata.normalize('NFD', my_unicode) if unicodedata.category(char) != 'Mn'))
1114
 
        for special_caracter in special_character_to_replace:
1115
 
            my_unicode_with_ascii_chars_only = my_unicode_with_ascii_chars_only.replace(special_caracter[0], special_caracter[1])
1116
 
        return str(my_unicode_with_ascii_chars_only)
1117
 
    # If the argument is already of string type, we return it with the same value
1118
 
    elif isinstance(my_unicode, str):
1119
 
        return my_unicode
1120
 
    else:
1121
 
        return False
1122
 
 
1123
 
class magerp_product_category_attribute_options(MagerpModel):
1124
 
    _name = "magerp.product_category_attribute_options"
1125
 
    _description = "Option products category Attributes"
1126
 
    _rec_name = "label"
1127
 
 
1128
 
    def _get_default_option(self, cr, uid, field_name, value, context=None):
1129
 
        res = self.search(cr, uid, [['attribute_name', '=', field_name], ['value', '=', value]], context=context)
1130
 
        return res and res[0] or False
1131
 
 
1132
 
 
1133
 
    def get_create_option_id(self, cr, uid, value, attribute_name, context=None):
1134
 
        id = self.search(cr, uid, [['attribute_name', '=', attribute_name], ['value', '=', value]], context=context)
1135
 
        if id:
1136
 
            return id[0]
1137
 
        else:
1138
 
            return self.create(cr, uid, {
1139
 
                                'value': value,
1140
 
                                'attribute_name': attribute_name,
1141
 
                                'label': value.replace('_', ' '),
1142
 
                                }, context=context)
1143
 
 
1144
 
    #TODO to finish : this is just the start of the implementation of attributs for category
1145
 
    _columns = {
1146
 
        #'attribute_id':fields.many2one('magerp.product_attributes', 'Attribute'),
1147
 
        'attribute_name':fields.char(string='Attribute Code',size=64),
1148
 
        'value':fields.char('Value', size=200),
1149
 
        #'ipcast':fields.char('Type cast', size=50),
1150
 
        'label':fields.char('Label', size=100),
1151
 
        }
1152
 
 
1153
 
 
1154
 
class product_category(MagerpModel):
1155
 
    _inherit = "product.category"
1156
 
 
1157
 
    def _merge_with_default_values(self, cr, uid, external_session, ressource, vals, sub_mapping_list, defaults=None, context=None):
1158
 
        vals = super(product_category, self)._merge_with_default_values(cr, uid, external_session, ressource, vals, sub_mapping_list, defaults=defaults, context=context)
1159
 
        #some time magento category doesn't have a name
1160
 
        if not vals.get('name'):
1161
 
            vals['name'] = 'Undefined'
1162
 
        return vals
1163
 
 
1164
 
    def _get_default_export_values(self, *args, **kwargs):
1165
 
        defaults = super(product_category, self)._get_default_export_values(*args, **kwargs)
1166
 
        if defaults == None: defaults={}
1167
 
        defaults.update({'magento_exportable': True})
1168
 
        return defaults
1169
 
 
1170
 
    def multi_lang_read(self, cr, uid, external_session, ids, fields_to_read, langs, resources=None, use_multi_lang = True, context=None):
1171
 
        return super(product_category, self).multi_lang_read(cr, uid, external_session, ids, fields_to_read, langs,
1172
 
                                                            resources=resources,
1173
 
                                                            use_multi_lang = False,
1174
 
                                                            context=context)
1175
 
 
1176
 
    def ext_create(self, cr, uid, external_session, resources, mapping=None, mapping_id=None, context=None):
1177
 
        ext_create_ids={}
1178
 
        storeview_to_lang = context['storeview_to_lang']
1179
 
        main_lang = context['main_lang']
1180
 
        for resource_id, resource in resources.items():
1181
 
            #Move this part of code in a python lib
1182
 
            parent_id = resource[main_lang]['parent_id']
1183
 
            del resource[main_lang]['parent_id']
1184
 
            ext_id = external_session.connection.call('catalog_category.create', [parent_id, resource[main_lang]])
1185
 
            for storeview, lang in storeview_to_lang.items():
1186
 
                external_session.connection.call('catalog_category.update', [ext_id, resource[lang], storeview])
1187
 
            ext_create_ids[resource_id] = ext_id
1188
 
        return ext_create_ids
1189
 
 
1190
 
 
1191
 
    def ext_update(self, cr, uid, external_session, resources, mapping=None, mapping_id=None, context=None):
1192
 
        ext_update_ids={}
1193
 
        storeview_to_lang = context['storeview_to_lang']
1194
 
        main_lang = context['main_lang']
1195
 
        for resource_id, resource in resources.items():
1196
 
            #Move this part of code in a python lib
1197
 
            ext_id = resource[main_lang]['ext_id']
1198
 
            del resource[main_lang]['ext_id']
1199
 
            parent_id = resource[main_lang]['parent_id']
1200
 
            del resource[main_lang]['parent_id']
1201
 
            external_session.connection.call('catalog_category.update', [ext_id, resource[main_lang], False])
1202
 
            external_session.connection.call('oerp_catalog_category.move', [ext_id, parent_id])
1203
 
            for storeview, lang in storeview_to_lang.items():
1204
 
                del resource[lang]['ext_id']
1205
 
                external_session.connection.call('catalog_category.update', [ext_id, resource[lang], storeview])
1206
 
            ext_update_ids[resource_id] = ext_id
1207
 
        return ext_update_ids
1208
 
 
1209
 
    _columns = {
1210
 
        'magerp_fields' : fields.serialized('Magento Product Categories Extra Fields'),
1211
 
        'create_date': fields.datetime('Created date', readonly=True),
1212
 
        'write_date': fields.datetime('Updated date', readonly=True),
1213
 
        'magento_exportable':fields.boolean('Export to Magento'),
1214
 
        'updated':fields.boolean('To synchronize', help="Set if the category underwent a change & has to be synched."),
1215
 
        #*************** Magento Fields ********************
1216
 
        #==== General Information ====
1217
 
        'level': fields.integer('Level', readonly=True),
1218
 
        'magento_parent_id': fields.integer('Magento Parent', readonly=True), #Key Changed from parent_id
1219
 
        'is_active': fields.boolean('Active?', help="Indicates whether active in magento"),
1220
 
        'description': fields.text('Description'),
1221
 
        'image': fields.binary('Image'),
1222
 
        'image_name':fields.char('File Name', size=100),
1223
 
        'meta_title': fields.char('Title (Meta)', size=75),
1224
 
        'meta_keywords': fields.text('Meta Keywords'),
1225
 
        'meta_description': fields.text('Meta Description'),
1226
 
        'url_key': fields.char('URL-key', size=100), #Readonly
1227
 
        #==== Display Settings ====
1228
 
        'display_mode': fields.selection([
1229
 
                    ('PRODUCTS', 'Products Only'),
1230
 
                    ('PAGE', 'Static Block Only'),
1231
 
                    ('PRODUCTS_AND_PAGE', 'Static Block & Products')], 'Display Mode', required=True),
1232
 
        'is_anchor': fields.boolean('Anchor?'),
1233
 
        'use_default_available_sort_by': fields.boolean('Default Config For Available Sort By', help="Use default config for available sort by"),
1234
 
        # 'available_sort_by': fields.sparse(type='many2many', relation='magerp.product_category_attribute_options', string='Available Product Listing (Sort By)', serialization_field='magerp_fields', domain="[('attribute_name', '=', 'sort_by'), ('value', '!=','None')]"),
1235
 
        # 'default_sort_by': fields.many2one('magerp.product_category_attribute_options', 'Default Product Listing Sort (Sort By)', domain="[('attribute_name', '=', 'sort_by')]", require=True),
1236
 
        'magerp_stamp':fields.datetime('Magento stamp'),
1237
 
        'include_in_menu': fields.boolean('Include in Navigation Menu'),
1238
 
        # 'page_layout': fields.many2one('magerp.product_category_attribute_options', 'Page Layout', domain="[('attribute_name', '=', 'page_layout')]"),
1239
 
        }
1240
 
 
1241
 
    _defaults = {
1242
 
        'display_mode':lambda * a:'PRODUCTS',
1243
 
        'use_default_available_sort_by': lambda * a:True,
1244
 
        # 'default_sort_by': lambda self,cr,uid,c: self.pool.get('magerp.product_category_attribute_options')._get_default_option(cr, uid, 'sort_by', 'None', context=c),
1245
 
        'level':lambda * a:1,
1246
 
        'include_in_menu': lambda * a:True,
1247
 
        # 'page_layout': lambda self,cr,uid,c: self.pool.get('magerp.product_category_attribute_options')._get_default_option(cr, uid, 'page_layout', 'None', context=c),
1248
 
        }
1249
 
 
1250
 
    def write(self, cr, uid, ids, vals, context=None):
1251
 
        if not 'magerp_stamp' in vals.keys():
1252
 
            vals['magerp_stamp'] = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
1253
 
        return super(product_category, self).write(cr, uid, ids, vals, context)
1254
 
 
1255
 
    # XXX reimplement in Connector as _ext_search_product_category
1256
 
    def _get_external_resource_ids(self, cr, uid, external_session, resource_filter=None, mapping=None, context=None):
1257
 
        def get_child_ids(tree):
1258
 
            result=[]
1259
 
            result.append(tree['category_id'])
1260
 
            for categ in tree['children']:
1261
 
                result += get_child_ids(categ)
1262
 
            return result
1263
 
        ids=[]
1264
 
        confirmation = external_session.connection.call('catalog_category.currentStore', [0])   #Set browse to root store
1265
 
        if confirmation:
1266
 
            categ_tree = external_session.connection.call('catalog_category.tree')             #Get the tree
1267
 
            ids = get_child_ids(categ_tree)
1268
 
        return ids
1269
 
 
1270
 
 
1271
 
class magerp_product_attributes(MagerpModel):
1272
 
    _name = "magerp.product_attributes"
1273
 
    _description = "Attributes of products"
1274
 
    _rec_name = "attribute_code"
1275
 
 
1276
 
    def _get_group(self, cr, uid, ids, prop, unknow_none, context=None):
1277
 
        res = {}
1278
 
        for attribute in self.browse(cr, uid, ids, context):
1279
 
            res[attribute.id] = self.pool.get('magerp.product_attribute_groups').extid_to_existing_oeid(cr, uid, attribute.group_id, attribute.referential_id.id)
1280
 
        return res
1281
 
 
1282
 
    _columns = {
1283
 
        'attribute_code':fields.char('Code', size=200),
1284
 
        'magento_id':fields.integer('ID'),
1285
 
        'set_id':fields.integer('Attribute Set'),
1286
 
        'options':fields.one2many('magerp.product_attribute_options', 'attribute_id', 'Attribute Options'),
1287
 
        #'set':fields.function(_get_set, type="many2one", relation="magerp.product_attribute_set", method=True, string="Attribute Set"), This field is invalid as attribs have m2m relation
1288
 
        'frontend_input':fields.selection([
1289
 
                                           ('text', 'Text'),
1290
 
                                           ('textarea', 'Text Area'),
1291
 
                                           ('select', 'Selection'),
1292
 
                                           ('multiselect', 'Multi-Selection'),
1293
 
                                           ('boolean', 'Yes/No'),
1294
 
                                           ('date', 'Date'),
1295
 
                                           ('price', 'Price'),
1296
 
                                           ('media_image', 'Media Image'),
1297
 
                                           ('gallery', 'Gallery'),
1298
 
                                           ('weee', 'Fixed Product Tax'),
1299
 
                                           ('file', 'File'), #this option is not a magento native field it will be better to found a generic solutionto manage this kind of custom option
1300
 
                                           ('weight', 'Weight'),
1301
 
                                           ], 'Frontend Input'
1302
 
                                          ),
1303
 
        'frontend_class':fields.char('Frontend Class', size=100),
1304
 
        'backend_model':fields.char('Backend Model', size=200),
1305
 
        'backend_type':fields.selection([
1306
 
                                         ('static', 'Static'),
1307
 
                                         ('varchar', 'Varchar'),
1308
 
                                         ('text', 'Text'),
1309
 
                                         ('decimal', 'Decimal'),
1310
 
                                         ('int', 'Integer'),
1311
 
                                         ('datetime', 'Datetime')], 'Backend Type'),
1312
 
        'frontend_label':fields.char('Label', size=100),
1313
 
        'is_visible_in_advanced_search':fields.boolean('Visible in advanced search?', required=False),
1314
 
        'is_global':fields.boolean('Global ?', required=False),
1315
 
        'is_filterable':fields.boolean('Filterable?', required=False),
1316
 
        'is_comparable':fields.boolean('Comparable?', required=False),
1317
 
        'is_visible':fields.boolean('Visible?', required=False),
1318
 
        'is_searchable':fields.boolean('Searchable ?', required=False),
1319
 
        'is_user_defined':fields.boolean('User Defined?', required=False),
1320
 
        'is_configurable':fields.boolean('Configurable?', required=False),
1321
 
        'is_visible_on_front':fields.boolean('Visible (Front)?', required=False),
1322
 
        'is_used_for_price_rules':fields.boolean('Used for pricing rules?', required=False),
1323
 
        'is_unique':fields.boolean('Unique?', required=False),
1324
 
        'is_required':fields.boolean('Required?', required=False),
1325
 
        'position':fields.integer('Position', required=False),
1326
 
        'group_id': fields.integer('Group') ,
1327
 
        'group':fields.function(_get_group, type="many2one", relation="magerp.product_attribute_groups", method=True, string="Attribute Group"),
1328
 
        'apply_to': fields.char('Apply to', size=200),
1329
 
        'default_value': fields.char('Default Value', size=10),
1330
 
        'note':fields.char('Note', size=200),
1331
 
        'entity_type_id':fields.integer('Entity Type'),
1332
 
        'referential_id':fields.many2one('external.referential', 'Magento Instance', readonly=True),
1333
 
        #These parameters are for automatic management
1334
 
        'field_name':fields.char('Open ERP Field name', size=100),
1335
 
        'attribute_set_info':fields.text('Attribute Set Information'),
1336
 
        'based_on':fields.selection([('product_product', 'Product Product'), ('product_template', 'Product Template')], 'Based On'),
1337
 
        }
1338
 
 
1339
 
    _defaults = {'based_on': lambda*a: 'product_template',
1340
 
                 }
1341
 
    #mapping magentofield:(openerpfield,typecast,)
1342
 
    #have an entry for each mapped field
1343
 
    _no_create_list = ['product_id',
1344
 
                       'name',
1345
 
                       'description',
1346
 
                       'short_description',
1347
 
                       'sku',
1348
 
                       'weight',
1349
 
                       'category_ids',
1350
 
                       'price',
1351
 
                       'cost',
1352
 
                       'set',
1353
 
                       'ean',
1354
 
                       ]
1355
 
    _translatable_default_codes = ['description',
1356
 
                                   'meta_description',
1357
 
                                   'meta_keyword',
1358
 
                                   'meta_title',
1359
 
                                   'name',
1360
 
                                   'short_description',
1361
 
                                   'url_key',
1362
 
                                   ]
1363
 
    _not_store_in_json = ['minimal_price',
1364
 
                          'special_price',
1365
 
                          'description',
1366
 
                          'meta_description',
1367
 
                          'meta_keyword',
1368
 
                          'meta_title',
1369
 
                          'name',
1370
 
                          'short_description',
1371
 
                          'url_key',
1372
 
                          ]
1373
 
    _type_conversion = {'':'char',
1374
 
                        'text':'char',
1375
 
                        'textarea':'text',
1376
 
                        'select':'many2one',
1377
 
                        'date':'date',
1378
 
                        'price':'float',
1379
 
                        'media_image':'binary',
1380
 
                        'gallery':'binary',
1381
 
                        'multiselect':'many2many',
1382
 
                        'boolean':'boolean',
1383
 
                        'weee':'char',
1384
 
                        False:'char',
1385
 
                        'file':'char', #this option is not a magento native field it will be better to found a generic solutionto manage this kind of custom option
1386
 
                        }
1387
 
    _type_casts = {'':'unicode',
1388
 
                   'text':'unicode',
1389
 
                   'textarea':'unicode',
1390
 
                   'select':'unicode',
1391
 
                   'date':'unicode',
1392
 
                   'price':'float',
1393
 
                   'media_image':'False',
1394
 
                   'gallery':'False',
1395
 
                   'multiselect':'list',
1396
 
                   'boolean':'int',
1397
 
                   'weee':'unicode',
1398
 
                   False:'unicode',
1399
 
                   'file':'unicode', #this option is not a magento native field it will be better to found a generic solutionto manage this kind of custom option
1400
 
                   }
1401
 
    _variant_fields = ['color',
1402
 
                       'dimension',
1403
 
                       'visibility',
1404
 
                       'special_price',
1405
 
                       'special_price_from_date',
1406
 
                       'special_price_to_date',
1407
 
                       ]
1408
 
 
1409
 
 
1410
 
    #For some field you can specify the syncronisation way
1411
 
    #in : Magento => OpenERP
1412
 
    #out : Magento <= OpenERP
1413
 
    #in_out (default_value) : Magento <=> OpenERP
1414
 
    #TODO check if this field have to be in only one way and if yes add this feature
1415
 
    _sync_way = {'has_options' : 'in',
1416
 
                 'tier_price': 'in',
1417
 
                 'special_price' : 'in',
1418
 
                 }
1419
 
 
1420
 
    def _is_attribute_translatable(self, vals):
1421
 
        """Tells if field associated to attribute should be translatable or not.
1422
 
        For now we are using a default list, later we could say that any attribute
1423
 
        which scope in Magento is 'store' should be translated."""
1424
 
        if vals['attribute_code'] in self._translatable_default_codes:
1425
 
            return True
1426
 
        else:
1427
 
            return False
1428
 
 
1429
 
    def write(self, cr, uid, ids, vals, context=None):
1430
 
        """Will recreate the mapping attributes, beware if you customized some!"""
1431
 
        if context is None:
1432
 
            context = {}
1433
 
 
1434
 
        if type(ids) == int:
1435
 
            ids = [ids]
1436
 
        result = super(magerp_product_attributes, self).write(cr, uid, ids, vals, context)
1437
 
        model_ids = self.pool.get('ir.model').search(cr, uid, [('model', 'in', ['product.product', 'product.template'])])
1438
 
        product_model_id = self.pool.get('ir.model').search(cr, uid, [('model', 'in', ['product.product'])])[0]
1439
 
        referential_id = context.get('referential_id', False)
1440
 
        if referential_id:
1441
 
            for id in ids:
1442
 
                all_vals = self.read(cr, uid, id, [], context)
1443
 
 
1444
 
                #Fetch Options
1445
 
                if 'frontend_input' in all_vals.keys() and all_vals['frontend_input'] in ['select', 'multiselect']:
1446
 
                    core_imp_conn = self.pool.get('external.referential').external_connection(cr, uid, [referential_id])
1447
 
                    options_data = core_imp_conn.call('ol_catalog_product_attribute.options', [all_vals['magento_id']])
1448
 
                    if options_data:
1449
 
                        self.pool.get('magerp.product_attribute_options').data_to_save(cr, uid, options_data, update=True, context={'attribute_id': id, 'referential_id': referential_id})
1450
 
 
1451
 
 
1452
 
                field_name = all_vals['field_name']
1453
 
                #TODO refactor me it will be better to add a one2many between the magerp_product_attributes and the ir.model.fields
1454
 
                field_ids = self.pool.get('ir.model.fields').search(cr, uid, [('name', '=', field_name), ('model_id', 'in', model_ids)])
1455
 
                if field_ids:
1456
 
                    self._create_mapping(cr, uid, self._type_conversion[all_vals.get('frontend_input', False)], field_ids[0], field_name, referential_id, product_model_id, all_vals, id)
1457
 
        return result
1458
 
 
1459
 
    def create(self, cr, uid, vals, context=None):
1460
 
        """Will create product.template new fields accordingly to Magento product custom attributes and also create mappings for them"""
1461
 
        if context is None:
1462
 
            context = {}
1463
 
        if not vals['attribute_code'] in self._no_create_list:
1464
 
            field_name = "x_magerp_" + vals['attribute_code']
1465
 
            field_name = convert_to_ascii(field_name)
1466
 
            vals['field_name']= field_name
1467
 
        if 'attribute_set_info' in vals.keys():
1468
 
            attr_set_info = eval(vals.get('attribute_set_info',{}))
1469
 
            for each_key in attr_set_info.keys():
1470
 
                vals['group_id'] = attr_set_info[each_key].get('group_id', False)
1471
 
 
1472
 
        crid = super(magerp_product_attributes, self).create(cr, uid, vals, context)
1473
 
        if not vals['attribute_code'] in self._no_create_list:
1474
 
            #If the field has to be created
1475
 
            if crid:
1476
 
                #Fetch Options
1477
 
                if 'frontend_input' in vals.keys() and vals['frontend_input'] in ['select',  'multiselect']:
1478
 
                    core_imp_conn = self.pool.get('external.referential').external_connection(cr, uid, vals['referential_id'])
1479
 
                    options_data = core_imp_conn.call('ol_catalog_product_attribute.options', [vals['magento_id']])
1480
 
                    if options_data:
1481
 
                        self.pool.get('magerp.product_attribute_options').data_to_save(cr, uid, options_data, update=False, context={'attribute_id': crid, 'referential_id': vals['referential_id']})
1482
 
 
1483
 
                #Manage fields
1484
 
                if vals['attribute_code'] and vals.get('frontend_input', False):
1485
 
                    #Code for dynamically generating field name and attaching to this
1486
 
                    if vals['attribute_code'] in self._variant_fields:
1487
 
                        model_name='product.product'
1488
 
                    else:
1489
 
                        model_name='product.template'
1490
 
 
1491
 
                    model_id = self.pool.get('ir.model').search(cr, uid, [('model', '=', model_name)])
1492
 
 
1493
 
                    if model_id and len(model_id) == 1:
1494
 
                        model_id = model_id[0]
1495
 
                        #Check if field already exists
1496
 
                        referential_id = context.get('referential_id',False)
1497
 
                        field_ids = self.pool.get('ir.model.fields').search(cr, uid, [('name', '=', field_name), ('model_id', '=', model_id)])
1498
 
                        field_vals = {
1499
 
                            'name':field_name,
1500
 
                            'model_id':model_id,
1501
 
                            'model':model_name,
1502
 
                            'field_description':vals.get('frontend_label', False) or vals['attribute_code'],
1503
 
                            'ttype':self._type_conversion[vals.get('frontend_input', False)],
1504
 
                            'translate': self._is_attribute_translatable(vals),
1505
 
                        }
1506
 
                        if not vals['attribute_code'] in self._not_store_in_json:
1507
 
                            if model_name == 'product.template':
1508
 
                                field_vals['serialization_field_id'] = self.pool.get('ir.model.fields').search(cr, uid, [('name', '=', 'magerp_tmpl'), ('model', '=', 'product.template')], context=context)[0]
1509
 
                            else:
1510
 
                                field_vals['serialization_field_id'] = self.pool.get('ir.model.fields').search(cr, uid, [('name', '=', 'magerp_variant'), ('model', '=', 'product.product')], context=context)[0]
1511
 
                        if not field_ids:
1512
 
                            #The field is not there create it
1513
 
                            #IF char add size
1514
 
                            if field_vals['ttype'] == 'char':
1515
 
                                field_vals['size'] = 100
1516
 
                            if field_vals['ttype'] == 'many2one':
1517
 
                                field_vals['relation'] = 'magerp.product_attribute_options'
1518
 
                                field_vals['domain'] = "[('attribute_id','='," + str(crid) + ")]"
1519
 
                            if field_vals['ttype'] == 'many2many':
1520
 
                                field_vals['relation'] = 'magerp.product_attribute_options'
1521
 
                                field_vals['domain'] = "[('attribute_id','='," + str(crid) + ")]"
1522
 
                            field_vals['state'] = 'manual'
1523
 
                            #All field values are computed, now save
1524
 
                            field_id = self.pool.get('ir.model.fields').create(cr, uid, field_vals)
1525
 
                            # mapping have to be based on product.product
1526
 
                            model_id = self.pool.get('ir.model').search(cr, uid, [('model', '=', 'product.product')])[0]
1527
 
                            self._create_mapping(cr, uid, field_vals['ttype'], field_id, field_name, referential_id, model_id, vals, crid)
1528
 
        return crid
1529
 
 
1530
 
    def _default_mapping(self, cr, uid, ttype, field_name, vals, attribute_id, model_id, mapping_line, referential_id):
1531
 
        #TODO refactor me and use the direct mapping
1532
 
        #If the field have restriction on domain
1533
 
        #Maybe we can give the posibility to map directly m2m and m2o field_description
1534
 
        #by filtrering directly with the domain and the string value
1535
 
        if ttype in ['char', 'text', 'date', 'float', 'weee', 'boolean']:
1536
 
            mapping_line['evaluation_type'] = 'direct'
1537
 
            if ttype == 'float':
1538
 
                mapping_line['external_type'] = 'float'
1539
 
            elif ttype == 'boolean':
1540
 
                mapping_line['external_type'] = 'int'
1541
 
            else:
1542
 
                mapping_line['external_type'] = 'unicode'
1543
 
 
1544
 
        elif ttype in ['many2one']:
1545
 
            mapping_line['evaluation_type'] = 'function'
1546
 
            mapping_line['in_function'] = \
1547
 
               ("if '%(attribute_code)s' in resource:\n"
1548
 
                "    option_id = self.pool.get('magerp.product_attribute_options').search(cr, uid, [('attribute_id','=',%(attribute_id)s),('value','=',ifield)])\n"
1549
 
                "    if option_id:\n"
1550
 
                "        result = [('%(field_name)s', option_id[0])]")  % ({'attribute_code': vals['attribute_code'], 'attribute_id': attribute_id, 'field_name': field_name})
1551
 
            # we browse on resource['%(field_name)s'][0] because resource[field_name] is in the form (id, name)
1552
 
            mapping_line['out_function'] = \
1553
 
               ("if '%(field_name)s' in resource:\n"
1554
 
                "    result = [('%(attribute_code)s', False)]\n"
1555
 
                "    if resource.get('%(field_name)s'):\n"
1556
 
                "        option = self.pool.get('magerp.product_attribute_options').browse(cr, uid, resource['%(field_name)s'][0])\n"
1557
 
                "        if option:\n"
1558
 
                "            result = [('%(attribute_code)s', option.value)]") % ({'field_name': field_name, 'attribute_code': vals['attribute_code']})
1559
 
        elif ttype in ['many2many']:
1560
 
            mapping_line['evaluation_type'] = 'function'
1561
 
            mapping_line['in_function'] = ("option_ids = []\n"
1562
 
                "opt_obj = self.pool.get('magerp.product_attribute_options')\n"
1563
 
                "for ext_option_id in ifield:\n"
1564
 
                "    option_ids.extend(opt_obj.search(cr, uid, [('attribute_id','=',%(attribute_id)s), ('value','=',ext_option_id)]))\n"
1565
 
                "result = [('%(field_name)s', [(6, 0, option_ids)])]") % ({'attribute_id': attribute_id, 'field_name': field_name})
1566
 
            mapping_line['out_function'] = ("result=[('%(attribute_code)s', [])]\n"
1567
 
                "if resource.get('%(field_name)s'):\n"
1568
 
                "    options = self.pool.get('magerp.product_attribute_options').browse(cr, uid, resource['%(field_name)s'])\n"
1569
 
                "    result = [('%(attribute_code)s', [option.value for option in options])]") % \
1570
 
               ({'field_name': field_name, 'attribute_code': vals['attribute_code']})
1571
 
        elif ttype in ['binary']:
1572
 
            warning_text = "Binary mapping is actually not supported (attribute: %s)" % (vals['attribute_code'],)
1573
 
            _logger.warn(warning_text)
1574
 
            warning_msg = ("import logging\n"
1575
 
                           "logger = logging.getLogger('in/out_function')\n"
1576
 
                           "logger.warn('%s')") % (warning_text,)
1577
 
            mapping_line['in_function'] = mapping_line['out_function'] = warning_msg
1578
 
        return mapping_line
1579
 
 
1580
 
    def _create_mapping(self, cr, uid, ttype, field_id, field_name, referential_id, model_id, vals, attribute_id):
1581
 
        """Search & create mapping entries"""
1582
 
        if vals['attribute_code'] in self._no_create_list:
1583
 
            return False
1584
 
        mapping_id = self.pool.get('external.mapping').search(cr, uid, [('referential_id', '=', referential_id), ('model_id', '=', model_id)])
1585
 
        if mapping_id:
1586
 
            existing_line = self.pool.get('external.mapping.line').search(cr, uid, [('external_field', '=', vals['attribute_code']), ('mapping_id', '=', mapping_id[0])])
1587
 
            if not existing_line:
1588
 
                mapping_line = {'external_field': vals['attribute_code'],
1589
 
                                'sequence': 0,
1590
 
                                'mapping_id': mapping_id[0],
1591
 
                                'type': self._sync_way.get(vals['attribute_code'], 'in_out'),
1592
 
                                'external_type': self._type_casts[vals.get('frontend_input', False)],
1593
 
                                'field_id': field_id, }
1594
 
                mapping_line = self._default_mapping(cr, uid, ttype, field_name, vals, attribute_id, model_id, mapping_line, referential_id)
1595
 
                self.pool.get('external.mapping.line').create(cr, uid, mapping_line)
1596
 
        return True
1597
 
 
1598
 
 
1599
 
"""Dont remove the code, we might need it --sharoon
1600
 
class magerp_product_attributes_set_info(Model):
1601
 
    _name="magerp.product_attributes.set_info"
1602
 
    _description = "Attribute Set information for each attribute"
1603
 
    _columns = {
1604
 
        'referential_id':fields.many2one('external.referential', 'Magento Instance', readonly=True),
1605
 
        'attribute_set_id':
1606
 
        'sort':fields.integer('sort')
1607
 
        'group_sort':fields.integer('group_sort')
1608
 
        'group_id':
1609
 
                }
1610
 
magerp_product_attributes_set_info()"""
1611
 
 
1612
 
class magerp_product_attribute_options(MagerpModel):
1613
 
    _name = "magerp.product_attribute_options"
1614
 
    _description = "Options  of selected attributes"
1615
 
    _rec_name = "label"
1616
 
 
1617
 
    _columns = {
1618
 
        'attribute_id':fields.many2one('magerp.product_attributes', 'Attribute'),
1619
 
        'attribute_name':fields.related('attribute_id', 'attribute_code', type='char', string='Attribute Code',),
1620
 
        'value':fields.char('Value', size=200),
1621
 
        'ipcast':fields.char('Type cast', size=50),
1622
 
        'label':fields.char('Label', size=100),
1623
 
        'referential_id':fields.many2one('external.referential', 'Magento Instance', readonly=True),
1624
 
    }
1625
 
 
1626
 
 
1627
 
    def data_to_save(self, cr, uid, vals_list, update=False, context=None):
1628
 
        """This method will take data from vals and use context to create record"""
1629
 
        if context is None:
1630
 
            context = {}
1631
 
        to_remove_ids = []
1632
 
        if update:
1633
 
            to_remove_ids = self.search(cr, uid, [('attribute_id', '=', context['attribute_id'])])
1634
 
 
1635
 
        for vals in vals_list:
1636
 
            if vals.get('value', False) and vals.get('label', False):
1637
 
                value = unicode(vals['value'])
1638
 
                #Fixme: What to do when Magento offers emty options which open erp doesnt?
1639
 
                #Such cases dictionary is: {'value':'','label':''}
1640
 
                if update:
1641
 
                    existing_ids = self.search(
1642
 
                        cr, uid,
1643
 
                        [('attribute_id', '=', context['attribute_id']),
1644
 
                         ('value', '=', value)],
1645
 
                        context=context)
1646
 
                    if len(existing_ids) == 1:
1647
 
                        to_remove_ids.remove(existing_ids[0])
1648
 
                        self.write(cr, uid, existing_ids[0], {'label': vals.get('label', False)})
1649
 
                        continue
1650
 
 
1651
 
                self.create(cr, uid, {
1652
 
                                        'attribute_id': context['attribute_id'],
1653
 
                                        'value': value,
1654
 
                                        'label': vals['label'],
1655
 
                                        'referential_id': context['referential_id'],
1656
 
                                    }
1657
 
                            )
1658
 
 
1659
 
        self.unlink(cr, uid, to_remove_ids) #if a product points to a removed option, it will get no option instead
1660
 
 
1661
 
    def get_option_id(self, cr, uid, attr_name, value, instance):
1662
 
        attr_id = self.search(cr, uid, [('attribute_name', '=', attr_name), ('value', '=', value), ('referential_id', '=', instance)])
1663
 
        if attr_id:
1664
 
            return attr_id[0]
1665
 
        else:
1666
 
            return False
1667
 
 
1668
 
 
1669
 
class magerp_product_attribute_set(MagerpModel):
1670
 
    _name = "magerp.product_attribute_set"
1671
 
    _description = "Attribute sets in products"
1672
 
    _rec_name = 'attribute_set_name'
1673
 
 
1674
 
    _columns = {
1675
 
        'sort_order':fields.integer('Sort Order'),
1676
 
        'attribute_set_name':fields.char('Set Name', size=100),
1677
 
        'attributes':fields.many2many('magerp.product_attributes', 'magerp_attrset_attr_rel', 'set_id', 'attr_id', 'Attributes'),
1678
 
        'referential_id':fields.many2one('external.referential', 'Magento Instance', readonly=True),
1679
 
        'magento_id':fields.integer('Magento ID'),
1680
 
        }
1681
 
 
1682
 
 
1683
 
    def update_attribute(self, cr, uid, ids, context=None):
1684
 
        ref_obj = self.pool.get('external.referential')
1685
 
        mag_ref_ids = ref_obj.search(cr, uid, [('version_id','ilike', 'magento')], context=context)
1686
 
        for referential in ref_obj.browse(cr, uid, mag_ref_ids, context=context):
1687
 
            external_session = ExternalSession(referential, referential)
1688
 
            for attr_set_id in ids:
1689
 
                attr_set_ext_id = self.get_extid(cr, uid, attr_set_id, referential.id, context=context)
1690
 
                if attr_set_ext_id:
1691
 
                    self._import_attribute(cr, uid, external_session, attr_set_ext_id, context=context)
1692
 
                    self._import_attribute_relation(cr, uid, external_session, [attr_set_ext_id], context=context)
1693
 
        return True
1694
 
 
1695
 
    #TODO refactor me
1696
 
    def _import_attribute(self, cr, uid, external_session, attr_set_ext_id, attributes_imported=None, context=None):
1697
 
        attr_obj = self.pool.get('magerp.product_attributes')
1698
 
        mage_inp = external_session.connection.call('ol_catalog_product_attribute.list', [attr_set_ext_id])             #Get the tree
1699
 
        mapping = {'magerp.product_attributes' : attr_obj._get_mapping(cr, uid, external_session.referential_id.id, context=context)}
1700
 
        attribut_to_import = []
1701
 
        if not attributes_imported: attributes_imported=[]
1702
 
        for attribut in mage_inp:
1703
 
            ext_id = attribut['attribute_id']
1704
 
            if not ext_id in attributes_imported:
1705
 
                attributes_imported.append(ext_id)
1706
 
                attr_obj._record_one_external_resource(cr, uid, external_session, attribut,
1707
 
                                                defaults={'referential_id':external_session.referential_id.id},
1708
 
                                                mapping=mapping,
1709
 
                                                context=context,
1710
 
                                            )
1711
 
        external_session.logger.info("All attributs for the attributs set id %s was succesfully imported", attr_set_ext_id)
1712
 
        return True
1713
 
 
1714
 
    #TODO refactor me
1715
 
    def _import_attribute_relation(self, cr, uid, external_session, attr_set_ext_ids, context=None):
1716
 
        #Relate attribute sets & attributes
1717
 
        mage_inp = {}
1718
 
        #Pass in {attribute_set_id:{attributes},attribute_set_id2:{attributes}}
1719
 
        #print "Attribute sets are:", attrib_sets
1720
 
        #TODO find a solution in order to import the relation in a incremental way (maybe splitting this function in two)
1721
 
        for attr_id in attr_set_ext_ids:
1722
 
            mage_inp[attr_id] = external_session.connection.call('ol_catalog_product_attribute.relations', [attr_id])
1723
 
        if mage_inp:
1724
 
            self.relate(cr, uid, mage_inp, external_session.referential_id.id, context)
1725
 
        return True
1726
 
 
1727
 
    def relate(self, cr, uid, mage_inp, instance, *args):
1728
 
        #TODO: Build the relations code
1729
 
        #Note: It is better to insert multiple record by cr.execute because:
1730
 
        #1. Everything ends in a sinlge query (Fast)
1731
 
        #2. If the values are updated using the return value for m2m field it may execute much slower
1732
 
        #3. Multirow insert is 4x faster than reduntant insert ref:http://kaiv.wordpress.com/2007/07/19/faster-insert-for-multiple-rows/
1733
 
        rel_dict = {}
1734
 
        #Get all attributes in onew place to convert from mage_id to oe_id
1735
 
        attr_ids = self.pool.get('magerp.product_attributes').search(cr, uid, [])
1736
 
        attr_list_oe = self.pool.get('magerp.product_attributes').read(cr, uid, attr_ids, ['magento_id'])
1737
 
        attr_list = {}
1738
 
        for each_set in attr_list_oe:
1739
 
            attr_list[each_set['magento_id']] = each_set['id']
1740
 
        attr_set_ids = self.search(cr, uid, [])
1741
 
        attr_set_list_oe = self.read(cr, uid, attr_set_ids, ['magento_id'])
1742
 
        attr_set_list = {}
1743
 
        #print attr_set_list_oe
1744
 
        for each_set in attr_set_list_oe:
1745
 
            attr_set_list[each_set['magento_id']] = each_set['id']
1746
 
        key_attrs = []
1747
 
        #print mage_inp
1748
 
        for each_key in mage_inp.keys():
1749
 
            self.write(cr, uid, attr_set_list[each_key], {'attributes': [[6, 0, []]]})
1750
 
            for each_attr in mage_inp[each_key]:
1751
 
                if each_attr['attribute_id']:
1752
 
                    try:
1753
 
                        key_attrs.append((attr_set_list[each_key], attr_list[int(each_attr['attribute_id'])]))
1754
 
                    except Exception, e:
1755
 
                        pass
1756
 
        #rel_dict {set_id:[attr_id_1,attr_id_2,],set_id2:[attr_id_1,attr_id_3]}
1757
 
        if len(key_attrs) > 0:
1758
 
            #rel_dict {set_id:[attr_id_1,attr_id_2,],set_id2:[attr_id_1,attr_id_3]}
1759
 
            query = "INSERT INTO magerp_attrset_attr_rel (set_id,attr_id) VALUES "
1760
 
            for each_pair in key_attrs:
1761
 
                query += str(each_pair)
1762
 
                query += ","
1763
 
            query = query[0:len(query) - 1] + ";"
1764
 
            cr.execute(query)
1765
 
        return True
1766
 
 
1767
 
 
1768
 
class magerp_product_attribute_groups(MagerpModel):
1769
 
    _name = "magerp.product_attribute_groups"
1770
 
    _description = "Attribute groups in Magento"
1771
 
    _rec_name = 'attribute_group_name'
1772
 
    _order = "sort_order"
1773
 
    def _get_set(self, cr, uid, ids, prop, unknow_none, context=None):
1774
 
        res = {}
1775
 
        for attribute_group in self.browse(cr, uid, ids, context):
1776
 
            res[attribute_group.id] = self.pool.get('magerp.product_attribute_set').extid_to_oeid(cr, uid, attribute_group.attribute_set_id, attribute_group.referential_id.id)
1777
 
        return res
1778
 
 
1779
 
    # XXX a deplacer dans MagentoConnector
1780
 
    def _get_filter(self, cr, uid, external_session, step, previous_filter=None, context=None):
1781
 
        attrset_ids = self.pool.get('magerp.product_attribute_set').get_all_extid_from_referential(cr, uid, external_session.referential_id.id, context=context)
1782
 
        return {'attribute_set_id':{'in':attrset_ids}}
1783
 
 
1784
 
    _columns = {
1785
 
        'attribute_set_id':fields.integer('Attribute Set ID'),
1786
 
        'attribute_set':fields.function(_get_set, type="many2one", relation="magerp.product_attribute_set", method=True, string="Attribute Set"),
1787
 
        'attribute_group_name':fields.char('Group Name', size=100),
1788
 
        'sort_order':fields.integer('Sort Order'),
1789
 
        'default_id':fields.integer('Default'),
1790
 
        'referential_id':fields.many2one('external.referential', 'Magento Instance', readonly=True),
1791
 
        }
1792
 
 
1793
 
class product_tierprice(Model):
1794
 
    _name = "product.tierprice"
1795
 
    _description = "Implements magento tier pricing"
1796
 
 
1797
 
    _columns = {
1798
 
        'web_scope':fields.selection([
1799
 
            ('all', 'All Websites'),
1800
 
            ('specific', 'Specific Website'),
1801
 
        ], 'Scope'),
1802
 
        'website_id':fields.many2one('external.shop.group', 'Website'),
1803
 
        'group_scope':fields.selection([
1804
 
            ('1', 'All groups'),
1805
 
            ('0', 'Specific group')
1806
 
        ]),
1807
 
        'cust_group':fields.many2one('res.partner.category', 'Customer Group'),
1808
 
        'website_price':fields.float('Website Price', digits=(10, 2),),
1809
 
        'price':fields.float('Price', digits=(10, 2),),
1810
 
        'price_qty':fields.float('Quantity Slab', digits=(10, 4), help="Slab & above eg.For 10 and above enter 10"),
1811
 
        'product':fields.many2one('product.product', 'Product'),
1812
 
        'referential_id':fields.many2one('external.referential', 'Magento Instance', readonly=True),
1813
 
        }
1814
 
    _mapping = {
1815
 
        'cust_group':(False, int, """result=self.pool.get('res.partner.category').mage_to_oe(cr,uid,cust_group,instance)\nif result:\n\tresult=[('cust_group',result[0])]\nelse:\n\tresult=[('cust_group',False)]"""),
1816
 
        'all_groups':(False, str, """if all_groups=='1':\n\tresult=[('group_scope','1')]\nelse:\n\tresult=[('group_scope','1')]"""),
1817
 
        'website_price':('website_price', float),
1818
 
        'price':('price', float),
1819
 
        'website_id':(False, int, """result=self.pool.get('external.shop.group').mage_to_oe(cr,uid,website_id,instance)\nif result:\n\tresult=[('website_id',result[0])]\nelse:\n\tresult=[('website_id',False)]"""),
1820
 
        'price_qty':('price_qty', float),
1821
 
        }
1822
 
 
1823
 
class product_product_type(Model):
1824
 
    _name = 'magerp.product_product_type'
1825
 
    _columns = {
1826
 
        'name': fields.char('Name', size=100, required=True, translate=True),
1827
 
        'product_type': fields.char('Type', size=100, required=True, help="Use the same name of Magento product type, for example 'simple'."),
1828
 
        'default_type': fields.selection([('product','Stockable Product'),('consu', 'Consumable'),('service','Service')], 'Default Product Type', required=True, help="Default product's type (Procurement) when a product is imported from Magento."),
1829
 
        }
1830
 
 
1831
 
 
1832
 
class product_mag_osv(MagerpModel):
1833
 
    _register = False # Set to false if the model shouldn't be automatically discovered.
1834
 
 
1835
 
    #remember one thing in life: Magento lies: it tells attributes are required while they are awkward to fill
1836
 
    #and will have a nice default vaule anyway, that's why we avoid making them mandatory in the product view
1837
 
    _magento_fake_mandatory_attrs = ['created_at', 'updated_at', 'has_options', 'required_options', 'model']
1838
 
 
1839
 
    def open_magento_fields(self, cr, uid, ids, context=None):
1840
 
        ir_model_data_obj = self.pool.get('ir.model.data')
1841
 
        ir_model_data_id = ir_model_data_obj.search(cr, uid, [['model', '=', 'ir.ui.view'], ['name', '=', self._name.replace('.','_') + '_wizard_form_view_magerpdynamic']], context=context)
1842
 
        if ir_model_data_id:
1843
 
            res_id = ir_model_data_obj.read(cr, uid, ir_model_data_id, fields=['res_id'])[0]['res_id']
1844
 
        set_id = self.read(cr, uid, ids, fields=['set'], context=context)[0]['set']
1845
 
 
1846
 
        if not set_id:
1847
 
            raise except_osv(_('User Error'), _('Please chose an attribute set before'))
1848
 
 
1849
 
        return {
1850
 
            'name': 'Magento Fields',
1851
 
            'view_type': 'form',
1852
 
            'view_mode': 'form',
1853
 
            'view_id': [res_id],
1854
 
            'res_model': self._name,
1855
 
            'context': "{'set': %s, 'open_from_button_object_id': %s}"%(set_id[0], ids),
1856
 
            'type': 'ir.actions.act_window',
1857
 
            'nodestroy': True,
1858
 
            'target': 'new',
1859
 
            'res_id': ids and ids[0] or False,
1860
 
        }
1861
 
 
1862
 
    def save_and_close_magento_fields(self, cr, uid, ids, context=None):
1863
 
        '''this empty function will save the magento field'''
1864
 
        return {'type': 'ir.actions.act_window_close'}
1865
 
 
1866
 
    def redefine_prod_view(self, cr, uid, field_names, context=None):
1867
 
        """
1868
 
        Rebuild the product view with attribute groups and attributes
1869
 
        """
1870
 
        if context is None: context = {}
1871
 
        attr_set_obj = self.pool.get('magerp.product_attribute_set')
1872
 
        attr_group_obj = self.pool.get('magerp.product_attribute_groups')
1873
 
        attr_obj = self.pool.get('magerp.product_attributes')
1874
 
        translation_obj = self.pool.get('ir.translation')
1875
 
 
1876
 
        attribute_set_id = context['set']
1877
 
        attr_set = attr_set_obj.browse(cr, uid, attribute_set_id)
1878
 
        attr_group_fields_rel = {}
1879
 
 
1880
 
        multiwebsites = context.get('multiwebsite', False)
1881
 
 
1882
 
        fields_get = self.fields_get(cr, uid, field_names, context)
1883
 
 
1884
 
        cr.execute("select attr_id, group_id, attribute_code, frontend_input, "
1885
 
                   "frontend_label, is_required, apply_to, field_name "
1886
 
                   "from magerp_attrset_attr_rel "
1887
 
                   "left join magerp_product_attributes "
1888
 
                   "on magerp_product_attributes.id = attr_id "
1889
 
                   "where magerp_attrset_attr_rel.set_id=%s" %
1890
 
                   attribute_set_id)
1891
 
 
1892
 
        results = cr.dictfetchall()
1893
 
        attribute = results.pop()
1894
 
        while results:
1895
 
            mag_group_id = attribute['group_id']
1896
 
            oerp_group_id = attr_group_obj.extid_to_existing_oeid(
1897
 
                cr, uid, attr_set.referential_id.id, mag_group_id)
1898
 
            # FIXME: workaround in multi-Magento instances (databases)
1899
 
            # where attribute group might not be found due to the way we
1900
 
            # share attributes currently
1901
 
            if not oerp_group_id:
1902
 
                ref_ids = self.pool.get(
1903
 
                    'external.referential').search(cr, uid, [])
1904
 
                for ref_id in ref_ids:
1905
 
                     if ref_id != attr_set.referential_id.id:
1906
 
                         oerp_group_id = attr_group_obj.extid_to_existing_oeid(
1907
 
                             cr, uid, ref_id, mag_group_id)
1908
 
                         if oerp_group_id:
1909
 
                             break
1910
 
 
1911
 
            group_name = attr_group_obj.read(
1912
 
                cr, uid, oerp_group_id,
1913
 
                ['attribute_group_name'],
1914
 
                context=context)['attribute_group_name']
1915
 
 
1916
 
            # Create a page for each attribute group
1917
 
            attr_group_fields_rel.setdefault(group_name, [])
1918
 
            while True:
1919
 
                if attribute['group_id'] != mag_group_id:
1920
 
                    break
1921
 
 
1922
 
                if attribute['field_name'] in field_names:
1923
 
                    if not attribute['attribute_code'] in attr_obj._no_create_list:
1924
 
                        if (group_name in  ['Meta Information',
1925
 
                                            'General',
1926
 
                                            'Custom Layout Update',
1927
 
                                            'Prices',
1928
 
                                            'Design']) or \
1929
 
                           GROUP_CUSTOM_ATTRS_TOGETHER==False:
1930
 
                            attr_group_fields_rel[group_name].append(attribute)
1931
 
                        else:
1932
 
                            attr_group_fields_rel.setdefault(
1933
 
                                'Custom Attributes', []).append(attribute)
1934
 
                if results:
1935
 
                    attribute = results.pop()
1936
 
                else:
1937
 
                    break
1938
 
 
1939
 
        notebook = etree.Element('notebook', colspan="4")
1940
 
 
1941
 
        attribute_groups = attr_group_fields_rel.keys()
1942
 
        attribute_groups.sort()
1943
 
        for group in attribute_groups:
1944
 
            lang = context.get('lang', '')
1945
 
            trans = translation_obj._get_source(
1946
 
                cr, uid, 'product.product', 'view', lang, group)
1947
 
            trans = trans or group
1948
 
            if attr_group_fields_rel.get(group):
1949
 
                page = etree.SubElement(notebook, 'page', string=trans)
1950
 
                for attribute in attr_group_fields_rel.get(group, []):
1951
 
                    if attribute['frontend_input'] == 'textarea':
1952
 
                        etree.SubElement(page, 'newline')
1953
 
                        etree.SubElement(
1954
 
                            page,
1955
 
                            'separator',
1956
 
                            colspan="4",
1957
 
                            string=fields_get[attribute['field_name']]['string'])
1958
 
 
1959
 
                    f = etree.SubElement(
1960
 
                        page, 'field', name=attribute['field_name'])
1961
 
 
1962
 
                    # apply_to is a string like
1963
 
                    # "simple,configurable,virtual,bundle,downloadable"
1964
 
                    req_apply_to = not attribute['apply_to'] or \
1965
 
                        'simple' in attribute['apply_to'] or \
1966
 
                        'configurable' in attribute['apply_to']
1967
 
                    if attribute['is_required'] and \
1968
 
                       req_apply_to and \
1969
 
                        attribute['attribute_code'] not in self._magento_fake_mandatory_attrs:
1970
 
                        f.set('attrs', "{'required': [('magento_exportable', '=', True)]}")
1971
 
 
1972
 
                    if attribute['frontend_input'] == 'textarea':
1973
 
                        f.set('nolabel', "1")
1974
 
                        f.set('colspan', "4")
1975
 
 
1976
 
                    setup_modifiers(f, fields_get[attribute['field_name']],
1977
 
                                    context=context)
1978
 
 
1979
 
        if multiwebsites:
1980
 
            website_page = etree.SubElement(
1981
 
                notebook, 'page', string=_('Websites'))
1982
 
            wf = etree.SubElement(
1983
 
                website_page, 'field', name='websites_ids', nolabel="1")
1984
 
            setup_modifiers(wf, fields_get['websites_ids'], context=context)
1985
 
 
1986
 
        return notebook
1987
 
 
1988
 
    def _filter_fields_to_return(self, cr, uid, field_names, context=None):
1989
 
        '''This function is a hook in order to filter the fields that appears on the view'''
1990
 
        return field_names
1991
 
 
1992
 
    def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1993
 
        if context is None:
1994
 
            context = {}
1995
 
        result = super(product_mag_osv, self).fields_view_get(
1996
 
            cr, uid, view_id,view_type,context,toolbar=toolbar)
1997
 
        if view_type == 'form':
1998
 
            eview = etree.fromstring(result['arch'])
1999
 
            btn = eview.xpath("//button[@name='open_magento_fields']")
2000
 
            if btn:
2001
 
                btn = btn[0]
2002
 
            page_placeholder = eview.xpath(
2003
 
                "//page[@string='attributes_placeholder']")
2004
 
 
2005
 
            attrs_mag_notebook = "{'invisible': [('set', '=', False)]}"
2006
 
 
2007
 
            if context.get('set'):
2008
 
                fields_obj = self.pool.get('ir.model.fields')
2009
 
                models = ['product.template']
2010
 
                if self._name == 'product.product':
2011
 
                    models.append('product.product')
2012
 
 
2013
 
                model_ids = self.pool.get('ir.model').search(
2014
 
                    cr, uid, [('model', 'in', models)], context=context)
2015
 
                field_ids = fields_obj.search(
2016
 
                    cr, uid,
2017
 
                    [('model_id', 'in', model_ids)],
2018
 
                    context=context)
2019
 
                #TODO it will be better to avoid adding fields here
2020
 
                #Moreover we should also add the field mag_manage_stock
2021
 
                field_names = ['product_type']
2022
 
                fields = fields_obj.browse(cr, uid, field_ids, context=context)
2023
 
                for field in fields:
2024
 
                    if field.name.startswith('x_'):
2025
 
                        field_names.append(field.name)
2026
 
                website_ids = self.pool.get('external.shop.group').search(
2027
 
                    cr, uid,
2028
 
                    [('referential_type', '=ilike', 'mag%')],
2029
 
                    context=context)
2030
 
                if len(website_ids) > 1:
2031
 
                    context['multiwebsite'] = True
2032
 
                    field_names.append('websites_ids')
2033
 
 
2034
 
                field_names = self._filter_fields_to_return(
2035
 
                    cr, uid, field_names, context)
2036
 
                result['fields'].update(
2037
 
                    self.fields_get(cr, uid, field_names, context))
2038
 
 
2039
 
                attributes_notebook = self.redefine_prod_view(
2040
 
                                    cr, uid, field_names, context)
2041
 
 
2042
 
                # if the placeholder is a "page", that means we are
2043
 
                # in the product main form. If it is a "separator", it
2044
 
                # means we are in the attributes popup
2045
 
                if page_placeholder:
2046
 
                    placeholder = page_placeholder[0]
2047
 
                    magento_page = etree.Element(
2048
 
                        'page',
2049
 
                        string=_('Magento Information'),
2050
 
                        attrs=attrs_mag_notebook)
2051
 
                    setup_modifiers(magento_page, context=context)
2052
 
                    f = etree.SubElement(
2053
 
                        magento_page,
2054
 
                        'field',
2055
 
                        name='product_type',
2056
 
                        attrs="{'required': [('magento_exportable', '=', True)]}")
2057
 
                    setup_modifiers(f, field=result['fields']['product_type'], context=context)
2058
 
                    magento_page.append(attributes_notebook)
2059
 
                    btn.getparent().remove(btn)
2060
 
                else:
2061
 
                    placeholder = eview.xpath(
2062
 
                        "//separator[@string='attributes_placeholder']")[0]
2063
 
                    magento_page = attributes_notebook
2064
 
 
2065
 
                placeholder.getparent().replace(placeholder, magento_page)
2066
 
            elif btn != []:
2067
 
                new_btn = etree.Element(
2068
 
                    'button',
2069
 
                    name='open_magento_fields',
2070
 
                    string=_('Open Magento Fields'),
2071
 
                    icon='gtk-go-forward',
2072
 
                    type='object',
2073
 
                    colspan='2',
2074
 
                    attrs=attrs_mag_notebook)
2075
 
                setup_modifiers(new_btn, context=context)
2076
 
                btn.getparent().replace(btn, new_btn)
2077
 
                if page_placeholder:
2078
 
                    placeholder = page_placeholder[0]
2079
 
                    placeholder.getparent().remove(placeholder)
2080
 
 
2081
 
            result['arch'] = etree.tostring(eview, pretty_print=True)
2082
 
            #TODO understand (and fix) why the orm fill the field size for the text field :S
2083
 
            for field in result['fields']:
2084
 
                if result['fields'][field]['type'] == 'text':
2085
 
                    if 'size' in result['fields'][field]: del result['fields'][field]['size']
2086
 
        return result
2087
 
 
2088
 
class product_template(product_mag_osv):
2089
 
    _inherit = "product.template"
2090
 
    _columns = {
2091
 
        'magerp_tmpl' : fields.serialized('Magento Template Fields'),
2092
 
        'set':fields.many2one('magerp.product_attribute_set', 'Attribute Set'),
2093
 
        'websites_ids': fields.many2many('external.shop.group', 'magerp_product_shop_group_rel', 'product_id', 'shop_group_id', 'Websites', help='By defaut product will be exported on every website, if you want to export it only on some website select them here'),
2094
 
        'mag_manage_stock': fields.selection([
2095
 
                                ('use_default','Use Default Config'),
2096
 
                                ('no', 'Do Not Manage Stock'),
2097
 
                                ('yes','Manage Stock')],
2098
 
                                'Manage Stock Level'),
2099
 
        'mag_manage_stock_shortage': fields.selection([
2100
 
                                ('use_default','Use Default Config'),
2101
 
                                ('no', 'No Sell'),
2102
 
                                ('yes','Sell qty < 0'),
2103
 
                                ('yes-and-notification','Sell qty < 0 and Use Customer Notification')],
2104
 
                                'Manage Inventory Shortage'),
2105
 
        }
2106
 
 
2107
 
    _defaults = {
2108
 
        'mag_manage_stock': 'use_default',
2109
 
        'mag_manage_stock_shortage': 'use_default',
2110
 
        }
2111
 
 
2112
 
 
2113
 
class product_product(orm.Model):
2114
 
    _inherit = 'product.product'
2115
 
 
2116
 
    def write(self, cr, uid, ids, vals, context=None):
2117
 
        if vals.get('referential_id', False):
2118
 
            instance = vals['referential_id']
2119
 
            #Filter the keys to be changes
2120
 
            if ids:
2121
 
                if type(ids) == list and len(ids) == 1:
2122
 
                    ids = ids[0]
2123
 
                elif type(ids) == int or type(ids) == long:
2124
 
                    ids = ids
2125
 
                else:
2126
 
                    return False
2127
 
            tier_price = False
2128
 
            if 'x_magerp_tier_price' in vals.keys():
2129
 
                tier_price = vals.pop('x_magerp_tier_price')
2130
 
            tp_obj = self.pool.get('product.tierprice')
2131
 
            #Delete existing tier prices
2132
 
            tier_price_ids = tp_obj.search(cr, uid, [('product', '=', ids)])
2133
 
            if tier_price_ids:
2134
 
                tp_obj.unlink(cr, uid, tier_price_ids)
2135
 
            #Save the tier price
2136
 
            if tier_price:
2137
 
                self.create_tier_price(cr, uid, tier_price, instance, ids)
2138
 
        stat = super(product_product, self).write(cr, uid, ids, vals, context)
2139
 
        #Perform other operation
2140
 
        return stat
2141
 
 
2142
 
    def create_tier_price(self, cr, uid, tier_price, instance, product_id):
2143
 
        tp_obj = self.pool.get('product.tierprice')
2144
 
        for each in eval(tier_price):
2145
 
            tier_vals = {}
2146
 
            cust_group = self.pool.get('res.partner.category').mage_to_oe(cr, uid, int(each['cust_group']), instance)
2147
 
            if cust_group:
2148
 
                tier_vals['cust_group'] = cust_group[0]
2149
 
            else:
2150
 
                tier_vals['cust_group'] = False
2151
 
            tier_vals['website_price'] = float(each['website_price'])
2152
 
            tier_vals['price'] = float(each['price'])
2153
 
            tier_vals['price_qty'] = float(each['price_qty'])
2154
 
            tier_vals['product'] = product_id
2155
 
            tier_vals['referential_id'] = instance
2156
 
            tier_vals['group_scope'] = each['all_groups']
2157
 
            if each['website_id'] == '0':
2158
 
                tier_vals['web_scope'] = 'all'
2159
 
            else:
2160
 
                tier_vals['web_scope'] = 'specific'
2161
 
                tier_vals['website_id'] = self.pool.get('external.shop.group').mage_to_oe(cr, uid, int(each['website_id']), instance)
2162
 
            tp_obj.create(cr, uid, tier_vals)
2163
 
 
2164
 
    def create(self, cr, uid, vals, context=None):
2165
 
        tier_price = False
2166
 
        if vals.get('referential_id', False):
2167
 
            instance = vals['referential_id']
2168
 
            #Filter keys to be changed
2169
 
            if 'x_magerp_tier_price' in vals.keys():
2170
 
                tier_price = vals.pop('x_magerp_tier_price')
2171
 
 
2172
 
        crid = super(product_product, self).create(cr, uid, vals, context)
2173
 
        #Save the tier price
2174
 
        if tier_price:
2175
 
            self.create_tier_price(cr, uid, tier_price, instance, crid)
2176
 
        #Perform other operations
2177
 
        return crid
2178
 
 
2179
 
    def copy(self, cr, uid, id, default=None, context=None):
2180
 
        if default is None:
2181
 
            default = {}
2182
 
 
2183
 
        default['magento_exportable'] = False
2184
 
 
2185
 
        return super(product_product, self).copy(cr, uid, id, default=default, context=context)
2186
 
 
2187
 
    def unlink(self, cr, uid, ids, context=None):
2188
 
        #if product is mapped to magento, not delete it
2189
 
        not_delete = False
2190
 
        sale_obj = self.pool.get('sale.shop')
2191
 
        search_params = [
2192
 
            ('magento_shop', '=', True),
2193
 
        ]
2194
 
        shops_ids = sale_obj.search(cr, uid, search_params)
2195
 
 
2196
 
        for shop in sale_obj.browse(cr, uid, shops_ids, context):
2197
 
            if shop.referential_id and shop.referential_id.type_id.name == 'Magento':
2198
 
                for product_id in ids:
2199
 
                    mgn_product = self.get_extid(cr, uid, product_id, shop.referential_id.id)
2200
 
                    if mgn_product:
2201
 
                        not_delete = True
2202
 
                        break
2203
 
        if not_delete:
2204
 
            if len(ids) > 1:
2205
 
                raise except_osv(_('Warning!'),
2206
 
                                 _('They are some products related to Magento. '
2207
 
                                   'They can not be deleted!\n'
2208
 
                                   'You can change their Magento status to "Disabled" '
2209
 
                                   'and uncheck the active box to hide them from OpenERP.'))
2210
 
            else:
2211
 
                raise except_osv(_('Warning!'),
2212
 
                                 _('This product is related to Magento. '
2213
 
                                   'It can not be deleted!\n'
2214
 
                                   'You can change it Magento status to "Disabled" '
2215
 
                                   'and uncheck the active box to hide it from OpenERP.'))
2216
 
        else:
2217
 
            return super(product_product, self).unlink(cr, uid, ids, context)
2218
 
 
2219
 
    def _prepare_inventory_magento_vals(self, cr, uid, product, stock, shop,
2220
 
                                        context=None):
2221
 
        """
2222
 
        Prepare the values to send to Magento (message product_stock.update).
2223
 
        Can be inherited to customize the values to send.
2224
 
 
2225
 
        :param browse_record product: browseable product
2226
 
        :param browse_record stock: browseable stock location
2227
 
        :param browse_record shop: browseable shop
2228
 
        :return: a dict of values which will be sent to Magento with a call to:
2229
 
        product_stock.update
2230
 
        """
2231
 
        map_shortage = {
2232
 
            "use_default": 0,
2233
 
            "no": 0,
2234
 
            "yes": 1,
2235
 
            "yes-and-notification": 2,
2236
 
        }
2237
 
 
2238
 
        stock_field = (shop.product_stock_field_id and
2239
 
                       shop.product_stock_field_id.name or
2240
 
                       'virtual_available')
2241
 
        stock_quantity = product[stock_field]
2242
 
 
2243
 
        return {'qty': stock_quantity,
2244
 
                'manage_stock': int(product.mag_manage_stock == 'yes'),
2245
 
                'use_config_manage_stock': int(product.mag_manage_stock == 'use_default'),
2246
 
                'backorders': map_shortage[product.mag_manage_stock_shortage],
2247
 
                'use_config_backorders':int(product.mag_manage_stock_shortage == 'use_default'),
2248
 
                # put the stock availability to "out of stock"
2249
 
                'is_in_stock': int(stock_quantity > 0)}
2250
 
 
2251
 
    def export_inventory(self, cr, uid, external_session, ids, context=None):
2252
 
        """
2253
 
        Export to Magento the stock quantity for the products in ids which
2254
 
        are already exported on Magento and are not service products.
2255
 
 
2256
 
        :param int shop_id: id of the shop where the stock inventory has
2257
 
        to be exported
2258
 
        :param Connection connection: connection object
2259
 
        :return: True
2260
 
        """
2261
 
        #TODO get also the list of product which the option mag_manage_stock have changed
2262
 
        #This can be base on the group_fields that can try tle last write date of a group of fields
2263
 
        if context is None: context = {}
2264
 
 
2265
 
        # use the stock location defined on the sale shop
2266
 
        # to compute the stock value
2267
 
        stock = external_session.sync_from_object.warehouse_id.lot_stock_id
2268
 
        location_ctx = context.copy()
2269
 
        location_ctx['location'] = stock.id
2270
 
        for product_id in ids:
2271
 
            self._export_inventory(cr, uid, external_session, product_id, context=location_ctx)
2272
 
 
2273
 
        return True
2274
 
 
2275
 
    @catch_error_in_report
2276
 
    def _export_inventory(self, cr, uid, external_session, product_id, context=None):
2277
 
        product = self.browse(cr, uid, product_id, context=context)
2278
 
        stock = external_session.sync_from_object.warehouse_id.lot_stock_id
2279
 
        mag_product_id = self.get_extid(
2280
 
            cr, uid, product.id, external_session.referential_id.id, context=context)
2281
 
        if not mag_product_id:
2282
 
            return False  # skip products which are not exported
2283
 
        inventory_vals = self._prepare_inventory_magento_vals(
2284
 
            cr, uid, product, stock, external_session.sync_from_object, context=context)
2285
 
 
2286
 
        external_session.connection.call('oerp_cataloginventory_stock_item.update',
2287
 
                        [mag_product_id, inventory_vals])
2288
 
 
2289
 
        external_session.logger.info(
2290
 
            "Successfully updated stock level at %s for "
2291
 
            "product with code %s " %
2292
 
            (inventory_vals['qty'], product.default_code))
2293
 
        return True
2294
 
 
2295
 
    #TODO change the magento api to be able to change the link direct from the function
2296
 
    # ol_catalog_product.update
2297
 
    def ext_update_link_data(self, cr, uid, external_session, resources, mapping=None, mapping_id=None, context=None):
2298
 
        for resource_id, resource in resources.items():
2299
 
            for type_selection in self.pool.get('product.link').get_link_type_selection(cr, uid, context):
2300
 
                link_type = type_selection[0]
2301
 
                position = {}
2302
 
                linked_product_ids = []
2303
 
                for link in resource[context['main_lang']].get('product_link', []):
2304
 
                    if link['type'] == link_type:
2305
 
                        if link['is_active']:
2306
 
                            linked_product_ids.append(link['link_product_id'])
2307
 
                            position[link['link_product_id']] = link['position']
2308
 
                self.ext_product_assign(cr, uid, external_session, link_type, resource[context['main_lang']]['ext_id'],
2309
 
                                            linked_product_ids, position=position, context=context)
2310
 
        return True
2311
 
 
2312
 
    def ext_product_assign(self, cr, uid, external_session, link_type, ext_parent_id, ext_child_ids,
2313
 
                                                    quantities=None, position=None, context=None):
2314
 
        context = context or {}
2315
 
        position = position or {}
2316
 
        quantities = quantities or {}
2317
 
 
2318
 
 
2319
 
        #Patch for magento api prototype
2320
 
        #for now the method for goodies is freeproduct
2321
 
        #It will be renammed soon and so this patch will be remove too
2322
 
        if link_type == 'goodies': link_type= 'freeproduct'
2323
 
        #END PATCH
2324
 
 
2325
 
        magento_args = [link_type, ext_parent_id]
2326
 
        # magento existing children ids
2327
 
        child_list = external_session.connection.call('product_link.list', magento_args)
2328
 
        old_child_ext_ids = [x['product_id'] for x in child_list]
2329
 
 
2330
 
        ext_id_to_remove = []
2331
 
        ext_id_to_assign = []
2332
 
        ext_id_to_update = []
2333
 
 
2334
 
        # compute the diff between openerp and magento
2335
 
        for c_ext_id in old_child_ext_ids:
2336
 
            if c_ext_id not in ext_child_ids:
2337
 
                ext_id_to_remove.append(c_ext_id)
2338
 
        for c_ext_id in ext_child_ids:
2339
 
            if c_ext_id in old_child_ext_ids:
2340
 
                ext_id_to_update.append(c_ext_id)
2341
 
            else:
2342
 
                ext_id_to_assign.append(c_ext_id)
2343
 
 
2344
 
        # calls to magento to delete, create or update the links
2345
 
        for c_ext_id in ext_id_to_remove:
2346
 
             # remove the product links that are no more setup on openerp
2347
 
            external_session.connection.call('product_link.remove', magento_args + [c_ext_id])
2348
 
            external_session.logger.info(("Successfully removed assignment of type %s for"
2349
 
                                 "product %s to product %s") % (link_type, ext_parent_id, c_ext_id))
2350
 
        for c_ext_id in ext_id_to_assign:
2351
 
            # assign new product links
2352
 
            external_session.connection.call('product_link.assign',
2353
 
                      magento_args +
2354
 
                      [c_ext_id,
2355
 
                          {'position': position.get(c_ext_id, 0),
2356
 
                           'qty': quantities.get(c_ext_id, 1)}])
2357
 
            external_session.logger.info(("Successfully assigned product %s to product %s"
2358
 
                                            "with type %s") %(link_type, ext_parent_id, c_ext_id))
2359
 
        for child_ext_id in ext_id_to_update:
2360
 
            # update products links already assigned
2361
 
            external_session.connection.call('product_link.update',
2362
 
                      magento_args +
2363
 
                      [c_ext_id,
2364
 
                          {'position': position.get(c_ext_id, 0),
2365
 
                           'qty': quantities.get(c_ext_id, 1)}])
2366
 
            external_session.logger.info(("Successfully updated assignment of type %s of"
2367
 
                                 "product %s to product %s") %(link_type, ext_parent_id, c_ext_id))
2368
 
        return True
2369
 
 
2370
 
    #TODO move this code (get exportable image) and also some code in product_image.py and sale.py in base_sale_multichannel or in a new module in order to be more generic
2371
 
    def get_exportable_images(self, cr, uid, external_session, ids, context=None):
2372
 
        shop = external_session.sync_from_object
2373
 
        image_obj = self.pool.get('product.images')
2374
 
        images_exportable_ids = image_obj.search(cr, uid, [('product_id', 'in', ids)], context=context)
2375
 
        images_to_update_ids = image_obj.get_all_oeid_from_referential(cr, uid, external_session.referential_id.id, context=None)
2376
 
        images_to_create = [x for x in images_exportable_ids if not x in images_to_update_ids]
2377
 
        if shop.last_images_export_date:
2378
 
            images_to_update_ids = image_obj.search(cr, uid, [('id', 'in', images_to_update_ids), '|', ('create_date', '>', shop.last_images_export_date), ('write_date', '>', shop.last_images_export_date)], context=context)
2379
 
        return {'to_create' : images_to_create, 'to_update' : images_to_update_ids}
2380
 
 
2381
 
    def _mag_import_product_links_type(self, cr, uid, product, link_type, external_session, context=None):
2382
 
        if context is None: context = {}
2383
 
        conn = external_session.connection
2384
 
        product_link_obj = self.pool.get('product.link')
2385
 
        selection_link_types = product_link_obj.get_link_type_selection(cr, uid, context)
2386
 
        mag_product_id = self.get_extid(
2387
 
            cr, uid, product.id, external_session.referential_id.id, context=context)
2388
 
        # This method could be completed to import grouped products too, you know, for Magento a product link is as
2389
 
        # well a cross-sell, up-sell, related than the assignment between grouped products
2390
 
        if link_type in [ltype[0] for ltype in selection_link_types]:
2391
 
            product_links = []
2392
 
            try:
2393
 
                product_links = conn.call('product_link.list', [link_type, mag_product_id])
2394
 
            except Exception, e:
2395
 
                self.log(cr, uid, product.id, "Error when retrieving the list of links in Magento for product with reference %s and product id %s !" % (product.default_code, product.id,))
2396
 
                conn.logger.debug("Error when retrieving the list of links in Magento for product with reference %s and product id %s !" % (product.magento_sku, product.id,))
2397
 
 
2398
 
            for product_link in product_links:
2399
 
                linked_product_id = self.get_or_create_oeid(
2400
 
                    cr, uid,
2401
 
                    external_session,
2402
 
                    product_link['product_id'],
2403
 
                    context=context)
2404
 
                link_data = {
2405
 
                    'product_id': product.id,
2406
 
                    'type': link_type,
2407
 
                    'linked_product_id': linked_product_id,
2408
 
                    'sequence': product_link['position'],
2409
 
                }
2410
 
 
2411
 
                existing_link = product_link_obj.search(cr, uid,
2412
 
                    [('product_id', '=', link_data['product_id']),
2413
 
                     ('type', '=', link_data['type']),
2414
 
                     ('linked_product_id', '=', link_data['linked_product_id'])
2415
 
                    ], context=context)
2416
 
                if existing_link:
2417
 
                    product_link_obj.write(cr, uid, existing_link, link_data, context=context)
2418
 
                else:
2419
 
                    product_link_obj.create(cr, uid, link_data, context=context)
2420
 
                conn.logger.info("Successfully imported product link of type %s on product %s to product %s" %(link_type, product.id, linked_product_id))
2421
 
        return True
2422
 
 
2423
 
    def mag_import_product_links_types(self, cr, uid, ids, link_types, external_session, context=None):
2424
 
        if isinstance(ids, (int, long)): ids = [ids]
2425
 
        for product in self.browse(cr, uid, ids, context=context):
2426
 
            for link_type in link_types:
2427
 
                self._mag_import_product_links_type(cr, uid, product, link_type, external_session, context=context)
2428
 
        return True
2429
 
 
2430
 
    def mag_import_product_links(self, cr, uid, ids, external_session, context=None):
2431
 
        link_types = self.pool.get('external.referential').get_magento_product_link_types(cr, uid, external_session.referential_id.id, external_session.connection, context=context)
2432
 
        local_cr = pooler.get_db(cr.dbname).cursor()
2433
 
        try:
2434
 
            for product_id in ids:
2435
 
                self.mag_import_product_links_types(local_cr, uid, [product_id], link_types, external_session, context=context)
2436
 
                local_cr.commit()
2437
 
        finally:
2438
 
            local_cr.close()
2439
 
        return True
2440
 
 
2441
 
 
2442
 
# transfered from product.py ###############################
2443
 
 
2444
 
 
2445
 
class product_images(MagerpModel):
2446
 
    _inherit = "product.images"
2447
 
    _columns = {
2448
 
        'base_image':fields.boolean('Base Image'),
2449
 
        'small_image':fields.boolean('Small Image'),
2450
 
        'thumbnail':fields.boolean('Thumbnail'),
2451
 
        'exclude':fields.boolean('Exclude'),
2452
 
        'position':fields.integer('Position'),
2453
 
        'sync_status':fields.boolean('Sync Status', readonly=True),
2454
 
        'create_date': fields.datetime('Created date', readonly=True),
2455
 
        'write_date': fields.datetime('Updated date', readonly=True),
2456
 
        }
2457
 
    _defaults = {
2458
 
        'sync_status':lambda * a: False,
2459
 
        'base_image':lambda * a:True,
2460
 
        'small_image':lambda * a:True,
2461
 
        'thumbnail':lambda * a:True,
2462
 
        'exclude':lambda * a:False
2463
 
        }
2464
 
 
2465
 
    def get_changed_ids(self, cr, uid, start_date=False):
2466
 
        proxy = self.pool.get('product.images')
2467
 
        domain = start_date and ['|', ('create_date', '>', start_date), ('write_date', '>', start_date)] or []
2468
 
        return proxy.search(cr, uid, domain)
2469
 
 
2470
 
    def del_image_name(self, cr, uid, id, context=None):
2471
 
        if context is None: context = {}
2472
 
        image_ext_name_obj = self.pool.get('product.images.external.name')
2473
 
        name_id = image_ext_name_obj.search(cr, uid, [('image_id', '=', id), ('external_referential_id', '=', context['referential_id'])], context=context)
2474
 
        if name_id:
2475
 
            return image_ext_name_obj.unlink(cr, uid, name_id, context=context)
2476
 
        return False
2477
 
 
2478
 
 
2479
 
 
2480
 
    @only_for_referential(ref_categ ='Multichannel Sale')
2481
 
    def _get_last_exported_date(self, cr, uid, external_session, context=None):
2482
 
        shop = external_session.sync_from_object
2483
 
        return shop.last_images_export_date
2484
 
 
2485
 
    @only_for_referential(ref_categ ='Multichannel Sale')
2486
 
    @commit_now
2487
 
    def _set_last_exported_date(self, cr, uid, external_session, date, context=None):
2488
 
        shop = external_session.sync_from_object
2489
 
        return self.pool.get('sale.shop').write(cr, uid, shop.id, {'last_images_export_date': date}, context=context)
2490
 
 
2491
 
 
2492
 
 
2493
 
    def update_remote_images(self, cr, uid, external_session, ids, context=None):
2494
 
        if context is None:
2495
 
            context = {}
2496
 
 
2497
 
        ir_model_data_obj = self.pool.get('ir.model.data')
2498
 
 
2499
 
        def detect_types(image):
2500
 
            types = []
2501
 
            if image.small_image:
2502
 
                types.append('small_image')
2503
 
            if image.base_image:
2504
 
                types.append('image')
2505
 
            if image.thumbnail:
2506
 
                types.append('thumbnail')
2507
 
            return types
2508
 
 
2509
 
        #TODO update the image file
2510
 
        def update_image(product_extid, image_name, image):
2511
 
            result = external_session.connection.call('catalog_product_attribute_media.update',
2512
 
                               [product_extid,
2513
 
                                image_name,
2514
 
                                {'label':image.name,
2515
 
                                 'exclude':image.exclude,
2516
 
                                 'types':detect_types(image),
2517
 
                                }
2518
 
                               ])
2519
 
            return result
2520
 
        list_image = []
2521
 
        list_image = self.read(cr, uid, ids, ['write_date', 'create_date'], context=context)
2522
 
 
2523
 
        date_2_image={}
2524
 
        image_2_date={}
2525
 
        for image in list_image:
2526
 
            if date_2_image.get(image['write_date'] or image['create_date'], False):
2527
 
                done = False
2528
 
                count = 0
2529
 
                while not done:
2530
 
                    count += 1
2531
 
                    if not date_2_image.get((image['write_date'] or image['create_date']) + '-' + str(count), False):
2532
 
                        date_2_image[(image['write_date'] or image['create_date']) + '-' + str(count)] = image['id']
2533
 
                        done = True
2534
 
            else:
2535
 
                date_2_image[image['write_date'] or image['create_date']] = image['id']
2536
 
            image_2_date[image['id']] = image['write_date'] or image['create_date']
2537
 
        list_date = date_2_image.keys()
2538
 
        list_date.sort()
2539
 
 
2540
 
        ids = [date_2_image[date] for date in list_date]
2541
 
 
2542
 
        while ids:
2543
 
            product_images = self.browse_w_order(cr, uid, ids[:1000], context=context)
2544
 
            for each in product_images:
2545
 
                product_extid = each.product_id.get_extid(external_session.referential_id.id)
2546
 
                if not product_extid:
2547
 
                    external_session.logger.info("The product %s do not exist on magento" %(each.product_id.default_code))
2548
 
                else:
2549
 
                    need_to_be_created = True
2550
 
                    ext_file_name = each.get_extid(external_session.referential_id.id)
2551
 
                    if ext_file_name: #If update
2552
 
                        try:
2553
 
                            external_session.logger.info("Updating %s's image: %s" %(each.product_id.default_code, each.name))
2554
 
                            result = update_image(product_extid, ext_file_name, each)
2555
 
                            external_session.logger.info("%s's image updated with sucess: %s" %(each.product_id.default_code, each.name))
2556
 
                            need_to_be_created = False
2557
 
                        except Exception, e:
2558
 
                            external_session.logger.error(_("Error in connecting:%s") % (e))
2559
 
                            if not "Fault 103" in str(e):
2560
 
                                external_session.logger.error(_("Unknow error stop export"))
2561
 
                                raise
2562
 
                            else:
2563
 
                                #If the image was deleded in magento, the external name is automatically deleded before trying to re-create the image in magento
2564
 
                                model_data_ids = ir_model_data_obj.search(cr, uid, [('model', '=', self._name), ('res_id', '=', each.id), ('referential_id', '=', external_session.referential_id.id)])
2565
 
                                if model_data_ids and len(model_data_ids) > 0:
2566
 
                                    ir_model_data_obj.unlink(cr, uid, model_data_ids, context=context)
2567
 
                                external_session.logger.error(_("The image don't exist in magento, try to create it"))
2568
 
                    if need_to_be_created:
2569
 
                        if each.product_id.default_code:
2570
 
                            pas_ok = True
2571
 
                            suceed = False
2572
 
                            external_session.logger.info("Sending %s's image: %s" %(each.product_id.default_code, each.name))
2573
 
                            data = {
2574
 
                                'file':{
2575
 
                                    'name':each.name,
2576
 
                                    'content': each.file,
2577
 
                                    'mime': each.link and each.url and mimetypes.guess_type(each.url)[0] \
2578
 
                                            or each.extention and mimetypes.guess_type(each.name + each.extention)[0] \
2579
 
                                            or 'image/jpeg',
2580
 
                                    }
2581
 
                            }
2582
 
                            result = external_session.connection.call('catalog_product_attribute_media.create', [product_extid, data, False, 'id'])
2583
 
 
2584
 
                            self.create_external_id_vals(cr, uid, each.id, result, external_session.referential_id.id, context=context)
2585
 
                            result = update_image(product_extid, result, each)
2586
 
                            external_session.logger.info("%s's image send with sucess: %s" %(each.product_id.default_code, each.name))
2587
 
 
2588
 
 
2589
 
                if context.get('last_images_export_date') and image_2_date[each.id] > context['last_images_export_date']: #indeed if a product was created a long time ago and checked as exportable recently, the write date of the image can be far away in the past
2590
 
                    self._set_last_exported_date(cr, uid, external_session, image_2_date[each.id], context=context)
2591
 
                cr.commit()
2592
 
            ids = ids[1000:]
2593
 
            external_session.logger.info("still %s image to export" %len(ids))
2594
 
        return True