feat(reports): Financial report (Initiative 1 Phase 4)
Builds the Financial report on the canonical payments + expenses tables (the CRM records money received; it does not invoice — invoices module is off, dev DB has zero invoice rows). The invoice-centric spec is reframed onto the payments model: "outstanding AR" → expected-deposit shortfall on active deals; "AR aging" → outstanding deposits bucketed by deal age. Service (financial.service.ts): - 7 KPIs: revenue collected (net of refunds), deposits, balance, pipeline expected, outstanding deposits, expenses, net contribution - 6 chart datasets: revenue by month (deposit/balance), collection funnel (EOI→deposit→contract→won), expected-deposit aging, cash flow (inflow vs outflow), expense breakdown by category - 4 tables: outstanding deposits, recent payments, refund log, expense ledger - every money figure normalised to port currency via a shared resolvePortCurrency/normalizeAmount helper (new reports/currency.ts) UI (financial-report-client.tsx): KPI strip + recharts (stacked bar / horizontal bar / line / donut) + month/quarter/year toggle + branded empty states; DateRangePicker + Templates + Export wired. Un-hidden the Financial card on the reports landing. Plumbing: added '1y' (trailing 12mo) preset to the shared range system (financial trends want a year); added 'financial'/'marketing' to the report-template kind enum for template parity. TDD: 6 financial-math unit tests (aging buckets, month keys/range, net contribution). tsc clean; full unit suite green except pre-existing Redis/storage-dependent integration tests. Browser-verified against live data: API 200, KPIs correct ($5,849 expenses / -$5,849 net, $0 revenue correct given 0 payment rows), expense ledger + breakdown populate, payment-derived sections show graceful empty states. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
21
src/app/(dashboard)/[portSlug]/reports/financial/page.tsx
Normal file
21
src/app/(dashboard)/[portSlug]/reports/financial/page.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { FinancialReportClient } from '@/components/reports/financial/financial-report-client';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
/**
|
||||
* Financial report.
|
||||
*
|
||||
* Sibling of the dynamic [kind] route so this page wins over the
|
||||
* placeholder for /reports/financial specifically. Spec lives in
|
||||
* docs/reports-content-spec.md § Report 02 — sourced from the canonical
|
||||
* payments + expenses tables (the CRM records money received; it does
|
||||
* not invoice).
|
||||
*/
|
||||
export default async function FinancialReportPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ portSlug: string }>;
|
||||
}) {
|
||||
const { portSlug } = await params;
|
||||
return <FinancialReportClient portSlug={portSlug} />;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import Link from 'next/link';
|
||||
import type { Route } from 'next';
|
||||
import { BookOpen, Calendar, Clock, Layers, Sparkles, TrendingUp } from 'lucide-react';
|
||||
import { BookOpen, Calendar, Clock, Layers, Sparkles, TrendingUp, Wallet } from 'lucide-react';
|
||||
|
||||
import { PageHeader } from '@/components/shared/page-header';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
@@ -39,6 +39,13 @@ const KIND_CARDS: KindCard[] = [
|
||||
'Berth utilisation timeline, occupancy heatmap, tenancy churn, signing turnaround.',
|
||||
icon: Layers,
|
||||
},
|
||||
{
|
||||
href: 'financial',
|
||||
label: 'Financial',
|
||||
description:
|
||||
'Revenue collected, deposits, outstanding balances, cash flow, and expense breakdown.',
|
||||
icon: Wallet,
|
||||
},
|
||||
{
|
||||
href: 'custom',
|
||||
label: 'Custom report',
|
||||
|
||||
Reference in New Issue
Block a user