Commit Graph

44 Commits

Author SHA1 Message Date
c0945e7264 fix: add photos column to room_categories via migration 019
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 12:53:44 +03:00
dd75794a78 feat: organize uploads by folder + cdn.hotelsync.ru active
- 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>
2026-03-23 12:41:27 +03:00
f625d82cc5 fix: downgrade @fastify/multipart to ^8 and @fastify/static to ^7 (Fastify 4 compat)
v9 of both plugins requires Fastify 5.x — we're on 4.x

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 12:32:29 +03:00
3ed3156fe8 feat: move amenities directory to RoomCategoriesPage + cdn.hotelsync.ru for uploads
- Remove "Справочник удобств" block from RoomsPage, add to RoomCategoriesPage
- Fix RoomsPage imports (add Pencil back, remove unused amenity icons)
- Update hint in CategoryForm to point to current page
- Upload route now returns cdn.hotelsync.ru URLs instead of api.hotelsync.ru/uploads/
- Nginx config for cdn.hotelsync.ru (serves /opt/hotelsync/uploads directly)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 12:31:08 +03:00
3ef7f8265a feat: real file upload to /opt/hotelsync/uploads + serve via /uploads/ on API 2026-03-23 12:23:13 +03:00
5540d0cdfa fix: category form — async save with error display, drag-and-drop photos, 20MB body limit 2026-03-23 12:17:53 +03:00
5c40b154d0 feat: categories + tariffs — migration, API routes, frontend integration 2026-03-23 11:42:29 +03:00
db05898c08 feat: 15-minute rental slots + compact calendar cell display
- Rental bookings now use minutes since midnight (start_hour/end_hour
  store e.g. 495 for 8:15, 570 for 9:30) — DB migration 017
- Buffer applied in exact minutes (no hour ceiling): 15min buffer after
  9:00 booking → next slot available at 9:15
- Calendar cell: removed working-hours line (8:00–22:00), now shows
  colored dot + compact time per booking (8–10, 8:15–9:15)
- Time selects show HH:MM format for 15-min intervals

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 23:02:24 +03:00
edd493e24e feat: early check-in / late checkout fees with room-level configuration
- DB migration 016: add early_checkin_fee, late_checkout_fee to rooms table
- Backend rooms: support new fee columns in POST/PATCH
- Backend hotel-settings GET: also returns check_in_time, check_out_time
- Room type + RoomPayload: add earlyCheckinFee, lateCheckoutFee fields
- RoomModal: add fee amount inputs in pricing section
- SettingsPage: add toggles for early_checkin_enabled / late_checkout_enabled
- BookingDetailPanel: on check-in shows fee warning + adds payment record if
  current time is before checkInTime and fee is set on the room; same logic
  for check-out after checkOutTime

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 19:24:36 +03:00
b48e50e62b Rental: connect to API, improve cell UI; remove price from room labels
Backend:
- Migration 015_rental.sql: rental_objects + rental_bookings tables, seed demo data
- New routes: GET/POST/PATCH/DELETE rental-objects and rental-bookings per hotel
- Register rentalRoutes in app.ts

Frontend:
- api.ts: add rental.listObjects, listBookings, createBooking, updateBooking, deleteBooking
- CalendarPage: fetch rental objects + bookings from API instead of mock data
- BookingCalendar: rental cell UI redesigned — colored squares + working hours + time slots (matches design spec)
- BookingCalendar: remove base rate price from desktop room label column (irrelevant with dynamic pricing)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 15:46:01 +03:00
80e3b21827 Fix: drag ghost visual, check-in date auto-fill, backend room_id patch
- BookingCalendar: replace tiny ghost badge with full-size booking block
  (same color, correct width based on nights × cell width, centered on cursor)
