191 lines
5.5 KiB
TypeScript
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'
|
|
});
|
|
}
|
|
});
|