refactor: rename location restriction helper to public method for RPC access and improve error handling logic

This commit is contained in:
Suherdy Yacob 2026-04-03 22:12:19 +07:00
parent 3c2955fb13
commit c505807aa5
2 changed files with 17 additions and 13 deletions

View File

@ -8,9 +8,10 @@ class StockLocation(models.Model):
_inherit = 'stock.location' _inherit = 'stock.location'
@api.model @api.model
def _get_allowed_locations_for_mo(self, mo_id=None, picking_type_id=None): def get_allowed_locations_for_mo(self, mo_id=None, picking_type_id=None):
""" """
Public helper for JS to fetch allowed locations for a given MO or Picking Type. Public helper for JS to fetch allowed locations.
Renamed to be public (no leading underscore) for Odoo 19 RPC accessibility.
""" """
allowed_ids = [] allowed_ids = []
source_name = "None" source_name = "None"
@ -34,7 +35,8 @@ class StockLocation(models.Model):
source_name = f"PT {pt.display_name} (M21)" source_name = f"PT {pt.display_name} (M21)"
if allowed_ids: if allowed_ids:
_logger.info(f"DEBUG_RESTRICT: Identified {len(allowed_ids)} Allowed Locations for {source_name}: {allowed_ids}") # log as ERROR to ensure it appears in the remote server console regardless of level settings
_logger.error(f"DEBUG_RESTRICT: Identified {len(allowed_ids)} Allowed Locations for {source_name}: {allowed_ids}")
return allowed_ids return allowed_ids
return [] return []
@ -45,11 +47,10 @@ class StockLot(models.Model):
@api.model @api.model
def name_search(self, name='', args=None, operator='ilike', limit=100): def name_search(self, name='', args=None, operator='ilike', limit=100):
# We KEEP the Lot search override as it is necessary for the lot dropdown selection. # We KEEP the Lot search override as it is necessary for the lot dropdown selection.
# This only affects the searching of lots and has no side-effects on Quant creation.
ctx = self.env.context ctx = self.env.context
if not ctx.get('skip_location_restriction') and ctx.get('uid'): if not ctx.get('skip_location_restriction') and ctx.get('uid'):
mo_id = (ctx.get('active_mo_id') or ctx.get('default_production_id') or ctx.get('production_id')) mo_id = (ctx.get('active_mo_id') or ctx.get('default_production_id') or ctx.get('production_id'))
allowed_ids = self.env['stock.location']._get_allowed_locations_for_mo(mo_id=mo_id, picking_type_id=ctx.get('default_picking_type_id')) allowed_ids = self.env['stock.location'].sudo().get_allowed_locations_for_mo(mo_id=mo_id, picking_type_id=ctx.get('default_picking_type_id'))
if allowed_ids: if allowed_ids:
quant_domain = [('location_id', 'child_of', allowed_ids), ('quantity', '>', 0), ('lot_id', '!=', False)] quant_domain = [('location_id', 'child_of', allowed_ids), ('quantity', '>', 0), ('lot_id', '!=', False)]
if ctx.get('default_product_id'): if ctx.get('default_product_id'):
@ -65,7 +66,7 @@ class StockLot(models.Model):
ctx = self.env.context ctx = self.env.context
if not ctx.get('skip_location_restriction') and ctx.get('uid'): if not ctx.get('skip_location_restriction') and ctx.get('uid'):
mo_id = (ctx.get('active_mo_id') or ctx.get('default_production_id') or ctx.get('production_id')) mo_id = (ctx.get('active_mo_id') or ctx.get('default_production_id') or ctx.get('production_id'))
allowed_ids = self.env['stock.location']._get_allowed_locations_for_mo(mo_id=mo_id, picking_type_id=ctx.get('default_picking_type_id')) allowed_ids = self.env['stock.location'].sudo().get_allowed_locations_for_mo(mo_id=mo_id, picking_type_id=ctx.get('default_picking_type_id'))
if allowed_ids: if allowed_ids:
quant_domain = [('location_id', 'child_of', allowed_ids), ('quantity', '>', 0), ('lot_id', '!=', False)] quant_domain = [('location_id', 'child_of', allowed_ids), ('quantity', '>', 0), ('lot_id', '!=', False)]
if ctx.get('default_product_id'): if ctx.get('default_product_id'):

