refactor: improve location filtering logic by updating context parsing in Python and overriding the quant selection modal domain in JS
This commit is contained in:
parent
20fb9a40d0
commit
84702c810f
@ -19,25 +19,33 @@ class StockLocation(models.Model):
|
|||||||
"""
|
"""
|
||||||
ctx = self.env.context
|
ctx = self.env.context
|
||||||
|
|
||||||
# 1. PRIORITY: Check the context for allowed locations (Passed by views)
|
# 1. PRIORITY: Check the context for allowed locations (Passed by views/JS)
|
||||||
# This is the most reliable source in the MO "Add line" catalog/details view.
|
target_keys = ['allowed_source_location_ids', 'default_allowed_source_location_ids']
|
||||||
# It handles both 'allowed_source_location_ids' and 'default_allowed_source_location_ids'
|
for key in target_keys:
|
||||||
context_ids = (ctx.get('allowed_source_location_ids') or
|
val = ctx.get(key)
|
||||||
ctx.get('default_allowed_source_location_ids'))
|
if not val:
|
||||||
|
continue
|
||||||
|
|
||||||
if context_ids:
|
# Case: List of IDs or Commands
|
||||||
# Handle list of IDs, or Command tuples (e.g. [Command.set([ID1, ID2])])
|
if isinstance(val, list):
|
||||||
if isinstance(context_ids, list):
|
# Simple list of integers
|
||||||
# Simple list of IDs
|
if val and all(isinstance(x, int) for x in val):
|
||||||
if all(isinstance(x, int) for x in context_ids):
|
return val
|
||||||
return context_ids
|
|
||||||
# Command format: [(6, 0, [ID1, ID2])]
|
# Command format: [(6, 0, [IDs]), (4, ID, 0), ...]
|
||||||
for cmd in context_ids:
|
col_ids = []
|
||||||
if isinstance(cmd, (list, tuple)) and cmd[0] == 6:
|
for entry in val:
|
||||||
return cmd[2]
|
if isinstance(entry, (list, tuple)):
|
||||||
# Single ID
|
if entry[0] == 6: # SET command
|
||||||
if isinstance(context_ids, int):
|
return entry[2]
|
||||||
return [context_ids]
|
if entry[0] == 4: # LINK command
|
||||||
|
col_ids.append(entry[1])
|
||||||
|
if col_ids:
|
||||||
|
return col_ids
|
||||||
|
|
||||||
|
# Case: Single ID
|
||||||
|
if isinstance(val, int):
|
||||||
|
return [val]
|
||||||
|
|
||||||
# 2. FALLBACK: Identify the Picking Type (Operation Type)
|
# 2. FALLBACK: Identify the Picking Type (Operation Type)
|
||||||
picking_type_id = (ctx.get('default_picking_type_id') or
|
picking_type_id = (ctx.get('default_picking_type_id') or
|
||||||
|
|||||||
@ -1,43 +1,71 @@
|
|||||||
/** @odoo-module **/
|
/** @odoo-module **/
|
||||||
|
|
||||||
|
import { _t } from "@web/core/l10n/translation";
|
||||||
|
import { Domain } from "@web/core/domain";
|
||||||
import { patch } from "@web/core/utils/patch";
|
import { patch } from "@web/core/utils/patch";
|
||||||
import { SMLX2ManyField } from "@stock/fields/stock_move_line_x2_many_field";
|
import { SMLX2ManyField } from "@stock/fields/stock_move_line_x2_many_field";
|
||||||
|
|
||||||
patch(SMLX2ManyField.prototype, {
|
patch(SMLX2ManyField.prototype, {
|
||||||
async onAdd({ context, editable } = {}) {
|
async onAdd({ context, editable } = {}) {
|
||||||
// Inject active_mo_id into context
|
// Only apply our custom logic when Odoo shows the Quant selection modal (catalog)
|
||||||
if (this.props.context.active_mo_id) {
|
if (!this.props.record.data.show_quant) {
|
||||||
context = {
|
|
||||||
...context,
|
|
||||||
active_mo_id: this.props.context.active_mo_id,
|
|
||||||
};
|
|
||||||
} else if (this.props.record.model.config.resModel === "stock.move" && this.props.record.data.raw_material_production_id) {
|
|
||||||
// Fallback: try to get MO ID from the move record if available (though raw_material_production_id might be a relation)
|
|
||||||
// The most reliable way is if it was passed in props.context which we did in XML
|
|
||||||
if (this.props.record.data.raw_material_production_id[0]) {
|
|
||||||
context = {
|
|
||||||
...context,
|
|
||||||
active_mo_id: this.props.record.data.raw_material_production_id[0],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also inject picking_type_id if available in props.context
|
|
||||||
if (this.props.context.default_picking_type_id) {
|
|
||||||
context = {
|
|
||||||
...context,
|
|
||||||
default_picking_type_id: this.props.context.default_picking_type_id,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Final Sync: ensure allowed locations from the parent view are passed to the modal
|
|
||||||
if (this.props.context.default_allowed_source_location_ids) {
|
|
||||||
context = {
|
|
||||||
...context,
|
|
||||||
default_allowed_source_location_ids: this.props.context.default_allowed_source_location_ids,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.onAdd({ context, editable });
|
return super.onAdd({ context, editable });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 1. Sync Dirty Data (Internal Odoo Logic)
|
||||||
|
await this.updateDirtyQuantsData();
|
||||||
|
|
||||||
|
// 2. Prepare Context (Bridge to Python)
|
||||||
|
context = {
|
||||||
|
...context,
|
||||||
|
single_product: true,
|
||||||
|
list_view_ref: "stock.view_stock_quant_tree_simple",
|
||||||
|
// Pass identification keys to our Python _get_allowed_locations()
|
||||||
|
active_mo_id: context?.active_mo_id || this.props.context.active_mo_id || this.props.context.default_production_id,
|
||||||
|
default_picking_type_id: context?.default_picking_type_id || this.props.context.default_picking_type_id,
|
||||||
|
default_allowed_source_location_ids: context?.default_allowed_source_location_ids || this.props.context.default_allowed_source_location_ids,
|
||||||
|
};
|
||||||
|
|
||||||
|
const productName = this.props.record.data.product_id.display_name;
|
||||||
|
const title = _t("Add line: %s", productName);
|
||||||
|
|
||||||
|
// 3. Construct Domain (The Fix)
|
||||||
|
// Odoo natively uses ODOO_DEFAULT_LOCATION_ID which is just ONE location.
|
||||||
|
// We override this to use ALL ALLOWED locations if they exist.
|
||||||
|
const allowedLocationIds = context.default_allowed_source_location_ids;
|
||||||
|
const targetLocation = allowedLocationIds || this.props.context.default_location_id;
|
||||||
|
|
||||||
|
let domain = [
|
||||||
|
["product_id", "=", this.props.record.data.product_id.id],
|
||||||
|
["location_id", "child_of", targetLocation],
|
||||||
|
["quantity", ">", 0.0],
|
||||||
|
];
|
||||||
|
|
||||||
|
if (this.quantListViewShowOnHandOnly) {
|
||||||
|
domain.push(["on_hand", "=", true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Filter out fully used quants (Internal Odoo Logic)
|
||||||
|
if (this.dirtyQuantsData.size) {
|
||||||
|
const notFullyUsed = [];
|
||||||
|
const fullyUsed = [];
|
||||||
|
for (const [quantId, quantData] of this.dirtyQuantsData.entries()) {
|
||||||
|
if (quantData.available_quantity > 0) {
|
||||||
|
notFullyUsed.push(quantId);
|
||||||
|
} else {
|
||||||
|
fullyUsed.push(quantId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fullyUsed.length) {
|
||||||
|
// Combine domains using Odoo's Domain utility
|
||||||
|
domain = Domain.and([domain, [["id", "not in", fullyUsed]]]).toList();
|
||||||
|
}
|
||||||
|
if (notFullyUsed.length) {
|
||||||
|
domain = Domain.or([domain, [["id", "in", notFullyUsed]]]).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Open the selection modal with our custom domain
|
||||||
|
return this.selectCreate({ domain, context, title });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user