mirror of
https://gitlab.com/hibou-io/hibou-odoo/suite.git
synced 2025-01-20 12:37:31 +02:00
[IMP] connector_opencart: skip canceled orders, add custom order line options
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
from odoo import api, fields, models
|
from odoo import api, fields, models
|
||||||
|
from odoo.exceptions import UserError
|
||||||
from odoo.addons.queue_job.exception import NothingToDoJob, RetryableJobError
|
from odoo.addons.queue_job.exception import NothingToDoJob, RetryableJobError
|
||||||
from odoo.addons.component.core import Component
|
from odoo.addons.component.core import Component
|
||||||
|
|
||||||
@@ -17,7 +18,57 @@ class OpencartProductTemplate(models.Model):
|
|||||||
'opencart_product_tmpl_id',
|
'opencart_product_tmpl_id',
|
||||||
string='Opencart Product Attribute Values')
|
string='Opencart Product Attribute Values')
|
||||||
|
|
||||||
|
def opencart_sale_line_custom_value_commands(self, options):
|
||||||
|
"""Receives 'custom options' and returns commands for SO lines to link to.
|
||||||
|
This method will setup the product template to support the supplied commands."""
|
||||||
|
commands = []
|
||||||
|
for option in options:
|
||||||
|
c_attr_name = option.get('name')
|
||||||
|
c_attr_value = option.get('value')
|
||||||
|
if not all((c_attr_name, c_attr_value)):
|
||||||
|
raise UserError('Mapping sale order custom values cannot happen if the option is missing name or value. Original option payload: ' + str(option))
|
||||||
|
# note this is a weak binding because the name could change, even due to translation
|
||||||
|
attr_line = self.odoo_id.attribute_line_ids.filtered(lambda l: l.attribute_id.name == c_attr_name)
|
||||||
|
if not attr_line:
|
||||||
|
attribute = self.env['product.attribute'].search([('name', '=', c_attr_name)], limit=1)
|
||||||
|
if not attribute:
|
||||||
|
# we will have to assume some things about the attribute
|
||||||
|
attribute = self.env['product.attribute'].create({
|
||||||
|
'name': c_attr_name,
|
||||||
|
'create_variant': 'no_variant',
|
||||||
|
# 'visibility': 'hidden', # TODO who adds this field
|
||||||
|
'value_ids': [(0, 0, {
|
||||||
|
'attribute_id': attribute.id,
|
||||||
|
'name': 'opencart-custom', # people can rename it.
|
||||||
|
'is_custom': True,
|
||||||
|
})],
|
||||||
|
})
|
||||||
|
value = attribute.value_ids.filtered('is_custom')
|
||||||
|
if len(value) > 1:
|
||||||
|
value = value[0]
|
||||||
|
# while we may not have a value here, the exception should tell us as much as us raising one ourself
|
||||||
|
# now we have an attribute, we can make an attribute value line with one custom va
|
||||||
|
self.odoo_id.write({
|
||||||
|
'attribute_line_ids': [(0, 0, {
|
||||||
|
'attribute_id': attribute.id,
|
||||||
|
'value_ids': [(4, value.id)]
|
||||||
|
})]
|
||||||
|
})
|
||||||
|
attr_line = self.odoo_id.attribute_line_ids.filtered(lambda l: l.attribute_id == attribute)
|
||||||
|
# now we have a product template attribute line, it should have a custom value
|
||||||
|
attr_line_value = attr_line.product_template_value_ids.filtered(lambda v: v.is_custom)
|
||||||
|
if len(attr_line_value) > 1:
|
||||||
|
attr_line_value = attr_line_value[0]
|
||||||
|
# again we may not have a value, but the exception will be on the SOL side
|
||||||
|
commands.append((0, 0, {
|
||||||
|
'custom_product_template_attribute_value_id': attr_line_value.id,
|
||||||
|
'custom_value': c_attr_value,
|
||||||
|
}))
|
||||||
|
return commands
|
||||||
|
|
||||||
def opencart_sale_get_combination(self, options, reentry=False):
|
def opencart_sale_get_combination(self, options, reentry=False):
|
||||||
|
# note we EXPECT every option passed in here to have a 'product_option_value_id'
|
||||||
|
# filtering them out at this step is not desirable because of the recursive entry with options
|
||||||
if not options:
|
if not options:
|
||||||
return self.odoo_id.product_variant_id
|
return self.odoo_id.product_variant_id
|
||||||
selected_attribute_values = self.env['product.template.attribute.value']
|
selected_attribute_values = self.env['product.template.attribute.value']
|
||||||
|
|||||||
@@ -2,13 +2,14 @@
|
|||||||
|
|
||||||
from copy import copy
|
from copy import copy
|
||||||
from html import unescape
|
from html import unescape
|
||||||
|
from datetime import datetime, timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from odoo import fields, _
|
from odoo import fields, _
|
||||||
from odoo.addons.component.core import Component
|
from odoo.addons.component.core import Component
|
||||||
from odoo.addons.connector.components.mapper import mapping
|
from odoo.addons.connector.components.mapper import mapping
|
||||||
from odoo.exceptions import ValidationError
|
from odoo.exceptions import ValidationError
|
||||||
from odoo.addons.queue_job.exception import RetryableJobError
|
from odoo.addons.queue_job.exception import RetryableJobError, NothingToDoJob, FailedJobError
|
||||||
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
@@ -131,7 +132,6 @@ class SaleOrderImportMapper(Component):
|
|||||||
onchange = self.component(
|
onchange = self.component(
|
||||||
usage='ecommerce.onchange.manager.sale.order'
|
usage='ecommerce.onchange.manager.sale.order'
|
||||||
)
|
)
|
||||||
# will I need more?!
|
|
||||||
return onchange.play(values, values['opencart_order_line_ids'])
|
return onchange.play(values, values['opencart_order_line_ids'])
|
||||||
|
|
||||||
@mapping
|
@mapping
|
||||||
@@ -228,8 +228,8 @@ class SaleOrderImporter(Component):
|
|||||||
return _('Already imported')
|
return _('Already imported')
|
||||||
|
|
||||||
def _before_import(self):
|
def _before_import(self):
|
||||||
# Check if status is ok, etc. on self.opencart_record
|
rules = self.component(usage='sale.import.rule')
|
||||||
pass
|
rules.check(self.opencart_record)
|
||||||
|
|
||||||
def _create_partner(self, values):
|
def _create_partner(self, values):
|
||||||
return self.env['res.partner'].create(values)
|
return self.env['res.partner'].create(values)
|
||||||
@@ -422,6 +422,85 @@ class SaleOrderImporter(Component):
|
|||||||
raise RetryableJobError('Products need setup. OpenCart Product IDs:' + str(products_need_setup), seconds=3600)
|
raise RetryableJobError('Products need setup. OpenCart Product IDs:' + str(products_need_setup), seconds=3600)
|
||||||
|
|
||||||
|
|
||||||
|
class SaleImportRule(Component):
|
||||||
|
_name = 'opencart.sale.import.rule'
|
||||||
|
_inherit = 'base.opencart.connector'
|
||||||
|
_apply_on = 'opencart.sale.order'
|
||||||
|
_usage = 'sale.import.rule'
|
||||||
|
|
||||||
|
_status_no_import = [
|
||||||
|
'Canceled',
|
||||||
|
'Canceled Reversal',
|
||||||
|
'Chargeback',
|
||||||
|
'Denied',
|
||||||
|
'Expired',
|
||||||
|
'Failed',
|
||||||
|
'Refunded',
|
||||||
|
'Reversed',
|
||||||
|
'Voided',
|
||||||
|
]
|
||||||
|
|
||||||
|
_status_import_later = [
|
||||||
|
'Pending',
|
||||||
|
'Processing',
|
||||||
|
]
|
||||||
|
|
||||||
|
def _rule_always(self, record, method):
|
||||||
|
""" Always import the order """
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _rule_check_status(self, record, method):
|
||||||
|
if record['order_status'] in self._status_import_later:
|
||||||
|
raise RetryableJobError('Order %s is in %s and will be re-tried later.')
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _rule_never(self, record, method):
|
||||||
|
""" Never import the order """
|
||||||
|
raise NothingToDoJob('Orders with payment method %s are never imported.' % method.name)
|
||||||
|
|
||||||
|
# currently, no good way of knowing if an order is paid or authorized
|
||||||
|
# we use these both to indicate you only want to import it if it makes it
|
||||||
|
# past a pending/processing state (the order itself)
|
||||||
|
_rules = {'always': _rule_always,
|
||||||
|
'paid': _rule_check_status,
|
||||||
|
'authorized': _rule_check_status,
|
||||||
|
'never': _rule_never,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _rule_global(self, record, method):
|
||||||
|
""" Rule always executed, whichever is the selected rule.
|
||||||
|
Discards orders based on it being in a canceled state or status.
|
||||||
|
Discards orders based on order date being outside of import window."""
|
||||||
|
order_id = record['order_id']
|
||||||
|
order_status = record['order_status']
|
||||||
|
if order_status in self._status_no_import:
|
||||||
|
raise NothingToDoJob('Order %s not imported for status %s' % (order_id, order_status))
|
||||||
|
max_days = method.days_before_cancel
|
||||||
|
if max_days:
|
||||||
|
order_date = self.backend_record.date_to_odoo(record['date_added'])
|
||||||
|
if order_date + timedelta(days=max_days) < datetime.now():
|
||||||
|
raise NothingToDoJob('Import of the order %s canceled '
|
||||||
|
'because it has not been paid since %d '
|
||||||
|
'days' % (order_id, max_days))
|
||||||
|
|
||||||
|
def check(self, record):
|
||||||
|
""" Check whether the current sale order should be imported
|
||||||
|
or not. It will actually use the payment method configuration
|
||||||
|
and see if the choosed rule is fullfilled.
|
||||||
|
:returns: True if the sale order should be imported
|
||||||
|
:rtype: boolean
|
||||||
|
"""
|
||||||
|
record_method = record['payment_method']
|
||||||
|
method = self.env['account.payment.mode'].search(
|
||||||
|
[('name', '=', record_method)],
|
||||||
|
limit=1,
|
||||||
|
)
|
||||||
|
if not method:
|
||||||
|
raise FailedJobError('Payment Mode named "%s", cannot be found.' % (record_method, ))
|
||||||
|
self._rule_global(record, method)
|
||||||
|
self._rules[method.import_rule](self, record, method)
|
||||||
|
|
||||||
|
|
||||||
class SaleOrderLineImportMapper(Component):
|
class SaleOrderLineImportMapper(Component):
|
||||||
|
|
||||||
_name = 'opencart.sale.order.line.mapper'
|
_name = 'opencart.sale.order.line.mapper'
|
||||||
@@ -433,10 +512,8 @@ class SaleOrderLineImportMapper(Component):
|
|||||||
('order_product_id', 'external_id'),
|
('order_product_id', 'external_id'),
|
||||||
]
|
]
|
||||||
|
|
||||||
@mapping
|
# Note mapping for name is removed due to desire to get
|
||||||
def name(self, record):
|
# custom attr values to display via computed sol description
|
||||||
return {'name': unescape(record['name'])}
|
|
||||||
|
|
||||||
@mapping
|
@mapping
|
||||||
def product_id(self, record):
|
def product_id(self, record):
|
||||||
product_id = record['product_id']
|
product_id = record['product_id']
|
||||||
@@ -445,5 +522,14 @@ class SaleOrderLineImportMapper(Component):
|
|||||||
# connector bindings are found with `active_test=False` but that also means computed fields
|
# connector bindings are found with `active_test=False` but that also means computed fields
|
||||||
# like `product.template.product_variant_id` could find different products because of archived variants
|
# like `product.template.product_variant_id` could find different products because of archived variants
|
||||||
opencart_product_template = binder.to_internal(product_id, unwrap=False).with_context(active_test=True)
|
opencart_product_template = binder.to_internal(product_id, unwrap=False).with_context(active_test=True)
|
||||||
product = opencart_product_template.opencart_sale_get_combination(record.get('option'))
|
line_options = record.get('option') or []
|
||||||
return {'product_id': product.id, 'product_uom': product.uom_id.id}
|
options_for_product = list(filter(lambda o: o.get('product_option_value_id'), line_options))
|
||||||
|
options_for_line = list(filter(lambda o: not o.get('product_option_value_id'), line_options))
|
||||||
|
product = opencart_product_template.opencart_sale_get_combination(options_for_product)
|
||||||
|
|
||||||
|
custom_option_commands = opencart_product_template.opencart_sale_line_custom_value_commands(options_for_line)
|
||||||
|
return {
|
||||||
|
'product_id': product.id,
|
||||||
|
'product_uom': product.uom_id.id,
|
||||||
|
'product_custom_attribute_value_ids': custom_option_commands,
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user