diff --git a/models/stock_location.py b/models/stock_location.py index 90643dd..455814d 100644 --- a/models/stock_location.py +++ b/models/stock_location.py @@ -8,9 +8,10 @@ class StockLocation(models.Model): _inherit = 'stock.location' @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 = [] source_name = "None" @@ -34,7 +35,8 @@ class StockLocation(models.Model): source_name = f"PT {pt.display_name} (M21)" 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 [] @@ -45,11 +47,10 @@ class StockLot(models.Model): @api.model 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. - # This only affects the searching of lots and has no side-effects on Quant creation. ctx = self.env.context 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')) - 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: quant_domain = [('location_id', 'child_of', allowed_ids), ('quantity', '>', 0), ('lot_id', '!=', False)] if ctx.get('default_product_id'): @@ -65,7 +66,7 @@ class StockLot(models.Model): ctx = self.env.context 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')) - 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: quant_domain = [('location_id', 'child_of', allowed_ids), ('quantity', '>', 0), ('lot_id', '!=', False)] if ctx.get('default_product_id'): diff --git a/static/src/js/stock_move_line_x2_many_field_patch.js b/static/src/js/stock_move_line_x2_many_field_patch.js index 644a968..c3f8dd1 100644 --- a/static/src/js/stock_move_line_x2_many_field_patch.js +++ b/static/src/js/stock_move_line_x2_many_field_patch.js @@ -12,7 +12,7 @@ patch(SMLX2ManyField.prototype, { 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) await this.updateDirtyQuantsData(); @@ -22,22 +22,23 @@ patch(SMLX2ManyField.prototype, { this.props.context.default_production_id || this.props.record.data.raw_material_production_id?.[0]); - // 3. FETCH Allowed Locations via RPC (The Pure JS Strategy) - // This ensures the browser has the FULL list of IDs from the database before searching. + // 3. FETCH Allowed Locations via PUBLIC RPC (Attempt 18) let allowedIds = []; try { allowedIds = await this.orm.call( "stock.location", - "_get_allowed_locations_for_mo", + "get_allowed_locations_for_mo", // Public name (no underscore) [], { mo_id: mo_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) { - 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 @@ -54,7 +55,8 @@ patch(SMLX2ManyField.prototype, { const title = _t("Add line: %s", productName); // 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 = [ ["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(); } 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(); } }