RiskStep returned early to a badge as soon as a risk level existed, so the
control was unreachable and a mis-assigned level could never be corrected.
Both the server and the sibling AssignRiskCard treat risk as correctable
until duty is advised off it — completeWithMetadata has no already-completed
guard and overwrites metadata.riskLevel. RiskStep was stricter than either.
It now keeps the control mounted alongside the assigned badge, offers
"Reassign risk", and locks to badge-only once DUTY_TAXES_ADVISED completes.
The control also reads the persisted level (it was hardcoded to GREEN, so
unhiding it alone would have misreported the assignment), and the T1 gate is
skipped once a level exists, since risk cannot be assigned without a closed
T1 and stale T1 data must not hide the badge.
Correcting a level previously left no record of the old value, who changed
it, or when — thin ground for a customer-visible level that may be disputed.
assignRisk now appends each decision to metadata.riskHistory: the level, the
level it replaced, the timestamp, the user id, and a display name resolved
at assignment time so the trail shows a person rather than a UUID. riskLevel
still carries the current value and always equals the last entry, so
existing consumers are unchanged.
History lives on the existing metadata JSONB column, so no migration is
needed, and the logic sits in assignRisk rather than the shared
completeWithMetadata that adviseDuty and others also use. Re-picking the
level already in force is not recorded — it changed nothing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Containers need a reach stacker or gantry, so only Indode, Modjo and Dire Dawa
take them. Bulk needs far less and is handled at all five facilities. Having a
facility was previously enough to load anything, so a container booking through
Sebeta or Adama would have been accepted and then had nothing to lift it.
- yard_facilities gains handles_container / handles_bulk, both defaulting true so
a facility handles everything unless told otherwise; the seeder states the real
capability.
- The intercity gate now refuses cargo a facility cannot lift, saying which type,
not just "no facility". canHandleFreight keeps that rule in the resolver so
callers cannot get it subtly wrong.
- The intercity list resolves each end against the booking's own freight type, so
the view flags a container booking routed through a bulk-only yard while the
train is still coming rather than when the load is refused.
Import/export untouched — the gate is still DOMESTIC-only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Introduced DEACTIVATED status for trains, allowing staff to park trains indefinitely.
- Implemented methods to deactivate and reactivate trains in the TrainBuilderService.
- Added UI components for train deactivation and reactivation in TrainBuilderDetailPage.
- Created a dropdown setting for admin-managed import train numbers, with corresponding migrations.
- Updated yard code length to accommodate soft-delete suffix.
- Enhanced train status handling to include DEACTIVATED state.
Adds prismaCode and prismaMeta fields to 500 responses so the exact
Prisma error code (P2022, P2023, etc.) is visible in curl/Swagger without
needing server log access. Temporary diagnostic aid.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The init migration created StopStatus with UPCOMING/APPROACHING/CURRENT/COMPLETED.
Migration 20260717000001 was an empty no-op file that claimed the rename was
"applied directly", but the ALTER TYPE RENAME VALUE SQL was never executed on
deployed environments. TripStopTime rows with status='UPCOMING' cause P2023
(PrismaClientKnownRequestError) on every search call that includes stopTimes.
This idempotent migration checks pg_enum before renaming so it is safe to run
whether or not the rename was previously applied.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without WHERE ("seatId" IS NOT NULL) the index creation fails in
environments that have JourneySegment rows with NULL seatId sharing
the same (scheduleId, departureStationId), which aborts the CI/CD
pipeline before the API server restarts. Also adds the passenger.
schema prefix to match the DELETE statement above it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Removed the BookingClearanceWorkflowBanner from ClearanceCard as the clearance progress is now integrated into the unified journey wizard.
- Eliminated the MilestoneTimeline component from DocumentsTab, consolidating customs progress into the JourneyWizard.
- Updated PageHeader to include a ContractReferenceLink for better navigation to contract details.
- Simplified ShipmentTrackingCard to focus on duty/tax payment slip upload, removing unnecessary milestone display.
- Integrated JourneyWizard component to visualize the booking journey, replacing the previous progress tracker.
- Enhanced ContractDetailPage to better categorize documents and improve user experience with clearer sections for profile, business license, clearance, and other documents.
- Introduced CSS for contracts table to manage column sizing and sticky headers effectively.
- Added ContractReferenceLink component for backoffice to link to contract details, ensuring consistent navigation across applications.
- Introduced new boolean fields (isHazardous, isReefer, isReturn) in UnitDraft and related interfaces to allow individual container handling options.
- Updated emptyUnit function to initialize these new fields.
- Modified GlCreateBookingForm to handle and display these options for each container.
- Adjusted calculations for hazardous, reefer, and return quantities based on the new handling options.
- Updated the schema for container units and booking container lines to include handling options.
- Added migration to support the new return flag in the database.
- Enhanced various components to reflect gross weight calculations, ensuring consistency across the application.