- 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
206 lines
6.4 KiB
TypeScript
206 lines
6.4 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
|
|
import { cookies } from "next/headers";
|
|
|
|
// =============================================================================
|
|
// POST /api/staff/virements-salaires/create
|
|
// Creates a new salary transfer record
|
|
// =============================================================================
|
|
export async function POST(req: NextRequest) {
|
|
try {
|
|
// 1) Check auth
|
|
const supabase = createRouteHandlerClient({ cookies });
|
|
const {
|
|
data: { session },
|
|
error: sessionError,
|
|
} = await supabase.auth.getSession();
|
|
|
|
if (sessionError || !session) {
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
|
|
const user = session.user;
|
|
|
|
// 2) Check if staff
|
|
const { data: staffData, error: staffError } = await supabase
|
|
.from("staff_users")
|
|
.select("is_staff")
|
|
.eq("user_id", user.id)
|
|
.maybeSingle();
|
|
|
|
const isStaff = staffData?.is_staff || false;
|
|
|
|
if (!isStaff) {
|
|
return NextResponse.json(
|
|
{ error: "Forbidden: staff only" },
|
|
{ status: 403 }
|
|
);
|
|
}
|
|
|
|
// 3) Parse request body
|
|
const body = await req.json();
|
|
const {
|
|
org_id,
|
|
period_month,
|
|
period_label,
|
|
deadline,
|
|
mode,
|
|
num_appel,
|
|
total_net,
|
|
notes,
|
|
selection_mode, // 'period' (default) or 'manual'
|
|
payslip_ids, // Array of payslip IDs (required if selection_mode = 'manual')
|
|
custom_amounts, // Object mapping payslip_id to custom amount (optional)
|
|
} = body;
|
|
|
|
// 4) Validate required fields
|
|
const selectionMode = selection_mode || 'period';
|
|
|
|
if (!org_id || !deadline || !mode) {
|
|
return NextResponse.json(
|
|
{
|
|
error: "Missing required fields: org_id, deadline, mode",
|
|
},
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
// Validate based on selection mode
|
|
if (selectionMode === 'period' && !period_month) {
|
|
return NextResponse.json(
|
|
{
|
|
error: "period_month is required when selection_mode is 'period'",
|
|
},
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
if (selectionMode === 'manual' && (!payslip_ids || !Array.isArray(payslip_ids) || payslip_ids.length === 0)) {
|
|
return NextResponse.json(
|
|
{
|
|
error: "payslip_ids array is required and must not be empty when selection_mode is 'manual'",
|
|
},
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
// 5) Verify organization exists
|
|
const { data: org, error: orgError } = await supabase
|
|
.from("organizations")
|
|
.select("id, name")
|
|
.eq("id", org_id)
|
|
.single();
|
|
if (orgError || !org) {
|
|
return NextResponse.json(
|
|
{ error: "Organization not found" },
|
|
{ status: 404 }
|
|
);
|
|
}
|
|
|
|
// 5b) If manual mode, verify all payslips exist and belong to the organization
|
|
if (selectionMode === 'manual') {
|
|
const { data: payslips, error: payslipsError } = await supabase
|
|
.from("payslips")
|
|
.select("id, organization_id, period_month")
|
|
.in("id", payslip_ids);
|
|
|
|
if (payslipsError || !payslips || payslips.length !== payslip_ids.length) {
|
|
return NextResponse.json(
|
|
{ error: "One or more payslips not found" },
|
|
{ status: 404 }
|
|
);
|
|
}
|
|
|
|
// Verify all payslips belong to the specified organization
|
|
const invalidPayslips = payslips.filter(p => p.organization_id !== org_id);
|
|
if (invalidPayslips.length > 0) {
|
|
return NextResponse.json(
|
|
{ error: `${invalidPayslips.length} payslip(s) do not belong to organization ${org_id}` },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
console.log("[create salary transfer] Manual mode: verified", payslips.length, "payslips");
|
|
}
|
|
|
|
// 6) Insert new salary transfer
|
|
const insertData = {
|
|
org_id,
|
|
period_month: selectionMode === 'period' ? period_month : null, // NULL for manual mode
|
|
period_label: period_label || null,
|
|
deadline,
|
|
mode,
|
|
num_appel: num_appel || null,
|
|
total_net: total_net || null,
|
|
notes: notes || null,
|
|
selection_mode: selectionMode,
|
|
notification_sent: false,
|
|
notification_ok: false,
|
|
salaires_payes: false,
|
|
created_at: new Date().toISOString(),
|
|
updated_at: new Date().toISOString(),
|
|
};
|
|
|
|
console.log("[create salary transfer] Insert data:", insertData);
|
|
|
|
const { data: newTransfer, error: insertError } = await supabase
|
|
.from("salary_transfers")
|
|
.insert(insertData)
|
|
.select("*, organizations!org_id(name)")
|
|
.single();
|
|
|
|
if (insertError) {
|
|
console.error("[create salary transfer] Insert error:", insertError);
|
|
return NextResponse.json(
|
|
{
|
|
error: "Failed to create salary transfer",
|
|
details: insertError.message,
|
|
code: insertError.code,
|
|
hint: insertError.hint
|
|
},
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
|
|
// 6b) If manual mode, insert payslip links
|
|
if (selectionMode === 'manual' && payslip_ids && payslip_ids.length > 0) {
|
|
const links = payslip_ids.map(payslip_id => ({
|
|
salary_transfer_id: newTransfer.id,
|
|
payslip_id,
|
|
custom_amount: custom_amounts?.[payslip_id] || null,
|
|
}));
|
|
|
|
const { error: linksError } = await supabase
|
|
.from("salary_transfer_payslips")
|
|
.insert(links);
|
|
|
|
if (linksError) {
|
|
console.error("[create salary transfer] Links insert error:", linksError);
|
|
// Rollback: delete the transfer we just created
|
|
await supabase.from("salary_transfers").delete().eq("id", newTransfer.id);
|
|
return NextResponse.json(
|
|
{
|
|
error: "Failed to link payslips to salary transfer",
|
|
details: linksError.message
|
|
},
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
|
|
console.log("[create salary transfer] Linked", payslip_ids.length, "payslips to transfer");
|
|
}
|
|
|
|
// 7) Return the new record
|
|
return NextResponse.json({
|
|
success: true,
|
|
data: newTransfer,
|
|
payslips_count: selectionMode === 'manual' ? payslip_ids?.length : null,
|
|
});
|
|
} catch (err: any) {
|
|
console.error("Error in create salary transfer:", err);
|
|
return NextResponse.json(
|
|
{ error: err.message || "Internal server error" },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|