feat(configurator): Express form CSS-Grid rebuild — match mockup pixel layout
Previous rebuild used Odoo's <group col='4'> which renders as an HTML table — colspan+nesting broke into a vertical stack. Replaced entirely with raw <div> + CSS Grid (display: grid; grid-template-columns: repeat(4, 1fr)) so the header layout matches the mockup exactly: - Row 1: Customer (span 2) + Shipping Address (span 2) - Row 2: PO block (span 2, accent-bordered card with PO#/PDF/Pending toggle/Expected date stacked + chase warning) + Customer Job # + Job Sorting - Row 3: Material/Process Tag + Lead Time (inline X to Y) + Payment Terms + Delivery Method - Row 4: Blanket SO + Currency/Pricelist + Quote Validity + Invoice Strategy Footer also rebuilt as CSS Grid (1fr 320px) — Notes/Terms cards stacked in the left column, Totals card with Grand Total + currency pill in the right column. Each card has a title + subtitle + body matching the mockup's card chrome. SCSS overrides Odoo's default field chrome inside .o_fp_xpr_cell so inputs render with the mockup's underline style (no Bootstrap form- control border, just a 1px bottom-border that thickens on focus).
This commit is contained in:
@@ -1,91 +1,199 @@
|
|||||||
// Express Orders — main stylesheet (v1.5 / 2026-05-26)
|
// Express Orders — styles for the CSS Grid rebuild
|
||||||
//
|
// (matches .claude/mockups/express_orders.html line-for-line where Odoo allows)
|
||||||
// Tokens load FIRST via the manifest's web.assets_backend ordering;
|
|
||||||
// $xpr-* are CSS custom-property wrappers from _express_tokens.scss.
|
|
||||||
//
|
|
||||||
// Goals (matching the .claude/mockups/express_orders.html mockup):
|
|
||||||
// - Header reads as a single 4-col grid (not stacked groups)
|
|
||||||
// - PO block reads as a single consolidated card-within-card
|
|
||||||
// - Lines feel like a spreadsheet (tight borders, bordered cells)
|
|
||||||
// - Bake column reads as a coloured pill, "no bake" reads italic muted
|
|
||||||
// - Customer Line Job # reads bold + uppercase
|
|
||||||
// - Footer is side-by-side Notes/Terms left + Totals right
|
|
||||||
// - Grand Total shows a prominent currency pill
|
|
||||||
|
|
||||||
.o_fp_express_order {
|
.o_fp_xpr {
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Header — 4-col grid (.o_fp_express_header)
|
// Title pill
|
||||||
// ============================================================
|
// ============================================================
|
||||||
.o_fp_express_header {
|
.o_fp_xpr_pill {
|
||||||
// Group renders as a table by default. Tighten spacing and let
|
background: $xpr-accent-bg;
|
||||||
// the colspan-2 spans on customer + shipping breathe.
|
color: $xpr-accent;
|
||||||
td.o_td_label > label { font-weight: 600; font-size: 12px; }
|
padding: 2px 10px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// PO Block — consolidated inside the header (.o_fp_po_block)
|
// HEADER GRID — 4-column CSS Grid (mirrors mockup .header-grid)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
.o_fp_po_block {
|
.o_fp_xpr_grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 14px 24px;
|
||||||
|
align-items: start;
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each grid cell — label on top, field below
|
||||||
|
.o_fp_xpr_cell {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
min-width: 0; // allow Many2One to shrink within grid track
|
||||||
|
|
||||||
|
> label {
|
||||||
|
font-size: 11px;
|
||||||
|
color: $xpr-text-muted;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.3px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The actual field input — kill Odoo's default block-level chrome
|
||||||
|
> .o_field_widget,
|
||||||
|
> .o_field_widget > div,
|
||||||
|
> .o_field_widget input,
|
||||||
|
> .o_field_widget select,
|
||||||
|
> .o_field_many2one,
|
||||||
|
> .o_field_char,
|
||||||
|
> .o_field_date {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Field input visual — underline style like the mockup
|
||||||
|
.o_input, .o_field_widget input {
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid $xpr-border-strong;
|
||||||
|
background: transparent;
|
||||||
|
padding: 4px 0;
|
||||||
|
border-radius: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: $xpr-text;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-bottom-color: $xpr-accent;
|
||||||
|
border-bottom-width: 2px;
|
||||||
|
outline: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.o_fp_xpr_cell.span-2 { grid-column: span 2; }
|
||||||
|
.o_fp_xpr_cell.required > label::after {
|
||||||
|
content: " *";
|
||||||
|
color: $xpr-bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lead Time range — inline X to Y
|
||||||
|
.o_fp_xpr_range {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.o_fp_xpr_range .o_field_widget,
|
||||||
|
.o_fp_xpr_range input {
|
||||||
|
width: 3.5em !important;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.o_fp_xpr_range_sep {
|
||||||
|
color: $xpr-text-dim;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// PO BLOCK — consolidated card-within-card
|
||||||
|
// ============================================================
|
||||||
|
.o_fp_xpr_po_block {
|
||||||
background: $xpr-accent-bg;
|
background: $xpr-accent-bg;
|
||||||
border: 1px solid $xpr-accent;
|
border: 1px solid lighten(#d8b4d4, 5%);
|
||||||
border-left: 4px solid $xpr-accent;
|
border-left: 4px solid $xpr-accent;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 4px 8px;
|
padding: 10px 14px;
|
||||||
margin: 4px 0;
|
gap: 6px;
|
||||||
|
|
||||||
.o_horizontal_separator {
|
.o_fp_xpr_po_head {
|
||||||
color: $xpr-accent;
|
color: $xpr-accent;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.4px;
|
margin-bottom: 6px;
|
||||||
border-top: none;
|
|
||||||
padding-top: 2px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
}
|
||||||
}
|
.o_fp_xpr_po_row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 130px 1fr;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 2px 0;
|
||||||
|
|
||||||
// ============================================================
|
> label {
|
||||||
// Lines list — spreadsheet feel (.o_fp_express_lines)
|
font-size: 12px;
|
||||||
// ============================================================
|
color: $xpr-text;
|
||||||
.o_fp_express_lines .o_list_view {
|
font-weight: 500;
|
||||||
table {
|
margin: 0;
|
||||||
border-collapse: collapse;
|
text-transform: none;
|
||||||
border: 1px solid $xpr-border-table;
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
input, .o_field_widget input {
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid $xpr-border-strong;
|
||||||
|
background: transparent;
|
||||||
|
padding: 2px 0;
|
||||||
|
font-size: 13px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
thead th {
|
.o_fp_xpr_po_chase {
|
||||||
background: $xpr-table-head;
|
color: $xpr-bake-text;
|
||||||
color: $xpr-text-muted;
|
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
text-transform: uppercase;
|
font-style: italic;
|
||||||
letter-spacing: 0.3px;
|
margin-top: 4px;
|
||||||
font-weight: 600;
|
padding: 4px 0 0;
|
||||||
border-bottom: 2px solid $xpr-border-table;
|
border-top: 1px dashed $xpr-bake-border;
|
||||||
border-right: 1px solid $xpr-border-table;
|
|
||||||
padding: 6px 8px;
|
|
||||||
|
|
||||||
&:last-child { border-right: none; }
|
|
||||||
}
|
|
||||||
tbody td {
|
|
||||||
border-bottom: 1px solid $xpr-border-table;
|
|
||||||
border-right: 1px solid $xpr-border-table;
|
|
||||||
padding: 4px 8px;
|
|
||||||
|
|
||||||
&:last-child { border-right: none; }
|
|
||||||
}
|
|
||||||
tbody tr:hover {
|
|
||||||
background: $xpr-row-hover;
|
|
||||||
}
|
|
||||||
tbody tr.o_data_row:focus-within {
|
|
||||||
background: $xpr-cell-focus;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Bake column — coloured pill (text-input visual)
|
// SECTION TITLE — between header and lines
|
||||||
// ============================================================
|
// ============================================================
|
||||||
.o_fp_express_lines .o_list_view td[name="bake_instructions"] input[type="text"] {
|
.o_fp_xpr_section_title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $xpr-accent;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
margin: 16px 0 8px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
border-bottom: 1px solid $xpr-border;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// LINES TABLE — tight bordered spreadsheet
|
||||||
|
// ============================================================
|
||||||
|
.o_fp_xpr_lines .o_list_view table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border: 1px solid $xpr-border-table;
|
||||||
|
}
|
||||||
|
.o_fp_xpr_lines .o_list_view thead th {
|
||||||
|
background: $xpr-table-head;
|
||||||
|
color: $xpr-text-muted;
|
||||||
|
font-size: 11px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.3px;
|
||||||
|
font-weight: 600;
|
||||||
|
border-bottom: 2px solid $xpr-border-table;
|
||||||
|
border-right: 1px solid $xpr-border-table;
|
||||||
|
padding: 6px 8px;
|
||||||
|
|
||||||
|
&:last-child { border-right: none; }
|
||||||
|
}
|
||||||
|
.o_fp_xpr_lines .o_list_view tbody td {
|
||||||
|
border-bottom: 1px solid $xpr-border-table;
|
||||||
|
border-right: 1px solid $xpr-border-table;
|
||||||
|
padding: 4px 8px;
|
||||||
|
|
||||||
|
&:last-child { border-right: none; }
|
||||||
|
}
|
||||||
|
.o_fp_xpr_lines .o_list_view tbody tr:hover { background: $xpr-row-hover; }
|
||||||
|
.o_fp_xpr_lines .o_list_view tbody tr.o_data_row:focus-within { background: $xpr-cell-focus; }
|
||||||
|
|
||||||
|
// Bake column — coloured pill input
|
||||||
|
.o_fp_xpr_lines td[name="bake_instructions"] input[type="text"] {
|
||||||
background: $xpr-bake-bg;
|
background: $xpr-bake-bg;
|
||||||
color: $xpr-bake-text;
|
color: $xpr-bake-text;
|
||||||
border: 1px solid $xpr-bake-border;
|
border: 1px solid $xpr-bake-border;
|
||||||
@@ -93,20 +201,12 @@
|
|||||||
padding: 2px 8px;
|
padding: 2px 8px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
min-width: 100px;
|
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
color: $xpr-text-dim;
|
color: $xpr-text-dim;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
background: transparent;
|
|
||||||
}
|
}
|
||||||
&:focus {
|
|
||||||
background: $xpr-cell-focus;
|
|
||||||
border-color: $xpr-accent;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
// Empty cell → render as muted italic "no bake"
|
|
||||||
&:not(:focus):placeholder-shown {
|
&:not(:focus):placeholder-shown {
|
||||||
background: $xpr-card-soft;
|
background: $xpr-card-soft;
|
||||||
border-color: $xpr-border-strong;
|
border-color: $xpr-border-strong;
|
||||||
@@ -114,29 +214,17 @@
|
|||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Line Job # — bold uppercase narrow
|
||||||
// ============================================================
|
.o_fp_xpr_lines td[name="customer_line_ref"] input {
|
||||||
// Customer Line Job # — bold, uppercase, narrow
|
|
||||||
// ============================================================
|
|
||||||
.o_fp_express_lines .o_list_view td[name="customer_line_ref"] input {
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
max-width: 70px;
|
max-width: 70px;
|
||||||
}
|
}
|
||||||
|
// Masking toggle — bigger
|
||||||
// ============================================================
|
.o_fp_xpr_lines td[name="masking_enabled"] .form-check-input { transform: scale(1.15); }
|
||||||
// Masking toggle — bigger so it reads at a glance
|
// Inline buttons
|
||||||
// ============================================================
|
.o_fp_xpr_lines .o_fp_xpr_inline_btn {
|
||||||
.o_fp_express_lines .o_list_view td[name="masking_enabled"] {
|
|
||||||
.form-check-input,
|
|
||||||
.o_field_boolean_toggle .form-check-input { transform: scale(1.15); }
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// Inline action buttons (DWG / OPEN / + bulk)
|
|
||||||
// ============================================================
|
|
||||||
.o_fp_express_lines .o_fp_inline_btn {
|
|
||||||
font-size: 10px !important;
|
font-size: 10px !important;
|
||||||
font-weight: 600 !important;
|
font-weight: 600 !important;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
@@ -157,90 +245,108 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Footer — Notes / Terms (left card) + Totals (right card)
|
// FOOTER — Notes/Terms left + Totals right (CSS Grid)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
.o_fp_express_footer {
|
.o_fp_xpr_footer {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 320px;
|
||||||
|
gap: 24px;
|
||||||
|
align-items: start;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
.o_fp_xpr_footer_left {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
> .o_group { gap: 16px; }
|
// Cards
|
||||||
|
.o_fp_xpr_card {
|
||||||
|
background: $xpr-card;
|
||||||
|
border: 1px solid $xpr-border;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
|
||||||
.o_fp_footer_left,
|
.o_fp_xpr_card_title {
|
||||||
.o_fp_footer_totals {
|
font-size: 13px;
|
||||||
background: $xpr-card;
|
font-weight: 700;
|
||||||
|
color: $xpr-text;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
|
||||||
|
.o_fp_xpr_chip {
|
||||||
|
background: $xpr-accent-bg;
|
||||||
|
color: $xpr-accent;
|
||||||
|
padding: 1px 8px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
margin-left: 6px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.o_fp_xpr_card_sub {
|
||||||
|
font-size: 11px;
|
||||||
|
color: $xpr-text-muted;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Textareas inside cards
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 90px;
|
||||||
border: 1px solid $xpr-border;
|
border: 1px solid $xpr-border;
|
||||||
border-radius: 4px;
|
border-radius: 3px;
|
||||||
padding: 12px 16px;
|
background: $xpr-card-soft;
|
||||||
}
|
color: $xpr-text;
|
||||||
|
padding: 8px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
resize: vertical;
|
||||||
|
|
||||||
.o_fp_footer_left {
|
&:focus {
|
||||||
.o_horizontal_separator {
|
border-color: $xpr-accent;
|
||||||
color: $xpr-text-muted;
|
background: $xpr-card;
|
||||||
font-size: 11px;
|
outline: none;
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.4px;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
.o_horizontal_separator:first-child { margin-top: 0; }
|
|
||||||
textarea { min-height: 80px; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.o_fp_footer_totals {
|
|
||||||
.o_horizontal_separator {
|
|
||||||
color: $xpr-text-muted;
|
|
||||||
font-size: 11px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.4px;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// Totals card
|
||||||
// Grand Total — bigger + currency pill
|
.o_fp_xpr_totals {
|
||||||
// ============================================================
|
.o_fp_xpr_total_row {
|
||||||
.o_fp_grand_total_label {
|
display: flex;
|
||||||
font-size: 14px;
|
justify-content: space-between;
|
||||||
font-weight: 700;
|
align-items: center;
|
||||||
color: $xpr-text;
|
padding: 6px 0;
|
||||||
padding-top: 6px;
|
font-size: 13px;
|
||||||
border-top: 2px solid $xpr-text;
|
color: $xpr-text;
|
||||||
margin-top: 4px;
|
|
||||||
|
.o_fp_xpr_total_label { color: $xpr-text-muted; }
|
||||||
|
}
|
||||||
|
.o_fp_xpr_total_row.o_fp_xpr_grand {
|
||||||
|
border-top: 2px solid $xpr-text;
|
||||||
|
margin-top: 8px;
|
||||||
|
padding-top: 12px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
|
||||||
|
.o_fp_xpr_total_label { color: $xpr-text; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.o_fp_grand_total {
|
.o_fp_xpr_currency_pill {
|
||||||
font-size: 20px;
|
background: $xpr-accent-bg !important;
|
||||||
font-weight: 700;
|
color: $xpr-accent !important;
|
||||||
color: $xpr-text;
|
|
||||||
padding-top: 6px;
|
|
||||||
border-top: 2px solid $xpr-text;
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
.o_fp_currency_pill {
|
|
||||||
background: $xpr-accent-bg;
|
|
||||||
color: $xpr-accent;
|
|
||||||
padding: 2px 10px;
|
padding: 2px 10px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// Section separators inside the sheet
|
|
||||||
// ============================================================
|
|
||||||
> .o_form_sheet .o_horizontal_separator {
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: $xpr-accent;
|
|
||||||
letter-spacing: 0.3px;
|
|
||||||
border-top: 1px solid $xpr-border;
|
|
||||||
padding-top: 6px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// view_source badge column on drafts list — accent-coloured for Express
|
||||||
// view_source badge column — Express vs Legacy in drafts list
|
|
||||||
// ============================================================
|
|
||||||
.o_list_view .badge.text-bg-info {
|
.o_list_view .badge.text-bg-info {
|
||||||
background-color: $xpr-accent !important;
|
background-color: $xpr-accent !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,21 +2,14 @@
|
|||||||
<odoo>
|
<odoo>
|
||||||
|
|
||||||
<!-- ============================================================
|
<!-- ============================================================
|
||||||
Express Orders form view (2026-05-26 — Phase C v1.5 rebuild)
|
Express Orders form view (2026-05-26 — rebuild #2)
|
||||||
|
|
||||||
Spreadsheet-style flat entry — header as a 4-column grid,
|
Uses raw <div> + CSS Grid for the header to match the mockup's
|
||||||
PO block as a consolidated multi-row mini-card, lines with
|
4-column flat grid layout. Odoo's <group col="4"> renders as
|
||||||
inline DWG/OPEN/+bulk action buttons, footer as side-by-side
|
an HTML table with broken cells when colspan + nested groups
|
||||||
Notes/Terms cards + Totals card with the currency pill.
|
are involved — switching to manual divs.
|
||||||
|
|
||||||
Matches docs/superpowers/2026-05-25-express-orders-brainstorm-handoff
|
Same model (fp.direct.order.wizard) as the legacy view.
|
||||||
+ .claude/mockups/express_orders.html as closely as Odoo's
|
|
||||||
form-view constraints allow.
|
|
||||||
|
|
||||||
Still deferred (separate OWL widget pass):
|
|
||||||
- Multi-row Part cell (FpExpressPartCell — part# stacked
|
|
||||||
over description over serial list + bulk button)
|
|
||||||
- Click-to-edit Bake pill (FpExpressBakePill)
|
|
||||||
============================================================ -->
|
============================================================ -->
|
||||||
|
|
||||||
<record id="view_fp_express_order_form" model="ir.ui.view">
|
<record id="view_fp_express_order_form" model="ir.ui.view">
|
||||||
@@ -24,7 +17,7 @@
|
|||||||
<field name="model">fp.direct.order.wizard</field>
|
<field name="model">fp.direct.order.wizard</field>
|
||||||
<field name="priority">10</field>
|
<field name="priority">10</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form string="Express Order Entry" class="o_fp_express_order">
|
<form string="Express Order Entry" class="o_fp_xpr">
|
||||||
<header>
|
<header>
|
||||||
<button name="action_create_order" type="object"
|
<button name="action_create_order" type="object"
|
||||||
string="Confirm Order"
|
string="Confirm Order"
|
||||||
@@ -68,7 +61,7 @@
|
|||||||
<label for="name" class="o_form_label"/>
|
<label for="name" class="o_form_label"/>
|
||||||
<h1 class="d-flex align-items-center gap-2">
|
<h1 class="d-flex align-items-center gap-2">
|
||||||
<field name="name" readonly="1"/>
|
<field name="name" readonly="1"/>
|
||||||
<span class="badge text-bg-info" style="font-size: 10px;">EXPRESS</span>
|
<span class="o_fp_xpr_pill">EXPRESS</span>
|
||||||
</h1>
|
</h1>
|
||||||
<field name="user_id" readonly="state != 'draft'"
|
<field name="user_id" readonly="state != 'draft'"
|
||||||
options="{'no_create': True}"/>
|
options="{'no_create': True}"/>
|
||||||
@@ -76,84 +69,116 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- =========================================================
|
<!-- =========================================================
|
||||||
ROW 1 — Customer (span 2) + Shipping (span 2) — 4-col grid
|
HEADER GRID — pure CSS Grid (4 cols × 4 rows)
|
||||||
========================================================= -->
|
========================================================= -->
|
||||||
<group col="4" class="o_fp_express_header">
|
<div class="o_fp_xpr_grid">
|
||||||
<field name="partner_id"
|
|
||||||
colspan="2"
|
|
||||||
options="{'no_create_edit': True}"/>
|
|
||||||
<field name="partner_shipping_id"
|
|
||||||
colspan="2"
|
|
||||||
options="{'no_create_edit': False}"
|
|
||||||
invisible="not partner_id"/>
|
|
||||||
|
|
||||||
<!-- ============================================
|
<!-- ROW 1 -->
|
||||||
ROW 2 — PO Block (span 2) + Job# + Job Sorting
|
<div class="o_fp_xpr_cell span-2 required">
|
||||||
============================================ -->
|
<label for="partner_id">Customer</label>
|
||||||
<group colspan="2" class="o_fp_po_block">
|
<field name="partner_id" nolabel="1"
|
||||||
<separator string="Customer PO" colspan="2"/>
|
options="{'no_create_edit': True}"/>
|
||||||
<field name="po_number"
|
</div>
|
||||||
placeholder="Enter the customer PO number"/>
|
<div class="o_fp_xpr_cell span-2">
|
||||||
<field name="po_attachment_file"
|
<label for="partner_shipping_id">Shipping Address</label>
|
||||||
filename="po_attachment_filename"
|
<field name="partner_shipping_id" nolabel="1"
|
||||||
string="PO Document (PDF)"/>
|
options="{'no_create_edit': False}"
|
||||||
<field name="po_attachment_filename" invisible="1"/>
|
invisible="not partner_id"/>
|
||||||
<field name="po_pending" widget="boolean_toggle"
|
</div>
|
||||||
string="PO Pending"/>
|
|
||||||
<field name="po_expected_date"
|
<!-- ROW 2 — PO block (span 2) + Job# + Job Sorting -->
|
||||||
string="PO Expected By"
|
<div class="o_fp_xpr_cell span-2 o_fp_xpr_po_block">
|
||||||
invisible="not po_pending"/>
|
<div class="o_fp_xpr_po_head">CUSTOMER PO</div>
|
||||||
<div colspan="2" class="alert alert-warning py-1 my-1 small"
|
<div class="o_fp_xpr_po_row">
|
||||||
role="alert"
|
<label for="po_number">PO #</label>
|
||||||
invisible="not po_pending">
|
<field name="po_number" nolabel="1"
|
||||||
|
placeholder="Enter the customer PO number"/>
|
||||||
|
</div>
|
||||||
|
<div class="o_fp_xpr_po_row">
|
||||||
|
<label for="po_attachment_file">PDF</label>
|
||||||
|
<field name="po_attachment_file" nolabel="1"
|
||||||
|
filename="po_attachment_filename"/>
|
||||||
|
<field name="po_attachment_filename" invisible="1"/>
|
||||||
|
</div>
|
||||||
|
<div class="o_fp_xpr_po_row">
|
||||||
|
<label for="po_pending">PO Pending</label>
|
||||||
|
<field name="po_pending" nolabel="1"
|
||||||
|
widget="boolean_toggle"/>
|
||||||
|
</div>
|
||||||
|
<div class="o_fp_xpr_po_row" invisible="not po_pending">
|
||||||
|
<label for="po_expected_date">Expected By</label>
|
||||||
|
<field name="po_expected_date" nolabel="1"/>
|
||||||
|
</div>
|
||||||
|
<div class="o_fp_xpr_po_chase" invisible="not po_pending">
|
||||||
<i class="fa fa-clock-o me-1"/>
|
<i class="fa fa-clock-o me-1"/>
|
||||||
Order will confirm without a PO. A chase activity
|
Order will confirm without a PO. A chase activity
|
||||||
will be scheduled for the expected date.
|
will fire on the expected date.
|
||||||
</div>
|
</div>
|
||||||
</group>
|
|
||||||
<field name="customer_job_number"
|
|
||||||
string="Customer Job #"/>
|
|
||||||
<field name="job_sort_id"
|
|
||||||
string="Job Sorting"
|
|
||||||
options="{'no_create_edit': False, 'no_open': True}"
|
|
||||||
placeholder="Type to create a new bucket..."/>
|
|
||||||
|
|
||||||
<!-- ============================================
|
|
||||||
ROW 3 — Material + Lead Time + Payment + Delivery
|
|
||||||
============================================ -->
|
|
||||||
<field name="material_process"
|
|
||||||
string="Material / Process Tag"
|
|
||||||
placeholder="e.g. ENP-STEEL-HP-ADVANCED"/>
|
|
||||||
<label for="lead_time_min_days" string="Lead Time (days)"/>
|
|
||||||
<div class="o_row">
|
|
||||||
<field name="lead_time_min_days" class="oe_inline" style="width: 3em;"/>
|
|
||||||
<span> to </span>
|
|
||||||
<field name="lead_time_max_days" class="oe_inline" style="width: 3em;"/>
|
|
||||||
</div>
|
</div>
|
||||||
<field name="payment_term_id"
|
<div class="o_fp_xpr_cell">
|
||||||
string="Payment Terms"
|
<label for="customer_job_number">Customer Job #</label>
|
||||||
options="{'no_create': True}"/>
|
<field name="customer_job_number" nolabel="1"/>
|
||||||
<field name="delivery_method"
|
</div>
|
||||||
string="Delivery Method"/>
|
<div class="o_fp_xpr_cell">
|
||||||
|
<label for="job_sort_id">Job Sorting</label>
|
||||||
|
<field name="job_sort_id" nolabel="1"
|
||||||
|
options="{'no_create_edit': False, 'no_open': True}"
|
||||||
|
placeholder="Type to create a new bucket..."/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- ============================================
|
<!-- ROW 3 — Material + Lead + Payment + Delivery -->
|
||||||
ROW 4 — Blanket + Currency + Quote Valid + Invoice
|
<div class="o_fp_xpr_cell">
|
||||||
============================================ -->
|
<label for="material_process">Material / Process Tag</label>
|
||||||
<field name="is_blanket_order"
|
<field name="material_process" nolabel="1"
|
||||||
string="Blanket SO"
|
placeholder="e.g. ENP-STEEL-HP-ADVANCED"/>
|
||||||
widget="boolean_toggle"/>
|
</div>
|
||||||
<field name="pricelist_id"
|
<div class="o_fp_xpr_cell">
|
||||||
string="Currency / Pricelist"
|
<label for="lead_time_min_days">Lead Time (days)</label>
|
||||||
context="{'fp_express_currency_picker': True}"
|
<div class="o_fp_xpr_range">
|
||||||
options="{'no_create_edit': True}"/>
|
<field name="lead_time_min_days" nolabel="1"
|
||||||
<field name="validity_date"
|
class="o_fp_xpr_range_input"/>
|
||||||
string="Quote Validity"/>
|
<span class="o_fp_xpr_range_sep">to</span>
|
||||||
<field name="invoice_strategy"
|
<field name="lead_time_max_days" nolabel="1"
|
||||||
string="Invoice Strategy"/>
|
class="o_fp_xpr_range_input"/>
|
||||||
</group>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="o_fp_xpr_cell">
|
||||||
|
<label for="payment_term_id">Payment Terms</label>
|
||||||
|
<field name="payment_term_id" nolabel="1"
|
||||||
|
options="{'no_create': True}"/>
|
||||||
|
</div>
|
||||||
|
<div class="o_fp_xpr_cell">
|
||||||
|
<label for="delivery_method">Delivery Method</label>
|
||||||
|
<field name="delivery_method" nolabel="1"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- ====== Order Lines — the spreadsheet ====== -->
|
<!-- ROW 4 — Blanket + Currency + Quote Valid + Invoice -->
|
||||||
<separator string="Order Lines"/>
|
<div class="o_fp_xpr_cell">
|
||||||
|
<label for="is_blanket_order">Blanket SO</label>
|
||||||
|
<field name="is_blanket_order" nolabel="1"
|
||||||
|
widget="boolean_toggle"/>
|
||||||
|
</div>
|
||||||
|
<div class="o_fp_xpr_cell">
|
||||||
|
<label for="pricelist_id">Currency / Pricelist</label>
|
||||||
|
<field name="pricelist_id" nolabel="1"
|
||||||
|
context="{'fp_express_currency_picker': True}"
|
||||||
|
options="{'no_create_edit': True}"/>
|
||||||
|
</div>
|
||||||
|
<div class="o_fp_xpr_cell">
|
||||||
|
<label for="validity_date">Quote Validity</label>
|
||||||
|
<field name="validity_date" nolabel="1"/>
|
||||||
|
</div>
|
||||||
|
<div class="o_fp_xpr_cell">
|
||||||
|
<label for="invoice_strategy">Invoice Strategy</label>
|
||||||
|
<field name="invoice_strategy" nolabel="1"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- =========================================================
|
||||||
|
ORDER LINES — spreadsheet
|
||||||
|
========================================================= -->
|
||||||
|
<div class="o_fp_xpr_section_title">Order Lines</div>
|
||||||
<div class="mb-2 d-flex gap-2">
|
<div class="mb-2 d-flex gap-2">
|
||||||
<button name="action_add_from_prior_so"
|
<button name="action_add_from_prior_so"
|
||||||
type="object"
|
type="object"
|
||||||
@@ -166,7 +191,7 @@
|
|||||||
class="btn-secondary btn-sm"
|
class="btn-secondary btn-sm"
|
||||||
invisible="not partner_id"/>
|
invisible="not partner_id"/>
|
||||||
</div>
|
</div>
|
||||||
<field name="line_ids" class="o_fp_express_lines">
|
<field name="line_ids" class="o_fp_xpr_lines">
|
||||||
<list editable="bottom"
|
<list editable="bottom"
|
||||||
decoration-warning="is_missing_info">
|
decoration-warning="is_missing_info">
|
||||||
<field name="is_missing_info" column_invisible="1"/>
|
<field name="is_missing_info" column_invisible="1"/>
|
||||||
@@ -176,23 +201,12 @@
|
|||||||
context="{'default_partner_id': parent.partner_id, 'default_revision': 'A'}"
|
context="{'default_partner_id': parent.partner_id, 'default_revision': 'A'}"
|
||||||
domain="[('partner_id', '=', parent.partner_id), ('is_latest_revision', '=', True)]"
|
domain="[('partner_id', '=', parent.partner_id), ('is_latest_revision', '=', True)]"
|
||||||
options="{'no_quick_create': True}"/>
|
options="{'no_quick_create': True}"/>
|
||||||
<field name="line_description"
|
<field name="line_description" string="Specification"/>
|
||||||
string="Specification"/>
|
<field name="customer_line_ref" string="Line Job #" placeholder="ABC"/>
|
||||||
<field name="customer_line_ref"
|
<field name="thickness_range" string="Thickness" placeholder=".0005-.0010"/>
|
||||||
string="Line Job #"
|
<field name="masking_enabled" string="Mask" widget="boolean_toggle"/>
|
||||||
placeholder="ABC"/>
|
<field name="bake_instructions" string="Bake" placeholder="no bake"/>
|
||||||
<field name="thickness_range"
|
<field name="internal_description" string="Internal Notes" optional="show"/>
|
||||||
string="Thickness"
|
|
||||||
placeholder=".0005-.0010"/>
|
|
||||||
<field name="masking_enabled"
|
|
||||||
string="Mask"
|
|
||||||
widget="boolean_toggle"/>
|
|
||||||
<field name="bake_instructions"
|
|
||||||
string="Bake"
|
|
||||||
placeholder="no bake"/>
|
|
||||||
<field name="internal_description"
|
|
||||||
string="Internal Notes"
|
|
||||||
optional="show"/>
|
|
||||||
<field name="serial_ids"
|
<field name="serial_ids"
|
||||||
widget="many2many_tags"
|
widget="many2many_tags"
|
||||||
options="{'no_quick_create': False, 'color_field': 'state_color'}"
|
options="{'no_quick_create': False, 'color_field': 'state_color'}"
|
||||||
@@ -200,8 +214,8 @@
|
|||||||
optional="show"/>
|
optional="show"/>
|
||||||
<button name="action_open_serial_bulk_add" type="object"
|
<button name="action_open_serial_bulk_add" type="object"
|
||||||
string="+ bulk"
|
string="+ bulk"
|
||||||
class="btn-link btn-sm o_fp_inline_btn"
|
class="btn-link btn-sm o_fp_xpr_inline_btn"
|
||||||
title="Bulk-add serial numbers (paste list or range fill)"
|
title="Bulk-add serial numbers"
|
||||||
invisible="not part_catalog_id"/>
|
invisible="not part_catalog_id"/>
|
||||||
<field name="quantity"/>
|
<field name="quantity"/>
|
||||||
<field name="unit_price"
|
<field name="unit_price"
|
||||||
@@ -213,13 +227,13 @@
|
|||||||
sum="Total"/>
|
sum="Total"/>
|
||||||
<button name="action_upload_drawing" type="object"
|
<button name="action_upload_drawing" type="object"
|
||||||
string="DWG"
|
string="DWG"
|
||||||
class="btn-link btn-sm o_fp_inline_btn"
|
class="btn-link btn-sm o_fp_xpr_inline_btn"
|
||||||
title="Upload a drawing for this part (saves to the part record)"
|
title="Upload a drawing for this part"
|
||||||
invisible="not part_catalog_id"/>
|
invisible="not part_catalog_id"/>
|
||||||
<button name="action_open_part" type="object"
|
<button name="action_open_part" type="object"
|
||||||
string="OPEN"
|
string="OPEN"
|
||||||
class="btn-link btn-sm o_fp_inline_btn"
|
class="btn-link btn-sm o_fp_xpr_inline_btn"
|
||||||
title="Open the part record in a modal"
|
title="Open part record"
|
||||||
invisible="not part_catalog_id"/>
|
invisible="not part_catalog_id"/>
|
||||||
<field name="process_variant_id"
|
<field name="process_variant_id"
|
||||||
string="Process / Recipe"
|
string="Process / Recipe"
|
||||||
@@ -230,44 +244,54 @@
|
|||||||
</list>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
|
|
||||||
<!-- ====== Footer — Notes/Terms (left card) + Totals (right card) ====== -->
|
<!-- =========================================================
|
||||||
<div class="o_fp_express_footer mt-3">
|
FOOTER GRID — Notes/Terms left + Totals right
|
||||||
<group col="2">
|
========================================================= -->
|
||||||
<!-- LEFT — Notes + Terms stacked -->
|
<div class="o_fp_xpr_footer">
|
||||||
<group class="o_fp_footer_left">
|
|
||||||
<separator string="Order-Level Internal Notes"/>
|
|
||||||
<field name="internal_notes" nolabel="1"
|
|
||||||
placeholder="Visible only to estimator / planner / shop. Never prints."/>
|
|
||||||
<separator string="Terms & Conditions"/>
|
|
||||||
<field name="terms_and_conditions" nolabel="1"
|
|
||||||
placeholder="Customer-facing terms — prints on quote / SO / invoice."/>
|
|
||||||
</group>
|
|
||||||
|
|
||||||
<!-- RIGHT — Totals card with currency pill -->
|
<div class="o_fp_xpr_footer_left">
|
||||||
<group class="o_fp_footer_totals">
|
<div class="o_fp_xpr_card">
|
||||||
<separator string="Totals"/>
|
<div class="o_fp_xpr_card_title">Order-Level Internal Notes</div>
|
||||||
<field name="total_line_count"
|
<div class="o_fp_xpr_card_sub">Visible only to estimator / planner / shop. Never prints.</div>
|
||||||
string="Total Lines"
|
<field name="internal_notes" nolabel="1"
|
||||||
readonly="1"/>
|
placeholder="Type internal notes..."/>
|
||||||
<field name="total_qty"
|
</div>
|
||||||
string="Total Quantity"
|
<div class="o_fp_xpr_card">
|
||||||
readonly="1"/>
|
<div class="o_fp_xpr_card_title">Terms & Conditions
|
||||||
<label for="total_amount" string="Grand Total"
|
<span class="o_fp_xpr_chip">PRINTS</span>
|
||||||
class="o_fp_grand_total_label"/>
|
|
||||||
<div class="d-flex align-items-center gap-2 o_fp_grand_total">
|
|
||||||
<field name="total_amount"
|
|
||||||
widget="monetary"
|
|
||||||
options="{'currency_field': 'currency_id'}"
|
|
||||||
readonly="1"
|
|
||||||
nolabel="1"
|
|
||||||
class="oe_inline"/>
|
|
||||||
<field name="currency_id"
|
|
||||||
readonly="1"
|
|
||||||
nolabel="1"
|
|
||||||
class="o_fp_currency_pill"/>
|
|
||||||
</div>
|
</div>
|
||||||
</group>
|
<div class="o_fp_xpr_card_sub">Customer-facing — prints on quote / SO / invoice.</div>
|
||||||
</group>
|
<field name="terms_and_conditions" nolabel="1"
|
||||||
|
placeholder="Customer-facing terms..."/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="o_fp_xpr_footer_right">
|
||||||
|
<div class="o_fp_xpr_card o_fp_xpr_totals">
|
||||||
|
<div class="o_fp_xpr_card_title">Totals</div>
|
||||||
|
<div class="o_fp_xpr_total_row">
|
||||||
|
<span class="o_fp_xpr_total_label">Total Lines</span>
|
||||||
|
<field name="total_line_count" readonly="1" nolabel="1"/>
|
||||||
|
</div>
|
||||||
|
<div class="o_fp_xpr_total_row">
|
||||||
|
<span class="o_fp_xpr_total_label">Total Quantity</span>
|
||||||
|
<field name="total_qty" readonly="1" nolabel="1"/>
|
||||||
|
</div>
|
||||||
|
<div class="o_fp_xpr_total_row o_fp_xpr_grand">
|
||||||
|
<span class="o_fp_xpr_total_label">Grand Total</span>
|
||||||
|
<div class="d-flex align-items-center gap-2">
|
||||||
|
<field name="total_amount"
|
||||||
|
widget="monetary"
|
||||||
|
options="{'currency_field': 'currency_id'}"
|
||||||
|
readonly="1" nolabel="1"/>
|
||||||
|
<field name="currency_id"
|
||||||
|
readonly="1" nolabel="1"
|
||||||
|
class="o_fp_xpr_currency_pill"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</sheet>
|
</sheet>
|
||||||
|
|||||||
Reference in New Issue
Block a user