- BookingModal: auto-set check-out to check-in+1 day when check-in changes
- Backend PATCH /bookings/🆔 add room_id to allowed fields so drag-to-move saves correctly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 00:56:49 +03:00
babd6e950e Fix: add passport_issued_by/issue_date to guests table for full autocomplete
- Migration 014: add passport_issued_by, passport_issue_date to guests table
- guests.ts: include new fields in SELECT, INSERT, PATCH + encrypt/decrypt
- booking-guests.ts: sync issued_by/issue_date back to guests on update
- GuestApiType: add passportIssuedBy, passportIssueDate fields
- selectAcGuest: fill passport_issued_by and passport_issue_date from guest profile

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 17:47:12 +03:00
887c362c18 Fix: widen passport columns to TEXT for encrypted values
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 17:28:52 +03:00
8282155237 Add AES-256-GCM encryption for sensitive personal data fields
- New backend/src/lib/crypto.ts: encrypt/decrypt (AES-256-GCM) + passportToken (HMAC-SHA256)
- Migration 012: birth_date/passport_issue_date → TEXT, add passport_search_token indexes
- guests.ts: encrypt passport_series/number/birth_date on write, decrypt on read,
  passport search via HMAC token (?passport= param) instead of plaintext LIKE
- booking-guests.ts: encrypt all sensitive fields, search token for passport lookup
- api.ts: guests.list() supports passport= param for exact passport lookup
- BookingDetailPanel: use passport param for passport-based autocomplete

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 17:19:27 +03:00
84cfca500c BookingModal: replace guestName with FIO+phone fields, remove passport tab
- Add lastName/firstName/middleName/phone fields to booking form
- Remove 'Документы гостя' tab entirely from BookingModal
- Auto-parse existing guestName into FIO parts when editing
- Add guestPhone to Booking type and propagate through CalendarPage/BookingsPage API calls
- Add middle_name to guests table (migration 011)
- Update guests backend route to include middle_name in all queries
- Add middleName to GuestApiType in api.ts
- Fix selectAcGuest in BookingDetailPanel to also fill middle_name
- Auto-open guest form pre-filled with booking FIO when no guests exist
- Add passport series+number lookup in BookingDetailPanel guest form

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 21:15:37 +03:00
873a9a4fc4 Add multi-guest management: per-booking roster, docs, guest autocomplete
- Migration 010: booking_guests table (roster per booking with passport data)
- Backend: /bookings/:id/guests CRUD — auto-links/creates guest profiles by passport
- Backend: /hotel-settings GET/PATCH for key-value settings (require_guest_docs)
- BookingDetailPanel: Гости tab with multi-guest list, inline add/edit form,
  guest autocomplete (debounced search in guests table), child/main badges,
  passport fields for adults, scan stub, require-docs warning
- SettingsPage: toggle "Обязательное заполнение документов гостей"
- Pass slug prop through CalendarPage → BookingCalendar → BookingDetailPanel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 20:53:07 +03:00
de4fb2126b Add Guests module: API integration + passport identification
- Migration 009: add passport_series, passport_number, rating to guests table
- Backend route /api/hotels/:slug/guests: list, get (with history), create/upsert by passport, update, delete
- Frontend GuestsPage: loads from API, passport tab in modal, add guest form, search by passport
- api.ts: add guests API methods and GuestApiType

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 14:03:04 +03:00
3da2d5e0ab Fix drag-to-move and WS keepalive
drag-to-move: use window-level mousemove/mouseup listeners for reliable
drag detection — React synthetic events on container were unreliable

