Commit Graph

349 Commits

Author SHA1 Message Date
6859817bdf feat: centralized payment gateways + booking widget API
Backend:
- Migration 065: hotel_payment_gateways table (migrates YooKassa from deposit_settings)
- online_bookings table for widget submissions
- Routes: CRUD /api/hotels/:slug/payment-gateways
- Public widget API: /api/widget/:slug/{config,availability,bookings}
- yookassa.ts: add createCharge() for immediate capture

Frontend:
- PaymentSettingsPage: add 'Онлайн-оплата' section with gateway CRUD and module toggles
- DepositSettingsPage: replace YooKassa fields with link to centralized settings
- BookingWidgetPage: connect to real API, show gateway status, real booking submit
- api.ts: add PaymentGateway types, paymentGateways and widget API methods

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 20:37:06 +03:00
340b3ddec9 feat: deposit auto-poll + billing folio tab
- DepositWidget: auto-poll every 5s while hold_created, clear QR message on hold_confirmed
- Add 'Счёт' tab (billing folio): room charges + minibar lines + payments + deposit badge + balance
- Billing data loads lazily on tab open

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 20:24:03 +03:00
bb72a24757 feat: deposit auto-apply to room payment on checkout
- 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>
2026-04-06 16:21:20 +03:00
f94d4723d5 feat: настройки оплаты — способы оплаты + контроль при заселении
- Миграция 064: таблица hotel_payment_methods, поле require_payment_checkin
- Бэкенд: CRUD /api/hotels/:slug/payment-methods, /api/hotels/:slug/payment-settings
- PaymentSettingsPage (/settings/payments): управление способами оплаты
  (название, валюта, тип, активность, сортировка), контроль при заселении (нет/мягкий/жёсткий)
- BookingDetailPanel: динамические методы оплаты вместо захардкоженных
- Кнопка «Заселить»: hard — заблокирована при балансе > 0; soft — предупреждение с выбором

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 16:12:38 +03:00
45a8cf7c10 ui: боковая панель — ширина 560px, вкладка «Оплата» двухколоночная
- w-[400px] → w-[560px] для боковой панели бронирования
- Вкладка «Оплата»: расчёт + платежи слева, депозит справа (grid 2 cols)
- На мобиле — одна колонка как прежде

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 15:59:20 +03:00
83d797c55f fix: minibar deposit — использовать актуальную цену из справочника, а не историческую
Ранее в форме удержания депозита показывалась цена на момент уборки (price_per_unit).
Теперь используется текущая цена позиции (mi.price) — чтобы изменения цен сразу
отражались в счёте. Если цена менялась — показываем индикатор ↑ с tooltip.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 15:51:34 +03:00
54d0d2d817 feat: deposit release — детальный список позиций + HTML-чек на email гостю
- Форма удержания: позиции мини-бара расписаны по отдельности (товар × кол-во, цена/шт.)
  с кнопками «+ Добавить» для каждой и «+ Добавить всё»
- Email гостю: HTML-шаблон с таблицей-чеком (позиция / сумма), итоговые строки,
  остаток возврата, брендинг отеля; текстовый fallback сохранён

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 15:42:14 +03:00
1f4def0a34 feat: minibar — category dropdown, учёт остатков (приходы/списания/инвентаризация/отчёт)
- MinibarSettingsPage: поле категории → выпадающий список с существующими + «Новая...»
- Кнопка «Учёт и остатки» ведёт на /settings/minibar/stock
- MinibarStockPage: 5 вкладок — Остатки, Приходы, Списания, Инвентаризация, Отчёт
- api.ts: добавлены методы и типы для stock/receipts/writeoffs/inventory/report

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 15:36:40 +03:00
5b46175c91 fix: minibar consumptions not showing in deposit release form
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>
2026-04-06 15:28:38 +03:00
d686f5abe6 feat: deposit presets UI, back button in history, onFocus select on inputs
- 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>
2026-04-06 13:03:20 +03:00
1ab24dfe27 feat: deposit deductions, booking payments persistence, minibar in release form
- Migrations 061 (booking_payments) + 062 (deposit_deduction_presets)
- Backend: booking payments CRUD (GET/POST/DELETE), auto-updates paid_amount on booking
- Backend: deposit deduction presets CRUD (GET/POST/PATCH/DELETE)
- Backend: GET /deposit/minibar endpoint returns minibar consumptions for booking
- Backend: release endpoint accepts items[] for itemized email to guest
- Frontend: payments loaded from API — persist across page reloads
- Frontend: deposit release form redesigned — preset buttons, minibar auto-fill, itemized list
- Frontend: onFocus select on all amount inputs (no more manual "0" removal)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 12:55:42 +03:00
633625e7be fix: deposit release — read captured_amount (snake_case) from request body
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 12:38:43 +03:00
a4fc4a0972 feat: deposit — history page, card info, cancel button, webhook notice, online booking email
- 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>
2026-04-06 11:47:51 +03:00
754f03b7fa feat: deposit module — QR flow, PayDepositPage, BookingDetailPanel widget
- 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>
2026-04-06 11:22:55 +03:00
b91859218a feat: add cancel button to revert in_progress task back to pending
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 10:00:35 +03:00
6feef47e51 feat: TaskCard step-by-step UX — auto-open checklist, minibar item inline, labeled buttons
- Auto-open checklist when task is in_progress (on mount)
- Detect 'минибар' checklist item: tap opens inline minibar selection
  - Record consumptions + confirm with single button
  - Auto-checks the item + sets minibar_checked
