mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
usd remove from the portal
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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} />}
|
||||
|
||||
@@ -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't available for USD bookings. Transfer the
|
||||
total amount to EDR'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 && (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user