diff --git a/magentoerpconnect/product.py b/magentoerpconnect/product.py
index a31f2e182..d05fca297 100644
--- a/magentoerpconnect/product.py
+++ b/magentoerpconnect/product.py
@@ -19,7 +19,6 @@
# along with this program. If not, see
.
#
##############################################################################
-
import logging
import urllib2
import base64
@@ -27,6 +26,7 @@
import sys
from collections import defaultdict
from openerp import models, fields, api, _
+from openerp.addons.connector.connector import ConnectorUnit
from openerp.addons.connector.queue.job import job, related_action
from openerp.addons.connector.event import on_record_write
from openerp.addons.connector.unit.synchronizer import (Importer,
@@ -44,6 +44,7 @@
)
from .unit.mapper import normalize_datetime
from .unit.import_synchronizer import (DelayedBatchImporter,
+ DirectBatchImporter,
MagentoImporter,
TranslationImporter,
AddCheckpoint,
@@ -60,6 +61,104 @@ def chunks(items, length):
yield items[index:index + length]
+class MagentoTaxClass(models.Model):
+ _name = 'magento.tax.class'
+ _inherit = 'magento.binding'
+ _description = 'Magento Tax Class'
+
+ name = fields.Char()
+
+
+class MagentoAttributeSet(models.Model):
+ _name = 'magento.attribute.set'
+ _inherit = 'magento.binding'
+ _description = 'Magento Attribute Set'
+
+ name = fields.Char()
+
+
+@magento
+class AttributeSetAdapter(GenericAdapter):
+ _model_name = 'magento.attribute.set'
+ _magento_model = 'product_attribute_set'
+
+ def create(self, name, skeleton):
+ return self._call('%s.create' % self._magento_model,
+ [name, skeleton])
+
+ def list(self):
+ """ Search records according to some criteria
+ and returns a list of ids
+
+ :rtype: list
+ """
+ return self._call('%s.list' % self._magento_model, [])
+
+ def read(self, id, attributes=None):
+ """ Returns the information of a record
+
+ :rtype: dict
+ """
+ results = self.list()
+ res = [result for result in results if result['set_id'] == id]
+ if res:
+ return res[0]
+ return {}
+
+
+@magento
+class AttributeSetBatchImporter(DirectBatchImporter):
+ """ Import the records directly, without delaying the jobs.
+
+ Import the Attribute Set
+
+ They are imported directly because this is a rare and fast operation,
+ and we don't really bother if it blocks the UI during this time.
+ (that's also a mean to rapidly check the connectivity with Magento).
+ """
+ _model_name = [
+ 'magento.attribute.set'
+ ]
+
+ def run(self, filters=None):
+ """ Run the synchronization """
+ records = self.backend_adapter.list()
+ for record in records:
+ importer = self.unit_for(MagentoImporter)
+ importer.run(record['set_id'], record=record)
+
+
+@magento
+class AttributeSetMapper(ImportMapper):
+ _model_name = 'magento.attribute.set'
+
+ direct = [('name', 'name')]
+
+ @mapping
+ def backend_id(self, record):
+ return {'backend_id': self.backend_record.id}
+
+
+@magento
+class AttributeSetImporter(MagentoImporter):
+ _model_name = ['magento.attribute.set']
+
+ def run(self, magento_id, force=False, record=None):
+ """ Run the synchronization
+
+ :param magento_id: identifier of the record on Magento
+ """
+ if record:
+ self.magento_record = record
+ return super(AttributeSetImporter, self).run(magento_id, force=force)
+
+ def _get_magento_data(self):
+ if self.magento_record:
+ return self.magento_record
+ else:
+ return super(AttributeSetImporter, self)._get_magento_data()
+
+
class MagentoProductProduct(models.Model):
_name = 'magento.product.product'
_inherit = 'magento.binding'
@@ -78,6 +177,22 @@ def product_type_get(self):
# ('downloadable', 'Downloadable Product'),
]
+ @api.model
+ def get_default_magento_tax(self):
+ mag_tax_obj = self.env['magento.tax.class']
+ tax_id = False
+ if self.backend_id:
+ tax_id = mag_tax_obj.search(
+ [('backend_id', '=', self.backend_id.id)])[0]
+ else:
+ tax_id = mag_tax_obj.search([])[0]
+ return tax_id
+
+ tax_class_id = fields.Many2one(comodel_name='magento.tax.class',
+ string='Tax class',
+ required=True,
+ ondelete='restrict',
+ default=get_default_magento_tax)
openerp_id = fields.Many2one(comodel_name='product.product',
string='Product',
required=True,
@@ -119,6 +234,16 @@ def product_type_get(self):
help="Check this to exclude the product "
"from stock synchronizations.",
)
+ visibility = fields.Selection(
+ selection=[('1', 'Not Visible Individually'),
+ ('2', 'Catalog'),
+ ('3', 'Search'),
+ ('4', 'Catalog, Search'),
+ ],
+ string='Visibility',
+ default='4',
+ required=True,
+ )
RECOMPUTE_QTY_STEP = 1000 # products at a time
@@ -199,6 +324,13 @@ class ProductProduct(models.Model):
)
+class ProductTemplate(models.Model):
+ _inherit = 'product.template'
+
+ attribute_set_id = fields.Many2one('magento.attribute.set',
+ string='Attribute Set')
+
+
@magento
class ProductProductAdapter(GenericAdapter):
_model_name = 'magento.product.product'
@@ -236,6 +368,11 @@ def search(self, filters=None, from_date=None, to_date=None):
in self._call('%s.list' % self._magento_model,
[filters] if filters else [{}])]
+ def create(self, product_type, attr_set_id, sku, data):
+ # Only ol_catalog_product.create works for export configurable product
+ return self._call('ol_catalog_product.create',
+ [product_type, attr_set_id, sku, data])
+
def read(self, id, storeview_id=None, attributes=None):
""" Returns the information of a record
@@ -418,6 +555,34 @@ def run(self, binding_id, magento_record):
"""
+@magento
+class WithCatalogProductImportMapper(ImportMapper):
+ """ Called at the end of the product Mapper
+
+ Does nothing, but is replaced in ``magentoerpconnect_catalog_simple``
+ which adds fields in the import.
+
+ """
+ _model_name = 'magento.product.product'
+
+
+@magento
+class CatalogImportMapperFinalizer(ConnectorUnit):
+ """ Finalize values for a product
+
+ Does nothing else than calling :class:`WithCatalogProductImportMapper`
+ but is meant to be extended if required, for instance to remove values
+ from the imported values when we are importing products.
+ """
+ _model_name = ['magento.product.product']
+
+ def finalize(self, map_record, values, options):
+ mapper = self.unit_for(WithCatalogProductImportMapper)
+ map_record = mapper.map_record(map_record.source)
+ values.update(map_record.values(**options))
+ return values
+
+
@magento
class ProductImportMapper(ImportMapper):
_model_name = 'magento.product.product'
@@ -431,8 +596,21 @@ class ProductImportMapper(ImportMapper):
('type_id', 'product_type'),
(normalize_datetime('created_at'), 'created_at'),
(normalize_datetime('updated_at'), 'updated_at'),
+ ('visibility', 'visibility'),
]
+ @mapping
+ def map_attribute_set(self, record):
+ binder = self.binder_for(model='magento.attribute.set')
+ binding_id = binder.to_openerp(record['set'])
+ return {'attribute_set_id': binding_id}
+
+ @mapping
+ def map_tax_class(self, record):
+ binder = self.binder_for(model='magento.tax.class')
+ binding_id = binder.to_openerp(record['tax_class_id'])
+ return {'tax_class_id': binding_id}
+
@mapping
def is_active(self, record):
mapper = self.unit_for(IsActiveProductImportMapper)
@@ -502,6 +680,11 @@ def bundle_mapping(self, record):
bundle_mapper = self.unit_for(BundleProductImportMapper)
return bundle_mapper.map_record(record).values(**self.options)
+ def finalize(self, map_record, values):
+ values = super(ProductImportMapper, self).finalize(map_record, values)
+ finalizer = self.unit_for(CatalogImportMapperFinalizer)
+ return finalizer.finalize(map_record, values, self.options)
+
@magento
class ProductImporter(MagentoImporter):
@@ -527,6 +710,10 @@ def _import_dependencies(self):
if record['type_id'] == 'bundle':
self._import_bundle_dependencies()
+ if record.get('set', False):
+ self._import_dependency(record['set'],
+ 'magento.attribute.set')
+
def _validate_product_type(self, data):
""" Check if the product type is in the selection (so we can
prevent the `except_orm` and display a better error message).
diff --git a/magentoerpconnect/product_category.py b/magentoerpconnect/product_category.py
index 232c7bdf9..9e3056d1c 100644
--- a/magentoerpconnect/product_category.py
+++ b/magentoerpconnect/product_category.py
@@ -62,6 +62,10 @@ class MagentoProductCategory(models.Model):
inverse_name='magento_parent_id',
string='Magento Child Categories',
)
+ is_active = fields.Boolean(string='Active in Magento',
+ default=True)
+ include_in_menu = fields.Boolean(string='Include in Magento menu',
+ default=False)
class ProductCategory(models.Model):
@@ -91,6 +95,15 @@ def _call(self, method, arguments):
else:
raise
+ def create(self, parent_id, data):
+ return self._call('%s.create' % self._magento_model,
+ [parent_id, data])
+
+ def write(self, id, data, storeview=False):
+ """ Update records on the external system """
+ return self._call('%s.update' % self._magento_model,
+ [int(id), data, storeview])
+
def search(self, filters=None, from_date=None, to_date=None):
""" Search records according to some criteria and return a
list of ids
@@ -239,6 +252,8 @@ class ProductCategoryImportMapper(ImportMapper):
direct = [
('description', 'description'),
+ ('is_active', 'is_active'),
+ ('include_in_menu', 'include_in_menu')
]
@mapping
diff --git a/magentoerpconnect/product_view.xml b/magentoerpconnect/product_view.xml
index 8e0f33505..fd26e56f0 100644
--- a/magentoerpconnect/product_view.xml
+++ b/magentoerpconnect/product_view.xml
@@ -75,9 +75,12 @@
+
+
+
diff --git a/magentoerpconnect/tests/common.py b/magentoerpconnect/tests/common.py
index 52bc310ff..1249aecf6 100644
--- a/magentoerpconnect/tests/common.py
+++ b/magentoerpconnect/tests/common.py
@@ -189,6 +189,7 @@ class SetUpMagentoBase(common.TransactionCase):
def setUp(self):
super(SetUpMagentoBase, self).setUp()
self.backend_model = self.env['magento.backend']
+ self.mag_tax_class_obj = self.env['magento.tax.class']
self.session = ConnectorSession(self.env.cr, self.env.uid,
context=self.env.context)
warehouse = self.env.ref('stock.warehouse0')
@@ -200,6 +201,17 @@ def setUp(self):
'warehouse_id': warehouse.id,
'password': '42'}
)
+ default_tax_list = [
+ {'name': 'default', 'magento_id': '0'},
+ {'name': 'Taxable Goods', 'magento_id': '1'},
+ {'name': 'normal', 'magento_id': '2'},
+ {'name': 'Shipping', 'magento_id': '3'},
+ ]
+ if not self.backend.tax_imported:
+ for tax_dict in default_tax_list:
+ tax_dict.update(backend_id=self.backend.id)
+ self.mag_tax_class_obj.create(tax_dict)
+ self.backend.tax_imported = True
self.backend_id = self.backend.id
# payment method needed to import a sale order
workflow = self.env.ref(
diff --git a/magentoerpconnect/tests/data_base.py b/magentoerpconnect/tests/data_base.py
index 970621554..b9b43a269 100644
--- a/magentoerpconnect/tests/data_base.py
+++ b/magentoerpconnect/tests/data_base.py
@@ -8487,6 +8487,7 @@
'sku': '1114',
'small_image_label': None,
'status': '1',
+ 'tax_class_id': '2',
'thumbnail_label': None,
'type': 'grouped',
'type_id': 'grouped',
@@ -26594,4 +26595,17 @@
'weight': '0.0000',
'x_forwarded_for': None},
('sales_order.get_parent', (900000691,)): False,
+ ('product_attribute_set.list', ()): [{'name': '9', 'set_id': '9'},
+ {'name': '38', 'set_id': '38'},
+ {'name': '39', 'set_id': '39'},
+ {'name': '40', 'set_id': '40'},
+ {'name': '41', 'set_id': '41'},
+ {'name': '42', 'set_id': '42'},
+ {'name': '44', 'set_id': '44'},
+ {'name': '58', 'set_id': '58'},
+ {'name': '59', 'set_id': '59'},
+ {'name': '60', 'set_id': '60'},
+ {'name': '61', 'set_id': '61'},
+ {'name': '62', 'set_id': '62'},
+ ],
}
diff --git a/magentoerpconnect/tests/test_export_invoice.py b/magentoerpconnect/tests/test_export_invoice.py
index 83a90c0f9..ccbbdee80 100644
--- a/magentoerpconnect/tests/test_export_invoice.py
+++ b/magentoerpconnect/tests/test_export_invoice.py
@@ -40,6 +40,7 @@ def setUp(self):
super(TestExportInvoice, self).setUp()
backend_model = self.env['magento.backend']
self.mag_sale_model = self.env['magento.sale.order']
+ self.mag_tax_class_obj = self.env['magento.tax.class']
self.session = ConnectorSession(self.env.cr, self.env.uid,
context=self.env.context)
warehouse = self.env.ref('stock.warehouse0')
@@ -50,6 +51,18 @@ def setUp(self):
'username': 'guewen',
'warehouse_id': warehouse.id,
'password': '42'})
+ # create taxes
+ default_tax_list = [
+ {'name': 'default', 'magento_id': '0'},
+ {'name': 'Taxable Goods', 'magento_id': '1'},
+ {'name': 'normal', 'magento_id': '2'},
+ {'name': 'Shipping', 'magento_id': '3'},
+ ]
+ if not backend.tax_imported:
+ for tax_dict in default_tax_list:
+ tax_dict.update(backend_id=backend.id)
+ self.mag_tax_class_obj.create(tax_dict)
+ backend.tax_imported = True
# payment method needed to import a sale order
workflow = self.env.ref('sale_automatic_workflow.manual_validation')
journal = self.env.ref('account.check_journal')
diff --git a/magentoerpconnect/tests/test_related_action.py b/magentoerpconnect/tests/test_related_action.py
index b6c3d9aa3..9a86f30b3 100644
--- a/magentoerpconnect/tests/test_related_action.py
+++ b/magentoerpconnect/tests/test_related_action.py
@@ -21,6 +21,7 @@ class TestRelatedActionStorage(common.TransactionCase):
def setUp(self):
super(TestRelatedActionStorage, self).setUp()
backend_model = self.env['magento.backend']
+ self.mag_tax_class_obj = self.env['magento.tax.class']
self.session = ConnectorSession(self.env.cr, self.env.uid,
context=self.env.context)
warehouse = self.env.ref('stock.warehouse0')
@@ -31,6 +32,17 @@ def setUp(self):
'username': 'username',
'warehouse_id': warehouse.id,
'password': '42'})
+ default_tax_list = [
+ {'name': 'default', 'magento_id': '0'},
+ {'name': 'Taxable Goods', 'magento_id': '1'},
+ {'name': 'normal', 'magento_id': '2'},
+ {'name': 'Shipping', 'magento_id': '3'},
+ ]
+ if not self.backend.tax_imported:
+ for tax_dict in default_tax_list:
+ tax_dict.update(backend_id=self.backend.id)
+ self.mag_tax_class_obj.create(tax_dict)
+ self.backend.tax_imported = True
# import the base informations
with mock_api(magento_base_responses):
import_batch(self.session, 'magento.website', self.backend.id)
diff --git a/magentoerpconnect/unit/binder.py b/magentoerpconnect/unit/binder.py
index 232a97563..7516e33c1 100644
--- a/magentoerpconnect/unit/binder.py
+++ b/magentoerpconnect/unit/binder.py
@@ -28,6 +28,129 @@ class MagentoBinder(Binder):
""" Generic Binder for Magento """
+@magento
+class MagentoDirectModelBinder(MagentoBinder):
+ """
+ """
+ _model_name = [
+ 'magento.website',
+ 'magento.store',
+ 'magento.storeview',
+ 'magento.attribute.set',
+ 'magento.tax.class',
+ ]
+
+ def to_openerp(self, external_id, unwrap=False, browse=False):
+ """ Give the OpenERP ID for an external ID
+
+ :param external_id: external ID for which we want the OpenERP ID
+ :param unwrap: if True, returns the normal record (the one
+ inherits'ed), else return the binding record
+ :param browse: if True, returns a recordset
+ :return: a recordset of one record, depending on the value of unwrap,
+ or an empty recordset if no binding is found
+ :rtype: recordset
+ """
+ bindings = self.model.with_context(active_test=False).search(
+ [('magento_id', '=', str(external_id)),
+ ('backend_id', '=', self.backend_record.id)]
+ )
+ if not bindings:
+ return self.model.browse() if browse else None
+ assert len(bindings) == 1, "Several records found: %s" % (bindings,)
+ return bindings if browse else bindings.id
+
+ def to_backend(self, record_id, wrap=False):
+ """ Give the external ID for an OpenERP ID
+
+ :param record_id: OpenERP ID for which we want the external id
+ or a recordset with one record
+ :param wrap: if False, record_id is the ID of the binding,
+ if True, record_id is the ID of the normal record, the
+ method will search the corresponding binding and returns
+ the backend id of the binding
+ :return: backend identifier of the record
+ """
+ record = self.model.browse()
+ if isinstance(record_id, openerp.models.BaseModel):
+ record_id.ensure_one()
+ record = record_id
+ record_id = record_id.id
+ if wrap:
+ binding = self.model.with_context(active_test=False).search(
+ [('id', '=', record_id),
+ ('backend_id', '=', self.backend_record.id),
+ ]
+ )
+ if binding:
+ binding.ensure_one()
+ return binding.magento_id
+ else:
+ return None
+ if not record:
+ record = self.model.browse(record_id)
+ assert record
+ return record.magento_id
+
+ def bind(self, external_id, binding_id):
+ """ Create the link between an external ID and an OpenERP ID and
+ update the last synchronization date.
+
+ :param external_id: External ID to bind
+ :param binding_id: OpenERP ID to bind
+ :type binding_id: int
+ """
+ # the external ID can be 0 on Magento! Prevent False values
+ # like False, None, or "", but not 0.
+ assert (external_id or external_id == 0) and binding_id, (
+ "external_id or binding_id missing, "
+ "got: %s, %s" % (external_id, binding_id)
+ )
+ # avoid to trigger the export when we modify the `magento_id`
+ now_fmt = openerp.fields.Datetime.now()
+ if not isinstance(binding_id, openerp.models.BaseModel):
+ binding_id = self.model.browse(binding_id)
+ binding_id.with_context(connector_no_export=True).write(
+ {'magento_id': str(external_id),
+ 'sync_date': now_fmt,
+ })
+
+ def unwrap_binding(self, binding_id, browse=False):
+ """ For a binding record, gives the normal record.
+
+ Example: when called with a ``magento.product.product`` id,
+ it will return the corresponding ``product.product`` id.
+
+ :param browse: when True, returns a browse_record instance
+ rather than an ID
+ """
+ if isinstance(binding_id, openerp.models.BaseModel):
+ binding = binding_id
+ else:
+ binding = self.model.browse(binding_id)
+
+ openerp_record = binding.openerp_id
+ if browse:
+ return openerp_record
+ return openerp_record.id
+
+ def unwrap_model(self):
+ """ For a binding model, gives the name of the normal model.
+
+ Example: when called on a binder for ``magento.product.product``,
+ it will return ``product.product``.
+
+ This binder assumes that the normal model lays in ``openerp_id`` since
+ this is the field we use in the ``_inherits`` bindings.
+ """
+ try:
+ column = self.model._fields['openerp_id']
+ except KeyError:
+ raise ValueError('Cannot unwrap model %s, because it has '
+ 'no openerp_id field' % self.model._name)
+ return column.comodel_name
+
+
@magento
class MagentoModelBinder(MagentoBinder):
"""
@@ -40,9 +163,6 @@ class MagentoModelBinder(MagentoBinder):
fields belonging to the Magento instance.
"""
_model_name = [
- 'magento.website',
- 'magento.store',
- 'magento.storeview',
'magento.res.partner',
'magento.address',
'magento.res.partner.category',
diff --git a/magentoerpconnect/unit/export_synchronizer.py b/magentoerpconnect/unit/export_synchronizer.py
index b419449f9..e3884bef0 100644
--- a/magentoerpconnect/unit/export_synchronizer.py
+++ b/magentoerpconnect/unit/export_synchronizer.py
@@ -414,6 +414,105 @@ def _run(self, fields=None):
return _('Record exported with ID %s on Magento.') % self.magento_id
+class MagentoTranslationExporter(MagentoBaseExporter):
+ """ A translation exporter
+
+ Must be called in the ``_after_export`` method of an exporter.
+ """
+
+ def _get_translatable_field(self, fields):
+ """ Find the translatable fields of the model
+
+ Note we consider that a translatable field in Magento must be a
+ translatable field in OpenERP and vice-versa. You can change
+ this behaviour in your own module.
+ """
+ all_fields = self.model.fields_get()
+
+ translatable_fields = [field for field, attrs in all_fields.iteritems()
+ if attrs.get('translate') and
+ (not fields or field in fields)]
+ return translatable_fields
+
+ def _map_data(self):
+ """ Returns an instance of
+ :py:class:`~openerp.addons.connector.unit.mapper.MapRecord`
+
+ """
+ return self.mapper.map_record(self.binding_record)
+
+ def _update_data(self, map_record, fields=None, **kwargs):
+ """ Get the data to pass to :py:meth:`_update` """
+ return map_record.values(fields=fields, **kwargs)
+
+ def _validate_data(self, data):
+ """ Check if the values to import are correct
+
+ Kept for retro-compatibility. To remove in 8.0
+
+ Pro-actively check before the ``Model.create`` or ``Model.update``
+ if some fields are missing or invalid
+
+ Raise `InvalidDataError`
+ """
+ _logger.warning('Deprecated: _validate_data is deprecated '
+ 'in favor of validate_create_data() '
+ 'and validate_update_data()')
+ self._validate_create_data(data)
+ self._validate_update_data(data)
+
+ def _validate_create_data(self, data):
+ """ Check if the values to import are correct
+
+ Pro-actively check before the ``Model.create`` if some fields
+ are missing or invalid
+
+ Raise `InvalidDataError`
+ """
+ return
+
+ def _validate_update_data(self, data):
+ """ Check if the values to import are correct
+
+ Pro-actively check before the ``Model.update`` if some fields
+ are missing or invalid
+
+ Raise `InvalidDataError`
+ """
+ return
+
+ def _run(self, fields=None):
+ assert self.magento_id
+ default_lang = self.backend_record.default_lang_id
+ session = self.session
+
+ storeviews = session.env['magento.storeview'].search([
+ ('backend_id', '=', self.backend_record.id)
+ ])
+ lang_storeviews = [sv for sv in storeviews
+ if sv.lang_id and sv.lang_id != default_lang]
+ if not lang_storeviews:
+ return
+ translatable_fields = self._get_translatable_field(fields)
+ if not translatable_fields:
+ return
+ for storeview in lang_storeviews:
+ lang_code = storeview.lang_id.code
+ model = self.model.with_context(lang=lang_code)
+ self.binding_record = model.browse(self.binding_id)
+ map_record = self._map_data()
+ record = self._update_data(map_record,
+ fields=translatable_fields)
+ if not record:
+ continue
+ # special check on data before export
+ self._validate_data(record)
+ binder = self.binder_for('magento.storeview')
+ magento_storeview_id = binder.to_backend(storeview)
+ self.backend_adapter.write(
+ self.magento_id, record, magento_storeview_id)
+
+
@job(default_channel='root.magento')
@related_action(action=unwrap_binding)
def export_record(session, model_name, binding_id, fields=None):
diff --git a/magentoerpconnect_catalog_simple/README.rst b/magentoerpconnect_catalog_simple/README.rst
new file mode 100644
index 000000000..698e79f09
--- /dev/null
+++ b/magentoerpconnect_catalog_simple/README.rst
@@ -0,0 +1,22 @@
+.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
+ :alt: License
+
+Magento Catalog Simple
+======================
+
+TODO
+
+Maintainer
+----------
+
+.. image:: http://odoo-community.org/logo.png
+ :alt: Odoo Community Association
+ :target: http://odoo-community.org
+
+This module is maintained by the OCA.
+
+OCA, or the Odoo Community Association, is a nonprofit organization
+whose mission is to support the collaborative development of Odoo
+features and promote its widespread use.
+
+To contribute to this module, please visit http://odoo-community.org.
diff --git a/magentoerpconnect_catalog_simple/__init__.py b/magentoerpconnect_catalog_simple/__init__.py
new file mode 100644
index 000000000..946c4d571
--- /dev/null
+++ b/magentoerpconnect_catalog_simple/__init__.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+#
+# Author: Damien Crier
+# Copyright 2015 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+
+from . import models
diff --git a/magentoerpconnect_catalog_simple/__openerp__.py b/magentoerpconnect_catalog_simple/__openerp__.py
new file mode 100644
index 000000000..bb214526f
--- /dev/null
+++ b/magentoerpconnect_catalog_simple/__openerp__.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+#
+# Author: Damien Crier
+# Copyright 2015 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+{'name': 'Magento Catalog Simple',
+ 'version': '8.0.1.0',
+ 'category': 'Connector',
+ 'depends': ['magentoerpconnect',
+ ],
+ 'author': "Camptocamp,Odoo Community Association (OCA)",
+ 'license': 'AGPL-3',
+ 'website': 'http://www.odoo-magento-connector.com',
+ 'data': ['security/ir.model.access.csv',
+ 'views/magento_model_view.xml',
+ 'views/product_view.xml',
+ ],
+ 'installable': True,
+ 'application': True,
+ }
diff --git a/magentoerpconnect_catalog_simple/models/__init__.py b/magentoerpconnect_catalog_simple/models/__init__.py
new file mode 100644
index 000000000..5a4aea290
--- /dev/null
+++ b/magentoerpconnect_catalog_simple/models/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+
+from . import magento_model
+from . import magento_product
diff --git a/magentoerpconnect_catalog_simple/models/magento_model/__init__.py b/magentoerpconnect_catalog_simple/models/magento_model/__init__.py
new file mode 100644
index 000000000..aa9c3f5d9
--- /dev/null
+++ b/magentoerpconnect_catalog_simple/models/magento_model/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+from . import common
diff --git a/magentoerpconnect_catalog_simple/models/magento_model/common.py b/magentoerpconnect_catalog_simple/models/magento_model/common.py
new file mode 100644
index 000000000..7f4c601a7
--- /dev/null
+++ b/magentoerpconnect_catalog_simple/models/magento_model/common.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+#
+# Author: Damien Crier
+# Copyright 2015 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+
+from openerp import models, fields
+
+
+class MagentoBackend(models.Model):
+ _inherit = 'magento.backend'
+
+ auto_bind_product = fields.Boolean(
+ string='Auto Bind Product',
+ default=False,
+ help="Tic that box if you want to automatically export the"
+ "product when it's available for sell (sale_ok is tic)"
+ )
+ default_mag_tax_id = fields.Many2one('magento.tax.class',
+ string='Default tax')
diff --git a/magentoerpconnect_catalog_simple/models/magento_product/__init__.py b/magentoerpconnect_catalog_simple/models/magento_product/__init__.py
new file mode 100644
index 000000000..e6daa3352
--- /dev/null
+++ b/magentoerpconnect_catalog_simple/models/magento_product/__init__.py
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+
+from . import common
+from . import consumer
+from . import importer
+from . import exporter
diff --git a/magentoerpconnect_catalog_simple/models/magento_product/common.py b/magentoerpconnect_catalog_simple/models/magento_product/common.py
new file mode 100644
index 000000000..35bc1c51d
--- /dev/null
+++ b/magentoerpconnect_catalog_simple/models/magento_product/common.py
@@ -0,0 +1,200 @@
+# -*- coding: utf-8 -*-
+#
+# Author: Damien Crier
+# Copyright 2015 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+
+from openerp import api, models, fields
+from openerp import exceptions
+from openerp.tools.translate import _
+from openerp.addons.connector.session import ConnectorSession
+from .event import on_product_create, on_product_write
+import logging
+_logger = logging.getLogger(__name__)
+
+
+class MagentoProductProduct(models.Model):
+ _inherit = 'magento.product.product'
+
+ website_ids = fields.Many2many(readonly=False)
+ active = fields.Boolean(default=True,
+ help="When a binding is unactivated, the product "
+ "is delete from Magento. This allow to remove"
+ " product from Magento and so to increase the"
+ " perf on Magento side")
+
+ @api.multi
+ def write(self, vals):
+ if vals.get('active') is True:
+ binding_ids = self.search([('active', '=', False)])
+ if len(binding_ids) > 0:
+ raise exceptions.Warning(
+ _('You can not reactivate the following binding ids: %s '
+ 'please add a new one instead') % binding_ids)
+ return super(MagentoProductProduct, self).write(vals)
+
+ @api.multi
+ def unlink(self):
+ synchronized_binding_ids = self.search([
+ ('id', 'in', self.ids),
+ ('magento_id', '!=', False),
+ ])
+ if synchronized_binding_ids:
+ raise exceptions.Warning(
+ _('This binding ids %s can not be remove as '
+ 'the field magento_id is not empty.\n'
+ 'Please unactivate it instead') % synchronized_binding_ids)
+ return super(MagentoProductProduct, self).unlink()
+
+
+class ProductProduct(models.Model):
+ _inherit = 'product.product'
+
+ magento_inactive_bind_ids = fields.One2many(
+ 'magento.product.product',
+ 'openerp_id',
+ string='Magento Inactive Bindings',
+ domain=[('active', '=', False)],
+ readonly=True,
+ )
+
+ @api.multi
+ def _prepare_create_magento_auto_binding(self, backend_id):
+ self.ensure_one()
+ bkend_obj = self.env['magento.backend']
+ bkend_brw = bkend_obj.browse(backend_id)
+ return {
+ 'backend_id': backend_id,
+ 'openerp_id': self.id,
+ 'visibility': '4',
+ 'status': '1',
+ 'created_at': fields.Date.today(),
+ 'updated_at': fields.Date.today(),
+ 'tax_class_id': bkend_brw.default_mag_tax_id.id
+ }
+
+ @api.multi
+ def _get_magento_binding(self, backend_id):
+ self.ensure_one()
+ binding_ids = self.env['magento.product.product'].search([
+ ('openerp_id', '=', self.id),
+ ('backend_id', '=', backend_id),
+ ])
+ if binding_ids:
+ return binding_ids[0]
+ else:
+ return None
+
+ @api.multi
+ def automatic_binding(self, sale_ok):
+ backend_obj = self.env['magento.backend']
+ mag_product_obj = self.env['magento.product.product']
+ back_rs = backend_obj.search([])
+ for backend in back_rs:
+ if backend.auto_bind_product:
+ for product in self:
+ binding_rs = product._get_magento_binding(backend.id)
+ if not binding_rs and sale_ok:
+ vals = product._prepare_create_magento_auto_binding(
+ backend.id)
+ mag_product_obj.create(vals)
+ else:
+ binding_rs.write({
+ 'status': '1' if sale_ok else '2',
+ })
+
+ @api.multi
+ def write(self, vals):
+ self_context = self.with_context(from_product_ids=self.ids)
+ result = super(ProductProduct, self_context).write(vals)
+ session = ConnectorSession.from_env(self.env)
+ for product_id in self.ids:
+ on_product_write.fire(session, self._name, product_id, vals)
+
+ if vals.get('active', True) is False:
+ for product in self:
+ for bind in product.magento_bind_ids:
+ bind.write({'active': False})
+
+ if 'sale_ok' in vals:
+ for product in self:
+ product.automatic_binding(vals['sale_ok'])
+
+ return result
+
+ @api.model
+ def create(self, vals):
+ self_context = self.with_context(from_product_ids=self.ids)
+ product = super(ProductProduct, self_context).create(vals)
+ session = ConnectorSession.from_env(self.env)
+ on_product_create.fire(session, self._name, product.id, vals)
+ if product.sale_ok:
+ if not product._context.get('connector_no_export', False):
+ product.automatic_binding(True)
+
+ return product
+
+ @api.constrains('name', 'description')
+ def _check_description(self):
+ if self.name == self.description:
+ raise exceptions.ValidationError(
+ "Fields name and description must be different")
+
+ @api.one
+ @api.constrains('backend_id', 'openerp_id', 'active')
+ def _check_uniq_magento_product(self):
+ self.env.cr.execute("""SELECT openerp_id
+ FROM magento_product_product
+ WHERE active=True
+ GROUP BY backend_id, openerp_id
+ HAVING count(id) > 1""")
+ result = self.env.cr.fetchall()
+ if result:
+ raise exceptions.Warning(
+ _('You can not have more than one active binding for '
+ 'a product. Here is the list of product ids with a '
+ 'duplicated binding : %s')
+ % ", ".join([str(x[0]) for x in result]))
+ return True
+
+
+class ProductTemplate(models.Model):
+ _inherit = 'product.template'
+
+ @api.multi
+ def write(self, vals):
+ result = super(ProductTemplate, self).write(vals)
+ # If a field of the template has been modified, we want to
+ # export this field on all the variants on this product.
+ # If only fields of variants have been modified, they have
+ # already be exported by the event on 'product.product'
+ if any(field for field in vals if field in self._columns):
+ session = ConnectorSession.from_env(self.env)
+ variants = self.mapped('product_variant_ids')
+ # When the 'write()' is done on 'product.product', avoid
+ # to fire the event 2 times. Event has been fired on the
+ # variant, do not fire it on the template.
+ # We'll export the *other* variants of the template though
+ # as soon as template fields have been modified.
+ if self.env.context.get('from_product_ids'):
+ from_product_ids = self.env.context['from_product_ids']
+ product_model = self.env['product.product']
+ triggered_products = product_model.browse(from_product_ids)
+ variants -= triggered_products
+ for variant_id in variants.ids:
+ on_product_write.fire(session, variants._model._name,
+ variant_id, vals)
+ return result
diff --git a/magentoerpconnect_catalog_simple/models/magento_product/consumer.py b/magentoerpconnect_catalog_simple/models/magento_product/consumer.py
new file mode 100644
index 000000000..3252a17fa
--- /dev/null
+++ b/magentoerpconnect_catalog_simple/models/magento_product/consumer.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+#
+# Author: Damien Crier
+# Copyright 2015 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+from openerp.addons.connector.event import (on_record_write,
+ on_record_create,
+ on_record_unlink
+ )
+
+import openerp.addons.magentoerpconnect.consumer as magentoerpconnect
+
+EXCLUDED_FIELDS_WRITING = {
+ 'product.product': ['magento_bind_ids', 'image_ids'],
+ 'product.category': ['magento_bind_ids'],
+ 'magento.product.category': ['magento_bind_ids'],
+}
+
+
+def exclude_fields_from_synchro(model_name, fields):
+ if fields and EXCLUDED_FIELDS_WRITING.get(model_name):
+ fields = list(set(fields).difference(EXCLUDED_FIELDS_WRITING))
+ return fields
+
+
+@on_record_create(model_names=[
+ 'magento.product.product',
+ ])
+@on_record_write(model_names=[
+ 'magento.product.product',
+ ])
+def delay_export(session, model_name, record_id, vals=None):
+ magentoerpconnect.delay_export(session, model_name,
+ record_id, vals=vals)
+
+
+@on_record_write(model_names=[
+ 'product.product',
+ 'product.category',
+ ])
+def delay_export_all_bindings(session, model_name, record_id, vals=None):
+ magentoerpconnect.delay_export_all_bindings(session, model_name,
+ record_id, vals=vals)
+
+
+@on_record_unlink(model_names=[
+ 'magento.product.product',
+ ])
+def delay_unlink(session, model_name, record_id):
+ magentoerpconnect.delay_unlink(session, model_name, record_id)
diff --git a/magentoerpconnect_catalog_simple/models/magento_product/event.py b/magentoerpconnect_catalog_simple/models/magento_product/event.py
new file mode 100644
index 000000000..bd3e540d0
--- /dev/null
+++ b/magentoerpconnect_catalog_simple/models/magento_product/event.py
@@ -0,0 +1,48 @@
+
+# -*- coding: utf-8 -*-
+#
+#
+# Authors: Guewen Baconnier
+# Copyright 2015 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+#
+
+from openerp.addons.connector.event import Event
+
+
+on_product_create = Event()
+"""
+``on_product_create`` is fired when a product is created.
+It takes care to not fire the event 2 times on the creation or modification
+of a variant.
+Listeners should take the following arguments:
+ * session: `connector.session.ConnectorSession` object
+ * model_name: name of the model
+ * record_id: id of the record
+ * vals: field values of the new record, e.g {'field_name': field_value, ...}
+"""
+
+on_product_write = Event()
+"""
+``on_product_write`` is fired when a product is updated.
+It takes care to not fire the event 2 times on the creation or modification
+of a variant.
+Listeners should take the following arguments:
+ * session: `connector.session.ConnectorSession` object
+ * model_name: name of the model
+ * record_id: id of the record
+ * vals: field values updated, e.g {'field_name': field_value, ...}
+"""
diff --git a/magentoerpconnect_catalog_simple/models/magento_product/exporter.py b/magentoerpconnect_catalog_simple/models/magento_product/exporter.py
new file mode 100644
index 000000000..a704361ea
--- /dev/null
+++ b/magentoerpconnect_catalog_simple/models/magento_product/exporter.py
@@ -0,0 +1,237 @@
+# -*- coding: utf-8 -*-
+#
+# Author: Damien Crier
+# Copyright 2015 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+
+from openerp import models, fields
+from openerp.addons.connector.unit.mapper import (mapping,
+ ExportMapper)
+from openerp.addons.magentoerpconnect.unit.delete_synchronizer import (
+ MagentoDeleteSynchronizer)
+from openerp.addons.magentoerpconnect.unit.export_synchronizer import (
+ MagentoExporter,
+ MagentoTranslationExporter)
+from openerp.addons.magentoerpconnect.backend import magento
+from openerp.addons.connector.exception import MappingError
+from openerp.addons.connector.event import (
+ on_record_write,
+ on_record_create
+ )
+from openerp.addons.connector.connector import ConnectorUnit
+import logging
+_logger = logging.getLogger(__name__)
+import openerp.addons.magentoerpconnect.consumer as magentoerpconnect
+from openerp.addons.magentoerpconnect.product import ProductInventoryExporter
+
+
+@on_record_write(model_names=[
+ 'magento.product.product',
+])
+def delay_export(session, model_name, record_id, vals=None):
+ if vals.get('active', True) is False:
+ magentoerpconnect.delay_unlink(session, model_name, record_id)
+
+
+@magento
+class ProductProductDeleteSynchronizer(MagentoDeleteSynchronizer):
+ """ Partner deleter for Magento """
+ _model_name = ['magento.product.product']
+
+
+@magento
+class ProductProductConfigurableExport(ConnectorUnit):
+ _model_name = ['magento.product.product']
+
+ def _export_configurable_link(self, binding):
+ """ Export the link for the configurable product"""
+ return
+
+
+class MagentoAttributeSet(models.Model):
+ _inherit = 'magento.attribute.set'
+
+ parent_model_id = fields.Many2one(comodel_name='magento.attribute.set',
+ string='Skeleton',
+ required=False,
+ ondelete='restrict')
+
+
+@magento
+class AttributeSetExporter(MagentoExporter):
+ _model_name = 'magento.attribute.set'
+
+ def _create(self, data):
+ """ Create the Magento record """
+ # special check on data before export
+ return self.backend_adapter.create(data['name'], data['parent_name'])
+
+
+@magento
+class AttributeSetExportMapper(ExportMapper):
+ _model_name = 'magento.attribute.set'
+
+ @mapping
+ def base(self, record):
+ binder = self.get_binder_for_model('magento.attribute.set')
+ parent_set_id = binder.to_backend(record.parent_model_id.id, wrap=True)
+ return {'name': record.name,
+ 'parent_name': parent_set_id}
+
+
+@on_record_create(model_names=[
+ 'magento.attribute.set',
+ ])
+def delay_export2(session, model_name, record_id, vals=None):
+ magentoerpconnect.delay_export(session, model_name,
+ record_id, vals=vals)
+
+
+@magento
+class ProductProductExportMapper(ExportMapper):
+ _model_name = 'magento.product.product'
+
+ @mapping
+ def all(self, record):
+ return {'name': record.name,
+ 'description': record.description,
+ 'weight': record.weight,
+ 'price': record.lst_price,
+ 'short_description': record.description_sale,
+ 'type': record.product_type,
+ 'visibility': record.visibility,
+ 'product_type': record.product_type,
+ }
+
+ @mapping
+ def sku(self, record):
+ sku = record.default_code
+ if not sku:
+ raise MappingError("The product attribute "
+ "default code cannot be empty.")
+ return {'sku': sku}
+
+ @mapping
+ def set(self, record):
+ binder = self.get_binder_for_model('magento.attribute.set')
+ set_id = binder.to_backend(record.attribute_set_id.id, wrap=True)
+ return {'attrset': set_id}
+
+ @mapping
+ def updated_at(self, record):
+ updated_at = record.updated_at
+ if not updated_at:
+ updated_at = '1970-01-01'
+ return {'updated_at': updated_at}
+
+ @mapping
+ def created_at(self, record):
+ created_at = record.created_at
+ if not created_at:
+ created_at = '1970-01-01'
+ return {'created_at': created_at}
+
+ @mapping
+ def website_ids(self, record):
+ website_ids = []
+ for website_id in record.website_ids:
+ magento_id = website_id.magento_id
+ website_ids.append(magento_id)
+ return {'website_ids': website_ids}
+
+ @mapping
+ def status(self, record):
+ return {'status': record.active and "1" or "2"}
+
+ @mapping
+ def tax_class(self, record):
+ binder = self.get_binder_for_model('magento.tax.class')
+ tax_class_id = binder.to_backend(record.tax_class_id.id, wrap=True)
+ return {'tax_class_id': str(tax_class_id)}
+
+ @mapping
+ def category(self, record):
+ categ_ids = []
+ if record.categ_id:
+ for m_categ in record.categ_id.magento_bind_ids:
+ if m_categ.backend_id.id == self.backend_record.id:
+ categ_ids.append(m_categ.magento_id)
+
+ for categ in record.categ_ids:
+ for m_categ in categ.magento_bind_ids:
+ if m_categ.backend_id.id == self.backend_record.id:
+ categ_ids.append(m_categ.magento_id)
+ return {'categories': categ_ids}
+
+
+@magento
+class ProductProductTranslationExporter(MagentoTranslationExporter):
+ _model_name = ['magento.product.product']
+
+ def _should_import(self):
+ """Product are only edited on OpenERP Side"""
+ return False
+
+
+@magento
+class ProductProductExporter(MagentoExporter):
+ _model_name = ['magento.product.product']
+
+ create_mode = False
+
+ def _run(self, fields=None):
+ self.create_mode = not(bool(self.magento_id))
+ return super(ProductProductExporter, self)._run(fields=fields)
+
+ @property
+ def mapper(self):
+ if self._mapper is None:
+ self._mapper = self.get_connector_unit_for_model(
+ ProductProductExportMapper)
+ return self._mapper
+
+ def _should_import(self):
+ """Product are only edited on OpenERP Side"""
+ return False
+
+ def _create(self, data):
+ """ Create the Magento record """
+ # special check on data before export
+ sku = data.pop('sku')
+ attr_set_id = data.pop('attrset')
+ product_type = data.pop('product_type')
+ self._validate_data(data)
+ return self.backend_adapter.create(product_type,
+ attr_set_id, sku, data)
+
+ def _after_export(self):
+ # translation export
+ translation_exporter = self.unit_for(ProductProductTranslationExporter)
+ translation_exporter.run(self.binding_id)
+
+ if self.create_mode:
+ inventory_exporter = self.unit_for(ProductInventoryExporter)
+ inventory_exporter.run(self.binding_id, ['magento_qty'])
+
+# @job(default_channel='root.magento')
+# @related_action(action=unwrap_binding)
+# def export_product(session, model_name, record_id):
+# """ Export a product. """
+# product = session.env[model_name].browse(record_id)
+# backend_id = product.backend_id.id
+# env = get_environment(session, model_name, backend_id)
+# product_exporter = env.get_connector_unit(ProductProductExporter)
+# return product_exporter.run(record_id)
diff --git a/magentoerpconnect_catalog_simple/models/magento_product/importer.py b/magentoerpconnect_catalog_simple/models/magento_product/importer.py
new file mode 100644
index 000000000..61362ed08
--- /dev/null
+++ b/magentoerpconnect_catalog_simple/models/magento_product/importer.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+#
+# Author: Damien Crier
+# Copyright 2015 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
diff --git a/magentoerpconnect_catalog_simple/security/ir.model.access.csv b/magentoerpconnect_catalog_simple/security/ir.model.access.csv
new file mode 100644
index 000000000..0c53a6031
--- /dev/null
+++ b/magentoerpconnect_catalog_simple/security/ir.model.access.csv
@@ -0,0 +1,5 @@
+"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
+"access_magento_tax_class","magento tax class manager","magentoerpconnect.model_magento_tax_class","connector.group_connector_manager",1,1,1,1
+"access_magento_attribute_set","magento attribute set manager","magentoerpconnect.model_magento_attribute_set","connector.group_connector_manager",1,1,1,1
+"access_magento__tax_class_user","magento tax class user","magentoerpconnect.model_magento_tax_class","base.group_sale_salesman",1,0,0,0
+"access_magento_attribute_set_user","magento attribute set user","magentoerpconnect.model_magento_attribute_set","base.group_sale_salesman",1,0,0,0
\ No newline at end of file
diff --git a/magentoerpconnect_catalog_simple/tests/__init__.py b/magentoerpconnect_catalog_simple/tests/__init__.py
new file mode 100644
index 000000000..e40ff3d07
--- /dev/null
+++ b/magentoerpconnect_catalog_simple/tests/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+from . import test_export_product
diff --git a/magentoerpconnect_catalog_simple/tests/test_export_product.py b/magentoerpconnect_catalog_simple/tests/test_export_product.py
new file mode 100644
index 000000000..a39054d51
--- /dev/null
+++ b/magentoerpconnect_catalog_simple/tests/test_export_product.py
@@ -0,0 +1,182 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Guewen Baconnier
+# Copyright 2013 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+import openerp.tests.common as common
+from openerp.addons.connector.session import ConnectorSession
+from openerp.addons.magentoerpconnect.unit.import_synchronizer import (
+ import_batch)
+from openerp.addons.magentoerpconnect.tests.common import (
+ mock_api,
+ mock_job_delay_to_direct
+ )
+from openerp.addons.magentoerpconnect.tests.data_base import (
+ magento_base_responses
+ )
+
+
+class TestExportProduct(common.TransactionCase):
+ """ Test the export of an invoice to Magento """
+
+ def setUp(self):
+ super(TestExportProduct, self).setUp()
+ backend_model = self.env['magento.backend']
+# self.mag_sale_model = self.env['magento.sale.order']
+ self.mag_tax_class_obj = self.env['magento.tax.class']
+ self.session = ConnectorSession(self.env.cr, self.env.uid,
+ context=self.env.context)
+ warehouse = self.env.ref('stock.warehouse0')
+ self.backend = backend = backend_model.create(
+ {'name': 'Test Magento',
+ 'version': '1.7',
+ 'location': 'http://anyurl',
+ 'username': 'guewen',
+ 'warehouse_id': warehouse.id,
+ 'password': '42'})
+ # create taxes
+ default_tax_list = [
+ {'name': 'default', 'magento_id': '0'},
+ {'name': 'Taxable Goods', 'magento_id': '1'},
+ {'name': 'normal', 'magento_id': '2'},
+ {'name': 'Shipping', 'magento_id': '3'},
+ ]
+ if not backend.tax_imported:
+ for tax_dict in default_tax_list:
+ tax_dict.update(backend_id=backend.id)
+ self.mag_tax_class_obj.create(tax_dict)
+ backend.tax_imported = True
+ # import the base informations
+ with mock_api(magento_base_responses):
+ import_batch(self.session, 'magento.website', backend.id)
+ import_batch(self.session, 'magento.store', backend.id)
+ import_batch(self.session, 'magento.storeview', backend.id)
+ import_batch(self.session, 'magento.attribute.set', backend.id)
+# with mock_urlopen_image():
+# import_record(self.session,
+# 'magento.sale.order',
+# backend.id, 900000691)
+ self.stores = self.backend.mapped('website_ids.store_ids')
+# sales = self.mag_sale_model.search(
+# [('backend_id', '=', backend.id),
+# ('magento_id', '=', '900000691')])
+# self.assertEqual(len(sales), 1)
+# self.mag_sale = sales
+# # ignore exceptions on the sale order
+# self.mag_sale.ignore_exceptions = True
+# self.mag_sale.openerp_id.action_button_confirm()
+# sale = self.mag_sale.openerp_id
+# invoice_id = sale.action_invoice_create()
+# assert invoice_id
+# self.invoice_model = self.env['account.invoice']
+# self.invoice = self.invoice_model.browse(invoice_id)
+
+ def testCreateProductApi(self):
+ """
+
+ """
+ job_path = ('openerp.addons.magentoerpconnect.consumer.export_record')
+ response = {
+ 'ol_catalog_product.create': 177,
+ }
+ with mock_job_delay_to_direct(job_path), \
+ mock_api(response, key_func=lambda m, a: m) as calls_done:
+ vals = {
+ 'name': 'TEST export',
+ 'default_code': 'default_code-export',
+ 'description': 'description',
+ 'description_sale': 'description sale',
+ 'weight': 4.56,
+ 'active': True,
+ 'magento_bind_ids': [
+ (0, 0, {
+ 'backend_id': self.backend.id,
+ 'website_ids': [
+ (6, 0,
+ self.env['magento.website'].search(
+ [('backend_id', '=', self.backend.id)]).ids
+ )
+ ],
+ 'updated_at': '2015-09-17',
+ 'created_at': '2015-09-17',
+ 'active': True,
+ }
+ )
+ ],
+ 'lst_price': 1.23,
+ 'attribute_set_id': self.env['magento.attribute.set'].search(
+ [('magento_id', '=', '9')]).id,
+ }
+ self.env['product.product'].create(vals)
+ self.assertEqual(len(calls_done), 1)
+ method, args_tuple = calls_done[0]
+ self.assertEqual(method, 'ol_catalog_product.create')
+
+ def testWriteProductApi(self):
+ """
+
+ """
+ job_path = ('openerp.addons.magentoerpconnect.consumer.export_record')
+ response = {
+ 'ol_catalog_product.create': 177,
+ }
+ with mock_job_delay_to_direct(job_path), \
+ mock_api(response, key_func=lambda m, a: m) as calls_done:
+ vals = {
+ 'name': 'TEST export',
+ 'default_code': 'default_code-export',
+ 'description': 'description',
+ 'description_sale': 'description sale',
+ 'weight': 4.56,
+ 'active': True,
+ 'magento_bind_ids': [
+ (0, 0, {
+ 'backend_id': self.backend.id,
+ 'website_ids': [
+ (6, 0,
+ self.env['magento.website'].search(
+ [('backend_id', '=', self.backend.id)]
+ ).ids
+ )
+ ],
+ 'updated_at': '2015-09-17',
+ 'created_at': '2015-09-17',
+ 'active': True,
+ }
+ )
+ ],
+ 'lst_price': 1.23,
+ 'attribute_set_id': self.env['magento.attribute.set'].search(
+ [('magento_id', '=', '9')]).id,
+ }
+ product = self.env['product.product'].create(vals)
+ self.assertEqual(len(calls_done), 1)
+ method, args_tuple = calls_done[0]
+ self.assertEqual(method, 'ol_catalog_product.create')
+
+ response_write = {'ol_catalog_product.update': True}
+ with mock_job_delay_to_direct(job_path), \
+ mock_api(response_write, key_func=lambda m, a: m) \
+ as calls_done_write:
+ product.write({'lst_price': 4.56})
+ self.assertEqual(len(calls_done_write), 1)
+ # raises because it launches write for product.template
+ # AND product.product objects
+ wmethod, wargs_tuple = calls_done_write[0]
+ self.assertEqual(wmethod, 'ol_catalog_product.update')
diff --git a/magentoerpconnect_catalog_simple/views/magento_model_view.xml b/magentoerpconnect_catalog_simple/views/magento_model_view.xml
new file mode 100644
index 000000000..a3cb4e795
--- /dev/null
+++ b/magentoerpconnect_catalog_simple/views/magento_model_view.xml
@@ -0,0 +1,109 @@
+
+
+
+
+
+ magento.backend
+
+
+
+
+
+
+
+
+
+
+ magento.tax.class.form
+ magento.tax.class
+
+
+
+
+
+
+ magento.tax.class.tree
+ magento.tax.class
+
+
+
+
+
+
+
+
+
+
+ Magento Product Tax
+ magento.tax.class
+ form
+ tree,form
+
+
+
+
+
+
+
+ magento.attribute.set.form
+ magento.attribute.set
+
+
+
+
+
+
+ magento.attribute.set.tree
+ magento.attribute.set
+
+
+
+
+
+
+
+
+
+
+ Magento Attribute Sets
+ magento.attribute.set
+ form
+ tree,form
+
+
+
+
+
+
+
diff --git a/magentoerpconnect_catalog_simple/views/product_view.xml b/magentoerpconnect_catalog_simple/views/product_view.xml
new file mode 100644
index 000000000..90abcfa4f
--- /dev/null
+++ b/magentoerpconnect_catalog_simple/views/product_view.xml
@@ -0,0 +1,46 @@
+
+
+
+
+ product.template.property.form.inherit
+ product.template
+
+
+
+
+
+
+
+
+
+
+
+
+ product.product
+
+
+
+
+
+
+
+
+
+
+ magento.product.product
+
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+
+
+
+
\ No newline at end of file