Replaces direct Supabase client instantiation with a unified authentication and authorization helper, introducing role-based access control to sensitive endpoints like escrow and payroll, and standardizing compliance event logging. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 9203795e-937a-4306-b81d-b4d5c78c240e Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: 5eb35c62-c5ab-4c7e-9552-8dc89efa29f3 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7c94b7a0-29c7-4f2e-94ef-44b2153872b7/9203795e-937a-4306-b81d-b4d5c78c240e/aPpJgbb Replit-Helium-Checkpoint-Created: true
124 lines
3.9 KiB
TypeScript
124 lines
3.9 KiB
TypeScript
import type { VercelRequest, VercelResponse } from "@vercel/node";
|
|
import { authenticateRequest, requireAdmin, logComplianceEvent } from "../_auth";
|
|
|
|
export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|
const auth = await authenticateRequest(req);
|
|
if (!requireAdmin(auth, res)) return;
|
|
|
|
const { adminClient, user } = auth;
|
|
|
|
if (req.method === 'GET') {
|
|
const { status, start_date, end_date, tax_year } = req.query;
|
|
|
|
let query = adminClient
|
|
.from('nexus_payouts')
|
|
.select(`
|
|
*,
|
|
nexus_talent_profiles!inner(
|
|
user_id,
|
|
legal_first_name,
|
|
legal_last_name,
|
|
tax_classification,
|
|
residency_state
|
|
)
|
|
`)
|
|
.order('created_at', { ascending: false });
|
|
|
|
if (status) query = query.eq('status', status);
|
|
if (tax_year) query = query.eq('tax_year', tax_year);
|
|
if (start_date) query = query.gte('scheduled_date', start_date);
|
|
if (end_date) query = query.lte('scheduled_date', end_date);
|
|
|
|
const { data, error } = await query;
|
|
|
|
if (error) {
|
|
return res.status(500).json({ error: error.message });
|
|
}
|
|
|
|
const totalPending = data?.filter(p => p.status === 'pending').reduce((sum, p) => sum + Number(p.net_amount), 0) || 0;
|
|
const totalProcessed = data?.filter(p => p.status === 'completed').reduce((sum, p) => sum + Number(p.net_amount), 0) || 0;
|
|
|
|
return res.status(200).json({
|
|
data,
|
|
summary: {
|
|
total_payouts: data?.length || 0,
|
|
pending_amount: totalPending,
|
|
processed_amount: totalProcessed
|
|
}
|
|
});
|
|
}
|
|
|
|
if (req.method === 'POST' && req.query.action === 'process') {
|
|
const { payout_ids } = req.body;
|
|
|
|
if (!payout_ids || !Array.isArray(payout_ids)) {
|
|
return res.status(400).json({ error: 'payout_ids array required' });
|
|
}
|
|
|
|
const { data: payouts } = await adminClient
|
|
.from('nexus_payouts')
|
|
.select('*')
|
|
.in('id', payout_ids)
|
|
.eq('status', 'pending');
|
|
|
|
if (!payouts || payouts.length === 0) {
|
|
return res.status(400).json({ error: 'No pending payouts found' });
|
|
}
|
|
|
|
const { data, error } = await adminClient
|
|
.from('nexus_payouts')
|
|
.update({
|
|
status: 'processing',
|
|
updated_at: new Date().toISOString()
|
|
})
|
|
.in('id', payout_ids)
|
|
.select();
|
|
|
|
if (error) {
|
|
return res.status(500).json({ error: error.message });
|
|
}
|
|
|
|
await logComplianceEvent(adminClient, {
|
|
entity_type: 'payroll',
|
|
entity_id: user!.id,
|
|
event_type: 'payroll_batch_processing',
|
|
event_category: 'financial',
|
|
actor_id: user!.id,
|
|
actor_role: 'admin',
|
|
realm_context: 'corp',
|
|
description: `Processing ${data?.length} payouts`,
|
|
payload: { payout_ids, total_amount: data?.reduce((sum, p) => sum + Number(p.net_amount), 0) },
|
|
legal_entity: 'for_profit'
|
|
}, req);
|
|
|
|
return res.status(200).json({
|
|
data,
|
|
processed_count: data?.length || 0
|
|
});
|
|
}
|
|
|
|
if (req.method === 'GET' && req.query.action === 'summary') {
|
|
const currentYear = new Date().getFullYear();
|
|
|
|
const { data: yearPayouts } = await adminClient
|
|
.from('nexus_payouts')
|
|
.select('net_amount, status, tax_year')
|
|
.eq('tax_year', currentYear);
|
|
|
|
const { data: azHours } = await adminClient
|
|
.from('nexus_time_logs')
|
|
.select('az_eligible_hours')
|
|
.eq('submission_status', 'approved')
|
|
.gte('log_date', `${currentYear}-01-01`);
|
|
|
|
return res.status(200).json({
|
|
tax_year: currentYear,
|
|
total_payouts: yearPayouts?.filter(p => p.status === 'completed').reduce((sum, p) => sum + Number(p.net_amount), 0) || 0,
|
|
pending_payouts: yearPayouts?.filter(p => p.status === 'pending').reduce((sum, p) => sum + Number(p.net_amount), 0) || 0,
|
|
total_az_hours: azHours?.reduce((sum, h) => sum + Number(h.az_eligible_hours), 0) || 0,
|
|
payout_count: yearPayouts?.length || 0
|
|
});
|
|
}
|
|
|
|
return res.status(405).json({ error: 'Method not allowed' });
|
|
}
|