mirror of
https://gitlab.com/hibou-io/hibou-odoo/suite.git
synced 2025-01-20 12:37:31 +02:00
405 lines
15 KiB
Python
405 lines
15 KiB
Python
# © 2019 Hibou Corp.
|
|
# 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):
|
|
_name = 'opencart.sale.order.batch.importer'
|
|
_inherit = 'opencart.delayed.batch.importer'
|
|
_apply_on = 'opencart.sale.order'
|
|
|
|
def _import_record(self, external_id, store_id, job_options=None, **kwargs):
|
|
if not job_options:
|
|
job_options = {
|
|
'max_retries': 0,
|
|
'priority': 5,
|
|
}
|
|
if store_id is not None:
|
|
store_binder = self.binder_for('opencart.store')
|
|
store = store_binder.to_internal(store_id)
|
|
user = store.sudo().warehouse_id.company_id.user_tech_id
|
|
if user and user != self.env.user:
|
|
# Note that this is a component, which has an env through it's 'colletion'
|
|
# however, when importing the 'model' is actually what runs the delayed job
|
|
env = self.env(user=user)
|
|
self.collection.env = env
|
|
self.model.env = env
|
|
return super(SaleOrderBatchImporter, self)._import_record(
|
|
external_id, job_options=job_options)
|
|
|
|
def run(self, filters=None):
|
|
""" Run the synchronization """
|
|
if filters is None:
|
|
filters = {}
|
|
external_ids = list(self.backend_adapter.search(filters))
|
|
for ids in external_ids:
|
|
self._import_record(ids[0], ids[1])
|
|
if external_ids:
|
|
last_id = list(sorted(external_ids, key=lambda i: i[0]))[-1][0]
|
|
self.backend_record.import_orders_after_id = last_id
|
|
|
|
|
|
class SaleOrderImportMapper(Component):
|
|
_name = 'opencart.sale.order.mapper'
|
|
_inherit = 'opencart.import.mapper'
|
|
_apply_on = 'opencart.sale.order'
|
|
|
|
direct = [('order_id', 'external_id'),
|
|
('store_id', 'store_id'),
|
|
]
|
|
|
|
children = [('products', 'opencart_order_line_ids', 'opencart.sale.order.line'),
|
|
]
|
|
|
|
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 = 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)
|
|
|
|
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,
|
|
'partner_invoice_id': self.options.partner_invoice_id,
|
|
'partner_shipping_id': self.options.partner_shipping_id,
|
|
})
|
|
onchange = self.component(
|
|
usage='ecommerce.onchange.manager.sale.order'
|
|
)
|
|
# will I need more?!
|
|
return onchange.play(values, values['opencart_order_line_ids'])
|
|
|
|
@mapping
|
|
def name(self, record):
|
|
name = str(record['order_id'])
|
|
prefix = self.options.store.sale_prefix or self.backend_record.sale_prefix
|
|
if prefix:
|
|
name = prefix + name
|
|
return {'name': name}
|
|
|
|
@mapping
|
|
def date_order(self, record):
|
|
return {'date_order': record.get('date_added', fields.Datetime.now())}
|
|
|
|
@mapping
|
|
def fiscal_position_id(self, record):
|
|
fiscal_position = self.options.store.fiscal_position_id or self.backend_record.fiscal_position_id
|
|
if fiscal_position:
|
|
return {'fiscal_position_id': fiscal_position.id}
|
|
|
|
@mapping
|
|
def team_id(self, record):
|
|
team = self.options.store.team_id or self.backend_record.team_id
|
|
if team:
|
|
return {'team_id': team.id}
|
|
|
|
@mapping
|
|
def payment_mode_id(self, record):
|
|
record_method = record['payment_method']
|
|
method = self.env['account.payment.mode'].search(
|
|
[('name', '=', record_method)],
|
|
limit=1,
|
|
)
|
|
if not method:
|
|
raise ValueError('Payment Mode named "%s", cannot be found.' % (record_method, ))
|
|
return {'payment_mode_id': method.id}
|
|
|
|
@mapping
|
|
def project_id(self, record):
|
|
analytic_account = self.options.store.analytic_account_id or self.backend_record.analytic_account_id
|
|
if analytic_account:
|
|
return {'project_id': analytic_account.id}
|
|
|
|
@mapping
|
|
def warehouse_id(self, record):
|
|
warehouse = self.options.store.warehouse_id or self.backend_record.warehouse_id
|
|
if warehouse:
|
|
return {'warehouse_id': warehouse.id}
|
|
|
|
@mapping
|
|
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:
|
|
carrier_domain += [
|
|
'|', ('company_id', '=', company.id), ('company_id', '=', False)
|
|
]
|
|
carrier = self.env['delivery.carrier'].search(carrier_domain, limit=1)
|
|
if not carrier:
|
|
raise ValueError('Delivery Carrier for method Code "%s", cannot be found.' % (method, ))
|
|
return {'carrier_id': carrier.id}
|
|
|
|
@mapping
|
|
def company_id(self, record):
|
|
company = self.options.store.company_id or self.backend_record.company_id
|
|
if not company:
|
|
raise ValidationError('Company not found in Opencart Backend or Store')
|
|
return {'company_id': company.id}
|
|
|
|
@mapping
|
|
def backend_id(self, record):
|
|
return {'backend_id': self.backend_record.id}
|
|
|
|
@mapping
|
|
def total_amount(self, record):
|
|
total_amount = record['total']
|
|
return {'total_amount': total_amount}
|
|
|
|
|
|
class SaleOrderImporter(Component):
|
|
_name = 'opencart.sale.order.importer'
|
|
_inherit = 'opencart.importer'
|
|
_apply_on = 'opencart.sale.order'
|
|
|
|
def _must_skip(self):
|
|
if self.binder.to_internal(self.external_id):
|
|
return _('Already imported')
|
|
|
|
def _before_import(self):
|
|
# Check if status is ok, etc. on self.opencart_record
|
|
pass
|
|
|
|
def _create_partner(self, values):
|
|
return self.env['res.partner'].create(values)
|
|
|
|
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
|
|
elif key == 'country_id':
|
|
if value != partner.country_id.id:
|
|
return False
|
|
elif bool(value) and value != getattr(partner, key):
|
|
return False
|
|
return True
|
|
|
|
def _make_partner_name(self, firstname, lastname):
|
|
name = (str(firstname or '').strip() + ' ' + str(lastname or '').strip()).strip()
|
|
if not name:
|
|
return 'Undefined'
|
|
return name
|
|
|
|
def _get_partner_values(self, info_string='shipping_'):
|
|
record = self.opencart_record
|
|
|
|
# find or make partner with these details.
|
|
email = record.get('email')
|
|
if not email:
|
|
raise ValueError('Order does not have email in : ' + str(record))
|
|
|
|
phone = record.get('telephone', False)
|
|
|
|
info = {}
|
|
for k, v in record.items():
|
|
# Strip the info_string so that the remainder of the code depends on it.
|
|
if k.find(info_string) == 0:
|
|
info[k[len(info_string):]] = v
|
|
|
|
|
|
name = self._make_partner_name(info.get('firstname', ''), info.get('lastname', ''))
|
|
street = info.get('address_1', '')
|
|
street2 = info.get('address_2', '')
|
|
city = info.get('city', '')
|
|
state_code = info.get('zone_code', '')
|
|
zip_ = info.get('postcode', '')
|
|
country_code = info.get('iso_code_2', '')
|
|
country = self.env['res.country'].search([('code', '=', country_code)], limit=1)
|
|
state = self.env['res.country.state'].search([
|
|
('country_id', '=', country.id),
|
|
('code', '=', state_code)
|
|
], limit=1)
|
|
|
|
return {
|
|
'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):
|
|
partner_values = self._get_partner_values()
|
|
partners = self.env['res.partner'].search([
|
|
('email', '=', partner_values['email']),
|
|
'|', ('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.
|
|
partner = self._create_partner(copy(partner_values))
|
|
|
|
if not self._partner_matches(partner, partner_values):
|
|
partner_values['parent_id'] = partner.id
|
|
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)):
|
|
# 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):
|
|
invoice_partner = shipping_partner
|
|
|
|
self.partner = partner
|
|
self.shipping_partner = shipping_partner
|
|
self.invoice_partner = invoice_partner
|
|
|
|
def _check_special_fields(self):
|
|
assert self.partner, (
|
|
"self.partner should have been defined "
|
|
"in SaleOrderImporter._import_addresses")
|
|
assert self.shipping_partner, (
|
|
"self.shipping_partner should have been defined "
|
|
"in SaleOrderImporter._import_addresses")
|
|
assert self.invoice_partner, (
|
|
"self.invoice_partner should have been defined "
|
|
"in SaleOrderImporter._import_addresses")
|
|
|
|
def _get_store(self, record):
|
|
store_binder = self.binder_for('opencart.store')
|
|
return store_binder.to_internal(record['store_id'])
|
|
|
|
def _create_data(self, map_record, **kwargs):
|
|
# non dependencies
|
|
# our current handling of partners doesn't require anything special for the store
|
|
self._check_special_fields()
|
|
store = self._get_store(map_record.source)
|
|
return super(SaleOrderImporter, self)._create_data(
|
|
map_record,
|
|
partner_id=self.partner.id,
|
|
partner_invoice_id=self.invoice_partner.id,
|
|
partner_shipping_id=self.shipping_partner.id,
|
|
store=store,
|
|
**kwargs
|
|
)
|
|
|
|
def _create(self, data):
|
|
binding = super(SaleOrderImporter, self)._create(data)
|
|
# Without this, it won't map taxes with the fiscal position.
|
|
if binding.fiscal_position_id:
|
|
binding.odoo_id._compute_tax_id()
|
|
|
|
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):
|
|
|
|
_name = 'opencart.sale.order.line.mapper'
|
|
_inherit = 'opencart.import.mapper'
|
|
_apply_on = 'opencart.sale.order.line'
|
|
|
|
direct = [('quantity', 'product_uom_qty'),
|
|
('price', 'price_unit'),
|
|
('order_product_id', 'external_id'),
|
|
]
|
|
|
|
@mapping
|
|
def name(self, record):
|
|
return {'name': unescape(record['name'])}
|
|
|
|
@mapping
|
|
def product_id(self, 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}
|