mirror of
https://gitlab.com/hibou-io/hibou-odoo/suite.git
synced 2025-01-20 12:37:31 +02:00
IMP connector_opencart Add bindings between Opencart Product Option Values and Odoo's Product Template Attribute Values
This commit is contained in:
@@ -21,6 +21,7 @@
|
|||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
'views/delivery_views.xml',
|
'views/delivery_views.xml',
|
||||||
'views/opencart_backend_views.xml',
|
'views/opencart_backend_views.xml',
|
||||||
|
'views/opencart_product_views.xml',
|
||||||
],
|
],
|
||||||
'installable': True,
|
'installable': True,
|
||||||
'application': False,
|
'application': False,
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ class Opencart:
|
|||||||
def stores(self):
|
def stores(self):
|
||||||
return Stores(connection=self)
|
return Stores(connection=self)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def products(self):
|
||||||
|
return Products(connection=self)
|
||||||
|
|
||||||
def get_headers(self, url, method):
|
def get_headers(self, url, method):
|
||||||
headers = {}
|
headers = {}
|
||||||
if method in ('POST', 'PUT', ):
|
if method in ('POST', 'PUT', ):
|
||||||
@@ -138,3 +142,14 @@ class Stores(Resource):
|
|||||||
def get(self, id):
|
def get(self, id):
|
||||||
url = self.url + ('/%s' % id)
|
url = self.url + ('/%s' % id)
|
||||||
return self.connection.send_request(method='GET', url=url)
|
return self.connection.send_request(method='GET', url=url)
|
||||||
|
|
||||||
|
|
||||||
|
class Products(Resource):
|
||||||
|
"""
|
||||||
|
Retrieves Product details
|
||||||
|
"""
|
||||||
|
path = 'products'
|
||||||
|
|
||||||
|
def get(self, id):
|
||||||
|
url = self.url + ('/%s' % id)
|
||||||
|
return self.connection.send_request(method='GET', url=url)
|
||||||
|
|||||||
@@ -20,4 +20,6 @@ class OpencartModelBinder(Component):
|
|||||||
'opencart.sale.order',
|
'opencart.sale.order',
|
||||||
'opencart.sale.order.line',
|
'opencart.sale.order.line',
|
||||||
'opencart.stock.picking',
|
'opencart.stock.picking',
|
||||||
|
'opencart.product.template',
|
||||||
|
'opencart.product.template.attribute.value',
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from . import delivery
|
from . import delivery
|
||||||
from . import opencart
|
from . import opencart
|
||||||
|
from . import product
|
||||||
from . import sale_order
|
from . import sale_order
|
||||||
from . import stock_picking
|
from . import stock_picking
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
||||||
from odoo import api, fields, models, _
|
from odoo import api, fields, models, _
|
||||||
from odoo.exceptions import UserError
|
from odoo.exceptions import UserError
|
||||||
|
from odoo.addons.connector.models.checkpoint import add_checkpoint
|
||||||
from ...components.api.opencart import Opencart
|
from ...components.api.opencart import Opencart
|
||||||
|
|
||||||
_logger = getLogger(__name__)
|
_logger = getLogger(__name__)
|
||||||
@@ -80,6 +80,13 @@ class OpencartBackend(models.Model):
|
|||||||
with _super.work_on(model_name, opencart_api=opencart_api, **kwargs) as work:
|
with _super.work_on(model_name, opencart_api=opencart_api, **kwargs) as work:
|
||||||
yield 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
|
@api.multi
|
||||||
def synchronize_metadata(self):
|
def synchronize_metadata(self):
|
||||||
try:
|
try:
|
||||||
|
|||||||
2
connector_opencart/models/product/__init__.py
Normal file
2
connector_opencart/models/product/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from . import common
|
||||||
|
from . import importer
|
||||||
77
connector_opencart/models/product/common.py
Normal file
77
connector_opencart/models/product/common.py
Normal 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')
|
||||||
78
connector_opencart/models/product/importer.py
Normal file
78
connector_opencart/models/product/importer.py
Normal 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()
|
||||||
@@ -316,7 +316,11 @@ class SaleOrderImporter(Component):
|
|||||||
return binding
|
return binding
|
||||||
|
|
||||||
def _import_dependencies(self):
|
def _import_dependencies(self):
|
||||||
|
record = self.opencart_record
|
||||||
self._import_addresses()
|
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):
|
class SaleOrderLineImportMapper(Component):
|
||||||
@@ -330,33 +334,18 @@ class SaleOrderLineImportMapper(Component):
|
|||||||
('order_product_id', 'external_id'),
|
('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 " 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
|
@mapping
|
||||||
def name(self, record):
|
def name(self, record):
|
||||||
return {'name': unescape(record['name'])}
|
return {'name': unescape(record['name'])}
|
||||||
|
|
||||||
@mapping
|
@mapping
|
||||||
def product_id(self, record):
|
def product_id(self, record):
|
||||||
reference = record['model']
|
product_id = record['product_id']
|
||||||
product = self.env['product.product'].search([
|
binder = self.binder_for('opencart.product.template')
|
||||||
('default_code', '=', reference)
|
# do not unwrap, because it would be a product.template, but I need a specific variant
|
||||||
], limit=1)
|
opencart_product_template = binder.to_internal(product_id, unwrap=False)
|
||||||
|
if record.get('option'):
|
||||||
if not product:
|
product = opencart_product_template.opencart_sale_get_combination(record.get('option'))
|
||||||
# we could use a record like (0, 0, values)
|
else:
|
||||||
product = self.env['product.product'].create(self._product_values(record))
|
product = opencart_product_template.odoo_id.product_variant_id
|
||||||
return {'product_id': product.id, 'product_uom': product.uom_id.id}
|
return {'product_id': product.id, 'product_uom': product.uom_id.id}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
"access_opencart_binding","opencart_binding connector manager","model_opencart_binding","connector.group_connector_manager",1,1,1,1
|
"access_opencart_binding","opencart_binding connector manager","model_opencart_binding","connector.group_connector_manager",1,1,1,1
|
||||||
"access_opencart_sale_order","opencart_sale_order connector manager","model_opencart_sale_order","connector.group_connector_manager",1,1,1,1
|
"access_opencart_sale_order","opencart_sale_order connector manager","model_opencart_sale_order","connector.group_connector_manager",1,1,1,1
|
||||||
"access_opencart_sale_order_line","opencart_sale_order_line connector manager","model_opencart_sale_order_line","connector.group_connector_manager",1,1,1,1
|
"access_opencart_sale_order_line","opencart_sale_order_line connector manager","model_opencart_sale_order_line","connector.group_connector_manager",1,1,1,1
|
||||||
|
"access_opencart_product_template","opencart_product_template connector manager","model_opencart_product_template","connector.group_connector_manager",1,1,1,1
|
||||||
|
"access_opencart_product_template_attribute_value","opencart_product_template_attribute_value connector manager","model_opencart_product_template_attribute_value","connector.group_connector_manager",1,1,1,1
|
||||||
"access_opencart_stock_picking","opencart_stock_picking connector manager","model_opencart_stock_picking","connector.group_connector_manager",1,1,1,1
|
"access_opencart_stock_picking","opencart_stock_picking connector manager","model_opencart_stock_picking","connector.group_connector_manager",1,1,1,1
|
||||||
"access_opencart_sale_order_sale_salesman","opencart_sale_order","model_opencart_sale_order","sales_team.group_sale_salesman",1,0,0,0
|
"access_opencart_sale_order_sale_salesman","opencart_sale_order","model_opencart_sale_order","sales_team.group_sale_salesman",1,0,0,0
|
||||||
"access_opencart_sale_order_sale_manager","opencart_sale_order","model_opencart_sale_order","sales_team.group_sale_manager",1,1,1,1
|
"access_opencart_sale_order_sale_manager","opencart_sale_order","model_opencart_sale_order","sales_team.group_sale_manager",1,1,1,1
|
||||||
|
|||||||
|
28
connector_opencart/views/opencart_product_views.xml
Normal file
28
connector_opencart/views/opencart_product_views.xml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="view_opencart_product_template_form" model="ir.ui.view">
|
||||||
|
<field name="name">opencart.product.template.form</field>
|
||||||
|
<field name="model">opencart.product.template</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Opencart Product">
|
||||||
|
<header/>
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<field name="odoo_id" readonly="1"/>
|
||||||
|
<field name="opencart_attribute_value_ids" options="{'no_create': True}">
|
||||||
|
<tree editable="top" decoration-warning="not odoo_id">
|
||||||
|
<field name="external_id" readonly="1"/>
|
||||||
|
<field name="opencart_name" readonly="1"/>
|
||||||
|
<field name="opencart_product_tmpl_id" invisible="1"/>
|
||||||
|
<field name="product_tmpl_id" invisible="1"/>
|
||||||
|
<field name="odoo_id" domain="[('product_tmpl_id', '=', product_tmpl_id)]"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
Reference in New Issue
Block a user