diff --git a/fusion_plating/fusion_plating_jobs/scripts/seed_workflow_states.py b/fusion_plating/fusion_plating_jobs/scripts/seed_workflow_states.py new file mode 100644 index 00000000..cafbd0c3 --- /dev/null +++ b/fusion_plating/fusion_plating_jobs/scripts/seed_workflow_states.py @@ -0,0 +1,785 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Nexa Systems Inc. +# License OPL-1 (Odoo Proprietary License v1.0) +# +# seed_workflow_states.py +# ======================= +# Builds 7-8 sale orders in EACH lifecycle state from quotation through +# paid invoice. The goal is a realistic dataset that exercises the full +# pipeline so we can validate UI, reports, KPIs, and notifications. +# +# Workflow walkthrough findings (run end-to-end on entech 2026-04-25): +# +# STAGE AUTO-CREATES REQUIRED FIELDS +# ---------------------------------------------------------------------- +# sale.order draft nothing partner_id, order_line +# commitment_date is optional but +# we always set it for realism +# sale.order sent nothing write state="sent" +# sale.order action_confirm fp.job (state=DRAFT, client_order_ref recommended; +# step_count=0, recipe payment_term_id REQUIRED for +# resolved from coating) downstream invoice posting +# fp.job action_confirm portal_job_id state moves draft -> confirmed +# fp.job._generate_steps_ step_ids populated must be called explicitly; +# from_recipe() action_confirm does NOT do it +# fp.job button_mark_done delivery (draft) + all steps must be done first; +# cert (draft, type=coc) sets state=done, date_finished +# fp.delivery scheduled -- scheduled_date, contact_name, +# contact_phone, delivery_address_id, +# x_fc_box_count_out, +# assigned_driver_id (hr.employee) +# fp.delivery delivered -- delivered_at +# fp.certificate issued -- issue_date, issued_by_id, +# entech_wo_number, customer_job_no, +# po_number, quantity_shipped, +# part_number, +# thickness_reading_ids (3-5 rows) +# account.move (draft) via so._create_invoices() invoice_date, invoice_date_due, +# invoice_payment_term_id REQUIRED +# or post fails +# account.move action_post name=INV/YYYY/NNNNN invoice_date_due +# account.payment.register account.payment partner_type, journal_id, +# wizard (state=in_process) payment_method_line_id, +# amount, payment_date +# account.payment state=paid on payment; ALL upstream prerequisites +# action_validate invoice payment_state= above +# in_payment (not paid - (Odoo 19 design - paid state +# that requires bank requires bank reconciliation) +# statement reconciliation) +# +# Strategy: +# - Each order is wrapped in its OWN savepoint. If anything fails, we +# ROLLBACK that savepoint and continue to the next order. +# - We commit at the end of each stage so partial successes still land. +# - Customer/part variety: spread across 10+ partners, cycle through all +# parts that have coatings. Operators round-robin across the 20. +# - Date variety: past 60 days for delivered/paid; future 1-30 days for +# active jobs. +# +# Usage (entech): see scripts/README.md. + +from datetime import datetime, timedelta +import random +import logging + +random.seed(2026) +_logger = logging.getLogger(__name__) + +# Stage targets - these are the seed counts per stage requested by spec. +# Adjust here if you want fewer/more. +TARGETS = { + "so_draft": 8, + "so_sent": 7, + "job_confirmed_no_steps_started": 8, + "job_in_progress_early": 7, + "job_in_progress_mid": 8, + "job_on_hold": 5, + "job_done_delivery_draft": 7, + "delivery_scheduled": 7, + "delivery_en_route": 5, + "delivery_delivered": 8, + "invoice_draft": 7, + "invoice_posted": 7, + "paid": 7, +} + + +def _pick_combo(combos, idx): + return combos[idx % len(combos)] + + +def _build_combos(env): + """List of (partner, part, coating) for SO-confirm seeding.""" + combos = [] + parts = env["fp.part.catalog"].search([ + ("x_fc_default_coating_config_id", "!=", False), + ("x_fc_default_coating_config_id.recipe_id", "!=", False), + ("partner_id", "!=", False), + ]) + for p in parts: + combos.append((p.partner_id, p, p.x_fc_default_coating_config_id)) + random.shuffle(combos) + return combos + + +def _operators(env): + g = env.ref("fusion_plating.group_fusion_plating_operator", + raise_if_not_found=False) + if not g: + return env["res.users"] + return env["res.users"].search([("all_group_ids", "in", g.id)]) + + +def _managers(env): + g = env.ref("fusion_plating.group_fusion_plating_manager", + raise_if_not_found=False) + if not g: + return env["res.users"] + return env["res.users"].search([("all_group_ids", "in", g.id)]) + + +def _employees(env): + return env["hr.employee"].search([]) + + +def _resolve_product(env): + """Find the right plating-service product to use for SO lines.""" + p = env["product.product"].search( + [("name", "=", "Plating Service")], limit=1) + if p: + return p + p = env["product.product"].search( + [("sale_ok", "=", True), ("type", "=", "service")], limit=1) + if p: + return p + return env["product.product"].search([("sale_ok", "=", True)], limit=1) + + +def _resolve_payment_term(env): + """Net 30 by default.""" + pt = env["account.payment.term"].search( + [("name", "=", "30 Days")], limit=1) + if not pt: + pt = env["account.payment.term"].search([], limit=1) + return pt + + +def _resolve_journals(env): + sales = env["account.journal"].search([("type", "=", "sale")], limit=1) + bank = env["account.journal"].search([("type", "=", "bank")], limit=1) + return sales, bank + + +def _resolve_facility(env): + return env["fusion.plating.facility"].search([], limit=1) + + +# ---------------------------------------------------------------------- +def _make_so(env, partner, part, coating, qty, price, ctx): + """Create a draft SO with one detailed plating line.""" + SOL_fields = env["sale.order.line"]._fields + line_vals = { + "product_id": ctx["product"].id, + "product_uom_qty": qty, + "price_unit": price, + "name": "ENP plating service: %s rev %s" % ( + part.part_number or part.name, part.revision or "A"), + } + if "x_fc_part_catalog_id" in SOL_fields: + line_vals["x_fc_part_catalog_id"] = part.id + if "x_fc_coating_config_id" in SOL_fields: + line_vals["x_fc_coating_config_id"] = coating.id + if "x_fc_internal_description" in SOL_fields: + line_vals["x_fc_internal_description"] = ( + "Internal: %s, %.1f mils target, mil-spec" % ( + coating.name, coating.thickness_max or 1.0)) + + so_vals = { + "partner_id": partner.id, + "partner_invoice_id": partner.id, + "partner_shipping_id": partner.id, + "client_order_ref": "CUST-PO-%05d" % random.randint(10000, 99999), + "commitment_date": datetime.now() + timedelta( + days=random.randint(7, 30)), + "validity_date": (datetime.now() + timedelta(days=30)).date(), + "payment_term_id": ctx["payment_term"].id, + "order_line": [(0, 0, line_vals)], + } + SO_fields = env["sale.order"]._fields + if "x_fc_po_number" in SO_fields: + so_vals["x_fc_po_number"] = "PO-%05d" % random.randint(10000, 99999) + if "x_fc_part_catalog_id" in SO_fields: + so_vals["x_fc_part_catalog_id"] = part.id + if "x_fc_coating_config_id" in SO_fields: + so_vals["x_fc_coating_config_id"] = coating.id + if "x_fc_internal_note" in SO_fields: + so_vals["x_fc_internal_note"] = ( + "

