From 5130e51941101eb4c3399ffe3b31eec09425032f Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Sat, 25 Apr 2026 05:53:52 -0400 Subject: [PATCH] =?UTF-8?q?feat(jobs):=20demo=20data=20=E2=80=94=20work=20?= =?UTF-8?q?centres=20+=20part=20coatings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two follow-up seed scripts after the main demo reset: 1. seed_work_centres.py: creates one fp.work.centre per existing fusion.plating.work.center (matched by code), classifying kind from name/code keywords. Then backfills work_centre_id on every fp.job.step whose recipe_node has a legacy work_center_id with a matching native code. Plant Overview kanban now has columns instead of one big 'Unassigned' bucket. 2. seed_part_coatings.py: assigns existing fp.coating.config rows (with recipes) to up to 20 bare fp.part.catalog rows round-robin. Field on the part is x_fc_default_coating_config_id. Future SO confirms via these parts will naturally generate full recipe-linked steps via _generate_steps_from_recipe. Both idempotent — re-running creates nothing new. Run on entech: 9 native work centres created, all 234 existing fp.job.step rows bound. Parts with coating: 2 -> 22 (28 -> 8 bare). Part of: native job model migration (spec 2026-04-25) Co-Authored-By: Claude Opus 4.7 (1M context) --- .../scripts/seed_part_coatings.py | 47 +++++++++ .../scripts/seed_work_centres.py | 98 +++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 fusion_plating/fusion_plating_jobs/scripts/seed_part_coatings.py create mode 100644 fusion_plating/fusion_plating_jobs/scripts/seed_work_centres.py diff --git a/fusion_plating/fusion_plating_jobs/scripts/seed_part_coatings.py b/fusion_plating/fusion_plating_jobs/scripts/seed_part_coatings.py new file mode 100644 index 00000000..ca90fcd9 --- /dev/null +++ b/fusion_plating/fusion_plating_jobs/scripts/seed_part_coatings.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Nexa Systems Inc. +# License OPL-1 (Odoo Proprietary License v1.0) +# +# Assigns existing fp.coating.config rows (with recipes) to parts that +# don't have one yet. Round-robin distribution. Idempotent. +# +# Field name on fp.part.catalog is x_fc_default_coating_config_id. + +COATING_FIELD = 'x_fc_default_coating_config_id' + + +def run(env): + print('=== Assigning coatings to bare parts ===') + + Part = env['fp.part.catalog'] + Coating = env['fp.coating.config'] + + coatings_with_recipe = Coating.search([('recipe_id', '!=', False)]) + if not coatings_with_recipe: + print(' No coatings with recipes available — abort') + return + print(f' Coatings with recipes: {len(coatings_with_recipe)}') + + bare_parts = Part.search([(COATING_FIELD, '=', False)]) + print(f' Parts without coating: {len(bare_parts)}') + + # Assign first 20 bare parts (or all if fewer) + to_assign = bare_parts[:20] + n = 0 + for i, part in enumerate(to_assign): + coating = coatings_with_recipe[i % len(coatings_with_recipe)] + part[COATING_FIELD] = coating.id + n += 1 + print(f' {part.name!r} -> {coating.name!r}') + + env.cr.commit() + print() + print(f'=== Done. Assigned coatings to {n} parts ===') + final_count = Part.search_count([(COATING_FIELD, '!=', False)]) + print(f' Parts with coating now: {final_count}') + + +try: + run(env) +except NameError: + print('Run inside `odoo shell`.') diff --git a/fusion_plating/fusion_plating_jobs/scripts/seed_work_centres.py b/fusion_plating/fusion_plating_jobs/scripts/seed_work_centres.py new file mode 100644 index 00000000..49761da1 --- /dev/null +++ b/fusion_plating/fusion_plating_jobs/scripts/seed_work_centres.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Nexa Systems Inc. +# License OPL-1 (Odoo Proprietary License v1.0) +# +# Seeds fp.work.centre rows that mirror the existing legacy +# fusion.plating.work.center records (by code). Then backfills the +# work_centre_id on every existing fp.job.step that has a +# recipe_node_id whose underlying recipe operation points to a legacy +# work centre we just created. +# +# Idempotent: skip rows that already exist by code. + +KIND_KEYWORDS = [ + ('bake', ['bake', 'oven']), + ('rack', ['rack']), + ('inspect',['inspect', 'qc', 'first', 'last']), + ('mask', ['mask']), + ('wet_line',['bath', 'plat', 'nickel', 'chrome', 'anodiz', 'rinse', 'tank', 'strip', 'etch']), +] + + +def _classify_kind(name, code): + text = (name + ' ' + (code or '')).lower() + for kind, keywords in KIND_KEYWORDS: + if any(k in text for k in keywords): + return kind + return 'other' + + +def run(env): + print('=== Seeding fp.work.centre from legacy fusion.plating.work.center ===') + + Native = env['fp.work.centre'] + Legacy = env['fusion.plating.work.center'] + + # 1. Create one fp.work.centre per legacy work centre (matched by code) + legacy_centres = Legacy.search([]) + print(f' Legacy centres found: {len(legacy_centres)}') + + created = 0 + for legacy in legacy_centres: + if not legacy.code: + print(f' SKIP (no code): {legacy.name}') + continue + existing = Native.search([('code', '=', legacy.code)], limit=1) + if existing: + continue + kind = _classify_kind(legacy.name or '', legacy.code or '') + vals = { + 'code': legacy.code, + 'name': legacy.name, + 'kind': kind, + 'active': True, + 'facility_id': legacy.facility_id.id if legacy.facility_id else False, + } + if hasattr(legacy, 'cost_per_hour'): + vals['cost_per_hour'] = legacy.cost_per_hour + Native.sudo().create(vals) + created += 1 + + print(f' Native work centres created: {created}') + print(f' Native total now: {Native.search_count([])}') + + # 2. Backfill work_centre_id on existing fp.job.step rows + print() + print('=== Backfilling work_centre_id on existing fp.job.step rows ===') + Step = env['fp.job.step'] + unbound = Step.search([('work_centre_id', '=', False), ('recipe_node_id', '!=', False)]) + print(f' Steps to backfill: {len(unbound)}') + + bound = 0 + no_legacy = 0 + no_match = 0 + for step in unbound: + legacy_wc = step.recipe_node_id.work_center_id + if not legacy_wc or not legacy_wc.code: + no_legacy += 1 + continue + match = Native.search([('code', '=', legacy_wc.code)], limit=1) + if not match: + no_match += 1 + continue + step.work_centre_id = match.id + bound += 1 + + print(f' Bound: {bound}') + print(f' Recipe op without legacy work centre: {no_legacy}') + print(f' No matching native code: {no_match}') + + env.cr.commit() + print() + print('=== Done ===') + + +try: + run(env) +except NameError: + print('Run inside `odoo shell`.')