@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,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/terminal_tracking/services/terminalTrackingService.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { EventBus } from '@open-mercato/events'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { TerminalConfig, TerminalTrackingJob, TerminalEvent, TerminalVesselVisit } from '../data/entities'\nimport type { ResolvedTerminalConfig, TerminalFetchedEvent, TerminalAdapter } from '../lib/terminal-adapter'\nimport type { TerminalRegistry } from './terminalRegistry'\nimport { checkRateLimit, type CacheService } from '../lib/rate-limiter'\nimport { semanticEventIdFor, pickVesselVisit, parseCargoCategory } from '../lib/adapters/n4/n4-semantics'\nimport { impedimentsChanged, isEmptyReady, isImportHoldsCleared } from '../lib/availability'\nimport { parseTerminalStops, stopsChanged, type TerminalStops } from '../lib/stops'\nimport { terminalLogger } from '../lib/logger'\n\n/** Default TTL for a cached /VESSEL visit lookup. Vessel ETA/ATA move on the\n * scale of hours and the shared rate bucket is mostly spent on /unit polls, so\n * one hour balances freshness against token spend. */\nconst DEFAULT_VESSEL_CACHE_TTL_SECONDS = 3600\n\n/** Max containers per /unit request. N4 accepts a comma-separated UNIT_NBR list\n * (the terminal supports up to ~500); we cap lower to bound URL/response size\n * and keep each batch to a single rate-limit token. */\nconst MAX_CONTAINERS_PER_UNIT_REQUEST = 100\n\nfunction chunk<T>(items: T[], size: number): T[][] {\n const out: T[][] = []\n for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size))\n return out\n}\n\n/** A container's terminal lifecycle is complete when this poll returned events\n * and every current leg has departed (`DEPA`). Empty results are NOT departed\n * (container not found / unmapped states) \u2014 keep polling. */\nfunction isFullyDeparted(events: TerminalFetchedEvent[]): boolean {\n return events.length > 0 && events.every((e) => e.eventCode === 'DEPA')\n}\n\ntype Deps = {\n em: () => EntityManager\n eventBus: EventBus\n terminalRegistry: TerminalRegistry\n cacheService: CacheService\n}\n\nconst MAX_ERROR_HISTORY = 20\nconst MAX_RETRIES = 10\n\nexport type CreateJobInput = {\n organizationId: string\n tenantId: string\n terminalCode: string\n containerNumber: string\n schedule?: string[] | null\n}\n\nexport type PollJobResult = { newEvents: number; updatedEvents: number }\nexport type PollAllResult = { polled: number; newEvents: number; updatedEvents: number; failed: number }\n\ntype EventLike = Pick<\n TerminalEvent,\n | 'eventClassifierCode'\n | 'eventDateTime'\n | 'transitState'\n | 'visitState'\n | 'facilityCode'\n | 'facilityCodeListProvider'\n | 'unlocode'\n | 'visitRefIn'\n | 'visitRefOut'\n | 'vesselName'\n | 'voyageNumber'\n | 'modeOfTransport'\n | 'seals'\n | 'vgmWeightKg'\n | 'impediments'\n | 'loadedAt'\n>\n\n/** Stable signature of an event's mutable container fields, to detect changes\n * between polls. Excludes the identity (key) fields, which never change. */\nfunction eventSignature(e: EventLike): string {\n return JSON.stringify({\n cls: e.eventClassifierCode ?? null,\n dt: e.eventDateTime ? new Date(e.eventDateTime).toISOString() : null,\n transitState: e.transitState ?? null,\n visitState: e.visitState ?? null,\n facilityCode: e.facilityCode ?? null,\n facilityCodeListProvider: e.facilityCodeListProvider ?? null,\n unlocode: e.unlocode ?? null,\n visitRefIn: e.visitRefIn ?? null,\n visitRefOut: e.visitRefOut ?? null,\n vesselName: e.vesselName ?? null,\n voyageNumber: e.voyageNumber ?? null,\n modeOfTransport: e.modeOfTransport ?? null,\n seals: e.seals ?? null,\n vgmWeightKg: e.vgmWeightKg ?? null,\n impediments: e.impediments ?? null,\n loadedAt: e.loadedAt ? new Date(e.loadedAt).toISOString() : null,\n })\n}\n\ntype VesselLike = {\n vesselName?: string | null\n ibVoyage?: string | null\n obVoyage?: string | null\n line?: string | null\n phase?: string | null\n eta?: Date | null\n etd?: Date | null\n ata?: Date | null\n atd?: Date | null\n beginReceive?: Date | null\n dryCutoff?: Date | null\n}\n\n/** Stable signature of a vessel visit's meaningful fields (ETA/ATA/etc.), to\n * detect when /VESSEL data changes between polls. */\nfunction vesselSignature(v: VesselLike): string {\n const d = (x?: Date | null) => (x ? x.toISOString() : null)\n return JSON.stringify({\n vesselName: v.vesselName ?? null,\n ibVoyage: v.ibVoyage ?? null,\n obVoyage: v.obVoyage ?? null,\n line: v.line ?? null,\n phase: v.phase ?? null,\n eta: d(v.eta),\n etd: d(v.etd),\n ata: d(v.ata),\n atd: d(v.atd),\n beginReceive: d(v.beginReceive),\n dryCutoff: d(v.dryCutoff),\n })\n}\n\nexport class TerminalTrackingService {\n constructor(private readonly deps: Deps) {}\n\n private toResolvedConfig(config: TerminalConfig): ResolvedTerminalConfig {\n return {\n terminalCode: config.terminalCode,\n adapterType: config.adapterType,\n displayName: config.displayName,\n baseUrl: config.baseUrl,\n proxyUrl: config.proxyUrl ?? null,\n endpoints: config.endpoints,\n authType: config.authType,\n tokenUrl: config.tokenUrl ?? null,\n scope: config.scope ?? null,\n clientId: config.clientId ?? null,\n authConfig: config.authConfig ?? null,\n rateLimitRequests: config.rateLimitRequests,\n rateLimitWindowSeconds: config.rateLimitWindowSeconds,\n unlocode: config.unlocode ?? null,\n facilityCode: (config.smdgCodes ?? [])[0] ?? (config.bicCodes ?? [])[0] ?? null,\n facilityCodeListProvider: (config.smdgCodes ?? []).length\n ? 'SMDG'\n : (config.bicCodes ?? []).length\n ? 'BIC'\n : null,\n }\n }\n\n async testTerminalConfig(input: {\n id: string\n organizationId: string\n tenantId: string\n }): Promise<{ success: boolean; message: string; latencyMs?: number }> {\n const em = this.deps.em().fork()\n const config = await findOneWithDecryption(em, TerminalConfig, {\n id: input.id,\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n deletedAt: null,\n })\n if (!config) return { success: false, message: 'Terminal config not found' }\n\n const adapter = this.deps.terminalRegistry.get(config.adapterType)\n if (!adapter) return { success: false, message: `No adapter registered for type '${config.adapterType}'` }\n\n return adapter.testConnection(this.toResolvedConfig(config))\n }\n\n async createJob(input: CreateJobInput): Promise<{ trackingJobId: string; newEvents: number }> {\n const em = this.deps.em().fork()\n\n const existing = await em.findOne(TerminalTrackingJob, {\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n terminalCode: input.terminalCode,\n containerNumber: input.containerNumber,\n // Reuse only a still-open job (avoid duplicate in-flight tracking). A\n // 'completed' job is NOT reused: a container that returns later gets a\n // fresh tracking job with clean history.\n status: { $in: ['active', 'paused'] },\n deletedAt: null,\n })\n\n if (existing) {\n // Re-poll the existing job so the caller gets fresh data.\n const result = await this.pollJob(existing.id)\n return { trackingJobId: existing.id, newEvents: result.newEvents }\n }\n\n const job = em.create(TerminalTrackingJob, {\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n terminalCode: input.terminalCode,\n containerNumber: input.containerNumber,\n status: 'active',\n schedule: input.schedule ?? null,\n nextPollAt: null,\n })\n await em.flush()\n\n // The initial-poll subscriber performs the first poll on this event.\n await this.deps.eventBus.emit('terminal_tracking.tracking_job.created', {\n id: job.id,\n terminalCode: job.terminalCode,\n containerNumber: job.containerNumber,\n tenantId: job.tenantId,\n organizationId: job.organizationId,\n })\n\n return { trackingJobId: job.id, newEvents: 0 }\n }\n\n async pollJob(jobId: string): Promise<PollJobResult> {\n const em = this.deps.em().fork()\n const job = await em.findOne(TerminalTrackingJob, { id: jobId, deletedAt: null })\n if (!job) throw new Error('Terminal tracking job not found')\n\n try {\n const config = await findOneWithDecryption(em, TerminalConfig, {\n organizationId: job.organizationId,\n tenantId: job.tenantId,\n terminalCode: job.terminalCode,\n isActive: true,\n deletedAt: null,\n })\n if (!config) throw new Error(`No active terminal config for '${job.terminalCode}'`)\n\n const limit = await checkRateLimit(\n this.deps.cacheService,\n job.tenantId,\n job.terminalCode,\n config.rateLimitRequests,\n config.rateLimitWindowSeconds,\n )\n if (!limit.allowed) {\n terminalLogger.debug('Rate limited, skipping poll', { jobId, terminalCode: job.terminalCode })\n return { newEvents: 0, updatedEvents: 0 }\n }\n\n const adapter = this.deps.terminalRegistry.get(config.adapterType)\n if (!adapter) throw new Error(`No adapter registered for type '${config.adapterType}'`)\n\n const resolved = this.toResolvedConfig(config)\n const result = await adapter.fetchEvents({ containerNumber: job.containerNumber, config: resolved })\n\n // Enrich events with vessel ETA/ATA from /VESSEL (cache-backed, rate-limited).\n const changedVesselRefs = await this.enrichWithVesselVisits(\n em,\n { organizationId: job.organizationId, tenantId: job.tenantId, terminalCode: job.terminalCode },\n config,\n resolved,\n adapter,\n result.events,\n )\n\n const { created, updated, currentByKey, priorImpedimentsByKey, priorStopsByKey } = await this.persistEvents(\n em,\n job,\n result.events,\n changedVesselRefs,\n )\n\n job.lastPollAt = new Date()\n job.nextPollAt = this.computeNextPoll(job.schedule)\n job.retryCount = 0\n // Every current leg departed \u2192 terminal lifecycle complete; drop from the\n // due-set. A re-appearing leg reverts to active on the next manual poll.\n job.status = isFullyDeparted(result.events) ? 'completed' : 'active'\n await em.flush()\n\n const vesselByEventId = new Map<string, TerminalFetchedEvent['vesselVisit']>(\n result.events.map((e) => [e.sourceEventId, e.vesselVisit ?? null]),\n )\n for (const event of created) {\n await this.emitCreated(event, vesselByEventId.get(event.sourceEventId) ?? null)\n }\n for (const event of updated) {\n await this.emitUpdated(event, vesselByEventId.get(event.sourceEventId) ?? null)\n }\n await this.evaluateAndEmitAvailability(em, job, currentByKey, priorImpedimentsByKey, priorStopsByKey, vesselByEventId)\n\n return { newEvents: created.length, updatedEvents: updated.length }\n } catch (err) {\n await this.recordJobError(em, job, err)\n return { newEvents: 0, updatedEvents: 0 }\n }\n }\n\n async pollAllActiveJobs(tenantId: string, organizationId?: string): Promise<PollAllResult> {\n const em = this.deps.em().fork()\n const now = new Date()\n const jobs = await em.find(TerminalTrackingJob, {\n tenantId,\n ...(organizationId ? { organizationId } : {}),\n status: 'active',\n deletedAt: null,\n $or: [{ nextPollAt: null }, { nextPollAt: { $lte: now } }],\n })\n\n // Group due jobs per terminal config (org + terminalCode). Each group shares\n // one /unit request budget and one vessel-visit cache, so we poll it in\n // batches rather than one container at a time.\n const groups = new Map<string, TerminalTrackingJob[]>()\n for (const job of jobs) {\n const key = `${job.organizationId}::${job.terminalCode}`\n const arr = groups.get(key)\n if (arr) arr.push(job)\n else groups.set(key, [job])\n }\n\n let newEvents = 0\n let updatedEvents = 0\n let failed = 0\n for (const group of groups.values()) {\n try {\n const r = await this.pollTerminalGroup(em, group)\n newEvents += r.newEvents\n updatedEvents += r.updatedEvents\n failed += r.failed\n } catch {\n failed += group.length\n }\n }\n return { polled: jobs.length, newEvents, updatedEvents, failed }\n }\n\n /**\n * Poll one terminal group (same org + terminalCode) in batches. Each batch of\n * up to MAX_CONTAINERS_PER_UNIT_REQUEST containers is a single /unit call, and\n * vessel visits are resolved once across the whole batch (deduped + cached),\n * minimising API requests. Adapters without batch support fall back to the\n * per-container path.\n */\n private async pollTerminalGroup(\n em: EntityManager,\n jobs: TerminalTrackingJob[],\n ): Promise<{ newEvents: number; updatedEvents: number; failed: number }> {\n const first = jobs[0]\n const scope = {\n organizationId: first.organizationId,\n tenantId: first.tenantId,\n terminalCode: first.terminalCode,\n }\n\n let config: TerminalConfig | null = null\n try {\n config = await findOneWithDecryption(em, TerminalConfig, {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n terminalCode: scope.terminalCode,\n isActive: true,\n deletedAt: null,\n })\n } catch {\n config = null\n }\n const adapter = config ? this.deps.terminalRegistry.get(config.adapterType) : undefined\n if (!config || !adapter) {\n const err = new Error(`No active terminal config/adapter for '${scope.terminalCode}'`)\n for (const job of jobs) await this.recordJobError(em, job, err)\n return { newEvents: 0, updatedEvents: 0, failed: jobs.length }\n }\n\n // Adapters lacking batch support: fall back to the per-container poll path.\n if (typeof adapter.fetchEventsBatch !== 'function') {\n let newEvents = 0\n let updatedEvents = 0\n let failed = 0\n for (const job of jobs) {\n try {\n const r = await this.pollJob(job.id)\n newEvents += r.newEvents\n updatedEvents += r.updatedEvents\n } catch {\n failed += 1\n }\n }\n return { newEvents, updatedEvents, failed }\n }\n\n const resolved = this.toResolvedConfig(config)\n let newEvents = 0\n let updatedEvents = 0\n let failed = 0\n\n for (const batch of chunk(jobs, MAX_CONTAINERS_PER_UNIT_REQUEST)) {\n const limit = await checkRateLimit(\n this.deps.cacheService,\n scope.tenantId,\n scope.terminalCode,\n config.rateLimitRequests,\n config.rateLimitWindowSeconds,\n )\n if (!limit.allowed) {\n terminalLogger.debug('Rate limited, skipping unit batch', {\n terminalCode: scope.terminalCode,\n batch: batch.length,\n })\n continue // jobs stay due; retried next cycle\n }\n\n try {\n const containerNumbers = batch.map((j) => j.containerNumber)\n const { events } = await adapter.fetchEventsBatch!({ containerNumbers, config: resolved })\n\n // Resolve vessel visits once across the whole batch (deduped + cached).\n const changedVesselRefs = await this.enrichWithVesselVisits(em, scope, config, resolved, adapter, events)\n\n const byContainer = new Map<string, TerminalFetchedEvent[]>()\n for (const e of events) {\n const arr = byContainer.get(e.containerNumber)\n if (arr) arr.push(e)\n else byContainer.set(e.containerNumber, [e])\n }\n\n for (const job of batch) {\n const jobEvents = byContainer.get(job.containerNumber) ?? []\n const { created, updated, currentByKey, priorImpedimentsByKey, priorStopsByKey } = await this.persistEvents(\n em,\n job,\n jobEvents,\n changedVesselRefs,\n )\n\n job.lastPollAt = new Date()\n job.nextPollAt = this.computeNextPoll(job.schedule)\n job.retryCount = 0\n job.status = isFullyDeparted(jobEvents) ? 'completed' : 'active'\n await em.flush()\n\n const vesselByEventId = new Map<string, TerminalFetchedEvent['vesselVisit']>(\n jobEvents.map((e) => [e.sourceEventId, e.vesselVisit ?? null]),\n )\n for (const ev of created) {\n await this.emitCreated(ev, vesselByEventId.get(ev.sourceEventId) ?? null)\n newEvents += 1\n }\n for (const ev of updated) {\n await this.emitUpdated(ev, vesselByEventId.get(ev.sourceEventId) ?? null)\n updatedEvents += 1\n }\n await this.evaluateAndEmitAvailability(em, job, currentByKey, priorImpedimentsByKey, priorStopsByKey, vesselByEventId)\n }\n } catch (err) {\n for (const job of batch) await this.recordJobError(em, job, err)\n failed += batch.length\n }\n }\n\n return { newEvents, updatedEvents, failed }\n }\n\n /**\n * Resolve the vessel visit for each fetched event/leg and attach ETA/ETD/\n * ATA/ATD + phase. The `TerminalVesselVisit` table is the cache: a row fresh\n * within the TTL is reused without an API call, so the many containers that\n * share one vessel visit cost a single /VESSEL fetch per cycle. Each fetch is\n * rate-limit-gated against the same bucket as /unit; when the budget is spent,\n * vessel enrichment is shed (the event still emits, sans vessel data) and\n * filled on the next cycle.\n */\n private async enrichWithVesselVisits(\n em: EntityManager,\n scope: { organizationId: string; tenantId: string; terminalCode: string },\n config: TerminalConfig,\n resolved: ResolvedTerminalConfig,\n adapter: TerminalAdapter,\n events: TerminalFetchedEvent[],\n ): Promise<Set<string>> {\n // Visit refs whose /VESSEL data changed (or first resolved) this poll.\n const changedRefs = new Set<string>()\n if (typeof adapter.fetchVesselVisit !== 'function') return changedRefs\n if (!resolved.endpoints.vessel) return changedRefs\n\n const picks = events.map((event) => ({ event, pick: pickVesselVisit(event) }))\n // All picked refs (for attaching cached data); only non-departed legs are\n // fetched \u2014 a DEPA leg's visit has sailed and /VESSEL returns empty.\n const allRefs = [...new Set(picks.map((p) => p.pick?.ref).filter((r): r is string => !!r))]\n const fetchRefs = [\n ...new Set(\n picks\n .filter((p) => p.pick && p.event.eventCode !== 'DEPA')\n .map((p) => p.pick!.ref),\n ),\n ]\n if (allRefs.length === 0) return changedRefs\n\n const ttlMs = (config.vesselCacheTtlSeconds ?? DEFAULT_VESSEL_CACHE_TTL_SECONDS) * 1000\n const now = Date.now()\n\n const existing = await em.find(TerminalVesselVisit, {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n terminalCode: scope.terminalCode,\n visitRef: { $in: allRefs },\n })\n const byRef = new Map<string, TerminalVesselVisit>(existing.map((r) => [r.visitRef, r]))\n\n for (const ref of fetchRefs) {\n const cached = byRef.get(ref)\n if (cached && now - cached.updatedAt.getTime() < ttlMs) continue // fresh cache hit\n\n const limit = await checkRateLimit(\n this.deps.cacheService,\n scope.tenantId,\n scope.terminalCode,\n config.rateLimitRequests,\n config.rateLimitWindowSeconds,\n )\n if (!limit.allowed) {\n terminalLogger.debug('Rate limited, skipping vessel fetch', { terminalCode: scope.terminalCode, visitRef: ref })\n continue\n }\n\n try {\n const visit = await adapter.fetchVesselVisit!({ visitRef: ref, config: resolved })\n if (!visit) continue\n // A first-time resolution, or any field difference, counts as a change.\n if (!cached || vesselSignature(cached) !== vesselSignature(visit)) changedRefs.add(ref)\n const row =\n cached ??\n em.create(TerminalVesselVisit, {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n terminalCode: scope.terminalCode,\n visitRef: ref,\n })\n row.vesselName = visit.vesselName\n row.ibVoyage = visit.ibVoyage\n row.obVoyage = visit.obVoyage\n row.line = visit.line\n row.phase = visit.phase\n row.eta = visit.eta\n row.etd = visit.etd\n row.ata = visit.ata\n row.atd = visit.atd\n row.beginReceive = visit.beginReceive\n row.dryCutoff = visit.dryCutoff\n row.rawData = visit.rawData\n row.updatedAt = new Date()\n byRef.set(ref, row)\n } catch (err) {\n terminalLogger.debug('Vessel fetch failed', {\n terminalCode: scope.terminalCode,\n visitRef: ref,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n await em.flush()\n\n // Attach the resolved visit onto each event (direction-correct voyage).\n for (const { event, pick } of picks) {\n if (!pick) continue\n const row = byRef.get(pick.ref)\n if (!row) continue\n event.vesselName = row.vesselName ?? null\n event.voyageNumber = (pick.dir === 'in' ? row.ibVoyage : row.obVoyage) ?? null\n event.vesselVisit = {\n visitRef: row.visitRef,\n vesselName: row.vesselName ?? null,\n ibVoyage: row.ibVoyage ?? null,\n obVoyage: row.obVoyage ?? null,\n line: row.line ?? null,\n phase: row.phase ?? null,\n eta: row.eta ?? null,\n etd: row.etd ?? null,\n ata: row.ata ?? null,\n atd: row.atd ?? null,\n beginReceive: row.beginReceive ?? null,\n dryCutoff: row.dryCutoff ?? null,\n rawData: row.rawData ?? null,\n }\n }\n\n return changedRefs\n }\n\n /**\n * Insert new events, and update existing ones whose container fields changed\n * or whose vessel visit data changed this poll. Returns both lists so the\n * caller can emit `.created` / `.updated` accordingly.\n */\n private async persistEvents(\n em: EntityManager,\n job: TerminalTrackingJob,\n fetched: TerminalFetchedEvent[],\n changedVesselRefs: Set<string>,\n ): Promise<{\n created: TerminalEvent[]\n updated: TerminalEvent[]\n currentByKey: Map<string, TerminalEvent>\n priorImpedimentsByKey: Map<string, string[] | null>\n priorStopsByKey: Map<string, TerminalStops>\n }> {\n if (fetched.length === 0) {\n return { created: [], updated: [], currentByKey: new Map(), priorImpedimentsByKey: new Map(), priorStopsByKey: new Map() }\n }\n\n const existing = await em.find(TerminalEvent, {\n job,\n sourceEventId: { $in: fetched.map((e) => e.sourceEventId) },\n })\n const byKey = new Map(existing.map((e) => [`${e.source}:${e.sourceEventId}`, e]))\n // Snapshot each existing event's holds BEFORE the update loop mutates them,\n // so availability detection can see the blocked \u2192 clear transition.\n const priorImpedimentsByKey = new Map<string, string[] | null>(\n existing.map((e) => [e.sourceEventId, e.impediments ?? null]),\n )\n // Same for the STOP flags (derived from rawData), so the availability step\n // can detect a stop-set change and emit `stops_updated`.\n const priorStopsByKey = new Map<string, TerminalStops>(\n existing.map((e) => [e.sourceEventId, parseTerminalStops(e.rawData)]),\n )\n\n const created: TerminalEvent[] = []\n const updated: TerminalEvent[] = []\n const handled = new Set<string>()\n for (const e of fetched) {\n const key = `${e.source}:${e.sourceEventId}`\n if (handled.has(key)) continue\n handled.add(key)\n\n const prior = byKey.get(key)\n if (!prior) {\n const entity = em.create(TerminalEvent, {\n organizationId: job.organizationId,\n tenantId: job.tenantId,\n job,\n source: e.source,\n sourceEventId: e.sourceEventId,\n eventType: e.eventType,\n eventCode: e.eventCode,\n eventClassifierCode: e.eventClassifierCode ?? null,\n eventDateTime: e.eventDateTime,\n containerNumber: e.containerNumber,\n ufvGkey: e.ufvGkey,\n transitState: e.transitState ?? null,\n visitState: e.visitState ?? null,\n facilityCode: e.facilityCode ?? null,\n facilityCodeListProvider: e.facilityCodeListProvider ?? null,\n unlocode: e.unlocode ?? null,\n visitRefIn: e.visitRefIn ?? null,\n visitRefOut: e.visitRefOut ?? null,\n vesselName: e.vesselName ?? null,\n voyageNumber: e.voyageNumber ?? null,\n modeOfTransport: e.modeOfTransport ?? null,\n seals: e.seals ?? null,\n vgmWeightKg: e.vgmWeightKg ?? null,\n impediments: e.impediments ?? null,\n loadedAt: e.loadedAt ?? null,\n rawData: e.rawData ?? null,\n })\n created.push(entity)\n continue\n }\n\n const containerChanged = eventSignature(prior) !== eventSignature(e)\n const vesselChanged = !!e.vesselVisit && changedVesselRefs.has(e.vesselVisit.visitRef)\n // The STOP flags live only in rawData, which eventSignature does not hash,\n // so a stop-only change must be detected separately to persist rawData.\n const stopFlagsChanged = stopsChanged(parseTerminalStops(prior.rawData), parseTerminalStops(e.rawData))\n if (!containerChanged && !vesselChanged && !stopFlagsChanged) continue\n\n if (containerChanged || stopFlagsChanged) {\n prior.eventClassifierCode = e.eventClassifierCode ?? null\n prior.eventDateTime = e.eventDateTime\n prior.transitState = e.transitState ?? null\n prior.visitState = e.visitState ?? null\n prior.facilityCode = e.facilityCode ?? null\n prior.facilityCodeListProvider = e.facilityCodeListProvider ?? null\n prior.unlocode = e.unlocode ?? null\n prior.visitRefIn = e.visitRefIn ?? null\n prior.visitRefOut = e.visitRefOut ?? null\n prior.vesselName = e.vesselName ?? null\n prior.voyageNumber = e.voyageNumber ?? null\n prior.modeOfTransport = e.modeOfTransport ?? null\n prior.seals = e.seals ?? null\n prior.vgmWeightKg = e.vgmWeightKg ?? null\n prior.impediments = e.impediments ?? null\n prior.loadedAt = e.loadedAt ?? null\n prior.rawData = e.rawData ?? null\n }\n updated.push(prior)\n }\n\n if (created.length || updated.length) await em.flush()\n\n // Current persisted entity for every fetched event (existing + created), so\n // availability detection evaluates the freshest snapshot per source event.\n const currentByKey = new Map<string, TerminalEvent>(existing.map((e) => [e.sourceEventId, e]))\n for (const c of created) currentByKey.set(c.sourceEventId, c)\n\n return { created, updated, currentByKey, priorImpedimentsByKey, priorStopsByKey }\n }\n\n private buildEventPayload(event: TerminalEvent, vesselVisit: TerminalFetchedEvent['vesselVisit'] = null) {\n return {\n id: event.id,\n jobId: event.job.id,\n tenantId: event.tenantId,\n organizationId: event.organizationId,\n source: event.source,\n sourceEventId: event.sourceEventId,\n eventType: event.eventType,\n eventCode: event.eventCode,\n eventClassifierCode: event.eventClassifierCode ?? null,\n eventDateTime: event.eventDateTime.toISOString(),\n containerNumber: event.containerNumber,\n ufvGkey: event.ufvGkey,\n transitState: event.transitState ?? null,\n // Direction of travel through the terminal ('import' | 'export' | null).\n // Consumers use it to decide which road leg a gate movement belongs to:\n // an import gates OUT onto the delivery leg, an export gates IN off the\n // pre-carriage leg. Derived from raw data, so it also holds for rows\n // stored before this field existed.\n cargoCategory: parseCargoCategory(event.rawData),\n facilityCode: event.facilityCode ?? null,\n facilityCodeListProvider: event.facilityCodeListProvider ?? null,\n unlocode: event.unlocode ?? null,\n vesselName: event.vesselName ?? null,\n voyageNumber: event.voyageNumber ?? null,\n modeOfTransport: event.modeOfTransport ?? null,\n seals: event.seals ?? null,\n impediments: event.impediments ?? null,\n // When the container was loaded onto transport (N4 `Loaded`), typed datetime.\n loadedAt: event.loadedAt ? event.loadedAt.toISOString() : null,\n // Per-mode load/pickup STOP flags derived from the raw N4 columns.\n stops: parseTerminalStops(event.rawData),\n // Nested vessel visit (from /VESSEL); null when unresolved. Dates ISO.\n vesselVisit: vesselVisit\n ? {\n visitRef: vesselVisit.visitRef,\n vesselName: vesselVisit.vesselName,\n ibVoyage: vesselVisit.ibVoyage,\n obVoyage: vesselVisit.obVoyage,\n line: vesselVisit.line,\n phase: vesselVisit.phase,\n eta: vesselVisit.eta ? vesselVisit.eta.toISOString() : null,\n etd: vesselVisit.etd ? vesselVisit.etd.toISOString() : null,\n ata: vesselVisit.ata ? vesselVisit.ata.toISOString() : null,\n atd: vesselVisit.atd ? vesselVisit.atd.toISOString() : null,\n beginReceive: vesselVisit.beginReceive ? vesselVisit.beginReceive.toISOString() : null,\n dryCutoff: vesselVisit.dryCutoff ? vesselVisit.dryCutoff.toISOString() : null,\n }\n : null,\n rawData: event.rawData ?? null,\n }\n }\n\n /** Emit a newly-created event: the raw `.created` event plus its semantic\n * milestone (gate_in / discharged / \u2026). */\n private async emitCreated(\n event: TerminalEvent,\n vesselVisit: TerminalFetchedEvent['vesselVisit'] = null,\n ): Promise<void> {\n const payload = this.buildEventPayload(event, vesselVisit)\n await this.deps.eventBus.emit('terminal_tracking.terminal_event.created', payload)\n const semanticId = semanticEventIdFor(event.eventCode)\n if (semanticId) await this.deps.eventBus.emit(semanticId, payload)\n }\n\n /** Emit `.updated` when an existing event's container or vessel data changed.\n * Semantic milestone events are NOT re-emitted \u2014 the milestone already fired\n * on creation; only the underlying data moved. */\n private async emitUpdated(\n event: TerminalEvent,\n vesselVisit: TerminalFetchedEvent['vesselVisit'] = null,\n ): Promise<void> {\n const payload = this.buildEventPayload(event, vesselVisit)\n await this.deps.eventBus.emit('terminal_tracking.terminal_event.updated', payload)\n }\n\n /**\n * Evaluate the container's availability after a poll and emit the one-shot\n * milestone when it first becomes collectable:\n * - export empty ready for pickup (`equipment.empty_ready`)\n * - import blocking holds cleared (`equipment.holds_cleared`)\n *\n * The job-level `emptyReadyAt` / `holdsClearedAt` markers guarantee each event\n * fires at most once per container. A job may carry both an import and an\n * export leg, so both are checked independently.\n */\n private async evaluateAndEmitAvailability(\n em: EntityManager,\n job: TerminalTrackingJob,\n currentByKey: Map<string, TerminalEvent>,\n priorImpedimentsByKey: Map<string, string[] | null>,\n priorStopsByKey: Map<string, TerminalStops>,\n vesselByEventId: Map<string, TerminalFetchedEvent['vesselVisit']>,\n ): Promise<void> {\n let emptyEvent: TerminalEvent | null = null\n let holdsEvent: TerminalEvent | null = null\n // Events whose raw impediment set changed since the last poll (incl. first\n // appearance with holds). Unlike the one-shot markers above, every one emits.\n const holdsChangedEvents: TerminalEvent[] = []\n // Events whose STOP flags changed since the last poll (incl. first\n // appearance). Mirrors holdsChangedEvents.\n const stopsChangedEvents: TerminalEvent[] = []\n\n for (const [sourceEventId, entity] of currentByKey) {\n if (!job.emptyReadyAt && !emptyEvent && isEmptyReady(entity)) emptyEvent = entity\n if (\n !job.holdsClearedAt &&\n !holdsEvent &&\n isImportHoldsCleared(entity, priorImpedimentsByKey.get(sourceEventId) ?? null)\n ) {\n holdsEvent = entity\n }\n // `priorImpedimentsByKey` has no entry for events created this poll, so a\n // first-seen container with holds is reported as a change (empty prior).\n if (impedimentsChanged(priorImpedimentsByKey.get(sourceEventId), entity.impediments)) {\n holdsChangedEvents.push(entity)\n }\n // Same for STOP flags: a created event has no prior entry, so an empty\n // prior (all-null) surfaces a first-seen stop set as a change.\n const priorStops = priorStopsByKey.get(sourceEventId) ?? { vsl: null, road: null, rail: null }\n if (stopsChanged(priorStops, parseTerminalStops(entity.rawData))) {\n stopsChangedEvents.push(entity)\n }\n }\n\n if (!emptyEvent && !holdsEvent && holdsChangedEvents.length === 0 && stopsChangedEvents.length === 0) return\n\n // Only the one-shot markers mutate the job; flush just for those.\n if (emptyEvent || holdsEvent) {\n const now = new Date()\n if (emptyEvent) job.emptyReadyAt = now\n if (holdsEvent) job.holdsClearedAt = now\n await em.flush()\n }\n\n if (emptyEvent) {\n await this.deps.eventBus.emit('terminal_tracking.equipment.empty_ready', {\n ...this.buildEventPayload(emptyEvent, vesselByEventId.get(emptyEvent.sourceEventId) ?? null),\n emptyReady: true,\n })\n }\n if (holdsEvent) {\n await this.deps.eventBus.emit('terminal_tracking.equipment.holds_cleared', {\n ...this.buildEventPayload(holdsEvent, vesselByEventId.get(holdsEvent.sourceEventId) ?? null),\n holdsCleared: true,\n })\n }\n for (const entity of holdsChangedEvents) {\n await this.deps.eventBus.emit('terminal_tracking.equipment.holds_updated', {\n ...this.buildEventPayload(entity, vesselByEventId.get(entity.sourceEventId) ?? null),\n impediments: entity.impediments ?? [],\n })\n }\n for (const entity of stopsChangedEvents) {\n await this.deps.eventBus.emit('terminal_tracking.equipment.stops_updated', {\n ...this.buildEventPayload(entity, vesselByEventId.get(entity.sourceEventId) ?? null),\n stops: parseTerminalStops(entity.rawData),\n })\n }\n }\n\n private computeNextPoll(schedule?: string[] | null): Date | null {\n if (!schedule || schedule.length === 0) return null\n const now = Date.now()\n const upcoming = schedule\n .map((s) => new Date(s).getTime())\n .filter((t) => Number.isFinite(t) && t > now)\n .sort((a, b) => a - b)\n return upcoming.length ? new Date(upcoming[0]) : null\n }\n\n private async recordJobError(em: EntityManager, job: TerminalTrackingJob, err: unknown): Promise<void> {\n const message = err instanceof Error ? err.message : String(err)\n terminalLogger.warn('Poll failed', { jobId: job.id, terminalCode: job.terminalCode, message })\n\n const history = job.errorHistory ?? []\n history.unshift({ date: new Date().toISOString(), message })\n job.errorHistory = history.slice(0, MAX_ERROR_HISTORY)\n job.retryCount = (job.retryCount ?? 0) + 1\n job.lastPollAt = new Date()\n if (job.retryCount >= MAX_RETRIES) job.status = 'failed'\n\n try {\n await em.flush()\n } catch (flushErr) {\n terminalLogger.error('Failed to persist job error', {\n jobId: job.id,\n message: flushErr instanceof Error ? flushErr.message : String(flushErr),\n })\n }\n\n await this.deps.eventBus.emit('terminal_tracking.tracking_job.poll_failed', {\n id: job.id,\n terminalCode: job.terminalCode,\n containerNumber: job.containerNumber,\n tenantId: job.tenantId,\n organizationId: job.organizationId,\n message,\n retryCount: job.retryCount,\n })\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["newEvents", "updatedEvents", "failed"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ensureTerminalPollSchedule } from "./lib/ensure-schedule.js";
|
|
2
|
+
const setup = {
|
|
3
|
+
defaultRoleFeatures: {
|
|
4
|
+
admin: ["terminal_tracking.*"],
|
|
5
|
+
employee: [
|
|
6
|
+
"terminal_tracking.terminal_configs.view",
|
|
7
|
+
"terminal_tracking.tracking_jobs.view",
|
|
8
|
+
"terminal_tracking.events.view"
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
seedDefaults: async (ctx) => {
|
|
12
|
+
await ensureTerminalPollSchedule(ctx.container, {
|
|
13
|
+
organizationId: ctx.organizationId,
|
|
14
|
+
tenantId: ctx.tenantId
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
var setup_default = setup;
|
|
19
|
+
export {
|
|
20
|
+
setup_default as default,
|
|
21
|
+
setup
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/modules/terminal_tracking/setup.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'\nimport { ensureTerminalPollSchedule } from './lib/ensure-schedule'\n\nexport const setup: ModuleSetupConfig = {\n defaultRoleFeatures: {\n admin: ['terminal_tracking.*'],\n employee: [\n 'terminal_tracking.terminal_configs.view',\n 'terminal_tracking.tracking_jobs.view',\n 'terminal_tracking.events.view',\n ],\n },\n\n seedDefaults: async (ctx) => {\n // Register the periodic poll schedule at org onboarding. Config/job saves\n // re-ensure it for orgs that predate the module (see ensureTerminalPollSchedule).\n await ensureTerminalPollSchedule(ctx.container, {\n organizationId: ctx.organizationId,\n tenantId: ctx.tenantId,\n })\n },\n}\n\nexport default setup\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,kCAAkC;AAEpC,MAAM,QAA2B;AAAA,EACtC,qBAAqB;AAAA,IACnB,OAAO,CAAC,qBAAqB;AAAA,IAC7B,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,cAAc,OAAO,QAAQ;AAG3B,UAAM,2BAA2B,IAAI,WAAW;AAAA,MAC9C,gBAAgB,IAAI;AAAA,MACpB,UAAU,IAAI;AAAA,IAChB,CAAC;AAAA,EACH;AACF;AAEA,IAAO,gBAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { terminalLogger } from "../lib/logger.js";
|
|
2
|
+
const metadata = {
|
|
3
|
+
event: "terminal_tracking.tracking_job.created",
|
|
4
|
+
persistent: false,
|
|
5
|
+
id: "terminal_tracking:tracking-job-initial-poll"
|
|
6
|
+
};
|
|
7
|
+
async function handle(payload, ctx) {
|
|
8
|
+
const service = ctx.resolve("terminalTrackingService");
|
|
9
|
+
try {
|
|
10
|
+
await service.pollJob(payload.id);
|
|
11
|
+
} catch (err) {
|
|
12
|
+
terminalLogger.error("Initial poll failed", {
|
|
13
|
+
jobId: payload.id,
|
|
14
|
+
message: err instanceof Error ? err.message : String(err)
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export {
|
|
19
|
+
handle as default,
|
|
20
|
+
metadata
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=terminal-tracking-job-initial-poll.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/terminal_tracking/subscribers/terminal-tracking-job-initial-poll.ts"],
|
|
4
|
+
"sourcesContent": ["import type { TerminalTrackingService } from '../services/terminalTrackingService'\nimport { terminalLogger } from '../lib/logger'\n\nexport const metadata = {\n event: 'terminal_tracking.tracking_job.created',\n persistent: false,\n id: 'terminal_tracking:tracking-job-initial-poll',\n}\n\ntype Payload = {\n id: string\n terminalCode: string\n containerNumber: string\n tenantId: string\n organizationId: string\n}\n\ntype ResolverContext = { resolve: <T = unknown>(name: string) => T }\n\nexport default async function handle(payload: Payload, ctx: ResolverContext) {\n // createJob already polls inline; this subscriber is a safety net for jobs\n // created through other paths. It is idempotent thanks to event dedup.\n const service = ctx.resolve<TerminalTrackingService>('terminalTrackingService')\n try {\n await service.pollJob(payload.id)\n } catch (err) {\n terminalLogger.error('Initial poll failed', {\n jobId: payload.id,\n message: err instanceof Error ? err.message : String(err),\n })\n }\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,sBAAsB;AAExB,MAAM,WAAW;AAAA,EACtB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,IAAI;AACN;AAYA,eAAO,OAA8B,SAAkB,KAAsB;AAG3E,QAAM,UAAU,IAAI,QAAiC,yBAAyB;AAC9E,MAAI;AACF,UAAM,QAAQ,QAAQ,QAAQ,EAAE;AAAA,EAClC,SAAS,KAAK;AACZ,mBAAe,MAAM,uBAAuB;AAAA,MAC1C,OAAO,QAAQ;AAAA,MACf,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,IAC1D,CAAC;AAAA,EACH;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// AUTO-GENERATED by scripts/generate-entity-fields.mjs (HEDGE-168). Do not edit.
|
|
2
|
+
//
|
|
3
|
+
// One file per entity, named `toSnake(ClassName)` — NOT the table name. Each
|
|
4
|
+
// export is a database column name, mirroring what `mercato generate` writes
|
|
5
|
+
// for `@open-mercato/*` packages. `loadStandaloneGeneratedPackageEntities()`
|
|
6
|
+
// reads these back to build the host app's `entity-fields-registry.ts`, which
|
|
7
|
+
// is what the Encryption and Translation managers list fields from.
|
|
8
|
+
//
|
|
9
|
+
// Regenerate with `node scripts/generate-entity-fields.mjs --all`; the package
|
|
10
|
+
// build does it for you. Guarded by
|
|
11
|
+
// `apps/web/src/__tests__/entity-fields-registry-contract.test.ts`.
|
|
12
|
+
export const id = "id";
|
|
13
|
+
export const organization_id = "organization_id";
|
|
14
|
+
export const tenant_id = "tenant_id";
|
|
15
|
+
export const terminal_code = "terminal_code";
|
|
16
|
+
export const adapter_type = "adapter_type";
|
|
17
|
+
export const display_name = "display_name";
|
|
18
|
+
export const base_url = "base_url";
|
|
19
|
+
export const proxy_url = "proxy_url";
|
|
20
|
+
export const endpoints = "endpoints";
|
|
21
|
+
export const auth_type = "auth_type";
|
|
22
|
+
export const token_url = "token_url";
|
|
23
|
+
export const scope = "scope";
|
|
24
|
+
export const client_id = "client_id";
|
|
25
|
+
export const auth_config = "auth_config";
|
|
26
|
+
export const rate_limit_requests = "rate_limit_requests";
|
|
27
|
+
export const rate_limit_window_seconds = "rate_limit_window_seconds";
|
|
28
|
+
export const vessel_cache_ttl_seconds = "vessel_cache_ttl_seconds";
|
|
29
|
+
export const unlocode = "unlocode";
|
|
30
|
+
export const bic_codes = "bic_codes";
|
|
31
|
+
export const smdg_codes = "smdg_codes";
|
|
32
|
+
export const name_aliases = "name_aliases";
|
|
33
|
+
export const is_active = "is_active";
|
|
34
|
+
export const created_at = "created_at";
|
|
35
|
+
export const updated_at = "updated_at";
|
|
36
|
+
export const deleted_at = "deleted_at";
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// AUTO-GENERATED by scripts/generate-entity-fields.mjs (HEDGE-168). Do not edit.
|
|
2
|
+
//
|
|
3
|
+
// One file per entity, named `toSnake(ClassName)` — NOT the table name. Each
|
|
4
|
+
// export is a database column name, mirroring what `mercato generate` writes
|
|
5
|
+
// for `@open-mercato/*` packages. `loadStandaloneGeneratedPackageEntities()`
|
|
6
|
+
// reads these back to build the host app's `entity-fields-registry.ts`, which
|
|
7
|
+
// is what the Encryption and Translation managers list fields from.
|
|
8
|
+
//
|
|
9
|
+
// Regenerate with `node scripts/generate-entity-fields.mjs --all`; the package
|
|
10
|
+
// build does it for you. Guarded by
|
|
11
|
+
// `apps/web/src/__tests__/entity-fields-registry-contract.test.ts`.
|
|
12
|
+
export const id = "id";
|
|
13
|
+
export const organization_id = "organization_id";
|
|
14
|
+
export const tenant_id = "tenant_id";
|
|
15
|
+
export const job = "job";
|
|
16
|
+
export const source = "source";
|
|
17
|
+
export const source_event_id = "source_event_id";
|
|
18
|
+
export const event_type = "event_type";
|
|
19
|
+
export const event_code = "event_code";
|
|
20
|
+
export const event_classifier_code = "event_classifier_code";
|
|
21
|
+
export const event_date_time = "event_date_time";
|
|
22
|
+
export const container_number = "container_number";
|
|
23
|
+
export const ufv_gkey = "ufv_gkey";
|
|
24
|
+
export const transit_state = "transit_state";
|
|
25
|
+
export const visit_state = "visit_state";
|
|
26
|
+
export const facility_code = "facility_code";
|
|
27
|
+
export const facility_code_list_provider = "facility_code_list_provider";
|
|
28
|
+
export const unlocode = "unlocode";
|
|
29
|
+
export const visit_ref_in = "visit_ref_in";
|
|
30
|
+
export const visit_ref_out = "visit_ref_out";
|
|
31
|
+
export const vessel_name = "vessel_name";
|
|
32
|
+
export const voyage_number = "voyage_number";
|
|
33
|
+
export const mode_of_transport = "mode_of_transport";
|
|
34
|
+
export const seals = "seals";
|
|
35
|
+
export const vgm_weight_kg = "vgm_weight_kg";
|
|
36
|
+
export const impediments = "impediments";
|
|
37
|
+
export const loaded_at = "loaded_at";
|
|
38
|
+
export const raw_data = "raw_data";
|
|
39
|
+
export const created_at = "created_at";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// AUTO-GENERATED by scripts/generate-entity-fields.mjs (HEDGE-168). Do not edit.
|
|
2
|
+
//
|
|
3
|
+
// One file per entity, named `toSnake(ClassName)` — NOT the table name. Each
|
|
4
|
+
// export is a database column name, mirroring what `mercato generate` writes
|
|
5
|
+
// for `@open-mercato/*` packages. `loadStandaloneGeneratedPackageEntities()`
|
|
6
|
+
// reads these back to build the host app's `entity-fields-registry.ts`, which
|
|
7
|
+
// is what the Encryption and Translation managers list fields from.
|
|
8
|
+
//
|
|
9
|
+
// Regenerate with `node scripts/generate-entity-fields.mjs --all`; the package
|
|
10
|
+
// build does it for you. Guarded by
|
|
11
|
+
// `apps/web/src/__tests__/entity-fields-registry-contract.test.ts`.
|
|
12
|
+
export const id = "id";
|
|
13
|
+
export const organization_id = "organization_id";
|
|
14
|
+
export const tenant_id = "tenant_id";
|
|
15
|
+
export const terminal_code = "terminal_code";
|
|
16
|
+
export const container_number = "container_number";
|
|
17
|
+
export const status = "status";
|
|
18
|
+
export const schedule = "schedule";
|
|
19
|
+
export const next_poll_at = "next_poll_at";
|
|
20
|
+
export const last_poll_at = "last_poll_at";
|
|
21
|
+
export const retry_count = "retry_count";
|
|
22
|
+
export const error_history = "error_history";
|
|
23
|
+
export const empty_ready_at = "empty_ready_at";
|
|
24
|
+
export const holds_cleared_at = "holds_cleared_at";
|
|
25
|
+
export const created_at = "created_at";
|
|
26
|
+
export const updated_at = "updated_at";
|
|
27
|
+
export const deleted_at = "deleted_at";
|
|
28
|
+
export const events = "events";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// AUTO-GENERATED by scripts/generate-entity-fields.mjs (HEDGE-168). Do not edit.
|
|
2
|
+
//
|
|
3
|
+
// One file per entity, named `toSnake(ClassName)` — NOT the table name. Each
|
|
4
|
+
// export is a database column name, mirroring what `mercato generate` writes
|
|
5
|
+
// for `@open-mercato/*` packages. `loadStandaloneGeneratedPackageEntities()`
|
|
6
|
+
// reads these back to build the host app's `entity-fields-registry.ts`, which
|
|
7
|
+
// is what the Encryption and Translation managers list fields from.
|
|
8
|
+
//
|
|
9
|
+
// Regenerate with `node scripts/generate-entity-fields.mjs --all`; the package
|
|
10
|
+
// build does it for you. Guarded by
|
|
11
|
+
// `apps/web/src/__tests__/entity-fields-registry-contract.test.ts`.
|
|
12
|
+
export const id = "id";
|
|
13
|
+
export const organization_id = "organization_id";
|
|
14
|
+
export const tenant_id = "tenant_id";
|
|
15
|
+
export const terminal_code = "terminal_code";
|
|
16
|
+
export const visit_ref = "visit_ref";
|
|
17
|
+
export const vessel_name = "vessel_name";
|
|
18
|
+
export const ib_voyage = "ib_voyage";
|
|
19
|
+
export const ob_voyage = "ob_voyage";
|
|
20
|
+
export const line = "line";
|
|
21
|
+
export const phase = "phase";
|
|
22
|
+
export const eta = "eta";
|
|
23
|
+
export const etd = "etd";
|
|
24
|
+
export const ata = "ata";
|
|
25
|
+
export const atd = "atd";
|
|
26
|
+
export const begin_receive = "begin_receive";
|
|
27
|
+
export const dry_cutoff = "dry_cutoff";
|
|
28
|
+
export const raw_data = "raw_data";
|
|
29
|
+
export const created_at = "created_at";
|
|
30
|
+
export const updated_at = "updated_at";
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Entity ids for `@freighttech/terminal-tracking` — HAND-MAINTAINED and AUTHORITATIVE.
|
|
2
|
+
//
|
|
3
|
+
// This header used to say "local typecheck stub", owned at runtime by apps/web.
|
|
4
|
+
// Both halves were false, and HEDGE-164 is what that cost. Nothing regenerates
|
|
5
|
+
// this file — `yarn generate` rewrites `generated/` only for modules declared
|
|
6
|
+
// `from: '@app'`. And it is not merely a typecheck aid: because
|
|
7
|
+
// `@open-mercato/core` installs as a real directory rather than a workspace
|
|
8
|
+
// symlink, `@open-mercato/cli` runs in STANDALONE mode, where
|
|
9
|
+
// `loadStandaloneGeneratedPackageEntities()` reads THIS file as the package's
|
|
10
|
+
// entity list and uses it INSTEAD of parsing `data/entities.ts`.
|
|
11
|
+
//
|
|
12
|
+
// An entity missing here is therefore missing from the host app's `E`, and `E`
|
|
13
|
+
// is a runtime registry rather than a bag of types: it backs the custom-field
|
|
14
|
+
// entity picker, query-index coverage and reindex, attachment assignment,
|
|
15
|
+
// dashboard widget sources and encryption field resolution. The entity's table,
|
|
16
|
+
// migrations and repositories keep working the whole time, which is why the
|
|
17
|
+
// failure reads as "the feature was never built" instead of as an error.
|
|
18
|
+
//
|
|
19
|
+
// One key per exported class in `data/entities.ts`, keyed `toSnake(ClassName)`
|
|
20
|
+
// with NO pluralisation — `InvoiceOrderLine` is `invoice_order_line`, never the
|
|
21
|
+
// `tableName`. Namespaces for OTHER modules are typecheck-only; the generator
|
|
22
|
+
// reads `E['<this module id>']` and ignores the rest.
|
|
23
|
+
//
|
|
24
|
+
// The mechanism in full, and the guard that fails the gate when this file and
|
|
25
|
+
// `data/entities.ts` drift apart:
|
|
26
|
+
// `apps/web/src/__tests__/entity-ids-registry-contract.test.ts`.
|
|
27
|
+
|
|
28
|
+
export const M = {
|
|
29
|
+
terminal_tracking: 'terminal_tracking',
|
|
30
|
+
} as const
|
|
31
|
+
|
|
32
|
+
export const E = {
|
|
33
|
+
terminal_tracking: {
|
|
34
|
+
terminal_config: 'terminal_tracking:terminal_config',
|
|
35
|
+
// HEDGE-164: `tracking_job` matches NO class in `data/entities.ts`. The
|
|
36
|
+
// real entity is `TerminalTrackingJob` -> `terminal_tracking_job`, added
|
|
37
|
+
// below; this key is a hand-written guess that has been reaching the host
|
|
38
|
+
// registry as a phantom id ever since. It is left in place rather than
|
|
39
|
+
// deleted because `ce.ts` registers a custom-entity record against
|
|
40
|
+
// `terminal_tracking:tracking_job`, and removing the id could orphan
|
|
41
|
+
// custom-field definitions already configured against it in a customer
|
|
42
|
+
// database. Removal needs a production check of `custom_field_def` /
|
|
43
|
+
// `custom_field_value` first and is tracked separately from this fix.
|
|
44
|
+
tracking_job: 'terminal_tracking:tracking_job',
|
|
45
|
+
terminal_event: 'terminal_tracking:terminal_event',
|
|
46
|
+
terminal_tracking_job: 'terminal_tracking:terminal_tracking_job',
|
|
47
|
+
terminal_vessel_visit: 'terminal_tracking:terminal_vessel_visit',
|
|
48
|
+
},
|
|
49
|
+
} as const
|
|
50
|
+
|
|
51
|
+
export type KnownModuleId = keyof typeof M
|
|
52
|
+
export type KnownEntities = typeof E
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@freighttech/terminal-tracking",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"src",
|
|
12
|
+
"generated",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"main": "./dist/index.js",
|
|
17
|
+
"types": "./src/index.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./src/index.ts",
|
|
21
|
+
"default": "./dist/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./modules/terminal_tracking/*": {
|
|
24
|
+
"types": [
|
|
25
|
+
"./src/modules/terminal_tracking/*.ts",
|
|
26
|
+
"./src/modules/terminal_tracking/*.tsx"
|
|
27
|
+
],
|
|
28
|
+
"default": "./dist/modules/terminal_tracking/*.js"
|
|
29
|
+
},
|
|
30
|
+
"./modules/terminal_tracking/*.json": "./src/modules/terminal_tracking/*.json"
|
|
31
|
+
},
|
|
32
|
+
"imports": {
|
|
33
|
+
"#generated/entities.ids.generated": {
|
|
34
|
+
"types": "./generated/entities.ids.generated.ts",
|
|
35
|
+
"default": "./generated/entities.ids.generated.ts"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"fuse.js": "^7.1.0",
|
|
40
|
+
"undici": "^7.25.0"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"@freighttech/ui": "^0.13.0",
|
|
44
|
+
"@mikro-orm/core": "^7.0.17",
|
|
45
|
+
"@mikro-orm/decorators": "^7.0.17",
|
|
46
|
+
"@mikro-orm/migrations": "^7.0.17",
|
|
47
|
+
"@mikro-orm/postgresql": "^7.0.17",
|
|
48
|
+
"@open-mercato/cache": "0.6.2",
|
|
49
|
+
"@open-mercato/core": "0.6.2",
|
|
50
|
+
"@open-mercato/events": "0.6.2",
|
|
51
|
+
"@open-mercato/queue": "0.6.2",
|
|
52
|
+
"@open-mercato/scheduler": "0.6.2",
|
|
53
|
+
"@open-mercato/shared": "0.6.2",
|
|
54
|
+
"@tanstack/react-query": "^5.90.12",
|
|
55
|
+
"awilix": "^12.0.5",
|
|
56
|
+
"lucide-react": "^0.556.0",
|
|
57
|
+
"next": "^16.2.3",
|
|
58
|
+
"react": "^19.0.0",
|
|
59
|
+
"react-dom": "^19.0.0",
|
|
60
|
+
"zod": "^4.0.0"
|
|
61
|
+
},
|
|
62
|
+
"peerDependenciesMeta": {
|
|
63
|
+
"@open-mercato/scheduler": {
|
|
64
|
+
"optional": true
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"@types/node": "^24.10.1",
|
|
69
|
+
"@types/react": "^19.2.7",
|
|
70
|
+
"@types/react-dom": "^19.2.3",
|
|
71
|
+
"typescript": "^5.9.3"
|
|
72
|
+
},
|
|
73
|
+
"scripts": {
|
|
74
|
+
"typecheck": "tsc --noEmit",
|
|
75
|
+
"test": "npx --no-install vitest run",
|
|
76
|
+
"test:watch": "npx --no-install vitest",
|
|
77
|
+
"build": "node ../../scripts/build-package.mjs",
|
|
78
|
+
"prepack": "node ../../scripts/build-package.mjs"
|
|
79
|
+
}
|
|
80
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @freighttech/terminal-tracking
|
|
3
|
+
*
|
|
4
|
+
* Container terminal (Navis N4) tracking: per-terminal config, periodic
|
|
5
|
+
* polling, container event emission, and terminal matching.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Entities
|
|
9
|
+
export {
|
|
10
|
+
TerminalConfig,
|
|
11
|
+
TerminalTrackingJob,
|
|
12
|
+
TerminalEvent,
|
|
13
|
+
} from './modules/terminal_tracking/data/entities'
|
|
14
|
+
|
|
15
|
+
// Types
|
|
16
|
+
export type {
|
|
17
|
+
TerminalEventType,
|
|
18
|
+
TerminalEventClassifierCode,
|
|
19
|
+
TerminalEventSource,
|
|
20
|
+
FacilityCodeProvider,
|
|
21
|
+
TerminalModeOfTransport,
|
|
22
|
+
TerminalAuthType,
|
|
23
|
+
TerminalTrackingJobStatus,
|
|
24
|
+
TerminalEndpoints,
|
|
25
|
+
TerminalSealInfo,
|
|
26
|
+
} from './modules/terminal_tracking/data/entities'
|
|
27
|
+
|
|
28
|
+
// Adapter contract
|
|
29
|
+
export type {
|
|
30
|
+
TerminalAdapter,
|
|
31
|
+
TerminalFetchedEvent,
|
|
32
|
+
TerminalFetchResult,
|
|
33
|
+
TerminalAdapterTestResult,
|
|
34
|
+
ResolvedTerminalConfig,
|
|
35
|
+
} from './modules/terminal_tracking/lib/terminal-adapter'
|
|
36
|
+
|
|
37
|
+
// Services
|
|
38
|
+
export { TerminalRegistry } from './modules/terminal_tracking/services/terminalRegistry'
|
|
39
|
+
export { TerminalTrackingService } from './modules/terminal_tracking/services/terminalTrackingService'
|
|
40
|
+
export { TerminalMatcherService } from './modules/terminal_tracking/services/terminalMatcherService'
|
|
41
|
+
|
|
42
|
+
// Events
|
|
43
|
+
export { eventsConfig, emitTerminalTrackingEvent } from './modules/terminal_tracking/events'
|
|
44
|
+
export type { TerminalTrackingEventId } from './modules/terminal_tracking/events'
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
import {
|
|
3
|
+
login,
|
|
4
|
+
createTerminalConfig,
|
|
5
|
+
testTerminalConfig,
|
|
6
|
+
deleteTerminalConfig,
|
|
7
|
+
type AuthSession,
|
|
8
|
+
} from './helpers'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* TC-TRACK-301 — GCT (Gdynia) adapter wiring, exercised through the real host app.
|
|
12
|
+
*
|
|
13
|
+
* These do NOT hit a live GCT terminal — test-connection fails deterministically
|
|
14
|
+
* (unreachable host / missing credentials). What they prove is that the Phase-1
|
|
15
|
+
* additions are wired end-to-end through the app:
|
|
16
|
+
* - the `gct_token` auth type passes create validation (validator change),
|
|
17
|
+
* - the `gct` adapter is registered and resolved by adapterType (registry) —
|
|
18
|
+
* the failure is a real adapter result, never "no adapter for type",
|
|
19
|
+
* - test-connection returns a STRUCTURED { success:false } payload, not a 500.
|
|
20
|
+
*
|
|
21
|
+
* Note: the exact failure message depends on the environment. Where tenant-data
|
|
22
|
+
* decryption of `auth_config` is active, an unreachable host yields a transport
|
|
23
|
+
* error; where it is not (and creds resolve empty), the adapter's credential
|
|
24
|
+
* guard fires first. Both are structured failures — the assertions below hold
|
|
25
|
+
* either way and deliberately do not pin the message text of the first case.
|
|
26
|
+
*/
|
|
27
|
+
test.describe('TC-TRACK-301: GCT terminal-config test connection', () => {
|
|
28
|
+
let session: AuthSession
|
|
29
|
+
const createdIds: string[] = []
|
|
30
|
+
|
|
31
|
+
test.beforeAll(async ({ request }) => {
|
|
32
|
+
session = await login(request)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test.afterEach(async ({ request }) => {
|
|
36
|
+
while (createdIds.length) {
|
|
37
|
+
await deleteTerminalConfig(request, session, createdIds.pop() as string)
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('accepts a gct_token config and reports a structured connection failure', async ({ request }) => {
|
|
42
|
+
const { id } = await createTerminalConfig(request, session)
|
|
43
|
+
createdIds.push(id)
|
|
44
|
+
expect(id).toBeTruthy()
|
|
45
|
+
|
|
46
|
+
const { status, body } = await testTerminalConfig(request, session, id)
|
|
47
|
+
// A structured 200 response — the adapter resolved and failed gracefully.
|
|
48
|
+
expect(status).toBe(200)
|
|
49
|
+
expect(body.success).toBe(false)
|
|
50
|
+
expect(typeof body.message).toBe('string')
|
|
51
|
+
expect(body.message).toBeTruthy()
|
|
52
|
+
// Registration proof: it is not the generic "no adapter for type" error.
|
|
53
|
+
expect(body.message?.toLowerCase()).not.toContain('no adapter')
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('surfaces missing GCT credentials as a clear failure message', async ({ request }) => {
|
|
57
|
+
const { id } = await createTerminalConfig(request, session, { authConfig: {} })
|
|
58
|
+
createdIds.push(id)
|
|
59
|
+
|
|
60
|
+
const { status, body } = await testTerminalConfig(request, session, id)
|
|
61
|
+
expect(status).toBe(200)
|
|
62
|
+
expect(body.success).toBe(false)
|
|
63
|
+
// The adapter's credential guard names the missing field.
|
|
64
|
+
expect(body.message).toContain('companyCode')
|
|
65
|
+
})
|
|
66
|
+
})
|