feat(freight-backoffice): support DJF in booking, contract and warehouse screens

Currency dropdowns/pickers (AdditionalPaymentsTab, ClearanceChargesTab,
PhasedClearanceActionPanel, AdviseDutyCard, ContractRequestsPage,
ruleEngine/resources, WarehouseRulesPage, VehicleDetailPage,
FeePreviewModal) offer DJF alongside ETB/USD; GlCreateBookingForm's
currency selector gets allowDjf next to allowUsd. Narrow 'ETB'|'USD'
type unions widened to include 'DJF' across the warehouse
billingCurrency plumbing (useWarehouses, warehouse.service, api.ts)
and the customer/invoice types.

Ad-hoc money() formatters (BookingTrucksPanel, AccrualDashboard,
ImportTrucksPage, EmptyReturnRequestsPage) and formatMoney call sites
that hardcoded 2 decimals (wagon-cancellation cards, BookingRequestDetailPage,
WagonCancellationsPage, PaymentsPage, WarehouseInvoicesPage) now use
currencyDecimals() from @edr/ui-common so DJF renders with 0 decimals
instead of forced cents. The 3 duplicate overview formatCurrency/
formatAmount helpers (typed 'ETB'|'USD') widen to accept any currency.

Two correctness fixes: OverviewRecentBookingsTable's currency==='USD'
? 'USD' : 'ETB' was mislabeling every non-USD currency as ETB; and
WarehouseInvoicesPage's gateway-method default now routes any
non-ETB currency (not just USD) to WAAFI, so DJF invoices get a
working default instead of TELEBIRR (ETB-only).

Claude-Session: https://claude.ai/code/session_01CZy77vCWhka3pnmVF9NDkL
This commit is contained in:
ghost2023
2026-09-04 11:53:56 +03:00
parent 34c49e4c50
commit 214f96dbae
30 changed files with 61 additions and 47 deletions

View File

