Skip to content

06 - Master data and constants ​

Source: wt-v2-prototype-core at commit 3d70475a. Record counts come from the local database tms_local_v2 (11 Nov 2026) and say what the demo data looks like, not what production will hold.

Master data here means reference records that change rarely and that transactions (delivery orders, plans) read from. The companion file 03-schema.md has the full field tables; this file says who creates each item, who reads it and what to know before using it.

Overview ​

#ItemCollectionWho creates / editsRecords locally
1StorestoresAdmin API only2
2Vehicle typevehicle_typesAdmin and store operator API0
3VehiclevehiclesAdmin API only0
4UserusersBoot seed, admin API12
5Rolenone (fixed list in code)Code release4 roles in code
6ApplicationapplicationsBoot seed, admin API3
7Plan templateplan_templatesAdmin and store operator API0
8Settings_settingsBoot-time code defaults, admin API6 records
9Storage settingrecord storage in _settingsSame as settings1 record
10TemplatetemplatesAdmin API2
11Export configexport_configsAdmin API0
12Field catalognone (built at boot)Computed from schemasnot stored
13ScheduleschedulesCode declaration plus admin API5 (QA rows)

Nothing in the Kafka intake creates master data. The only Kafka input is the delivery order message (do_consume), and it only reads store to fill the source_store snapshot.

All admin endpoints are under /v1/... and need the role shown in the "API" column of each section.


1. Store ​

QuestionAnswer
What it is forA depot or shop where deliveries start; its opening and closing time bound the planning day.
Created or edited byAdmin API: POST /store, PUT /store/:code, GET /store, GET /store/:code (role admin). No seed, no Kafka.
Read byDelivery order intake (fills source_store and source_geo from source_store.code), plan creation (copies { id, code, name, location } into plan.store and computes start_date and end_date), script and export entity store.
Records locally2 (001 "Store 001", 002 "Store 002")
Key fieldscode (unique), name, open_time and close_time ("HH:mm"), location (latitude, longitude), loading_bay.
Watch outChanging a store does not update the copies already inside plans or delivery orders. loading_bay is stored but nothing reads it.

2. Vehicle type ​

QuestionAnswer
What it is forClass of truck: capacity per round and the time it spends loading and unloading. Drives the optimiser.
Created or edited byAdmin API: POST /type/vehicle, PUT /type/vehicle/:code, GET (roles admin, store_operator). No seed.
Read byPlan creation (copies capacity, loading_time, unloading_time into each shift; fails if a template names a type that no longer exists), vehicle create / update (snapshot).
Records locally0
Key fieldscode (unique), name, capacity { weight kg, volume m3 }, loading_time (min, default 0), unloading_time (min per order, default 0; the planner uses 20 when this is 0).

3. Vehicle ​

QuestionAnswer
What it is forA physical truck identified by plate number.
Created or edited byAdmin API: POST /vehicle, PUT /vehicle/:code, GET (role admin). No seed.
Read byOnly its own API and the script entity vehicle. The planner does not read vehicles yet: plan shifts are built from vehicle types, and shift.vehicle stays empty.
Records locally0
Key fieldscode (required, unique), plate_number, vehicle_type snapshot { id, code, name }. Columns status, type, delivery_support_type, loading_time, unloading_time exist but are never written.
Watch outcode is required by the schema but optional in the request; the default-code line is a comparison, so a request without code fails.

4. User ​

QuestionAnswer
What it is forLogin accounts: admins, store operators, drivers, and an internal system actor.
Created or edited byBoot seed (two accounts, below), admin API POST /user (role admin; assignable roles admin, store_operator, driver), PUT /user/me (any signed-in user edits own name; role cannot be changed). Login stamps last_logged_in_at.
Read byLogin and token issue (auth service), role guard on every route, activity log actor, plan (driver and actor references), script entity user.
Records locally12 (8 active admins, 2 active store operators, 1 active driver, 1 internal system user; most are QA accounts)
Key fieldsuser_id (uuid, unique), username (unique), first_name, last_name, role, state, password (hash, hidden in output), system.

