spb/air Public MIT
AIR — The Language of Accounting.
Python 100%
1# =============================================================================2# Projet : AIR — Accounting Intermediate Representation3# Auteur : Simon-Pierre Boucher4# Contact : contact@spboucher.ai5# Fichier : backend.py6# Description : Native backend — posts into AIR's own ledger (standalone mode, no ERP needed).7# =============================================================================8"""Native backend: AIR as a self-sufficient accounting system.910When no third-party system (QuickBooks, Xero, ...) is used, compiled journals11post into AIR's own append-only, hash-chained ledger, from which all12statements (general ledger, trial balance, income statement, balance sheet)13are generated in multiple formats.14"""15from __future__ import annotations1617from pathlib import Path1819from backends.base import (20 Backend,21 BackendCapabilities,22 PostingReceipt,23 ReversalReceipt,24 TargetPayload,25)26from core.journal import CompiledJournal27from kernel.ledger import Ledger282930class NativeLedgerBackend(Backend):31 def __init__(self, ledger_path: str | Path | None = None):32 self.ledger = Ledger(path=Path(ledger_path) if ledger_path else None)3334 def capabilities(self) -> BackendCapabilities:35 return BackendCapabilities(36 name="native",37 posts_remotely=False,38 native_reversal=True,39 multi_currency=True,40 idempotency="native",41 notes="AIR's own append-only hash-chained ledger; full reporting built in",42 )4344 def compile(self, journal: CompiledJournal) -> TargetPayload:45 # The native target consumes the journal as-is: no lowering needed.46 return TargetPayload(backend="native", format="journal", body=journal)4748 def post(self, payload: TargetPayload, idempotency_key: str) -> PostingReceipt:49 journal = payload.body50 assert isinstance(journal, CompiledJournal)51 appended = self.ledger.post_journal(journal, idempotency_key)52 return PostingReceipt(53 backend="native",54 reference=str(self.ledger.path or "<memory>"),55 idempotency_key=idempotency_key,56 details={"entries_appended": str(appended)},57 )5859 def reverse(self, receipt: PostingReceipt) -> ReversalReceipt:60 # Reverse every entry posted under the receipt's idempotency key.61 reversed_ids = []62 for entry in list(self.ledger.entries):63 if entry.id.startswith("rev_"):64 continue65 contra = self.ledger.reverse_entry(66 entry.id, f"rev:{receipt.idempotency_key}:{entry.id}"67 )68 reversed_ids.append(contra.id)69 return ReversalReceipt(70 backend="native",71 reference=",".join(reversed_ids),72 reversed_reference=receipt.idempotency_key,73 )74