Customer Name + Customer ID columns removed from the Export Marshalling Document

This commit is contained in:
Hagernesh
2026-07-22 07:41:48 +00:00
parent 64b205a71e
commit ecd85d9d7c
5 changed files with 59 additions and 10 deletions

View File

@@ -2811,13 +2811,12 @@ export class TrainSchedulingService {
return [
`<tr class="empty">
${wagonCells}
<td colspan="6">EMPTY — no cargo allocated</td>
<td colspan="4">EMPTY — no cargo allocated</td>
</tr>`,
];
}
return allocations.map((allocation) => {
const booking = allocation.booking ?? bookingById.get(allocation.bookingId);
const company = booking?.company as Record<string, unknown> | null | undefined;
const cargoType = (booking as unknown as { cargoType?: { name?: string; code?: string } } | undefined)?.cargoType;
const containerItems = allocation.containerItems ?? [];
const firstContainer = containerItems[0];
@@ -2826,8 +2825,6 @@ export class TrainSchedulingService {
const chassisNumbers = containerItems.map((item) => item.chassisNumber).filter(Boolean).join(', ');
return `<tr>
${wagonCells}
<td>${esc(company?.name ?? company?.legalName ?? company?.tradeName ?? booking?.companyId)}</td>
<td>${esc(booking?.companyId)}</td>
<td>${esc(cargoType?.name ?? cargoType?.code ?? allocation.loadType)}</td>
<td>${esc(containerNumbers || firstContainer?.containerNumber)}</td>
<td>${esc(chassisNumbers)}</td>
@@ -2909,8 +2906,6 @@ export class TrainSchedulingService {
<th class="num">Equated Length</th>
<th class="num">Tare Weight</th>
<th class="num">Load Capacity</th>
<th>Customer Name</th>
<th>Customer ID</th>
<th>Cargo Type</th>
<th>Container No</th>
<th>Chassis No</th>
@@ -2918,7 +2913,7 @@ export class TrainSchedulingService {
</tr>
</thead>
<tbody>
${rows || '<tr><td colspan="12">No wagons on this train set.</td></tr>'}
${rows || '<tr><td colspan="10">No wagons on this train set.</td></tr>'}
</tbody>
</table>

View File

@@ -9,6 +9,7 @@ import { Textarea } from '@/components/ui/textarea';
import { useMutation } from '@tanstack/react-query';
import { api } from '@/services/api';
import { useToast } from '@/hooks/use-toast';
import { isBackdated, nowLocalDateTimeInput } from '@/lib/no-backdate';
/**
* Customer Pickup + Proof of Delivery capture for a LOADED cargo.
@@ -27,6 +28,10 @@ export function DeliverCargoDialog({ cargoId, onSuccess }: { cargoId: string; on
toast({ title: 'Receiver name is required', variant: 'destructive' });
return;
}
if (isBackdated(pickupDate)) {
toast({ title: 'Pickup date cannot be in the past', variant: 'destructive' });
return;
}
try {
await deliver.mutateAsync({
id: cargoId,
@@ -75,6 +80,7 @@ export function DeliverCargoDialog({ cargoId, onSuccess }: { cargoId: string; on
<Label>Pickup date</Label>
<Input
type="datetime-local"
min={nowLocalDateTimeInput()}
value={pickupDate}
onChange={(e) => setPickupDate(e.target.value)}
/>

View File

@@ -18,6 +18,7 @@ import { useEffect, useState } from 'react';
import { QUERY_KEYS } from '@/constants/QUERY_KEYS';
import { useToast } from '@/hooks/use-toast';
import { isBackdated } from '@/lib/no-backdate';
import { lastMileService, type LastMileRecord } from '@/services/last-mile.service';
interface TruckDetentionModalProps {
@@ -111,6 +112,7 @@ export function TruckDetentionModal({ opened, onClose, record }: TruckDetentionM
description="Detention clock start"
value={arrived}
onChange={(v) => setArrived(v ? new Date(v) : null)}
minDate={new Date()}
clearable
/>
<DateTimePicker
@@ -118,11 +120,26 @@ export function TruckDetentionModal({ opened, onClose, record }: TruckDetentionM
description="Clock end (blank = still out)"
value={delivered}
onChange={(v) => setDelivered(v ? new Date(v) : null)}
minDate={new Date()}
clearable
/>
</Group>
<Group justify="flex-end">
<Button variant="light" loading={saveTimes.isPending} onClick={() => saveTimes.mutate()}>
<Button
variant="light"
loading={saveTimes.isPending}
onClick={() => {
// No backdating: detention times are recorded as they happen.
if (isBackdated(arrived) || isBackdated(delivered)) {
toast({
variant: 'destructive',
title: 'Detention times cannot be in the past',
});
return;
}
saveTimes.mutate();
}}
>
Save times
</Button>
</Group>

View File

@@ -6,6 +6,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { api } from '@/services/api';
import { useToast } from '@/hooks/use-toast';
import { isBackdated, nowLocalDateTimeInput } from '@/lib/no-backdate';
import { warehouseService } from '@/services/warehouse.service';
import type { WarehouseInventoryItem } from '@/types/warehouse';
import { extractErrorMessage } from './options';
@@ -440,6 +441,12 @@ export function ReleaseOrderModal({ opened, onClose, item, truckPrefill }: Relea
});
return;
}
// No backdating: gate times are recorded as they happen. The locked
// entrance (exit step) keeps its original past gate-in untouched.
if (!isEntranceLocked && isBackdated(gateInTime)) {
toast({ variant: 'destructive', title: 'Gate in time cannot be in the past' });
return;
}
if (isExitStep && (!gateOutTime || (!skipWeighing && grossWeight === ''))) {
toast({
variant: 'destructive',
@@ -447,6 +454,10 @@ export function ReleaseOrderModal({ opened, onClose, item, truckPrefill }: Relea
});
return;
}
if (isExitStep && isBackdated(gateOutTime)) {
toast({ variant: 'destructive', title: 'Gate out time cannot be in the past' });
return;
}
if (isExitStep && hasContainerWeights && selectedContainerNumbers.length === 0) {
toast({ variant: 'destructive', title: 'Select the containers loaded on this truck' });
return;
@@ -646,7 +657,7 @@ export function ReleaseOrderModal({ opened, onClose, item, truckPrefill }: Relea
</SimpleGrid>
</Stack>
)}
<TextInput label="Gate in time" type="datetime-local" value={gateInTime} onChange={(e) => setGateInTime(e.currentTarget.value)} readOnly={isEntranceLocked} />
<TextInput label="Gate in time" type="datetime-local" min={isEntranceLocked ? undefined : nowLocalDateTimeInput()} value={gateInTime} onChange={(e) => setGateInTime(e.currentTarget.value)} readOnly={isEntranceLocked} />
</Group>
{hasContainerWeights && (
<Group gap="md" align="center">
@@ -679,7 +690,7 @@ export function ReleaseOrderModal({ opened, onClose, item, truckPrefill }: Relea
<Text size="sm" c={weightMismatch ? 'red' : 'dimmed'}>
Computed net: <b>{computedNetWeight == null ? '-' : `${computedNetWeight.toLocaleString()} t`}</b>
</Text>
<TextInput label="Gate out time" type="datetime-local" value={gateOutTime} onChange={(e) => setGateOutTime(e.currentTarget.value)} disabled={!isExitStep || hasTruckLeft} />
<TextInput label="Gate out time" type="datetime-local" min={nowLocalDateTimeInput()} value={gateOutTime} onChange={(e) => setGateOutTime(e.currentTarget.value)} disabled={!isExitStep || hasTruckLeft} />
</Group>
{weightMismatch && (
<Alert icon={<Scale size={16} />} color="red" variant="light">

View File

@@ -0,0 +1,20 @@
/**
* Backdating guard for operational time entries (gate in/out, mile truck
* times, delivery pickups): times must be recorded as they happen, never
* dated back. A one-hour grace covers real-world lag (weighbridge queue,
* operator finishing the form after the event).
*/
export const BACKDATE_GRACE_MS = 60 * 60 * 1000;
/** Local-time "YYYY-MM-DDTHH:mm" for a datetime-local input's `min`. */
export const nowLocalDateTimeInput = (): string =>
new Date(Date.now() - new Date().getTimezoneOffset() * 60_000)
.toISOString()
.slice(0, 16);
/** True when the value is more than the grace period in the past. */
export const isBackdated = (value: string | Date | null | undefined): boolean => {
if (!value) return false;
const t = value instanceof Date ? value.getTime() : new Date(value).getTime();
return Number.isFinite(t) && t < Date.now() - BACKDATE_GRACE_MS;
};