- Форма удержания: позиции мини-бара расписаны по отдельности (товар × кол-во, цена/шт.)
с кнопками «+ Добавить» для каждой и «+ Добавить всё»
- 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>
- 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>
- 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>
- 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>
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>
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>
- 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>
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>
- 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>
- 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>
- 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>
- 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>
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>
- TvWelcomePage: hide server URL input when connection_type=agent
- Test button enabled in agent mode without serverUrl
- Backend test endpoint: derive server_url from workstation netup device
- getNetupCfg: resolve URL from netup device in agent mode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- WorkstationCard: useEffect to sync name state when ws.name prop changes
- agent-ws: update hostname column on hello message
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Relative /agent-updates/ path was resolving to app.hotelsync.ru
(frontend) instead of api.hotelsync.ru, returning HTML instead of .exe
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- EquipmentPage: added "ID устройства в ДТО" field (config.dto_device_id),
shown on device card as #1
- api.ts: typed config.dto_device_id in WorkstationDevice
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Equipment page: add login/password fields in KKT fiscal mode form
- Stored in device config JSON (dto_user, dto_pass)
- Backend passes credentials to agent in test_device command
- Agent uses per-device credentials for all ATOL DTO API calls
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Migration 045: fiscal_mode + dto_port columns on workstation_devices
- Backend: pass fiscal_mode/dto_port to agent in test_device command
- Agent atol.ts: remove global DTO singleton, accept dtoPort per function call
- Agent test_device: returns real KKT info (model, serial, FN, shift status)
- Equipment page: fiscal/non-fiscal toggle + DTO port field when adding KKT
- Equipment page: show fiscal mode badge on device card, DTO port if non-default
- Equipment page: KKT test result shows model, serial, FN, shift status
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- EquipmentPage: add COM-port connection type with port field (COM1, /dev/ttyUSB0)
- api.ts: add comPort field to WorkstationDevice type
- workstations route: pass com_port to INSERT/PATCH
- migration 042: extend connection check constraint to include 'com', add com_port column
- agent index.html: highlight first cell on load, reset input on unpair
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
setupAgentWsRoute was called outside the encapsulated wsRoutes plugin
scope where @fastify/websocket was registered. Register the plugin at
the root level and convert setupAgentWsRoute to FastifyPluginAsync.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ALTER TABLE ADD CONSTRAINT validates existing rows immediately, so the
UPDATE must run first to migrate 'standard' → 'bronze' before the new
constraint is applied.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>