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

172 lines
6.2 KiB
Python

# Comprehensive internal-process walk.
#
# Phases:
# A) Pause / resume — multiple intervals merge into duration_actual
# B) Skip an opt-in step
# C) Skipped steps don't block job mark-done
# D) Wet plating step finish auto-spawns bake.window with right window_hours
# E) Bake-window state evolves (awaiting_bake → bake_in_progress → baked)
# F) Failure: try to start a step already done
import time
from datetime import timedelta
from odoo import fields
W = env['fp.direct.order.wizard']
Line = env['fp.direct.order.line']
P = env['res.partner']
Part = env['fp.part.catalog']
Coating = env['fp.coating.config']
target = P.browse(2529)
# Find or build a coating that requires bake relief.
coating = Coating.search([('requires_bake_relief', '=', True)], limit=1)
if not coating:
coating = Coating.search([], limit=1)
coating.requires_bake_relief = True
coating.bake_window_hours = 4.0
coating.bake_temperature = 375.0
coating.bake_temperature_uom = 'F'
coating.bake_duration_hours = 4.0
print(f'[setup] Configured {coating.name} to require bake relief (4h window @ 375°F for 4h)')
else:
print(f'[setup] Using existing bake-required coating: {coating.name} ({coating.bake_window_hours}h window)')
# Build a part using this coating as default.
part = Part.create({
'partner_id': target.id,
'part_number': 'INT-' + fields.Datetime.now().strftime('%H%M%S'),
'revision': 'A',
'name': 'Internal-process test bracket',
'substrate_material': 'steel',
'x_fc_default_coating_config_id': coating.id,
})
w = W.create({
'partner_id': target.id, 'po_pending': True,
'po_number': 'PO-INT-' + 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': coating.id,
'quantity': 5, 'unit_price': 22.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] Job {job.name} with {len(job.step_ids)} steps')
# ====================================================================== A
print()
print('='*72)
print('A — Pause + resume on a step. Multiple intervals must merge.')
print('='*72)
masking = job.step_ids.sorted('sequence')[0]
masking.button_start()
print(f' start → state={masking.state}, open logs={len(masking.time_log_ids)}')
time.sleep(2)
masking.button_pause()
print(f' pause → state={masking.state}, logs={len(masking.time_log_ids)}, '
f'log[0]={masking.time_log_ids[0].duration_minutes:.3f} min')
time.sleep(2)
masking.button_start() # resume
print(f' resume → state={masking.state}, logs={len(masking.time_log_ids)}')
time.sleep(2)
masking.button_finish()
print(f' finish → state={masking.state}, logs={len(masking.time_log_ids)}')
total = sum(masking.time_log_ids.mapped('duration_minutes'))
print(f' duration_actual={masking.duration_actual:.3f} min (sum of logs={total:.3f} min)')
if abs(masking.duration_actual - total) < 0.001:
print(f' ✓ Pause/resume merged correctly')
else:
print(f' ❌ Mismatch')
# ====================================================================== B
print()
print('='*72)
print('B — Skip an opt-in step')
print('='*72)
racking = job.step_ids.sorted('sequence')[1]
print(f' Step: {racking.name} state={racking.state}')
racking.button_skip()
print(f' After Skip: state={racking.state}')
if racking.state == 'skipped':
print(f' ✓ Skip works')
# ====================================================================== C — walk rest, then mark-done
print()
print('='*72)
print('C — Walk remaining steps (some will spawn bake-window). Mark job done.')
print('='*72)
spawn_count_before = env['fusion.plating.bake.window'].search_count([])
for s in job.step_ids.sorted('sequence'):
if s.state in ('done', 'skipped', 'cancelled'):
continue
if s.state in ('pending', 'ready'):
s.button_start()
if s.state == 'in_progress':
s.button_finish()
spawn_count_after = env['fusion.plating.bake.window'].search_count([])
created_bw = spawn_count_after - spawn_count_before
print(f' Walked all remaining steps to done')
print(f' Bake windows spawned during walk: {created_bw}')
bws = env['fusion.plating.bake.window'].search([('part_ref', '=', job.name)])
for bw in bws:
print(f' {bw.name}: state={bw.state}, plate_exit={bw.plate_exit_time}, required_by={bw.bake_required_by}, time_remaining={bw.time_remaining_display}')
# ====================================================================== D — try to mark job done
print()
print('='*72)
print('D — Mark job done (skipped+done steps both count as terminal)')
print('='*72)
try:
job.button_mark_done()
print(f' ✓ Job done — state={job.state}')
except Exception as e:
print(f'{e}')
# ====================================================================== E — bake-window lifecycle
if bws:
bw = bws[0]
print()
print('='*72)
print('E — Bake-window lifecycle: start → end')
print('='*72)
print(f' Before start: state={bw.state}, color={bw.status_color}')
bw.action_start_bake()
print(f' After start_bake: state={bw.state}, bake_start={bw.bake_start_time}, color={bw.status_color}')
time.sleep(1)
bw.action_end_bake()
print(f' After end_bake: state={bw.state}, bake_end={bw.bake_end_time}, duration_h={bw.bake_duration_hours:.4f}')
# ====================================================================== F — failure: start a done step
print()
print('='*72)
print('F — Failure paths')
print('='*72)
done_step = job.step_ids.filtered(lambda s: s.state == 'done')[:1]
if done_step:
try:
done_step.button_start()
print(f' ❌ Allowed re-start of a done step')
except Exception as e:
print(f' ✓ Blocked: {str(e)[:80]}')
# Try to skip an already-done step
try:
done_step.button_skip()
print(f' ❌ Allowed skip of done step')
except Exception as e:
print(f' ✓ Blocked: {str(e)[:80]}')
env.cr.commit()
print()
print('== Internal-process walk complete ==')