mirror of
https://gitlab.com/hibou-io/hibou-odoo/suite.git
synced 2025-01-20 12:37:31 +02:00
[REL] connector_amazon_sp: for 11.0
This commit is contained in:
293
connector_amazon_sp/models/product/common.py
Normal file
293
connector_amazon_sp/models/product/common.py
Normal file
@@ -0,0 +1,293 @@
|
||||
# © 2021 Hibou Corp.
|
||||
|
||||
from base64 import b64encode
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.addons.component.core import Component
|
||||
|
||||
PRODUCT_SKU_WITH_WAREHOUSE = '%s-%s'
|
||||
|
||||
|
||||
class AmazonProductProduct(models.Model):
|
||||
_name = 'amazon.product.product'
|
||||
_inherit = 'amazon.binding'
|
||||
_inherits = {'product.product': 'odoo_id'}
|
||||
_description = 'Amazon Product Listing'
|
||||
_rec_name = 'external_id'
|
||||
|
||||
odoo_id = fields.Many2one('product.product',
|
||||
string='Product',
|
||||
required=True,
|
||||
ondelete='cascade')
|
||||
asin = fields.Char(string='ASIN')
|
||||
state = fields.Selection([
|
||||
('draft', 'Draft'),
|
||||
('sent', 'Submitted'),
|
||||
], default='draft')
|
||||
warehouse_id = fields.Many2one('stock.warehouse',
|
||||
string='Warehouse',
|
||||
ondelete='set null')
|
||||
backend_warehouse_ids = fields.Many2many(related='backend_id.warehouse_ids')
|
||||
backend_fba_warehouse_ids = fields.Many2many(related='backend_id.fba_warehouse_ids')
|
||||
date_product_sent = fields.Datetime(string='Last Product Update')
|
||||
date_price_sent = fields.Datetime(string='Last Price Update')
|
||||
date_inventory_sent = fields.Datetime(string='Last Inventory Update')
|
||||
buffer_qty = fields.Integer(string='Buffer Quantity',
|
||||
help='Stock to hold back from Amazon for listings. (-1 means use the backend default)',
|
||||
default=-1)
|
||||
|
||||
@api.onchange('odoo_id', 'warehouse_id', 'default_code')
|
||||
def _onchange_suggest_external_id(self):
|
||||
with_code_and_warehouse = self.filtered(lambda p: p.default_code and p.warehouse_id)
|
||||
with_code = (self - with_code_and_warehouse).filtered('default_code')
|
||||
other = (self - with_code_and_warehouse - with_code)
|
||||
for product in with_code_and_warehouse:
|
||||
product.external_id = PRODUCT_SKU_WITH_WAREHOUSE % (product.default_code, product.warehouse_id.code)
|
||||
for product in with_code:
|
||||
product.external_id = product.default_code
|
||||
for product in other:
|
||||
product.external_id = product.external_id
|
||||
|
||||
@api.multi
|
||||
def button_submit_product(self):
|
||||
backends = self.mapped('backend_id')
|
||||
for backend in backends:
|
||||
products = self.filtered(lambda p: p.backend_id == backend)
|
||||
products._submit_product()
|
||||
return 1
|
||||
|
||||
@api.multi
|
||||
def button_update_inventory(self):
|
||||
backends = self.mapped('backend_id')
|
||||
for backend in backends:
|
||||
products = self.filtered(lambda p: p.backend_id == backend)
|
||||
products._update_inventory()
|
||||
return 1
|
||||
|
||||
@api.multi
|
||||
def button_update_price(self):
|
||||
backends = self.mapped('backend_id')
|
||||
for backend in backends:
|
||||
products = self.filtered(lambda p: p.backend_id == backend)
|
||||
products._update_price()
|
||||
return 1
|
||||
|
||||
def _submit_product(self):
|
||||
# this should be called on a product set that has the same backend
|
||||
backend = self[0].backend_id
|
||||
with backend.work_on(self._name) as work:
|
||||
exporter = work.component(usage='amazon.product.product.exporter')
|
||||
exporter.run(self)
|
||||
self.write({'date_product_sent': fields.Datetime.now(), 'state': 'sent'})
|
||||
|
||||
def _update_inventory(self):
|
||||
# this should be called on a product set that has the same backend
|
||||
backend = self[0].backend_id
|
||||
with backend.work_on(self._name) as work:
|
||||
exporter = work.component(usage='amazon.product.product.exporter')
|
||||
exporter.run_inventory(self)
|
||||
self.write({'date_inventory_sent': fields.Datetime.now()})
|
||||
|
||||
def _update_price(self):
|
||||
# this should be called on a product set that has the same backend
|
||||
backend = self[0].backend_id
|
||||
with backend.work_on(self._name) as work:
|
||||
exporter = work.component(usage='amazon.product.product.exporter')
|
||||
exporter.run_price(self)
|
||||
self.write({'date_price_sent': fields.Datetime.now()})
|
||||
|
||||
def _update_for_backend_products(self, backend):
|
||||
return self.search([
|
||||
('backend_id', '=', backend.id),
|
||||
('state', '=', 'sent'),
|
||||
])
|
||||
|
||||
def update_inventory(self, backend):
|
||||
products = self._update_for_backend_products(backend)
|
||||
if products:
|
||||
products._update_inventory()
|
||||
|
||||
def update_price(self, backend):
|
||||
products = self._update_for_backend_products(backend)
|
||||
if products:
|
||||
products._update_price()
|
||||
|
||||
|
||||
class ProductProduct(models.Model):
|
||||
_inherit = 'product.product'
|
||||
|
||||
amazon_bind_ids = fields.One2many('amazon.product.product', 'odoo_id', string='Amazon Listings')
|
||||
|
||||
|
||||
class ProductAdapter(Component):
|
||||
_name = 'amazon.product.product.adapter'
|
||||
_inherit = 'amazon.adapter'
|
||||
_apply_on = 'amazon.product.product'
|
||||
|
||||
def _api(self):
|
||||
return self.api_instance.feeds()
|
||||
|
||||
def _submit_feed(self, bindings, type, content_type, data):
|
||||
feed_values = {
|
||||
'backend_id': bindings[0].backend_id.id,
|
||||
'type': type,
|
||||
'content_type': content_type,
|
||||
'data': b64encode(data),
|
||||
}
|
||||
if len(bindings) == 1:
|
||||
feed_values['amazon_product_product_id'] = bindings.id
|
||||
feed = self.env['amazon.feed'].create(feed_values)
|
||||
feed.with_delay(priority=19).submit_feed() # slightly higher than regular submit_feed calls
|
||||
return feed
|
||||
|
||||
def create(self, bindings):
|
||||
feed_root, _message = self._product_data_feed(bindings)
|
||||
feed_data = self._feed_string(feed_root)
|
||||
self._submit_feed(bindings, 'POST_PRODUCT_DATA', 'text/xml', feed_data)
|
||||
|
||||
def create_inventory(self, bindings):
|
||||
feed_root, _message = self._product_inventory_feed(bindings)
|
||||
feed_data = self._feed_string(feed_root)
|
||||
self._submit_feed(bindings, 'POST_INVENTORY_AVAILABILITY_DATA', 'text/xml', feed_data)
|
||||
|
||||
def create_price(self, bindings):
|
||||
feed_root, _message = self._product_price_feed(bindings)
|
||||
feed_data = self._feed_string(feed_root)
|
||||
self._submit_feed(bindings, 'POST_PRODUCT_PRICING_DATA', 'text/xml', feed_data)
|
||||
|
||||
def _process_product_data(self, bindings):
|
||||
res = []
|
||||
for amazon_product in bindings:
|
||||
# why iterate? because we probably need more data eventually...
|
||||
if not amazon_product.external_id:
|
||||
raise UserError('Amazon Product Listing (%s) must have an Amazon SKU filled.' % (amazon_product.id, ))
|
||||
res.append({
|
||||
'SKU': amazon_product.external_id,
|
||||
})
|
||||
return res
|
||||
|
||||
def _product_data_feed(self, bindings):
|
||||
product_datas = self._process_product_data(bindings)
|
||||
root, message = self._feed('Product', bindings[0].backend_id)
|
||||
root.remove(message)
|
||||
self.ElementTree.SubElement(root, 'PurgeAndReplace').text = 'false'
|
||||
for i, product_data in enumerate(product_datas, 1):
|
||||
message = self.ElementTree.SubElement(root, 'Message')
|
||||
self.ElementTree.SubElement(message, 'MessageID').text = str(i)
|
||||
# ElementTree.SubElement(message, 'OperationType').text = 'Update'
|
||||
self.ElementTree.SubElement(message, 'OperationType').text = 'PartialUpdate'
|
||||
product = self.ElementTree.SubElement(message, 'Product')
|
||||
self.ElementTree.SubElement(product, 'SKU').text = product_data['SKU']
|
||||
# standard_product_id = ElementTree.SubElement(product, 'StandardProductID')
|
||||
# ElementTree.SubElement(standard_product_id, 'Type').text = product_data['StandardProductID.Type']
|
||||
# ElementTree.SubElement(standard_product_id, 'Value').text = product_data['StandardProductID.Value']
|
||||
# description_data = ElementTree.SubElement(product, 'DescriptionData')
|
||||
# ElementTree.SubElement(description_data, 'Title').text = product_data['Title']
|
||||
# ElementTree.SubElement(description_data, 'Brand').text = product_data['Brand']
|
||||
# ElementTree.SubElement(description_data, 'Description').text = product_data['Description']
|
||||
# for bullet in product_data['BulletPoints']:
|
||||
# ElementTree.SubElement(description_data, 'BulletPoint').text = bullet
|
||||
# ElementTree.SubElement(description_data, 'Manufacturer').text = product_data['Manufacturer']
|
||||
# ElementTree.SubElement(description_data, 'ItemType').text = product_data['ItemType']
|
||||
return root, message
|
||||
|
||||
def _process_product_inventory(self, bindings):
|
||||
def _qty(binding, buffer_qty):
|
||||
# qty available is all up inventory, less outgoing inventory gives qty to send
|
||||
qty = binding.qty_available - binding.outgoing_qty
|
||||
if binding.buffer_qty >= 0.0:
|
||||
return max((0.0, qty - binding.buffer_qty))
|
||||
return max((0.0, qty - buffer_qty))
|
||||
|
||||
res = []
|
||||
backend = bindings[0].backend_id
|
||||
backend_warehouses = backend.warehouse_ids
|
||||
backend_fba_warehouses = backend.fba_warehouse_ids
|
||||
warehouses = bindings.mapped('warehouse_id')
|
||||
for warehouse in warehouses:
|
||||
wh_bindings = bindings.filtered(lambda p: p.warehouse_id == warehouse).with_context(warehouse=warehouse.id)
|
||||
buffer_qty = backend.fba_buffer_qty if warehouse in backend_fba_warehouses else backend.buffer_qty
|
||||
for binding in wh_bindings:
|
||||
res.append((binding.external_id, _qty(binding, buffer_qty)))
|
||||
|
||||
buffer_qty = backend.buffer_qty
|
||||
for binding in bindings.filtered(lambda p: not p.warehouse_id).with_context(warehouse=backend_warehouses.ids):
|
||||
res.append((binding.external_id, _qty(binding, buffer_qty)))
|
||||
return res
|
||||
|
||||
def _product_inventory_feed(self, bindings):
|
||||
product_datas = self._process_product_inventory(bindings)
|
||||
root, message = self._feed('Inventory', bindings[0].backend_id)
|
||||
root.remove(message)
|
||||
for i, product_data in enumerate(product_datas, 1):
|
||||
sku, qty = product_data
|
||||
message = self.ElementTree.SubElement(root, 'Message')
|
||||
self.ElementTree.SubElement(message, 'MessageID').text = str(i)
|
||||
# ElementTree.SubElement(message, 'OperationType').text = 'Update'
|
||||
self.ElementTree.SubElement(message, 'OperationType').text = 'Update'
|
||||
inventory = self.ElementTree.SubElement(message, 'Inventory')
|
||||
self.ElementTree.SubElement(inventory, 'SKU').text = sku
|
||||
self.ElementTree.SubElement(inventory, 'Quantity').text = str(int(qty))
|
||||
return root, message
|
||||
|
||||
def _process_product_price(self, bindings):
|
||||
def _process_product_price_internal(env, binding, pricelist, res):
|
||||
price = binding.lst_price
|
||||
sale_price = None
|
||||
date_start = None
|
||||
date_end = None
|
||||
if pricelist:
|
||||
rule = None
|
||||
sale_price, rule_id = pricelist.get_product_price_rule(binding.odoo_id, 1.0, None)
|
||||
if rule_id:
|
||||
rule = env['product.pricelist.item'].browse(rule_id).exists()
|
||||
if rule and (rule.date_start or rule.date_end):
|
||||
date_start = rule.date_start
|
||||
date_end = rule.date_end
|
||||
res.append((binding.external_id, price, sale_price, date_start, date_end))
|
||||
|
||||
res = []
|
||||
backend = bindings[0].backend_id
|
||||
pricelist = backend.pricelist_id
|
||||
fba_pricelist = backend.fba_pricelist_id
|
||||
backend_fba_warehouses = backend.fba_warehouse_ids
|
||||
fba_bindings = bindings.filtered(lambda b: b.warehouse_id and b.warehouse_id in backend_fba_warehouses)
|
||||
for binding in fba_bindings:
|
||||
_process_product_price_internal(self.env, binding, fba_pricelist, res)
|
||||
for binding in (bindings - fba_bindings):
|
||||
_process_product_price_internal(self.env, binding, pricelist, res)
|
||||
return res
|
||||
|
||||
def _product_price_feed(self, bindings):
|
||||
backend = bindings[0].backend_id
|
||||
product_datas = self._process_product_price(bindings)
|
||||
root, message = self._feed('Price', backend)
|
||||
root.remove(message)
|
||||
now = fields.Datetime.now()
|
||||
tomorrow = str(fields.Datetime.from_string(now) + timedelta(days=1))
|
||||
|
||||
for i, product_data in enumerate(product_datas, 1):
|
||||
sku, _price, _sale_price, date_start, date_end = product_data
|
||||
message = self.ElementTree.SubElement(root, 'Message')
|
||||
self.ElementTree.SubElement(message, 'MessageID').text = str(i)
|
||||
# ElementTree.SubElement(message, 'OperationType').text = 'Update'
|
||||
# self.ElementTree.SubElement(message, 'OperationType').text = 'Update'
|
||||
price = self.ElementTree.SubElement(message, 'Price')
|
||||
self.ElementTree.SubElement(price, 'SKU').text = sku
|
||||
standard_price = self.ElementTree.SubElement(price, 'StandardPrice')
|
||||
standard_price.text = '%0.2f' % (_price, )
|
||||
standard_price.attrib['currency'] = 'USD' # TODO gather currency
|
||||
if _sale_price and abs(_price - _sale_price) > 0.01:
|
||||
sale = self.ElementTree.SubElement(price, 'Sale')
|
||||
if not date_start:
|
||||
date_start = now
|
||||
self.ElementTree.SubElement(sale, 'StartDate').text = fields.Datetime.from_string(date_start).isoformat()
|
||||
if not date_end:
|
||||
date_end = tomorrow
|
||||
self.ElementTree.SubElement(sale, 'EndDate').text = fields.Datetime.from_string(date_end).isoformat()
|
||||
sale_price = self.ElementTree.SubElement(sale, 'SalePrice')
|
||||
sale_price.text = '%0.2f' % (_sale_price, )
|
||||
sale_price.attrib['currency'] = 'USD' # TODO gather currency
|
||||
return root, message
|
||||
Reference in New Issue
Block a user