fix: minibar deposit — использовать актуальную цену из справочника, а не историческую
Ранее в форме удержания депозита показывалась цена на момент уборки (price_per_unit). Теперь используется текущая цена позиции (mi.price) — чтобы изменения цен сразу отражались в счёте. Если цена менялась — показываем индикатор ↑ с tooltip. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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`,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user