Split 49 modules/suites into independent git repos; untrack from monorepo
Some checks failed
fusion_accounting CI / test (fusion_accounting_ai) (push) Has been cancelled
fusion_accounting CI / test (fusion_accounting_core) (push) Has been cancelled
fusion_accounting CI / test (fusion_accounting_migration) (push) Has been cancelled

Each top-level module/suite folder is now its own private repo on GitHub
(gsinghpal/<name>) and gitea (admin/<name>), with a fresh single initial
commit. The monorepo no longer tracks them (added to .gitignore + git rm
--cached); working-tree files are retained on disk and managed in their
own repos. The monorepo keeps shared root files (CLAUDE.md, docs/, scripts/,
tools/, AGENTS.md, WIP/obsolete dirs) and full history.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-06-07 01:54:34 -04:00
parent 2a7b315e98
commit a66cdefc01
6740 changed files with 51 additions and 1277207 deletions

View File

@@ -1,29 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_fp_additional_charge_type_list" model="ir.ui.view">
<field name="name">fp.additional.charge.type.list</field>
<field name="model">fp.additional.charge.type</field>
<field name="arch" type="xml">
<list editable="bottom">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="default_amount" widget="monetary"
options="{'currency_field': 'currency_id'}"/>
<field name="active" widget="boolean_toggle"/>
<field name="currency_id" column_invisible="1"/>
</list>
</field>
</record>
<record id="action_fp_additional_charge_type" model="ir.actions.act_window">
<field name="name">Additional Charge Types</field>
<field name="res_model">fp.additional.charge.type</field>
<field name="view_mode">list</field>
</record>
<menuitem id="menu_fp_additional_charge_type"
name="Additional Charge Types"
parent="fusion_plating.menu_fp_config_pricing_billing"
action="action_fp_additional_charge_type"
sequence="30"/>
</odoo>

View File

@@ -1,106 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Part of the Fusion Plating product family.
-->
<odoo>
<!-- ===== Window actions (must be defined before menus reference them) ===== -->
<record id="action_fp_customers" model="ir.actions.act_window">
<field name="name">Customers</field>
<field name="res_model">res.partner</field>
<field name="view_mode">list,form,kanban</field>
<field name="domain">[('customer_rank', '>', 0)]</field>
<field name="context">{'default_customer_rank': 1}</field>
</record>
<!--
The Plating app's landing screen is now resolved by a server
action defined in fusion_plating core (Phase 1):
user.x_fc_plating_landing_action_id →
company.x_fc_default_landing_action_id →
action_fp_sale_orders fallback
See fusion_plating/data/fp_landing_data.xml.
-->
<!-- ===== SALES submenu under Fusion Plating root ===== -->
<menuitem id="menu_fp_sales"
name="Sales"
parent="fusion_plating.menu_fp_root"
sequence="5"
groups="fusion_plating.group_fp_sales_rep"/>
<!-- === New Quote - top-of-menu entry point for a fresh quote === -->
<menuitem id="menu_fp_new_quote"
name="New Quote"
parent="menu_fp_sales"
action="action_fp_quote_configurator"
sequence="1"/>
<menuitem id="menu_fp_direct_order"
name="New Direct Order"
parent="menu_fp_sales"
action="action_fp_direct_order_wizard"
sequence="5"/>
<menuitem id="menu_fp_direct_order_drafts"
name="Direct Order Drafts"
parent="menu_fp_sales"
action="action_fp_direct_order_drafts"
sequence="6"/>
<menuitem id="menu_fp_quotations"
name="Quotations"
parent="menu_fp_sales"
action="action_fp_quotations"
sequence="10"/>
<menuitem id="menu_fp_sale_orders"
name="Sale Orders"
parent="menu_fp_sales"
action="action_fp_sale_orders"
sequence="20"/>
<menuitem id="menu_fp_customers"
name="Customers"
parent="menu_fp_sales"
action="action_fp_customers"
sequence="30"/>
<menuitem id="menu_fp_part_catalog"
name="Part Catalog"
parent="menu_fp_sales"
action="action_fp_part_catalog"
sequence="40"/>
<menuitem id="menu_fp_part_catalog_import"
name="Import Parts (CSV)"
parent="menu_fp_sales"
action="action_fp_part_catalog_import_wizard"
sequence="45"/>
<!-- The Configurator top-level menu was retired in Phase F (2026-05-15)
after the Promote Customer Spec refactor left only 3 admin items
under it. They've been re-homed into the Configuration hub's
themed folders, where managers expect to find admin records:
Pricing Rules → Configuration → Pricing & Billing
Materials → Configuration → Materials & Tanks
Line Desc Tpl → Configuration → Quality & Documents (in
fp_sale_description_template_views.xml)
-->
<menuitem id="menu_fp_pricing_rules"
name="Pricing Rules"
parent="fusion_plating.menu_fp_config_pricing_billing"
action="action_fp_pricing_rule"
sequence="40"
groups="group_fp_estimator,fusion_plating.group_fusion_plating_manager"/>
<menuitem id="menu_fp_part_materials"
name="Materials"
parent="fusion_plating.menu_fp_config_materials_tanks"
action="action_fp_part_material"
sequence="40"
groups="group_fp_estimator,fusion_plating.group_fusion_plating_manager"/>
</odoo>

View File

