feat(plating): Sub 4 — Contract Review (optional, QA-005 1:1 PDF)

Per-part contract review record (fp.contract.review) gated by a
customer-level toggle, signed in two sections (QA Assistant → QA
Manager), settings-based signer rosters (no new res.groups), banner on
the part form that auto-dismisses once the first MO for the part hits
confirmed. QA-005 Rev. 0 paper form reproduced 1:1 in a QWeb PDF.

Never blocks MO/SO/WO — review is purely an audit artefact.

Smoke test run on entech: 12 assertions pass including the 25-cell
risk matrix parity with the paper form and 22 KB PDF render.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-22 21:43:06 -04:00
parent 98a8bc234b
commit 21da526aa7
17 changed files with 1472 additions and 2 deletions

View File

@@ -0,0 +1,270 @@
<?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.
Sub 4 — Contract Review (QA-005 1:1 reproduction).
-->
<odoo>
<!-- ================================================================== -->
<!-- FORM VIEW — laid out to mirror QA-005 Rev. 0 as closely as Odoo -->
<!-- backend forms permit. Printed PDF is the 1:1 artefact; this form -->
<!-- is the data-entry counterpart. -->
<!-- ================================================================== -->
<record id="view_fp_contract_review_form" model="ir.ui.view">
<field name="name">fp.contract.review.form</field>
<field name="model">fp.contract.review</field>
<field name="arch" type="xml">
<form string="Contract Review (QA-005)">
<header>
<button name="action_sign_section_20" type="object"
string="Sign Section 2.0"
class="btn-primary"
invisible="s20_locked or state == 'dismissed'"/>
<button name="action_sign_section_30" type="object"
string="Sign Section 3.0"
class="btn-primary"
invisible="s30_locked or not s20_locked or state == 'dismissed'"/>
<button name="action_print_qa005" type="object"
string="Print QA-005"
class="btn-secondary"/>
<button name="action_reopen" type="object"
string="Re-open"
confirm="This will clear both signatures and reset the review. Continue?"
groups="fusion_plating.group_fusion_plating_manager"
invisible="state != 'complete'"/>
<button name="action_dismiss" type="object"
string="Dismiss"
invisible="state in ('complete','dismissed')"/>
<field name="state" widget="statusbar"
statusbar_visible="draft,assistant_review,manager_review,complete"/>
</header>
<sheet>
<div class="oe_title">
<h1><field name="name" readonly="1"/></h1>
</div>
<!-- ===== HEADER (Customer, PO, Part, Qty, Due) ===== -->
<group>
<group string="Customer &amp; Order">
<field name="customer_id" readonly="1"/>
<field name="contract_po_number"/>
<field name="quote_or_job_number"/>
<field name="date_received"/>
</group>
<group string="Part">
<field name="part_id" readonly="1"/>
<field name="part_number" readonly="1"/>
<field name="part_revision" readonly="1"/>
<field name="qty"/>
<field name="due_date"/>
</group>
</group>
<notebook>
<!-- ========= SECTION 2.0 — Planning / Production Review ========= -->
<page string="2.0 Planning / Production Review"
name="section_20">
<group>
<field name="s20_locked" invisible="1"/>
</group>
<group col="4" string="Checklist">
<field name="s20_acceptable_lead_time" readonly="s20_locked"/>
<field name="s20_capacity_to_process" readonly="s20_locked"/>
<field name="s20_skills_to_process" readonly="s20_locked"/>
<field name="s20_fixtures_required" readonly="s20_locked"/>
<field name="s20_prime_approvals" readonly="s20_locked"/>
<field name="s20_pricing" readonly="s20_locked"/>
<field name="s20_approved_technique" readonly="s20_locked"/>
<field name="s20_drawings_available" readonly="s20_locked"/>
<field name="s20_process_type_class_grade" readonly="s20_locked"/>
<field name="s20_pre_post_processing_steps" readonly="s20_locked"/>
</group>
<group string="Outcome">
<field name="s20_accepted" readonly="s20_locked"/>
<field name="s20_evaluate_risk" readonly="s20_locked"/>
<field name="s20_risk_level" readonly="s20_locked"
invisible="not s20_evaluate_risk"/>
</group>
<group string="Comments">
<field name="s20_comments" nolabel="1" readonly="s20_locked"/>
</group>
<group string="Signature" invisible="not s20_locked">
<field name="s20_signed_by" readonly="1"/>
<field name="s20_signed_date" readonly="1"/>
</group>
</page>
<!-- ========= SECTION 3.0 — Quality Review ========= -->
<page string="3.0 Quality Review" name="section_30">
<group>
<field name="s30_locked" invisible="1"/>
</group>
<group col="4" string="Checklist">
<field name="s30_source_control_docs" readonly="s30_locked"/>
<field name="s30_quality_clauses_supplied" readonly="s30_locked"/>
<field name="s30_quality_clauses_attainable" readonly="s30_locked"/>
<field name="s30_critical_tolerance" readonly="s30_locked"/>
<field name="s30_measuring_tooling" readonly="s30_locked"/>
<field name="s30_quality_tests_verified" readonly="s30_locked"/>
<field name="s30_specification_revisions" readonly="s30_locked"/>
<field name="s30_certifications_requirements" readonly="s30_locked"/>
<field name="s30_psd_rfd_reviewed" readonly="s30_locked"/>
<field name="s30_specification_deviations" readonly="s30_locked"/>
<field name="s30_design_authority" readonly="s30_locked"/>
</group>
<group string="Outcome">
<field name="s30_accepted" readonly="s30_locked"/>
<field name="s30_evaluate_risk" readonly="s30_locked"/>
</group>
<group string="Risk Assessment"
invisible="not s30_evaluate_risk">
<field name="s30_risk_consequence" readonly="s30_locked"/>
<field name="s30_risk_likelihood" readonly="s30_locked"/>
<field name="s30_risk_band" readonly="1"
decoration-success="s30_risk_band == 'green'"
decoration-warning="s30_risk_band == 'yellow'"
decoration-danger="s30_risk_band == 'red'"
widget="badge"/>
<field name="s30_mitigation_plan_required" readonly="s30_locked"/>
</group>
<group string="Signature" invisible="not s30_locked">
<field name="s30_signed_by" readonly="1"/>
<field name="s30_signed_date" readonly="1"/>
</group>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<!-- ================================================================== -->
<!-- LIST VIEW -->
<!-- ================================================================== -->
<record id="view_fp_contract_review_list" model="ir.ui.view">
<field name="name">fp.contract.review.list</field>
<field name="model">fp.contract.review</field>
<field name="arch" type="xml">
<list string="Contract Reviews"
decoration-success="state == 'complete'"
decoration-muted="state == 'dismissed'"
decoration-info="state == 'manager_review'"
decoration-warning="state == 'assistant_review'">
<field name="name"/>
<field name="customer_id"/>
<field name="part_number"/>
<field name="part_revision"/>
<field name="date_received"/>
<field name="s20_signed_by"/>
<field name="s30_signed_by"/>
<field name="state" widget="badge"/>
</list>
</field>
</record>
<!-- ================================================================== -->
<!-- KANBAN VIEW (grouped by state) -->
<!-- ================================================================== -->
<record id="view_fp_contract_review_kanban" model="ir.ui.view">
<field name="name">fp.contract.review.kanban</field>
<field name="model">fp.contract.review</field>
<field name="arch" type="xml">
<kanban default_group_by="state"
class="o_kanban_small_column">
<field name="name"/>
<field name="customer_id"/>
<field name="part_number"/>
<field name="part_revision"/>
<field name="state"/>
<templates>
<t t-name="card">
<div class="oe_kanban_global_click">
<div class="o_kanban_record_title">
<strong><field name="name"/></strong>
</div>
<div class="o_kanban_record_subtitle">
<field name="customer_id"/>
</div>
<div>
<span>Part: </span>
<field name="part_number"/>
<span> Rev </span>
<field name="part_revision"/>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<!-- ================================================================== -->
<!-- SEARCH VIEW -->
<!-- ================================================================== -->
<record id="view_fp_contract_review_search" model="ir.ui.view">
<field name="name">fp.contract.review.search</field>
<field name="model">fp.contract.review</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<field name="customer_id"/>
<field name="part_number"/>
<field name="part_revision"/>
<field name="contract_po_number"/>
<field name="quote_or_job_number"/>
<separator/>
<filter name="filter_pending"
string="Pending"
domain="[('state','in',['draft','assistant_review','manager_review'])]"/>
<filter name="filter_complete"
string="Complete"
domain="[('state','=','complete')]"/>
<filter name="filter_dismissed"
string="Dismissed"
domain="[('state','=','dismissed')]"/>
<group>
<filter name="group_state" string="Status"
context="{'group_by': 'state'}"/>
<filter name="group_customer" string="Customer"
context="{'group_by': 'customer_id'}"/>
</group>
</search>
</field>
</record>
<!-- ================================================================== -->
<!-- ACTION -->
<!-- ================================================================== -->
<record id="action_fp_contract_review" model="ir.actions.act_window">
<field name="name">Contract Reviews</field>
<field name="res_model">fp.contract.review</field>
<field name="view_mode">kanban,list,form</field>
<field name="search_view_id" ref="view_fp_contract_review_search"/>
<field name="context">{'search_default_filter_pending': 1}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No Contract Reviews yet.
</p>
<p>
Contract Reviews (QA-005) capture QA sign-off on new parts
before production. They are always optional and never
block manufacturing. Toggle
<b>Require Contract Review for new parts</b> on a customer
to show the reminder banner on new parts under that customer.
</p>
</field>
</record>
<!-- ================================================================== -->
<!-- MENU (under Plating → Quality) -->
<!-- ================================================================== -->
<menuitem id="menu_fp_quality_contract_review"
name="Contract Reviews"
parent="menu_fp_quality"
action="action_fp_contract_review"
sequence="15"/>
</odoo>

