Commit Graph

142 Commits

Author SHA1 Message Date
9af11df1b0 feat: housekeeping automation — auto-task on checkout + room status on done
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>
2026-03-23 22:27:18 +03:00
ce20c6545d fix: robust night price calculation in BookingModal + clear test DB data
- Use parseISO instead of new Date() for safer date parsing
- Use Math.floor(nights) to avoid float iteration issues
- Type-check override price before using (typeof p === 'number')
- Label now shows actual sum from overrides instead of baseRate × nights

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 16:54:17 +03:00
15936753b9 feat: use availability rate overrides in calendar tooltip and booking modal
- Load rate_overrides in CalendarPage, pass as priceOverrides to BookingCalendar
- Tooltip shows override price for the hovered date (falls back to baseRate)
- BookingModal sums per-night prices from overrides for the stay period
- Room baseRate now only used as initial default for availability grid

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 16:35:13 +03:00
8312d2372e fix: maintenance overlay + context menu UX improvements
- Calendar overlay: normalize date strings (slice 0-10) to handle full ISO timestamps from Postgres; fix off-by-one (to = exclusive); stronger stripe visibility; guard against vFrom >= vTo
- Context menu: dateFrom defaults to today, indefinite defaults to false (both date inputs shown immediately)
- DateRangeForm: extracted DateInput component with CalendarDays icon that triggers native date picker
- HK status order: clean → inspect → cleaning → dirty

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 16:03:17 +03:00
c3a30be74e feat: calendar hover price tooltip + AvailabilityPage API integration
- 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>
2026-03-23 15:05:12 +03:00
63da3d21cc feat: maintenance/blocked date ranges with booking overlap warning
- 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>
2026-03-23 14:49:19 +03:00
4a39d5f731 fix: context menu reposition after mount to prevent viewport overflow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 14:41:56 +03:00
7bcc1ca0ab feat: visual overlay for maintenance/blocked rooms in calendar
- Diagonal stripe overlay on date cells: orange tint for maintenance,
  grey for blocked
- Room number and name dimmed when maintenance/blocked
- Show 🧹 dirty/cleaning indicator under room number (when not in maintenance)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 14:38:36 +03:00
fd50e02be0 feat: right-click context menu for room status + bed type capacity
- Add RoomContextMenu component (portal, Escape/click-outside to close)
  - Change room status: available / maintenance / blocked
  - Change housekeeping status: clean / dirty / cleaning / inspect
  - Open room edit (optional)
- Calendar: right-click on room label → context menu; onRoomUpdate prop
  wired to CalendarPage (calls api.rooms.update); show 🔧/🔒 badge on label
- Rooms page: right-click on any room card → same context menu;
  show orange "ремонт" badge on maintenance rooms
- BedTypesContext: add capacity field (max guests per bed type)
- BedTypes reference UI: add capacity input to add/edit form; show "N чел."

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 14:30:14 +03:00
8c6cdfb1c7 feat: configurable bed types via BedTypesContext
- Add BedTypesContext with default types + add/remove/update operations
- Replace hardcoded BED_ITEM_TYPES array in RoomModal with useBedTypes()
- Add "Справочник спальных мест" section to RoomCategoriesPage
  (expandable, same UX as amenities: add icon+label, rename, delete)
- BedItem.type changed from union to string for dynamic support

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 14:20:09 +03:00
3e9622d281 feat: bulk room creation + category filter on rooms page
- Add BulkRoomModal: set number range (from→to), category, bed type,
  max guests, base rate, amenities; auto-derives floor from room number;
  preview shows new vs skipped (already existing) rooms
- Add category filter chips on Rooms page (alongside existing floor filter)
- Add "Групповое добавление" button next to "Добавить номер"
- Wire onDelete prop to RoomModal on rooms page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 13:57:31 +03:00
eea76657e1 fix: smart next-free room number suggestion
- Placeholder shows next free after global max (e.g. 204 if 203 is max)
- Error message suggests next free starting from typed+1:
  type 101 (taken) → suggests 102
  type 201 (taken) → suggests 202
  type 1011 (taken) → suggests 1012
