Files
Odoo-Modules/fusion_plating/docs/superpowers/specs/2026-04-29-deadline-architecture-design.md
gsinghpal df43737b1b spec(deadlines): per-part effective deadlines with customer-profile cascade
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>
2026-04-29 21:08:21 -04:00

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_date doesn'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

  1. Customer profile remains the single source of manual durations.
  2. Per-line effective deadline auto-populates with no manual input required, using a layered fallback chain.
  3. User can override at the line level via either an absolute date OR a days-offset.
  4. A part can carry its own typical lead time so it auto-applies whenever that part lands on a line.
  5. 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_date when 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:

  1. SO line viewviews/sale_order_views.xml (x_fc_* prefix)
  2. Direct Order wizardwizard/fp_direct_order_wizard_views.xml (bare names)
  3. 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; readonly
  • x_fc_is_late_forecast — computed; renders as a ⚠ Late badge when True

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

  1. planned_start_date blank — fall back to fields.Date.today() for any anchor calculation (matches existing _fp_recompute_default_deadlines behaviour).
  2. Customer profile values both = 0commitment_date ends up = planned_start_date. Don't error; user must set or override.
  3. commitment_date blank (orphan order) — effective_part_deadline falls back to planned_start_date so the field is never null.
  4. Both x_fc_part_deadline AND x_fc_part_deadline_offset_days set on a line — absolute date wins (rule 1 of the chain). Add a soft @api.constrains that suggests clearing one (UserError would be too aggressive; use a chatter log instead).
  5. Negative buffer (customer profile has internal_days > customer_days) — effective_internal_deadline would land after effective_part_deadline. Clamp internal to ≤ effective_part_deadline so it can never exceed the customer date.
  6. Empty orderorder_completion_date = False, is_late_forecast = False.
  7. Archived lines — exclude from order_completion_date rollup (x_fc_archived = False filter).
  8. Blanket ordersis_late_forecast suppressed (always returns False) 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_date for "promise to customer".
  • New shop-scheduling consumers should use x_fc_effective_part_deadline per line and x_fc_order_completion_date per order.
  • x_fc_is_late_forecast=True becomes 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

  1. Customer cascade unchanged — create SO, pick partner with deadline-days set, plan a start date → header commitment_date and x_fc_internal_deadline auto-populate (no regression).
  2. Line with no overrides — add part to line → x_fc_effective_part_deadline = commitment_date.
  3. Part with default_lead_time_days = 14 — line picks that part → effective_part_deadline = planned_start + 14. Order's completion_date reflects the later of header or line.
  4. Per-line offset — set part_deadline_offset_days = 3effective = commitment_date + 3 days.
  5. Per-line absolute override — set x_fc_part_deadline = Xeffective = X regardless of other settings.
  6. Mixed-deadline order — two lines with different effective_part_deadlineorder_completion_date = max(both). is_late_forecast = True if max > commitment_date.
  7. Blanket orderis_late_forecast = False even when completion > commitment.
  8. Archived line — excluded from order_completion_date calc.
  9. 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_date change.
  • Customer-portal effective-deadline display.
  • Per-process-variant lead time.
  • Notification/email when is_late_forecast flips to True.