WS keepalive: server sends ping JSON every 25s so nginx proxy_read_timeout
never fires; client ignores pings (not broadcast to peers anymore)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 13:44:12 +03:00
ed397dbcd9 Remove pull integration; persist push log to DB (last 50 per hotel)
- Remove TravelLine/netup-pms pull route (travelline.ts unused)
- Remove pull integration section from TvWelcomePage (token field, API URL, etc.)
- Migration 008: netup_push_log table (hotel_id, action, status, request/response body)
- recordPush() now writes to DB in addition to in-memory buffer
- getLog reads from DB so logs survive server restarts
- clearLog deletes from DB
- Limit enforced: 50 rows per hotel (oldest auto-deleted)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 12:55:39 +03:00
32115399ee Fix reservation_id: numeric string via hex→decimal UUID conversion 2026-03-19 12:40:52 +03:00
f5ade5cb53 Fix: reservation_id must be string not number (NetUP API) 2026-03-19 12:34:54 +03:00
c656d0c6ae Log NetUP request/response body; fix reservation_id to be numeric
- reservation_id converted to integer (NetUP likely requires number not UUID string)
- Log requestBody sent to NetUP and responseBody received from NetUP
- Push event rows now expandable: click to see URL, request JSON, NetUP error response
- Same for checkout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 12:29:45 +03:00
44fa9d24f1 Improve NetUP diagnostics: push event log + test check-in button
- notifyNetupCheckin/Checkout now log every attempt (ok/error/skipped)
  with room number, NetUP room, URL, HTTP status, error message
- Push log exposed via GET /netup/log alongside pull request log
- New POST /netup/test-checkin: manually trigger a test check-in from UI
- Diagnostics tab split into two sections:
  - Outgoing (push): table of check-in/check-out events with status dots
  - Incoming (pull): NetUP poll requests (if TravelLine integration works)
- Test button picks first mapped room and fires a real check-in call

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 12:19:48 +03:00
142be49211 Add Diagnostics tab to TV Welcome: view NetUP poll requests in UI
- Rename pull endpoint /travelline → /netup-pms (cleaner branding)
- Add GET/DELETE /api/hotels/:slug/netup/log endpoints
- TvWelcomePage: new 'Диагностика' tab shows incoming NetUP requests
  with method, URL, timestamp list + detail panel (headers, query, body)
- Remove 'TravelLine' wording from UI, replace with 'Pull-интеграция'

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 19:08:57 +03:00
8350309645 Add TravelLine pull integration for NetUP: capture endpoint + tl_token
- New /travelline route: accepts all NetUP polls, logs them to in-memory
  ring buffer (GET /travelline/_log to inspect), responds with active
  reservations in a generic PMS format
- Migration 007: adds tl_token column to netup_settings
- PATCH netup/settings now saves tl_token; GET returns it
- TvWelcomePage: new TravelLine section with read-only API URL + copy
  button and token input field

Setup in NetUP: Integration type = TravelLine,
API URL = https://api.hotelsync.ru/travelline, Token = <tl_token>

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 18:57:10 +03:00
cc5f9c18d3 Fix NetUP API path: /mw/a → /mw/api (actual server URL from spec)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 18:27:55 +03:00
61d25628ae Add NetUP IPTV integration module (TV Welcome settings page)
- DB: migrations/002_netup.sql — netup_settings + netup_room_mapping tables
- Backend: routes/netup.ts — GET/PATCH settings, POST test, GET/POST room
  mappings, POST send TV message; notifyNetupCheckin/Checkout helpers