@@ -36,7 +36,7 @@ import { downloadBookingFile, fetchViewableFile } from "@/services/files.service
import { formatDate, formatDateTime } from "@/lib/format";
import { extractErrorMessage } from "@/utils/errorExtractor";
const CURRENCIES = ["ETB", "USD"];
const CURRENCIES = ["ETB", "USD", "DJF"];
const STATUS_META: Record<Freight.AdditionalChargeStatus, { label: string; color: string }> = {
DRAFT: { label: "Draft", color: "gray" },

View File

@@ -2,6 +2,7 @@ import { useMemo, useState } from "react";
import { useQueries, useQuery } from "@tanstack/react-query";
import { Button, Center, Group, Loader, SimpleGrid, Stack, Table, Text } from "@mantine/core";
import { Coins, Truck } from "lucide-react";
import { currencyDecimals } from "@edr/ui-common";
import { api } from "@/services/api";
import { FeePreviewModal } from "@/components/warehouses/FeePreviewModal";
@@ -12,8 +13,8 @@ import { MetricTile } from "./MetricTile";
const money = (amount: number, currency: string) =>
`${Number(amount).toLocaleString(undefined, {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
minimumFractionDigits: currencyDecimals(currency),
maximumFractionDigits: currencyDecimals(currency),
})} ${currency === "ETB" ? "Birr (ETB)" : currency}`;
/**

View File

@@ -2,7 +2,7 @@ import { useEffect, useMemo, useState } from "react";
import { Button, Group, Modal, Select, Stack, Text, TextInput } from "@mantine/core";
import { useMutation, useQuery } from "@tanstack/react-query";
import toast from "react-hot-toast";
import { OperationDatePicker } from "@edr/ui-common";
import { OperationDatePicker, currencyDecimals } from "@edr/ui-common";
import { api } from "@/auth/http";
import { api as rpc } from "@/services/api";
@@ -223,7 +223,7 @@ export function RebookWagonCancellationModal({
<Text size="sm">
{cancellation.booking?.reference ?? cancellation.bookingId} ·{" "}
{cancellation.wagonsCancelled} wagon(s) · credit{" "}
{formatMoney(cancellation.creditAmount, cancellation.feeCurrency, 2)}
{formatMoney(cancellation.creditAmount, cancellation.feeCurrency, currencyDecimals(cancellation.feeCurrency))}
</Text>
<Text size="sm" fw={600}>
Shipment day

View File

@@ -8,6 +8,7 @@ import { api } from "@/auth/http";
import { useAuth } from "@/auth/useAuth";
import { SectionCard } from "@/components/bookings/detail/SectionCard";
import { formatDate, formatMoney } from "@/lib/format";
import { currencyDecimals } from "@edr/ui-common";
import { RebookWagonCancellationModal } from "./RebookWagonCancellationModal";
import {
canRebookWagonCancellations,
@@ -73,7 +74,7 @@ export function WagonCancellationCreditCard({
<Group gap={8} wrap="nowrap">
<Text size="sm" fw={600}>
{Number(r.wagonsCancelled)} wagon(s) · credit{" "}
{formatMoney(Number(r.creditAmount), r.feeCurrency, 2)}
{formatMoney(Number(r.creditAmount), r.feeCurrency, currencyDecimals(r.feeCurrency))}
</Text>
<Badge color={chip.color} variant="light" size="sm" radius="md">
{chip.label}
@@ -83,7 +84,7 @@ export function WagonCancellationCreditCard({
Cancelled {formatDate(r.createdAt)}
{r.fault ? ` · ${r.fault === "EDR" ? "EDR fault (no fee)" : "customer fault"}` : ""}
{Number(r.feeAmount) > 0
? ` · fee ${formatMoney(Number(r.feeAmount), r.feeCurrency, 2)}${
? ` · fee ${formatMoney(Number(r.feeAmount), r.feeCurrency, currencyDecimals(r.feeCurrency))}${
r.feePaidAt ? " paid" : " unpaid"
}`
: ""}

View File

@@ -39,7 +39,7 @@ import {
import { formatDateTime } from "@/lib/format";
import { extractErrorMessage } from "@/utils/errorExtractor";
const CURRENCIES = ["ETB", "USD"];
const CURRENCIES = ["ETB", "USD", "DJF"];
const STATUS_META: Record<
Freight.ClearanceChargeStatus,

View File

@@ -345,7 +345,7 @@ export default function GlCreateBookingForm() {
const [notes, setNotes] = useState("");
// IMPORT bookings pick ETB or USD — starts empty so the choice is
// deliberate (required before pricing). Everything else is forced to ETB.
const [paymentCurrency, setPaymentCurrency] = useState<"USD" | "ETB" | "">("");
const [paymentCurrency, setPaymentCurrency] = useState<"USD" | "ETB" | "DJF" | "">("");
// What the containers carry — captured per booking (moved off the contract).
const [cargoDescription, setCargoDescription] = useState("");
const [containerLines, setContainerLines] = useState<ContainerLineDraft[]>([]);
@@ -1144,7 +1144,7 @@ export default function GlCreateBookingForm() {
]);
// Only IMPORT actually chooses — the rest bill ETB regardless of the state.
const effectiveCurrency: "USD" | "ETB" =
const effectiveCurrency: "USD" | "ETB" | "DJF" =
isImport && paymentCurrency ? paymentCurrency : "ETB";
const currencyError =
isImport && !paymentCurrency
@@ -2351,7 +2351,7 @@ export default function GlCreateBookingForm() {
{requestCurrencyLocked
? "The customer chose the billing currency on the shipment request — it cannot be changed."
: isImport
? "Import shipments may be invoiced in ETB or USD. USD is paid by bank transfer, not online."
? "Import shipments may be invoiced in ETB, USD or DJF. USD is paid by bank transfer, not online."
: "Shipments are invoiced in ETB."}
</Text>
<CurrencySelector
@@ -2359,6 +2359,7 @@ export default function GlCreateBookingForm() {
onChange={setPaymentCurrency}
disabled={!isImport || requestCurrencyLocked}
allowUsd={isImport}
allowDjf={isImport}
error={currencyError}
/>
</Box>

View File

@@ -1554,7 +1554,7 @@ function SecondDutyStep({
/>
<Select
label="Currency"
data={["ETB", "USD"]}
data={["ETB", "USD", "DJF"]}
value={currency}
onChange={(v) => setCurrency(v ?? "ETB")}
size="sm"
@@ -1944,7 +1944,7 @@ function DraftDeclarationStep({
/>
<Select
label="Currency"
data={["ETB", "USD"]}
data={["ETB", "USD", "DJF"]}
value={currency}
onChange={(v) => setCurrency(v ?? "ETB")}
size="sm"
@@ -2063,7 +2063,7 @@ function DutyStep({
/>
<Select
label="Currency"
data={["ETB", "USD"]}
data={["ETB", "USD", "DJF"]}
value={currency}
onChange={(v) => setCurrency(v ?? "ETB")}
size="sm"

View File

@@ -54,7 +54,7 @@ export function AdviseDutyCard({
/>
<Select
label="Currency"
data={["ETB", "USD"]}
data={["ETB", "USD", "DJF"]}
value={currency}
onChange={(v) => setCurrency(v ?? "ETB")}
size="sm"

View File

@@ -18,7 +18,7 @@ function formatDateLabel(date: string) {
return parsed.toLocaleDateString(undefined, { month: "short", day: "numeric" });
}
function formatAmount(value: number, currency: "ETB" | "USD") {
function formatAmount(value: number, currency: string) {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency,

View File

@@ -9,10 +9,9 @@ import { SummaryCard } from "./summary/SummaryCard";
function formatAmount(amount: number | null, currency: string | null) {
if (amount == null) return "—";
const code = currency === "USD" ? "USD" : "ETB";
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: code,
currency: currency || "ETB",
maximumFractionDigits: 0,
}).format(amount);
}

View File

@@ -4,7 +4,7 @@ import { KpiStrip, type KpiItem } from "@/components/page";
import type { IOverviewKpis, IOverviewPeriodTotals } from "@/types/overview";
import { CountUp } from "./CountUp";
function formatCurrency(amount: number, currency: "ETB" | "USD") {
function formatCurrency(amount: number, currency: string) {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency,
@@ -13,7 +13,7 @@ function formatCurrency(amount: number, currency: "ETB" | "USD") {
}
/** Compact form ("ETB 58.6M") — the hero cell is too narrow for nine digits. */
function formatCompactCurrency(amount: number, currency: "ETB" | "USD") {
function formatCompactCurrency(amount: number, currency: string) {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency,

View File

@@ -18,7 +18,7 @@ import { OverviewKpiStrip } from "../OverviewKpiStrip";
import { OverviewPaymentChart } from "../OverviewPaymentChart";
import { overviewChartColors } from "../overview.styles";
function formatCurrency(amount: number, currency: "ETB" | "USD") {
function formatCurrency(amount: number, currency: string) {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency,

View File

@@ -2,6 +2,7 @@ import { useMemo } from 'react';
import { ActionIcon, Badge, Card, Group, Loader, Menu, SimpleGrid, Stack, Table, Text, ThemeIcon } from '@mantine/core';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { AlertTriangle, Bell, BellOff, Check, Clock, DollarSign, MoreVertical } from 'lucide-react';
import { currencyDecimals } from '@edr/ui-common';
import { useAccrualDashboard } from '@/hooks/useWarehouses';
import { warehouseService } from '@/services/warehouse.service';
@@ -15,7 +16,8 @@ const ALERT_META: Record<AccrualAlert, { color: string; label: string }> = {
};
function money(amount: number, currency: string): string {
return `${amount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} ${currency}`;
const decimals = currencyDecimals(currency);
return `${amount.toLocaleString(undefined, { minimumFractionDigits: decimals, maximumFractionDigits: decimals })} ${currency}`;
}
function freeDaysLabel(row: AccrualDashboardRow): string {

View File

@@ -113,7 +113,7 @@ function Row({ label, value }: { label: string; value: string }) {
/** Batch 5 fee preview + Batch 6 invoice generation / gate clearance for an inventory item. */
export function FeePreviewModal({ opened, onClose, inventoryId }: FeePreviewModalProps) {
const { toast } = useToast();
const [billingCurrency, setBillingCurrency] = useState<'ETB' | 'USD'>('USD');
const [billingCurrency, setBillingCurrency] = useState<'ETB' | 'USD' | 'DJF'>('USD');
const enabledId = opened ? inventoryId ?? undefined : undefined;
const { data, isLoading } = useQuery(
api.warehouses.feePreview.queryOptions({
@@ -211,10 +211,11 @@ export function FeePreviewModal({ opened, onClose, inventoryId }: FeePreviewModa
<SegmentedControl
size="xs"
value={billingCurrency}
onChange={(value) => setBillingCurrency(value as 'ETB' | 'USD')}
onChange={(value) => setBillingCurrency(value as 'ETB' | 'USD' | 'DJF')}
data={[
{ value: 'USD', label: 'USD' },
{ value: 'ETB', label: 'Birr' },
{ value: 'DJF', label: 'DJF' },
]}
disabled={Boolean(activeInvoice)}
/>

View File

@@ -221,7 +221,7 @@ export function useOnTimeDispatch() {
}
/** Live per-item fee accrual (storage/demurrage) with alerts. */
export function useAccrualDashboard(billingCurrency?: 'ETB' | 'USD') {
export function useAccrualDashboard(billingCurrency?: 'ETB' | 'USD' | 'DJF') {
return useQuery({
queryKey: ['warehouse-fees', 'accrual-dashboard', billingCurrency ?? 'USD'],
queryFn: () => warehouseService.accrualDashboard(billingCurrency).then((r) => r.data),
@@ -598,7 +598,7 @@ export const useUpdateFeeRule = () =>
export const useDeleteFeeRule = () =>
useRuleMutation((id: string) => warehouseService.deleteFeeRule(id), ['warehouse-fee-rules']);
export function useFeePreview(inventoryId?: string, billingCurrency: 'ETB' | 'USD' = 'USD') {
export function useFeePreview(inventoryId?: string, billingCurrency: 'ETB' | 'USD' | 'DJF' = 'USD') {
return useQuery({
queryKey: ['warehouse-inventory', inventoryId, 'fee-preview', billingCurrency],
queryFn: () => warehouseService.feePreview(inventoryId as string, billingCurrency).then((r) => r.data),
@@ -649,7 +649,7 @@ export function useGenerateInvoice() {
}: {
inventoryId: string;
confirmZero?: boolean;
billingCurrency?: 'ETB' | 'USD';
billingCurrency?: 'ETB' | 'USD' | 'DJF';
}) => warehouseService.generateInvoice(inventoryId, confirmZero, billingCurrency).then((r) => r.data),
onSuccess,
});

View File

@@ -72,6 +72,7 @@ import { AdditionalPaymentsTab } from "@/components/bookings/AdditionalPaymentsT
import { getStatusMeta } from "@/features/bookings/booking-status.config";
import { toBookingListRow } from "@/features/bookings/mapBookingListRow";
import { formatDateTime, formatMoney } from "@/lib/format";
import { currencyDecimals } from "@edr/ui-common";
import { cargoTonsAndItems } from "@/utils/cargoWeight";
import type { BookingDetail } from "@/types/booking";
import {
@@ -254,7 +255,7 @@ export default function BookingRequestDetailPage() {
const kpis: KpiItem[] = [
{
label: "Total value",
value: formatMoney(amount, booking.paymentCurrency, 2),
value: formatMoney(amount, booking.paymentCurrency, currencyDecimals(booking.paymentCurrency)),
hint: booking.paymentStatus,
icon: Wallet,
color: "edr-green",

View File

@@ -25,6 +25,7 @@ import { useAuth } from "@/auth/useAuth";
import { PageContainer, PageHeader } from "@/components/page";
import { toDayString } from "@/hooks/useListControls";
import { formatDate, formatMoney } from "@/lib/format";
import { currencyDecimals } from "@edr/ui-common";
import { FREIGHT_PERMS, hasPermission } from "@/lib/permissions";
import {
DataTable,
@@ -166,7 +167,7 @@ export default function WagonCancellationsPage() {
header: () => <span>Fee</span>,
cell: ({ row }) => (
<Text size="sm" style={{ fontVariantNumeric: "tabular-nums" }}>
{formatMoney(row.original.feeAmount, row.original.feeCurrency, 2)}
{formatMoney(row.original.feeAmount, row.original.feeCurrency, currencyDecimals(row.original.feeCurrency))}
</Text>
),
},
@@ -175,7 +176,7 @@ export default function WagonCancellationsPage() {
header: () => <span>Credit</span>,
cell: ({ row }) => (
<Text size="sm" style={{ fontVariantNumeric: "tabular-nums" }}>
{formatMoney(row.original.creditAmount, row.original.feeCurrency, 2)}
{formatMoney(row.original.creditAmount, row.original.feeCurrency, currencyDecimals(row.original.feeCurrency))}
</Text>
),
},
@@ -347,7 +348,7 @@ export default function WagonCancellationsPage() {
<Text size="sm">
{voiding.booking?.reference ?? voiding.bookingId} ·{" "}
{voiding.wagonsCancelled} wagon(s) · fee{" "}
{formatMoney(voiding.feeAmount, voiding.feeCurrency, 2)}
{formatMoney(voiding.feeAmount, voiding.feeCurrency, currencyDecimals(voiding.feeCurrency))}
</Text>
<Text size="sm" c="dimmed">
The pending fee is dropped and the wagons stay on the booking.

View File

@@ -82,6 +82,7 @@ const CONTRACT_KIND_OPTIONS = [
const CURRENCY_OPTIONS = [
{ value: "ETB", label: "ETB" },
{ value: "USD", label: "USD" },
{ value: "DJF", label: "DJF" },
];
/** value = `${sortBy}:${sortOrder}` for the sort Select. */

View File

@@ -279,6 +279,7 @@ const OperationsTab = ({ vehicle }: { vehicle: Vehicle }) => {
<Group gap="lg" mt={6}>
<Radio value="ETB" label="ETB" />
<Radio value="USD" label="USD" />
<Radio value="DJF" label="DJF" />
</Group>
</Radio.Group>
</SimpleGrid>

View File

@@ -274,7 +274,7 @@ function ConfirmCell({
export default function UsdPaymentsPanel({
currency,
}: {
currency: "USD" | "ETB";
currency: "USD" | "ETB" | "DJF";
}) {
const navigate = useNavigate();
// Namespaced: the ETB and USD tabs share this panel and live on the same URL

View File

@@ -27,6 +27,7 @@ import { useQuery } from "@tanstack/react-query";
import { KpiStrip } from "@/components/page";
import { ExportButton } from "@/components/export/ExportButton";
import { formatDate, formatMoney } from "@/lib/format";
import { currencyDecimals } from "@edr/ui-common";
import { api } from "@/services/api";
import type { PaymentMethod, PaymentRow } from "@/services/payments.service";
import {
@@ -149,7 +150,7 @@ export default function PaymentsPanel() {
header: () => <span className={tableHeader}>Amount</span>,
cell: ({ row }) => (
<span className="font-mono text-sm font-semibold tabular-nums text-foreground">
{formatMoney(row.original.amount, row.original.currency, 2)}
{formatMoney(row.original.amount, row.original.currency, currencyDecimals(row.original.currency))}
</span>
),
},

View File

@@ -450,6 +450,7 @@ export const rateUnitOptions = (
const CURRENCIES = [
{ label: "ETB (Birr)", value: "ETB" },
{ label: "USD", value: "USD" },
{ label: "DJF", value: "DJF" },
];
const PRIORITY_CONFIG_TYPES = [

View File

@@ -16,7 +16,7 @@ import {
Text,
Textarea,
} from "@mantine/core";
import { DataTable, type ColumnDef } from "@edr/ui-common";
import { DataTable, type ColumnDef, currencyDecimals } from "@edr/ui-common";
import { useAuth } from "@/auth/useAuth";
import { FREIGHT_PERMS, hasPermission } from "@/lib/permissions";
@@ -45,7 +45,7 @@ const STATUS_META: Record<EmptyReturnRequestStatus, { label: string; color: stri
const money = (amount: number | null | undefined, currency: string | null | undefined) =>
amount == null
? "—"
: `${Number(amount).toLocaleString(undefined, { minimumFractionDigits: 2 })} ${currency ?? ""}`.trim();
: `${Number(amount).toLocaleString(undefined, { minimumFractionDigits: currencyDecimals(currency) })} ${currency ?? ""}`.trim();
/**
* The queue for customer-initiated empty container returns: a booking sold

View File

@@ -1,4 +1,5 @@
import { Fragment, useMemo, useState } from "react";
import { currencyDecimals } from "@edr/ui-common";
import { useQueries, useQuery, useQueryClient } from "@tanstack/react-query";
import {
ActionIcon,
@@ -78,7 +79,7 @@ const TRUCK_COLUMNS = [
] as const;
const money = (amount: number, currency: string) =>
`${Number(amount).toLocaleString(undefined, { maximumFractionDigits: 2 })} ${currency === "ETB" ? "ETB" : currency}`;
`${Number(amount).toLocaleString(undefined, { maximumFractionDigits: currencyDecimals(currency) })} ${currency === "ETB" ? "ETB" : currency}`;
export interface BookingGroup {

View File

@@ -17,7 +17,7 @@ import {
} from '@mantine/core';
import { Ban, CreditCard, DoorOpen, Download, ExternalLink, Eye, Receipt } from 'lucide-react';
import { useNavigate } from 'react-router-dom';
import { DataTable, type ColumnDef } from '@edr/ui-common';
import { DataTable, type ColumnDef, currencyDecimals } from '@edr/ui-common';
import { applyClientFilters, FilterBar, useFilters, type FilterDef } from '@/components/filters';
import { PageContainer, PageHeader } from '@/components/page';
@@ -50,7 +50,7 @@ const STATUS_COLOR: Record<WarehouseInvoiceStatus, string> = {
CANCELLED: 'gray',
};
const fmt = (n: number, c: string) => formatMoney(n, c, 2);
const fmt = (n: number, c: string) => formatMoney(n, c, currencyDecimals(c));
const fmtDate = (d?: string | null) => (d ? new Date(d).toLocaleDateString() : '—');
const INVOICE_FILTER_DEFS: FilterDef[] = [
@@ -204,7 +204,8 @@ function InvoiceDetailModal({ id, onClose }: { id: string | null; onClose: () =>
const canGateClear = inv?.status === 'PAID' && Boolean(inv.inventoryId);
useEffect(() => {
setGatewayMethod(inv?.currency === 'USD' ? 'WAAFI' : 'TELEBIRR');
// WAAFI settles USD and DJF; TELEBIRR is ETB-only.
setGatewayMethod(inv?.currency !== 'ETB' ? 'WAAFI' : 'TELEBIRR');
setPayerAccount('');
}, [inv?.id, inv?.currency]);

View File

@@ -69,6 +69,7 @@ const TRADE = [
const CURRENCIES = [
{ value: 'USD', label: 'USD - Dollar' },
{ value: 'ETB', label: 'ETB - Birr' },
{ value: 'DJF', label: 'DJF - Djibouti Franc' },
];
const clean = (s: string) => s.trim() || undefined;

View File

@@ -1532,7 +1532,7 @@ export const api = {
),
feePreview: endpoint<
{ inventoryId: string; billingCurrency?: "ETB" | "USD" },
{ inventoryId: string; billingCurrency?: "ETB" | "USD" | "DJF" },
FeePreview[]
>(
"warehouse-inventory",
@@ -1884,7 +1884,7 @@ export const api = {
{
inventoryId: string;
confirmZero?: boolean;
billingCurrency?: "ETB" | "USD";
billingCurrency?: "ETB" | "USD" | "DJF";
},
WarehouseFeeInvoice
>(

View File

@@ -558,11 +558,11 @@ export const warehouseService = {
updateFeeRule: (id: string, payload: Partial<SaveFeeRulePayload>) =>
apiClient.patch<FeeRule>(URL_CONSTANTS.WAREHOUSE_RULES.FEES_BY_ID(id), payload),
deleteFeeRule: (id: string) => apiClient.delete(URL_CONSTANTS.WAREHOUSE_RULES.FEES_BY_ID(id)),
feePreview: (inventoryId: string, billingCurrency?: 'ETB' | 'USD') =>
feePreview: (inventoryId: string, billingCurrency?: 'ETB' | 'USD' | 'DJF') =>
apiClient.get<FeePreview[]>(URL_CONSTANTS.WAREHOUSE_RULES.FEE_PREVIEW(inventoryId), {
params: cleanParams({ billingCurrency }),
}),
accrualDashboard: (billingCurrency?: 'ETB' | 'USD') =>
accrualDashboard: (billingCurrency?: 'ETB' | 'USD' | 'DJF') =>
apiClient.get<AccrualDashboardRow[]>(URL_CONSTANTS.WAREHOUSE_RULES.ACCRUAL_DASHBOARD, {
params: cleanParams({ billingCurrency }),
}),
@@ -592,7 +592,7 @@ export const warehouseService = {
apiClient.get<WarehouseFeeInvoice[]>(URL_CONSTANTS.WAREHOUSE_INVOICES.FOR_INVENTORY(inventoryId)),
invoicesForBooking: (bookingId: string) =>
apiClient.get<WarehouseFeeInvoice[]>(URL_CONSTANTS.WAREHOUSE_INVOICES.FOR_BOOKING(bookingId)),
generateInvoice: (inventoryId: string, confirmZero = false, billingCurrency?: 'ETB' | 'USD') =>
generateInvoice: (inventoryId: string, confirmZero = false, billingCurrency?: 'ETB' | 'USD' | 'DJF') =>
apiClient.post<WarehouseFeeInvoice>(URL_CONSTANTS.WAREHOUSE_INVOICES.GENERATE(inventoryId), {
confirmZero,
billingCurrency,

View File

@@ -420,7 +420,7 @@ export interface CustomerBooking {
originLabel: string;
destinationLabel: string;
totalAmount: number;
currency: "ETB" | "USD";
currency: "ETB" | "USD" | "DJF";
scheduledDate?: string | null;
createdAt: string;
}
@@ -469,7 +469,7 @@ export interface CustomerPayment {
/** Booking reference the payment settles. */
bookingReference: string;
amount: number;
currency: "ETB" | "USD";
currency: "ETB" | "USD" | "DJF";
method: CustomerPaymentMethod;
status: CustomerPaymentStatus;
paidAt?: string | null;

View File

@@ -77,7 +77,7 @@ export interface InvoiceListFilter {
/** CSV of normalised UPPER_SNAKE payment methods (see `PAYMENT_METHOD_OPTIONS`). */
paymentMethods?: string;
search?: string;
currency?: "USD" | "ETB";
currency?: "USD" | "ETB" | "DJF";
/** ISO instants — inclusive bounds on `issuedAt` / `dueAt`. */
issuedFrom?: string;
issuedTo?: string;