@@ -1,413 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<!-- ============================================================
Express Orders form view (2026-05-26 - rebuild #2)
Uses raw <div> + CSS Grid for the header to match the mockup's
4-column flat grid layout. Odoo's <group col="4"> renders as
an HTML table with broken cells when colspan + nested groups
are involved - switching to manual divs.
Same model (fp.direct.order.wizard) as the legacy view.
============================================================ -->
<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_xpr">
<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-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 class="d-flex align-items-center gap-2">
<field name="name" readonly="1"/>
<span class="o_fp_xpr_pill">EXPRESS</span>
</h1>
<field name="view_source" invisible="1"/>
</div>
<!-- =========================================================
HEADER GRID - pure CSS Grid (4 cols × 4 rows)
========================================================= -->
<div class="o_fp_xpr_grid">
<!-- ROW 1 -->
<div class="o_fp_xpr_cell span-2 required">
<label for="partner_id">Customer</label>
<field name="partner_id" nolabel="1"
options="{'no_create_edit': True}"/>
</div>
<div class="o_fp_xpr_cell span-2">
<label for="partner_shipping_id">Shipping Address</label>
<field name="partner_shipping_id" nolabel="1"
options="{'no_create_edit': False}"
invisible="not partner_id"/>
</div>
<!-- ============================================================
PO Block fills LEFT half (cols 1-2) across rows 2-7.
RIGHT half (cols 3-4) flows 6 pairs of fields
alongside it - Customer Job #/Job Sorting, Material
Process/Lead Time, Payment Terms/Delivery Method,
Pricelist/Quote Validity, Blanket SO/Invoice Strategy,
Sales Rep/conditional Deposit-or-Progress %.
Net: PO block height matches 6 × ~60px right stack -
no dead air on either side.
============================================================ -->
<div class="o_fp_xpr_cell span-2 row-span-6 o_fp_xpr_po_block">
<div class="o_fp_xpr_po_head">
<span>CUSTOMER PO</span>
<field name="po_status" widget="badge"
decoration-success="po_status == 'received'"
decoration-warning="po_status == 'pending'"
decoration-danger="po_status == 'missing'"
nolabel="1"/>
</div>
<div class="o_fp_xpr_po_row">
<label for="po_number">PO #</label>
<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"/>
Order will confirm without a PO. A chase activity
will fire on the expected date.
</div>
</div>
<!-- Right side row 2: Customer Job # + Job Sorting -->
<div class="o_fp_xpr_cell">
<label for="customer_job_number">Customer Job #</label>
<field name="customer_job_number" nolabel="1"/>
</div>
<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>
<!-- Right side row 3: Material/Process + Lead Time -->
<div class="o_fp_xpr_cell">
<label for="material_process">Material / Process</label>
<field name="material_process" nolabel="1"
options="{'no_create_edit': True}"
placeholder="Pick a recipe..."/>
</div>
<div class="o_fp_xpr_cell">
<label for="lead_time_min_days">Lead Time (days)</label>
<div class="o_fp_xpr_range">
<field name="lead_time_min_days" nolabel="1"
class="o_fp_xpr_range_input"/>
<span class="o_fp_xpr_range_sep">to</span>
<field name="lead_time_max_days" nolabel="1"
class="o_fp_xpr_range_input"/>
</div>
</div>
<!-- Right side row 4: Payment Terms + Delivery Method -->
<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>
<!-- Right side row 5: Pricelist + Quote Validity -->
<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>
<!-- Right side row 6: Blanket Sales Order + Invoice Strategy -->
<div class="o_fp_xpr_cell o_fp_xpr_inline_label">
<label for="is_blanket_order">Blanket Sales Order</label>
<div class="o_fp_xpr_inline_pair">
<field name="is_blanket_order" nolabel="1"
widget="boolean_toggle"/>
<field name="block_partial_shipments" nolabel="1"
widget="boolean_toggle"
invisible="not is_blanket_order"/>
<span class="o_fp_xpr_inline_help"
invisible="not is_blanket_order">Block partial</span>
</div>
</div>
<div class="o_fp_xpr_cell">
<label for="invoice_strategy">Invoice Strategy</label>
<field name="invoice_strategy" nolabel="1"/>
</div>
<!-- Right side row 7: Sales Rep + conditional Deposit/Progress % -->
<div class="o_fp_xpr_cell">
<label for="user_id">Sales Rep</label>
<field name="user_id" nolabel="1"
readonly="state != 'draft'"
options="{'no_create': True}"/>
</div>
<div class="o_fp_xpr_cell" invisible="invoice_strategy != 'deposit'">
<label for="deposit_percent">Deposit %</label>
<field name="deposit_percent" nolabel="1"/>
</div>
<div class="o_fp_xpr_cell" invisible="invoice_strategy != 'progress'">
<label for="progress_initial_percent">Progress Initial %</label>
<field name="progress_initial_percent" nolabel="1"/>
</div>
</div>
<!-- =========================================================
ORDER LINES - spreadsheet
========================================================= -->
<div class="o_fp_xpr_section_title">Order Lines</div>
<!-- Legend bar - like the mockup -->
<div class="o_fp_xpr_legend">
<span><strong>Mask ✓</strong> include all masking + de-masking recipe steps</span>
<span><strong>Bake pill</strong> click to type bake instruction (empty = skip bake)</span>
<span><strong>DWG</strong> upload drawing to part</span>
<span><strong>OPEN</strong> open the part record</span>
</div>
<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_xpr_lines">
<list editable="bottom"
decoration-warning="is_missing_info">
<field name="is_missing_info" column_invisible="1"/>
<field name="sequence" widget="handle"/>
<!-- The multi-row Part cell. Owns part_catalog_id picker
PLUS displays part_revision_display, part_name_display,
serial_ids + the inline + bulk button. -->
<field name="part_catalog_id"
string="Part Number"
widget="fp_express_part_cell"
width="230px"
context="{'default_partner_id': parent.partner_id, 'default_revision': 'A', 'fp_express_part_picker': True}"
domain="[('partner_id', '=', parent.partner_id), ('is_latest_revision', '=', True)]"
options="{'no_quick_create': True}"/>
<!-- Hidden related fields the widget reads. Must be on the
list so they're prefetched per row. -->
<field name="part_number_display" column_invisible="1"/>
<field name="part_revision_display" column_invisible="1"/>
<field name="part_name_display" column_invisible="1"/>
<!-- Writable bridges used by the Part cell widget for
editable rows 2 (description) and 3 (serials). -->
<field name="part_name_editable" column_invisible="1"/>
<field name="serials_text" column_invisible="1"/>
<field name="serial_ids"
widget="many2many_tags"
options="{'no_quick_create': False, 'color_field': 'state_color'}"
domain="[('part_id', '=', part_catalog_id)]"
column_invisible="1"/>
<!-- Specification + Internal Notes: NO width attr, let them grow with available space -->
<field name="line_description" string="Specification (Customer-Facing)"/>
<field name="customer_line_ref" string="Line Job #" placeholder="ABC" width="80px"/>
<field name="thickness_range" string="Thickness" placeholder=".0005-.0010" width="100px"/>
<field name="masking_enabled" string="Mask" widget="boolean_toggle" width="55px"/>
<field name="masking_attachment_ids" column_invisible="1"/>
<!-- Bake pill - click to edit -->
<field name="bake_instructions"
string="Bake"
widget="fp_express_bake_pill"
width="120px"/>
<field name="internal_description" string="Internal Notes" optional="show"/>
<field name="quantity" string="Qty" width="55px"/>
<field name="unit_price"
string="Price"
widget="monetary"
options="{'currency_field': 'currency_id'}"
width="80px"/>
<field name="line_subtotal"
string="Subtotal"
widget="monetary"
options="{'currency_field': 'currency_id'}"
sum="Total"
width="90px"/>
<!-- Stacked DWG / OPEN buttons in ONE column -->
<field name="action_btns_anchor"
string=" "
widget="fp_express_action_btns"
width="60px"/>
<field name="process_variant_id"
string="Process / Recipe"
options="{'no_quick_create': True}"
invisible="not part_catalog_id"
optional="hide"/>
<field name="tax_ids"
string="Tax"
widget="many2many_tags"
options="{'no_create': True}"
optional="hide"
width="110px"/>
<field name="currency_id" column_invisible="1"/>
</list>
</field>
<!-- =========================================================
FOOTER GRID - Notes/Terms left + Totals right
========================================================= -->
<div class="o_fp_xpr_footer">
<div class="o_fp_xpr_footer_left">
<div class="o_fp_xpr_card">
<div class="o_fp_xpr_card_title">Order-Level Internal Notes</div>
<div class="o_fp_xpr_card_sub">Visible only to estimator / planner / shop. Never prints.</div>
<field name="internal_notes" nolabel="1"
placeholder="Type internal notes..."/>
</div>
<div class="o_fp_xpr_card">
<div class="o_fp_xpr_card_title">Terms &amp; Conditions
<span class="o_fp_xpr_chip">PRINTS</span>
</div>
<div class="o_fp_xpr_card_sub">Customer-facing - prints on quote / SO / invoice.</div>
<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_total_row">
<span class="o_fp_xpr_total_label">Subtotal</span>
<field name="total_subtotal"
widget="monetary"
options="{'currency_field': 'currency_id'}"
readonly="1" nolabel="1"/>
</div>
<div class="o_fp_xpr_total_row">
<span class="o_fp_xpr_total_label">Tax</span>
<field name="total_tax"
widget="monetary"
options="{'currency_field': 'currency_id'}"
readonly="1" nolabel="1"/>
</div>
<div class="o_fp_xpr_total_row">
<span class="o_fp_xpr_total_label">Tooling Charge</span>
<field name="tooling_charge"
widget="monetary"
options="{'currency_field': 'currency_id'}"
nolabel="1"/>
</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>
</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>

View File

@@ -1,386 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Part of the Fusion Plating product family.
-->
<odoo>
<!-- ===== Part Catalog List View ===== -->
<record id="view_fp_part_catalog_list" model="ir.ui.view">
<field name="name">fp.part.catalog.list</field>
<field name="model">fp.part.catalog</field>
<field name="arch" type="xml">
<list string="Part Catalog" decoration-muted="not active">
<field name="part_number"/>
<field name="name" string="Part Name"/>
<field name="partner_id"/>
<field name="revision"/>
<field name="material_id" string="Material"/>
<field name="substrate_material" optional="hide"/>
<field name="surface_area"/>
<field name="complexity"/>
<field name="active" widget="boolean_toggle"/>
</list>
</field>
</record>
<!-- ===== Part Catalog Form View ===== -->
<record id="view_fp_part_catalog_form" model="ir.ui.view">
<field name="name">fp.part.catalog.form</field>
<field name="model">fp.part.catalog</field>
<field name="arch" type="xml">
<form string="Part Catalog">
<header>
<button name="action_open_revision_wizard"
string="Create New Revision"
type="object"
class="btn-secondary"
icon="fa-code-fork"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="action_view_customer"
type="object"
class="oe_stat_button"
icon="fa-user"
invisible="not partner_id">
<div class="o_stat_info">
<span class="o_stat_text">Customer</span>
</div>
</button>
<button name="action_view_sale_orders"
type="object"
class="oe_stat_button"
icon="fa-file-text-o"
invisible="sale_order_count == 0">
<field name="sale_order_count" widget="statinfo" string="Sale Orders"/>
</button>
<button name="action_view_workorders"
type="object"
class="oe_stat_button"
icon="fa-cogs"
invisible="workorder_count == 0">
<field name="workorder_count" widget="statinfo" string="Work Orders"/>
</button>
<button name="action_view_configurators"
type="object"
class="oe_stat_button"
icon="fa-sliders"
invisible="configurator_count == 0">
<field name="configurator_count" widget="statinfo" string="Quotes"/>
</button>
<button name="action_view_revisions"
type="object"
class="oe_stat_button"
icon="fa-code-fork"
invisible="revision_count &lt; 2">
<field name="revision_count" widget="statinfo" string="Revisions"/>
</button>
<button name="%(action_fp_sale_description_template)d"
type="action"
class="oe_stat_button"
icon="fa-file-text-o"
context="{'search_default_part_catalog_id': id, 'default_part_catalog_id': id}">
<field name="description_template_count" widget="statinfo" string="Descriptions"/>
</button>
<!-- Sub 3 follow-up: Process smart button. Click
opens the part-scoped Process Composer so
users have a one-tap path from any part
form to its process tree. -->
<button name="action_open_part_composer"
type="object"
class="oe_stat_button"
icon="fa-sitemap">
<div class="o_stat_info">
<span class="o_stat_value"
invisible="default_process_id">
None
</span>
<span class="o_stat_value text-success"
invisible="not default_process_id">
<i class="fa fa-check-circle"/>
</span>
<span class="o_stat_text">Process</span>
</div>
</button>
</div>
<widget name="web_ribbon" title="Archived" bg_color="text-bg-danger" invisible="active"/>
<widget name="web_ribbon" title="Superseded" bg_color="text-bg-warning" invisible="is_latest_revision"/>
<div class="oe_title">
<label for="part_number" string="Part Number"/>
<h1><field name="part_number" placeholder="e.g. VS-R392007E01"/></h1>
</div>
<group>
<group string="Identity">
<field name="name" string="Part Name"
placeholder="Descriptive part name (e.g. Valve Body Housing)"/>
<field name="partner_id"/>
<field name="revision"/>
<field name="is_latest_revision" invisible="1"/>
<field name="parent_part_id" invisible="not parent_part_id"/>
</group>
<group string="Manufacturing Defaults">
<field name="material_id"
options="{'no_quick_create': True}"/>
<field name="substrate_material" invisible="1"/>
<field name="x_fc_default_lead_time_days"/>
<field name="certificate_requirement"/>
</group>
</group>
<!-- Quality & Delivery moved into its own notebook tab below
(was a top-level group above the notebook). -->
<!-- Auto-extracted geometry from 3D model -->
<group string="3D Model Analysis"
invisible="not volume_mm3 and not bbox_summary_in and hole_count == 0">
<group>
<field name="bbox_summary_in" readonly="1"/>
<field name="volume_mm3" readonly="1"/>
<field name="bbox_length_mm" invisible="1"/>
<field name="bbox_width_mm" invisible="1"/>
<field name="bbox_height_mm" invisible="1"/>
</group>
<group>
<field name="hole_count" readonly="1"/>
<field name="hole_summary" readonly="1" invisible="not hole_summary"/>
<field name="is_manifold" widget="boolean_toggle" readonly="1"/>
</group>
<div class="alert alert-warning mb-0"
colspan="2"
invisible="is_manifold or not model_attachment_id">
<i class="fa fa-exclamation-triangle me-1"/>
<strong>Geometry warning:</strong> 3D model is not watertight (manifold).
This often indicates open shells or invalid surfaces. Review before quoting.
</div>
</group>
<notebook>
<page string="Process" name="process">
<group>
<field name="default_process_id" readonly="1"
help="The variant used by default when an order line does not pick another."/>
<field name="process_variant_count" readonly="1"/>
</group>
<div class="mt-2">
<button name="action_open_part_composer" type="object"
string="Compose"
icon="fa-wrench"
class="btn-primary"
help="Open the Process Composer to manage this part's process variants."/>
<button name="action_open_default_simple_editor" type="object"
string="Edit Default (Simple)"
icon="fa-list-alt"
class="btn-info ms-1"
invisible="not default_process_id"
help="Jump straight to the Simple Recipe Editor for the default variant - flat 2-pane drag-drop layout."/>
<button name="action_open_default_tree_editor" type="object"
string="Edit Default (Tree)"
icon="fa-sitemap"
class="btn-secondary ms-1"
invisible="not default_process_id"
help="Jump straight to the Tree Editor for the default variant."/>
</div>
<p class="text-muted mt-3">
The <strong>Compose</strong> button opens the Process Composer where you can add
multiple process <em>variants</em> for this part - for example "Standard ENP",
"Selective Masking", "Rework". One variant is flagged as default; estimators
may pick a different variant on a per-order basis. Each variant can be edited
in either the <strong>Tree</strong> or <strong>Simple</strong> view - same data,
two layouts.
</p>
<field name="process_variant_ids" readonly="1">
<list>
<field name="is_default_variant" widget="boolean_toggle" readonly="1"/>
<field name="variant_label"/>
<field name="name"/>
<field name="estimated_duration" optional="hide"/>
<button name="action_open_simple_editor" type="object"
string="Simple" icon="fa-list-alt"
class="btn-link"/>
<button name="action_open_tree_editor" type="object"
string="Tree" icon="fa-sitemap"
class="btn-link"/>
</list>
</field>
<!-- Default Specification picker added by
fusion_plating_quality view inherit. -->
<separator string="Default Thickness" class="mt-4"/>
<group>
<field name="x_fc_default_thickness_range"
placeholder="e.g. 0.0005-0.0008 mils"/>
</group>
<p class="text-muted">
Defaults pre-fill new direct-order lines
for this part. Thickness also auto-fills
from the most recent order for the same
(part, customer) pair when one exists.
</p>
</page>
<page string="Dimensions &amp; Complexity" name="dimensions">
<group>
<field name="geometry_source"/>
</group>
<group>
<group string="Surface &amp; Weight">
<label for="surface_area"/>
<div class="d-flex align-items-center gap-2">
<field name="surface_area" class="oe_inline"/>
<button name="action_calculate_surface_area" type="object"
string="Calculate from 3D Model"
class="btn-link" icon="fa-calculator"
invisible="not model_attachment_id"/>
</div>
<field name="surface_area_uom"/>
<field name="masking_area_sqin"/>
<field name="effective_area_sqin" readonly="1"/>
<field name="weight"/>
<field name="material_weight_kg" readonly="1"/>
</group>
<group string="Bounding Box">
<field name="dimensions_length"/>
<field name="dimensions_width"/>
<field name="dimensions_height"/>
</group>
<group string="Complexity">
<field name="complexity"/>
<field name="masking_zones"/>
<field name="has_blind_holes"/>
<field name="has_recesses"/>
<field name="has_threads"/>
</group>
</group>
<field name="masking_description" placeholder="e.g. Mask threaded holes, mask bore ID"/>
</page>
<page string="Attachments" name="attachments">
<group>
<!-- Upload slot: Binary field that wraps the file
in an ir.attachment on change. Hidden once a
3D model is already attached. -->
<label for="model_upload" string="Upload 3D Model"
invisible="model_attachment_id"/>
<div class="o_row" invisible="model_attachment_id">
<field name="model_upload" nolabel="1"
filename="model_upload_filename"
class="oe_inline"/>
<field name="model_upload_filename" invisible="1"/>
<span class="text-muted ms-2 small">
STEP / STP / STL / IGES / IGS / BREP
</span>
</div>
<!-- Current attachment + remove affordance -->
<label for="model_attachment_id" string="3D Model File"
invisible="not model_attachment_id"/>
<div class="o_row" invisible="not model_attachment_id">
<field name="model_attachment_id" nolabel="1" class="oe_inline"/>
</div>
<field name="drawing_attachment_ids" widget="fp_pdf_preview_binary"/>
</group>
<div invisible="not model_attachment_id" class="mt-3">
<field name="model_attachment_id" widget="fp_3d_preview" nolabel="1"/>
</div>
</page>
<page string="Descriptions" name="descriptions">
<div class="alert alert-info" role="alert">
<strong>Canned descriptions for this part.</strong>
Internal = what the shop floor sees on the WO / traveler.
Customer-Facing = what prints on SO, invoice, packing slip.
Whichever row the estimator picks on the order wizard lands both values on the SO line.
</div>
<field name="description_template_ids"
context="{'default_part_catalog_id': id, 'default_partner_id': partner_id}">
<list editable="bottom">
<field name="sequence" widget="handle"/>
<field name="name" placeholder="e.g. Standard, With threaded holes masked"/>
<field name="tag" optional="show"/>
<field name="internal_description" placeholder="What the shop floor sees on the WO / traveler"/>
<field name="customer_facing_description" placeholder="What prints on SO, invoice, packing slip"/>
<field name="usage_count" string="Used" optional="hide"/>
<field name="active" widget="boolean_toggle"/>
</list>
</field>
<separator string="Description History (auto-saved per order)"/>
<field name="description_version_ids" readonly="1">
<list>
<field name="version_no" string="#"/>
<field name="name" string="Reference"/>
<field name="customer_facing_description" string="Customer-Facing"/>
<field name="sale_order_id" string="Order"/>
<field name="create_uid" string="By"/>
<field name="create_date" string="When"/>
</list>
</field>
</page>
<page string="Revision History" name="revisions"
invisible="not parent_part_id and not revision_ids">
<field name="revision_ids" mode="list">
<list default_order="revision_date desc">
<field name="revision"/>
<field name="revision_note"/>
<field name="revision_date"/>
<field name="surface_area"/>
<field name="model_attachment_id" string="3D Model"/>
<field name="is_latest_revision" widget="boolean_toggle"/>
</list>
</field>
</page>
<page string="Notes" name="notes">
<field name="notes" placeholder="Additional notes about this part..."/>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<!-- ===== Part Catalog Search View ===== -->
<record id="view_fp_part_catalog_search" model="ir.ui.view">
<field name="name">fp.part.catalog.search</field>
<field name="model">fp.part.catalog</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<field name="part_number"/>
<field name="partner_id"/>
<field name="material_id" string="Material"/>
<separator/>
<filter string="Aluminium" name="material_aluminium" domain="[('substrate_material','=','aluminium')]"/>
<filter string="Steel" name="material_steel" domain="[('substrate_material','=','steel')]"/>
<filter string="Stainless Steel" name="material_stainless" domain="[('substrate_material','=','stainless')]"/>
<filter string="Copper" name="material_copper" domain="[('substrate_material','=','copper')]"/>
<filter string="Titanium" name="material_titanium" domain="[('substrate_material','=','titanium')]"/>
<filter string="Other" name="material_other" domain="[('substrate_material','=','other')]"/>
<separator/>
<filter string="Simple" name="complexity_simple" domain="[('complexity','=','simple')]"/>
<filter string="Moderate" name="complexity_moderate" domain="[('complexity','=','moderate')]"/>
<filter string="Complex" name="complexity_complex" domain="[('complexity','=','complex')]"/>
<filter string="Very Complex" name="complexity_very_complex" domain="[('complexity','=','very_complex')]"/>
<separator/>
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
<group>
<filter string="Customer" name="group_partner" context="{'group_by':'partner_id'}"/>
<filter string="Material" name="group_material" context="{'group_by':'material_id'}"/>
<filter string="Material Category" name="group_material_category" context="{'group_by':'substrate_material'}"/>
<filter string="Complexity" name="group_complexity" context="{'group_by':'complexity'}"/>
</group>
</search>
</field>
</record>
<!-- ===== Window Action ===== -->
<record id="action_fp_part_catalog" model="ir.actions.act_window">
<field name="name">Part Catalog</field>
<field name="res_model">fp.part.catalog</field>
<field name="view_mode">list,form</field>
<field name="search_view_id" ref="view_fp_part_catalog_search"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No parts in the catalog yet
</p>
<p>
Add customer parts with geometry, material, and complexity data
for instant re-quoting on repeat orders.
</p>
</field>
</record>
</odoo>

View File

@@ -1,92 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Part of the Fusion Plating product family.
-->
<odoo>
<record id="view_fp_part_material_list" model="ir.ui.view">
<field name="name">fp.part.material.list</field>
<field name="model">fp.part.material</field>
<field name="arch" type="xml">
<list string="Materials" editable="bottom" decoration-muted="not active">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="category"/>
<field name="density" string="Density (g/cm³)"/>
<field name="notes" optional="show"/>
<field name="active" widget="boolean_toggle"/>
</list>
</field>
</record>
<record id="view_fp_part_material_form" model="ir.ui.view">
<field name="name">fp.part.material.form</field>
<field name="model">fp.part.material</field>
<field name="arch" type="xml">
<form string="Material">
<sheet>
<widget name="web_ribbon" title="Archived" bg_color="text-bg-danger" invisible="active"/>
<div class="oe_title">
<label for="name" string="Material"/>
<h1><field name="name" placeholder="e.g. Aluminium 6061"/></h1>
</div>
<group>
<group>
<field name="category"/>
<field name="density"/>
<field name="sequence"/>
<field name="active" widget="boolean_toggle"/>
</group>
<group>
<field name="notes" placeholder="Alloy spec, source, supplier note..."/>
</group>
</group>
<div class="text-muted">
Leave Density at 0 to use the category default
(Aluminium 2.70, Steel 7.85, Stainless 8.00,
Copper 8.96, Titanium 4.51 g/cm³).
</div>
</sheet>
</form>
</field>
</record>
<record id="view_fp_part_material_search" model="ir.ui.view">
<field name="name">fp.part.material.search</field>
<field name="model">fp.part.material</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<field name="category"/>
<separator/>
<filter string="Aluminium" name="cat_aluminium" domain="[('category','=','aluminium')]"/>
<filter string="Steel" name="cat_steel" domain="[('category','=','steel')]"/>
<filter string="Stainless" name="cat_stainless" domain="[('category','=','stainless')]"/>
<filter string="Copper" name="cat_copper" domain="[('category','=','copper')]"/>
<filter string="Titanium" name="cat_titanium" domain="[('category','=','titanium')]"/>
<filter string="Other" name="cat_other" domain="[('category','=','other')]"/>
<separator/>
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
<group>
<filter string="Category" name="group_category" context="{'group_by':'category'}"/>
</group>
</search>
</field>
</record>
<record id="action_fp_part_material" model="ir.actions.act_window">
<field name="name">Materials</field>
<field name="res_model">fp.part.material</field>
<field name="view_mode">list,form</field>
<field name="search_view_id" ref="view_fp_part_material_search"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">No materials yet</p>
<p>Define the materials your shop processes. Each material
picks a category (Aluminium, Steel, etc.) used for pricing
rules and density-based weight calculations.</p>
</field>
</record>
</odoo>

View File

@@ -1,137 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Part of the Fusion Plating product family.
-->
<odoo>
<!-- ===== Pricing Rule List View ===== -->
<record id="view_fp_pricing_rule_list" model="ir.ui.view">
<field name="name">fp.pricing.rule.list</field>
<field name="model">fp.pricing.rule</field>
<field name="arch" type="xml">
<list string="Pricing Rules" decoration-muted="not active">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="substrate_material"/>
<field name="certification_level"/>
<field name="pricing_method"/>
<field name="currency_id" column_invisible="1"/>
<field name="base_rate" widget="monetary"
options="{'currency_field': 'currency_id'}" sum="Total"/>
<field name="minimum_charge" widget="monetary"
options="{'currency_field': 'currency_id'}"/>
<field name="active" widget="boolean_toggle"/>
</list>
</field>
</record>
<!-- ===== Pricing Rule Form View ===== -->
<record id="view_fp_pricing_rule_form" model="ir.ui.view">
<field name="name">fp.pricing.rule.form</field>
<field name="model">fp.pricing.rule</field>
<field name="arch" type="xml">
<form string="Pricing Rule">
<sheet>
<widget name="web_ribbon" title="Archived" bg_color="text-bg-danger" invisible="active"/>
<div class="oe_title">
<label for="name"/>
<h1><field name="name" placeholder="e.g. EN Mid-Phos Aluminium - Commercial"/></h1>
</div>
<group string="Filters">
<group>
<field name="substrate_material"/>
<field name="certification_level"/>
</group>
<group>
<div class="text-muted" colspan="2">
Leave filter fields blank to create a global rule
that matches any configuration.
</div>
</group>
</group>
<group string="Pricing">
<group>
<field name="pricing_method"/>
<field name="currency_id"/>
<field name="base_rate" widget="monetary"
options="{'currency_field': 'currency_id'}"/>
<field name="thickness_factor"/>
</group>
<group>
<field name="masking_rate_per_zone" widget="monetary"
options="{'currency_field': 'currency_id'}"/>
<field name="setup_fee" widget="monetary"
options="{'currency_field': 'currency_id'}"/>
<field name="minimum_charge" widget="monetary"
options="{'currency_field': 'currency_id'}"/>
<label for="rush_surcharge_percent"/>
<div class="o_row">
<field name="rush_surcharge_percent" nolabel="1" class="oe_inline"/>
<span class="ms-1">%</span>
</div>
</group>
</group>
<notebook>
<page string="Complexity Surcharges" name="surcharges">
<field name="complexity_surcharge_ids">
<list editable="bottom">
<field name="complexity"/>
<field name="surcharge_percent" string="Surcharge %"/>
</list>
</field>
</page>
<page string="Notes" name="notes">
<field name="notes" placeholder="Internal notes about this pricing rule..."/>
</page>
</notebook>
<group>
<field name="sequence"/>
<field name="active" widget="boolean_toggle"/>
</group>
</sheet>
</form>
</field>
</record>
<!-- ===== Pricing Rule Search View ===== -->
<record id="view_fp_pricing_rule_search" model="ir.ui.view">
<field name="name">fp.pricing.rule.search</field>
<field name="model">fp.pricing.rule</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<separator/>
<filter string="Per Square Inch" name="per_sqin" domain="[('pricing_method','=','per_sqin')]"/>
<filter string="Per Square Foot" name="per_sqft" domain="[('pricing_method','=','per_sqft')]"/>
<filter string="Per Piece" name="per_piece" domain="[('pricing_method','=','per_piece')]"/>
<filter string="Flat Rate" name="flat_rate" domain="[('pricing_method','=','flat_rate')]"/>
<separator/>
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
<group>
<filter string="Pricing Method" name="group_pricing_method" context="{'group_by':'pricing_method'}"/>
</group>
</search>
</field>
</record>
<!-- ===== Window Action ===== -->
<record id="action_fp_pricing_rule" model="ir.actions.act_window">
<field name="name">Pricing Rules</field>
<field name="res_model">fp.pricing.rule</field>
<field name="view_mode">list,form</field>
<field name="search_view_id" ref="view_fp_pricing_rule_search"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No pricing rules defined yet
</p>
<p>
Define formula-based pricing rules matched by coating
configuration, substrate material, and certification level.
The first matching rule (by sequence) wins.
</p>
</field>
</record>
</odoo>

View File

@@ -1,118 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Part of the Fusion Plating product family.
Separates shared recipe templates from part-scoped clones in the
backend so thousands of part clones don't bury the 5-10 real
templates in the main Recipes list.
* Narrow the existing Process Recipes action to templates only
(part_catalog_id = False).
* Add a sibling "Part Processes" menu + action for audit /
administration of every clone, grouped by part by default.
* Extend the search view with template / part-scoped toggles
so admins can cross between the two lists without switching
menus.
-->
<odoo>
<!-- ========== Extend search view: add part_catalog_id filters ========== -->
<record id="view_fp_process_node_search_part_scoped"
model="ir.ui.view">
<field name="name">fusion.plating.process.node.search.part.scoped</field>
<field name="model">fusion.plating.process.node</field>
<field name="inherit_id"
ref="fusion_plating.view_fp_process_node_search"/>
<field name="arch" type="xml">
<xpath expr="//filter[@name='operations']" position="after">
<separator/>
<filter name="templates_only"
string="Shared Templates"
domain="[('part_catalog_id', '=', False)]"/>
<filter name="part_scoped"
string="Part-Scoped"
domain="[('part_catalog_id', '!=', False)]"/>
</xpath>
<xpath expr="//filter[@name='group_wc']" position="after">
<filter name="group_part" string="Part"
context="{'group_by': 'part_catalog_id'}"/>
</xpath>
</field>
</record>
<!-- ========== Extend form view: Linked Parts smart button ========== -->
<!-- Smart button visible only on shared template recipes (no
part_catalog_id). Opens the list of part-cloned recipe roots
that were copied from this template. -->
<record id="view_fp_process_node_form_linked_parts"
model="ir.ui.view">
<field name="name">fusion.plating.process.node.form.linked.parts</field>
<field name="model">fusion.plating.process.node</field>
<field name="inherit_id"
ref="fusion_plating.view_fp_process_node_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='button_box']" position="inside">
<button name="action_open_cloned_recipes" type="object"
class="oe_stat_button" icon="fa-link"
invisible="node_type != 'recipe' or part_catalog_id">
<field name="cloned_recipe_count" widget="statinfo"
string="Linked Parts"/>
</button>
</xpath>
</field>
</record>
<!-- ========== Extend list view: surface part column ========== -->
<record id="view_fp_process_node_tree_part_scoped"
model="ir.ui.view">
<field name="name">fusion.plating.process.node.tree.part.scoped</field>
<field name="model">fusion.plating.process.node</field>
<field name="inherit_id"
ref="fusion_plating.view_fp_process_node_tree"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='name']" position="after">
<field name="part_catalog_id" string="Part" optional="show"/>
</xpath>
</field>
</record>
<!-- ========== Narrow main Recipes action to templates only ========== -->
<record id="fusion_plating.action_fp_process_recipe"
model="ir.actions.act_window">
<field name="domain">[('node_type', '=', 'recipe'), ('part_catalog_id', '=', False)]</field>
<field name="context">{'default_node_type': 'recipe', 'search_default_recipes_only': 1, 'search_default_templates_only': 1}</field>
</record>
<!-- ========== NEW action - Part Processes ========== -->
<record id="action_fp_process_recipe_part_scoped"
model="ir.actions.act_window">
<field name="name">Part Processes</field>
<field name="res_model">fusion.plating.process.node</field>
<field name="view_mode">list,form</field>
<field name="domain">[('node_type', '=', 'recipe'), ('part_catalog_id', '!=', False)]</field>
<field name="context">{'search_default_part_scoped': 1, 'search_default_group_part': 1}</field>
<field name="search_view_id"
ref="fusion_plating.view_fp_process_node_search"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No part-customised processes yet
</p>
<p>
This view lists every process tree that has been
composed for a specific part via the part form's
<b>Compose</b> button. Each row is a clone of a shared
template with per-part tweaks. To browse the shared
templates themselves, use <b>Process Recipes</b>.
</p>
</field>
</record>
<menuitem id="menu_fp_process_part_scoped"
name="Part Processes"
parent="fusion_plating.menu_fp_operations"
action="action_fp_process_recipe_part_scoped"
sequence="6"/>
</odoo>

