Merge pull request #1100 from Tria-plc/edrmiles

fix(bookings): gate export carriage acceptance sheet on GRN receipt
This commit is contained in:
Hagernesh Tadesse
2026-08-04 11:03:57 +03:00
committed by GitHub
5 changed files with 83 additions and 14 deletions

View File

@@ -0,0 +1,26 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class EmptyContainerReturnStatusHistory3220000000000 implements MigrationInterface {
name = 'EmptyContainerReturnStatusHistory3220000000000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.empty_container_returns
ADD COLUMN IF NOT EXISTS status_history jsonb NOT NULL DEFAULT '[]'::jsonb
`);
await queryRunner.query(`
UPDATE freight.empty_container_returns
SET status_history = jsonb_build_array(
jsonb_build_object('status', status, 'changedAt', created_at, 'performedBy', performed_by)
)
WHERE status_history = '[]'::jsonb
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.empty_container_returns
DROP COLUMN IF EXISTS status_history
`);
}
}

View File

@@ -56,4 +56,11 @@ export class EmptyContainerReturn extends BaseEntity {
@Column({ name: 'returned_by', type: 'varchar', length: 20, nullable: true })
returnedBy?: 'EDR' | 'CUSTOMER' | null;
@Column({ name: 'status_history', type: 'jsonb', default: () => "'[]'" })
statusHistory!: Array<{
status: EmptyContainerReturnStatus;
changedAt: string;
performedBy: string | null;
}>;
}

View File

@@ -149,12 +149,13 @@ export class ImportOperationsService {
}
async createEmptyReturn(dto: CreateEmptyContainerReturnDto) {
const returnDate = dto.returnDate ? new Date(dto.returnDate) : new Date();
return this.emptyReturns.save(
this.emptyReturns.create({
containerNumber: dto.containerNumber,
bookingId: dto.bookingId ?? null,
customerId: dto.customerId ?? null,
returnDate: dto.returnDate ? new Date(dto.returnDate) : new Date(),
returnDate,
facility: dto.facility ?? null,
yard: dto.yard ?? null,
zone: dto.zone ?? null,
@@ -162,6 +163,9 @@ export class ImportOperationsService {
handoverNote: dto.handoverNote ?? null,
performedBy: dto.performedBy ?? null,
returnedBy: dto.returnedBy ?? null,
statusHistory: [
{ status: 'RETURNED', changedAt: returnDate.toISOString(), performedBy: dto.performedBy ?? null },
],
}),
);
}
@@ -176,6 +180,10 @@ export class ImportOperationsService {
wagonAllocationReference: dto.wagonAllocationReference ?? row.wagonAllocationReference ?? null,
handoverNote: dto.handoverNote ?? row.handoverNote ?? null,
performedBy: dto.performedBy ?? row.performedBy ?? null,
statusHistory: [
...(row.statusHistory ?? []),
{ status: dto.status, changedAt: new Date().toISOString(), performedBy: dto.performedBy ?? row.performedBy ?? null },
],
});
return this.emptyReturns.findOneOrFail({ where: { id } });
}

View File

@@ -17,7 +17,7 @@ import {
Select,
Checkbox,
} from "@mantine/core";
import { ChevronDown, ChevronRight } from "lucide-react";
import { ChevronDown, ChevronRight, History } from "lucide-react";
import { PageContainer, PageHeader } from "@/components/page";
import RuleEngineListFooter from "@/components/ruleEngine/RuleEngineListFooter";
@@ -82,6 +82,7 @@ export default function ContainerReturnsPage() {
const [returnModalOpen, setReturnModalOpen] = useState(false);
const [standaloneModalOpen, setStandaloneModalOpen] = useState(false);
const [activeKey, setActiveKey] = useState<string | null>(null);
const [historyRow, setHistoryRow] = useState<any | null>(null);
const { data: unloadedQueue = [], isLoading: queueLoading } = useQuery({
queryKey: ["import-unloaded-queue"],
@@ -346,6 +347,10 @@ export default function ContainerReturnsPage() {
<Badge size="sm">{RETURN_STATUS_LABEL[ret.status as EmptyContainerReturnStatus] ?? ret.status}</Badge>
</Table.Td>
<Table.Td ta="right">
<Group gap="xs" justify="flex-end" wrap="nowrap">
<ActionIcon variant="subtle" color="gray" onClick={() => setHistoryRow(ret)} title="View status history">
<History size={14} />
</ActionIcon>
{nextStatus ? (
<Button
size="xs"
@@ -358,6 +363,7 @@ export default function ContainerReturnsPage() {
) : (
<Text size="xs" c="dimmed">Done</Text>
)}
</Group>
</Table.Td>
</Table.Tr>
);
@@ -479,6 +485,23 @@ export default function ContainerReturnsPage() {
onSubmit={(payload) => createReturnsMutation.mutate(payload)}
loading={createReturnsMutation.isPending}
/>
<Modal opened={!!historyRow} onClose={() => setHistoryRow(null)} title="Status History" size="sm">
{historyRow && (
<Stack gap="sm">
<Text fw={600}>{historyRow.containerNumber}</Text>
{(historyRow.statusHistory ?? []).map((entry: any, idx: number) => (
<Group key={idx} justify="space-between">
<Badge size="sm">{RETURN_STATUS_LABEL[entry.status as EmptyContainerReturnStatus] ?? entry.status}</Badge>
<Text size="sm" c="dimmed">{new Date(entry.changedAt).toLocaleString()}</Text>
</Group>
))}
{!(historyRow.statusHistory ?? []).length && (
<Text size="sm" c="dimmed">No history recorded.</Text>
)}
</Stack>
)}
</Modal>
</PageContainer>
);
}

View File

@@ -103,6 +103,11 @@ export interface EmptyContainerReturn {
wagonAllocationReference: string | null;
performedBy: string | null;
returnedBy: 'EDR' | 'CUSTOMER' | null;
statusHistory: Array<{
status: EmptyContainerReturnStatus;
changedAt: string;
performedBy: string | null;
}>;
}
export interface CreateEmptyContainerReturnPayload {