mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 07:15:45 +00:00
chnages
This commit is contained in:
@@ -276,9 +276,8 @@ export default function GlCreateBookingForm() {
|
||||
const [trainScheduleId, setTrainScheduleId] = useState("");
|
||||
const [contractRouteId, setContractRouteId] = useState<string | null>(null);
|
||||
const [notes, setNotes] = useState("");
|
||||
// The customer states the billing currency on their shipment request — GL
|
||||
// books in it. Intercity is always ETB (the API enforces this too).
|
||||
const [paymentCurrency, setPaymentCurrency] = useState<"USD" | "ETB">("USD");
|
||||
// ponytail: ETB-only for now — widen back to "USD" | "ETB" when multi-currency billing returns.
|
||||
const [paymentCurrency, setPaymentCurrency] = useState<"USD" | "ETB">("ETB");
|
||||
// What the containers carry — captured per booking (moved off the contract).
|
||||
const [cargoDescription, setCargoDescription] = useState("");
|
||||
const [containerLines, setContainerLines] = useState<ContainerLineDraft[]>([]);
|
||||
@@ -449,9 +448,6 @@ export default function GlCreateBookingForm() {
|
||||
}
|
||||
if (bookingRequest.contractRouteId)
|
||||
setContractRouteId(bookingRequest.contractRouteId);
|
||||
if (bookingRequest.paymentCurrency === "USD" || bookingRequest.paymentCurrency === "ETB") {
|
||||
setPaymentCurrency(bookingRequest.paymentCurrency);
|
||||
}
|
||||
if (bookingRequest.notes) setNotes(bookingRequest.notes);
|
||||
}, [bookingRequest, prefilled]);
|
||||
|
||||
@@ -1761,11 +1757,7 @@ export default function GlCreateBookingForm() {
|
||||
Billing currency
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" mb={8}>
|
||||
{isIntercity
|
||||
? "Intercity shipments are invoiced in ETB."
|
||||
: bookingRequest?.paymentCurrency
|
||||
? "Requested by the customer on their shipment request."
|
||||
: "The contract is quoted in USD — pick the currency this shipment is invoiced in."}
|
||||
Shipments are invoiced in ETB.
|
||||
</Text>
|
||||
<CurrencySelector
|
||||
value={isIntercity ? "ETB" : paymentCurrency}
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
import { Alert, Badge, Button, Card, Group, SimpleGrid, Stack, Text } from "@mantine/core";
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { AlertTriangle, RefreshCw, Send, ShieldCheck } from "lucide-react";
|
||||
|
||||
import { useAuth } from "@/auth/useAuth";
|
||||
import { FREIGHT_PERMS, hasPermission } from "@/lib/permissions";
|
||||
import { api } from "@/services/api";
|
||||
import type { EimsInvoiceStatus } from "@/types/eims";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
|
||||
const STATUS_COLOR: Record<EimsInvoiceStatus, string> = {
|
||||
NOT_SUBMITTED: "gray",
|
||||
SUBMITTING: "yellow",
|
||||
REGISTERED: "edr-green",
|
||||
FAILED: "red",
|
||||
UNKNOWN: "orange",
|
||||
};
|
||||
|
||||
const STATUS_LABEL: Record<EimsInvoiceStatus, string> = {
|
||||
NOT_SUBMITTED: "Not filed",
|
||||
SUBMITTING: "Filing…",
|
||||
REGISTERED: "Filed",
|
||||
FAILED: "Rejected",
|
||||
UNKNOWN: "Unacknowledged",
|
||||
};
|
||||
|
||||
function Field({ label, value }: { label: string; value?: string | number | null }) {
|
||||
return (
|
||||
<Stack gap={2}>
|
||||
<Text size="xs" fw={600} c="edr-muted" tt="uppercase">
|
||||
{label}
|
||||
</Text>
|
||||
<Text size="sm" c="edr-text" style={{ wordBreak: "break-all" }}>
|
||||
{value === null || value === undefined || value === "" ? "—" : value}
|
||||
</Text>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* MoR EIMS filing state for one invoice, with the manual actions.
|
||||
*
|
||||
* Filing normally happens on the API's cron sweep, not here — these controls exist for controlled
|
||||
* testing and for the exceptional cases the sweep deliberately refuses: a rejected invoice that
|
||||
* needs re-filing, and an unacknowledged one that has blocked all further filing.
|
||||
*/
|
||||
export function EimsFilingCard({ invoiceId }: { invoiceId: string }) {
|
||||
const { user } = useAuth();
|
||||
const { toast } = useToast();
|
||||
const canFile = hasPermission(user, FREIGHT_PERMS.invoices.eimsRegister);
|
||||
|
||||
const { data: eims, isLoading } = useQuery(
|
||||
api.invoices.eimsStatus.queryOptions({ input: { id: invoiceId }, enabled: Boolean(invoiceId) }),
|
||||
);
|
||||
|
||||
const register = useMutation(
|
||||
api.invoices.eimsRegister.mutationOptions({
|
||||
onSuccess: (result) =>
|
||||
toast({
|
||||
title: result.eimsIrn ? "Filed with MoR" : "Filing finished",
|
||||
description: result.eimsIrn ? `IRN ${result.eimsIrn}` : `Status ${result.eimsStatus}`,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
const verify = useMutation(
|
||||
api.invoices.eimsVerify.mutationOptions({
|
||||
onSuccess: (result) =>
|
||||
toast({
|
||||
title: "MoR confirmed the filing",
|
||||
description: `Document ${result.body?.DocumentDetails?.DocumentNumber ?? "—"}`,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
if (isLoading || !eims) return null;
|
||||
|
||||
const status = eims.eimsStatus;
|
||||
const busy = register.isPending || verify.isPending;
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Stack gap="lg">
|
||||
<Group justify="space-between">
|
||||
<Text fw={600} c="edr-text">
|
||||
MoR e-invoicing
|
||||
</Text>
|
||||
<Badge color={STATUS_COLOR[status] ?? "gray"} variant="light" size="sm" radius="md" fw={600}>
|
||||
{STATUS_LABEL[status] ?? status}
|
||||
</Badge>
|
||||
</Group>
|
||||
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, lg: 4 }} spacing="lg">
|
||||
<Field label="IRN" value={eims.eimsIrn} />
|
||||
<Field label="Invoice counter" value={eims.eimsInvoiceCounter} />
|
||||
<Field
|
||||
label="Submitted"
|
||||
value={eims.eimsSubmittedAt ? new Date(eims.eimsSubmittedAt).toLocaleString() : null}
|
||||
/>
|
||||
<Field label="Acknowledged" value={eims.eimsAckDate} />
|
||||
</SimpleGrid>
|
||||
|
||||
{status === "UNKNOWN" && (
|
||||
<Alert color="orange" icon={<AlertTriangle size={16} />} title="All filing is blocked">
|
||||
This invoice was sent but never acknowledged, so its IRN is unknown and no further
|
||||
invoice can be filed. Confirm its status with MoR, then have a supervisor record the IRN
|
||||
or discard the attempt.
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{eims.eimsLastError && (
|
||||
<Alert
|
||||
color={status === "FAILED" ? "red" : "orange"}
|
||||
icon={<AlertTriangle size={16} />}
|
||||
title={`MoR reported: ${eims.eimsLastError.kind}`}
|
||||
>
|
||||
{eims.eimsLastError.message}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{canFile && (
|
||||
<Group gap="sm">
|
||||
{/* UNKNOWN is never re-filed from here: resubmitting risks a duplicate registration. */}
|
||||
{status !== "REGISTERED" && status !== "UNKNOWN" && (
|
||||
<Button
|
||||
size="xs"
|
||||
variant="light"
|
||||
radius="md"
|
||||
loading={register.isPending}
|
||||
disabled={busy}
|
||||
leftSection={status === "FAILED" ? <RefreshCw size={14} /> : <Send size={14} />}
|
||||
onClick={() => register.mutate({ id: invoiceId })}
|
||||
>
|
||||
{status === "FAILED" ? "File again" : "File with MoR"}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{eims.eimsIrn && (
|
||||
<Button
|
||||
size="xs"
|
||||
variant="light"
|
||||
radius="md"
|
||||
loading={verify.isPending}
|
||||
disabled={busy}
|
||||
leftSection={<ShieldCheck size={14} />}
|
||||
onClick={() => verify.mutate({ id: invoiceId })}
|
||||
>
|
||||
Verify with MoR
|
||||
</Button>
|
||||
)}
|
||||
</Group>
|
||||
)}
|
||||
</Stack>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default EimsFilingCard;
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
ArrowLeftRight,
|
||||
BookOpen,
|
||||
Boxes,
|
||||
Building2,
|
||||
BarChart3,
|
||||
@@ -286,19 +287,21 @@ export const buildSidebarSections = (demoItems: SidebarItem[]): SidebarSection[]
|
||||
label: "Compliance & Alerts",
|
||||
href: "/dashboard/compliance",
|
||||
icon: <ShieldCheck />,
|
||||
permission: FREIGHT_PERMS.fleet.view,
|
||||
permission: [FREIGHT_PERMS.compliance.view, FREIGHT_PERMS.fleet.view],
|
||||
},
|
||||
{
|
||||
label: "Incidents",
|
||||
href: "/dashboard/incidents",
|
||||
icon: <FileText />,
|
||||
// No dedicated backend key exists for incidents yet — stuck on the
|
||||
// blanket fleet:view fallback until one is added.
|
||||
permission: FREIGHT_PERMS.fleet.view,
|
||||
},
|
||||
{
|
||||
label: "Procurement",
|
||||
href: "/dashboard/procurement",
|
||||
icon: <Package />,
|
||||
permission: FREIGHT_PERMS.fleet.view,
|
||||
permission: [FREIGHT_PERMS.procurement.view, FREIGHT_PERMS.fleet.view],
|
||||
},
|
||||
{
|
||||
label: "Financial Reports",
|
||||
@@ -483,13 +486,13 @@ export const buildSidebarSections = (demoItems: SidebarItem[]): SidebarSection[]
|
||||
label: "File settings",
|
||||
href: "/dashboard/file-settings",
|
||||
icon: <Paperclip />,
|
||||
permission: FREIGHT_PERMS.admin,
|
||||
permission: [FREIGHT_PERMS.settings.fileUpload.view, FREIGHT_PERMS.admin],
|
||||
},
|
||||
{
|
||||
label: "Dropdown settings",
|
||||
href: "/dashboard/dropdown-settings",
|
||||
icon: <Settings />,
|
||||
permission: FREIGHT_PERMS.admin,
|
||||
permission: [FREIGHT_PERMS.settings.dropdown.view, FREIGHT_PERMS.admin],
|
||||
},
|
||||
{
|
||||
label: "Contract templates",
|
||||
@@ -501,6 +504,16 @@ export const buildSidebarSections = (demoItems: SidebarItem[]): SidebarSection[]
|
||||
FREIGHT_PERMS.admin,
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Portal content",
|
||||
href: "/dashboard/portal-content",
|
||||
icon: <BookOpen />,
|
||||
permission: [
|
||||
FREIGHT_PERMS.settings.supportContent.view,
|
||||
FREIGHT_PERMS.settings.supportContent.manage,
|
||||
FREIGHT_PERMS.admin,
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Audit logs",
|
||||
href: "/dashboard/audit-logs",
|
||||
@@ -521,12 +534,12 @@ export const buildSidebarSections = (demoItems: SidebarItem[]): SidebarSection[]
|
||||
{
|
||||
label: "Trade access",
|
||||
href: "/dashboard/configuration/trade-access",
|
||||
permission: FREIGHT_PERMS.admin,
|
||||
permission: [FREIGHT_PERMS.tradeAccess.view, FREIGHT_PERMS.admin],
|
||||
},
|
||||
{
|
||||
label: "Exchange rate",
|
||||
href: "/dashboard/configuration/exchange-rate",
|
||||
permission: FREIGHT_PERMS.admin,
|
||||
permission: [FREIGHT_PERMS.settings.exchangeRate.view, FREIGHT_PERMS.admin],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user