diff --git a/src/components/bookings/BookingDetailPanel.tsx b/src/components/bookings/BookingDetailPanel.tsx index 3f07811..8c8251f 100644 --- a/src/components/bookings/BookingDetailPanel.tsx +++ b/src/components/bookings/BookingDetailPanel.tsx @@ -59,7 +59,7 @@ const DOCUMENTS = [ type ReleaseItem = { id: string; name: string; amount: string } -function DepositWidget({ slug, bookingId }: { slug: string; bookingId: string }) { +function DepositWidget({ slug, bookingId, onDepositChange }: { slug: string; bookingId: string; onDepositChange?: (d: BookingDeposit | null) => void }) { const [depositSettings, setDepositSettings] = useState(null) const [deposit, setDeposit] = useState(null) const [depositLoading, setDepositLoading] = useState(true) @@ -75,6 +75,8 @@ function DepositWidget({ slug, bookingId }: { slug: string; bookingId: string }) const [minibarItems, setMinibarItems] = useState>([]) const [minibarTotal, setMinibarTotal] = useState(0) + useEffect(() => { onDepositChange?.(deposit) }, [deposit]) // eslint-disable-line react-hooks/exhaustive-deps + useEffect(() => { Promise.all([ api.deposits.getSettings(slug), @@ -734,6 +736,13 @@ export function BookingDetailPanel({ booking, room, rooms, allBookings, slug, on const [earlyOutMethod, setEarlyOutMethod] = useState<'cash' | 'card' | 'transfer'>('cash') const [earlyOutSaving, setEarlyOutSaving] = useState(false) + // Active deposit (populated via DepositWidget callback) + const [activeDeposit, setActiveDeposit] = useState(null) + + // Deposit → room payment apply at checkout + const [depositApply, setDepositApply] = useState(false) + const [depositApplyAmt, setDepositApplyAmt] = useState('') + const [cardIssuing, setCardIssuing] = useState(false) const [cardEncoders, setCardEncoders] = useState>([]) const [cardEncoderPickerOpen, setCardEncoderPickerOpen] = useState(false) @@ -777,6 +786,21 @@ export function BookingDetailPanel({ booking, room, rooms, allBookings, slug, on if (earlyOutDate > booking.checkOut) { showToast('Дата не может быть позже плановой'); return } setEarlyOutSaving(true) try { + // Apply deposit to room payment if requested + if (depositApply && activeDeposit && slug) { + const applyAmt = parseFloat(depositApplyAmt) || 0 + if (applyAmt > 0) { + const methodLabel = activeDeposit.paymentMethod === 'yookassa_hold' ? 'Депозит (ЮКасса)' : 'Депозит (наличные)' + await api.deposits.release(slug, booking.id, applyAmt, 'Зачёт в счёт оплаты проживания') + const payment = await api.payments.add(slug, booking.id, applyAmt, methodLabel, 'Зачёт страхового депозита') + setPayments(prev => { + const next = [...prev, payment] + onUpdate(booking.id, { paidAmount: next.reduce((s, p) => s + Number(p.amount), 0) }) + return next + }) + } + } + const origNights = differenceInDays(new Date(booking.checkOut), new Date(booking.checkIn)) const actualNights = differenceInDays(new Date(earlyOutDate), new Date(booking.checkIn)) const refundNights = origNights - actualNights @@ -791,7 +815,10 @@ export function BookingDetailPanel({ booking, room, rooms, allBookings, slug, on } await onUpdate(booking.id, { checkOut: earlyOutDate, status: 'checked_out' }) setEarlyOutOpen(false) + setDepositApply(false) showToast('Гость выселен') + } catch { + showToast('Ошибка при выселении') } finally { setEarlyOutSaving(false) } @@ -1803,7 +1830,7 @@ export function BookingDetailPanel({ booking, room, rooms, allBookings, slug, on {/* Правая колонка — депозит */} {slug && (
- +
)} @@ -1974,6 +2001,14 @@ export function BookingDetailPanel({ booking, room, rooms, allBookings, slug, on } setEarlyOutOpen(true) setEarlyOutDate(checkOutDate) + // Init deposit apply state + if (activeDeposit && (activeDeposit.status === 'hold_confirmed' || activeDeposit.status === 'paid_cash') && balance > 0) { + setDepositApply(true) + setDepositApplyAmt(String(Math.min(activeDeposit.amount, balance))) + } else { + setDepositApply(false) + setDepositApplyAmt('') + } }} className="w-full btn-primary justify-center bg-emerald-600 hover:bg-emerald-700" > @@ -2043,6 +2078,46 @@ export function BookingDetailPanel({ booking, room, rooms, allBookings, slug, on {refundNights <= 0 && (

Возврат не требуется

)} + + {/* Deposit apply to room payment */} + {activeDeposit && (activeDeposit.status === 'hold_confirmed' || activeDeposit.status === 'paid_cash') && ( +
+ + {depositApply && ( +
+ Сумма: + setDepositApplyAmt(e.target.value)} + className="input py-0.5 text-xs w-28" + /> + + {activeDeposit.paymentMethod === 'yookassa_hold' ? '(ЮКасса)' : '(наличные)'} + +
+ )} +
+ )} +