diff --git a/backend/src/routes/deposit.ts b/backend/src/routes/deposit.ts
index f999378..c698c47 100644
--- a/backend/src/routes/deposit.ts
+++ b/backend/src/routes/deposit.ts
@@ -263,31 +263,101 @@ const deposit: FastifyPluginAsync = async (fastify) => {
// Send email to guest if email is available
const { rows: bRows } = await db.query(
- 'SELECT guest_name, guest_email FROM bookings WHERE id = $1',
+ `SELECT b.guest_name, b.guest_email, h.name AS hotel_name
+ FROM bookings b
+ JOIN hotels h ON h.id = b.hotel_id
+ WHERE b.id = $1`,
[bookingId],
)
if (bRows[0]?.guest_email) {
const guest = bRows[0]
- let bodyLines: string[]
+ const hotelName: string = guest.hotel_name ?? 'HotelSync'
+ const fmt = (n: number) => n.toLocaleString('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' ₽'
+ const refund = Number(dep.amount) - capturedAmount
+
+ let html: string
if (capturedAmount === 0) {
- bodyLines = ['Депозит был полностью возвращён. Средства поступят на карту в течение нескольких рабочих дней.']
+ html = `
+
Уважаемый(ая) ${guest.guest_name},
+Страховой депозит был полностью возвращён.
+Средства поступят на вашу карту в течение нескольких рабочих дней.
`
} else {
- bodyLines = [`Из страхового депозита удержана сумма ${capturedAmount.toFixed(2)} ₽.`]
- if (items && items.length > 0) {
- bodyLines.push('', 'Позиции удержания:')
- for (const item of items) {
- bodyLines.push(` • ${item.name} — ${Number(item.amount).toFixed(2)} ₽`)
- }
- }
- if (reason) bodyLines.push('', `Комментарий: ${reason}`)
- const refund = Number(dep.amount) - capturedAmount
- if (refund > 0) bodyLines.push('', `Остаток депозита ${refund.toFixed(2)} ₽ возвращён.`)
+ const itemsHtml = items && items.length > 0
+ ? `
+
+
+ | Позиция |
+ Сумма |
+
+
+
+ ${items.map((it, i) => `
+
+ | ${it.name} |
+ ${fmt(Number(it.amount))} |
+
`).join('')}
+
+
+
+ | Итого удержано |
+ ${fmt(capturedAmount)} |
+
+ ${refund > 0 ? `| Возврат остатка | ${fmt(refund)} |
` : ''}
+
+
`
+ : `Удержана сумма: ${fmt(capturedAmount)}
${refund > 0 ? `Остаток ${fmt(refund)} возвращён.
` : ''}`
+
+ html = `
+Уважаемый(ая) ${guest.guest_name},
+Из страхового депозита удержана сумма за следующие позиции:
+${itemsHtml}
+${reason ? `Комментарий: ${reason}
` : ''}
+${refund > 0 && (!items || items.length === 0) ? `Остаток депозита ${fmt(refund)} возвращён.
` : ''}`
}
+
+ const emailHtml = `
+
+
+
+
+
+ |
+ ${hotelName}
+ Информация о страховом депозите
+ |
+ |
+ ${html}
+ Спасибо за проживание.
+ |
+ |
+ © 2026 ${hotelName} · Powered by HotelSync
+ |
+
+ |
+
+`
+
+ const textFallback = capturedAmount === 0
+ ? `Уважаемый(ая) ${guest.guest_name},\n\nСтраховой депозит полностью возвращён.\n\nСпасибо за проживание.\n\n© 2026 ${hotelName}`
+ : [
+ `Уважаемый(ая) ${guest.guest_name},`,
+ '',
+ `Из страхового депозита удержана сумма ${fmt(capturedAmount)}.`,
+ ...(items && items.length > 0 ? ['', 'Позиции удержания:', ...items.map(it => ` • ${it.name} — ${fmt(Number(it.amount))}`)] : []),
+ ...(reason ? ['', `Комментарий: ${reason}`] : []),
+ ...(refund > 0 ? ['', `Остаток депозита ${fmt(refund)} возвращён.`] : []),
+ '',
+ 'Спасибо за проживание.',
+ '',
+ `© 2026 ${hotelName}`,
+ ].join('\n')
+
transporter.sendMail({
- from: `"HotelSync" <${process.env.SMTP_USER ?? 'noreply@hotelsync.ru'}>`,
+ from: `"${hotelName}" <${process.env.SMTP_USER ?? 'noreply@hotelsync.ru'}>`,
to: guest.guest_email,
- subject: 'Информация о депозите — HotelSync',
- text: [`Уважаемый(ая) ${guest.guest_name},`, '', ...bodyLines, '', 'Спасибо за проживание.', '', '© 2026 HotelSync'].join('\n'),
+ subject: `Информация о депозите — ${hotelName}`,
+ text: textFallback,
+ html: emailHtml,
}).catch(() => {})
await db.query(
diff --git a/src/components/bookings/BookingDetailPanel.tsx b/src/components/bookings/BookingDetailPanel.tsx
index d2c0fcd..ad86f74 100644
--- a/src/components/bookings/BookingDetailPanel.tsx
+++ b/src/components/bookings/BookingDetailPanel.tsx
@@ -317,19 +317,42 @@ function DepositWidget({ slug, bookingId }: { slug: string; bookingId: string })
Возврат / Удержание депозита
+ {/* Minibar items breakdown */}
+ {minibarItems.length > 0 && (
+
+
+ Минибар — {formatCurrency(minibarTotal)}
+
+
+
+ {minibarItems.map((it, i) => (
+
+
+ {it.itemName} × {it.quantity} ({formatCurrency(it.pricePerUnit)} / шт.)
+
+ {formatCurrency(it.lineTotal)}
+
+
+ ))}
+
+
+ )}
+
{/* Quick presets from settings */}
- {(presets.length > 0 || minibarTotal > 0) && (
+ {(presets.length > 0 || minibarTotal === 0) && (
Добавить позицию:
- {minibarTotal > 0 && (
-
- )}
{presets.map(p => (
)}
+ {presets.length === 0 && minibarTotal > 0 && (
+
+ )}
{/* Added items */}
{releaseItems.length > 0 && (
@@ -383,15 +414,6 @@ function DepositWidget({ slug, bookingId }: { slug: string; bookingId: string })
✓ Полный возврат депозита
)}
- {/* Quick add if no presets */}
- {presets.length === 0 && minibarTotal === 0 && (
-
- )}