espace-paie-odentas/app/api/staff/contrats/temps-reel/route.ts
odentas 965b1fb9cd feat: Ajouter interface de saisie en masse du temps de travail réel
- Création de la page /staff/contrats/saisie-temps-reel avec tableau éditable
- Ajout des colonnes jours_representations et jours_repetitions dans l'API
- Construction intelligente du TT Contractuel (concaténation des sources)
- Ajout de la colonne temps_reel_traite pour marquer les contrats traités
- Interface avec filtres (année, mois, organisation, recherche)
- Tri par date/salarié
- Édition inline avec auto-save via API
- Checkbox pour marquer comme traité (masque automatiquement la ligne)
- Toggle pour afficher/masquer les contrats traités
- Migration SQL pour la colonne temps_reel_traite
- Ajout du menu 'Temps de travail réel' dans la sidebar
- Logs de débogage pour le suivi des sauvegardes
2025-11-28 12:31:02 +01:00

166 lines
5.2 KiB
TypeScript

import { NextResponse } from "next/server";
import { createSbServer } from "@/lib/supabaseServer";
export async function GET(req: Request) {
const supabase = createSbServer();
// Vérifier l'authentification
const { data: { user }, error: authError } = await supabase.auth.getUser();
if (authError || !user) {
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
}
// Vérifier que l'utilisateur est staff
const { data: staffUser } = await supabase
.from("staff_users")
.select("is_staff")
.eq("user_id", user.id)
.maybeSingle();
if (!staffUser?.is_staff) {
return NextResponse.json({ error: "Accès réservé au staff" }, { status: 403 });
}
// Récupérer les paramètres de filtrage depuis l'URL
const { searchParams } = new URL(req.url);
const yearFilter = searchParams.get("year");
const monthFilter = searchParams.get("month");
const orgFilter = searchParams.get("org");
try {
let query = supabase
.from("cddu_contracts")
.select(`
id,
production_name,
employee_name,
profession,
start_date,
end_date,
org_id,
jours_travail,
jours_travail_non_artiste,
jours_representations,
jours_repetitions,
cachets_representations,
services_repetitions,
nombre_d_heures,
precisions_salaire,
jours_travail_reel,
nb_representations_reel,
nb_services_repetitions_reel,
nb_heures_repetitions_reel,
nb_heures_annexes_reel,
nb_cachets_aem_reel,
nb_heures_aem_reel,
temps_reel_traite,
organizations(name)
`);
// Filtre par année
if (yearFilter && yearFilter !== "all") {
const yearStart = `${yearFilter}-01-01`;
const yearEnd = `${yearFilter}-12-31`;
query = query.gte("start_date", yearStart).lte("start_date", yearEnd);
}
// Filtre par mois
if (monthFilter && monthFilter !== "all" && yearFilter && yearFilter !== "all") {
const monthPadded = monthFilter.padStart(2, "0");
const monthStart = `${yearFilter}-${monthPadded}-01`;
const nextMonth = parseInt(monthFilter) === 12 ? 1 : parseInt(monthFilter) + 1;
const nextYear = parseInt(monthFilter) === 12 ? parseInt(yearFilter) + 1 : parseInt(yearFilter);
const monthEnd = `${nextYear}-${String(nextMonth).padStart(2, "0")}-01`;
query = query.gte("start_date", monthStart).lt("start_date", monthEnd);
}
// Filtre par organisation
if (orgFilter && orgFilter !== "all") {
query = query.eq("org_id", orgFilter);
}
const { data, error } = await query.order("start_date", { ascending: false });
if (error) {
console.error("Erreur récupération contrats:", error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
// Ajouter le nom de l'organisation
const contracts = (data || []).map((contract: any) => ({
...contract,
organization_name: contract.organizations?.name || "—",
}));
return NextResponse.json(contracts);
} catch (error: any) {
console.error("Erreur serveur:", error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
export async function PATCH(req: Request) {
const supabase = createSbServer();
// Vérifier l'authentification
const { data: { user }, error: authError } = await supabase.auth.getUser();
if (authError || !user) {
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
}
// Vérifier que l'utilisateur est staff
const { data: staffUser } = await supabase
.from("staff_users")
.select("is_staff")
.eq("user_id", user.id)
.maybeSingle();
if (!staffUser?.is_staff) {
return NextResponse.json({ error: "Accès réservé au staff" }, { status: 403 });
}
try {
const body = await req.json();
const { contractId, field, value } = body;
console.log("[PATCH temps-reel] Mise à jour:", { contractId, field, value });
if (!contractId || !field) {
return NextResponse.json({ error: "contractId et field requis" }, { status: 400 });
}
// Liste des champs autorisés pour la mise à jour
const allowedFields = [
"jours_travail_reel",
"nb_representations_reel",
"nb_services_repetitions_reel",
"nb_heures_repetitions_reel",
"nb_heures_annexes_reel",
"nb_cachets_aem_reel",
"nb_heures_aem_reel",
"temps_reel_traite",
];
if (!allowedFields.includes(field)) {
return NextResponse.json({ error: "Champ non autorisé" }, { status: 400 });
}
const { data, error } = await supabase
.from("cddu_contracts")
.update({ [field]: value })
.eq("id", contractId)
.select()
.single();
if (error) {
console.error("[PATCH temps-reel] Erreur:", error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
console.log("[PATCH temps-reel] Succès:", data);
return NextResponse.json({ success: true, data });
} catch (error: any) {
console.error("[PATCH temps-reel] Erreur serveur:", error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}