- 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 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>
- 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>
- 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>
- MinibarSettingsPage: поле категории → выпадающий список с существующими + «Новая...»
- Кнопка «Учёт и остатки» ведёт на /settings/minibar/stock
- MinibarStockPage: 5 вкладок — Остатки, Приходы, Списания, Инвентаризация, Отчёт
- api.ts: добавлены методы и типы для stock/receipts/writeoffs/inventory/report
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>
- Floating toast notifications (top-center, auto-dismiss 4s) instead of
inline alerts in section 2 — visible regardless of scroll position
- Edit button on each lock mapping (inline form, supports MAC/room/name change)
- Sector picker: 2 rows of 8 (grid-cols-8), removed "Обычно 1–10." hint
- Removed API server selector (always EU, hardcoded in save)
- Removed buildNo/floorNo fields from UI (backend defaults to 1)
- Renamed "Проверить API" → "Проверить подключение"
- Save COM-port button: HardDriveDownload → Save (floppy disk) icon
- Removed debug-only Terminal and Search buttons from workstations row
- Unmap now deletes by MAC (consistent with edit flow)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Agent: ttlock:find_api_config command scans TTHotel app JS files
for CE_ConfigServer calls, oauth2 endpoints, API server URLs
- Backend: POST /ttlock/find-api-config route
- UI: Search button on each workstation row to trigger scan
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix: api_server was missing from SELECT in issue-card route (always fell back to EU default)
- Add "Проверить API" button in Step 1 that calls ttlock:test_api via agent
- Agent: testCloudApi() — gets OAuth token + lists locks, returns count
- Agent: apiPost() now includes response body in error message (was just HTTP status)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add api_server column to hotel_ttlock_config (migration 053)
- GET/PATCH config includes apiServer field
- issue-card passes apiServer to agent command
- Agent: WriteCardCmd.apiServer optional, all API calls use it
- TTLockPage: radio buttons EU/China with hint about 404 error
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Parse .xls/.xlsx via SheetJS (xlsx package)
- Auto-match Door Name → room number; unmatched rows kept with empty room
- Preview table: green=matched, amber=unmatched, grey=already mapped
- Per-row room dropdown for manual correction before import
- room_id nullable in room_lock_mappings (migration 052)
- Conflict key changed from (hotel_id, room_id) to (hotel_id, lock_mac)
- LEFT JOIN in GET room-locks to show unmatched locks
- DELETE by UUID or MAC depending on whether room is assigned
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>