feat: Add client extraction, amount cross-validation, and workflow fixes

OCR improvements:
- Extract client data (name, CUI, address) from B2B receipts
- Cross-validate amounts using payment methods and TVA entries
- OCR-tolerant patterns for "TOTAL LEI" with common OCR errors
- Better BON FISCAL vs CHITANTA detection

Backend workflow fixes:
- Fix SQLAlchemy deleted instance error in resubmit/submit workflow
- Add session.refresh() after deleting accounting entries
- Add unapprove endpoint (APPROVED → PENDING_REVIEW)
- Add direction filter for receipt listing

Frontend improvements:
- Fix Vue v-else-if chain broken by Menu component
- Unified OCR Preview layout with values table
- Receipt list filter by direction (plati/incasari)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-17 23:16:10 +02:00
parent 93185120df
commit a90d9093de
12 changed files with 3125 additions and 855 deletions

View File

@@ -139,7 +139,6 @@ class ReceiptService:
account_code=expense_type.account_code,
account_name=expense_type.account_name,
amount=net_amount,
partner_id=receipt.partner_id,
))
# Debit: VAT deductible
@@ -156,7 +155,6 @@ class ReceiptService:
account_code=expense_type.account_code,
account_name=expense_type.account_name,
amount=amount,
partner_id=receipt.partner_id,
))
# Credit entry - based on payment_mode (new) or cash_register (legacy)
@@ -245,6 +243,9 @@ class ReceiptService:
await AccountingEntryCRUD.delete_all_for_receipt(session, receipt_id)
await AccountingEntryCRUD.create_bulk(session, receipt_id, entries, is_auto_generated=True)
# Refresh receipt to clear stale relationship references after entry deletion
await session.refresh(receipt)
# Update status
updated = await ReceiptCRUD.update_status(
session, receipt, ReceiptStatus.PENDING_REVIEW
@@ -288,6 +289,31 @@ class ReceiptService:
return True, "Receipt approved", updated
@staticmethod
async def unapprove_receipt(
session: AsyncSession,
receipt_id: int,
username: str,
) -> Tuple[bool, str, Optional[Receipt]]:
"""
Unapprove receipt (APPROVED → PENDING_REVIEW).
Returns receipt to pending review for corrections.
"""
receipt = await ReceiptCRUD.get_by_id(session, receipt_id)
if not receipt:
return False, "Receipt not found", None
if receipt.status != ReceiptStatus.APPROVED:
return False, "Receipt is not approved", None
# Update status back to pending review
updated = await ReceiptCRUD.update_status(
session, receipt, ReceiptStatus.PENDING_REVIEW
)
return True, "Receipt returned to pending review", updated
@staticmethod
async def reject_receipt(
session: AsyncSession,
@@ -342,6 +368,9 @@ class ReceiptService:
await AccountingEntryCRUD.delete_all_for_receipt(session, receipt_id)
await AccountingEntryCRUD.create_bulk(session, receipt_id, entries, is_auto_generated=True)
# Refresh receipt to clear stale relationship references after entry deletion
await session.refresh(receipt)
# Update status
updated = await ReceiptCRUD.update_status(
session, receipt, ReceiptStatus.PENDING_REVIEW