ShopFloor - Unit Flow
Unit Flow: How a Unit Moves Through the System
Section titled “Unit Flow: How a Unit Moves Through the System”This is the single most important document for understanding CTLShopFloor. It traces a physical unit (laptop, desktop, monitor) from the moment its serial number is scanned at the receiving dock until it leaves the building — sold, scrapped, or shipped back to a vendor.
Read 03-glossary.md first if any term is unfamiliar.
Items marked ⚠️ Assumption are inferred from code and need confirmation by someone with operational knowledge. Items marked 🔧 Refactor finding are tracked in 20-refactor-findings.md.
1. Mental model: the system in one paragraph
Section titled “1. Mental model: the system in one paragraph”A unit’s lifecycle is a state machine where the state is WorkTracking.StationID and transitions are gated by RouteFlow. Every unit belongs to a WorkOrder. Every WorkOrder picks a Route. A Route is an ordered list of StationTypes (via RouteFlow.ScanOrder). To move a unit forward, an operator scans its serial number at a physical Station; the dispatcher confirms the scanned station’s type is the next type in the unit’s RouteFlow, and only then updates WorkTracking.StationID. Branches off the happy path (Repair Center, BER, Quarantine, Harvesting) are not in the RouteFlow — they’re explicit station re-assignments triggered by specific business rules (e.g., a failing Triage score, three failed repair attempts, an RTV flag).
2. The scan dispatcher
Section titled “2. The scan dispatcher”All forward motion goes through one chokepoint: ScanUnitControllerService.UpdateWorkTrackingSN(sn, st, project) (lines 19–156).
The flow:
- Look up
WorkTrackingby serial + project. - Look up the unit’s
WorkOrder.RouteID. - Pull
RouteFlowrows for that Route whereScanOrder > 0, ordered ascending. - Find the unit’s current station’s position (
stationofItemIndex) and the scanned station’s position (nextStationIndex) in that ordered list. - Reject unless
stationofItemIndex == nextStationIndex - 1. No skipping, no jumping backwards. - Call
StationGeneralActionService.IsStationChangeNotRestrictionCompliant()to verify both the unit’s WorkOrder and the target station share aProductLineCategory(when strict membership is enabled). - Update
WorkTracking.StationIDandLastScanDate. Save.
Implication: the Route is a strictly-linear linked list per unit. If a station doesn’t apply to a given product (e.g., a monitor doesn’t need Kitting), it must be absent from that Route’s RouteFlow — there is no “skip” mechanism.
🔧 Refactor finding: route-compliance validation is duplicated in 6+ station services instead of living solely in the dispatcher. See 20-refactor-findings.md.
3. The happy path (a Grade A laptop, end to end)
Section titled “3. The happy path (a Grade A laptop, end to end)”This is the canonical flow. Branches and exceptions are covered in the following sections.
flowchart LR subgraph Receiving A[Dock<br/>InboundPallet check-in] --> B[Debox] B --> C[Sorting] end subgraph WIP C --> D[Triage<br/>Cosmetic + Functional] D -->|Grade A| E[Image Download] E -.->|leaves shop floor| F[(Windows Test System<br/>OS install + spec capture + DPK injection)] F -.->|results uploaded| G[MoveFromWindowsTestSystem] G --> H[Cleaning] H --> I[Grading] end subgraph PreFGI[Pre-FGI] I --> J[Kitting<br/>scan accessories] J --> K[Packing] K --> L[QA] end subgraph FGI L --> M[Pallet Station] M --> N[Finished Good<br/>close OutboundPallet] end subgraph PostFGI[Post-FGI] N --> O[Sales Inventory] O --> P[Pick/Ship] P --> Q[Sold/Fulfilled] endStep-by-step narrative
Section titled “Step-by-step narrative”Receiving (StationCategory 7)
Section titled “Receiving (StationCategory 7)”- Dock. Truck arrives. Each unit’s serial is scanned against the
InboundPallet+ pre-alert. AWorkTrackingrow is created (or, for an in-system RMA, an existing row is reset — see §5).ArrivedSKUandLabelSKUare recorded. Special inbound types (IMC, AS-IS, FLIP) get aGradingIDauto-assigned here perDockStationControllerServicelines 91–169. - Debox. Units are unpacked from shipping cartons.
- Sorting. Units are routed by destination type. ⚠️ Assumption — sorting is primarily decisional; not all routes include a Sorting station.
WIP (StationCategory 8)
Section titled “WIP (StationCategory 8)”- Triage. Operator administers Cosmetic + Functional evaluations. Both must be submitted. For a Grade A unit (Cosmetic ≤ 9 AND Functional ≤ 9 on the Mexicali tenant), the unit auto-promotes to Image Download. See §4 for the full branch tree.
- Image Download. Unit physically leaves the shop floor and is moved to the Windows Test System. The shop-floor station’s purpose is to record
WorkTracking.StationID= Image Download so the unit appears in a “waiting for imaging” queue. - Windows Test System. Off-floor. Windows is installed, the Digital Product Key from
DigitalProductKeyis injected, hardware specs are captured intoSystemInformation+SystemInformationDisk+SystemInformationGraphic+ theEngineering*tables, and the captured JSON blob is also stored onWorkTracking.SystemInformationBlob. Test results upload viaWindowsTestResultsUpload.ImagingAndTestingLogrecords the operation. - MoveFromWindowsTestSystem. Unit returns to the shop floor; this station re-integrates it into the main flow.
🔧 Refactor finding: the station
PromoteImageDownloadUnitdoes the same job asMoveFromWindowsTestSystemunder a misleading name. ⚠️ Assumption — they may be used in different production flows (one for repaired units re-imaging, one for first-time). Confirm before consolidating.
- Cleaning. Physical wipe-down. Verifies
WindowsTestResultsUploadexists before allowing advance — a defense against units progressing without imaging results. - Grading. Operator scans a QR code that encodes the SKU+grade. The system looks up the matching
GradingTypesrow, setsWorkTracking.GradingID, optionallySubGradingID.
Pre-FGI (StationCategory 9)
Section titled “Pre-FGI (StationCategory 9)”- Kitting. For each accessory listed in the unit’s
KittingBOM, the operator scans the barcode. Each scan creates aKittingLogrow tying theKeypartsNumberIDto the unit. The station blocks advance until all required items are scanned (or anIsExceptionflag is recorded). - Packing. Unit is sealed in its outbound box. If
Project.GradingValidation == true, rejects units missing aGradingID. May trigger creation of aBoxPallet(sub-pallet) for multi-box shipments (see SF-220). - QA. Final inspection. ⚠️ Assumption — exact validation rules per project not fully traced.
FGI (StationCategory 10)
Section titled “FGI (StationCategory 10)”- Pallet Station. Unit is assigned to an
OutboundPallet. Pallet identifier is auto-generated fromPalletPrefix(e.g.,OB000001). - Finished Good. When the pallet is full, the operator closes it:
OutboundPallets.IsOpen=false,CloseDateset. The unit is now FGI and visible to sales.
Post-FGI (StationCategory 11)
Section titled “Post-FGI (StationCategory 11)”- Sales Inventory. Pallet/units are available for sale. A
SalesImportjob ([14-jobs-and-integrations.md] — TBD) reconciles sales data. - Pick / Ship.
UnitShippedDateis set onWorkTracking. - Sold / Fulfilled.
UnitSoldDateis set.SoldFulfilledOrScrapedLogrecords the terminal disposition.
4. Triage decision tree
Section titled “4. Triage decision tree”Triage is the first major branching point. The logic lives in TriageEvaluationService.cs and is branched by tenant — a significant smell flagged below.
Inputs
Section titled “Inputs”- A serial number scanned at a Triage station.
- ⚠️ Assumption — the trigger gate is
WorkTracking.StationTypeID == Deboxing(10)perTriageEvaluationController.cs:53. Confirm whether Sorting is also a valid predecessor. - Operator submits both evaluations (Cosmetic and Functional). The system requires
latestScores.Count == 2before routing.
Scoring → grade
Section titled “Scoring → grade”Each evaluation is scored 0–100. A worse-than-threshold grade or any option flagged autoFail=true triggers a failure. Score thresholds map to A/B/C/D grades via TriageEvaluationGradesScore. ⚠️ The mapping is per-evaluation-type and stored in the DB, not in code — confirm current thresholds against the live data.
Branch logic by tenant
Section titled “Branch logic by tenant”Tenant: Dallas SL (CanBeProccesed method, lines 271–371)
Section titled “Tenant: Dallas SL (CanBeProccesed method, lines 271–371)”This tenant uses interactive routing — the worker is offered destinations.
| Cosmetic | Functional | RTV flag set? | Offered destination(s) |
|---|---|---|---|
| ≥ 46 | any | yes (any option) | Quarantine (primary) or Repair Center |
| ≥ 46 | any | no | Repair Center only |
| ≤ 45 | ≤ 9 | — | Ineligible — CanBeProccesed=false. ⚠️ No automatic destination; unit appears to stay at Triage. |
| ≤ 45 | > 9 | yes | Repair Center or Quarantine (RTV optional) |
| ≤ 45 | > 9 | no | Repair Center or Quarantine |
Tenant: Mexicali (default) (PromoteBasedOnGrade method, lines 374–512)
Section titled “Tenant: Mexicali (default) (PromoteBasedOnGrade method, lines 374–512)”This tenant uses automatic routing.
| Cosmetic | Functional | Auto destination | What’s written |
|---|---|---|---|
| ≤ 9 (A) | ≤ 9 (A) | Image Download | WorkTracking.StationID updated |
| any other combination | Repair Center | Error codes extracted from both evaluations; TriageEvaluationSubmission flags needRepair, needPaint, needRelam per matched codes; TriageErrorRecords row per error; RepairCenter row created |
Data written at Triage
Section titled “Data written at Triage”| Entity | When | Purpose |
|---|---|---|
TriageEvaluationSubmission | Each evaluation submitted | Final score, grade letter, serialized Q&A JSON, auto-fail flag, needRepair/needPaint/needRelam flags |
TriageErrorRecords | Routing to Repair Center | Per-error: SN, SKU, model, BOL, error name, user, timestamp, KeypartCategoryErrorCodeID |
Quarantine | Routing to Quarantine | SN, error type, error code, failure description, timestamp |
UnitNotes | Routing to Quarantine | Narrative “Failed from Triage for the next issues: …” |
WorkTracking | Final promotion | Updated StationID, LastScanDate |
Triage smells (collected into 20-refactor-findings.md)
Section titled “Triage smells (collected into 20-refactor-findings.md)”- 🔧 Hardcoded tenant branch at line 294 —
if (tenantName == "Dallas SL"). The author left a comment://<-- HATE THIS HARDCODED THING. - 🔧 Hard-indexed evaluation order (lines 401–404) — relies on the alphabetical sort of evaluation names putting “Cosmetic” first, “Functional” second.
- 🔧 Magic score thresholds (46, 45, 9) with no configuration table or constants.
- 🔧 Three split entry points —
CanBeProccesed,PromoteBasedOnGrade,ProcessItemToStation— with no unified orchestrator. - 🔧 Serialized JSON as source of truth — questionnaire responses are stored as a JSON blob in
SerializedSelections, deserialized in-memory for decision logic. No queryable normalized table. - 🔧 Inconsistent auto-fail logic — functional triggers on substring “Not Functional” or the
autoFailflag; cosmetic only on the flag.
5. RMA flow (returns)
Section titled “5. RMA flow (returns)”A returned unit enters through one of two paths depending on whether it’s already in the database.
Path A: In-system RMA
Section titled “Path A: In-system RMA”The returned unit was previously shipped from this facility, so its serial exists in WorkTracking.
flowchart LR A[Customer return arrives] --> B[Operator creates<br/>RmaHeader + RmaItemsRelation<br/>RMAControllerService.CreateNewRma] B --> C[WorkTracking reset:<br/>StationID = Dock<br/>ReceivedDate, GradingID,<br/>OutboundPalletID, ItemTypeID,<br/>SoldDate, ShippedDate cleared] C --> D[Unit re-enters normal route<br/>starting from Dock]Triggered via RMAControllerService.CreateNewRma() (lines 107–113, 184, 198). Requires the unit’s current StationID to be in FG, Shipped, Sold, or Absent state (gated at lines 168–172).
Path B: Out-of-system RMA
Section titled “Path B: Out-of-system RMA”The returned unit’s serial is not in WorkTracking — typical for units shipped by a different facility/program.
flowchart LR A[Customer pre-alert<br/>Excel uploaded] --> B[Rows staged in<br/>RmaNotInsystemPreAlert] B --> C{Validation<br/>RmaForOutOfSystemUnitsControllerService.ProcessFile} C -->|valid| D[GenerateRMA:<br/>New WorkOrder IsRMA=true<br/>+ New WorkTracking per unit<br/>+ RmaHeader + RmaItemsRelation] C -->|already in system| E[Treated as in-system path:<br/>existing WorkTracking reset to Dock] D --> F[Units enter at Dock] E --> FIf the ArrivedSKU references a model not yet in BOMHeader, the system auto-creates a placeholder BOMHeader and emails admins via SendNewBomHeaderItemEmailNotification(). ⚠️ Errors here are silently swallowed (lines 451–454).
What happens at the unit level on RMA receipt
Section titled “What happens at the unit level on RMA receipt”Whether in-system or out-of-system, the converging behavior is:
WorkTracking.StationIDis set to Dock.- The following fields are cleared:
ReceivedDate,GradingID,OutboundPalletID,ItemTypeID,UnitSoldDate,UnitShippedDate,SentToSalesDate. LabelSKUis preserved (carries the original outbound label).- The unit re-enters the normal Route from the start. There is no “RMA-specific” route — the same
Routeis reused, gated byWorkOrder.RouteID.
RMA → Repair Center linkage
Section titled “RMA → Repair Center linkage”⚠️ Important quirk: RmaHeader / RmaItemsRelation do not have a foreign key to RepairCenter. The linkage is implicit — when the RMA’d unit re-enters the flow and later reaches Repair Center, the join is on WorkTracking.SerialNumber == RepairCenter.SerialNumber (a string match).
🔧 Refactor finding: no enforceable referential integrity between RMA and the subsequent repair record. Recommend adding
RepairCenter.RmaItemsRelationIDor routing repair lookups throughWorkTrackingID.
RMA smells
Section titled “RMA smells”- 🔧 No
RmaOutcomefield onWorkTracking— there’s no way to tell whether the RMA was successfully re-processed or returned a second time. - 🔧 Reset logic duplicated across
CreateNewRma,AddItemToExistingRMA, and the out-of-systemGenerateRMAold-path branch. - 🔧 Magic station IDs like
item.StationID.Equals(21)instead of usingStationTypeEnum. - 🔧
RmaNotInsystemPreAlert.IsValidcan betruewhileErrorDescription != "OK"— ambiguous semantics. - 🔧 Nothing prevents one unit being attached to multiple
RmaHeaders simultaneously.
6. Repair Center flow (with purchased + harvested parts)
Section titled “6. Repair Center flow (with purchased + harvested parts)”The Repair Center is the operational hub for everything that fails the happy path — Triage failures, in-flow defects, RMA returns, and re-test failures. It is also the destination for inventory in the form of purchased parts (PO receipts) and harvested parts (from cannibalized BER units), and the source of consumption against that inventory.
6.1. What happens at the Repair Center station
Section titled “6.1. What happens at the Repair Center station”flowchart TD A[Unit arrives at RC<br/>scanned in via SearchSN] --> B[Technician documents errors] B --> C[Errors written to:<br/>RepairCenterError + RepairCenter] C --> D{Repair attempt} D -->|Repaired = true| E[RepairDate, RepairDescription,<br/>RepairTechUser, ReplacementParts set<br/>RepairCenterError.Status = 'Issue Fixed'] D -->|Repaired = false| F[Unit stays at RC for retry] E --> G[WorkTracking.ErrorFlag<br/>recalculated] G -->|no unresolved errors| H[Unit re-enters route<br/>⚠️ exact re-entry point<br/>not enforced in code] F --> I{RepairedErrorCount<br/>>= 3 on same error?} I -->|yes| J[Auto-send to BER<br/>sendToBER lines 955-999] I -->|no| D J --> K[Grading = TYPE BER<br/>LabelSKU updated<br/>StationID = BER station] K --> L[Eligible for Harvesting]Key implementation references:
RepairCenterStationControllerService.cslines 339–665 (intake), 1418–1452 (error creation), 1578–1802 (addErrorrepair recording), 1697–1705 (ErrorFlagrecalculation), 955–999 (sendToBER).RepairCenter.RepairedErrorCountincrements per re-repair attempt; 3 strikes = BER (line 1438).RepairCenterError.Statususes magic strings like"PENDING","Issue Fixed"— not an enum.
6.2. Where does the unit go after repair?
Section titled “6.2. Where does the unit go after repair?”⚠️ Genuinely unclear from the code. When the technician marks the unit Repaired=true, WorkTracking.ErrorFlag is cleared but there is no explicit code that returns the unit to a specific station in the Route. The implication is that the unit is physically walked back to the appropriate station and the operator there re-scans it — but the scan dispatcher (§2) enforces strict sequential ordering and there’s no special-case bypass for “returning from RC.”
Possibilities, all needing confirmation:
- RC is itself a
RouteFlowentry in some Routes, so the unit naturally proceeds to the next station on its next scan. - The operator manually overrides via
ProcessItemToStation(which exists in TriageEvaluationService and may have a sibling in RC). - The unit’s
StationIDis reset to a specific predecessor station (e.g., Imaging) by some service method I haven’t traced.
🔧 Refactor finding (high priority): the post-repair re-entry path is undocumented and likely fragile. Worth investigating with operators before any refactor.
6.3. Purchased parts: from PO to repair-ready inventory
Section titled “6.3. Purchased parts: from PO to repair-ready inventory”flowchart LR A[PurchaseOrder created<br/>status DRAFT] --> B[PO approved<br/>status APPROVED] B --> C[Parts arrive<br/>ReceivePurchaseOrderAsync] C --> D[For each PurchaseOrderItem<br/>with FulfilledQuantity > 0] D --> E[CreateParts: Part rows created<br/>Source = 'PO'<br/>PurchaseOrderItemID FK] E --> F[InventoryTransaction<br/>TransactionType = 'IN'] F --> G[InventoryTracker<br/>AvailableQuantity updated] G --> H{High value?} H -->|yes| I[Each Part gets unique<br/>PartSerialNumber<br/>format P-yyMMdd-A00000] H -->|no| J[Aggregate quantity tracking]Key implementation references in PurchaseOrderControllerService.cs: lines 284–358 (PO creation), 827 (receipt), 940 (CreateParts), 1637–1678 (high-value branch), 1680–1716 (standard branch), 671–821 (GetAvailableKeypart).
Note: PendingPartsCatalog despite its name is not a list of parts pending procurement. It’s a BOMHeader-linked reference of parts associated with a product family. Naming is misleading — see glossary and 🔧 refactor findings.
6.4. Harvested parts: from BER unit to repair-ready inventory
Section titled “6.4. Harvested parts: from BER unit to repair-ready inventory”flowchart LR A[BER unit at Harvesting station] --> B[Technician CheckSerialNumberAsync<br/>validates eligibility<br/>RC or BER grading] B --> C[HarvestingEvent created] C --> D[Technician lists extracted parts:<br/>KeypartNumberID, Quantity, NeedsRepairing] D --> E[CreateParts: Part rows created<br/>Source = 'Harvesting'<br/>HarvestingEventID FK] E --> F[InventoryTransaction TransactionType = 'IN'] F --> G[InventoryTracker AvailableQuantity updated] G --> H{High value?} H -->|yes| I[Unique PartSerialNumber<br/>format T-yyMMdd-A00000] H -->|no| J[Aggregate quantity tracking]Key implementation references in HarvestingControllerService.cs: lines 65 (eligibility), 137–445 (overall flow), 158–170 (component extraction), 175 (CreateParts invocation), 356–395 (high-value branch).
Distinct serial prefix: purchased parts get P{yyMMdd}A...; harvested parts get T{yyMMdd}A.... ⚠️ Assumption — the “T” likely stands for “Take/Tear-down”. This is useful provenance.
Condition flag: HarvestingEvent.NeedsRepairing lets a harvested part be flagged as “needs to be reconditioned before use.” Repair Center availability lists filter on this flag (see RepairCenterStationControllerService lines 792–794).
6.5. Recovery vs. Harvesting (unclear)
Section titled “6.5. Recovery vs. Harvesting (unclear)”⚠️ Unresolved. RecoveryEvent exists alongside HarvestingEvent with similar shape, but RecoveryEvent ties to a WarehouseID instead of a unit. Hypotheses to confirm:
- RecoveryEvent is a legacy entity superseded by HarvestingEvent.
- RecoveryEvent represents warehouse-level manual stock-in (parts found loose, parts received without a PO).
- RecoveryEvent is a planned-but-incomplete feature.
This needs a definitive answer before any refactor touches either entity. See 20-refactor-findings.md.
6.6. Part consumption (incomplete)
Section titled “6.6. Part consumption (incomplete)”When a part is used in a repair, two entities track it — but the actual inventory write-down appears to be incomplete.
| Entity | What it does | Status |
|---|---|---|
RCReplacementParts | Records that part X was used in repair Y. Fields: RepairID, HarvastingSN (sic, string not FK), PartNumberExtracted, PartNumberForReparation. | ✅ Recorded |
ConsumptionRequest + ConsumptionRequestItems | A request to consume specific parts from inventory. Status APPROVED exists. | ⚠️ Created by PurchaseOrderControllerService.CreateConsumptionForRepairCenter() but the inventory write-down (InventoryTracker.AvailableQuantity decrement, Part.ConsumedByWorkTrackingID set) is not visibly wired up. |
InventoryTransaction with TransactionType="OUT" | The proper ledger entry for consumption. | ⚠️ Not observed being created on repair-part consumption. |
🔧 Refactor finding (high priority — inventory integrity): the visible code creates a
ConsumptionRequestand recordsRCReplacementPartsbut never decrements available inventory.GetAvailableKeypart()(line 686) filtersPartrows onConsumedByWorkTrackingID == null, implying parts should be marked consumed, but no service method writing that field was found. Either this logic lives in a code path I haven’t traced (a Hangfire job? a database trigger?) or it is a real bug that has been masked by manual inventory adjustments. Confirm before refactor.
6.7. BER and Harvesting as the “graveyard” path
Section titled “6.7. BER and Harvesting as the “graveyard” path”flowchart LR A[Unit at Repair Center] -->|3 failed repair attempts| B[Auto-BER<br/>sendToBER] B --> C[Unit at BER station<br/>GradingTypes = TYPE BER<br/>LabelSKU marked] C --> D{Salvageable?} D -->|yes| E[Harvesting station] D -->|no| F[Scrap / disposal<br/>outside this system] E --> G[Parts extracted<br/>added to inventory] G --> H[Remaining hulk → Scrap]BER is not a separate Route — it’s an explicit station re-assignment. The unit’s lifecycle effectively ends at BER unless Harvesting extracts value from it.
⚠️ Assumption — once a unit hits BER, it never re-enters the happy path. No code path was found that would re-grade a BER unit back to a sellable state.
7. Side branches summary
Section titled “7. Side branches summary”| Branch | Trigger | What writes it | Terminal? |
|---|---|---|---|
| Quarantine | RTV flag in Triage, suspicious SKU, manual hold | Quarantine row + UnitNotes | No — can be released back to flow |
| Repair Center | Triage failure, in-flow failure, RMA receipt | RepairCenter + RepairCenterError | No — Repaired=true returns unit to flow |
| BER | 3 failed repairs on same error code | WorkTracking.GradingID = TYPE BER, LabelSKU updated | ⚠️ Effectively yes |
| Harvesting | BER unit deemed salvageable | HarvestingEvent + Part rows | Yes — donor unit is consumed |
| As-Is Shipments | Special inbound type | Auto-graded AS-IS at Dock | Path leads to a different outbound treatment ⚠️ details TBD |
| Internal Transfer | Movement between facilities | InternalTransfer + InternalTransferPallet + InternalTransferSerial | No — unit re-emerges at destination |
| Scrap | Hand-off to physical recycling | SoldFulfilledOrScrapedLog | Yes |
8. Open questions for confirmation
Section titled “8. Open questions for confirmation”These are the things I could not answer from the code alone. Each one materially affects refactor planning.
- Triage → Imaging gating: is
StationTypeID == Deboxing(10)really the only valid predecessor to Triage? Where does Sorting fit? - Post-repair re-entry: how does a
Repaired=trueunit actually return to the happy path? Operator-driven scan? Special service method? See §6.2. - RecoveryEvent vs. HarvestingEvent: what is the operational distinction? See §6.5.
- Inventory write-down on consumption: where is
Part.ConsumedByWorkTrackingIDset, and where isInventoryTracker.AvailableQuantitydecremented? See §6.6. - IMC / FLIP grades: what do these mean operationally and what’s different in the route for these units?
- AS-IS path: does an AS-IS unit skip Cleaning/Grading entirely, or just visit modified versions of them? Where in the Route does the divergence happen?
- PromoteImageDownloadUnit vs. MoveFromWindowsTestSystem: are these genuinely two stations with two purposes, or duplication?
- DPK injection trigger: when is
WorkOrders.DoesBolNeedDigitalKeyInjectionset, and by what (UI, API, upload)? - Pallet Station vs. Finished Good: is Pallet Station always present, or only for projects that batch units onto pallets vs. ship individually?
- Pre-FGI vs FGI boundary: at exactly which station does StationCategory transition from 9 (Pre-FGI) to 10 (FGI)? Implementation suggests “when pallet closes” — confirm.
9. Reading on from here
Section titled “9. Reading on from here”- For the data shape behind these flows → 11-data-model.md (TBD)
- For the full list of every station type and what entity it owns → 12-station-catalog.md (TBD)
- For why the route engine works the way it does → 13-workflow-engine.md (TBD)
- For the running list of every code smell flagged above → 20-refactor-findings.md (TBD)