espace-paie-odentas/app/api/staff/facturation/clients-sans-facture/route.ts
odentas 897af4b23a feat: Ajout fonctionnalités virements, facturation, signatures et emails
- Ajout sous-header total net à payer sur page virements-salaires
- Migration transfer_done_at pour tracking précis des virements
- Nouvelle page saisie tableau pour création factures en masse
- APIs bulk pour mise à jour dates signature et jours technicien
- API demande mandat SEPA avec email template
- Webhook DocuSeal pour signature contrats (mode TEST)
- Composants modaux détails et vérification PDF fiches de paie
- Upload/suppression/remplacement PDFs dans PayslipsGrid
- Amélioration affichage colonnes et filtres grilles contrats/paies
- Template email mandat SEPA avec sous-texte CTA
- APIs bulk facturation (création, update statut/date paiement)
- API clients sans facture pour période donnée
- Corrections calculs dates et montants avec auto-remplissage
2025-11-02 23:26:19 +01:00

88 lines
3.4 KiB
TypeScript

// app/api/staff/facturation/clients-sans-facture/route.ts
import { NextResponse } from "next/server";
import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
export const dynamic = 'force-dynamic';
export const revalidate = 0;
export const runtime = 'nodejs';
async function isStaffUser(supabase: any, userId: string): Promise<boolean> {
try {
const { data: staffRow } = await supabase
.from('staff_users')
.select('is_staff')
.eq('user_id', userId)
.maybeSingle();
return !!staffRow?.is_staff;
} catch {
return false;
}
}
// GET - Récupérer les clients actifs sans facture pour une période donnée
export async function GET(req: Request) {
try {
const url = new URL(req.url);
const periode = url.searchParams.get('periode');
if (!periode) {
return NextResponse.json({ error: 'missing_periode', message: 'Le paramètre "periode" est requis' }, { status: 400 });
}
const supabase = createRouteHandlerClient({ cookies });
const { data: { session } } = await supabase.auth.getSession();
if (!session) return NextResponse.json({ error: 'unauthorized' }, { status: 401 });
// Vérifier que l'utilisateur est staff
const isStaff = await isStaffUser(supabase, session.user.id);
if (!isStaff) {
return NextResponse.json({ error: 'forbidden', message: 'Staff access required' }, { status: 403 });
}
// 1. Récupérer tous les clients actifs depuis organization_details
const { data: activeOrgs, error: orgsError } = await supabase
.from('organization_details')
.select('org_id, organizations!inner(id, name, structure_api)')
.eq('statut', 'Actif')
.order('organizations(name)');
if (orgsError) {
console.error('[api/staff/facturation/clients-sans-facture] organizations error:', orgsError.message);
return NextResponse.json({ error: 'supabase_error', detail: orgsError.message }, { status: 500 });
}
// 2. Récupérer toutes les factures de la période
const { data: invoicesForPeriod, error: invoicesError } = await supabase
.from('invoices')
.select('org_id')
.eq('period_label', periode);
if (invoicesError) {
console.error('[api/staff/facturation/clients-sans-facture] invoices error:', invoicesError.message);
return NextResponse.json({ error: 'supabase_error', detail: invoicesError.message }, { status: 500 });
}
// 3. Créer un Set des org_id qui ont une facture pour cette période
const orgsWithInvoice = new Set(invoicesForPeriod?.map((inv: any) => inv.org_id) || []);
// 4. Filtrer les organisations actives qui n'ont pas de facture pour cette période
const clientsWithoutInvoice = (activeOrgs || [])
.filter((org: any) => !orgsWithInvoice.has(org.org_id))
.map((org: any) => ({
id: org.org_id,
name: org.organizations?.name || 'Nom inconnu',
structure_api: org.organizations?.structure_api || null,
}));
return NextResponse.json({
clients: clientsWithoutInvoice,
periode,
count: clientsWithoutInvoice.length
});
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.error('[api/staff/facturation/clients-sans-facture] error:', message);
return NextResponse.json({ error: 'internal_server_error', message }, { status: 500 });
}
}