Added source column
This commit is contained in:
+7
-4
@@ -29,23 +29,25 @@ Base = declarative_base()
|
|||||||
def create_db_tables():
|
def create_db_tables():
|
||||||
"""Create database tables safely with error handling"""
|
"""Create database tables safely with error handling"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Check if tables already exist to avoid unnecessary DDL operations
|
# Check if tables already exist to avoid unnecessary DDL operations
|
||||||
from sqlalchemy import inspect
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
inspector = inspect(engine)
|
inspector = inspect(engine)
|
||||||
existing_tables = inspector.get_table_names()
|
existing_tables = inspector.get_table_names()
|
||||||
|
|
||||||
if existing_tables:
|
if existing_tables:
|
||||||
logger.info(f"Database tables already exist: {existing_tables}")
|
logger.info(f"Database tables already exist: {existing_tables}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Create tables with timeout protection
|
# Create tables with timeout protection
|
||||||
logger.info("Creating database tables...")
|
logger.info("Creating database tables...")
|
||||||
Base.metadata.create_all(bind=engine, checkfirst=True)
|
Base.metadata.create_all(bind=engine, checkfirst=True)
|
||||||
logger.info("Database tables created successfully")
|
logger.info("Database tables created successfully")
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
logger.warning("Database creation interrupted by user")
|
logger.warning("Database creation interrupted by user")
|
||||||
raise
|
raise
|
||||||
@@ -81,6 +83,7 @@ class DBTransaction(Base):
|
|||||||
tax_amount = Column(Float, nullable=True)
|
tax_amount = Column(Float, nullable=True)
|
||||||
categorisation_id = Column(String, nullable=True)
|
categorisation_id = Column(String, nullable=True)
|
||||||
user_id = Column(String, nullable=True)
|
user_id = Column(String, nullable=True)
|
||||||
|
source = Column(String, nullable=True) # e.g., "csv", "image", "manual", "api"
|
||||||
|
|
||||||
# Additional QuickBooks CSV columns
|
# Additional QuickBooks CSV columns
|
||||||
TxnId = Column(String, nullable=True)
|
TxnId = Column(String, nullable=True)
|
||||||
|
|||||||
+25
-10
@@ -1,7 +1,6 @@
|
|||||||
import csv
|
import csv
|
||||||
import io
|
import io
|
||||||
import logging
|
import logging
|
||||||
import uuid
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
@@ -147,6 +146,7 @@ async def import_transactions_csv(
|
|||||||
vendor_name = row.get("VendorName", "").strip()
|
vendor_name = row.get("VendorName", "").strip()
|
||||||
account_id = row.get("AccountId", "").strip()
|
account_id = row.get("AccountId", "").strip()
|
||||||
account_name = row.get("AccountName", "").strip()
|
account_name = row.get("AccountName", "").strip()
|
||||||
|
source = row.get("Source", "").strip()
|
||||||
|
|
||||||
# Compose internal ID
|
# Compose internal ID
|
||||||
internal_txn_id = (
|
internal_txn_id = (
|
||||||
@@ -189,6 +189,7 @@ async def import_transactions_csv(
|
|||||||
description=memo,
|
description=memo,
|
||||||
categorisation_id=categorization_id,
|
categorisation_id=categorization_id,
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
|
source=source, # Source of this transaction
|
||||||
# QuickBooks CSV columns
|
# QuickBooks CSV columns
|
||||||
TxnId=txn_id,
|
TxnId=txn_id,
|
||||||
AccountType=account_type,
|
AccountType=account_type,
|
||||||
@@ -229,6 +230,7 @@ async def import_transactions_csv(
|
|||||||
"VendorName": vendor_name,
|
"VendorName": vendor_name,
|
||||||
"AccountId": account_id,
|
"AccountId": account_id,
|
||||||
"AccountName": account_name,
|
"AccountName": account_name,
|
||||||
|
"source": source,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -352,7 +354,7 @@ async def import_transactions_from_image(
|
|||||||
tags=["Document Processing"],
|
tags=["Document Processing"],
|
||||||
)
|
)
|
||||||
async def upload_multiple_documents(
|
async def upload_multiple_documents(
|
||||||
files: List[UploadFile] = File(...), db: db_dependency = None
|
files: List[UploadFile] = File(...), db: db_dependency = None
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Upload multiple receipt images for processing.
|
Upload multiple receipt images for processing.
|
||||||
@@ -360,7 +362,7 @@ async def upload_multiple_documents(
|
|||||||
This endpoint accepts multiple image files and returns file IDs
|
This endpoint accepts multiple image files and returns file IDs
|
||||||
that can be used with the /process/{file_id} endpoint.
|
that can be used with the /process/{file_id} endpoint.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
responses = []
|
responses = []
|
||||||
|
|
||||||
@@ -375,18 +377,22 @@ async def upload_multiple_documents(
|
|||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
detail=f"Unsupported file type for {file.filename}. Allowed: {allowed_types}",
|
detail=f"Unsupported file type for {file.filename}. Allowed: {allowed_types}",
|
||||||
)
|
)
|
||||||
logger.info(f"filename: {file_name}, extension: {file_extension}, google_file_id: {google_file_id}")
|
logger.info(
|
||||||
|
f"filename: {file_name}, extension: {file_extension}, google_file_id: {google_file_id}"
|
||||||
|
)
|
||||||
|
|
||||||
# Generate unique file ID
|
# Generate unique file ID
|
||||||
file_id = google_file_id # Using Google Drive file ID as file_id
|
file_id = google_file_id # Using Google Drive file ID as file_id
|
||||||
|
|
||||||
# Check if file already exists in database
|
# Check if file already exists in database
|
||||||
existing_file = get_uploaded_file_from_db(db, file_id)
|
existing_file = get_uploaded_file_from_db(db, file_id)
|
||||||
|
|
||||||
if existing_file:
|
if existing_file:
|
||||||
# File already exists, return existing record
|
# File already exists, return existing record
|
||||||
logger.info(f"File {file_name} with ID {file_id} already exists, returning existing record")
|
logger.info(
|
||||||
|
f"File {file_name} with ID {file_id} already exists, returning existing record"
|
||||||
|
)
|
||||||
responses.append(
|
responses.append(
|
||||||
DocumentUploadResponse(
|
DocumentUploadResponse(
|
||||||
file_id=existing_file.file_id,
|
file_id=existing_file.file_id,
|
||||||
@@ -475,10 +481,12 @@ async def process_document(
|
|||||||
|
|
||||||
# Check if receipt already exists for this file_id
|
# Check if receipt already exists for this file_id
|
||||||
existing_receipt = get_receipt_from_db(db, file_id)
|
existing_receipt = get_receipt_from_db(db, file_id)
|
||||||
|
|
||||||
if existing_receipt:
|
if existing_receipt:
|
||||||
# Receipt already processed, return existing data
|
# Receipt already processed, return existing data
|
||||||
logger.info(f"Receipt for file {file_id} already exists, returning existing record")
|
logger.info(
|
||||||
|
f"Receipt for file {file_id} already exists, returning existing record"
|
||||||
|
)
|
||||||
return DocumentProcessResponse(
|
return DocumentProcessResponse(
|
||||||
file_id=file_id,
|
file_id=file_id,
|
||||||
receipt_id=existing_receipt.receipt_id,
|
receipt_id=existing_receipt.receipt_id,
|
||||||
@@ -494,7 +502,9 @@ async def process_document(
|
|||||||
receipt_currency=existing_receipt.receipt_currency,
|
receipt_currency=existing_receipt.receipt_currency,
|
||||||
receipt_location=existing_receipt.receipt_location,
|
receipt_location=existing_receipt.receipt_location,
|
||||||
calculated_tax=existing_receipt.calculated_tax,
|
calculated_tax=existing_receipt.calculated_tax,
|
||||||
is_depreciable=existing_receipt.is_depreciable == "True" if existing_receipt.is_depreciable else None,
|
is_depreciable=existing_receipt.is_depreciable == "True"
|
||||||
|
if existing_receipt.is_depreciable
|
||||||
|
else None,
|
||||||
name_of_asset=existing_receipt.name_of_asset,
|
name_of_asset=existing_receipt.name_of_asset,
|
||||||
cca_rate=existing_receipt.cca_rate,
|
cca_rate=existing_receipt.cca_rate,
|
||||||
useful_life=existing_receipt.useful_life,
|
useful_life=existing_receipt.useful_life,
|
||||||
@@ -614,6 +624,7 @@ async def match_specific_receipts(request: MatchSpecificRequest, db: db_dependen
|
|||||||
amount=db_txn.amount,
|
amount=db_txn.amount,
|
||||||
vendor=db_txn.vendor,
|
vendor=db_txn.vendor,
|
||||||
notes=db_txn.description or "",
|
notes=db_txn.description or "",
|
||||||
|
source=db_txn.source,
|
||||||
# QuickBooks CSV fields
|
# QuickBooks CSV fields
|
||||||
TxnId=db_txn.TxnId,
|
TxnId=db_txn.TxnId,
|
||||||
AccountType=db_txn.AccountType,
|
AccountType=db_txn.AccountType,
|
||||||
@@ -752,6 +763,10 @@ async def match_specific_receipts(request: MatchSpecificRequest, db: db_dependen
|
|||||||
tax_analysis=result.tax_analysis,
|
tax_analysis=result.tax_analysis,
|
||||||
flag_for_review=flag_for_review,
|
flag_for_review=flag_for_review,
|
||||||
auto_approve=auto_approve,
|
auto_approve=auto_approve,
|
||||||
|
# Transaction metadata
|
||||||
|
transaction_source=result.transaction.source
|
||||||
|
if result.transaction
|
||||||
|
else None,
|
||||||
# QuickBooks CSV fields
|
# QuickBooks CSV fields
|
||||||
TxnId=result.transaction.TxnId if result.transaction else None,
|
TxnId=result.transaction.TxnId if result.transaction else None,
|
||||||
AccountType=result.transaction.AccountType
|
AccountType=result.transaction.AccountType
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ class Transaction:
|
|||||||
# Tax rule fields
|
# Tax rule fields
|
||||||
currency: str = "CAD"
|
currency: str = "CAD"
|
||||||
fx_rate: Optional[float] = None
|
fx_rate: Optional[float] = None
|
||||||
|
source: Optional[str] = None # e.g., "csv", "image", "manual", "api"
|
||||||
|
|
||||||
# QuickBooks CSV fields
|
# QuickBooks CSV fields
|
||||||
TxnId: Optional[str] = None
|
TxnId: Optional[str] = None
|
||||||
@@ -115,6 +116,7 @@ class TransactionRequest(BaseModel):
|
|||||||
# Tax rule fields
|
# Tax rule fields
|
||||||
currency: str = "CAD"
|
currency: str = "CAD"
|
||||||
fx_rate: Optional[float] = None
|
fx_rate: Optional[float] = None
|
||||||
|
source: Optional[str] = None # e.g., "csv", "image", "manual", "api"
|
||||||
|
|
||||||
# QuickBooks CSV fields
|
# QuickBooks CSV fields
|
||||||
TxnId: Optional[str] = None
|
TxnId: Optional[str] = None
|
||||||
@@ -163,6 +165,9 @@ class MatchResponse(BaseModel):
|
|||||||
flag_for_review: Optional[bool] = None
|
flag_for_review: Optional[bool] = None
|
||||||
auto_approve: Optional[bool] = None
|
auto_approve: Optional[bool] = None
|
||||||
|
|
||||||
|
# Transaction metadata
|
||||||
|
transaction_source: Optional[str] = None # Source of the transaction
|
||||||
|
|
||||||
# QuickBooks CSV fields from transaction
|
# QuickBooks CSV fields from transaction
|
||||||
TxnId: Optional[str] = None
|
TxnId: Optional[str] = None
|
||||||
AccountType: Optional[str] = None
|
AccountType: Optional[str] = None
|
||||||
|
|||||||
Reference in New Issue
Block a user