Files
gsinghpal f08f328688 changes
2026-04-27 00:11:18 -04:00

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 ==')