espace-paie-odentas/app/api/staff/clients/[id]/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

406 lines
No EOL
16 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { createSbServer, createSbServiceRole } from "@/lib/supabaseServer";
export const dynamic = 'force-dynamic';
export const revalidate = 0;
async function checkStaffAccess(supabase: any, userId: string) {
const { data: staff } = await supabase
.from("staff_users")
.select("is_staff")
.eq("user_id", userId)
.maybeSingle();
return staff?.is_staff || false;
}
export async function GET(
req: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const supabase = createSbServer();
// Vérification de l'authentification
const { data: { user }, error: authError } = await supabase.auth.getUser();
if (authError || !user) {
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
}
// Vérification des droits staff
const isStaff = await checkStaffAccess(supabase, user.id);
if (!isStaff) {
return NextResponse.json({ error: "Accès refusé - Staff uniquement" }, { status: 403 });
}
const clientId = params.id;
// Récupération des données de l'organisation
const { data: org, error: orgError } = await supabase
.from("organizations")
.select("*")
.eq("id", clientId)
.maybeSingle();
if (orgError) {
return NextResponse.json({ error: "Erreur lors de la récupération du client" }, { status: 500 });
}
if (!org) {
return NextResponse.json({ error: "Client non trouvé" }, { status: 404 });
}
// Récupération des détails de l'organisation
const { data: details, error: detailsError } = await supabase
.from("organization_details")
.select("*")
.eq("org_id", clientId)
.maybeSingle();
if (detailsError && detailsError.code !== 'PGRST116') {
console.error("Erreur récupération détails:", detailsError);
}
// Mapping des données comme dans l'API informations
const structureInfos = {
// Votre structure
raison_sociale: org.name || null,
code_employeur: details?.code_employeur || null, // Remplace structure_api
siret: details?.siret || null,
siren: details?.siren || null,
forme_juridique: details?.forme_juridique || null,
declaration: details?.declaration || null,
convention_collective: details?.ccn || null,
ccn_raccourci: details?.ccn_raccourci || null,
code_ape: details?.ape || null,
tva: details?.tva || null,
rna: details?.rna || null,
adresse_siege: details?.adresse_siege || (details?.adresse && (details?.cp || details?.ville)
? `${details.adresse}${details.cp ? ', ' + details.cp : ''}${details.ville ? ' ' + details.ville : ''}`
: null),
adresse: details?.adresse || null,
cp: details?.cp || null,
ville: details?.ville || null,
presidente: details?.president || null,
tresoriere: details?.tresorier || null,
structure_a_spectacles: details?.structure_a_spectacles || false,
entree_en_relation: details?.entree_en_relation || null,
logo_base64: details?.logo || null,
// Responsable de traitement (RGPD)
nom_responsable_traitement: details?.nom_responsable_traitement || null,
qualite_responsable_traitement: details?.qualite_responsable_traitement || null,
email_responsable_traitement: details?.email_responsable_traitement || null,
// Nouveaux champs abonnement
statut: details?.statut || null,
ouverture_compte: details?.ouverture_compte || null,
offre_speciale: details?.offre_speciale || null,
notes: details?.notes || null,
// Gestion paie
virements_salaires: details?.virements_salaires || null,
// Informations de contact
contact_principal: details?.nom_contact || (details?.prenom_contact ? `${details.prenom_contact} ${details?.nom_contact ?? ''}`.trim() : null),
email: details?.email_notifs || null, // Modifié pour utiliser email_notifs au lieu de email_contact
email_cc: details?.email_notifs_cc || null, // Nouveau champ
email_signature: details?.email_signature || null, // Nouveau champ
telephone: details?.tel_contact || null,
signataire_contrats: (details?.prenom_signataire || details?.nom_signataire)
? `${details?.prenom_signataire ?? ''} ${details?.nom_signataire ?? ''}`.trim()
: null,
signataire_delegation: details?.delegation_signature || false, // Boolean depuis la DB
// Caisses & organismes
licence_spectacles: details?.licence_spectacles || null,
urssaf: details?.urssaf || null,
audiens: details?.audiens || null,
conges_spectacles: details?.conges_spectacles_id || null,
pole_emploi_spectacle: details?.pole_emploi_id || null,
recouvrement_pe_spectacle: details?.recouvrement_pe_id || null,
afdas: details?.afdas_id || null,
fnas: details?.fnas_id || null,
fcap: details?.fcap_id || null,
};
return NextResponse.json({
organization: org,
structureInfos,
details: details || {}
});
} catch (error) {
console.error("Erreur API client:", error);
return NextResponse.json({ error: "Erreur interne" }, { status: 500 });
}
}
export async function PUT(
req: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const supabase = createSbServer();
// Vérification de l'authentification
const { data: { user }, error: authError } = await supabase.auth.getUser();
if (authError || !user) {
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
}
// Vérification des droits staff
const isStaff = await checkStaffAccess(supabase, user.id);
if (!isStaff) {
return NextResponse.json({ error: "Accès refusé - Staff uniquement" }, { status: 403 });
}
const clientId = params.id;
const body = await req.json();
console.log("📝 [CLIENT UPDATE] Body reçu:", JSON.stringify(body, null, 2));
// Vérifier que le client existe avant la mise à jour
const { data: existingClient, error: clientCheckError } = await supabase
.from("organizations")
.select("id")
.eq("id", clientId)
.maybeSingle();
if (clientCheckError) {
console.error("Erreur vérification client:", clientCheckError);
return NextResponse.json({ error: "Erreur lors de la vérification du client" }, { status: 500 });
}
if (!existingClient) {
return NextResponse.json({ error: "Client non trouvé" }, { status: 404 });
}
console.log("✅ [CLIENT UPDATE] Client trouvé:", existingClient.id);
// Extraction des champs autorisés à être modifiés
const {
name,
// Nouveaux champs abonnement
statut,
ouverture_compte,
offre_speciale,
notes,
// Gestion paie
virements_salaires,
// Apporteur d'affaires
is_referred,
referrer_code,
commission_rate,
// Champs de détails de l'organisation
code_employeur, // Remplace structure_api
siret,
siren,
forme_juridique,
declaration,
ccn,
ccn_raccourci,
ape,
tva,
rna,
adresse_siege,
adresse,
cp,
ville,
president,
tresorier,
structure_a_spectacles,
entree_en_relation,
logo_base64,
nom_contact,
prenom_contact,
email_notifs, // Modifié pour utiliser email_notifs au lieu d'email_contact
email_notifs_cc, // Nouveau champ
email_signature, // Nouveau champ
tel_contact,
prenom_signataire,
nom_signataire,
qualite_signataire,
delegation_signature, // Boolean remplace qualite_signataire pour la délégation
licence_spectacles,
urssaf,
audiens,
conges_spectacles_id,
pole_emploi_id,
recouvrement_pe_id,
afdas_id,
fnas_id,
fcap_id,
// Responsable de traitement (RGPD)
nom_responsable_traitement,
qualite_responsable_traitement,
email_responsable_traitement,
// Facturation (SEPA)
iban,
bic,
id_mandat_sepa,
} = body;
const orgUpdateData: any = {};
const detailsUpdateData: any = {};
// Données de l'organisation principale
if (name !== undefined) orgUpdateData.name = name;
// Données des détails de l'organisation
// Nouveaux champs abonnement
if (statut !== undefined) detailsUpdateData.statut = statut;
if (ouverture_compte !== undefined) detailsUpdateData.ouverture_compte = ouverture_compte;
if (offre_speciale !== undefined) detailsUpdateData.offre_speciale = offre_speciale;
if (notes !== undefined) detailsUpdateData.notes = notes;
// Gestion paie
if (virements_salaires !== undefined) detailsUpdateData.virements_salaires = virements_salaires;
// Apporteur d'affaires
if (is_referred !== undefined) detailsUpdateData.is_referred = is_referred;
if (referrer_code !== undefined) detailsUpdateData.referrer_code = referrer_code;
if (commission_rate !== undefined) detailsUpdateData.commission_rate = commission_rate;
// Autres champs
if (code_employeur !== undefined) detailsUpdateData.code_employeur = code_employeur; // Nouveau champ
if (siret !== undefined) detailsUpdateData.siret = siret;
if (siren !== undefined) detailsUpdateData.siren = siren;
if (forme_juridique !== undefined) detailsUpdateData.forme_juridique = forme_juridique;
if (declaration !== undefined) detailsUpdateData.declaration = declaration;
if (ccn !== undefined) detailsUpdateData.ccn = ccn;
if (ccn_raccourci !== undefined) detailsUpdateData.ccn_raccourci = ccn_raccourci;
if (ape !== undefined) detailsUpdateData.ape = ape;
if (tva !== undefined) detailsUpdateData.tva = tva;
if (rna !== undefined) detailsUpdateData.rna = rna;
if (adresse_siege !== undefined) detailsUpdateData.adresse_siege = adresse_siege;
if (adresse !== undefined) detailsUpdateData.adresse = adresse;
if (cp !== undefined) detailsUpdateData.cp = cp;
if (ville !== undefined) detailsUpdateData.ville = ville;
if (president !== undefined) detailsUpdateData.president = president;
if (tresorier !== undefined) detailsUpdateData.tresorier = tresorier;
if (structure_a_spectacles !== undefined) detailsUpdateData.structure_a_spectacles = structure_a_spectacles;
if (entree_en_relation !== undefined) detailsUpdateData.entree_en_relation = entree_en_relation;
if (logo_base64 !== undefined) detailsUpdateData.logo = logo_base64;
if (nom_contact !== undefined) detailsUpdateData.nom_contact = nom_contact;
if (prenom_contact !== undefined) detailsUpdateData.prenom_contact = prenom_contact;
if (email_notifs !== undefined) detailsUpdateData.email_notifs = email_notifs; // Modifié
if (email_notifs_cc !== undefined) detailsUpdateData.email_notifs_cc = email_notifs_cc; // Nouveau champ
if (email_signature !== undefined) detailsUpdateData.email_signature = email_signature; // Nouveau champ
if (tel_contact !== undefined) detailsUpdateData.tel_contact = tel_contact;
if (prenom_signataire !== undefined) detailsUpdateData.prenom_signataire = prenom_signataire;
if (nom_signataire !== undefined) detailsUpdateData.nom_signataire = nom_signataire;
if (qualite_signataire !== undefined) detailsUpdateData.qualite_signataire = qualite_signataire;
if (delegation_signature !== undefined) detailsUpdateData.delegation_signature = delegation_signature; // Boolean
if (licence_spectacles !== undefined) detailsUpdateData.licence_spectacles = licence_spectacles;
if (urssaf !== undefined) detailsUpdateData.urssaf = urssaf;
if (audiens !== undefined) detailsUpdateData.audiens = audiens;
if (conges_spectacles_id !== undefined) detailsUpdateData.conges_spectacles_id = conges_spectacles_id;
if (pole_emploi_id !== undefined) detailsUpdateData.pole_emploi_id = pole_emploi_id;
if (recouvrement_pe_id !== undefined) detailsUpdateData.recouvrement_pe_id = recouvrement_pe_id;
if (afdas_id !== undefined) detailsUpdateData.afdas_id = afdas_id;
if (fnas_id !== undefined) detailsUpdateData.fnas_id = fnas_id;
if (fcap_id !== undefined) detailsUpdateData.fcap_id = fcap_id;
// Responsable de traitement (RGPD)
if (nom_responsable_traitement !== undefined) detailsUpdateData.nom_responsable_traitement = nom_responsable_traitement;
if (qualite_responsable_traitement !== undefined) detailsUpdateData.qualite_responsable_traitement = qualite_responsable_traitement;
if (email_responsable_traitement !== undefined) detailsUpdateData.email_responsable_traitement = email_responsable_traitement;
// Facturation (SEPA)
if (iban !== undefined) detailsUpdateData.iban = iban;
if (bic !== undefined) detailsUpdateData.bic = bic;
if (id_mandat_sepa !== undefined) detailsUpdateData.id_mandat_sepa = id_mandat_sepa;
if (Object.keys(orgUpdateData).length === 0 && Object.keys(detailsUpdateData).length === 0) {
return NextResponse.json({ error: "Aucune donnée à mettre à jour" }, { status: 400 });
}
console.log("📝 [CLIENT UPDATE] orgUpdateData:", JSON.stringify(orgUpdateData, null, 2));
console.log("📝 [CLIENT UPDATE] detailsUpdateData:", JSON.stringify(detailsUpdateData, null, 2));
// Utiliser le service role pour les mises à jour afin de bypass les RLS
const supabaseServiceRole = createSbServiceRole();
let updatedOrg = null;
let updatedDetails = null;
// Mise à jour de l'organisation
if (Object.keys(orgUpdateData).length > 0) {
console.log("🔄 [CLIENT UPDATE] Tentative mise à jour organisation pour ID:", clientId);
const { data, error: updateError } = await supabaseServiceRole
.from("organizations")
.update(orgUpdateData)
.eq("id", clientId)
.select();
console.log("🔄 [CLIENT UPDATE] Résultat mise à jour organisation:", { data, error: updateError });
if (updateError) {
console.error("Erreur mise à jour organisation:", updateError);
return NextResponse.json({ error: "Erreur lors de la mise à jour de l'organisation" }, { status: 500 });
}
if (!data || data.length === 0) {
console.error("❌ [CLIENT UPDATE] Aucune ligne mise à jour pour l'organisation ID:", clientId);
return NextResponse.json({ error: "Client non trouvé pour la mise à jour" }, { status: 404 });
}
console.log("✅ [CLIENT UPDATE] Organisation mise à jour avec succès");
updatedOrg = data[0];
}
// Mise à jour des détails de l'organisation
if (Object.keys(detailsUpdateData).length > 0) {
// Vérifier si les détails existent déjà
const { data: existingDetails } = await supabaseServiceRole
.from("organization_details")
.select("org_id")
.eq("org_id", clientId)
.maybeSingle();
if (existingDetails) {
// Mise à jour
const { data, error: updateError } = await supabaseServiceRole
.from("organization_details")
.update(detailsUpdateData)
.eq("org_id", clientId)
.select();
if (updateError) {
console.error("Erreur mise à jour détails:", updateError);
return NextResponse.json({ error: "Erreur lors de la mise à jour des détails" }, { status: 500 });
}
if (!data || data.length === 0) {
return NextResponse.json({ error: "Détails du client non trouvés pour la mise à jour" }, { status: 404 });
}
updatedDetails = data[0];
} else {
// Création
const { data, error: insertError } = await supabaseServiceRole
.from("organization_details")
.insert({ ...detailsUpdateData, org_id: clientId })
.select();
if (insertError) {
console.error("Erreur création détails:", insertError);
return NextResponse.json({ error: "Erreur lors de la création des détails" }, { status: 500 });
}
if (!data || data.length === 0) {
return NextResponse.json({ error: "Erreur lors de la création des détails" }, { status: 500 });
}
updatedDetails = data[0];
}
}
return NextResponse.json({
message: "Client mis à jour avec succès",
organization: updatedOrg,
details: updatedDetails
});
} catch (error) {
console.error("Erreur API mise à jour client:", error);
return NextResponse.json({ error: "Erreur interne" }, { status: 500 });
}
}