Multi-layer duplicate prevention system: - Enhanced frontend duplicate detection with tracking number, amount, and date comparison - Added MongoDB compound index to prevent database-level duplicates - Improved backend error handling for duplicate key violations - Added cleanup endpoint to remove existing duplicates - Enhanced user feedback for import operations Frontend changes: - Stricter duplicate detection comparing tracking number, vendor, amount, and date - Better error handling and user feedback for duplicate scenarios - Added 'Clean Duplicates' button to remove existing duplicates Backend changes: - Database compound index on reference, vendor, userId, amount, date - Enhanced error responses with duplicate detection flags - New POST /expenses/cleanup-duplicates endpoint - Improved duplicate key error handling This should eliminate the double Australia Post expense entries.
53 lines
1.4 KiB
TypeScript
53 lines
1.4 KiB
TypeScript
import mongoose, { Document, Schema } from 'mongoose';
|
|
|
|
export interface IExpense extends Document {
|
|
category: string;
|
|
description: string;
|
|
amount: number;
|
|
date: Date;
|
|
taxDeductible: boolean;
|
|
vendor?: string;
|
|
reference?: string;
|
|
receiptUrl?: string;
|
|
notes?: string;
|
|
userId: mongoose.Types.ObjectId;
|
|
dateCreated: Date;
|
|
dateUpdated: Date;
|
|
}
|
|
|
|
const ExpenseSchema: Schema = new Schema({
|
|
category: { type: String, required: true },
|
|
description: { type: String, required: true, trim: true },
|
|
amount: { type: Number, required: true, min: 0 },
|
|
date: { type: Date, required: true },
|
|
taxDeductible: { type: Boolean, default: false },
|
|
vendor: { type: String },
|
|
reference: { type: String },
|
|
receiptUrl: { type: String },
|
|
notes: { type: String },
|
|
userId: { type: Schema.Types.ObjectId, ref: 'User', required: true, index: true },
|
|
dateCreated: { type: Date, default: Date.now },
|
|
dateUpdated: { type: Date, default: Date.now },
|
|
});
|
|
|
|
ExpenseSchema.pre('save', function (next) {
|
|
this.dateUpdated = new Date();
|
|
next();
|
|
});
|
|
|
|
// Create compound index to prevent duplicate expenses with same reference, vendor, and user
|
|
ExpenseSchema.index({
|
|
reference: 1,
|
|
vendor: 1,
|
|
userId: 1,
|
|
amount: 1,
|
|
date: 1
|
|
}, {
|
|
unique: true,
|
|
partialFilterExpression: {
|
|
reference: { $exists: true, $ne: null },
|
|
vendor: { $exists: true, $ne: null }
|
|
}
|
|
});
|
|
|
|
export default mongoose.model<IExpense>('Expense', ExpenseSchema);
|