feat(configurator): C6 - Express Orders form view + menu (v1, stock widgets)
Spreadsheet-style flat entry view on the existing fp.direct.order.wizard model. Renders: - Customer + Shipping prominent (row 1) - PO block (PO# + attachment + Pending toggle + Expected date) - Scheduling/Lead Time + Pricing/Pricelist (row 2) - Order Lines spreadsheet with: Part #, Specification, Line Job #, Thickness, Mask toggle, Bake text, Internal Notes, Serials, Qty, Price, Subtotal, Process/Recipe (optional-show) - Notes + Terms split (internal vs customer-facing) - View-switch buttons to bounce between Express and Legacy on a draft New menu: Plating > Sales > '+ New Express Order' (sequence 3). Same DB rows as legacy view (view_source='express' for routing). v1 deferments: OWL FpExpressPartCell (multi-row part cell), OWL FpExpressBakePill (click-to-edit), DWG/OPEN/+bulk inline buttons, custom SCSS, quick-create part view, pricelist display_name override. These come in later iterations.
This commit is contained in:
@@ -50,6 +50,7 @@ Provides:
|
||||
'views/fp_sale_description_template_views.xml',
|
||||
'views/fp_serial_views.xml',
|
||||
'wizard/fp_direct_order_wizard_views.xml',
|
||||
'views/fp_express_order_views.xml',
|
||||
'wizard/fp_add_from_so_wizard_views.xml',
|
||||
'wizard/fp_add_from_quote_wizard_views.xml',
|
||||
'wizard/fp_quote_promote_wizard_views.xml',
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- ============================================================
|
||||
Express Orders form view (2026-05-26 — Phase C v1)
|
||||
|
||||
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.
|
||||
|
||||
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)
|
||||
============================================================ -->
|
||||
|
||||
<record id="view_fp_express_order_form" model="ir.ui.view">
|
||||
<field name="name">fp.express.order.form</field>
|
||||
<field name="model">fp.direct.order.wizard</field>
|
||||
<field name="priority">10</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Express Order Entry" class="o_fp_express_order">
|
||||
<header>
|
||||
<button name="action_create_order" type="object"
|
||||
string="Confirm Order"
|
||||
class="btn-primary"
|
||||
invisible="state != 'draft'"/>
|
||||
<button name="action_view_sale_order" type="object"
|
||||
string="Open Sale Order"
|
||||
class="btn-primary"
|
||||
invisible="state != 'confirmed' or not sale_order_id"/>
|
||||
<button name="action_switch_to_legacy" type="object"
|
||||
string="Switch to Legacy View"
|
||||
class="btn-secondary"
|
||||
invisible="state != 'draft'"/>
|
||||
<button name="action_cancel" type="object"
|
||||
string="Discard Draft"
|
||||
confirm="Mark this draft as cancelled? The data is preserved for audit."
|
||||
invisible="state != 'draft'"/>
|
||||
<field name="state" widget="statusbar"
|
||||
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">
|
||||
<i class="fa fa-exclamation-triangle me-2"/>
|
||||
<field name="missing_info_msg" readonly="1" nolabel="1"/>
|
||||
</div>
|
||||
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="action_view_sale_order" type="object"
|
||||
class="oe_stat_button" icon="fa-shopping-cart"
|
||||
invisible="not sale_order_id">
|
||||
<div class="o_stat_info">
|
||||
<span class="o_stat_text">Sale Order</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="oe_title">
|
||||
<label for="name" class="o_form_label"/>
|
||||
<h1><field name="name" readonly="1"/></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>
|
||||
|
||||
<!-- ====== PO Block ====== -->
|
||||
<group string="Purchase Order">
|
||||
<field name="po_number"
|
||||
placeholder="Enter the customer PO number"/>
|
||||
<field name="po_attachment_file"
|
||||
filename="po_attachment_filename"/>
|
||||
<field name="po_attachment_filename" invisible="1"/>
|
||||
<field name="po_pending" widget="boolean_toggle"/>
|
||||
<field name="po_expected_date"
|
||||
invisible="not po_pending"/>
|
||||
<div colspan="2" class="text-muted small"
|
||||
invisible="not po_pending">
|
||||
<i class="fa fa-info-circle me-1"/>
|
||||
Order will confirm without a PO. A chase activity
|
||||
will be scheduled for the expected date so sales
|
||||
follows up.
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<!-- ====== Row 2 — Scheduling + Pricing ====== -->
|
||||
<group>
|
||||
<group string="Scheduling & 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 & Fulfilment">
|
||||
<field name="pricelist_id"
|
||||
string="Currency / Pricelist"
|
||||
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>
|
||||
</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>
|
||||
<field name="total_amount"
|
||||
widget="monetary"
|
||||
options="{'currency_field': 'currency_id'}"
|
||||
readonly="1"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
<!-- ====== Notes + Terms ====== -->
|
||||
<page string="Notes & 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 & Conditions (prints on customer docs)">
|
||||
<field name="terms_and_conditions" nolabel="1"
|
||||
placeholder="Customer-facing terms — seeded from company default."/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ====== Action + Menu ====== -->
|
||||
<record id="action_fp_express_order" model="ir.actions.act_window">
|
||||
<field name="name">+ New Express Order</field>
|
||||
<field name="res_model">fp.direct.order.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="view_fp_express_order_form"/>
|
||||
<field name="target">current</field>
|
||||
<field name="context">{'default_view_source': 'express'}</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_fp_express_order"
|
||||
name="+ New Express Order"
|
||||
parent="fusion_plating_configurator.menu_fp_sales"
|
||||
action="action_fp_express_order"
|
||||
sequence="3"/>
|
||||
|
||||
</odoo>
|
||||
@@ -164,6 +164,35 @@ class FpDirectOrderWizard(models.Model):
|
||||
def _fp_default_pricelist(self):
|
||||
"""Default pricelist = company's default. Re-resolved on partner pick."""
|
||||
return self.env.company.partner_id.property_product_pricelist.id or False
|
||||
|
||||
# ---- View switching (Express ↔ Legacy) ----
|
||||
def action_switch_to_express(self):
|
||||
"""Re-open this draft in the Express view."""
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'fp.direct.order.wizard',
|
||||
'res_id': self.id,
|
||||
'view_mode': 'form',
|
||||
'view_id': self.env.ref(
|
||||
'fusion_plating_configurator.view_fp_express_order_form'
|
||||
).id,
|
||||
'target': 'current',
|
||||
}
|
||||
|
||||
def action_switch_to_legacy(self):
|
||||
"""Re-open this draft in the Legacy direct-order view."""
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'fp.direct.order.wizard',
|
||||
'res_id': self.id,
|
||||
'view_mode': 'form',
|
||||
'view_id': self.env.ref(
|
||||
'fusion_plating_configurator.view_fp_direct_order_wizard_form'
|
||||
).id,
|
||||
'target': 'current',
|
||||
}
|
||||
invoice_strategy = fields.Selection(
|
||||
[('deposit', 'Deposit'), ('progress', 'Progress Billing'),
|
||||
('net_terms', 'Net Terms'), ('cod_prepay', 'COD / Prepay')],
|
||||
|
||||
Reference in New Issue
Block a user