- Backend: bookings PATCH — fire-and-forget NetUP check-in/out on status change
- Frontend: TvWelcomePage — connection settings tab + room mapping tab
- Frontend: sidebarItem added to tv-welcome module (shows in sidebar menu)
- Frontend: ModulesPage Settings button navigates to /tv-welcome
- Frontend: BookingModal — "Сообщение гостю на TV" panel for existing bookings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 18:18:41 +03:00
bda0cbb7d5 Improve email deliverability: plain text, contrast fix, Content-Language, List-Unsubscribe
- Add plain text (text:) version to all emails — helps SpamAssassin scoring
- Add lang="ru" to <html> and Content-Language: ru header — signals intentional Cyrillic
- Fix HTML_FONT_LOW_CONTRAST: change #94a3b8 to #475569 / #334155 for note text
- Add List-Unsubscribe header (reduces spam score)
- Refactor: extract baseHtml() helper to avoid duplication

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 20:01:25 +03:00
684073c4ad Fix booking creation: localStorage token, conflict detection, paid_amount
- Fix api.ts getToken/saveToken to read from localStorage (matching AuthContext) — was reading sessionStorage causing all API requests to fail auth until refresh
- Fix session cleanup on 401 to use localStorage
- Add isConflict/availableRooms helpers to BookingModal; show amber warning when selected room is occupied with clickable free room suggestions
- Pass bookings prop to BookingModal via BookingCalendar
- Add paid_amount to backend POST /bookings INSERT
- Show alert() when booking creation fails so user sees the error
- Pass paidAmount in CalendarPage handleCreate

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 19:48:37 +03:00
e59bc752b0 Add password letter requirement + labeled strength UI + auto-capitalize name fields
- Reject pure-numeric passwords (must contain ≥1 letter) in RegisterForm, ResetPasswordPage, and backend register/reset-password handlers
- Replace bare strength bars with labeled 2×2 grid: ≥8 символов, Содержит букву, Заглавная буква, Цифра
- Auto-capitalize first letter of each word in "Название отеля" and "Контактное лицо" fields (autoCapitalize="words" + JS handler)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 19:35:54 +03:00
e3f37744b0 Improve login UX for unconfirmed email + resend confirmation
- LoginForm: detect 403 separately, show amber warning with email address
- Add 'Resend confirmation email' button (calls POST /api/auth/resend-confirmation)
- Backend: POST /api/auth/resend-confirmation endpoint (generates new token, resends email)
- api.ts: add resendConfirmation method

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 19:05:55 +03:00
02c3b7452a Fix login page UX + add password reset flow
- Move Field component outside RegisterForm — fixes focus loss on every keystroke
- Remove setError('') at submit start — fixes error "flash" that looked like page reload
- Clear errors onChange instead, so error persists until user starts correcting
- Add 'Forgot password?' link in LoginForm
- Add ForgotPasswordForm (email input, success state)
- Add ResetPasswordPage at /reset-password?token=...
- Backend: POST /api/auth/forgot-password — generates 1h token, sends email
- Backend: POST /api/auth/reset-password — validates token, updates password hash
- Backend: migration 006 — reset_token/reset_token_expires columns
- Backend: sendPasswordResetEmail in email.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 18:43:05 +03:00
7b045b38a6 Fix ws.ts: use SocketStream from @fastify/websocket v8
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 18:16:12 +03:00
d3045194a6 Downgrade @fastify/websocket to v8 (Fastify 4 compatible)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 18:13:34 +03:00
25b1a96740 Fix TypeScript: add @types/ws and RawData type for WebSocket message
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 18:08:14 +03:00
3506105fc1 Add real-time booking collaboration via WebSocket
- Backend: /ws relay endpoint (@fastify/websocket)
- Frontend: useHotelSocket hook with lock/unlock/booking events
- Calendar: lock overlay with diagonal stripes shows other manager editing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 18:05:09 +03:00
791d8637f9 Fix migration 005: add phone column to users table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 17:35:16 +03:00
152a8c0463 Add hotel registration with email confirmation (nodemailer, confirm-email endpoint)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 17:30:07 +03:00
55a34dc0dc Connect ChannelsPage, FloorMapPage, SettingsPage, UsersPage to real API
- ChannelsPage: load from API, toggle/sync call real endpoints; normalize
  enabled→isEnabled, lastSyncedAt→lastSyncAt, add displayName/mappings defaults
- FloorMapPage: load rooms+bookings from API; create booking via API
- SettingsPage: load hotel via GET /api/hotels/:slug; save general section
  via PATCH (name, address, timezone, currency, check_in/out times)
- UsersPage: load users from API; create/update/delete via API;
  map backend User (name/role) → StaffUser (firstName/lastName/StaffRole)
- api.ts: add hotels.get/update, users.delete, channel normalization,
  HotelPayload/toHotelPayload
- types/index.ts: extend Hotel with phone/checkInTime/checkOutTime/optional fields;
  add User.createdAt/updatedAt