Boot seeds

AccountCreated whenRole / statePurpose
systemModule init, before any other hooksystem_admin, inactive, system: trueThe actor recorded for work no request started (Kafka consumer, schedule tick, boot). Cannot log in.
system_adminApplication bootstrap, only if missingadmin, activeFirst admin. Password comes from the environment variable DEFAULT_SYSTEM_ADMIN_PASSWORD (value not shown here).

5. Role ​

QuestionAnswer
What it is forDecides which API routes a user may call.
Created or edited byCode only. There is no role collection or role schema in v2: the four roles are the UserRole enum and each controller states its roles with @Roles([...]).
Read byAuth guard, user create validation, application roles validation, activity log actor.
Records locallyThe local db still has a roles collection with 9 rows (code, name) copied from the v1 clone. v2 code never reads it. Do not treat it as part of the model.
ValuesSee the constants section below.

6. Application ​

QuestionAnswer
What it is forA client (admin web, driver app, integration) that exchanges its key pair for an access token. Decides which roles may sign in through it and whether 2FA or a registered device is required.
Created or edited byBoot seed creates default if missing; admin API POST /application, PUT /application/:code, PATCH /application/revoke (new secret), DELETE /application/:code. The list hides the default row.
Read byAuth service when issuing a token (looks up by app_key), role check on sign-in.
Records locally3 (default, qa_app_ac13, qa_app_driver)
Key fieldsname (unique), code (unique), app_key (unique, 32 chars), secret_key (44 chars; masked in API output, value not shown here), enabled, roles[], enable_2fa, require_device.
Boot seeddefault: enabled, roles [admin]. Key and secret come from DEFAULT_APP_KEY and DEFAULT_SECRET_KEY when set, otherwise random.

7. Plan template ​

QuestionAnswer
What it is forRecipe for a plan day: how many trucks of each vehicle type work and between which hours.
Created or edited byAdmin and store operator API: POST /template/plan, PUT /template/plan/:code, GET (roles admin, store_operator). No seed.
Read byPlan creation: copies { id, code, name } into plan.plan_template and expands each entry into one shift per truck (amount rows, labelled <vehicle_type>-<n>).
Records locally0
Key fieldscode (uuid generated by service), name, shifts[] = { vehicle_type (code), amount >= 1, start_time, end_time }.
Watch outEditing a template does not change plans already created from it. Deleting a vehicle type breaks plan creation from any template that names it.

8. Settings (records and keys) ​

QuestionAnswer
What it is forRuntime configuration that operations can change without a deploy: log retention, login OTP, file storage, Kafka connection and topics, Google keys, script limits.
How it worksCode declares each record and key with a type and a default. At boot every declared record is inserted if missing (values: {}, rev: 1), then all records are loaded into an in-memory cache on each pod. A key absent from values reads as its code default. A stored value that fails validation is ignored and the code default is used (with a warning). Pods reload every 60 s and on a Redis publish after each save.
Created or edited byBoot code (records and defaults); admin API: GET /settings, GET /settings/:code, PUT /settings/:code (send current rev; a stale rev returns 409), POST /settings/:code/reset (back to code default; encrypted keys cannot be reset). Role admin. Every change writes an activity_logs row; secret changes record only that the key changed.
Read byThe owning module through SettingsService.get(key) or getSecret(key).
Records locally6 (log, authen, storage, kafka, google, script)
SecretsKeys marked secret are encrypted at rest and shown as a mask (••••) by the API. Values are not listed here.

Setting keys and code defaults

