feat(configurator): Part cell rows 2-3 now editable — type to save
Customer feedback: rows 2 (description) and 3 (serials) in the Part cell rendered as read-only spans. User wanted to edit directly. New writable computed fields on fp.direct.order.line: - part_name_editable: compute reads part_catalog_id.name, inverse writes back to part.name on the linked catalog record - serials_text: compute joins serial_ids names with commas; inverse parses the typed string and find-or-creates fp.serial records, updates the line's serial_ids M2M Removed the redundant rev separator (display_name already includes '(Rev X)' so showing it twice was clutter). Rev edits happen by editing the part record directly via the OPEN button. OWL widget templates updated: - Row 2: <input> bound to part_name_editable, t-on-change saves - Row 3: <input> bound to serials_text, t-on-change parses + saves SCSS: - Row 2 input: italic, transparent border, focus tints background yellow - Row 3 input: small grey text, comma-separated friendly placeholder - Both disabled-look when no part is picked Both inputs trigger the inverse method on blur. The G4 sync chain takes over from there to push line.line_description etc. back to the part as before — so editing in the line keeps the part defaults fresh for future orders.
This commit is contained in:
@@ -1,25 +1,17 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
// Express Orders — multi-row Part cell widget (2026-05-26)
|
||||
// Express Orders — multi-row Part cell widget (2026-05-26, revised 2026-05-27)
|
||||
//
|
||||
// Replaces the standard Many2One renderer for `part_catalog_id` in
|
||||
// the Express Orders line list. Shows three stacked rows in ONE
|
||||
// cell, mirroring the brainstorm mockup:
|
||||
//
|
||||
// Row 1: Part # / Revision (bold; part # is the active picker)
|
||||
// Row 2: Part description (italic, muted)
|
||||
// Row 3: Serial #(s) (small grey, joined by ", ")
|
||||
// + small "+ bulk" button
|
||||
//
|
||||
// Pre-fetched fields used (read off the line record, no RPC needed):
|
||||
// - part_catalog_id (M2O, the actual picker)
|
||||
// - part_number_display (related Char from part.part_number)
|
||||
// - part_revision_display (related Char from part.revision)
|
||||
// - part_name_display (related Char from part.name)
|
||||
// - serial_ids (M2M list)
|
||||
//
|
||||
// Bulk button calls action_open_serial_bulk_add on the line which
|
||||
// opens the existing fp.serial.bulk.add.wizard.
|
||||
// Row 1: Many2OneField picker (shows part_catalog_id.display_name —
|
||||
// which is just the part_number when fp_express_part_picker
|
||||
// context flag is set).
|
||||
// Row 2: editable input bound to part_name_editable (writable compute
|
||||
// with inverse that writes part.name on the linked catalog
|
||||
// record — see fp_direct_order_line.py).
|
||||
// Row 3: editable input bound to serials_text (parses comma-separated
|
||||
// names, finds-or-creates fp.serial records, updates the line's
|
||||
// serial_ids M2M) + small "+ bulk" button that opens the existing
|
||||
// fp.serial.bulk.add.wizard for paste-list / range-fill entry.
|
||||
|
||||
import { Component } from "@odoo/owl";
|
||||
import { registry } from "@web/core/registry";
|
||||
@@ -38,37 +30,40 @@ export class FpExpressPartCell extends Component {
|
||||
this.notification = useService("notification");
|
||||
}
|
||||
|
||||
get partRev() {
|
||||
return this.props.record.data.part_revision_display || "";
|
||||
get hasPart() {
|
||||
return !!this.props.record.data.part_catalog_id;
|
||||
}
|
||||
|
||||
get partName() {
|
||||
return this.props.record.data.part_name_display || "";
|
||||
return this.props.record.data.part_name_editable || "";
|
||||
}
|
||||
|
||||
get serialsList() {
|
||||
const serials = this.props.record.data.serial_ids;
|
||||
if (!serials || !serials.records) return [];
|
||||
return serials.records
|
||||
.map(r => r.data && (r.data.display_name || r.data.name))
|
||||
.filter(Boolean);
|
||||
get serialsText() {
|
||||
return this.props.record.data.serials_text || "";
|
||||
}
|
||||
|
||||
get serialsDisplay() {
|
||||
const list = this.serialsList;
|
||||
if (!list.length) return "";
|
||||
if (list.length <= 3) return list.join(", ");
|
||||
return list.slice(0, 3).join(", ") + ` +${list.length - 3} more`;
|
||||
async onNameChange(ev) {
|
||||
const newName = (ev.target.value || "").trim();
|
||||
if (newName === this.partName.trim()) return;
|
||||
await this.props.record.update({ part_name_editable: newName });
|
||||
}
|
||||
|
||||
get hasPart() {
|
||||
return !!this.props.record.data.part_catalog_id;
|
||||
async onSerialsChange(ev) {
|
||||
const newVal = (ev.target.value || "").trim();
|
||||
if (newVal === this.serialsText.trim()) return;
|
||||
await this.props.record.update({ serials_text: newVal });
|
||||
}
|
||||
|
||||
async _ensureSaved() {
|
||||
if (!this.props.record.resId) {
|
||||
await this.props.record.save();
|
||||
}
|
||||
return !!this.props.record.resId;
|
||||
}
|
||||
|
||||
async onBulkClick(ev) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
|
||||
if (!this.hasPart) {
|
||||
this.notification.add(
|
||||
"Pick a part first, then click + bulk to add serials.",
|
||||
@@ -76,20 +71,13 @@ export class FpExpressPartCell extends Component {
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the record is brand-new (no resId yet), persist it first so
|
||||
// the bulk-add wizard has a valid target id to write into.
|
||||
if (!this.props.record.resId) {
|
||||
await this.props.record.save();
|
||||
}
|
||||
if (!this.props.record.resId) {
|
||||
if (!(await this._ensureSaved())) {
|
||||
this.notification.add(
|
||||
"Save the order first before bulk-adding serials.",
|
||||
{ type: "warning" }
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const action = await this.orm.call(
|
||||
this.props.record.resModel,
|
||||
"action_open_serial_bulk_add",
|
||||
@@ -99,8 +87,6 @@ export class FpExpressPartCell extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
// Register the field widget. supportedTypes=many2one because the field
|
||||
// it binds to (part_catalog_id) is a Many2One.
|
||||
export const fpExpressPartCell = {
|
||||
...many2OneField,
|
||||
component: FpExpressPartCell,
|
||||
|
||||
Reference in New Issue
Block a user