Merge pull request #459 from Tria-plc/CutomerTruckAssign

Cutomer truck assign

Fields: Truck Plate Number, Driver Name, Truck Type, Container Number to Load (now the Select I just wired) → Verify & Submit Assignment.
This commit is contained in:
Hagernesh Tadesse
2026-07-04 10:59:48 +03:00
committed by GitHub
6 changed files with 68 additions and 10 deletions

View File

@@ -7,6 +7,9 @@ RUN apk add --no-cache libc6-compat
# `--mount=type=cache,target=/pnpm/store` cache actually persists deps across builds.
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
# Puppeteer uses the system Chromium installed in the runner stage — skip the
# ~150MB bundled-Chromium download during pnpm install.
ENV PUPPETEER_SKIP_DOWNLOAD=true
RUN corepack enable
WORKDIR /app
@@ -32,8 +35,14 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
pnpm deploy --filter="@edr/freight-api" --prod --legacy /deploy
FROM node:24.15.0-alpine AS runner
RUN apk add --no-cache libc6-compat
# Chromium + fonts for headless PDF rendering (puppeteer). Alpine ships the
# binary at /usr/bin/chromium-browser, which the PDF renderer auto-detects
# (also pinned via PUPPETEER_EXECUTABLE_PATH). Without this, PDF generation
# falls back to a degraded hand-built layout.
RUN apk add --no-cache libc6-compat \
chromium nss freetype harfbuzz ca-certificates ttf-freefont
ENV NODE_ENV=production
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
WORKDIR /app
RUN addgroup --system --gid 1001 nodejs \
&& adduser --system --uid 1001 --ingroup nodejs nestjs

View File

@@ -1086,6 +1086,9 @@ export class BookingTransitionService {
offeredAmount: number;
paymentDeadline: Date;
} | null;
/** Flat list of physical container numbers on this booking (for the
* customer truck-assignment container picker). */
containerNumbers: string[];
}
> {
// This enrichment runs AFTER the transition has committed. A failure here
@@ -1141,12 +1144,20 @@ export class BookingTransitionService {
`enrichBookingResponse: batch-offer lookup failed for ${booking.id}: ${(err as Error).message}`,
);
}
// Physical container numbers entered at booking time (booking_container
// units), flattened for the customer truck-assignment container picker.
const containerNumbers = (booking.bookingContainers ?? [])
.flatMap((bc) => bc.units ?? [])
.map((unit) => unit.containerNumber)
.filter((n): n is string => Boolean(n));
return {
...booking,
latestChangeRequestNote: note?.note ?? null,
contractSummary: summary,
nextStep,
activeBatchOffer,
containerNumbers,
};
}
}

View File

@@ -1265,7 +1265,9 @@ export class TrainSchedulingService {
performedBy: 'DOCUMENT_GENERATION',
});
const html = this.buildImportLoadListHtml(loadList);
const buffer = await this.pdfDocuments.htmlToPdfBuffer(html);
// Generic render — NOT the release-order fallback (would mislabel this as a
// gate-clearance / release order when Chromium is unavailable).
const buffer = await this.pdfDocuments.renderDocumentHtml(html, 'Import marshalling / load list');
const reference = loadList.trainNumber ?? loadList.trainScheduleId;
return {
filename: `import-marshalling-${this.safeDocumentName(reference)}.pdf`,
@@ -1283,7 +1285,8 @@ export class TrainSchedulingService {
}
const html = this.buildExportLoadListHtml(schedule);
const buffer = await this.pdfDocuments.htmlToPdfBuffer(html);
// Generic render — NOT the release-order fallback (see importLoadListDocument).
const buffer = await this.pdfDocuments.renderDocumentHtml(html, 'Export marshalling / load list');
const reference = schedule.trainNumber ?? schedule.id;
return {
filename: `export-marshalling-${this.safeDocumentName(reference)}.pdf`,

View File

@@ -20,6 +20,18 @@ export class WarehouseReleaseDocumentService {
});
}
/**
* Render arbitrary document HTML to PDF via the shared renderer WITHOUT the
* release-order fallback. Non-release documents (e.g. the import/export
* marshalling load list) must use this so a Chromium-less fallback degrades to
* a plain-text dump of *their own* content — instead of masquerading as a
* "Warehouse Gate Clearance / Release Order", which the release-specific
* fallback would otherwise draw regardless of the input HTML.
*/
renderDocumentHtml(html: string, label = 'Document'): Promise<Buffer> {
return this.pdf.htmlToPdfBuffer(html, { label });
}
private htmlToBasicPdfBuffer(html: string): Buffer {
const doc = this.extractReleaseDocument(html);
const body: string[] = [

View File

@@ -38,6 +38,11 @@ export function CustomerTruckAssignmentCard({
);
const [error, setError] = useState<string | null>(null);
// Physical container numbers on this booking — the customer picks which one to
// load onto the truck instead of typing it. Falls back to free entry when the
// booking has no container numbers recorded.
const containerOptions = booking.containerNumbers ?? [];
const assignMutation = useMutation(api.bookings.assignCustomerTruck.mutationOptions());
const downloadMutation = useMutation(api.bookings.downloadCustomerTruckFreightOrder.mutationOptions());
@@ -120,13 +125,27 @@ export function CustomerTruckAssignmentCard({
onChange={(value) => setTruckType(value ?? "")}
disabled={assigned}
/>
<TextInput
label="Container Number to Load"
required
value={containerNumberToLoad}
onChange={(e) => setContainerNumberToLoad(e.currentTarget.value.toUpperCase())}
readOnly={assigned}
/>
{containerOptions.length > 0 ? (
<Select
label="Container Number to Load"
required
placeholder="Select a container from this booking"
data={containerOptions}
value={containerNumberToLoad || null}
onChange={(value) => setContainerNumberToLoad(value ?? "")}
searchable
disabled={assigned}
nothingFoundMessage="No matching container"
/>
) : (
<TextInput
label="Container Number to Load"
required
value={containerNumberToLoad}
onChange={(e) => setContainerNumberToLoad(e.currentTarget.value.toUpperCase())}
readOnly={assigned}
/>
)}
</SimpleGrid>
<Group justify="flex-end">

View File

@@ -466,6 +466,10 @@ export interface IBooking extends BaseEntity {
containers?: Array<{ type: string; qty: number; vgm: number }> | null;
/** Physical container numbers on this booking (from booking container units),
* surfaced for the customer truck-assignment container picker. */
containerNumbers?: string[] | null;
versionNumber: number;
priorityScore: number;