This commit is contained in:
gsinghpal
2026-04-28 19:39:37 -04:00
parent 2d42b33d68
commit 13e300d90e
103 changed files with 4959 additions and 331 deletions

View File

@@ -202,6 +202,14 @@
</tr>
</table>
<!-- ===== ROUTING TABLE =====
Continues on subsequent pages — paperformat handles
page break automatically. Footer (Ship Order To +
Additional Notes) closes the document. -->
<!-- inline routing follows; footer appears below -->
<!-- (placed after the routing table — see end-of-template) -->
<!-- ===== ROUTING TABLE ===== -->
<table class="bordered" style="margin-top: 4px;">
<thead>
@@ -284,6 +292,37 @@
</t>
</tbody>
</table>
<!-- ===== FOOTER — SHIP ORDER + NOTES ===== -->
<table class="bordered" style="margin-top: 6px;">
<tr>
<th style="width: 30%;">Ship Order To</th>
<th style="width: 70%;">Additional Notes</th>
</tr>
<tr>
<td style="vertical-align: top;">
<strong t-esc="(job.partner_id and job.partner_id.name) or '—'"/><br/>
<span t-esc="(job.partner_id and job.partner_id.street) or ''"/><br/>
<span t-if="job.partner_id and job.partner_id.street2"
t-esc="job.partner_id.street2"/>
<t t-if="job.partner_id and job.partner_id.street2"><br/></t>
<span t-esc="(job.partner_id and job.partner_id.city) or ''"/>,
<span t-esc="(job.partner_id and job.partner_id.state_id and job.partner_id.state_id.code) or ''"/>
<span t-esc="(job.partner_id and job.partner_id.zip) or ''"/><br/>
<span t-esc="(job.partner_id and job.partner_id.country_id and job.partner_id.country_id.code) or ''"/>
</td>
<td class="fp-trav-stamp" style="min-height: 18mm;">
<t t-if="'special_requirements' in job._fields and job.special_requirements">
<span t-esc="job.special_requirements"
style="white-space: pre-wrap; font-size: 7.5pt;"/>
</t>
</td>
</tr>
</table>
<div style="text-align: center; margin-top: 4px; font-size: 7pt; color: #666;">
<span t-esc="job.name"/>
</div>
</div>
</t>
</t>

View File

