117 lines
4.2 KiB
Python
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.'
|
|
)
|