Back to Roadmap

Phase 0: Master Spec Reconciliation

Releases 1.33 + 1.34 — Pre-Implementation Decision Document

Every decision, field name, lifecycle, precision rule, and convention that must be resolved in the spec files before a single line of code is written. Derived from the Cross-Release Continuity Audit (42 findings across 61 spec files).

Updated April 8, 2026 — Post Corey + Emma review meeting. Each item now reflects the final decision from the meeting. Badge colors: APPROVED/REVISED = final, proceed. DEFERRED = blocked or needs more info. SKIPPED = explicitly rejected. Items A5/A9 blocked on PO v1 PR merge. Items A10/A11 blocked on Emma MAT-7 review. Item B1 blocked on Emma spec review. Item B2 needs Chris review.
37
Resolved
Approved, revised, or partial — ready to implement
6
Deferred
Blocked on PR merge, plan review, or Chris review
2
Skipped
Explicitly rejected — no action needed
1
Needs Research
Direction agreed, details TBD
Total Items
48
Across 8 categories
Spec Files Affected
55
16 appello-sdlc • 25 appello-roadmap • 14 appello
Audit Findings Mapped
42
4 Critical • 16 Should Fix • 17 Nice to Have • 5 Resolved
Implementation Phases
7
Every phase depends on Phase 0
14 A: Spec Reconciliation
4 B: Model Consolidation
3 C: PriceBookImport
4 D: Decimal Precision
4 E: Permissions
1 F: Notifications
12 G: Conventions
3 H: Shared Services
File Inventory (55 files across 3 repos) Reference
RepoPathTypeDomain
appello-sdlc (16 files)
appello-sdlcdocs/uis-15-equipment-module.htmlUISEquipment
appello-sdlcdocs/uis-15a-equipment-job-pl.htmlUISEquipment P&L
appello-sdlcdocs/uis-15b-equipment-invoicing.htmlUISEquipment Invoicing
appello-sdlcdocs/uis-15c-equipment-variable-rates.htmlUISEquipment Rates
appello-sdlcdocs/uis-15d-equipment-maintenance-triggers.htmlUISMaintenance
appello-sdlcdocs/uis-15e-equipment-search-mobile.htmlUISEquipment Mobile
appello-sdlcdocs/uis-15f-daily-equipment-checklist.htmlUISEquipment Checklist
appello-sdlcdocs/uis-15g-equipment-maintenance-scheduling.htmlUISMaintenance
appello-sdlcdocs/uis-15h-equipment-timesheet-capture.htmlUISEquipment Time
appello-sdlcdocs/plans/uis-15a-equipment-job-pl.htmlPlanEquipment P&L
appello-sdlcdocs/plans/uis-15b-equipment-invoicing.htmlPlanEquipment Invoicing
appello-sdlcdocs/plans/uis-15c-equipment-variable-rates.htmlPlanEquipment Rates
appello-sdlcdocs/plans/uis-15d-equipment-maintenance-triggers.htmlPlanMaintenance
appello-sdlcdocs/plans/uis-15e-equipment-search-mobile.htmlPlanEquipment Mobile
appello-sdlcdocs/plans/uis-15g-equipment-maintenance-scheduling.htmlPlanMaintenance
appello-sdlcdocs/plans/uis-15h-equipment-timesheet-capture.htmlPlanEquipment Time
appello-roadmap (25 files)
appello-roadmapplans/LEM-1-labor-billable-rates.htmlPlanLEM
appello-roadmapplans/LEM-2-equipment-billable-rates.htmlPlanLEM
appello-roadmapplans/WO-2-line-item-tables.htmlPlanWork Orders
appello-roadmapplans/WO-3a-schema-form-foundation.htmlPlanWork Orders
appello-roadmapplans/WO-3b-line-item-editors.htmlPlanWork Orders
appello-roadmapplans/WO-3d-wo-property-settings.htmlPlanWork Orders
appello-roadmapplans/WO-4-detail-page.htmlPlanWork Orders
appello-roadmapplans/WO-5-tm-log.htmlPlanWork Orders
appello-roadmapplans/WO-6-mobile-list-detail.htmlPlanWork Orders
appello-roadmapplans/WO-7-conversion.htmlPlanWork Orders
appello-roadmapplans/WO-8a-stepped-form-wizard.htmlPlanWork Orders
appello-roadmapplans/WO-8b-collect-signatures.htmlPlanWork Orders
appello-roadmapplans/MAT-0-supply-chain-model.htmlPlanMaterial
appello-roadmapplans/MAT-1-customer-po-entity.htmlPlanPO
appello-roadmapplans/MAT-2a-lem-consumption-desktop.htmlPlanMaterial
appello-roadmapplans/MAT-2b-lem-consumption-mobile.htmlPlanMaterial
appello-roadmapplans/MAT-3-price-book-import.htmlPlanProcurement
appello-roadmapplans/MAT-4-mobile-material-requests.htmlPlanProcurement
appello-roadmapplans/MAT-5-supplier-po.htmlPlanProcurement
appello-roadmapplans/MAT-6-receiving-workflow.htmlPlanOperations
appello-roadmapplans/MAT-7-inventory-management.htmlPlanOperations
appello-roadmapplans/MAT-8-vendor-credits.htmlPlanOperations
appello-roadmapplans/MAT-9-discount-management.htmlPlanProcurement
appello-roadmaprelease-1.33-timeline.htmlTimelineRelease 1.33
appello-roadmaprelease-1.34-timeline.htmlTimelineRelease 1.34
appello (14 files)
appello.cursor/plans/uis-17-material-management.htmlUISMaterial
appello.cursor/plans/uis-25-purchase-order-management.htmlUISPO
appello.cursor/plans/uis-25a-customer-po-management.htmlUISCustomer PO
appello.cursor/plans/uis-25b-supplier-po-receiving.htmlUISSupplier PO
appelloplan/clearstory-tm-workorders/uis/uis-20-tm-work-orders.htmlUISWork Orders
appelloplan/clearstory-tm-workorders/uis/uis-20a-job-type-config.htmlUISJob Config
appelloplan/clearstory-tm-workorders/uis/uis-20b-line-item-tables.htmlUISLine Items
appelloplan/clearstory-tm-workorders/uis/uis-20c-create-edit-form.htmlUISWO Form
appelloplan/clearstory-tm-workorders/uis/uis-20d-detail-page.htmlUISWO Detail
appelloplan/clearstory-tm-workorders/uis/uis-20e-tm-log.htmlUISWO Log
appelloplan/clearstory-tm-workorders/uis/uis-20f-co-log.htmlUISCO Log
appelloplan/clearstory-tm-workorders/uis/uis-20g-conversion.htmlUISConversion
appelloplan/clearstory-tm-workorders/uis/uis-20h-mobile-creation.htmlUISMobile WO
Category A: Spec Reconciliation 14 Items • CRITICAL-5, 6, 7, 8
A1 Fix 33 field name conflicts — adopt Plan (Emma’s) naming as canonical SKIPPED