- Suggestion is a clickable link that auto-fills the field

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 13:51:10 +03:00
4ea6dcc368 fix: calendar room label dedup + room number validation
- BookingCalendar: remove type badge below room number (already shown in group header)
- RoomModal: validate number against existingNumbers before save
  - Red border + error message if number taken
  - Placeholder shows next free number (e.g. if 101,102 exist → placeholder 103)
  - Save button disabled while number is taken

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 13:44:33 +03:00
31d135563f fix: cdn.hotelsync.ru nginx config + RoomModal key for state reset
- Add cdn.hotelsync.ru server block to nginx config (serves /var/www/uploads)
- Add cdn.hotelsync.ru to HTTP redirect server_name
- RoomModal: add key={editingRoom?.id ?? 'new'} to force state reinit on room change
- RoomModal: remove categories[0] fallback for categoryId (show 'Без категории' instead)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 13:14:20 +03:00
1f761e65cf feat: room category — remove hardcoded types, show 'Без категории' with link when none exist 2026-03-23 12:08:47 +03:00
04258a4f50 feat: room type dropdown uses categories from API when available 2026-03-23 12:02:32 +03:00
2475c76d45 feat: RentalBookingModal 2-column layout
Left col: existing bookings list, full-day toggle, time selectors
Right col: guest autocomplete, phone, link-to-room, notes, price+payment block
Modal widened to max-w-2xl; payment section unified with BookingModal style

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 01:38:25 +03:00
7c65bebc22 feat: rental tab 2-column layout + payment section
Left col: object picker, date, full-day toggle, time selectors
Right col: guest autocomplete, phone, notes, total + payment block
Payment: Наличные/Терминал toggle buttons, оплачено input, 'Всю сумму' shortcut, debt indicator; paidAmount saved to booking

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 01:31:16 +03:00
b1b6854663 feat: single ФИО field with autocomplete replaces 3 separate name inputs
- BookingModal (room tab): replace Фамилия/Имя/Отчество grid with single 'ФИО *' input; parseFullName() splits by spaces into last/first/middle; DB search via api.guests.list (debounced 300ms); dropdown shows current bookings + DB guests with phone/email autofill
- BookingDetailPanel: merge Фамилия (with autocomplete) + Имя + Отчество into single ФИО * input; existing acQuery/acResults infrastructure reused; parseFio() updates all 3 bgForm fields on every keystroke; openEditForm initializes ФИО from full name
- RentalBookingModal + BookingModal rental tab: include middleName when displaying DB guest results and when setting guestName on selection; update type to include middleName field

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 01:22:46 +03:00
e403689522 fix: toggle CSS + guest autocomplete in BookingModal rental tab
- Toggle: add type=button, overflow-hidden, p-0, explicit left-[2px] + translate-x-[18px] pattern for reliable knob positioning across all toggles
- Guest field in BookingModal rental tab: add debounced DB search (api.guests.list) + suggestions dropdown (same as RentalBookingModal)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 01:08:25 +03:00
f21c5d5054 fix: rental calendar cells cap at 3 bookings + show +N overflow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 23:08:25 +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
370fe8a9a9 feat: move rental module to main nav under Цены и тарифы
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 22:46:39 +03:00
20582581be fix: sync endHour state when effectiveStartHour shifts past it
When existing bookings block the initial startHour, effectiveStartHour
snaps to the first available slot. Added useLayoutEffect to update
endHour state immediately so start and end times are never equal.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 22:37:49 +03:00
3961585196 fix: rental booking buffer applied to end times + phone mask
- getMaxEndHour now subtracts breakHours from next booking's start,
  so you can't book right up to a slot that needs buffer time before it
- effectiveStartHour moved before availableEndHours so equal start/end
  times are no longer possible
- maskPhone (+7 (XXX) XXX-XX-XX) applied to phone field in RentalBookingModal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 22:16:12 +03:00
31858ca5cc feat: room/rental tabs in booking modal + smart time slot filtering
BookingModal:
- Add 'Номер' / 'Аренда объекта' tabs when rental objects exist
- Rental tab: object picker, date, full-day toggle, conflict-aware time dropdowns, guest/phone fields, total amount

