From dc64841256fc47a9ff0033c037b0035b8df1986f Mon Sep 17 00:00:00 2001 From: lokeshramchand-ctrl Date: Wed, 20 Aug 2025 10:37:44 +0530 Subject: [PATCH] OHO-12(Partial Sorted) --- Backend/index.js | 94 ++++++++++++++++++++++++----------- Backend/testing.js | 70 ++++++++++++++++++++++++++ Frontend/lib/emai_screen.dart | 6 ++- Frontend/lib/email.dart | 5 +- Frontend/lib/login.dart | 18 ++++--- 5 files changed, 155 insertions(+), 38 deletions(-) create mode 100644 Backend/testing.js diff --git a/Backend/index.js b/Backend/index.js index dca7f2d..b10da02 100644 --- a/Backend/index.js +++ b/Backend/index.js @@ -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, diff --git a/Backend/testing.js b/Backend/testing.js new file mode 100644 index 0000000..1430814 --- /dev/null +++ b/Backend/testing.js @@ -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); +}); diff --git a/Frontend/lib/emai_screen.dart b/Frontend/lib/emai_screen.dart index dcf6960..f7246bd 100644 --- a/Frontend/lib/emai_screen.dart +++ b/Frontend/lib/emai_screen.dart @@ -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 { // 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(() { diff --git a/Frontend/lib/email.dart b/Frontend/lib/email.dart index aec6c60..cae6f60 100644 --- a/Frontend/lib/email.dart +++ b/Frontend/lib/email.dart @@ -36,7 +36,10 @@ class _EmailsScreenState extends State { 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) { diff --git a/Frontend/lib/login.dart b/Frontend/lib/login.dart index 00e389a..bf1130f 100644 --- a/Frontend/lib/login.dart +++ b/Frontend/lib/login.dart @@ -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 { 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'],), ), );