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:
@@ -0,0 +1,21 @@
|
||||
<?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 QWeb report action.
|
||||
-->
|
||||
<odoo>
|
||||
|
||||
<record id="action_report_contract_review" model="ir.actions.report">
|
||||
<field name="name">Contract Review (QA-005)</field>
|
||||
<field name="model">fp.contract.review</field>
|
||||
<field name="report_type">qweb-pdf</field>
|
||||
<field name="report_name">fusion_plating_quality.report_contract_review_qa005</field>
|
||||
<field name="report_file">fusion_plating_quality.report_contract_review_qa005</field>
|
||||
<field name="print_report_name">'QA-005 - %s' % (object.name or '').replace('/', '-')</field>
|
||||
<field name="binding_model_id" ref="model_fp_contract_review"/>
|
||||
<field name="binding_type">report</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,296 @@
|
||||
<?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 QWeb template (1:1 paper form).
|
||||
-->
|
||||
<odoo>
|
||||
|
||||
<template id="report_contract_review_qa005">
|
||||
<t t-call="web.html_container">
|
||||
<t t-foreach="docs" t-as="doc">
|
||||
<t t-call="web.external_layout">
|
||||
<div class="page" style="font-family: Arial, sans-serif; font-size: 10pt; color: #000;">
|
||||
|
||||
<style>
|
||||
.qa005-title-row { display: table; width: 100%; border-collapse: collapse; margin-bottom: 8pt; }
|
||||
.qa005-title-row > div { display: table-cell; border: 1.5pt solid #000; padding: 6pt; vertical-align: middle; }
|
||||
.qa005-title-logo { width: 22%; text-align: center; }
|
||||
.qa005-title-center { width: 44%; text-align: center; font-weight: bold; font-size: 13pt; }
|
||||
.qa005-title-frm { width: 17%; font-weight: bold; }
|
||||
.qa005-title-date { width: 17%; font-weight: bold; }
|
||||
.qa005-header-tbl { width: 100%; border-collapse: collapse; margin-bottom: 6pt; }
|
||||
.qa005-header-tbl td { border: 1pt solid #000; padding: 4pt 6pt; }
|
||||
.qa005-section-title { font-weight: bold; margin-top: 6pt; margin-bottom: 3pt; border-bottom: 1pt solid #000; padding-bottom: 2pt; }
|
||||
.qa005-section-desc { font-style: italic; margin-bottom: 6pt; }
|
||||
.qa005-check-tbl { width: 100%; border-collapse: collapse; }
|
||||
.qa005-check-tbl td { border: 1pt solid #000; padding: 6pt; width: 25%; }
|
||||
.qa005-sig-tbl { width: 100%; border-collapse: collapse; margin-top: 4pt; }
|
||||
.qa005-sig-tbl td { border: 1pt solid #000; padding: 6pt; }
|
||||
.qa005-risk-line { margin: 6pt 0; }
|
||||
.qa005-matrix { border-collapse: collapse; margin-top: 8pt; }
|
||||
.qa005-matrix td { border: 1pt solid #000; width: 28pt; height: 22pt; text-align: center; }
|
||||
.qa005-matrix .cell-g { background-color: #8bb36b; }
|
||||
.qa005-matrix .cell-y { background-color: #fff59d; }
|
||||
.qa005-matrix .cell-r { background-color: #e06666; }
|
||||
.qa005-matrix .cell-sel { outline: 3pt solid #000; outline-offset: -3pt; font-weight: bold; }
|
||||
.qa005-consequences-tbl, .qa005-likelihood-tbl { font-size: 9pt; width: 100%; }
|
||||
.qa005-consequences-tbl td, .qa005-likelihood-tbl td { padding: 2pt 4pt; }
|
||||
.qa005-box-filled::before { content: "\2612 "; font-size: 12pt; }
|
||||
.qa005-box-empty::before { content: "\2610 "; font-size: 12pt; }
|
||||
.qa005-evaluate-inline span { margin-right: 14pt; }
|
||||
</style>
|
||||
|
||||
<!-- ============ HEADER BAR (logo + title + code + date) ============ -->
|
||||
<div class="qa005-title-row">
|
||||
<div class="qa005-title-logo">
|
||||
<img t-if="doc.company_id.logo"
|
||||
t-att-src="'data:image/png;base64,%s' % to_text(doc.company_id.logo)"
|
||||
style="max-height: 45pt; max-width: 100%;"/>
|
||||
</div>
|
||||
<div class="qa005-title-center">
|
||||
CONTRACT REVIEW AND<br/>RISK ASSESSMENT
|
||||
</div>
|
||||
<div class="qa005-title-frm">
|
||||
FRM: QA-005<br/>Rev. 0
|
||||
</div>
|
||||
<div class="qa005-title-date">
|
||||
Issue Date:<br/>Nov 25, 2021
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ IDENTITY TABLE ============ -->
|
||||
<table class="qa005-header-tbl">
|
||||
<tr>
|
||||
<td style="width:12%;"><b>Customer:</b></td>
|
||||
<td style="width:28%;"><span t-field="doc.customer_id.name"/></td>
|
||||
<td style="width:14%;"><b>Quote or Job #:</b></td>
|
||||
<td style="width:14%;"><span t-field="doc.quote_or_job_number"/></td>
|
||||
<td style="width:10%;"><b>Part No:<br/>Prime</b></td>
|
||||
<td style="width:12%;">
|
||||
<span t-field="doc.part_number"/>
|
||||
<br/>Rev: <span t-field="doc.part_revision"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Contract / P.O. No:</b></td>
|
||||
<td><span t-field="doc.contract_po_number"/></td>
|
||||
<td><b>Date Rec'd:</b></td>
|
||||
<td><span t-field="doc.date_received"/></td>
|
||||
<td><b>Qty</b></td>
|
||||
<td><span t-field="doc.qty"/>
|
||||
— Due <span t-field="doc.due_date"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p class="qa005-section-desc">
|
||||
I have reviewed the Purchase Order / RFQ and have verified that all
|
||||
the information supplied is accurate and reflects the terms and
|
||||
conditions as specified on the referenced quotation (if applicable),
|
||||
and meets the general criteria for acceptance for the reviewing
|
||||
function.
|
||||
</p>
|
||||
|
||||
<!-- ============ SECTION 2.0 ============ -->
|
||||
<div class="qa005-section-title">2.0 Planning / Production Review</div>
|
||||
<table class="qa005-check-tbl">
|
||||
<tr>
|
||||
<td><span t-attf-class="qa005-box-{{ 'filled' if doc.s20_acceptable_lead_time else 'empty' }}"/>Acceptable Lead Time</td>
|
||||
<td><span t-attf-class="qa005-box-{{ 'filled' if doc.s20_capacity_to_process else 'empty' }}"/>Capacity to Process</td>
|
||||
<td><span t-attf-class="qa005-box-{{ 'filled' if doc.s20_skills_to_process else 'empty' }}"/>Skills to Process</td>
|
||||
<td><span t-attf-class="qa005-box-{{ 'filled' if doc.s20_fixtures_required else 'empty' }}"/>Fixtures Required</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span t-attf-class="qa005-box-{{ 'filled' if doc.s20_prime_approvals else 'empty' }}"/>Prime approvals on file</td>
|
||||
<td><span t-attf-class="qa005-box-{{ 'filled' if doc.s20_pricing else 'empty' }}"/>Pricing</td>
|
||||
<td colspan="2"><span t-attf-class="qa005-box-{{ 'filled' if doc.s20_approved_technique else 'empty' }}"/>Approved Technique by Customer</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span t-attf-class="qa005-box-{{ 'filled' if doc.s20_drawings_available else 'empty' }}"/>Drawings available</td>
|
||||
<td colspan="2"><span t-attf-class="qa005-box-{{ 'filled' if doc.s20_process_type_class_grade else 'empty' }}"/>Process Type / Class / Grade</td>
|
||||
<td><span t-attf-class="qa005-box-{{ 'filled' if doc.s20_pre_post_processing_steps else 'empty' }}"/>Pre / Post Processing Steps</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table class="qa005-sig-tbl">
|
||||
<tr>
|
||||
<td style="width:12%;">
|
||||
<b>Accepted</b>
|
||||
<span t-attf-class="qa005-box-{{ 'filled' if doc.s20_accepted else 'empty' }}"/>
|
||||
</td>
|
||||
<td style="width:20%;"><b>Production Signature</b></td>
|
||||
<td style="width:48%;">
|
||||
<span t-field="doc.s20_signed_by.name"/>
|
||||
</td>
|
||||
<td style="width:8%;"><b>Date:</b></td>
|
||||
<td style="width:12%;">
|
||||
<span t-field="doc.s20_signed_date"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="qa005-risk-line">
|
||||
<b>Comments:</b> <span t-field="doc.s20_comments"/>
|
||||
</div>
|
||||
<div class="qa005-risk-line">
|
||||
<b><i>EVALUATE RISK</i></b>
|
||||
<span class="qa005-evaluate-inline">
|
||||
<span>
|
||||
<span t-attf-class="qa005-box-{{ 'filled' if doc.s20_evaluate_risk else 'empty' }}"/>YES
|
||||
</span>
|
||||
<span>
|
||||
<span t-attf-class="qa005-box-{{ 'filled' if not doc.s20_evaluate_risk else 'empty' }}"/>NO
|
||||
</span>
|
||||
<b>Level:</b>
|
||||
<span t-esc="' '.join(['[%s]' % n if str(n) == doc.s20_risk_level else str(n) for n in range(1, 6)])"/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- ============ SECTION 3.0 ============ -->
|
||||
<div class="qa005-section-title" style="margin-top:10pt;">3.0 Quality Review</div>
|
||||
<table class="qa005-check-tbl">
|
||||
<tr>
|
||||
<td colspan="2"><span t-attf-class="qa005-box-{{ 'filled' if doc.s30_source_control_docs else 'empty' }}"/>Source Control Documents (Customer Spec's)</td>
|
||||
<td><span t-attf-class="qa005-box-{{ 'filled' if doc.s30_quality_clauses_supplied else 'empty' }}"/>Quality Clause(s) supplied</td>
|
||||
<td><span t-attf-class="qa005-box-{{ 'filled' if doc.s30_quality_clauses_attainable else 'empty' }}"/>Quality Clause(s) attainable</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span t-attf-class="qa005-box-{{ 'filled' if doc.s30_critical_tolerance else 'empty' }}"/>Critical Tolerance(s)</td>
|
||||
<td colspan="2"><span t-attf-class="qa005-box-{{ 'filled' if doc.s30_measuring_tooling else 'empty' }}"/>Measuring Tooling Available</td>
|
||||
<td><span t-attf-class="qa005-box-{{ 'filled' if doc.s30_quality_tests_verified else 'empty' }}"/>Quality Tests Requirements Verified</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span t-attf-class="qa005-box-{{ 'filled' if doc.s30_specification_revisions else 'empty' }}"/>Specification Revisions</td>
|
||||
<td><span t-attf-class="qa005-box-{{ 'filled' if doc.s30_certifications_requirements else 'empty' }}"/>Certifications Requirements</td>
|
||||
<td colspan="2"><span t-attf-class="qa005-box-{{ 'filled' if doc.s30_psd_rfd_reviewed else 'empty' }}"/>PSD, RFD etc. Reviewed</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span t-attf-class="qa005-box-{{ 'filled' if doc.s30_specification_deviations else 'empty' }}"/>Specification Deviations</td>
|
||||
<td><span t-attf-class="qa005-box-{{ 'filled' if doc.s30_design_authority else 'empty' }}"/>Design Authority</td>
|
||||
<td colspan="2"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table class="qa005-sig-tbl">
|
||||
<tr>
|
||||
<td style="width:12%;">
|
||||
<b>Accepted</b>
|
||||
<span t-attf-class="qa005-box-{{ 'filled' if doc.s30_accepted else 'empty' }}"/>
|
||||
</td>
|
||||
<td style="width:20%;"><b>Quality Signature</b></td>
|
||||
<td style="width:48%;">
|
||||
<span t-field="doc.s30_signed_by.name"/>
|
||||
</td>
|
||||
<td style="width:8%;"><b>Date:</b></td>
|
||||
<td style="width:12%;">
|
||||
<span t-field="doc.s30_signed_date"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="qa005-risk-line">
|
||||
<b><i>EVALUATE RISK</i></b>
|
||||
<span class="qa005-evaluate-inline">
|
||||
<span>
|
||||
<span t-attf-class="qa005-box-{{ 'filled' if doc.s30_evaluate_risk else 'empty' }}"/>YES
|
||||
</span>
|
||||
<span>
|
||||
<span t-attf-class="qa005-box-{{ 'filled' if not doc.s30_evaluate_risk else 'empty' }}"/>NO
|
||||
</span>
|
||||
<b>Level:</b>
|
||||
<span t-esc="doc.s30_risk_band and doc.s30_risk_band.upper() or ''"/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- ============ RISK MATRIX ============ -->
|
||||
<div style="display: table; width: 100%; margin-top: 10pt;">
|
||||
|
||||
<!-- Consequences legend (left) -->
|
||||
<div style="display: table-cell; width: 42%; vertical-align: top; padding-right: 10pt;">
|
||||
<b>CONSEQUENCES</b>
|
||||
<table class="qa005-consequences-tbl">
|
||||
<tr><td>1</td><td>Minimal</td><td>No impact</td></tr>
|
||||
<tr><td>2</td><td>Moderate</td><td>Additional activities req'd</td></tr>
|
||||
<tr><td>3</td><td>Mod. / Applicable</td><td>Unable to meet commitments</td></tr>
|
||||
<tr><td>4</td><td>Major / Changes</td><td>Unable to meet commitments</td></tr>
|
||||
<tr><td>5</td><td>Unacceptable</td><td>Unable to meet commitments</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Likelihood legend (right) -->
|
||||
<div style="display: table-cell; width: 58%; vertical-align: top;">
|
||||
<b>LIKELIHOOD</b>
|
||||
<table class="qa005-likelihood-tbl">
|
||||
<tr><td>1</td><td>Not Likely</td><td>Current approach / process will effectively avoid this risk</td></tr>
|
||||
<tr><td>2</td><td>Low Likelihood</td><td>Current approach / process has usually mitigated</td></tr>
|
||||
<tr><td>3</td><td>Likely</td><td>Current approach / process may mitigate this risk</td></tr>
|
||||
<tr><td>4</td><td>Highly Likely</td><td>Current approach / process cannot mitigate — different approach might</td></tr>
|
||||
<tr><td>5</td><td>Near Certainty</td><td>Current approach / process cannot mitigate the risk and no processes are available</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 5x5 coloured grid. The selected cell (consequence, likelihood)
|
||||
gets an extra black outline for visual pickup on print. -->
|
||||
<div style="display: table; width: 100%; margin-top: 10pt;">
|
||||
<div style="display: table-cell; vertical-align: top;">
|
||||
<table class="qa005-matrix">
|
||||
<t t-set="bands" t-value="{
|
||||
(1,5):'g',(2,5):'y',(3,5):'r',(4,5):'r',(5,5):'r',
|
||||
(1,4):'g',(2,4):'y',(3,4):'y',(4,4):'r',(5,4):'r',
|
||||
(1,3):'g',(2,3):'y',(3,3):'y',(4,3):'y',(5,3):'r',
|
||||
(1,2):'g',(2,2):'g',(3,2):'y',(4,2):'y',(5,2):'y',
|
||||
(1,1):'g',(2,1):'g',(3,1):'g',(4,1):'y',(5,1):'y',
|
||||
}"/>
|
||||
<t t-set="sel_c" t-value="int(doc.s30_risk_consequence) if doc.s30_risk_consequence else 0"/>
|
||||
<t t-set="sel_l" t-value="int(doc.s30_risk_likelihood) if doc.s30_risk_likelihood else 0"/>
|
||||
<tr t-foreach="[5,4,3,2,1]" t-as="lik">
|
||||
<td style="border:none; font-weight:bold;" t-esc="lik"/>
|
||||
<t t-foreach="[1,2,3,4,5]" t-as="cons">
|
||||
<t t-set="band" t-value="bands.get((cons, lik))"/>
|
||||
<t t-set="is_sel" t-value="(cons == sel_c) and (lik == sel_l)"/>
|
||||
<td t-attf-class="cell-#{band}#{' cell-sel' if is_sel else ''}">
|
||||
<t t-if="is_sel">X</t>
|
||||
</td>
|
||||
</t>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="border:none;"/>
|
||||
<td style="border:none; font-weight:bold;">1</td>
|
||||
<td style="border:none; font-weight:bold;">2</td>
|
||||
<td style="border:none; font-weight:bold;">3</td>
|
||||
<td style="border:none; font-weight:bold;">4</td>
|
||||
<td style="border:none; font-weight:bold;">5</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div style="font-size: 8pt; margin-top: 2pt;">
|
||||
<span style="writing-mode: vertical-rl; transform: rotate(180deg);">LIKELIHOOD</span>
|
||||
      CONSEQUENCES
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mitigation plan required Y/N -->
|
||||
<div style="display: table-cell; vertical-align: top; padding-left: 20pt;">
|
||||
<div style="border: 1pt solid #000; padding: 6pt; margin-top: 10pt;">
|
||||
<b>Mitigation Plan Required?</b>
|
||||
<div style="margin-top: 4pt;">
|
||||
<span>
|
||||
<span t-attf-class="qa005-box-{{ 'filled' if doc.s30_mitigation_plan_required else 'empty' }}"/>YES
|
||||
</span>
|
||||
  
|
||||
<span>
|
||||
<span t-attf-class="qa005-box-{{ 'filled' if not doc.s30_mitigation_plan_required else 'empty' }}"/>NO
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user