@freighttech/terminal-tracking 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +121 -0
- package/dist/generated/entities/terminal_config/index.js +53 -0
- package/dist/generated/entities/terminal_config/index.js.map +7 -0
- package/dist/generated/entities/terminal_event/index.js +59 -0
- package/dist/generated/entities/terminal_event/index.js.map +7 -0
- package/dist/generated/entities/terminal_tracking_job/index.js +37 -0
- package/dist/generated/entities/terminal_tracking_job/index.js.map +7 -0
- package/dist/generated/entities/terminal_vessel_visit/index.js +41 -0
- package/dist/generated/entities/terminal_vessel_visit/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +26 -0
- package/dist/generated/entities.ids.generated.js.map +7 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +7 -0
- package/dist/modules/terminal_tracking/acl.js +14 -0
- package/dist/modules/terminal_tracking/acl.js.map +7 -0
- package/dist/modules/terminal_tracking/api/openapi.js +27 -0
- package/dist/modules/terminal_tracking/api/openapi.js.map +7 -0
- package/dist/modules/terminal_tracking/api/terminal-configs/route.js +140 -0
- package/dist/modules/terminal_tracking/api/terminal-configs/route.js.map +7 -0
- package/dist/modules/terminal_tracking/api/terminal-configs/test/route.js +58 -0
- package/dist/modules/terminal_tracking/api/terminal-configs/test/route.js.map +7 -0
- package/dist/modules/terminal_tracking/api/tracking-jobs/details/route.js +109 -0
- package/dist/modules/terminal_tracking/api/tracking-jobs/details/route.js.map +7 -0
- package/dist/modules/terminal_tracking/api/tracking-jobs/poll/route.js +58 -0
- package/dist/modules/terminal_tracking/api/tracking-jobs/poll/route.js.map +7 -0
- package/dist/modules/terminal_tracking/api/tracking-jobs/route.js +189 -0
- package/dist/modules/terminal_tracking/api/tracking-jobs/route.js.map +7 -0
- package/dist/modules/terminal_tracking/api/utils.js +12 -0
- package/dist/modules/terminal_tracking/api/utils.js.map +7 -0
- package/dist/modules/terminal_tracking/api/vessel-visits/route.js +83 -0
- package/dist/modules/terminal_tracking/api/vessel-visits/route.js.map +7 -0
- package/dist/modules/terminal_tracking/backend/terminal-configs/page.js +669 -0
- package/dist/modules/terminal_tracking/backend/terminal-configs/page.js.map +7 -0
- package/dist/modules/terminal_tracking/backend/terminal-configs/page.meta.js +19 -0
- package/dist/modules/terminal_tracking/backend/terminal-configs/page.meta.js.map +7 -0
- package/dist/modules/terminal_tracking/backend/terminal-containers/page.js +275 -0
- package/dist/modules/terminal_tracking/backend/terminal-containers/page.js.map +7 -0
- package/dist/modules/terminal_tracking/backend/terminal-containers/page.meta.js +19 -0
- package/dist/modules/terminal_tracking/backend/terminal-containers/page.meta.js.map +7 -0
- package/dist/modules/terminal_tracking/ce.js +35 -0
- package/dist/modules/terminal_tracking/ce.js.map +7 -0
- package/dist/modules/terminal_tracking/commands/index.js +4 -0
- package/dist/modules/terminal_tracking/commands/index.js.map +7 -0
- package/dist/modules/terminal_tracking/commands/terminal-configs.js +107 -0
- package/dist/modules/terminal_tracking/commands/terminal-configs.js.map +7 -0
- package/dist/modules/terminal_tracking/commands/terminal-tracking.js +27 -0
- package/dist/modules/terminal_tracking/commands/terminal-tracking.js.map +7 -0
- package/dist/modules/terminal_tracking/commands/tracking-jobs.js +56 -0
- package/dist/modules/terminal_tracking/commands/tracking-jobs.js.map +7 -0
- package/dist/modules/terminal_tracking/components/ContainerDetailsDrawer.js +371 -0
- package/dist/modules/terminal_tracking/components/ContainerDetailsDrawer.js.map +7 -0
- package/dist/modules/terminal_tracking/data/entities.js +337 -0
- package/dist/modules/terminal_tracking/data/entities.js.map +7 -0
- package/dist/modules/terminal_tracking/data/validators.js +98 -0
- package/dist/modules/terminal_tracking/data/validators.js.map +7 -0
- package/dist/modules/terminal_tracking/di.js +39 -0
- package/dist/modules/terminal_tracking/di.js.map +7 -0
- package/dist/modules/terminal_tracking/encryption.js +14 -0
- package/dist/modules/terminal_tracking/encryption.js.map +7 -0
- package/dist/modules/terminal_tracking/events.js +45 -0
- package/dist/modules/terminal_tracking/events.js.map +7 -0
- package/dist/modules/terminal_tracking/i18n/de.json +175 -0
- package/dist/modules/terminal_tracking/i18n/en.json +178 -0
- package/dist/modules/terminal_tracking/i18n/es.json +175 -0
- package/dist/modules/terminal_tracking/i18n/pl.json +206 -0
- package/dist/modules/terminal_tracking/index.js +15 -0
- package/dist/modules/terminal_tracking/index.js.map +7 -0
- package/dist/modules/terminal_tracking/lib/adapters/bct/adapter.js +164 -0
- package/dist/modules/terminal_tracking/lib/adapters/bct/adapter.js.map +7 -0
- package/dist/modules/terminal_tracking/lib/adapters/bct/auth/basic.js +23 -0
- package/dist/modules/terminal_tracking/lib/adapters/bct/auth/basic.js.map +7 -0
- package/dist/modules/terminal_tracking/lib/adapters/bct/incos-semantics.js +146 -0
- package/dist/modules/terminal_tracking/lib/adapters/bct/incos-semantics.js.map +7 -0
- package/dist/modules/terminal_tracking/lib/adapters/bct/types.js +1 -0
- package/dist/modules/terminal_tracking/lib/adapters/bct/types.js.map +7 -0
- package/dist/modules/terminal_tracking/lib/adapters/gct/adapter.js +106 -0
- package/dist/modules/terminal_tracking/lib/adapters/gct/adapter.js.map +7 -0
- package/dist/modules/terminal_tracking/lib/adapters/gct/auth/token.js +120 -0
- package/dist/modules/terminal_tracking/lib/adapters/gct/auth/token.js.map +7 -0
- package/dist/modules/terminal_tracking/lib/adapters/gct/gct-semantics.js +77 -0
- package/dist/modules/terminal_tracking/lib/adapters/gct/gct-semantics.js.map +7 -0
- package/dist/modules/terminal_tracking/lib/adapters/gct/types.js +1 -0
- package/dist/modules/terminal_tracking/lib/adapters/gct/types.js.map +7 -0
- package/dist/modules/terminal_tracking/lib/adapters/index.js +12 -0
- package/dist/modules/terminal_tracking/lib/adapters/index.js.map +7 -0
- package/dist/modules/terminal_tracking/lib/adapters/n4/adapter.js +121 -0
- package/dist/modules/terminal_tracking/lib/adapters/n4/adapter.js.map +7 -0
- package/dist/modules/terminal_tracking/lib/adapters/n4/auth/ropc.js +81 -0
- package/dist/modules/terminal_tracking/lib/adapters/n4/auth/ropc.js.map +7 -0
- package/dist/modules/terminal_tracking/lib/adapters/n4/http.js +66 -0
- package/dist/modules/terminal_tracking/lib/adapters/n4/http.js.map +7 -0
- package/dist/modules/terminal_tracking/lib/adapters/n4/n4-semantics.js +95 -0
- package/dist/modules/terminal_tracking/lib/adapters/n4/n4-semantics.js.map +7 -0
- package/dist/modules/terminal_tracking/lib/adapters/n4/parsers/data-table.js +70 -0
- package/dist/modules/terminal_tracking/lib/adapters/n4/parsers/data-table.js.map +7 -0
- package/dist/modules/terminal_tracking/lib/adapters/n4/parsers/vessel-data-table.js +44 -0
- package/dist/modules/terminal_tracking/lib/adapters/n4/parsers/vessel-data-table.js.map +7 -0
- package/dist/modules/terminal_tracking/lib/adapters/n4/proxy.js +15 -0
- package/dist/modules/terminal_tracking/lib/adapters/n4/proxy.js.map +7 -0
- package/dist/modules/terminal_tracking/lib/adapters/n4/types.js +1 -0
- package/dist/modules/terminal_tracking/lib/adapters/n4/types.js.map +7 -0
- package/dist/modules/terminal_tracking/lib/adapters/zoned-time.js +27 -0
- package/dist/modules/terminal_tracking/lib/adapters/zoned-time.js.map +7 -0
- package/dist/modules/terminal_tracking/lib/availability.js +40 -0
- package/dist/modules/terminal_tracking/lib/availability.js.map +7 -0
- package/dist/modules/terminal_tracking/lib/ensure-schedule.js +51 -0
- package/dist/modules/terminal_tracking/lib/ensure-schedule.js.map +7 -0
- package/dist/modules/terminal_tracking/lib/holds.js +240 -0
- package/dist/modules/terminal_tracking/lib/holds.js.map +7 -0
- package/dist/modules/terminal_tracking/lib/logger.js +19 -0
- package/dist/modules/terminal_tracking/lib/logger.js.map +7 -0
- package/dist/modules/terminal_tracking/lib/rate-limiter.js +30 -0
- package/dist/modules/terminal_tracking/lib/rate-limiter.js.map +7 -0
- package/dist/modules/terminal_tracking/lib/stops.js +48 -0
- package/dist/modules/terminal_tracking/lib/stops.js.map +7 -0
- package/dist/modules/terminal_tracking/lib/terminal-adapter.js +1 -0
- package/dist/modules/terminal_tracking/lib/terminal-adapter.js.map +7 -0
- package/dist/modules/terminal_tracking/lib/totp.js +41 -0
- package/dist/modules/terminal_tracking/lib/totp.js.map +7 -0
- package/dist/modules/terminal_tracking/migrations/Migration20260609120000_terminal_tracking.js +101 -0
- package/dist/modules/terminal_tracking/migrations/Migration20260609120000_terminal_tracking.js.map +7 -0
- package/dist/modules/terminal_tracking/migrations/Migration20260610120000_terminal_vessel_visits.js +39 -0
- package/dist/modules/terminal_tracking/migrations/Migration20260610120000_terminal_vessel_visits.js.map +7 -0
- package/dist/modules/terminal_tracking/migrations/Migration20260611120000_terminal_config_proxy.js +13 -0
- package/dist/modules/terminal_tracking/migrations/Migration20260611120000_terminal_config_proxy.js.map +7 -0
- package/dist/modules/terminal_tracking/migrations/Migration20260707120000_terminal_job_availability_markers.js +15 -0
- package/dist/modules/terminal_tracking/migrations/Migration20260707120000_terminal_job_availability_markers.js.map +7 -0
- package/dist/modules/terminal_tracking/migrations/Migration20260805120000_terminal_event_loaded_at.js +13 -0
- package/dist/modules/terminal_tracking/migrations/Migration20260805120000_terminal_event_loaded_at.js.map +7 -0
- package/dist/modules/terminal_tracking/services/terminalMatcherService.js +61 -0
- package/dist/modules/terminal_tracking/services/terminalMatcherService.js.map +7 -0
- package/dist/modules/terminal_tracking/services/terminalRegistry.js +24 -0
- package/dist/modules/terminal_tracking/services/terminalRegistry.js.map +7 -0
- package/dist/modules/terminal_tracking/services/terminalTrackingService.js +687 -0
- package/dist/modules/terminal_tracking/services/terminalTrackingService.js.map +7 -0
- package/dist/modules/terminal_tracking/setup.js +23 -0
- package/dist/modules/terminal_tracking/setup.js.map +7 -0
- package/dist/modules/terminal_tracking/subscribers/terminal-tracking-job-initial-poll.js +22 -0
- package/dist/modules/terminal_tracking/subscribers/terminal-tracking-job-initial-poll.js.map +7 -0
- package/generated/entities/terminal_config/index.ts +36 -0
- package/generated/entities/terminal_event/index.ts +39 -0
- package/generated/entities/terminal_tracking_job/index.ts +28 -0
- package/generated/entities/terminal_vessel_visit/index.ts +30 -0
- package/generated/entities.ids.generated.ts +52 -0
- package/package.json +80 -0
- package/src/index.ts +44 -0
- package/src/modules/terminal_tracking/__integration__/TC-TRACK-301-gct-config-test-connection.spec.ts +66 -0
- package/src/modules/terminal_tracking/__integration__/TC-TRACK-317-bct-config-test-connection.spec.ts +68 -0
- package/src/modules/terminal_tracking/__integration__/helpers.ts +148 -0
- package/src/modules/terminal_tracking/__integration__/meta.ts +4 -0
- package/src/modules/terminal_tracking/acl.ts +10 -0
- package/src/modules/terminal_tracking/api/openapi.ts +28 -0
- package/src/modules/terminal_tracking/api/terminal-configs/route.ts +140 -0
- package/src/modules/terminal_tracking/api/terminal-configs/test/route.ts +64 -0
- package/src/modules/terminal_tracking/api/tracking-jobs/details/route.ts +116 -0
- package/src/modules/terminal_tracking/api/tracking-jobs/poll/route.ts +64 -0
- package/src/modules/terminal_tracking/api/tracking-jobs/route.ts +243 -0
- package/src/modules/terminal_tracking/api/utils.ts +10 -0
- package/src/modules/terminal_tracking/api/vessel-visits/route.ts +95 -0
- package/src/modules/terminal_tracking/backend/terminal-configs/page.meta.ts +16 -0
- package/src/modules/terminal_tracking/backend/terminal-configs/page.tsx +728 -0
- package/src/modules/terminal_tracking/backend/terminal-containers/page.meta.ts +16 -0
- package/src/modules/terminal_tracking/backend/terminal-containers/page.tsx +328 -0
- package/src/modules/terminal_tracking/ce.ts +31 -0
- package/src/modules/terminal_tracking/commands/index.ts +3 -0
- package/src/modules/terminal_tracking/commands/terminal-configs.ts +132 -0
- package/src/modules/terminal_tracking/commands/terminal-tracking.ts +34 -0
- package/src/modules/terminal_tracking/commands/tracking-jobs.ts +69 -0
- package/src/modules/terminal_tracking/components/ContainerDetailsDrawer.tsx +540 -0
- package/src/modules/terminal_tracking/data/entities.ts +386 -0
- package/src/modules/terminal_tracking/data/validators.ts +113 -0
- package/src/modules/terminal_tracking/di.ts +46 -0
- package/src/modules/terminal_tracking/encryption.ts +12 -0
- package/src/modules/terminal_tracking/events.ts +48 -0
- package/src/modules/terminal_tracking/i18n/de.json +175 -0
- package/src/modules/terminal_tracking/i18n/en.json +178 -0
- package/src/modules/terminal_tracking/i18n/es.json +175 -0
- package/src/modules/terminal_tracking/i18n/pl.json +206 -0
- package/src/modules/terminal_tracking/index.ts +13 -0
- package/src/modules/terminal_tracking/lib/__tests__/availability.test.ts +136 -0
- package/src/modules/terminal_tracking/lib/__tests__/ensure-schedule.test.ts +67 -0
- package/src/modules/terminal_tracking/lib/__tests__/holds.test.ts +73 -0
- package/src/modules/terminal_tracking/lib/__tests__/stops.test.ts +80 -0
- package/src/modules/terminal_tracking/lib/__tests__/totp.test.ts +38 -0
- package/src/modules/terminal_tracking/lib/adapters/bct/__tests__/adapter.test.ts +196 -0
- package/src/modules/terminal_tracking/lib/adapters/bct/__tests__/incos-semantics.test.ts +303 -0
- package/src/modules/terminal_tracking/lib/adapters/bct/adapter.ts +219 -0
- package/src/modules/terminal_tracking/lib/adapters/bct/auth/basic.ts +34 -0
- package/src/modules/terminal_tracking/lib/adapters/bct/incos-semantics.ts +291 -0
- package/src/modules/terminal_tracking/lib/adapters/bct/types.ts +113 -0
- package/src/modules/terminal_tracking/lib/adapters/gct/__tests__/adapter.test.ts +238 -0
- package/src/modules/terminal_tracking/lib/adapters/gct/__tests__/gct-semantics.test.ts +133 -0
- package/src/modules/terminal_tracking/lib/adapters/gct/adapter.ts +152 -0
- package/src/modules/terminal_tracking/lib/adapters/gct/auth/token.ts +219 -0
- package/src/modules/terminal_tracking/lib/adapters/gct/gct-semantics.ts +136 -0
- package/src/modules/terminal_tracking/lib/adapters/gct/types.ts +58 -0
- package/src/modules/terminal_tracking/lib/adapters/index.ts +16 -0
- package/src/modules/terminal_tracking/lib/adapters/n4/__tests__/adapter-proxy.test.ts +69 -0
- package/src/modules/terminal_tracking/lib/adapters/n4/__tests__/data-table.test.ts +179 -0
- package/src/modules/terminal_tracking/lib/adapters/n4/__tests__/http.test.ts +98 -0
- package/src/modules/terminal_tracking/lib/adapters/n4/__tests__/n4-semantics.test.ts +140 -0
- package/src/modules/terminal_tracking/lib/adapters/n4/__tests__/proxy.test.ts +19 -0
- package/src/modules/terminal_tracking/lib/adapters/n4/__tests__/vessel-data-table.test.ts +67 -0
- package/src/modules/terminal_tracking/lib/adapters/n4/adapter.ts +163 -0
- package/src/modules/terminal_tracking/lib/adapters/n4/auth/ropc.ts +104 -0
- package/src/modules/terminal_tracking/lib/adapters/n4/http.ts +127 -0
- package/src/modules/terminal_tracking/lib/adapters/n4/n4-semantics.ts +156 -0
- package/src/modules/terminal_tracking/lib/adapters/n4/parsers/data-table.ts +102 -0
- package/src/modules/terminal_tracking/lib/adapters/n4/parsers/vessel-data-table.ts +60 -0
- package/src/modules/terminal_tracking/lib/adapters/n4/proxy.ts +24 -0
- package/src/modules/terminal_tracking/lib/adapters/n4/types.ts +56 -0
- package/src/modules/terminal_tracking/lib/adapters/zoned-time.ts +65 -0
- package/src/modules/terminal_tracking/lib/availability.ts +119 -0
- package/src/modules/terminal_tracking/lib/ensure-schedule.ts +83 -0
- package/src/modules/terminal_tracking/lib/holds.ts +344 -0
- package/src/modules/terminal_tracking/lib/logger.ts +21 -0
- package/src/modules/terminal_tracking/lib/rate-limiter.ts +52 -0
- package/src/modules/terminal_tracking/lib/stops.ts +124 -0
- package/src/modules/terminal_tracking/lib/terminal-adapter.ts +136 -0
- package/src/modules/terminal_tracking/lib/totp.ts +63 -0
- package/src/modules/terminal_tracking/migrations/Migration20260609120000_terminal_tracking.ts +104 -0
- package/src/modules/terminal_tracking/migrations/Migration20260610120000_terminal_vessel_visits.ts +40 -0
- package/src/modules/terminal_tracking/migrations/Migration20260611120000_terminal_config_proxy.ts +11 -0
- package/src/modules/terminal_tracking/migrations/Migration20260707120000_terminal_job_availability_markers.ts +13 -0
- package/src/modules/terminal_tracking/migrations/Migration20260805120000_terminal_event_loaded_at.ts +16 -0
- package/src/modules/terminal_tracking/services/__tests__/availabilityEmit.test.ts +277 -0
- package/src/modules/terminal_tracking/services/__tests__/createJob.test.ts +59 -0
- package/src/modules/terminal_tracking/services/__tests__/pollAllBatch.test.ts +147 -0
- package/src/modules/terminal_tracking/services/__tests__/terminalMatcher.test.ts +70 -0
- package/src/modules/terminal_tracking/services/__tests__/vesselResolution.test.ts +453 -0
- package/src/modules/terminal_tracking/services/terminalMatcherService.ts +97 -0
- package/src/modules/terminal_tracking/services/terminalRegistry.ts +30 -0
- package/src/modules/terminal_tracking/services/terminalTrackingService.ts +912 -0
- package/src/modules/terminal_tracking/setup.ts +24 -0
- package/src/modules/terminal_tracking/subscribers/terminal-tracking-job-initial-poll.ts +32 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load/pickup "STOP" flags for Navis N4 terminals (Baltic Hub / BCT).
|
|
3
|
+
*
|
|
4
|
+
* The N4 `/unit` endpoint gained three columns (Baltic Hub API update, Aug 2026)
|
|
5
|
+
* that state, per transport mode, whether the container currently CANNOT be
|
|
6
|
+
* loaded / picked up:
|
|
7
|
+
*
|
|
8
|
+
* - **Stop-Vsl** — no loading onto a **vessel** (export blocked).
|
|
9
|
+
* - **Stop-Road** — no pickup by **road / truck**.
|
|
10
|
+
* - **Stop-Rail** — no pickup by **rail**.
|
|
11
|
+
*
|
|
12
|
+
* Unlike the `Unit Impediments` hold list (from which blocking is *inferred* via
|
|
13
|
+
* the catalogue in `./holds`), these are the terminal stating the restriction
|
|
14
|
+
* directly, so they are a cleaner mode-specific signal.
|
|
15
|
+
*
|
|
16
|
+
* Pure helper — no DI, no i18n, no React. Reads from the N4 row's `rawData` map
|
|
17
|
+
* (header → value) so the SAME parse serves the `/unit` parser, the availability
|
|
18
|
+
* logic (which runs on the persisted event's `rawData`), and the details drawer.
|
|
19
|
+
*
|
|
20
|
+
* ── Header + value format ───────────────────────────────────────────────────
|
|
21
|
+
* Confirmed against a real Baltic Hub `/unit` payload (2026-07): headers are
|
|
22
|
+
* `Stop-Vsl` / `Stop-Rail` / `Stop-Road`, values are the strings `"true"` /
|
|
23
|
+
* `"false"`. Header matching is still done **tolerantly** (case- and
|
|
24
|
+
* punctuation-insensitive) and value parsing treats any non-empty, non-falsy
|
|
25
|
+
* token as an active stop, so a minor terminal-side format change (or another
|
|
26
|
+
* N4 terminal spelling the columns differently) keeps working — everything
|
|
27
|
+
* keys off this one file.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/** Transport mode a STOP flag applies to. */
|
|
31
|
+
export type TerminalStopMode = 'vsl' | 'road' | 'rail'
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The three STOP flags for a container. `null` means the terminal did not report
|
|
35
|
+
* that column at all (older terminals / pre-update payloads) — distinct from
|
|
36
|
+
* `false`, which means the column was present and no restriction is active.
|
|
37
|
+
*/
|
|
38
|
+
export type TerminalStops = {
|
|
39
|
+
/** No loading onto a vessel (export). */
|
|
40
|
+
vsl: boolean | null
|
|
41
|
+
/** No pickup by road / truck. */
|
|
42
|
+
road: boolean | null
|
|
43
|
+
/** No pickup by rail. */
|
|
44
|
+
rail: boolean | null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** All-unknown stops (nothing reported). */
|
|
48
|
+
export const NO_STOPS: TerminalStops = { vsl: null, road: null, rail: null }
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Candidate column headers per mode, in normalized form (lower-case,
|
|
52
|
+
* non-alphanumerics stripped). First match wins. `stopvessel` is kept alongside
|
|
53
|
+
* `stopvsl` in case the header spells the word out.
|
|
54
|
+
*/
|
|
55
|
+
const STOP_HEADERS: Record<TerminalStopMode, readonly string[]> = {
|
|
56
|
+
vsl: ['stopvsl', 'stopvessel'],
|
|
57
|
+
road: ['stoproad'],
|
|
58
|
+
rail: ['stoprail'],
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Values that mean "no stop" when the column is present. */
|
|
62
|
+
const FALSY_TOKENS = new Set(['', '0', 'n', 'no', 'false', 'f', '-'])
|
|
63
|
+
|
|
64
|
+
/** Normalize a header for tolerant matching: lower-case, drop non-alphanumerics. */
|
|
65
|
+
function normalizeHeader(header: string): string {
|
|
66
|
+
return header.toLowerCase().replace(/[^a-z0-9]/g, '')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Interpret a present column value as an active stop (true) or not (false). */
|
|
70
|
+
function isActiveStop(value: unknown): boolean {
|
|
71
|
+
if (value == null) return false
|
|
72
|
+
return !FALSY_TOKENS.has(String(value).trim().toLowerCase())
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Parse the STOP flags out of an N4 row's `rawData` (header → value). Returns
|
|
77
|
+
* `null` for a mode whose column is absent, `false`/`true` when present.
|
|
78
|
+
*/
|
|
79
|
+
export function parseTerminalStops(rawData: Record<string, unknown> | null | undefined): TerminalStops {
|
|
80
|
+
if (!rawData) return { ...NO_STOPS }
|
|
81
|
+
|
|
82
|
+
const byNorm = new Map<string, unknown>()
|
|
83
|
+
for (const [key, value] of Object.entries(rawData)) byNorm.set(normalizeHeader(key), value)
|
|
84
|
+
|
|
85
|
+
const read = (mode: TerminalStopMode): boolean | null => {
|
|
86
|
+
for (const candidate of STOP_HEADERS[mode]) {
|
|
87
|
+
if (byNorm.has(candidate)) return isActiveStop(byNorm.get(candidate))
|
|
88
|
+
}
|
|
89
|
+
return null
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return { vsl: read('vsl'), road: read('road'), rail: read('rail') }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** True only when road pickup is explicitly stopped (not on null/false). */
|
|
96
|
+
export function isRoadStopped(stops: TerminalStops): boolean {
|
|
97
|
+
return stops.road === true
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Whether the set of *actively-stopped* modes differs between two snapshots.
|
|
102
|
+
* Drives the `equipment.stops_updated` event. Compares on truthiness, so
|
|
103
|
+
* `null` (unreported) and `false` (reported, not stopped) are equivalent —
|
|
104
|
+
* both mean "not stopped" — mirroring how `impedimentsChanged` treats a null
|
|
105
|
+
* and an empty hold list as equal. This keeps a first poll of containers that
|
|
106
|
+
* merely carry all-`false` STOP columns from emitting spurious change events.
|
|
107
|
+
*/
|
|
108
|
+
export function stopsChanged(prior: TerminalStops, next: TerminalStops): boolean {
|
|
109
|
+
const active = (v: boolean | null) => v === true
|
|
110
|
+
return (
|
|
111
|
+
active(prior.vsl) !== active(next.vsl) ||
|
|
112
|
+
active(prior.road) !== active(next.road) ||
|
|
113
|
+
active(prior.rail) !== active(next.rail)
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** The modes with an active stop, in vsl → road → rail order. */
|
|
118
|
+
export function activeStopModes(stops: TerminalStops): TerminalStopMode[] {
|
|
119
|
+
const modes: TerminalStopMode[] = []
|
|
120
|
+
if (stops.vsl === true) modes.push('vsl')
|
|
121
|
+
if (stops.road === true) modes.push('road')
|
|
122
|
+
if (stops.rail === true) modes.push('rail')
|
|
123
|
+
return modes
|
|
124
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
TerminalEventType,
|
|
3
|
+
TerminalEventClassifierCode,
|
|
4
|
+
TerminalEventSource,
|
|
5
|
+
FacilityCodeProvider,
|
|
6
|
+
TerminalModeOfTransport,
|
|
7
|
+
TerminalAuthType,
|
|
8
|
+
TerminalEndpoints,
|
|
9
|
+
TerminalSealInfo,
|
|
10
|
+
} from '../data/entities'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A terminal config with its `auth_config` already decrypted, handed to
|
|
14
|
+
* adapters so they never touch the ORM or encryption layer directly.
|
|
15
|
+
*/
|
|
16
|
+
export type ResolvedTerminalConfig = {
|
|
17
|
+
terminalCode: string
|
|
18
|
+
adapterType: string
|
|
19
|
+
displayName: string
|
|
20
|
+
baseUrl: string
|
|
21
|
+
/** Optional HTTP(S) forward proxy for this terminal's API calls. */
|
|
22
|
+
proxyUrl?: string | null
|
|
23
|
+
endpoints: TerminalEndpoints
|
|
24
|
+
authType: TerminalAuthType
|
|
25
|
+
tokenUrl?: string | null
|
|
26
|
+
scope?: string | null
|
|
27
|
+
clientId?: string | null
|
|
28
|
+
/** Decrypted secrets, e.g. { username, password } or { clientSecret } */
|
|
29
|
+
authConfig?: Record<string, unknown> | null
|
|
30
|
+
rateLimitRequests: number
|
|
31
|
+
rateLimitWindowSeconds: number
|
|
32
|
+
unlocode?: string | null
|
|
33
|
+
facilityCode?: string | null
|
|
34
|
+
facilityCodeListProvider?: FacilityCodeProvider | null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A normalized, terminal-sourced event ready to persist as a TerminalEvent. */
|
|
38
|
+
export type TerminalFetchedEvent = {
|
|
39
|
+
source: TerminalEventSource
|
|
40
|
+
sourceEventId: string // '{terminalCode}:{ufvGkey}:{eventCode}'
|
|
41
|
+
eventType: TerminalEventType
|
|
42
|
+
eventCode: string
|
|
43
|
+
eventClassifierCode?: TerminalEventClassifierCode | null
|
|
44
|
+
eventDateTime: Date
|
|
45
|
+
containerNumber: string
|
|
46
|
+
ufvGkey: string
|
|
47
|
+
transitState?: string | null
|
|
48
|
+
visitState?: string | null
|
|
49
|
+
facilityCode?: string | null
|
|
50
|
+
facilityCodeListProvider?: FacilityCodeProvider | null
|
|
51
|
+
unlocode?: string | null
|
|
52
|
+
visitRefIn?: string | null
|
|
53
|
+
visitRefOut?: string | null
|
|
54
|
+
vesselName?: string | null
|
|
55
|
+
voyageNumber?: string | null
|
|
56
|
+
modeOfTransport?: TerminalModeOfTransport | null
|
|
57
|
+
seals?: TerminalSealInfo[] | null
|
|
58
|
+
vgmWeightKg?: number | null
|
|
59
|
+
impediments?: string[] | null
|
|
60
|
+
// When the container was loaded onto its transport (N4 `Loaded` column):
|
|
61
|
+
// export = onto the vessel, import = onto road/rail. Parsed regardless of the
|
|
62
|
+
// container's current transit state, so it survives past S60_LOADED.
|
|
63
|
+
loadedAt?: Date | null
|
|
64
|
+
// Nested vessel visit enrichment (from /VESSEL); populated by the service layer.
|
|
65
|
+
vesselVisit?: NormalizedVesselVisit | null
|
|
66
|
+
rawData?: Record<string, unknown> | null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* A normalized N4 `/VESSEL` visit, keyed by visit ref. Carries the full vessel
|
|
71
|
+
* row: name, both voyages, line, phase, all four timestamps, plus the
|
|
72
|
+
* begin-receive / dry-cutoff window dates.
|
|
73
|
+
*/
|
|
74
|
+
export type NormalizedVesselVisit = {
|
|
75
|
+
visitRef: string
|
|
76
|
+
vesselName: string | null
|
|
77
|
+
ibVoyage: string | null
|
|
78
|
+
obVoyage: string | null
|
|
79
|
+
line: string | null
|
|
80
|
+
phase: string | null
|
|
81
|
+
eta: Date | null
|
|
82
|
+
etd: Date | null
|
|
83
|
+
ata: Date | null
|
|
84
|
+
atd: Date | null
|
|
85
|
+
beginReceive: Date | null
|
|
86
|
+
dryCutoff: Date | null
|
|
87
|
+
rawData: Record<string, unknown> | null
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export type TerminalFetchResult = {
|
|
91
|
+
events: TerminalFetchedEvent[]
|
|
92
|
+
containerNumber: string
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type TerminalAdapterTestResult = {
|
|
96
|
+
success: boolean
|
|
97
|
+
message: string
|
|
98
|
+
latencyMs?: number
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface TerminalAdapter {
|
|
102
|
+
/** Selects this adapter from the registry, e.g. 'n4'. */
|
|
103
|
+
readonly adapterType: string
|
|
104
|
+
|
|
105
|
+
/** Standard endpoint paths for this adapter family; used when a terminal config omits them. */
|
|
106
|
+
readonly defaultEndpoints: TerminalEndpoints
|
|
107
|
+
|
|
108
|
+
fetchEvents(input: {
|
|
109
|
+
containerNumber: string
|
|
110
|
+
config: ResolvedTerminalConfig
|
|
111
|
+
}): Promise<TerminalFetchResult>
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Fetch events for many containers in a single call (N4 /unit accepts a
|
|
115
|
+
* comma-separated UNIT_NBR list). Optional: callers feature-detect and fall
|
|
116
|
+
* back to per-container `fetchEvents`. Each returned event carries its own
|
|
117
|
+
* `containerNumber` for grouping back onto the originating job.
|
|
118
|
+
*/
|
|
119
|
+
fetchEventsBatch?(input: {
|
|
120
|
+
containerNumbers: string[]
|
|
121
|
+
config: ResolvedTerminalConfig
|
|
122
|
+
}): Promise<{ events: TerminalFetchedEvent[] }>
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Resolve a single vessel visit by its visit reference. Optional: adapters
|
|
126
|
+
* that don't support a vessel endpoint omit it, and callers must feature-detect
|
|
127
|
+
* before invoking. Returns null when the endpoint is unconfigured or the visit
|
|
128
|
+
* is not found.
|
|
129
|
+
*/
|
|
130
|
+
fetchVesselVisit?(input: {
|
|
131
|
+
visitRef: string
|
|
132
|
+
config: ResolvedTerminalConfig
|
|
133
|
+
}): Promise<NormalizedVesselVisit | null>
|
|
134
|
+
|
|
135
|
+
testConnection(config: ResolvedTerminalConfig): Promise<TerminalAdapterTestResult>
|
|
136
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TOTP (RFC 6238) code generation for terminal adapters whose login expects an
|
|
3
|
+
* authenticator one-time code in place of a static password (e.g. GCT).
|
|
4
|
+
*
|
|
5
|
+
* This is a deliberate, self-contained COPY of the generation half of
|
|
6
|
+
* `packages/two-factor/src/modules/two_factor/lib/totp.ts` — kept in sync by
|
|
7
|
+
* hand. We copy rather than depend on `@freighttech/two-factor` so this backend
|
|
8
|
+
* tracking module stays dependency-light (only `fuse.js` + `undici`) and free of
|
|
9
|
+
* that package's Next/React/mikro-orm peer deps. The algorithm is pure Node
|
|
10
|
+
* `crypto`; only the consume-side pieces are copied (base32 decode + HOTP +
|
|
11
|
+
* `generateTotp`) — enrollment helpers (secret/QR generation, verification) live
|
|
12
|
+
* in the two-factor package and are not needed here.
|
|
13
|
+
*
|
|
14
|
+
* Parameters match a standard authenticator app: SHA1, 6 digits, 30s period.
|
|
15
|
+
*/
|
|
16
|
+
import crypto from 'crypto'
|
|
17
|
+
|
|
18
|
+
// ── Base32 (RFC 4648, no padding) ──────────────────────────────────────────
|
|
19
|
+
const BASE32_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
|
20
|
+
|
|
21
|
+
/** Decode a base32 authenticator secret into its raw key bytes. */
|
|
22
|
+
export function base32Decode(input: string): Buffer {
|
|
23
|
+
const clean = input.replace(/=+$/g, '').replace(/\s+/g, '').toUpperCase()
|
|
24
|
+
let bits = 0
|
|
25
|
+
let value = 0
|
|
26
|
+
const bytes: number[] = []
|
|
27
|
+
for (const ch of clean) {
|
|
28
|
+
const idx = BASE32_ALPHABET.indexOf(ch)
|
|
29
|
+
if (idx === -1) throw new Error('totp: invalid base32 character in secret')
|
|
30
|
+
value = (value << 5) | idx
|
|
31
|
+
bits += 5
|
|
32
|
+
if (bits >= 8) {
|
|
33
|
+
bytes.push((value >>> (bits - 8)) & 0xff)
|
|
34
|
+
bits -= 8
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return Buffer.from(bytes)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── TOTP (RFC 6238) over HOTP (RFC 4226) ────────────────────────────────────
|
|
41
|
+
export const TOTP_PERIOD_SECONDS = 30
|
|
42
|
+
export const TOTP_DIGITS = 6
|
|
43
|
+
|
|
44
|
+
function hotp(secret: Buffer, counter: number): string {
|
|
45
|
+
const buf = Buffer.alloc(8)
|
|
46
|
+
// 64-bit big-endian counter (JS bitops are 32-bit, so split hi/lo).
|
|
47
|
+
buf.writeUInt32BE(Math.floor(counter / 0x100000000), 0)
|
|
48
|
+
buf.writeUInt32BE(counter >>> 0, 4)
|
|
49
|
+
const hmac = crypto.createHmac('sha1', secret).update(buf).digest()
|
|
50
|
+
const offset = hmac[hmac.length - 1] & 0xf
|
|
51
|
+
const binary =
|
|
52
|
+
((hmac[offset] & 0x7f) << 24) |
|
|
53
|
+
((hmac[offset + 1] & 0xff) << 16) |
|
|
54
|
+
((hmac[offset + 2] & 0xff) << 8) |
|
|
55
|
+
(hmac[offset + 3] & 0xff)
|
|
56
|
+
return (binary % 10 ** TOTP_DIGITS).toString().padStart(TOTP_DIGITS, '0')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Compute the current TOTP code for a base32 secret (default time: now). */
|
|
60
|
+
export function generateTotp(base32Secret: string, nowMs: number = Date.now()): string {
|
|
61
|
+
const counter = Math.floor(nowMs / 1000 / TOTP_PERIOD_SECONDS)
|
|
62
|
+
return hotp(base32Decode(base32Secret), counter)
|
|
63
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations'
|
|
2
|
+
|
|
3
|
+
export class Migration20260609120000_terminal_tracking extends Migration {
|
|
4
|
+
override async up(): Promise<void> {
|
|
5
|
+
// ─── terminal_tracking_terminal_configs ──────────────────
|
|
6
|
+
this.addSql(`create table if not exists "terminal_tracking_terminal_configs" (
|
|
7
|
+
"id" uuid not null default gen_random_uuid(),
|
|
8
|
+
"organization_id" uuid not null,
|
|
9
|
+
"tenant_id" uuid not null,
|
|
10
|
+
"terminal_code" text not null,
|
|
11
|
+
"adapter_type" text not null,
|
|
12
|
+
"display_name" text not null,
|
|
13
|
+
"base_url" text not null,
|
|
14
|
+
"endpoints" jsonb not null,
|
|
15
|
+
"auth_type" text not null,
|
|
16
|
+
"token_url" text null,
|
|
17
|
+
"scope" text null,
|
|
18
|
+
"client_id" text null,
|
|
19
|
+
"auth_config" jsonb null,
|
|
20
|
+
"rate_limit_requests" integer not null default 200,
|
|
21
|
+
"rate_limit_window_seconds" integer not null default 60,
|
|
22
|
+
"unlocode" varchar(5) null,
|
|
23
|
+
"bic_codes" jsonb null,
|
|
24
|
+
"smdg_codes" jsonb null,
|
|
25
|
+
"name_aliases" jsonb null,
|
|
26
|
+
"is_active" boolean not null default true,
|
|
27
|
+
"created_at" timestamptz not null default now(),
|
|
28
|
+
"updated_at" timestamptz not null default now(),
|
|
29
|
+
"deleted_at" timestamptz null,
|
|
30
|
+
constraint "terminal_tracking_terminal_configs_pkey" primary key ("id")
|
|
31
|
+
);`)
|
|
32
|
+
this.addSql(`create index if not exists "tt_terminal_configs_org_tenant_idx" on "terminal_tracking_terminal_configs" ("organization_id", "tenant_id");`)
|
|
33
|
+
this.addSql(`create unique index if not exists "tt_terminal_configs_code_uniq" on "terminal_tracking_terminal_configs" ("organization_id", "tenant_id", "terminal_code");`)
|
|
34
|
+
|
|
35
|
+
// ─── terminal_tracking_jobs ──────────────────────────────
|
|
36
|
+
this.addSql(`create table if not exists "terminal_tracking_jobs" (
|
|
37
|
+
"id" uuid not null default gen_random_uuid(),
|
|
38
|
+
"organization_id" uuid not null,
|
|
39
|
+
"tenant_id" uuid not null,
|
|
40
|
+
"terminal_code" text not null,
|
|
41
|
+
"container_number" text not null,
|
|
42
|
+
"status" text not null default 'active',
|
|
43
|
+
"schedule" jsonb null,
|
|
44
|
+
"next_poll_at" timestamptz null,
|
|
45
|
+
"last_poll_at" timestamptz null,
|
|
46
|
+
"retry_count" integer not null default 0,
|
|
47
|
+
"error_history" jsonb null,
|
|
48
|
+
"created_at" timestamptz not null default now(),
|
|
49
|
+
"updated_at" timestamptz not null default now(),
|
|
50
|
+
"deleted_at" timestamptz null,
|
|
51
|
+
constraint "terminal_tracking_jobs_pkey" primary key ("id")
|
|
52
|
+
);`)
|
|
53
|
+
this.addSql(`create index if not exists "tt_jobs_org_tenant_idx" on "terminal_tracking_jobs" ("organization_id", "tenant_id");`)
|
|
54
|
+
this.addSql(`create index if not exists "tt_jobs_status_idx" on "terminal_tracking_jobs" ("status");`)
|
|
55
|
+
this.addSql(`create index if not exists "tt_jobs_next_poll_idx" on "terminal_tracking_jobs" ("next_poll_at");`)
|
|
56
|
+
this.addSql(`create index if not exists "tt_jobs_container_idx" on "terminal_tracking_jobs" ("container_number");`)
|
|
57
|
+
|
|
58
|
+
// ─── terminal_tracking_events ────────────────────────────
|
|
59
|
+
this.addSql(`create table if not exists "terminal_tracking_events" (
|
|
60
|
+
"id" uuid not null default gen_random_uuid(),
|
|
61
|
+
"organization_id" uuid not null,
|
|
62
|
+
"tenant_id" uuid not null,
|
|
63
|
+
"job_id" uuid not null,
|
|
64
|
+
"source" text not null default 'terminal',
|
|
65
|
+
"source_event_id" text not null,
|
|
66
|
+
"event_type" text not null,
|
|
67
|
+
"event_code" text not null,
|
|
68
|
+
"event_classifier_code" text null,
|
|
69
|
+
"event_date_time" timestamptz not null,
|
|
70
|
+
"container_number" text not null,
|
|
71
|
+
"ufv_gkey" text not null,
|
|
72
|
+
"transit_state" text null,
|
|
73
|
+
"visit_state" text null,
|
|
74
|
+
"facility_code" text null,
|
|
75
|
+
"facility_code_list_provider" text null,
|
|
76
|
+
"unlocode" varchar(5) null,
|
|
77
|
+
"visit_ref_in" text null,
|
|
78
|
+
"visit_ref_out" text null,
|
|
79
|
+
"vessel_name" text null,
|
|
80
|
+
"voyage_number" text null,
|
|
81
|
+
"mode_of_transport" text null,
|
|
82
|
+
"seals" jsonb null,
|
|
83
|
+
"vgm_weight_kg" double precision null,
|
|
84
|
+
"impediments" jsonb null,
|
|
85
|
+
"raw_data" jsonb null,
|
|
86
|
+
"created_at" timestamptz not null default now(),
|
|
87
|
+
constraint "terminal_tracking_events_pkey" primary key ("id")
|
|
88
|
+
);`)
|
|
89
|
+
this.addSql(`do $$ begin alter table "terminal_tracking_events"
|
|
90
|
+
add constraint "terminal_tracking_events_job_id_foreign"
|
|
91
|
+
foreign key ("job_id") references "terminal_tracking_jobs" ("id") on update cascade on delete cascade; exception when duplicate_object then null; when duplicate_table then null; end $$;`)
|
|
92
|
+
this.addSql(`create index if not exists "tt_events_org_tenant_idx" on "terminal_tracking_events" ("organization_id", "tenant_id");`)
|
|
93
|
+
this.addSql(`create index if not exists "tt_events_job_idx" on "terminal_tracking_events" ("job_id");`)
|
|
94
|
+
this.addSql(`create index if not exists "tt_events_container_idx" on "terminal_tracking_events" ("container_number");`)
|
|
95
|
+
this.addSql(`create index if not exists "tt_events_event_date_idx" on "terminal_tracking_events" ("event_date_time");`)
|
|
96
|
+
this.addSql(`create unique index if not exists "tt_events_source_event_uniq" on "terminal_tracking_events" ("job_id", "source", "source_event_id");`)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
override async down(): Promise<void> {
|
|
100
|
+
this.addSql(`drop table if exists "terminal_tracking_events" cascade;`)
|
|
101
|
+
this.addSql(`drop table if exists "terminal_tracking_jobs" cascade;`)
|
|
102
|
+
this.addSql(`drop table if exists "terminal_tracking_terminal_configs" cascade;`)
|
|
103
|
+
}
|
|
104
|
+
}
|
package/src/modules/terminal_tracking/migrations/Migration20260610120000_terminal_vessel_visits.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations'
|
|
2
|
+
|
|
3
|
+
export class Migration20260610120000_terminal_vessel_visits extends Migration {
|
|
4
|
+
override async up(): Promise<void> {
|
|
5
|
+
// ─── terminal_tracking_vessel_visits ─────────────────────
|
|
6
|
+
this.addSql(`create table if not exists "terminal_tracking_vessel_visits" (
|
|
7
|
+
"id" uuid not null default gen_random_uuid(),
|
|
8
|
+
"organization_id" uuid not null,
|
|
9
|
+
"tenant_id" uuid not null,
|
|
10
|
+
"terminal_code" text not null,
|
|
11
|
+
"visit_ref" text not null,
|
|
12
|
+
"vessel_name" text null,
|
|
13
|
+
"ib_voyage" text null,
|
|
14
|
+
"ob_voyage" text null,
|
|
15
|
+
"line" text null,
|
|
16
|
+
"phase" text null,
|
|
17
|
+
"eta" timestamptz null,
|
|
18
|
+
"etd" timestamptz null,
|
|
19
|
+
"ata" timestamptz null,
|
|
20
|
+
"atd" timestamptz null,
|
|
21
|
+
"begin_receive" timestamptz null,
|
|
22
|
+
"dry_cutoff" timestamptz null,
|
|
23
|
+
"raw_data" jsonb null,
|
|
24
|
+
"created_at" timestamptz not null default now(),
|
|
25
|
+
"updated_at" timestamptz not null default now(),
|
|
26
|
+
constraint "terminal_tracking_vessel_visits_pkey" primary key ("id")
|
|
27
|
+
);`)
|
|
28
|
+
this.addSql(`create index if not exists "tt_vessel_visits_org_tenant_idx" on "terminal_tracking_vessel_visits" ("organization_id", "tenant_id");`)
|
|
29
|
+
this.addSql(`create index if not exists "tt_vessel_visits_visit_ref_idx" on "terminal_tracking_vessel_visits" ("visit_ref");`)
|
|
30
|
+
this.addSql(`create unique index if not exists "tt_vessel_visits_uniq" on "terminal_tracking_vessel_visits" ("organization_id", "tenant_id", "terminal_code", "visit_ref");`)
|
|
31
|
+
|
|
32
|
+
// ─── terminal_tracking_terminal_configs: vessel cache TTL ─
|
|
33
|
+
this.addSql(`alter table "terminal_tracking_terminal_configs" add column if not exists "vessel_cache_ttl_seconds" integer null;`)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
override async down(): Promise<void> {
|
|
37
|
+
this.addSql(`alter table "terminal_tracking_terminal_configs" drop column if exists "vessel_cache_ttl_seconds";`)
|
|
38
|
+
this.addSql(`drop table if exists "terminal_tracking_vessel_visits" cascade;`)
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/modules/terminal_tracking/migrations/Migration20260611120000_terminal_config_proxy.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations'
|
|
2
|
+
|
|
3
|
+
export class Migration20260611120000_terminal_config_proxy extends Migration {
|
|
4
|
+
override async up(): Promise<void> {
|
|
5
|
+
this.addSql(`alter table "terminal_tracking_terminal_configs" add column if not exists "proxy_url" text null;`)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
override async down(): Promise<void> {
|
|
9
|
+
this.addSql(`alter table "terminal_tracking_terminal_configs" drop column if exists "proxy_url";`)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations'
|
|
2
|
+
|
|
3
|
+
export class Migration20260707120000_terminal_job_availability_markers extends Migration {
|
|
4
|
+
override async up(): Promise<void> {
|
|
5
|
+
this.addSql(`alter table "terminal_tracking_jobs" add column if not exists "empty_ready_at" timestamptz null;`)
|
|
6
|
+
this.addSql(`alter table "terminal_tracking_jobs" add column if not exists "holds_cleared_at" timestamptz null;`)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
override async down(): Promise<void> {
|
|
10
|
+
this.addSql(`alter table "terminal_tracking_jobs" drop column if exists "empty_ready_at";`)
|
|
11
|
+
this.addSql(`alter table "terminal_tracking_jobs" drop column if exists "holds_cleared_at";`)
|
|
12
|
+
}
|
|
13
|
+
}
|
package/src/modules/terminal_tracking/migrations/Migration20260805120000_terminal_event_loaded_at.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Additive-only: a nullable `loaded_at` timestamp on terminal_tracking_events.
|
|
5
|
+
* The N4 `Loaded` column parsed into a typed datetime, captured on every event
|
|
6
|
+
* regardless of the container's current transit state.
|
|
7
|
+
*/
|
|
8
|
+
export class Migration20260805120000_terminal_event_loaded_at extends Migration {
|
|
9
|
+
override async up(): Promise<void> {
|
|
10
|
+
this.addSql(`alter table "terminal_tracking_events" add column if not exists "loaded_at" timestamptz null;`)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
override async down(): Promise<void> {
|
|
14
|
+
this.addSql(`alter table "terminal_tracking_events" drop column if exists "loaded_at";`)
|
|
15
|
+
}
|
|
16
|
+
}
|