- 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>
- 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>
- 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>
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>
- 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>
- 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>
- PosPage.tsx: полный редизайн вокруг реальной кассовой работы
• Автодетект агента (localhost:9000), статус смены каждые 15с
• Экраны: агент не подключён / смена закрыта / смена истекла / касса
• Шапка смены: №, длительность, остаток наличных, статус ОФД
• Кнопки: X-отчёт, Внести/Изъять наличные, Закрыть смену
• Левая панель: гости с балансом + быстрые услуги
• Правая панель: чек с количеством, итого, выбор наличные/карта
• Экран успеха после пробития чека с номером ФД
- agent.ts: клиент localhost:9000 — identity + все kkt операции
- index.css: btn-danger класс
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Migration 034: extend users table with phone, position, active columns; add new roles (receptionist, accountant, security)
- Backend users.ts: accept phone/position on create, accept phone/position/active/role on update; managers can now assign all non-admin roles
- types/index.ts + api.ts: add phone, position, active to User type and users create/update payload types
- UsersPage: fix mapRole to handle all 5 roles, fix toStaffUser to use real phone/position/active from API, fix handleSave to pass all fields
- SettingsPage: load SMTP settings from hotelSettings API on mount, save SMTP on handleSave when section === 'notify'
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add hourly_price column to rate_overrides, base_price/allow_hourly/hourly_base_price to room_categories (migration 033)
- Update categories and rate-overrides backend routes to handle new fields
- AvailabilityPage: hourly/nightly mode toggle, period prices auto-applied to grid with 'П' indicator, confirmation dialog when overriding period prices
- BookingModal: nightly/hourly toggle at top, pricing hierarchy (override → category base → room rate)
- RoomCategoriesPage: base_price/allow_hourly/hourly_base_price fields in category form; inline rooms panel
- CalendarPage + BookingCalendar: pass hourlyPriceOverrides and categoryBasePrices down to BookingModal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- TechnicalPage: redesign task cards with separate "Задача" and "Решение" sections
- "Задача": problem description + problem photos (editable before done)
- "Решение": technician resolution comment + resolution photos (green section)
- Resolution comment in CompletionModal now saves to resolution_notes field
- Resolution photos save to resolution_photos (no longer mixed with problem photos)
- RoomModal history tab: maintenance entries show separate problem/resolution sections
- Backend: add resolution_notes, resolution_photos to PATCH allowed fields
- Migration 029: resolution_notes TEXT + resolution_photos TEXT[] columns
- RoomsPage: load active maintenance tasks, show 🔧 неисправность badge on room cards
- CalendarPage: load active maintenance tasks, show 🔧 неиспр. label in calendar row
- BookingCalendar: activeMaintenance prop, WS handler updates maintenance set on task changes
- CalendarPage WS: handle housekeeping_task_created to update activeMaintenance set
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- RoomModal: new "История" tab showing all housekeeping and maintenance tasks for the room
- Groups tasks by date (newest first), expandable cards
- Shows: assignee, start time, end time, duration (started_at → completed_at)
- For maintenance: description, problem/solution photos with lightbox viewer
- Timing grid: start / completion timestamps
- Backend GET /housekeeping: add room_id filter param
- TechnicalPage CompletionModal: add resolution comment field saved to task notes
- api.ts: housekeeping.list() accepts roomId param
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Calendar: hover tooltip on '🧹 убирается' shows who is cleaning (assignee name)
fetched from active HK tasks on mount, updated via WS housekeeping_updated events
- HousekeepingPage: 'Сообщить о неисправности' now creates a real maintenance task
in the DB (category=maintenance) + sends WS so TechnicalPage receives it instantly;
photo upload added to the report form
- RoomContextMenu: photo upload added to tech task creation form (camera button,
preview thumbnails); photos passed through to API on task creation
- TechnicalPage: selecting 'Выполнено' now opens CompletionModal with photo upload;
technician can attach completion photos before confirming; if require_completion_photo
setting is on — at least one photo is required
- Settings (HousekeepingPage plans tab): new toggle 'Обязательное фото при завершении
тех. задачи' saved to housekeeping_settings.require_completion_photo (migration 027)
- Backend: housekeeping-settings updated with require_completion_photo field
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove 'Убирается' (cleaning) from manually selectable HK statuses in calendar context menu — it now only shows automatically when a task is in_progress
- Reduce priorities from 4 to 3: remove 'high', keep urgent/medium/low across context menu, tech tasks modal, and HousekeepingTask type
- Add photo upload to technical tasks: upload via camera button per task, display thumbnails with hover-to-remove, stored as TEXT[] in DB (migration 026)
- Fix HousekeepingPage WS handler to filter by category — no longer adds maintenance tasks to housekeeping board
- Add 'tasks' folder support to upload route
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add migration 025 to add `category` column (housekeeping/maintenance) to housekeeping_tasks
- Backend: filter by ?category= in GET, store category in POST, allow patching category
- API client: HkPayload + housekeeping.list() now support category param
- HousekeepingPage: filters by category=housekeeping so maintenance tasks don't appear
- RoomContextMenu: priority sub-form for dirty/cleaning status, tech task creation inline form
- BookingCalendar: accepts onMaintenanceTaskCreate prop, passes priority to onRoomUpdate
- CalendarPage: handleRoomUpdate creates task for dirty+cleaning (with priority), new handleMaintenanceTaskCreate for category=maintenance tasks
- TechnicalPage: new page for viewing/creating/updating maintenance tasks
- App.tsx: /technical route added
- Sidebar: Тех. задачи nav item added under Управление
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- notifications table (024 migration) — stores per-hotel notifications
- GET/PATCH/POST/DELETE /api/hotels/:slug/notifications endpoints
- createNotification() helper used by jobs + future hooks
- jobs.ts — background tasks every 10min: auto-cancel no-shows and
auto-checkout overdue stays based on hotel_settings, broadcasts WS
events and creates notifications for each automated action
Frontend:
- CalendarPage: handle housekeeping_done WS → update room status in
calendar in real-time (bug fix)
- CalendarPage: when manually setting room to dirty via context menu,
auto-create housekeeping task and broadcast via WS (bug fix)
- NotificationsContext: replaced mock data with real API integration,
polls every 60s, syncs markRead/delete to backend
- SettingsPage booking section: auto-cancel no-show toggle + hours
selector; auto-checkout toggle + hours selector
- api.ts: notifications API methods
- useHotelSocket: notification:new WS message type
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PostgreSQL NUMERIC columns are returned as strings by node-postgres.
Added normalizeRoom() that coerces baseRate/hourlyRate/floor to JS numbers
before they reach components, eliminating string concatenation bugs like
"300005000.005000.00" in price calculations.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- Migration 023: housekeeping_settings table (checkout_auto, checkout_priority, inspection_after_clean)
- New route: GET/PATCH /api/hotels/:slug/housekeeping-settings
- bookings.ts: on checked_out → auto-create turnover task + mark room dirty + broadcast WS
- housekeeping.ts: on task done → update room status (inspect|clean per setting) + broadcast WS
- ws.ts: export broadcast() for use in other routes
Frontend:
- HousekeepingPage: load/save settings to API, live Loader on save button
- useHotelSocket: add housekeeping WS message types
- HousekeepingPage: subscribe to WS — new tasks appear instantly without refresh
- BookingModal: show total section when room + nights selected (not just when total > 0)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Calendar cells: hold mouse 600ms to show price tooltip for that date/room (baseRate + hourly rate if enabled)
- AvailabilityPage: load real categories from API, derive basePrice from room baseRates, fall back to room types if no categories
- Rate periods: persisted to DB via new /api/hotels/:slug/rate-periods endpoint
- New backend migration 021_rate_periods.sql + rate-periods route
- Added api.ratePeriods.{list,create,update,delete} to frontend API client
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DB migration 020: add maintenance_from, maintenance_to to rooms
- Room type + RoomPayload: maintenanceFrom, maintenanceTo fields
- Context menu: clicking "На ремонт"/"Закрыт" expands inline date picker
(С / По + "Без срока" checkbox), confirms with "Применить" button
- Calendar: stripe overlay covers only the maintenance date range
(full row if no dates set, partial if date range specified)
- Booking creation: if drag-selected dates overlap with maintenance period,
shows warning dialog with "Отмена" / "Всё равно забронировать"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DELETE /api/upload?url= endpoint removes file from disk (with path traversal protection)
- api.upload.deletePhoto() helper in frontend
- removePhoto() now calls deletePhoto for cdn.hotelsync.ru URLs (best-effort)
- handleDragOver only highlights drop zone for external file drags (not thumbnail DnD)
- <img> in preview gets draggable={false} + pointer-events-none to not interfere with drops
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- api.upload.photo used wrong localStorage key ('access_token' vs 'hotelsync-session')
- Removed standalone 'Загрузить фото' button — drop zone handles click+drag
- Added '+' thumbnail button to add more photos when gallery is non-empty
- Show spinner inline during upload instead of disabled button
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Upload route now accepts ?folder=categories|rooms|hotels|guests
- Files saved to /uploads/{folder}/uuid.ext, served at cdn.hotelsync.ru/{folder}/uuid.ext
- api.upload.photo() accepts optional folder param (default: 'rooms')
- RoomCategoriesPage passes folder='categories' on photo upload
- SSL cert issued and nginx config active for cdn.hotelsync.ru
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>