- Standalone page: ?width=full removes max-w-lg container constraint
- iframeSnippet: passes &width=full to URL when 100% width selected
- public/widget-loader.js: hosted script, reads data-* attrs, no inline code needed
- data-mode="floating" → styled FAB button bottom-right
- data-mode="trigger" → listens for .hotelsync-book class clicks
- Proper modal with close button and backdrop click
- Code tab snippets for floating/trigger are now single <script src> lines
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- displayRooms now maps r.photos (URL strings) to photoUrls[]
- Collapsed thumbnail: shows <img> if real photo URL exists, falls back to emoji
- Expanded gallery: full-width <img> for real photos, gradient for mock
- Thumbnail strip: renders real photo thumbnails when available
- Photo nav buttons use totalPhotos (real or mock count)
- beds/area/rating hidden when value is 0 (real rooms have no beds/area/rating data)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- Cards now show wide photo (h-44) at top instead of small thumbnail
- Gallery navigation: left/right arrows on hover, dot indicators, counter
- Gradient overlay with available badge (top-left) and photo count
- Select button at bottom of each card
- Graceful fallback to 🛏️ placeholder when no photos
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Services can now be added, edited inline (icon/name/price/unit), and deleted
- List persisted to DB as widget_services key in hotel_settings (auto-save)
- Services loaded from DB on settings page init; fall back to DEFAULT_SERVICES
- Toggle enable/disable retained; edit/delete buttons appear on row hover
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Category cards now show photos[0] as <img> when available, fallback to 🛏️ emoji
- Settings page preview loads rentalObjects from API and passes to WidgetPreview
- MockCategory type and MOCK_CATEGORIES get photos: [] to match updated type
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- 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>
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>
- 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>
- 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>
- 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>
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>
- On API error: show error message instead of false success screen
- Success message: "Оплата прошла успешно" only when paidViaGateway=true (user actually clicked pay in YooKassa); otherwise "Бронирование принято. Менеджер свяжется с вами" or "Оплата на месте"
- Payment screen: simplified to YooKassa-only redirect (removed bank transfer variant)
- Removed bankDetails from WidgetSettings entirely (not needed per product decision)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Always show payment step when paymentEnabled=true even if YooKassa
confirmationUrl is null (payment creation failed silently)
- Add third payment screen variant: "manager will contact you" when no
YooKassa URL and no bank details configured
- Add Russian phone mask +7 (___) ___-__-__ on phone type fields
- Default check-in/check-out dates to today/tomorrow instead of hardcoded past dates
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
BookingWidgetStandalonePage now fetches widgetSettings from the public
config API and applies them (color, language, mode, etc.) instead of
always falling back to URL param defaults. URL params still work as
overrides if present.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- 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>
- Add activeDeposit state to BookingDetailPanel via DepositWidget callback
- On checkout, if active deposit (hold_confirmed/paid_cash) exists and balance > 0:
show checkbox 'Зачесть депозит в счёт оплаты' with editable amount
- On confirm: capture deposit via /deposit/release + add booking_payment record
- Default apply amount = min(deposit.amount, outstanding balance)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- w-[400px] → w-[560px] для боковой панели бронирования
- Вкладка «Оплата»: расчёт + платежи слева, депозит справа (grid 2 cols)
- На мобиле — одна колонка как прежде
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ранее в форме удержания депозита показывалась цена на момент уборки (price_per_unit).
Теперь используется текущая цена позиции (mi.price) — чтобы изменения цен сразу
отражались в счёте. Если цена менялась — показываем индикатор ↑ с tooltip.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Форма удержания: позиции мини-бара расписаны по отдельности (товар × кол-во, цена/шт.)
с кнопками «+ Добавить» для каждой и «+ Добавить всё»
- Email гостю: HTML-шаблон с таблицей-чеком (позиция / сумма), итоговые строки,
остаток возврата, брендинг отеля; текстовый fallback сохранён
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- MinibarSettingsPage: поле категории → выпадающий список с существующими + «Новая...»
- Кнопка «Учёт и остатки» ведёт на /settings/minibar/stock
- MinibarStockPage: 5 вкладок — Остатки, Приходы, Списания, Инвентаризация, Отчёт
- api.ts: добавлены методы и типы для stock/receipts/writeoffs/inventory/report
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wrong column names in SQL query: price_at_time→price_per_unit,
created_at→recorded_at. Also fixed type mismatch priceAtTime→pricePerUnit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DepositSettingsPage: deduction presets management (add/edit/delete inline)
- DepositHistoryPage: "← Назад" link back to settings
- Amount inputs: onFocus selects all text
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Migration 060: card_last4, card_brand columns in booking_deposits
- Backend: webhook saves card brand/last4 from YooKassa notification
- Backend: GET /api/hotels/:slug/deposits — payment history with guest/room info
- Backend: DELETE /api/hotels/:slug/bookings/:bookingId/deposit — cancel hold_created or paid_cash deposit
- Backend: auto-create YooKassa hold + send deposit email for source='online' bookings
- Frontend: DepositHistoryPage with filters, card display, retention reasons
- Frontend: deposit cancel button for hold_created and paid_cash states
- Frontend: show card last4/brand in hold_confirmed status
- Frontend: release form presets (full return / hold all) + improved UX
- Frontend: webhook setup instructions in DepositSettingsPage
- Frontend: "История платежей" link in deposit settings header
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Backend: GET /api/pay/:slug (public) — returns active hold confirmation URL
- PayDepositPage: public page /:slug/pay for guests (no login required)
- DepositSettingsPage: QR code section with print button
- BookingDetailPanel: DepositWidget in payment tab
- Наличными / ЮКасса (QR) buttons
- hold_created: copy link + refresh
- hold_confirmed: release form (вернуть / списать)
- Final status badges
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- MinibarSettingsPage: Категории section — inline rename updates all items
- HistoryCard: expand button loads checklist + minibar on demand
- Minibar ✓ badge shown on history cards when minibarChecked
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>