OHO-12(Partial Sorted)
This commit is contained in:
parent
594e8b6353
commit
dc64841256
5 changed files with 155 additions and 38 deletions
|
|
@ -30,11 +30,20 @@ const User = mongoose.model('User', userSchema);
|
|||
|
||||
const transactionSchema = new mongoose.Schema({
|
||||
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
|
||||
description: String,
|
||||
amount: Number,
|
||||
category: String,
|
||||
|
||||
description: { type: String, required: true },
|
||||
amount: { type: Number, required: true },
|
||||
category: { type: String, default: "Other" },
|
||||
date: { type: Date, default: Date.now },
|
||||
|
||||
type: { type: String, default: "unknown" },
|
||||
vendor: String, // Optional vendor/store name
|
||||
|
||||
source: { type: String, enum: ['manual', 'voice', 'email'], required: true },
|
||||
|
||||
referenceNumber: { type: String, unique: true, sparse: true }, // Unique transaction id for deduplication
|
||||
});
|
||||
|
||||
const Transaction = mongoose.model('Transaction', transactionSchema);
|
||||
|
||||
/* ---------- SESSION ---------- */
|
||||
|
|
@ -164,7 +173,8 @@ app.post('/api/transaction/add', async (req, res) => {
|
|||
userId,
|
||||
description,
|
||||
amount,
|
||||
category
|
||||
category,
|
||||
source: 'manual',
|
||||
});
|
||||
await newTransaction.save();
|
||||
|
||||
|
|
@ -230,6 +240,7 @@ app.post('/api/transactions/voice', async (req, res) => {
|
|||
description,
|
||||
amount,
|
||||
category,
|
||||
source: 'voice'
|
||||
});
|
||||
await newTransaction.save();
|
||||
res.status(200).json({
|
||||
|
|
@ -371,6 +382,14 @@ app.post('/api/sync-gmail', async (req, res) => {
|
|||
console.warn('⚠️ Parsed email missing amount, skipping:', parsed);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract unique transaction reference number from snippet
|
||||
let referenceNumber = null;
|
||||
const refMatch = email.snippet.match(/(?:reference number is|Ref\s*[:No]*\s*)([A-Za-z0-9]+)/i);
|
||||
if (refMatch) {
|
||||
referenceNumber = refMatch[1];
|
||||
}
|
||||
|
||||
let category = 'Other';
|
||||
try {
|
||||
const predictRes = await axios.post('http://10.231.8.114:5000/api/predict', {
|
||||
|
|
@ -387,33 +406,54 @@ app.post('/api/sync-gmail', async (req, res) => {
|
|||
}
|
||||
|
||||
const parseDateString = (dateStr) => {
|
||||
if (!dateStr) return null;
|
||||
const [day, month, yearSuffix] = dateStr.split('-');
|
||||
const year = 2000 + parseInt(yearSuffix, 10);
|
||||
return new Date(year, parseInt(month, 10) - 1, parseInt(day, 10));
|
||||
};
|
||||
|
||||
const dateValue = parseDateString(parsed.date) || new Date();
|
||||
|
||||
const newTxn = new Transaction({
|
||||
userId,
|
||||
description: parsed.vendor || "Unknown Vendor",
|
||||
amount: parsed.amount,
|
||||
type: parsed.type || "unknown",
|
||||
date: dateValue,
|
||||
vendor: parsed.vendor,
|
||||
category,
|
||||
source: "email",
|
||||
bank: email.from,
|
||||
});
|
||||
if (!dateStr) return null;
|
||||
const [day, month, yearSuffix] = dateStr.split('-');
|
||||
const year = 2000 + parseInt(yearSuffix, 10);
|
||||
return new Date(year, parseInt(month, 10) - 1, parseInt(day, 10));
|
||||
};
|
||||
|
||||
const dateValue = parseDateString(parsed.date) || new Date();
|
||||
|
||||
try {
|
||||
await newTxn.save();
|
||||
savedTxns.push(newTxn);
|
||||
if (referenceNumber) {
|
||||
// Upsert transaction based on unique referenceNumber for emails, mark source as 'email'
|
||||
const updatedTxn = await Transaction.findOneAndUpdate(
|
||||
{ referenceNumber }, // search criteria for deduplication
|
||||
{
|
||||
userId,
|
||||
description: parsed.vendor || "Unknown Vendor",
|
||||
amount: parsed.amount,
|
||||
type: parsed.type || "unknown",
|
||||
date: dateValue,
|
||||
vendor: parsed.vendor,
|
||||
category,
|
||||
source: "email", // mark source explicitly
|
||||
bank: email.from,
|
||||
referenceNumber,
|
||||
},
|
||||
{ upsert: true, new: true, setDefaultsOnInsert: true }
|
||||
);
|
||||
savedTxns.push(updatedTxn);
|
||||
} else {
|
||||
// No reference number available, just save normally; source still 'email'
|
||||
const newTxn = new Transaction({
|
||||
userId,
|
||||
description: parsed.vendor || "Unknown Vendor",
|
||||
amount: parsed.amount,
|
||||
type: parsed.type || "unknown",
|
||||
date: dateValue,
|
||||
vendor: parsed.vendor,
|
||||
category,
|
||||
source: "email", // mark source explicitly
|
||||
bank: email.from,
|
||||
});
|
||||
await newTxn.save();
|
||||
savedTxns.push(newTxn);
|
||||
}
|
||||
} catch (saveError) {
|
||||
console.error('❌ Error saving transaction to database:', saveError, 'Transaction:', newTxn);
|
||||
console.error('❌ Error saving transaction to database:', saveError);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return res.json({
|
||||
|
|
@ -421,14 +461,12 @@ const newTxn = new Transaction({
|
|||
count: savedTxns.length,
|
||||
transactions: savedTxns,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Unexpected error in /api/sync-gmail:', error);
|
||||
return res.status(500).json({ error: 'Failed to fetch and save Gmail messages' });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/* ---------- SERVER ---------- */
|
||||
mongoose.connect(process.env.MONGO_URI, {
|
||||
useNewUrlParser: true,
|
||||
|
|
|
|||
70
Backend/testing.js
Normal file
70
Backend/testing.js
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
const testEmails = [
|
||||
`Dear Customer,
|
||||
Rs.60.00 has been debited from account 5488 to VPA pavithramart.63434836@hdfcbank PAVITHRA MART on 24-07-25.
|
||||
Your UPI transaction reference number is 108642900372.
|
||||
If you did not authorize this transaction, please report it immediately by calling 18002586161 Or SMS BLOCK UPI to 7308080808.
|
||||
Warm Regards,
|
||||
HDFC Bank`,
|
||||
|
||||
`Rs.75.00 has been debited to VPA pavithramart.63434836@hdfcbank PAVITHRA MART on 01-08-25.
|
||||
Your UPI transaction reference number is 109128631655.`,
|
||||
|
||||
`Rs.299.00 credited to account 1234 from AmazonMarketplace on 19-08-25.
|
||||
UPI reference number is 123456789012.`,
|
||||
|
||||
`INR 150.00 debited at STARBUCKS on 20-08-25.
|
||||
Ref No: 9876543210`,
|
||||
];
|
||||
|
||||
function parseBankMessage(snippet) {
|
||||
const result = {
|
||||
amount: null,
|
||||
vendor: null,
|
||||
type: null,
|
||||
date: null,
|
||||
referenceNumber: null,
|
||||
};
|
||||
|
||||
// Amount
|
||||
const amtMatch = snippet.match(/(?:Rs\.?|INR)\s*([\d,]+\.?\d*)/i);
|
||||
if (amtMatch) result.amount = parseFloat(amtMatch[1].replace(/,/g, ""));
|
||||
|
||||
// Debit / Credit
|
||||
if (/debited/i.test(snippet)) result.type = "debit";
|
||||
else if (/credited/i.test(snippet)) result.type = "credit";
|
||||
|
||||
// Date
|
||||
const dateMatch = snippet.match(/on\s+(\d{2}[-/]\d{2}[-/]\d{2,4})/i);
|
||||
if (dateMatch) result.date = dateMatch[1];
|
||||
|
||||
// Reference Number (flexible regex)
|
||||
const refMatch = snippet.match(/(?:reference number is|UPI reference number is|Ref No:?)\s*([A-Za-z0-9]+)/i);
|
||||
if (refMatch) result.referenceNumber = refMatch[1].trim();
|
||||
|
||||
// Vendor extraction - remove emails, pick last uppercase phrase
|
||||
let vendorCandidate = snippet.replace(/[a-z0-9._%+-]+@[a-z0-9.-]+/gi, '').trim();
|
||||
|
||||
const vendorWords = vendorCandidate.match(/(?:\b[A-Z]{2,}\b(?:\s)?)+/g);
|
||||
if (vendorWords && vendorWords.length > 0) {
|
||||
result.vendor = vendorWords[vendorWords.length - 1].trim();
|
||||
} else {
|
||||
// Fallback: extract after "to " or "at "
|
||||
const toMatch = snippet.match(/to\s+([A-Za-z0-9\s&.-]+)/i);
|
||||
if (toMatch) {
|
||||
let v = toMatch[1].trim();
|
||||
v = v.replace(/[a-z0-9._%+-]+@[a-z0-9.-]+/gi, '').trim();
|
||||
v = v.replace(/\s{2,}/g, ' ');
|
||||
result.vendor = v;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
console.log("=== Bank Email Regex Test ===");
|
||||
testEmails.forEach((email, idx) => {
|
||||
console.log(`\n-- Email ${idx + 1} --`);
|
||||
console.log(email);
|
||||
const parsed = parseBankMessage(email);
|
||||
console.log("Parsed Result:", parsed);
|
||||
});
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:monarch/main_pages/HomePage/homepage.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:monarch/other_pages/enviroment.dart';
|
||||
|
|
@ -60,7 +61,10 @@ class _AutoSyncEmailsScreenState extends State<AutoSyncEmailsScreen> {
|
|||
|
||||
// Optional: navigate back or to another screen after a delay
|
||||
Future.delayed(const Duration(seconds: 2), () {
|
||||
Navigator.pop(context, true);
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const FinTrackHomePage()),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,10 @@ class _EmailsScreenState extends State<EmailsScreen> {
|
|||
final res = await http.post(
|
||||
uri,
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: jsonEncode({"accessToken": widget.accessToken}),
|
||||
body: jsonEncode({
|
||||
"accessToken": widget.accessToken,
|
||||
"userId": widget.userId, // Added userId here
|
||||
}),
|
||||
);
|
||||
|
||||
if (res.statusCode == 200) {
|
||||
|
|
|
|||
|
|
@ -61,10 +61,10 @@
|
|||
// debugPrint("✅ User logged in: ${data['user']}");
|
||||
// final prefs = await SharedPreferences.getInstance();
|
||||
// prefs.setString('userId', data['user']['_id']);
|
||||
// Navigator.pushReplacement(
|
||||
// context,
|
||||
// MaterialPageRoute(builder: (_) => const EmailsScreen(accessToken: '', userId: 'userId',)),
|
||||
// );
|
||||
// Navigator.pushReplacement(
|
||||
// context,
|
||||
// MaterialPageRoute(builder: (_) => const EmailsScreen(accessToken: '', userId: 'userId',)),
|
||||
// );
|
||||
// } else {
|
||||
// debugPrint("❌ Login failed: ${data['error']}");
|
||||
// }
|
||||
|
|
@ -116,6 +116,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:google_sign_in/google_sign_in.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:monarch/emai_screen.dart';
|
||||
import 'package:monarch/email.dart';
|
||||
//import 'package:monarch/email.dart';
|
||||
import 'package:monarch/other_pages/enviroment.dart';
|
||||
import 'dart:convert';
|
||||
|
|
@ -181,10 +182,11 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
// builder: (_) => EmailsScreen(
|
||||
// accessToken: accessToken,
|
||||
// userId: data['user']['_id'],
|
||||
// ),
|
||||
// builder:
|
||||
// (_) => EmailsScreen(
|
||||
// accessToken: accessToken,
|
||||
// userId: data['user']['_id'],
|
||||
// ),
|
||||
builder: (_) => AutoSyncEmailsScreen(accessToken: accessToken,userId: data['user']['_id'],),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue