- Créer hook useStaffOrgSelection avec persistence localStorage - Ajouter badge StaffOrgBadge dans Sidebar - Synchroniser filtres org dans toutes les pages (contrats, cotisations, facturation, etc.) - Fix calcul cachets: utiliser totalQuantities au lieu de dates.length - Fix structure field bug: ne plus écraser avec production_name - Ajouter création note lors modification contrat - Implémenter montants personnalisés pour virements salaires - Migrations SQL: custom_amount + fix_structure_field - Réorganiser boutons ContractEditor en carte flottante droite
171 lines
6.7 KiB
TypeScript
171 lines
6.7 KiB
TypeScript
// app/api/informations/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';
|
|
|
|
// Helpers (duplicated in a few routes; consider centralizing if reused more)
|
|
async function getOrganizationFromDatabase(supabase: any, userId: string) {
|
|
try {
|
|
const { data: memberData, error: memberError } = await supabase
|
|
.from('organization_members')
|
|
.select('org_id')
|
|
.eq('user_id', userId)
|
|
.single();
|
|
|
|
if (memberError || !memberData?.org_id) {
|
|
return null;
|
|
}
|
|
|
|
const { data: orgData, error: orgError } = await supabase
|
|
.from('organizations')
|
|
.select('name')
|
|
.eq('id', memberData.org_id)
|
|
.single();
|
|
|
|
if (orgError || !orgData?.name) {
|
|
return null;
|
|
}
|
|
|
|
return { id: memberData.org_id, name: orgData.name, isStaff: false };
|
|
} catch (error) {
|
|
console.error('Error fetching organization:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function getClientInfoFromSession(session: any, supabase: any) {
|
|
const userMeta = session?.user?.user_metadata || {};
|
|
const appMeta = session?.user?.app_metadata || {};
|
|
|
|
// Priorité: valeur côté serveur (table staff_users)
|
|
let isStaff = false;
|
|
try {
|
|
const { data: staffRow } = await supabase.from('staff_users').select('is_staff').eq('user_id', session.user.id).maybeSingle();
|
|
isStaff = !!staffRow?.is_staff;
|
|
} catch (e) {
|
|
isStaff = Boolean(
|
|
(userMeta.is_staff === true || userMeta.role === 'staff') ||
|
|
(Array.isArray(appMeta.roles) && appMeta.roles.includes('staff'))
|
|
);
|
|
}
|
|
|
|
if (isStaff) {
|
|
const cookieStore = cookies();
|
|
const activeOrgId = cookieStore.get('active_org_id')?.value;
|
|
if (!activeOrgId) {
|
|
return { id: null, name: 'Staff Access', isStaff: true };
|
|
}
|
|
const { data: orgData } = await supabase.from('organizations').select('name').eq('id', activeOrgId).single();
|
|
return { id: activeOrgId, name: orgData?.name || 'Staff Access', isStaff: true };
|
|
}
|
|
|
|
const orgInfo = await getOrganizationFromDatabase(supabase, session.user.id);
|
|
if (!orgInfo) throw new Error('User is not associated with any organization');
|
|
return orgInfo;
|
|
}
|
|
|
|
export async function GET(req: Request) {
|
|
try {
|
|
const supabase = createRouteHandlerClient({ cookies });
|
|
const { data: { session } } = await supabase.auth.getSession();
|
|
if (!session) {
|
|
return NextResponse.json({ error: 'unauthorized' }, { status: 401 });
|
|
}
|
|
|
|
// Vérifier si un org_id est passé en query ou via header
|
|
const url = new URL(req.url);
|
|
const queryOrgId = url.searchParams.get('org_id');
|
|
const headerOrgId = req.headers.get('x-active-org-id');
|
|
|
|
let clientInfo;
|
|
let targetOrgId = queryOrgId || headerOrgId;
|
|
|
|
// Si pas d'org_id en query/header, récupérer l'org de l'utilisateur
|
|
if (!targetOrgId) {
|
|
try {
|
|
clientInfo = await getClientInfoFromSession(session, supabase);
|
|
targetOrgId = clientInfo.id;
|
|
} catch (e) {
|
|
const message = e instanceof Error ? e.message : String(e);
|
|
return NextResponse.json({ error: 'forbidden', message }, { status: 403 });
|
|
}
|
|
}
|
|
|
|
// Read details from Supabase organization_details
|
|
let details: any = null;
|
|
let error: any = null;
|
|
if (targetOrgId) {
|
|
const res = await supabase.from('organization_details').select('*').eq('org_id', targetOrgId).single();
|
|
details = res.data;
|
|
error = res.error;
|
|
}
|
|
|
|
if (error && error.code !== 'PGRST116') { // PGRST116 = Not found / no rows
|
|
console.error('[api/informations] Supabase error:', error.message);
|
|
return NextResponse.json({ error: 'supabase_error', detail: error.message }, { status: 500 });
|
|
}
|
|
|
|
// If we don't have a raison sociale on details, try to get the organization name
|
|
let orgName: string | null = clientInfo?.name ?? null;
|
|
if (!orgName && targetOrgId) {
|
|
try {
|
|
const { data: orgRow } = await supabase.from('organizations').select('name').eq('id', targetOrgId).single();
|
|
orgName = orgRow?.name ?? null;
|
|
} catch (e) {
|
|
// ignore and fallback to details
|
|
orgName = orgName ?? null;
|
|
}
|
|
}
|
|
|
|
// Map to StructureInfos expected by frontend
|
|
const infos = {
|
|
// Votre structure
|
|
raison_sociale: details?.structure_api || orgName || null,
|
|
siret: details?.siret || null,
|
|
forme_juridique: details?.forme_juridique || null,
|
|
// La "date de déclaration" correspond à `entree_en_relation` (YYYY-MM-DD)
|
|
declaration: (details?.entree_en_relation as any) || (details?.declaration as any) || (details?.date_declaration as any) || (details?.declaration_date as any) || null,
|
|
convention_collective: details?.ccn || null,
|
|
code_ape: details?.ape || 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),
|
|
presidente: (details?.president as any) || null,
|
|
tresoriere: (details?.tresorier as any) || null,
|
|
|
|
// Informations de contact
|
|
contact_principal: details?.nom_contact || (details?.prenom_contact ? `${details.prenom_contact} ${details?.nom_contact ?? ''}`.trim() : null),
|
|
email: details?.email_contact || null,
|
|
telephone: details?.tel_contact || null,
|
|
signataire_contrats: (details?.prenom_signataire || details?.nom_signataire)
|
|
? `${details?.prenom_signataire ?? ''} ${details?.nom_signataire ?? ''}`.trim()
|
|
: null,
|
|
// Inférence légère: si la qualité mentionne 'délég', on considère 'Oui'
|
|
signataire_delegation: (typeof details?.qualite_signataire === 'string' && /d[ée]l[ée]g/i.test(details.qualite_signataire))
|
|
? 'Oui'
|
|
: null,
|
|
|
|
// 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({ infos, email: details?.email_contact || null });
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
console.error('[api/informations] error:', message);
|
|
return NextResponse.json({ error: 'internal_server_error', message }, { status: 500 });
|
|
}
|
|
}
|