- Ajout détails cachets/répétitions/heures au modal ContractDetails - Card verte avec validation quand tous les contrats ont une fiche de paie - Système complet de création de fiches de paie avec recherche et vérification - Modal liste des contrats sans paie avec création directe - Amélioration édition dates dans PayslipDetailsModal - Optimisation recherche contrats (ordre des filtres) - Augmentation limite pagination ContractsGrid à 200 - Ajout logs debug génération PDF logo - Script SQL vérification cohérence structure/organisation
182 lines
6.4 KiB
TypeScript
182 lines
6.4 KiB
TypeScript
// app/api/staff/payslips/create/route.ts
|
|
import { createSbServer } from "@/lib/supabaseServer";
|
|
import { NextRequest, NextResponse } from "next/server";
|
|
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
const sb = createSbServer();
|
|
|
|
// Vérifier l'authentification
|
|
const { data: { user } } = await sb.auth.getUser();
|
|
if (!user) {
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
|
|
// Vérifier que c'est un staff
|
|
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 body = await request.json();
|
|
const {
|
|
contract_id,
|
|
period_start,
|
|
period_end,
|
|
pay_date,
|
|
pay_number: requestedPayNumber,
|
|
gross_amount,
|
|
net_amount,
|
|
net_after_withholding,
|
|
employer_cost,
|
|
} = body;
|
|
|
|
// Validation
|
|
if (!contract_id || !period_start || !period_end) {
|
|
return NextResponse.json(
|
|
{ error: "contract_id, period_start et period_end sont requis" },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
// Récupérer les infos du contrat pour obtenir l'organization_id et les dates
|
|
const { data: contract, error: contractError } = await sb
|
|
.from("cddu_contracts")
|
|
.select("org_id, start_date, end_date")
|
|
.eq("id", contract_id)
|
|
.single();
|
|
|
|
if (contractError || !contract) {
|
|
return NextResponse.json(
|
|
{ error: "Contrat non trouvé" },
|
|
{ status: 404 }
|
|
);
|
|
}
|
|
|
|
// Déterminer si le contrat est mono-mois ou multi-mois
|
|
const contractStartDate = new Date(contract.start_date);
|
|
const contractEndDate = new Date(contract.end_date);
|
|
const isMultiMonth = contractStartDate.getMonth() !== contractEndDate.getMonth() ||
|
|
contractStartDate.getFullYear() !== contractEndDate.getFullYear();
|
|
|
|
console.log("[POST /api/staff/payslips/create] Contract dates:", {
|
|
start_date: contract.start_date,
|
|
end_date: contract.end_date,
|
|
isMultiMonth
|
|
});
|
|
|
|
// Calculer period_month (format YYYY-MM-01 - premier jour du mois basé sur period_start)
|
|
const periodStartDate = new Date(period_start);
|
|
const period_month = `${periodStartDate.getFullYear()}-${String(periodStartDate.getMonth() + 1).padStart(2, '0')}-01`;
|
|
|
|
// Vérifier si une paie existe déjà pour ce contrat et ce mois
|
|
const { data: existingPayslips, error: checkError } = await sb
|
|
.from("payslips")
|
|
.select("id, pay_number, period_month")
|
|
.eq("contract_id", contract_id)
|
|
.order("pay_number", { ascending: false });
|
|
|
|
if (checkError) {
|
|
console.error("[POST /api/staff/payslips/create] Error checking existing payslips:", checkError);
|
|
}
|
|
|
|
// Si contrat mono-mois et une paie existe déjà, interdire la création
|
|
if (!isMultiMonth && existingPayslips && existingPayslips.length > 0) {
|
|
const monthYear = new Date(period_month).toLocaleDateString('fr-FR', { month: 'long', year: 'numeric' });
|
|
return NextResponse.json(
|
|
{ error: `Une fiche de paie existe déjà pour ce contrat mono-mois (${monthYear})` },
|
|
{ status: 409 }
|
|
);
|
|
}
|
|
|
|
// Si contrat multi-mois, vérifier si une paie existe pour CE mois précis
|
|
if (isMultiMonth && existingPayslips && existingPayslips.length > 0) {
|
|
const existingForThisMonth = existingPayslips.find(p => p.period_month === period_month);
|
|
if (existingForThisMonth) {
|
|
const monthYear = new Date(period_month).toLocaleDateString('fr-FR', { month: 'long', year: 'numeric' });
|
|
return NextResponse.json(
|
|
{ error: `Une fiche de paie existe déjà pour ce mois (${monthYear})` },
|
|
{ status: 409 }
|
|
);
|
|
}
|
|
}
|
|
|
|
// Déterminer le numéro de paie
|
|
let pay_number = 1;
|
|
if (isMultiMonth) {
|
|
if (requestedPayNumber !== undefined && requestedPayNumber !== null) {
|
|
// Utiliser le numéro fourni
|
|
pay_number = requestedPayNumber;
|
|
} else if (existingPayslips && existingPayslips.length > 0) {
|
|
// Trouver le numéro de paie le plus élevé et ajouter 1
|
|
const maxPayNumber = Math.max(...existingPayslips.map(p => p.pay_number || 0));
|
|
pay_number = maxPayNumber + 1;
|
|
}
|
|
console.log("[POST /api/staff/payslips/create] Multi-month contract, setting pay_number to:", pay_number);
|
|
} else {
|
|
// Pour les contrats mono-mois, toujours 1
|
|
pay_number = 1;
|
|
}
|
|
|
|
console.log("[POST /api/staff/payslips/create] Creating payslip with data:", {
|
|
contract_id,
|
|
organization_id: contract.org_id,
|
|
period_start,
|
|
period_end,
|
|
period_month,
|
|
pay_date,
|
|
pay_number
|
|
});
|
|
|
|
// Créer la fiche de paie
|
|
const { data: payslip, error: createError } = await sb
|
|
.from("payslips")
|
|
.insert({
|
|
contract_id,
|
|
organization_id: contract.org_id,
|
|
period_start,
|
|
period_end,
|
|
period_month,
|
|
pay_date: pay_date || null,
|
|
pay_number,
|
|
gross_amount: gross_amount || null,
|
|
net_amount: net_amount || null,
|
|
net_after_withholding: net_after_withholding || null,
|
|
employer_cost: employer_cost || null,
|
|
processed: false,
|
|
transfer_done: false,
|
|
})
|
|
.select()
|
|
.single();
|
|
|
|
if (createError) {
|
|
console.error("[POST /api/staff/payslips/create] Database error:", createError);
|
|
|
|
// Gérer l'erreur de contrainte d'unicité
|
|
if (createError.code === '23505' && createError.message.includes('payslips_contract_id_period_month_key')) {
|
|
const monthYear = new Date(period_month).toLocaleDateString('fr-FR', { month: 'long', year: 'numeric' });
|
|
return NextResponse.json(
|
|
{ error: `Une fiche de paie existe déjà pour ce contrat pour le mois de ${monthYear}` },
|
|
{ status: 409 }
|
|
);
|
|
}
|
|
|
|
return NextResponse.json(
|
|
{ error: "Erreur lors de la création de la fiche de paie", details: createError.message },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
|
|
console.log("[POST /api/staff/payslips/create] Payslip created successfully:", payslip.id);
|
|
|
|
return NextResponse.json({ payslip }, { status: 201 });
|
|
} catch (error) {
|
|
console.error("[POST /api/staff/payslips/create] Error:", error);
|
|
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
|
|
}
|
|
}
|