Staff could suspend or reactivate a customer role with one silent click:
no reason captured, nothing stored, and the customer was never told.
The API now rejects a suspend or reactivate without a non-empty note,
keeps the note in reviewNote while suspended, and sends the customer an
SMS/email/in-app notification quoting the staff message. In the
backoffice the reject-note modal is generalised into a decision modal
shared by reject, suspend and reactivate, so all three force a message.
EDRFREIGHT-188
A customer whose role was rejected saw only a bare "Rejected" chip in
settings - the reviewer's note and the resubmit action existed solely
inside the contract wizard's block modal, so fixing and reapplying from
the profile page was impossible. The settings role card now shows the
reviewer note (for suspended roles too) and offers "Resubmit for
approval", which flips the role back to Pending and notifies the
backoffice through the existing roleReapplied inbox event.
RoleCard's locked variant now renders as a plain box instead of a
button so it can host the action button (buttons cannot nest) and the
new detail line.
EDRFREIGHT-233
Marketing asked for approval requests to surface in order instead of the
alphabetical default. New sortBy=review tiers the list by what needs
action - submitted applications awaiting first approval, then approved
customers with a pending change request, then everyone else (drafts
included) - newest first within each tier. Exposed as the backoffice
"Needs review first" sort option and made the default on both ends.
EDRFREIGHT-232
Two review-workflow gaps for freight customer onboarding:
Request for change per document. Backoffice can now flag a single uploaded
document (company document, profile licence, or POA delegation letter) with a
note the customer sees, instead of rejecting the whole role over it. Adds
review_status/review_note/reviewed_by/reviewed_at to freight.files (migration
AddFileReviewStatus, partial index for the gate), a POST
documents/:fileId/request-change endpoint, the backoffice action + modal, and a
portal banner/badge so the customer knows what to re-upload. Re-uploading clears
the flag. Approving a role is blocked while any of its documents has an open
correction; the gate check and the status write share a pessimistic write lock
on the company row (as does the change-request write) so a correction can never
slip in between the check and the profile going Active.
Resubmission is visible to reviewers. When a customer resubmits a rejected role
or amends a change request, backoffice staff are notified (allBackoffice inbox
item, deep-linked to the customer) and the resubmission surfaces in a new
"Pending changes" list view + KPI, since such companies are status = active and
never matched the pending-approval filter.
The documents step of CompanyProfileForm uploads company documents via
onUploadDocuments() and then triggers submit synchronously in the same
nextStep() call. The setDocumentFiles({}) that clears the staged files has
not re-rendered by the time finishMutation's closure runs, so reading
documentFiles there re-sent the exact same files and created a duplicate
row per document.
Drop the company-document upload from finishMutation — the documents step
already persisted them. Licenses stay, since they have no auto-upload path.
- Integrated CompanyInfoCard, ContainersCard, and ContractInfoCard into ChangesRequestedView, DraftBookingView, and ReadonlyBookingView components.
- Updated utils to handle new booking detail types and improve container summary calculations.
- Enhanced ShipmentDetailsCard and ScheduleCard to utilize updated booking detail structure and display relevant information more effectively.
The inventory table has one row per inventory item, so on a self-haul booking
with several trucks there was no way to tell which container sat on which
truck without opening a document. The plate column now lists every plate, but
not the mapping.
Each row with a customer truck gets a chevron that opens a per-truck
breakdown: plate, driver, type, the containers it carries, and whether it has
arrived. Bulk trucks show "Bulk" — they haul loose tonnage, not containers.
The breakdown fetches only when opened, so a table nobody expands costs no
extra requests, and it reuses the existing per-booking trucks endpoint rather
than widening the inventory query.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Multi-truck self-haul had no list anywhere on the warehouse side. The ops
dashboard counted trucks on site and offered no way to open the list, and
the inventory table showed a blank plate on exactly the bookings that have
several trucks: it read booking.customer_truck_plate_number, which
multi-truck self-haul leaves null because plates live in
customer_truck_assignments. Booking BK-2026-000033 has a truck and a driver
on file and displayed neither.
Adds a Trucks on Site page listing every truck that has arrived and not yet
departed, across bookings, with plate, driver, booking, customer, containers
and dwell time. It covers both haulage paths because the gate does — a
customer's own truck and an EDR last-mile truck reach the same barrier — and
flags anything sitting over four hours. It lives under Warehouse Management
rather than Imports or Exports, since the yard is not per-direction.
The inventory queries now read plates and drivers from the assignments and
keep the booking columns as the fallback for single-truck bookings written
before that table existed.
Both new statements were EXPLAIN-validated against the live schema; the
plate fix returns the data that was previously null.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>