fix(audit-trail): 3 production bugs found via end-to-end Anodize battle test

Battle-tested complete workflow on entech: ABC Manufacturing + Anodize
recipe (id=136) cloned to part-variant (id=1775) → SO S00276 confirmed →
fp.job 1234 with 17 steps → recorded 56 measurement values exercising all
13 input types (incl. all 4 new types) → CoC chronological report renders
69KB with all values incl. photo thumbnails.

Bugs found and fixed:

1. fp.process.node.input_ids missing copy=True — when a master recipe
   was cloned per-part (the standard variant pattern), the operator
   prompts on each step did NOT get copied to the variant. Result: jobs
   built from variants ran with zero prompts even though the master had
   them. Fixed: input_ids now copy=True so cloning auto-duplicates.

2. CoC chronological template read dest.input_ids where dest is
   fp.job.step. Steps don't carry input_ids — that field lives on the
   recipe node. Result: AttributeError aborted the entire CoC render.
   Fixed: walk via dest.recipe_node_id.input_ids; preserves the existing
   collect=True filter.

3. CoC chronological template used hasattr() in a t-value expression.
   QWeb's expression engine doesn't expose Python builtins, raised
   KeyError: 'hasattr'. Fixed: use 'collect' in i._fields instead.

Also enhanced photo rendering in CoC: was just "[Attachment]" placeholder;
now renders an actual <img> thumbnail (max 80px tall) plus the filename.

Battle-test script saved to fusion_plating/scripts/bt_e2e_anodize_v2.py
for re-runs / regression testing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-29 22:53:59 -04:00
parent b187192c58
commit ec0a07fbe9
13 changed files with 1246 additions and 3 deletions

View File

@@ -95,7 +95,10 @@
<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 ''"/>
<t t-set="captured" t-value="(dest and dest.input_ids.filtered(lambda i: (i.kind or 'step_input') == 'step_input' and (not hasattr(i, 'collect') or i.collect)).sorted('sequence')) or []"/>
<!-- Sub 12d — input_ids lives on recipe_node, not job.step.
Walk via recipe_node_id; filter to step_input + collect=True. -->
<t t-set="recipe_node" t-value="(dest and dest.recipe_node_id) or False"/>
<t t-set="captured" t-value="(recipe_node and recipe_node.input_ids.filtered(lambda i: (i.kind or 'step_input') == 'step_input' and (i.collect if 'collect' in i._fields else True)).sorted('sequence')) or []"/>
<h3>
<span t-esc="(dest and dest.name) or '—'"/>
@@ -184,7 +187,13 @@
<strong t-esc="actual_str"/>
</t>
<t t-elif="cv and cv.value_attachment_id">
<span style="font-size: 7.5pt; color: #555;">[Attachment]</span>
<!-- Photo: render as a thumbnail (height-capped); fall back to filename. -->
<img t-att-src="'/web/image/%s' % cv.value_attachment_id.id"
style="max-height: 80px; max-width: 160px; border: 1px solid #ccc;"
t-att-alt="cv.value_attachment_id.name"/>
<div style="font-size: 7.5pt; color: #555;">
<span t-esc="cv.value_attachment_id.name"/>
</div>
</t>
</td>
<td>