fix: report cancellation bugs + missing OrderItem model columns
Backend: - OrderItem model: add cancelled_at, cancelled_by, cancel_reason columns (migration existed but model was missing them — caused 500 on cancellations_log) - shifts.py _enrich_shift: count cancellations by quantity sum (not row count), add cancellation_value (sum of unit_price * quantity) - reports.py current business day: add cancelled_items count (per-item quantity sum, across all orders in the day, not just fully-cancelled orders) Manager dashboard: - PrintFontsTab: fix SSE auth token key (access_token → manager_token, was causing 403) - Today: show cancelled_items count as sub-label on Ακυρώσεις stat card - OrderHistory: items/cancellations columns now use quantity sum, not row count - WorkDaySummary drill-down: same quantity-sum fix - ShiftsOverview: add Αξία Ακυρ. column next to Ακυρώσεις Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -804,7 +804,7 @@ function FindPrintersModal({ onClose, onSelect }) {
|
||||
setError(null)
|
||||
setScanning(true)
|
||||
|
||||
const token = localStorage.getItem('access_token') || ''
|
||||
const token = localStorage.getItem('manager_token') || ''
|
||||
const params = new URLSearchParams({ subnet, port })
|
||||
// Use fetch with ReadableStream to consume SSE (EventSource doesn't support auth headers)
|
||||
fetch(`/api/system/printers/scan?${params}`, {
|
||||
|
||||
@@ -95,8 +95,11 @@ export default function OrderHistory({ initialBusinessDayId } = {}) {
|
||||
</THead>
|
||||
<tbody>
|
||||
{orders.slice(0, 200).map(o => {
|
||||
const activeItems = (o.items || []).filter(i => i.status !== 'cancelled')
|
||||
const cancelledItems = (o.items || []).filter(i => i.status === 'cancelled')
|
||||
const allItems = o.items || []
|
||||
const activeItems = allItems.filter(i => i.status !== 'cancelled')
|
||||
const cancelledItems = allItems.filter(i => i.status === 'cancelled')
|
||||
const totalQty = allItems.reduce((s, i) => s + (i.quantity || 1), 0)
|
||||
const cancelledQty = cancelledItems.reduce((s, i) => s + (i.quantity || 1), 0)
|
||||
const total = activeItems.reduce((s, i) => s + i.unit_price * i.quantity, 0)
|
||||
const isCancelled = o.status === 'cancelled'
|
||||
return (
|
||||
@@ -106,10 +109,10 @@ export default function OrderHistory({ initialBusinessDayId } = {}) {
|
||||
<TD mono>{fmtDateTime(o.opened_at)}</TD>
|
||||
<TD mono>{fmtDateTime(o.closed_at)}</TD>
|
||||
<TD><StatusBadge status={o.status} /></TD>
|
||||
<TD mono align="right">{activeItems.length}</TD>
|
||||
<TD mono align="right">{totalQty}</TD>
|
||||
<TD mono align="right">
|
||||
{cancelledItems.length > 0
|
||||
? <span className="text-red-500 font-semibold">{cancelledItems.length}</span>
|
||||
{cancelledQty > 0
|
||||
? <span className="text-red-500 font-semibold">{cancelledQty}</span>
|
||||
: <span className="text-slate-300">—</span>}
|
||||
</TD>
|
||||
<TD mono align="right" className="font-semibold text-slate-900">{fmtEUR(total)}</TD>
|
||||
|
||||
@@ -119,7 +119,12 @@ export default function Today() {
|
||||
{bd.top_product && (
|
||||
<StatCard label="Κορυφαίο Προϊόν" value={bd.top_product.name} sub={`${bd.top_product.qty} τεμ.`} icon={Trophy} />
|
||||
)}
|
||||
<StatCard label="Ακυρώσεις" value={fmtNum(bd.cancellations)} icon={XCircle} />
|
||||
<StatCard
|
||||
label="Ακυρώσεις"
|
||||
value={fmtNum(bd.cancellations)}
|
||||
sub={bd.cancelled_items > 0 ? `${fmtNum(bd.cancelled_items)} είδη` : 'παραγγελίες'}
|
||||
icon={XCircle}
|
||||
/>
|
||||
{bd.total_cost > 0 && (
|
||||
<StatCard label="Κόστος Πωλήσεων" value={fmtEUR(bd.total_cost)} sub="κόστος ειδών" icon={ShoppingBag} />
|
||||
)}
|
||||
|
||||
@@ -75,8 +75,11 @@ function WorkDayModal({ day, onClose, onDeleteShift }) {
|
||||
</THead>
|
||||
<tbody>
|
||||
{orders.map(o => {
|
||||
const activeItems = (o.items || []).filter(i => i.status !== 'cancelled')
|
||||
const cancelledItems = (o.items || []).filter(i => i.status === 'cancelled')
|
||||
const allItems = o.items || []
|
||||
const activeItems = allItems.filter(i => i.status !== 'cancelled')
|
||||
const cancelledItems = allItems.filter(i => i.status === 'cancelled')
|
||||
const totalQty = allItems.reduce((s, i) => s + (i.quantity || 1), 0)
|
||||
const cancelledQty = cancelledItems.reduce((s, i) => s + (i.quantity || 1), 0)
|
||||
const total = activeItems.reduce((s, i) => s + i.unit_price * i.quantity, 0)
|
||||
return (
|
||||
<TR key={o.id} striped onClick={() => setDrillOrder(o)} className="cursor-pointer">
|
||||
@@ -84,10 +87,10 @@ function WorkDayModal({ day, onClose, onDeleteShift }) {
|
||||
<TD>{o.table_name ?? o.table_id}</TD>
|
||||
<TD mono>{fmtDateTime(o.opened_at)}</TD>
|
||||
<TD mono>{o.closed_at ? fmtDateTime(o.closed_at) : '—'}</TD>
|
||||
<TD mono align="right">{activeItems.length}</TD>
|
||||
<TD mono align="right">{totalQty}</TD>
|
||||
<TD mono align="right">
|
||||
{cancelledItems.length > 0
|
||||
? <span className="text-red-500 font-semibold">{cancelledItems.length}</span>
|
||||
{cancelledQty > 0
|
||||
? <span className="text-red-500 font-semibold">{cancelledQty}</span>
|
||||
: <span className="text-slate-300">—</span>}
|
||||
</TD>
|
||||
<TD mono align="right" className="font-semibold text-slate-900">{fmtEUR(total)}</TD>
|
||||
|
||||
@@ -126,6 +126,7 @@ export default function ShiftsOverview({ onNavigate } = {}) {
|
||||
<TH align="right">Οφείλει</TH>
|
||||
<TH align="right">Ταμείο</TH>
|
||||
<TH align="right">Ακυρώσεις</TH>
|
||||
<TH align="right">Αξία Ακυρ.</TH>
|
||||
<TH>Κατάσταση</TH>
|
||||
<TH className="w-28" />
|
||||
</THead>
|
||||
@@ -183,6 +184,11 @@ export default function ShiftsOverview({ onNavigate } = {}) {
|
||||
? <span className="text-red-600 font-semibold">{s.cancellations}</span>
|
||||
: <span className="text-slate-300">—</span>}
|
||||
</TD>
|
||||
<TD mono align="right">
|
||||
{s.cancellation_value > 0
|
||||
? <span className="text-red-500">{fmtEUR(s.cancellation_value)}</span>
|
||||
: <span className="text-slate-300">—</span>}
|
||||
</TD>
|
||||
<TD><StatusBadge status={s.is_active ? 'active' : 'closed'} pulse /></TD>
|
||||
<TD align="right">
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
@@ -207,7 +213,7 @@ export default function ShiftsOverview({ onNavigate } = {}) {
|
||||
</TR>,
|
||||
isOpen && (
|
||||
<tr key={`${s.id}-detail`}>
|
||||
<td colSpan={13} className="border-b border-slate-100 bg-slate-50/60 px-6 py-3">
|
||||
<td colSpan={14} className="border-b border-slate-100 bg-slate-50/60 px-6 py-3">
|
||||
<div className="flex items-center gap-4 text-[12px] text-slate-500">
|
||||
<span>
|
||||
{'Εργάσιμη Μέρα: '}
|
||||
|
||||
Reference in New Issue
Block a user