diff --git a/fusion_plating/fusion_plating_configurator/static/src/js/express_part_cell.js b/fusion_plating/fusion_plating_configurator/static/src/js/express_part_cell.js index 78d9ddac..812a4f76 100644 --- a/fusion_plating/fusion_plating_configurator/static/src/js/express_part_cell.js +++ b/fusion_plating/fusion_plating_configurator/static/src/js/express_part_cell.js @@ -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, diff --git a/fusion_plating/fusion_plating_configurator/static/src/scss/express_order.scss b/fusion_plating/fusion_plating_configurator/static/src/scss/express_order.scss index cfa32a36..c56f6a3b 100644 --- a/fusion_plating/fusion_plating_configurator/static/src/scss/express_order.scss +++ b/fusion_plating/fusion_plating_configurator/static/src/scss/express_order.scss @@ -582,23 +582,62 @@ } } .o_fp_xpr_part_name { - font-style: italic; - font-size: 12px; - color: $xpr-text; + .o_fp_xpr_part_name_input { + width: 100%; + border: none; + background: transparent; + font-style: italic; + font-size: 12px; + color: $xpr-text; + padding: 2px 4px; + box-sizing: border-box; - &.o_fp_xpr_part_name_empty { color: $xpr-text-dim; } + &::placeholder { + color: $xpr-text-dim; + font-style: italic; + } + &:focus { + outline: none; + background: $xpr-cell-focus; + } + &:disabled { + background: transparent; + color: $xpr-text-dim; + cursor: not-allowed; + } + } } .o_fp_xpr_part_serial { - font-size: 11px; - color: $xpr-text-muted; justify-content: space-between; + gap: 6px; - .o_fp_xpr_serials { letter-spacing: 0.2px; } - .o_fp_xpr_serials_empty { - font-style: italic; - color: $xpr-text-dim; + .o_fp_xpr_serial_input { + flex: 1; + min-width: 0; + width: 100%; + border: none; + background: transparent; + font-size: 11px; + color: $xpr-text-muted; + padding: 2px 4px; + letter-spacing: 0.2px; + box-sizing: border-box; + + &::placeholder { + font-style: italic; + color: $xpr-text-dim; + } + &:focus { + outline: none; + background: $xpr-cell-focus; + color: $xpr-text; + } + &:disabled { + cursor: not-allowed; + } } .o_fp_xpr_bulk_btn { + flex: 0 0 auto; background: $xpr-section-bg; border: 1px solid $xpr-border-strong; color: $xpr-text-muted; diff --git a/fusion_plating/fusion_plating_configurator/static/src/xml/express_part_cell.xml b/fusion_plating/fusion_plating_configurator/static/src/xml/express_part_cell.xml index 7e4762fa..7dcb9a41 100644 --- a/fusion_plating/fusion_plating_configurator/static/src/xml/express_part_cell.xml +++ b/fusion_plating/fusion_plating_configurator/static/src/xml/express_part_cell.xml @@ -1,27 +1,39 @@ - +
- +
- / -
- -
- + +
+
- +
- +