View File

@@ -1,386 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Part of the Fusion Plating product family.
-->
<odoo>
<!-- ===== Configurator Form View ===== -->
<record id="view_fp_quote_configurator_form" model="ir.ui.view">
<field name="name">fp.quote.configurator.form</field>
<field name="model">fp.quote.configurator</field>
<field name="arch" type="xml">
<form string="Quote Configurator">
<header>
<button name="action_promote_to_direct_order"
string="Add to Direct Order"
type="object"
class="btn-primary"
invisible="state != 'draft'"
help="Add this quote as a line on a Direct Order draft. Multiple quotes can land on the same draft so one PO covers them all."/>
<button name="action_recalculate_price"
string="Recalculate"
type="object"
class="btn-secondary"/>
<button name="action_save_to_catalog"
string="Save to Catalog"
type="object"
class="btn-secondary"
confirm="This will overwrite the part catalog's geometry, substrate, masking area, and complexity with values from this quote. Continue?"
invisible="not part_catalog_id"/>
<button name="action_mark_lost"
string="Mark as Lost"
type="object"
class="btn-warning"
invisible="state != 'draft'"
confirm="Mark this quote as Lost? Set the Lost Reason first."/>
<button name="action_mark_expired"
string="Mark as Expired"
type="object"
invisible="state != 'draft'"/>
<button name="action_cancel"
string="Cancel"
type="object"
invisible="state == 'cancelled'"/>
<button name="action_reset_draft"
string="Reset to Draft"
type="object"
invisible="state == 'draft'"/>
<field name="state" widget="statusbar"
statusbar_visible="draft,confirmed,lost,expired,cancelled"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="action_view_sale_order"
type="object"
class="oe_stat_button"
icon="fa-file-text-o"
invisible="not sale_order_id">
<field name="sale_order_id" widget="statinfo" string="Sale Order"/>
</button>
<button name="action_view_part_catalog"
type="object"
class="oe_stat_button"
icon="fa-cube"
invisible="not part_catalog_id">
<field name="part_catalog_id" widget="statinfo" string="Part"/>
</button>
<!--
3D Model + Drawings smart buttons.
Both open a modal preview (action_open_3d_fullscreen
and action_view_drawings) that replaces what used
to be the right-column inline previews.
-->
<button name="action_open_3d_fullscreen"
type="object"
class="oe_stat_button"
icon="fa-cube"
invisible="not model_attachment_id">
<div class="o_stat_info">
<span class="o_stat_value">1</span>
<span class="o_stat_text">3D Model</span>
</div>
</button>
<button name="action_view_drawings"
type="object"
class="oe_stat_button"
icon="fa-file-pdf-o"
invisible="drawing_count == 0">
<field name="drawing_count" widget="statinfo" string="Drawings"/>
</button>
<button name="action_view_rfq"
type="object"
class="oe_stat_button"
icon="fa-envelope-o"
invisible="not rfq_attachment_id">
<div class="o_stat_info">
<span class="o_stat_value">1</span>
<span class="o_stat_text">RFQ</span>
</div>
</button>
<button name="action_view_po"
type="object"
class="oe_stat_button"
icon="fa-file-o"
invisible="not po_attachment_id">
<div class="o_stat_info">
<span class="o_stat_value">1</span>
<span class="o_stat_text">PO</span>
</div>
</button>
</div>
<div class="oe_title">
<h1>
<field name="name" readonly="1"/>
</h1>
</div>
<!--
Single-column layout. The right-side 3D viewer +
Drawing preview were removed (commit pending) - both
live behind the 3D Model / Drawings smart buttons at
the top of the form, plus inline "Preview" links
next to each respective field.
-->
<div class="o_fp_cfg_layout">
<div class="o_fp_cfg_fields">
<group>
<group string="Customer &amp; Part">
<field name="partner_id"/>
<field name="part_catalog_id"/>
<field name="recipe_id"/>
<!-- 3D File: upload before, filename + clear button after -->
<field name="upload_3d_file" filename="upload_3d_filename"
invisible="state != 'draft' or model_attachment_id"
string="Attach 3D File"/>
<field name="upload_3d_filename" invisible="1"/>
<!--
3D Model + inline Preview link. Field shows
the attachment name, the small Preview link
opens the same fullscreen wizard as the
smart button at the top of the form.
-->
<label for="model_attachment_id" string="3D Model"
invisible="not model_attachment_id"/>
<div class="o_row" invisible="not model_attachment_id">
<field name="model_attachment_id" nolabel="1"
readonly="state != 'draft'"/>
<button name="action_open_3d_fullscreen"
type="object"
string="Preview"
icon="fa-eye"
class="btn btn-link btn-sm ms-2 p-0"
title="Open 3D model preview"/>
</div>
<!-- Drawing: upload before, filename + Preview link after -->
<field name="upload_drawing" filename="upload_drawing_filename"
invisible="state != 'draft' or drawing_count > 0"
string="Attach Drawing"/>
<field name="upload_drawing_filename" invisible="1"/>
<label for="first_drawing_id" string="Drawing"
invisible="drawing_count == 0"/>
<div class="o_row" invisible="drawing_count == 0">
<field name="first_drawing_id" nolabel="1"
readonly="state != 'draft'"/>
<button name="action_view_drawings"
type="object"
string="Preview"
icon="fa-eye"
class="btn btn-link btn-sm ms-2 p-0"
title="Open drawing preview"/>
</div>
<field name="drawing_count" invisible="1"/>
</group>
<group string="RFQ / PO Documents">
<field name="upload_rfq_file"
filename="upload_rfq_filename"
invisible="state != 'draft' or rfq_attachment_id"
string="Attach RFQ"/>
<field name="upload_rfq_filename" invisible="1"/>
<field name="rfq_attachment_id"
invisible="not rfq_attachment_id"
readonly="state != 'draft'"/>
<field name="upload_po_file"
filename="upload_po_filename"
invisible="state != 'draft' or po_attachment_id"
string="Attach PO"/>
<field name="upload_po_filename" invisible="1"/>
<field name="po_attachment_id"
invisible="not po_attachment_id"
readonly="state != 'draft'"/>
<field name="po_number_preliminary"
string="PO Number"
readonly="state != 'draft'"/>
</group>
</group>
<!--
Row 2 - Quantity / Options on the LEFT, Auto-from-3D on
the RIGHT (visible only when a part catalog is linked).
Quantity moved out of the RFQ/PO group so the right
column has a peer instead of stretching alone.
-->
<group>
<group string="Quantity &amp; Options">
<field name="quantity"/>
<field name="batch_size"/>
<field name="complexity"/>
<field name="rush_order"/>
</group>
<group string="Auto from 3D"
invisible="not part_catalog_id">
<field name="bbox_summary_in"
string="Dimensions"
readonly="1"/>
<field name="material_weight_kg"
string="Weight (kg)"
readonly="1"/>
<field name="hole_count"
string="Holes"
readonly="1"/>
<field name="hole_summary"
readonly="1"
invisible="not hole_summary"/>
<field name="is_manifold"
widget="boolean_toggle"
readonly="1"/>
</group>
</group>
<div class="alert alert-warning"
invisible="is_manifold or not part_catalog_id or not hole_count">
<i class="fa fa-exclamation-triangle me-1"/>
<strong>Warning:</strong> 3D model is not watertight.
Surface area calculation may be inaccurate. Review the file before quoting.
</div>
<!--
Row 3 - Geometry on the LEFT, Delivery &amp; Fees on the
RIGHT. Delivery/Fees used to live in its own row with
an empty right side; pairing it with Geometry keeps
both columns balanced.
-->
<group>
<group string="Geometry">
<field name="surface_area"/>
<field name="surface_area_uom"/>
<field name="masking_area_sqin"
string="Masking Area (sq in)"/>
<field name="effective_area_sqin"
string="Effective Plating Area"
readonly="1"/>
<field name="thickness_requested"/>
<field name="material_id"
options="{'no_quick_create': True}"/>
<field name="substrate_material" invisible="1"/>
<field name="masking_zones"/>
<field name="turnaround_days"/>
</group>
<group string="Delivery &amp; Fees">
<field name="delivery_method"/>
<field name="currency_id" invisible="1"/>
<field name="shipping_fee" widget="monetary"
options="{'currency_field': 'currency_id'}"/>
<field name="delivery_fee" widget="monetary"
options="{'currency_field': 'currency_id'}"/>
</group>
</group>
<separator string="Pricing"/>
<group>
<group>
<field name="calculated_price" widget="monetary" readonly="1"
options="{'currency_field': 'currency_id'}"
class="fw-bold fs-4"/>
</group>
<group>
<field name="estimator_override_price" widget="monetary"
options="{'currency_field': 'currency_id'}"/>
</group>
</group>
<field name="price_breakdown_html" readonly="1" colspan="2"/>
</div>
</div>
<notebook>
<page string="Sale Order" name="sale_order">
<group>
<field name="sale_order_id" readonly="1"/>
<field name="won_date" readonly="1"/>
</group>
</page>
<page string="Win / Loss" name="win_loss">
<group>
<group>
<field name="lost_reason"/>
<field name="lost_competitor_name"
invisible="lost_reason != 'competitor'"/>
<field name="lost_date" readonly="1"/>
</group>
</group>
<separator string="Notes"/>
<field name="lost_details" colspan="2"
placeholder="What did we learn? (Price point competitor beat, spec we didn't meet, etc.)"/>
</page>
<page string="Notes" name="notes">
<field name="notes" placeholder="Internal notes about this quote..."/>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<!-- ===== Configurator List View ===== -->
<record id="view_fp_quote_configurator_list" model="ir.ui.view">
<field name="name">fp.quote.configurator.list</field>
<field name="model">fp.quote.configurator</field>
<field name="arch" type="xml">
<list string="Quote Configurators"
decoration-info="state == 'draft'"
decoration-muted="state == 'cancelled'"
default_order="create_date desc">
<field name="create_date" string="Date"/>
<field name="name"/>
<field name="partner_id"/>
<field name="recipe_id"/>
<field name="surface_area"/>
<field name="quantity"/>
<field name="currency_id" column_invisible="1"/>
<field name="calculated_price" widget="monetary"
options="{'currency_field': 'currency_id'}" sum="Total"/>
<field name="estimator_override_price" string="Final Price"
widget="monetary"
options="{'currency_field': 'currency_id'}" sum="Total"/>
<field name="state" widget="badge"
decoration-success="state == 'confirmed'"
decoration-info="state == 'draft'"
decoration-danger="state == 'cancelled'"/>
</list>
</field>
</record>
<!-- ===== Configurator Search View ===== -->
<record id="view_fp_quote_configurator_search" model="ir.ui.view">
<field name="name">fp.quote.configurator.search</field>
<field name="model">fp.quote.configurator</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<field name="partner_id"/>
<field name="recipe_id"/>
<separator/>
<filter string="Draft" name="draft" domain="[('state', '=', 'draft')]"/>
<filter string="Confirmed" name="confirmed" domain="[('state', '=', 'confirmed')]"/>
<filter string="Cancelled" name="cancelled" domain="[('state', '=', 'cancelled')]"/>
<group>
<filter string="Customer" name="group_customer" context="{'group_by': 'partner_id'}"/>
<filter string="Recipe" name="group_recipe" context="{'group_by': 'recipe_id'}"/>
<filter string="Status" name="group_state" context="{'group_by': 'state'}"/>
</group>
</search>
</field>
</record>
<!-- ===== Window Action ===== -->
<record id="action_fp_quote_configurator" model="ir.actions.act_window">
<field name="name">Quote Configurator</field>
<field name="res_model">fp.quote.configurator</field>
<field name="view_mode">list,form</field>
<field name="search_view_id" ref="view_fp_quote_configurator_search"/>
<field name="context">{}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new quote configurator session
</p>
<p>
Select a customer and coating configuration, enter part geometry,
and the pricing engine will calculate a quote. The estimator can
override the calculated price before creating a sale order.
</p>
</field>
</record>
</odoo>

View File

@@ -1,118 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Part of the Fusion Plating product family.
Manage reusable descriptions that estimators can drop onto sale order lines.
-->
<odoo>
<record id="view_fp_sale_description_template_list" model="ir.ui.view">
<field name="name">fp.sale.description.template.list</field>
<field name="model">fp.sale.description.template</field>
<field name="arch" type="xml">
<list multi_edit="1">
<field name="sequence" widget="handle"/>
<field name="part_catalog_id" optional="show"/>
<field name="name"/>
<field name="tag" widget="badge"
decoration-info="tag == 'standard'"
decoration-warning="tag == 'masking'"
decoration-danger="tag == 'rework'"
decoration-success="tag in ('aerospace','nuclear')"/>
<field name="partner_id" optional="show"/>
<field name="usage_count" string="Used"/>
<field name="active" widget="boolean_toggle"/>
</list>
</field>
</record>
<record id="view_fp_sale_description_template_form" model="ir.ui.view">
<field name="name">fp.sale.description.template.form</field>
<field name="model">fp.sale.description.template</field>
<field name="arch" type="xml">
<form>
<sheet>
<div class="oe_title">
<label for="name"/>
<h1><field name="name" placeholder="e.g. ENP - Standard Aluminium"/></h1>
</div>
<group>
<group>
<field name="part_catalog_id"/>
<field name="partner_id" readonly="part_catalog_id"/>
<field name="tag"/>
</group>
<group>
<field name="sequence"/>
<field name="usage_count" readonly="1"/>
<field name="active" widget="boolean_toggle"/>
</group>
</group>
<group string="Internal Description (Shop Floor Only)">
<field name="internal_description" nolabel="1" colspan="2"
placeholder="What the shop floor sees on the WO / traveler…"/>
</group>
<separator string="Customer-Facing Description"/>
<field name="customer_facing_description" colspan="2"
placeholder="Electroless nickel plating per AMS 2404, Class I, Type II…"/>
</sheet>
</form>
</field>
</record>
<record id="view_fp_sale_description_template_search" model="ir.ui.view">
<field name="name">fp.sale.description.template.search</field>
<field name="model">fp.sale.description.template</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<field name="internal_description"/>
<field name="customer_facing_description"/>
<field name="part_catalog_id"/>
<field name="partner_id"/>
<field name="tag"/>
<filter name="active" string="Active" domain="[('active','=',True)]"/>
<filter name="with_part" string="Part-Specific"
domain="[('part_catalog_id','!=',False)]"/>
<filter name="no_part" string="Generic (No Part)"
domain="[('part_catalog_id','=',False)]"/>
<group>
<filter name="group_part" string="Part"
context="{'group_by': 'part_catalog_id'}"/>
<filter name="group_customer" string="Customer"
context="{'group_by': 'partner_id'}"/>
<filter name="group_tag" string="Category"
context="{'group_by': 'tag'}"/>
</group>
</search>
</field>
</record>
<record id="action_fp_sale_description_template" model="ir.actions.act_window">
<field name="name">Line Description Templates</field>
<field name="res_model">fp.sale.description.template</field>
<field name="view_mode">list,form</field>
<field name="search_view_id" ref="view_fp_sale_description_template_search"/>
<field name="context">{'search_default_active': 1}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create your first line description template
</p>
<p>
Save the language you use on repeat orders - masking rules,
spec callouts, packaging notes. The estimator picks one,
tweaks it, and it lands on the order line.
</p>
</field>
</record>
<menuitem id="menu_fp_sale_description_templates"
name="Line Description Templates"
parent="fusion_plating.menu_fp_config_quality_docs"
action="action_fp_sale_description_template"
sequence="90"
groups="fusion_plating_configurator.group_fp_estimator,fusion_plating.group_fusion_plating_manager"/>
</odoo>

View File

@@ -1,191 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Part of the Fusion Plating product family.
Phase 2 (2026-04-28) - relocates the fp.serial views from
fusion_plating_bridge_mrp (uninstalled in Sub 11) into configurator
where the model lives. Adds the new state machine to the form +
list with workflow buttons + status badge.
-->
<odoo>
<record id="view_fp_serial_form" model="ir.ui.view">
<field name="name">fp.serial.form</field>
<field name="model">fp.serial</field>
<field name="arch" type="xml">
<form string="Serial Number">
<header>
<button name="action_mark_racked" type="object"
string="Mark Racked" class="btn-primary"
invisible="state != 'received'"/>
<button name="action_mark_in_process" type="object"
string="Start Processing" class="btn-primary"
invisible="state != 'racked'"/>
<button name="action_mark_inspected" type="object"
string="Pass Inspection" class="btn-success"
invisible="state != 'in_process'"/>
<button name="action_mark_packed" type="object"
string="Mark Packed" class="btn-primary"
invisible="state != 'inspected'"/>
<button name="action_mark_shipped" type="object"
string="Mark Shipped" class="btn-success"
invisible="state != 'packed'"/>
<button name="action_mark_on_hold" type="object"
string="Hold" class="btn-warning"
invisible="state in ('on_hold', 'shipped', 'scrapped')"/>
<button name="action_release_hold" type="object"
string="Release Hold" class="btn-secondary"
invisible="state != 'on_hold'"/>
<button name="action_mark_scrapped" type="object"
string="Scrap" class="btn-danger"
invisible="state in ('shipped', 'scrapped')"
confirm="Mark this serial as scrapped? This is reversible only via Reopen."/>
<button name="action_mark_returned" type="object"
string="Returned by Customer" class="btn-secondary"
invisible="state != 'shipped'"/>
<button name="action_reopen" type="object"
string="Reopen" class="btn-secondary"
groups="fusion_plating.group_fusion_plating_manager"
invisible="state not in ('shipped', 'scrapped')"
confirm="Reopen a terminal-state serial? Manager-only override; the chatter audit will record who, when, why."/>
<field name="state" widget="statusbar"
statusbar_visible="received,racked,in_process,inspected,packed,shipped"/>
</header>
<sheet>
<widget name="web_ribbon" title="Scrapped" bg_color="text-bg-danger"
invisible="state != 'scrapped'"/>
<widget name="web_ribbon" title="On Hold" bg_color="text-bg-warning"
invisible="state != 'on_hold'"/>
<div class="oe_button_box" name="button_box">
<button name="action_view_sale_order" type="object"
class="oe_stat_button" icon="fa-file-text-o"
invisible="not sale_order_id">
<div class="o_stat_info">
<span class="o_stat_text">Sale Order</span>
</div>
</button>
<button name="action_view_invoices" type="object"
class="oe_stat_button" icon="fa-money"
invisible="invoice_count == 0">
<field name="invoice_count" widget="statinfo" string="Invoices"/>
</button>
<button name="action_view_part" type="object"
class="oe_stat_button" icon="fa-cube"
invisible="not part_id">
<div class="o_stat_info">
<span class="o_stat_text">Part</span>
</div>
</button>
</div>
<div class="oe_title">
<label for="name" string="Serial #"/>
<h1><field name="name" placeholder="e.g. SN-12345"/></h1>
</div>
<group>
<group>
<field name="customer_id" readonly="1"/>
<field name="part_id" readonly="1"/>
<field name="sale_order_id" readonly="1"/>
<field name="sale_order_line_id" readonly="1"/>
</group>
<group>
<field name="last_state_change" readonly="1"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
</group>
<separator string="Scrap / Return Reason"
invisible="state not in ('scrapped', 'returned', 'on_hold')"/>
<field name="scrap_reason"
invisible="state not in ('scrapped', 'returned', 'on_hold')"
placeholder="What happened? Cause / disposition / who decided..."/>
<separator string="Notes"/>
<field name="notes"/>
</sheet>
<chatter/>
</form>
</field>
</record>
<record id="view_fp_serial_list" model="ir.ui.view">
<field name="name">fp.serial.list</field>
<field name="model">fp.serial</field>
<field name="arch" type="xml">
<list string="Serial Numbers"
decoration-success="state in ('inspected', 'packed', 'shipped')"
decoration-info="state in ('received', 'racked', 'in_process')"
decoration-warning="state in ('on_hold', 'returned')"
decoration-danger="state == 'scrapped'">
<field name="name"/>
<field name="state" widget="badge"
decoration-success="state in ('inspected', 'packed', 'shipped')"
decoration-info="state in ('received', 'racked', 'in_process')"
decoration-warning="state in ('on_hold', 'returned')"
decoration-danger="state == 'scrapped'"/>
<field name="customer_id"/>
<field name="part_id"/>
<field name="sale_order_id"/>
<field name="last_state_change" optional="show"/>
<field name="invoice_count" string="Invoices" optional="hide"/>
<field name="create_date" optional="hide"/>
</list>
</field>
</record>
<record id="view_fp_serial_search" model="ir.ui.view">
<field name="name">fp.serial.search</field>
<field name="model">fp.serial</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<field name="customer_id"/>
<field name="part_id"/>
<field name="sale_order_id"/>
<separator/>
<filter name="state_received" string="Received"
domain="[('state','=','received')]"/>
<filter name="state_in_process" string="In Process"
domain="[('state','=','in_process')]"/>
<filter name="state_inspected" string="Inspected"
domain="[('state','=','inspected')]"/>
<filter name="state_packed" string="Packed"
domain="[('state','=','packed')]"/>
<filter name="state_shipped" string="Shipped"
domain="[('state','=','shipped')]"/>
<separator/>
<filter name="state_on_hold" string="On Hold"
domain="[('state','=','on_hold')]"/>
<filter name="state_scrapped" string="Scrapped"
domain="[('state','=','scrapped')]"/>
<filter name="state_returned" string="Returned"
domain="[('state','=','returned')]"/>
<separator/>
<filter name="active_only" string="In Flight (not shipped/scrapped)"
domain="[('state','not in',('shipped','scrapped'))]"/>
<group>
<filter name="group_state" string="Status"
context="{'group_by': 'state'}"/>
<filter name="group_customer" string="Customer"
context="{'group_by': 'customer_id'}"/>
<filter name="group_part" string="Part"
context="{'group_by': 'part_id'}"/>
</group>
</search>
</field>
</record>
<record id="action_fp_serial" model="ir.actions.act_window">
<field name="name">Serial Numbers</field>
<field name="res_model">fp.serial</field>
<field name="view_mode">list,form</field>
<field name="search_view_id" ref="view_fp_serial_search"/>
</record>
<menuitem id="menu_fp_serial"
name="Serial Numbers"
parent="fusion_plating_configurator.menu_fp_sales"
action="action_fp_serial"
sequence="60"/>
</odoo>

View File

