fix: minibar deposit — использовать актуальную цену из справочника, а не историческую

Ранее в форме удержания депозита показывалась цена на момент уборки (price_per_unit).
Теперь используется текущая цена позиции (mi.price) — чтобы изменения цен сразу
отражались в счёте. Если цена менялась — показываем индикатор ↑ с tooltip.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-06 15:51:34 +03:00
parent 54d0d2d817
commit 83d797c55f
3 changed files with 33 additions and 18 deletions

View File

@@ -497,10 +497,13 @@ ${refund > 0 && (!items || items.length === 0) ? `<p style="color:#16a34a">Ос
if (!bRows[0]?.room_id) return { items: [], total: 0 } if (!bRows[0]?.room_id) return { items: [], total: 0 }
// Get all minibar consumptions from housekeeping tasks for this room // Get all minibar consumptions from housekeeping tasks for this room
// Use current item price (mi.price) — so price changes in settings are reflected
const { rows } = await db.query( const { rows } = await db.query(
`SELECT mc.quantity, mc.price_per_unit, `SELECT mc.quantity,
mc.price_per_unit AS recorded_price,
mi.price AS current_price,
mi.name AS item_name, mi.name AS item_name,
(mc.quantity * mc.price_per_unit) AS line_total (mc.quantity * mi.price) AS line_total
FROM minibar_consumptions mc FROM minibar_consumptions mc
JOIN minibar_items mi ON mi.id = mc.item_id JOIN minibar_items mi ON mi.id = mc.item_id
JOIN housekeeping_tasks ht ON ht.id = mc.task_id JOIN housekeeping_tasks ht ON ht.id = mc.task_id

View File

@@ -72,7 +72,7 @@ function DepositWidget({ slug, bookingId }: { slug: string; bookingId: string })
const [yookassaMsg, setYookassaMsg] = useState<string | null>(null) const [yookassaMsg, setYookassaMsg] = useState<string | null>(null)
const [releaseError, setReleaseError] = useState<string | null>(null) const [releaseError, setReleaseError] = useState<string | null>(null)
const [presets, setPresets] = useState<DepositPreset[]>([]) const [presets, setPresets] = useState<DepositPreset[]>([])
const [minibarItems, setMinibarItems] = useState<Array<{ itemName: string; quantity: number; pricePerUnit: number; lineTotal: number }>>([]) const [minibarItems, setMinibarItems] = useState<Array<{ itemName: string; quantity: number; recordedPrice: number; currentPrice: number; lineTotal: number }>>([])
const [minibarTotal, setMinibarTotal] = useState(0) const [minibarTotal, setMinibarTotal] = useState(0)
useEffect(() => { useEffect(() => {
@@ -330,12 +330,23 @@ function DepositWidget({ slug, bookingId }: { slug: string; bookingId: string })
</button> </button>
</div> </div>
<div className="divide-y divide-amber-100 dark:divide-amber-900/40"> <div className="divide-y divide-amber-100 dark:divide-amber-900/40">
{minibarItems.map((it, i) => ( {minibarItems.map((it, i) => {
<div key={i} className="flex items-center justify-between px-3 py-1.5"> const priceChanged = Math.abs(it.currentPrice - it.recordedPrice) > 0.001
<span className="text-xs text-amber-800 dark:text-amber-300 flex-1"> return (
{it.itemName} × {it.quantity} <span className="text-amber-500 dark:text-amber-500">({formatCurrency(it.pricePerUnit)} / шт.)</span> <div key={i} className="flex items-center justify-between px-3 py-1.5 gap-2">
<span className="text-xs text-amber-800 dark:text-amber-300 flex-1 min-w-0">
{it.itemName} × {it.quantity}
{' '}
<span className={priceChanged ? 'text-orange-500 dark:text-orange-400' : 'text-amber-500'}>
({formatCurrency(it.currentPrice)} / шт.)
</span> </span>
<span className="text-xs font-medium text-amber-800 dark:text-amber-300 mr-2">{formatCurrency(it.lineTotal)}</span> {priceChanged && (
<span className="ml-1 text-orange-500 dark:text-orange-400" title={`Цена изменена с ${formatCurrency(it.recordedPrice)}`}>
</span>
)}
</span>
<span className="text-xs font-medium text-amber-800 dark:text-amber-300 shrink-0">{formatCurrency(it.lineTotal)}</span>
<button <button
onClick={() => addReleaseItem(`${it.itemName} × ${it.quantity}`, it.lineTotal)} onClick={() => addReleaseItem(`${it.itemName} × ${it.quantity}`, it.lineTotal)}
className="text-xs text-amber-600 dark:text-amber-400 hover:underline font-medium shrink-0" className="text-xs text-amber-600 dark:text-amber-400 hover:underline font-medium shrink-0"
@@ -343,7 +354,8 @@ function DepositWidget({ slug, bookingId }: { slug: string; bookingId: string })
+ Добавить + Добавить
</button> </button>
</div> </div>
))} )
})}
</div> </div>
</div> </div>
)} )}

View File

@@ -806,7 +806,7 @@ export const api = {
req<void>('DELETE', `/api/hotels/${slug}/bookings/${bookingId}/deposit`), req<void>('DELETE', `/api/hotels/${slug}/bookings/${bookingId}/deposit`),
getMinibarForBooking: (slug: string, bookingId: string) => getMinibarForBooking: (slug: string, bookingId: string) =>
req<{ items: Array<{ itemName: string; quantity: number; pricePerUnit: number; lineTotal: number }>; total: number }>( req<{ items: Array<{ itemName: string; quantity: number; recordedPrice: number; currentPrice: number; lineTotal: number }>; total: number }>(
'GET', `/api/hotels/${slug}/bookings/${bookingId}/deposit/minibar`, 'GET', `/api/hotels/${slug}/bookings/${bookingId}/deposit/minibar`,
), ),