add usd for import

This commit is contained in:
Marshal
2026-08-08 15:14:10 +00:00
parent 88b995d3d7
commit fb6f8afc0c
7 changed files with 67 additions and 22 deletions

View File

@@ -348,6 +348,9 @@ export default function GlCreateBookingForm() {
// Intercity shipments ride a passing import/export train staff pick at // Intercity shipments ride a passing import/export train staff pick at
// finalize time — no shipment day is chosen and no window gate applies. // finalize time — no shipment day is chosen and no window gate applies.
const isIntercity = contract?.tradeDirection === "DOMESTIC"; const isIntercity = contract?.tradeDirection === "DOMESTIC";
// USD billing is offered on import traffic only — export and domestic
// shipments are always invoiced in ETB.
const isImport = contract?.tradeDirection === "IMPORT";
// ONE_TIME split-remainder mode: a previous booking on this contract was // ONE_TIME split-remainder mode: a previous booking on this contract was
// split on train capacity, so the capacity endpoint reports the outstanding // split on train capacity, so the capacity endpoint reports the outstanding
@@ -1757,12 +1760,15 @@ export default function GlCreateBookingForm() {
Billing currency Billing currency
</Text> </Text>
<Text size="xs" c="dimmed" mb={8}> <Text size="xs" c="dimmed" mb={8}>
Shipments are invoiced in ETB. {isImport
? "Import shipments may be invoiced in ETB or USD. USD is paid by bank transfer, not online."
: "Shipments are invoiced in ETB."}
</Text> </Text>
<CurrencySelector <CurrencySelector
value={isIntercity ? "ETB" : paymentCurrency} value={isIntercity ? "ETB" : paymentCurrency}
onChange={setPaymentCurrency} onChange={setPaymentCurrency}
disabled={isIntercity} disabled={isIntercity}
allowUsd={isImport}
/> />
</Box> </Box>

View File

@@ -683,7 +683,11 @@ export default function EditBookingPage() {
/> />
</SimpleGrid> </SimpleGrid>
<PaymentCurrencyField control={form.control} /> {/* USD billing is import-only; export/intercity stay ETB. */}
<PaymentCurrencyField
control={form.control}
allowUsd={operationType === "import"}
/>
{(selectedService?.includesFirstMile || {(selectedService?.includesFirstMile ||
selectedService?.includesLastMile || selectedService?.includesLastMile ||

View File

@@ -19,14 +19,25 @@ const CURRENCY_ICONS: Record<
export function PaymentCurrencyField({ export function PaymentCurrencyField({
control, control,
allowUsd = false,
}: { }: {
control: Control<BookingFormInputValues, any, BookingFormValues>; control: Control<BookingFormInputValues, any, BookingFormValues>;
/**
* Offer USD alongside ETB. Import shipments only — export and domestic
* traffic is always invoiced in ETB.
*/
allowUsd?: boolean;
}) { }) {
const options = allowUsd
? PAYMENT_CURRENCY_OPTIONS
: PAYMENT_CURRENCY_OPTIONS.filter((o) => o.value !== "USD");
return ( return (
<Box mt={24}> <Box mt={24}>
<StepLabel>Payment currency</StepLabel> <StepLabel>Payment currency</StepLabel>
<Text fz={12} c="#6B7C8E" mt={4} mb={12}> <Text fz={12} c="#6B7C8E" mt={4} mb={12}>
Choose the currency for your freight quote and invoices. {allowUsd
? "Choose the currency for your freight quote and invoices. USD is paid by bank transfer, not online."
: "Choose the currency for your freight quote and invoices."}
</Text> </Text>
<Controller <Controller
name="paymentCurrency" name="paymentCurrency"
@@ -44,7 +55,7 @@ export function PaymentCurrencyField({
border: "1px solid #E6ECF2", border: "1px solid #E6ECF2",
}} }}
> >
{PAYMENT_CURRENCY_OPTIONS.map((option) => { {options.map((option) => {
const Icon = CURRENCY_ICONS[option.value].icon; const Icon = CURRENCY_ICONS[option.value].icon;
const selected = field.value === option.value; const selected = field.value === option.value;
return ( return (
@@ -97,10 +108,7 @@ export function PaymentCurrencyField({
</Group> </Group>
{/* Description for the active currency, kept subtle. */} {/* Description for the active currency, kept subtle. */}
<Text fz={11.5} c="#6B7C8E" mt={8}> <Text fz={11.5} c="#6B7C8E" mt={8}>
{ {options.find((o) => o.value === field.value)?.description}
PAYMENT_CURRENCY_OPTIONS.find((o) => o.value === field.value)
?.description
}
</Text> </Text>
<OptionFieldError error={fieldState.error} /> <OptionFieldError error={fieldState.error} />
</div> </div>

View File

@@ -81,11 +81,16 @@ export const PAYMENT_CURRENCY_OPTIONS: Array<{
label: string; label: string;
description: string; description: string;
}> = [ }> = [
// ponytail: ETB-only for now — re-add the USD option when multi-currency billing returns.
{ {
value: "ETB", value: "ETB",
label: "ETB", label: "ETB",
description: "Ethiopian Birr — local pricing and invoicing.", description: "Ethiopian Birr — pay online through the payment gateway.",
},
// Import shipments only; the field is hidden on export/domestic traffic.
{
value: "USD",
label: "USD",
description: "US Dollar — paid by bank transfer, slip sent to Finance.",
}, },
]; ];

View File

@@ -133,7 +133,11 @@ export function Step2ServiceType({
)} )}
/> />
<PaymentCurrencyField control={form.control} /> {/* USD billing is import-only; export/intercity stay ETB. */}
<PaymentCurrencyField
control={form.control}
allowUsd={operationType === "import"}
/>
{showServiceSections && ( {showServiceSections && (
<Stack gap={12} mt={24}> <Stack gap={12} mt={24}>

View File

@@ -1215,6 +1215,9 @@ function ScheduleStep({
})(); })();
const isIntercity = contract.tradeDirection === "DOMESTIC"; const isIntercity = contract.tradeDirection === "DOMESTIC";
// USD billing is offered on import traffic only — export and domestic
// shipments are always invoiced in ETB.
const isImport = contract.tradeDirection === "IMPORT";
const { data: availableDays, isLoading } = useQuery({ const { data: availableDays, isLoading } = useQuery({
...api.bookings.getAvailableDaysForCargo.queryOptions({ ...api.bookings.getAvailableDaysForCargo.queryOptions({
input: input:
@@ -1310,12 +1313,15 @@ function ScheduleStep({
<Box mb="lg"> <Box mb="lg">
<StepLabel>Billing currency *</StepLabel> <StepLabel>Billing currency *</StepLabel>
<Text fz={12.5} c="dimmed" mt={4} mb={10}> <Text fz={12.5} c="dimmed" mt={4} mb={10}>
Shipments are invoiced in ETB. {isImport
? "Import shipments may be invoiced in ETB or USD. USD is paid by bank transfer, not online."
: "Shipments are invoiced in ETB."}
</Text> </Text>
<CurrencySelector <CurrencySelector
value={field.value || ""} value={field.value || ""}
onChange={(v) => field.onChange(v)} onChange={(v) => field.onChange(v)}
error={fieldState.error?.message} error={fieldState.error?.message}
allowUsd={isImport}
/> />
</Box> </Box>
)} )}

View File

@@ -8,17 +8,27 @@ export interface CurrencySelectorProps {
disabled?: boolean; disabled?: boolean;
/** Validation error shown under the cards. */ /** Validation error shown under the cards. */
error?: string; error?: string;
/**
* Offer USD alongside ETB. Import shipments only — export and domestic
* traffic is invoiced in ETB, so the option stays hidden everywhere else.
* USD is settled by bank transfer, never through the online gateway.
*/
allowUsd?: boolean;
} }
// ponytail: ETB-only for now — restore the USD entry when multi-currency billing returns. const ETB_OPTION = {
const OPTIONS = [ code: "ETB",
{ symbol: "Br",
code: "ETB", name: "Ethiopian Birr",
symbol: "Br", hint: "Pay online through the payment gateway",
name: "Ethiopian Birr", } as const;
hint: "All shipments are invoiced in ETB",
}, const USD_OPTION = {
] as const; code: "USD",
symbol: "$",
name: "US Dollar",
hint: "Paid by bank transfer — send the slip to Finance",
} as const;
/** /**
* Card-style USD/ETB billing-currency picker. Renders unselected when `value` * Card-style USD/ETB billing-currency picker. Renders unselected when `value`
@@ -29,7 +39,9 @@ export function CurrencySelector({
onChange, onChange,
disabled = false, disabled = false,
error, error,
allowUsd = false,
}: CurrencySelectorProps) { }: CurrencySelectorProps) {
const options = allowUsd ? [ETB_OPTION, USD_OPTION] : [ETB_OPTION];
return ( return (
<Box> <Box>
<Box <Box
@@ -39,7 +51,7 @@ export function CurrencySelector({
gap: 10, gap: 10,
}} }}
> >
{OPTIONS.map((o) => { {options.map((o) => {
const selected = value === o.code; const selected = value === o.code;
return ( return (
<button <button