Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 5x 5x 5x 5x 5x 5x 4x 5x 5x 5x 5x 6x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 6x 1x 1x 6x | /**
* @file finance.ts
* @module api/analytics/finance
*
* @summary
* Financial analytics (WAC-based) for a given date window and optional supplier.
* Tolerant parsing: unknown/missing fields → 0. Functions never throw; callers
* get a fully-populated object with zeros on error to keep the UI resilient.
* @enterprise
* - Finance endpoint uses `from/to` (not `start/end` like other analytics).
* - Accepts either a direct object or an envelope (e.g., { summary } or { data }).
* - Returns a fully-populated zero object on any error.
*/
import http from '../httpClient';
import { isRecord, pickNumber } from './util';
import type { Rec } from './util';
/** Canonical FE shape for financial summary (numbers are always defined).
* Returns 0 for missing/invalid fields.
* @example
* ```typescript
* const summary = await getFinancialSummary({
* from: '2025-09-01',
* to: '2025-11-30',
* supplierId: 'SUP-001'
* });
* return (
* <FinanceDashboard data={summary} />
* );
* ```
*/
export type FinancialSummary = {
purchases: number;
cogs: number;
writeOffs: number;
returns: number;
openingValue: number;
endingValue: number;
};
/** Zero object for graceful fallbacks.
* @internal
*/
const ZERO_FINANCE: FinancialSummary = {
purchases: 0,
cogs: 0,
writeOffs: 0,
returns: 0,
openingValue: 0,
endingValue: 0,
};
/**
* Fetch financial summary for a window.
* Backend: GET /api/analytics/financial/summary?from&to[&supplierId]
*
* @enterprise
* - Finance endpoint uses `from/to` (not `start/end` like other analytics).
* - Accepts either a direct object or an envelope (e.g., { summary } or { data }).
* - Returns a fully-populated zero object on any error.
*/
export async function getFinancialSummary(
p?: { from?: string; to?: string; supplierId?: string }
): Promise<FinancialSummary> {
try {
const params: Record<string, string> = {};
if (p?.from) params.from = p.from;
if (p?.to) params.to = p.to;
if (p?.supplierId) params.supplierId = p.supplierId;
const { data } = await http.get<unknown>('/api/analytics/financial/summary', { params });
// Accept direct object or envelope
const pickPayload = (x: unknown): Rec | null => {
if (!isRecord(x)) return null;
if (isRecord(x.summary)) return x.summary as Rec;
if (isRecord(x.data)) return x.data as Rec;
return x as Rec;
};
// Parse fields with tolerant picking
const body = pickPayload(data);
if (!body) return ZERO_FINANCE;
return {
purchases: pickNumber(body, ['purchases', 'purchasesCost', 'totalPurchases', 'purchaseTotal']),
cogs: pickNumber(body, ['cogs', 'cogsCost', 'costOfGoodsSold']),
writeOffs: pickNumber(body, ['writeOffs', 'writeOffCost', 'writeoffs', 'write_offs']),
returns: pickNumber(body, ['returns', 'returnsInCost', 'returnsCost', 'salesReturns', 'returnsTotal']),
openingValue: pickNumber(body, ['openingValue', 'opening', 'startValue']),
endingValue: pickNumber(body, ['endingValue', 'ending', 'endValue']),
};
} catch {
return ZERO_FINANCE;
}
}
|