Decision (April 8 Meeting)

SKIPPED — Keep verbose field names. Emma: supplierCompanyId is clearer than supplierId, expectedDeliveryDate is clearer than deliveryDate (you may need both expected and actual). Do not rename fields for brevity. The spec’s more descriptive naming is preferred over shorter Prisma-style conventions.
No file changes needed
A2 Fix 6 UIS xxxById fields to xxxByUserId CRITICAL-5

Decision

Systematic UIS bug. The codebase has 20+ instances of the xxxByUserId pattern (createdByUserId, approvedByUserId, submittedByUserId). UIS uses xxxById which is ambiguous — “by” what? Always suffix with UserId.

Fields to Rename

  • createdByIdcreatedByUserId
  • updatedByIdupdatedByUserId
  • approvedByIdapprovedByUserId
  • submittedByIdsubmittedByUserId
  • receivedByIdreceivedByUserId
  • requestedByIdrequestedByUserId
UIS Files to Update
  • uis-17-material-management.html
  • uis-25-purchase-order-management.html
  • uis-25b-supplier-po-receiving.html
A3 Remove UIS Product model — use existing Material + MaterialGroup CRITICAL-5

Decision

Do NOT build a Product model. It conflicts with the existing Material + MaterialGroup + MaterialAttribute architecture in production. Plan correctly extends these with ManufacturerProduct (manufacturer part numbers) and DistributorPrice (supplier pricing).
Files to Update
  • uis-17-material-management.html — Remove/rewrite all Product model references to use Material and ManufacturerProduct
A4 Remove @default(cuid()) and @default(nanoid()) from UIS specs CRITICAL-5

Decision

