From 15e25ca50baceed4bc3a61d48ae8346f24c0eceb Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Tue, 26 May 2026 22:35:00 -0400 Subject: [PATCH] =?UTF-8?q?feat(configurator):=20Express=20form=20polish?= =?UTF-8?q?=20=E2=80=94=204=20fixes=20per=20user=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Blanket Sales Order — match legacy field shape. Renamed label from 'Blanket SO' to 'Blanket Sales Order' (matches legacy view), removed the boolean_toggle widget (defaults to checkbox), and added the sibling 'block_partial_shipments' field inline (only visible when blanket is checked, with 'Block partial shipments' helper text). 2. Column widths — give roomier columns where data needs space, tighten numeric columns. Part Number 230px, Specification min 220px, Internal Notes min 140px, Qty 60px, Price 80px, Subtotal 90px, Mask 55px, Bake 130px, Action stack 60px. 3. Stacked DWG / OPEN buttons — new OWL widget FpExpressActionBtns (express_action_btns.js + .xml) renders both buttons vertically in ONE column to save horizontal space. Widget binds to a new action_btns_anchor field (related from part_catalog_id) on the line. Each button shows tooltip + disabled state when no part is picked; DWG triggers the native file picker, OPEN navigates to the part record. 4. Field activation — clicking the cell anywhere now focuses the input, not just clicking the label. Achieved via: - cursor: text on .o_fp_xpr_cell - cursor: pointer on labels - min-height: 30px on all inputs (larger click target) - width: 100% propagated through Many2One wrappers (.o-dropdown, .o-autocomplete) so the input genuinely fills the cell - box-sizing: border-box so widths are predictable - Background tint on focus for visual feedback --- .../__manifest__.py | 5 + .../static/src/js/express_action_btns.js | 101 +++++++++++++ .../static/src/scss/express_order.scss | 133 +++++++++++++++++- .../static/src/xml/express_action_btns.xml | 22 +++ .../views/fp_express_order_views.xml | 27 ++-- .../wizard/fp_direct_order_line.py | 8 ++ 6 files changed, 276 insertions(+), 20 deletions(-) create mode 100644 fusion_plating/fusion_plating_configurator/static/src/js/express_action_btns.js create mode 100644 fusion_plating/fusion_plating_configurator/static/src/xml/express_action_btns.xml diff --git a/fusion_plating/fusion_plating_configurator/__manifest__.py b/fusion_plating/fusion_plating_configurator/__manifest__.py index 502600c1..ae1be85c 100644 --- a/fusion_plating/fusion_plating_configurator/__manifest__.py +++ b/fusion_plating/fusion_plating_configurator/__manifest__.py @@ -79,10 +79,13 @@ Provides: 'fusion_plating_configurator/static/src/scss/_express_tokens.scss', 'fusion_plating_configurator/static/src/scss/express_order.scss', # OWL widgets — multi-row Part cell + click-to-edit Bake pill + # + stacked DWG/OPEN action buttons 'fusion_plating_configurator/static/src/js/express_part_cell.js', 'fusion_plating_configurator/static/src/js/express_bake_pill.js', + 'fusion_plating_configurator/static/src/js/express_action_btns.js', 'fusion_plating_configurator/static/src/xml/express_part_cell.xml', 'fusion_plating_configurator/static/src/xml/express_bake_pill.xml', + 'fusion_plating_configurator/static/src/xml/express_action_btns.xml', ], # Register colour-aware SCSS in both bundles so the # `@if $o-webclient-color-scheme == dark` branch compiles for @@ -94,8 +97,10 @@ Provides: 'fusion_plating_configurator/static/src/scss/express_order.scss', 'fusion_plating_configurator/static/src/js/express_part_cell.js', 'fusion_plating_configurator/static/src/js/express_bake_pill.js', + 'fusion_plating_configurator/static/src/js/express_action_btns.js', 'fusion_plating_configurator/static/src/xml/express_part_cell.xml', 'fusion_plating_configurator/static/src/xml/express_bake_pill.xml', + 'fusion_plating_configurator/static/src/xml/express_action_btns.xml', ], }, 'installable': True, diff --git a/fusion_plating/fusion_plating_configurator/static/src/js/express_action_btns.js b/fusion_plating/fusion_plating_configurator/static/src/js/express_action_btns.js new file mode 100644 index 00000000..c300fd0d --- /dev/null +++ b/fusion_plating/fusion_plating_configurator/static/src/js/express_action_btns.js @@ -0,0 +1,101 @@ +/** @odoo-module **/ + +// Express Orders — stacked DWG / OPEN action buttons widget (2026-05-26) +// +// Renders BOTH the upload-drawing and open-part buttons stacked +// vertically in one list cell, saving horizontal width. The widget +// binds to part_catalog_id (read-only — picker is owned by the +// FpExpressPartCell widget on the same field; this widget is +// declared on a separate dummy column). + +import { Component } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { useService } from "@web/core/utils/hooks"; +import { standardFieldProps } from "@web/views/fields/standard_field_props"; + + +export class FpExpressActionBtns extends Component { + static template = "fusion_plating_configurator.FpExpressActionBtns"; + static props = { ...standardFieldProps }; + + setup() { + this.action = useService("action"); + this.orm = useService("orm"); + this.notification = useService("notification"); + } + + get hasPart() { + return !!this.props.record.data.part_catalog_id; + } + + async _ensureSaved() { + if (!this.props.record.resId) { + await this.props.record.save(); + } + return !!this.props.record.resId; + } + + async onUpload(ev) { + ev.stopPropagation(); + ev.preventDefault(); + if (!this.hasPart) { + this.notification.add("Pick a part first.", { type: "warning" }); + return; + } + // Trigger native file picker via hidden input + const input = document.createElement("input"); + input.type = "file"; + input.accept = ".pdf,.dwg,.dxf,.png,.jpg,.jpeg,application/pdf,image/*"; + input.onchange = async () => { + const file = input.files && input.files[0]; + if (!file) return; + const reader = new FileReader(); + reader.onload = async () => { + const base64 = reader.result.split(",")[1]; + if (!(await this._ensureSaved())) return; + try { + await this.orm.call( + this.props.record.resModel, + "action_upload_drawing", + [[this.props.record.resId]], + { + context: { + fp_drawing_file: base64, + fp_drawing_filename: file.name, + }, + }, + ); + this.notification.add(`Drawing "${file.name}" uploaded.`, { type: "success" }); + await this.props.record.load(); + } catch (e) { + this.notification.add(`Upload failed: ${e.message || e}`, { type: "danger" }); + } + }; + reader.readAsDataURL(file); + }; + input.click(); + } + + async onOpen(ev) { + ev.stopPropagation(); + ev.preventDefault(); + if (!this.hasPart) { + this.notification.add("Pick a part first.", { type: "warning" }); + return; + } + if (!(await this._ensureSaved())) return; + const action = await this.orm.call( + this.props.record.resModel, + "action_open_part", + [[this.props.record.resId]], + ); + if (action) await this.action.doAction(action); + } +} + +export const fpExpressActionBtns = { + component: FpExpressActionBtns, + supportedTypes: ["many2one"], +}; + +registry.category("fields").add("fp_express_action_btns", fpExpressActionBtns); 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 571ff76e..64394411 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 @@ -33,6 +33,7 @@ flex-direction: column; gap: 2px; min-width: 0; // allow Many2One to shrink within grid track + cursor: text; // hint that clicking the cell focuses the input > label { font-size: 11px; @@ -41,36 +42,80 @@ letter-spacing: 0.3px; font-weight: 500; margin-bottom: 0; + cursor: pointer; // label is explicitly clickable to focus input } - // The actual field input — kill Odoo's default block-level chrome + // The actual field input — kill Odoo's default block-level chrome. + // Aggressive width 100% on all known wrappers so the click target + // matches the cell's visible width. > .o_field_widget, > .o_field_widget > div, - > .o_field_widget input, - > .o_field_widget select, + > .o_field_widget > .o-dropdown, + > .o_field_widget > .o-autocomplete, > .o_field_many2one, > .o_field_char, - > .o_field_date { + > .o_field_date, + > .o_field_text, + > .o_field_integer, + > .o_field_selection { width: 100%; + display: block; + } + .o-dropdown, .o-autocomplete { + width: 100%; + display: block; } // Field input visual — underline style like the mockup - .o_input, .o_field_widget input { + .o_input, .o_field_widget input, + .o_field_widget select { border: none; border-bottom: 1px solid $xpr-border-strong; background: transparent; - padding: 4px 0; + padding: 5px 4px; border-radius: 0; font-size: 14px; color: $xpr-text; + width: 100%; + min-height: 30px; + box-sizing: border-box; + cursor: text; &:focus { border-bottom-color: $xpr-accent; border-bottom-width: 2px; outline: none; box-shadow: none; + background: $xpr-cell-focus; } } + .o_field_widget select { cursor: pointer; } + + // Ensure Boolean/toggle aligns visually with the underline + .o_field_boolean, + .o_field_boolean_toggle { + padding: 5px 0; + min-height: 30px; + display: flex; + align-items: center; + } + } + + // Inline pair (Blanket SO toggle + Block partial checkbox) + .o_fp_xpr_inline_pair { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; + padding: 5px 0; + min-height: 30px; + + .o_field_widget { width: auto !important; } + } + .o_fp_xpr_inline_help { + font-size: 11px; + color: $xpr-text-muted; + font-style: italic; } .o_fp_xpr_cell.span-2 { grid-column: span 2; } @@ -168,6 +213,50 @@ .o_fp_xpr_lines .o_list_view table { border-collapse: collapse; border: 1px solid $xpr-border-table; + table-layout: auto; + } + + // Column widths — give Part Number + Specification room, + // tighten Qty/Price/Subtotal/Mask + .o_fp_xpr_lines .o_list_view { + th[data-name="part_catalog_id"], + td[name="part_catalog_id"] { + min-width: 230px; + width: 230px; + } + th[data-name="line_description"], + td[name="line_description"] { + min-width: 220px; + } + th[data-name="customer_line_ref"], + td[name="customer_line_ref"] { width: 75px; } + th[data-name="thickness_range"], + td[name="thickness_range"] { width: 110px; } + th[data-name="masking_enabled"], + td[name="masking_enabled"] { + width: 55px; + text-align: center; + } + th[data-name="bake_instructions"], + td[name="bake_instructions"] { width: 130px; } + th[data-name="internal_description"], + td[name="internal_description"] { min-width: 140px; } + th[data-name="quantity"], + td[name="quantity"] { width: 60px; } + th[data-name="unit_price"], + td[name="unit_price"] { width: 80px; } + th[data-name="line_subtotal"], + td[name="line_subtotal"] { + width: 90px; + font-weight: 600; + } + // Stacked DWG / OPEN action column + th[data-name="action_btns_anchor"], + td[name="action_btns_anchor"] { + width: 60px; + text-align: center; + padding: 2px !important; + } } .o_fp_xpr_lines .o_list_view thead th { background: $xpr-table-head; @@ -244,6 +333,38 @@ } } + // Stacked DWG / OPEN buttons widget + .o_fp_xpr_action_stack { + display: flex; + flex-direction: column; + gap: 2px; + align-items: stretch; + + .o_fp_xpr_action_stack_btn { + font-size: 10px; + font-weight: 600; + letter-spacing: 0.5px; + text-transform: uppercase; + padding: 2px 6px; + border: 1px solid $xpr-border-strong; + border-radius: 3px; + color: $xpr-text-muted; + background: transparent; + cursor: pointer; + line-height: 1.3; + + &:hover:not(:disabled) { + color: $xpr-accent; + border-color: $xpr-accent; + background: $xpr-accent-bg; + } + &:disabled { + opacity: 0.3; + cursor: not-allowed; + } + } + } + // ============================================================ // FOOTER — Notes/Terms left + Totals right (CSS Grid) // ============================================================ diff --git a/fusion_plating/fusion_plating_configurator/static/src/xml/express_action_btns.xml b/fusion_plating/fusion_plating_configurator/static/src/xml/express_action_btns.xml new file mode 100644 index 00000000..bfbd448c --- /dev/null +++ b/fusion_plating/fusion_plating_configurator/static/src/xml/express_action_btns.xml @@ -0,0 +1,22 @@ + + + + + +
+ + +
+
+ +
diff --git a/fusion_plating/fusion_plating_configurator/views/fp_express_order_views.xml b/fusion_plating/fusion_plating_configurator/views/fp_express_order_views.xml index 874f5198..b241fa0c 100644 --- a/fusion_plating/fusion_plating_configurator/views/fp_express_order_views.xml +++ b/fusion_plating/fusion_plating_configurator/views/fp_express_order_views.xml @@ -159,11 +159,16 @@ - +
- - + +
+ + + Block partial shipments +
@@ -250,16 +255,10 @@ widget="monetary" options="{'currency_field': 'currency_id'}" sum="Total"/> -