# Part of Odoo. See LICENSE file for full copyright and licensing details. import logging from xml.etree import ElementTree from odoo import api, fields, models from odoo.addons.sale_amazon import utils as amazon_utils _logger = logging.getLogger(__name__) class AmazonOffer(models.Model): _name = 'amazon.offer' _description = "Amazon Offer" def _default_marketplace(self): """ Return the single marketplace of this offer's account if it exists. """ account_id = self.env.context.get('default_account_id') if account_id: marketplaces = self.env['amazon.account'].browse([account_id]).active_marketplace_ids return len(marketplaces) == 1 and marketplaces[0] account_id = fields.Many2one( string="Account", help="The seller account used to manage this product.", comodel_name='amazon.account', required=True, ondelete='cascade', ) # The default account provided in the context of the list view. company_id = fields.Many2one(related='account_id.company_id', readonly=True) active_marketplace_ids = fields.Many2many(related='account_id.active_marketplace_ids') marketplace_id = fields.Many2one( string="Marketplace", help="The marketplace of this offer.", comodel_name='amazon.marketplace', default=_default_marketplace, required=True, domain="[('id', 'in', active_marketplace_ids)]", ) product_id = fields.Many2one( string="Product", comodel_name='product.product', required=True, ondelete='cascade' ) product_template_id = fields.Many2one( related="product_id.product_tmpl_id", store=True, readonly=True ) sku = fields.Char(string="SKU", help="The Stock Keeping Unit.", required=True) amazon_sync_status = fields.Selection( string="Amazon Synchronization Status", help="The synchronization status of the product's stock level to Amazon:\n" "- Processing: The stock level has been sent and is being processed.\n" "- Done: The stock level has been processed.\n" "- Error: The synchronization of the stock level failed.", selection=[('processing', "Processing"), ('done', "Done"), ('error', "Error")], readonly=True, ) amazon_feed_ref = fields.Char(string="Amazon Feed Reference", readonly=True) _sql_constraints = [( 'unique_sku', 'UNIQUE(account_id, sku)', "SKU must be unique for a given account." )] @api.onchange('product_id') def _onchange_product_id(self): """ Set the SKU to the internal reference of the product if it exists. """ for offer in self: offer.sku = offer.product_id.default_code def action_view_online(self): self.ensure_one() url = f'{self.marketplace_id.seller_central_url}/skucentral?mSku={self.sku}' return { 'type': 'ir.actions.act_url', 'url': url, 'target': 'new', } def _update_inventory_availability(self, account): """ Update the stock quantity of Amazon products to Amazon. :param record account: The Amazon account of the delivery to confirm on Amazon, as an `amazon.account` record. :return: None """ def build_feed_messages(root_): """ Build the 'Message' elements to add to the feed. :param Element root_: The root XML element to which messages should be added. :return: None """ location_ = self.account_id.location_id quant_ids_ = location_.quant_ids.filtered(lambda q: q.product_id in self.product_id) fba_offers_ = self.filtered(lambda o: o.product_id in quant_ids_.product_id) for offer_ in self: # Build the message base. message_ = ElementTree.SubElement(root_, 'Message') inventory_ = ElementTree.SubElement(message_, 'Inventory') ElementTree.SubElement(inventory_, 'SKU').text = offer_.sku available_qty = offer_._get_available_product_qty() # We consider products in the Amazon location to be FBA. Their quantity is set to 0 # as we don't add any fulfillment channel to the feed. Amazon won't change their # quantity on hand, but by forcing the quantity here, we make sure Amazon will not # consider we are selling it through another channel. is_fbm = offer_ not in fba_offers_ quantity_ = available_qty if is_fbm and available_qty > 0 else 0 ElementTree.SubElement(inventory_, 'Quantity').text = str(int(quantity_)) xml_feed = amazon_utils.build_feed(account, 'Inventory', build_feed_messages) try: feed_ref = amazon_utils.submit_feed( account, xml_feed, 'POST_INVENTORY_AVAILABILITY_DATA' ) except amazon_utils.AmazonRateLimitError: _logger.info( "Rate limit reached while sending inventory availability notification for Amazon" " account with id %s.", account.id ) else: _logger.info( "Sent inventory availability notification (feed_ref %s) to amazon for offers with" " SKU %s.", feed_ref, ', '.join(self.mapped('sku')), ) self.write({'amazon_sync_status': 'processing', 'amazon_feed_ref': feed_ref}) def _get_available_product_qty(self): """ Retrieve the current available and free product quantity. This hook can be overridden to set a finer quantity. :return: The free quantity. :rtype: float """ self.ensure_one() return self.product_id.free_qty