feat(promote-customer-spec): Phase D — reports + tablet payload include spec

Reports updated to print Specification (with revision via display_name):
- report_fp_sale.xml — header sections show "SPECIFICATION" instead
  of "COATING CONFIG", reads doc.x_fc_customer_spec_id (added on
  sale.order via quality inherit, computed from line.customer_spec_id)
- report_fp_wo_sticker.xml — propagates _spec alongside _coating
- fusion_plating_reports/report_fp_job_traveller.xml — header row
  now shows Specification (falls back to coating)
- fusion_plating_jobs/report_fp_job_traveller.xml — same fall-back
- fusion_plating_jobs/report_fp_job_sticker.xml — _spec added

sale.order.x_fc_customer_spec_id added as a stored compute on
sale.order (in quality) so reports can render order-level spec.
Mirrors the line's first spec; updates on line edit.

Tablet payload (shopfloor_controller.py):
- spec_label added to the job payload dict
- defensive 'customer_spec_id' in job._fields check (shopfloor doesn't
  depend on quality — circular if added)

Portal: deferred (same circular-dep issue, more substantial UI rewrite
needed; Phase E backlog item).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-15 01:30:05 -04:00
parent c637f82ae2
commit e0eacc2530
11 changed files with 63 additions and 16 deletions

View File

@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating — Shop Floor',
'version': '19.0.25.2.1',
'version': '19.0.26.0.0',
'category': 'Manufacturing/Plating',
'summary': 'Shop-floor tablet stations, QR scanning, bake window enforcer, '
'first-piece inspection gates.',

View File

@@ -1137,11 +1137,16 @@ class FpShopfloorController(http.Controller):
# Now we walk each unique job once and stash the answers.
unique_jobs = steps.mapped('job_id')
# Prefetch the fields we'll touch (saves N+1 SQL fetches)
unique_jobs.read([
# customer_spec_id present when fusion_plating_quality is installed
# (added there as an inherit on fp.job).
job_read_fields = [
'name', 'origin', 'priority', 'partner_id', 'product_id',
'qty', 'qty_done', 'date_planned_start', 'date_deadline',
'part_catalog_id', 'coating_config_id',
])
]
if 'customer_spec_id' in unique_jobs._fields:
job_read_fields.append('customer_spec_id')
unique_jobs.read(job_read_fields)
step_idx_by_id = {} # step_id → 1-based ordinal in its job
job_step_count_by_id = {} # job_id → total step count
queued_start_by_step_id = {} # step_id → predecessor's date_finished
@@ -1554,6 +1559,12 @@ class FpShopfloorController(http.Controller):
job.coating_config_id
if 'coating_config_id' in job._fields else False
)
# Specification (added by fusion_plating_quality)
spec = (
job.customer_spec_id
if 'customer_spec_id' in job._fields else False
)
spec_label = (spec.display_name if spec else '') or ''
part_number = ''
part_revision = ''
if part:
@@ -1622,6 +1633,7 @@ class FpShopfloorController(http.Controller):
'part_number': part_number,
'part_revision': part_revision,
'coating_label': coating_label,
'spec_label': spec_label,
# ISO deadline for sort tiebreaker (v19.0.24.8.0)
'date_deadline_iso': (
_flds.Datetime.to_string(job.date_deadline)