# -*- coding: utf-8 -*- import logging from odoo import models, fields _logger = logging.getLogger(__name__) class StockMove(models.Model): """Extend stock moves to push changes to remote Odoo instance.""" _inherit = 'stock.move' def _action_done(self, cancel_backorder=False): """Override to trigger remote sync after stock moves complete.""" res = super()._action_done(cancel_backorder=cancel_backorder) # After moves are done, queue a remote stock push for affected products try: self._push_stock_to_remote() except Exception as e: # Never block local operations due to sync failure _logger.warning(f'Remote stock push failed (non-blocking): {e}') return res def _push_stock_to_remote(self): """Push stock level changes to the remote Odoo instance. Only pushes for products that have a sync mapping. Runs async-safe: failures don't block local operations. """ if not self: return # Get unique product templates from completed moves product_tmpls = self.mapped('product_id.product_tmpl_id') if not product_tmpls: return # Find sync mappings for these products Mapping = self.env['fusion.product.sync.mapping'] mappings = Mapping.search([ ('local_product_id', 'in', product_tmpls.ids), ('config_id.active', '=', True), ('config_id.state', '=', 'connected'), ('remote_product_id', '!=', 0), ]) if not mappings: return # Group by config for efficient batch push configs = {} for mapping in mappings: config = mapping.config_id if config.id not in configs: configs[config.id] = { 'config': config, 'mappings': self.env['fusion.product.sync.mapping'], } configs[config.id]['mappings'] |= mapping for config_data in configs.values(): config = config_data['config'] config_mappings = config_data['mappings'] try: self._push_stock_levels(config, config_mappings) except Exception as e: _logger.warning( f'Failed to push stock to {config.name}: {e}' ) def _push_stock_levels(self, config, mappings): """Push current local stock levels to the remote instance. This updates the remote side with our current on-hand qty so the remote instance knows what we have available. """ uid, models_proxy = config._get_xmlrpc_connection() for mapping in mappings: local_product = mapping.local_product_id if not local_product: continue # Get current local stock for this product local_qty = local_product.qty_available local_forecast = local_product.virtual_available # Update the mapping record with current local stock mapping.write({ 'last_stock_sync': fields.Datetime.now(), }) # Log the push for debugging _logger.info( f'Stock push: {local_product.name} -> {config.name} ' f'(local_qty={local_qty}, remote_id={mapping.remote_product_id})' ) # Optionally update a custom field on the remote side # This writes to a field on the remote product to track # what the partner store has available try: models_proxy.execute_kw( config.db_name, uid, config.api_key, 'product.template', 'write', [[mapping.remote_product_id], { 'x_partner_qty_available': local_qty, }] ) except Exception as e: # If the remote field doesn't exist yet, just log it _logger.debug( f'Could not update remote field x_partner_qty_available: {e}. ' f'Create this field on the remote instance for full bi-directional sync.' )