feat(plating): close 2 workflow gaps surfaced by workforce E2E simulation
Built a comprehensive simulator (scripts/fp_e2e_workforce.py) that
role-plays 10 employees driving an order quote → invoice using real
operator timers (button_start / button_finish with elapsed time.sleep).
Initial run: 31 PASS / 2 WARN / 0 FAIL exposed two gaps that would
hurt a real shop:
**Gap 1 — Thickness readings never reached the CoC**
The Fischerscope readings inspectors take during post-plate inspection
had no path to the CoC. The cert came out empty, useless for AS9100
or aerospace audits.
Fixes:
- New tablet endpoint `/fp/shopfloor/log_thickness_reading` so the
inspector can record one reading at a time during the inspection WO
(auto-numbers, defaults the operator, supports microscope image).
- mrp_production._fp_mark_done_post_actions now bulk-links any
orphan thickness readings (those with production_id=mo.id but no
certificate_id) to the freshly-created CoC. So inspectors can log
during inspection AND the cert PDF picks them up automatically.
**Gap 2 — Operator queue leaked other people's work + simulator missed it**
fusion.plating.operator.queue.build_for_user pulled EVERY ready /
in-progress WO regardless of assignment. Tom would see John's masking
WO in his "Up Next" list — bad for aerospace traceability where you
want strict per-operator accountability.
Fix: build_for_user now filters MRP WOs by
`(x_fc_assigned_user_id == user_id OR x_fc_assigned_user_id == False)`.
Operators see their own assigned tasks first, plus any unassigned
tasks anyone can grab. Other operators' assigned WOs no longer leak
through.
Also caught: simulator was using wrong field name on the queue model.
Fixed and added a "queue isolation" check that verifies no operator
sees another operator's assigned WOs.
After fixes: **39 PASS / 2 WARN / 0 FAIL** (out of 41 checks).
Remaining WARNs are both expected behaviour:
- bake-window auto-create: this coating doesn't require_bake_relief
(the recipe has an inline Oven step instead)
- first-piece gate: same — coating-driven, only fires when needed
Areas validated end-to-end:
- quote → SO with PO# carried into client_order_ref
- SO confirm → MO + portal job auto-created
- receiving qty prefill + accept
- 9 WOs generated from recipe + assigned to specific operators
- All 9 WOs ran with real elapsed timers + 17 productivity records
across 4 distinct operators
- MO done triggers CoC auto-issue with 5 thickness readings linked,
319 KB rich PDF, customer-slug filename
- Delivery auto-created with prefilled date + driver + CoC link
- Delivery delivered, 2 chain-of-custody entries
- Invoice posted (NOT auto-paid)
- All 5 customer notifications fired (so_confirmed +
parts_received + mo_complete + shipped + invoice_posted) with
correct attachments
- Portal job → complete, SO workflow_stage → invoicing
- Chemistry log persisted, operator proficiency tracked
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -256,6 +256,75 @@ class FpShopfloorController(http.Controller):
|
||||
'duration': wo.duration,
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Thickness reading — Fischerscope log entry from inspection station
|
||||
# ----------------------------------------------------------------------
|
||||
@http.route('/fp/shopfloor/log_thickness_reading', type='jsonrpc', auth='user')
|
||||
def log_thickness_reading(self, production_id, nip_mils=None,
|
||||
ni_percent=None, p_percent=None,
|
||||
position_label=None, reading_number=None,
|
||||
equipment_model=None, calibration_std_ref=None,
|
||||
microscope_image=None,
|
||||
microscope_image_filename=None):
|
||||
"""Record a single Fischerscope reading against an MO.
|
||||
|
||||
Auto-links to the CoC certificate later when the MO is marked
|
||||
done (see mrp_production._fp_mark_done_post_actions). Keeps the
|
||||
endpoint simple so the inspector can fire-and-forget per reading.
|
||||
"""
|
||||
Reading = request.env.get('fp.thickness.reading')
|
||||
if Reading is None:
|
||||
return {'ok': False, 'error': 'Certificates module not installed'}
|
||||
mo = request.env['mrp.production'].browse(int(production_id))
|
||||
if not mo.exists():
|
||||
return {'ok': False, 'error': f'MO {production_id} not found'}
|
||||
|
||||
# Auto-number if caller didn't pass one.
|
||||
if not reading_number:
|
||||
existing = Reading.search_count([('production_id', '=', mo.id)])
|
||||
reading_number = existing + 1
|
||||
|
||||
vals = {
|
||||
'production_id': mo.id,
|
||||
'reading_number': int(reading_number),
|
||||
'nip_mils': float(nip_mils or 0.0),
|
||||
'ni_percent': float(ni_percent or 0.0),
|
||||
'p_percent': float(p_percent or 0.0),
|
||||
'position_label': position_label or '',
|
||||
'operator_id': request.env.user.id,
|
||||
}
|
||||
if equipment_model:
|
||||
vals['equipment_model'] = equipment_model
|
||||
if calibration_std_ref:
|
||||
vals['calibration_std_ref'] = calibration_std_ref
|
||||
# If the inspector snapped a microscope image, attach it.
|
||||
if microscope_image:
|
||||
import base64 as _b64
|
||||
att = request.env['ir.attachment'].create({
|
||||
'name': microscope_image_filename or f'thickness_{reading_number}.jpg',
|
||||
'datas': microscope_image,
|
||||
'res_model': 'fp.thickness.reading',
|
||||
'mimetype': 'image/jpeg',
|
||||
})
|
||||
vals['microscope_image_id'] = att.id
|
||||
|
||||
# Auto-link to existing CoC if one already exists for this MO.
|
||||
Cert = request.env.get('fp.certificate')
|
||||
if Cert is not None:
|
||||
existing_cert = Cert.search([
|
||||
('production_id', '=', mo.id),
|
||||
('certificate_type', '=', 'coc'),
|
||||
], limit=1)
|
||||
if existing_cert:
|
||||
vals['certificate_id'] = existing_cert.id
|
||||
|
||||
reading = Reading.create(vals)
|
||||
return {
|
||||
'ok': True,
|
||||
'reading_id': reading.id,
|
||||
'reading_number': reading.reading_number,
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Quality hold — partial qty split
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user