IMP connector_opencart Add bindings between Opencart Product Option Values and Odoo's Product Template Attribute Values

This commit is contained in:
Jared Kipe
2020-02-20 15:03:10 -08:00
parent 9eb593b6ae
commit ce8bb41c03
11 changed files with 226 additions and 24 deletions

View File

@@ -1,4 +1,5 @@
from . import delivery
from . import opencart
from . import product
from . import sale_order
from . import stock_picking

View File

@@ -2,12 +2,12 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from datetime import datetime, timedelta
from logging import getLogger
from contextlib import contextmanager
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.addons.connector.models.checkpoint import add_checkpoint
from ...components.api.opencart import Opencart
_logger = getLogger(__name__)
@@ -80,6 +80,13 @@ class OpencartBackend(models.Model):
with _super.work_on(model_name, opencart_api=opencart_api, **kwargs) as work:
yield work
@api.multi
def add_checkpoint(self, record):
self.ensure_one()
record.ensure_one()
return add_checkpoint(self.env, record._name, record.id,
self._name, self.id)
@api.multi
def synchronize_metadata(self):
try:

View File

@@ -0,0 +1,2 @@
from . import common
from . import importer

View File

@@ -0,0 +1,77 @@
from odoo import api, fields, models
from odoo.addons.queue_job.exception import NothingToDoJob, RetryableJobError
from odoo.addons.component.core import Component
class OpencartProductTemplate(models.Model):
_name = 'opencart.product.template'
_inherit = 'opencart.binding'
_inherits = {'product.template': 'odoo_id'}
_description = 'Opencart Product'
odoo_id = fields.Many2one('product.template',
string='Product',
required=True,
ondelete='restrict')
opencart_attribute_value_ids = fields.One2many('opencart.product.template.attribute.value',
'opencart_product_tmpl_id',
string='Opencart Product Attribute Values')
def opencart_sale_get_combination(self, options, reentry=False):
selected_attribute_values = self.env['product.template.attribute.value']
for option in options:
product_option_value_id = str(option['product_option_value_id'])
opencart_attribute_value = self.opencart_attribute_value_ids.filtered(lambda v: v.external_id == product_option_value_id)
if not opencart_attribute_value:
if reentry:
# we have already triggered an import.
raise Exception('Order Product has option (%s) "%s" that does not exist on the product.' % (product_option_value_id, option.get('name', '<Empty>')))
# need to re-import product.
try:
self.import_record(self.backend_id, self.external_id, force=True)
return self.opencart_sale_get_combination(options, reentry=True)
except NothingToDoJob:
if reentry:
raise RetryableJobError('Product imported, but selected option is not available.')
if not opencart_attribute_value.odoo_id:
raise RetryableJobError('Order Product has option (%s) "%s" that is not mapped to an Odoo Attribute Value.' % (opencart_attribute_value.external_id, opencart_attribute_value.opencart_name))
selected_attribute_values += opencart_attribute_value.odoo_id
# Now that we know what options are selected, we can load a variant with those options
return self.odoo_id._create_product_variant(selected_attribute_values)
class ProductTemplate(models.Model):
_inherit = 'product.template'
opencart_bind_ids = fields.One2many('opencart.product.template', 'odoo_id', string='Opencart Bindings')
class OpencartProductTemplateAdapter(Component):
_name = 'opencart.product.template.adapter'
_inherit = 'opencart.adapter'
_apply_on = 'opencart.product.template'
def read(self, id):
api_instance = self.api_instance
record = api_instance.products.get(id)
if 'data' in record and record['data']:
return record['data']
raise RetryableJobError('Product "' + str(id) + '" did not return an product response. ' + str(record))
# Product Attribute Value, cannot "inherits" the odoo_id as then it cannot be empty
class OpencartProductTemplateAttributeValue(models.Model):
_name = 'opencart.product.template.attribute.value'
_inherit = 'opencart.binding'
_description = 'Opencart Product Attribute Value'
odoo_id = fields.Many2one('product.template.attribute.value',
string='Product Attribute Value',
required=False,
ondelete='cascade')
opencart_name = fields.Char(string='Opencart Name', help='For matching purposes.')
opencart_product_tmpl_id = fields.Many2one('opencart.product.template',
string='Opencart Product',
required=True,
ondelete='cascade')
product_tmpl_id = fields.Many2one(related='opencart_product_tmpl_id.odoo_id')

