feat(configurator): Express form layout rebuild — match mockup

Restructures the Express form to align with the brainstorming mockup:

Header (4-column grid via <group col='4'>):
- Row 1: Customer (colspan=2) + Shipping Address (colspan=2)
- Row 2: Consolidated PO Block (colspan=2 with PO#/PDF/Pending toggle/
  Expected date stacked + chase warning inline) + Customer Job # + Job Sorting
- Row 3: Material/Process Tag + Lead Time (X to Y inline) + Payment Terms + Delivery Method
- Row 4: Blanket SO + Currency/Pricelist + Quote Validity + Invoice Strategy

Lines: 13 inline columns including the Express-specific Line Job #,
masking toggle, bake text, plus per-line action buttons (DWG, OPEN,
+ bulk) wired to the Phase B helpers.

Footer: side-by-side cards — Notes + Terms stacked in the left card,
Totals card on the right with Total Lines / Total Qty / Grand Total
+ currency pill.

SCSS adds:
- PO block: accent-bordered card-within-card
- Lines: tight spreadsheet borders, hover row highlight
- Bake column: amber pill style, italic 'no bake' for empty
- Customer Line Job #: bold, uppercase, narrow column
- Inline action buttons: small uppercase bordered chips
- Footer cards with prominent Grand Total + currency pill

OWL multi-row Part cell (FpExpressPartCell) and click-to-edit Bake
pill (FpExpressBakePill) are still deferred — they need real OWL
components, separate pass.
This commit is contained in:
gsinghpal
2026-05-26 21:35:54 -04:00
parent 43abb8ef25
commit 713ba17e37
2 changed files with 369 additions and 185 deletions

View File