Record (owner)KeyCode defaultSecretUsed for
log (log)activity_logs{ days: 60 }noRetention of audit rows. Allowed range 1 to 365 days.
logkafka_logs{ days: 60 }noRetention of Kafka message log.
loggoogle_logs{ days: 30 }noRetention of Google call log.
authen (auth)enable_universal_otpfalsenoAccept a fixed OTP list at login (test use).
authenuniversal_otpnoneyesJSON list of accepted codes.
storage (storage_setting)max_file_size_mb10 (1 to 100)noUpload size limit.
storageallowed_extensions[]noExtra extensions.
storageprovideraws_s3noOnly provider supported.
storagebucket""noEmpty means "storage not configured" and uploads return 503.
storageregion""no
storageprefix""no
storagekms_key_id""no
storagecredential_modeiam_rolenoiam_role or static.
storageaccess_key_id""noUsed only in static mode.
storagesecret_access_keynoneyesUsed only in static mode.
kafka (kafka)bootstrap_serversenvironment KAFKA_BROKERS split by comma, else localhost:9092noBroker list.
kafkaauthen_typePLAINTEXTnoPLAINTEXT, SSL, SASL_PLAINTEXT, SASL_SSL.
kafkaauthen_setting{}noSASL mechanism, username, CA / cert PEM, reject-unauthorised flag.
kafkaenabledtruenoMaster switch.
kafkaeventsthe six declared events (table below) with their default topic and enabled: truenoOps can change topic name or switch an event off. An event missing from the list takes the declared default.
kafkasasl_passwordnoneyes
kafkassl_key_pemnoneyes
google (google)google_map_api_key""noBrowser map key. Not encrypted on purpose: it is returned to any visitor by the public route GET /settings/public.
googleroute_optimization_service_accountnoneyesService account JSON text for Google Route Optimization.
script (script_runner)max_calls20noDatabase calls a script may make per run.
scriptmax_docs1000noDocuments a script may read per run.
scriptwall_ms2000noTime limit per run.
scriptheap_mb64noMemory limit per run.
scriptexport_state_kb64noState a script may keep between export batches.
scriptmax_bulk_records50noRecords per bulk call.

Local drift to know about: the log record also holds kafka_api_logs and google_api_logs keys, and the storage record holds allowed_image_extensions, endpoint and force_path_style. Current code declares none of them, so they are ignored.

9. Storage setting ​

QuestionAnswer
What it is forWhere uploaded files go (S3 bucket, region, key prefix, KMS key, credentials) and the upload limits.
Created or edited byIt has no schema or collection of its own. It is the settings record storage (owner storage_setting, display name "Storage"); edit it through the settings API above.
Read byStorageSettingService builds the S3 client (rebuilt when the record changes) for the attachment module. S3_ENDPOINT (environment) overrides the host for local testing. Attachment purposes limit file types per use (constants section).
Records locally1 record in _settings. The old v1 storage_settings collection (1 row) is also present and unused by v2.
Key fieldsSee the storage rows in the settings table.

10. Template (print, email, SMS) ​

QuestionAnswer
What it is forA document layout rendered from one entity: print sheet, email, or SMS text.
Created or edited byAdmin API: GET /templates, GET /templates/:id, POST /templates, PUT /templates/:id (send revision), POST /templates/render (preview). No seed.
Read byTemplate render service, which builds the context for the chosen entity, runs the template script and fills the body.
Records locally2 (qa-dispatch-sheet print, qa-sms-fixture sms; both on entity plan)
Key fieldscode (unique), name, channel, entity, page (paper, orientation, margin, font), subject (email), script, body, revision.

11. Export config ​

QuestionAnswer
What it is forAn admin-defined spreadsheet export for one entity: which columns, number formats, optional script and footer sums.
Created or edited byAdmin API: GET /export_configs, GET /export_configs/:id, POST /export_configs, PUT /export_configs/:id (send revision; stale revision returns 409). No seed.
Read byThe export run, with the field catalog validating every column path.
Records locally0
Key fieldsname, entity, columns[] (type field, text or script; path, header, format, align, width, footer_sum), script, script_reads, revision.
Number formats allowed#,##0, #,##0.00, #,##0.000

