port-nimara-client-portal/server/api/expenses/duplicates/merge.ts

191 lines
5.5 KiB
TypeScript

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'
});
}
});