@@ -1,261 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Job Sorting:
- Section model views (list/form) under Configuration → Sales.
- Alternate SO list ("Sale Orders by Sorting") grouped by job sort
with foldable sections and create-from-here support.
-->
<odoo>
<!-- ===== Section management (Configuration) ===== -->
<record id="view_fp_so_job_sort_list" model="ir.ui.view">
<field name="name">fp.so.job.sort.list</field>
<field name="model">fp.so.job.sort</field>
<field name="arch" type="xml">
<list string="Job Sorting" editable="bottom">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="color" widget="color_picker"/>
<field name="fold" widget="boolean_toggle"/>
<field name="sale_order_count"/>
<field name="active" widget="boolean_toggle" optional="hide"/>
</list>
</field>
</record>
<record id="view_fp_so_job_sort_form" model="ir.ui.view">
<field name="name">fp.so.job.sort.form</field>
<field name="model">fp.so.job.sort</field>
<field name="arch" type="xml">
<form string="Job Sorting">
<sheet>
<div class="oe_button_box" name="button_box">
<button name="action_view_sale_orders" type="object"
class="oe_stat_button" icon="fa-shopping-cart">
<field name="sale_order_count" widget="statinfo"
string="Sale Orders"/>
</button>
</div>
<div class="oe_title">
<label for="name"/>
<h1><field name="name" placeholder="e.g. Rush Orders"/></h1>
</div>
<group>
<group>
<field name="sequence"/>
<field name="color" widget="color_picker"/>
<field name="fold"/>
</group>
<group>
<field name="active"/>
</group>
</group>
<field name="description"
placeholder="What kinds of orders belong in this section?"/>
</sheet>
</form>
</field>
</record>
<record id="action_fp_so_job_sort" model="ir.actions.act_window">
<field name="name">Job Sorting</field>
<field name="res_model">fp.so.job.sort</field>
<field name="view_mode">list,form</field>
</record>
<menuitem id="menu_fp_so_job_sort"
name="Job Sorting"
parent="fusion_plating.menu_fp_config_pricing_billing"
action="action_fp_so_job_sort"
sequence="25"/>
<!-- ===== Kanban grouped by Job Sorting =====
Groups SOs into foldable columns by x_fc_job_sort_id.
Drag-drop between columns rewrites the bucket; quick-create on
the column header creates a new fp.so.job.sort row. Wired into
the existing Sale Orders action below so it shows up in the
view-switcher next to the flat list. -->
<record id="view_sale_order_kanban_fp_by_sorting" model="ir.ui.view">
<field name="name">sale.order.kanban.fp.by_sorting</field>
<field name="model">sale.order</field>
<field name="arch" type="xml">
<kanban default_group_by="x_fc_job_sort_id"
group_create="true"
group_edit="true"
group_delete="true"
quick_create="false"
sample="1">
<field name="name"/>
<field name="partner_id"/>
<field name="amount_total"/>
<field name="currency_id"/>
<field name="x_fc_part_numbers_summary"/>
<field name="x_fc_customer_job_number"/>
<field name="x_fc_deadline_countdown"/>
<field name="x_fc_deadline_urgency"/>
<field name="x_fc_fp_job_status"/>
<field name="state"/>
<templates>
<t t-name="card">
<div class="o_kanban_card_content p-2">
<div class="d-flex justify-content-between align-items-start mb-1">
<strong><field name="name"/></strong>
<span t-att-class="'badge ' + (
record.x_fc_deadline_urgency.raw_value == 'overdue' and 'text-bg-danger' or
record.x_fc_deadline_urgency.raw_value == 'urgent' and 'text-bg-warning' or
record.x_fc_deadline_urgency.raw_value == 'safe' and 'text-bg-success' or
'text-bg-light')"
t-if="record.x_fc_deadline_countdown.raw_value">
<field name="x_fc_deadline_countdown"/>
</span>
</div>
<div class="text-muted small mb-1">
<field name="partner_id"/>
</div>
<div class="small mb-1" t-if="record.x_fc_part_numbers_summary.raw_value">
<i class="fa fa-cube me-1"/>
<field name="x_fc_part_numbers_summary"/>
</div>
<div class="small mb-2" t-if="record.x_fc_customer_job_number.raw_value">
<i class="fa fa-hashtag me-1"/>
<field name="x_fc_customer_job_number"/>
</div>
<div class="d-flex justify-content-between align-items-center">
<field name="x_fc_fp_job_status" widget="html"/>
<strong>
<field name="amount_total" widget="monetary"
options="{'currency_field': 'currency_id'}"/>
</strong>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<!-- ===== Sale Orders by Sorting (alternate SO list) ===== -->
<!-- Duplicate of view_sale_order_list_fp but renamed and intended
to be opened with group_by=x_fc_job_sort_id by default so the
user sees foldable sections per Job Sorting bucket. -->
<record id="view_sale_order_list_fp_by_sorting" model="ir.ui.view">
<field name="name">sale.order.list.fp.by_sorting</field>
<field name="model">sale.order</field>
<field name="priority">99</field>
<field name="arch" type="xml">
<list string="Sale Orders by Sorting" create="0"
decoration-info="state == 'draft'"
decoration-muted="state == 'cancel'"
decoration-danger="x_fc_is_late_forecast">
<header>
<button name="%(action_fp_direct_order_wizard)d"
type="action"
string="New Order"
class="btn-primary"
display="always"/>
</header>
<field name="name" optional="show"/>
<field name="partner_id" optional="show"/>
<field name="x_fc_po_number" optional="show"/>
<field name="x_fc_customer_job_number" optional="show"/>
<field name="x_fc_job_sort_id" optional="show"
options="{'no_create_edit': False, 'no_open': True}"/>
<field name="x_fc_internal_deadline" optional="show"/>
<field name="commitment_date" string="Customer Deadline"
optional="show"/>
<field name="x_fc_order_completion_date" string="Completion"
optional="show"/>
<field name="x_fc_is_late_forecast" optional="hide"
widget="boolean_toggle"/>
<field name="x_fc_deadline_urgency" column_invisible="1"/>
<field name="x_fc_deadline_countdown" optional="show"
decoration-danger="x_fc_deadline_urgency == 'overdue'"
decoration-warning="x_fc_deadline_urgency == 'urgent'"
decoration-success="x_fc_deadline_urgency == 'safe'"/>
<field name="x_fc_wo_completion" optional="show"/>
<field name="x_fc_planned_start_date" optional="hide"/>
<field name="x_fc_part_numbers_summary" string="Part"
optional="show"/>
<field name="amount_total" sum="Total" optional="show"/>
<field name="x_fc_invoiced_amount" sum="Invoiced"
optional="hide"
widget="monetary"
options="{'currency_field': 'currency_id'}"/>
<field name="x_fc_fp_job_status" widget="html"
string="Job Status" optional="show" readonly="1"/>
<field name="x_fc_receiving_status" widget="badge"
optional="hide"
decoration-warning="x_fc_receiving_status == 'not_received'"
decoration-info="x_fc_receiving_status == 'partial'"
decoration-success="x_fc_receiving_status == 'received'"/>
<field name="x_fc_delivery_method" optional="hide"/>
<field name="currency_id" column_invisible="1"/>
<field name="state" widget="badge" optional="show"/>
</list>
</field>
</record>
<!-- Search view for the alternate list: surface "Group by Job
Sorting" as a search-default filter. -->
<record id="view_sale_order_search_fp_by_sorting" model="ir.ui.view">
<field name="name">sale.order.search.fp.by_sorting</field>
<field name="model">sale.order</field>
<field name="arch" type="xml">
<search string="Sale Orders by Sorting">
<field name="name"/>
<field name="partner_id"/>
<field name="x_fc_part_numbers_summary" string="Part"/>
<field name="x_fc_customer_job_number"/>
<field name="x_fc_po_number"/>
<field name="x_fc_job_sort_id"/>
<filter name="late_forecast" string="Late Forecast"
domain="[('x_fc_is_late_forecast','=',True)]"/>
<filter name="cancelled" string="Cancelled"
domain="[('state','=','cancel')]"/>
<separator/>
<group>
<filter name="group_by_job_sort"
string="Job Sorting"
context="{'group_by': 'x_fc_job_sort_id'}"/>
<filter name="group_by_customer"
string="Customer"
context="{'group_by': 'partner_id'}"/>
<filter name="group_by_state"
string="Status"
context="{'group_by': 'state'}"/>
</group>
</search>
</field>
</record>
<!-- Append the kanban view to the existing Sale Orders action so
users can switch from the flat list to the grouped-by-sorting
kanban (foldable columns, drag-drop bucket reassignment) via
the view-switcher icon in the top-right of the SO list. -->
<record id="action_fp_sale_orders" model="ir.actions.act_window">
<field name="view_mode">list,kanban,form</field>
<field name="view_ids" eval="[(5, 0, 0),
(0, 0, {'view_mode': 'list', 'view_id': ref('view_sale_order_list_fp')}),
(0, 0, {'view_mode': 'kanban', 'view_id': ref('view_sale_order_kanban_fp_by_sorting')})]"/>
</record>
<record id="action_fp_sale_orders_by_sorting" model="ir.actions.act_window">
<field name="name">Sale Orders (by Sorting)</field>
<field name="res_model">sale.order</field>
<field name="view_mode">list,form</field>
<field name="view_id" ref="view_sale_order_list_fp_by_sorting"/>
<field name="search_view_id" ref="view_sale_order_search_fp_by_sorting"/>
<field name="domain">[('state', 'not in', ('draft', 'sent'))]</field>
<field name="context">{'search_default_group_by_job_sort': 1}</field>
</record>
<menuitem id="menu_fp_sale_orders_by_sorting"
name="Sale Orders (by Sorting)"
parent="fusion_plating_configurator.menu_fp_sales"
action="action_fp_sale_orders_by_sorting"
sequence="12"/>
</odoo>

View File

@@ -1,59 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_partner_form_fp_parts" model="ir.ui.view">
<field name="name">res.partner.form.fp.parts</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='button_box']" position="inside">
<button name="action_view_parts"
type="object"
class="oe_stat_button"
icon="fa-cube"
invisible="x_fc_part_count == 0">
<field name="x_fc_part_count" widget="statinfo" string="Parts"/>
</button>
<button name="action_fp_new_direct_order"
type="object"
class="oe_stat_button"
icon="fa-shopping-cart"
invisible="customer_rank == 0">
<div class="o_stat_info">
<span class="o_stat_text">Direct Order</span>
</div>
</button>
<button name="action_fp_import_parts"
type="object"
class="oe_stat_button"
icon="fa-upload"
invisible="customer_rank == 0">
<div class="o_stat_info">
<span class="o_stat_text">Import Parts</span>
</div>
</button>
</xpath>
<xpath expr="//page[@name='sales_purchases']" position="after">
<page string="Part Library" name="part_library">
<field name="x_fc_part_catalog_ids" mode="list"
domain="[('is_latest_revision', '=', True)]">
<list default_order="name" >
<field name="part_number"/>
<field name="name"/>
<field name="revision"/>
<field name="substrate_material"/>
<field name="surface_area"/>
<field name="surface_area_uom" string="UoM"/>
<field name="complexity"/>
<field name="model_attachment_id" string="3D Model"/>
</list>
</field>
</page>
</xpath>
</field>
</record>
</odoo>

View File

