Files
Odoo-Modules/fusion_inventory_sync/models/stock_move.py
2026-02-22 01:22:18 -05:00

117 lines
4.2 KiB
Python

# -*- 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.'
)