Checkpoint: Factures BAP inclut désormais, après Année et Mois, les filtres Destinataires, Entités et Ventilations comptables de la page Factures. Leur logique est identique, avec valeurs absentes et règle SANTINOVA/ITINOVA fondée sur DSI SANTINOVA. Les filtres sont conservés au retour depuis le détail. Tests, TypeScript, build et rendu visuel validés en sandbox.

This commit is contained in:
Manus
2026-09-02 11:52:20 +00:00
parent 0b2f1b781d
commit da19629ee0
4 changed files with 100 additions and 4 deletions

View File

@@ -116,6 +116,9 @@ export default function InvoicesBAP() {
const [searchQuery, setSearchQuery] = useState(initialFilters.searchQuery);
const [selectedIds, setSelectedIds] = useState<number[]>([]);
const [statusFilter, setStatusFilter] = useState<string>(initialFilters.statusFilter);
const [recipientFilter, setRecipientFilter] = useState<string>(initialFilters.recipientFilter);
const [entityFilter, setEntityFilter] = useState<string>(initialFilters.entityFilter);
const [ventilationFilter, setVentilationFilter] = useState<string>(initialFilters.ventilationFilter);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [invoiceToDelete, setInvoiceToDelete] = useState<number | null>(null);
const [addDialogOpen, setAddDialogOpen] = useState(false);
@@ -143,6 +146,9 @@ export default function InvoicesBAP() {
statusFilter,
selectedYear,
selectedMonth,
recipientFilter,
entityFilter,
ventilationFilter,
sortField,
sortDir,
}));
@@ -169,6 +175,14 @@ export default function InvoicesBAP() {
const { data: allInvoices, isLoading } = trpc.invoices.list.useQuery();
// Filter for BAP invoices only (Abonnement = NON, isSubscription = 0)
const invoices = allInvoices?.filter(inv => inv.isSubscription === 0);
const uniqueRecipients = useMemo(
() => Array.from(new Set((invoices || []).map(inv => (inv as any).recipientName).filter(Boolean))).sort(),
[invoices],
);
const uniqueVentilations = useMemo(
() => Array.from(new Set((invoices || []).map(inv => (inv as any).ventilationComptable).filter(Boolean))).sort(),
[invoices],
);
// IDs des factures déjà validées BAP (pour charger leurs pdfUrl depuis bapHistory)
// Stabilisé avec useMemo pour éviter les re-renders infinis (anti-pattern tRPC)
const validatedInvoiceIds = useMemo(
@@ -440,6 +454,27 @@ export default function InvoicesBAP() {
if (statusFilter === "bap_pending" && inv.bapValidated === 1) return false;
if (statusFilter === "to_complete" && !isToComplete(inv)) return false;
}
// Même convention que la page Factures : valeur vide et entités déduites du service.
if (recipientFilter !== "all") {
if (recipientFilter === "__empty__") {
if ((inv as any).recipientName) return false;
} else if ((inv as any).recipientName !== recipientFilter) {
return false;
}
}
if (entityFilter !== "all") {
const service = ((inv as any).serviceConcerne || "").trim().toUpperCase();
if (entityFilter === "santinova" && service !== "DSI SANTINOVA") return false;
if (entityFilter === "itinova" && service === "DSI SANTINOVA") return false;
}
if (ventilationFilter !== "all") {
if (ventilationFilter === "__empty__") {
if ((inv as any).ventilationComptable) return false;
} else if ((inv as any).ventilationComptable !== ventilationFilter) {
return false;
}
}
return true;
});
@@ -778,6 +813,40 @@ export default function InvoicesBAP() {
))}
</SelectContent>
</Select>
<Select value={recipientFilter} onValueChange={setRecipientFilter}>
<SelectTrigger className="w-[180px] h-8 text-sm bg-white border-emerald-200">
<SelectValue placeholder="Destinataire" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Tous destinataires</SelectItem>
<SelectItem value="__empty__">Sans destinataire</SelectItem>
{uniqueRecipients.map((recipient) => (
<SelectItem key={recipient} value={recipient}>{recipient}</SelectItem>
))}
</SelectContent>
</Select>
<Select value={entityFilter} onValueChange={setEntityFilter}>
<SelectTrigger className="w-[150px] h-8 text-sm bg-white border-emerald-200">
<SelectValue placeholder="Entité" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Toutes entités</SelectItem>
<SelectItem value="santinova">SANTINOVA</SelectItem>
<SelectItem value="itinova">ITINOVA</SelectItem>
</SelectContent>
</Select>
<Select value={ventilationFilter} onValueChange={setVentilationFilter}>
<SelectTrigger className="w-[170px] h-8 text-sm bg-white border-emerald-200">
<SelectValue placeholder="Ventilation" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Toutes ventilations</SelectItem>
<SelectItem value="__empty__">Sans ventilation</SelectItem>
{uniqueVentilations.map((ventilation) => (
<SelectItem key={ventilation} value={ventilation}>{ventilation}</SelectItem>
))}
</SelectContent>
</Select>
{(selectedYear !== "all" || selectedMonth !== "all") && (
<button
onClick={() => { setSelectedYear(String(currentYear)); setSelectedMonth("all"); }}