Merge branch 'imp/12.0/connector_opencart' into 12.0

# Conflicts:
#	connector_opencart/models/opencart/backend.py
#	connector_opencart/models/product/common.py
#	connector_opencart/models/product/importer.py
#	connector_opencart/models/sale_order/importer.py
#	connector_opencart/views/opencart_product_views.xml
This commit is contained in:
Jared Kipe
2020-11-21 06:34:56 -08:00
16 changed files with 425 additions and 82 deletions

View File

@@ -3,7 +3,7 @@
{
'name': 'Opencart Connector',
'version': '12.0.1.0.0',
'version': '12.0.1.1.0',
'category': 'Connector',
'depends': [
'account',
@@ -17,9 +17,12 @@
'license': 'AGPL-3',
'website': 'https://hibou.io',
'data': [
'data/connector_opencart_data.xml',
'security/ir.model.access.csv',
'views/delivery_views.xml',
'views/opencart_backend_views.xml',
'views/opencart_product_views.xml',
'views/product_views.xml',
],
'installable': True,
'application': False,

View File

@@ -5,6 +5,9 @@ import requests
from urllib.parse import urlencode
from json import loads, dumps
import logging
_logger = logging.getLogger(__name__)
class Opencart:
@@ -22,6 +25,10 @@ class Opencart:
def stores(self):
return Stores(connection=self)
@property
def products(self):
return Products(connection=self)
def get_headers(self, url, method):
headers = {}
if method in ('POST', 'PUT', ):
@@ -33,11 +40,19 @@ class Opencart:
if params:
encoded_url += '?%s' % urlencode(params)
headers = self.get_headers(encoded_url, method)
_logger.debug('send_request method: %s url: %s headers: %s params: %s body: %s' % (
method,
url,
headers,
params,
body
))
if method == 'GET':
return loads(self.session.get(url, params=params, headers=headers).text)
result_text = self.session.get(url, params=params, headers=headers).text
elif method == 'PUT' or method == 'POST':
return loads(self.session.put(url, data=body, headers=headers).text)
result_text = self.session.put(url, data=body, headers=headers).text
_logger.debug('raw_text: ' + str(result_text))
return loads(result_text)
class Resource:
@@ -138,3 +153,14 @@ class Stores(Resource):
def get(self, id):
url = self.url + ('/%s' % id)
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)

View File

@@ -20,4 +20,6 @@ class OpencartModelBinder(Component):
'opencart.sale.order',
'opencart.sale.order.line',
'opencart.stock.picking',
'opencart.product.template',
'opencart.product.template.attribute.value',
]

View File

