import { NextRequest, NextResponse } from 'next/server'; import { getPrisma } from '@/lib/db'; export async function POST(req: NextRequest) { try { const signature = req.headers.get('x-n8n-signature'); const expectedSecret = process.env.N8N_WEBHOOK_SECRET; if (!expectedSecret || signature !== expectedSecret) { return NextResponse.json({ success: false, error: { code: 'UNAUTHORIZED', metadata: {} } }, { status: 401 }); } const body = await req.json(); const { idempotencyKey, uploaderId, sales } = body; if (!idempotencyKey || !uploaderId || !Array.isArray(sales)) { return NextResponse.json({ success: false, error: { code: 'BAD_REQUEST', metadata: {} } }, { status: 400 }); } const prisma = getPrisma(); const result = await prisma.$transaction(async (tx: any) => { // Elevate privileges to admin role to bypass RLS for n8n batch operations await tx.$executeRawUnsafe(`SET LOCAL app.current_user_role = 'admin';`); // 1. Check idempotency const existing = await tx.salesResult.findMany({ where: { idempotencyKey: { startsWith: `${idempotencyKey}-` } } }); if (existing.length > 0) { return { alreadyProcessed: true, count: existing.length }; } // 2. Resolve usernames and hotel codes const uniqueUsernames = Array.from(new Set(sales.map(s => s.username).filter(Boolean))) as string[]; const uniqueHotelCodes = Array.from(new Set(sales.map(s => s.hotelCode).filter(Boolean))) as string[]; const dbUsers = await tx.user.findMany({ where: { username: { in: uniqueUsernames } } }); const dbHotels = await tx.hotel.findMany({ where: { code: { in: uniqueHotelCodes } } }); const userMap = new Map(dbUsers.map(u => [u.username, u])); const hotelMap = new Map(dbHotels.map(h => [h.code, h])); // 3. Save records const createdSales = []; for (let i = 0; i < sales.length; i++) { const s = sales[i]; const user = userMap.get(s.username); const hotel = hotelMap.get(s.hotelCode); if (!user || !hotel) { throw new Error(`User or Hotel not found for record: ${JSON.stringify(s)}`); } const created = await tx.salesResult.create({ data: { source: 'API', hotelId: hotel.id, userId: user.id, period: s.period, amount: Number(s.amount), salesCount: Number(s.salesCount), idempotencyKey: `${idempotencyKey}-${i}`, transactionId: s.transactionId || null, uploadedBy: uploaderId, status: 'PENDING' } }); createdSales.push(created); } // 4. Create Audit Log await tx.auditLog.create({ data: { userId: uploaderId, action: 'CREATE', targetTable: 'sales_results', targetId: createdSales[0]?.id || 0, newValue: { count: createdSales.length, idempotencyKey } } }); return { alreadyProcessed: false, count: createdSales.length }; }); return NextResponse.json({ success: true, code: result.alreadyProcessed ? 'IMPORT_ALREADY_PROCESSED' : 'IMPORT_SUCCESSFUL', metadata: { count: result.count, idempotencyKey } }, { status: 201 }); } catch (err: any) { console.error('Batch save error:', err); return NextResponse.json({ success: false, error: { code: 'BATCH_SAVE_FAILED', metadata: { message: err.message } } }, { status: 500 }); } }