# ============================================================================= # Projet : AIR — Accounting Intermediate Representation # Auteur : Simon-Pierre Boucher # Contact : contact@spboucher.ai # Fichier : base.py # Description : Common Backend interface — capabilities / compile / post / reverse. # ============================================================================= """Backend interface (the LLVM target abstraction). A backend lowers a CompiledJournal into a target payload (CSV rows, QBO JournalEntry JSON, Xero ManualJournal, the native ledger...) and posts it. Design informed by docs/research/erp-apis.md: - amounts are unsigned Decimals + a debit/credit side (the canonical form every surveyed API can be derived from); - post() takes an idempotency key — retries must never double-post; - reversal is a first-class operation (strategies vary by target: native, void, or contra entry) — posted entries are NEVER deleted. """ from __future__ import annotations import abc from dataclasses import dataclass, field from typing import Any from core.journal import CompiledJournal @dataclass(frozen=True, slots=True) class BackendCapabilities: name: str posts_remotely: bool # False: file/local targets native_reversal: bool # target supports reversal natively multi_currency: bool idempotency: str # "native" | "external-id" | "client-side" notes: str = "" @dataclass(frozen=True, slots=True) class TargetPayload: """What compile() produces: target-shaped data, not yet posted.""" backend: str format: str # e.g. "csv", "json", "qbo.journalentry" body: Any # backend-specific representation @dataclass(frozen=True, slots=True) class PostingReceipt: backend: str reference: str # file path, remote id, ledger sequence... idempotency_key: str details: dict[str, str] = field(default_factory=dict) @dataclass(frozen=True, slots=True) class ReversalReceipt: backend: str reference: str reversed_reference: str class Backend(abc.ABC): """Every AIR target implements this interface.""" @abc.abstractmethod def capabilities(self) -> BackendCapabilities: ... @abc.abstractmethod def compile(self, journal: CompiledJournal) -> TargetPayload: ... @abc.abstractmethod def post(self, payload: TargetPayload, idempotency_key: str) -> PostingReceipt: ... @abc.abstractmethod def reverse(self, receipt: PostingReceipt) -> ReversalReceipt: ...