Enhance duplicate detection with sales/admin access and field updates
- Extend duplicate detection access from admin-only to sales/admin users - Update field names for better clarity (Email → Email Address, etc.) - Add duplicate notification banner to expenses page - Improve authorization checks with role-based access control
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
import { requireAuth } from '~/server/utils/auth';
|
||||
import { requireAuth, requireSalesOrAdmin } from '~/server/utils/auth';
|
||||
import { getNocoDbConfiguration } from '~/server/utils/nocodb';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
console.log('[DUPLICATES] Find duplicates request');
|
||||
|
||||
try {
|
||||
// Require authentication (any authenticated user with interest access)
|
||||
await requireAuth(event);
|
||||
// Require sales or admin access for duplicate detection
|
||||
await requireSalesOrAdmin(event);
|
||||
|
||||
const query = getQuery(event);
|
||||
const threshold = query.threshold ? parseFloat(query.threshold as string) : 0.8;
|
||||
@@ -121,22 +121,22 @@ function calculateSimilarity(interest1: any, interest2: any) {
|
||||
const scores: Array<{ type: string; score: number; weight: number }> = [];
|
||||
|
||||
// Email similarity (highest weight)
|
||||
if (interest1.Email && interest2.Email) {
|
||||
const emailScore = interest1.Email.toLowerCase() === interest2.Email.toLowerCase() ? 1.0 : 0.0;
|
||||
if (interest1['Email Address'] && interest2['Email Address']) {
|
||||
const emailScore = interest1['Email Address'].toLowerCase() === interest2['Email Address'].toLowerCase() ? 1.0 : 0.0;
|
||||
scores.push({ type: 'email', score: emailScore, weight: 0.4 });
|
||||
}
|
||||
|
||||
// Phone similarity
|
||||
if (interest1.Phone && interest2.Phone) {
|
||||
const phone1 = normalizePhone(interest1.Phone);
|
||||
const phone2 = normalizePhone(interest2.Phone);
|
||||
if (interest1['Phone Number'] && interest2['Phone Number']) {
|
||||
const phone1 = normalizePhone(interest1['Phone Number']);
|
||||
const phone2 = normalizePhone(interest2['Phone Number']);
|
||||
const phoneScore = phone1 === phone2 ? 1.0 : 0.0;
|
||||
scores.push({ type: 'phone', score: phoneScore, weight: 0.3 });
|
||||
}
|
||||
|
||||
// Name similarity
|
||||
if (interest1.Name && interest2.Name) {
|
||||
const nameScore = calculateNameSimilarity(interest1.Name, interest2.Name);
|
||||
if (interest1['Full Name'] && interest2['Full Name']) {
|
||||
const nameScore = calculateNameSimilarity(interest1['Full Name'], interest2['Full Name']);
|
||||
scores.push({ type: 'name', score: nameScore, weight: 0.2 });
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ function selectMasterCandidate(interests: any[]) {
|
||||
* Calculate completeness score for an interest record
|
||||
*/
|
||||
function calculateCompletenessScore(interest: any): number {
|
||||
const fields = ['Name', 'Email', 'Phone', 'Address', 'Comments', 'BerthRequirements'];
|
||||
const fields = ['Full Name', 'Email Address', 'Phone Number', 'Address', 'Extra Comments', 'Berth Size Desired'];
|
||||
const filledFields = fields.filter(field =>
|
||||
interest[field] && interest[field].toString().trim().length > 0
|
||||
);
|
||||
@@ -245,8 +245,8 @@ function calculateCompletenessScore(interest: any): number {
|
||||
let score = filledFields.length / fields.length;
|
||||
|
||||
// Bonus for recent creation
|
||||
if (interest.CreatedAt) {
|
||||
const created = new Date(interest.CreatedAt);
|
||||
if (interest['Created At']) {
|
||||
const created = new Date(interest['Created At']);
|
||||
const now = new Date();
|
||||
const daysOld = (now.getTime() - created.getTime()) / (1000 * 60 * 60 * 24);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user