usd remove from the portal

This commit is contained in:
Marshal
2026-08-08 12:40:07 +00:00
parent 7a84826947
commit 83b9e32670
9 changed files with 104 additions and 10 deletions

View File

@@ -32,7 +32,7 @@ export class Container extends BaseEntity {
type: 'varchar',
nullable: true,
})
sealNumber!: string | null;
sealNumber!: string | null;
@Column({ type: 'varchar', default: 'AVAILABLE' })
status!: string; // AVAILABLE, LOADED, IN_TRANSIT, MAINTENANCE, DAMAGED

View File

@@ -1,5 +1,7 @@
import type { Freight } from "@edr/types";
import { isUsdOfflineBooking } from "@/pages/bookings/payments/offline-payment";
/** A pending customer action surfaced on the home "needs attention" card. */
export interface ActionItem {
id: string;
@@ -13,6 +15,8 @@ export interface ActionItem {
targetId: string;
/** Highlighted as action-required in the home card. */
urgent?: boolean;
/** USD booking: paid by bank transfer, no online payment modal. */
offlinePay?: boolean;
}
/**
@@ -60,13 +64,17 @@ export function deriveActionItems(
? b.status === "FULLY_EXECUTED"
: b.status === "SELECTED_FOR_BATCH");
if (canPay) {
const offlinePay = isUsdOfflineBooking(b);
items.push({
id: `pay-${b.id}`,
kind: "pay",
reference: b.reference,
description: "Payment due for this shipment",
description: offlinePay
? "Payment due — pay by bank transfer and send the slip to Finance"
: "Payment due for this shipment",
targetId: b.id,
urgent: true,
offlinePay,
});
}
}

View File

@@ -65,7 +65,10 @@ export function ActionNeededSection({ items: base }: ActionNeededSectionProps) {
const handleClick = (item: ActionItem) => {
switch (item.kind) {
case "pay":
setPayItem(item);
// USD is paid by bank transfer — the booking page shows the countdown
// and the pay-by-bank instructions instead of the payment modal.
if (item.offlinePay) navigate(`/bookings/${item.targetId}`);
else setPayItem(item);
break;
case "sign":
navigate(`/contracts/${item.targetId}/view`);
@@ -151,7 +154,9 @@ export function ActionNeededSection({ items: base }: ActionNeededSectionProps) {
leftSection={<FilePlus2 size={14} />}
>
{item.kind === "pay"
? "Pay now"
? item.offlinePay
? "Pay by bank"
: "Pay now"
: item.kind === "sign"
? "Sign"
: "Book"}

View File

@@ -3,6 +3,7 @@ import { useNavigate, useParams } from "react-router-dom";
import { useQuery } from "@tanstack/react-query";
import {
Alert,
Badge,
Box,
Button,
Center,
@@ -21,6 +22,7 @@ import {
CreditCard,
Download,
ExternalLink,
Landmark,
Receipt,
} from "lucide-react";
import toast from "react-hot-toast";
@@ -30,6 +32,7 @@ import { invoicesService } from "@/services/invoices.service";
import { useInvoicePayment } from "@/hooks/useInvoicePayment";
import { warehouseInvoicesService } from "@/services/warehouse-invoices.service";
import { PaymentMethodModal } from "@/pages/bookings/BookingDetailPage/components/PaymentMethodModal";
import { isUsdCurrency } from "@/pages/bookings/payments/offline-payment";
import { saveBlob } from "@/utils/download";
import { formatCurrency } from "@/lib/currency";
import { BORDER, INK, MUTED } from "../contracts/contract-ui";
@@ -224,7 +227,7 @@ export default function InvoiceDetailPage() {
Receipt
</Button>
)}
{payable && (
{payable && !isUsdCurrency(invoice.currency) && (
<Button
color="edr-green"
radius="md"
@@ -239,6 +242,18 @@ export default function InvoiceDetailPage() {
Pay {formatCurrency(amountDue, invoice.currency)}
</Button>
)}
{payable && isUsdCurrency(invoice.currency) && (
<Badge
size="lg"
radius="md"
variant="light"
color="yellow"
leftSection={<Landmark size={12} />}
styles={{ label: { textTransform: "none", fontWeight: 700 } }}
>
Pay by bank transfer send the slip to Finance
</Badge>
)}
</Group>
</Group>

View File

@@ -32,6 +32,7 @@ import { Freight } from "@edr/types";
import { api } from "@/services/api";
import { formatCurrency } from "@/lib/currency";
import { isUsdCurrency } from "@/pages/bookings/payments/offline-payment";
import {
BORDER,
GREEN,
@@ -276,7 +277,10 @@ export default function InvoicesList() {
{!isLoading &&
!isError &&
pageRows.map((inv) => {
const payable = isPayable(inv.status);
// USD invoices are paid by bank transfer — the detail page
// shows the instructions, so the row action reads "View".
const payable =
isPayable(inv.status) && !isUsdCurrency(inv.currency);
return (
<Table.Tr
key={inv.id}

View File

@@ -53,6 +53,7 @@ import { WagonsTab } from "./components/WagonsTab";
import { fmtDate, isNegative, priceTotal } from "./utils";
import { useScrollToHash } from "@/hooks/useScrollToHash";
import { useBookingPayment } from "@/pages/bookings/payments/useBookingPayment";
import { isUsdOfflineBooking } from "@/pages/bookings/payments/offline-payment";
// Pre-payment statuses the customer may self-cancel from this view (free of
// charge). DRAFT / CHANGES_REQUESTED render their own views and drafts can
@@ -188,7 +189,7 @@ export function ReadonlyBookingView({
{canApproveDelivery && (
<ApproveDeliveryButton bookingId={booking.id} />
)}
{canPay && !showCountdown && (
{canPay && !showCountdown && !isUsdOfflineBooking(booking) && (
<HeaderButton
green
icon={<CreditCard size={16} />}

View File

@@ -17,6 +17,7 @@ import type { Freight } from "@edr/types";
import { invoicesService, type PortalInvoice } from "@/services/invoices.service";
import { InvoiceStatusBadge, titleCase } from "@/pages/billing/invoice-ui";
import { paymentStatusLabel } from "@/pages/bookings/booking-display";
import { isUsdOfflineBooking } from "@/pages/bookings/payments/offline-payment";
import { saveBlob } from "@/utils/download";
import { fmtDate, priceLineItems, priceTotal, type Pricing } from "../utils";
@@ -190,6 +191,7 @@ export function BookingPaymentPanel({
}) {
const navigate = useNavigate();
const paid = booking.paymentStatus === "PAID";
const offlineUsd = isUsdOfflineBooking(booking);
const isAdjusted =
booking.adjustedTotalAmount !== null &&
booking.adjustedTotalAmount !== undefined;
@@ -268,11 +270,36 @@ export function BookingPaymentPanel({
<PartialOfferNotice offer={booking.activeBatchOffer} />
)}
{/* USD: no online payment — bank transfer + slip to Finance, who confirm
the payment (backoffice flow lands in a later phase). Shown for any
unpaid USD booking, with or without an open pay window. */}
{!paid && offlineUsd && (
<Box
mt={14}
p={14}
style={{
borderRadius: 10,
backgroundColor: "#FEF6E6",
border: "1px solid #F3E2B8",
}}
>
<Text fz="13px" fw={800} c="#9A5B00">
Pay by bank transfer
</Text>
<Text mt={4} fz="12.5px" c="#7A5A1E" lh={1.55}>
Online payment isn&apos;t available for USD bookings. Transfer the
total amount to EDR&apos;s bank account before the payment deadline,
then send the payment slip to the EDR Finance department they
will confirm your payment.
</Text>
</Box>
)}
{showCountdown && booking.paymentDeadline && (
<Box mt={16}>
<Countdown
deadline={booking.paymentDeadline}
onPay={onPay}
onPay={offlineUsd ? undefined : onPay}
paying={paying}
/>
{showConsolidationNote && (

View File

@@ -1,11 +1,12 @@
import { Button, type ButtonProps } from "@mantine/core";
import { CreditCard } from "lucide-react";
import { Badge, Button, type ButtonProps } from "@mantine/core";
import { CreditCard, Landmark } from "lucide-react";
import { Freight } from "@edr/types";
import { ModalSafeWrapper } from "@/components/customer-actions/ModalSafeWrapper";
import { PaymentMethodModal } from "../BookingDetailPage/components/PaymentMethodModal";
import { priceTotal } from "../BookingDetailPage/utils";
import { isUsdOfflineBooking } from "./offline-payment";
import { useBookingPayment } from "./useBookingPayment";
interface PayNowButtonProps {
@@ -29,6 +30,23 @@ export function PayNowButton({
const pay = useBookingPayment(booking.id);
const pricing = booking.pricingBreakdown;
// USD is paid by bank transfer and confirmed by Finance — no online payment.
if (isUsdOfflineBooking(booking)) {
return (
<Badge
size={size === "xs" ? "md" : "lg"}
radius="md"
variant="light"
color="yellow"
fullWidth={fullWidth}
leftSection={<Landmark size={12} />}
styles={{ label: { textTransform: "none", fontWeight: 700 } }}
>
Pay by bank transfer
</Badge>
);
}
return (
<ModalSafeWrapper>
<Button

View File

@@ -0,0 +1,16 @@
import type { Freight } from "@edr/types";
/**
* USD bookings are never paid online: the customer pays by bank transfer and
* the Finance department confirms the payment from the slip. Phase 1 is
* portal-only — Finance's confirm flow lands in the backoffice later.
*/
export function isUsdCurrency(currency?: string | null): boolean {
return currency?.toUpperCase() === "USD";
}
export function isUsdOfflineBooking(booking: Freight.IBooking): boolean {
return isUsdCurrency(
booking.pricingBreakdown?.currency ?? booking.paymentCurrency,
);
}