Reports & Analytics
Pre-built reports, a custom pivot builder, and 95+ columns — no external BI tool required.
Key Capabilities
From one-click standard reports to fully custom pivot analysis with charts.
Pre-Built Report Library
Reports auto-seeded for every new company: sales by product, by customer, purchase analysis, vendor bill summary, inventory aging, low stock, stock velocity, and sales margin. Grouped by category.
Custom Report Builder
Build your own pivot reports from scratch. Pick a data source (sales, purchases, vendor bills, stock moves, current stock), choose grouping dimensions, select measures, add enrichment columns, and attach a chart.
95+ Column Registry
Central column registry with field path, display name, format type, width, and aggregation mode. Drag-and-drop column picker (SortableJS). Save column selections as named views.
Multi-Currency Pivot
Columns with currency_pivot_config auto-expand at runtime. Single-currency companies see a normal total. Multi-currency companies get home total plus one column per active currency.
How It Works
Select or Build
Choose from pre-built reports grouped by category, or open the Custom Report Builder to create your own pivot analysis from any data source — sales, purchases, stock, or bills.
Filter & Configure
Set runtime filters — date ranges, customers, suppliers, brands, warehouses. Customize columns, toggle totals, sort by any field. Custom reports add time grouping (daily to yearly) and Top N limiting.
Save, Chart & Export
Save views and custom reports for instant reuse. Attach bar, line, pie, or doughnut charts to custom reports. Export any report to Excel with one click — respects your column selection and filters.
Custom report builder screenshot
Custom Report Builder
Build pivot reports from 5 data sources with configurable dimensions, measures, enrichments, time grouping, Top N limiting, and Chart.js visualizations. Save and share across your team.
- 5 data sources: Sales (Invoice Lines), Purchases (PO Lines), Vendor Bills, Stock Movements, Current Stock
- Dimensions per source: customer, supplier, brand, product, EAN, SKU, category, warehouse, location, move type
- Measures: quantity, revenue, cost, gross margin, margin %, avg price, line count, invoice/PO/bill count, stock value, on hand, reserved, available
- Time grouping: daily, weekly, monthly, quarterly, yearly — auto-truncates the source date field
- Enrichment columns: current stock, last buy price, standard cost, reorder point — joined from other tables at runtime
- Top N limiting at DB level, Chart.js visualization (bar/line/pie/doughnut), save as favorite, share with company
Column registry code screenshot
Column Registry Architecture
95+ columns defined once in column_registry.py — the single source of truth. Each column specifies field path, display name, format type, width, and optional aggregation. The col() helper allows overrides per report.
- 95+ columns defined once in column_registry.py with COLUMNS dict — single source of truth
- Prefix convention: il_ (InvoiceLine), inv_ (Invoice), po_ (PurchaseOrder), pol_ (PurchaseOrderLine), h_ (handler)
- Each column: field_path (ORM or dict key), display_name, format_type (text/number/currency/decimal/date/percent), width
- col() helper fetches a column definition with optional overrides for aggregation, visibility, or display name
- Currency pivot config auto-expands columns at runtime — one definition, N output columns based on active currencies
- Calculation fields support expressions like "quantity * price_unit" rendered as ORM annotations
Report engine architecture diagram
Generic vs Specialized Handlers
Generic handler builds ORM queries from columns + filters automatically. Specialized handlers (Python classes) return list[dict] for logic that can't be expressed as ORM annotations.
- Generic handler (report_handler="generic"): engine builds ORM queries from columns + filters automatically
- Specialized handlers: Python classes in handlers.py that return list[dict] for complex logic
- Built-in handlers: LowStockHandler, InventoryAgingHandler, StockVelocityHandler, SalesMarginHandler
- Handler registration in engine.py maps handler keys to classes — extensible pattern for custom reports
- Specialized handlers are used when data spans multiple models, needs Python-level calculations, or can't be expressed as ORM annotations
- All handlers receive the company context — tenant isolation enforced at the engine level, not the handler
Saved views UI screenshot
Saved Views & Excel Export
ReportView stores column config, sort config, and totals preference per user per report. The column picker uses SortableJS for drag-and-drop. Excel export respects the current view.
- ReportView model stores user + report + column_config (JSON) + sort_config (JSON) + is_default + show_totals
- Column picker uses SortableJS for drag-and-drop reordering — shows all columns including hidden ones
- Saved views load via ?view_id= parameter — shareable URLs that reproduce the exact configuration
- Default view auto-loads when opening the report — skip the column picker for your daily workflow
- Excel export respects current column selection, sort order, and active filters — what you see is what you export
- Server-side pagination preserves all query parameters — navigate pages without losing your configuration
Works Seamlessly With
Reports That Actually Work
Pre-built reports, a custom pivot builder, and Excel export — all tenant-isolated and currency-aware.