Skip to content

03 - Data model (schema reference) ​

Source: wt-v2-prototype-core at commit 3d70475a (branch main-v2-prototype), schemas under src/*/. Counts come from the local database tms_local_v2 (read-only look, taken 11 Nov 2026).

How to read this document ​

TopicRule in TMS v2
StackPlain NestJS 10 + Fastify + mongoose 9. No cc-core-lib.
RelationsPlain string ids or business codes. No mongoose populate. A few fields carry ref: 'x' only as a hint (the field catalog uses it to walk source_store_id.name).
SnapshotsSome documents copy part of another document when they are created (for example plan.store). The copy is not refreshed when the source changes.
Base fieldsEvery schema that extends BaseSchema gets _id, created_at, updated_at, __v and an unused _translates (Mixed). They are left out of the tables below. JSON output drops _id and __v and exposes id.
EnumsAlmost no enum is enforced by mongoose. Statuses and types are plain strings, checked only by DTO validators or service code. Real schema-level enums: export_config.columns[].type (field, text, script), template.channel / page.paper / page.orientation, kafka_logs.direction / status.
MoneyDecimal128 fields are turned back into numbers (2 decimals) when a document is serialised.
Required"Required" in the tables means required: true in the mongoose schema. DTO-level rules are mentioned in the note.
Collection nameModel name is snake_case(ClassName), mongoose pluralises it. Exceptions are stated.

Collection count: 28 mongoose collections in code. Two more names in the spec list have no collection of their own: storage_setting (a settings record, see section 16) and field_catalog (computed at boot from the other schemas, see section 18).

Entity relationship diagram ​

Solid lines with a snapshot label mean the child copies fields of the parent. Plain labels mean only an id or code is stored.

Not drawn: kafka_logs, google_logs (stand-alone log tables joined by trace_id only) and counters (the running-number table).

Shared building blocks ​

These small shapes are reused inside several schemas. They are plain nested objects, not collections.

NameFieldsUsed in
GEO_POINTlatitude Number, longitude Numberdelivery_order.source_geo / destination_geo, device.last_location, plan stop location
TIME_WINDOWfrom String "HH:mm", to String "HH:mm" (wall clock, resolved against the plan date, never a timestamp)plan shifts, rounds, stops, stop orders
CAPACITYweight Number (kg), volume Number (m3)plan shifts, tasks, stop load_after
USER_REFuser_id String (ref user), name Stringplan driver / moved_by, plan_history by
VEHICLE_REFcode String (ref vehicle), plate_number Stringplan shift and task vehicle

1. delivery_order ​

PurposeOne delivery job (DO) received from OMS over Kafka or created by API; the main transaction record.
Collectiondelivery_orders
Points tostore (by source_store_id, plus snapshot), task (by current_task_id, never written)
Written byKafka consumer do_consume (create or update by delivery_no), admin API. Items service rewrites the totals.
Embedded snapshotsource_store = { id, code, name } copied from store when the message carries source_store.code; source_geo is also taken from the store location.
Local data7 documents
FieldTypeRequiredDefaultEnum / valuesNote
delivery_noStringyes--Business key, unique. Generated as DO + 8 digits when missing.
typeStringno-DELIVERY, PICKUP, RETURN (DTO only)
delivery_typeStringno-STANDARD, ON_DEMAND (DTO only)
statusStringnoset to PENDING on createPENDING, PLANNED, IN_TRANSIT, DELIVERED, FAILED, CANCELLEDNot enforced by schema. Only PENDING is ever set by code today (and every local row is PENDING). A status change enqueues the Kafka updates.
source_store_idString (ref store)no--Store id. Not indexed.
chanelMixedno--Spelling is from the source system. Object or array; chanel_code / chanel_name. Code decides routing: DONJAI, CHOKCHAI, HLH to OMS; SPO, OD, HLH to staff app.
source_from_storeBooleannotrue-
source_storeMixedno--Snapshot { id, code, name }.
source_addressMixedno--Free-form address object.
source_geoGEO_POINTno--Resolved from several candidate paths in the payload.
destination_to_storeBooleannofalse-
destination_addressMixedno--Free-form; planner reads first_name, last_name, street1.
destination_geoGEO_POINTno--Planner skips orders without a latitude.
expect_delivery_dateDateno--
delivery_time_windowMixedno--Shape { from, to } as "HH:mm". Not typed as TIME_WINDOW here.
current_task_idString (ref task)no--Never written by any service. Indexed.
total_weightNumberno--kg. Recomputed from items.
total_volumeNumberno--m3. Recomputed from items.
total_amountDecimal128no--Recomputed from items.
item_countNumberno--Recomputed from items.
is_codBooleannofalse-
cod_amountDecimal128no--
noteStringno--
sourceMixedno--Raw Kafka payload block (source[0].source_address[0].latlong is read from it). Not in the DTO.
destinationMixedno--Raw Kafka payload block. Not in the DTO.
itemsMixedno--Raw Kafka item list. Not the same thing as collection delivery_order_items.

Indexes

IndexKind
delivery_nounique
current_task_idnormal

2. job_counter ​

PurposePer-DO event sequence number, used as the version of outgoing do_update_* messages. Its own collection so deleting finished jobs never resets a version.
Collectionjob_counters (class name JobCounter, file delivery_order_version.schema.ts)
Points todelivery_order (_id is the delivery_no)
Local data4 documents
FieldTypeRequiredDefaultEnumNote
_idString---The delivery_no.
seqNumberno0-Incremented in the same transaction as the DO change.

Indexes: only _id. No created_at / updated_at (versionKey: false, but it still extends the base factory).

3. delivery_order_item ​

PurposeOne product line of a delivery order.
Collectiondelivery_order_items
Points todelivery_order (delivery_order_id and delivery_no)
Written byItem API only (create, update, delete); each write recomputes the totals on the parent DO. The Kafka intake does not create rows here (it keeps the raw list in delivery_order.items).
Local data0 documents
FieldTypeRequiredDefaultEnumNote
item_idStringyes--Unique; uuid when not supplied.
delivery_order_idString (ref delivery_order)no--Set from the DO found by delivery_no.
delivery_noStringno--Duplicate of the parent key; DTO makes it required.
product_skuStringno--
product_nameStringno--
quantityNumberno--
uomStringno--
amountDecimal128no--
weight_kgNumberno--Summed into delivery_order.total_weight.
volume_cbmNumberno--Summed into delivery_order.total_volume.

Indexes: item_id unique; delivery_order_id; delivery_no; product_sku.

4. delivery_order_revision ​

PurposeIntended trail of every placement of a DO onto a task (revision goes up on each reschedule).
Collectiondelivery_order_revisions
Points todelivery_order, task, plan (all by id string)
Written byNobody. createRevision exists in the service but no code calls it and the module has no controller.
Embedded snapshotsdelivery_order (Mixed) and task (Mixed) were meant to hold full copies at that moment.
Local data0 documents
FieldTypeRequiredDefaultEnumNote
codeStringnouuid set by service-Unique.
delivery_order_idStringno--
delivery_noStringno--
revisionNumbernolatest + 1-nth placement of this DO.
task_idStringno--
task_noStringno--
plan_idStringno--
reasonStringno--
delivery_orderMixedno--Snapshot of the DO.
taskMixedno--Snapshot of the task.

Indexes: code unique; delivery_order_id + revision(-1); task_id + created_at(-1).

5. task ​

PurposeIntended stand-alone delivery task (one round for one driver) with a running number TK########.
Collectiontasks
Points toNothing in the schema.
Written byNobody. createTask is not called anywhere; there is no controller. Real tasks live inside plan.tasks (section 6), keyed by task_key.
Local data0 documents
FieldTypeRequiredDefaultEnumNote
task_noStringnogenerated TK + 8 digits-Unique. The only field.

Indexes: task_no unique.

6. plan ​

PurposeOne day of route planning for one store: declared shifts, the optimiser output (tasks, stops) and the unallocated pool, all in a single document.
Collectionplans
Points tostore (snapshot), plan_template (snapshot), vehicle_type (values copied into shifts), vehicle and user (refs inside shifts and tasks), delivery_order (ids inside stop orders)
Written byPlan API (create from template), optimiser (replaces tasks, unallocated, optimize, sets status to planned), stop operations (move / assign / unassign edit tasks in place).
Embedded snapshotsstore = { id, code, name, location }; plan_template = { id, code, name }.
Local data0 documents

Top-level fields

FieldTypeRequiredDefaultEnum / valuesNote
codeStringnouuid set by service-Unique business key.
nameStringno--
target_dateDateno--The delivery day.
storeMixedno--Snapshot, see above. Indexed through store.code.
statusStringnopending on createpending, planned, generatedgenerated is only checked, never set. See oddities.
start_dateDateno--target_date + store open_time.
end_dateDateno--target_date + store close_time.
plan_templateMixedno--Snapshot, see above.
shiftsarray of Shiftno[]-Declared resource, one row per vehicle per day (one Gantt row). Built from the template and vehicle types at create time.
tasksarray of Taskno[]-Optimiser output; each one is a round (depot, deliveries, depot).
unallocatedarray of Unallocatedno[]-Orders the optimiser could not place.
optimizeMixedno--{ revision, ran_at, ran_by, engine: 'google_ro', summary }. revision goes +1 per successful run and is the optimistic-lock value.
generatedMixedno--Meant to be { task_ids, generated_at, generated_by }. Never written.

Shift (shifts[], no _id)

FieldTypeDefaultNote
shift_indexNumber-Position in the array; tasks point back to it.
shift_keyString-Stable uuid for the life of the plan.
shift_labelString-Example 4_wheels-1.
vehicle_typeString-Vehicle type code.
capacityCAPACITY-Copied from the vehicle type.
loading_timeNumber0Minutes at depot before each round.
unloading_timeNumber0Minutes per order at a stop.
windowTIME_WINDOW-Vehicle availability.
rounds[]{ round Number, window TIME_WINDOW }-Currently always one round.
vehicleVEHICLE_REF-Empty until real vehicles are bound; no code binds it yet.
driverUSER_REF-Empty; no code sets it yet.

Task (tasks[], no _id)

FieldTypeDefaultNote
task_keyString-Stable uuid; drag-and-drop target.
shift_key, shift_index, round, seqString, Number, Number, Number-Where the task sits in the plan.
shift_label, vehicle_type, capacity, round_window, vehicle, drivermixed-Copied from the shift so the task renders alone.
planned_start_at, planned_end_atDate-Optimiser vehicle start / end time.
stops[]array of Stop-See next table.
total_weight, total_volumeNumber0Derived.
utilization{ weight_percent, volume_percent }0 eachAgainst this round only.
total_distance_m, total_duration_s, order_count, stop_countNumber0Derived.
route_polylineString-Encoded polyline of the whole round.
colorString-One of 8 fixed trip colours.
violations[]{ code, message, stop_seq }-Codes: EXCEED_SHIFT_WINDOW, EXCEED_WEIGHT, EXCEED_VOLUME, LATE_AT_STOP, STALE_ETA (the type file also lists ROUND_OVERLAP, INSUFFICIENT_TURNAROUND).
is_dirtyBooleanfalseEdited by hand since the last optimise.

Stop (tasks[].stops[], no _id)

FieldTypeDefaultNote
stop_keyString-Stable across moves.
seqNumber-Order inside the task.
typeString-DEPARTURE, DELIVERY, RETURN.
locationGEO_POINT + name, address-
eta, etdDate-
service_duration_s, distance_from_prev_m, duration_from_prev_sNumber0
is_eta_staleBooleanfalseTrue after a manual move until the next optimise.
orders[]{ delivery_order_id, delivery_no, customer_name, weight, volume, cod_amount, time_window }weight / volume / cod 0Snapshot of each DO at this stop.
total_weight, total_volumeNumber0
load_afterCAPACITY-Running load after this stop.
time_windowTIME_WINDOW-Narrowest window among the orders.
is_lateBooleanfalseeta after time_window.to.
is_pinnedBooleanfalseMoved by hand; kept on re-optimise.
moved_from_task_key, moved_at, moved_byString, Date, USER_REF-Manual move trail.

Unallocated (unallocated[], no _id)

FieldTypeNote
delivery_noString
reason_codes[]StringGoogle Route Optimization reason codes, or MANUALLY_UNASSIGNED.
reason_labelStringThai wording.

Indexes

IndexKind
codeunique
store.code + target_datenormal

7. plan_history ​

PurposeThe plan screen's own audit trail: one row per optimise run or manual stop operation.
Collectionplan_histories
Points toplan (by plan_code), user (by by.user_id)
Written byPlan optimise and stop-operation services, inside the same transaction as the plan save (each write also leaves an activity_logs row). A failed optimise writes a stand-alone row with error.
Local data0 documents
FieldTypeRequiredDefaultEnum / valuesNote
event_keyStringyes--Unique: plan code + revision + action.
plan_codeStringyes--
atDateyesnow-
byUSER_REFno--Absent when the system acted.
actionStringyes-CREATE, RE_PLAN, RECALCULATE, MOVE_STOP, ASSIGN_STOP, UNASSIGN_STOP, RESETOnly RE_PLAN, RECALCULATE, MOVE_STOP, ASSIGN_STOP, UNASSIGN_STOP are written today.
revisionNumberno--Plan optimise revision at that moment.
delivery_nos[String]no--
from, to{ task_key, shift_label, round }no--Position before / after.
modeStringno--Never written. scope is used instead.
scopeStringno-all, dirty
affected_task_keys[String]no--
diffMixedno--{ assigned_in, assigned_out, moved, resequenced, unchanged_count }.
duration_msNumberno--
log_idStringno--Meant to point at the heavy Google payload; never written.
errorMixedno--{ code, message } on a failed run.

Indexes: event_key unique; plan_code + at(-1); by.user_id + at(-1).

8. plan_template ​

PurposeReusable recipe of shifts (vehicle type, how many, start and end time) a plan is created from.
Collectionplan_templates
Points tovehicle_type (by code inside shifts[].vehicle_type)
Local data0 documents
FieldTypeRequiredDefaultEnumNote
codeStringnouuid set by service-Unique.
nameStringno--
shiftsMixedno--Array of { vehicle_type code, amount (min 1), start_time "HH:mm", end_time "HH:mm" }, shape enforced by the DTO only.

Indexes: code unique.

9. vehicle_type ​

PurposeClass of truck with its capacity and handling times.
Collectionvehicle_types
Points toNothing.
Local data0 documents
FieldTypeRequiredDefaultEnumNote
codeStringno--Unique (declared twice, see oddities).
nameStringno--
capacityMixedno--{ weight kg, volume m3 }; DTO requires both numbers.
loading_timeNumberno0-Minutes at depot per round.
unloading_timeNumberno0-Minutes per order; planner falls back to 20 when 0.

Indexes: code unique (twice).

10. vehicle ​

PurposeA physical truck identified by plate.
Collectionvehicles
Points tovehicle_type (snapshot)
Embedded snapshotvehicle_type = { id, code, name } copied on create and update.
Local data0 documents
FieldTypeRequiredDefaultEnumNote
codeStringyes--Unique. DTO makes it optional but the code that should default it contains a comparison (==) instead of an assignment, so a missing code fails on save.
statusStringno--Never written.
plate_numberStringno--
typeStringno--Never written; overlaps vehicle_type.
delivery_support_typeStringno--Never written.
vehicle_typeMixedno--Snapshot.
loading_timeNumberno--Never written; the vehicle type holds this value.
unloading_timeNumberno--Never written.

Indexes: code unique. No index on plate_number.

11. store ​

PurposeA store or depot: where deliveries start and the opening hours that bound a plan day.
Collectionstores
Points toNothing.
Local data2 documents (001, 002)
FieldTypeRequiredDefaultEnumNote
codeStringyes--Unique. Same == problem as vehicle when no code is sent.
nameStringno--
open_timeStringno--"HH:mm". Plan start_date uses it.
close_timeStringno--"HH:mm".
locationMixedno--{ latitude, longitude }; DTO types both as strings, then parsed with pickGeo.
loading_bayNumberno--Stored; not read by any service.

Indexes: code unique.

12. user ​

PurposeLogin account for admin, store operator, driver and the system user.
Collectionusers
Points toNothing (role is a fixed enum, there is no role collection in code).
Local data12 documents
FieldTypeRequiredDefaultEnumNote
user_idStringyes--Unique uuid.
usernameStringyes--Unique.
first_name, last_nameStringno--
roleStringno-admin, store_operator, driver, system_adminsystem_admin only for the boot-seeded system user.
stateStringno-staged, active, inactive, locked_out, suspended
passwordStringno--Salted hash. Hidden from API output.
last_logged_in_atDateno--
systemBooleanno--True for the internal system user.

Indexes: user_id unique; username unique (declared twice).

13. application ​

PurposeA client app (admin web, driver app, integration) allowed to ask for access tokens, with its key pair.
Collectionapplications
Points toNothing (roles holds role names).
Local data3 documents
FieldTypeRequiredDefaultEnumNote
nameStringyes--Unique.
codeStringyes--Unique. default is the boot-seeded one.
app_keyStringnogenerated 32 chars-Unique.
secret_keyStringnogenerated 44 chars-Masked in API output.
enabledBooleanno--
roles[String]no-user role namesWhich roles may sign in through this app.
enable_2faBooleanno--
require_deviceBooleanno--Driver apps must register a device.

Indexes: name unique; app_key unique; code unique.

14. device ​

PurposeA driver's phone registered on login; keeps last position and activity.
Collectiondevices
Points touser (by user_id)
Local data0 documents
FieldTypeRequiredDefaultEnumNote
device_idStringno--Unique.
user_idStringno--Not indexed.
statusStringno--No service sets it.
name, brand, platform, platform_version, app_versionStringno-platform: android, ios (DTO)
last_logged_in_at, last_actived_atDateno--Updated on register and heartbeat.
last_locationGEO_POINTno--Updated by heartbeat.

Indexes: device_id unique.

15. attachment ​

PurposeMetadata of a file uploaded to S3 through presigned URLs, with virus-scan state.
Collectionattachments
Points touser (by uploader_id)
Written byAttachment API and the sweep job (every minute, fails stale pending / scanning rows).
Local data36 documents (statuses seen: ready, failed; purpose seen: admin_file)
FieldTypeRequiredDefaultEnum / valuesNote
purposeStringno-admin_fileDefined in code (attachment.purposes.ts): jpg, jpeg, png, webp, pdf, max 20 MB.
visibilityStringno-private
sourceStringno-v2
incoming_keyStringno--S3 key under v2/incoming. Unique.
keyStringno--S3 key under v2/files once confirmed.
name, size, mime, detected_mimeString / Numberno--
scan_statusStringno-pending, clean, infected
uploader_idStringno--
statusStringno-pending, scanning, ready, failed
failed_reasonStringno--
scanning_started_at, confirmed_atDateno--

Indexes: status + created_at; uploader_id + status; incoming_key unique.

16. settings (and the storage setting) ​

PurposeOne document per setting record (log, authen, storage, kafka, google, script); keys inside values. Code declares each key with a default; the document only stores overrides.
Collection_settings (explicit name, because the old v1 settings collection already exists in the same db)
Points toNothing. Each change also writes an activity_logs row (entity setting).
Local data6 documents (one per record)
Storage settingThere is no separate storage schema or collection. "storage_setting" is the setting record with code storage, owner storage_setting; see 06-master-data.
FieldTypeRequiredDefaultEnumNote
codeStringyes--Record code, unique. Pattern ^[a-z][a-z0-9_]*$.
ownerStringno--Module that declared the record.
nameStringno--Display name.
revNumberno0 (boot inserts 1)-Optimistic lock: a save must send the current rev.
valuesMixedno{}-Key to value. Encrypted keys hold an enc string; the API only returns a mask.
updated_byMixedno--{ id, name }.
updated_atDateno--Declared by hand because mongoose updatedAt is switched off for this schema.

Indexes: code unique.

17. export_config ​

PurposeAdmin-defined spreadsheet export for one entity: columns, optional script, footer sums.
Collectionexport_configs
Points toA script entity by name (entity), checked against the field catalog.
Local data0 documents
FieldTypeRequiredDefaultEnumNote
nameStringyes--
entityStringyes-script entity name
scriptStringno--Sandboxed JS.
script_reads[String]no--Entities the script may read.
columns[]{ type, path, text, header, format, align, width, footer_sum }no[]type: field, text, script
revisionNumberno0-Optimistic lock on update.
created_by, updated_byStringno--User id.

Indexes: only _id. No index on entity or name.

18. template (and the field catalog) ​

PurposePrint, email or SMS layout rendered from an entity with a small script and a body.
Collectiontemplates
Points toA script entity by name (entity).
Local data2 documents (both QA fixtures on entity plan)
Field catalogfield_catalog has no collection. A service builds, at application boot, a list of fields per script entity (delivery_order, delivery_order_item, vehicle, plan, user, store) by reading the mongoose schemas, minus fields the read API hides. Exports and templates validate paths against it.
FieldTypeRequiredDefaultEnumNote
codeStringyes--Unique.
nameStringyes--
channelStringyes-print, email, sms
entityStringyes-script entity name
pagesub-documentno-paper: A4, A3, A5, Letter; orientation: portrait, landscapeAlso margin_mm, font_url, font_family.
subjectStringno--Email only.
scriptStringno--
bodyStringno--
revisionNumberno0-Optimistic lock.
created_by, updated_byStringno--

Indexes: code unique.

19. jobs ​

PurposeDurable outbox and work queue: Kafka sends, schedule runs and other side effects, split into 64 partitions with a fast and a long lane.
Collectionjobs
Points toschedule (target schedule:<code>), job_partition (by partition)
Local data0 documents (successful jobs are deleted by the workers; failed ones stay as DEAD until retried through the jobs API)
FieldTypeRequiredDefaultEnumNote
codeStringyes--Event name, for example do_update_oms.
targetStringyes--Handler name, for example a Kafka topic target or schedule:<code>.
laneStringyes-fast, long
keyStringno--Ordering key, for example delivery_no.
partitionNumberyes-0 to 63
versionNumberno0-Event sequence for the key.
payloadMixedno--
dedupe_keyStringyes--Unique, for example do_update_oms:<delivery_no>:<version>.
statusStringnoPENDINGPENDING, RUNNING, DEAD
attemptsNumberno0-Max 6 (retry after 1, 2, 4, 8, 16 minutes).
next_run_atDateno--
last_errorStringno--
claimed_by, lease_until, claim_token, outcome_token, running_codeString / Dateno--Lease bookkeeping. running_code is unique among RUNNING rows (one run of an exclusive code at a time).
closed_atDateno--
trace_idStringno--
actorObjectno--Who enqueued it.
headersObjectno--Kafka headers such as event_id.

Indexes: dedupe_key unique; lane + partition + next_run_at (only PENDING); lease_until (only RUNNING); running_code unique (only RUNNING with the field set).

20. job_partition and job_worker ​

PurposeLease table for the 64 job partitions, and one heartbeat row per live pod, so pods share the partitions.
Collectionsjob_partitions, job_workers
Points tojob_partition.owner = job_worker.pod_id
Local data64 partitions, 3 workers

job_partitions

FieldTypeRequiredDefaultNote
partitionNumberyes-0 to 63, unique.
ownerStringnonullPod id holding the lease.
lease_untilDateno-Lease 30 s, heartbeat 10 s.
inflight[String]no[]Job ids running now; re-run by a pod that takes over.
inflight_tokenStringno-

Indexes: partition unique; owner.

job_workers

FieldTypeRequiredDefaultNote
pod_idStringyes-Unique.
heartbeat_atDateyes-

Indexes: pod_id unique; heartbeat_at. No TTL: rows of dead pods stay until cleaned by code.

21. schedule ​

PurposeCron table. Code declares the cron, missed-slot policy and retry count; admin owns enabled and cron_override.
Collectionschedules
Points tojobs through target schedule:<code>
Written byBoot sync (upsert of declared schedules), schedules API.
Local data5 documents, all qa_* test rows. No feature module declares a real schedule yet.
FieldTypeRequiredDefaultEnumNote
codeStringyes--Unique.
cronStringyes--Fires in a fixed UTC+7 offset.
enabledBooleannotrue-
cron_overrideStringno--Admin replacement of cron.
missed_slot_policyStringnorun_oncerun_once, skipCrons running every 5 minutes or faster default to skip.
retry_countNumberno3-
last_run_at, last_skipped_at, next_run_atDateno--

Indexes: code unique; next_run_at (only enabled: true).

22. Log collections (activity_logs, kafka_logs, google_logs) ​

All three share the base log fields and are built by the log registry: collection name = log key, indexes are created at boot (not by mongoose autoIndex), every row has an expire_at and a TTL index (expireAfterSeconds: 0). Retention days are a setting (log.<key>), read when the row is written, so a change applies to new rows only. activity_logs is written inside the business transaction; the other two are buffered (flushed every 1 s or 100 rows, capped at 1000 rows per pod).

Base fields (all three)

FieldTypeRequiredNote
atDateyesEvent time.
expire_atDatenoat + retention days; TTL.
trace_idString or nullnoRequest or job trace. Indexed on kafka_logs and google_logs, not on activity_logs.

22.1 activity_logs ​

PurposeBusiness audit trail: who did what to which record, with a before / after diff.
Collectionactivity_logs
Points toAny entity by entity + entity_id; user by actor.id
Local data142 documents; entities seen: delivery_order, setting, user, role (the role rows have no writer in the current code; no role schema exists)
Default retention60 days
FieldTypeRequiredNote
entityStringyesExamples delivery_order, plan, setting.
entity_idStringyesRecord id or code.
actionStringyesIn code today: do-create, do-update, setting-change, plus plan actions from plan history.
action_idStringyes
actor{ type, id, role, name, via, meta }notype is user or system.
channel, reasonStringno
changesMixednoField-level diff; secrets are recorded as { key, changed: true } only.
changes_truncated, changes_bytesBoolean, NumbernoLarge diffs are cut.
refsMixednoExtra context, for example the plan history event.
event_keyStringyesUnique; makes a replayed event a no-op.

Indexes: expire_at TTL; entity + entity_id + at(-1) + _id(-1); actor.id + at(-1) + _id(-1); event_key unique.

22.2 kafka_logs ​

PurposeOne row per Kafka message sent or handled.
Collectionkafka_logs
Local data4 documents (topics seen include bigc.lastmiles.do-post, lastmiles.oms.do-update, lastmiles.staffapp.do-update)
Default retention60 days
FieldTypeRequiredEnumNote
directionStringyesin, out
topic, key, message_idStringno-
partitionNumberno-
offsetStringno-
req_body, res_bodyMixedno-Bodies, truncated by size.
req_body_truncated, res_body_truncatedBooleanno-
req_body_bytes, res_body_bytesNumberno-
statusStringyessent, consumed, failed
error{ message }no-

Indexes: expire_at TTL; trace_id; at(-1).

22.3 google_logs ​

PurposeOne row per outbound Google call (token request, route optimisation).
Collectiongoogle_logs
Local data0 documents
Default retention30 days
FieldTypeRequiredEnumNote
systemStringyes-google.
operationStringyes-token, optimize.
method, urlStringno-
statusNumberno-HTTP status.
outcomeStringyesOK, ERROR, TIMEOUT
duration_ms, attemptNumberno-
req_headers, res_headers, req_body, res_bodyMixedno-
req_body_truncated, res_body_truncated, req_body_bytes, res_body_bytesBoolean / Numberno-
error{ code, message }no-

Indexes: expire_at TTL; trace_id; system + operation + at(-1).

23. Kafka intake bookkeeping (kafka_consumed, kafka_consume_dlq) ​

Purposekafka_consumeds de-duplicates inbound messages by consume_id (claim before handling). kafka_consume_dlqs keeps messages that failed permanently so ops can inspect and replay.
Collectionskafka_consumeds, kafka_consume_dlqs
Points toEach other through consume_id; no business entity.
Local data5 consumed, 0 dead letters

kafka_consumeds

FieldTypeRequiredDefaultNote
consume_idStringyes-Unique.
eventStringyes-Declared event name, for example do_consume.
topicStringno-
statusStringnoprocessingSet to done after success.
consumed_atDatenonowTTL anchor.

Indexes: consume_id unique; consumed_at TTL 7 days.

kafka_consume_dlqs

FieldTypeRequiredDefaultNote
consume_idStringyes-Unique.
eventStringyes-
topic, key, offsetStringno-
partitionNumberno-
rawMixedno-Original payload.
errorStringno-
attemptsNumberno1
statusStringnopendingresolved once a retry succeeds.
failed_atDatenonow

Indexes: consume_id unique. No TTL, so rows stay until removed by hand.

24. counters (running numbers) ​

PurposeAtomic counters for human-readable numbers: DO + 8 digits for delivery orders, TK + 8 digits for tasks.
Collectioncounters (model counter)
Defined insrc/common/utils/running_number.ts (an inline schema, not a *.schema.ts file). The file also calls mongoose.connect at import time, which is why it is easy to miss.
Local data0 documents (every local DO has an explicit delivery_no)
FieldTypeRequiredNote
codeStringyesCounter name (delivery_order, task). Unique.
prefixStringnoUpper-cased. A different prefix restarts the counter at 1.
counterNumberno

Schema oddities worth raising ​

#FindingWhereWhy it matters
1Collections that nothing writes: tasks, delivery_order_revisions (services exist, no caller, no controller). Field delivery_order.current_task_id is also never written.task, delivery_order_revision, delivery_orderTasks live inside plan.tasks; decide whether the standalone collections stay in the model.
2plan.generated and status generated are never set (only a guard reads it). plan_history.mode and log_id are never written.plan, plan_historyThe "generate" step after planning is not built yet.
3Plan status spelling differs: enum says pending, the type file says draft.plan.enum.ts vs plan.types.tsFront end must follow the enum (pending).
4Vehicle fields never written: status, type, delivery_support_type, loading_time, unloading_time. type overlaps vehicle_type. plan.shifts[].vehicle and driver are also never bound.vehicle, planDead columns; trucks are not used by the planner yet.
5Code default for store and vehicle is data.code == uuid (comparison, not assignment). A request with no code fails on the required unique field.store.service, vehicle.serviceBug; DTO says code is optional for vehicle.
6Same unique index declared twice (field unique: true plus schema.index): vehicle_type.code, user.username. Mongoose warns about duplicate index.vehicle_type, userHarmless, remove one.
7Duplicated data inside a DO: source / destination raw blocks beside source_address / destination_address / *_geo; source_store_id beside source_store.id; delivery_no copied into each item.delivery_order, delivery_order_itemTwo sources of truth for address and store.
8Missing indexes: delivery_orders has none on status, expect_delivery_date, source_store_id; devices.user_id; vehicles.plate_number; export_configs and templates on entity; plans.status.variousFine at 7 rows, matters at production volume.
9Enums are not enforced by mongoose for any status or type; only the DTO guards input. Kafka intake bypasses the DTO, so a bad status or type from a message is stored as is.all
10kafka_consume_dlqs and job_workers have no TTL; dead-pod rows and old dead letters accumulate.kafka, jobs
11settings.updated_at is declared by hand and timestamps.updatedAt is off, so it is only set by save / reset, not by the boot upsert.settingsSmall, explained by the schema comment.
12user.status is assigned when the initial admin is seeded but the schema has no such field, so mongoose drops it.user.serviceDead assignment.
13The local db carries tables from the v1 clone that no v2 schema owns: roles (9), settings, app_settings, script_settings, storage_settings, google_api_logs, kafka_api_logs, job_stats, qa_*, probes. Some stale indexes remain (plans.store_id, delivery_order_items.store_id). The log setting record also still holds keys kafka_api_logs and google_api_logs; the storage record holds allowed_image_extensions, endpoint, force_path_style that code does not declare.tms_local_v2Do not read these as part of the model.