espace-paie-odentas/app/api/informations/productions/route.ts
odentas 266eb3598a feat: Implémenter store global Zustand + calcul total quantités + fix structure field + montants personnalisés virements
- 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
2025-12-01 21:51:57 +01:00

122 lines
4.6 KiB
TypeScript

// app/api/informations/productions/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 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('structure_api')
.eq('id', memberData.org_id)
.single();
if (orgError || !orgData?.structure_api) return null;
return { id: memberData.org_id, name: orgData.structure_api, isStaff: false };
} catch (e) {
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('structure_api').eq('id', activeOrgId).single();
return { id: activeOrgId, name: orgData?.structure_api || '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 url = new URL(req.url);
const page = Math.max(1, parseInt(url.searchParams.get('page') || '1', 10));
const limit = Math.max(1, Math.min(50, parseInt(url.searchParams.get('limit') || '25', 10)));
const from = (page - 1) * limit;
const to = from + limit - 1;
const supabase = createRouteHandlerClient({ cookies });
const { data: { session } } = await supabase.auth.getSession();
if (!session) return NextResponse.json({ error: 'unauthorized' }, { status: 401 });
let clientInfo;
try {
clientInfo = await getClientInfoFromSession(session, supabase);
} catch (e) {
const message = e instanceof Error ? e.message : String(e);
return NextResponse.json({ error: 'forbidden', message }, { status: 403 });
}
// 🔧 OVERRIDE pour le staff : si header x-active-org-id est présent, l'utiliser
const headerOrgId = req.headers.get('x-active-org-id');
if (clientInfo.isStaff && headerOrgId) {
const { data: orgData } = await supabase.from('organizations').select('structure_api').eq('id', headerOrgId).maybeSingle();
clientInfo = {
id: headerOrgId,
name: orgData?.structure_api || 'Staff Access',
isStaff: true
};
}
// Query productions for this organization
let query: any = supabase.from('productions').select('*', { count: 'exact' });
if (clientInfo.id) {
query = query.eq('org_id', clientInfo.id);
}
query = query.order('declaration_date', { ascending: false, nullsFirst: false }).order('name', { ascending: true }).range(from, to);
const { data, error, count } = await query;
if (error) {
console.error('[api/informations/productions] Supabase error:', error.message);
return NextResponse.json({ error: 'supabase_error', detail: error.message }, { status: 500 });
}
const items = (data || []).map((r: any) => ({
nom: r.name as string,
numero_objet: r.reference as string | null,
declaration: r.declaration_date as string | null,
}));
const total = typeof count === 'number' ? count : items.length;
const hasMore = from + items.length < total;
return NextResponse.json({ items, total, hasMore, page, limit });
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return NextResponse.json({ error: 'internal_server_error', message }, { status: 500 });
}
}