RentalBookingModal:
- Filter start times: exclude hours inside booked slots + bufferMinutes gap
- Filter end times: cap at next booking's start hour
- Disable 'Весь день' if any timed bookings exist for the day
- Show 'Все слоты заняты' when no start hours are available
- Show buffer duration in time section label

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 22:07:40 +03:00
5cab0ce8da fix: checkout form defaults to planned checkOut date, not today
Previously earlyOutDate defaulted to today, which caused bookings checked
out on their first day (checkIn == today) to become 0-night invisible blocks
in the calendar. Now defaults to the planned checkOut date so the bar stays
visible; manager explicitly sets an earlier date for early departure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 19:39:24 +03:00
c0a086a7b8 fix: normalize checkIn/checkOut dates with .slice(0,10) before comparison
PostgreSQL DATE columns serialize as ISO strings with time component
(2026-03-21T00:00:00.000Z), causing string comparison with yyyy-MM-dd
to incorrectly show the early check-in banner for same-day bookings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 19:30:02 +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
9f1dd0c8c9 fix: block 'Заселить' button for future bookings (checkIn > today)
Future bookings can only be checked in via the early check-in flow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 19:15:51 +03:00
2c458dfc09 feat: early checkout with refund calculation for any checked_in booking
Replaces simple 'Выселить' button with a checkout form:
- Date picker (min=checkIn, max=original checkOut)
- If leaving early: shows refund nights, refund amount, and payment method selector
- Adds negative payment record to local payment history
- On confirm: sets status=checked_out and updates checkOut date

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 19:11:29 +03:00
b111cc0898 fix: early check-in checkout date capped at original planned checkout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 19:07:14 +03:00
6dbff276c8 feat: early check-in for guests who arrive before booking date
Show amber banner on confirmed bookings where check-in is in the future.
Opens an inline form to check in today: checks if current room is free,
otherwise shows available rooms with price difference. Allows overriding
checkout date. On confirm, sets status=checked_in and check-in=today.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 18:49:23 +03:00
b831106ae2 Rental modal: require phone for new guests, auto-create in guests DB
- Track guestIsKnown: true when selected from dropdown, false when typed manually
- If guest is new (typed manually): phone field is required (red border + hint)
- On save: if new guest + slug available, call api.guests.create before saving rental
- Button shows 'Сохранение...' while saving, disabled during async flow

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 17:02:59 +03:00
033882790d Rental guest search: show phone number as subtitle in suggestions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 16:49:44 +03:00
dbccc59dfc Fix: rental booking save (date comparison), add DB guest search in rental modal
- BookingCalendar: normalize date comparison with .slice(0,10) to fix PostgreSQL DATE ISO format mismatch
- CalendarPage: show alert on rental booking save failure
- RentalBookingModal: add debounced DB guest search (api.guests.list) with 300ms delay
- RentalBookingModal: combine booking suggestions + DB results in unified dropdown
- Pass slug prop to RentalBookingModal for API access

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 16:45:37 +03:00
68c9caf46f RentalBookingModal: guest search autocomplete + link to room booking
- Guest name field shows dropdown with matching guests from all bookings
- Checked-in guests (🏠) shown first, then confirmed (📅)
- Selecting a guest auto-fills name and phone
- New 'Привязать к номеру' dropdown to link rental to a room booking
- Selecting a linked room auto-fills guest name if empty
- Pass bookings + rooms props from BookingCalendar to modal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 15:56:13 +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
3f4d0dac4c Calendar: compact mobile layout — narrow left column, hide View btn, move + to month row
- Left column: 80px on mobile (was 160px), hide room name/price/type badge
- Category headers: truncate text, reduce padding on mobile
- Toolbar: hide "Вид" button on mobile (sm:hidden)
- Toolbar: add compact "+" button next to month label on mobile only
- Original "+" button hidden on mobile (hidden sm:inline-flex)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 14:52:32 +03:00
7bd0e038ba Fix: block drag-to-move if target room has conflicting booking
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 01:05:40 +03:00
456a35e00d Revert: drag ghost back to small badge style
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 01:02:43 +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
97b2ca1f91 Fix drag drop: use ref for target room ID instead of elementFromPoint in mouseup (stale closure fix) 2026-03-21 00:44:30 +03:00
3e4a2a3cd2 Calendar: add drag ghost element + grabbing cursor; BookingDetail: add Relocate room for checked_in guests 2026-03-21 00:37:26 +03:00
75d7d6aa08 Fix: drag booking passes roomId to API; checked_out rooms available for new booking; remove time-based theme text 2026-03-21 00:22:03 +03:00
fd804926e4 BookingModal: move guest fields to right column; Theme: auto mode follows system prefers-color-scheme 2026-03-21 00:04:24 +03:00
d3581f27f2 BookingModal: move extra beds+tags to right col, payment to full-width bottom; Calendar: fix today booking left edge fill 2026-03-20 23:56:02 +03:00
0464259273 BookingModal: redesign — dates first, category cards, specific room toggle 2026-03-20 23:45:11 +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
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