"Copy permissions from" listed every position type in every
organization, because the dropdown read an unfiltered GET
/position-types. IAM exposes no organization-scoped route and carries no
organizationId on a position type, so the list is now narrowed
client-side to the built-in (isSystem) types plus those whose unit
belongs to the selected organization, with the type being edited
excluded.
Also in position management:
- Invalidate every position-type cache key root after a mutation. React
Query matches prefixes element by element, so ["position-type"] never
reached ["position-types-common", ...] and the department pickers kept
serving a stale list. invalidatePositionTypeQueries() covers all three
roots and is shared by the hook and the form.
- Drop getByOrganizationId and getCommonTypesByOrganizationId. Both
issued the same requests as their unit counterparts and had no callers.
- Surface errors that were being swallowed. Three mutations had empty
onError handlers, hiding IAM's 403 for built-in position types, and
CreatePositionForm's bare catch discarded the reason for every failure.
- Move organization and unit into the zod schema so they validate with
translated messages and inline errors instead of an ad-hoc toast, and
keep submit disabled through the permission-assignment call that
follows the save.
- Report the two outcomes the form used to hide: a save that succeeded
while permission assignment failed, and clearing every permission,
which assign-seconds-for-first cannot express.
- Fix the list page's loading and error states, which rendered the
"Add User" string as a spinner, ignored the unit-scoped query, and
left the export button stuck after a failed download.
- Halve PermissionSearch's requests. It fetched 50 rows, read the total
off the response and immediately refetched, and it re-filtered results
on the undebounced term, blanking the list while typing.
Remove the three record toggles. They never worked: IAM's
PositionTypeConfiguration holds only { id, organizationId,
positionTypeId, timeframe } in every published build, canAssignRecord
and canCreateBankRecord exist nowhere in the package, and the global
ValidationPipe runs with forbidNonWhitelisted, so every write was a 400.
The reads were broken too, passing a positionTypeId to a route that
filters on organizationId. A TODO records where the real flag lives:
PositionConfiguration.canReceiveRecord, keyed by positionId.
Delete ActionsColumn.tsx, which had no references.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Creating a department under a unit required picking a position type,
which is not always known at that point. Sub-department creation is
unchanged and still requires one.
PositionPayload.positionTypeId becomes optional and the field is omitted
from the request body when unset rather than sent as an empty string.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The External Truck Assignment card only exposed the Excel template from inside the Bulk Upload modal. A Download Template button now sits beside Bulk Upload in the card header, matching the container-import pattern in the booking form, so customers can grab the template without opening the modal first.
createUniqueConstraint/createForeignKey used bare table names, which
resolve against the default schema (public) while the table was created
in freight — the migration failed on boot and rolled back every start.
Rewritten as idempotent schema-qualified SQL (unique index replaces the
constraint, FK inline on the column).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Store the signer's saved-signature URL on booking_handovers (new
signature_image_url column) when a handover is signed — per-truck or
booking-level — so signed handover documents can render the actual
signature, matching the contract-signing flow.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add maintenance intervals configuration to track maintenance by kilometers
driven. When a maintenance is marked COMPLETED, automatically calculate and
schedule the next maintenance based on interval + current odometer reading.
Features:
- MaintenanceInterval entity: stores KM/day intervals per vehicle & type
- scheduleNextMaintenance(): creates next SCHEDULED item after completion
- nextDueKm field: tracks when next maintenance is due (in kilometers)
- getDueBoard() queries already support KM-based tracking
Maintenance now "marches forward" based on distance driven, not just dates.
Each vehicle type can have different intervals (e.g., oil every 10k km, tires 50k km).
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Implement full train import journey with six container bookings filling a 54-wagon train.
- Create tests for split offer and rebooking scenarios, handling payment expiry and waiting list promotions.
- Add tests for handling waiting bookings expiration when the train is full.
- Implement tests for reopening booking windows after expired reservations.
- Seed database with necessary corridor data for import flows, including yards, container types, locomotives, and rates.