-
Notifications
You must be signed in to change notification settings - Fork 74
added editing for ocr extracted receipt Closes #11 #117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
6d6f59c
3c2693b
202cb04
d56bee1
797fd4b
e8a018f
03eb368
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -75,19 +75,6 @@ const uploadReceipt = async (req, res) => { | |||||
|
|
||||||
| const savedReceipt = await newReceipt.save(); | ||||||
|
|
||||||
| // Automatically create a corresponding expense transaction | ||||||
| if (savedReceipt) { | ||||||
| const newTransaction = new IncomeExpense({ | ||||||
| user: req.user.id, | ||||||
| name: savedReceipt.extractedData.merchant, | ||||||
| category: savedReceipt.extractedData.category, | ||||||
| cost: savedReceipt.extractedData.amount, | ||||||
| addedOn: savedReceipt.extractedData.date, | ||||||
| isIncome: false, | ||||||
| }); | ||||||
| await newTransaction.save(); | ||||||
| } | ||||||
|
|
||||||
| res.status(201).json(savedReceipt); | ||||||
| } catch (error) { | ||||||
| console.error("Error with Gemini API:", error); | ||||||
|
|
@@ -103,6 +90,59 @@ const uploadReceipt = async (req, res) => { | |||||
| } | ||||||
| }; | ||||||
|
|
||||||
| // @desc Save transaction after user confirmation and edits | ||||||
| // @route POST /api/receipts/save-transaction | ||||||
| // @access Private | ||||||
| const saveTransactionFromReceipt = async (req, res) => { | ||||||
| try { | ||||||
| const { receiptId, transactionData } = req.body; | ||||||
|
|
||||||
| // Validate required fields | ||||||
| if (!receiptId || !transactionData) { | ||||||
| return res.status(400).json({ message: 'Receipt ID and transaction data are required' }); | ||||||
| } | ||||||
|
|
||||||
| // Verify the receipt belongs to the user | ||||||
| const receipt = await Receipt.findOne({ _id: receiptId, user: req.user.id }); | ||||||
| if (!receipt) { | ||||||
| return res.status(404).json({ message: 'Receipt not found' }); | ||||||
| } | ||||||
|
|
||||||
| // Create the transaction with user-confirmed data | ||||||
| const newTransaction = new IncomeExpense({ | ||||||
| user: req.user.id, | ||||||
| name: transactionData.name, | ||||||
| category: transactionData.category, | ||||||
| cost: transactionData.cost, | ||||||
| addedOn: new Date(transactionData.addedOn), | ||||||
| isIncome: transactionData.isIncome || false, | ||||||
| }); | ||||||
|
|
||||||
| const savedTransaction = await newTransaction.save(); | ||||||
|
|
||||||
| // Update the receipt with the final confirmed data | ||||||
| receipt.extractedData = { | ||||||
| merchant: transactionData.name, | ||||||
| amount: transactionData.cost, | ||||||
| category: transactionData.category, | ||||||
| date: new Date(transactionData.addedOn), | ||||||
| isIncome: transactionData.isIncome || false, | ||||||
| }; | ||||||
| await receipt.save(); | ||||||
|
|
||||||
| res.status(201).json({ | ||||||
| message: 'Transaction saved successfully', | ||||||
| transaction: savedTransaction, | ||||||
| receipt: receipt | ||||||
|
||||||
| receipt: receipt | |
| receipt |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,10 @@ | ||
| const express = require('express'); | ||
| const router = express.Router(); | ||
| const { uploadReceipt } = require('../controllers/receiptController'); | ||
| const { uploadReceipt, saveTransactionFromReceipt } = require('../controllers/receiptController'); | ||
| const { protect } = require('../middleware/authMiddleware'); | ||
| const upload = require('../middleware/uploadMiddleware'); | ||
|
|
||
| router.post('/upload', protect, upload, uploadReceipt); | ||
| router.post('/save-transaction', protect, saveTransactionFromReceipt); | ||
|
|
||
| module.exports = router; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,7 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import React, { useState } from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import React, { useState, useEffect } from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useNavigate } from 'react-router-dom'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import api from '../api/axios'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import TransactionModal from '../components/TransactionModal'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const ReceiptsPage = () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [file, setFile] = useState(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -9,6 +10,25 @@ const ReceiptsPage = () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [error, setError] = useState(''); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const navigate = useNavigate(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [openEditReceiptResult, setOpenEditReceiptResult] = useState(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [categories, setCategories] = useState([]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [isEditingResult, setIsEditingResult] = useState(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [isSaving, setIsSaving] = useState(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Fetch categories when component mounts | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const fetchCategories = async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await api.get('/transactions/categories'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setCategories(response.data); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error('Failed to fetch categories:', error); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fetchCategories(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, []); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleFileChange = (e) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setFile(e.target.files[0]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setReceiptResult(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -34,8 +54,9 @@ const ReceiptsPage = () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setReceiptResult(response.data); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alert('Receipt processed successfully and transaction created! Redirecting to dashboard...'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| navigate('/dashboard'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Open the modal to allow user to edit the extracted data | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setOpenEditReceiptResult(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setError('Upload failed. Please try again.'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error(err); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -44,6 +65,66 @@ const ReceiptsPage = () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleEditReceiptSubmit = async (formData) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Update the receiptResult with the edited data | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const updatedReceiptResult = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...receiptResult, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| extractedData: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| merchant: formData.name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| amount: parseFloat(formData.cost), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| category: formData.category, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| date: formData.addedOn, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isIncome: formData.isIncome | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setReceiptResult(updatedReceiptResult); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setOpenEditReceiptResult(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setError('Failed to update receipt data. Please try again.'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error(err); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleEditReceiptSubmit = async (formData) => { | |
| try { | |
| // Update the receiptResult with the edited data | |
| const updatedReceiptResult = { | |
| ...receiptResult, | |
| extractedData: { | |
| merchant: formData.name, | |
| amount: parseFloat(formData.cost), | |
| category: formData.category, | |
| date: formData.addedOn, | |
| isIncome: formData.isIncome | |
| } | |
| }; | |
| setReceiptResult(updatedReceiptResult); | |
| setOpenEditReceiptResult(false); | |
| } catch (err) { | |
| setError('Failed to update receipt data. Please try again.'); | |
| console.error(err); | |
| } | |
| const handleEditReceiptSubmit = (formData) => { | |
| // Update the receiptResult with the edited data | |
| const updatedReceiptResult = { | |
| ...receiptResult, | |
| extractedData: { | |
| merchant: formData.name, | |
| amount: parseFloat(formData.cost), | |
| category: formData.category, | |
| date: formData.addedOn, | |
| isIncome: formData.isIncome | |
| } | |
| }; | |
| setReceiptResult(updatedReceiptResult); | |
| setOpenEditReceiptResult(false); |
Copilot
AI
Oct 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential runtime error if receiptResult.extractedData.amount is null or undefined. Since the amount comes from user-edited data and parseFloat can return NaN, consider adding a fallback: {(receiptResult.extractedData.amount || 0).toFixed(2)}
| <p className="text-gray-700 dark:text-gray-300"><strong>Amount:</strong> {receiptResult.extractedData.amount.toFixed(2)}</p> | |
| <p className="text-gray-700 dark:text-gray-300"><strong>Amount:</strong> {(parseFloat(receiptResult.extractedData.amount) || 0).toFixed(2)}</p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If
transactionData.addedOnis an invalid date string,new Date()will create an Invalid Date object, which could cause issues downstream. Add validation to ensure the date is valid before creating the transaction.