- Replace icon-only buttons with labeled: 'Замечание' + 'Завершить уборку'
- 'Замечание' always accessible without completing cleaning

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 09:56:25 +03:00
d6bb46a67f feat: show maintenance note in history card expanded details
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 23:35:47 +03:00
8e6d66bef8 feat: minibar category rename + history card checklist/minibar details
- 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>
2026-04-02 23:27:30 +03:00
cc7c29abc9 fix: checklists.ts — housekeeping-rules routes were outside export function
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 23:12:03 +03:00
d98389e2bc feat: enforce checklist/minibar completion before marking task done
- ApiError now carries errors[] array from 422 responses
- updateStatus re-throws 422 so TaskCard can surface errors
- TaskCard: minibarCheckedLocal state + 'Подтвердить проверку минибара' button
- TaskCard: async handleComplete with completionErrors display
- Backend validation errors shown inline in completion panel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 23:09:03 +03:00
3c1a6ccebf feat: checklists, minibar and deposit modules
- DB migrations 057-059: checklist_templates/items/completions, minibar_items/consumptions, hotel_deposit_settings, booking_deposits
- Backend routes: checklists (templates CRUD + task completions), minibar (items + consumptions), deposit (settings, cash/yookassa hold, release/capture)
- YooKassa service for hold/capture/cancel payments
- Frontend: ChecklistSettingsPage, MinibarSettingsPage, DepositSettingsPage
- HousekeepingPage: task cards now show checklist + minibar panel with checkboxes and quantity buttons
- BookingModal: minibar charges summary + deposit management (cash/YooKassa/release) for existing bookings
- Sidebar + App.tsx: new routes /settings/checklists, /settings/minibar, /settings/deposit

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 19:24:53 +03:00
ed2dfd4c7f feat: TTLock UI — toast notifications, edit mappings, sector grid 2x8
- 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>
2026-04-02 18:58:33 +03:00
1ad2a8057a feat: TTLock — correct DLL flow with buildNo/floorNo per lock
Backend: room-locks now store/return build_no, floor_no (required by
CE_WriteCard). issue-card passes buildNo/floorNo to agent instead of
cardSectors/username/password. testApi route simplified (no OAuth).

Frontend: remove username/password fields (not needed), add buildNo/floorNo
fields to lock mapping form, show building/floor in lock list.

Migration 056: adds build_no, floor_no columns to room_lock_mappings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 11:59:38 +03:00
8c215f2dcd feat: TTLock — add Учетная запись + Пароль fields for OAuth
Per Sciener docs: OAuth requires TTHotel account credentials (Учетная запись + Пароль)
separate from developer app client_id/client_secret.