View File

@@ -0,0 +1,78 @@
from html import unescape
from odoo.addons.component.core import Component
from odoo.addons.connector.components.mapper import mapping, only_create
class ProductImportMapper(Component):
_name = 'opencart.product.template.import.mapper'
_inherit = 'opencart.import.mapper'
_apply_on = ['opencart.product.template']
@mapping
def backend_id(self, record):
return {'backend_id': self.backend_record.id}
@mapping
def name(self, record):
name = record.get('product_description', [{}])[0].get('name', record.get('id'))
return {'name': unescape(name)}
# TODO more fields like pricing....
@mapping
def product_type(self, record):
return {'type': 'product' if record.get('shipping') else 'service'}
@only_create
@mapping
def existing_product(self, record):
product_template = self.env['product.template']
template = product_template.browse()
if record.get('sku'):
template = product_template.search([('default_code', '=', record.get('sku'))], limit=1)
if not template and record.get('model'):
template = product_template.search([('default_code', '=', record.get('model'))], limit=1)
if not template and record.get('name'):
name = record.get('product_description', [{}])[0].get('name')
if name:
template = product_template.search([('name', '=', unescape(name))], limit=1)
return {'odoo_id': template.id}
class ProductImporter(Component):
_name = 'opencart.product.template.importer'
_inherit = 'opencart.importer'
_apply_on = ['opencart.product.template']
def _create(self, data):
binding = super(ProductImporter, self)._create(data)
self.backend_record.add_checkpoint(binding)
return binding
def _after_import(self, binding):
self._sync_options(binding)
def _sync_options(self, binding):
existing_option_values = binding.opencart_attribute_value_ids
mapped_option_values = binding.opencart_attribute_value_ids.browse()
record = self.opencart_record
backend = self.backend_record
for option in record.get('options', []):
for record_option_value in option.get('option_value', []):
option_value = existing_option_values.filtered(lambda v: v.external_id == record_option_value['product_option_value_id'])
name = unescape(record_option_value.get('name', ''))
if not option_value:
option_value = existing_option_values.create({
'backend_id': backend.id,
'external_id': record_option_value['product_option_value_id'],
'opencart_name': name,
'opencart_product_tmpl_id': binding.id,
})
# Keep options consistent with Opencart by renaming them
if option_value.opencart_name != name:
option_value.opencart_name = name
mapped_option_values += option_value
to_unlink = existing_option_values - mapped_option_values
to_unlink.unlink()

View File

@@ -316,7 +316,11 @@ class SaleOrderImporter(Component):
return binding
def _import_dependencies(self):
record = self.opencart_record
self._import_addresses()
for product in record.get('products', []):
if 'product_id' in product and product['product_id']:
self._import_dependency(product['product_id'], 'opencart.product.template')
class SaleOrderLineImportMapper(Component):
@@ -330,33 +334,18 @@ class SaleOrderLineImportMapper(Component):
('order_product_id', 'external_id'),
]
def _finalize_product_values(self, record, values):
# This would be a good place to create a vendor or add a route...
return values
def _product_values(self, record):
reference = record['model']
values = {
'default_code': reference,
'name': unescape(record.get('name', reference)), # unknown if other fields, but have observed &quot; in product names
'type': 'product',
'list_price': record.get('price', 0.0),
'categ_id': self.backend_record.product_categ_id.id,
}
return self._finalize_product_values(record, values)
@mapping
def name(self, record):
return {'name': unescape(record['name'])}
@mapping
def product_id(self, record):
reference = record['model']
product = self.env['product.product'].search([
('default_code', '=', reference)
], limit=1)
if not product:
# we could use a record like (0, 0, values)
product = self.env['product.product'].create(self._product_values(record))
product_id = record['product_id']
binder = self.binder_for('opencart.product.template')
# do not unwrap, because it would be a product.template, but I need a specific variant
opencart_product_template = binder.to_internal(product_id, unwrap=False)
if record.get('option'):
product = opencart_product_template.opencart_sale_get_combination(record.get('option'))
else:
product = opencart_product_template.odoo_id.product_variant_id
return {'product_id': product.id, 'product_uom': product.uom_id.id}