Resolution chain: explicit override → days offset → part lead time → order commitment. Adds x_fc_default_lead_time_days on part catalog; per-line effective_part_deadline + effective_internal_deadline computes; order-level completion_date rollup + is_late_forecast warning. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
12 KiB
Deadline Architecture — Per-Part Effective Deadlines
Date: 2026-04-29
Module: fusion_plating_configurator (+ fusion_plating_invoicing for partner fields)
Status: Approved by user, ready to implement
Problem
Three deadline fields exist today (commitment_date, x_fc_internal_deadline, x_fc_part_deadline) but the per-line x_fc_part_deadline is a free-form override with no inheritance rule. There is no way to express:
- "This part on this order needs more time than the rest" without manual date entry on every line
- "This specific part normally takes 14 days from start" as a part-catalog default
- "When does the order actually finish" when lines have varying deadlines (the order's
commitment_datedoesn't roll up) - "We'll be late" warning when planned line deadlines exceed the customer-promise
Customer profile already carries duration defaults (x_fc_default_customer_deadline_days, x_fc_default_internal_deadline_days) that cascade to the order header, but those defaults stop at the header — they don't reach the line.
Goals
- Customer profile remains the single source of manual durations.
- Per-line effective deadline auto-populates with no manual input required, using a layered fallback chain.
- User can override at the line level via either an absolute date OR a days-offset.
- A part can carry its own typical lead time so it auto-applies whenever that part lands on a line.
- Order header gets a computed completion date and a "we'll be late" forecast that distinguishes from the customer-promise date.
Non-Goals
- Rescheduling logic (auto-shift
planned_start_datewhen shop is overloaded) — separate project. - Customer-portal display of effective deadlines — touches
fusion_plating_portal; not in this spec. - Per-process-variant lead time — explicitly rejected during brainstorming; revisit only if shops report estimation drift.
Concept Map
| Field | Lives on | Type | Source |
|---|---|---|---|
x_fc_default_customer_deadline_days |
res.partner |
Integer | Manual (existing) |
x_fc_default_internal_deadline_days |
res.partner |
Integer | Manual (existing) |
x_fc_default_lead_time_days |
fp.part.catalog |
Integer | Manual (NEW) |
x_fc_planned_start_date |
sale.order |
Date | Manual or scheduler (existing) |
commitment_date (Customer Deadline) |
sale.order |
Date | Manual; cascaded from customer (existing — unchanged) |
x_fc_internal_deadline |
sale.order |
Date | Manual; cascaded from customer (existing — unchanged) |
x_fc_part_deadline |
sale.order.line |
Date | Manual override (existing — semantics now one of two override forms) |
x_fc_part_deadline_offset_days |
sale.order.line |
Integer | Manual override (NEW) — alternative to absolute date |
x_fc_effective_part_deadline |
sale.order.line |
Date | Computed (NEW) — see resolution chain |
x_fc_effective_internal_deadline |
sale.order.line |
Date | Computed (NEW) — line's customer date minus order buffer |
x_fc_order_completion_date |
sale.order |
Date | Computed (NEW) = max(line.x_fc_effective_part_deadline) |
x_fc_is_late_forecast |
sale.order |
Boolean | Computed (NEW) = order_completion_date > commitment_date |
Resolution Chain — x_fc_effective_part_deadline
First match wins:
1. line.x_fc_part_deadline # explicit absolute-date override
2. line.x_fc_part_deadline_offset_days # "+N days from order"
→ commitment_date + offset_days
3. line.x_fc_part_catalog_id.x_fc_default_lead_time_days # part-specific lead time
→ planned_start_date + part.default_lead_time_days
4. order.commitment_date # falls through to order promise
# (which is itself derived from customer profile)
Every fallback ultimately routes back to the customer profile via the order header's commitment_date. No record is ever blank — even a brand-new line with no overrides immediately shows the order's customer-promise date.
Resolution — x_fc_effective_internal_deadline
buffer_days = commitment_date - x_fc_internal_deadline
(= the gap implied by customer profile: customer_days - internal_days)
x_fc_effective_internal_deadline = x_fc_effective_part_deadline - buffer_days
If the customer profile says "5 days customer / 3 days internal", every line's internal target = its effective customer date minus 2 days. Automatic, no per-line entry.
Order-Level Rollups
x_fc_order_completion_date = max(line.x_fc_effective_part_deadline)
for line in order_line if not line.x_fc_archived
x_fc_is_late_forecast = bool(x_fc_order_completion_date > commitment_date)
Suppress is_late_forecast when x_fc_is_blanket_order=True (blanket orders span months and would always trigger).
UI Changes — Apply on All Three Surfaces
Per the project parity rule (memory: feedback_direct_order_three_surface_parity), changes apply to:
- SO line view —
views/sale_order_views.xml(x_fc_*prefix) - Direct Order wizard —
wizard/fp_direct_order_wizard_views.xml(bare names) - Quote Configurator — n/a, single-line model — skip unless quote needs deadline at line level (it doesn't today)
SO line columns
| Column | Default | Purpose |
|---|---|---|
x_fc_part_deadline (Part Deadline Override) |
optional, hide | Absolute-date manual override |
x_fc_part_deadline_offset_days (Days Offset) |
optional, hide | "+5" form for users who think in days |
x_fc_effective_part_deadline (Effective Deadline) |
optional, show | Computed; visible badge with late decoration when > commitment_date |
x_fc_effective_internal_deadline (Shop Target) |
optional, hide | Available for shop-floor views |
Tooltip on Effective Deadline: "Manual override → offset → part lead time → order date".
SO header — Scheduling group
Add two readonly fields next to existing deadline group:
x_fc_order_completion_date— computed; readonlyx_fc_is_late_forecast— computed; renders as a⚠ Latebadge whenTrue
Part catalog form
Add Default Lead Time (days) Integer field next to Surface Area in the existing geometry group. Tooltip: "Optional: how many days from planned_start_date this part typically needs. Used as a smart default on order lines when no explicit deadline is set."
Customer profile (res.partner)
No UI change. Existing x_fc_default_customer_deadline_days and x_fc_default_internal_deadline_days already drive everything via the order header cascade.
Edge Cases
planned_start_dateblank — fall back tofields.Date.today()for any anchor calculation (matches existing_fp_recompute_default_deadlinesbehaviour).- Customer profile values both = 0 —
commitment_dateends up =planned_start_date. Don't error; user must set or override. commitment_dateblank (orphan order) —effective_part_deadlinefalls back toplanned_start_dateso the field is never null.- Both
x_fc_part_deadlineANDx_fc_part_deadline_offset_daysset on a line — absolute date wins (rule 1 of the chain). Add a soft@api.constrainsthat suggests clearing one (UserErrorwould be too aggressive; use a chatter log instead). - Negative buffer (customer profile has
internal_days > customer_days) —effective_internal_deadlinewould land aftereffective_part_deadline. Clamp internal to ≤ effective_part_deadline so it can never exceed the customer date. - Empty order —
order_completion_date = False,is_late_forecast = False. - Archived lines — exclude from
order_completion_daterollup (x_fc_archived = Falsefilter). - Blanket orders —
is_late_forecastsuppressed (always returnsFalse) since blanket spans are intentionally long.
Migration
| Field | Action |
|---|---|
fp.part.catalog.x_fc_default_lead_time_days |
New Integer, default 0. No backfill — 0 = "no part default" |
sale.order.line.x_fc_part_deadline_offset_days |
New Integer, default 0 |
sale.order.line.x_fc_effective_part_deadline |
New Date, computed stored. Auto-populates on first _recompute |
sale.order.line.x_fc_effective_internal_deadline |
New Date, computed stored. Auto-populates on first _recompute |
sale.order.x_fc_order_completion_date |
New Date, computed stored. Auto-populates on first _recompute |
sale.order.x_fc_is_late_forecast |
New Boolean, computed (non-stored OK; cheap to recompute) |
No data migration script needed. Existing commitment_date, x_fc_internal_deadline, x_fc_part_deadline keep their semantics. The new compute fields populate via Odoo ORM's standard recompute pass during module upgrade.
Reporting / Downstream
- Existing reports (Plant Overview kanban, KPI dashboards, late-orders filter) keep using
commitment_datefor "promise to customer". - New shop-scheduling consumers should use
x_fc_effective_part_deadlineper line andx_fc_order_completion_dateper order. x_fc_is_late_forecast=Truebecomes a natural filter for "orders we'll be late on" — list view decoration, dashboard tile, future notification.
Implementation Files
| File | Change |
|---|---|
fusion_plating_configurator/models/fp_part_catalog.py |
Add x_fc_default_lead_time_days field |
fusion_plating_configurator/views/fp_part_catalog_views.xml |
Add field to form (geometry group) |
fusion_plating_configurator/models/sale_order_line.py |
Add x_fc_part_deadline_offset_days, x_fc_effective_part_deadline, x_fc_effective_internal_deadline + computes |
fusion_plating_configurator/models/sale_order.py |
Add x_fc_order_completion_date, x_fc_is_late_forecast + computes |
fusion_plating_configurator/views/sale_order_views.xml |
Add new line columns + header rollup fields + late badge |
fusion_plating_configurator/wizard/fp_direct_order_line.py |
Mirror line fields (bare names: part_deadline_offset_days, effective_part_deadline, effective_internal_deadline) |
fusion_plating_configurator/wizard/fp_direct_order_wizard_views.xml |
Add wizard line columns |
fusion_plating_configurator/wizard/fp_direct_order_wizard.py |
Map bare names → x_fc_* when creating SO |
fusion_plating_configurator/__manifest__.py |
Bump version to 19.0.18.6.0 |
Test Plan
- Customer cascade unchanged — create SO, pick partner with deadline-days set, plan a start date → header
commitment_dateandx_fc_internal_deadlineauto-populate (no regression). - Line with no overrides — add part to line →
x_fc_effective_part_deadline = commitment_date. - Part with
default_lead_time_days = 14— line picks that part →effective_part_deadline = planned_start + 14. Order'scompletion_datereflects the later of header or line. - Per-line offset — set
part_deadline_offset_days = 3→effective = commitment_date + 3 days. - Per-line absolute override — set
x_fc_part_deadline = X→effective = Xregardless of other settings. - Mixed-deadline order — two lines with different
effective_part_deadline→order_completion_date = max(both).is_late_forecast = Trueif max >commitment_date. - Blanket order —
is_late_forecast = Falseeven when completion > commitment. - Archived line — excluded from
order_completion_datecalc. - Direct Order wizard parity — same auto-fill + override behaviour on the wizard before creating an SO; values transfer correctly.
Out of Scope (Defer)
- Rescheduling auto-adjust on
planned_start_datechange. - Customer-portal effective-deadline display.
- Per-process-variant lead time.
- Notification/email when
is_late_forecastflips to True.