- Migration 055: add ttlock_username, ttlock_password columns
- Backend: store and pass new fields to agent
- UI: add input fields matching TTHotel PMS integration page
- Agent: getAccessToken() uses ttlockUsername/ttlockPassword when provided

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 11:46:30 +03:00
a7b59c4881 fix: TTLock — revert to 2-field credentials, improve UI hints
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 11:38:07 +03:00
84ead2af99 fix: TTLock — correct OAuth server (api.sciener.com) and API servers (euapi/cnapi)
- OAuth token: always https://api.sciener.com/oauth2/token (was euopen.ttlock.com = wrong)
- EU API: https://euapi.ttlock.com/v3/... (was euopen.ttlock.com)
- CN API: https://cnapi.ttlock.com/v3/... (was open.ttlock.com)
- Migration 054: update existing DB records with old server URLs
- UI: update radio button values to correct server names

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 11:21:37 +03:00
ee7e515f61 feat: TTLock — scan TTHotel sources for API config (CE_ConfigServer)
- 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>
2026-04-02 11:10:40 +03:00
92821a8436 fix: replace Wifi icon with Globe for TTLock API test button
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 00:48:04 +03:00
54469e7ad1 feat: TTLock diagnostics — test cloud API button + fix api_server SELECT bug
- 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>
2026-04-02 00:45:32 +03:00
a986470f8e feat: TTLock API server selector (EU vs China) + pass apiServer to agent
- 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>
2026-04-01 23:48:37 +03:00
1da3373f29 fix: xlsx static import + type cast for TS build
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 23:16:38 +03:00
1ec3664886 feat: TTLockPage — import locks from TTHotel XLS file
- 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>
2026-04-01 23:08:24 +03:00
ea63dec525 fix: TTLockPage — move alerts to workstations section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 22:56:09 +03:00
061fc165b5 feat: TTLock page redesign — sector circles, COM dropdown, DLL diagnostics
- SectorPicker: circles 1–16, click to toggle (blue=selected, gray=not)
- COM port: dropdown populated from agent's serial ports list + refresh button
- Per-workstation buttons: HardDriveDownload (save), Zap (test encoder)
- Workstations without COM port shown as dimmed with amber 'Энкодер не настроен'
- DLL entry point error shown with hint to check TTHotel libs/index.js
- Backend: GET /ttlock/dll-info?workstation_id= → reads TTHotel index.js via agent
- api.ts: added dllInfo() call

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 22:11:22 +03:00
070a333609 fix: agent-ws — safe socket.close() for non-WebSocket requests
HEAD/GET requests without WS upgrade caused 'socket.close is not a function'
crash → 500 errors in logs. Wrap close() in try-catch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 21:11:40 +03:00
4b1b7f4be8 feat: agent status badge + auto workstation detection in BookingDetailPanel
- Check localhost:9000 on panel open via getIdentity()
- If local agent is running → use its workstation_id directly, no picker
- If agent is offline → show subtle badge 'Агент не запущен · ключи недоступны' under tabs
- If agent offline but multiple server-side encoders online → still show picker

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 18:04:26 +03:00
1c1c88a37f fix: TTLock backend — use snake_case request body keys
req() auto-transforms camelCase → snake_case before sending.
Backend was destructuring camelCase (roomId, lockMac, isEnabled etc)
but receiving snake_case (room_id, lock_mac, is_enabled) — all values
were undefined, so nothing was ever saved.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 00:35:40 +03:00
f837a84db5 feat: TTLock encoder per workstation (Option A)
- Migration 051: add ttlock_com_port to workstations, drop com_port/workstation_id from hotel_ttlock_config
- Backend workstations PATCH: accepts ttlock_com_port alongside name
- Backend ttlock: test/issue-card use per-workstation com_port; auto-select online encoder if workstationId not specified
- TTLockPage Step 2: per-workstation table with COM port input, Save, Test buttons per row
- BookingDetailPanel: auto-issue if 1 encoder online, picker modal if multiple
- api.ts: TTLockConfig/Update cleaned up, Workstation gets ttlockComPort, issueCard/testEncoder updated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 23:40:29 +03:00
45697b56c3 fix: total_spent counts all non-cancelled bookings, not only checked_out
Previously guests showed 0 ₽ spent when they had confirmed/checked_in
bookings with paid_amount > 0. Now paid_amount is summed for all statuses
except cancelled and no_show.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 21:11:15 +03:00
948744e077 feat: tariff selector in BookingModal
- Migration 050: add tariff_id FK to bookings table
- Backend: accept tariff_id in POST/PATCH bookings
- Frontend: load active tariffs in BookingModal, show tariff dropdown
- Price calculation applies tariff modifier (percent or fixed) before discount
- tariffId forwarded through CalendarPage → api.bookings.create/update

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 21:02:56 +03:00
249492cf3a feat: TTLock settings redesign — correct TTHotel DLL architecture
- Settings page rebuilt with 3-step flow:
  Step 1: client_id / client_secret (from TTHotel → Settings → Integrations)
  Step 2: workstation + COM port + card sectors
  Step 3: room → lock MAC address mapping (from lock.ttlock.com)
