import { requireSalesOrAdmin } from '~/server/utils/auth'; import { getNocoDbConfiguration, normalizePersonName } from '~/server/utils/nocodb'; import { logAuditEvent } from '~/server/utils/audit-logger'; export default defineEventHandler(async (event) => { console.log('[EXPENSES] Merge duplicates request'); let body: any; try { // Require sales or admin access const session = await requireSalesOrAdmin(event); const userId = session.user?.id || 'unknown'; const userEmail = session.user?.email || 'unknown'; body = await readBody(event); const { masterId, duplicateIds, mergeData } = body; if (!masterId || !duplicateIds || !Array.isArray(duplicateIds) || duplicateIds.length === 0) { throw createError({ statusCode: 400, statusMessage: 'Invalid merge request. Master ID and duplicate IDs required.' }); } console.log('[EXPENSES] Merging', duplicateIds.length, 'duplicates into master expense', masterId); // Get NocoDB configuration const config = getNocoDbConfiguration(); const expenseTableId = "mxfcefkk4dqs6uq"; // First, get all expenses involved const allIds = [masterId, ...duplicateIds]; const expensesToMerge: any[] = []; for (const id of allIds) { try { const expense = await $fetch(`${config.url}/api/v2/tables/${expenseTableId}/records/${id}`, { headers: { 'xc-token': config.token } }); expensesToMerge.push(expense); } catch (error) { console.error(`[EXPENSES] Failed to fetch expense ${id}:`, error); } } if (expensesToMerge.length < 2) { throw createError({ statusCode: 404, statusMessage: 'Could not find enough expenses to merge' }); } const masterExpense = expensesToMerge.find(e => e.Id === parseInt(masterId)); if (!masterExpense) { throw createError({ statusCode: 404, statusMessage: 'Master expense not found' }); } // Log the action before making changes await logAuditEvent(event, 'MERGE_EXPENSE_DUPLICATES', 'expense', { resourceId: masterId, changes: { duplicateIds, mergeData, originalExpenses: expensesToMerge } }); // Update master expense with merged data if (mergeData) { const updateData: any = { Id: parseInt(masterId) }; // Normalize payer name if provided if (mergeData.Payer) { updateData.Payer = normalizePersonName(mergeData.Payer); } // Copy other fields const allowedFields = [ 'Establishment Name', 'Price', 'Category', 'Contents', 'Time', 'Payment Method', 'currency', 'Paid' ]; allowedFields.forEach(field => { if (mergeData[field] !== undefined) { updateData[field] = mergeData[field]; } }); console.log('[EXPENSES] Updating master expense with:', updateData); await $fetch(`${config.url}/api/v2/tables/${expenseTableId}/records`, { method: 'PATCH', headers: { 'xc-token': config.token, 'Content-Type': 'application/json' }, body: updateData }); } // Delete duplicate expenses const deleteResults = []; for (const duplicateId of duplicateIds) { try { console.log('[EXPENSES] Deleting duplicate expense:', duplicateId); await $fetch(`${config.url}/api/v2/tables/${expenseTableId}/records`, { method: 'DELETE', headers: { 'xc-token': config.token, 'Content-Type': 'application/json' }, body: { Id: parseInt(duplicateId) } }); deleteResults.push({ id: duplicateId, success: true }); } catch (error: any) { console.error('[EXPENSES] Failed to delete duplicate:', duplicateId, error); deleteResults.push({ id: duplicateId, success: false, error: error.message || 'Delete failed' }); } } // Check if all deletes were successful const failedDeletes = deleteResults.filter(r => !r.success); if (failedDeletes.length > 0) { console.warn('[EXPENSES] Some duplicates could not be deleted:', failedDeletes); } // Log successful completion await logAuditEvent(event, 'MERGE_EXPENSE_DUPLICATES_COMPLETE', 'expense', { resourceId: masterId, changes: { deletedCount: deleteResults.filter(r => r.success).length, failedDeletes, mergeData }, status: 'success' }); return { success: true, data: { masterId, mergedData: mergeData, deletedCount: deleteResults.filter(r => r.success).length, deleteResults, message: `Successfully merged ${deleteResults.filter(r => r.success).length} duplicates into expense ${masterId}` } }; } catch (error: any) { console.error('[EXPENSES] Failed to merge duplicates:', error); // Log the failure await logAuditEvent(event, 'MERGE_EXPENSE_DUPLICATES_FAILED', 'expense', { resourceId: body?.masterId || 'unknown', changes: { error: error.message || 'Unknown error', requestBody: body }, status: 'failure', errorMessage: error.message || 'Unknown error' }); if (error.statusCode) { throw error; } throw createError({ statusCode: 500, statusMessage: 'Failed to merge duplicate expenses' }); } });