Appearance
TMS v2 — system overview
One picture of TMS v2 as it stands on 11 Nov 2026: what the system does, its parts, which module owns what, and how far the build has come. The code facts come from bigc-tms-core-v2 branch main-v2-prototype at commit 3d70475a. The design facts come from the seven module designs in the discovery workspace. For the per-feature status, see 01-feature-list.md.
What the system does
TMS (transport management system) delivers BigC customer orders from a store to the customer's door.
- BigC's order system (OMS) sends each order over Kafka; TMS turns it into a delivery order (DO) for the store that ships it.
- Each day an ops admin plans one store's waiting orders into vehicle routes with Google Route Optimization, adjusts them by hand, and turns each route into a driver task.
- The driver runs the task in the driver app: load at the store, deliver stop by stop with proof of delivery, collect cash on delivery (COD), report failed attempts, return undelivered goods.
- The store takes the driver's cash per task; ops follow open cash, holds and overdue tasks.
- Every DO change is sent back to OMS and to BigC's staff app over Kafka, and every change is kept as history.
- v2 is a ground-up NestJS rewrite of v1 (which was built on the internal cc-core-lib framework). Stores will move from v1 to v2 one at a time.
Components
| component | what it is | state |
|---|---|---|
core-v2 (bigc-tms-core-v2) | NestJS backend: REST API, Kafka consumer and producer, job workers, schedule tick. Every pod runs all of them | prototype on main-v2-prototype; 28 modules, 26 with code that runs |
store-v2 (bigc-tms-store-v2) | Next.js admin. Its own API routes (login, revoke, entity CRUD, settings, template preview, Kafka test) proxy to core-v2, so the browser never calls core directly | prototype; built alongside core |
| Driver app v2 | Mobile app for drivers | not started; v1 driver app is the reference; the backend endpoints it needs are designed, not built |
| MongoDB | One database for every v2 stack; business writes use transactions so a change, its history row and its outbound event commit together | in use |
| Redis | Login sessions (one key per token) and the Bull queue for jobs that may be lost | in use |
| Kafka / OMS | Inbound orders from OMS; outbound DO updates to OMS and the staff app, routed by sales channel | in use (one inbound topic, two outbound topics) |
| Route Optimization API for planning; Maps key handed to the admin | in use | |
| S3 | Files and photos, uploaded straight from the client with a presigned URL; a sweeper removes unconfirmed uploads | in use |
Module map
Each core-v2 module owns the collections it writes; others call its service. Calls go one way: business modules call platform modules, platform modules call common. One known wrong-way link remains: the login guard in common uses the application module; it is left for the auth work.
Business modules
| module | owns (collections) | talks to | state |
|---|---|---|---|
| delivery_order | delivery_order, delivery_order_version (outbound version counter) | store, activity_log, jobs (outbox), kafka (consumer registration), script_runner | prototype |
| delivery_order_item | delivery_order_item | delivery_order, script_runner | prototype |
| delivery_order_revision | delivery_order_revision (reschedule trail) | none yet | schema and service only, not called |
| plan | plan (with routes, stops and unassigned list inside) | delivery_order, plan_template, store, vehicle_type, plan_history, google, script_runner | prototype |
| plan_history | plan_history | activity_log | prototype |
| plan_template | plan_template | none | prototype |
| task | task (number only) | none | stub |
| store | store | script_runner | prototype |
| vehicle | vehicle | vehicle_type, script_runner | prototype |
| vehicle_type | vehicle_type | none | prototype |
| user | user | activity_log, script_runner | prototype |
| auth | sessions in Redis (no collection) | application, device, user, settings | prototype; security fixes open |
| application | application (API clients) | none | prototype |
| device | device | none | prototype |
Platform modules
| module | owns (collections) | talks to | state |
|---|---|---|---|
| jobs | jobs, job_partitions, job workers, job counters | Redis (Bull) for the lossy lane | built |
| schedules | schedules | jobs | built; no business schedule declared |
| kafka | kafka_consumed (7-day dedupe), kafka consume dead letters, kafka_log | jobs, log, settings | built |
| settings | settings | none (others read it) | built |
| storage_setting | an encrypted entry in settings | settings | built |
| log | the log base: expiry indexes, retention setting, writers | settings | built |
| activity_log | activity_logs (history of every record) | log, settings | built |
| attachment | attachment | storage_setting, S3 | built |
| google_log | log, settings, Google APIs | built | |
| script_runner | none (sandboxed data scripts; a registry of entities scripts may read) | settings | built |
| template, template_engine | template | script_runner | built |
| export_config, field_catalog | export_config; field catalog has no collection | script_runner | built; running an export is not |
Designed modules with no code yet (from the module designs)
| module | will own | will talk to |
|---|---|---|
| intake (on top of delivery_order) | intake_message (parked orders), ops_follow_up, channel, Kafka mapping versions | delivery_order, store, channel, platform versioned config |
| execution | task (full), stop, visit record per task and DO, proof of delivery, attempt, hold, GPS points, failure reasons | plan, delivery_order, workforce, cod-payment, attachment, jobs |
| cod-payment | COD fields on the DO, cash handover, handover adjustment, short-handover reasons | execution, staff PIN, store settings |
| workforce | driver, partner, booking, duty record, leave, break reasons | user, vehicle, planning |
| admin-reporting | export runs, import runs, roles and permissions | every module (read), jobs |
Build order
Seven phases, approved on 29 Oct. Phases 2 and 3 can run side by side after phase 1; phase 7 starts after phase 2 and ends after phase 6. Sizes are relative.
| phase | modules | what works at the end | size | state today |
|---|---|---|---|---|
| 1 | platform | Login without today's security gaps; every event, email and job saved with its business change and retried; one history table; files straight to S3; notifications | L | In progress. Done: jobs engine, schedules, Kafka outbox, history, typed settings, log retention, trace id, S3 files. Open: token lifetime, revoke and password hashing fixes; refresh tokens and lockout; email sending. Roles and permissions as data: designed, build about to start |
| 2 | intake + masters | Stores and channels; Kafka orders become DOs within 5 s, each message applied once, unknown-store orders parked and replayable; cancel and redelivery rules; admin create and Excel import | M | Started in the prototype, ahead of phase 1 finishing. Done: DO and item REST, Kafka intake applied once, store master, DO updates out. Open: routing rules, parking, cancel, redelivery, channel master, Excel import, mapping versions |
| 3 | workforce | Drivers, vehicles (unique plate), partners, leave; bookings; duty record with clock-in and out, break, hold; day close | L | Not started. Only users, devices, vehicles and vehicle types exist |
| 4 | planning | One chosen plan per store and date; optimisation as a job; hand edits; generate tasks in one all-or-nothing step; add an order to a route not yet started | L | Prototype exists, ahead of order. Done: plan from template, Google optimisation, move, assign, unassign, pins, history. Open: seven plan states, variants, claim at generate, optimisation as a job, task generation (endpoint is a stub) |
| 5 | execution + COD collection | The driver's run end to end, COD in the delivery step, failed attempt to hold, return and close-out, GPS trail, audited ops corrections | L | Not started (task record is a stub; driver app v2 not started) |
| 6 | COD handover | Cashier PIN of the task's store; one handover per task with shortage reason; ops list of open cash; optional dispatch block | M | Not started |
| 7 | admin-reporting | Admin menus; reports on live data (up to 31 days, first page in 2 s); the import and export configs ops still use; roles with partner scope | M | Foundations only. Done: templates (print, email, SMS render), export configs, field catalog, data script sandbox. Open: running exports, imports, reports, permission per endpoint, partner scope |
Before each phase starts, what is already on main-v2-prototype is read and reused, not rebuilt. Each phase also waits on BigC rules that touch it (18 are still open, listed in workscope.md § "Rules BigC must confirm").
Where code and design differ today
These are the gaps a reader is most likely to trip over when comparing the prototype with the design.
| topic | code today | design |
|---|---|---|
| DO status names | v1-style: PENDING, PLANNED, IN_TRANSIT, DELIVERED, FAILED, CANCELLED | v2 names: UNASSIGNED, ASSIGNED, DISPATCHED, OUT_FOR_DELIVERY, HOLD, DELIVERED, FAILED_DELIVERY, CANCELLED, with a fixed move table |
| Plan states | 3 (pending, planned, generated) | 7 (adds optimising, failed, generating, cancelled) |
| Optimisation | runs inside the HTTP request | runs as a background job that answers at once |
| Generate tasks | endpoint returns success and does nothing | one transaction per route: task, stops, totals, orders ASSIGNED |
| Order claim | none; optimise reads PENDING orders of the store and date | claimed only at generate, with a conditional write |
| Unknown store on an order | the message fails and is dead-lettered | the order is parked with its payload and can be replayed |
| Outbound channel lists | fixed in code (OMS: DONJAI, CHOKCHAI, HLH; staff app: SPO, OD, HLH) | configuration |
| Load, task and driver Kafka topics | event names still declared in code, never sent | not built; PROD never had them |
| DO lines | REST allows create, update and delete | DO lines are transactional and change only through intake and execution commands |
| Vehicle plate | not unique | unique plate |
| Auth | 1-year tokens, revoke accepts any token, SHA-256 hash salted with the creation date, role check by a fixed role list | short-lived access token plus rotating refresh token, lockout, stronger hash, roles as data with permission keys |
| Postal-code zones | none | planning design retires them; a later decision moves them into a v2 zoning design that is not written yet |