12. Field catalog ​

QuestionAnswer
What it is forList of the entities and fields that exports and templates may use, so an admin cannot name a field that does not exist.
Created or edited byNobody. Built at application bootstrap by reading the mongoose schemas of the registered script entities, dropping internal fields and anything the read API hides (for example user.password).
Read byExport config validation, template validation, admin UI field picker through GET /export_fields and GET /export_fields/:entity (role admin). It can follow a ref, so source_store_id.name is valid.
Records locallyNot stored. Entities offered: delivery_order, delivery_order_item, vehicle, plan, user, store (six).
Key fieldsPer field: name (array children written parent[].child), type, ref, array, system.

13. Schedule ​

QuestionAnswer
What it is forRecurring jobs (cron).
Created or edited byA feature module declares a schedule in code (code, cron, missed-slot policy, retry count); the boot sync upserts it. Admin API: GET /schedules, enable, disable, set or clear cron_override (role admin). Code owns the cron; admin owns enabled and cron_override.
Read byThe schedule ticker (every 15 s), which enqueues a job per due slot.
Records locally5, all qa_* test rows. No feature module declares a real schedule yet.
Key fieldscode, cron, cron_override, enabled, missed_slot_policy, retry_count, next_run_at.

Enum and constant lists that act as master data ​

These live in code (release to change), but the screens, the Kafka messages and the planner depend on them.

Delivery order ​

ListValuesWhere used
StatusPENDING, PLANNED, IN_TRANSIT, DELIVERED, FAILED, CANCELLEDdelivery_order.status. A status change enqueues the OMS and staff-app updates. Only PENDING is set by v2 code.
TypeDELIVERY, PICKUP, RETURNdelivery_order.type
Delivery typeSTANDARD, ON_DEMANDdelivery_order.delivery_type
Channel codes routed to OMSDONJAI, CHOKCHAI, HLHChoose whether do_update_oms is sent (chanel.chanel_code)
Channel codes routed to staff appSPO, OD, HLHChoose whether do_update_staffapp is sent
Running numberDO + 8 digits (counter delivery_order); tasks TK + 8 digits (counter task)delivery_no, task_no when none supplied

A DO whose channel code is in neither list is stored but no outbound update is queued (a warning is logged).

Plan ​

ListValuesWhere used
Plan statuspending, planned, generatedplan.status (stop edits need planned; generated blocks optimise)
Plan operation typefixed, re_generateEnum only; not stored on any schema
Stop typeDEPARTURE, DELIVERY, RETURNplan.tasks[].stops[].type
Violation codesEXCEED_SHIFT_WINDOW, EXCEED_WEIGHT, EXCEED_VOLUME, LATE_AT_STOP, STALE_ETA (the type file also names ROUND_OVERLAP, INSUFFICIENT_TURNAROUND)plan.tasks[].violations[].code
Plan history actionsCREATE, RE_PLAN, RECALCULATE, MOVE_STOP, ASSIGN_STOP, UNASSIGN_STOP, RESETplan_history.action; scope values all, dirty
Trip colours#2563eb, #16a34a, #ea580c, #9333ea, #0891b2, #dc2626, #ca8a04, #4f46e5One per task index, shared by Gantt, map and list
Planner knobsTimezone Asia/Bangkok; same-address precision 4 decimals (about 11 m); default unloading 20 min; cost per km 15, per hour 250, fixed per round 500; avoid tolls and highways; load scale 1000 for Googleplan.constants.ts, google.constants.ts

Unallocated reason codes ​

Written to plan.unallocated[].reason_codes, with Thai text in reason_label.

