Analytics & Reports
Overview
Bambu aggregates all raw data (Sales, Expenses, Debts) and transforms it into actionable charts and CSV/Excel exports for business owners.
Why it exists
Data is useless without insight. Small shop owners need simple visual answers to questions like: "Am I profitable today?", "What products generate the most cash?", and "Are my debts spiraling out of control?".
User flow
- User clicks the
Analyticstab. - The UI renders Recharts.js line and bar graphs showing
Gross Revenue vs. Net Profitover the last 30 days. - The user cycles the time-series filters (Today, This Week, This Month, Last Year).
- The user clicks "Export to Excel" to download their raw ledger.
UI walkthrough


Backend logic
Because querying calculating margin aggregates across tens of thousands of sales rows dynamically is slow:
- Redis is used heavily.
GET /api/analytics/metricshits the Redis layer first. - If stale or missing, Prisma executes grouped summation queries (
sum,avg) inside the specifictenant schema. - The payload is cached for 15 minutes to reduce database load.
- Excel exporting uses
exceljslibrary within Node.js to stream a buffer directly to the browser.
Database tables involved
Tenant Schema:
Sale,SaleItem,Expense: Queried via aggregations. No mutations occur here.
API endpoints
GET /api/analytics/metrics- High-level summation figures.GET /api/analytics/reports- Export endpoint returning application headers (e.g., CSV/XLSX text blobs).
Permissions / roles
- Owner: Full dashboard visibility.
- Cashier: Access completely denied. An attempted route bypass throws a
403.
Edge cases
- Changing time zones. The user's browser TZ is sent in headers so Next.js groups "Today's" sales based on their local time, not UTC.
Validation rules
- Date range queries are restricted to a maximum 1-year spread to prevent out-of-memory crashes on Vercel Edge functions.
Error handling
- If
exceljsfails to stream, a generic500triggers the React Error Boundary, asking the user to try a smaller date window.
Screenshots placeholders

Troubleshooting
- "Why does my Gross Revenue not match the cash in my drawer?" -> Because Gross Revenue includes sales made on Credit/Debt. Focus on the "Cash Collected" metric instead.