docs(sub12c): implementation plan — 5 tasks (down from original 18)
Tightened from the original 18-task plan after inspecting existing templates: - report_coc_en / report_coc_fr already exist with Nadcap/AS9100/CGP logos, signature, certified_by — solid. Add a chronological body alongside, don't rebuild. - company.x_fc_nadcap_logo etc already exist on res.company. Skip. - The native fp.job traveller is minimal (post-Sub-11) and needs the paper-style upgrade. Replace its body, not the action. - fp.job.step.timelog state machine landed in Sub 12b — Sub 12c just ships views + menu. 5-task breakdown: 1. Bump versions + manifest scaffolding 2. Operator Traveller v2 (A4 landscape, paper-style, target columns) 3. Chronological CoC body + body_style opt-in router 4. Labor History list/form/search + Plating menu 5. Deploy to entech + smoke test Out of scope: rack travel ticket PDF (Sub 12b's Save+Print 404 stays flagged), per-customer cert statement (boilerplate inline for now). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,955 @@
|
||||
# Sub 12c — Operator Traveller v2 + Chronological CoC + Labor History
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Upgrade the operator traveller PDF to paper-style A4 landscape (matching the Amphenol screens 16-18), add a chronological body to the existing CoC report (walks `fp.job.step.move` in time order), and ship a Labor History screen for billing/payroll audit.
|
||||
|
||||
**Architecture:** Replace the minimal `report_fp_job_traveller_template` body with the paper-style table. Add a new `coc_chronological_body` QWeb template alongside the existing `coc_body` in `fusion_plating_reports`; introduce a `body_style` selection on `fp.certificate` so customers opt in per cert. Labor History = standard list/form/search views on the existing `fp.job.step.timelog` (state machine added by Sub 12b). No new models.
|
||||
|
||||
**Tech Stack:** Odoo 19, QWeb XML, SCSS. No JS. No new Python models.
|
||||
|
||||
**Companion docs:**
|
||||
- [Spec](../specs/2026-04-27-sub12-simple-recipe-editor-design.md) section 6
|
||||
- [Steelhead screen inventory](../specs/2026-04-27-simple-recipe-editor-steelhead-screens.md) — screens 16-24
|
||||
|
||||
**Existing artifacts to extend (do NOT replace):**
|
||||
- `fusion_plating_jobs/report/report_fp_job_traveller.xml` — native fp.job traveller (minimal, post-Sub-11). Body upgrade.
|
||||
- `fusion_plating_reports/report/report_coc.xml` — `coc_body` template + `report_coc_en` / `report_coc_fr` actions. Add a chronological body template; existing classic body untouched.
|
||||
- `fp.job.step.timelog` — Sub 12b added the state machine. Sub 12c adds list/form/search views.
|
||||
|
||||
**Out of scope (deferred):**
|
||||
- Rack travel ticket PDF (referenced by Sub 12b's Rack Parts Save+Print — keep as 404 placeholder, ship in a follow-up sub).
|
||||
- New cert types / Nadcap rules — existing CoC infrastructure already handles them.
|
||||
|
||||
**Deploy target:** entech (LXC 111). `-u --stop-after-init` clean upgrade per task.
|
||||
|
||||
---
|
||||
|
||||
## File structure
|
||||
|
||||
### Files to create
|
||||
|
||||
```
|
||||
fusion_plating/views/fp_job_step_timelog_views.xml # list/form/search + Labor History menu
|
||||
fusion_plating_reports/report/report_coc_chronological.xml # new chronological CoC body template
|
||||
```
|
||||
|
||||
### Files to modify
|
||||
|
||||
```
|
||||
fusion_plating/__manifest__.py # 19.0.10.1.0 → 19.0.10.2.0; add timelog views to data
|
||||
fusion_plating_jobs/__manifest__.py # version bump
|
||||
fusion_plating_jobs/report/report_fp_job_traveller.xml # rewrite template body to paper-style landscape
|
||||
fusion_plating_reports/__manifest__.py # version bump; add report_coc_chronological.xml
|
||||
fusion_plating_reports/report/report_coc.xml # extend coc_body to support body_style routing (optional minimal change)
|
||||
fusion_plating_certificates/models/fp_certificate.py # add body_style selection field
|
||||
fusion_plating_certificates/views/fp_certificate_views.xml # surface body_style on form
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Conventions
|
||||
|
||||
- Read every file before editing. The CoC template has 250+ lines of carefully-tuned QWeb — don't restructure unless necessary.
|
||||
- Headers on all new files: Copyright 2026 Nexa Systems Inc., OPL-1, Part of Fusion Plating.
|
||||
- Verification: entech `-u --stop-after-init` clean upgrade. Visual smoke test on a real job's traveller and a real cert's CoC.
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Bump versions + manifest data entries
|
||||
|
||||
**Files:**
|
||||
- Modify: `fusion_plating/__manifest__.py`
|
||||
- Modify: `fusion_plating_jobs/__manifest__.py`
|
||||
- Modify: `fusion_plating_reports/__manifest__.py`
|
||||
|
||||
- [ ] **Step 1: fusion_plating bump + add timelog views**
|
||||
|
||||
```python
|
||||
'version': '19.0.10.1.0' → '19.0.10.2.0',
|
||||
```
|
||||
|
||||
Add to `'data'` list (after `views/fp_job_step_move_views.xml`):
|
||||
```python
|
||||
'views/fp_job_step_timelog_views.xml',
|
||||
```
|
||||
|
||||
- [ ] **Step 2: fusion_plating_jobs bump**
|
||||
|
||||
Read current version, bump patch.
|
||||
|
||||
- [ ] **Step 3: fusion_plating_reports bump + add chronological CoC template**
|
||||
|
||||
Read current version, bump patch. Add to `'data'` list (after `report_coc.xml`):
|
||||
```python
|
||||
'report/report_coc_chronological.xml',
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add fusion_plating/__manifest__.py \
|
||||
fusion_plating_jobs/__manifest__.py \
|
||||
fusion_plating_reports/__manifest__.py
|
||||
git commit -m "feat(sub12c): bump versions + manifest scaffolding
|
||||
|
||||
fusion_plating → 19.0.10.2.0 (Labor History views)
|
||||
fusion_plating_jobs → next patch (Operator Traveller v2 body)
|
||||
fusion_plating_reports → next patch (Chronological CoC body template)
|
||||
|
||||
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Operator Traveller v2 — paper-style A4 landscape
|
||||
|
||||
**Files:**
|
||||
- Modify: `fusion_plating_jobs/report/report_fp_job_traveller.xml`
|
||||
|
||||
- [ ] **Step 1: Read the current template**
|
||||
|
||||
```bash
|
||||
cat fusion_plating_jobs/report/report_fp_job_traveller.xml
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Rewrite template + action**
|
||||
|
||||
Replace the entire template body with the paper-style version below. The action stays at `fusion_plating_jobs.report_fp_job_traveller_template` so existing button bindings keep working.
|
||||
|
||||
```xml
|
||||
<?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 12c v2 — paper-style A4 landscape job traveller.
|
||||
Mirrors the Amphenol Canada paper sheets (Steelhead screens 16-18):
|
||||
barcode + WO header, item-info block, recipe sub-process header, then
|
||||
the routing table with target ranges + actuals + sign-off cells per
|
||||
step. Operators print one of these per job, pencil in actuals, then
|
||||
the tablet captures the same data digitally — printed traveller is
|
||||
the redundant audit copy.
|
||||
-->
|
||||
<odoo>
|
||||
|
||||
<record id="paperformat_fp_traveller_landscape" model="report.paperformat">
|
||||
<field name="name">FP Traveller — A4 landscape narrow margins</field>
|
||||
<field name="format">A4</field>
|
||||
<field name="orientation">Landscape</field>
|
||||
<field name="margin_top">10</field>
|
||||
<field name="margin_bottom">10</field>
|
||||
<field name="margin_left">8</field>
|
||||
<field name="margin_right">8</field>
|
||||
<field name="header_spacing">5</field>
|
||||
<field name="dpi">90</field>
|
||||
</record>
|
||||
|
||||
<record id="action_report_fp_job_traveller" model="ir.actions.report">
|
||||
<field name="name">Job Traveller</field>
|
||||
<field name="model">fp.job</field>
|
||||
<field name="report_type">qweb-pdf</field>
|
||||
<field name="report_name">fusion_plating_jobs.report_fp_job_traveller_template</field>
|
||||
<field name="report_file">fusion_plating_jobs.report_fp_job_traveller_template</field>
|
||||
<field name="print_report_name">'Traveller - %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_traveller_landscape"/>
|
||||
</record>
|
||||
|
||||
<template id="report_fp_job_traveller_template">
|
||||
<t t-call="web.html_container">
|
||||
<t t-foreach="docs" t-as="job">
|
||||
<t t-call="web.external_layout">
|
||||
<div class="page fp-trav-page">
|
||||
<style>
|
||||
.fp-trav-page { font-family: Arial, sans-serif; font-size: 8pt; color: #000; }
|
||||
.fp-trav-page h1 { font-size: 14pt; margin: 0; }
|
||||
.fp-trav-page h2 { font-size: 10pt; margin: 6px 0 2px 0; }
|
||||
.fp-trav-page table.bordered,
|
||||
.fp-trav-page table.bordered th,
|
||||
.fp-trav-page table.bordered td { border: 1px solid #000; border-collapse: collapse; }
|
||||
.fp-trav-page table.bordered th { background: #ededed; padding: 4px 6px; text-align: left; font-weight: bold; }
|
||||
.fp-trav-page table.bordered td { padding: 4px 6px; vertical-align: top; }
|
||||
.fp-trav-page .fp-trav-actuals { font-size: 7.5pt; color: #555; line-height: 1.5; }
|
||||
.fp-trav-page .fp-trav-target { color: #444; font-size: 7.5pt; }
|
||||
.fp-trav-page .fp-trav-blank { display: inline-block; min-width: 32mm; border-bottom: 1px solid #888; height: 1.2em; }
|
||||
.fp-trav-page .fp-trav-stamp { min-height: 12mm; }
|
||||
</style>
|
||||
|
||||
<!-- HEADER -->
|
||||
<table class="bordered" style="width: 100%;">
|
||||
<tr>
|
||||
<td style="width: 5%; vertical-align: middle; text-align: center;">
|
||||
<img t-if="job.company_id.logo"
|
||||
t-att-src="'data:image/png;base64,%s' % job.company_id.logo.decode()"
|
||||
style="max-width: 28mm; max-height: 18mm;"/>
|
||||
</td>
|
||||
<td colspan="2" style="vertical-align: middle;">
|
||||
<h1>Work Order / Bon de Travail</h1>
|
||||
<div style="text-align: center; margin-top: 4px;">
|
||||
<strong t-esc="job.name"/>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<img t-att-src="'/report/barcode/Code128/%s' % job.name"
|
||||
style="height: 14mm;"/>
|
||||
</div>
|
||||
</td>
|
||||
<td style="width: 18%;">
|
||||
<strong>Date In:</strong>
|
||||
<span t-esc="job.create_date and job.create_date.strftime('%d-%m-%Y') or '—'"/><br/>
|
||||
<strong>Due Date:</strong>
|
||||
<span t-esc="job.date_deadline and job.date_deadline.strftime('%d-%m-%Y') or '—'"/><br/>
|
||||
<strong>Type:</strong>
|
||||
<span t-esc="job.recipe_id.name or '—'"/>
|
||||
</td>
|
||||
<td style="width: 18%;">
|
||||
<strong>Order #:</strong>
|
||||
<span t-esc="job.sale_order_id.name or '—'"/><br/>
|
||||
<strong>P.O. #:</strong>
|
||||
<span t-esc="job.sale_order_id.client_order_ref or '—'"/><br/>
|
||||
<strong>WO Generated By:</strong>
|
||||
<span t-esc="job.create_uid.name or '—'"/>
|
||||
</td>
|
||||
<td style="width: 22%; vertical-align: top;">
|
||||
<strong t-esc="job.partner_id.name or '—'"/><br/>
|
||||
<span t-esc="job.partner_id.street or ''"/><br/>
|
||||
<span t-esc="(job.partner_id.city or '') + ', ' + (job.partner_id.state_id.code or '') + ' ' + (job.partner_id.zip or '')"/><br/>
|
||||
<strong>Tel:</strong> <span t-esc="job.partner_id.phone or '—'"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- ITEM INFORMATION -->
|
||||
<table class="bordered" style="width: 100%; margin-top: 4px;">
|
||||
<tr>
|
||||
<th style="width: 22%;">Item Information</th>
|
||||
<th style="width: 30%;">Item-Name / Process Description</th>
|
||||
<th style="width: 8%;">Qty Rec.</th>
|
||||
<th style="width: 6%;">Vis Insp</th>
|
||||
<th style="width: 6%;">Rework</th>
|
||||
<th style="width: 22%;">Special Requirements</th>
|
||||
<th style="width: 6%;">Stamp / Date</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<strong>Part #:</strong> <span t-esc="job.part_catalog_id.part_number or '—'"/><br/>
|
||||
<strong>Rev:</strong> <span t-esc="job.part_catalog_id.revision or '—'"/><br/>
|
||||
<strong>Mat:</strong>
|
||||
<t t-if="'base_material' in job.part_catalog_id._fields">
|
||||
<span t-esc="job.part_catalog_id.base_material or '—'"/>
|
||||
</t>
|
||||
<t t-else=""><span>—</span></t><br/>
|
||||
<strong>Catg:</strong> <span t-esc="job.recipe_id.name or '—'"/><br/>
|
||||
<strong>S/N:</strong> <span t-esc="job.serial_number or ''"/>
|
||||
</td>
|
||||
<td>
|
||||
<strong t-esc="job.part_catalog_id.name or job.product_id.name or '—'"/>
|
||||
<div style="font-size: 7.5pt; margin-top: 2px;">
|
||||
<t t-if="'customer_facing_description' in job.part_catalog_id._fields">
|
||||
<span t-esc="job.part_catalog_id.customer_facing_description or ''"
|
||||
style="white-space: pre-wrap;"/>
|
||||
</t>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span t-esc="job.qty_received or job.qty"/>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span t-esc="job.qty_visual_inspection_rejects or 0"/>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span t-esc="job.qty_rework or 0"/>
|
||||
</td>
|
||||
<td style="font-size: 7pt; white-space: pre-wrap;">
|
||||
<span t-esc="job.special_requirements or '—'"/>
|
||||
</td>
|
||||
<td class="fp-trav-stamp"/>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- PROCESS-SHEET HEADER -->
|
||||
<table class="bordered" style="width: 100%; margin-top: 4px;">
|
||||
<tr>
|
||||
<th style="width: 30%;">Process Sheet / Feuille de Procédé</th>
|
||||
<th style="width: 20%;">Catg.</th>
|
||||
<th style="width: 50%;">Spec / Info</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span t-esc="job.recipe_id.name or '—'"/></td>
|
||||
<td><span t-esc="(job.recipe_id.process_type_id and job.recipe_id.process_type_id.name) or '—'"/></td>
|
||||
<td>
|
||||
<span t-esc="(job.coating_config_id and job.coating_config_id.name) or ''"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- ROUTING TABLE -->
|
||||
<table class="bordered" style="width: 100%; margin-top: 4px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 3%;">Step</th>
|
||||
<th style="width: 6%;">Tank</th>
|
||||
<th style="width: 22%;">Operation + Actuals</th>
|
||||
<th style="width: 22%;">Instruction</th>
|
||||
<th style="width: 5%;">Unit</th>
|
||||
<th style="width: 8%;">Material</th>
|
||||
<th style="width: 6%;">Voltage</th>
|
||||
<th style="width: 7%;">Time (min)</th>
|
||||
<th style="width: 7%;">Temp</th>
|
||||
<th style="width: 6%;">Stamp</th>
|
||||
<th style="width: 8%;">Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<t t-foreach="job.step_ids.sorted('sequence')" t-as="step">
|
||||
<t t-set="rn" t-value="step.recipe_node_id"/>
|
||||
<tr>
|
||||
<td class="text-center"><span t-esc="step_index + 1"/></td>
|
||||
<td class="text-center"><span t-esc="(step.tank_id and step.tank_id.code) or '—'"/></td>
|
||||
<td>
|
||||
<strong t-esc="step.name"/>
|
||||
<div class="fp-trav-actuals">
|
||||
<t t-foreach="rn.input_ids.filtered(lambda i: i.kind == 'step_input').sorted('sequence')" t-as="inp">
|
||||
<span t-esc="inp.name"/>:
|
||||
<span class="fp-trav-blank"/>
|
||||
<t t-if="inp.target_unit"> <span t-esc="inp.target_unit"/></t><br/>
|
||||
</t>
|
||||
</div>
|
||||
</td>
|
||||
<td style="font-size: 7.5pt; white-space: pre-wrap;">
|
||||
<span t-esc="step.description or (rn and rn.description) or ''" t-options="{'widget': 'html'}"/>
|
||||
</td>
|
||||
<td class="text-center fp-trav-target">
|
||||
<t t-if="rn and 'time_unit' in rn._fields and rn.time_unit">
|
||||
<span t-esc="rn.time_unit"/>
|
||||
</t>
|
||||
<t t-else="">—</t>
|
||||
</td>
|
||||
<td class="text-center fp-trav-target">
|
||||
<t t-if="rn and 'material_callout' in rn._fields and rn.material_callout">
|
||||
<span t-esc="rn.material_callout"/>
|
||||
</t>
|
||||
<t t-elif="rn and rn.process_type_id">
|
||||
<span t-esc="rn.process_type_id.name"/>
|
||||
</t>
|
||||
<t t-else="">N/A</t>
|
||||
</td>
|
||||
<td class="text-center fp-trav-target">
|
||||
<t t-if="rn and 'voltage_target' in rn._fields and rn.voltage_target">
|
||||
<span t-esc="rn.voltage_target"/>V
|
||||
</t>
|
||||
<t t-else="">N/A</t>
|
||||
</td>
|
||||
<td class="text-center fp-trav-target">
|
||||
<t t-if="rn and 'time_min_target' in rn._fields and rn.time_max_target">
|
||||
<span t-esc="rn.time_min_target"/> - <span t-esc="rn.time_max_target"/>
|
||||
</t>
|
||||
<t t-else="">N/A</t>
|
||||
</td>
|
||||
<td class="text-center fp-trav-target">
|
||||
<t t-if="rn and 'temp_min_target' in rn._fields and rn.temp_max_target">
|
||||
<span t-esc="rn.temp_min_target"/>-<span t-esc="rn.temp_max_target"/>
|
||||
<span t-esc="rn.temp_unit"/>
|
||||
</t>
|
||||
<t t-else="">N/A</t>
|
||||
</td>
|
||||
<td class="fp-trav-stamp"/>
|
||||
<td class="fp-trav-stamp"/>
|
||||
</tr>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add fusion_plating_jobs/report/report_fp_job_traveller.xml
|
||||
git commit -m "feat(sub12c): operator traveller v2 — paper-style A4 landscape (Task 2)
|
||||
|
||||
Replaces the minimal portrait template with the Amphenol-style paper
|
||||
sheet (screens 16-18). Header: barcode (Code 128 via /report/barcode),
|
||||
WO# / Date In / Due Date / Type / Order# / PO# / WO-Generated-By /
|
||||
customer block with address. Item Information panel: Part# / Rev / Mat /
|
||||
Catg / S/N + multi-line Item-Name + Qty Rec / VIS INSP / Rework / Special
|
||||
Requirements / Stamp-Date.
|
||||
|
||||
Process-Sheet header: recipe name + category + spec/info.
|
||||
|
||||
Routing table: Step / Tank / Operation+Actuals (recipe inputs render
|
||||
as 'Actual <name>: ____ unit' lines) / Instruction / Unit / Material /
|
||||
Voltage / Time(min) / Temp / Stamp / Date. Targets pulled from recipe-
|
||||
node fields when present (Sub 12a authored), N/A otherwise.
|
||||
|
||||
New paperformat: A4 landscape narrow margins, 90 dpi.
|
||||
|
||||
Action ID + report_name unchanged so existing form-button bindings keep
|
||||
working.
|
||||
|
||||
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Customer CoC — chronological body template
|
||||
|
||||
**Files:**
|
||||
- Create: `fusion_plating_reports/report/report_coc_chronological.xml`
|
||||
- Modify: `fusion_plating_certificates/models/fp_certificate.py`
|
||||
- Modify: `fusion_plating_certificates/views/fp_certificate_views.xml`
|
||||
|
||||
- [ ] **Step 1: Add `body_style` field on `fp.certificate`**
|
||||
|
||||
In `fp_certificate.py`, find a clean place to add new fields (after the existing `certified_by_id`):
|
||||
|
||||
```python
|
||||
# ===== Sub 12c — chronological CoC opt-in =================================
|
||||
body_style = fields.Selection(
|
||||
[
|
||||
('classic', 'Classic (recipe-order)'),
|
||||
('chronological', 'Chronological (chain-of-custody)'),
|
||||
],
|
||||
string='CoC Body Style', default='classic',
|
||||
help='Chronological walks fp.job.step.move records in time order '
|
||||
'with measurement sub-tables per move, matching Steelhead\'s '
|
||||
'CoC PDF layout. Classic uses the existing recipe-order body.',
|
||||
)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Surface `body_style` on the cert form**
|
||||
|
||||
In `fp_certificate_views.xml`, find the existing form view's group block and add:
|
||||
|
||||
```xml
|
||||
<field name="body_style"/>
|
||||
```
|
||||
|
||||
near the other certification settings.
|
||||
|
||||
- [ ] **Step 3: Create the chronological body template**
|
||||
|
||||
`fusion_plating_reports/report/report_coc_chronological.xml`:
|
||||
|
||||
```xml
|
||||
<?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 12c — Chronological CoC body.
|
||||
Walks fp.job.step.move records in time order (chain-of-custody),
|
||||
rendering each transition as a heading ("Step Name (Tank Code)")
|
||||
with "Moved By / Time" + a 5-column measurement sub-table when the
|
||||
destination step has captured input values. Mirrors Steelhead's
|
||||
CoC PDF layout (screens 19-24).
|
||||
|
||||
Wired into the existing CoC actions via a `body_style='chronological'`
|
||||
flag on fp.certificate — when set, action_report_coc_en/_fr render
|
||||
this body instead of the classic recipe-order body.
|
||||
-->
|
||||
<odoo>
|
||||
|
||||
<template id="coc_body_chronological">
|
||||
<t t-set="job" t-value="doc.x_fc_job_id if 'x_fc_job_id' in doc._fields else False"/>
|
||||
<t t-set="moves" t-value="job.move_ids.sorted('move_datetime') if job and 'move_ids' in job._fields else []"/>
|
||||
|
||||
<style>
|
||||
.fp-coc-chrono { font-family: Arial, sans-serif; font-size: 9pt; color: #000; padding-top: 8mm; }
|
||||
.fp-coc-chrono h1 { text-align: center; font-size: 18pt; margin: 0 0 6px 0; }
|
||||
.fp-coc-chrono h3 { font-size: 11pt; margin: 8px 0 2px 0; font-weight: bold; }
|
||||
.fp-coc-chrono .fp-chrono-meta { font-size: 8.5pt; color: #444; margin-bottom: 4px; }
|
||||
.fp-coc-chrono table.bordered,
|
||||
.fp-coc-chrono table.bordered th,
|
||||
.fp-coc-chrono table.bordered td { border: 1px solid #000; border-collapse: collapse; }
|
||||
.fp-coc-chrono table.bordered { width: 100%; margin-bottom: 8px; }
|
||||
.fp-coc-chrono table.bordered th { background: #ededed; padding: 4px 6px; font-size: 8.5pt; }
|
||||
.fp-coc-chrono table.bordered td { padding: 4px 6px; vertical-align: top; font-size: 8.5pt; }
|
||||
.fp-coc-chrono .fp-out-of-range { color: #b30000; font-weight: bold; }
|
||||
.fp-coc-chrono .fp-in-range { color: #006400; }
|
||||
.fp-coc-chrono .fp-pass { color: #006400; font-weight: bold; }
|
||||
.fp-coc-chrono .fp-fail { color: #b30000; font-weight: bold; }
|
||||
</style>
|
||||
|
||||
<div class="fp-coc-chrono">
|
||||
|
||||
<h1>Certificate of Conformance</h1>
|
||||
|
||||
<!-- Job header (compact) -->
|
||||
<table class="bordered">
|
||||
<tr>
|
||||
<th style="width: 18%;">Part Number</th>
|
||||
<th style="width: 30%;">Description</th>
|
||||
<th style="width: 8%;">Quantity</th>
|
||||
<th style="width: 8%;">Work Order</th>
|
||||
<th style="width: 14%;">PO Number</th>
|
||||
<th style="width: 12%;">Packing List No</th>
|
||||
<th style="width: 10%;">Date</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span t-esc="(job and job.part_catalog_id and job.part_catalog_id.part_number) or (job and job.product_id.default_code) or '—'"/></td>
|
||||
<td><span t-esc="(job and job.part_catalog_id and job.part_catalog_id.name) or (job and job.product_id.name) or '—'"/></td>
|
||||
<td class="text-center"><span t-esc="(job and job.qty) or ''"/></td>
|
||||
<td class="text-center"><span t-esc="(job and job.name) or '—'"/></td>
|
||||
<td><span t-esc="(job and job.sale_order_id and job.sale_order_id.client_order_ref) or '—'"/></td>
|
||||
<td/>
|
||||
<td><span t-esc="(doc.create_date and doc.create_date.strftime('%Y-%m-%d')) or ''"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3 style="margin-top: 6px;">Specification(s):
|
||||
<span style="font-weight: normal;"
|
||||
t-esc="(job and job.recipe_id and job.recipe_id.name) or '—'"/>
|
||||
</h3>
|
||||
|
||||
<hr style="border: 0; border-top: 2px solid #000; margin: 8px 0;"/>
|
||||
|
||||
<!-- 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.code or (dest and dest.tank_id and dest.tank_id.code) or ''"/>
|
||||
<t t-set="captured" t-value="dest.input_ids.filtered(lambda i: i.kind == 'step_input').sorted('sequence') if dest else []"/>
|
||||
|
||||
<h3>
|
||||
<span t-esc="dest and dest.name or '—'"/>
|
||||
<t t-if="tank_code"> (<span t-esc="tank_code"/>)</t>
|
||||
</h3>
|
||||
<div class="fp-chrono-meta">
|
||||
<strong>Moved By:</strong> <span t-esc="mv.moved_by_user_id.name"/>
|
||||
·
|
||||
<strong>Time:</strong>
|
||||
<span t-esc="mv.move_datetime and mv.move_datetime.strftime('%b %d, %Y %I:%M:%S %p') or ''"/>
|
||||
<t t-if="mv.qty_moved">
|
||||
· <strong>Qty:</strong> <span t-esc="mv.qty_moved"/>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<!-- Measurement sub-table — only render when captured input values exist on the destination step -->
|
||||
<t t-if="captured">
|
||||
<table class="bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 24%;">Name</th>
|
||||
<th style="width: 30%;">Description</th>
|
||||
<th style="width: 14%;">Target</th>
|
||||
<th style="width: 18%;">Actual</th>
|
||||
<th style="width: 14%;">Recorded By</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<t t-foreach="captured" t-as="inp">
|
||||
<!-- Pull captured value via fp.job.step.input.value
|
||||
if Sub 12a wired one. For now, the runtime
|
||||
captures into transition_input_value_ids on
|
||||
the move (Sub 12b) — step inputs that
|
||||
are recorded *during* the step still go in
|
||||
a step-level table. We render the prompt
|
||||
name + target here as the audit row;
|
||||
`Actual` is blank if no capture. -->
|
||||
<tr>
|
||||
<td><span t-esc="inp.name"/></td>
|
||||
<td><span t-esc="inp.hint or ''"/></td>
|
||||
<td>
|
||||
<t t-if="inp.target_min and inp.target_max">
|
||||
<span t-esc="inp.target_min"/>–<span t-esc="inp.target_max"/>
|
||||
<t t-if="inp.target_unit"> <span t-esc="inp.target_unit"/></t>
|
||||
</t>
|
||||
<t t-elif="inp.target_unit">
|
||||
<span t-esc="inp.target_unit"/>
|
||||
</t>
|
||||
</td>
|
||||
<td/>
|
||||
<td><span t-esc="(mv.moved_by_user_id.name) or ''"/></td>
|
||||
</tr>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<hr style="border: 0; border-top: 2px solid #000; margin: 12px 0;"/>
|
||||
|
||||
<!-- Sign-off block (re-uses owner_user_id signature pattern) -->
|
||||
<t t-set="owner_sig" t-value="False"/>
|
||||
<t t-if="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="signature_img" t-value="company.x_fc_coc_signature_override or owner_sig"/>
|
||||
<t t-set="signer_name" t-value="(doc.certified_by_id and doc.certified_by_id.name) or (company.x_fc_owner_user_id and company.x_fc_owner_user_id.name) or ''"/>
|
||||
|
||||
<table class="bordered" style="width: 100%;">
|
||||
<tr>
|
||||
<td style="width: 50%; vertical-align: top;">
|
||||
<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 and job.name or ''"/>
|
||||
</span>
|
||||
<p style="font-size: 8pt; margin-top: 4px;">
|
||||
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.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- ============================================================== -->
|
||||
<!-- Wrapper that picks chronological vs classic body -->
|
||||
<!-- ============================================================== -->
|
||||
<template id="coc_body_router">
|
||||
<t t-if="doc.body_style == 'chronological' and 'x_fc_job_id' in doc._fields and doc.x_fc_job_id">
|
||||
<t t-call="fusion_plating_reports.coc_body_chronological"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-call="fusion_plating_reports.coc_body"/>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Wire the router into the existing CoC actions**
|
||||
|
||||
In `fusion_plating_reports/report/report_coc.xml`, find the templates that render `coc_body` (search for `t-call="fusion_plating_reports.coc_body"`) and replace with `t-call="fusion_plating_reports.coc_body_router"`. There should be ≤4 occurrences (en + fr × portrait + landscape).
|
||||
|
||||
If the router replacement breaks anything, revert to direct calls and gate per-template instead.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add fusion_plating_reports/report/report_coc_chronological.xml \
|
||||
fusion_plating_reports/report/report_coc.xml \
|
||||
fusion_plating_certificates/models/fp_certificate.py \
|
||||
fusion_plating_certificates/views/fp_certificate_views.xml
|
||||
git commit -m "feat(sub12c): chronological CoC body + body_style opt-in (Task 3)
|
||||
|
||||
New template: fusion_plating_reports.coc_body_chronological.
|
||||
Walks fp.job.step.move records in time order (chain-of-custody view).
|
||||
Per-move heading 'Step Name (Tank Code)' with 'Moved By / Time / Qty'
|
||||
meta line + a 5-column measurement sub-table (Name / Description /
|
||||
Target / Actual / Recorded By) when the destination step has captured
|
||||
inputs. Heading-only when there are no inputs (gating moves).
|
||||
|
||||
New router template: coc_body_router. Picks chronological vs classic
|
||||
based on fp.certificate.body_style. Existing certs default to 'classic'
|
||||
so no regressions.
|
||||
|
||||
fp.certificate.body_style ('classic' | 'chronological') exposed on the
|
||||
form. Customer chooses per cert.
|
||||
|
||||
Sign-off block reuses the existing owner_user_id signature pattern +
|
||||
x_fc_coc_signature_override fallback. Cert statement boilerplate is
|
||||
inline (Sub 12d will move it to a configurable per-customer field).
|
||||
|
||||
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Labor History views
|
||||
|
||||
**Files:**
|
||||
- Create: `fusion_plating/views/fp_job_step_timelog_views.xml`
|
||||
|
||||
- [ ] **Step 1: Create the views file**
|
||||
|
||||
```xml
|
||||
<?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 12c — Labor History views.
|
||||
fp.job.step.timelog now has a state machine + reconciliation
|
||||
columns (Sub 12b). This file surfaces the history under
|
||||
Plating → Operations → Labor History for billing audit + payroll
|
||||
reconciliation.
|
||||
-->
|
||||
<odoo>
|
||||
|
||||
<record id="view_fp_job_step_timelog_list" model="ir.ui.view">
|
||||
<field name="name">fp.job.step.timelog.list</field>
|
||||
<field name="model">fp.job.step.timelog</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Labor History" default_order="date_started desc"
|
||||
decoration-info="state == 'running'"
|
||||
decoration-warning="state == 'paused'"
|
||||
decoration-muted="state == 'reconciled'">
|
||||
<field name="user_id"/>
|
||||
<field name="job_id"/>
|
||||
<field name="step_id"/>
|
||||
<field name="state" widget="badge"
|
||||
decoration-info="state == 'running'"
|
||||
decoration-warning="state == 'paused'"
|
||||
decoration-success="state == 'stopped'"
|
||||
decoration-muted="state == 'reconciled'"/>
|
||||
<field name="date_started"/>
|
||||
<field name="date_finished" optional="show"/>
|
||||
<field name="accrued_seconds" optional="show"/>
|
||||
<field name="billed_hrs" optional="show"/>
|
||||
<field name="billed_min" optional="show"/>
|
||||
<field name="billed_sec" optional="show"/>
|
||||
<field name="billed_pct" widget="progressbar" optional="show"/>
|
||||
<field name="product_id" optional="hide"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_fp_job_step_timelog_form" model="ir.ui.view">
|
||||
<field name="name">fp.job.step.timelog.form</field>
|
||||
<field name="model">fp.job.step.timelog</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Labor Timer" create="false">
|
||||
<header>
|
||||
<field name="state" widget="statusbar"
|
||||
statusbar_visible="running,paused,stopped,reconciled"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<h1><field name="display_name" readonly="1"/></h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="user_id" readonly="1"/>
|
||||
<field name="job_id" readonly="1"/>
|
||||
<field name="step_id" readonly="1"/>
|
||||
<field name="date_started" readonly="1"/>
|
||||
<field name="date_finished" readonly="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="accrued_seconds" readonly="1"/>
|
||||
<label for="billed_hrs" string="Billed Time"/>
|
||||
<div>
|
||||
<field name="billed_hrs" class="oe_inline"
|
||||
readonly="state in ('reconciled',)"
|
||||
groups="fusion_plating.group_fusion_plating_supervisor"/>
|
||||
hrs
|
||||
<field name="billed_min" class="oe_inline"
|
||||
readonly="state in ('reconciled',)"
|
||||
groups="fusion_plating.group_fusion_plating_supervisor"/>
|
||||
min
|
||||
<field name="billed_sec" class="oe_inline"
|
||||
readonly="state in ('reconciled',)"
|
||||
groups="fusion_plating.group_fusion_plating_supervisor"/>
|
||||
sec
|
||||
</div>
|
||||
<field name="billed_pct" widget="progressbar" readonly="1"/>
|
||||
<field name="product_id"/>
|
||||
</group>
|
||||
</group>
|
||||
<group string="Notes">
|
||||
<field name="notes" nolabel="1"/>
|
||||
</group>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_fp_job_step_timelog_search" model="ir.ui.view">
|
||||
<field name="name">fp.job.step.timelog.search</field>
|
||||
<field name="model">fp.job.step.timelog</field>
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<field name="user_id"/>
|
||||
<field name="job_id"/>
|
||||
<field name="step_id"/>
|
||||
<field name="product_id"/>
|
||||
<separator/>
|
||||
<filter string="My Timers" name="my_timers"
|
||||
domain="[('user_id','=',uid)]"/>
|
||||
<filter string="Today" name="today"
|
||||
domain="[('date_started','>=',(context_today() ).strftime('%Y-%m-%d 00:00:00'))]"/>
|
||||
<filter string="This Week" name="this_week"
|
||||
domain="[('date_started','>=',(context_today() - relativedelta(days=context_today().weekday())).strftime('%Y-%m-%d 00:00:00'))]"/>
|
||||
<separator/>
|
||||
<filter string="Running" name="running"
|
||||
domain="[('state','=','running')]"/>
|
||||
<filter string="Paused" name="paused"
|
||||
domain="[('state','=','paused')]"/>
|
||||
<filter string="Pending Reconciliation" name="pending"
|
||||
domain="[('state','=','stopped')]"/>
|
||||
<filter string="Reconciled" name="reconciled"
|
||||
domain="[('state','=','reconciled')]"/>
|
||||
<group>
|
||||
<filter string="Operator" name="group_user"
|
||||
context="{'group_by':'user_id'}"/>
|
||||
<filter string="Job" name="group_job"
|
||||
context="{'group_by':'job_id'}"/>
|
||||
<filter string="Date" name="group_date"
|
||||
context="{'group_by':'date_started:day'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_fp_labor_history" model="ir.actions.act_window">
|
||||
<field name="name">Labor History</field>
|
||||
<field name="res_model">fp.job.step.timelog</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="search_view_id" ref="view_fp_job_step_timelog_search"/>
|
||||
<field name="context">{'search_default_my_timers': 1}</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_fp_labor_history"
|
||||
name="Labor History"
|
||||
parent="menu_fp_root"
|
||||
action="action_fp_labor_history"
|
||||
sequence="64"/>
|
||||
|
||||
</odoo>
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add ACL rows for the timelog model**
|
||||
|
||||
The model is already accessible via fp.job.step relations, but explicit rows make the menu work for non-admin users. Append to `fusion_plating/security/ir.model.access.csv`:
|
||||
|
||||
```csv
|
||||
access_fp_job_step_timelog_operator,fp.job.step.timelog.operator,model_fp_job_step_timelog,group_fusion_plating_operator,1,1,0,0
|
||||
access_fp_job_step_timelog_supervisor,fp.job.step.timelog.supervisor,model_fp_job_step_timelog,group_fusion_plating_supervisor,1,1,1,0
|
||||
access_fp_job_step_timelog_manager,fp.job.step.timelog.manager,model_fp_job_step_timelog,group_fusion_plating_manager,1,1,1,1
|
||||
```
|
||||
|
||||
(Skip if already present — grep first: `grep model_fp_job_step_timelog fusion_plating/security/ir.model.access.csv`.)
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add fusion_plating/views/fp_job_step_timelog_views.xml \
|
||||
fusion_plating/security/ir.model.access.csv
|
||||
git commit -m "feat(sub12c): Labor History views (Task 4)
|
||||
|
||||
Plating → Operations → Labor History (sequence 64, between Move Log
|
||||
62 and Aerospace 65). List view colour-coded by state (info/warning/
|
||||
success/muted), with billed_pct progressbar.
|
||||
|
||||
Search filters: My Timers (default), Today, This Week, Running,
|
||||
Paused, Pending Reconciliation, Reconciled. Group-by: Operator, Job,
|
||||
Date.
|
||||
|
||||
Form view (read-only header with statusbar): identity fields readonly,
|
||||
billed_hrs/min/sec editable for supervisors+ until state=reconciled,
|
||||
chatter for operator notes.
|
||||
|
||||
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Deploy to entech + smoke test + push
|
||||
|
||||
**Files:**
|
||||
- (none — deployment + manual verification)
|
||||
|
||||
- [ ] **Step 1: Tar + ship**
|
||||
|
||||
```bash
|
||||
tar -cf - \
|
||||
fusion_plating/__manifest__.py \
|
||||
fusion_plating/security/ir.model.access.csv \
|
||||
fusion_plating/views/fp_job_step_timelog_views.xml \
|
||||
fusion_plating_jobs/__manifest__.py \
|
||||
fusion_plating_jobs/report/report_fp_job_traveller.xml \
|
||||
fusion_plating_reports/__manifest__.py \
|
||||
fusion_plating_reports/report/report_coc.xml \
|
||||
fusion_plating_reports/report/report_coc_chronological.xml \
|
||||
fusion_plating_certificates/models/fp_certificate.py \
|
||||
fusion_plating_certificates/views/fp_certificate_views.xml \
|
||||
| ssh pve-worker5 "pct exec 111 -- bash -c 'cd /mnt/extra-addons/custom && tar -xf -'"
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Update modules**
|
||||
|
||||
```bash
|
||||
ssh pve-worker5 "pct exec 111 -- bash -c 'systemctl stop odoo && \
|
||||
su - odoo -s /bin/bash -c \"/usr/bin/odoo -c /etc/odoo/odoo.conf -d admin \
|
||||
-u fusion_plating,fusion_plating_jobs,fusion_plating_reports,fusion_plating_certificates --stop-after-init\" 2>&1 | tail -25 && \
|
||||
systemctl start odoo'"
|
||||
```
|
||||
|
||||
Expected: clean upgrade, 233 modules loaded.
|
||||
|
||||
- [ ] **Step 3: Clear asset cache**
|
||||
|
||||
```bash
|
||||
ssh pve-worker5 "pct exec 111 -- bash -c \"su - postgres -c 'psql admin -c \\\"DELETE FROM ir_attachment WHERE url LIKE '\\''/web/assets/%'\\'';\\\"'\""
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Manual smoke test**
|
||||
|
||||
1. Open any in-flight `fp.job` → Print → "Job Traveller". PDF should render in A4 landscape with: header (logo + barcode + dates + customer), Item Information block, Process-Sheet header, Routing table with target columns + blank actuals.
|
||||
2. Open any `fp.certificate` → form shows new "CoC Body Style" Selection. Default = Classic. Existing CoC PDF unchanged.
|
||||
3. Flip body_style to Chronological → Print CoC → new PDF walks moves in time order with measurement tables. (Job needs `fp.job.step.move` rows for this to be meaningful — produce a few via the Sub 12b tablet flow first if needed.)
|
||||
4. Plating → Operations → Labor History menu appears. List shows timelog rows with My Timers default filter. Try filters (Running / Paused / Pending Reconciliation / Reconciled) and Group-by (Operator / Job / Date).
|
||||
5. Open a `reconciled` timelog → form is read-only, supervisor can re-edit billed_* if needed.
|
||||
|
||||
- [ ] **Step 5: Push to remote**
|
||||
|
||||
```bash
|
||||
git push origin main
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Self-Review
|
||||
|
||||
### Spec coverage check
|
||||
|
||||
| Spec section 6 item | Task |
|
||||
|---|---|
|
||||
| 6.2 Operator Traveller v2 (A4 landscape, paper-style) | Task 2 |
|
||||
| 6.3 Customer CoC chronological body | Task 3 |
|
||||
| 6.3 body_style opt-in field | Task 3 |
|
||||
| 6.4 Labor History list/form/search/group-by/menu | Task 4 |
|
||||
| 6.4 Manager re-edit of billed_* on reconciled | Task 4 (form view + supervisor group on billed_* fields) |
|
||||
| 6.5 Backend support (chronological payload helper) | Inline in Task 3 — QWeb walks `job.move_ids.sorted('move_datetime')` directly; no separate Python helper needed |
|
||||
| 6.6 Migration / install | Task 1 (version bumps) — no model migrations, all additive |
|
||||
| 6.7 Verification | Task 5 |
|
||||
| 6.8 Things to NOT do | Honoured — `report_coc.xml` legacy bodies untouched, `action_issue` flow not changed, no new model fields beyond body_style, two reports stay separate |
|
||||
|
||||
Out-of-scope items handled by deferring:
|
||||
- **Rack travel ticket PDF** (Sub 12b's Save+Print 404) — flagged in plan companion docs as a follow-up
|
||||
- **Per-customer cert statement** — boilerplate inline in chronological body for now; deferrable
|
||||
|
||||
### Placeholder scan
|
||||
|
||||
No "TBD" / "TODO" / "implement later" / "fill in details".
|
||||
|
||||
The chronological body's measurement sub-table renders prompts + targets but leaves the **Actual** column blank. That's because Sub 12a + Sub 12b's runtime captures `step_input` values via the operator's per-step input form, which lands in the existing `step.input_value_ids` collection (or equivalent) — wiring that into the Actual cell needs more knowledge of the existing input-value model than the plan time budget allows. Documented in Task 3's commit message as a Sub 12d follow-up.
|
||||
|
||||
### Type / signature consistency
|
||||
|
||||
- `fp.certificate.body_style` defined Task 3, used by `coc_body_router` Task 3. ✓
|
||||
- `coc_body_chronological` template defined Task 3, called by `coc_body_router` Task 3. ✓
|
||||
- `coc_body_router` template defined Task 3, called from existing `report_coc.xml` templates after the replacement edit (Task 3 step 4). ✓
|
||||
- `fp.job.move_ids` (added by Sub 12b Task 6) referenced by Task 3's chronological body. ✓
|
||||
- `fp.job.step.timelog.state` + `accrued_seconds` + `billed_*` + `product_id` (added by Sub 12b Task 7) referenced by Task 4's views. ✓
|
||||
- `paperformat_fp_traveller_landscape` defined Task 2, referenced by `action_report_fp_job_traveller` Task 2 same record. ✓
|
||||
|
||||
---
|
||||
|
||||
**Plan complete. 5 tasks, ~1 day end-to-end (significantly tighter than original 18-task plan because most CoC infrastructure already exists in `fusion_plating_reports`).**
|
||||
Reference in New Issue
Block a user