@@ -0,0 +1,281 @@
<?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.
Steelhead-style "Work Order Detail" PDF — the post-job audit cert
that walks fp.job.step.move records chronologically, lists captured
inputs per step, and ends with a Certified By + Cert Statement
page. Bound to fp.job directly (not fp.certificate) so a manager
can print the audit document straight from the job form.
Layout mirrors the customer-shared `job card.pdf` (see CLAUDE.md
Sub 12c). Reuses the same per-customer cert-statement resolution
chain (partner.x_fc_cert_statement → company.x_fc_default_cert_statement
→ hardcoded boilerplate) so we don't fork two cert templates.
-->
<odoo>
<record id="paperformat_fp_wo_detail" model="report.paperformat">
<field name="name">FP Work Order Detail — A4 portrait</field>
<field name="format">A4</field>
<field name="orientation">Portrait</field>
<field name="margin_top">15</field>
<field name="margin_bottom">15</field>
<field name="margin_left">12</field>
<field name="margin_right">12</field>
<field name="header_spacing">8</field>
<field name="dpi">90</field>
</record>
<record id="action_report_fp_job_wo_detail" model="ir.actions.report">
<field name="name">Work Order Detail</field>
<field name="model">fp.job</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">fusion_plating_jobs.report_fp_job_wo_detail_template</field>
<field name="report_file">fusion_plating_jobs.report_fp_job_wo_detail_template</field>
<field name="print_report_name">'WO Detail - %s' % (object.name or '').replace('/', '-')</field>
<field name="binding_model_id" ref="fusion_plating.model_fp_job"/>
<field name="binding_type">report</field>
<field name="paperformat_id" ref="paperformat_fp_wo_detail"/>
</record>
<template id="report_fp_job_wo_detail_template">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="job">
<t t-call="web.external_layout">
<t t-set="company" t-value="job.company_id"/>
<t t-set="moves" t-value="job.move_ids.sorted('move_datetime')"/>
<div class="page fp-wo-detail">
<style>
.fp-wo-detail { font-family: Arial, sans-serif; font-size: 9pt; color: #000; }
.fp-wo-detail h1 { text-align: center; font-size: 18pt; margin: 0 0 6px 0; font-weight: bold; color: #1a4d80; }
.fp-wo-detail h3 { font-size: 11pt; margin: 8px 0 2px 0; font-weight: bold; }
.fp-wo-detail .fp-meta { font-size: 8.5pt; color: #444; margin-bottom: 4px; }
.fp-wo-detail table.bordered,
.fp-wo-detail table.bordered th,
.fp-wo-detail table.bordered td { border: 1px solid #000; border-collapse: collapse; }
.fp-wo-detail table.bordered { width: 100%; margin-bottom: 8px; }
.fp-wo-detail table.bordered th { background: #ededed; padding: 4px 6px; font-size: 8.5pt; text-align: left; }
.fp-wo-detail table.bordered td { padding: 4px 6px; vertical-align: top; font-size: 8.5pt; }
.fp-wo-detail .text-center { text-align: center; }
.fp-wo-detail hr.heavy { border: 0; border-top: 2px solid #000; margin: 8px 0; }
.fp-wo-detail .fp-spec { font-size: 10pt; font-weight: bold; margin: 8px 0 4px 0; }
.fp-wo-detail .fp-step-block { page-break-inside: avoid; margin-bottom: 6px; }
</style>
<h1>Work Order Detail</h1>
<!-- ===== HEADER — Prepared For + summary table ===== -->
<div style="margin-bottom: 8px;">
<strong>Prepared For:</strong>
<span style="font-size: 11pt;"
t-esc="(job.partner_id and job.partner_id.name) or '—'"/>
</div>
<table class="bordered">
<tr>
<th style="width: 20%;">Part Number</th>
<th style="width: 30%;">Description</th>
<th style="width: 8%;">Quantity</th>
<th style="width: 10%;">Work Order</th>
<th style="width: 14%;">PO Number</th>
<th style="width: 8%;">Packing List No</th>
<th style="width: 10%;">Date</th>
</tr>
<tr>
<td>
<t t-if="'part_catalog_id' in job._fields and job.part_catalog_id">
<span t-esc="job.part_catalog_id.part_number or '—'"/>
</t>
<t t-else="">
<span t-esc="(job.product_id and job.product_id.default_code) or '—'"/>
</t>
</td>
<td style="white-space: pre-wrap;">
<t t-if="'part_catalog_id' in job._fields and job.part_catalog_id">
<span t-esc="job.part_catalog_id.name or job.product_id.name or '—'"/>
</t>
<t t-else="">
<span t-esc="(job.product_id and job.product_id.name) or '—'"/>
</t>
<t t-if="'special_requirements' in job._fields and job.special_requirements">
<br/>
<span style="font-size: 7.5pt;"
t-esc="job.special_requirements"/>
</t>
</td>
<td class="text-center">
<span t-esc="job.qty"/>
</td>
<td class="text-center">
<span t-esc="job.name"/>
</td>
<td>
<span t-esc="(job.sale_order_id and job.sale_order_id.client_order_ref) or '—'"/>
</td>
<td/>
<td>
<span t-esc="(job.date_finished or job.date_started or job.create_date) and (job.date_finished or job.date_started or job.create_date).strftime('%Y-%m-%d') or ''"/>
</td>
</tr>
</table>
<div class="fp-spec">Specification(s):
<span style="font-weight: normal;"
t-esc="(job.recipe_id and job.recipe_id.name) or '—'"/>
</div>
<hr class="heavy"/>
<!-- ===== CHAIN-OF-CUSTODY WALK ===== -->
<t t-foreach="moves" t-as="mv">
<t t-set="dest" t-value="mv.to_step_id"/>
<t t-set="tank_code" t-value="(mv.to_tank_id and mv.to_tank_id.code) or (dest and dest.tank_id and dest.tank_id.code) or ''"/>
<div class="fp-step-block">
<h3>
<span t-esc="(dest and dest.name) or '—'"/>
<t t-if="tank_code"> (<span t-esc="tank_code"/>)</t>
</h3>
<div class="fp-meta">
<strong>Part Number:</strong>
<t t-if="'part_catalog_id' in job._fields and job.part_catalog_id">
<span t-esc="job.part_catalog_id.part_number or ''"/>
<t t-if="job.part_catalog_id.name">
<span> </span><span t-esc="job.part_catalog_id.name"/>
</t>
</t>
<t t-else="">
<span t-esc="(job.product_id and (job.product_id.default_code or job.product_id.name)) or ''"/>
</t>
<br/>
<strong>Moved By:</strong> <span t-esc="mv.moved_by_user_id.name"/>
<span> </span>
<strong>Time:</strong>
<span t-esc="mv.move_datetime and mv.move_datetime.strftime('%b %d, %Y %I:%M:%S %p') or ''"/>
</div>
<!-- Captured input values for this move -->
<t t-set="captured_values_by_input"
t-value="{v.node_input_id.id: v for v in mv.transition_input_value_ids}"/>
<t t-set="prompts" t-value="False"/>
<t t-if="dest and dest.recipe_node_id">
<t t-set="prompts"
t-value="dest.recipe_node_id.input_ids.filtered(lambda i: (i.kind or 'step_input') == 'step_input').sorted('sequence')"/>
</t>
<t t-if="not prompts and mv.transition_input_value_ids">
<t t-set="prompts"
t-value="mv.transition_input_value_ids.mapped('node_input_id')"/>
</t>
<t t-if="prompts and mv.transition_input_value_ids">
<table class="bordered">
<thead>
<tr>
<th style="width: 24%;">Name</th>
<th style="width: 30%;">Description</th>
<th style="width: 18%;">Value</th>
<th style="width: 28%;">Recorded By</th>
</tr>
</thead>
<tbody>
<t t-foreach="prompts" t-as="inp">
<t t-set="cv" t-value="captured_values_by_input.get(inp.id)"/>
<t t-if="cv">
<t t-set="actual_str" t-value="''"/>
<t t-if="cv.value_text">
<t t-set="actual_str" t-value="cv.value_text"/>
</t>
<t t-elif="cv.value_number">
<t t-set="actual_str"
t-value="('%s %s' % (cv.value_number, (inp.target_unit if 'target_unit' in inp._fields and inp.target_unit else ''))).strip()"/>
</t>
<t t-elif="cv.value_boolean is not False">
<t t-set="actual_str" t-value="'PASS' if cv.value_boolean else 'FAIL'"/>
</t>
<t t-elif="cv.value_date">
<t t-set="actual_str" t-value="cv.value_date.strftime('%Y-%m-%d %H:%M')"/>
</t>
<tr>
<td><span t-esc="inp.name"/></td>
<td>
<t t-if="'hint' in inp._fields and inp.hint">
<span t-esc="inp.hint"/>
</t>
</td>
<td>
<strong t-esc="actual_str"/>
</td>
<td>
<span t-esc="(mv.moved_by_user_id and mv.moved_by_user_id.name) or ''"/>
</td>
</tr>
</t>
</t>
</tbody>
</table>
</t>
</div>
</t>
<t t-if="not moves">
<p style="color: #888; font-style: italic;">
No move log entries yet — this job hasn't progressed
through any steps. Operators move the job forward
via the tablet or the backend Move wizard.
</p>
</t>
<!-- ===== CERTIFIED BY + CERT STATEMENT ===== -->
<p style="page-break-before: always;"/>
<t t-set="owner_sig" t-value="False"/>
<t t-if="'x_fc_owner_user_id' in company._fields and company.x_fc_owner_user_id">
<t t-set="_emp" t-value="company.x_fc_owner_user_id.employee_ids[:1]"/>
<t t-if="_emp and 'signature' in _emp._fields">
<t t-set="owner_sig" t-value="_emp['signature']"/>
</t>
</t>
<t t-set="sig_override" t-value="('x_fc_coc_signature_override' in company._fields and company.x_fc_coc_signature_override) or False"/>
<t t-set="signature_img" t-value="sig_override or owner_sig"/>
<t t-set="signer_name" t-value="(job.manager_id and job.manager_id.name) or ('x_fc_owner_user_id' in company._fields and company.x_fc_owner_user_id and company.x_fc_owner_user_id.name) or ''"/>
<t t-set="_cust_stmt" t-value="(job.partner_id and 'x_fc_cert_statement' in job.partner_id._fields and job.partner_id.x_fc_cert_statement) or False"/>
<t t-set="_co_stmt" t-value="('x_fc_default_cert_statement' in company._fields and company.x_fc_default_cert_statement) or False"/>
<t t-set="cert_statement" t-value="_cust_stmt or _co_stmt or 'We certify that the parts listed above have been processed in accordance with the specifications referenced and that all required tests have been performed. Records on file at our facility per AS9100 / ISO 9001 retention policy.'"/>
<table class="bordered">
<tr>
<td style="width: 50%; vertical-align: top; height: 40mm;">
<strong>Certified By:</strong><br/>
<t t-if="signature_img">
<img t-att-src="'data:image/png;base64,%s' % signature_img.decode()"
style="max-height: 22mm; max-width: 70mm;"/>
</t><br/>
<strong>Name:</strong> <span t-esc="signer_name"/>
</td>
<td style="width: 50%; vertical-align: top;">
<strong>Certification Statement:</strong>
<span style="font-size: 8.5pt;">
Ref. WO# <span t-esc="job.name"/>
</span>
<p style="font-size: 8pt; margin-top: 4px; white-space: pre-wrap;"
t-esc="cert_statement"/>
</td>
</tr>
<tr>
<td colspan="2" style="height: 25mm;">
<strong>Other Comments:</strong>
</td>
</tr>
</table>
</div>
</t>
</t>
</t>
</template>
</odoo>