diff --git a/data-entry-app/backend/app/db/crud/receipt.py b/data-entry-app/backend/app/db/crud/receipt.py index 5231448..3243937 100644 --- a/data-entry-app/backend/app/db/crud/receipt.py +++ b/data-entry-app/backend/app/db/crud/receipt.py @@ -1,7 +1,9 @@ """CRUD operations for receipts.""" +import json from datetime import datetime, date -from typing import Optional, List, Tuple +from decimal import Decimal +from typing import Optional, List, Tuple, Any from sqlalchemy import select, func, or_ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload @@ -10,6 +12,31 @@ from app.db.models.receipt import Receipt, ReceiptStatus from app.schemas.receipt import ReceiptCreate, ReceiptUpdate, ReceiptFilter +def _serialize_tva_breakdown(tva_breakdown: Optional[List[Any]]) -> Optional[str]: + """Serialize TVA breakdown list to JSON string for SQLite storage.""" + if tva_breakdown is None: + return None + + # Convert Decimal to float for JSON serialization + serializable = [] + for entry in tva_breakdown: + if hasattr(entry, 'model_dump'): + # Pydantic model + item = entry.model_dump() + elif isinstance(entry, dict): + item = entry.copy() + else: + item = dict(entry) + + # Convert Decimal to float + if 'amount' in item and isinstance(item['amount'], Decimal): + item['amount'] = float(item['amount']) + + serializable.append(item) + + return json.dumps(serializable) + + class ReceiptCRUD: """CRUD operations for Receipt model.""" @@ -20,8 +47,12 @@ class ReceiptCRUD: created_by: str, ) -> Receipt: """Create a new receipt.""" + # Get data as dict and serialize tva_breakdown to JSON string + receipt_data = data.model_dump() + receipt_data['tva_breakdown'] = _serialize_tva_breakdown(receipt_data.get('tva_breakdown')) + receipt = Receipt( - **data.model_dump(), + **receipt_data, created_by=created_by, status=ReceiptStatus.DRAFT, ) @@ -132,6 +163,10 @@ class ReceiptCRUD: """Update receipt fields.""" update_data = data.model_dump(exclude_unset=True) + # Serialize tva_breakdown to JSON string if present + if 'tva_breakdown' in update_data: + update_data['tva_breakdown'] = _serialize_tva_breakdown(update_data['tva_breakdown']) + for field, value in update_data.items(): setattr(receipt, field, value) diff --git a/data-entry-app/backend/app/schemas/receipt.py b/data-entry-app/backend/app/schemas/receipt.py index e45f0a6..98ca36b 100644 --- a/data-entry-app/backend/app/schemas/receipt.py +++ b/data-entry-app/backend/app/schemas/receipt.py @@ -1,9 +1,10 @@ """Pydantic schemas for receipts API.""" +import json from datetime import datetime, date from decimal import Decimal -from typing import Optional, List -from pydantic import BaseModel, Field, ConfigDict +from typing import Optional, List, Any, Union +from pydantic import BaseModel, Field, ConfigDict, field_validator from app.db.models.receipt import ReceiptType, ReceiptDirection, ReceiptStatus from app.db.models.accounting_entry import EntryType @@ -148,6 +149,21 @@ class ReceiptResponse(ReceiptBase): attachments: List[AttachmentResponse] = [] entries: List[AccountingEntryResponse] = [] + @field_validator('tva_breakdown', mode='before') + @classmethod + def parse_tva_breakdown(cls, v: Any) -> Optional[List[dict]]: + """Deserialize tva_breakdown from JSON string if needed.""" + if v is None: + return None + if isinstance(v, str): + try: + return json.loads(v) + except (json.JSONDecodeError, TypeError): + return None + if isinstance(v, list): + return v + return None + class ReceiptListResponse(BaseModel): """Schema for paginated receipt list response."""