- 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
406 lines
No EOL
16 KiB
TypeScript
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 });
|
|
}
|
|
} |