Fix gross sales zero bug; add Excel stats bar

- Backend: sales_breakdown column matching was always failing because
  Newbook returns numeric gl_group_id but column settings use string codes
  like 'ACCOMMODATION'. Added normalised name-fuzzy fallback matching and
  a daily_gross_sales field that sums ALL earned revenue for the date,
  bypassing column config entirely.
- Frontend: dayGrossSales() now uses daily_gross_sales from backend first
- Add floating Excel-style status bar (bottom-right): when cells are
  selected it shows Count, Sum, and Average of the selected values;
  disappears when ✕ clicked or selection cleared

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-02 00:14:40 +00:00
parent e7af072851
commit f62aa6f347
2 changed files with 74 additions and 14 deletions

View file

@ -131,26 +131,43 @@ export async function reportRoutes(app) {
{ category: 'bacs', banked_amount: pt.bacs, reported_amount: pt.bacs },
]
// Sales breakdown from earned revenue
let displayedGross = 0
// Build a lookup from gl_group_id → name using the accounts list
// gl_group_id from Newbook is often numeric; normalise to string for comparison
const glGroupById = {}
for (const a of (glAccountList || [])) {
const gid = String(a.gl_group_id ?? '')
if (gid && !glGroupById[gid]) glGroupById[gid] = (a.gl_group_name ?? '').toLowerCase().replace(/[^a-z0-9]/g, '')
}
// Sales breakdown — match earned revenue to configured columns
// Try: (1) exact code match, (2) normalised name contains code / vice-versa
const salesBreakdown = enabledColumns.map(col => {
const item = earnedRevenue.find(r =>
r.period === date && r.gl_group_id.toUpperCase() === col.gl_code.toUpperCase()
)
const net = parseFloat(item?.earned_revenue_ex || 0)
const vat = parseFloat(item?.earned_revenue_tax || 0)
const gross = parseFloat(item?.earned_revenue || 0)
displayedGross += gross
const code = col.gl_code.toLowerCase().replace(/[^a-z0-9]/g, '')
const item = earnedRevenue.find(r => {
if (r.period !== date) return false
const gid = String(r.gl_group_id ?? '')
if (gid === col.gl_code || gid.toUpperCase() === col.gl_code.toUpperCase()) return true
const gName = glGroupById[gid] ?? ''
return gName.includes(code) || code.includes(gName)
})
const net = parseFloat(item?.earned_revenue_ex || 0)
const vat = parseFloat(item?.earned_revenue_tax || 0)
const gross = parseFloat(item?.earned_revenue || 0)
return { gl_code: col.gl_code, category: col.display_name, net_amount: net, vat_amount: vat, gross_amount: gross }
})
// Daily gross sales = ALL earned revenue for this date (bypasses column config)
const daily_gross_sales = earnedRevenue
.filter(r => r.period === date)
.reduce((s, r) => s + (r.earned_revenue || 0), 0)
// Daily stats from payments
const grossSales = freshPayments.reduce((s, p) => s + parseFloat(p.amount), 0)
const dailyStats = grossSales > 0
? { business_date: date, gross_sales: grossSales, transaction_count: freshPayments.length }
: null
return { date, cash_up: cashUp, reconciliation, daily_stats: dailyStats, sales_breakdown: salesBreakdown }
return { date, cash_up: cashUp, reconciliation, daily_stats: dailyStats, sales_breakdown: salesBreakdown, daily_gross_sales }
}))
return {