Customer is OK with rush production if capacity allows.

") + if "x_fc_external_note" in SO_fields: + so_vals["x_fc_external_note"] = ( + "

Please confirm receipt of parts before processing.

") + return env["sale.order"].sudo().create(so_vals) + + +def _populate_job(env, job, ctx, fill_facility=True, fill_manager=True): + """Fill out fp.job extra fields after creation.""" + if not job: + return + vals = {} + if fill_facility and ctx["facility"] and not job.facility_id: + vals["facility_id"] = ctx["facility"].id + if fill_manager and ctx["managers"] and not job.manager_id: + vals["manager_id"] = random.choice(ctx["managers"]).id + if not job.priority or job.priority == "normal": + vals["priority"] = random.choices( + ["low", "normal", "high", "rush"], + weights=[10, 70, 15, 5], + )[0] + if vals: + job.write(vals) + + +def _ensure_steps(env, job): + """Force step generation. action_confirm doesn t do this on its own.""" + if not job: + return + if not job.recipe_id: + return + if job.step_ids: + return + try: + job._generate_steps_from_recipe() + except Exception as e: + _logger.warning("Job %s step gen failed: %s", job.name, e) + + +def _assign_step_users(env, job, ctx, n_done=0, current_idx=None): + """Assign operators to all steps; mark first n_done as done, and + optionally one step at current_idx as in_progress. + """ + operators = ctx["operators"] + steps = job.step_ids.sorted("sequence") + if not steps: + return + for s in steps: + if operators and not s.assigned_user_id: + s.assigned_user_id = operators[ + random.randrange(len(operators))] + + base = datetime.now() - timedelta(hours=len(steps) * 2) + for i, s in enumerate(steps[:n_done]): + start = base + timedelta(hours=i * 2) + finish = start + timedelta(minutes=random.randint(20, 90)) + s.write({ + "state": "done", + "date_started": start, + "date_finished": finish, + "duration_actual": (finish - start).total_seconds() / 60.0, + "started_by_user_id": s.assigned_user_id.id if s.assigned_user_id else env.user.id, + "finished_by_user_id": s.assigned_user_id.id if s.assigned_user_id else env.user.id, + }) + env["fp.job.step.timelog"].sudo().create({ + "step_id": s.id, + "user_id": s.assigned_user_id.id if s.assigned_user_id else env.user.id, + "date_started": start, + "date_finished": finish, + "duration_minutes": (finish - start).total_seconds() / 60.0, + }) + + if current_idx is not None and current_idx < len(steps): + cur = steps[current_idx] + if cur.state != "done": + start = datetime.now() - timedelta( + minutes=random.randint(5, 90)) + cur.write({ + "state": "in_progress", + "date_started": start, + "started_by_user_id": cur.assigned_user_id.id if cur.assigned_user_id else env.user.id, + }) + env["fp.job.step.timelog"].sudo().create({ + "step_id": cur.id, + "user_id": cur.assigned_user_id.id if cur.assigned_user_id else env.user.id, + "date_started": start, + }) + + if current_idx is not None and current_idx + 1 < len(steps): + next_step = steps[current_idx + 1] + if next_step.state == "pending": + next_step.write({"state": "ready"}) + + +def _fill_step_realistic_data(env, job): + for s in job.step_ids: + kind = s.kind + if kind == "bake": + if not s.bake_setpoint_temp: + s.bake_setpoint_temp = random.choice([375.0, 400.0, 425.0]) + if not s.bake_actual_duration and s.state == "done": + s.bake_actual_duration = random.uniform(3.5, 4.5) + elif kind == "wet": + if not s.thickness_target: + s.thickness_target = round(random.uniform(0.5, 2.0), 2) + s.thickness_uom = "mil" + + +def _make_delivery_full(env, delivery, partner, ctx, state, scheduled_offset_days=1): + """Fill delivery with realistic logistics fields and advance state.""" + if not delivery: + return + employees = ctx["employees"] + facility = ctx["facility"] + vals = { + "delivery_address_id": partner.id, + "contact_name": partner.name, + "contact_phone": partner.phone or "555-%04d" % random.randint(1000, 9999), + "scheduled_date": datetime.now() + timedelta(days=scheduled_offset_days), + } + if "x_fc_box_count_out" in delivery._fields: + vals["x_fc_box_count_out"] = random.randint(1, 5) + if employees and "assigned_driver_id" in delivery._fields: + vals["assigned_driver_id"] = employees[ + random.randrange(len(employees))].id + if facility and "source_facility_id" in delivery._fields: + vals["source_facility_id"] = facility.id + if "notes" in delivery._fields: + vals["notes"] = ( + "

Standard delivery - handle with care, parts plated to spec.

") + delivery.write(vals) + delivery.write({"state": state}) + if state == "delivered": + delivery.write({"delivered_at": datetime.now() - timedelta( + hours=random.randint(1, 48))}) + + +def _issue_certificate(env, job, so, part, ctx): + cert = env["fp.certificate"].search( + [("x_fc_job_id", "=", job.id)], limit=1) + if not cert: + cert = env["fp.certificate"].sudo().create({ + "partner_id": job.partner_id.id, + "certificate_type": "coc", + "state": "draft", + "x_fc_job_id": job.id, + "sale_order_id": so.id if so else False, + }) + vals = { + "state": "issued", + "issue_date": datetime.now().date(), + "issued_by_id": env.user.id, + "entech_wo_number": job.name, + "customer_job_no": so.client_order_ref if so else "", + "po_number": so.x_fc_po_number if so and "x_fc_po_number" in so._fields else "", + "quantity_shipped": int(job.qty or 1), + "part_number": part.part_number or part.name, + "process_description": "Electroless Nickel Plating, MIL-C-26074", + } + if "spec_min_mils" in cert._fields and part.x_fc_default_coating_config_id: + c = part.x_fc_default_coating_config_id + if c.thickness_min: + vals["spec_min_mils"] = c.thickness_min + if c.thickness_max: + vals["spec_max_mils"] = c.thickness_max + vals["spec_reference"] = c.spec_reference or "AMS-2404" + cert.write(vals) + for i in range(5): + env["fp.thickness.reading"].sudo().create({ + "certificate_id": cert.id, + "reading_number": i + 1, + "nip_mils": round(random.uniform(0.95, 1.15), 3), + "ni_percent": round(random.uniform(88.0, 92.0), 2), + "p_percent": round(random.uniform(8.0, 12.0), 2), + "position_label": "Pos %d" % (i + 1), + "reading_datetime": datetime.now() - timedelta( + minutes=30 - i * 5), + "operator_id": env.user.id, + "x_fc_job_id": job.id, + "equipment_model": "Fischerscope X-Ray XDV-SD", + "calibration_std_ref": "CAL-2026-04-01", + }) + return cert + + +def _create_quality_hold(env, job, ctx): + if "fusion.plating.quality.hold" not in env: + return + Hold = env["fusion.plating.quality.hold"].sudo() + steps = job.step_ids.sorted("sequence") + affected_step = None + for s in steps: + if s.state in ("paused", "in_progress"): + affected_step = s + break + vals = { + "hold_reason": random.choice( + ["out_of_spec", "damaged", "contamination", "process_deviation"]), + "qty_on_hold": max(1, int((job.qty or 1) // 4)), + "qty_original": int(job.qty or 1), + "description": "Sample inspection caught dimensional drift on first-piece. Holding for engineering review.", + "state": "on_hold", + } + if "x_fc_job_id" in Hold._fields: + vals["x_fc_job_id"] = job.id + if affected_step and "x_fc_step_id" in Hold._fields: + vals["x_fc_step_id"] = affected_step.id + if ctx["facility"] and "facility_id" in Hold._fields: + vals["facility_id"] = ctx["facility"].id + if "operator_id" in Hold._fields and ctx["operators"]: + vals["operator_id"] = random.choice(ctx["operators"]).id + if "part_ref" in Hold._fields: + vals["part_ref"] = job.part_catalog_id.part_number if job.part_catalog_id else "" + try: + Hold.create(vals) + except Exception as e: + _logger.warning("Hold create failed for %s: %s", job.name, e) + + +def _create_invoice(env, so, ctx, post=False): + inv_recordset = so._create_invoices() + if not inv_recordset: + return env["account.move"] + inv = inv_recordset[0] if hasattr(inv_recordset, "ids") else env["account.move"].browse(inv_recordset) + inv_vals = { + "invoice_date": (datetime.now() - timedelta( + days=random.randint(0, 5))).date(), + "invoice_date_due": (datetime.now() + timedelta( + days=random.randint(15, 30))).date(), + } + if not inv.invoice_payment_term_id: + inv_vals["invoice_payment_term_id"] = ctx["payment_term"].id + inv.write(inv_vals) + if post: + inv.action_post() + return inv + + +def _register_payment(env, inv, ctx, validate=True): + bank = ctx["bank_journal"] + pml = bank.inbound_payment_method_line_ids[:1] + wizard = env["account.payment.register"].with_context( + active_model="account.move", + active_ids=inv.ids, + ).create({ + "amount": inv.amount_total, + "journal_id": bank.id, + "payment_method_line_id": pml.id if pml else False, + "payment_date": (datetime.now() - timedelta( + days=random.randint(0, 7))).date(), + }) + wizard.action_create_payments() + pmt = env["account.payment"].search( + [("partner_id", "=", inv.partner_id.id), + ("amount", "=", inv.amount_total)], + order="id desc", limit=1) + if pmt and validate: + try: + pmt.action_validate() + except Exception as e: + _logger.warning("Payment validate failed: %s", e) + try: + pmt.write({"state": "paid"}) + except Exception as e2: + _logger.warning("Payment direct write paid failed: %s", e2) + return pmt + + +# ---------------------------------------------------------------------- +def _stage(env, label, fn, n, combos, idx_holder, ctx, results): + print("-- %s (target %d) --" % (label, n)) + success = 0 + sp_safe = "".join(c if c.isalnum() else "_" for c in label).strip("_") + sp_label = "seed_" + sp_safe + for i in range(n): + partner, part, coating = _pick_combo(combos, idx_holder[0]) + idx_holder[0] += 1 + sp = "%s_%d" % (sp_label, i) + env.cr.execute("SAVEPOINT %s" % sp) + try: + if fn(env, partner, part, coating, ctx): + env.cr.execute("RELEASE SAVEPOINT %s" % sp) + success += 1 + else: + env.cr.execute("ROLLBACK TO SAVEPOINT %s" % sp) + except Exception as e: + print(" WARN [%s #%d]: %s" % (label, i, e)) + try: + env.cr.execute("ROLLBACK TO SAVEPOINT %s" % sp) + except Exception: + pass + results[label] = success + print(" -> %d/%d succeeded" % (success, n)) + + +# -------------------- Stage handlers -------------------- +def stage_so_draft(env, partner, part, coating, ctx): + so = _make_so(env, partner, part, coating, + qty=random.choice([5, 10, 25, 50, 100]), + price=random.uniform(75.0, 350.0), ctx=ctx) + return bool(so) + + +def stage_so_sent(env, partner, part, coating, ctx): + so = _make_so(env, partner, part, coating, + qty=random.choice([10, 25, 50, 100]), + price=random.uniform(75.0, 350.0), ctx=ctx) + so.write({"state": "sent"}) + return True + + +def stage_job_confirmed(env, partner, part, coating, ctx): + so = _make_so(env, partner, part, coating, + qty=random.choice([10, 25, 50]), + price=random.uniform(100.0, 350.0), ctx=ctx) + so.action_confirm() + job = env["fp.job"].search([("sale_order_id", "=", so.id)], limit=1) + if not job: + return False + if job.state == "draft": + job.action_confirm() + _ensure_steps(env, job) + _populate_job(env, job, ctx) + _assign_step_users(env, job, ctx, n_done=0, current_idx=None) + _fill_step_realistic_data(env, job) + return True + + +def stage_job_in_progress_early(env, partner, part, coating, ctx): + so = _make_so(env, partner, part, coating, + qty=random.choice([10, 25, 50]), + price=random.uniform(100.0, 300.0), ctx=ctx) + so.action_confirm() + job = env["fp.job"].search([("sale_order_id", "=", so.id)], limit=1) + if not job: + return False + if job.state == "draft": + job.action_confirm() + _ensure_steps(env, job) + _populate_job(env, job, ctx) + n_done = random.choice([1, 2]) + _assign_step_users(env, job, ctx, n_done=n_done, current_idx=n_done) + _fill_step_realistic_data(env, job) + job.write({ + "state": "in_progress", + "date_started": datetime.now() - timedelta( + days=random.randint(1, 4)), + }) + return True + + +def stage_job_in_progress_mid(env, partner, part, coating, ctx): + so = _make_so(env, partner, part, coating, + qty=random.choice([10, 25, 50]), + price=random.uniform(100.0, 300.0), ctx=ctx) + so.action_confirm() + job = env["fp.job"].search([("sale_order_id", "=", so.id)], limit=1) + if not job: + return False + if job.state == "draft": + job.action_confirm() + _ensure_steps(env, job) + _populate_job(env, job, ctx) + total = len(job.step_ids) + n_done = max(1, total // 2) if total else 0 + _assign_step_users(env, job, ctx, n_done=n_done, current_idx=n_done) + _fill_step_realistic_data(env, job) + job.write({ + "state": "in_progress", + "date_started": datetime.now() - timedelta( + days=random.randint(2, 7)), + }) + return True + + +def stage_job_on_hold(env, partner, part, coating, ctx): + so = _make_so(env, partner, part, coating, + qty=random.choice([10, 25, 50]), + price=random.uniform(100.0, 300.0), ctx=ctx) + so.action_confirm() + job = env["fp.job"].search([("sale_order_id", "=", so.id)], limit=1) + if not job: + return False + if job.state == "draft": + job.action_confirm() + _ensure_steps(env, job) + _populate_job(env, job, ctx) + total = len(job.step_ids) + n_done = min(2, max(1, total // 3)) if total else 0 + _assign_step_users(env, job, ctx, n_done=n_done, current_idx=n_done) + if total > n_done: + cur = job.step_ids.sorted("sequence")[n_done] + cur.write({"state": "paused"}) + _fill_step_realistic_data(env, job) + job.write({"state": "on_hold"}) + _create_quality_hold(env, job, ctx) + return True + + +def stage_job_done_delivery_draft(env, partner, part, coating, ctx): + so = _make_so(env, partner, part, coating, + qty=random.choice([5, 10, 25]), + price=random.uniform(80.0, 250.0), ctx=ctx) + so.action_confirm() + job = env["fp.job"].search([("sale_order_id", "=", so.id)], limit=1) + if not job: + return False + if job.state == "draft": + job.action_confirm() + _ensure_steps(env, job) + _populate_job(env, job, ctx) + _assign_step_users(env, job, ctx, + n_done=len(job.step_ids), + current_idx=None) + _fill_step_realistic_data(env, job) + job.write({ + "state": "in_progress", + "date_started": datetime.now() - timedelta( + days=random.randint(3, 10)), + }) + job.button_mark_done() + return True + + +def stage_delivery_scheduled(env, partner, part, coating, ctx): + if not stage_job_done_delivery_draft(env, partner, part, coating, ctx): + return False + job = env["fp.job"].search( + [("partner_id", "=", partner.id)], + order="id desc", limit=1) + if not job or not job.delivery_id: + return False + _make_delivery_full(env, job.delivery_id, partner, ctx, + state="scheduled", + scheduled_offset_days=random.randint(1, 5)) + return True + + +def stage_delivery_en_route(env, partner, part, coating, ctx): + if not stage_job_done_delivery_draft(env, partner, part, coating, ctx): + return False + job = env["fp.job"].search( + [("partner_id", "=", partner.id)], + order="id desc", limit=1) + if not job or not job.delivery_id: + return False + _make_delivery_full(env, job.delivery_id, partner, ctx, + state="en_route", + scheduled_offset_days=0) + return True + + +def stage_delivery_delivered(env, partner, part, coating, ctx): + if not stage_job_done_delivery_draft(env, partner, part, coating, ctx): + return False + job = env["fp.job"].search( + [("partner_id", "=", partner.id)], + order="id desc", limit=1) + if not job or not job.delivery_id: + return False + _make_delivery_full(env, job.delivery_id, partner, ctx, + state="delivered", + scheduled_offset_days=-2) + so = job.sale_order_id + _issue_certificate(env, job, so, part, ctx) + return True + + +def stage_invoice_draft(env, partner, part, coating, ctx): + if not stage_delivery_delivered(env, partner, part, coating, ctx): + return False + job = env["fp.job"].search( + [("partner_id", "=", partner.id)], + order="id desc", limit=1) + so = job.sale_order_id + if not so: + return False + inv = _create_invoice(env, so, ctx, post=False) + return bool(inv) + + +def stage_invoice_posted(env, partner, part, coating, ctx): + if not stage_delivery_delivered(env, partner, part, coating, ctx): + return False + job = env["fp.job"].search( + [("partner_id", "=", partner.id)], + order="id desc", limit=1) + so = job.sale_order_id + if not so: + return False + inv = _create_invoice(env, so, ctx, post=True) + return inv and inv.state == "posted" + + +def stage_paid(env, partner, part, coating, ctx): + if not stage_delivery_delivered(env, partner, part, coating, ctx): + return False + job = env["fp.job"].search( + [("partner_id", "=", partner.id)], + order="id desc", limit=1) + so = job.sale_order_id + if not so: + return False + inv = _create_invoice(env, so, ctx, post=True) + if not inv or inv.state != "posted": + return False + pmt = _register_payment(env, inv, ctx, validate=True) + return bool(pmt) + + +# ---------------------------------------------------------------------- +def run(env): + print("=" * 70) + print("seed_workflow_states.py - full pipeline seeding") + print("=" * 70) + + combos = _build_combos(env) + print("Customer/part combos: %d" % len(combos)) + if not combos: + print("ERROR: no parts with coating + recipe + partner. Cannot seed.") + return + operators = _operators(env) + managers = _managers(env) + employees = _employees(env) + facility = _resolve_facility(env) + product = _resolve_product(env) + payment_term = _resolve_payment_term(env) + sales_journal, bank_journal = _resolve_journals(env) + print("Operators: %d, Managers: %d, Employees: %d" % ( + len(operators), len(managers), len(employees))) + print("Facility: %s, Product: %s, PaymentTerm: %s" % ( + facility.name if facility else "NONE", + product.name if product else "NONE", + payment_term.name if payment_term else "NONE")) + print("Sales journal: %s, Bank journal: %s" % ( + sales_journal.name if sales_journal else "NONE", + bank_journal.name if bank_journal else "NONE")) + + if not (product and payment_term and sales_journal and bank_journal): + print("ERROR: missing required masters; cannot proceed.") + return + + ctx = { + "product": product, + "payment_term": payment_term, + "sales_journal": sales_journal, + "bank_journal": bank_journal, + "operators": operators, + "managers": managers, + "employees": employees, + "facility": facility, + } + + idx_holder = [0] + results = {} + + stages = [ + ("Quotation (sale.order draft)", stage_so_draft, TARGETS["so_draft"]), + ("Quote Sent (sale.order sent)", stage_so_sent, TARGETS["so_sent"]), + ("Order Confirmed Job Just Started", stage_job_confirmed, TARGETS["job_confirmed_no_steps_started"]), + ("Job In Progress Early", stage_job_in_progress_early, TARGETS["job_in_progress_early"]), + ("Job In Progress Mid", stage_job_in_progress_mid, TARGETS["job_in_progress_mid"]), + ("Job On Hold", stage_job_on_hold, TARGETS["job_on_hold"]), + ("Job Done Delivery Draft", stage_job_done_delivery_draft, TARGETS["job_done_delivery_draft"]), + ("Delivery Scheduled", stage_delivery_scheduled, TARGETS["delivery_scheduled"]), + ("Delivery En Route", stage_delivery_en_route, TARGETS["delivery_en_route"]), + ("Delivered", stage_delivery_delivered, TARGETS["delivery_delivered"]), + ("Invoice Draft", stage_invoice_draft, TARGETS["invoice_draft"]), + ("Invoice Posted", stage_invoice_posted, TARGETS["invoice_posted"]), + ("Paid", stage_paid, TARGETS["paid"]), + ] + + for label, fn, n in stages: + _stage(env, label, fn, n, combos, idx_holder, ctx, results) + env.cr.commit() + + print() + print("=" * 70) + print("SEED RESULTS") + print("=" * 70) + for label, count in results.items(): + print(" %-45s %d" % (label, count)) + print() + + +try: + run(env) +except NameError: + print("Run inside odoo shell.")