WBS Alignment Proposal β
Alignment status (updated June 2026): This proposal has been reviewed against the parallel Time Reporting initiative (
time-reporting-prd.md). Both efforts converge on the same project-WBS spine but require coordinated changes β primarily a flatterCostLineshape, a unifiedResourcereference, and an explicit bridge toTimeEntry. The substantive changes are noted inline; a dedicated Β§ Time-reporting alignment section below describes how labourCostLines materialise from clocked time. D1:B signed off (WBS author, June 2026): a single flatCostLinecarries all type-specific fields conditional oncostKind(no sub-lines) β see the D1:B note under Β§ Proposed alternative.PO review (8 June 2026, Anton Hillestam). The proposal was reviewed against the Rise product track. Four decisions were confirmed β (1) the product-canonical name for the WBS node is
Work Package; its cost-breakdown leaf line is namedCostLinein this document (reservingResourcefor the unified registry β see Β§ Open questions #1), mapping owned by Pedro; (2)phaseis metadata on a work package, not a hierarchy level; (3) MVP seeding priority (see Β§ Quotation β Project); (4) concept labour lines are supported. The per-question outcomes are folded into Β§ Open questions below. Epics: VUL-316 (WBS & Work Packages), VUL-732 (project setup / baseline).
This document compares the current Work Breakdown Structure (WBS) across the Calculations β Quotation β Projects pipeline with a proposed alternative that aligns the Projects service to the same WBS shape as Calculations and Quotation.
See also: Data Model & WBS.
Current state β
The hierarchical WBS lives in Calculations and Quotation. The Projects service is deliberately flat β there is no nested WBS. Today Activity is the budget holder: the Activity entity carries BudgetHours / BudgetCost / ActualHours / ActualCost directly and groups a flat list of WorkOrders, with no line-level link back to the calculation it was budgeted from.
flowchart TD
subgraph CALC["Calculations β Budget Tree (recursive)"]
B[Budget] --> E1[Element root]
E1 --> E2[Element]
E2 --> E3[Element ...n deep]
E2 --> CI1[CostItem]
E3 --> CI2[CostItem]
end
subgraph QUOT["Quotation β WBS, 3 fixed levels"]
Q[Quote] --> A[Activity]
A --> WP[WorkPackage]
WP --> R[Resource: Labor/Material/Equipment/Subcontractor]
end
subgraph PROJ["Projects β FLAT, no nested WBS"]
P[Project] --> PA[Activity: budget holder]
P --> WO[WorkOrder]
PA -.optional.-> WO
WO --> TE[TimeEntry]
end
B -. "SourceBudgetId" .-> Q
Q -. "ContractId then Project" .-> PProposed alternative β
The Projects column adopts the same WBS as Quotation, with the WorkPackage as the spine: a WorkPackage hangs directly under Project (mandatory parent) and breaks down into CostLines. Activity and WorkOrder become optional parents of a WorkPackage β extra grouping layers that can slot in where useful, without being required.
flowchart TD
subgraph CALC["Calculations β Budget Tree (recursive)"]
B[Budget] --> E1[Element root]
E1 --> E2[Element]
E2 --> E3[Element ...n deep]
E2 --> CI1[CostItem]
E3 --> CI2[CostItem]
end
subgraph QUOT["Quotation β WBS, 3 fixed levels"]
Q[Quote] --> A[Activity]
A --> WP[WorkPackage]
WP --> R[CostLine: Labor/Material/Equipment/Subcontractor]
end
subgraph PROJ["Projects β same WBS as Quotation"]
P[Project] ==> PWP[WorkPackage]
PWP -->|"cost lines (concept β realized)"| PR[CostLine: Labor/Material/Equipment/Subcontractor]
PR -. "optionally detailed by" .-> TE[TimeEntry]
PR -. "optionally detailed by" .-> POL[PurchaseOrderLine]
PA[Activity] -. "optional parent" .-> PWP
WO[WorkOrder] -. "optional parent" .-> PWP
end
B -. "SourceBudgetId" .-> Q
Q -. "ContractId then Project" .-> P
CI1 -. "estimate ref" .-> PWP
R -. "estimate ref" .-> PWPWorkPackage parentage β
In the proposed model a WorkPackage has one mandatory parent and two optional ones. Note that WorkOrder is a separate execution entity, not a node inside the project WBS β it's an optional grouper for WorkPackages under a single execution unit.
| Parent | Cardinality | Meaning |
|---|---|---|
Project | Mandatory (1) | Every work package belongs to exactly one project. |
Activity | Optional (0..1) | A work package may be grouped under an activity. In the proposed model Activity is a pure grouping label β it no longer holds the budget (see below). |
WorkOrder | Optional (0..1) | A work package may be grouped under a work order for execution-level breakdown. WorkOrder lives outside the project WBS as its own entity. |
erDiagram
Project ||--o{ WorkPackage : "mandatory parent"
Activity ||--o{ WorkPackage : "optional parent"
WorkOrder ||--o{ WorkPackage : "optional parent"
WorkPackage ||--o{ WorkPackageSource : "estimate references"
WorkPackage ||--o{ CostLine : "cost lines (concept/realized)"
Project ||--o{ Activity : "groups"
Project ||--o{ WorkOrder : "execution units"
Resource ||--o{ CostLine : "physical thing used"
TimeEntry ||--o{ CostLine : "realized labour bridge (D4)"
Article ||--o{ CostLine : "billable line (labour)"
SalaryType ||--o{ CostLine : "tier (labour)"
WorkPackage {
Guid Id PK
Guid ProjectId FK "mandatory"
Guid ActivityId FK "optional, nullable"
Guid WorkOrderId FK "optional, nullable"
string BudgetCode
string Name
Guid InstallationId FK "optional, nullable"
string WorkDescription
Guid ChecklistId[] FK "optional, nullable"
enum PriceDetermination "Fixed|Billable + flags(warranty,additional)"
Guid SellingCondition FK "mandatory"
Totals CostPriceTotals
Totals SellingPriceTotals
}
WorkPackageSource {
Guid Id PK
Guid WorkPackageId FK
Guid CalculationCostItemId FK "calculation, nullable"
Guid QuotationCostLineId FK "quotation, nullable"
}
CostLine {
Guid Id PK
Guid WorkPackageId FK
enum CostKind "labour|material|equipment|subcontracting|service|other"
Guid ResourceId FK "β Resource (type matches costKind) β replaces per-kind FKs"
string Name
decimal Quantity
string UnitOfMeasurement
Price CostPrice
decimal MarkupPercentage
enum PriceStrategy "gross|net|margin"
Price SellingPrice
decimal CostPriceAmount "pre-computed β keeps the common aggregate a single-table SUM"
decimal SellingPriceAmount "pre-computed β keeps the common aggregate a single-table SUM"
date Date "set when realized"
Guid EmployeeId FK "set when realized (when costKind=labour, redundant with Resource)"
Guid OriginCalcLineId FK "optional, links realized β concept line"
Guid TimeEntryId FK "labour only β backs to TimeEntry (D4)"
Guid AppointedSubtypeId FK "labour only β ResourceSubtype actually worked"
Guid SalaryTypeId FK "labour only β tier (NORMAL/OB1/OB2/OT)"
Guid ArticleId FK "labour only β billable Article via Agreement"
decimal SnapCostRate "labour only β snapshot at materialization (D3)"
decimal SnapMultiplier "labour only β snapshot at materialization (D3)"
decimal SnapSellingRate "labour only β snapshot at materialization (D3)"
TradeRef TradeItem "material only"
decimal ArticleIdRef "material only"
PurchaseOrderRef[] PurchaseOrderReferences "material/equipment/subco"
WarehouseIssueRef[] WarehouseIssueReferences "material only"
}
TimeEntry {
Guid Id PK
Guid ResourceId FK "β Resource (labor)"
Guid AppointedSubtypeId FK
string EntityType "work_package|work_order|absence"
Guid EntityId "polymorphic"
timestamp StartTime
timestamp EndTime
int BreakMinutes
enum Kind "work|absence:*"
enum Status "draft|submitted|approved"
}
Resource {
Guid Id PK
Guid ResourceTypeId FK "LABOR|EQUIPMENT|MATERIAL|SUBCONTRACTOR|SERVICE|OTHER"
Guid ResourceSubtypeId FK "Plumber, Excavator 14-18T, β¦"
string Number
string Name
decimal HourlyCost "labour only, nullable"
Guid RulesetId FK "labour only, nullable"
}
Price {
decimal GrossPrice
decimal discountPercentage
decimal NetPrice
}
Totals {
decimal Labour
decimal Hours
decimal Materials
decimal Subcontracting
decimal Equipment
decimal Service
decimal Other
}D1:B β flat
CostLine(WBS author sign-off, June 2026). A cost line is not split into type-specific sub-line entities; the model uses one widerCostLinewith conditional fields bycostKind. Material fields (tradeItem,purchaseOrderReferences,warehouseIssueReferences), equipment/subcontractor refs, and the labour substantiation (appointed subtype, salary tier, billableArticle, theTimeEntryback-reference and the D3 snapshot rates) all live directly onCostLine, populated only when the matchingcostKindapplies. Pre-computedCostPriceAmount/SellingPriceAmountkeep the common aggregate query a single-table SUM. This is exactly the flat shape the time-reporting PRD specifies (D1); the VUL-918 engine materialises labour rows straight ontoCostLine.
D1+D2 β unified Resource registry. All four type-specific references (
employee,equipment,subcontractor,tradeItem) collapse into oneResourcereference. Resource lives in coresetup as a shared registry β same row whether referenced from the project WBS, planning availability, quotation line items, or the time-reporting clock-in. Resource carriestype(LABOR/EQUIPMENT/MATERIAL/SUBCONTRACTOR/SERVICE/OTHER) +subtype(Plumber/Excavator 14-18T/...). Labour-specific fields (hourly_cost,ruleset,competencies) are nullable on Resource β populated only when type=LABOR.
Budget ownership: from Activity to WorkPackage β
This is the core structural shift, and it changes where the budget lives.
In the current Projects service the budget sits on Activity. The Activity entity carries BudgetHours, BudgetCost, ActualHours and ActualCost directly, and WorkOrders hang underneath it β but nothing links those figures back to the calculation line items they were derived from. The budget is a number typed onto a grouping node.
In the proposed model the budget moves down to the WorkPackage, because that is where the link to the originating calculation is kept:
- A
WorkPackagecollects the relevant calculationCostItems (and/or quoteCostLines) throughWorkPackageSource, and rolls them up into itsCostPriceTotals/SellingPriceTotals. That roll-up is the work package's budget β and it stays traceable line-by-line back to the calculation, instead of being a standalone typed-in figure. Activityis demoted to a pure grouping label β name, code, status, dates, sort order. It no longer ownsBudgetCost/BudgetHours/ActualCost/ActualHours; any activity-level figure shown in the UI is a roll-up of its child work packages, not stored budget.- Actuals live on the work package's
CostLines. ComparingCostPriceTotals(estimate) against the sum of realizedCostLines gives estimate-vs-actual at the work-package level (see below), which the flatActivity.BudgetCost/ActualCostpair could only ever do per grouping.
In short: the budget is held where the calculation link is held β the WorkPackage β not on the grouping node.
Estimate vs. actuals β
The project side separates the estimate (what was planned/sold) from the actuals (what was really spent):
WorkPackagecarries the estimate. It holds a set of source references (WorkPackageSource) back to the originatingCostItems of the calculation and/orCostLines of the quotation, and rolls those up into itsCostPriceTotals/SellingPriceTotals. This is where the planned numbers live.CostLines break the work down for execution. ACostLineis the leaf under aWorkPackage(percostKind: Labor / Material / Equipment / Subcontractor), with its own cost price, markup and selling price. aWorkPackagecan be pre-filled with planned (concept) cost lines long before any cost is incurred, and a line becomes realized once the work is actually done (see Concept vs. realized cost lines).
TimeEntry and PurchaseOrderLine are optional detailings of a cost line, not a prerequisite for it β they back a realized line with its underlying registration:
| Detailing | Backs | Meaning |
|---|---|---|
TimeEntry | Labor CostLines | Actual hours logged against the cost line. |
PurchaseOrderLine | Material / Equipment / Subcontractor CostLines | Actual procurement booked against the cost line. |
Both are optional (0..many) β a cost line is valid without any detailing. Comparing a WorkPackage's estimate totals against the sum of its cost lines gives a direct estimate-vs-actual view.
Why align the models β
- Consistency β a near-identical leaf shape (
WorkPackage β CostLineon the quote,WorkPackage β CostLineon the project) means budget/quote line items map cleanly onto project work, removing the flattening step that currently loses structure. - Traceability β cost lines estimated in the quote can be tracked directly against the project WBS, with
TimeEntryandPurchaseOrderLineproviding actuals-vs-estimate at the cost line leaf. - Flexible execution β keeping
ActivityandWorkOrderas optional parents of theWorkPackagepreserves the operational/grouping layers while letting work packages sit directly under the project when no extra grouping is needed.
Translating the WBS across the pipeline β
Work flows Calculation β Quote β Project, but the WBS is re-derived at every step rather than copied verbatim. Each service owns its own tree, and the Project may deliberately diverge from both the calculation and the quote.
The base mapping between levels is (the project renames the levels but keeps the same shape):
| Calculation | Quote | Project | Notes |
|---|---|---|---|
Element | Activity | Activity | Grouping level. |
CostItem | WorkPackage | WorkPackage | The work package carrying quantity, cost price and selling price. |
CostItem cost component (Labor / Material / Equipment / Subcontractor) | CostLine | CostLine | The cost breakdown of a cost item becomes the work package's cost lines. |
The quotation service implements this leaf as its own
Resourceline item; In this document the WBS leaf is calledCostLineβ distinct from the unifiedResourceregistry it points at.
Calculation β Quotation β
Element β Activity, CostItem β WorkPackage. The catch: a budget is an unbounded Element tree, while the quote has a flat activity level. So the tree must be flattened to two levels before it maps.
flowchart TD
subgraph BUD["Calculation β Budget tree (unbounded depth)"]
E0[Element root] --> E1[Element A]
E0 --> E2[Element B]
E1 --> CI1[CostItem a1]
E1 --> CI2[CostItem a2]
E2 --> CI3[CostItem b1]
end
subgraph QWBS["Quote β flattened to 2 levels"]
A1[Activity A] --> W1[WorkPackage a1]
A1 --> W2[WorkPackage a2]
A2[Activity B] --> W3[WorkPackage b1]
end
E1 -->|"Element β Activity"| A1
E2 -->|"Element β Activity"| A2
CI1 -->|"CostItem β WorkPackage"| W1
CI2 --> W2
CI3 --> W3Flattening method. Walk the budget tree depth-first:
- Every
CostItembecomes oneWorkPackage(carrying its quantity, unit cost and unit price). - The
WorkPackage's parentActivityis derived by collapsing the owning element path. Which element becomes the activity is controlled by the existing depth markersQuote.SourceActivityLevelandQuote.SourceWorkPackageLevel:- Elements at the activity level become
Activitys (intermediate ancestor codes/names joined into a path label so the activity stays uniquely identifiable). - Elements between the activity level and the cost items are folded into the
WorkPackage.
- Elements at the activity level become
- Each
CostItem's non-zero cost components map to the work package'sCostLines.
Provenance is preserved: Quote.SourceBudgetId points back to the budget, and each QuoteLineItem keeps SourceActivityId / SourceWorkPackageId / SourceResourceId.
Quotation β Project (on approval) β
When a quote is approved, the project gets its own WBS. It is not automatically the quote's WBS β the way work is broken down for execution is often completely different from how it was structured for sales. There are three ways to seed the project WBS:
flowchart TD
CALC["Calculation / Budget<br/>(Project.CalculationId)"]
QUOTE["Approved Quote<br/>Activity β WorkPackage"]
PROJ["Project WBS<br/>Project β WorkPackage β CostLine"]
CALC -->|"1. cost-kind-based<br/>(group by per cost-kind)"| PROJ
CALC -->|"2. code-based<br/>(group by CostItem unique code / URC)"| PROJ
QUOTE -->|"3. Quotation<br/>(use WBR of quote)"| PROJ- From calculation, cost-kind-based β create a work package for every cost-kind used in the calculation. So a work package for Labour, materials, subcontracting etc.
- From calculation, code-based β structure by the unique code (URC) of the
CostItems instead of the element tree. Cost items are grouped/aggregated by theirCoderegardless of where they sit in the element tree, giving a trade/resource-oriented breakdown (e.g. all items with code21.*under one grouping). Also re-derivable viaProject.CalculationId. - From quote β Use the same WBS as the quotation
Activity β WorkPackagestructure (the project keeps the same two levels:Activity β WorkPackage).
| Strategy | Source | Driven by | When to use |
|---|---|---|---|
| Cost-kind-based | Calculation | Cost-kind | A work package per cost-kind |
| Code-based | Calculation | CostItem.Code (URC) | Trade/resource-oriented breakdown, ignoring element nesting. |
| Quotation | Quote | Quote Activity/WorkPackage | Execution mirrors the estimate structure. |
PO decision (8 June 2026) β MVP seeding priority. For MVP the Quotation strategy is the default whenever a linked quote exists; cost-kind-based is the automatic fallback when there is no linked quote (manual project creation). Code-based (URC) seeding is deferred to phase 2 β not built or exposed in the UI for MVP. Seeded work packages carry a
SourceQuoteResourceIdback-reference, cannot be deleted, and cannot have theirsellingPriceTotalschanged.
In all three cases the resulting Project WBS is independent and editable β once seeded, it evolves on its own (with WorkPackage directly under Project and optional Activity / WorkOrder parents as described above). Work packages can be added for Time&Material work in a project, or for additional work (deviations). Work packages seeded by this logic can only be edited in some ways. They cannot be deleted, changed in a way that would change the sellingPriceTotals.
Concept vs. realized cost lines β
A cost line's date and employee are only filled in once the work has actually been done β together they signal that the line is realized.
When you edit the work packages and cost lines under a project or work order, the status of the work order decides how newly created lines are treated (and when a work package has no work order, the project's status is used instead):
- "New" or "needs revisit" β new lines get no date or employee. You are pre-filling the work packages with suggestions or planned work, i.e. concept cost lines.
- "In progress" β the mechanic is actively working on the order, so any cost line they enter automatically gets today's date and the mechanic assigned as employee.
- "Done" β lines added now are assumed to register the work actually done, so the date and employee are filled in automatically as well.
When the work order is moved to "done" while some cost lines still have no date or employee, the mechanic is prompted to enter the actual quantities used for every such line; those lines are then stamped with the current date and the mechanic, with the option to adjust either value.
So a cost line without a date and employee is a concept line, and one with a date and employee is a realized line.
Realized cost lines β
A realized cost line carries a reference to its underlying registration, depending on its type:
- Material β a purchase-order line or a dispatch line.
- Labour β an employee, an hourly rate, and an hour-registration line.
- Equipment β an equipment id.
- Subcontracting β a subcontractor, plus either a purchase order or an assigned "external employee".
- Realized other lines do not have restrictions
Time-reporting alignment β
This section describes how the project WBS integrates with the parallel Time Reporting initiative (time-reporting-prd.md). The two efforts share data β labour CostLines under a project's WorkPackage materialise from clocked time captured via the time-reporting service.
Two entry surfaces for labour CostLines (D7) β
A labour CostLine can become realized through either of two flows:
Time-first (mobile clock-in flow):
- The worker (
Resourceof type LABOR) clocks in on the mobile app against a project + activity β aTimeEntryis created in the time service withentity_type="work_package"andentity_id=<workPackage.id> - The worker clocks out β
TimeEntry.statustransitions tosubmitted - The Rule Engine immediately materialises labour
CostLines under the parentWorkPackageβ oneCostLineperSalaryTypesegment (NORMAL, OB1, OB2, OT) perArticle(D5: write-through on clock-out) - Materialised
CostLines are editable untilTimeEntry.status=approved(D5: PM weekly approval is the lock point)
Work-first (project execution UI flow):
- PM pre-fills
WorkPackagewith concept labourCostLines (planned hours per Article) - Mechanic working through the WorkPackage edits a concept line β fills in
dateandemployee(or these auto-fill based on parentWorkOrder.status) - The line is now realized β but no corresponding
TimeEntryis created (this flow bypasses the time service)
Both flows produce the same shape of realized labour CostLine. The time-first flow additionally creates a TimeEntry with a back-reference; the work-first flow leaves TimeEntry empty on the line.
flowchart LR
subgraph T["Time-first (mobile clock-in)"]
TE["TimeEntry<br/>(time service)"] --> ENG["Rule Engine<br/>derivation"]
ENG --> WL1["realized<br/>Labour CostLine(s)"]
WL1 -.back-ref.-> TE
end
subgraph W["Work-first (project UI)"]
CL["concept<br/>Labour CostLine"] --> WL2["realized<br/>Labour CostLine"]
end
WL1 --> WI["WorkPackage<br/>(under Project)"]
WL2 --> WIThe engine produces multiple CostLines per shift (D3, D4) β
A single 11-hour shift typically materialises as two or more labour CostLines β not one. The Rule Engine splits the shift along SalaryType boundaries (per the worker's assigned Ruleset), and each tier-segment becomes its own CostLine keyed to its billable Article:
| Hours | SalaryType | Article | β produces |
|---|---|---|---|
| 8h | NORMAL | Electrician hour (90-100) | one CostLine, qty 8, articleRef = 90-100, salaryTypeRef = NORMAL |
| 2h | OB1 | Electrician OT (90-101) | merged with the OB2 row β one CostLine, qty 3 (2h OB1 + 1h OB2), articleRef = 90-101, salaryTypeRef = OB1/OB2 |
| 1h | OB2 | Electrician OT (90-101) | β merged into the OB1 row above (same Article) |
The Article routing happens through the Agreement resolution chain (Project β Contract β Framework β Company default). The number of CostLines materialised equals the number of distinct Articles the shift segments route to.
Snapshot fields on materialised CostLines (D3) β
When the engine materialises a labour CostLine, it writes both the FK references AND a snapshot of the resolved values at that moment:
CostLine (labour, realized)
resourceRef β Resource (the worker)
appointedSubtypeRef β ResourceSubtype (role worked β may differ from default)
salaryTypeRef β SalaryType (which tier)
articleRef β Article (billable line)
timeEntryRef β TimeEntry (back-reference for audit)
snapCostRate decimal β Resource.hourly_cost at time of materialisation
snapMultiplier decimal β SalaryType.multiplier at time of materialisation
snapSellingRate decimal β Article rate (via Agreement) at time of materialisation
quantity decimal β hours in this tier-segment
costPriceAmount (derived) β qty Γ snapCostRate Γ snapMultiplier
sellingPriceAmount (derived) β qty Γ snapSellingRateThe engine never silently rewrites historical CostLines. A change to Resource.hourly_cost, Article.default_billable_rate, or Agreement.billing_rules only affects CostLines materialised after the change. To re-derive historical lines, an admin must explicitly trigger "Recompute since date X" β confirmation-gated. This prevents a typo correction on an article rate from retroactively changing last quarter's invoiced revenue.
Fixed-price WorkPackages (D11) β
When WorkPackage.priceDetermination = Fixed, the customer invoice is driven by the WorkPackage's fixed sellingPriceTotals, not by individual CostLine selling prices. By default the engine still produces labour billable rows for fixed-price work β they are informational, used for margin tracking and "have we burned through the fixed price?" visibility.
A company-level toggle (derive_billable_on_fixed, default ON) controls this behaviour. Companies that don't care about labour margin visibility on fixed-price work can disable it to reduce engine load.
Naming alignment (D6) β
The time service's TimeEntry.entity_type enum includes work_package to point at this WBS proposal's WorkPackage. (Open question #1 is now resolved: the WBS node is named Work Package and its leaf line CostLine. The time-service contract keys off a stable entity_type string, not the entity's display name.)
Open questions β
Status legend. β Resolved Β· β Direction agreed, detail open Β· β Open. Outcomes from the PO review of 8 June 2026 (Anton Hillestam) are folded in inline, with owners and cross-references where given.
- β
Naming alignment across contexts.Resolved (D6 + PO decision 1, refined): the PO confirmed the product-canonical nameWork Packagefor the WBS node. To avoid a clash between the WBS leaf line and the unifiedResourceregistry (the physical thing β person / equipment / material / subcontractor), this document names the WBS leaf lineCostLine(formerlyWorkLine), reservingResourceexclusively for the registry. So:WorkPackage= WBS node,CostLine= its cost-breakdown leaf,Resource= the registry entity the leaf points at. - β
Checklist structure on a work package.Resolved (PO): a work package supports multiple checklists β theChecklistId[]array is the confirmed model. Sub-questions still open before checklist stories are written: how work-package checklists relate to the project-level Control Plan (kontrollplan.md/ VUL-733) β same engine or separate β and whether the Form.io / TBR checklist engine serves both. Owner: Ivo Huizinga (Checklist Engine phase). - β Cost typing at cost-line level. Only
CostLine.costKindis modelled. The full chain cost group β cost kind β general-ledger account, plus the import flow from the accounting integration, is unresolved. Open: how a cost line maps to a GL account in Exact Online (NL) and Fortnox (SE), and whether that mapping is configurable per company or fixed per cost kind. Owner: Pedro (VIL / accounting layer) + Niels (Exact Online). Affects VUL-734 (material & cost reporting) and the Exact Online integration. - β Change-order ("additional work") handling. The
additionalprice-determination type exists, but the contractual process is not yet designed. Agreed direction (PO): distinguish an internal deviation (affectsactualCostonly, never customer-visible) from an external change order (affectscontractValue+wbsBudgetCost, requires customer approval).project-schema-gaps.mdproposes addingvisibility,priceImpactandcontractAmendmentIdfields β not yet in Jira. Owner: Anton + Mladjan; relates to VUL-321 (deviation management) and VUL-365 (convert to change order). Needs a schema design session with Pedro. - β Country-specific checklist regulations. Regulatory checklist requirements per country (NL vs. SE β fire safety, excavation, asbestos, β¦) are not inventoried. Open: which checklist types are legally required in NL for our ICP (residential trade contractors), and whether the Form.io / TBR database covers them or a separate regulatory library is needed. Owner: Niels Poort (NL) + Ivo; input from NL pilot customers during Phase 2 discovery.
- β
Labour as concept lines.Resolved (D8 + PO decision 4): concept labourCostLines exist β a PM can plan "8h of Electrician work" before any clock-in. Realised lines later materialise fromTimeEntryand link viaoriginCalcLine, enabling planned-vs-actual at the line level. Flagged to the time-reporting team (VUL-896) so the L1 model treats a concept labour line as distinct from a time entry. - β Phased concept β realized for materials. When a material quantity is consumed in steps (e.g. 100 units over several weeks), the conceptβrealized mechanic is undefined. Two candidates: split the concept line (a new realized line per partial delivery; the concept line keeps the remainder) vs. status per period (one line, status/quantity updated incrementally). Owner: Kick / Enk (Work Order microservice owns material realization); affects VUL-734 and purchase-order reconciliation.
- β Payment-plan coupling. How a work package links to progress billing is unresolved. Two candidate models: fixed price with progress % (bill at a defined completion percentage) vs. milestones (billing tied to discrete events). The contract already captures payment milestones (VUL-525) but the link from those to work-package progress is not modelled. Owner: Anton + Mladjan (project-based invoicing); relates to VUL-318. NL residential billing often uses fixed milestones (deposit / midpoint / completion).
- β
priceDeterminationsimplification (suggested by time-reporting alignment, D12). (Not reviewed in the 8 June PO session.) Optional refactor: replace the four-value enum (fixed | T&M | warranty | additional) withFixed | Billable+ orthogonal flags (isWarranty,isAdditional,tmCap?). Cleaner separation of "how is it priced" from "what kind of work is it". Adopt only if the refactor cost is low.
