docs(jobs): split fp.job §5.1 fields by module ownership (Task 1.4)

Originally Task 1.4 was to add all spec §5.1 extension fields to
fp.job in core. The dependency-graph audit during implementation
revealed that 6 of those fields point to models in dependent
modules (configurator, quality, portal, logistics, bridge_mrp).
Adding them in core would invert the dependency graph.

Spec §5.1 now has a Module column. Core-safe fields stay in
fusion_plating/models/fp_job.py; cross-module fields are deferred
to their owning modules via _inherit = 'fp.job' in Phase 2.

Plan Task 1.4 narrative updated to reflect the reduced scope.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-24 21:54:35 -04:00
parent e4111ad000
commit f7a4cba5a8
2 changed files with 75 additions and 73 deletions

View File

@@ -453,7 +453,10 @@ Create `fusion_plating/data/fp_job_sequences.xml`:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<!-- noupdate="1" is REQUIRED — without it, every -u fusion_plating
resets number_next back to 1, which corrupts the live sequence
on every module update. Matches the convention in fp_sequence_data.xml. -->
<odoo noupdate="1">
<!-- Sequence for fp.job. Format: WH/JOB/00001 onwards.
Migrated mrp.production records keep their WH/MO/... names. -->
<record id="seq_fp_job" model="ir.sequence">
@@ -513,11 +516,20 @@ Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
---
### Task 1.4: Add SO/origin/extension fields to `fp.job`
### Task 1.4: Add core-safe extension fields to `fp.job`
The full field list from spec §5.1 — added in chunks so each commit is reviewable.
**Scope reduction (2026-04-25):** Originally this task added all spec §5.1 fields.
But the dependency audit during Task 1.4 implementation revealed that 6 of those
fields point to models in modules that depend on `fusion_plating` core (configurator,
quality, portal, logistics, bridge_mrp). Adding them in core would invert the
dependency graph. **Per the updated spec §5.1**, those fields are deferred to their
owning modules via `_inherit = 'fp.job'` and re-bundled by `fusion_plating_jobs` in
Phase 2.
- [ ] **Step 1: Add SO + recipe + portal/delivery fields**
This task now lands ONLY the fields whose target models are reachable from core's
existing `depends` (sale_management → sale → account, and our own process.node):
- [ ] **Step 1: Add SO + recipe core-safe fields**
Modify `fusion_plating/models/fp_job.py` — add fields after `company_id`:
@@ -528,19 +540,6 @@ Modify `fusion_plating/models/fp_job.py` — add fields after `company_id`:
'job_id', 'line_id',
string='Source SO Lines',
)
part_catalog_id = fields.Many2one(
'fp.part.catalog',
string='Part',
index=True,
)
coating_config_id = fields.Many2one(
'fp.coating.config',
string='Coating Configuration',
)
customer_spec_id = fields.Many2one(
'fusion.plating.customer.spec',
string='Customer Spec',
)
recipe_id = fields.Many2one(
'fusion.plating.process.node',
string='Recipe',
@@ -551,26 +550,22 @@ Modify `fusion_plating/models/fp_job.py` — add fields after `company_id`:
string='Start at Node',
help='Rework: start the job at this recipe node (skip earlier).',
)
portal_job_id = fields.Many2one(
'fusion.plating.portal.job',
string='Portal Job',
)
delivery_id = fields.Many2one(
'fusion.plating.delivery',
string='Delivery',
)
invoice_ids = fields.Many2many(
'account.move',
'fp_job_account_move_rel',
'job_id', 'move_id',
string='Invoices',
)
qc_check_id = fields.Many2one(
'fp.quality.check',
string='Active QC Check',
)
```
**Deferred to bridge modules (DO NOT add in this task):**
- `part_catalog_id`, `coating_config_id` → owned by `fusion_plating_configurator`
- `customer_spec_id` → owned by `fusion_plating_quality`
- `portal_job_id` → owned by `fusion_plating_portal`
- `delivery_id` → owned by `fusion_plating_logistics`
- `qc_check_id` → owned by `fusion_plating_jobs` (Phase 2; the underlying model
`fusion.plating.quality.check` currently lives in `fusion_plating_bridge_mrp`)
- [ ] **Step 2: Add cost rollup fields (computed)**
Append:

View File

@@ -96,50 +96,57 @@ process tree with cost/time aggregates.
Replaces `mrp.production` for plating jobs. One record per shop-floor job.
| Field | Type | Notes |
|---|---|---|
| `name` | Char | Sequence: `WH/JOB/00033`. The legacy "WH/MO/00033" labels stay only on migrated records (see §7). |
| `state` | Selection | `draft`, `confirmed`, `in_progress`, `done`, `cancelled`, `on_hold` |
| `partner_id` | Many2one(res.partner) | Customer; copied from SO |
| `product_id` | Many2one(product.product) | Reference part product (for inventory only) |
| `part_catalog_id` | Many2one(fp.part.catalog) | The actual part being plated; primary identifier |
| `qty` | Float | Quantity to plate |
| `qty_done` | Float | Quantity completed |
| `qty_scrapped` | Float | Quantity scrapped (rolled up from holds) |
| `date_deadline` | Datetime | Promised completion date |
| `date_planned_start` | Datetime | Planned start |
| `date_started` | Datetime | Actual start (first step start) |
| `date_finished` | Datetime | Actual completion |
| `origin` | Char | SO name for traceability |
| `sale_order_id` | Many2one(sale.order) | Source SO |
| `sale_order_line_ids` | Many2many(sale.order.line) | Lines that fed this job (group_tag collapse) |
| `recipe_id` | Many2one(fusion.plating.process.node) | The recipe template used |
| `step_ids` | One2many(fp.job.step, job_id) | The operations |
| `step_count` | Integer | Computed |
| `step_done_count` | Integer | Computed |
| `step_progress_pct` | Float | Computed: `step_done_count / step_count * 100` |
| `current_step_id` | Many2one(fp.job.step) | The operation currently in progress (or next ready) |
| `coating_config_id` | Many2one(fp.coating.config) | The coating spec |
| `facility_id` | Many2one(fp.facility) | Hard gate at confirm |
| `manager_id` | Many2one(res.users) | Plating manager |
| `priority` | Selection | `low`, `normal`, `high`, `rush` (operator-relevant ordering) |
| `customer_spec_id` | Many2one(fp.customer.spec) | Optional spec |
| `portal_job_id` | Many2one(fp.portal.job) | Customer portal binding (renamed from `x_fc_portal_job_id`) |
| `delivery_id` | Many2one(fp.delivery) | The shipment |
| `invoice_ids` | Many2many(account.move) | Linked invoices |
| `certificate_ids` | One2many(fp.certificate, job_id) | Certs generated |
| `batch_ids` | One2many(fp.batch, job_id) | Batches that ran through |
| `quality_hold_ids` | One2many(fp.quality.hold, job_id) | Holds raised |
| `consumption_ids` | One2many(fp.job.consumption, job_id) | Consumables |
| `qc_check_id` | Many2one(fp.quality.check) | Active QC check |
| `quoted_revenue` | Monetary | From SO |
| `actual_cost` | Monetary | Computed from steps + consumables |
| `margin` | Monetary | Computed |
| `margin_pct` | Float | Computed |
| `start_at_node_id` | Many2one(fusion.plating.process.node) | Rework: start at this recipe node |
| `override_ids` | One2many(fp.job.node.override, job_id) | Per-job opt-in/out |
| `current_location` | Char | Computed: "Queued: Bath 3" / "In progress: Oven A" / "Ready to ship" |
| `mail.thread, mail.activity.mixin` | Inherits | Chatter |
**Module ownership:** `fp.job` lives in `fusion_plating` core. Cross-module fields
(referencing models from `fusion_plating_configurator`, `_portal`, `_logistics`,
`_quality`, `_bridge_mrp`) **cannot** live in core without inverting the dependency
graph. Each owning module extends `fp.job` via `_inherit` to add its field. The
Phase 2 module `fusion_plating_jobs` becomes the umbrella that pulls all the
extensions together. Ownership is called out in the **Module** column below.
| Field | Type | Module | Notes |
|---|---|---|---|
| `name` | Char | core | Sequence: `WH/JOB/00033`. The legacy "WH/MO/00033" labels stay only on migrated records (see §7). |
| `state` | Selection | core | `draft`, `confirmed`, `in_progress`, `done`, `cancelled`, `on_hold` |
| `partner_id` | Many2one(res.partner) | core | Customer; copied from SO |
| `product_id` | Many2one(product.product) | core | Reference part product (for inventory only) |
| `qty` | Float | core | Quantity to plate |
| `qty_done` | Float | core | Quantity completed |
| `qty_scrapped` | Float | core | Quantity scrapped (rolled up from holds) |
| `date_deadline` | Datetime | core | Promised completion date |
| `date_planned_start` | Datetime | core | Planned start |
| `date_started` | Datetime | core | Actual start (first step start) |
| `date_finished` | Datetime | core | Actual completion |
| `origin` | Char | core | SO name for traceability |
| `sale_order_id` | Many2one(sale.order) | core | Source SO (sale_management is in core depends) |
| `sale_order_line_ids` | Many2many(sale.order.line) | core | Lines that fed this job (group_tag collapse) |
| `recipe_id` | Many2one(fusion.plating.process.node) | core | The recipe template used |
| `step_ids` | One2many(fp.job.step, job_id) | core | The operations |
| `step_count` | Integer | core | Computed |
| `step_done_count` | Integer | core | Computed |
| `step_progress_pct` | Float | core | Computed: `step_done_count / step_count * 100` |
| `current_step_id` | Many2one(fp.job.step) | core | The operation currently in progress (or next ready) |
| `facility_id` | Many2one(fusion.plating.facility) | core | Hard gate at confirm |
| `manager_id` | Many2one(res.users) | core | Plating manager |
| `priority` | Selection | core | `low`, `normal`, `high`, `rush` (operator-relevant ordering) |
| `invoice_ids` | Many2many(account.move) | core | Linked invoices (account is reachable via sale_management → sale → account) |
| `quoted_revenue` | Monetary | core | From SO |
| `actual_cost` | Monetary | core | Computed from steps + consumables |
| `margin` | Monetary | core | Computed |
| `margin_pct` | Float | core | Computed |
| `start_at_node_id` | Many2one(fusion.plating.process.node) | core | Rework: start at this recipe node |
| `current_location` | Char | core | Computed: "Queued: Bath 3" / "In progress: Oven A" / "Ready to ship" |
| `mail.thread, mail.activity.mixin` | Inherits | core | Chatter |
| `part_catalog_id` | Many2one(fp.part.catalog) | **`fusion_plating_configurator`** (`_inherit = 'fp.job'`) | The actual part being plated; primary identifier |
| `coating_config_id` | Many2one(fp.coating.config) | **`fusion_plating_configurator`** | The coating spec |
| `customer_spec_id` | Many2one(fusion.plating.customer.spec) | **`fusion_plating_quality`** | Optional spec |
| `portal_job_id` | Many2one(fusion.plating.portal.job) | **`fusion_plating_portal`** | Customer portal binding |
| `delivery_id` | Many2one(fusion.plating.delivery) | **`fusion_plating_logistics`** | The shipment |
| `qc_check_id` | Many2one(fusion.plating.quality.check) | **`fusion_plating_jobs`** (Phase 2) | Active QC check; model lives in current bridge_mrp, will move to jobs module |
| `certificate_ids` | One2many(fp.certificate, job_id) | **`fusion_plating_certificates`** | Certs generated |
| `batch_ids` | One2many(fp.batch, job_id) | **`fusion_plating_batch`** | Batches that ran through |
| `quality_hold_ids` | One2many(fp.quality.hold, job_id) | **`fusion_plating_quality`** | Holds raised |
| `consumption_ids` | One2many(fp.job.consumption, job_id) | **`fusion_plating_jobs`** (Phase 2) | Consumables |
| `override_ids` | One2many(fp.job.node.override, job_id) | **`fusion_plating_jobs`** (Phase 2) | Per-job opt-in/out |
**State machine:**
```