IDs are app-generated, not schema defaults. cuid() generates 25-char IDs but the column is VarChar(21). The codebase generates IDs in application code using nanoid. Remove all @default() from ID fields in UIS specs.
UIS Files to Update
  • uis-17-material-management.html — Remove @default(cuid()) from model definitions
  • uis-25-purchase-order-management.html — Remove @default(nanoid())
A5 Change UIS Prisma enums to const-string pattern for PO and MaterialOrder status DEFERRED

Decision (April 8 Meeting)

DEFERRED — PO v1 model exists in 1.32 PR (not yet merged). Emma has already built a version 1 Purchase Order model in Release 1.32 that hasn’t been merged yet. Do not modify PO status patterns until that PR is merged and plans are updated to reflect the actual implementation. Status storage approach (PropertyValue vs const-string) will be decided after merge — see A8/A9.
Blocked until PO v1 PR merges into main
A6 Add instanceId to ALL new models in Plan specs SKIPPED

Decision (April 8 Meeting)

SKIPPED — Appello is single-tenant per deployment. Emma: “We’re not multi-tenanted. There’s only one instance.” Do NOT add instanceId to any new models. Each Appello deployment serves exactly one company. The existing instanceId on legacy models is historical and does not need to be propagated to new models.
No file changes needed
A7 Add workflow timestamp fields to Plan specs PARTIAL

Decision (April 8 Meeting)

Approved for VendorCredit + MaterialConsumption. PO timestamps deferred (see A5). It makes sense to track submitted and approved times and users for approval workflows. Model after Expenses pattern (submitted, approved, rejected). PO-specific timestamps are deferred until the PO v1 PR merges.

Fields to Add Per Entity

  • MaterialConsumptionEntry: submittedAt, submittedByUserId, approvedAt, approvedByUserId
  • VendorCredit: approvedAt, approvedByUserId, appliedAt, appliedByUserId, voidedAt, voidedByUserId, creditDate
  • PurchaseOrder: deferred until PO v1 PR merges (see A5)
Plan Files to Update (Emma)
  • plans/MAT-2a-lem-consumption-desktop.html — Add consumption approval timestamps
  • plans/MAT-8-vendor-credits.html — Add credit workflow timestamps
  • plans/MAT-5-supplier-po.html — Deferred (PO v1 PR)
A8 Finalize VendorCredit lifecycle: approval workflow via PropertyValue REVISED

Decision (April 8 Meeting)

Use PropertyValue storage pattern (not const-string). Model approval workflow after Expenses: Submitted → Approved → Rejected. Status values are user-configurable via PropertyValue settings, enabling Kanban boards. Emma: “For most of the status type stuff, we use property values.” The implied ordering of property values serves as the expected workflow sequence, but there is no enforced state machine — users can rename or reorder statuses. Add vendorCredit.approve RBAC permission. Only Approved credits can be applied. Void reverses the job cost adjustment.
Files to Update
  • plans/MAT-8-vendor-credits.html (Emma) — Use PropertyValue for status, add approval workflow, add 6 audit fields, add RBAC gate
A9 Finalize PO lifecycle: PropertyValue-based status with user-configurable values DEFERRED + REVISED

Decision (April 8 Meeting)

DEFERRED + REVISED — Use PropertyValue storage (not const-string). Full lifecycle decisions deferred until PO v1 PR merges (see A5). When ready, status values will be PropertyValue-based for user configurability and Kanban support. Emma: “Maybe they don’t want to call it draft, submitted, approved. Maybe they don’t have partially received.” PropertyValue gives customers control over their own status labels and ordering. Default seed values will be provided but are fully editable. Kanban boards are already set up to work with property values.
Blocked until PO v1 PR merges into main (see A5)
A10 Finalize InventoryMovement architecture: adopt InventoryItem FK (bin card) pattern DEFERRED

Decision (April 8 Meeting)

DEFERRED — Emma needs to read the full MAT-7 plan before deciding. InventoryItem FK (bin card) pattern is recommended but needs validation against the actual implementation approach once Emma has reviewed the full inventory management plan. Revisit when inventory management is actively in development.
Pending Emma’s review of MAT-7 plan
A11 Add transferGroupId, costCodeId to InventoryMovement DEFERRED

Decision (April 8 Meeting)