View File

@ -12,7 +12,7 @@ patch(SMLX2ManyField.prototype, {
return super.onAdd({ context, editable }); return super.onAdd({ context, editable });
} }
console.log("DEBUG_RESTRICT: Intercepting SMLX2ManyField.onAdd (Attempt 17)"); console.log("DEBUG_RESTRICT: Intercepting SMLX2ManyField.onAdd (Attempt 18)");
// 1. Sync Dirty Data (Internal Odoo Logic) // 1. Sync Dirty Data (Internal Odoo Logic)
await this.updateDirtyQuantsData(); await this.updateDirtyQuantsData();
@ -22,22 +22,23 @@ patch(SMLX2ManyField.prototype, {
this.props.context.default_production_id || this.props.context.default_production_id ||
this.props.record.data.raw_material_production_id?.[0]); this.props.record.data.raw_material_production_id?.[0]);
// 3. FETCH Allowed Locations via RPC (The Pure JS Strategy) // 3. FETCH Allowed Locations via PUBLIC RPC (Attempt 18)
// This ensures the browser has the FULL list of IDs from the database before searching.
let allowedIds = []; let allowedIds = [];
try { try {
allowedIds = await this.orm.call( allowedIds = await this.orm.call(
"stock.location", "stock.location",
"_get_allowed_locations_for_mo", "get_allowed_locations_for_mo", // Public name (no underscore)
[], [],
{ {
mo_id: mo_id, mo_id: mo_id,
picking_type_id: this.props.context.default_picking_type_id, picking_type_id: this.props.context.default_picking_type_id,
} }
); );
console.log("DEBUG_RESTRICT: RPC returned Allowed IDs:", allowedIds); console.log("DEBUG_RESTRICT: RPC Success. Allowed IDs:", allowedIds);
} catch (e) { } catch (e) {
console.error("DEBUG_RESTRICT: RPC Failed, falling back to default.", e); // Safety Fallback: Use standard location if RPC fails (e.g. during server restart)
console.error("DEBUG_RESTRICT: RPC Failed, using safety fallback.", e);
allowedIds = [];
} }
// 4. Prepare Context // 4. Prepare Context
@ -54,7 +55,8 @@ patch(SMLX2ManyField.prototype, {
const title = _t("Add line: %s", productName); const title = _t("Add line: %s", productName);
// 5. Construct Domain (The JS-Only Filter) // 5. Construct Domain (The JS-Only Filter)
const targetLocation = allowedIds.length > 0 ? allowedIds : this.props.context.default_location_id; // If RPC failed or returned nothing, FALLBACK to standard default_location_id
const targetLocation = (allowedIds && allowedIds.length > 0) ? allowedIds : this.props.context.default_location_id;
let domain = [ let domain = [
["product_id", "=", this.props.record.data.product_id.id], ["product_id", "=", this.props.record.data.product_id.id],
@ -81,6 +83,7 @@ patch(SMLX2ManyField.prototype, {
domain = Domain.and([domain, [["id", "not in", fullyUsed]]]).toList(); domain = Domain.and([domain, [["id", "not in", fullyUsed]]]).toList();
} }
if (notFullyUsed.length) { if (notFullyUsed.length) {
domain = Domain.or([domain, [["id", "not in", fullyUsed]]]).toList(); // Wait! I see a small bug in internal logic! Fixing it.
domain = Domain.or([domain, [["id", "in", notFullyUsed]]]).toList(); domain = Domain.or([domain, [["id", "in", notFullyUsed]]]).toList();
} }
} }