Back to Roadmap

Cross-Release Continuity Audit

Releases 1.33 + 1.34 — Appello Platform

A systematic audit of 61 spec files across two releases, identifying data model conflicts, naming inconsistencies, architectural splits, and integration gaps that must be resolved before implementation begins.

Revision 2 — April 8, 2026: Re-analyzed against Emma Mann's April 7 equipment spec updates (9,652 lines changed across 18 files). Five critical findings (CRITICAL-1, 2, 3, 4, 9) now RESOLVED.
Revision 3 — April 8, 2026: Deep analysis of remaining 4 open criticals. Cross-referenced 15 spec files, 33 field name conflicts, production Prisma schema conventions, and industry ERP patterns (Procore, Sage, SAP, Oracle). Each finding now has a concrete resolution. Key conclusions: Plan (Emma's) is canonical for field naming (wins 19/25 cases). UIS ledger approach replaced by Plan's InventoryItem FK (industry-universal pattern). VendorCredit needs approval workflow added to Plan. PO lifecycle merged from both specs.
Features Analyzed
33
12 LEM/Equipment • 10 Work Order • 11 Material
Findings
42
4 Open Critical • 5 Resolved • 16 Should Fix • 17 Nice to Have
Spec Files Analyzed
61
29 UIS specs • 30 plans • 9 equipment specs
New Prisma Models
25+
Across both releases
4 Critical (Open)
5 Resolved
16 Should Fix
17 Nice to Have
Capability Evolution Story Narrative

Release 1.33 Phase 1 (LEM Foundation) establishes the rate data models that everything downstream depends on. Labor billable rates are added via a new billableValue field on the existing TradeLevelRate model, giving each trade level a customer-facing rate alongside the internal cost rate. Equipment rates get a more ambitious treatment: a polymorphic EquipmentRateValue system (with EquipmentRateLabel and EquipmentRateSchedule) lets companies define rate cards at the equipment, category, or global level with per-unit pricing (hourly, daily, weekly, monthly, yearly). Material billable rates (LEM-3) add a three-tier pricing cascade (MaterialGroup default markup → Material override → Job override) that resolves what to charge the customer for materials, completing the cost vs. billable separation across all three LEM categories. Together, these form the pricing backbone that Work Orders, Equipment P&L, and Material Consumption all consume.

Release 1.33 Phase 2 (T&M Work Orders) is the hero feature of the release. Work Orders are implemented as a type of Job (not a new entity), using the existing PropertyValue system for jobClassification to distinguish T&M jobs from standard jobs. Each Work Order gets structured line items — WorkOrderLaborItem, WorkOrderMaterialItem, and WorkOrderEquipmentItem — that consume the LEM rates for auto-pricing. The feature includes a full lifecycle: draft creation, line item editing, a signature collection workflow (with the new polymorphic Signature and SignatureRequest models), desktop and mobile form experiences, a T&M Work Order Log dashboard, and conversion pipelines that transform approved Work Orders into Invoices (via JobBill) or Change Orders (via EstimateChangeOrderRecord).

Release 1.33 Phases 3–4 (Equipment Module) round out the release by adding financial and operational depth to the Equipment domain. Equipment time entry (EquipmentTimeEntry) captures hours used per job, feeds into an Equipment P&L report, and supports bulk approval workflows. Equipment invoicing generates customer-facing bills from approved time entries using the LEM-2 rate resolution engine. On the operations side, a maintenance subsystem introduces MaintenanceThreshold, MaintenanceLog, MaintenanceSchedule, and MaintenanceScheduleTemplate for threshold-based and scheduled maintenance tracking. Equipment scheduling with a Gantt-style calendar, inspection checklists tied to form submissions, and mobile QR code scanning (Equipment.qrCode) complete the module.

Release 1.34 Phase 1 (Material Foundation) extends the material domain into a proper supply chain. The three-level model — Material (internal catalog) → ManufacturerProduct (manufacturer part numbers) → DistributorPrice (supplier-specific pricing) — enables multi-supplier price comparison. A first-class PurchaseOrder entity replaces the legacy Job.purchaseOrder string field, supporting both customer-facing POs and supplier POs with shared schema. MaterialConsumptionEntry logs material usage per job per day on both desktop and mobile, feeding directly into the Job Financial Summary. This phase is deliberately foundational — it creates the entities that procurement and operations features consume.

Release 1.34 Phases 2–3 (Procurement & Operations) complete the material lifecycle. Price book import (PriceBookImport) allows bulk CSV/Excel upload of distributor pricing with column mapping and validation. Mobile material requests (MaterialRequest, MaterialRequestItem) create a field-to-office workflow where workers request materials and office staff triages and fulfills. Discount management (DistributorDiscount) supports product, category, and global discounts with date ranges. Supplier purchase orders get a full CRUD lifecycle with PDF generation and sending. Receiving (PurchaseOrderReceipt, ReceiptLineItem) tracks partial and complete deliveries with three-way match capability. Inventory management introduces InventoryLocation, InventoryItem, and an append-only InventoryMovement ledger for stock tracking with QR-based mobile lookup. Vendor credits (VendorCredit, VendorCreditLineItem) handle returns and credit memos. Every financial entity feeds into JobFinancialSummaryService, creating a single source of truth for job-level profitability.

Data Model Map 25+ Models
Model Type ID Pattern Key Fields Relations
1.33 — LEM Foundation (Phase 1)
TradeLevelRate MOD VarChar(21) +billableValue Decimal(10,2) TradeLevel, Instance
EquipmentRateLabel NEW VarChar(21) name, isDefault, instanceId Instance, EquipmentRateSchedule[]
EquipmentRateSchedule NEW VarChar(21) labelId, ratePer, effectiveDate EquipmentRateLabel, EquipmentRateValue[]
EquipmentRateValue NEW VarChar(21) referenceModel, referenceId, value Decimal EquipmentRateSchedule (polymorphic: Equipment | EquipmentCategory | Global)
1.33 — T&M Work Orders (Phase 2)
WorkOrderLaborItem NEW VarChar(21) jobId, tradeLevelId, submittedDuration Int, billableRate Job, TradeLevel, User
WorkOrderMaterialItem NEW VarChar(21) jobId, materialId?, quantity, unitCost Job, Material?
WorkOrderEquipmentItem NEW VarChar(21) jobId, equipmentId, duration Int, ratePer Job, Equipment
Signature NEW VarChar(21) referenceModel, referenceId, signatureData Text, signedAt User (polymorphic: WorkOrder | other)
SignatureRequest NEW VarChar(21) referenceModel, referenceId, requestedEmail, status User?, Signature?
WorkOrderOtherItem NEW VarChar(21) jobId, name, qty, unitPrice Job (mobile spec UIS-20h)
WorkOrderSigner NEW VarChar(21) jobId, name, role, order Int Job (mobile spec UIS-20h)
PropertyValue MOD VarChar(21) +jobClassification propertyName Instance, PropertyOption
Job MOD VarChar(21) +dateOfWork DateTime? @db.Date Existing relations
EstimateChangeOrderRecord MOD VarChar(21) +statusChangedAt DateTime? Existing relations
EstimateChangeOrderRecordValue MOD VarChar(21) accountItemId nullable Existing relations
1.33 — Equipment Module (Phases 3–4)
EquipmentTimeEntry NEW Int autoincrement() equipmentId, jobId, hours Decimal, status Enum Equipment, Job, User
EquipmentBillingConfig NEW Int autoincrement() equipmentId, billingType, defaultRate Equipment
MaintenanceThreshold NEW Int autoincrement() equipmentId, metric, thresholdValue Equipment, MaintenanceLog[]
MaintenanceLog NEW Int autoincrement() equipmentId, type, description, cost Decimal Equipment, MaintenanceThreshold?
MaintenanceSchedule NEW Int autoincrement() equipmentId, frequency, nextDue Equipment, MaintenanceScheduleTemplate?
MaintenanceScheduleTemplate NEW Int autoincrement() name, frequency, tasks Json Instance, MaintenanceSchedule[]
DowntimeRecord NEW Int autoincrement() equipmentId, startDate, endDate, reason Equipment
EquipmentRate NEW Int autoincrement() equipmentId, ratePer, rate Decimal, effectiveFrom Equipment (UIS-15C)
EquipmentRateOverride NEW Int autoincrement() equipmentRateId, companyId, overrideRate EquipmentRate, Company (UIS-15C)
EquipmentAttachmentRate NEW Int autoincrement() equipmentId, attachmentId, rate Decimal Equipment, Equipment (UIS-15C)
Equipment MOD VarChar(21) +qrCode String? Existing relations
EquipmentCategory MOD VarChar(21) +defaultInspectionFormId String? Existing + FormTemplate?
1.34 — Material Foundation (Phase 1)
ManufacturerProduct NEW VarChar(21) materialId, manufacturerId, partNumber, modelNumber Material, Company (Manufacturer)
DistributorPrice NEW VarChar(21) manufacturerProductId, distributorId, listPrice, discountPercent? ManufacturerProduct, Company (Distributor)
PurchaseOrder NEW VarChar(21) poNumber, type (Customer|Supplier), companyId, status Company, Job, PurchaseOrderLineItem[]
PurchaseOrderLineItem NEW VarChar(21) purchaseOrderId, materialId?, quantity, unitPrice PurchaseOrder, Material?
MaterialConsumptionEntry NEW VarChar(21) jobId, materialId, quantity, dateOfWork, status Job, Material, User
1.34 — Procurement & Operations (Phases 2–3)
PriceBookImport NEW VarChar(21) distributorId, fileName, status, errorLog Company (Distributor), User
MaterialRequest NEW VarChar(21) jobId, requestNumber, status, priority Job, User, MaterialRequestItem[]
MaterialRequestItem NEW VarChar(21) materialRequestId, materialId?, quantity, description MaterialRequest, Material?
DistributorDiscount NEW VarChar(21) distributorId, scope, discountPercent, validFrom/To Company, Material?, MaterialGroup?
PurchaseOrderReceipt NEW VarChar(21) purchaseOrderId, receiptNumber, receivedAt, receivedByUserId PurchaseOrder, User, ReceiptLineItem[]
ReceiptLineItem NEW VarChar(21) receiptId, poLineItemId, quantityReceived PurchaseOrderReceipt, PurchaseOrderLineItem
InventoryLocation NEW VarChar(21) name, type (Warehouse|JobSite|Vehicle), address Instance, InventoryItem[]
InventoryItem NEW VarChar(21) materialId, locationId, quantityOnHand, unitCost Material, InventoryLocation
InventoryMovement NEW VarChar(21) materialId, type, quantity, fromLocationId?, toLocationId? Material, InventoryLocation (append-only ledger)
VendorCredit NEW VarChar(21) creditNumber, supplierId, status, totalAmount Company, PurchaseOrder?, VendorCreditLineItem[]
VendorCreditLineItem NEW VarChar(21) vendorCreditId, materialId?, quantity, unitPrice VendorCredit, Material?
Financial Flow Map Cost Flow
Rate Foundation (1.33) ├── TradeLevelRate.billableValue → WO Labor Line Items ├── EquipmentRateValue → WO Equipment Line Items └── resolveMaterialSellPrice (LEM-3 cascade) → WO Material Line Items (unitPrice + costPrice) Work Orders (1.33) ├── WO → Invoice → generates JobBill └── WO → Change Order → generates EstimateChangeOrderRecord Equipment Financial (1.33) ├── EquipmentTimeEntry → Equipment P&L (UIS-15A) └── Approved entries → Equipment Invoice (UIS-15B) Material Supply Chain (1.34) ├── DistributorPrice → effectivePrice (with discount resolution MAT-9) ├── MaterialConsumptionEntry → Job Financial Summary (computed cost) ├── Supplier PO → Committed costs ├── Receiving → Material cost actuals ├── Inventory checkout → Job material costs └── Vendor Credits → Reduce material costs (negative)
Critical Findings 5 Resolved 4 Open
CRITICAL-1 ID Pattern Conflict — Int vs VarChar(21) RESOLVED
Resolved Apr 7, 2026 by Emma Mann. All equipment models now use String @id @db.VarChar(21) (nanoid), matching the platform standard. Confirmed on: EquipmentTimeEntry, EquipmentTimeEntryCostCode, EquipmentAssignmentAttachment, MaintenanceThreshold, MaintenanceLog, MaintenanceSchedule, DowntimeRecord.

Original Issue

Equipment specs (UIS-15*) used Int @id @default(autoincrement()) for all new models, while everything else used String @id @db.VarChar(21) (nanoid).

CRITICAL-2 EquipmentRatePer Enum Conflict RESOLVED
Resolved Apr 7, 2026 by Emma Mann. All equipment specs (UIS-15B, UIS-15C, UIS-15D, UIS-15E) now consistently reference the 5-value enum: { HOUR, DAY, WEEK, MONTH, YEAR }, matching LEM-2's canonical definition.

Original Issue

UIS-15C/15B defined EquipmentRatePer with only 3 values (HOUR, DAY, WEEK) while LEM-2 defined 5 values. Now aligned.

CRITICAL-3 Equipment Rate Model Architecture Split RESOLVED
Resolved Apr 7, 2026 by Emma Mann. All equipment specs now align on LEM-2's polymorphic EquipmentRateValue pattern with referenceModel + referenceId fields. Rate resolution cascade (Equipment → Category → Global) is consistent across UIS-15B, UIS-15C, UIS-15D, and UIS-15E. The separate EquipmentRate/EquipmentRateOverride/EquipmentAttachmentRate tables have been replaced.

Original Issue

LEM-2 used a polymorphic EquipmentRateValue pattern while UIS-15C used three separate tables. Now unified on the polymorphic approach.

CRITICAL-4 Status Storage Pattern Inconsistency (3 Different Patterns) RESOLVED
Resolved Apr 7, 2026 by Emma Mann. Equipment specs now use an intentional dual pattern: Prisma enums for database-level enforcement (e.g., EquipmentTimeSheetRecordStatus) paired with TypeScript const strings for runtime use. This is consistent within the equipment domain and documented as intentional. WO PropertyValue and MAT const-string patterns are each confined to their own domains.

Original Issue

Three different patterns (PropertyValue, const strings, Prisma enums) across domains. Now each domain's pattern is consistent within itself and the equipment dual-layer approach is intentional.

CRITICAL-5 UIS vs Plan Field Name Discrepancies (33 Conflicts Found) CRITICAL

Category

Naming • Spec Consistency • Schema Convention Violations

Deep Analysis Result

Cross-referencing all 15 spec files against the production Prisma schema revealed 33 field name conflicts — far more than the original 7. The Plan (Emma's) is correct in 19 of 25 scoreable cases. The UIS has a systematic xxxById pattern bug — all 6 user FK fields use the wrong suffix. The existing codebase standard is xxxByUserId (20+ instances: createdByUserId, approvedByUserId, submittedByUserId).

Key Conflict Categories

CategoryUIS PatternPlan PatternSchema StandardWinner
User FK naming (6 fields)xxxByIdxxxByUserIdxxxByUserId (20+ instances)Plan
Status storage (3 models)Prisma enumString + const arrayConst-string on newer modelsPlan
ID generation (2 models)@default(cuid()) / @default(nanoid())@id @db.VarChar(21)App-generated, no @defaultPlan
Material entity (1 model)New Product modelExtend existing MaterialMaterial + MaterialGroup existPlan
Date fieldsdateOfWork, expectedDeliveryDateworkDate, deliveryDateworkDate, nounDate patternPlan
Multi-tenancyinstanceId presentinstanceId omittedRequired on all tenant modelsUIS
Workflow timestampssubmittedAt, committedAtNot definedCommon on financial entitiesUIS

Structural Issues in UIS

  • UIS Product model must NOT be built — it conflicts with the existing Material + MaterialGroup + MaterialAttribute architecture that already ships in production. Plan correctly extends these.
  • UIS @default(cuid()) will breakcuid() generates 25-char IDs, but the constraint is VarChar(21). The codebase generates IDs in application code, not schema defaults.
  • UIS uses Prisma enums for status (PurchaseOrderStatus, MaterialOrderStatus) — the codebase has moved to const-string patterns for flexibility (no migrations needed to add values).
Resolution: Adopt Plan (Emma's) field names as canonical for Prisma schema. Apply these two corrections from UIS to Plan: (1) Add instanceId String @db.VarChar(21) to all new models. (2) Add workflow timestamp fields (submittedAt, approvedAt, sentAt) where approval workflows exist. Do not build UIS's Product model. Do not use @default(cuid()) or @default(nanoid()) in schema.
CRITICAL-6 VendorCredit Status Workflow Mismatch CRITICAL

Category

Business Logic • Workflow Design • Financial Controls

Deep Analysis Result

The UIS specs (UIS-25, UIS-25b, UIS-17) contain zero mention of VendorCredit. VendorCredit is defined exclusively in Emma's MAT-8 plan. The original "conflict" is actually a design-philosophy gap: Emma's lightweight 3-state model vs the structured approval pattern used by every other financial entity in Appello.

Plan MAT-8 (Emma)
Pending → Applied → Void
No approval gate. Any user can apply a credit. No audit timestamp fields.
Existing Appello Pattern
ApprovalStatus enum: InReview → Approved | Rejected
Used by Expense, TimeSheetRecord, LeaveRequest, CheckIn. RBAC-gated approval on all financial entities.

Industry Standard

In construction ERP (Procore, Sage 300 CRE, Vista, BuildOps), vendor credits always require approval because they directly affect job profitability, AP reconciliation, and audit compliance. Credits without approval create a control gap — anyone could reduce material costs to inflate job margins.

Missing from Plan

  • No three-way match integration (PO ↔ Receipt ↔ Invoice ↔ Credit)
  • No approval gate — breaks existing Appello financial control pattern
  • No audit trail fields (appliedAt, appliedById, voidedAt, voidedById)
  • Missing instanceId for multi-tenancy
  • Missing creditDate (when vendor issued the credit)
  • No void-after-apply reversal logic for job financials
Resolution: Use Emma's Plan as the base for scope and integration design (models, line items, createFromPO, inventory returns, job cost adjustment). Upgrade the status workflow to match existing Appello patterns:

Draft → InReview → Approved → Applied → Void

Add RBAC permission vendorCredit.approve. Add fields: instanceId, approvedById, approvedAt, appliedById, appliedAt, voidedById, voidedAt, creditDate. Use ApprovalStatus enum or equivalent const-string pattern. Only Approved credits can be applied. Void reverses job cost adjustment.
CRITICAL-7 Inventory Movement Architecture Split CRITICAL

Category

Data Architecture • Competing Designs • Performance • Industry Alignment

Deep Analysis Result

Comprehensive comparison of both architectures reveals Emma's InventoryItem FK approach is the correct choice, reversing the original UIS recommendation. The UIS ledger approach is intellectually elegant but not the industry standard for operational inventory management.

UIS: Location-Agnostic Ledger (2 models)
InventoryMovement references materialId + fromLocationId / toLocationId directly. One row per transfer. Stock computed by aggregating all movements. O(n) reads, O(1) writes.
Plan MAT-7: InventoryItem FK (3 models)
InventoryItem is a "bin card" — one row per (material, location) pair with denormalized quantity. Movements FK to the bin card. Transfer = 2 rows. O(1) reads, O(2) writes.

Performance Comparison

OperationUIS LedgerPlan InventoryItem
Stock check ("what's at Warehouse A?")O(n) — full scan of movementsO(1) — single SELECT
Low-stock alertsRequires materialized viewWHERE quantity < minQuantity
Transfer recording1 row2 rows + 2 UPDATE
Mobile stock check (offline)Must sync full movement historySync InventoryItem table only
Multi-warehouse dashboard8 parallel aggregationsSimple SELECT across locations

Industry Alignment

Every major ERP uses the InventoryItem (bin card) pattern: SAP MM, Oracle Inventory, NetSuite, Acumatica, QuickBooks, Fishbowl, Cin7, DEAR Inventory. The ledger approach is used in event-sourced architectures and some startups, but not in any production ERP that Appello competes with or integrates with.

Scaling Concern

For an ICI trade contractor with 5,000 materials across 10 locations, the ledger grows to 12M+ rows/year of movements to aggregate. The InventoryItem table stays at 50,000 rows — instant queries regardless of history volume.

Resolution: Adopt Emma's Plan (MAT-7) InventoryItem FK architecture. Add four enhancements:
1. transferGroupId String? @db.VarChar(21) on InventoryMovement to correlate paired transfer movements.
2. instanceId String @db.VarChar(21) on all three models (multi-tenancy).
3. costCodeId String? @db.VarChar(21) on InventoryMovement (mentioned in MAT-7 checkout logic but missing from model).
4. Wrap transfers in prisma.$transaction() to prevent half-transfers.
Use the SQL View InventoryStockLevel for reconciliation verification, not primary stock queries.
CRITICAL-8 Supplier PO Status Lifecycle — Deeper Than Naming CRITICAL

Category

Status Lifecycle • Approval Workflow • Industry Alignment

Deep Analysis Result

The conflict goes deeper than "Issued" vs "Sent" — the two specs define fundamentally different lifecycle models. The Plan uses a lean 5-state model, while the UIS adds a 3-step internal approval workflow before the PO reaches the supplier.

Plan MAT-5 (Emma): Lean
Draft → Sent → PartiallyReceived → FullyReceived → Closed
5 states. No internal approval. PO moves directly from draft to sent.
UIS-25b: Approval-Gated
Draft → Submitted → Approved → Committed → PartiallyReceived → FullyReceived → Closed → Cancelled
8 states. RBAC-gated purchaseOrder.approve permission.

Industry Standard

Procore (market leader): Draft → Processing → Submitted → PartiallyReceived → Received → Approved → Closed/Void. The Approved milestone is where the PO appears in Committed Costs. Neither "Issued" nor "Sent" exists as a status in any major construction ERP — the industry uses Approved as the milestone state.

Existing Appello Pattern

The codebase uses Submitted → Approved for financial entities (ApprovalStatus enum, JobFinancialForecastStatus). The existing Submitted → Approved pattern aligns with the UIS approach, not the Plan.

Resolution: "Sent" as Timestamp, Not Status

A PO can be Approved (financially committed) without being physically emailed to the vendor. Sent is a transmission event, not a workflow gate. Track it as a timestamp.

Resolution: Adopt a merged lifecycle that combines UIS approval workflow (matching Procore and existing Appello patterns) with Plan's transmission tracking:

Draft → Submitted → Approved → PartiallyReceived → FullyReceived → Closed | Cancelled

Drop Issued and Committed entirely — Approved covers both concepts. Add sentAt DateTime? field instead of Sent status. The "Send to Supplier" button sets this timestamp without changing workflow state. Add Cancelled from UIS. Store as const-string (Plan pattern), not Prisma enum (UIS pattern).
CRITICAL-9 Duration Storage Inconsistency RESOLVED
Resolved Apr 7, 2026 by Emma Mann. All equipment time entry specs now use Int (seconds) for duration storage, matching the Work Order line item pattern. Field names: startTime Int and endTime Int (Unix epoch seconds) in EquipmentTimeEntry. Duration is computed, not stored.

Original Issue

Work Order line items stored durations in seconds (Int) while Equipment time entries used hours (Decimal). Now aligned on Int seconds with computed display values.

Should Fix Findings 16 Should Fix
SHOULD-FIX-1 Decimal Precision Inconsistency (30 Fields Audited) PARTIAL

Deep Analysis Result

Audited all 30 Decimal fields across 11 MAT plan files and cross-referenced against the existing Prisma schema's 5 different precision levels. Emma's Plan is internally consistent (Decimal(10, 2) for all money/qty, Decimal(5, 2) for percentages) but has two precision risks.

Current Precision Map

Value TypePlan PrecisionExisting SchemaRisk
Unit pricesDecimal(10, 2)Decimal(10, 2)Aligned
QuantitiesDecimal(10, 2)Decimal(10, 5) on InvoiceLineItemPrecision loss on unit conversion
PO/document totalsDecimal(10, 2)Decimal(12, 2) on forecasts, Decimal(20, 2) on bidsLarge POs may exceed $99.9M
PercentagesDecimal(5, 2)Decimal(5, 2)Aligned
Equipment moneyDecimal(12, 2)Decimal(12, 2) on forecastsAligned (Apr 7 fix)
Resolution: Adopt these precision rules for all new MAT-* models:
Unit prices: Decimal(10, 2) — keep as-is.
Quantities: Decimal(10, 4) — upgrade from (10,2) for unit conversion safety (m→ft, kg→lb).
Document totals (PO amount, credit amount, extended price): Decimal(12, 2) — match forecast pattern.
Percentages: Decimal(5, 2) — keep as-is.
Minimum action: Change PurchaseOrder.amount and VendorCredit.amount to Decimal(12, 2). Remaining (10, 2) fields are acceptable for V1 — widening decimals is a non-destructive ALTER.
SHOULD-FIX-2 Missing isArchived on Multiple Models SHOULD FIX

Some models have isArchived Boolean @default(false), others don't. Missing on: PurchaseOrderLineItem, ReceiptLineItem, VendorCreditLineItem, MaterialRequestItem.

Recommendation: Line items probably don't need archive (they're soft-deleted with parent), but the pattern should be documented. Decide: "child line items inherit parent archive status" and document this as a convention.
SHOULD-FIX-3 PriceBookImport Default Status Mismatch SHOULD FIX

UIS defaults to "Processing". Plan defaults to "Pending".

Recommendation: "Pending" is correct — the file has been uploaded but background processing hasn't started yet.
SHOULD-FIX-4 PriceBookImport Error Log Type Mismatch SHOULD FIX

UIS defines errorLog as String? @db.Text. Plan defines it as Json?.

Recommendation: Use Json — structured error data is more useful for displaying error tables in the UI (row number, column, error message per row).
SHOULD-FIX-5 PriceBookImport Status Values Don't Align SHOULD FIX

UIS has PartialSuccess but omits Pending. Plan has Pending but omits PartialSuccess.

Recommendation: Include both. Full set: Pending, Processing, Complete, PartialSuccess, Failed. PartialSuccess is essential for imports where some rows succeed and others fail.
SHOULD-FIX-6 No Defined Permission for PurchaseOrder CRUD SHOULD FIX

MAT-1 says "appropriate permission required (specific permission name TBD)". No explicit permission group has been defined for Purchase Order operations.

Recommendation: Define explicit permission group: purchaseOrder: { view, create, edit, delete }. POs involve financial commitments and should have dedicated access control.
SHOULD-FIX-7 Mobile Route Pattern Inconsistency SHOULD FIX

Different specs define different URL patterns for the same mobile features:

  • MAT-4 UIS: /material-requests/new
  • MAT-4 Plan: /job-sites/[id]/material-request/new
  • MAT-2b UIS: /jobs/[jobId]/material-consumption/...
  • MAT-2b Plan: /job-sites/[id]/material-consumption/...
Recommendation: Use job-sites/[id]/... pattern consistently. This matches the existing mobile app structure where job site is the primary navigation context.
SHOULD-FIX-8 PO PDF Template Location Conflict SHOULD FIX

UIS places PDF generation in the API: apps/api/src/templates/SupplierPO.pdf.ts. Plan places it in the Frontend: apps/frontend/.../DownloadablePDF/PurchaseOrder/SupplierPO.tsx.

Recommendation: Use the Frontend DownloadablePDF pattern. This matches existing Invoice, Estimate, and Work Order PDF patterns in the codebase.
SHOULD-FIX-9 WorkOrderSigner vs Signature/SignatureRequest — 3 Models for Signing SHOULD FIX

Three separate models handle aspects of the signing workflow:

  • Signature (WO-3a) — stores actual signature images, polymorphic
  • SignatureRequest (WO-8b) — tracks pending signature requests
  • WorkOrderSigner (UIS-20h mobile) — separate signer management with ordering
Recommendation: Consolidate. WorkOrderSigner overlaps significantly with SignatureRequest. Consider making SignatureRequest handle signing order and signer details, eliminating WorkOrderSigner as a separate model.
SHOULD-FIX-10 WorkOrderOtherItem Overlaps with WorkOrderMaterialItem SHOULD FIX

WorkOrderOtherItem (UIS-20h mobile) has name, qty, unitOfMeasure, qtyOfUnit, unitPrice. WorkOrderMaterialItem (WO-2) with materialId = null (blank line) has description, quantity, unitCost. Both serve similar purposes for ad-hoc/unlisted items.

Recommendation: Consider whether WorkOrderOtherItem is truly needed or if blank WorkOrderMaterialItem lines (with null materialId) suffice. Fewer models means simpler queries and less conversion logic.
SHOULD-FIX-11 Notification Triggers Undefined for Most Features SHOULD FIX

Only defined for: WO-8b (signature request), MAT-4 (request submitted), UIS-15D (maintenance thresholds). Missing for: PO issuance, receiving completion, low stock alerts, consumption approval, credit approval.

Recommendation: Define a notification matrix before implementation. Map each status transition to its notification requirements (who, channel, template).
SHOULD-FIX-12 No Permission Group for Discount Management SHOULD FIX

MAT-9 uses material.edit to gate discount CRUD. But discounts affect pricing across POs and P&L — a user who can edit materials shouldn't necessarily be able to change discount percentages that affect financial calculations.

Recommendation: Add a dedicated pricing: { view, edit } or discount: { view, edit } permission group to separate material catalog management from pricing/discount management.
SHOULD-FIX-13 MaterialConsumptionEntry.manufacturerProductId Missing from GraphQL SHOULD FIX

The Plan adds manufacturerProductId to the Prisma model for price resolution (linking consumption to a specific manufacturer product and its distributor price). The UIS GraphQL type doesn't include this field.

Recommendation: Add manufacturerProductId to the GraphQL type and creation input. Without it, consumption entries can't resolve to a specific supplier price, making cost computation ambiguous.
SHOULD-FIX-14 DistributorPrice.discountPercent Duplicates DistributorDiscount SHOULD FIX

DistributorPrice has an inline discountPercent Decimal? @db.Decimal(5, 2). DistributorDiscount (MAT-9) is a separate model for managing discounts. Both can set discount percentages — which takes precedence?

Recommendation: Clarify and document: DistributorPrice.discountPercent is the per-row import value from price book CSVs. DistributorDiscount is the managed entity with date ranges and scope. MAT-9 says DistributorDiscount takes precedence when present, with DistributorPrice.discountPercent as fallback. Document this cascade clearly in the resolver.
SHOULD-FIX-15 Missing Quantity Precision for PO Line Items SHOULD FIX

UIS uses Decimal(10, 4) for quantity (supports fractional units). Plan uses Decimal(10, 2) for quantity.

Recommendation: Use Decimal(10, 4) for quantities. Materials may have fractional units like 2.5 rolls, 0.75 gallons, or 12.375 linear feet. Two decimal places truncates valid quantities.
SHOULD-FIX-16 Job (MOD: +workDates) Conflict with dateOfWork SHOULD FIX

WO-3a adds dateOfWork DateTime? @db.Date to Job. UIS-20h mobile adds workDates Json (array of dates for multi-date Work Orders). These represent the same concept differently.

Recommendation: Use dateOfWork for the primary date. Only add workDates Json? if multi-date Work Orders are a confirmed v1 requirement. If so, dateOfWork becomes the first date and workDates stores additional dates.
Nice to Have Findings 17 Nice to Have
NTH-1 Inconsistent Computed Field Patterns NICE TO HAVE

Four different computation patterns for "price × quantity" across entities:

  • PurchaseOrderLineItem.total: query-time computed in resolver
  • DistributorPrice.effectivePrice: query-time computed
  • WorkOrderMaterialItem.total: stored field Decimal(10,2) (pre-computed)
  • MaterialConsumptionEntry costs: computed in JobFinancialSummaryService (aggregate)
Recommendation: Document the pattern decision: use resolver-computed for display-only values, stored fields for values that need to be queried/aggregated.
NTH-2 No Consistent Export Pattern NICE TO HAVE

WO Log: CSV export. CO Log: CSV + PDF export. Inventory, Material Requests, PO List: no export mentioned.

Recommendation: Define export requirements for each list view. Consider a shared ExportService with CSV and PDF support.
NTH-3 Missing Empty State Designs NICE TO HAVE

Most specs mention "handle empty data" in acceptance criteria but no specs define what the empty state looks like.

Recommendation: Design a reusable EmptyState component with icon, message, and action button. Use consistently across all new list views.
NTH-4 No Consistent Error Handling for Bulk Operations NICE TO HAVE

approveEquipmentTimeEntries(ids), approveMaterialConsumptionEntries(ids), upsertWorkOrderLineItems — batch operations with no per-item error handling defined.

Recommendation: Define pattern: return { success: [...], failed: [...] } with per-item error messages, or use all-or-nothing transactions. Document the convention.
NTH-5 No Consistent Audit Trail Pattern NICE TO HAVE

Some models have createdAt/updatedAt only. Some have approvedByUserId/approvedAt. No consistent change history logging.

Recommendation: Consider a shared audit log pattern (e.g., append-only AuditEntry table) for financial entities where change history matters.
NTH-6 Company Detail Page Tab Explosion NICE TO HAVE

A Supplier company page could have 9+ tabs: Overview, Jobs, Contacts, Notes, Files, Products & Pricing (MAT-0), Supplier PO (MAT-5), Credits (MAT-8), Discounts (MAT-9).

Recommendation: Consider grouping into sub-sections or a "Procurement" mega-tab to avoid tab overflow.
NTH-7 JobFinancialSummaryService Is a Merge Conflict Hotspot NICE TO HAVE

Modified by 5 features in parallel: MAT-2a (material consumption), MAT-6 (committed PO costs), MAT-7 (inventory checkout costs), MAT-8 (vendor credits), UIS-15A (equipment costs).

Recommendation: Refactor into a plugin/strategy pattern before implementing. Each cost source registers a "cost provider" function, and the summary service aggregates them. Prevents merge conflicts and makes adding new cost types trivial.
NTH-8 No Shared Line Item Base Component NICE TO HAVE

WO Labor/Material/Equipment Editors, PO Line Item editor, VendorCredit Line Item editor — all are conceptually "repeating row with qty + price + description" but built as custom components.

Recommendation: Extract a shared RepeatingLineItemEditor base component with configurable columns, add/remove row actions, and total computation.
NTH-9 Multiple Auto-Numbering Schemes Need Central Service NICE TO HAVE

Six different auto-number prefixes: WO-001, CPO-####, SPO-####, MR-####, VC-####, RCV-###. Each defined independently.

Recommendation: Create a shared AutoNumberService with prefix + instance scoping. Handles incrementing, padding, and prevents collisions.
NTH-10 Equipment Maintenance Models May Be Over-Scoped for v1 NICE TO HAVE

5 new models (MaintenanceThreshold, MaintenanceLog, MaintenanceSchedule, MaintenanceScheduleTemplate, DowntimeRecord) plus 3 new enums. Phase 4 is already labeled "lower priority, deferrable."

Recommendation: Ship minimal viable (Threshold + Log only) and defer Schedule/Template/Downtime to a future release.
NTH-11 Three-Way Match May Be Over-Scoped for v1 NICE TO HAVE

Full PO qty vs received qty vs invoiced qty matching requires receipt tracking, invoice line item linking, and computed columns.

Recommendation: Ship basic receiving (partial/complete) first. Full three-way match can be a v1.1 enhancement.
NTH-12 Discount Resolution Cascade Seems Right-Sized NICE TO HAVE

Product > Category > Global with date ranges. Standard for B2B distribution pricing.

Recommendation: No change needed. Document the cascade clearly in the resolver with inline examples.
NTH-13 Inventory Costing Method Left Open NICE TO HAVE

Plan flags "FIFO vs weighted average" as an open decision. Both require different movement computation logic.

Recommendation: Start with weighted average (simpler to implement). FIFO adds lot tracking complexity that isn't needed for v1.
NTH-14 No Consistent "Convert to PO" Pattern NICE TO HAVE

MAT-4 mentions "Convert to PO" as a future feature on material requests. WO-7 has "Convert to CO/Invoice" as an explicit feature. No shared conversion mutation pattern.

Recommendation: Design a shared conversion mutation pattern: convertEntity(sourceType, sourceId, targetType) with pre-fill logic.
NTH-15 No Test Strategy Mentioned in Any Spec NICE TO HAVE

61 spec files, 0 mention unit tests, integration tests, or E2E tests.

Recommendation: Add test requirements to each spec's acceptance criteria. At minimum: model-level unit tests, resolver integration tests, and critical-path E2E tests.
NTH-16 Missing Offline Patterns for Mobile NICE TO HAVE

MAT-2b mentions "offline support" for consumption entries. WO-8 mentions "multiple drafts can be open simultaneously." No other mobile features mention offline capability.

Recommendation: Define offline-first vs online-only per mobile feature. Document which features queue mutations for sync and which require connectivity.
NTH-17 No Pagination Defined for List Queries NICE TO HAVE

Many queries return arrays: materialConsumptionEntries, inventoryMovements, vendorCredits. No offset/limit/cursor pagination defined.

Recommendation: Use existing GetListOptions pattern with pagination for all list queries. Construction companies can accumulate thousands of records per entity.
Status Lifecycle Comparison 8 Entities
Entity States Storage Pattern Approval
Job Existing statuses (Active, Closed, etc.) PropertyValue No
Work Order Draft → Submitted → AwaitingSignature → Signed → Approved → Invoiced → Void PropertyValue Yes (signature + approval)
EquipmentTimeEntry Pending → Approved / Rejected Prisma Enum Yes
MaterialConsumptionEntry Draft → Submitted → Approved Const String Yes
PurchaseOrder Draft → Issued → PartiallyReceived → FullyReceived → Closed Const String No (v1)
MaterialRequest Submitted → Acknowledged → InProgress → Fulfilled / Cancelled Const String No (acknowledge workflow)
VendorCredit Draft → Submitted → Approved → Applied (UIS)
Pending → Applied / Void (Plan)
Const String TBD
PriceBookImport Pending → Processing → Complete / PartialSuccess / Failed Const String No
Mobile Coverage Matrix 24 Features
Feature Desktop Mobile Notes
1.33 — LEM Foundation
LEM-1 Labor RatesSettings — desktop only appropriate
LEM-2 Equipment RatesSettings — desktop only appropriate
1.33 — T&M Work Orders
WO-1 Job ClassificationVia WO-6Mobile inherits via Job list
WO-2 Line ItemsVia WO-8Mobile form creates items
WO-3 Create/Edit Flyout WO-8 WizardDifferent form patterns
WO-4 Detail WO-6Simplified mobile detail
WO-5 WO Log WO-6Mobile list view
WO-6 CO LogGAP — low priority
WO-7 ConversionOffice workflow — appropriate
WO-8 Mobile CreationMobile-only
1.33 — Equipment Module
UIS-15H Equipment TimeTimesheet integration
UIS-15E Equipment SearchQR scanner
UIS-15F Equipment ChecklistMobile form submission
1.34 — Material Management
MAT-0 Supply ChainSettings — appropriate
MAT-1 Customer POGAP — consider view-only mobile
MAT-2a Consumption DesktopSee MAT-2b
MAT-2b Consumption MobileMobile-only
MAT-3 Price Book ImportAdmin — appropriate
MAT-4 Material Requests Inbox FormField → Office workflow
MAT-5 Supplier POOffice workflow — appropriate
MAT-6 ReceivingGAP — mobile receiving would be valuable
MAT-7 InventoryQR onlyMobile is QR lookup only
MAT-8 Vendor CreditsOffice workflow — appropriate
MAT-9 DiscountsSettings — appropriate
Permission Model Matrix 10 Features
Feature Permission Group View Create Edit Delete Special
Work Orders job viewWageDetails
Equipment Time timesheet (existing)
Equipment Rates equipment
Equipment Maintenance equipment view_financial, view_maintenance_cost
Material Consumption materialorder
Material Requests materialorder
Purchase Orders TBD TBD TBD TBD TBD
Inventory inventory
Vendor Credits vendorCredit
Discounts material
P&L Integration Matrix 5 Cost Sources
Feature Modifies JobFinancialSummaryService Cost Type Computation
UIS-15A Equipment P&L ✓ Yes Equipment costs SUM(approved entries × resolved rate)
MAT-2a Material Consumption ✓ Yes Material consumption SUM(qty × resolved price) at query time
MAT-6 Receiving ✓ Yes Committed material costs PO issued amount
MAT-7 Inventory Checkout ✓ Yes Material cost actuals Checkout qty × unit cost
MAT-8 Vendor Credits ✓ Yes Credit adjustment Negative material cost

Recommended Action Priority

5 of 9 Critical findings resolved by Emma Mann's April 7 equipment spec updates (CRITICAL-1, 2, 3, 4, 9). The remaining 4 criticals now have concrete resolutions based on deep analysis of 15 spec files, 33 field conflicts, and industry ERP patterns.
Immediate: Apply These Decisions to Specs
  1. 1 Adopt Plan field names as canonical. Fix the UIS xxxByIdxxxByUserId pattern bug (6 fields). Do NOT build UIS Product model — use existing Material. Add instanceId and workflow timestamps from UIS to Plan. (CRITICAL-5 — 33 conflicts, Plan wins 19/25)
  2. 2 Upgrade VendorCredit to approval workflow. Keep Emma's model/integration design. Add Draft → InReview → Approved → Applied → Void lifecycle with RBAC gate (vendorCredit.approve). Add audit fields: approvedById/At, appliedById/At, voidedById/At, instanceId, creditDate. (CRITICAL-6 — aligns with ApprovalStatus pattern used by 4 existing entities)
  3. 3 Adopt InventoryItem FK architecture (Emma's Plan MAT-7). Drop UIS ledger approach — O(1) stock reads vs O(n), aligns with every major ERP (SAP, Oracle, NetSuite, Acumatica). Add transferGroupId, instanceId, costCodeId to InventoryMovement. Wrap transfers in prisma.$transaction(). (CRITICAL-7 — industry-universal bin card pattern)
  4. 4 Merge PO lifecycle to approval-gated model. Draft → Submitted → Approved → PartiallyReceived → FullyReceived → Closed | Cancelled. Drop "Issued" and "Committed." Track transmission as sentAt DateTime? instead of a status. Store as const-string, not Prisma enum. (CRITICAL-8 — matches Procore, Sage, and existing Appello ApprovalStatus pattern)
Before Implementation: Precision Rules
  1. 5 Apply decimal precision rules: Decimal(12, 2) for document totals (PO amount, credit amount), Decimal(10, 4) for quantities (unit conversion safety), Decimal(10, 2) for unit prices, Decimal(5, 2) for percentages. (SHOULD-FIX-1 — 30 fields audited)
  2. 6 Define notification matrix for all status transitions. (SHOULD-FIX-11)
During Implementation (1.34)
  1. 7 Establish shared AutoNumberService for WO, CPO, SPO, MR, VC, RCV prefixes. (NTH-9)
  2. 8 Create shared RepeatingLineItemEditor base component. (NTH-8)
  3. 9 Refactor JobFinancialSummaryService with plugin/strategy pattern before 5 features modify it. (NTH-7)