DEFERRED — Too detailed for features that haven’t been built yet. These fields make sense conceptually but the inventory management module is not yet in active development. Revisit transferGroupId and costCodeId when inventory management is actively in scope and A10 has been resolved.
Revisit when inventory management (A10) is resolved
A12 Line items DO get isArchived + isDeleted — parent cascades to children REVISED

Decision (April 8 Meeting)

Line items DO get their own isArchived + isDeleted fields. This is the existing Appello convention per Emma: “We generally put isArchived on line items. We do isArchived and isDeleted so that we can… if you archive the parent, it will archive the children, but we still have them.” Parent archive/delete cascades to children via application logic. This applies to: PurchaseOrderLineItem, ReceiptLineItem, VendorCreditLineItem, MaterialRequestItem, WorkOrderLaborItem, WorkOrderMaterialItem, WorkOrderEquipmentItem.
Convention — ensure all line item models include isArchived + isDeleted fields
A13 Decide dateOfWork vs multi-date Work Orders — use sub-model, not JSON NEEDS RESEARCH

Decision (April 8 Meeting)

NEEDS RESEARCH — Multi-date WOs confirmed needed, but JSON rejected. Corey confirms work orders will span multiple days: “I’m sure the work order will start on a day and then we’re going to have to come back.” Emma: “JSON is not really filterable. If we just store the 10th, 12th, and 13th, we can’t easily filter. We’d prefer it as a field or a submodel — like work order dates.” Direction: Use a sub-model (e.g., WorkOrderDate) instead of workDates Json for filterability. More research needed on how multi-date WO functionality should work end-to-end.
Files Affected by Decision
  • plans/WO-3a-schema-form-foundation.html — Uses dateOfWork
  • uis-20h-mobile-creation.html — Uses workDates Json — needs to change to sub-model
A14 Decide inventory costing method: weighted average for V1 NTH-13

Decision

Weighted average for V1. FIFO adds lot tracking complexity not needed for construction trade contractors. Weighted average: unit cost on InventoryItem is recalculated on each receipt as (existing qty × existing cost + received qty × receipt cost) / total qty.
Files to Update
  • plans/MAT-7-inventory-management.html (Emma) — Document costing method explicitly
Category B: Model Consolidation Decisions 4 Items • SF-9, 10, 13, NTH-10
B1 Consolidate WorkOrderSigner into SignatureRequest DEFERRED

Decision (April 8 Meeting)

DEFERRED — Emma needs to review the specs. Emma: “SignatureRequest is, I think it’s a new model that I made for the work order signing, but I don’t remember the differences between the two off the top of my head.” The consolidation may still make sense but needs Emma to re-read both specs and compare the two models before a final decision.
Pending Emma’s review of WO-8b plan
B2 Eliminate WorkOrderOtherItem — use 3 line item types with nullable FK REVISED • NEEDS CHRIS

Decision (April 8 Meeting)

Do NOT create WorkOrderOtherItem as a separate model. Each of the 3 existing line item types (WorkOrderLaborItem, WorkOrderMaterialItem, WorkOrderEquipmentItem) can function as an “other” item when the tied relationship FK is null. Emma: “Any of those three should be able to function as a work order other item that has no tied relationship and has a description and a value and a quantity rather than having a separate model.” If you give a labor ID or equipment ID, you don’t need the description. If you don’t pick a linked entity, you describe it freeform. Needs Chris review before finalizing.
Files to Update
  • uis-20h-mobile-creation.html — Remove WorkOrderOtherItem model
  • plans/WO-2-line-item-tables.html — Document nullable FK = ad-hoc item pattern
  • plans/WO-8a-stepped-form-wizard.html — Merge Step 5 (“Other”) into the 3 existing steps
B3 Add manufacturerProductId to MaterialConsumptionEntry GraphQL type SF-13

Decision

Add to GraphQL type and creation input. Without it, consumption entries can’t resolve to a specific supplier price, making cost computation ambiguous when a material has multiple manufacturer products at different prices.
Files to Update
  • uis-17-material-management.html — Add manufacturerProductId to GraphQL consumption type
  • plans/MAT-2a-lem-consumption-desktop.html — Already has it in Prisma, confirm in GraphQL
B4 Scope maintenance models — drop only MaintenanceScheduleTemplate REVISED

Decision (April 8 Meeting)