@@ -1,670 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Part of the Fusion Plating product family.
-->
<odoo>
<!-- ===== Inherit SO Form - add Plating tab ===== -->
<record id="view_sale_order_form_fp" model="ir.ui.view">
<field name="name">sale.order.form.fp.configurator</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<!-- Header buttons: make draft Confirm the primary CTA, demote/rename
Send to "Send Email" (red), and reorder so Confirm sits first.
Phase D5 - gate Confirm button to Sales Manager + higher; matches
the model-level gate from Phase G so Sales Rep sees the SO in
draft but no Confirm button. -->
<xpath expr="//header/button[@name='action_confirm' and not(@id)]" position="attributes">
<attribute name="class">btn-primary</attribute>
<attribute name="groups">fusion_plating.group_fp_sales_manager</attribute>
</xpath>
<!-- Also gate the state=sent Confirm button (id="action_confirm") so
Sales Reps don't see EITHER variant. Matches the model-level gate. -->
<xpath expr="//header/button[@id='action_confirm']" position="attributes">
<attribute name="groups">fusion_plating.group_fp_sales_manager</attribute>
</xpath>
<xpath expr="//header/button[@id='quotation_send_primary']" position="attributes">
<attribute name="string">Send Email</attribute>
<attribute name="class">btn-danger</attribute>
</xpath>
<xpath expr="//header/button[@id='quotation_send_primary']" position="before">
<xpath expr="//header/button[@name='action_confirm' and not(@id)]" position="move"/>
</xpath>
<!-- Hide standard Delivery button: our Transfers button (below) shows
all stock.picking records - inbound receipts AND outbound deliveries -
which matches the plating workflow better than outbound-only. -->
<xpath expr="//button[@name='action_view_delivery']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<!-- Insert before standard Invoices: PO, then RFQ (multiple position="before"
stack so last-inserted ends up closest to anchor; order here is PO, RFQ). -->
<xpath expr="//button[@name='action_view_invoice']" position="before">
<button name="action_view_po"
type="object"
class="oe_stat_button"
icon="fa-file-o"
invisible="not x_fc_po_attachment_id">
<div class="o_stat_info">
<span class="o_stat_value">1</span>
<span class="o_stat_text">PO</span>
</div>
</button>
<button name="action_view_rfq"
type="object"
class="oe_stat_button"
icon="fa-envelope-o"
invisible="not x_fc_rfq_attachment_id">
<div class="o_stat_info">
<span class="o_stat_value">1</span>
<span class="o_stat_text">RFQ</span>
</div>
</button>
</xpath>
<!-- After standard Invoices: Transfers (all stock.picking for this SO). -->
<xpath expr="//button[@name='action_view_invoice']" position="after">
<button name="action_view_pickings"
type="object"
class="oe_stat_button"
icon="fa-truck"
invisible="x_fc_picking_count == 0">
<field name="x_fc_picking_count" widget="statinfo"
string="Transfers"/>
</button>
</xpath>
<!-- Sub 11 - MRP gone. The "Work Orders" button used to count
mrp.workorder; removed because Plating Jobs (added by
fusion_plating_jobs) now counts the canonical fp.job.step
rows. NCRs surfaces only when there's at least one open;
BOM Items and By Job Group only when the SO is actually
multi-part / tagged (otherwise both render one column with
one card - pure noise). Anchored after Transfers; the two
conditional ones go last so the typical clean SO shows
just the meaningful buttons up front. -->
<xpath expr="//button[@name='action_view_pickings']" position="after">
<button name="action_view_ncrs"
type="object"
class="oe_stat_button"
icon="fa-exclamation-triangle"
invisible="x_fc_ncr_count == 0">
<field name="x_fc_ncr_count" widget="statinfo"
string="NCRs"/>
</button>
</xpath>
<!-- Push BOM Items / By Job Group to the end of the button
box (after the Plating Jobs / Holds row added by jobs +
quality). They sit hidden by default and only surface
when the SO actually has multi-part lines or job-group
tags. -->
<xpath expr="//div[hasclass('oe_button_box')]" position="inside">
<button name="action_view_bom_items"
type="object"
class="oe_stat_button"
icon="fa-list-alt"
invisible="x_fc_distinct_part_count &lt; 2">
<field name="x_fc_distinct_part_count" widget="statinfo"
string="BOM Items"/>
</button>
<button name="action_view_wo_perspective"
type="object"
class="oe_stat_button"
icon="fa-th-large"
invisible="not x_fc_has_wo_group_tag">
<field name="x_fc_wo_group_count" widget="statinfo"
string="Job Groups"/>
</button>
</xpath>
<!-- Surface Delivery Date (commitment_date) right after Order
Date in the header info group. The standard view only
shows it buried under the Delivery section in the Other
Info tab - having it at the top keeps it visible without
scrolling, since most operators are setting it on every
order. The same field is also surfaced lower in our
Plating tab Scheduling group as "Customer Deadline"; both
reference the same field so edits sync. -->
<xpath expr="//group[@name='order_details']/field[@name='payment_term_id']" position="before">
<field name="commitment_date" string="Delivery Date"
readonly="state in ('cancel',)"/>
</xpath>
<!-- Job Sorting sits right under Payment Terms - a free-form
bucket that groups the SO in the "Sale Orders by Sorting"
list. Quick-create from the dropdown. -->
<xpath expr="//group[@name='order_details']/field[@name='payment_term_id']" position="after">
<field name="x_fc_job_sort_id"
options="{'no_create_edit': False, 'no_open': True}"
placeholder="Type to create a new bucket..."/>
</xpath>
<xpath expr="//notebook" position="inside">
<page string="Plating" name="plating_tab">
<!-- Multi-part summary: read-only list of every order line
showing part / coating / process. The Order Lines tab
is the editable surface; this is the at-a-glance view
so you can confirm an order has the right parts/coatings
without scrolling pricing columns. The pre-Sub-12 SO-
header singletons (x_fc_part_catalog_id /
x_fc_customer_spec_id) only ever populated when the
order was built via the quote configurator - they're
silent on direct orders, which is why they appeared
empty after confirm. They still exist on the model
(used by configurator/portal) but are no longer the
primary display. -->
<separator string="Parts on this order"/>
<field name="order_line" nolabel="1"
context="{'tree_view_ref': 'fusion_plating_configurator.view_sale_order_line_plating_summary'}"
readonly="1">
<list create="false" delete="false" edit="false">
<field name="x_fc_part_catalog_id"/>
<field name="x_fc_thickness_range" optional="show"/>
<field name="x_fc_process_variant_id" optional="show"
string="Process"/>
<field name="product_uom_qty" string="Qty"/>
<field name="x_fc_effective_part_deadline" optional="show"
string="Effective Deadline"/>
<field name="x_fc_part_deadline" optional="hide"
string="Part Deadline Override"/>
<field name="x_fc_rush_order" optional="hide"/>
<field name="x_fc_job_number" optional="show"
string="Job #"/>
</list>
</field>
<!-- Row 1: RFQ/PO (left) + Scheduling (right) - pairs the two
tallest groups so neither column dangles empty. -->
<group>
<group string="RFQ / PO">
<field name="x_fc_po_number"/>
<field name="upload_rfq_file"
filename="upload_rfq_filename"
invisible="x_fc_rfq_attachment_id"
string="Attach RFQ"/>
<field name="upload_rfq_filename" invisible="1"/>
<field name="x_fc_rfq_attachment_id"
invisible="not x_fc_rfq_attachment_id"/>
<field name="upload_po_file"
filename="upload_po_filename"
invisible="x_fc_po_attachment_id"
string="Attach PO"/>
<field name="upload_po_filename" invisible="1"/>
<field name="x_fc_po_attachment_id"
invisible="not x_fc_po_attachment_id"/>
<field name="x_fc_po_received"/>
<field name="x_fc_po_pending" widget="boolean_toggle"/>
<field name="x_fc_po_expected_date"
invisible="not x_fc_po_pending"/>
<field name="x_fc_po_override"
groups="fusion_plating.group_fusion_plating_manager"/>
<field name="x_fc_po_override_reason"
invisible="not x_fc_po_override"/>
</group>
<group string="Scheduling">
<field name="x_fc_planned_start_date"/>
<field name="x_fc_internal_deadline"/>
<field name="commitment_date" string="Customer Deadline"/>
<field name="x_fc_deadline_countdown" readonly="1"/>
<label for="x_fc_order_completion_date"/>
<div class="o_row">
<field name="x_fc_order_completion_date"
readonly="1" class="oe_inline"/>
<span class="badge text-bg-danger ms-2"
invisible="not x_fc_is_late_forecast">
<i class="fa fa-exclamation-triangle me-1"/>Late
</span>
<field name="x_fc_is_late_forecast" invisible="1"/>
</div>
<field name="x_fc_is_blanket_order"/>
<field name="x_fc_block_partial_shipments"/>
<!-- Lead Time range. Both 0 = "Standard" on
the PDF; otherwise renders "X-Y days"
(or "X days" if min==max or one is 0). -->
<label for="x_fc_lead_time_min_days" string="Lead Time (days)"/>
<div class="o_row">
<field name="x_fc_lead_time_min_days" class="oe_inline" style="width: 4em;"/>
<span> to </span>
<field name="x_fc_lead_time_max_days" class="oe_inline" style="width: 4em;"/>
</div>
<field name="x_fc_lead_time_display" readonly="1"/>
</group>
</group>
<!-- Row 2: Invoicing + Delivery (unchanged pairing). -->
<group>
<group string="Invoicing">
<field name="x_fc_invoice_strategy"/>
<field name="x_fc_deposit_percent"
invisible="x_fc_invoice_strategy != 'deposit'"/>
<field name="x_fc_progress_initial_percent"
invisible="x_fc_invoice_strategy != 'progress'"/>
<field name="x_fc_final_invoice_id" readonly="1"
invisible="not x_fc_final_invoice_id"/>
</group>
<group string="Delivery">
<field name="x_fc_rush_order"/>
<field name="x_fc_delivery_method"/>
<field name="x_fc_receiving_status"/><!-- Will become computed when fusion_plating_receiving is installed -->
</group>
</group>
<!-- Row 3: Customer Reference + Margin - both short groups, so
pairing them keeps the right column from going blank. -->
<group>
<group string="Customer Reference">
<field name="x_fc_customer_job_number"/>
<field name="x_fc_contact_phone"/>
<field name="x_fc_ship_via"/>
</group>
<group string="Margin">
<div colspan="2"
invisible="x_fc_margin_available"
class="text-muted">
<i class="fa fa-info-circle me-1"/>
Margin n/a - coating cost rollup not yet
populated on any line's treatment.
</div>
<field name="x_fc_margin_amount"
widget="monetary"
options="{'currency_field': 'currency_id'}"
invisible="not x_fc_margin_available"/>
<field name="x_fc_margin_percent"
widget="percentage"
invisible="not x_fc_margin_available"/>
<field name="x_fc_margin_available" invisible="1"/>
</group>
</group>
<!-- Row 4: Notes - two side-by-side textareas instead of the
previous broken separator-in-group layout. -->
<group>
<group string="Internal Notes">
<field name="x_fc_internal_note" nolabel="1"
placeholder="Internal notes for estimator / planner / shop floor..."/>
</group>
<group string="External Notes (customer-visible)">
<field name="x_fc_external_note" nolabel="1"
placeholder="Notes that appear on the acknowledgement and portal..."/>
</group>
</group>
<!-- Legacy configurator block - invisible on new SOs (only
the handful that came through the old quote configurator
flow have x_fc_configurator_id set). Kept at the bottom
so it doesn't waste vertical space on the common case. -->
<group invisible="not x_fc_configurator_id">
<group string="Configurator (legacy)">
<field name="x_fc_configurator_id" readonly="1"/>
<field name="x_fc_process_summary" readonly="1"/>
</group>
</group>
</page>
</xpath>
<!-- Make the standard customer-facing description column togglable.
The base sale.order line view shows `name` always; flipping it
to optional lets estimators hide/show it like the other columns. -->
<xpath expr="//field[@name='order_line']/list/field[@name='name']" position="attributes">
<attribute name="string">Customer-Facing</attribute>
<attribute name="optional">show</attribute>
</xpath>
<xpath expr="//field[@name='order_line']/list/field[@name='product_uom_qty']" position="before">
<field name="x_fc_part_catalog_id" optional="show"/>
<field name="x_fc_description_template_id"
domain="[('part_catalog_id', '=', x_fc_part_catalog_id)]"
context="{'default_part_catalog_id': x_fc_part_catalog_id}"
invisible="not x_fc_part_catalog_id"
optional="hide"/>
<field name="x_fc_internal_description"
placeholder="Shop-floor workflow instructions (prints on WO / traveler)"
optional="hide"/>
<field name="x_fc_process_variant_id"
string="Process / Recipe"
options="{'no_quick_create': True}"
invisible="not x_fc_part_catalog_id"
optional="show"/>
<field name="x_fc_save_as_default_process"
string="Set as Part Default"
widget="boolean_toggle"
invisible="not x_fc_process_variant_id"
optional="hide"/>
<field name="x_fc_thickness_range"
placeholder="e.g. 0.0005-0.0008 mils"
optional="show"/>
<field name="x_fc_serial_ids"
widget="many2many_tags"
options="{'no_quick_create': False, 'color_field': 'state_color'}"
domain="[('part_id', '=', x_fc_part_catalog_id)]"
optional="show"/>
<field name="x_fc_serial_count"
string="Serial Count"
optional="hide"/>
<button name="action_open_serial_bulk_add" type="object"
title="Bulk add serials"
icon="fa-list-ol"
class="btn-link"
invisible="not x_fc_part_catalog_id or x_fc_serial_count &gt; 0"/>
<field name="x_fc_job_number" optional="show"/>
<field name="x_fc_revision_pick_id"
string="Revision"
options="{'no_create': True}"
invisible="not x_fc_part_catalog_id"
optional="hide"/>
<field name="x_fc_revision_snapshot"
readonly="1"
optional="hide"/>
<field name="x_fc_part_deadline" string="Part Deadline Override" optional="hide"/>
<field name="x_fc_part_deadline_offset_days" string="Days Offset" optional="hide"/>
<field name="x_fc_effective_part_deadline" string="Effective Deadline"
optional="show"
readonly="1"/>
<field name="x_fc_effective_internal_deadline" string="Shop Target"
optional="hide"
readonly="1"/>
<field name="x_fc_wo_group_tag" optional="hide"/>
<field name="x_fc_start_at_node_id" optional="hide"/>
<field name="x_fc_is_one_off" optional="hide"/>
<field name="x_fc_quote_id" optional="hide"/>
<field name="x_fc_rush_order" optional="hide"/>
</xpath>
<!-- Phase D5 - gate pricing columns/totals to Sales Rep + higher
(defense in depth - Technician/Shop Manager don't see pricing
even if they navigate to an SO). -->
<xpath expr="//field[@name='order_line']/list/field[@name='price_unit']" position="attributes">
<attribute name="groups">fusion_plating.group_fp_sales_rep</attribute>
</xpath>
<xpath expr="//field[@name='order_line']/list/field[@name='price_subtotal']" position="attributes">
<attribute name="groups">fusion_plating.group_fp_sales_rep</attribute>
</xpath>
<!-- Default-hidden columns (still toggleable via the optional-
columns menu). Keeps the order-line grid focused on the
plating fields; the generic FP-SERVICE product, delivered/
invoiced quantities, and the per-line tax column are noise
on a plating order. Estimators can switch any back on. -->
<xpath expr="//field[@name='order_line']/list/field[@name='product_template_id']" position="attributes">
<attribute name="optional">hide</attribute>
</xpath>
<xpath expr="//field[@name='order_line']/list/field[@name='qty_delivered']" position="attributes">
<attribute name="optional">hide</attribute>
</xpath>
<xpath expr="//field[@name='order_line']/list/field[@name='qty_invoiced']" position="attributes">
<attribute name="optional">hide</attribute>
</xpath>
<xpath expr="//field[@name='order_line']/list/field[@name='tax_ids']" position="attributes">
<attribute name="optional">hide</attribute>
</xpath>
<!-- Odoo 19: amount_total / amount_untaxed / amount_tax are rendered
by the single tax_totals widget; no separate fields. Gate the
widget itself to hide the entire totals block from non-Sales-Rep. -->
<xpath expr="//field[@name='tax_totals']" position="attributes">
<attribute name="groups">fusion_plating.group_fp_sales_rep</attribute>
</xpath>
</field>
</record>
<!-- ===== Custom SO List View for Fusion Plating ===== -->
<record id="view_sale_order_list_fp" model="ir.ui.view">
<field name="name">sale.order.list.fp</field>
<field name="model">sale.order</field>
<field name="arch" type="xml">
<list string="Sale Orders" create="0" decoration-info="state == 'draft'"
decoration-muted="state == 'cancel'"
decoration-danger="x_fc_is_late_forecast">
<header>
<button name="%(action_fp_direct_order_wizard)d"
type="action"
string="New Order"
class="btn-primary"
display="always"/>
</header>
<field name="name" optional="show"/>
<field name="partner_id" optional="show"/>
<field name="x_fc_po_number" optional="show"/>
<field name="x_fc_customer_job_number" optional="show"/>
<field name="x_fc_internal_deadline" optional="show"/>
<field name="commitment_date" string="Customer Deadline" optional="show"/>
<field name="x_fc_order_completion_date" string="Completion" optional="show"/>
<field name="x_fc_is_late_forecast" optional="hide" widget="boolean_toggle"/>
<field name="x_fc_deadline_urgency" column_invisible="1"/>
<field name="x_fc_deadline_countdown" optional="show"
decoration-danger="x_fc_deadline_urgency == 'overdue'"
decoration-warning="x_fc_deadline_urgency == 'urgent'"
decoration-success="x_fc_deadline_urgency == 'safe'"/>
<field name="x_fc_wo_completion" optional="show"/>
<field name="x_fc_planned_start_date" optional="hide"/>
<!-- "Part" column - walks order_line.x_fc_part_catalog_id
and shows a compact summary (e.g. "M1234, M5678
(+3 more)"). The header x_fc_part_catalog_id field
is rarely populated in the configurator flow; the
line carries the authoritative part link. -->
<field name="x_fc_part_numbers_summary" string="Part"
optional="show"/>
<field name="amount_total" sum="Total" optional="show"/>
<field name="x_fc_invoiced_amount" sum="Invoiced" optional="hide"
widget="monetary"
options="{'currency_field': 'currency_id'}"/>
<field name="x_fc_margin_amount" sum="Margin" optional="hide"
widget="monetary"
options="{'currency_field': 'currency_id'}"/>
<field name="x_fc_margin_percent" optional="hide"
widget="percentage"/>
<field name="x_fc_is_blanket_order" optional="hide"/>
<!-- Single Job Status pill. Renders as HTML with a
per-kind class (.fp-kind-*) so every phase carries
its own distinct tint - see
static/src/scss/fp_job_status_pill.scss. -->
<field name="x_fc_fp_job_status" widget="html"
string="Job Status" optional="show"
readonly="1"/>
<field name="x_fc_receiving_status" widget="badge"
optional="hide"
decoration-warning="x_fc_receiving_status == 'not_received'"
decoration-info="x_fc_receiving_status == 'partial'"
decoration-success="x_fc_receiving_status == 'received'"/>
<field name="x_fc_delivery_method" optional="hide"/>
<field name="currency_id" column_invisible="1"/>
<field name="state" widget="badge" optional="show"/>
</list>
</field>
</record>
<!-- ===== BOM Items view (lines grouped by part) - Phase D2 ===== -->
<record id="view_sale_order_line_bom_kanban" model="ir.ui.view">
<field name="name">sale.order.line.bom.kanban</field>
<field name="model">sale.order.line</field>
<field name="arch" type="xml">
<kanban default_group_by="x_fc_part_catalog_id" records_draggable="0">
<field name="x_fc_part_catalog_id"/>
<field name="product_uom_qty"/>
<field name="qty_delivered"/>
<field name="x_fc_wo_group_tag"/>
<field name="x_fc_archived"/>
<field name="currency_id"/>
<templates>
<t t-name="card">
<div class="o_kanban_card_content">
<div class="o_kanban_record_title">
<strong><field name="x_fc_part_catalog_id"/></strong>
</div>
<div class="text-muted">
Qty: <field name="product_uom_qty"/>
/ Delivered: <field name="qty_delivered"/>
</div>
<div t-if="record.x_fc_wo_group_tag.raw_value">
<span class="badge bg-info">
<field name="x_fc_wo_group_tag"/>
</span>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<!-- ===== WO-perspective view: lines grouped by WO tag - Phase D10 ===== -->
<record id="view_sale_order_line_wo_kanban" model="ir.ui.view">
<field name="name">sale.order.line.wo.kanban</field>
<field name="model">sale.order.line</field>
<field name="arch" type="xml">
<kanban default_group_by="x_fc_wo_group_tag" records_draggable="0">
<field name="x_fc_wo_group_tag"/>
<field name="x_fc_part_catalog_id"/>
<field name="product_uom_qty"/>
<templates>
<t t-name="card">
<div class="o_kanban_card_content">
<div>
<strong><field name="x_fc_part_catalog_id"/></strong>
</div>
<div>
Qty: <field name="product_uom_qty"/>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<!-- ===== Quotes list view (state in draft/sent) ===== -->
<record id="view_sale_order_list_fp_quotes" model="ir.ui.view">
<field name="name">sale.order.list.fp.quotes</field>
<field name="model">sale.order</field>
<field name="arch" type="xml">
<list string="Quotations" decoration-muted="state == 'cancel'">
<field name="name" optional="show"/>
<field name="partner_id" optional="show"/>
<field name="x_fc_part_numbers_summary" optional="show"/>
<field name="x_fc_po_number" optional="hide"/>
<field name="x_fc_customer_job_number" optional="hide"/>
<field name="create_date" string="Created" optional="show"/>
<field name="validity_date" string="Expires" optional="show"/>
<field name="x_fc_follow_up_date" optional="show"/>
<field name="x_fc_follow_up_user_id" optional="show"/>
<field name="amount_total" sum="Total" optional="show"/>
<field name="x_fc_is_signed" widget="boolean_toggle"
string="Signed" optional="show"/>
<field name="x_fc_email_status" widget="badge"
optional="show"
decoration-info="x_fc_email_status == 'sent'"
decoration-warning="x_fc_email_status == 'opened'"
decoration-success="x_fc_email_status == 'won'"/>
<field name="currency_id" column_invisible="1"/>
<field name="state" widget="badge" optional="show"/>
</list>
</field>
</record>
<!-- ===== Quotes search view ===== -->
<record id="view_sale_order_search_fp_quotes" model="ir.ui.view">
<field name="name">sale.order.search.fp.quotes</field>
<field name="model">sale.order</field>
<field name="arch" type="xml">
<search string="Quotations">
<field name="name"/>
<field name="partner_id"/>
<field name="x_fc_part_numbers_summary" string="Part Number"/>
<filter name="my_quotes" string="My Quotes"
domain="[('user_id', '=', uid)]"/>
<separator/>
<filter name="draft" string="Draft"
domain="[('state', '=', 'draft')]"/>
<filter name="sent" string="Sent"
domain="[('state', '=', 'sent')]"/>
<filter name="won" string="Won"
domain="[('state', 'in', ('sale', 'done'))]"/>
<separator/>
<filter name="from_rfq" string="From RFQ"
domain="[('x_fc_rfq_attachment_id', '!=', False)]"/>
<filter name="needs_followup" string="Needs Follow-Up"
domain="[('x_fc_follow_up_date', '&lt;=', context_today()), ('state', 'in', ('draft', 'sent'))]"/>
<filter name="expired" string="Expired"
domain="[('validity_date', '&lt;', context_today()), ('state', 'in', ('draft', 'sent'))]"/>
<group>
<filter string="Customer" name="group_partner"
context="{'group_by': 'partner_id'}"/>
<filter string="Status" name="group_state"
context="{'group_by': 'state'}"/>
<filter string="Follow-Up Owner" name="group_followup"
context="{'group_by': 'x_fc_follow_up_user_id'}"/>
</group>
</search>
</field>
</record>
<!-- ===== Search view for Fusion Plating SO list ===== -->
<record id="view_sale_order_search_fp" model="ir.ui.view">
<field name="name">sale.order.search.fp</field>
<field name="model">sale.order</field>
<field name="arch" type="xml">
<search string="Sales Orders">
<field name="name"/>
<field name="partner_id"/>
<field name="x_fc_po_number" string="Customer PO #"/>
<field name="x_fc_customer_job_number" string="Customer Job #"/>
<filter name="my_orders" string="My Orders"
domain="[('user_id', '=', uid)]"/>
<separator/>
<filter name="open_orders" string="Open"
domain="[('state', 'in', ('draft', 'sent', 'sale'))]"/>
<filter name="confirmed" string="Confirmed"
domain="[('state', '=', 'sale')]"/>
<filter name="done" string="Done"
domain="[('state', '=', 'done')]"/>
<separator/>
<filter name="blanket_orders" string="Blanket Orders"
domain="[('x_fc_is_blanket_order', '=', True)]"/>
<filter name="late_forecast" string="Will Be Late"
domain="[('x_fc_is_late_forecast', '=', True)]"/>
<filter name="rush_lines" string="Has Rush Line"
domain="[('order_line.x_fc_rush_order', '=', True)]"/>
<filter name="overdue" string="Overdue"
domain="[('commitment_date', '&lt;', context_today()), ('state', 'in', ('sale',))]"/>
<group>
<filter string="Customer" name="group_partner"
context="{'group_by': 'partner_id'}"/>
<filter string="Status" name="group_state"
context="{'group_by': 'state'}"/>
<filter string="Customer Deadline" name="group_deadline"
context="{'group_by': 'commitment_date'}"/>
</group>
</search>
</field>
</record>
<!-- ===== Window Action - Quotations (for Fusion Plating menu) ===== -->
<record id="action_fp_quotations" model="ir.actions.act_window">
<field name="name">Quotations</field>
<field name="res_model">sale.order</field>
<field name="view_mode">list,form,kanban</field>
<field name="domain">[('state', 'in', ('draft', 'sent'))]</field>
<field name="view_ids" eval="[(5, 0, 0),
(0, 0, {'view_mode': 'list', 'view_id': ref('view_sale_order_list_fp_quotes')})]"/>
<field name="search_view_id" ref="view_sale_order_search_fp_quotes"/>
<field name="context">{'default_x_fc_delivery_method': 'shipping_partner'}</field>
<field name="x_fc_pickable_landing" eval="True"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new quotation
</p>
</field>
</record>
<!-- ===== Window Action - Confirmed Sale Orders =====
The kanban view_mode + kanban view_id are appended in
fp_so_job_sort_views.xml after the kanban view is defined, so
we don't hit a missing-ref at module load. -->
<record id="action_fp_sale_orders" model="ir.actions.act_window">
<field name="name">Sale Orders</field>
<field name="res_model">sale.order</field>
<field name="view_mode">list,form,kanban</field>
<field name="domain">[('state', 'in', ('sale', 'done'))]</field>
<field name="view_ids" eval="[(5, 0, 0),
(0, 0, {'view_mode': 'list', 'view_id': ref('view_sale_order_list_fp')})]"/>
<field name="x_fc_pickable_landing" eval="True"/>
</record>
</odoo>