@@ -2,18 +2,21 @@
<odoo>
<!-- ============================================================
Express Orders form view (2026-05-26 — Phase C v1)
Express Orders form view (2026-05-26 — Phase C v1.5 rebuild)
Spreadsheet-style flat entry for repeat customers. Reuses the
existing fp.direct.order.wizard model end-to-end (Q1=D from the
design spec). This view is shipped alongside the legacy form;
both write to the same DB rows.
Spreadsheet-style flat entry — header as a 4-column grid,
PO block as a consolidated multi-row mini-card, lines with
inline DWG/OPEN/+bulk action buttons, footer as side-by-side
Notes/Terms cards + Totals card with the currency pill.
v1 deferments (will be added in later iterations):
- OWL widget for multi-row Part cell (FpExpressPartCell)
- OWL widget for click-to-edit Bake pill (FpExpressBakePill)
- Inline DWG / OPEN / + bulk buttons per line
- Custom SCSS (uses stock Odoo styles)
Matches docs/superpowers/2026-05-25-express-orders-brainstorm-handoff
+ .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">
@@ -43,13 +46,6 @@
statusbar_visible="draft,confirmed"/>
</header>
<div class="alert alert-info py-2 mb-0 small"
role="alert"
invisible="state != 'draft'">
<i class="fa fa-bolt me-1"/>
<strong>Express Orders.</strong> Spreadsheet-style flat entry — every column on the line, every field inline.
Drafts auto-save. Switch to the Legacy view any time via the header button.
</div>
<div class="alert alert-warning mb-0"
role="alert"
invisible="not missing_info_msg">
@@ -70,174 +66,209 @@
<div class="oe_title">
<label for="name" class="o_form_label"/>
<h1><field name="name" readonly="1"/></h1>
<h1 class="d-flex align-items-center gap-2">
<field name="name" readonly="1"/>
<span class="badge text-bg-info" style="font-size: 10px;">EXPRESS</span>
</h1>
<field name="user_id" readonly="state != 'draft'"
options="{'no_create': True}"/>
<field name="view_source" invisible="1"/>
</div>
<!-- ====== Row 1 — Customer + Shipping (both prominent) ====== -->
<group>
<group string="Customer">
<field name="partner_id" options="{'no_create_edit': True}"/>
<field name="partner_invoice_id"
options="{'no_create_edit': True}"
invisible="not partner_id"/>
<field name="partner_shipping_id"
options="{'no_create_edit': False}"
invisible="not partner_id"/>
<field name="customer_job_number"/>
<field name="job_sort_id"
options="{'no_create_edit': False, 'no_open': True}"
placeholder="Type to create a new bucket..."/>
<field name="material_process"
placeholder="e.g. ENP-STEEL-HP-ADVANCED"/>
</group>
<!-- =========================================================
ROW 1 — Customer (span 2) + Shipping (span 2) — 4-col grid
========================================================= -->
<group col="4" class="o_fp_express_header">
<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"/>
<!-- ====== PO Block ====== -->
<group string="Purchase Order">
<!-- ============================================
ROW 2 — PO Block (span 2) + Job# + Job Sorting
============================================ -->
<group colspan="2" class="o_fp_po_block">
<separator string="Customer PO" colspan="2"/>
<field name="po_number"
placeholder="Enter the customer PO number"/>
<field name="po_attachment_file"
filename="po_attachment_filename"/>
filename="po_attachment_filename"
string="PO Document (PDF)"/>
<field name="po_attachment_filename" invisible="1"/>
<field name="po_pending" widget="boolean_toggle"/>
<field name="po_pending" widget="boolean_toggle"
string="PO Pending"/>
<field name="po_expected_date"
string="PO Expected By"
invisible="not po_pending"/>
<div colspan="2" class="text-muted small"
<div colspan="2" class="alert alert-warning py-1 my-1 small"
role="alert"
invisible="not po_pending">
<i class="fa fa-info-circle me-1"/>
<i class="fa fa-clock-o me-1"/>
Order will confirm without a PO. A chase activity
will be scheduled for the expected date so sales
follows up.
will be scheduled for the expected date.
</div>
</group>
</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 2 — Scheduling + Pricing ====== -->
<group>
<group string="Scheduling &amp; Lead Time">
<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: 4em;"/>
<span> to </span>
<field name="lead_time_max_days" class="oe_inline" style="width: 4em;"/>
</div>
<field name="planned_start_date"/>
<field name="customer_deadline" string="Delivery Date"/>
<field name="validity_date" string="Quote Validity"/>
<field name="is_blanket_order"/>
</group>
<group string="Pricing &amp; Fulfilment">
<field name="pricelist_id"
string="Currency / Pricelist"
context="{'fp_express_currency_picker': True}"
options="{'no_create_edit': True}"/>
<field name="payment_term_id"
options="{'no_create': True}"/>
<field name="delivery_method"/>
<field name="invoice_strategy"/>
<label for="deposit_percent"
invisible="invoice_strategy != 'deposit'"/>
<div class="o_row"
invisible="invoice_strategy != 'deposit'">
<field name="deposit_percent" nolabel="1" class="oe_inline"/>
<span class="ms-1">%</span>
</div>
</group>
<!-- ============================================
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>
<field name="payment_term_id"
string="Payment Terms"
options="{'no_create': True}"/>
<field name="delivery_method"
string="Delivery Method"/>
<!-- ============================================
ROW 4 — Blanket + Currency + Quote Valid + Invoice
============================================ -->
<field name="is_blanket_order"
string="Blanket SO"
widget="boolean_toggle"/>
<field name="pricelist_id"
string="Currency / Pricelist"
context="{'fp_express_currency_picker': True}"
options="{'no_create_edit': True}"/>
<field name="validity_date"
string="Quote Validity"/>
<field name="invoice_strategy"
string="Invoice Strategy"/>
</group>
<!-- ====== Order Lines — the spreadsheet ====== -->
<notebook>
<page string="Lines" name="lines">
<div class="mb-2 d-flex gap-2">
<button name="action_add_from_prior_so"
type="object"
string="+ Add From Prior SO"
class="btn-secondary btn-sm"
invisible="not partner_id"/>
<button name="action_add_from_quotes"
type="object"
string="+ Add From Quotes"
class="btn-secondary btn-sm"
invisible="not partner_id"/>
</div>
<field name="line_ids">
<list editable="bottom"
decoration-warning="is_missing_info">
<field name="is_missing_info" column_invisible="1"/>
<field name="sequence" widget="handle"/>
<field name="part_catalog_id"
string="Part Number"
context="{'default_partner_id': parent.partner_id, 'default_revision': 'A'}"
domain="[('partner_id', '=', parent.partner_id), ('is_latest_revision', '=', True)]"
options="{'no_quick_create': True}"/>
<field name="line_description"
string="Specification (Customer-Facing)"/>
<field name="customer_line_ref"
string="Line Job #"
placeholder="ABC"/>
<field name="thickness_range"
placeholder="e.g. .0005-.0010"/>
<field name="masking_enabled"
string="Mask"
widget="boolean_toggle"/>
<field name="bake_instructions"
string="Bake"
placeholder="empty = no bake"/>
<field name="internal_description"
string="Internal Notes"
optional="show"/>
<field name="serial_ids"
widget="many2many_tags"
options="{'no_quick_create': False, 'color_field': 'state_color'}"
domain="[('part_id', '=', part_catalog_id)]"
optional="show"/>
<field name="quantity"/>
<field name="unit_price"
widget="monetary"
options="{'currency_field': 'currency_id'}"/>
<field name="line_subtotal"
widget="monetary"
options="{'currency_field': 'currency_id'}"
sum="Total"/>
<field name="process_variant_id"
string="Process / Recipe"
options="{'no_quick_create': True}"
invisible="not part_catalog_id"
optional="hide"/>
<field name="currency_id" column_invisible="1"/>
</list>
</field>
<group class="mt-3">
<group>
<field name="total_line_count" readonly="1"/>
<field name="total_qty" readonly="1"/>
</group>
<group>
<separator string="Order Lines"/>
<div class="mb-2 d-flex gap-2">
<button name="action_add_from_prior_so"
type="object"
string="+ Add From Prior SO"
class="btn-secondary btn-sm"
invisible="not partner_id"/>
<button name="action_add_from_quotes"
type="object"
string="+ Add From Quotes"
class="btn-secondary btn-sm"
invisible="not partner_id"/>
</div>
<field name="line_ids" class="o_fp_express_lines">
<list editable="bottom"
decoration-warning="is_missing_info">
<field name="is_missing_info" column_invisible="1"/>
<field name="sequence" widget="handle"/>
<field name="part_catalog_id"
string="Part Number"
context="{'default_partner_id': parent.partner_id, 'default_revision': 'A'}"
domain="[('partner_id', '=', parent.partner_id), ('is_latest_revision', '=', True)]"
options="{'no_quick_create': True}"/>
<field name="line_description"
string="Specification"/>
<field name="customer_line_ref"
string="Line Job #"
placeholder="ABC"/>
<field name="thickness_range"
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"
widget="many2many_tags"
options="{'no_quick_create': False, 'color_field': 'state_color'}"
domain="[('part_id', '=', part_catalog_id)]"
optional="show"/>
<button name="action_open_serial_bulk_add" type="object"
string="+ bulk"
class="btn-link btn-sm o_fp_inline_btn"
title="Bulk-add serial numbers (paste list or range fill)"
invisible="not part_catalog_id"/>
<field name="quantity"/>
<field name="unit_price"
widget="monetary"
options="{'currency_field': 'currency_id'}"/>
<field name="line_subtotal"
widget="monetary"
options="{'currency_field': 'currency_id'}"
sum="Total"/>
<button name="action_upload_drawing" type="object"
string="DWG"
class="btn-link btn-sm o_fp_inline_btn"
title="Upload a drawing for this part (saves to the part record)"
invisible="not part_catalog_id"/>
<button name="action_open_part" type="object"
string="OPEN"
class="btn-link btn-sm o_fp_inline_btn"
title="Open the part record in a modal"
invisible="not part_catalog_id"/>
<field name="process_variant_id"
string="Process / Recipe"
options="{'no_quick_create': True}"
invisible="not part_catalog_id"
optional="hide"/>
<field name="currency_id" column_invisible="1"/>
</list>
</field>
<!-- ====== Footer — Notes/Terms (left card) + Totals (right card) ====== -->
<div class="o_fp_express_footer mt-3">
<group col="2">
<!-- LEFT — Notes + Terms stacked -->
<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 &amp; Conditions"/>
<field name="terms_and_conditions" nolabel="1"
placeholder="Customer-facing terms — prints on quote / SO / invoice."/>
</group>
<!-- RIGHT — Totals card with currency pill -->
<group class="o_fp_footer_totals">
<separator string="Totals"/>
<field name="total_line_count"
string="Total Lines"
readonly="1"/>
<field name="total_qty"
string="Total Quantity"
readonly="1"/>
<label for="total_amount" string="Grand Total"
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"/>
<field name="currency_id" invisible="1"/>
</group>
readonly="1"
nolabel="1"
class="oe_inline"/>
<field name="currency_id"
readonly="1"
nolabel="1"
class="o_fp_currency_pill"/>
</div>
</group>
</page>
<!-- ====== Notes + Terms ====== -->
<page string="Notes &amp; Terms" name="notes">
<group>
<group string="Order-Level Internal Notes (never prints)">
<field name="internal_notes" nolabel="1"
placeholder="Visible only to estimator / planner / shop."/>
</group>
<group string="Terms &amp; Conditions (prints on customer docs)">
<field name="terms_and_conditions" nolabel="1"
placeholder="Customer-facing terms — seeded from company default."/>
</group>
</group>
</page>
</notebook>
</group>
</div>
</sheet>
<chatter/>