@freighttech/shipment-tracking 0.6.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/NOTICE +25 -0
- package/dist/generated/entities/bic_config/index.js +21 -0
- package/dist/generated/entities/bic_config/index.js.map +7 -0
- package/dist/generated/entities/carrier_config/index.js +27 -0
- package/dist/generated/entities/carrier_config/index.js.map +7 -0
- package/dist/generated/entities/location_override/index.js +33 -0
- package/dist/generated/entities/location_override/index.js.map +7 -0
- package/dist/generated/entities/shipment/index.js +73 -0
- package/dist/generated/entities/shipment/index.js.map +7 -0
- package/dist/generated/entities/ships_go_config/index.js +31 -0
- package/dist/generated/entities/ships_go_config/index.js.map +7 -0
- package/dist/generated/entities/tracking_event/index.js +101 -0
- package/dist/generated/entities/tracking_event/index.js.map +7 -0
- package/dist/generated/entities/tracking_job/index.js +47 -0
- package/dist/generated/entities/tracking_job/index.js.map +7 -0
- package/dist/generated/entities/webhook/index.js +25 -0
- package/dist/generated/entities/webhook/index.js.map +7 -0
- package/dist/generated/entities/webhook_delivery/index.js +25 -0
- package/dist/generated/entities/webhook_delivery/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +21 -0
- package/dist/generated/entities.ids.generated.js.map +7 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +7 -0
- package/dist/modules/shipment_tracking/acl.js +22 -0
- package/dist/modules/shipment_tracking/acl.js.map +7 -0
- package/dist/modules/shipment_tracking/api/bic-configs/route.js +176 -0
- package/dist/modules/shipment_tracking/api/bic-configs/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/bic-configs/test/route.js +95 -0
- package/dist/modules/shipment_tracking/api/bic-configs/test/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/cargo-events/route.js +93 -0
- package/dist/modules/shipment_tracking/api/cargo-events/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/carrier-configs/route.js +139 -0
- package/dist/modules/shipment_tracking/api/carrier-configs/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/dashboard/ships-map/route.js +177 -0
- package/dist/modules/shipment_tracking/api/dashboard/ships-map/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/location-overrides/route.js +179 -0
- package/dist/modules/shipment_tracking/api/location-overrides/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/openapi.js +27 -0
- package/dist/modules/shipment_tracking/api/openapi.js.map +7 -0
- package/dist/modules/shipment_tracking/api/shipments/route.js +192 -0
- package/dist/modules/shipment_tracking/api/shipments/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/shipsgo-configs/route.js +185 -0
- package/dist/modules/shipment_tracking/api/shipsgo-configs/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/tracking-jobs/route.js +144 -0
- package/dist/modules/shipment_tracking/api/tracking-jobs/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/tracking-jobs/sync/route.js +107 -0
- package/dist/modules/shipment_tracking/api/tracking-jobs/sync/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/utils.js +12 -0
- package/dist/modules/shipment_tracking/api/utils.js.map +7 -0
- package/dist/modules/shipment_tracking/api/webhooks/deliveries/route.js +76 -0
- package/dist/modules/shipment_tracking/api/webhooks/deliveries/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/webhooks/route.js +135 -0
- package/dist/modules/shipment_tracking/api/webhooks/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/webhooks/test/route.js +71 -0
- package/dist/modules/shipment_tracking/api/webhooks/test/route.js.map +7 -0
- package/dist/modules/shipment_tracking/assets/carriers/cma_cgm.png +0 -0
- package/dist/modules/shipment_tracking/assets/carriers/cosco.png +0 -0
- package/dist/modules/shipment_tracking/assets/carriers/evergreen.png +0 -0
- package/dist/modules/shipment_tracking/assets/carriers/hapag_lloyd.png +0 -0
- package/dist/modules/shipment_tracking/assets/carriers/maersk.png +0 -0
- package/dist/modules/shipment_tracking/assets/carriers/msc.png +0 -0
- package/dist/modules/shipment_tracking/assets/carriers/one.png +0 -0
- package/dist/modules/shipment_tracking/assets/carriers/oocl.png +0 -0
- package/dist/modules/shipment_tracking/assets/carriers/yang_ming.png +0 -0
- package/dist/modules/shipment_tracking/assets/carriers/zim.png +0 -0
- package/dist/modules/shipment_tracking/assets/index.js +52 -0
- package/dist/modules/shipment_tracking/assets/index.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/carrier-configs/page.js +621 -0
- package/dist/modules/shipment_tracking/backend/carrier-configs/page.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/carrier-configs/page.meta.js +19 -0
- package/dist/modules/shipment_tracking/backend/carrier-configs/page.meta.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/location-overrides/page.js +198 -0
- package/dist/modules/shipment_tracking/backend/location-overrides/page.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/location-overrides/page.meta.js +19 -0
- package/dist/modules/shipment_tracking/backend/location-overrides/page.meta.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/shipment-tracking/page.js +279 -0
- package/dist/modules/shipment_tracking/backend/shipment-tracking/page.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/shipment-tracking/page.meta.js +19 -0
- package/dist/modules/shipment_tracking/backend/shipment-tracking/page.meta.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/tracking-jobs/page.js +10 -0
- package/dist/modules/shipment_tracking/backend/tracking-jobs/page.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/tracking-jobs/page.meta.js +19 -0
- package/dist/modules/shipment_tracking/backend/tracking-jobs/page.meta.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/webhooks/page.js +198 -0
- package/dist/modules/shipment_tracking/backend/webhooks/page.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/webhooks/page.meta.js +19 -0
- package/dist/modules/shipment_tracking/backend/webhooks/page.meta.js.map +7 -0
- package/dist/modules/shipment_tracking/ce.js +52 -0
- package/dist/modules/shipment_tracking/ce.js.map +7 -0
- package/dist/modules/shipment_tracking/cli.js +229 -0
- package/dist/modules/shipment_tracking/cli.js.map +7 -0
- package/dist/modules/shipment_tracking/commands/bic-configs.js +66 -0
- package/dist/modules/shipment_tracking/commands/bic-configs.js.map +7 -0
- package/dist/modules/shipment_tracking/commands/carrier-configs.js +65 -0
- package/dist/modules/shipment_tracking/commands/carrier-configs.js.map +7 -0
- package/dist/modules/shipment_tracking/commands/index.js +9 -0
- package/dist/modules/shipment_tracking/commands/index.js.map +7 -0
- package/dist/modules/shipment_tracking/commands/location-overrides.js +93 -0
- package/dist/modules/shipment_tracking/commands/location-overrides.js.map +7 -0
- package/dist/modules/shipment_tracking/commands/shipments.js +200 -0
- package/dist/modules/shipment_tracking/commands/shipments.js.map +7 -0
- package/dist/modules/shipment_tracking/commands/shipsgo-configs.js +75 -0
- package/dist/modules/shipment_tracking/commands/shipsgo-configs.js.map +7 -0
- package/dist/modules/shipment_tracking/commands/tracking-jobs.js +120 -0
- package/dist/modules/shipment_tracking/commands/tracking-jobs.js.map +7 -0
- package/dist/modules/shipment_tracking/commands/tracking.js +140 -0
- package/dist/modules/shipment_tracking/commands/tracking.js.map +7 -0
- package/dist/modules/shipment_tracking/commands/webhooks.js +115 -0
- package/dist/modules/shipment_tracking/commands/webhooks.js.map +7 -0
- package/dist/modules/shipment_tracking/components/CombinedTimestampCell.js +168 -0
- package/dist/modules/shipment_tracking/components/CombinedTimestampCell.js.map +7 -0
- package/dist/modules/shipment_tracking/components/FacilityLocationInput.js +202 -0
- package/dist/modules/shipment_tracking/components/FacilityLocationInput.js.map +7 -0
- package/dist/modules/shipment_tracking/components/LocationOverrideDrawer.js +501 -0
- package/dist/modules/shipment_tracking/components/LocationOverrideDrawer.js.map +7 -0
- package/dist/modules/shipment_tracking/components/ShipmentDetailsDrawer.js +818 -0
- package/dist/modules/shipment_tracking/components/ShipmentDetailsDrawer.js.map +7 -0
- package/dist/modules/shipment_tracking/components/ShipmentDrawer.js +404 -0
- package/dist/modules/shipment_tracking/components/ShipmentDrawer.js.map +7 -0
- package/dist/modules/shipment_tracking/components/ShipsGoConfigSection.js +200 -0
- package/dist/modules/shipment_tracking/components/ShipsGoConfigSection.js.map +7 -0
- package/dist/modules/shipment_tracking/components/TimestampCell.js +108 -0
- package/dist/modules/shipment_tracking/components/TimestampCell.js.map +7 -0
- package/dist/modules/shipment_tracking/components/TrackingJobDrawer.js +343 -0
- package/dist/modules/shipment_tracking/components/TrackingJobDrawer.js.map +7 -0
- package/dist/modules/shipment_tracking/components/VesselTrackingMap.js +496 -0
- package/dist/modules/shipment_tracking/components/VesselTrackingMap.js.map +7 -0
- package/dist/modules/shipment_tracking/components/WebhookDrawer.js +332 -0
- package/dist/modules/shipment_tracking/components/WebhookDrawer.js.map +7 -0
- package/dist/modules/shipment_tracking/data/entities.js +686 -0
- package/dist/modules/shipment_tracking/data/entities.js.map +7 -0
- package/dist/modules/shipment_tracking/data/validators.js +434 -0
- package/dist/modules/shipment_tracking/data/validators.js.map +7 -0
- package/dist/modules/shipment_tracking/di.js +60 -0
- package/dist/modules/shipment_tracking/di.js.map +7 -0
- package/dist/modules/shipment_tracking/encryption.js +12 -0
- package/dist/modules/shipment_tracking/encryption.js.map +7 -0
- package/dist/modules/shipment_tracking/events.js +87 -0
- package/dist/modules/shipment_tracking/events.js.map +7 -0
- package/dist/modules/shipment_tracking/hooks/useVesselTracking.js +137 -0
- package/dist/modules/shipment_tracking/hooks/useVesselTracking.js.map +7 -0
- package/dist/modules/shipment_tracking/i18n/de.json +327 -0
- package/dist/modules/shipment_tracking/i18n/en.json +327 -0
- package/dist/modules/shipment_tracking/i18n/es.json +327 -0
- package/dist/modules/shipment_tracking/i18n/pl.json +327 -0
- package/dist/modules/shipment_tracking/index.js +15 -0
- package/dist/modules/shipment_tracking/index.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/adapters/cma-cgm.js +66 -0
- package/dist/modules/shipment_tracking/lib/adapters/cma-cgm.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/adapters/cosco-types.js +1 -0
- package/dist/modules/shipment_tracking/lib/adapters/cosco-types.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/adapters/cosco.js +236 -0
- package/dist/modules/shipment_tracking/lib/adapters/cosco.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/adapters/evergreen.js +87 -0
- package/dist/modules/shipment_tracking/lib/adapters/evergreen.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/adapters/hapag-lloyd.js +68 -0
- package/dist/modules/shipment_tracking/lib/adapters/hapag-lloyd.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/adapters/index.js +20 -0
- package/dist/modules/shipment_tracking/lib/adapters/index.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/adapters/maersk.js +84 -0
- package/dist/modules/shipment_tracking/lib/adapters/maersk.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/adapters/msc.js +170 -0
- package/dist/modules/shipment_tracking/lib/adapters/msc.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/adapters/zim.js +85 -0
- package/dist/modules/shipment_tracking/lib/adapters/zim.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/audit-helpers.js +20 -0
- package/dist/modules/shipment_tracking/lib/audit-helpers.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/auth/base64url.js +8 -0
- package/dist/modules/shipment_tracking/lib/auth/base64url.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/auth/oauth-client.js +38 -0
- package/dist/modules/shipment_tracking/lib/auth/oauth-client.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/background.js +15 -0
- package/dist/modules/shipment_tracking/lib/background.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/bic-api-client.js +106 -0
- package/dist/modules/shipment_tracking/lib/bic-api-client.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/carrier-adapter.js +17 -0
- package/dist/modules/shipment_tracking/lib/carrier-adapter.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/current-vessel.js +149 -0
- package/dist/modules/shipment_tracking/lib/current-vessel.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/dcsa-event-mapping.js +124 -0
- package/dist/modules/shipment_tracking/lib/dcsa-event-mapping.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/dcsa-event-parser.js +112 -0
- package/dist/modules/shipment_tracking/lib/dcsa-event-parser.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/dcsa-params.js +16 -0
- package/dist/modules/shipment_tracking/lib/dcsa-params.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/empty-return.js +48 -0
- package/dist/modules/shipment_tracking/lib/empty-return.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/location-overrides.js +85 -0
- package/dist/modules/shipment_tracking/lib/location-overrides.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/location-types.js +59 -0
- package/dist/modules/shipment_tracking/lib/location-types.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/logger.js +113 -0
- package/dist/modules/shipment_tracking/lib/logger.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/poi-event-processor.js +183 -0
- package/dist/modules/shipment_tracking/lib/poi-event-processor.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/poi-nats-config.js +52 -0
- package/dist/modules/shipment_tracking/lib/poi-nats-config.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/poi-types.js +43 -0
- package/dist/modules/shipment_tracking/lib/poi-types.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/rate-limiter.js +31 -0
- package/dist/modules/shipment_tracking/lib/rate-limiter.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/route-extraction.js +187 -0
- package/dist/modules/shipment_tracking/lib/route-extraction.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/route-inference.js +106 -0
- package/dist/modules/shipment_tracking/lib/route-inference.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/schedule-generator.js +63 -0
- package/dist/modules/shipment_tracking/lib/schedule-generator.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/shipsgo-client.js +143 -0
- package/dist/modules/shipment_tracking/lib/shipsgo-client.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/shipsgo-mapper.js +204 -0
- package/dist/modules/shipment_tracking/lib/shipsgo-mapper.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/status-machine.js +131 -0
- package/dist/modules/shipment_tracking/lib/status-machine.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/time-extraction.js +41 -0
- package/dist/modules/shipment_tracking/lib/time-extraction.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/timestamp-utils.js +100 -0
- package/dist/modules/shipment_tracking/lib/timestamp-utils.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/vessel-api.js +111 -0
- package/dist/modules/shipment_tracking/lib/vessel-api.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/vessel-cache.js +50 -0
- package/dist/modules/shipment_tracking/lib/vessel-cache.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/vessel-extraction.js +13 -0
- package/dist/modules/shipment_tracking/lib/vessel-extraction.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/webhook-dispatcher.js +62 -0
- package/dist/modules/shipment_tracking/lib/webhook-dispatcher.js.map +7 -0
- package/dist/modules/shipment_tracking/migrations/Migration20260422161208_shipment_tracking.js +48 -0
- package/dist/modules/shipment_tracking/migrations/Migration20260422161208_shipment_tracking.js.map +7 -0
- package/dist/modules/shipment_tracking/migrations/Migration20260529120000_shipment_tracking_poi_region_names.js +15 -0
- package/dist/modules/shipment_tracking/migrations/Migration20260529120000_shipment_tracking_poi_region_names.js.map +7 -0
- package/dist/modules/shipment_tracking/migrations/Migration20260821120000_shipment_tracking_shipsgo.js +47 -0
- package/dist/modules/shipment_tracking/migrations/Migration20260821120000_shipment_tracking_shipsgo.js.map +7 -0
- package/dist/modules/shipment_tracking/notifications.js +145 -0
- package/dist/modules/shipment_tracking/notifications.js.map +7 -0
- package/dist/modules/shipment_tracking/services/carrierRegistry.js +24 -0
- package/dist/modules/shipment_tracking/services/carrierRegistry.js.map +7 -0
- package/dist/modules/shipment_tracking/services/poiNatsService.js +397 -0
- package/dist/modules/shipment_tracking/services/poiNatsService.js.map +7 -0
- package/dist/modules/shipment_tracking/services/shipsGoProvider.js +163 -0
- package/dist/modules/shipment_tracking/services/shipsGoProvider.js.map +7 -0
- package/dist/modules/shipment_tracking/services/trackingService.js +1283 -0
- package/dist/modules/shipment_tracking/services/trackingService.js.map +7 -0
- package/dist/modules/shipment_tracking/services/webhookService.js +263 -0
- package/dist/modules/shipment_tracking/services/webhookService.js.map +7 -0
- package/dist/modules/shipment_tracking/setup.js +72 -0
- package/dist/modules/shipment_tracking/setup.js.map +7 -0
- package/dist/modules/shipment_tracking/subscribers/cargo-event-created.js +60 -0
- package/dist/modules/shipment_tracking/subscribers/cargo-event-created.js.map +7 -0
- package/dist/modules/shipment_tracking/subscribers/dcsa-event-webhook-dispatch.js +58 -0
- package/dist/modules/shipment_tracking/subscribers/dcsa-event-webhook-dispatch.js.map +7 -0
- package/dist/modules/shipment_tracking/subscribers/poi-event-notification.js +82 -0
- package/dist/modules/shipment_tracking/subscribers/poi-event-notification.js.map +7 -0
- package/dist/modules/shipment_tracking/subscribers/poi-event-webhook-dispatch.js +53 -0
- package/dist/modules/shipment_tracking/subscribers/poi-event-webhook-dispatch.js.map +7 -0
- package/dist/modules/shipment_tracking/subscribers/shipment-status-changed.js +28 -0
- package/dist/modules/shipment_tracking/subscribers/shipment-status-changed.js.map +7 -0
- package/dist/modules/shipment_tracking/subscribers/terminal-event-ingest.js +67 -0
- package/dist/modules/shipment_tracking/subscribers/terminal-event-ingest.js.map +7 -0
- package/dist/modules/shipment_tracking/subscribers/tracking-job-initial-poll.js +30 -0
- package/dist/modules/shipment_tracking/subscribers/tracking-job-initial-poll.js.map +7 -0
- package/dist/modules/shipment_tracking/subscribers/tracking-job-webhook-dispatch.js +19 -0
- package/dist/modules/shipment_tracking/subscribers/tracking-job-webhook-dispatch.js.map +7 -0
- package/dist/modules/shipment_tracking/tables/index.js +6 -0
- package/dist/modules/shipment_tracking/tables/index.js.map +7 -0
- package/dist/modules/shipment_tracking/tables/tracking_job/table.client.js +284 -0
- package/dist/modules/shipment_tracking/tables/tracking_job/table.client.js.map +7 -0
- package/dist/modules/shipment_tracking/tables/tracking_job/table.js +36 -0
- package/dist/modules/shipment_tracking/tables/tracking_job/table.js.map +7 -0
- package/dist/modules/shipment_tracking/widgets/dashboard/ships-map/config.js +25 -0
- package/dist/modules/shipment_tracking/widgets/dashboard/ships-map/config.js.map +7 -0
- package/dist/modules/shipment_tracking/widgets/dashboard/ships-map/widget.client.js +355 -0
- package/dist/modules/shipment_tracking/widgets/dashboard/ships-map/widget.client.js.map +7 -0
- package/dist/modules/shipment_tracking/widgets/dashboard/ships-map/widget.js +26 -0
- package/dist/modules/shipment_tracking/widgets/dashboard/ships-map/widget.js.map +7 -0
- package/dist/types/assets.d.js +1 -0
- package/dist/types/assets.d.js.map +7 -0
- package/generated/entities/bic_config/index.ts +20 -0
- package/generated/entities/carrier_config/index.ts +23 -0
- package/generated/entities/location_override/index.ts +26 -0
- package/generated/entities/shipment/index.ts +46 -0
- package/generated/entities/ships_go_config/index.ts +25 -0
- package/generated/entities/tracking_event/index.ts +60 -0
- package/generated/entities/tracking_job/index.ts +33 -0
- package/generated/entities/webhook/index.ts +22 -0
- package/generated/entities/webhook_delivery/index.ts +22 -0
- package/generated/entities.ids.generated.ts +47 -0
- package/package.json +81 -0
- package/src/index.ts +39 -0
- package/src/modules/shipment_tracking/__integration__/TC-TRACK-410-shipsgo-config-api.spec.ts +133 -0
- package/src/modules/shipment_tracking/__integration__/meta.ts +4 -0
- package/src/modules/shipment_tracking/__tests__/empty-return-stop.test.ts +231 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/hapag-lloyd-transship-container.json +702 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/index.ts +434 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/maersk-multi-transship-completed.json +1518 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/maersk-transship-intransit.json +558 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/msc-direct.json +222 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/msc-multi-transship-completed.json +974 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/msc-multi.json +4846 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/msc-transship.json +409 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/poi-events.json +240 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/synthetic/index.ts +138 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/synthetic-eta-updates.json +288 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/synthetic-route-inference.json +201 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/synthetic-voyage-progression.json +410 -0
- package/src/modules/shipment_tracking/__tests__/shipsgo-air-mapper.test.ts +157 -0
- package/src/modules/shipment_tracking/__tests__/shipsgo-client.test.ts +155 -0
- package/src/modules/shipment_tracking/__tests__/shipsgo-mapper.test.ts +201 -0
- package/src/modules/shipment_tracking/__tests__/shipsgo-poll.test.ts +357 -0
- package/src/modules/shipment_tracking/__tests__/shipsgo-provider.test.ts +364 -0
- package/src/modules/shipment_tracking/__tests__/trackingService.integration.test.ts +2248 -0
- package/src/modules/shipment_tracking/__tests__/webhookPayload.test.ts +151 -0
- package/src/modules/shipment_tracking/acl.ts +18 -0
- package/src/modules/shipment_tracking/api/bic-configs/route.ts +206 -0
- package/src/modules/shipment_tracking/api/bic-configs/test/route.ts +108 -0
- package/src/modules/shipment_tracking/api/cargo-events/route.ts +101 -0
- package/src/modules/shipment_tracking/api/carrier-configs/route.ts +142 -0
- package/src/modules/shipment_tracking/api/dashboard/ships-map/route.ts +246 -0
- package/src/modules/shipment_tracking/api/location-overrides/route.ts +185 -0
- package/src/modules/shipment_tracking/api/openapi.ts +28 -0
- package/src/modules/shipment_tracking/api/shipments/route.ts +197 -0
- package/src/modules/shipment_tracking/api/shipsgo-configs/route.ts +215 -0
- package/src/modules/shipment_tracking/api/tracking-jobs/route.ts +149 -0
- package/src/modules/shipment_tracking/api/tracking-jobs/sync/route.ts +116 -0
- package/src/modules/shipment_tracking/api/utils.ts +10 -0
- package/src/modules/shipment_tracking/api/webhooks/deliveries/route.ts +82 -0
- package/src/modules/shipment_tracking/api/webhooks/route.ts +138 -0
- package/src/modules/shipment_tracking/api/webhooks/test/route.ts +84 -0
- package/src/modules/shipment_tracking/assets/carriers/cma_cgm.png +0 -0
- package/src/modules/shipment_tracking/assets/carriers/cosco.png +0 -0
- package/src/modules/shipment_tracking/assets/carriers/evergreen.png +0 -0
- package/src/modules/shipment_tracking/assets/carriers/hapag_lloyd.png +0 -0
- package/src/modules/shipment_tracking/assets/carriers/maersk.png +0 -0
- package/src/modules/shipment_tracking/assets/carriers/msc.png +0 -0
- package/src/modules/shipment_tracking/assets/carriers/one.png +0 -0
- package/src/modules/shipment_tracking/assets/carriers/oocl.png +0 -0
- package/src/modules/shipment_tracking/assets/carriers/yang_ming.png +0 -0
- package/src/modules/shipment_tracking/assets/carriers/zim.png +0 -0
- package/src/modules/shipment_tracking/assets/index.ts +66 -0
- package/src/modules/shipment_tracking/backend/carrier-configs/page.meta.ts +16 -0
- package/src/modules/shipment_tracking/backend/carrier-configs/page.tsx +782 -0
- package/src/modules/shipment_tracking/backend/location-overrides/page.meta.ts +16 -0
- package/src/modules/shipment_tracking/backend/location-overrides/page.tsx +241 -0
- package/src/modules/shipment_tracking/backend/shipment-tracking/page.meta.ts +16 -0
- package/src/modules/shipment_tracking/backend/shipment-tracking/page.tsx +340 -0
- package/src/modules/shipment_tracking/backend/tracking-jobs/page.meta.ts +16 -0
- package/src/modules/shipment_tracking/backend/tracking-jobs/page.tsx +16 -0
- package/src/modules/shipment_tracking/backend/webhooks/page.meta.ts +16 -0
- package/src/modules/shipment_tracking/backend/webhooks/page.tsx +228 -0
- package/src/modules/shipment_tracking/ce.ts +48 -0
- package/src/modules/shipment_tracking/cli.ts +321 -0
- package/src/modules/shipment_tracking/commands/bic-configs.ts +94 -0
- package/src/modules/shipment_tracking/commands/carrier-configs.ts +89 -0
- package/src/modules/shipment_tracking/commands/index.ts +8 -0
- package/src/modules/shipment_tracking/commands/location-overrides.ts +119 -0
- package/src/modules/shipment_tracking/commands/shipments.ts +247 -0
- package/src/modules/shipment_tracking/commands/shipsgo-configs.ts +101 -0
- package/src/modules/shipment_tracking/commands/tracking-jobs.ts +158 -0
- package/src/modules/shipment_tracking/commands/tracking.ts +230 -0
- package/src/modules/shipment_tracking/commands/webhooks.ts +159 -0
- package/src/modules/shipment_tracking/components/CombinedTimestampCell.tsx +236 -0
- package/src/modules/shipment_tracking/components/FacilityLocationInput.tsx +235 -0
- package/src/modules/shipment_tracking/components/LocationOverrideDrawer.tsx +586 -0
- package/src/modules/shipment_tracking/components/ShipmentDetailsDrawer.tsx +1364 -0
- package/src/modules/shipment_tracking/components/ShipmentDrawer.tsx +498 -0
- package/src/modules/shipment_tracking/components/ShipsGoConfigSection.tsx +266 -0
- package/src/modules/shipment_tracking/components/TimestampCell.tsx +146 -0
- package/src/modules/shipment_tracking/components/TrackingJobDrawer.tsx +467 -0
- package/src/modules/shipment_tracking/components/VesselTrackingMap.tsx +761 -0
- package/src/modules/shipment_tracking/components/WebhookDrawer.tsx +434 -0
- package/src/modules/shipment_tracking/data/entities.ts +748 -0
- package/src/modules/shipment_tracking/data/validators.ts +523 -0
- package/src/modules/shipment_tracking/di.ts +73 -0
- package/src/modules/shipment_tracking/encryption.ts +17 -0
- package/src/modules/shipment_tracking/events.ts +122 -0
- package/src/modules/shipment_tracking/hooks/useVesselTracking.ts +297 -0
- package/src/modules/shipment_tracking/i18n/de.json +327 -0
- package/src/modules/shipment_tracking/i18n/en.json +327 -0
- package/src/modules/shipment_tracking/i18n/es.json +327 -0
- package/src/modules/shipment_tracking/i18n/pl.json +327 -0
- package/src/modules/shipment_tracking/index.ts +13 -0
- package/src/modules/shipment_tracking/lib/__tests__/dcsa-event-mapping.test.ts +385 -0
- package/src/modules/shipment_tracking/lib/__tests__/dcsa-event-parser.test.ts +398 -0
- package/src/modules/shipment_tracking/lib/__tests__/empty-return.test.ts +174 -0
- package/src/modules/shipment_tracking/lib/__tests__/poi-event-processor.test.ts +699 -0
- package/src/modules/shipment_tracking/lib/__tests__/route-inference.test.ts +402 -0
- package/src/modules/shipment_tracking/lib/__tests__/status-machine.test.ts +447 -0
- package/src/modules/shipment_tracking/lib/__tests__/time-extraction.test.ts +331 -0
- package/src/modules/shipment_tracking/lib/__tests__/timestamp-utils.test.ts +260 -0
- package/src/modules/shipment_tracking/lib/__tests__/vessel-api.test.ts +125 -0
- package/src/modules/shipment_tracking/lib/__tests__/vessel-extraction.test.ts +108 -0
- package/src/modules/shipment_tracking/lib/adapters/cma-cgm.ts +84 -0
- package/src/modules/shipment_tracking/lib/adapters/cosco-types.ts +145 -0
- package/src/modules/shipment_tracking/lib/adapters/cosco.ts +314 -0
- package/src/modules/shipment_tracking/lib/adapters/evergreen.ts +107 -0
- package/src/modules/shipment_tracking/lib/adapters/hapag-lloyd.ts +88 -0
- package/src/modules/shipment_tracking/lib/adapters/index.ts +18 -0
- package/src/modules/shipment_tracking/lib/adapters/maersk.ts +104 -0
- package/src/modules/shipment_tracking/lib/adapters/msc.ts +223 -0
- package/src/modules/shipment_tracking/lib/adapters/zim.ts +106 -0
- package/src/modules/shipment_tracking/lib/audit-helpers.ts +29 -0
- package/src/modules/shipment_tracking/lib/auth/base64url.ts +4 -0
- package/src/modules/shipment_tracking/lib/auth/oauth-client.ts +53 -0
- package/src/modules/shipment_tracking/lib/background.ts +24 -0
- package/src/modules/shipment_tracking/lib/bic-api-client.ts +199 -0
- package/src/modules/shipment_tracking/lib/carrier-adapter.ts +136 -0
- package/src/modules/shipment_tracking/lib/current-vessel.ts +274 -0
- package/src/modules/shipment_tracking/lib/dcsa-event-mapping.ts +202 -0
- package/src/modules/shipment_tracking/lib/dcsa-event-parser.ts +163 -0
- package/src/modules/shipment_tracking/lib/dcsa-params.ts +14 -0
- package/src/modules/shipment_tracking/lib/empty-return.ts +122 -0
- package/src/modules/shipment_tracking/lib/location-overrides.ts +150 -0
- package/src/modules/shipment_tracking/lib/location-types.ts +154 -0
- package/src/modules/shipment_tracking/lib/logger.ts +195 -0
- package/src/modules/shipment_tracking/lib/poi-event-processor.ts +302 -0
- package/src/modules/shipment_tracking/lib/poi-nats-config.ts +134 -0
- package/src/modules/shipment_tracking/lib/poi-types.ts +198 -0
- package/src/modules/shipment_tracking/lib/rate-limiter.ts +58 -0
- package/src/modules/shipment_tracking/lib/route-extraction.ts +439 -0
- package/src/modules/shipment_tracking/lib/route-inference.ts +238 -0
- package/src/modules/shipment_tracking/lib/schedule-generator.ts +92 -0
- package/src/modules/shipment_tracking/lib/shipsgo-client.ts +350 -0
- package/src/modules/shipment_tracking/lib/shipsgo-mapper.ts +296 -0
- package/src/modules/shipment_tracking/lib/status-machine.ts +230 -0
- package/src/modules/shipment_tracking/lib/time-extraction.ts +85 -0
- package/src/modules/shipment_tracking/lib/timestamp-utils.ts +244 -0
- package/src/modules/shipment_tracking/lib/vessel-api.ts +299 -0
- package/src/modules/shipment_tracking/lib/vessel-cache.ts +102 -0
- package/src/modules/shipment_tracking/lib/vessel-extraction.ts +39 -0
- package/src/modules/shipment_tracking/lib/webhook-dispatcher.ts +93 -0
- package/src/modules/shipment_tracking/migrations/.snapshot-open-mercato.json +2914 -0
- package/src/modules/shipment_tracking/migrations/Migration20260422161208_shipment_tracking.ts +60 -0
- package/src/modules/shipment_tracking/migrations/Migration20260529120000_shipment_tracking_poi_region_names.ts +22 -0
- package/src/modules/shipment_tracking/migrations/Migration20260821120000_shipment_tracking_shipsgo.ts +65 -0
- package/src/modules/shipment_tracking/notifications.ts +151 -0
- package/src/modules/shipment_tracking/services/carrierRegistry.ts +29 -0
- package/src/modules/shipment_tracking/services/poiNatsService.ts +526 -0
- package/src/modules/shipment_tracking/services/shipsGoProvider.ts +231 -0
- package/src/modules/shipment_tracking/services/trackingService.ts +1777 -0
- package/src/modules/shipment_tracking/services/webhookService.ts +347 -0
- package/src/modules/shipment_tracking/setup.ts +99 -0
- package/src/modules/shipment_tracking/subscribers/__tests__/terminal-event-ingest.test.ts +141 -0
- package/src/modules/shipment_tracking/subscribers/cargo-event-created.ts +115 -0
- package/src/modules/shipment_tracking/subscribers/dcsa-event-webhook-dispatch.ts +115 -0
- package/src/modules/shipment_tracking/subscribers/poi-event-notification.ts +144 -0
- package/src/modules/shipment_tracking/subscribers/poi-event-webhook-dispatch.ts +94 -0
- package/src/modules/shipment_tracking/subscribers/shipment-status-changed.ts +41 -0
- package/src/modules/shipment_tracking/subscribers/terminal-event-ingest.ts +109 -0
- package/src/modules/shipment_tracking/subscribers/tracking-job-initial-poll.ts +53 -0
- package/src/modules/shipment_tracking/subscribers/tracking-job-webhook-dispatch.ts +50 -0
- package/src/modules/shipment_tracking/tables/index.ts +4 -0
- package/src/modules/shipment_tracking/tables/tracking_job/table.client.tsx +337 -0
- package/src/modules/shipment_tracking/tables/tracking_job/table.ts +34 -0
- package/src/modules/shipment_tracking/widgets/dashboard/ships-map/config.ts +32 -0
- package/src/modules/shipment_tracking/widgets/dashboard/ships-map/widget.client.tsx +448 -0
- package/src/modules/shipment_tracking/widgets/dashboard/ships-map/widget.ts +25 -0
- package/src/types/assets.d.ts +5 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-source timestamp utilities for shipment tracking.
|
|
3
|
+
*
|
|
4
|
+
* Supports tracking ETD/ETA/ATD/ATA from multiple sources with full history (SCD pattern).
|
|
5
|
+
* Primary value is computed using "latest update wins" strategy.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type TimestampSource = 'carrier_api' | 'manual' | 'ais' | 'port' | 'edi'
|
|
9
|
+
|
|
10
|
+
export type TimestampType = 'ETD' | 'ETA' | 'ATD' | 'ATA'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A single timestamp entry tracking value, source, and when it was recorded.
|
|
14
|
+
* Multiple entries per source are allowed (SCD - Slowly Changing Dimension pattern).
|
|
15
|
+
*/
|
|
16
|
+
export type ShipmentTimestampEntry = {
|
|
17
|
+
/** ISO 8601 datetime value (e.g., "2026-02-24T15:30:00") */
|
|
18
|
+
value: string
|
|
19
|
+
/** Original timezone offset (e.g., "+08:00", "-05:00", "Z") */
|
|
20
|
+
offset: string | null
|
|
21
|
+
/** Source system that provided this timestamp */
|
|
22
|
+
source: TimestampSource
|
|
23
|
+
/** When this entry was recorded/updated (ISO 8601) */
|
|
24
|
+
updatedAt: string
|
|
25
|
+
/** Optional reference to the source tracking event ID */
|
|
26
|
+
sourceEventId?: string | null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Computed primary timestamp with its metadata.
|
|
31
|
+
*/
|
|
32
|
+
export type ComputedTimestamp = {
|
|
33
|
+
value: Date
|
|
34
|
+
offset: string | null
|
|
35
|
+
source: TimestampSource
|
|
36
|
+
updatedAt: Date
|
|
37
|
+
sourceEventId?: string | null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Gets the latest (primary) timestamp from an array of entries.
|
|
42
|
+
* Uses "latest update wins" strategy - the entry with most recent updatedAt is primary.
|
|
43
|
+
*
|
|
44
|
+
* @param entries Array of timestamp entries or null/undefined
|
|
45
|
+
* @returns The latest entry or null if no entries
|
|
46
|
+
*/
|
|
47
|
+
export function getLatestTimestamp(
|
|
48
|
+
entries: ShipmentTimestampEntry[] | null | undefined,
|
|
49
|
+
): ComputedTimestamp | null {
|
|
50
|
+
if (!entries?.length) return null
|
|
51
|
+
|
|
52
|
+
// Use >= to prefer later entries when updatedAt is equal.
|
|
53
|
+
// This handles the case where entries are added in the same millisecond.
|
|
54
|
+
const latest = entries.reduce((best, entry) =>
|
|
55
|
+
entry.updatedAt >= best.updatedAt ? entry : best,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
value: new Date(latest.value),
|
|
60
|
+
offset: latest.offset,
|
|
61
|
+
source: latest.source,
|
|
62
|
+
updatedAt: new Date(latest.updatedAt),
|
|
63
|
+
sourceEventId: latest.sourceEventId,
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Gets the primary value as a Date, or null if no entries.
|
|
69
|
+
* Convenience wrapper around getLatestTimestamp.
|
|
70
|
+
*/
|
|
71
|
+
export function getPrimaryTimestampValue(
|
|
72
|
+
entries: ShipmentTimestampEntry[] | null | undefined,
|
|
73
|
+
): Date | null {
|
|
74
|
+
return getLatestTimestamp(entries)?.value ?? null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Gets the primary offset, or null if no entries.
|
|
79
|
+
*/
|
|
80
|
+
export function getPrimaryTimestampOffset(
|
|
81
|
+
entries: ShipmentTimestampEntry[] | null | undefined,
|
|
82
|
+
): string | null {
|
|
83
|
+
return getLatestTimestamp(entries)?.offset ?? null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Creates a new timestamp entry.
|
|
88
|
+
*
|
|
89
|
+
* @param value The timestamp value (Date or ISO string)
|
|
90
|
+
* @param offset Timezone offset string (e.g., "+08:00") or null
|
|
91
|
+
* @param source Source system
|
|
92
|
+
* @param sourceEventId Optional source event ID for traceability
|
|
93
|
+
* @returns A new ShipmentTimestampEntry
|
|
94
|
+
*/
|
|
95
|
+
export function createTimestampEntry(
|
|
96
|
+
value: Date | string,
|
|
97
|
+
offset: string | null,
|
|
98
|
+
source: TimestampSource,
|
|
99
|
+
sourceEventId?: string | null,
|
|
100
|
+
): ShipmentTimestampEntry {
|
|
101
|
+
const dateValue = typeof value === 'string' ? new Date(value) : value
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
value: dateValue.toISOString(),
|
|
105
|
+
offset,
|
|
106
|
+
source,
|
|
107
|
+
updatedAt: new Date().toISOString(),
|
|
108
|
+
sourceEventId: sourceEventId ?? null,
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Adds a timestamp entry to an array with time-based deduplication.
|
|
114
|
+
* Deduplication: if an entry with the same value+source exists, it won't be added.
|
|
115
|
+
* Multiple entries per source with different values ARE allowed (SCD pattern).
|
|
116
|
+
*
|
|
117
|
+
* @param entries Existing entries array (will be mutated) or null
|
|
118
|
+
* @param newEntry Entry to add
|
|
119
|
+
* @returns The updated array (creates new array if input was null)
|
|
120
|
+
*/
|
|
121
|
+
export function addTimestampEntry(
|
|
122
|
+
entries: ShipmentTimestampEntry[] | null | undefined,
|
|
123
|
+
newEntry: ShipmentTimestampEntry,
|
|
124
|
+
): ShipmentTimestampEntry[] {
|
|
125
|
+
const result = entries ? [...entries] : []
|
|
126
|
+
|
|
127
|
+
// Dedupe: check if exact same value+source combo already exists
|
|
128
|
+
const isDuplicate = result.some(
|
|
129
|
+
(e) => e.value === newEntry.value && e.source === newEntry.source,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
if (!isDuplicate) {
|
|
133
|
+
result.push(newEntry)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return result
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Merges extracted times into existing timestamp arrays.
|
|
141
|
+
* Used when processing tracking events to update shipment timestamps.
|
|
142
|
+
*
|
|
143
|
+
* @param current Current timestamp arrays from shipment
|
|
144
|
+
* @param extracted Newly extracted timestamps from events
|
|
145
|
+
* @param source Source of the extracted timestamps
|
|
146
|
+
* @param sourceEventId Optional source event ID
|
|
147
|
+
* @returns Updated timestamp arrays
|
|
148
|
+
*/
|
|
149
|
+
export function mergeExtractedTimestamps(
|
|
150
|
+
current: {
|
|
151
|
+
etdTimestamps?: ShipmentTimestampEntry[] | null
|
|
152
|
+
etaTimestamps?: ShipmentTimestampEntry[] | null
|
|
153
|
+
atdTimestamps?: ShipmentTimestampEntry[] | null
|
|
154
|
+
ataTimestamps?: ShipmentTimestampEntry[] | null
|
|
155
|
+
},
|
|
156
|
+
extracted: {
|
|
157
|
+
etd?: Date | null
|
|
158
|
+
etdOffset?: string | null
|
|
159
|
+
eta?: Date | null
|
|
160
|
+
etaOffset?: string | null
|
|
161
|
+
atd?: Date | null
|
|
162
|
+
atdOffset?: string | null
|
|
163
|
+
ata?: Date | null
|
|
164
|
+
ataOffset?: string | null
|
|
165
|
+
},
|
|
166
|
+
source: TimestampSource,
|
|
167
|
+
sourceEventId?: string | null,
|
|
168
|
+
): {
|
|
169
|
+
etdTimestamps: ShipmentTimestampEntry[] | null
|
|
170
|
+
etaTimestamps: ShipmentTimestampEntry[] | null
|
|
171
|
+
atdTimestamps: ShipmentTimestampEntry[] | null
|
|
172
|
+
ataTimestamps: ShipmentTimestampEntry[] | null
|
|
173
|
+
changed: boolean
|
|
174
|
+
} {
|
|
175
|
+
let changed = false
|
|
176
|
+
let etdTimestamps = current.etdTimestamps ?? null
|
|
177
|
+
let etaTimestamps = current.etaTimestamps ?? null
|
|
178
|
+
let atdTimestamps = current.atdTimestamps ?? null
|
|
179
|
+
let ataTimestamps = current.ataTimestamps ?? null
|
|
180
|
+
|
|
181
|
+
if (extracted.etd) {
|
|
182
|
+
const entry = createTimestampEntry(extracted.etd, extracted.etdOffset ?? null, source, sourceEventId)
|
|
183
|
+
const before = etdTimestamps?.length ?? 0
|
|
184
|
+
etdTimestamps = addTimestampEntry(etdTimestamps, entry)
|
|
185
|
+
if ((etdTimestamps?.length ?? 0) > before) changed = true
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (extracted.eta) {
|
|
189
|
+
const entry = createTimestampEntry(extracted.eta, extracted.etaOffset ?? null, source, sourceEventId)
|
|
190
|
+
const before = etaTimestamps?.length ?? 0
|
|
191
|
+
etaTimestamps = addTimestampEntry(etaTimestamps, entry)
|
|
192
|
+
if ((etaTimestamps?.length ?? 0) > before) changed = true
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (extracted.atd) {
|
|
196
|
+
const entry = createTimestampEntry(extracted.atd, extracted.atdOffset ?? null, source, sourceEventId)
|
|
197
|
+
const before = atdTimestamps?.length ?? 0
|
|
198
|
+
atdTimestamps = addTimestampEntry(atdTimestamps, entry)
|
|
199
|
+
if ((atdTimestamps?.length ?? 0) > before) changed = true
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (extracted.ata) {
|
|
203
|
+
const entry = createTimestampEntry(extracted.ata, extracted.ataOffset ?? null, source, sourceEventId)
|
|
204
|
+
const before = ataTimestamps?.length ?? 0
|
|
205
|
+
ataTimestamps = addTimestampEntry(ataTimestamps, entry)
|
|
206
|
+
if ((ataTimestamps?.length ?? 0) > before) changed = true
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
etdTimestamps,
|
|
211
|
+
etaTimestamps,
|
|
212
|
+
atdTimestamps,
|
|
213
|
+
ataTimestamps,
|
|
214
|
+
changed,
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Checks if a timestamp array has any entries.
|
|
220
|
+
*/
|
|
221
|
+
export function hasTimestamps(entries: ShipmentTimestampEntry[] | null | undefined): boolean {
|
|
222
|
+
return !!entries && entries.length > 0
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Gets all timestamps from a specific source.
|
|
227
|
+
*/
|
|
228
|
+
export function getTimestampsBySource(
|
|
229
|
+
entries: ShipmentTimestampEntry[] | null | undefined,
|
|
230
|
+
source: TimestampSource,
|
|
231
|
+
): ShipmentTimestampEntry[] {
|
|
232
|
+
if (!entries) return []
|
|
233
|
+
return entries.filter((e) => e.source === source)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Gets the history of timestamp changes, sorted by updatedAt (oldest first).
|
|
238
|
+
*/
|
|
239
|
+
export function getTimestampHistory(
|
|
240
|
+
entries: ShipmentTimestampEntry[] | null | undefined,
|
|
241
|
+
): ShipmentTimestampEntry[] {
|
|
242
|
+
if (!entries) return []
|
|
243
|
+
return [...entries].sort((a, b) => a.updatedAt.localeCompare(b.updatedAt))
|
|
244
|
+
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vessel API client for fetching real-time vessel position and trace data.
|
|
3
|
+
*
|
|
4
|
+
* The base URL is deployment-specific and MUST be provided via the
|
|
5
|
+
* `NEXT_PUBLIC_VESSEL_API_URL` environment variable. There is no default: when
|
|
6
|
+
* it is unset the vessel/trace/POI calls throw a clear "not configured" error
|
|
7
|
+
* and the map features that depend on them are simply unavailable.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// ─── Types ───────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
export interface VesselDimensions {
|
|
13
|
+
a: number
|
|
14
|
+
b: number
|
|
15
|
+
c: number
|
|
16
|
+
d: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface PortInfo {
|
|
20
|
+
locode: string
|
|
21
|
+
name: string
|
|
22
|
+
city: string
|
|
23
|
+
country: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface Position {
|
|
27
|
+
lat: number
|
|
28
|
+
lng: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type VesselStatus = 'AT_SEA' | 'IN_PORT'
|
|
32
|
+
export type PoiType = 'PORT' | 'TERMINAL' | 'WAYPOINT' | 'PORT_GEOMETRIC_CENTER'
|
|
33
|
+
|
|
34
|
+
export interface VesselInfo {
|
|
35
|
+
imo: number
|
|
36
|
+
mmsi: number | null
|
|
37
|
+
name: string
|
|
38
|
+
callsign: string | null
|
|
39
|
+
shipType: string | null
|
|
40
|
+
operatorName: string | null
|
|
41
|
+
owner: string | null
|
|
42
|
+
dimensions: VesselDimensions | null
|
|
43
|
+
status: VesselStatus | null
|
|
44
|
+
lastPosition: Position | null
|
|
45
|
+
lastHeading: number | null
|
|
46
|
+
lastDestination: string | null
|
|
47
|
+
lastDestinationPort: PortInfo | null
|
|
48
|
+
currentPort: PortInfo | null
|
|
49
|
+
currentPoiCode: string | null
|
|
50
|
+
currentPoiType: PoiType | null
|
|
51
|
+
isStationary: boolean
|
|
52
|
+
updatedAt: string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type TraceEventType =
|
|
56
|
+
| 'POSITION_UPDATE'
|
|
57
|
+
| 'PORT_ARRIVAL'
|
|
58
|
+
| 'PORT_DEPARTURE'
|
|
59
|
+
| 'TERMINAL_ARRIVAL'
|
|
60
|
+
| 'TERMINAL_DEPARTURE'
|
|
61
|
+
| 'DESTINATION_CHANGE'
|
|
62
|
+
|
|
63
|
+
export interface TracePoint {
|
|
64
|
+
lat: number
|
|
65
|
+
lng: number
|
|
66
|
+
speed: number | null
|
|
67
|
+
heading: number | null
|
|
68
|
+
eventType: TraceEventType
|
|
69
|
+
destination: string | null
|
|
70
|
+
timestamp: string
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface BoundingBox {
|
|
74
|
+
north: number
|
|
75
|
+
south: number
|
|
76
|
+
east: number
|
|
77
|
+
west: number
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A point of interest (port, terminal, waypoint, or port geometric center) returned by the
|
|
82
|
+
* vessel-api `/poi` endpoint, modeled as a circle (center + radius) ready to draw on a map.
|
|
83
|
+
*/
|
|
84
|
+
export interface PoiInfo {
|
|
85
|
+
code: string
|
|
86
|
+
locode: string
|
|
87
|
+
type: PoiType
|
|
88
|
+
center: Position
|
|
89
|
+
radiusMeters: number
|
|
90
|
+
portName: string | null
|
|
91
|
+
terminalName: string | null
|
|
92
|
+
city: string | null
|
|
93
|
+
country: string | null
|
|
94
|
+
/** Maritime region name in Polish (waypoints; may fall back to English). */
|
|
95
|
+
regionNamePl: string | null
|
|
96
|
+
/** Maritime region name in English. */
|
|
97
|
+
regionNameEn: string | null
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface PoiListResponse {
|
|
101
|
+
pois: PoiInfo[]
|
|
102
|
+
count: number
|
|
103
|
+
limit: number
|
|
104
|
+
/** Present only when a bounding-box filter was applied. */
|
|
105
|
+
bounds?: BoundingBox
|
|
106
|
+
/** Present only when a type filter was applied. */
|
|
107
|
+
type?: PoiType
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface TimeRange {
|
|
111
|
+
from?: string // ISO 8601 date
|
|
112
|
+
to?: string // ISO 8601 date
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface VesselResponse {
|
|
116
|
+
vessel: VesselInfo
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface TraceResponse {
|
|
120
|
+
vessel: {
|
|
121
|
+
imo: number
|
|
122
|
+
mmsi: number | null
|
|
123
|
+
name: string
|
|
124
|
+
}
|
|
125
|
+
trace: TracePoint[]
|
|
126
|
+
count: number
|
|
127
|
+
limit: number
|
|
128
|
+
bounds?: BoundingBox
|
|
129
|
+
timeRange?: TimeRange
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ─── Configuration ───────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
function vesselApiBaseUrl(): string {
|
|
135
|
+
const baseUrl = process.env.NEXT_PUBLIC_VESSEL_API_URL || ''
|
|
136
|
+
if (!baseUrl) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
'Vessel API is not configured — set NEXT_PUBLIC_VESSEL_API_URL to your vessel-tracking API base URL'
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
return baseUrl.replace(/\/+$/, '')
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ─── API Functions ───────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Fetch vessel information by IMO number or MMSI.
|
|
148
|
+
*
|
|
149
|
+
* @param imoOrMmsi - IMO number (7 digits) or MMSI (9 digits)
|
|
150
|
+
* @returns Vessel info or null if not found
|
|
151
|
+
*/
|
|
152
|
+
export async function fetchVessel(imoOrMmsi: number | string): Promise<VesselInfo | null> {
|
|
153
|
+
const id = typeof imoOrMmsi === 'string' ? imoOrMmsi : String(imoOrMmsi)
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
const response = await fetch(`${vesselApiBaseUrl()}/vessel/${encodeURIComponent(id)}`)
|
|
157
|
+
|
|
158
|
+
if (response.status === 404) {
|
|
159
|
+
return null
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!response.ok) {
|
|
163
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }))
|
|
164
|
+
throw new Error(error.error || `HTTP ${response.status}`)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const data: VesselResponse = await response.json()
|
|
168
|
+
return data.vessel
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error('[vessel-api] Failed to fetch vessel:', error)
|
|
171
|
+
throw error
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Fetch vessel trace (position history) with optional bounding box and time filters.
|
|
177
|
+
*
|
|
178
|
+
* @param imoOrMmsi - IMO number or MMSI
|
|
179
|
+
* @param bounds - Optional bounding box to filter trace points
|
|
180
|
+
* @param limit - Maximum number of points to return (default: 5000)
|
|
181
|
+
* @param timeRange - Optional time range filter (from/to in ISO 8601)
|
|
182
|
+
* @returns Trace data with array of position points
|
|
183
|
+
*/
|
|
184
|
+
export async function fetchVesselTrace(
|
|
185
|
+
imoOrMmsi: number | string,
|
|
186
|
+
bounds?: BoundingBox,
|
|
187
|
+
limit?: number,
|
|
188
|
+
timeRange?: TimeRange
|
|
189
|
+
): Promise<TraceResponse> {
|
|
190
|
+
const id = typeof imoOrMmsi === 'string' ? imoOrMmsi : String(imoOrMmsi)
|
|
191
|
+
|
|
192
|
+
const params = new URLSearchParams()
|
|
193
|
+
|
|
194
|
+
if (bounds) {
|
|
195
|
+
params.set('north', String(bounds.north))
|
|
196
|
+
params.set('south', String(bounds.south))
|
|
197
|
+
params.set('east', String(bounds.east))
|
|
198
|
+
params.set('west', String(bounds.west))
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (limit) {
|
|
202
|
+
params.set('limit', String(limit))
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (timeRange?.from) {
|
|
206
|
+
params.set('from', timeRange.from)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (timeRange?.to) {
|
|
210
|
+
params.set('to', timeRange.to)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const queryString = params.toString()
|
|
214
|
+
const url = `${vesselApiBaseUrl()}/vessel/${encodeURIComponent(id)}/trace${queryString ? `?${queryString}` : ''}`
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const response = await fetch(url)
|
|
218
|
+
|
|
219
|
+
if (response.status === 404) {
|
|
220
|
+
return {
|
|
221
|
+
vessel: { imo: 0, mmsi: null, name: 'Unknown' },
|
|
222
|
+
trace: [],
|
|
223
|
+
count: 0,
|
|
224
|
+
limit: limit || 5000,
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (!response.ok) {
|
|
229
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }))
|
|
230
|
+
throw new Error(error.error || `HTTP ${response.status}`)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const data: TraceResponse = await response.json()
|
|
234
|
+
return data
|
|
235
|
+
} catch (error) {
|
|
236
|
+
console.error('[vessel-api] Failed to fetch vessel trace:', error)
|
|
237
|
+
throw error
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Fetch points of interest (ports, terminals, waypoints) as map circles, optionally
|
|
243
|
+
* constrained to a viewport bounding box and/or a single POI type.
|
|
244
|
+
*
|
|
245
|
+
* @param bounds - Optional bounding box to fetch only POIs in the current viewport
|
|
246
|
+
* @param options - Optional `type` filter and `limit` (API default/cap is 5000)
|
|
247
|
+
* @returns Array of POIs (empty on 404)
|
|
248
|
+
*/
|
|
249
|
+
export async function fetchPois(
|
|
250
|
+
bounds?: BoundingBox,
|
|
251
|
+
options?: { type?: PoiType; limit?: number }
|
|
252
|
+
): Promise<PoiInfo[]> {
|
|
253
|
+
const params = new URLSearchParams()
|
|
254
|
+
|
|
255
|
+
if (bounds) {
|
|
256
|
+
params.set('north', String(bounds.north))
|
|
257
|
+
params.set('south', String(bounds.south))
|
|
258
|
+
params.set('east', String(bounds.east))
|
|
259
|
+
params.set('west', String(bounds.west))
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (options?.type) {
|
|
263
|
+
params.set('type', options.type)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (options?.limit) {
|
|
267
|
+
params.set('limit', String(options.limit))
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const queryString = params.toString()
|
|
271
|
+
const url = `${vesselApiBaseUrl()}/poi${queryString ? `?${queryString}` : ''}`
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
const response = await fetch(url)
|
|
275
|
+
|
|
276
|
+
if (response.status === 404) {
|
|
277
|
+
return []
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (!response.ok) {
|
|
281
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }))
|
|
282
|
+
throw new Error(error.error || `HTTP ${response.status}`)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const data: PoiListResponse = await response.json()
|
|
286
|
+
// The API contract says `code` is unique, but the live endpoint can return the same code
|
|
287
|
+
// more than once. Dedupe by code so downstream React keys stay unique and we don't draw
|
|
288
|
+
// overlapping duplicate circles.
|
|
289
|
+
const seen = new Set<string>()
|
|
290
|
+
return (data.pois ?? []).filter((poi) => {
|
|
291
|
+
if (seen.has(poi.code)) return false
|
|
292
|
+
seen.add(poi.code)
|
|
293
|
+
return true
|
|
294
|
+
})
|
|
295
|
+
} catch (error) {
|
|
296
|
+
console.error('[vessel-api] Failed to fetch POIs:', error)
|
|
297
|
+
throw error
|
|
298
|
+
}
|
|
299
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-global, bounded, short-TTL cache for public AIS vessel data, plus a small
|
|
3
|
+
* concurrency limiter for fanning out `fetchVessel` calls.
|
|
4
|
+
*
|
|
5
|
+
* Why this exists: the ships-map dashboard widget resolves many shipments to a handful
|
|
6
|
+
* of distinct vessels (a vessel carries several of our containers), then fetches each
|
|
7
|
+
* vessel's live position from the external `vessel-api`. That service has no batch
|
|
8
|
+
* endpoint, so we make one HTTP call per distinct IMO. To keep latency and upstream
|
|
9
|
+
* quota in check we:
|
|
10
|
+
* - cache the public `VesselInfo` keyed by IMO (TTL ~5 min),
|
|
11
|
+
* - bound the cache so it can't grow over the process lifetime,
|
|
12
|
+
* - cap concurrency on the fan-out.
|
|
13
|
+
*
|
|
14
|
+
* The cache stores ONLY public maritime data (`VesselInfo` keyed by IMO) — never
|
|
15
|
+
* tenant-scoped rows — so it is safe to share across tenants. The tenant-scoped
|
|
16
|
+
* `Shipment` query and the shipment→vessel grouping are computed per request and
|
|
17
|
+
* never cached here.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { fetchVessel, type VesselInfo } from './vessel-api'
|
|
21
|
+
|
|
22
|
+
// ─── Configuration ───────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
const TTL_MS = 5 * 60 * 1000 // 5 minutes
|
|
25
|
+
const MAX_ENTRIES = 500 // hard cap so the cache can't grow unbounded over process life
|
|
26
|
+
const DEFAULT_CONCURRENCY = 8
|
|
27
|
+
|
|
28
|
+
type CacheEntry = { v: VesselInfo | null; exp: number }
|
|
29
|
+
|
|
30
|
+
// Module-level (process-global) cache. Keyed by IMO string.
|
|
31
|
+
const cache = new Map<string, CacheEntry>()
|
|
32
|
+
|
|
33
|
+
// ─── Cache ───────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Evict expired entries; if still at capacity, drop the oldest insertion (Map preserves
|
|
37
|
+
* insertion order) so the cache stays bounded.
|
|
38
|
+
*/
|
|
39
|
+
function evictIfNeeded(now: number): void {
|
|
40
|
+
for (const [key, entry] of cache) {
|
|
41
|
+
if (entry.exp <= now) cache.delete(key)
|
|
42
|
+
}
|
|
43
|
+
while (cache.size >= MAX_ENTRIES) {
|
|
44
|
+
const oldest = cache.keys().next().value
|
|
45
|
+
if (oldest === undefined) break
|
|
46
|
+
cache.delete(oldest)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Returns the cached `VesselInfo` for an IMO, fetching from `vessel-api` on a miss.
|
|
52
|
+
*
|
|
53
|
+
* A fetch failure is NOT cached — it propagates to the caller so the request handler can
|
|
54
|
+
* fold the IMO into `unresolvedCount` (via `Promise.allSettled`). A successful fetch that
|
|
55
|
+
* yields `null` (404 / unknown vessel) IS cached to avoid re-hitting a known-miss.
|
|
56
|
+
*/
|
|
57
|
+
export async function getVesselCached(imo: string): Promise<VesselInfo | null> {
|
|
58
|
+
const now = Date.now()
|
|
59
|
+
const hit = cache.get(imo)
|
|
60
|
+
if (hit && hit.exp > now) return hit.v
|
|
61
|
+
|
|
62
|
+
const vessel = await fetchVessel(imo)
|
|
63
|
+
evictIfNeeded(Date.now())
|
|
64
|
+
cache.set(imo, { v: vessel, exp: Date.now() + TTL_MS })
|
|
65
|
+
return vessel
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Test-only: clear the process-global cache. */
|
|
69
|
+
export function __clearVesselCache(): void {
|
|
70
|
+
cache.clear()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ─── Concurrency-capped fan-out ──────────────────────────────
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Run `task` over `items` with at most `concurrency` in flight, returning results in the
|
|
77
|
+
* same order as `items`. Mirrors `Promise.allSettled` semantics: never rejects — each slot
|
|
78
|
+
* resolves to `{ status: 'fulfilled', value }` or `{ status: 'rejected', reason }`.
|
|
79
|
+
*/
|
|
80
|
+
export async function mapWithConcurrency<T, R>(
|
|
81
|
+
items: readonly T[],
|
|
82
|
+
task: (item: T) => Promise<R>,
|
|
83
|
+
concurrency: number = DEFAULT_CONCURRENCY,
|
|
84
|
+
): Promise<PromiseSettledResult<R>[]> {
|
|
85
|
+
const results: PromiseSettledResult<R>[] = new Array(items.length)
|
|
86
|
+
let cursor = 0
|
|
87
|
+
|
|
88
|
+
async function worker(): Promise<void> {
|
|
89
|
+
while (cursor < items.length) {
|
|
90
|
+
const index = cursor++
|
|
91
|
+
try {
|
|
92
|
+
results[index] = { status: 'fulfilled', value: await task(items[index]) }
|
|
93
|
+
} catch (reason) {
|
|
94
|
+
results[index] = { status: 'rejected', reason }
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const workers = Array.from({ length: Math.min(concurrency, items.length) }, () => worker())
|
|
100
|
+
await Promise.all(workers)
|
|
101
|
+
return results
|
|
102
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vessel info extraction utilities for shipment tracking.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type VesselInfo = {
|
|
6
|
+
vesselName: string | null
|
|
7
|
+
vesselImo: string | null
|
|
8
|
+
voyageNumber: string | null
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type EventWithVesselFields = {
|
|
12
|
+
vesselName?: string | null
|
|
13
|
+
vesselImo?: string | null
|
|
14
|
+
voyageNumber?: string | null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Finds the latest vessel info from a list of tracking events.
|
|
19
|
+
*
|
|
20
|
+
* For completed voyages, the last event may be a gate operation (GTOT/GTIN)
|
|
21
|
+
* that doesn't have vessel data. This function finds the latest event
|
|
22
|
+
* that has any vessel info (vesselName, vesselImo, or voyageNumber).
|
|
23
|
+
*
|
|
24
|
+
* @param events - Array of tracking events sorted by eventDateTime
|
|
25
|
+
* @returns VesselInfo from the latest event with vessel data, or null if none found
|
|
26
|
+
*/
|
|
27
|
+
export function findLatestVesselInfo(events: EventWithVesselFields[]): VesselInfo | null {
|
|
28
|
+
const eventWithVessel = [...events]
|
|
29
|
+
.reverse()
|
|
30
|
+
.find((e) => e.vesselName || e.vesselImo || e.voyageNumber)
|
|
31
|
+
|
|
32
|
+
if (!eventWithVessel) return null
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
vesselName: eventWithVessel.vesselName ?? null,
|
|
36
|
+
vesselImo: eventWithVessel.vesselImo ?? null,
|
|
37
|
+
voyageNumber: eventWithVessel.voyageNumber ?? null,
|
|
38
|
+
}
|
|
39
|
+
}
|