147 lines
5.7 KiB
Python
147 lines
5.7 KiB
Python
# Internal-process walk — test time tracking, pause, skip, bake-window
|
|
# auto-spawn, duration overrun. Persona: Carlos (operator) walking the
|
|
# tablet station for a real plating job.
|
|
#
|
|
# Goals:
|
|
# 1) Time tracking captures every start/stop interval correctly
|
|
# 2) Multiple intervals (start/finish/start/finish) sum to duration_actual
|
|
# 3) Pause / resume flow works (currently NOT implemented — gap to fix)
|
|
# 4) Skip flow works for opt-in steps (currently NOT implemented)
|
|
# 5) Wet plating step finishing auto-spawns a bake.window when the
|
|
# coating requires hydrogen embrittlement relief
|
|
# 6) Bake-window state machine reflects elapsed time
|
|
|
|
import time
|
|
from datetime import timedelta
|
|
from odoo import fields
|
|
|
|
# Set up a fresh job to walk.
|
|
W = env['fp.direct.order.wizard']
|
|
Line = env['fp.direct.order.line']
|
|
P = env['res.partner']
|
|
Part = env['fp.part.catalog']
|
|
target = P.browse(2529)
|
|
part = Part.search([('x_fc_default_coating_config_id', '!=', False)], limit=1)
|
|
|
|
w = W.create({
|
|
'partner_id': target.id, 'po_pending': True,
|
|
'po_number': 'PO-INTERNAL-' + fields.Datetime.now().strftime('%H%M%S'),
|
|
'invoice_strategy': 'net_terms',
|
|
})
|
|
w._onchange_partner_id()
|
|
ln = Line.new({'wizard_id': w.id})
|
|
ln.part_catalog_id = part
|
|
ln._onchange_part_clears_variant()
|
|
Line.create({
|
|
'wizard_id': w.id, 'part_catalog_id': part.id,
|
|
'coating_config_id': part.x_fc_default_coating_config_id.id,
|
|
'quantity': 5, 'unit_price': 18.0,
|
|
})
|
|
result = w.action_create_order()
|
|
so = env['sale.order'].browse(result['res_id'])
|
|
so.action_confirm()
|
|
job = env['fp.job'].search([('sale_order_id', '=', so.id)], limit=1)
|
|
print(f'[setup] Fresh job {job.name} with {len(job.step_ids)} steps')
|
|
|
|
# ====================================================================== STEP 1
|
|
print()
|
|
print('='*72)
|
|
print('STEP 1 — Carlos opens the first step on the tablet, clicks Start')
|
|
print('='*72)
|
|
first = job.step_ids.sorted('sequence')[0]
|
|
print(f' Step: {first.name} (kind={first.kind}, state={first.state})')
|
|
print(f' duration_expected: {first.duration_expected} min')
|
|
|
|
before = fields.Datetime.now()
|
|
first.button_start()
|
|
print(f' After Start: state={first.state}, date_started={first.date_started}, started_by={first.started_by_user_id.name}')
|
|
print(f' Open time-log rows: {len(first.time_log_ids.filtered(lambda l: not l.date_finished))}')
|
|
|
|
# ====================================================================== STEP 2
|
|
print()
|
|
print('='*72)
|
|
print('STEP 2 — Carlos works for 6 seconds, then clicks Finish')
|
|
print('='*72)
|
|
time.sleep(6)
|
|
first.button_finish()
|
|
print(f' After Finish: state={first.state}, date_finished={first.date_finished}')
|
|
print(f' Time-log rows: {len(first.time_log_ids)}')
|
|
for log in first.time_log_ids:
|
|
print(f' - {log.user_id.name} {log.date_started} → {log.date_finished or "OPEN"} = {log.duration_minutes:.3f} min')
|
|
print(f' duration_actual: {first.duration_actual:.3f} min')
|
|
print(f' ✓ Single interval captured cleanly')
|
|
|
|
# ====================================================================== STEP 3
|
|
print()
|
|
print('='*72)
|
|
print('STEP 3 — Test pause/resume on the next step (currently NotImplementedError)')
|
|
print('='*72)
|
|
second = job.step_ids.sorted('sequence')[1]
|
|
second.button_start()
|
|
print(f' Started step: {second.name} (state={second.state})')
|
|
print(f' Carlos now needs a smoke break — clicks Pause')
|
|
try:
|
|
second.button_pause()
|
|
print(f' ✓ Paused: state={second.state}, open timelog={len(second.time_log_ids.filtered(lambda l: not l.date_finished))}')
|
|
except NotImplementedError as e:
|
|
print(f' ❌ button_pause not implemented: {e}')
|
|
except Exception as e:
|
|
print(f' ❌ {type(e).__name__}: {e}')
|
|
|
|
# ====================================================================== STEP 4
|
|
print()
|
|
print('='*72)
|
|
print('STEP 4 — Test skip (currently NotImplementedError)')
|
|
print('='*72)
|
|
third = job.step_ids.sorted('sequence')[2]
|
|
print(f' Step: {third.name}, state={third.state}')
|
|
print(f' Planner wants to skip this opt-in step')
|
|
try:
|
|
third.button_skip()
|
|
print(f' ✓ Skipped: state={third.state}')
|
|
except NotImplementedError as e:
|
|
print(f' ❌ button_skip not implemented: {e}')
|
|
except Exception as e:
|
|
print(f' ❌ {type(e).__name__}: {e}')
|
|
|
|
# ====================================================================== STEP 5
|
|
print()
|
|
print('='*72)
|
|
print('STEP 5 — Wet plating step finishes, does a bake.window auto-spawn?')
|
|
print('='*72)
|
|
# Find a step with kind='wet' (or use step #4 as plating analog)
|
|
wet_step = job.step_ids.filtered(lambda s: 'plating' in (s.name or '').lower())[:1]
|
|
if not wet_step:
|
|
wet_step = job.step_ids.sorted('sequence')[3:4]
|
|
print(f' Using as plating step: {wet_step.name} (kind={wet_step.kind})')
|
|
|
|
coating = job.coating_config_id
|
|
print(f' Coating: {coating.name}')
|
|
print(f' coating.requires_bake_relief: {coating.requires_bake_relief}')
|
|
print(f' coating.bake_window_hours: {coating.bake_window_hours}')
|
|
|
|
# Count bake.window before
|
|
BW = env['fusion.plating.bake.window']
|
|
bw_before = BW.search_count([('part_ref', '=', job.name)])
|
|
print(f' Bake windows for this job BEFORE finish: {bw_before}')
|
|
|
|
# Skip if currently in_progress (it is — paused step #2 still open)
|
|
if wet_step.state in ('pending', 'ready'):
|
|
wet_step.button_start()
|
|
if wet_step.state == 'in_progress':
|
|
wet_step.button_finish()
|
|
print(f' After Finish: state={wet_step.state}')
|
|
|
|
bw_after = BW.search_count([('part_ref', '=', job.name)])
|
|
print(f' Bake windows for this job AFTER finish: {bw_after}')
|
|
if coating.requires_bake_relief and bw_after == bw_before:
|
|
print(f' ❌ Coating requires bake relief BUT no bake.window was auto-created!')
|
|
elif not coating.requires_bake_relief:
|
|
print(f' (coating doesn\'t require bake relief — auto-spawn would skip anyway)')
|
|
else:
|
|
print(f' ✓ Bake window spawned')
|
|
|
|
env.cr.commit()
|
|
print()
|
|
print('== Walk complete ==')
|