@@ -88,7 +88,8 @@ class OpencartImporter(AbstractComponent):
if not external_id:
return
binder = self.binder_for(binding_model)
if always or not binder.to_internal(external_id):
record = binder.to_internal(external_id)
if always or not record:
if importer is None:
importer = self.component(usage='record.importer',
model_name=binding_model)
@@ -99,6 +100,13 @@ class OpencartImporter(AbstractComponent):
'Dependency import of %s(%s) has been ignored.',
binding_model._name, external_id
)
return True
if binding_model == 'opencart.product.template' and record.backend_id.so_require_product_setup:
# Though this is not the "right" place to do this,
# we need to return True if there is a checkpoint for a product.
if record.backend_id.find_checkpoint(record):
return True
return False
def _import_dependencies(self):
""" Import the dependencies for the record

View File

@@ -4,13 +4,13 @@
<record model="ir.cron" id="ir_cron_import_sale_orders" forcecreate="True">
<field name="name">Opencart - Import Sales Orders</field>
<field eval="False" name="active"/>
<field name="active" eval="False"/>
<field name="state">code</field>
<field name="user_id" ref="base.user_root"/>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="interval_type">hours</field>
<field name="numbercall">-1</field>
<field eval="False" name="doall"/>
<field name="doall" eval="False"/>
<field ref="connector_opencart.model_opencart_backend" name="model_id"/>
<field name="code">model._scheduler_import_sale_orders()</field>
</record>
@@ -27,26 +27,7 @@ Check your taxes and fiscal positions configuration and correct them if necessar
<field name="sequence">30</field>
<field name="model">sale.order</field>
<field name="rule_group">sale</field>
<field name="code">if sale.opencart_bind_ids and abs(sale.amount_total - sale.opencart_bind_ids[0].total_amount) >= 0.01:
failed = True</field>
<field name="active" eval="True"/>
</record>
<record id="excep_wrong_total_amount_tax" model="exception.rule">
<field name="name">Total Tax Amount differs from Opencart</field>
<field name="description">The tax amount computed in Odoo doesn't match with the tax amount in Opencart.
Cause:
The taxes are probably different between Odoo and Opencart. A fiscal position could have changed the final price.
Resolution:
Check your taxes and fiscal positions configuration and correct them if necessary.</field>
<field name="sequence">30</field>
<field name="model">sale.order</field>
<field name="rule_group">sale</field>
<field name="code"># By default, a cent of difference for the tax amount is allowed, feel free to customise it in your own module
if sale.opencart_bind_ids and abs(sale.amount_tax - sale.opencart_bind_ids[0].total_amount_tax) > 0.01:
failed = True</field>
<field name="code">failed = sale.opencart_bind_ids and abs(sale.amount_total - sale.opencart_bind_ids[0].total_amount) >= 0.01</field>
<field name="active" eval="True"/>
</record>

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__)
@@ -62,6 +62,8 @@ class OpencartBackend(models.Model):
"in Odoo.",
)
# payment_mode_id = fields.Many2one(comodel_name='account.payment.mode', string="Payment Mode")
coupon_product_id = fields.Many2one(comodel_name='product.product', string='Coupon Product',
help='Product to represent coupon discounts.')
# New Product fields.
product_categ_id = fields.Many2one(comodel_name='product.category', string='Product Category',
@@ -71,6 +73,9 @@ class OpencartBackend(models.Model):
string='Import sale orders after id',
)
so_require_product_setup = fields.Boolean(string='SO Require Product Setup',
help='Prevents SO from being confirmed (failed queue job), if one or more products has an open checkpoint.')
@contextmanager
@api.multi
def work_on(self, model_name, **kwargs):
@@ -80,6 +85,27 @@ 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 find_checkpoint(self, record):
self.ensure_one()
record.ensure_one()
checkpoint_model = self.env['connector.checkpoint']
model_model = self.env['ir.model']
model = model_model.search([('model', '=', record._name)], limit=1)
return checkpoint_model.search([
('backend_id', '=', '%s,%s' % (self._name, self.id)),
('model_id', '=', model.id),
('record_id', '=', record.id),
('state', '=', 'need_review'),
], limit=1)
@api.multi
def synchronize_metadata(self):
try:

View File

@@ -50,6 +50,8 @@ class OpencartStore(models.Model):
"order 36071 in Opencart, will be named 'OC-36071' "
"in Odoo. (overridden from backend)",
)
coupon_product_id = fields.Many2one(comodel_name='product.product', string='Coupon Product',
help='Product to represent coupon discounts.')
class OpencartStoreAdapter(Component):

View File

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

View File

@@ -0,0 +1,81 @@
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='cascade') # cascade so that you can delete an Odoo product that was created by connector
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 (%s) has option (%s) "%s" that is not mapped to an Odoo Attribute Value.' % (self, 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
product = self.odoo_id._create_product_variant(selected_attribute_values, log_warning=True)
if not product:
raise Exception('No product can be created for selected attribute values, check logs. ' + str(selected_attribute_values))
return product
class ProductTemplate(models.Model):
_inherit = 'product.template'
opencart_sku = fields.Char('Opencart SKU')
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,91 @@
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'}
@mapping
def opencart_sku(self, record):
sku = str(record.get('model') or record.get('sku') or '').strip()
return {'opencart_sku': sku}
@only_create
@mapping
def existing_product(self, record):
product_template = self.env['product.template']
template = product_template.browse()
if record.get('model'):
model = str(record.get('model') or '').strip()
# Try to match our own field
template = product_template.search([('opencart_sku', '=', model)], limit=1)
if not template:
# Try to match the default_code
template = product_template.search([('default_code', '=', model)], limit=1)
if not template and record.get('sku'):
sku = str(record.get('sku') or '').strip()
template = product_template.search([('opencart_sku', '=', sku)], limit=1)
if not template:
template = product_template.search([('default_code', '=', sku)], 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 == str(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

@@ -2,11 +2,13 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from copy import deepcopy, copy
from html import unescape
from odoo import fields, _
from odoo.addons.component.core import Component
from odoo.addons.connector.components.mapper import mapping
from odoo.exceptions import ValidationError
from odoo.addons.queue_job.exception import RetryableJobError
class SaleOrderBatchImporter(Component):
@@ -52,31 +54,55 @@ class SaleOrderImportMapper(Component):
direct = [('order_id', 'external_id'),
('store_id', 'store_id'),
# ('customerOrderId', 'customer_order_id'),
]
children = [('products', 'opencart_order_line_ids', 'opencart.sale.order.line'),
]
# def _map_child(self, map_record, from_attr, to_attr, model_name):
# return super(SaleOrderImportMapper, self)._map_child(map_record, from_attr, to_attr, model_name)
def _add_coupon_lines(self, map_record, values):
# Data from API
# 'coupons': [{'amount': '7.68', 'code': '1111'}],
record = map_record.source
coupons = record.get('coupons')
if not coupons:
return values
coupon_product = self.options.store.coupon_product_id or self.backend_record.coupon_product_id
if not coupon_product:
coupon_product = self.env.ref('connector_ecommerce.product_product_discount', raise_if_not_found=False)
if not coupon_product:
raise ValueError('Coupon %s on order requires coupon product in configuration.' % (coupons, ))
for coupon in coupons:
line_builder = self.component(usage='order.line.builder')
line_builder.price_unit = -float(coupon.get('amount', 0.0))
line_builder.product = coupon_product
# `order.line.builder` does not allow naming.
line_values = line_builder.get_line()
code = coupon.get('code')
if code:
line_values['name'] = '%s Code: %s' % (coupon_product.name, code)
values['order_line'].append((0, 0, line_values))
return values
def _add_shipping_line(self, map_record, values):
record = map_record.source
line_builder = self.component(usage='order.line.builder.shipping')
line_builder.price_unit = 0.0
line_builder.price_unit = record.get('shipping_exclude_tax', 0.0)
if values.get('carrier_id'):
carrier = self.env['delivery.carrier'].browse(values['carrier_id'])
line_builder.product = carrier.product_id
line = (0, 0, line_builder.get_line())
values['order_line'].append(line)
line = (0, 0, line_builder.get_line())
values['order_line'].append(line)
return values
def finalize(self, map_record, values):
values.setdefault('order_line', [])
self._add_coupon_lines(map_record, values)
self._add_shipping_line(map_record, values)
values.update({
'partner_id': self.options.partner_id,
@@ -120,9 +146,8 @@ class SaleOrderImportMapper(Component):
[('name', '=', record_method)],
limit=1,
)
assert method, ("method %s should exist because the import fails "
"in SaleOrderImporter._before_import when it is "
" missing" % record_method)
if not method:
raise ValueError('Payment Mode named "%s", cannot be found.' % (record_method, ))
return {'payment_mode_id': method.id}
@mapping
@@ -138,8 +163,11 @@ class SaleOrderImportMapper(Component):
return {'warehouse_id': warehouse.id}
@mapping
def shipping_method(self, record):
method = record['shipping_method'] or ''
def shipping_code(self, record):
method = record.get('shipping_code') or record.get('shipping_method')
if not method:
return {'carrier_id': False}
carrier_domain = [('opencart_code', '=', method.strip())]
company = self.options.store.company_id or self.backend_record.company_id
if company:
@@ -148,8 +176,8 @@ class SaleOrderImportMapper(Component):
]
carrier = self.env['delivery.carrier'].search(carrier_domain, limit=1)
if not carrier:
raise ValueError('Delivery Carrier for methodCode "%s", cannot be found.' % (method, ))
return {'carrier_id': carrier.id, 'shipping_method_code': method}
raise ValueError('Delivery Carrier for method Code "%s", cannot be found.' % (method, ))
return {'carrier_id': carrier.id}
@mapping
def company_id(self, record):
@@ -186,6 +214,9 @@ class SaleOrderImporter(Component):
def _partner_matches(self, partner, values):
for key, value in values.items():
if key in ('active', 'parent_id', 'type'):
continue
if key == 'state_id':
if value != partner.state_id.id:
return False
@@ -197,7 +228,7 @@ class SaleOrderImporter(Component):
return True
def _make_partner_name(self, firstname, lastname):
name = (str(firstname) + ' ' + str(lastname)).strip()
name = (str(firstname or '').strip() + ' ' + str(lastname or '').strip()).strip()
if not name:
return 'Undefined'
return name
@@ -233,24 +264,31 @@ class SaleOrderImporter(Component):
], limit=1)
return {
'email': email,
'name': name,
'phone': phone,
'street': street,
'street2': street2,
'zip': zip_,
'city': city,
'email': email.strip(),
'name': name.strip(),
'phone': phone.strip(),
'street': street.strip(),
'street2': street2.strip(),
'zip': zip_.strip(),
'city': city.strip(),
'state_id': state.id,
'country_id': country.id,
}
def _import_addresses(self):
record = self.opencart_record
partner_values = self._get_partner_values()
partner = self.env['res.partner'].search([
partners = self.env['res.partner'].search([
('email', '=', partner_values['email']),
], limit=1)
'|', ('active', '=', False), ('active', '=', True),
], order='active DESC, id ASC')
partner = None
for possible in partners:
if self._partner_matches(possible, partner_values):
partner = possible
break
if not partner and partners:
partner = partners[0]
if not partner:
# create partner.
@@ -258,18 +296,25 @@ class SaleOrderImporter(Component):
if not self._partner_matches(partner, partner_values):
partner_values['parent_id'] = partner.id
partner_values['active'] = False
shipping_partner = self._create_partner(copy(partner_values))
shipping_values = copy(partner_values)
shipping_values['type'] = 'delivery'
shipping_partner = self._create_partner(shipping_values)
else:
shipping_partner = partner
invoice_values = self._get_partner_values(info_string='payment_')
invoice_values['type'] = 'invoice'
if (not self._partner_matches(partner, invoice_values)
and not self._partner_matches(shipping_partner, invoice_values)):
partner_values['parent_id'] = partner.id
partner_values['active'] = False
invoice_partner = self._create_partner(copy(invoice_values))
# Try to find existing invoice address....
for possible in partners:
if self._partner_matches(possible, invoice_values):
invoice_partner = possible
break
else:
invoice_values['parent_id'] = partner.id
invoice_partner = self._create_partner(copy(invoice_values))
elif self._partner_matches(partner, invoice_values):
invoice_partner = partner
elif self._partner_matches(shipping_partner, invoice_values):
@@ -317,7 +362,18 @@ class SaleOrderImporter(Component):
return binding
def _import_dependencies(self):
record = self.opencart_record
self._import_addresses()
products_need_setup = []
for product in record.get('products', []):
if 'product_id' in product and product['product_id']:
needs_product_setup = self._import_dependency(product['product_id'], 'opencart.product.template')
if needs_product_setup:
products_need_setup.append(product['product_id'])
if products_need_setup and self.backend_record.so_require_product_setup:
# There are products that were either just imported, or
raise RetryableJobError('Products need setup. OpenCart Product IDs:' + str(products_need_setup), seconds=3600)
class SaleOrderLineImportMapper(Component):
@@ -328,33 +384,21 @@ class SaleOrderLineImportMapper(Component):
direct = [('quantity', 'product_uom_qty'),
('price', 'price_unit'),
('name', 'name'),
('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': record.get('name', reference),
'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}

View File

@@ -4,6 +4,8 @@
"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_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_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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
4 access_opencart_binding opencart_binding connector manager model_opencart_binding connector.group_connector_manager 1 1 1 1
5 access_opencart_sale_order opencart_sale_order connector manager model_opencart_sale_order connector.group_connector_manager 1 1 1 1
6 access_opencart_sale_order_line opencart_sale_order_line connector manager model_opencart_sale_order_line connector.group_connector_manager 1 1 1 1
7 access_opencart_product_template opencart_product_template connector manager model_opencart_product_template connector.group_connector_manager 1 1 1 1
8 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
9 access_opencart_stock_picking opencart_stock_picking connector manager model_opencart_stock_picking connector.group_connector_manager 1 1 1 1
10 access_opencart_sale_order_sale_salesman opencart_sale_order model_opencart_sale_order sales_team.group_sale_salesman 1 0 0 0
11 access_opencart_sale_order_sale_manager opencart_sale_order model_opencart_sale_order sales_team.group_sale_manager 1 1 1 1

View File

@@ -37,6 +37,8 @@
</group>
<group name="product_configuration" string="Product Defaults">
<field name="product_categ_id"/>
<field name="coupon_product_id"/>
<field name="so_require_product_setup"/>
</group>
</group>
<notebook name="import_config">
@@ -131,6 +133,7 @@
<field name="fiscal_position_id"/>
<field name="team_id"/>
<field name="sale_prefix"/>
<field name="coupon_product_id"/>
</group>
</group>
</sheet>

View File

@@ -0,0 +1,56 @@
<?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"/>
<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>
<record id="view_opencart_product_template_tree" model="ir.ui.view">
<field name="name">opencart.product.template.tree</field>
<field name="model">opencart.product.template</field>
<field name="arch" type="xml">
<tree string="Opencart Products">
<field name="backend_id"/>
<field name="external_id" string="Opencart ID"/>
<field name="odoo_id" string="Product Template"/>
<field name="create_date"/>
<field name="write_date"/>
</tree>
</field>
</record>
<record id="action_opencart_product_template" model="ir.actions.act_window">
<field name="name">Opencart Products</field>
<field name="res_model">opencart.product.template</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="view_opencart_product_template_tree"/>
</record>
<menuitem id="menu_opencart_product"
name="Products"
parent="menu_opencart_root"
sequence="50"
action="action_opencart_product_template"/>
</odoo>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="product_template_only_form_view_inherit" model="ir.ui.view">
<field name="name">product.template.product.form.inherit</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_only_form_view"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='barcode']" position="after">
<field name="opencart_sku"/>
</xpath>
</field>
</record>
</odoo>