Keep MaintenanceThreshold + MaintenanceLog + MaintenanceSchedule + DowntimeRecord in v1 scope. Drop only MaintenanceScheduleTemplate. Emma replaced the template concept with form attachments: “I got rid of that, and I said you can tie safety forms to it — like these are the four forms that you do when you do an oil change.” DowntimeRecord is important for tracking equipment availability metrics (e.g., VOR — Vehicles Off Road). This removes 1 model from scope, not 3 as originally proposed.
Files to Update
  • docs/uis-15g-equipment-maintenance-scheduling.html — Remove MaintenanceScheduleTemplate only, keep Schedule + DowntimeRecord
  • docs/plans/uis-15g-equipment-maintenance-scheduling.html — Same
Category C: PriceBookImport Fixes 3 Items • SF-3, 4, 5
C1 PriceBookImport default status = "Pending" SF-3
“Pending” is correct. The file has been uploaded but background processing hasn’t started yet. UIS defaults to “Processing” which skips the queued state.
Files to Update
  • uis-17-material-management.html — Change default status from "Processing" to "Pending"
C2 PriceBookImport errorLog uses Json? not String? @db.Text SF-4
Use Json?. Structured error data (row number, column, error message per row) is more useful for displaying error tables in the UI than a raw text string.
Files to Update
  • uis-17-material-management.html — Change errorLog String? @db.Text to errorLog Json?
C3 PriceBookImport status set includes PartialSuccess SF-5
Full status set: Pending, Processing, Complete, PartialSuccess, Failed. UIS has PartialSuccess but omits Pending. Plan has Pending but omits PartialSuccess. Both are needed — PartialSuccess is essential for imports where some rows succeed and others fail.
Files to Update
  • uis-17-material-management.html — Add Pending to status set
  • plans/MAT-3-price-book-import.html — Add PartialSuccess to status set
Category D: Decimal Precision Standardization 4 Items • SF-1, SF-15

Canonical precision rules for all new models. These match the existing Prisma schema conventions (forecasts use Decimal(12,2), InvoiceLineItem uses Decimal(10,5) for quantity).

Value TypePrecisionRationaleExample Fields
Unit pricesDecimal(10, 2)Standard money precisionunitPrice, unitCost, listPrice, rate
QuantitiesDecimal(10, 4)Unit conversion safety (m→ft, kg→lb, 2.5 rolls)quantity, quantityReceived, quantityOnHand
Document totalsDecimal(12, 2)Match forecast pattern, supports $99Bamount, totalAmount, extendedPrice
PercentagesDecimal(5, 2)Standard percent precisiondiscountPercent, taxRate
D1 Change PurchaseOrder.amount from Decimal(10, 2) to Decimal(12, 2) SF-1
Files to Update
  • plans/MAT-1-customer-po-entity.html — Change amount precision
  • plans/MAT-5-supplier-po.html — Change amount precision
  • uis-25-purchase-order-management.html — Change amount precision
D2 Change VendorCredit.amount from Decimal(10, 2) to Decimal(12, 2) SF-1
Files to Update
  • plans/MAT-8-vendor-credits.html — Change totalAmount precision
D3 Change all quantity fields from Decimal(10, 2) to Decimal(10, 4) SF-15

Affected Models

  • PurchaseOrderLineItem.quantity
  • MaterialConsumptionEntry.quantity
  • MaterialRequestItem.quantity
  • ReceiptLineItem.quantityReceived
  • InventoryItem.quantityOnHand, minQuantity, maxQuantity
  • InventoryMovement.quantity
  • VendorCreditLineItem.quantity
Files to Update
  • All plans/MAT-*.html files with quantity fields
  • uis-17-material-management.html, uis-25b-supplier-po-receiving.html
D4 Align SQL CTE in Equipment P&L plan to Decimal(12, 2) SF-1
Equipment Prisma models already use Decimal(12, 2) (Emma’s Apr 7 fix), but the SQL CTE for P&L computation may still reference Decimal(10, 2) casts.
Files to Update
  • docs/plans/uis-15a-equipment-job-pl.html — Check SQL CTE casts
Category E: Permission Groups to Define 4 Items • SF-6, SF-12
Permission GroupviewcreateeditdeleteapproveSource
purchaseOrderSF-6
vendorCreditSF-6
pricingSF-12
inventorySF-6
E1purchaseOrder permission groupSF-6
POs involve financial commitments. approve permission gates the Submitted → Approved transition. view supports ALL/OWN/DEPARTMENT scoping.
Files to Update
  • plans/MAT-1-customer-po-entity.html, plans/MAT-5-supplier-po.html, uis-25-purchase-order-management.html, uis-25a-customer-po-management.html, uis-25b-supplier-po-receiving.html
