diff --git a/apps/dashboard/src/components/front-desk/GuestDetailsModal.tsx b/apps/dashboard/src/components/front-desk/GuestDetailsModal.tsx index cee3433..4d08a90 100644 --- a/apps/dashboard/src/components/front-desk/GuestDetailsModal.tsx +++ b/apps/dashboard/src/components/front-desk/GuestDetailsModal.tsx @@ -225,6 +225,7 @@ export default function GuestDetailsModal({ roomTypeId={reservation.roomTypeId} ratePlanId={reservation.ratePlanId} totalAmount={reservation.totalAmount} + currencyCode={currencyCode} /> diff --git a/apps/dashboard/src/components/reservations/ReservationPartyPanel.tsx b/apps/dashboard/src/components/reservations/ReservationPartyPanel.tsx index 48f5972..74d48a0 100644 --- a/apps/dashboard/src/components/reservations/ReservationPartyPanel.tsx +++ b/apps/dashboard/src/components/reservations/ReservationPartyPanel.tsx @@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { UserPlus, Split, ArrowRightLeft, Trash2 } from 'lucide-react'; import { api } from '../../lib/api'; -import { moneyString, requirePropertyId } from '../../lib/api-helpers'; +import { moneyString, requirePropertyId, requireCurrency } from '../../lib/api-helpers'; import { useToast } from '../ui/Toast'; import FindGuest from '../guests/FindGuest'; import type { Guest } from '../../types/guest'; @@ -54,14 +54,14 @@ export default function ReservationPartyPanel({ roomTypeId, ratePlanId, totalAmount, - currencyCode = 'USD', + currencyCode, }: { reservationId: string; propertyId: string; roomTypeId?: string; ratePlanId?: string; totalAmount?: string; - currencyCode?: string; + currencyCode?: string | null; }) { const { t } = useTranslation(); const { toast } = useToast(); @@ -166,6 +166,7 @@ export default function ReservationPartyPanel({ const splitMutation = useMutation({ mutationFn: () => { requirePropertyId(propertyId); + requireCurrency(currencyCode ?? null); return api.post( `/v1/reservations/${reservationId}/split`, { diff --git a/apps/dashboard/src/pages/Accounting.tsx b/apps/dashboard/src/pages/Accounting.tsx index 69c5e70..f23ce80 100644 --- a/apps/dashboard/src/pages/Accounting.tsx +++ b/apps/dashboard/src/pages/Accounting.tsx @@ -279,10 +279,11 @@ function AccountingHome() { const recordArPayment = useMutation({ mutationFn: () => { requirePropertyId(propertyId); + requireCurrency(selectedLedger?.currencyCode ?? null); return api.post(`/v1/ar/ledgers/${selectedLedger!.id}/payments`, { propertyId, amount: moneyString(arPaymentAmount), - currencyCode: selectedLedger?.currencyCode ?? 'USD', + currencyCode: selectedLedger?.currencyCode, }); }, onSuccess: () => { diff --git a/apps/dashboard/src/pages/Folios.tsx b/apps/dashboard/src/pages/Folios.tsx index 81a7b23..0de23eb 100644 --- a/apps/dashboard/src/pages/Folios.tsx +++ b/apps/dashboard/src/pages/Folios.tsx @@ -243,14 +243,17 @@ function SplitFolioPanel({ }); const createSplitFolio = useMutation({ - mutationFn: () => - api.post('/v1/folios', { + mutationFn: () => { + requirePropertyId(propertyId); + requireCurrency(folio.currencyCode ?? null); + return api.post('/v1/folios', { propertyId, reservationId, guestId: folio.guestId, type: 'guest', - currencyCode: folio.currencyCode ?? 'USD', - }), + currencyCode: folio.currencyCode, + }); + }, onSuccess: () => { refetchSiblings(); setSplitOpen(false); @@ -497,7 +500,7 @@ function FolioDetail() { const folio: Folio | null = folioData?.data ?? folioData ?? null; const charges: Charge[] = chargesData?.data ?? chargesData ?? []; const payments: Payment[] = paymentsData?.data ?? paymentsData ?? []; - const currencyCode = folio?.currencyCode ?? 'USD'; + const currencyCode = folio?.currencyCode ?? null; const reversedIds = new Set( charges.filter((c) => c.isReversal && c.originalChargeId).map((c) => c.originalChargeId!), diff --git a/apps/dashboard/src/pages/FrontDesk.tsx b/apps/dashboard/src/pages/FrontDesk.tsx index 7f9e657..36ff45c 100644 --- a/apps/dashboard/src/pages/FrontDesk.tsx +++ b/apps/dashboard/src/pages/FrontDesk.tsx @@ -4,7 +4,7 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { ConciergeBell, LogIn, Users, LogOut, UserPlus, UsersRound, ArrowRightLeft, StickyNote, UserRound, Plus, X } from 'lucide-react'; import { addDays, differenceInCalendarDays, format, parseISO } from 'date-fns'; import { api } from '../lib/api'; -import { moneyString, requirePropertyId } from '../lib/api-helpers'; +import { moneyString, requirePropertyId, requireCurrency } from '../lib/api-helpers'; import { useProperty } from '../context/PropertyContext'; import StatusBadge from '../components/ui/StatusBadge'; import Modal from '../components/ui/Modal'; @@ -472,6 +472,7 @@ export default function FrontDesk() { const plans: any[] = Array.isArray(ratePlans) ? ratePlans : ratePlans?.data ?? []; const plan = plans.find((p) => p.id === wiRatePlanId); + requireCurrency(plan?.currencyCode ?? null); const nightly = Number(plan?.baseAmount ?? 0); const guestId = wiGuest.id; const resCreate = await api.post( @@ -486,7 +487,7 @@ export default function FrontDesk() { adults: 1 + primaryAdditionalIds.length, source: 'walk_in', totalAmount: moneyString(nightly * wiNights), - currencyCode: plan?.currencyCode ?? 'USD', + currencyCode: plan?.currencyCode, }, { skipErrorToast: true }, ); @@ -518,6 +519,7 @@ export default function FrontDesk() { for (let i = 0; i < wiExtraRooms.length; i++) { const extra = wiExtraRooms[i]; const planExtra = plans.find((p) => p.id === extra.ratePlanId); + requireCurrency(planExtra?.currencyCode ?? null); const nightlyExtra = Number(planExtra?.baseAmount ?? 0); const extraGuestIds = [extra.guest!.id, ...extraAdditionalIds[i]]; // Occupants must be attached to the source (primary) reservation before they @@ -539,7 +541,7 @@ export default function FrontDesk() { roomTypeId: extra.roomTypeId, ratePlanId: extra.ratePlanId, totalAmount: moneyString(nightlyExtra * wiNights), - currencyCode: planExtra?.currencyCode ?? 'USD', + currencyCode: planExtra?.currencyCode, roomId: extra.roomId, adults: extraGuestIds.length, overrideMaxOccupancy: extra.overrideOccupancy, diff --git a/apps/dashboard/src/pages/HouseAccounts.tsx b/apps/dashboard/src/pages/HouseAccounts.tsx index 5bb516d..b521027 100644 --- a/apps/dashboard/src/pages/HouseAccounts.tsx +++ b/apps/dashboard/src/pages/HouseAccounts.tsx @@ -65,6 +65,7 @@ function HouseAccountList() { const createMutation = useMutation({ mutationFn: () => { requirePropertyId(propertyId); + requireCurrency(currencyCode); return api.post('/v1/house-accounts', { propertyId, name, kind, currencyCode }); }, onSuccess: () => { @@ -321,7 +322,7 @@ function HouseAccountDetail() { const account: HouseAccount | null = data?.data ?? data ?? null; const products: Product[] = productsData?.data ?? productsData ?? []; - const currencyCode = account?.currencyCode ?? 'USD'; + const currencyCode = account?.currencyCode ?? null; const postCharge = useMutation({ mutationFn: () => { diff --git a/apps/dashboard/src/pages/RatePlans.tsx b/apps/dashboard/src/pages/RatePlans.tsx index de2a40e..a7335c7 100644 --- a/apps/dashboard/src/pages/RatePlans.tsx +++ b/apps/dashboard/src/pages/RatePlans.tsx @@ -541,7 +541,7 @@ function RatePlanDetail() {

{t('ratePlans.code')}

{plan.code}

{t('ratePlans.baseAmount')}

{plan.baseAmount != null ? formatMoney(plan.baseAmount, currencyCode) : '—'}

{t('ratePlans.roomType')}

{plan.roomTypeName ?? '—'}

-

{t('ratePlans.currency')}

{plan.currency ?? plan.currencyCode ?? 'USD'}

+

{t('ratePlans.currency')}

{plan.currency ?? plan.currencyCode ?? '—'}

{plan.type === 'derived' && ( <>

{t('ratePlans.parentRatePlan')}

{plan.parentRatePlanId ?? '—'}

diff --git a/apps/dashboard/src/pages/Reservations.tsx b/apps/dashboard/src/pages/Reservations.tsx index 6d9a193..f04abd6 100644 --- a/apps/dashboard/src/pages/Reservations.tsx +++ b/apps/dashboard/src/pages/Reservations.tsx @@ -73,7 +73,7 @@ function parseImportRows(text: string): Record[] { .split('\n') .map((line) => line.trim()) .filter(Boolean) - .map((line) => { + .map((line, index) => { const [ guestId, arrivalDate, @@ -86,6 +86,12 @@ function parseImportRows(text: string): Record[] { adults, children, ] = line.split(',').map((s) => s.trim()); + if (!currencyCode) { + // Was `currencyCode || 'USD'`. A missing column is not a USD booking — + // it is a row we cannot price, and importing it as dollars against a + // property trading in anything else writes a wrong number to a ledger. + throw new Error(`Row ${index + 1}: currencyCode is required`); + } const row: Record = { guestId, arrivalDate, @@ -93,7 +99,7 @@ function parseImportRows(text: string): Record[] { roomTypeId, ratePlanId, totalAmount: totalAmount ? moneyString(totalAmount) : undefined, - currencyCode: currencyCode || 'USD', + currencyCode, source: source || 'direct', }; if (adults) row.adults = Number(adults); @@ -646,7 +652,7 @@ function ReservationList() { totalAmount={ detailRes.totalAmount != null ? String(detailRes.totalAmount) : undefined } - currencyCode={detailRes.currencyCode ?? 'USD'} + currencyCode={detailRes.currencyCode ?? null} />