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:
12
server/db.ts
12
server/db.ts
@@ -50,6 +50,7 @@ import {
|
||||
WebImportSource
|
||||
} from "../drizzle/schema";
|
||||
import { ENV } from './_core/env';
|
||||
import { buildAnnualInvoiceSummary } from "@shared/invoiceAnalytics";
|
||||
|
||||
let _db: ReturnType<typeof drizzle> | null = null;
|
||||
|
||||
@@ -332,7 +333,7 @@ export async function searchInvoices(userId: number | null, query: string): Prom
|
||||
.orderBy(desc(invoices.createdAt));
|
||||
}
|
||||
|
||||
export async function getInvoiceStats(userId: number) {
|
||||
export async function getInvoiceStats(userId?: number) {
|
||||
const db = await getDb();
|
||||
if (!db) return {
|
||||
total: 0,
|
||||
@@ -346,10 +347,12 @@ export async function getInvoiceStats(userId: number) {
|
||||
topSuppliers: [],
|
||||
suppliersList: [],
|
||||
topRecipients: [],
|
||||
recipientsList: []
|
||||
recipientsList: [],
|
||||
annualSummary: [],
|
||||
};
|
||||
|
||||
const allInvoices = await getInvoicesByUserId(userId);
|
||||
// Le tableau de bord est global pour les administrateurs, comme les listes Factures.
|
||||
const allInvoices = userId ? await getInvoicesByUserId(userId) : await getAllInvoices();
|
||||
|
||||
// Calculate basic stats
|
||||
const completed = allInvoices.filter(i => i.status === "completed");
|
||||
@@ -458,7 +461,8 @@ export async function getInvoiceStats(userId: number) {
|
||||
topSuppliers,
|
||||
suppliersList,
|
||||
topRecipients,
|
||||
recipientsList
|
||||
recipientsList,
|
||||
annualSummary: buildAnnualInvoiceSummary(allInvoices),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
24
server/invoiceAnalytics.test.ts
Normal file
24
server/invoiceAnalytics.test.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildAnnualInvoiceSummary, buildSupplierBudgetSummary } from "@shared/invoiceAnalytics";
|
||||
|
||||
const invoices = [
|
||||
{ invoiceDate: "2026-05-15", createdAt: "2026-05-16", status: "completed", isSubscription: 0, supplierName: "SFR", totalAmount: "100" },
|
||||
{ invoiceDate: "2026-05-20", createdAt: "2026-05-21", status: "completed", isSubscription: 1, supplierName: "SFR", totalAmount: "20" },
|
||||
{ invoiceDate: "2025-03-10", createdAt: "2025-03-11", status: "completed", isSubscription: 1, supplierName: "Microsoft", totalAmount: "50" },
|
||||
{ invoiceDate: "2026-05-01", createdAt: "2026-05-01", status: "error", isSubscription: 0, supplierName: "Ignorée", totalAmount: "999" },
|
||||
];
|
||||
|
||||
describe("agrégats de facturation", () => {
|
||||
it("calcule les volumes et montants BAP et abonnements par année", () => {
|
||||
expect(buildAnnualInvoiceSummary(invoices)).toEqual([
|
||||
{ year: 2026, bapCount: 1, bapAmount: 100, subscriptionCount: 1, subscriptionAmount: 20, totalAmount: 120 },
|
||||
{ year: 2025, bapCount: 0, bapAmount: 0, subscriptionCount: 1, subscriptionAmount: 50, totalAmount: 50 },
|
||||
]);
|
||||
});
|
||||
|
||||
it("filtre le budget par période et ventile chaque fournisseur par type", () => {
|
||||
expect(buildSupplierBudgetSummary(invoices, "2026", "05")).toEqual([
|
||||
{ supplierName: "SFR", subscriptionCount: 1, subscriptionAmount: 20, nonSubscriptionCount: 1, nonSubscriptionAmount: 100, totalAmount: 120 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -1045,7 +1045,8 @@ export const appRouter = router({
|
||||
}),
|
||||
|
||||
getStats: protectedProcedure.query(async ({ ctx }) => {
|
||||
return getInvoiceStats(ctx.user.id);
|
||||
// Les administrateurs consultent les mêmes données globales que les listes Factures.
|
||||
return getInvoiceStats(ctx.user.role === "admin" ? undefined : ctx.user.id);
|
||||
}),
|
||||
}),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user