CodeMeaning (Thai label in code)
MANUALLY_UNASSIGNEDA person removed the order from the plan; keeps it out of the next re-plan
NO_VEHICLENo vehicle can take it
DEMAND_EXCEEDS_VEHICLE_CAPACITYWeight or volume above every vehicle
CANNOT_BE_PERFORMED_WITHIN_VEHICLE_DISTANCE_LIMITToo far
CANNOT_BE_PERFORMED_WITHIN_VEHICLE_DURATION_LIMITNot enough working time
CANNOT_BE_PERFORMED_WITHIN_VEHICLE_TRAVEL_DURATION_LIMITTravel time over the limit
CANNOT_BE_PERFORMED_WITHIN_VEHICLE_TIME_WINDOWSNo round meets the promised time
VEHICLE_NOT_ALLOWEDLocked to a vehicle that cannot take it
NO_GEO_LOCATIONNo destination coordinate
(fallback text)Used when Google returns no reason

Users, devices, applications ​

ListValuesWhere used
User roleadmin, store_operator, driver, system_adminuser.role, application.roles, @Roles(...) on controllers. system_admin only for the seeded internal user; assignable by API: the other three.
User statestaged, active, inactive, locked_out, suspendeduser.state. Login requires active.
Device platformandroid, iosdevice.platform
Fixed namesDefault application name Default Application, code default; system username system; seeded admin username system_adminBoot seeds
Key lengthsApp key 32 chars, secret 44 chars, access token 44 chars, token lifetime 1 yearAuth

Files ​

ListValuesWhere used
Attachment purposeadmin_file: private, extensions jpg, jpeg, png, webp, pdf, max 20 MBUpload rules per purpose (attachment.purposes.ts)
Attachment statuspending, scanning, ready, failedattachment.status
Scan statuspending, clean, infectedattachment.scan_status
Visibilityprivateattachment.visibility
Storage provider and credential modeaws_s3; iam_role, staticStorage setting
LimitsUpload URL valid 5 min, download URL 15 min, max 20 pending per user, 30 upload URLs per minute, pending expires after 6 min, scanning stale after 5 minattachment.constants.ts

Templates and exports ​

ListValues
Template channelprint, email, sms
PaperA4, A3, A5, Letter
Orientationportrait, landscape
Export column typefield, text, script
Number formats#,##0, #,##0.00, #,##0.000
Script limits (code fallback when the setting is missing)calls 20, docs 1000, wall 2000 ms, heap 64 MB, export state 64 KB, bulk 50; forbidden filter operators $where, $function, $accumulator, $expr; database functions find, get, count

Kafka events (master list of topics) ​

Declared in kafka.constants.ts; the kafka.events setting can rename a topic or disable an event.

EventDirectionDefault topic
do_consumeconsumebigc.lastmiles.do-post
do_update_omsproducelastmiles.oms.do-update
do_update_staffappproducelastmiles.staffapp.do-update
load_updateproducelastmiles.staffapp.load-update
task_updateproducelastmiles.staffapp.task-update
driver_updateproducelastmiles.staffapp.driver-update

Only do_consume, do_update_oms and do_update_staffapp have code behind them today. Consumer group prefix: environment KAFKA_GROUP_PREFIX, else KAFKA_CLIENT_ID, else bigc-tms-core-v2. Kafka log status values: sent, consumed, failed; direction in, out.

Jobs and schedules ​

ListValues
Job statusPENDING, RUNNING, DEAD
Job lanefast, long
Delivery modeat_most_once, at_least_once
Partitions and batches64 partitions; fast batch 100, long batch 10
Retry delays1, 2, 4, 8, 16 minutes (6 attempts in total)
LeasesPartition lease 30 s with 10 s heartbeat; long job lease 5 min with 1 min heartbeat
Missed-slot policyrun_once, skip (crons running every 5 minutes or faster default to skip)
Schedule defaultsRetry count 3; tick every 15 s; slot late after 60 s counts as missed; cron timezone fixed at UTC+7
Activity actor typesuser, system
Activity actions in codedo-create, do-update, setting-change, and the plan history actions above