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>
This commit is contained in:
2026-03-21 19:24:36 +03:00
parent 9f1dd0c8c9
commit edd493e24e
8 changed files with 195 additions and 25 deletions

View File

@@ -75,14 +75,18 @@ export function SettingsPage() {
// Booking / assignment settings
const [assignmentStrategy, setAssignmentStrategy] = useState<'spread' | 'together' | 'sequential' | 'manual'>('spread')
const [showBookingSource, setShowBookingSource] = useState(false)
const [requireGuestDocs, setRequireGuestDocs] = useState(false)
const [hotelSettingsLoaded, setHotelSettingsLoaded] = useState(false)
const [requireGuestDocs, setRequireGuestDocs] = useState(false)
const [earlyCheckinEnabled, setEarlyCheckinEnabled] = useState(false)
const [lateCheckoutEnabled, setLateCheckoutEnabled] = useState(false)
const [hotelSettingsLoaded, setHotelSettingsLoaded] = useState(false)
useEffect(() => {
if (!slug || hotelSettingsLoaded) return
api.hotelSettings.get(slug)
.then(s => {
setRequireGuestDocs(Boolean(s.require_guest_docs))
setEarlyCheckinEnabled(Boolean(s.early_checkin_enabled))
setLateCheckoutEnabled(Boolean(s.late_checkout_enabled))
setHotelSettingsLoaded(true)
})
.catch(console.error)
@@ -98,6 +102,26 @@ export function SettingsPage() {
}
}
const toggleEarlyCheckin = async () => {
const next = !earlyCheckinEnabled
setEarlyCheckinEnabled(next)
try {
await api.hotelSettings.update(slug, { early_checkin_enabled: next })
} catch {
setEarlyCheckinEnabled(!next)
}
}
const toggleLateCheckout = async () => {
const next = !lateCheckoutEnabled
setLateCheckoutEnabled(next)
try {
await api.hotelSettings.update(slug, { late_checkout_enabled: next })
} catch {
setLateCheckoutEnabled(!next)
}
}
// Guest settings
const [guestTags, setGuestTags] = useState([
{ id: 'vip', label: 'VIP', color: '#F59E0B' },
@@ -299,6 +323,28 @@ export function SettingsPage() {
<Toggle on={requireGuestDocs} onChange={toggleRequireGuestDocs} />
</div>
{/* Early check-in fee */}
<div className="flex items-center justify-between p-3 rounded-lg bg-slate-50 dark:bg-slate-700/40">
<div>
<p className="text-sm font-medium text-slate-900 dark:text-slate-100">Платный ранний заезд</p>
<p className="text-xs text-slate-500 dark:text-slate-400">
Предлагать доп. оплату при заселении до официального времени заезда. Сумма задаётся в настройках каждого номера.
</p>
</div>
<Toggle on={earlyCheckinEnabled} onChange={toggleEarlyCheckin} />
</div>
{/* Late checkout fee */}
<div className="flex items-center justify-between p-3 rounded-lg bg-slate-50 dark:bg-slate-700/40">
<div>
<p className="text-sm font-medium text-slate-900 dark:text-slate-100">Платный поздний выезд</p>
<p className="text-xs text-slate-500 dark:text-slate-400">
Предлагать доп. оплату при выезде после официального времени выезда. Сумма задаётся в настройках каждого номера.
</p>
</div>
<Toggle on={lateCheckoutEnabled} onChange={toggleLateCheckout} />
</div>
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
Стратегия автоматического расселения