# Step 3 — Sarah hits "Create Order" in wizard, then confirms SO. # Watch every side-effect: SO state, fp.job auto-spawn, fp.receiving # auto-spawn, fp.racking.inspection, portal.job mirror, QC check. 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'] target = P.browse(2529) # 2CM INNOVATIVE part = Part.search([('x_fc_default_coating_config_id', '!=', False)], limit=1) # Build the wizard exactly the way Sarah would after Step 1+2 fixes. w = W.create({ 'partner_id': target.id, 'po_number': 'PO-STEP3-001', 'po_pending': False, 'customer_job_number': 'CUSTJOB-STEP3', 'planned_start_date': fields.Date.today(), 'customer_deadline': fields.Date.add(fields.Date.today(), days=14), 'invoice_strategy': 'net_terms', 'delivery_method': 'shipping_partner', 'po_attachment_file': b'fake-pdf-bytes', 'po_attachment_filename': 'po.pdf', }) w._onchange_partner_id() print(f'[Sarah] Created wizard {w.name} for {target.display_name}') ln = Line.new({'wizard_id': w.id}) ln.part_catalog_id = part ln._onchange_part_clears_variant() ln_vals = ln._convert_to_write({n: ln[n] for n in ln._fields}) ln_vals.update({ 'wizard_id': w.id, 'quantity': 25, 'unit_price': 12.50, 'line_description': 'EN plating per part default coating', 'internal_description': 'Standard recipe; bake within 4h.', }) real_line = Line.create(ln_vals) print(f'[Sarah] Added line: part={real_line.part_catalog_id.part_number}, ' f'coating={real_line.coating_config_id.name}, qty={real_line.quantity}') # Hit Create Order. print('[Sarah] Clicking "Create Order"...') result = w.action_create_order() so_id = (result or {}).get('res_id') SO = env['sale.order'] so = SO.browse(so_id) if so_id else SO.search( [('x_fc_po_number', '=', 'PO-STEP3-001')], order='id desc', limit=1, ) print(f' -> SO created: {so.name} (state={so.state})') # Now confirm the SO (Sarah does this from the SO form, not the wizard). print('[Sarah] Confirming SO...') try: so.action_confirm() print(f' -> SO state={so.state}, x_fc_receiving_status={so.x_fc_receiving_status}') except Exception as e: print(f' ❌ confirm failed: {e}') env.cr.rollback() raise SystemExit # Verify side-effects. print() print('=== Side effects of SO confirm ===') Job = env['fp.job'] jobs = Job.search([('sale_order_id', '=', so.id)]) print(f' fp.job auto-spawn: {len(jobs)} job(s)') for j in jobs: print(f' {j.name}: state={j.state}, qty={j.qty}, recipe={j.recipe_id.name or "(no recipe)"}, steps={len(j.step_ids)}') Receiving = env['fp.receiving'] receivings = Receiving.search([('sale_order_id', '=', so.id)]) print(f' fp.receiving auto-spawn: {len(receivings)} record(s)') for r in receivings: print(f' {r.name}: state={r.state}, expected_qty={r.expected_qty}') if 'fp.racking.inspection' in env: Inspection = env['fp.racking.inspection'] racks = Inspection.search([('sale_order_id', '=', so.id)]) print(f' fp.racking.inspection auto-spawn: {len(racks)} record(s)') for ri in racks: print(f' {ri.name}: state={ri.state if "state" in ri._fields else "?"}, x_fc_job_id={ri.x_fc_job_id.name if ri.x_fc_job_id else None}') PortalJob = env['fusion.plating.portal.job'] portal_jobs = PortalJob.search([('x_fc_job_id', 'in', jobs.ids)]) print(f' portal.job mirror: {len(portal_jobs)} record(s)') for pj in portal_jobs: print(f' {pj.name}: state={pj.state}') QC = env['fusion.plating.quality.check'] qcs = QC.search([('job_id', 'in', jobs.ids)]) print(f' QC checks: {len(qcs)} (customer x_fc_requires_qc={getattr(target, "x_fc_requires_qc", "NOFIELD")})') for qc in qcs: print(f' {qc.name}: state={qc.state}, lines={len(qc.line_ids)}') # x_fc_receiving_status check print() print(f' SO x_fc_receiving_status (post-confirm, no receipt yet): {so.x_fc_receiving_status}') print(f' Expected: not_received (parts haven\'t arrived)') env.cr.commit() print() print(f'== Step 3 complete. SO ID for next steps: {so.id} ==')