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"/> -