Refactor duplicate handling to allow any authenticated user to check for duplicates, update API methods to require general authentication, and enhance expense fetching with improved error handling and logging.
This commit is contained in:
parent
587c9b6422
commit
ac7176ff17
|
|
@ -41,15 +41,15 @@ const showBanner = ref(true);
|
||||||
const duplicateCount = ref(0);
|
const duplicateCount = ref(0);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
// Check for duplicates on mount (admin only)
|
// Check for duplicates on mount (any authenticated user)
|
||||||
const checkForDuplicates = async () => {
|
const checkForDuplicates = async () => {
|
||||||
if (!isAdmin() || loading.value) return;
|
if (loading.value) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const response = await $fetch('/api/admin/duplicates/find', {
|
const response = await $fetch('/api/admin/duplicates/find', {
|
||||||
method: 'POST',
|
method: 'GET',
|
||||||
body: { threshold: 80 }
|
query: { threshold: 0.8 }
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.success && response.data?.duplicateGroups) {
|
if (response.success && response.data?.duplicateGroups) {
|
||||||
|
|
@ -82,10 +82,8 @@ onMounted(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only check for duplicates if user is admin
|
// Check for duplicates for any authenticated user
|
||||||
if (isAdmin()) {
|
// Small delay to let other components load first
|
||||||
// Small delay to let other components load first
|
setTimeout(checkForDuplicates, 2000);
|
||||||
setTimeout(checkForDuplicates, 2000);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,9 @@ const interestMenu = computed(() => {
|
||||||
|
|
||||||
console.log('[Dashboard] Computing interest menu - isAdmin:', userIsAdmin, 'groups:', userGroups);
|
console.log('[Dashboard] Computing interest menu - isAdmin:', userIsAdmin, 'groups:', userGroups);
|
||||||
|
|
||||||
|
// Check if user has sales or admin privileges
|
||||||
|
const hasSalesAccess = userGroups.includes('sales') || userGroups.includes('admin');
|
||||||
|
|
||||||
const baseMenu = [
|
const baseMenu = [
|
||||||
//{
|
//{
|
||||||
// to: "/dashboard/interest-eoi-queue",
|
// to: "/dashboard/interest-eoi-queue",
|
||||||
|
|
@ -125,11 +128,6 @@ const interestMenu = computed(() => {
|
||||||
icon: "mdi-account-check",
|
icon: "mdi-account-check",
|
||||||
title: "Interest Status",
|
title: "Interest Status",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
to: "/dashboard/expenses",
|
|
||||||
icon: "mdi-receipt",
|
|
||||||
title: "Expenses",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
to: "/dashboard/file-browser",
|
to: "/dashboard/file-browser",
|
||||||
icon: "mdi-folder",
|
icon: "mdi-folder",
|
||||||
|
|
@ -137,6 +135,18 @@ const interestMenu = computed(() => {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Only show expenses to sales and admin users
|
||||||
|
if (hasSalesAccess) {
|
||||||
|
console.log('[Dashboard] Adding expenses to menu (user has sales/admin access)');
|
||||||
|
baseMenu.push({
|
||||||
|
to: "/dashboard/expenses",
|
||||||
|
icon: "mdi-receipt",
|
||||||
|
title: "Expenses",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('[Dashboard] Hiding expenses from menu (user role:', userGroups, ')');
|
||||||
|
}
|
||||||
|
|
||||||
// Add admin menu items if user is admin
|
// Add admin menu items if user is admin
|
||||||
if (userIsAdmin) {
|
if (userIsAdmin) {
|
||||||
console.log('[Dashboard] Adding admin console to interest menu');
|
console.log('[Dashboard] Adding admin console to interest menu');
|
||||||
|
|
@ -216,33 +226,48 @@ const safeMenu = computed(() => {
|
||||||
|
|
||||||
console.warn('[Dashboard] Menu is not an array, returning fallback menu');
|
console.warn('[Dashboard] Menu is not an array, returning fallback menu');
|
||||||
|
|
||||||
// Fallback menu with essential items (including admin for safety)
|
// Get current user permissions for fallback menu
|
||||||
return [
|
const userIsAdmin = isAdmin();
|
||||||
|
const userGroups = getUserGroups();
|
||||||
|
const hasSalesAccess = userGroups.includes('sales') || userGroups.includes('admin');
|
||||||
|
|
||||||
|
// Fallback menu with essential items (respecting permissions)
|
||||||
|
const fallbackMenu = [
|
||||||
{
|
{
|
||||||
to: "/dashboard/interest-list",
|
to: "/dashboard/interest-list",
|
||||||
icon: "mdi-view-list",
|
icon: "mdi-view-list",
|
||||||
title: "Interest List",
|
title: "Interest List",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
to: "/dashboard/expenses",
|
|
||||||
icon: "mdi-receipt",
|
|
||||||
title: "Expenses",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
to: "/dashboard/file-browser",
|
to: "/dashboard/file-browser",
|
||||||
icon: "mdi-folder",
|
icon: "mdi-folder",
|
||||||
title: "File Browser",
|
title: "File Browser",
|
||||||
},
|
},
|
||||||
{
|
];
|
||||||
|
|
||||||
|
// Only add expenses if user has sales/admin access
|
||||||
|
if (hasSalesAccess) {
|
||||||
|
fallbackMenu.push({
|
||||||
|
to: "/dashboard/expenses",
|
||||||
|
icon: "mdi-receipt",
|
||||||
|
title: "Expenses",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only add admin console if user is admin
|
||||||
|
if (userIsAdmin) {
|
||||||
|
fallbackMenu.push({
|
||||||
to: "/dashboard/admin",
|
to: "/dashboard/admin",
|
||||||
icon: "mdi-shield-crown",
|
icon: "mdi-shield-crown",
|
||||||
title: "Admin Console",
|
title: "Admin Console",
|
||||||
},
|
});
|
||||||
];
|
}
|
||||||
|
|
||||||
|
return fallbackMenu;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Dashboard] Error computing menu:', error);
|
console.error('[Dashboard] Error computing menu:', error);
|
||||||
|
|
||||||
// Emergency fallback menu
|
// Emergency fallback menu - only essential items
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
to: "/dashboard/interest-list",
|
to: "/dashboard/interest-list",
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { requireAdmin } from '~/server/utils/auth';
|
import { requireAuth } from '~/server/utils/auth';
|
||||||
import { getNocoDbConfiguration } from '~/server/utils/nocodb';
|
import { getNocoDbConfiguration } from '~/server/utils/nocodb';
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
console.log('[ADMIN] Find duplicates request');
|
console.log('[DUPLICATES] Find duplicates request');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Require admin authentication
|
// Require authentication (any authenticated user with interest access)
|
||||||
await requireAdmin(event);
|
await requireAuth(event);
|
||||||
|
|
||||||
const query = getQuery(event);
|
const query = getQuery(event);
|
||||||
const threshold = query.threshold ? parseFloat(query.threshold as string) : 0.8;
|
const threshold = query.threshold ? parseFloat(query.threshold as string) : 0.8;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { requireAdmin } from '~/server/utils/auth';
|
import { requireAuth } from '~/server/utils/auth';
|
||||||
import { getNocoDbConfiguration, updateInterest, deleteInterest } from '~/server/utils/nocodb';
|
import { getNocoDbConfiguration, updateInterest, deleteInterest } from '~/server/utils/nocodb';
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
console.log('[ADMIN] Merge duplicates request');
|
console.log('[ADMIN] Merge duplicates request');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Require admin authentication
|
// Require authentication (any authenticated user with interest access)
|
||||||
await requireAdmin(event);
|
await requireAuth(event);
|
||||||
|
|
||||||
const body = await readBody(event);
|
const body = await readBody(event);
|
||||||
const { masterId, duplicateIds, mergeData } = body;
|
const { masterId, duplicateIds, mergeData } = body;
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export default defineEventHandler(async (event) => {
|
||||||
|
|
||||||
// Process expenses with currency conversion
|
// Process expenses with currency conversion
|
||||||
const processedExpenses = await Promise.all(
|
const processedExpenses = await Promise.all(
|
||||||
result.list.map(expense => processExpenseWithCurrency(expense))
|
result.list.map((expense: any) => processExpenseWithCurrency(expense))
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -68,7 +68,7 @@ export default defineEventHandler(async (event) => {
|
||||||
|
|
||||||
// Process expenses with currency conversion
|
// Process expenses with currency conversion
|
||||||
const processedExpenses = await Promise.all(
|
const processedExpenses = await Promise.all(
|
||||||
result.list.map(expense => processExpenseWithCurrency(expense))
|
result.list.map((expense: any) => processExpenseWithCurrency(expense))
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add formatted dates
|
// Add formatted dates
|
||||||
|
|
|
||||||
|
|
@ -838,31 +838,40 @@ export const updateBerth = async (id: string, data: Partial<Berth>): Promise<Ber
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Expense functions
|
// Expense functions with resilient HTTP handling
|
||||||
export const getExpenses = async (filters?: ExpenseFilters) => {
|
export const getExpenses = async (filters?: ExpenseFilters) => {
|
||||||
console.log('[nocodb.getExpenses] Fetching expenses from NocoDB...', filters);
|
console.log('[nocodb.getExpenses] Fetching expenses from NocoDB...', filters);
|
||||||
|
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const params: any = { limit: 1000 };
|
const params: any = { limit: 1000 };
|
||||||
|
|
||||||
// Build filter conditions
|
// Build filter conditions (fixed date logic)
|
||||||
if (filters?.startDate && filters?.endDate) {
|
if (filters?.startDate && filters?.endDate) {
|
||||||
params.where = `(Time,gte,${filters.startDate})~and(Time,lte,${filters.endDate})`;
|
// Ensure dates are in YYYY-MM-DD format
|
||||||
|
const startDate = filters.startDate.includes('T') ? filters.startDate.split('T')[0] : filters.startDate;
|
||||||
|
const endDate = filters.endDate.includes('T') ? filters.endDate.split('T')[0] : filters.endDate;
|
||||||
|
|
||||||
|
console.log('[nocodb.getExpenses] Date filter:', { startDate, endDate });
|
||||||
|
params.where = `(Time,gte,${startDate})~and(Time,lte,${endDate})`;
|
||||||
} else if (filters?.startDate) {
|
} else if (filters?.startDate) {
|
||||||
params.where = `(Time,gte,${filters.startDate})`;
|
const startDate = filters.startDate.includes('T') ? filters.startDate.split('T')[0] : filters.startDate;
|
||||||
|
params.where = `(Time,gte,${startDate})`;
|
||||||
} else if (filters?.endDate) {
|
} else if (filters?.endDate) {
|
||||||
params.where = `(Time,lte,${filters.endDate})`;
|
const endDate = filters.endDate.includes('T') ? filters.endDate.split('T')[0] : filters.endDate;
|
||||||
|
params.where = `(Time,lte,${endDate})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add payer filter
|
// Add payer filter
|
||||||
if (filters?.payer) {
|
if (filters?.payer) {
|
||||||
const payerFilter = `(Payer,eq,${filters.payer})`;
|
const payerFilter = `(Payer,eq,${encodeURIComponent(filters.payer)})`;
|
||||||
params.where = params.where ? `${params.where}~and${payerFilter}` : payerFilter;
|
params.where = params.where ? `${params.where}~and${payerFilter}` : payerFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add category filter
|
// Add category filter
|
||||||
if (filters?.category) {
|
if (filters?.category) {
|
||||||
const categoryFilter = `(Category,eq,${filters.category})`;
|
const categoryFilter = `(Category,eq,${encodeURIComponent(filters.category)})`;
|
||||||
params.where = params.where ? `${params.where}~and${categoryFilter}` : categoryFilter;
|
params.where = params.where ? `${params.where}~and${categoryFilter}` : categoryFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -870,29 +879,64 @@ export const getExpenses = async (filters?: ExpenseFilters) => {
|
||||||
params.sort = '-Time';
|
params.sort = '-Time';
|
||||||
|
|
||||||
console.log('[nocodb.getExpenses] Request params:', params);
|
console.log('[nocodb.getExpenses] Request params:', params);
|
||||||
|
console.log('[nocodb.getExpenses] Request URL:', createTableUrl(Table.Expense));
|
||||||
|
|
||||||
|
// Use regular $fetch with better error handling
|
||||||
const result = await $fetch<ExpensesResponse>(createTableUrl(Table.Expense), {
|
const result = await $fetch<ExpensesResponse>(createTableUrl(Table.Expense), {
|
||||||
headers: {
|
headers: {
|
||||||
"xc-token": getNocoDbConfiguration().token,
|
"xc-token": getNocoDbConfiguration().token,
|
||||||
|
"Content-Type": "application/json"
|
||||||
},
|
},
|
||||||
params
|
params
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('[nocodb.getExpenses] Successfully fetched expenses, count:', result.list?.length || 0);
|
console.log('[nocodb.getExpenses] Successfully fetched expenses, count:', result.list?.length || 0);
|
||||||
|
console.log('[nocodb.getExpenses] Request duration:', Date.now() - startTime, 'ms');
|
||||||
|
|
||||||
// Transform expenses to add computed price numbers
|
// Transform expenses to add computed price numbers
|
||||||
if (result.list && Array.isArray(result.list)) {
|
if (result.list && Array.isArray(result.list)) {
|
||||||
result.list = result.list.map(expense => ({
|
result.list = result.list.map(expense => ({
|
||||||
...expense,
|
...expense,
|
||||||
// Parse price string to number for calculations
|
// Parse price string to number for calculations
|
||||||
PriceNumber: parseFloat(expense.Price.replace(/[€$,]/g, '')) || 0
|
PriceNumber: parseFloat(expense.Price?.replace(/[€$,]/g, '') || '0') || 0
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('[nocodb.getExpenses] Error fetching expenses:', error);
|
console.error('[nocodb.getExpenses] =========================');
|
||||||
console.error('[nocodb.getExpenses] Error details:', error instanceof Error ? error.message : 'Unknown error');
|
console.error('[nocodb.getExpenses] EXPENSE FETCH FAILED');
|
||||||
|
console.error('[nocodb.getExpenses] Duration:', Date.now() - startTime, 'ms');
|
||||||
|
console.error('[nocodb.getExpenses] Error type:', error.constructor?.name || 'Unknown');
|
||||||
|
console.error('[nocodb.getExpenses] Error status:', error.statusCode || error.status || 'Unknown');
|
||||||
|
console.error('[nocodb.getExpenses] Error message:', error.message || 'Unknown error');
|
||||||
|
console.error('[nocodb.getExpenses] Error data:', error.data);
|
||||||
|
console.error('[nocodb.getExpenses] Full error:', JSON.stringify(error, null, 2));
|
||||||
|
console.error('[nocodb.getExpenses] =========================');
|
||||||
|
|
||||||
|
// Provide more specific error messages
|
||||||
|
if (error.statusCode === 401 || error.status === 401) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 401,
|
||||||
|
statusMessage: 'Authentication failed when accessing expense database. Please check your access permissions.'
|
||||||
|
});
|
||||||
|
} else if (error.statusCode === 403 || error.status === 403) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 403,
|
||||||
|
statusMessage: 'Access denied to expense database. This feature requires appropriate privileges.'
|
||||||
|
});
|
||||||
|
} else if (error.statusCode === 404 || error.status === 404) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 404,
|
||||||
|
statusMessage: 'Expense database table not found. Please contact your administrator.'
|
||||||
|
});
|
||||||
|
} else if (error.code === 'NETWORK_ERROR' || error.code === 'TIMEOUT') {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 503,
|
||||||
|
statusMessage: 'Expense database is temporarily unavailable. Please try again in a moment.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue