Invoice genaration and seal

This commit is contained in:
hagiye
2026-06-27 00:22:38 +03:00
parent 9534950e09
commit 1d84cad99d
4 changed files with 104 additions and 20 deletions

View File

@@ -726,6 +726,12 @@ function ImportTrainDetailTable({ scheduleId }: { scheduleId: string }) {
}
/** Import Arrive Queue: arrived IMPORT trains, with Open (detail) + Auto Unload per train. */
const getPendingUnloadBookings = (train: ImportTrain) =>
train.pendingUnloadBookings ?? train.totalBookings;
const isFullyUnloaded = (train: ImportTrain) =>
Boolean(train.fullyUnloaded) || (train.totalBookings > 0 && getPendingUnloadBookings(train) === 0);
function ImportArriveQueueTab({
enabled,
onChanged,
@@ -744,9 +750,19 @@ function ImportArriveQueueTab({
const [busyId, setBusyId] = useState<string | null>(null);
const autoUnload = async (train: ImportTrain) => {
if (isFullyUnloaded(train)) {
toast({
title: 'Already unloaded',
description: `${train.trainNumber ?? 'This train'} has no remaining bookings to auto unload.`,
});
return;
}
setBusyId(train.scheduleId);
try {
const r = await autoUnloadMutation.mutateAsync(train.scheduleId);
const alreadyUnloaded = r.unloadedCount === 0 && r.skippedCount > 0 && r.failedCount === 0;
const firstReason = r.results.find((item) => item.reason)?.reason;
const extra = [
r.skippedCount ? `${r.skippedCount} skipped` : '',
r.failedCount ? `${r.failedCount} failed` : '',
@@ -754,8 +770,8 @@ function ImportArriveQueueTab({
.filter(Boolean)
.join(', ');
toast({
title: `${r.unloadedCount} unloaded`,
description: extra || undefined,
title: alreadyUnloaded ? 'Already unloaded' : `${r.unloadedCount} unloaded`,
description: alreadyUnloaded ? firstReason ?? 'This train is already in warehouse inventory.' : extra || undefined,
});
onChanged?.();
} catch (error) {
@@ -800,6 +816,8 @@ function ImportArriveQueueTab({
<Table.Tbody>
{trains.map((t: ImportTrain) => {
const isOpen = openId === t.scheduleId;
const fullyUnloaded = isFullyUnloaded(t);
const unloadedBookings = t.unloadedBookings ?? t.totalBookings - getPendingUnloadBookings(t);
return (
<Fragment key={t.scheduleId}>
<Table.Tr>
@@ -817,7 +835,14 @@ function ImportArriveQueueTab({
<Table.Td ta="center">{t.totalContainers}</Table.Td>
<Table.Td ta="center">{t.totalCargoes}</Table.Td>
<Table.Td>
<Badge color="indigo" variant="light" size="sm">{t.status}</Badge>
<Stack gap={2}>
<Badge color={fullyUnloaded ? 'green' : 'indigo'} variant="light" size="sm">
{fullyUnloaded ? 'UNLOADED' : t.status}
</Badge>
<Text size="xs" c="dimmed">
{Math.max(unloadedBookings, 0)}/{t.totalBookings} unloaded
</Text>
</Stack>
</Table.Td>
<Table.Td ta="right">
<Group gap="xs" justify="flex-end" wrap="nowrap">
@@ -831,12 +856,13 @@ function ImportArriveQueueTab({
</Button>
<Button
size="compact-xs"
color="indigo"
color={fullyUnloaded ? 'gray' : 'indigo'}
leftSection={<Truck size={14} />}
loading={busyId === t.scheduleId}
disabled={fullyUnloaded || t.totalBookings === 0}
onClick={() => autoUnload(t)}
>
Auto Unload Arrived Bookings
{fullyUnloaded ? 'Already Unloaded' : 'Auto Unload Arrived Bookings'}
</Button>
</Group>
</Table.Td>

View File

@@ -37,6 +37,12 @@ const getErrorMessage = (error: unknown) => {
return error instanceof Error ? error.message : undefined;
};
const getPendingUnloadBookings = (train: ImportTrain) =>
train.pendingUnloadBookings ?? train.totalBookings;
const isFullyUnloaded = (train: ImportTrain) =>
Boolean(train.fullyUnloaded) || (train.totalBookings > 0 && getPendingUnloadBookings(train) === 0);
function ImportTrainDetailRows({ scheduleId }: { scheduleId: string }) {
const { data: items = [], isLoading } = useImportTrainItems(scheduleId);
@@ -105,10 +111,20 @@ export default function ArrivalQueuePage() {
const [busyScheduleId, setBusyScheduleId] = useState<string | null>(null);
const unloadTrain = async (train: ImportTrain) => {
if (isFullyUnloaded(train)) {
toast({
title: 'Already unloaded',
description: `${train.trainNumber ?? 'This train'} has no remaining bookings to auto unload.`,
});
return;
}
setBusyScheduleId(train.scheduleId);
try {
const res = (await autoUnload.mutateAsync(train.scheduleId)) as { data: AutoUnloadArrivedResult };
const result = res.data;
const alreadyUnloaded = result.unloadedCount === 0 && result.skippedCount > 0 && result.failedCount === 0;
const firstReason = result.results.find((item) => item.reason)?.reason;
const details = [
result.skippedCount ? `${result.skippedCount} skipped` : '',
result.failedCount ? `${result.failedCount} failed` : '',
@@ -117,8 +133,10 @@ export default function ArrivalQueuePage() {
.join(', ');
toast({
title: `${result.unloadedCount} booking(s) unloaded`,
description: details || `${train.trainNumber ?? 'Train'} moved into warehouse inventory.`,
title: alreadyUnloaded ? 'Already unloaded' : `${result.unloadedCount} booking(s) unloaded`,
description: alreadyUnloaded
? firstReason ?? `${train.trainNumber ?? 'Train'} is already in warehouse inventory.`
: details || `${train.trainNumber ?? 'Train'} moved into warehouse inventory.`,
});
} catch (error) {
toast({
@@ -181,6 +199,8 @@ export default function ArrivalQueuePage() {
<Table.Tbody>
{trains.map((train: ImportTrain) => {
const isOpen = openScheduleId === train.scheduleId;
const fullyUnloaded = isFullyUnloaded(train);
const unloadedBookings = train.unloadedBookings ?? train.totalBookings - getPendingUnloadBookings(train);
return (
<Fragment key={train.scheduleId}>
<Table.Tr>
@@ -204,9 +224,14 @@ export default function ArrivalQueuePage() {
<Table.Td ta="center">{train.totalContainers}</Table.Td>
<Table.Td ta="center">{train.totalCargoes}</Table.Td>
<Table.Td>
<Badge variant="light" color="teal" size="sm">
{train.status}
</Badge>
<Stack gap={2}>
<Badge variant="light" color={fullyUnloaded ? 'green' : 'teal'} size="sm">
{fullyUnloaded ? 'UNLOADED' : train.status}
</Badge>
<Text size="xs" c="dimmed">
{Math.max(unloadedBookings, 0)}/{train.totalBookings} unloaded
</Text>
</Stack>
</Table.Td>
<Table.Td>
<Group gap="xs" justify="flex-end" wrap="nowrap">
@@ -220,12 +245,13 @@ export default function ArrivalQueuePage() {
</Button>
<Button
size="compact-xs"
color="orange"
color={fullyUnloaded ? 'gray' : 'orange'}
leftSection={busyScheduleId === train.scheduleId ? <PackageOpen size={14} /> : <Truck size={14} />}
loading={busyScheduleId === train.scheduleId}
disabled={fullyUnloaded || train.totalBookings === 0}
onClick={() => unloadTrain(train)}
>
Auto Unload
{fullyUnloaded ? 'Already Unloaded' : 'Auto Unload'}
</Button>
</Group>
</Table.Td>

View File

@@ -430,6 +430,9 @@ export interface ImportTrain {
totalBookings: number;
totalContainers: number;
totalCargoes: number;
unloadedBookings?: number;
pendingUnloadBookings?: number;
fullyUnloaded?: boolean;
status: string;
}