mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 00:38:11 +00:00
get clearance fix
This commit is contained in:
@@ -134,15 +134,23 @@ export function FeePreviewModal({ opened, onClose, inventoryId }: FeePreviewModa
|
||||
const pdfWindow = window.open('', '_blank');
|
||||
try {
|
||||
const releasedItem = await gateClear.mutateAsync(inventoryId) as WarehouseInventoryItem;
|
||||
const documentResponse = await warehouseService.downloadReleaseDocument(inventoryId);
|
||||
const filename = `release-${releasedItem?.booking?.reference ?? releasedItem?.bookingId ?? inventoryId}.pdf`;
|
||||
const opened = openPdfBlob(documentResponse.data, filename, pdfWindow);
|
||||
toast({
|
||||
title: 'Gate clearance recorded',
|
||||
description: opened
|
||||
? 'The release PDF opened in a browser tab.'
|
||||
: 'The browser blocked the preview tab, so the PDF was downloaded.',
|
||||
});
|
||||
try {
|
||||
const documentResponse = await warehouseService.downloadReleaseDocument(inventoryId);
|
||||
const filename = `release-${releasedItem?.booking?.reference ?? releasedItem?.bookingId ?? inventoryId}.pdf`;
|
||||
const opened = openPdfBlob(documentResponse.data, filename, pdfWindow);
|
||||
toast({
|
||||
title: 'Gate clearance recorded',
|
||||
description: opened
|
||||
? 'The release PDF opened in a browser tab.'
|
||||
: 'The browser blocked the preview tab, so the PDF was downloaded.',
|
||||
});
|
||||
} catch (documentError) {
|
||||
pdfWindow?.close();
|
||||
toast({
|
||||
title: 'Gate clearance recorded',
|
||||
description: `Release paper could not be opened: ${extractErrorMessage(documentError)}`,
|
||||
});
|
||||
}
|
||||
onClose();
|
||||
} catch (error) {
|
||||
pdfWindow?.close();
|
||||
|
||||
@@ -358,6 +358,8 @@ export const URL_CONSTANTS = {
|
||||
WAREHOUSE_INVOICES: {
|
||||
BASE: '/warehouse-fee-invoices',
|
||||
BY_ID: (id: string) => `/warehouse-fee-invoices/${id}`,
|
||||
DOCUMENT: (id: string) => `/warehouse-fee-invoices/${id}/document`,
|
||||
RECEIPT: (id: string) => `/warehouse-fee-invoices/${id}/receipt`,
|
||||
CANCEL: (id: string) => `/warehouse-fee-invoices/${id}/cancel`,
|
||||
PAY: (id: string) => `/warehouse-fee-invoices/${id}/pay`,
|
||||
GENERATE: (inventoryId: string) => `/warehouse-inventory/${inventoryId}/generate-fee-invoice`,
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export const API_BASE_URL = 'https://edrfreightapi.triaplc.com';
|
||||
|
||||
//export const API_BASE_URL = 'http://localhost:3001';
|
||||
export const API_BASE_URL =
|
||||
import.meta.env.VITE_API_URL?.replace(/\/+$/, '') || 'https://edrfreightapi.triaplc.com';
|
||||
|
||||
@@ -15,19 +15,21 @@ import {
|
||||
Text,
|
||||
TextInput,
|
||||
} from '@mantine/core';
|
||||
import { Ban, CreditCard, Eye, Search } from 'lucide-react';
|
||||
import { Ban, CreditCard, DoorOpen, Download, Eye, Receipt, Search } from 'lucide-react';
|
||||
import { DataTable, type ColumnDef } from '@edr/ui-common';
|
||||
|
||||
import { PageContainer, PageHeader } from '@/components/page';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '@/services/api';
|
||||
import { warehouseService } from '@/services/warehouse.service';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import {
|
||||
WAREHOUSE_INVOICE_STATUSES,
|
||||
type WarehouseFeeInvoice,
|
||||
type WarehouseInvoiceStatus,
|
||||
} from '@/types/warehouse';
|
||||
import { openPdfBlob } from '@/components/warehouses/pdf';
|
||||
|
||||
const STATUS_COLOR: Record<WarehouseInvoiceStatus, string> = {
|
||||
DRAFT: 'gray',
|
||||
@@ -158,16 +160,86 @@ function InvoiceDetailModal({ id, onClose }: { id: string | null; onClose: () =>
|
||||
);
|
||||
const pay = useMutation(api.warehouses.payInvoice.mutationOptions());
|
||||
const cancel = useMutation(api.warehouses.cancelInvoice.mutationOptions());
|
||||
const gateClear = useMutation(api.warehouses.gateClearance.mutationOptions());
|
||||
const [payAmount, setPayAmount] = useState<number | ''>('');
|
||||
|
||||
const canPay = inv && (inv.status === 'ISSUED' || inv.status === 'PARTIALLY_PAID');
|
||||
const canGateClear = inv?.status === 'PAID' && Boolean(inv.inventoryId);
|
||||
|
||||
const downloadInvoicePdf = async (invoice: WarehouseFeeInvoice) => {
|
||||
try {
|
||||
const response = await warehouseService.downloadInvoiceDocument(invoice.id);
|
||||
openPdfBlob(response.data, `warehouse-invoice-${invoice.invoiceNumber}.pdf`);
|
||||
} catch (e) {
|
||||
toast({ variant: 'destructive', title: 'Invoice download failed', description: (e as Error)?.message });
|
||||
}
|
||||
};
|
||||
|
||||
const downloadReceiptPdf = async (invoice: WarehouseFeeInvoice) => {
|
||||
try {
|
||||
const response = await warehouseService.downloadInvoiceReceipt(invoice.id);
|
||||
openPdfBlob(response.data, `warehouse-receipt-${invoice.invoiceNumber}.pdf`);
|
||||
} catch (e) {
|
||||
toast({ variant: 'destructive', title: 'Receipt download failed', description: (e as Error)?.message });
|
||||
}
|
||||
};
|
||||
|
||||
const handleGateClearance = async (invoice: WarehouseFeeInvoice) => {
|
||||
if (!invoice.inventoryId) {
|
||||
toast({
|
||||
variant: 'destructive',
|
||||
title: 'Gate clearance failed',
|
||||
description: 'This invoice is not linked to an inventory item.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const pdfWindow = window.open('', '_blank');
|
||||
try {
|
||||
const releasedItem = await gateClear.mutateAsync(invoice.inventoryId);
|
||||
let documentResponse: Awaited<ReturnType<typeof warehouseService.downloadReleaseDocument>>;
|
||||
try {
|
||||
documentResponse = await warehouseService.downloadReleaseDocument(invoice.inventoryId);
|
||||
} catch (documentError) {
|
||||
pdfWindow?.close();
|
||||
toast({
|
||||
variant: 'destructive',
|
||||
title: 'Exit paper failed',
|
||||
description: (documentError as Error)?.message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const filename = `release-${releasedItem?.booking?.reference ?? releasedItem?.bookingId ?? invoice.inventoryId}.pdf`;
|
||||
const opened = openPdfBlob(documentResponse.data, filename, pdfWindow);
|
||||
toast({
|
||||
title: 'Gate clearance recorded',
|
||||
description: opened
|
||||
? 'The exit paper opened in a browser tab.'
|
||||
: 'The browser blocked the preview tab, so the exit paper was downloaded.',
|
||||
});
|
||||
onClose();
|
||||
} catch (e) {
|
||||
pdfWindow?.close();
|
||||
toast({
|
||||
variant: 'destructive',
|
||||
title: 'Gate clearance failed',
|
||||
description: (e as Error)?.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handlePay = async () => {
|
||||
if (!inv || !payAmount) return;
|
||||
try {
|
||||
await pay.mutateAsync({ id: inv.id, payload: { amount: Number(payAmount), method: 'MANUAL' } });
|
||||
toast({ title: 'Payment recorded' });
|
||||
const paidInvoice = await pay.mutateAsync({ id: inv.id, payload: { amount: Number(payAmount), method: 'MANUAL' } });
|
||||
setPayAmount('');
|
||||
if (paidInvoice.status === 'PAID') {
|
||||
toast({ title: 'Payment recorded', description: 'Downloading receipt, then generating gate clearance and exit paper.' });
|
||||
await downloadReceiptPdf(paidInvoice);
|
||||
await handleGateClearance(paidInvoice);
|
||||
} else {
|
||||
toast({ title: 'Payment recorded' });
|
||||
}
|
||||
} catch (e) {
|
||||
toast({ variant: 'destructive', title: 'Payment failed', description: (e as Error)?.message });
|
||||
}
|
||||
@@ -247,6 +319,34 @@ function InvoiceDetailModal({ id, onClose }: { id: string | null; onClose: () =>
|
||||
)}
|
||||
|
||||
<Group justify="flex-end" mt="sm">
|
||||
<Button
|
||||
variant="light"
|
||||
color="gray"
|
||||
leftSection={<Download size={16} />}
|
||||
onClick={() => downloadInvoicePdf(inv)}
|
||||
>
|
||||
Invoice PDF
|
||||
</Button>
|
||||
{Number(inv.paidAmount) > 0 && (
|
||||
<Button
|
||||
variant="light"
|
||||
color="teal"
|
||||
leftSection={<Receipt size={16} />}
|
||||
onClick={() => downloadReceiptPdf(inv)}
|
||||
>
|
||||
Receipt PDF
|
||||
</Button>
|
||||
)}
|
||||
{canGateClear && (
|
||||
<Button
|
||||
color="edr-green"
|
||||
leftSection={<DoorOpen size={16} />}
|
||||
loading={gateClear.isPending}
|
||||
onClick={() => handleGateClearance(inv)}
|
||||
>
|
||||
Gate clearance & exit paper
|
||||
</Button>
|
||||
)}
|
||||
{inv.status !== 'PAID' && inv.status !== 'CANCELLED' && (
|
||||
<Button variant="light" color="red" leftSection={<Ban size={16} />} loading={cancel.isPending} onClick={handleCancel}>
|
||||
Cancel invoice
|
||||
|
||||
@@ -263,6 +263,14 @@ export const warehouseService = {
|
||||
}),
|
||||
getInvoice: (id: string) =>
|
||||
apiClient.get<WarehouseFeeInvoice>(URL_CONSTANTS.WAREHOUSE_INVOICES.BY_ID(id)),
|
||||
downloadInvoiceDocument: (id: string) =>
|
||||
apiClient.get<Blob>(URL_CONSTANTS.WAREHOUSE_INVOICES.DOCUMENT(id), {
|
||||
responseType: 'blob',
|
||||
}),
|
||||
downloadInvoiceReceipt: (id: string) =>
|
||||
apiClient.get<Blob>(URL_CONSTANTS.WAREHOUSE_INVOICES.RECEIPT(id), {
|
||||
responseType: 'blob',
|
||||
}),
|
||||
invoicesForInventory: (inventoryId: string) =>
|
||||
apiClient.get<WarehouseFeeInvoice[]>(URL_CONSTANTS.WAREHOUSE_INVOICES.FOR_INVENTORY(inventoryId)),
|
||||
invoicesForBooking: (bookingId: string) =>
|
||||
|
||||
Reference in New Issue
Block a user