178 Commits

Author SHA1 Message Date
31de5be873 feat: review form logo/header image customizer, fix unread msg job startup
Some checks failed
Deploy to Production / deploy (push) Has been cancelled
- ReviewsPage: logo upload (circle/square), header bg image, show-email toggle
- GuestReviewQrPage/GuestReviewPage: render logo image, header bg image, logo shape
- Backend: return review_logo_url, review_logo_shape, review_header_image_url, show_email from both public review endpoints
- jobs.ts: run unread messages job 20s after startup (was only on interval)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 23:09:43 +03:00
9bda48c020 feat: guest email field on QR review form for reply
Some checks failed
Deploy to Production / deploy (push) Has been cancelled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 17:43:49 +03:00
8ab71b1e45 feat: email field for QR review replies, internal note if no email
Some checks failed
Deploy to Production / deploy (push) Has been cancelled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 17:37:22 +03:00
d83ffcb3c7 fix: notification links use app routes without slug prefix
Some checks failed
Deploy to Production / deploy (push) Has been cancelled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 17:31:20 +03:00
bb401095dc fix: add qr to reviews source constraint + notif type fix
Some checks failed
Deploy to Production / deploy (push) Has been cancelled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 17:26:20 +03:00
76101e8811 fix: skip macOS ._* artifact files in migrations runner
Some checks failed
Deploy to Production / deploy (push) Has been cancelled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 17:08:24 +03:00
6d07632f01 feat: notification settings per role + review/unread-msg notifications
- Add notificationSettings JSONB to role_permissions (migration 089)
- sendPushForNotification() — pushes only to users with the notif type enabled
- reviews.ts — push + in-app on new direct and QR reviews
- room-service.ts — use sendPushForNotification('room_service_order')
- publicWidget.ts — push + in-app on new online booking
- jobs.ts — runUnreadMessagesJob() every 15 min, deduped by in-process set
- UsersPage: NOTIFICATION_GROUPS UI in roles tab with per-type toggles
- api.ts / RolePermissionsContext: notificationSettings in types and save()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 16:43:39 +03:00
1975663cdd feat: full Room Service backend integration
- Migrations 086-088: room_service_settings, menu_items, orders+order_items
- Backend: /api/hotels/:slug/room-service/* routes (settings, menu CRUD,
  orders CRUD, public config, public order status)
- push.ts: sendPushToHotel() — push to all hotel staff on new order
- On new order: in-app notification + push to hotel staff
- api.ts: RoomService types + roomService API methods
- RoomServicePage: replaced mocked state with real API, 30s polling,
  add/edit/delete menu items modal, real advance/cancel order
- GuestRoomServicePage: fetches real menu + config, uses hotel payment
  methods, submits real order, polls status every 15s

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 11:44:28 +03:00
0e2180e3bc feat: configurable source→platform map, custom location QR codes
- Settings tab: replace static source map rows with editable table
  (add/delete mappings, persisted as review_source_map in hotel_settings)
- QR tab: add custom location QR codes (Ресепшн, Ресторан, etc.)
  not tied to a room, stored as review_custom_locations
- Backend: GET /api/public/review/:token reads review_source_map from
  hotel_settings instead of hardcoded SOURCE_KEYWORD mapping

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 10:44:07 +03:00
47215c7d64 fix: reviews — correct pending/archive logic, rejected_at field, stats, reply email
- Migration 085: add rejected_at to reviews
- pending = !isPublic && !reply && !rejectedAt
- archived = isPublic || reply || rejectedAt
- Stats: avgRating/NPS from all reviews; 'В архиве' = archived count
- sendReply: no longer sets isPublic=true on negative reviews
- reject: sets rejected_at=NOW() (distinct from pending)
- restore: clears rejected_at (now actually works)
- Badge 'Отклонён' for rejected reviews, opacity-60

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 10:33:01 +03:00
89e919bd8d feat: reply email to guest, QR code reviews per room
- PATCH reviews/:id now sends email to guest when manager adds reply
  (sendReviewReplyEmail: hotel-branded, shows original rating, manager's text)
- New public endpoints: GET/POST /api/public/review-qr/:slug/:room
  (anonymous QR review, no token/booking required, source='qr')
- New page GuestReviewQrPage at /review-qr/:slug/:room
- ReviewsPage: new QR-коды tab — grid of QR codes per room with download SVG + print

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 10:23:54 +03:00
67c47a6d01 fix: review email — hotel-branded header, proper first name, 30d guard, brand color
- Email no longer shows HotelSync branding, shows hotel name instead
- Greeting uses second word of guest_name as first name (Фамилия Имя format)
- Footer link changed to hotelsync.ru
- Brand color from hotel settings applied to email button/header
- Job: only process bookings checked out within last 30 days (prevents bulk send on first activation)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 10:14:22 +03:00
2de9b9e27d fix: ReviewsPage use useAuth for slug; reviews redirect by booking source
- Replace useParams with useAuth().user.hotelSlug in ReviewsPage
- Backend: filter platforms by booking source when review_redirect_source=true
  (booking_com→Booking.com, yandex_travel→Яндекс, airbnb, expedia, vrbo)
- If source has matching platform — show only that one; otherwise all platforms

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 00:21:26 +03:00
6ba591928a fix: use request.user.sub instead of .id in users route
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 00:07:16 +03:00
1ab2751854 feat: reviews module — email request after checkout, API, job
- Migration 084: add review_token, review_email_sent_at, review_retry_sent_at to bookings
- Backend route /api/hotels/:slug/reviews (GET list, PATCH reply/status)
- Backend public routes /api/public/review/:token (GET config, POST submit)
- email.ts: sendReviewRequestEmail template
- jobs.ts: runReviewRequestJob — sends email N hours after checkout, retry after 24h
- app.ts: register reviewsRoutes
- api.ts: add ReviewApi, PublicReviewConfig types + reviews/publicReview methods
- ReviewsPage: connect to real API, load/save settings, remove SMS option
- GuestReviewPage: use :token param, fetch config from API, submit to API
- App.tsx: route /review/:slug → /review/:token

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 00:04:59 +03:00
696cf4d38c fix: refresh token re-reads role from DB; remove staff stats cards
- /api/auth/refresh now fetches current role from DB instead of using
  stale Redis payload — role changes now take effect on next page refresh
  without requiring re-login. Also deactivates token if user.active=false.
- Removed role-count stat cards from staff page (user request)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 20:17:09 +03:00
a05bc3c48e feat: staff invite flow — email invitation instead of manual password
- POST /api/hotels/:slug/users: password now optional
  - with password → create active user (email_confirmed=true)
  - without password → create inactive user, send invite email with 7-day token
- POST /api/auth/accept-invite: validates token, sets password, activates
  account, returns JWT for auto-login
- Migration 083: invite_token + invite_expires columns on users
- email.ts: sendInviteEmail() with branded HTML template
- AcceptInvitePage at /invite/:token — set password form, auto-login on success
- AuthContext: loginWithToken() for programmatic session set
- UserModal: password field optional for new users, hint about invite email

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 20:11:53 +03:00
153e1d72d6 fix: role-permissions GET/PUT return camelCase (isSystem, roleKey, homePage)
Backend was returning raw Postgres snake_case column names; frontend
SavedRolePermission interface expected camelCase. This caused isSystem
to always be undefined → all roles treated as custom in UserModal.
Also adds home_page to the SELECT so homePage is available.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 20:04:01 +03:00
9e1eff30a6 fix: persist custom roles immediately, show them in user modal
- createRole() now calls api.rolePermissions.save() on creation so the
  role survives a page refresh (was only in local state before)
- UserModal now renders custom roles from savedPermissions context
  alongside the system role buttons
- mapRole() passes custom_* role keys through unchanged
- handleSave falls back to u.role for custom roles (no backendRoleMap entry)
- Table badge handles missing ROLE_META entry for custom roles
- DB migration 082: removes users.role CHECK constraint so custom role
  keys can be stored in the users table
- Backend POST/PATCH: allow custom_* roles through role allowlist checks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 19:57:59 +03:00
a701366216 feat: route permission guards, home page per role — RolePermissionsContext, PermissionGuard, App routes, DB migration
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 10:50:22 +03:00
512e3c6659 feat: full role permissions system — new modules, API persistence, sidebar context
- Backend: migration 080_role_permissions table (hotel-scoped, upsert)
- Backend: routes GET/PUT/DELETE /api/hotels/:slug/role-permissions
- Frontend: RolePermissionsContext — loads saved perms from API, provides can()
- Frontend: Sidebar uses context can() instead of hardcoded ROLE_PERMS
- Frontend: fixed module ID→permKey mapping (room-service, olap-reports, website-builder)
- Frontend: Documents page added to Управление nav (was missing)
- Frontend: equipment/wifi/ttlock get own permission keys (not bundled under 'settings')
- Frontend: floor_map gets own permission key (not bundled under 'rooms')
- Frontend: new module group 'Технологии': wifi, equipment, ttlock, floor_map
- Frontend: 'schedule' added to Администрирование module group
- Updated INITIAL_ROLE_PERMISSIONS: receptionist+availability+reports+documents+website, accountant+documents, technician+floor_map+equipment+ttlock

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 12:16:15 +03:00
ea58cffa88 fix: use correct super_admin role in group ownership transfer check
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 11:50:07 +03:00
18a6359adb feat: group chat owner protection — prevent removing creator, transfer ownership before leaving
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 11:47:18 +03:00
1641b14f29 feat: PWA push notifications — service worker, VAPID, subscription management
- Add web-push VAPID backend with push_subscriptions table (migration 078)
- Backend push module with sendPushToUser/sendPushToRoom helpers
- Push routes: GET vapid-key, POST subscribe, DELETE unsubscribe
- Trigger push on new chat messages in direct/group rooms
- PWA manifest, service worker, and icons (72–512px)
- Frontend push lib: SW registration, subscribe/unsubscribe helpers
- Push API in api.ts, SW registered in main.tsx
- usePushSubscription hook for auto-subscribe on permission grant
- PushPermissionRow component in ChatWidget settings view

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 12:14:03 +03:00
e6caf10a98 feat: chat — group settings (rename, photo, add/remove members), fix mention underline style 2026-04-15 11:32:52 +03:00
2cdc44b495 fix: chat — group creation (snake_case member_ids), readable mention color 2026-04-14 17:54:05 +03:00
18e1eb86af fix: chat — group members endpoint (all roles), mention first-name only, restore direct-chat button 2026-04-14 17:44:33 +03:00
8353eb7c7f feat: chat — group chat creation (create-group view, backend endpoint, group room display) 2026-04-14 17:36:04 +03:00
cf552d4b40 feat: chat — role on hover, role in direct header, @mention dropdown
- MessageBubble: role hidden by default, appears on hover of sender name (group/name CSS)
- Direct chat header: shows other user's role (from otherUserRole field added to backend)
- @mention: typing @ triggers filtered user dropdown, click inserts @Name into message
- Hint text updated: '@ — упомянуть'
- Backend: other_user_role added to rooms query

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 16:42:32 +03:00
e7ad9862ba feat: chat pagination — infinite scroll / load older messages
- Backend: cursor-based pagination via ?before=<ISO timestamp>
- Frontend: auto-load when scrolling to top (<60px), spinner + manual 'Загрузить ещё' button
- Poll merge: preserves older history when new messages arrive from polling
- hasMoreMsgs flag: shown only when exactly 50 msgs returned (more may exist)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 16:35:38 +03:00
abf1920e6f feat: chat — ⋮ room menu, typing indicator, online presence, read receipts
- RoomRow: ⋮ button (hover) for pin/unpin context menu
- MessageBubble: right-click context menu with emoji reactions + edit/delete
- Typing indicator: debounced setTyping, animated TypingDots, polling getTyping every 2s
- Online presence: heartbeat setPresence every 30s, green dot on avatars/header
- Read receipts: ✓/✓✓ for own messages in direct rooms via otherUserLastRead
- Migration 075: chat_presence + chat_typing tables
- Context menus clamped to viewport bounds

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 13:16:35 +03:00
a29f58282f feat: chat — edit/delete messages + emoji reactions
- Migration 074: edited_at, deleted_at columns + chat_reactions table
- Backend: PATCH edit (own only), DELETE soft-delete (own/admin), POST toggle reaction
  fetchMessage helper returns full message with aggregated reactions in one query
- API types: ChatReaction interface, editedAt/deletedAt/reactions in ChatMessage
- ChatWidget: hover toolbar (😊 react · ✏️ edit · 🗑️ delete), inline edit with
  save/cancel (Enter/Esc), reaction emoji picker (8 emojis), reaction pills below
  messages (click to toggle), '· изм.' label on edited messages, soft-deleted
  messages show 'Сообщение удалено' placeholder

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 12:31:17 +03:00
7ccf752429 feat: chat — photo attachments
- Migration 073: attachment_url column in chat_messages
- upload.ts: add 'chat' folder to ALLOWED_FOLDERS
- chat.ts: accept attachment_url in send message, return it in GET messages
- api.ts: attachmentUrl in ChatMessage type, uploadImage() with raw FormData fetch
- ChatWidget: paperclip button, file preview thumbnail with remove, image display
  inline in message bubbles (clickable, opens original), send enabled with image only

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 12:13:07 +03:00
d0dbf33701 fix: chat — VARCHAR(10) overflow for notifications type, skeleton loading
- Migration 072: widen type column to VARCHAR(20), add partial unique index for general rooms
- Backend: replace brittle ON CONFLICT with explicit SELECT-then-INSERT ensureRoom helper
- Frontend: show room skeletons (Общий чат + Уведомления) during load instead of spinner,
  hide '0 чатов' subtitle when list is empty

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 12:03:35 +03:00
3c4c40da44 feat: chat — search, notifications room, settings
- Migration 071: nullable sender_id, is_system flag, notifications room type
- Backend: notifications room auto-created per hotel, search messages endpoint,
  notify endpoint (manager/admin posts system messages), read-only enforcement
- API: ChatSearchResult type, chat.search(), chat.notify(), updated ChatMessage type
- Frontend ChatWidget: search view (people + messages tabs), settings panel
  (notifications visibility toggle, sound toggle), notifications room (bell icon,
  read-only banner, amber styling for system messages), new-chat button

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 11:25:40 +03:00
c153e12543 feat: widget additional services — sourced from rental objects module
- Additional services in widget are now derived from rental_objects (not a separate list)
- Settings page shows rental objects with enable/disable toggle; link to rental module
- Saves only {id, enabled}[] to widget_services — rental objects manage name/price/icon
- Standalone widget also derives additionalServices from config.rentalObjects
- Widget config API includes services[] in widgetSettings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 12:13:13 +03:00
36015e9cee fix: loyalty_tier default 'bronze' in guestUpsert (constraint requires bronze/silver/gold/platinum)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 12:21:01 +03:00
1c19334fb2 feat: auto-create guest in CRM on booking creation
- Add upsertGuestFromBooking() service — creates or updates guest record
  when a booking is made (matches by email/phone, creates if not found)
- Integrated into bookings.ts (manual PMS bookings) and publicWidget.ts
  (online widget bookings)
- Name parsed as Russian format: "Фамилия Имя [Отчество]"
- Non-throwing: booking creation never fails due to guest upsert errors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 11:59:41 +03:00
59307ba6b4 feat: widget rental tab — real objects, time slots, actual rental_bookings
- Add rental_objects to /api/widget/:slug/config response
- Add POST /api/widget/:slug/rental-bookings endpoint with availability check
- Widget rental tab now shows real rental objects (not additionalServices)
- Date picker, hourly or full-day toggle, start/end hour selectors
- Booking submit creates actual rental_booking record in DB
- Success screen shows rental object, date and time slot
- WidgetRentalObject + WidgetRentalBookingPayload types added to api.ts
- BookingWidgetStandalonePage passes rentalObjects to WidgetPreview

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 11:32:10 +03:00
cda515c0d8 fix: create booking_payments record on YooKassa webhook payment.succeeded
Payment tab shows 'Оплачено' from booking_payments table, not from paid_amount.
Webhook now inserts a payment record so the tab shows the correct amount.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 03:23:21 +03:00
60c49d97d1 fix: unify YooKassa webhook to single URL + add webhook instruction in UI
- Consolidate online booking payment handling into existing /api/webhooks/yookassa
  (was previously split — deposit.ts had partial handling, new route had full)
- deposit.ts webhook now fully handles online_bookings: reserved→confirmed,
  payment_status=paid, paid_amount, WS broadcast, and confirmation email
- Remove POST /api/yookassa/webhook (keep only GET /api/online-bookings/:id)
- PaymentSettingsPage: add webhook URL instruction panel with copy button
  showing https://api.hotelsync.ru/api/webhooks/yookassa and required events

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 03:08:02 +03:00
c8d2d87db4 fix: BookingConfirmPage date format + guest confirmation email on payment
- Fix date display: slice ISO timestamp to first 10 chars before splitting
- Add sendBookingConfirmedEmail() to email.ts — sends green styled HTML email
  with booking details (dates, amount) on payment.succeeded webhook
- Webhook now sends confirmation email to guest email if present

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 03:02:18 +03:00
fb7d907b40 feat: YooKassa webhook, payment timeout, reserved status, BookingConfirmPage
- Add YooKassa webhook handler (POST /api/yookassa/webhook) that updates
  booking to confirmed+paid on payment.succeeded, cancelled on payment.canceled
- Add public status endpoint GET /api/online-bookings/:id for return URL polling
- Add 'reserved' booking status (violet) shown in calendar while awaiting payment
- Add 'website' to BookingSource type
- Payment timeout job (every 2 min) auto-cancels expired unpaid bookings
- Widget setting: "Время ожидания оплаты" (5–60 min, default 15)
- BookingConfirmPage at /booking-confirm/:id — polls status, countdown timer,
  shows success/pending/cancelled state
- Widget bookings now use status='reserved' instead of 'inquiry' when YooKassa
  is configured; set to 'confirmed' by webhook on payment.succeeded
- Migration 070: add 'reserved' to bookings status constraint + payment_expires_at

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 02:55:24 +03:00
b0bbb11c87 fix: dates timezone, guest lookup fallback to bookings, regex fix
- localDate() uses local timezone instead of UTC (prevents showing yesterday
  after midnight in UTC+3)
- Guest lookup: fallback to bookings table by guest_email/guest_phone when
  no guest profile found in CRM (guests table)
- Fix REGEXP_REPLACE pattern from '\D' (PCRE) to '[^0-9]' (PostgreSQL POSIX)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 02:41:38 +03:00
e1d74ff63d fix: add 'website' to bookings source check constraint
Widget bookings use source='website' which violated the existing
constraint. Migration 069 expands the constraint to include 'website'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 02:28:17 +03:00
2b20fc18b3 feat: blacklist, guest lookup, bank transfer payment page
- Add is_blacklisted column to guests (migration 068)
- POST /widget/bookings: block blacklisted guests (403, generic message)
- GET /widget/:slug/guests/lookup: find existing guest by email/phone for auto-fill
- Widget form: debounce guest lookup on email/phone input, show "Заполнить" suggestion
- Widget payment screen: replace fake card form with proper YooKassa redirect screen or bank transfer details page
- Add bank transfer details field to widget settings (saved as widget_bank_details)
- GuestsPage: blacklist toggle button in edit modal + 🚫 ЧС badge in table

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 01:31:10 +03:00
328753fc1e feat: persist assignment_strategy + apply to widget category bookings 2026-04-07 21:45:12 +03:00
ab37430243 feat: widget — room display mode toggle (by room / by category) 2026-04-07 20:46:30 +03:00
57531f2022 feat: long-stay deposit charge + refunds + auto-confirm widget + white page fix
- Widget white page fix: fallback photo when real rooms have no photos array
- Long-stay deposits: if booking > threshold days and toggle on → full charge instead of hold (bypasses YooKassa 7-day limit)
- DepositSettingsPage: toggle + day threshold input for long-stay full payment
- Refund endpoint: POST /deposit/refund supports full and partial refunds via YooKassa refunds API
- DepositWidget: refund UI for yookassa_charge deposits (full/partial, shows remaining amount)
- partially_refunded status support with badge and "Вернуть ещё" button
- Auto-confirm: webhook now auto-confirms booking status on payment.succeeded for widget bookings
- PaymentSettingsPage: auto-confirm toggle per gateway (shown when booking-widget module active)
- Migration 067: long_stay columns on hotel_deposit_settings, auto_confirm_on_payment on gateways, refunded_amount on deposits

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 20:30:26 +03:00
bddfa355f2 feat: deposit release lock + billing fixes + centralized payment gateway
- Add releaseRequiresCheckout toggle in DepositSettingsPage (UI + payload)
- Migration 066: release_requires_checkout column on hotel_deposit_settings
- Migration 065: hotel_payment_gateways table for centralized YooKassa config
- Block deposit release buttons when releaseRequiresCheckout && not checked_out
- Fix billing calculation: use Number() to prevent PostgreSQL NUMERIC string concat
- Fix minibar billing: show current price (JOIN minibar_items.price) not recorded price
- Auto-poll deposit status every 5s while hold_created; clear QR message on confirmation
- Add billing/folio tab in BookingDetailPanel (room charges + minibar + payments + balance)
- PaymentSettingsPage: new "Онлайн-оплата (ЮКасса)" section with gateway CRUD
- DepositSettingsPage: replace YooKassa fields with link to centralized payment settings
- BookingWidgetPage: connect to real API, gateway status banner, async booking submission
- New backend routes: paymentGateways, publicWidget
- YooKassa service: add createCharge() for immediate payment (online bookings)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 20:47:52 +03:00