View File

@@ -0,0 +1,84 @@
<?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.
Sub 4 — Contract Review banner + tab on fp.part.catalog.
-->
<odoo>
<record id="view_fp_part_catalog_form_contract_review" model="ir.ui.view">
<field name="name">fp.part.catalog.form.contract.review</field>
<field name="model">fp.part.catalog</field>
<field name="inherit_id" ref="fusion_plating_configurator.view_fp_part_catalog_form"/>
<field name="arch" type="xml">
<!-- ===== Reminder banner above the title block ===== -->
<xpath expr="//div[hasclass('oe_title')]" position="before">
<field name="x_fc_contract_review_banner_visible" invisible="1"/>
<field name="x_fc_contract_review_state" invisible="1"/>
<field name="x_fc_contract_review_dismissed" invisible="1"/>
<div class="alert alert-info"
role="alert"
invisible="not x_fc_contract_review_banner_visible">
<i class="fa fa-info-circle me-2"/>
<strong>New part created.</strong>
Please complete the Contract Review (QA-005) if applicable.
This is optional — it can be dismissed and never blocks production.
<div class="mt-2">
<button name="action_start_contract_review"
type="object"
string="Start Contract Review"
class="btn btn-primary btn-sm me-2"/>
<button name="action_dismiss_contract_review"
type="object"
string="Dismiss"
class="btn btn-link btn-sm"/>
</div>
</div>
</xpath>
<!-- ===== Contract Review tab at end of notebook ===== -->
<xpath expr="//notebook" position="inside">
<page string="Contract Review" name="contract_review">
<group>
<field name="x_fc_contract_review_id" readonly="1"/>
<field name="x_fc_contract_review_state" readonly="1"
widget="badge"
decoration-success="x_fc_contract_review_state == 'complete'"
decoration-info="x_fc_contract_review_state == 'manager_review'"
decoration-warning="x_fc_contract_review_state == 'assistant_review'"
decoration-muted="x_fc_contract_review_state == 'dismissed'"
invisible="not x_fc_contract_review_id"/>
</group>
<div invisible="x_fc_contract_review_id or not partner_id">
<field name="x_fc_customer_requires_contract_review" invisible="1"/>
<p invisible="x_fc_customer_requires_contract_review">
This customer is not configured to require
Contract Reviews on new parts. You can still
start one manually.
</p>
<button name="action_start_contract_review"
type="object"
string="Start Contract Review"
class="btn btn-primary"/>
</div>
<div invisible="not x_fc_contract_review_id">
<button name="action_start_contract_review"
type="object"
string="Open Review"
class="btn btn-secondary me-2"/>
<button name="action_undismiss_contract_review"
type="object"
string="Undismiss Reminder"
class="btn btn-link"
groups="fusion_plating.group_fusion_plating_manager"
invisible="not x_fc_contract_review_dismissed"/>
</div>
</page>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,48 @@
<?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.
Sub 4 — Contract Review signer rosters in Fusion Plating settings.
-->
<odoo>
<record id="res_config_settings_view_form_fp_quality" model="ir.ui.view">
<field name="name">res.config.settings.view.form.fp.quality</field>
<field name="model">res.config.settings</field>
<field name="inherit_id"
ref="fusion_plating.res_config_settings_view_form_fp_core"/>
<field name="arch" type="xml">
<xpath expr="//app[@name='fusion_plating']" position="inside">
<block title="Contract Review (QA-005)"
name="fp_contract_review_settings"
help="Choose which users are authorised to sign each
section of a Contract Review. Users on these
lists gain only the right to sign — no other
permissions are affected. Plating Managers can
always sign regardless of these lists.">
<setting id="fp_qa_assistant_signers"
string="QA Assistant Signers"
help="Users authorised to sign Section 2.0
(Planning / Production Review). Typically
one or two people. Leave empty if any
user should be able to sign — in that
case a Plating Manager is the only
required approver.">
<field name="x_fc_qa_assistant_user_ids"
widget="many2many_tags"/>
</setting>
<setting id="fp_qa_manager_signers"
string="QA Manager Signers"
help="Users authorised to sign Section 3.0
(Quality Review). Typically the QA
Manager for the shop.">
<field name="x_fc_qa_manager_user_ids"
widget="many2many_tags"/>
</setting>
</block>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,37 @@
<?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.
Sub 4 — Contract Review toggle on customer.
-->
<odoo>
<record id="view_partner_form_fp_contract_review" model="ir.ui.view">
<field name="name">res.partner.form.fp.contract.review</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<xpath expr="//notebook" position="inside">
<page string="Plating Contract Review"
name="fp_contract_review_prefs"
invisible="is_company != True">
<group name="fp_contract_review_group">
<p class="text-muted" colspan="2">
Toggle this on so QA sees a Contract Review
reminder banner whenever a new part is created
under this customer. The reminder is always
optional — it can be dismissed and never blocks
production. Once the part reaches a confirmed
manufacturing order, the banner disappears on
its own.
</p>
<field name="x_fc_contract_review_required"
widget="boolean_toggle"/>
</group>
</page>
</xpath>
</field>
</record>
</odoo>