| from datetime import datetime, timezone |
| import logging |
| from typing import Optional |
| from accounting.models import Account, AccountType, EntryType, JournalEntry, Transaction |
| from ecommerce.models import EcommerceOrder |
| from sqlalchemy.orm import Session |
|
|
| from core.identity_resolver import CustomerResolutionEngine |
|
|
| logger = logging.getLogger(__name__) |
|
|
| class OrderToLedgerMapper: |
| def __init__(self, db: Session): |
| self.db = db |
| self.resolver = CustomerResolutionEngine(db) |
|
|
| def process_order(self, order_id: str) -> Optional[str]: |
| """ |
| Takes an EcommerceOrder and creates appropriate accounting entries. |
| Returns the ID of the created Transaction. |
| """ |
| order = self.db.query(EcommerceOrder).filter(EcommerceOrder.id == order_id).first() |
| if not order: |
| logger.error(f"Order {order_id} not found for ledger mapping") |
| return None |
|
|
| if order.is_ledger_synced: |
| logger.info(f"Order {order_id} already synced to ledger") |
| return order.ledger_transaction_id |
|
|
| |
| customer = self.resolver.resolve_customer( |
| order.workspace_id, |
| order.customer.email, |
| order.customer.first_name, |
| order.customer.last_name |
| ) |
| |
| |
| |
| |
| |
| cash_account = self._get_account(order.workspace_id, AccountType.ASSET, "Bank") |
| revenue_account = self._get_account(order.workspace_id, AccountType.REVENUE, "Service Revenue") |
| tax_account = self._get_account(order.workspace_id, AccountType.LIABILITY, "Sales Tax Payable") |
| shipping_account = self._get_account(order.workspace_id, AccountType.REVENUE, "Shipping Income") |
|
|
| |
| tx = Transaction( |
| workspace_id=order.workspace_id, |
| transaction_date=order.created_at or datetime.now(timezone.utc), |
| description=f"Shopify Order #{order.order_number}", |
| amount=order.total_price, |
| source="ecommerce" |
| ) |
| self.db.add(tx) |
| self.db.flush() |
|
|
| |
| |
| self.db.add(JournalEntry( |
| transaction_id=tx.id, |
| account_id=cash_account.id, |
| type=EntryType.DEBIT, |
| amount=order.total_price |
| )) |
|
|
| |
| if order.subtotal_price > 0: |
| self.db.add(JournalEntry( |
| transaction_id=tx.id, |
| account_id=revenue_account.id, |
| type=EntryType.CREDIT, |
| amount=order.subtotal_price |
| )) |
|
|
| |
| if order.total_tax > 0: |
| self.db.add(JournalEntry( |
| transaction_id=tx.id, |
| account_id=tax_account.id, |
| type=EntryType.CREDIT, |
| amount=order.total_tax |
| )) |
|
|
| |
| if order.total_shipping > 0: |
| self.db.add(JournalEntry( |
| transaction_id=tx.id, |
| account_id=shipping_account.id, |
| type=EntryType.CREDIT, |
| amount=order.total_shipping |
| )) |
|
|
| |
| order.ledger_transaction_id = tx.id |
| order.is_ledger_synced = True |
| |
| self.db.commit() |
| logger.info(f"Successfully synced Order {order_id} to Ledger Transaction {tx.id}") |
| return tx.id |
|
|
| def _get_account(self, workspace_id: str, account_type: AccountType, default_name: str) -> Account: |
| """Helper to find or create a default account for a given type and name.""" |
| account = self.db.query(Account).filter( |
| Account.workspace_id == workspace_id, |
| Account.type == account_type, |
| Account.name == default_name |
| ).first() |
| |
| if not account: |
| |
| account = self.db.query(Account).filter( |
| Account.workspace_id == workspace_id, |
| Account.type == account_type |
| ).first() |
|
|
| if not account: |
| account = Account( |
| workspace_id=workspace_id, |
| name=default_name, |
| type=account_type, |
| code=f"{account_type.value[:1]}000-AUTO" |
| ) |
| self.db.add(account) |
| self.db.flush() |
| logger.info(f"Created default {account_type} account: {default_name}") |
| |
| return account |
|
|