changes
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

This commit is contained in:
gsinghpal
2026-05-17 03:20:33 -04:00
parent f8586611c9
commit d3c5c25865
30 changed files with 712 additions and 183 deletions

View File

@@ -3,7 +3,7 @@
# License OPL-1 (Odoo Proprietary License v1.0)
{
'name': 'Fusion Plating — Native Jobs',
'version': '19.0.10.2.0',
'version': '19.0.10.8.0',
'category': 'Manufacturing/Plating',
'summary': 'Native plating job model — replaces mrp.production / mrp.workorder bridge.',
'author': 'Nexa Systems Inc.',

View File

@@ -25,6 +25,7 @@
<field name="header_line" eval="False"/>
<field name="header_spacing">0</field>
<field name="disable_shrinking" eval="True"/>
<!-- dpi=300 calibrated — see CLAUDE.md rule 14, 600 broke layout. -->
<field name="dpi">300</field>
</record>
@@ -47,6 +48,14 @@
reads (so `_so or ...` doesn't NameError). We
then override the ones we have data for. -->
<t t-call="fusion_plating_reports.report_fp_wo_sticker_defaults"/>
<!-- Multi-line trigger: parent SO has 2+ part-bearing lines.
Even though this job is for a single specific part (jobs
are grouped by recipe+part+coating+thickness+SN), the
consolidated PO sticker is the requested behaviour. -->
<t t-set="_so_part_lines" t-value="job.sale_order_id
and job.sale_order_id.order_line.filtered(lambda l: l.x_fc_part_catalog_id)
or job.env['sale.order.line']"/>
<t t-set="_multi_line" t-value="len(_so_part_lines) &gt;= 2"/>
<!-- Pre-resolve the variables the shared inner template
expects, sourcing them from fp.job's native fields. -->
<t t-set="_order_id" t-value="job.name"/>
@@ -54,13 +63,13 @@
<t t-set="_scan_path" t-value="'/fp/job/'"/>
<t t-set="_mo" t-value="False"/>
<t t-set="_so" t-value="job.sale_order_id"/>
<t t-set="_line" t-value="job.sale_order_line_ids[:1]"/>
<t t-set="_part" t-value="('part_catalog_id' in job._fields and job.part_catalog_id) or False"/>
<t t-set="_spec" t-value="('customer_spec_id' in job._fields and job.customer_spec_id) or False"/>
<t t-set="_process" t-value="job.recipe_id or False"/>
<t t-set="_due" t-value="job.date_deadline or False"/>
<t t-set="_qty" t-value="job.qty"/>
<t t-set="_qty_total" t-value="job.qty"/>
<t t-set="_line" t-value="False if _multi_line else job.sale_order_line_ids[:1]"/>
<t t-set="_part" t-value="False if _multi_line else (('part_catalog_id' in job._fields and job.part_catalog_id) or False)"/>
<t t-set="_spec" t-value="False if _multi_line else (('customer_spec_id' in job._fields and job.customer_spec_id) or False)"/>
<t t-set="_process" t-value="False if _multi_line else (job.recipe_id or False)"/>
<t t-set="_due" t-value="(job.sale_order_id and job.sale_order_id.commitment_date) if _multi_line else (job.date_deadline or False)"/>
<t t-set="_qty" t-value="sum(_so_part_lines.mapped('product_uom_qty')) if _multi_line else job.qty"/>
<t t-set="_qty_total" t-value="1 if _multi_line else job.qty"/>
<t t-set="_partner_name" t-value="job.partner_id.name"/>
<!-- The fp.job's own name (WH/JOB/00033) is already
printed in the header as "WO #...", so suppress
@@ -91,25 +100,31 @@
<t t-call="web.html_container">
<t t-foreach="docs" t-as="job">
<t t-call="fusion_plating_reports.report_fp_wo_sticker_defaults"/>
<t t-set="_so_part_lines" t-value="job.sale_order_id
and job.sale_order_id.order_line.filtered(lambda l: l.x_fc_part_catalog_id)
or job.env['sale.order.line']"/>
<t t-set="_multi_line" t-value="len(_so_part_lines) &gt;= 2"/>
<t t-set="_order_id" t-value="job.name"/>
<t t-set="_scan_id" t-value="job.id"/>
<t t-set="_scan_path" t-value="'/fp/job/'"/>
<t t-set="_mo" t-value="False"/>
<t t-set="_so" t-value="job.sale_order_id"/>
<t t-set="_line" t-value="job.sale_order_line_ids[:1]"/>
<t t-set="_part" t-value="('part_catalog_id' in job._fields and job.part_catalog_id) or False"/>
<t t-set="_spec" t-value="('customer_spec_id' in job._fields and job.customer_spec_id) or False"/>
<t t-set="_process" t-value="job.recipe_id or False"/>
<t t-set="_due" t-value="job.date_deadline or False"/>
<t t-set="_qty" t-value="job.qty"/>
<t t-set="_qty_total" t-value="job.qty"/>
<t t-set="_line" t-value="False if _multi_line else job.sale_order_line_ids[:1]"/>
<t t-set="_part" t-value="False if _multi_line else (('part_catalog_id' in job._fields and job.part_catalog_id) or False)"/>
<t t-set="_spec" t-value="False if _multi_line else (('customer_spec_id' in job._fields and job.customer_spec_id) or False)"/>
<t t-set="_process" t-value="False if _multi_line else (job.recipe_id or False)"/>
<t t-set="_due" t-value="(job.sale_order_id and job.sale_order_id.commitment_date) if _multi_line else (job.date_deadline or False)"/>
<t t-set="_qty" t-value="sum(_so_part_lines.mapped('product_uom_qty')) if _multi_line else job.qty"/>
<t t-set="_qty_total" t-value="1 if _multi_line else job.qty"/>
<t t-set="_partner_name" t-value="job.partner_id.name"/>
<t t-set="_mo_ref" t-value="''"/>
<!-- Internal override: read x_fc_internal_description from
the first linked SO line. -->
<t t-set="_notes_content" t-value="(job.sale_order_line_ids[:1]
and 'x_fc_internal_description' in job.sale_order_line_ids[:1]._fields
and job.sale_order_line_ids[:1].x_fc_internal_description) or '-'"/>
the first linked SO line. Multi-line PO blanks it
since each line has its own description. -->
<t t-set="_notes_content" t-value="'-' if _multi_line else
((job.sale_order_line_ids[:1]
and 'x_fc_internal_description' in job.sale_order_line_ids[:1]._fields
and job.sale_order_line_ids[:1].x_fc_internal_description) or '-')"/>
<t t-call="fusion_plating_reports.report_fp_wo_sticker_inner"/>
</t>
</t>

View File

@@ -114,7 +114,7 @@
<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 14px 0; font-weight: bold; color: #1a4d80; }
.fp-wo-detail h1 { text-align: center; font-size: 22pt; margin: 0 0 14px 0; font-weight: bold; color: #2e2e2e; }
.fp-wo-detail h3 { font-size: 11pt; margin: 12px 0 4px 0; font-weight: bold; }
.fp-wo-detail .fp-meta { font-size: 8.5pt; color: #444; margin-bottom: 6px; }
.fp-wo-detail table.bordered,
@@ -134,8 +134,8 @@
keeps captions glued to their image. */
.fp-wo-detail .fp-photo-section { margin-top: 18px; }
.fp-wo-detail .fp-photo-section h2 {
font-size: 13pt; font-weight: bold; color: #1a4d80;
margin: 0 0 8px 0; border-bottom: 2px solid #1a4d80;
font-size: 13pt; font-weight: bold; color: #2e2e2e;
margin: 0 0 8px 0; border-bottom: 2px solid #c1c1c1;
padding-bottom: 3px;
}
.fp-wo-detail .fp-photo-grid {
@@ -162,7 +162,7 @@
font-size: 8pt; color: #444; line-height: 1.25;
}
.fp-wo-detail .fp-photo-ref {
font-size: 8pt; color: #1a4d80; font-style: italic;
font-size: 8pt; color: #4e4e4e; font-style: italic;
white-space: nowrap;
}
/* Inline signature image inside the step
@@ -243,6 +243,65 @@
</tr>
</table>
<!-- ===== Contract Review (QA-005) block =====
Surfaces the part's QA-005 audit trail on
every WO Detail print. Per Rule 4 of the
contract-review flow, on repeat orders the
contract-review WO step is auto-completed
at job creation using the reviewer's
identity + date from the existing review;
this block makes that audit visible to
customers and inspectors. Hidden when no
review exists (e.g. customer doesn't
require contract review). -->
<t t-set="review"
t-value="('part_catalog_id' in job._fields and job.part_catalog_id
and 'x_fc_contract_review_id' in job.part_catalog_id._fields
and job.part_catalog_id.x_fc_contract_review_id) or False"/>
<t t-if="review">
<t t-set="_signer"
t-value="review.s30_signed_by or review.s20_signed_by"/>
<t t-set="_signed_dt"
t-value="review.s30_signed_date or review.s20_signed_date"/>
<t t-set="_initials_src"
t-value="(_signer and _signer.name) or ''"/>
<t t-set="_initials"
t-value="''.join([w[:1].upper() for w in _initials_src.split()[:3]])"/>
<table class="bordered" style="margin-top: 8px;">
<tr>
<th colspan="4" style="font-size: 10pt;">Contract Review (QA-005)</th>
</tr>
<tr>
<th style="width: 28%;">Status</th>
<th style="width: 30%;">Reviewer</th>
<th style="width: 14%;">Initials</th>
<th style="width: 28%;">Date Reviewed</th>
</tr>
<tr>
<td>
<t t-if="review.state == 'complete'">
<span style="font-weight: bold; color: #2e2e2e;">QA-005 Approved</span>
</t>
<t t-else="">
<span style="color: #aa0000;">Pending — <span t-esc="dict(review._fields['state'].selection).get(review.state, review.state)"/></span>
</t>
</td>
<td>
<span t-esc="(_signer and _signer.name) or '—'"/>
</td>
<td>
<span style="font-weight: bold;" t-esc="_initials or '—'"/>
</td>
<td>
<t t-if="_signed_dt">
<span t-esc="job.fp_format_local(_signed_dt, '%Y-%m-%d')"/>
</t>
<t t-else=""></t>
</td>
</tr>
</table>
</t>
<div class="fp-spec">Specification(s):
<span style="font-weight: normal;"
t-esc="(job.recipe_id and job.recipe_id.name) or '—'"/>
@@ -481,21 +540,30 @@
<div style="page-break-before: always;"/>
<div style="height: 8mm;"/>
<!-- Certifier = the job's plating manager. Pulls
their Plating Signature (`x_fc_signature_image`)
from Preferences → My Profile. Falls back to
the company owner's signature, then to the
settings override only if no user has one. -->
<t t-set="certifier_user" t-value="job.manager_id or (('x_fc_owner_user_id' in company._fields and company.x_fc_owner_user_id) or False)"/>
<!-- Certifier = the company's QA Manager, set in
Settings → Fusion Plating → Contract Review.
Falls back to the job's plating manager, then
the company owner, then the settings signature
override. Pulls the certifier's Plating
Signature (`x_fc_signature_image`) from
Preferences → My Profile.
Resolution priority added 2026-05-17 per
ops request — was auto-defaulting to the
current user (whoever the job manager
happened to be) which signed every cert as
the wrong person. -->
<t t-set="_qa_managers" t-value="('x_fc_qa_manager_user_ids' in company._fields and company.x_fc_qa_manager_user_ids) or False"/>
<t t-set="certifier_user" t-value="(_qa_managers and _qa_managers[:1])
or job.manager_id
or (('x_fc_owner_user_id' in company._fields and company.x_fc_owner_user_id) or False)"/>
<t t-set="signature_img" t-value="False"/>
<t t-if="certifier_user and 'x_fc_signature_image' in certifier_user._fields and certifier_user.x_fc_signature_image">
<t t-set="signature_img" t-value="certifier_user.x_fc_signature_image"/>
</t>
<!-- Final fallback: company-level override for sites
whose certifier hasn't uploaded their signature yet. -->
<t t-if="not signature_img and 'x_fc_coc_signature_override' in company._fields and company.x_fc_coc_signature_override">
<t t-set="signature_img" t-value="company.x_fc_coc_signature_override"/>
</t>
<!-- Signature Override Image fallback retired
2026-05-17. If no certifier user has uploaded
their Plating Signature, the cert prints
without a signature image (Name still shows). -->
<t t-set="signer_name" t-value="(certifier_user and certifier_user.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"/>