E2vendorCredit permission groupSF-6
approve gates InReview → Approved. Credits directly affect job profitability.
Files to Update
  • plans/MAT-8-vendor-credits.html
E3pricing permission group (separate from material)SF-12
Discounts affect pricing across POs and P&L. A user who can edit materials shouldn’t necessarily be able to change discount percentages. Separate pricing: { view, edit } from material: { view, edit }.
Files to Update
  • plans/MAT-9-discount-management.html — Replace material.edit with pricing.edit
E4inventory permission groupSF-6
Inventory operations (receive, checkout, transfer, adjust) need dedicated access control separate from general material catalog management.
Files to Update
  • plans/MAT-7-inventory-management.html, uis-17-material-management.html
Category F: Notification Matrix 1 Item • SF-11
F1Define notifications for all new status transitionsSF-11
EventNotify WhoChannelCurrently Defined?
PO submitted for approvalApprovers (PO permission group)In-app + emailNo
PO approvedCreatorIn-appNo
PO sent to supplierCreator + job PMIn-appNo
Receiving completedPO creatorIn-appNo
Low stock alertInventory managersIn-app + emailNo
Consumption submittedApproversIn-appNo
Credit submitted for reviewApproversIn-app + emailNo
Credit approvedCreatorIn-appNo
Material request submittedOffice material managersIn-app + emailMAT-4
Material request fulfilledRequesterIn-appNo
Signature requestedSignerEmailWO-8b
Maintenance threshold reachedEquipment managersIn-app + emailUIS-15D
Files to Update
  • All Plan files for the 9 undefined notifications above need notification trigger definitions added
Category G: Cross-Cutting Conventions 12 Items • SF-7,8,14 + NTH-1 through 17

These are patterns/conventions that must be decided once and documented so every spec and every developer applies them consistently. They don’t require spec-level file changes — they’re documented here as the authoritative reference.

G1Mobile routes use job-sites/[id]/... patternSF-7
All mobile routes nest under /job-sites/[id]/. This matches the existing mobile app structure. Affects: material consumption (/job-sites/[id]/material-consumption/new), material requests (/job-sites/[id]/material-request/new), WO creation, equipment time. Fix inconsistent patterns like /material-requests/new and /jobs/[jobId]/....
Files to Update
  • uis-20h-mobile-creation.html, plans/WO-6-mobile-list-detail.html, plans/MAT-2b-lem-consumption-mobile.html, plans/MAT-4-mobile-material-requests.html
G2PDF templates go in Frontend DownloadablePDF/SF-8
Frontend DownloadablePDF pattern. This matches existing Invoice, Estimate, and Work Order PDF patterns. UIS incorrectly placed PO PDF in the API (apps/api/src/templates/). Correct path: apps/frontend/src/components/DownloadablePDF/PurchaseOrder/SupplierPO.tsx.
Files to Update
  • uis-25b-supplier-po-receiving.html — Fix PDF template location
G3Discount precedence: DistributorDiscount > DistributorPrice.discountPercentSF-14
Documented cascade: DistributorDiscount (managed entity with date ranges and scope) takes precedence when present. DistributorPrice.discountPercent (per-row import value from CSV) is the fallback. Discount resolution: Product-specific > Category > Global.
Files to Update
  • plans/MAT-9-discount-management.html — Document precedence cascade explicitly
