From 7296d0931970d07a750a503ea238fbcaf717bae6 Mon Sep 17 00:00:00 2001 From: bolade Date: Thu, 23 Oct 2025 19:37:50 +0100 Subject: [PATCH] Added quickbooks data --- app/database.py | 14 +++++ app/main.py | 138 +++++++++++++++++++++++++++++++++++++++--------- app/schemas.py | 42 +++++++++++++++ 3 files changed, 170 insertions(+), 24 deletions(-) diff --git a/app/database.py b/app/database.py index 9343a2f..82bb52b 100644 --- a/app/database.py +++ b/app/database.py @@ -82,6 +82,20 @@ class DBTransaction(Base): categorisation_id = Column(String, nullable=True) user_id = Column(String, nullable=True) + # Additional QuickBooks CSV columns + TxnId = Column(String, nullable=True) + AccountType = Column(String, nullable=True) + AccountNumber = Column(String, nullable=True) + TransactionDate = Column(String, nullable=True) + TransactionType = Column(String, nullable=True) + ChequeNumber = Column(String, nullable=True) + Description1 = Column(String, nullable=True) + Description2 = Column(String, nullable=True) + VendorId = Column(String, nullable=True) + VendorName = Column(String, nullable=True) + AccountId = Column(String, nullable=True) + AccountName = Column(String, nullable=True) + # Uploaded Files table class DBUploadedFile(Base): diff --git a/app/main.py b/app/main.py index 733ff69..a983783 100644 --- a/app/main.py +++ b/app/main.py @@ -133,46 +133,75 @@ async def import_transactions_csv( errors = [] for idx, row in enumerate(reader): try: - # Use correct headers and strip whitespace - account_number = row.get("Account Number") or row.get( - "Account Number ".strip() + # Extract all CSV columns with proper header handling + txn_id = row.get("TxnId", "").strip() + account_type = row.get("Account Type", "").strip() + account_number = row.get("Account Number", "").strip() + transaction_date = row.get("Transaction Date", "").strip() + transaction_type = row.get("Transaction Type", "").strip() + cheque_number = row.get("Cheque Number", "").strip() + description_1 = row.get("Description 1", "").strip() + description_2 = row.get("Description 2", "").strip() + amount_raw = row.get("Amount", "").strip() + vendor_id = row.get("VendorId", "").strip() + vendor_name = row.get("VendorName", "").strip() + account_id = row.get("AccountId", "").strip() + account_name = row.get("AccountName", "").strip() + + # Compose internal ID + internal_txn_id = ( + f"{account_number}_{idx + 1}" + if account_number + else f"txn_{idx + 1}" ) - txn_date_raw = row.get("Transaction Date") or row.get( - "Transaction Date ".strip() or row.get("Date") - ) - amount_raw = row.get("Amount") or row.get("Amount ".strip()) - payee_name = row.get("Description 2") or row.get( - "Description 2 ".strip() - ) - memo = f"{row.get('Account Type', '').strip()} {row.get('Cheque Number', '').strip()} {row.get('Description 1', '').strip()}".strip() - # Compose ID - txn_id = f"{account_number}_{idx + 1}" + # Parse date (try multiple formats) - txn_date_str = txn_date_raw.strip() txn_date = None - for fmt in ("%m/%d/%y", "%m/%d/%Y"): + for fmt in ("%m/%d/%y", "%m/%d/%Y", "%Y-%m-%d"): try: - txn_date = datetime.strptime(txn_date_str, fmt).strftime( + txn_date = datetime.strptime(transaction_date, fmt).strftime( "%Y-%m-%d" ) break except Exception: continue if not txn_date: - raise ValueError(f"Could not parse date: {txn_date_str}") - # Parse amount - amount = float(amount_raw.replace(",", "").strip()) + raise ValueError(f"Could not parse date: {transaction_date}") - # Create database transaction object + # Parse amount + amount = ( + float(amount_raw.replace(",", "").strip()) if amount_raw else 0.0 + ) + + # Use vendor_name (Description 2) as the vendor, fallback to description_2 + vendor = vendor_name if vendor_name else description_2 + + # Compose description/memo from multiple fields + memo = f"{account_type} {cheque_number} {description_1}".strip() + + # Create database transaction object with all QuickBooks fields txn_date_obj = datetime.strptime(txn_date, "%Y-%m-%d") db_transaction = DBTransaction( - transaction_id=txn_id, + transaction_id=internal_txn_id, amount=amount, date=txn_date_obj, - vendor=payee_name.strip(), + vendor=vendor, description=memo, categorisation_id=categorization_id, user_id=user_id, + # QuickBooks CSV columns + TxnId=txn_id, + AccountType=account_type, + AccountNumber=account_number, + TransactionDate=transaction_date, + TransactionType=transaction_type, + ChequeNumber=cheque_number, + Description1=description_1, + Description2=description_2, + VendorId=vendor_id, + VendorName=vendor_name, + AccountId=account_id, + AccountName=account_name, ) # Add to database @@ -180,13 +209,26 @@ async def import_transactions_csv( transactions.append( { - "id": txn_id, + "id": internal_txn_id, "txn_date": txn_date, "amount": amount, - "payee_name": payee_name.strip(), + "payee_name": vendor, "memo": memo, "categorization_id": categorization_id, "user_id": user_id, + # Include QuickBooks fields in response + "TxnId": txn_id, + "AccountType": account_type, + "AccountNumber": account_number, + "TransactionDate": transaction_date, + "TransactionType": transaction_type, + "ChequeNumber": cheque_number, + "Description1": description_1, + "Description2": description_2, + "VendorId": vendor_id, + "VendorName": vendor_name, + "AccountId": account_id, + "AccountName": account_name, } ) except Exception as e: @@ -572,6 +614,19 @@ async def match_specific_receipts(request: MatchSpecificRequest, db: db_dependen amount=db_txn.amount, vendor=db_txn.vendor, notes=db_txn.description or "", + # QuickBooks CSV fields + TxnId=db_txn.TxnId, + AccountType=db_txn.AccountType, + AccountNumber=db_txn.AccountNumber, + TransactionDate=db_txn.TransactionDate, + TransactionType=db_txn.TransactionType, + ChequeNumber=db_txn.ChequeNumber, + Description1=db_txn.Description1, + Description2=db_txn.Description2, + VendorId=db_txn.VendorId, + VendorName=db_txn.VendorName, + AccountId=db_txn.AccountId, + AccountName=db_txn.AccountName, ) transactions.append(transaction) except Exception as e: @@ -697,6 +752,41 @@ async def match_specific_receipts(request: MatchSpecificRequest, db: db_dependen tax_analysis=result.tax_analysis, flag_for_review=flag_for_review, auto_approve=auto_approve, + # QuickBooks CSV fields + TxnId=result.transaction.TxnId if result.transaction else None, + AccountType=result.transaction.AccountType + if result.transaction + else None, + AccountNumber=result.transaction.AccountNumber + if result.transaction + else None, + TransactionDate=result.transaction.TransactionDate + if result.transaction + else None, + TransactionType=result.transaction.TransactionType + if result.transaction + else None, + ChequeNumber=result.transaction.ChequeNumber + if result.transaction + else None, + Description1=result.transaction.Description1 + if result.transaction + else None, + Description2=result.transaction.Description2 + if result.transaction + else None, + VendorId=result.transaction.VendorId + if result.transaction + else None, + VendorName=result.transaction.VendorName + if result.transaction + else None, + AccountId=result.transaction.AccountId + if result.transaction + else None, + AccountName=result.transaction.AccountName + if result.transaction + else None, ) match_responses.append(match_response) diff --git a/app/schemas.py b/app/schemas.py index 488ef4b..42a058d 100644 --- a/app/schemas.py +++ b/app/schemas.py @@ -44,6 +44,20 @@ class Transaction: currency: str = "CAD" fx_rate: Optional[float] = None + # QuickBooks CSV fields + TxnId: Optional[str] = None + AccountType: Optional[str] = None + AccountNumber: Optional[str] = None + TransactionDate: Optional[str] = None + TransactionType: Optional[str] = None + ChequeNumber: Optional[str] = None + Description1: Optional[str] = None + Description2: Optional[str] = None + VendorId: Optional[str] = None + VendorName: Optional[str] = None + AccountId: Optional[str] = None + AccountName: Optional[str] = None + @dataclass class Asset: @@ -102,6 +116,20 @@ class TransactionRequest(BaseModel): currency: str = "CAD" fx_rate: Optional[float] = None + # QuickBooks CSV fields + TxnId: Optional[str] = None + AccountType: Optional[str] = None + AccountNumber: Optional[str] = None + TransactionDate: Optional[str] = None + TransactionType: Optional[str] = None + ChequeNumber: Optional[str] = None + Description1: Optional[str] = None + Description2: Optional[str] = None + VendorId: Optional[str] = None + VendorName: Optional[str] = None + AccountId: Optional[str] = None + AccountName: Optional[str] = None + class AssetRequest(BaseModel): id: str @@ -135,6 +163,20 @@ class MatchResponse(BaseModel): flag_for_review: Optional[bool] = None auto_approve: Optional[bool] = None + # QuickBooks CSV fields from transaction + TxnId: Optional[str] = None + AccountType: Optional[str] = None + AccountNumber: Optional[str] = None + TransactionDate: Optional[str] = None + TransactionType: Optional[str] = None + ChequeNumber: Optional[str] = None + Description1: Optional[str] = None + Description2: Optional[str] = None + VendorId: Optional[str] = None + VendorName: Optional[str] = None + AccountId: Optional[str] = None + AccountName: Optional[str] = None + class MatchingResponse(BaseModel): matches: List[MatchResponse]