@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,386 @@
|
|
|
1
|
+
import { Collection, OptionalProps } from '@mikro-orm/core'
|
|
2
|
+
import { Entity, PrimaryKey, Property, Index, Unique, ManyToOne, OneToMany } from '@mikro-orm/decorators/legacy'
|
|
3
|
+
|
|
4
|
+
// ─── Enums / shared types ────────────────────────────────────
|
|
5
|
+
// Re-declared locally on purpose: terminal_tracking must NOT depend on
|
|
6
|
+
// shipment_tracking internals. The cross-module contract is the event payload.
|
|
7
|
+
|
|
8
|
+
export type TerminalEventType = 'EQUIPMENT' | 'TRANSPORT'
|
|
9
|
+
export type TerminalEventClassifierCode = 'ACT' | 'PLN' | 'EST'
|
|
10
|
+
export type TerminalEventSource = 'terminal'
|
|
11
|
+
export type FacilityCodeProvider = 'SMDG' | 'BIC'
|
|
12
|
+
export type TerminalModeOfTransport = 'VESSEL' | 'RAIL' | 'TRUCK' | 'BARGE'
|
|
13
|
+
|
|
14
|
+
export type TerminalAuthType = 'oauth2_password' | 'oauth2_client_credentials' | 'gct_token' | 'basic'
|
|
15
|
+
|
|
16
|
+
export type TerminalTrackingJobStatus = 'active' | 'paused' | 'deactivated' | 'failed' | 'completed'
|
|
17
|
+
|
|
18
|
+
export type TerminalEndpoints = {
|
|
19
|
+
unit: string
|
|
20
|
+
vessel?: string
|
|
21
|
+
trainVisits?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type TerminalSealInfo = {
|
|
25
|
+
number: string
|
|
26
|
+
type?: string | null
|
|
27
|
+
source?: string | null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ─── TerminalConfig ──────────────────────────────────────────
|
|
31
|
+
// Per (org, tenant, terminalCode) terminal definition + tenant credentials.
|
|
32
|
+
// `auth_config` is encrypted at rest (see encryption.ts). `client_id`,
|
|
33
|
+
// `token_url`, `scope` are NOT secrets and stay as plain columns.
|
|
34
|
+
|
|
35
|
+
@Entity({ tableName: 'terminal_tracking_terminal_configs' })
|
|
36
|
+
@Index({ name: 'tt_terminal_configs_org_tenant_idx', properties: ['organizationId', 'tenantId'] })
|
|
37
|
+
@Unique({ name: 'tt_terminal_configs_code_uniq', properties: ['organizationId', 'tenantId', 'terminalCode'] })
|
|
38
|
+
export class TerminalConfig {
|
|
39
|
+
[OptionalProps]?:
|
|
40
|
+
| 'isActive'
|
|
41
|
+
| 'createdAt'
|
|
42
|
+
| 'updatedAt'
|
|
43
|
+
| 'deletedAt'
|
|
44
|
+
| 'rateLimitRequests'
|
|
45
|
+
| 'rateLimitWindowSeconds'
|
|
46
|
+
| 'vesselCacheTtlSeconds'
|
|
47
|
+
| 'proxyUrl'
|
|
48
|
+
|
|
49
|
+
@PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
|
|
50
|
+
id!: string
|
|
51
|
+
|
|
52
|
+
@Property({ name: 'organization_id', type: 'uuid' })
|
|
53
|
+
organizationId!: string
|
|
54
|
+
|
|
55
|
+
@Property({ name: 'tenant_id', type: 'uuid' })
|
|
56
|
+
tenantId!: string
|
|
57
|
+
|
|
58
|
+
// Lowercase terminal identifier, e.g. 'bct'
|
|
59
|
+
@Property({ name: 'terminal_code', type: 'text' })
|
|
60
|
+
terminalCode!: string
|
|
61
|
+
|
|
62
|
+
// Selects the adapter implementation from the registry, e.g. 'n4'
|
|
63
|
+
@Property({ name: 'adapter_type', type: 'text' })
|
|
64
|
+
adapterType!: string
|
|
65
|
+
|
|
66
|
+
@Property({ name: 'display_name', type: 'text' })
|
|
67
|
+
displayName!: string
|
|
68
|
+
|
|
69
|
+
@Property({ name: 'base_url', type: 'text' })
|
|
70
|
+
baseUrl!: string
|
|
71
|
+
|
|
72
|
+
// Optional HTTP(S) forward-proxy URL for this terminal's API calls (used to
|
|
73
|
+
// egress from a whitelisted IP). Null = direct. No embedded credentials —
|
|
74
|
+
// secure the proxy by IP allowlist.
|
|
75
|
+
@Property({ name: 'proxy_url', type: 'text', nullable: true })
|
|
76
|
+
proxyUrl?: string | null
|
|
77
|
+
|
|
78
|
+
@Property({ name: 'endpoints', type: 'jsonb' })
|
|
79
|
+
endpoints!: TerminalEndpoints
|
|
80
|
+
|
|
81
|
+
@Property({ name: 'auth_type', type: 'text' })
|
|
82
|
+
authType!: TerminalAuthType
|
|
83
|
+
|
|
84
|
+
// OAuth token endpoint (may include a policy query param, e.g. ?p=b2c_1_ropc_login)
|
|
85
|
+
@Property({ name: 'token_url', type: 'text', nullable: true })
|
|
86
|
+
tokenUrl?: string | null
|
|
87
|
+
|
|
88
|
+
@Property({ name: 'scope', type: 'text', nullable: true })
|
|
89
|
+
scope?: string | null
|
|
90
|
+
|
|
91
|
+
// Public OAuth client id (NOT a secret)
|
|
92
|
+
@Property({ name: 'client_id', type: 'text', nullable: true })
|
|
93
|
+
clientId?: string | null
|
|
94
|
+
|
|
95
|
+
// Encrypted at rest: { username, password } (ROPC) or { clientSecret }
|
|
96
|
+
@Property({ name: 'auth_config', type: 'jsonb', nullable: true })
|
|
97
|
+
authConfig?: Record<string, unknown> | null
|
|
98
|
+
|
|
99
|
+
@Property({ name: 'rate_limit_requests', type: 'integer', default: 200 })
|
|
100
|
+
rateLimitRequests: number = 200
|
|
101
|
+
|
|
102
|
+
@Property({ name: 'rate_limit_window_seconds', type: 'integer', default: 60 })
|
|
103
|
+
rateLimitWindowSeconds: number = 60
|
|
104
|
+
|
|
105
|
+
// TTL (seconds) for the cached /VESSEL visit lookup; null = module default.
|
|
106
|
+
@Property({ name: 'vessel_cache_ttl_seconds', type: 'integer', nullable: true })
|
|
107
|
+
vesselCacheTtlSeconds?: number | null
|
|
108
|
+
|
|
109
|
+
// ─── Matching fields (resolve a facility -> this terminal) ────
|
|
110
|
+
@Property({ name: 'unlocode', type: 'text', length: 5, nullable: true })
|
|
111
|
+
unlocode?: string | null
|
|
112
|
+
|
|
113
|
+
@Property({ name: 'bic_codes', type: 'jsonb', nullable: true })
|
|
114
|
+
bicCodes?: string[] | null
|
|
115
|
+
|
|
116
|
+
@Property({ name: 'smdg_codes', type: 'jsonb', nullable: true })
|
|
117
|
+
smdgCodes?: string[] | null
|
|
118
|
+
|
|
119
|
+
@Property({ name: 'name_aliases', type: 'jsonb', nullable: true })
|
|
120
|
+
nameAliases?: string[] | null
|
|
121
|
+
|
|
122
|
+
@Property({ name: 'is_active', type: 'boolean', default: true })
|
|
123
|
+
isActive: boolean = true
|
|
124
|
+
|
|
125
|
+
@Property({ name: 'created_at', type: Date, defaultRaw: 'now()' })
|
|
126
|
+
createdAt!: Date
|
|
127
|
+
|
|
128
|
+
@Property({ name: 'updated_at', type: Date, defaultRaw: 'now()', onUpdate: () => new Date() })
|
|
129
|
+
updatedAt!: Date
|
|
130
|
+
|
|
131
|
+
@Property({ name: 'deleted_at', type: Date, nullable: true })
|
|
132
|
+
deletedAt?: Date | null
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ─── TerminalTrackingJob ─────────────────────────────────────
|
|
136
|
+
// A module-owned watchlist unit: a container number tracked at one terminal.
|
|
137
|
+
|
|
138
|
+
@Entity({ tableName: 'terminal_tracking_jobs' })
|
|
139
|
+
@Index({ name: 'tt_jobs_org_tenant_idx', properties: ['organizationId', 'tenantId'] })
|
|
140
|
+
@Index({ name: 'tt_jobs_status_idx', properties: ['status'] })
|
|
141
|
+
@Index({ name: 'tt_jobs_next_poll_idx', properties: ['nextPollAt'] })
|
|
142
|
+
@Index({ name: 'tt_jobs_container_idx', properties: ['containerNumber'] })
|
|
143
|
+
export class TerminalTrackingJob {
|
|
144
|
+
[OptionalProps]?:
|
|
145
|
+
| 'createdAt'
|
|
146
|
+
| 'updatedAt'
|
|
147
|
+
| 'deletedAt'
|
|
148
|
+
| 'status'
|
|
149
|
+
| 'retryCount'
|
|
150
|
+
| 'emptyReadyAt'
|
|
151
|
+
| 'holdsClearedAt'
|
|
152
|
+
|
|
153
|
+
@PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
|
|
154
|
+
id!: string
|
|
155
|
+
|
|
156
|
+
@Property({ name: 'organization_id', type: 'uuid' })
|
|
157
|
+
organizationId!: string
|
|
158
|
+
|
|
159
|
+
@Property({ name: 'tenant_id', type: 'uuid' })
|
|
160
|
+
tenantId!: string
|
|
161
|
+
|
|
162
|
+
@Property({ name: 'terminal_code', type: 'text' })
|
|
163
|
+
terminalCode!: string
|
|
164
|
+
|
|
165
|
+
@Property({ name: 'container_number', type: 'text' })
|
|
166
|
+
containerNumber!: string
|
|
167
|
+
|
|
168
|
+
@Property({ type: 'text', default: 'active' })
|
|
169
|
+
status: TerminalTrackingJobStatus = 'active'
|
|
170
|
+
|
|
171
|
+
// JSON array of scheduled poll dates (ISO); null = follow the module default cadence
|
|
172
|
+
@Property({ type: 'jsonb', nullable: true })
|
|
173
|
+
schedule?: string[] | null
|
|
174
|
+
|
|
175
|
+
@Property({ name: 'next_poll_at', type: Date, nullable: true })
|
|
176
|
+
nextPollAt?: Date | null
|
|
177
|
+
|
|
178
|
+
@Property({ name: 'last_poll_at', type: Date, nullable: true })
|
|
179
|
+
lastPollAt?: Date | null
|
|
180
|
+
|
|
181
|
+
@Property({ name: 'retry_count', type: 'integer', default: 0 })
|
|
182
|
+
retryCount: number = 0
|
|
183
|
+
|
|
184
|
+
@Property({ name: 'error_history', type: 'jsonb', nullable: true })
|
|
185
|
+
errorHistory?: Array<{ date: string; message: string }> | null
|
|
186
|
+
|
|
187
|
+
// One-shot availability markers. Set the first time this container satisfies
|
|
188
|
+
// the availability rule at the terminal, so the corresponding notification
|
|
189
|
+
// fires exactly once. `empty_ready` = export empty collectable;
|
|
190
|
+
// `holds_cleared` = import container's blocking holds removed.
|
|
191
|
+
@Property({ name: 'empty_ready_at', type: Date, nullable: true })
|
|
192
|
+
emptyReadyAt?: Date | null
|
|
193
|
+
|
|
194
|
+
@Property({ name: 'holds_cleared_at', type: Date, nullable: true })
|
|
195
|
+
holdsClearedAt?: Date | null
|
|
196
|
+
|
|
197
|
+
@Property({ name: 'created_at', type: Date, defaultRaw: 'now()' })
|
|
198
|
+
createdAt!: Date
|
|
199
|
+
|
|
200
|
+
@Property({ name: 'updated_at', type: Date, defaultRaw: 'now()', onUpdate: () => new Date() })
|
|
201
|
+
updatedAt!: Date
|
|
202
|
+
|
|
203
|
+
@Property({ name: 'deleted_at', type: Date, nullable: true })
|
|
204
|
+
deletedAt?: Date | null
|
|
205
|
+
|
|
206
|
+
@OneToMany(() => TerminalEvent, (e) => e.job)
|
|
207
|
+
events = new Collection<TerminalEvent>(this)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ─── TerminalEvent ───────────────────────────────────────────
|
|
211
|
+
// A normalized terminal event. `source_event_id` embeds the N4 Ufv_Gkey so
|
|
212
|
+
// that multiple Unit Facility Visits per container (import + export legs)
|
|
213
|
+
// remain distinct under the unique constraint.
|
|
214
|
+
|
|
215
|
+
@Entity({ tableName: 'terminal_tracking_events' })
|
|
216
|
+
@Index({ name: 'tt_events_org_tenant_idx', properties: ['organizationId', 'tenantId'] })
|
|
217
|
+
@Index({ name: 'tt_events_job_idx', properties: ['job'] })
|
|
218
|
+
@Index({ name: 'tt_events_container_idx', properties: ['containerNumber'] })
|
|
219
|
+
@Index({ name: 'tt_events_event_date_idx', properties: ['eventDateTime'] })
|
|
220
|
+
@Unique({ name: 'tt_events_source_event_uniq', properties: ['job', 'source', 'sourceEventId'] })
|
|
221
|
+
export class TerminalEvent {
|
|
222
|
+
[OptionalProps]?: 'createdAt' | 'source'
|
|
223
|
+
|
|
224
|
+
@PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
|
|
225
|
+
id!: string
|
|
226
|
+
|
|
227
|
+
@Property({ name: 'organization_id', type: 'uuid' })
|
|
228
|
+
organizationId!: string
|
|
229
|
+
|
|
230
|
+
@Property({ name: 'tenant_id', type: 'uuid' })
|
|
231
|
+
tenantId!: string
|
|
232
|
+
|
|
233
|
+
@ManyToOne(() => TerminalTrackingJob, { fieldName: 'job_id' })
|
|
234
|
+
job!: TerminalTrackingJob
|
|
235
|
+
|
|
236
|
+
@Property({ type: 'text', default: 'terminal' })
|
|
237
|
+
source: TerminalEventSource = 'terminal'
|
|
238
|
+
|
|
239
|
+
// '{terminalCode}:{ufvGkey}:{eventCode}'
|
|
240
|
+
@Property({ name: 'source_event_id', type: 'text' })
|
|
241
|
+
sourceEventId!: string
|
|
242
|
+
|
|
243
|
+
@Property({ name: 'event_type', type: 'text' })
|
|
244
|
+
eventType!: TerminalEventType
|
|
245
|
+
|
|
246
|
+
@Property({ name: 'event_code', type: 'text' })
|
|
247
|
+
eventCode!: string
|
|
248
|
+
|
|
249
|
+
@Property({ name: 'event_classifier_code', type: 'text', nullable: true })
|
|
250
|
+
eventClassifierCode?: TerminalEventClassifierCode | null
|
|
251
|
+
|
|
252
|
+
@Property({ name: 'event_date_time', type: Date })
|
|
253
|
+
eventDateTime!: Date
|
|
254
|
+
|
|
255
|
+
@Property({ name: 'container_number', type: 'text' })
|
|
256
|
+
containerNumber!: string
|
|
257
|
+
|
|
258
|
+
@Property({ name: 'ufv_gkey', type: 'text' })
|
|
259
|
+
ufvGkey!: string
|
|
260
|
+
|
|
261
|
+
@Property({ name: 'transit_state', type: 'text', nullable: true })
|
|
262
|
+
transitState?: string | null
|
|
263
|
+
|
|
264
|
+
@Property({ name: 'visit_state', type: 'text', nullable: true })
|
|
265
|
+
visitState?: string | null
|
|
266
|
+
|
|
267
|
+
// ─── Location ─────────────────────────────────────────────
|
|
268
|
+
@Property({ name: 'facility_code', type: 'text', nullable: true })
|
|
269
|
+
facilityCode?: string | null
|
|
270
|
+
|
|
271
|
+
@Property({ name: 'facility_code_list_provider', type: 'text', nullable: true })
|
|
272
|
+
facilityCodeListProvider?: FacilityCodeProvider | null
|
|
273
|
+
|
|
274
|
+
@Property({ name: 'unlocode', type: 'text', length: 5, nullable: true })
|
|
275
|
+
unlocode?: string | null
|
|
276
|
+
|
|
277
|
+
// ─── Visit references (for vessel/train enrichment) ───────
|
|
278
|
+
@Property({ name: 'visit_ref_in', type: 'text', nullable: true })
|
|
279
|
+
visitRefIn?: string | null
|
|
280
|
+
|
|
281
|
+
@Property({ name: 'visit_ref_out', type: 'text', nullable: true })
|
|
282
|
+
visitRefOut?: string | null
|
|
283
|
+
|
|
284
|
+
// ─── Transport ────────────────────────────────────────────
|
|
285
|
+
@Property({ name: 'vessel_name', type: 'text', nullable: true })
|
|
286
|
+
vesselName?: string | null
|
|
287
|
+
|
|
288
|
+
@Property({ name: 'voyage_number', type: 'text', nullable: true })
|
|
289
|
+
voyageNumber?: string | null
|
|
290
|
+
|
|
291
|
+
@Property({ name: 'mode_of_transport', type: 'text', nullable: true })
|
|
292
|
+
modeOfTransport?: TerminalModeOfTransport | null
|
|
293
|
+
|
|
294
|
+
// ─── Cargo extras ─────────────────────────────────────────
|
|
295
|
+
@Property({ name: 'seals', type: 'jsonb', nullable: true })
|
|
296
|
+
seals?: TerminalSealInfo[] | null
|
|
297
|
+
|
|
298
|
+
@Property({ name: 'vgm_weight_kg', type: 'float', nullable: true })
|
|
299
|
+
vgmWeightKg?: number | null
|
|
300
|
+
|
|
301
|
+
@Property({ name: 'impediments', type: 'jsonb', nullable: true })
|
|
302
|
+
impediments?: string[] | null
|
|
303
|
+
|
|
304
|
+
// When the container was loaded onto its transport (N4 `Loaded` column),
|
|
305
|
+
// parsed regardless of current transit state. Export = onto vessel; import =
|
|
306
|
+
// onto road/rail.
|
|
307
|
+
@Property({ name: 'loaded_at', type: Date, nullable: true })
|
|
308
|
+
loadedAt?: Date | null
|
|
309
|
+
|
|
310
|
+
@Property({ name: 'raw_data', type: 'jsonb', nullable: true })
|
|
311
|
+
rawData?: Record<string, unknown> | null
|
|
312
|
+
|
|
313
|
+
@Property({ name: 'created_at', type: Date, defaultRaw: 'now()' })
|
|
314
|
+
createdAt!: Date
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ─── TerminalVesselVisit ─────────────────────────────────────
|
|
318
|
+
// A resolved N4 vessel visit (from /VESSEL), keyed by visit ref and shared by
|
|
319
|
+
// every container on that visit. Doubles as a TTL cache: `updatedAt` is the
|
|
320
|
+
// freshness signal. Upsert-only (no soft delete); no FK to job.
|
|
321
|
+
|
|
322
|
+
@Entity({ tableName: 'terminal_tracking_vessel_visits' })
|
|
323
|
+
@Index({ name: 'tt_vessel_visits_org_tenant_idx', properties: ['organizationId', 'tenantId'] })
|
|
324
|
+
@Index({ name: 'tt_vessel_visits_visit_ref_idx', properties: ['visitRef'] })
|
|
325
|
+
@Unique({ name: 'tt_vessel_visits_uniq', properties: ['organizationId', 'tenantId', 'terminalCode', 'visitRef'] })
|
|
326
|
+
export class TerminalVesselVisit {
|
|
327
|
+
[OptionalProps]?: 'createdAt' | 'updatedAt'
|
|
328
|
+
|
|
329
|
+
@PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
|
|
330
|
+
id!: string
|
|
331
|
+
|
|
332
|
+
@Property({ name: 'organization_id', type: 'uuid' })
|
|
333
|
+
organizationId!: string
|
|
334
|
+
|
|
335
|
+
@Property({ name: 'tenant_id', type: 'uuid' })
|
|
336
|
+
tenantId!: string
|
|
337
|
+
|
|
338
|
+
@Property({ name: 'terminal_code', type: 'text' })
|
|
339
|
+
terminalCode!: string
|
|
340
|
+
|
|
341
|
+
// The N4 visit ref (the `Visit` column); equals an event's visitRefIn/Out.
|
|
342
|
+
@Property({ name: 'visit_ref', type: 'text' })
|
|
343
|
+
visitRef!: string
|
|
344
|
+
|
|
345
|
+
@Property({ name: 'vessel_name', type: 'text', nullable: true })
|
|
346
|
+
vesselName?: string | null
|
|
347
|
+
|
|
348
|
+
@Property({ name: 'ib_voyage', type: 'text', nullable: true })
|
|
349
|
+
ibVoyage?: string | null
|
|
350
|
+
|
|
351
|
+
@Property({ name: 'ob_voyage', type: 'text', nullable: true })
|
|
352
|
+
obVoyage?: string | null
|
|
353
|
+
|
|
354
|
+
@Property({ name: 'line', type: 'text', nullable: true })
|
|
355
|
+
line?: string | null
|
|
356
|
+
|
|
357
|
+
@Property({ name: 'phase', type: 'text', nullable: true })
|
|
358
|
+
phase?: string | null
|
|
359
|
+
|
|
360
|
+
@Property({ name: 'eta', type: Date, nullable: true })
|
|
361
|
+
eta?: Date | null
|
|
362
|
+
|
|
363
|
+
@Property({ name: 'etd', type: Date, nullable: true })
|
|
364
|
+
etd?: Date | null
|
|
365
|
+
|
|
366
|
+
@Property({ name: 'ata', type: Date, nullable: true })
|
|
367
|
+
ata?: Date | null
|
|
368
|
+
|
|
369
|
+
@Property({ name: 'atd', type: Date, nullable: true })
|
|
370
|
+
atd?: Date | null
|
|
371
|
+
|
|
372
|
+
@Property({ name: 'begin_receive', type: Date, nullable: true })
|
|
373
|
+
beginReceive?: Date | null
|
|
374
|
+
|
|
375
|
+
@Property({ name: 'dry_cutoff', type: Date, nullable: true })
|
|
376
|
+
dryCutoff?: Date | null
|
|
377
|
+
|
|
378
|
+
@Property({ name: 'raw_data', type: 'jsonb', nullable: true })
|
|
379
|
+
rawData?: Record<string, unknown> | null
|
|
380
|
+
|
|
381
|
+
@Property({ name: 'created_at', type: Date, defaultRaw: 'now()' })
|
|
382
|
+
createdAt!: Date
|
|
383
|
+
|
|
384
|
+
@Property({ name: 'updated_at', type: Date, defaultRaw: 'now()', onUpdate: () => new Date() })
|
|
385
|
+
updatedAt!: Date
|
|
386
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
const endpointsSchema = z.object({
|
|
4
|
+
unit: z.string().min(1),
|
|
5
|
+
vessel: z.string().optional(),
|
|
6
|
+
trainVisits: z.string().optional(),
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
const authTypeSchema = z.enum(['oauth2_password', 'oauth2_client_credentials', 'gct_token', 'basic'])
|
|
10
|
+
|
|
11
|
+
// ─── TerminalConfig ──────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
export const terminalConfigCreateSchema = z.object({
|
|
14
|
+
organizationId: z.string().uuid(),
|
|
15
|
+
tenantId: z.string().uuid(),
|
|
16
|
+
terminalCode: z.string().min(1).transform((s) => s.toLowerCase()),
|
|
17
|
+
adapterType: z.string().min(1),
|
|
18
|
+
displayName: z.string().min(1),
|
|
19
|
+
baseUrl: z.string().url(),
|
|
20
|
+
proxyUrl: z.string().url().nullish(),
|
|
21
|
+
endpoints: endpointsSchema.optional(),
|
|
22
|
+
authType: authTypeSchema,
|
|
23
|
+
tokenUrl: z.string().url().nullish(),
|
|
24
|
+
scope: z.string().nullish(),
|
|
25
|
+
clientId: z.string().nullish(),
|
|
26
|
+
authConfig: z.record(z.string(), z.unknown()).nullish(),
|
|
27
|
+
rateLimitRequests: z.number().int().positive().optional(),
|
|
28
|
+
rateLimitWindowSeconds: z.number().int().positive().optional(),
|
|
29
|
+
unlocode: z.string().length(5).nullish(),
|
|
30
|
+
bicCodes: z.array(z.string()).nullish(),
|
|
31
|
+
smdgCodes: z.array(z.string()).nullish(),
|
|
32
|
+
nameAliases: z.array(z.string()).nullish(),
|
|
33
|
+
isActive: z.boolean().optional(),
|
|
34
|
+
})
|
|
35
|
+
export type TerminalConfigCreateInput = z.infer<typeof terminalConfigCreateSchema>
|
|
36
|
+
|
|
37
|
+
export const terminalConfigUpdateSchema = terminalConfigCreateSchema
|
|
38
|
+
.partial()
|
|
39
|
+
.extend({ id: z.string().uuid() })
|
|
40
|
+
export type TerminalConfigUpdateInput = z.infer<typeof terminalConfigUpdateSchema>
|
|
41
|
+
|
|
42
|
+
export const terminalConfigDeleteSchema = z.object({
|
|
43
|
+
id: z.string().uuid(),
|
|
44
|
+
organizationId: z.string().uuid(),
|
|
45
|
+
tenantId: z.string().uuid(),
|
|
46
|
+
})
|
|
47
|
+
export type TerminalConfigDeleteInput = z.infer<typeof terminalConfigDeleteSchema>
|
|
48
|
+
|
|
49
|
+
export const terminalConfigTestSchema = z.object({
|
|
50
|
+
id: z.string().uuid(),
|
|
51
|
+
organizationId: z.string().uuid(),
|
|
52
|
+
tenantId: z.string().uuid(),
|
|
53
|
+
})
|
|
54
|
+
export type TerminalConfigTestInput = z.infer<typeof terminalConfigTestSchema>
|
|
55
|
+
|
|
56
|
+
export const terminalConfigListSchema = z.object({
|
|
57
|
+
page: z.coerce.number().int().positive().optional(),
|
|
58
|
+
pageSize: z.coerce.number().int().positive().max(100).optional(),
|
|
59
|
+
sortField: z.string().optional(),
|
|
60
|
+
sortDir: z.enum(['asc', 'desc']).optional(),
|
|
61
|
+
terminalCode: z.string().optional(),
|
|
62
|
+
isActive: z.string().optional(),
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// ─── TerminalTrackingJob ─────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
export const trackingJobCreateSchema = z.object({
|
|
68
|
+
organizationId: z.string().uuid(),
|
|
69
|
+
tenantId: z.string().uuid(),
|
|
70
|
+
terminalCode: z.string().min(1).transform((s) => s.toLowerCase()),
|
|
71
|
+
containerNumber: z.string().min(4).transform((s) => s.toUpperCase()),
|
|
72
|
+
schedule: z.array(z.string()).nullish(),
|
|
73
|
+
})
|
|
74
|
+
export type TrackingJobCreateInput = z.infer<typeof trackingJobCreateSchema>
|
|
75
|
+
|
|
76
|
+
export const trackingJobDeleteSchema = z.object({
|
|
77
|
+
id: z.string().uuid(),
|
|
78
|
+
organizationId: z.string().uuid(),
|
|
79
|
+
tenantId: z.string().uuid(),
|
|
80
|
+
})
|
|
81
|
+
export type TrackingJobDeleteInput = z.infer<typeof trackingJobDeleteSchema>
|
|
82
|
+
|
|
83
|
+
export const trackingJobPollSchema = z.object({
|
|
84
|
+
jobId: z.string().uuid(),
|
|
85
|
+
organizationId: z.string().uuid(),
|
|
86
|
+
tenantId: z.string().uuid(),
|
|
87
|
+
})
|
|
88
|
+
export type TrackingJobPollInput = z.infer<typeof trackingJobPollSchema>
|
|
89
|
+
|
|
90
|
+
export const trackingJobListSchema = z.object({
|
|
91
|
+
page: z.coerce.number().int().positive().optional(),
|
|
92
|
+
// DynamicTable sends both `limit` and `pageSize` (same value); accept either.
|
|
93
|
+
pageSize: z.coerce.number().int().positive().max(100).optional(),
|
|
94
|
+
limit: z.coerce.number().int().positive().max(100).optional(),
|
|
95
|
+
sortField: z.string().optional(),
|
|
96
|
+
sortDir: z.enum(['asc', 'desc']).optional(),
|
|
97
|
+
terminalCode: z.string().optional(),
|
|
98
|
+
containerNumber: z.string().optional(),
|
|
99
|
+
status: z.string().optional(),
|
|
100
|
+
// DynamicTable filter popover — JSON-stringified FilterRow[] sent as `?filters=`
|
|
101
|
+
filters: z.string().optional(),
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
// ─── TerminalVesselVisit ─────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
export const vesselVisitListSchema = z.object({
|
|
107
|
+
page: z.coerce.number().int().positive().optional(),
|
|
108
|
+
pageSize: z.coerce.number().int().positive().max(100).optional(),
|
|
109
|
+
visitRef: z.string().optional(),
|
|
110
|
+
containerNumber: z.string().optional().transform((s) => (s ? s.toUpperCase() : s)),
|
|
111
|
+
terminalCode: z.string().optional(),
|
|
112
|
+
})
|
|
113
|
+
export type VesselVisitListInput = z.infer<typeof vesselVisitListSchema>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { asClass } from 'awilix'
|
|
2
|
+
import type { AppContainer } from '@open-mercato/shared/lib/di/container'
|
|
3
|
+
import { TerminalRegistry } from './services/terminalRegistry'
|
|
4
|
+
import { TerminalTrackingService } from './services/terminalTrackingService'
|
|
5
|
+
import { TerminalMatcherService } from './services/terminalMatcherService'
|
|
6
|
+
import { registerAllAdapters } from './lib/adapters'
|
|
7
|
+
|
|
8
|
+
export function register(container: AppContainer) {
|
|
9
|
+
// Adapter registry (singleton - adapters registered once)
|
|
10
|
+
container.register({
|
|
11
|
+
terminalTrackingRegistry: asClass(TerminalRegistry).singleton(),
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
// Tracking service (poll loop, dedup, emit)
|
|
15
|
+
container.register({
|
|
16
|
+
terminalTrackingService: {
|
|
17
|
+
resolve: () => {
|
|
18
|
+
const cache = container.resolve<any>('cache')
|
|
19
|
+
return new TerminalTrackingService({
|
|
20
|
+
em: () => container.resolve('em'),
|
|
21
|
+
eventBus: container.resolve('eventBus'),
|
|
22
|
+
terminalRegistry: container.resolve('terminalTrackingRegistry'),
|
|
23
|
+
cacheService: {
|
|
24
|
+
get: (key: string) => cache.get(key),
|
|
25
|
+
set: (key: string, value: string, ttlSeconds?: number) =>
|
|
26
|
+
cache.set(key, value, ttlSeconds ? { ttl: ttlSeconds * 1000 } : undefined),
|
|
27
|
+
},
|
|
28
|
+
})
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
// Terminal matcher (fuzzy name + exact BIC/SMDG/UNLOCODE)
|
|
34
|
+
container.register({
|
|
35
|
+
terminalMatcherService: {
|
|
36
|
+
resolve: () =>
|
|
37
|
+
new TerminalMatcherService({
|
|
38
|
+
em: () => container.resolve('em'),
|
|
39
|
+
}),
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// Register built-in adapters
|
|
44
|
+
const registry = container.resolve<TerminalRegistry>('terminalTrackingRegistry')
|
|
45
|
+
registerAllAdapters(registry)
|
|
46
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ModuleEncryptionMap } from '@open-mercato/shared/modules/encryption'
|
|
2
|
+
|
|
3
|
+
export const defaultEncryptionMaps: ModuleEncryptionMap[] = [
|
|
4
|
+
{
|
|
5
|
+
entityId: 'terminal_tracking:terminal_config',
|
|
6
|
+
fields: [
|
|
7
|
+
{ field: 'auth_config' },
|
|
8
|
+
],
|
|
9
|
+
},
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
export default defaultEncryptionMaps
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createModuleEvents } from '@open-mercato/shared/modules/events'
|
|
2
|
+
|
|
3
|
+
const events = [
|
|
4
|
+
// ─── Tracking job lifecycle (internal) ─────────────────────────
|
|
5
|
+
{ id: 'terminal_tracking.tracking_job.created', label: 'Terminal Tracking Job Created', entity: 'tracking_job', category: 'crud', excludeFromTriggers: true },
|
|
6
|
+
{ id: 'terminal_tracking.tracking_job.updated', label: 'Terminal Tracking Job Updated', entity: 'tracking_job', category: 'crud', excludeFromTriggers: true },
|
|
7
|
+
{ id: 'terminal_tracking.tracking_job.failed', label: 'Terminal Tracking Job Failed', entity: 'tracking_job', category: 'lifecycle', excludeFromTriggers: true },
|
|
8
|
+
{ id: 'terminal_tracking.tracking_job.poll_failed', label: 'Terminal Tracking Job Poll Failed', entity: 'tracking_job', category: 'lifecycle', excludeFromTriggers: true },
|
|
9
|
+
|
|
10
|
+
// ─── Raw terminal event (the bridge trigger) ───────────────────
|
|
11
|
+
{ id: 'terminal_tracking.terminal_event.created', label: 'Terminal Event Created', entity: 'terminal_event', category: 'crud' },
|
|
12
|
+
{ id: 'terminal_tracking.terminal_event.updated', label: 'Terminal Event Updated', entity: 'terminal_event', category: 'crud' },
|
|
13
|
+
|
|
14
|
+
// ─── Semantic milestones (per container) ───────────────────────
|
|
15
|
+
{ id: 'terminal_tracking.equipment.gate_in', label: 'Container Gate In (Terminal)', entity: 'terminal_event', category: 'lifecycle' },
|
|
16
|
+
{ id: 'terminal_tracking.equipment.discharged', label: 'Container Discharged (Terminal)', entity: 'terminal_event', category: 'lifecycle' },
|
|
17
|
+
{ id: 'terminal_tracking.equipment.loaded', label: 'Container Loaded (Terminal)', entity: 'terminal_event', category: 'lifecycle' },
|
|
18
|
+
{ id: 'terminal_tracking.transport.departed', label: 'Container Departed (Terminal)', entity: 'terminal_event', category: 'lifecycle' },
|
|
19
|
+
|
|
20
|
+
// ─── Availability milestones (fire once per container) ─────────
|
|
21
|
+
// Export empty released for collection: empty container in the yard with no
|
|
22
|
+
// movement-blocking holds.
|
|
23
|
+
{ id: 'terminal_tracking.equipment.empty_ready', label: 'Empty Container Ready For Pickup (Terminal)', entity: 'terminal_event', category: 'lifecycle' },
|
|
24
|
+
// Import container's blocking holds removed: it became collectable (transition
|
|
25
|
+
// from blocked → unblocked while in the yard).
|
|
26
|
+
{ id: 'terminal_tracking.equipment.holds_cleared', label: 'Import Holds Cleared (Terminal)', entity: 'terminal_event', category: 'lifecycle' },
|
|
27
|
+
// The raw impediment (hold) set changed between polls. Unlike the two
|
|
28
|
+
// one-shot availability events above, this fires every poll the set changes
|
|
29
|
+
// (including first appearance with holds) so downstream consumers can mirror
|
|
30
|
+
// the current hold list. Payload carries `impediments: string[]`.
|
|
31
|
+
{ id: 'terminal_tracking.equipment.holds_updated', label: 'Container Holds Updated (Terminal)', entity: 'terminal_event', category: 'lifecycle' },
|
|
32
|
+
// The per-mode load/pickup STOP flags (Stop-Vsl / Stop-Road / Stop-Rail)
|
|
33
|
+
// changed between polls. Like `holds_updated`, fires every poll the set
|
|
34
|
+
// changes (incl. first appearance) so downstream consumers can mirror the
|
|
35
|
+
// current stops. Payload carries `stops: { vsl, road, rail: boolean | null }`.
|
|
36
|
+
{ id: 'terminal_tracking.equipment.stops_updated', label: 'Container Load Stops Updated (Terminal)', entity: 'terminal_event', category: 'lifecycle' },
|
|
37
|
+
] as const
|
|
38
|
+
|
|
39
|
+
export const eventsConfig = createModuleEvents({
|
|
40
|
+
moduleId: 'terminal_tracking',
|
|
41
|
+
events,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
export const emitTerminalTrackingEvent = eventsConfig.emit
|
|
45
|
+
|
|
46
|
+
export type TerminalTrackingEventId = typeof events[number]['id']
|
|
47
|
+
|
|
48
|
+
export default eventsConfig
|