From c656d0c6ae8d3348a3c3000269b059951af8f48b Mon Sep 17 00:00:00 2001 From: HotelSync Date: Thu, 19 Mar 2026 12:29:45 +0300 Subject: [PATCH] Log NetUP request/response body; fix reservation_id to be numeric - reservation_id converted to integer (NetUP likely requires number not UUID string) - Log requestBody sent to NetUP and responseBody received from NetUP - Push event rows now expandable: click to see URL, request JSON, NetUP error response - Same for checkout Co-Authored-By: Claude Sonnet 4.6 --- backend/src/routes/netup.ts | 35 ++++++++++++------- src/lib/api.ts | 2 +- src/pages/TvWelcomePage.tsx | 69 +++++++++++++++++++++++++++---------- 3 files changed, 75 insertions(+), 31 deletions(-) diff --git a/backend/src/routes/netup.ts b/backend/src/routes/netup.ts index 2ef7798..e611b38 100644 --- a/backend/src/routes/netup.ts +++ b/backend/src/routes/netup.ts @@ -7,15 +7,17 @@ type SlugIdParam = { Params: { slug: string; id: string } } // ── Push event log (check-in / check-out calls to NetUP) ───────────────────── export type PushEvent = { - ts: string - action: 'check-in' | 'check-out' | 'message' - hotelId: string - roomNumber: string // PMS room number - netupRoom: string // NetUP room number - url: string - status: 'ok' | 'error' | 'skipped' - httpStatus?: number - error?: string + ts: string + action: 'check-in' | 'check-out' | 'message' + hotelId: string + roomNumber: string // PMS room number + netupRoom: string // NetUP room number + url: string + requestBody?: unknown // what we sent + status: 'ok' | 'error' | 'skipped' + httpStatus?: number + responseBody?: string // what NetUP replied + error?: string } const MAX_PUSH = 100 export const pushLog: PushEvent[] = [] @@ -366,24 +368,31 @@ async function notifyNetupCheckinInternal( const url = `${base}/mw/api/hotel-room/${encodeURIComponent(mapping.netup_room_number)}/check-in` const cred = Buffer.from(`${cfg.username}:${cfg.password}`).toString('base64') + // reservation_id must be a number in NetUP — convert UUID/string to integer + const resIdNum = parseInt(reservationId.replace(/\D/g, '').slice(-8), 10) || Date.now() % 1000000 + const requestBody = { reservation_id: resIdNum, name: guestName, language: language ?? cfg.default_language } + try { const res = await fetch(url, { method: 'POST', headers: { Authorization: `Basic ${cred}`, 'Content-Type': 'application/json' }, - body: JSON.stringify({ reservation_id: reservationId, name: guestName, language: language ?? cfg.default_language }), + body: JSON.stringify(requestBody), signal: AbortSignal.timeout(6000), }) + const responseText = await res.text().catch(() => '') const event: PushEvent = { ts: new Date().toISOString(), action: 'check-in', hotelId, roomNumber, netupRoom: mapping.netup_room_number, url, + requestBody, status: res.ok ? 'ok' : 'error', httpStatus: res.status, + responseBody: responseText || undefined, } if (!res.ok) event.error = `HTTP ${res.status}` recordPush(event) - return res.ok ? { ok: true, netupRoom: mapping.netup_room_number } : { ok: false, error: `HTTP ${res.status}` } + return res.ok ? { ok: true, netupRoom: mapping.netup_room_number } : { ok: false, error: `HTTP ${res.status}: ${responseText}` } } catch (err) { const error = err instanceof Error ? err.message : 'Ошибка соединения' - recordPush({ ts: new Date().toISOString(), action: 'check-in', hotelId, roomNumber, netupRoom: mapping.netup_room_number, url, status: 'error', error }) + recordPush({ ts: new Date().toISOString(), action: 'check-in', hotelId, roomNumber, netupRoom: mapping.netup_room_number, url, requestBody, status: 'error', error }) return { ok: false, error } } } @@ -419,10 +428,12 @@ export async function notifyNetupCheckout(hotelId: string, roomId: string) { headers: { Authorization: `Basic ${cred}` }, signal: AbortSignal.timeout(6000), }) + const responseText = await res.text().catch(() => '') recordPush({ ts: new Date().toISOString(), action: 'check-out', hotelId, roomNumber, netupRoom: mapping.netup_room_number, url, status: res.ok ? 'ok' : 'error', httpStatus: res.status, + responseBody: responseText || undefined, error: res.ok ? undefined : `HTTP ${res.status}`, }) } catch (err) { diff --git a/src/lib/api.ts b/src/lib/api.ts index f606e40..26dce3a 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -290,7 +290,7 @@ export const api = { getLog: (slug: string) => req<{ - pushEvents: { ts: string; action: string; roomNumber: string; netupRoom: string; url: string; status: 'ok' | 'error' | 'skipped'; httpStatus?: number; error?: string }[] + pushEvents: { ts: string; action: string; roomNumber: string; netupRoom: string; url: string; requestBody?: unknown; status: 'ok' | 'error' | 'skipped'; httpStatus?: number; responseBody?: string; error?: string }[] pullRequests: { ts: string; method: string; url: string; headers: Record; query: Record; body: unknown }[] }>('GET', `/api/hotels/${slug}/netup/log`), diff --git a/src/pages/TvWelcomePage.tsx b/src/pages/TvWelcomePage.tsx index bd79b0f..d0e8be4 100644 --- a/src/pages/TvWelcomePage.tsx +++ b/src/pages/TvWelcomePage.tsx @@ -31,8 +31,10 @@ type PushEvent = { roomNumber: string netupRoom: string url: string + requestBody?: unknown status: 'ok' | 'error' | 'skipped' httpStatus?: number + responseBody?: string error?: string } @@ -65,7 +67,8 @@ export function TvWelcomePage() { const [pushEvents, setPushEvents] = useState([]) const [pullEntries, setPullEntries] = useState([]) const [logLoading, setLogLoading] = useState(false) - const [selectedPull, setSelectedPull] = useState(null) + const [selectedPull, setSelectedPull] = useState(null) + const [selectedPush, setSelectedPush] = useState(null) // Test check-in const [testingCheckin, setTestingCheckin] = useState(false) const [testCheckinResult, setTestCheckinResult] = useState<{ ok: boolean; msg: string } | null>(null) @@ -548,23 +551,53 @@ export function TvWelcomePage() { ) : (
{pushEvents.map((e, i) => ( -
- - - {e.action === 'check-in' ? 'Заселение' : 'Выезд'} - - - Номер {e.roomNumber} - {e.netupRoom && <> → NetUP {e.netupRoom}} - - {e.error && {e.error}} - {e.httpStatus && e.status === 'ok' && HTTP {e.httpStatus}} - - {new Date(e.ts).toLocaleTimeString('ru-RU')} - +
+ + {selectedPush === e && ( +
+
+

URL запроса

+

{e.url}

+
+ {e.requestBody != null && ( +
+

Тело запроса (отправили в NetUP)

+
+                              {JSON.stringify(e.requestBody, null, 2)}
+                            
+
+ )} + {e.responseBody && ( +
+

Ответ NetUP

+
{e.responseBody}
+
+ )} +
+ )}
))}