G4Computed field pattern: resolver-computed for display, stored for aggregationNTH-1
Convention: Use resolver-computed fields for display-only values (effectivePrice, extendedTotal). Use stored fields for values that need to be queried, sorted, or aggregated (PurchaseOrder.amount, VendorCredit.totalAmount). Stored values are set on create/update by the resolver.
G5All list views support CSV + PDF exportNTH-2
Convention: Every new ListTableView includes an export action with CSV and PDF options. Use the existing export button pattern from Job List. Add to acceptance criteria for: WO Log, CO Log, PO List, Material Request Inbox, Inventory Stock Levels, Vendor Credit List.
G6Standardize EmptyState componentNTH-3
Convention: All new list views and detail sections use a reusable EmptyState component from @useanzen/ui with: icon (contextual), message, and action button (e.g., “Create First PO”). Check if EmptyState already exists in the UI library before building.
G7Batch operations return { success: [...], failed: [...] }NTH-4
Convention: All batch mutations (approveEquipmentTimeEntries, approveMaterialConsumptionEntries, upsertWorkOrderLineItems) return { success: [ids], failed: [{ id, error }] } with per-item error messages. Use prisma.$transaction() for all-or-nothing when appropriate.
G8Audit trail fields for financial entitiesNTH-5
Convention: All financial entities (PO, VendorCredit, MaterialConsumptionEntry) include: createdAt, updatedAt, createdByUserId, updatedByUserId. Entities with approval add: submittedAt, submittedByUserId, approvedAt, approvedByUserId. Use the existing AuditEntry pattern for change history on entities where it matters (v1.1).
G9Three-way match: defer to v1.1NTH-11
Decision: Ship basic receiving (partial/complete receipt tracking) for v1. Full three-way match (PO qty vs received qty vs invoiced qty) is deferred to v1.1. Do not design match UI or computed columns for v1.
G10Shared conversion mutation patternNTH-14
Convention: Entity conversion mutations follow the pattern: convert[Source]To[Target](sourceId: ID!): [Target]!. Pre-fills target from source line items. Used by: WO → Invoice (convertWorkOrderToInvoice), WO → Change Order (convertWorkOrderToChangeOrder). Future: Material Request → PO.
G11Offline strategy per mobile featureNTH-16
Decision: Offline-first: Material consumption (MAT-2b), WO creation (WO-8). Online-only: Material requests (MAT-4), Equipment time (UIS-15H), Equipment search (UIS-15E), Mobile receiving (MAT-6). Offline features queue mutations for sync when connectivity returns.
G12All new list queries use GetListOptions with paginationNTH-17
Convention: All new list queries accept GetListOptions input type with pagination, sorting, and filtering. Construction companies accumulate thousands of records per entity. No unbounded arrays. Applies to: purchaseOrders, materialConsumptionEntries, inventoryMovements, vendorCredits, materialRequests, workOrders.
Category H: Shared Services to Spec Before Implementation 3 Items • NTH-7, 8, 9
H1AutoNumberService for WO, CPO, SPO, MR, VC, RCV prefixesNTH-9
Spec: Centralized auto-numbering service at apps/api/src/models/AutoNumberService.ts. Input: { prefix: string, instanceId: string }. Output: next number string (e.g., WO-001, SPO-0042). Uses a counter table (AutoNumberCounter model) with @@unique([instanceId, prefix]). Handles padding, collision prevention via prisma.$transaction(). Configurable pad width per prefix.

Prefixes

  • WO — Work Orders
  • CPO — Customer Purchase Orders
  • SPO — Supplier Purchase Orders
  • MR — Material Requests
  • VC — Vendor Credits
  • RCV — Receiving Receipts
H2RepeatingLineItemEditor base componentNTH-8
Spec: Shared React component at packages/ui/src/components/RepeatingLineItemEditor/. Props: columns (array of column defs with render, width, label), rows (data), onAdd, onRemove, onUpdate, showTotals, totalColumns. Used by: WO Labor Editor, WO Material Editor, WO Equipment Editor, PO Line Item Editor, VendorCredit Line Item Editor. Build before any feature-specific editors.
H3JobFinancialSummaryService plugin/strategy refactorNTH-7
Spec: Refactor JobFinancialSummaryService from a monolithic computation to a plugin/strategy pattern. Each cost source registers a “cost provider” function with signature: (jobId: string) => Promise<{ label: string, revenue: Decimal, cost: Decimal }>. The service aggregates all registered providers. Prevents merge conflicts when 5 features modify it in parallel.

Cost Providers to Register

  • Equipment time costs (UIS-15A)
  • Material consumption costs (MAT-2a)
  • PO committed costs (MAT-6)
  • Inventory checkout costs (MAT-7)
  • Vendor credit adjustments (MAT-8, negative)
Status Lifecycle Final State Machines 8 Entities

These are the canonical, final status lifecycles for every entity with a workflow. Each spec file must match these exactly.