- Agent command ttlock:write_card now uses lockMac, comPort, cardSectors
  instead of cloud-only lockId approach
- New agent command ttlock:ping_encoder for encoder connectivity check
- Migration 049_ttlock_v2.sql: lock_mac TEXT replaces lock_id BIGINT,
  card_sectors + com_port added, ttlock_username/password removed
- API types updated: TTLockConfig, RoomLockMapping, CardIssuance

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 16:29:30 +03:00
be92d026e8 feat: TTLock integration + fix agent offline status
- Add TTLock electronic lock module: settings page, room-lock mapping,
  card issuance via agent command (ttlock:write_card)
- Fix agent offline: EquipmentPage now polls every 30s + reacts to
  workstation:online/offline WebSocket events instantly
- Backend broadcasts workstation online/offline events to hotel clients
  via existing WebSocket (agent-ws.ts)
- New DB migration 048_ttlock.sql: hotel_ttlock_config, room_lock_mappings,
  card_issuances tables
- New backend routes: GET/PATCH ttlock/config, POST ttlock/token,
  GET ttlock/locks, POST/DELETE ttlock/room-locks, POST issue-card
- "Выдать ключ (TTLock)" button in BookingDetailPanel for confirmed/checked_in
- TTLock page added to sidebar under Настройки

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 15:46:17 +03:00
7a3772d1cf fix: add error handling and loading state to tariff save 2026-03-31 11:36:46 +03:00
1ffc171175 fix: add is_active to RentalObjectPayload type 2026-03-31 11:24:57 +03:00
f6d3c78dd0 feat: rental drafts, room modal cleanup, tariff auto-code
- New hotels get 3 rental object templates (tennis/sauna/conference) as drafts (is_active=false)
- RentalPage: draft templates shown separately with Activate/Delete buttons
- Migration 047: adds is_active column to rental_objects
- RoomModal: hide bed type/status/housekeeping in create mode; optional price (from category); restructured Places tab with extra beds constructor
- TariffsPage: auto-generate tariff code from name initials if code field is empty
- types: ExtraPlace.beds for extra place bed configuration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 11:19:54 +03:00
1970afdd13 fix: registration form — phone mask, required fields, snake_case body
- Address and phone now required (frontend + backend)
- Phone input mask: +7 (XXX) XXX-XX-XX
- Fix Bad Request: register body now sends snake_case keys
  (req() converts camelCase→snake_case, backend schema updated to match)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 11:23:24 +03:00
0088457f51 feat: add WiFi authorization module
- Backend: hotel_wifi_settings table (migration 046), CRUD routes
- Public API endpoint /api/wifi-auth/verify for captive portal
- Auth methods: room+lastname, room+birthdate, room+any, room_only
- Frontend: WiFiPage with settings, token management, setup guide
- Guide tabs: MikroTik, UniFi, Universal with copy-paste instructions
- Sidebar: WiFi item in Settings group

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 12:08:51 +03:00
46524077a0 fix: collapse ping section by default, expand on click
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 10:39:18 +03:00
a443ec9d28 fix: hide reconnect button when agent is online; add printer encoding/paper_width settings
- WorkstationCard: show RefreshCw reconnect only when agentId set but offline
- DeviceForm: add encoding (utf8/cp866) and paper_width fields for network printers
- Backend: pass device.config in test_device command to agent

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 10:31:17 +03:00
11300bccbf feat: ping through agent — send ICMP ping from workstation via agent
Adds a ping tool in the Equipment page workstation card (visible when
agent is online). User enters an IP/hostname, the request goes through
the PMS backend → WebSocket → agent → executes ping -n 4, returns
raw output displayed in a monospace block.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 10:08:38 +03:00