- 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
149 lines
7 KiB
TypeScript
149 lines
7 KiB
TypeScript
export const dynamic = 'force-dynamic';
|
|
export const revalidate = 0;
|
|
export const runtime = 'nodejs';
|
|
|
|
import { NextResponse } from "next/server";
|
|
import { createSbServer } from "@/lib/supabaseServer";
|
|
|
|
export async function GET(req: Request) {
|
|
try {
|
|
const sb = createSbServer();
|
|
const { data: { user } } = await sb.auth.getUser();
|
|
if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
|
|
const { data: me } = await sb.from("staff_users").select("is_staff").eq("user_id", user.id).maybeSingle();
|
|
if (!me?.is_staff) return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
|
|
|
const url = new URL(req.url);
|
|
const q = url.searchParams.get("q");
|
|
const structure = url.searchParams.get("structure");
|
|
const type_de_contrat = url.searchParams.get("type_de_contrat");
|
|
const production_name = url.searchParams.get("production_name");
|
|
const etat_de_la_demande = url.searchParams.get("etat_de_la_demande");
|
|
const etat_de_la_paie = url.searchParams.get("etat_de_la_paie");
|
|
const dpae = url.searchParams.get("dpae");
|
|
const signature_state = url.searchParams.get("signature_state");
|
|
const employee_matricule = url.searchParams.get("employee_matricule");
|
|
const start_from = url.searchParams.get("start_from");
|
|
const start_to = url.searchParams.get("start_to");
|
|
const end_from = url.searchParams.get("end_from");
|
|
const end_to = url.searchParams.get("end_to");
|
|
const sort = url.searchParams.get("sort") || "created_at";
|
|
const order = (url.searchParams.get("order") || "desc").toLowerCase() === "asc" ? "asc" : "desc";
|
|
const limit = parseInt(url.searchParams.get("limit") || "10000", 10); // Limite par défaut élevée pour récupérer tous les contrats
|
|
const offset = Math.max(0, parseInt(url.searchParams.get("offset") || "0", 10));
|
|
|
|
// Build base query with salaries join
|
|
let query = sb.from("cddu_contracts").select(`
|
|
id, contract_number, employee_name, employee_matricule, employee_id, structure, type_de_contrat,
|
|
profession, production_name,
|
|
start_date, end_date, created_at, etat_de_la_demande, etat_de_la_paie, dpae, gross_pay,
|
|
contrat_signe_par_employeur, contrat_signe, org_id,
|
|
last_employer_notification_at, last_employee_notification_at,
|
|
analytique, nombre_d_heures, n_objet, objet_spectacle,
|
|
salaries!employee_id(salarie, nom, prenom, adresse_mail, code_salarie),
|
|
organizations!org_id(organization_details(code_employeur))
|
|
`, { count: "exact" });
|
|
|
|
// Appliquer d'abord les filtres exacts (non-OR) pour éviter les conflits
|
|
if (employee_matricule) query = query.eq("employee_matricule", employee_matricule);
|
|
if (structure) query = query.eq("structure", structure.trim());
|
|
if (production_name) query = query.eq("production_name", production_name);
|
|
|
|
// Handle special "RG" filter for common law contracts (CDD de droit commun + CDI)
|
|
if (type_de_contrat === "RG") {
|
|
query = query.in("type_de_contrat", ["CDD de droit commun", "CDI"]);
|
|
} else if (type_de_contrat) {
|
|
query = query.eq("type_de_contrat", type_de_contrat);
|
|
}
|
|
|
|
// Handle multiple etat_de_la_demande values (comma-separated)
|
|
if (etat_de_la_demande) {
|
|
const etats = etat_de_la_demande.split(',').map(e => e.trim()).filter(Boolean);
|
|
if (etats.length === 1) {
|
|
query = query.eq("etat_de_la_demande", etats[0]);
|
|
} else if (etats.length > 1) {
|
|
query = query.in("etat_de_la_demande", etats);
|
|
}
|
|
}
|
|
if (etat_de_la_paie) query = query.eq("etat_de_la_paie", etat_de_la_paie);
|
|
if (dpae) query = query.eq("dpae", dpae);
|
|
|
|
// Handle signature state filter
|
|
if (signature_state === "non_signe") {
|
|
query = query.or("contrat_signe_par_employeur.eq.Non,contrat_signe.eq.Non");
|
|
} else if (signature_state === "employeur_seulement") {
|
|
query = query.eq("contrat_signe_par_employeur", "Oui").eq("contrat_signe", "Non");
|
|
} else if (signature_state === "signe_complet") {
|
|
query = query.eq("contrat_signe_par_employeur", "Oui").eq("contrat_signe", "Oui");
|
|
}
|
|
|
|
if (start_from) query = query.gte("start_date", start_from);
|
|
if (start_to) query = query.lte("start_date", start_to);
|
|
if (end_from) query = query.gte("end_date", end_from);
|
|
if (end_to) query = query.lte("end_date", end_to);
|
|
|
|
// Appliquer le filtre de recherche textuelle EN DERNIER pour éviter les conflits avec les autres filtres
|
|
if (q) {
|
|
// Recherche sur plusieurs colonnes avec OR
|
|
query = query.or(`contract_number.ilike.%${q}%,employee_name.ilike.%${q}%,employee_matricule.ilike.%${q}%`);
|
|
}
|
|
|
|
// allow sort by start_date or end_date or created_at or employee_name or production_name
|
|
const allowedSorts = new Set(["start_date", "end_date", "created_at", "contract_number", "employee_name", "production_name"]);
|
|
const sortCol = allowedSorts.has(sort) ? sort : "created_at";
|
|
|
|
// Pour le tri par nom, on doit traiter différemment
|
|
if (sortCol === "employee_name") {
|
|
// D'abord récupérer les données sans tri
|
|
query = query.range(offset, offset + limit - 1);
|
|
|
|
const { data: contractsData, error: contractsError, count } = await query;
|
|
if (contractsError) return NextResponse.json({ error: contractsError.message }, { status: 500 });
|
|
|
|
if (!contractsData || contractsData.length === 0) {
|
|
return NextResponse.json({ rows: [], count: count ?? 0 });
|
|
}
|
|
|
|
// Récupérer les informations des salariés pour le tri
|
|
const employeeIds = contractsData.map(c => c.employee_id).filter(Boolean);
|
|
const { data: salariesData, error: salariesError } = await sb
|
|
.from("salaries")
|
|
.select("id, nom, prenom")
|
|
.in("id", employeeIds);
|
|
|
|
if (salariesError) return NextResponse.json({ error: salariesError.message }, { status: 500 });
|
|
|
|
// Créer une map pour le tri
|
|
const salariesMap = new Map();
|
|
salariesData?.forEach(s => {
|
|
salariesMap.set(s.id, s.nom);
|
|
});
|
|
|
|
// Trier les contrats par nom de famille
|
|
const sortedContracts = contractsData.sort((a, b) => {
|
|
const nomA = salariesMap.get(a.employee_id) || '';
|
|
const nomB = salariesMap.get(b.employee_id) || '';
|
|
if (order === "asc") {
|
|
return nomA.localeCompare(nomB);
|
|
} else {
|
|
return nomB.localeCompare(nomA);
|
|
}
|
|
});
|
|
|
|
return NextResponse.json({ rows: sortedContracts, count: count ?? sortedContracts.length });
|
|
} else {
|
|
// Tri normal pour les autres colonnes
|
|
query = query.order(sortCol, { ascending: order === "asc" });
|
|
query = query.range(offset, offset + limit - 1);
|
|
|
|
const { data, error, count } = await query;
|
|
if (error) return NextResponse.json({ error: error.message }, { status: 500 });
|
|
|
|
return NextResponse.json({ rows: data ?? [], count: count ?? (data ? data.length : 0) });
|
|
}
|
|
} catch (err: any) {
|
|
console.error(err);
|
|
return NextResponse.json({ error: "Internal" }, { status: 500 });
|
|
}
|
|
}
|