fix: normalizeRoom converts NUMERIC baseRate string to number in api.ts

PostgreSQL NUMERIC columns are returned as strings by node-postgres.
Added normalizeRoom() that coerces baseRate/hourlyRate/floor to JS numbers
before they reach components, eliminating string concatenation bugs like
"300005000.005000.00" in price calculations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-23 22:48:51 +03:00
parent 9af11df1b0
commit b631a281d2
2 changed files with 16 additions and 6 deletions

View File

@@ -337,13 +337,14 @@ export function BookingModal({ open, draft, rooms, bookings = [], onClose, onSav
if (!room || !form.checkIn || nightCount <= 0) return 0 if (!room || !form.checkIn || nightCount <= 0) return 0
const catId = room.categoryId const catId = room.categoryId
const catOverrides = catId ? priceOverrides?.[catId] : undefined const catOverrides = catId ? priceOverrides?.[catId] : undefined
const fallback = room.baseRate ?? 0 // room.baseRate is NUMERIC in DB — pg returns it as a string; always coerce
const fallback = Number(room.baseRate) || 0
if (!catOverrides) return fallback * nightCount if (!catOverrides) return fallback * nightCount
let sum = 0 let sum = 0
for (let i = 0; i < nightCount; i++) { for (let i = 0; i < nightCount; i++) {
const d = format(addDays(parseISO(form.checkIn), i), 'yyyy-MM-dd') const d = format(addDays(parseISO(form.checkIn), i), 'yyyy-MM-dd')
const p = catOverrides[d] const p = Number(catOverrides[d])
sum += (typeof p === 'number' && isFinite(p)) ? p : fallback sum += (isFinite(p) && p > 0) ? p : fallback
} }
return sum return sum
})() })()

View File

@@ -119,6 +119,15 @@ const CHANNEL_DISPLAY_NAMES: Record<string, string> = {
onetwotrip: 'OneTwoTrip', onetwotrip: 'OneTwoTrip',
} }
function normalizeRoom(r: Room): Room {
return {
...r,
baseRate: Number(r.baseRate) || 0,
hourlyRate: r.hourlyRate != null ? Number(r.hourlyRate) : undefined,
floor: Number(r.floor) || 0,
}
}
function normalizeChannel(raw: Record<string, unknown>): Channel { function normalizeChannel(raw: Record<string, unknown>): Channel {
const name = raw.name as ChannelName const name = raw.name as ChannelName
return { return {
@@ -171,13 +180,13 @@ export const api = {
// ── Rooms ───────────────────────────────────────────────────────────────── // ── Rooms ─────────────────────────────────────────────────────────────────
rooms: { rooms: {
list: (slug: string) => list: (slug: string) =>
req<Room[]>('GET', `/api/hotels/${slug}/rooms`), req<Room[]>('GET', `/api/hotels/${slug}/rooms`).then(rooms => rooms.map(normalizeRoom)),
create: (slug: string, data: RoomPayload) => create: (slug: string, data: RoomPayload) =>
req<Room>('POST', `/api/hotels/${slug}/rooms`, toRoomPayload(data)), req<Room>('POST', `/api/hotels/${slug}/rooms`, toRoomPayload(data)).then(normalizeRoom),
update: (slug: string, id: string, data: Partial<RoomPayload>) => update: (slug: string, id: string, data: Partial<RoomPayload>) =>
req<Room>('PATCH', `/api/hotels/${slug}/rooms/${id}`, toRoomPayload(data)), req<Room>('PATCH', `/api/hotels/${slug}/rooms/${id}`, toRoomPayload(data)).then(normalizeRoom),
delete: (slug: string, id: string) => delete: (slug: string, id: string) =>
req<void>('DELETE', `/api/hotels/${slug}/rooms/${id}`), req<void>('DELETE', `/api/hotels/${slug}/rooms/${id}`),