Checkpoint: Ajout du récapitulatif annuel BAP et abonnements au tableau de bord, et de Budget réel par fournisseur avec filtres Année/Mois et ventilation abonnements/hors abonnement.
This commit is contained in:
108
shared/invoiceAnalytics.ts
Normal file
108
shared/invoiceAnalytics.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { getInvoicePeriodDate, matchesInvoicePeriod } from "./invoicePeriod";
|
||||
|
||||
export type AnalyticsInvoice = {
|
||||
invoiceDate?: Date | string | number | null;
|
||||
createdAt?: Date | string | number | null;
|
||||
isSubscription?: number | null;
|
||||
status?: string | null;
|
||||
supplierName?: string | null;
|
||||
totalAmount?: number | string | null;
|
||||
};
|
||||
|
||||
export type AnnualInvoiceSummary = {
|
||||
year: number;
|
||||
bapCount: number;
|
||||
bapAmount: number;
|
||||
subscriptionCount: number;
|
||||
subscriptionAmount: number;
|
||||
totalAmount: number;
|
||||
};
|
||||
|
||||
export type SupplierBudgetSummary = {
|
||||
supplierName: string;
|
||||
subscriptionCount: number;
|
||||
subscriptionAmount: number;
|
||||
nonSubscriptionCount: number;
|
||||
nonSubscriptionAmount: number;
|
||||
totalAmount: number;
|
||||
};
|
||||
|
||||
function getAmount(value: AnalyticsInvoice["totalAmount"]): number {
|
||||
const amount = Number(value);
|
||||
return Number.isFinite(amount) ? amount : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Agrège les factures finalisées par année métier. « BAP » désigne ici les
|
||||
* factures hors abonnement, éligibles au circuit BAP, validées ou non.
|
||||
*/
|
||||
export function buildAnnualInvoiceSummary(invoices: AnalyticsInvoice[]): AnnualInvoiceSummary[] {
|
||||
const summaryByYear = new Map<number, AnnualInvoiceSummary>();
|
||||
|
||||
for (const invoice of invoices) {
|
||||
if (invoice.status !== "completed") continue;
|
||||
const date = getInvoicePeriodDate(invoice);
|
||||
if (!date) continue;
|
||||
|
||||
const year = date.getFullYear();
|
||||
const current = summaryByYear.get(year) ?? {
|
||||
year,
|
||||
bapCount: 0,
|
||||
bapAmount: 0,
|
||||
subscriptionCount: 0,
|
||||
subscriptionAmount: 0,
|
||||
totalAmount: 0,
|
||||
};
|
||||
const amount = getAmount(invoice.totalAmount);
|
||||
|
||||
if (invoice.isSubscription === 1) {
|
||||
current.subscriptionCount += 1;
|
||||
current.subscriptionAmount += amount;
|
||||
} else {
|
||||
current.bapCount += 1;
|
||||
current.bapAmount += amount;
|
||||
}
|
||||
current.totalAmount += amount;
|
||||
summaryByYear.set(year, current);
|
||||
}
|
||||
|
||||
return Array.from(summaryByYear.values()).sort((a, b) => b.year - a.year);
|
||||
}
|
||||
|
||||
/** Agrège le budget réellement facturé par fournisseur sur la période choisie. */
|
||||
export function buildSupplierBudgetSummary(
|
||||
invoices: AnalyticsInvoice[],
|
||||
year: string,
|
||||
month: string,
|
||||
): SupplierBudgetSummary[] {
|
||||
const summaryBySupplier = new Map<string, SupplierBudgetSummary>();
|
||||
|
||||
for (const invoice of invoices) {
|
||||
if (invoice.status !== "completed" || !matchesInvoicePeriod(invoice, year, month)) continue;
|
||||
|
||||
const supplierName = invoice.supplierName?.trim() || "Fournisseur inconnu";
|
||||
const current = summaryBySupplier.get(supplierName) ?? {
|
||||
supplierName,
|
||||
subscriptionCount: 0,
|
||||
subscriptionAmount: 0,
|
||||
nonSubscriptionCount: 0,
|
||||
nonSubscriptionAmount: 0,
|
||||
totalAmount: 0,
|
||||
};
|
||||
const amount = getAmount(invoice.totalAmount);
|
||||
|
||||
if (invoice.isSubscription === 1) {
|
||||
current.subscriptionCount += 1;
|
||||
current.subscriptionAmount += amount;
|
||||
} else {
|
||||
current.nonSubscriptionCount += 1;
|
||||
current.nonSubscriptionAmount += amount;
|
||||
}
|
||||
current.totalAmount += amount;
|
||||
summaryBySupplier.set(supplierName, current);
|
||||
}
|
||||
|
||||
return Array.from(summaryBySupplier.values()).sort((a, b) =>
|
||||
b.totalAmount - a.totalAmount || a.supplierName.localeCompare(b.supplierName, "fr"),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user