Work Order (PropertyValue) Draft Submitted AwaitingSignature Signed Approved Invoiced Void PurchaseOrder (Supplier) (PropertyValue) [DEFERRED — awaiting PO v1 PR merge] Draft Submitted Approved PartiallyReceived FullyReceived Closed Cancelled Note: Status values are user-configurable PropertyValues. Default seed values shown above. PurchaseOrder (Customer) (PropertyValue) [DEFERRED — awaiting PO v1 PR merge] Active FullyInvoiced Closed Cancelled MaterialConsumptionEntry (const-string) Draft Submitted Approved MaterialRequest (const-string) Submitted Acknowledged InProgress Fulfilled Cancelled VendorCredit (PropertyValue) Draft InReview Approved Applied Void (reverses job cost adjustment) PriceBookImport (const-string) Pending Processing Complete | PartialSuccess | Failed EquipmentTimeEntry (Prisma enum + TypeScript const) Pending Approved | Rejected
Cross-Reference: Phase 0 Items → Implementation Phase They Unblock Validation

Every implementation phase has been validated. Items marked with * are DEFERRED or SKIPPED per the April 8 meeting. Deferred items do not block the phase — they will be resolved when their blockers clear (PO v1 PR merge, Emma plan review, Chris review).

Implementation PhaseScopePhase 0 Items Required
Phase 1: LEM Foundation TradeLevelRate mod, 3 equipment rate models, desktop rate UI D4
Phase 2: T&M Work Orders 8+ models, desktop CRUD/log/conversion, mobile wizard, WO PDF A1*, A2, A6*, A7, A12, A13*, B1*, B2*, G1, G2, G4, G5, G6, G7, G10, G11, G12, H1, H2
Phase 3: Equipment Module 8 models, P&L, invoicing, maintenance, mobile QR/inspection B4, D4, G1, G4, G5, G6, G12
Phase 4: Material Foundation 4 models, data migration, supply chain settings, consumption A1*, A2, A3, A4, A5*, A6*, A7, A9*, B3, C1–C3, D1, D3, E1, F1, G1, G4, G5, G6, G8, G11, G12, H1, H3
Phase 5: Procurement 4 models, price book import, material requests, supplier PO, discounts A5*, C1–C3, D3, E1, E3, G2, G3, G5, G6, G12, H1, H2
Phase 6: Operations 5 models, receiving, inventory, vendor credits, 3 JFS integrations A8, A10*, A11*, A14, D1, D2, E2, E4, F1, G4, G5, G6, G7, G8, G9, G12, H2, H3
Phase 7: Cross-Cutting Shared components, services, mobile routes, pagination, testing G1, G5, G6, G7, G10, G12, H1, H2, H3
LEM-3 Material Charge-Out Pricing (Added April 8, 2026): During codebase investigation it was confirmed that existing material pricing (MaterialAttributePrice, DistributorPrice) only covers cost (what the contractor pays). There was no mechanism for charge-out (what the customer pays). This gap has been closed by LEM-3: Material Billable Rates — a three-tier cascade (MaterialGroup.defaultMarkupPercentMaterial.sellPriceOverride/markupPercentOverrideJob.materialMarkupPercent) that auto-calculates sell prices. All downstream specs (WO-2, WO-3, WO-7, MAT-2a) have been updated to use unitPrice (sell) and costPrice (cost) instead of unitCost.
Who Does What Ownership
OwnerFilesItemsNotes
Emma (Plan specs) appello-roadmap/plans/MAT-*.html, WO-*.html, LEM-*.html A7, A8, A14, B2, B3, C3, D1, D2, D3, E1–E4, F1, G3 A9/A11 deferred, A6 skipped
Corey/AI (UIS specs) appello/.cursor/plans/uis-*.html, appello/plan/clearstory-tm-workorders/uis/*.html A2, A3, A4, B2, C1, C2, D3, G1, G2 A1/A6 skipped, A5/A10 deferred, B1 deferred
Emma (Equipment specs) appello-sdlc/docs/*.html, appello-sdlc/docs/plans/*.html B4, D4 B4 revised: keep Schedule + Downtime, drop Template only
Conventions (this doc) No file changes — documented here A12, A13, G4–G12, H1–H3 A12 revised (line items DO get isArchived). A13 needs research.
Blocked / Deferred Various A5, A9, A10, A11, B1 A5/A9: PO v1 PR. A10/A11: Emma MAT-7 review. B1: Emma WO-8b review.
Needs Chris Review WO line item specs B2 3 line item types with nullable FK pattern tentatively approved