- backend/routes/hotels.ts: extend PATCH to allow address/phone/check_in_time/check_out_time
- backend/migrations/004_hotels_contacts.sql: add address/phone/check_in/out_time to hotels

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 14:21:45 +03:00
728b20417a Connect frontend to real API — rooms, bookings, housekeeping, calendar
- src/lib/api.ts: central API client with JWT auto-refresh, snake_case→camelCase transform
- src/contexts/AuthContext.tsx: real login via POST /api/auth/login
- src/pages/RoomsPage.tsx: load rooms from API, create/update via API
- src/pages/BookingsPage.tsx: load bookings + rooms from API
- src/pages/HousekeepingPage.tsx: load today's tasks from API, update status via API
- src/pages/CalendarPage.tsx: load rooms + bookings from API
- src/types/index.ts: fix HousekeepingTask.priority to match DB (medium/urgent)
- backend/src/routes/rooms.ts: update to use new column names (max_guests, base_rate) + all new fields
- backend/src/routes/bookings.ts: update price_per_night→base_rate, add paid_amount to PATCH
- backend/migrations/003_fix_constraints.sql: fix rooms.status values, add paid_amount, inquiry status, other source
- public/robots.txt + index.html: noindex for SPA inner pages

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 13:49:30 +03:00
17dbf629a2 Add docs, robots.txt and fix migration syntax
- backend/README.md: full API and DB documentation
- public/robots.txt: block all pages except root for SEO
- index.html: add noindex meta tag for inner SPA pages
- 002_extend.sql: fix RENAME COLUMN syntax (remove unsupported IF EXISTS)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 13:33:27 +03:00
ef1603d896 Add migration 002_extend.sql — extended database schema
Covers all entities present in the UI:
- Hotels: additional fields (address, phone, check_in/out times, is_active)
- room_categories table
- rooms: beds JSONB, extra_place, child_policy, housekeeping_status, allow_hourly; rename capacity→max_guests, price_per_night→base_rate
- guests table with loyalty points/tiers
- bookings: guest_id, payment_status, meal_plan, timestamps
- tariffs, discounts, loyalty_rules, loyalty_tiers
- reviews, maintenance_tasks
- pos_categories, pos_items, pos_orders
- document_templates, hotel_settings (key-value JSONB)
- All necessary indexes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 13:28:54 +03:00
6010531114 Add migration 002: missing updated_at columns + auto-update triggers + indexes
Tables fixed:
- rooms              → added updated_at
- housekeeping_tasks → added updated_at
- channels           → added created_at + updated_at

Trigger function set_updated_at() attached to all 6 tables (hotels, users,
rooms, bookings, housekeeping_tasks, channels) — updated_at now auto-sets
on every UPDATE via BEFORE UPDATE trigger.

Indexes added for archiving / range queries:
- bookings(hotel_id, created_at), bookings(hotel_id, updated_at)
- housekeeping_tasks(hotel_id, created_at)
- rooms(hotel_id, updated_at)
- channels(hotel_id, updated_at)

Also patched 001_schema.sql so fresh installs start correct.
Migration runner is idempotent — skips already applied files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 12:09:53 +03:00
c233590c4b Major UX improvements across multiple pages
Шахматка:
- Date/period picker dropdown on navigation button (choose start date + days window)
- Cancelled bookings fade out with animation after 1 second

Бронирования:
- Clickable column headers with sort asc/desc (Гость, Заезд, Выезд, Статус, Источник, Сумма)

Страница входа:
- Removed role-based account selector — just email + password
- System auto-detects role/hotel from credentials

Настройки:
- New "Бронирование" section with room assignment strategy (spread/together/sequential/manual)
- Notifications: SMTP email config + SMS provider config (SMSC, SMS.ru, МТС, etc.)

Модули:
- Added Housekeeping and Channel Manager as proper modules
- Channel Manager lists: Booking.com, Airbnb, Яндекс Путешествия, Островок, Суточно.ру, OneTwoTrip
- Housekeeping visible to all roles (including housekeeper) via module status
- Sidebar now uses module status to show/hide Уборка and Каналы

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 15:20:05 +03:00