Add DATEV accounting adapter boundary
This commit is contained in:
parent
fcec177ded
commit
90b7e97533
8 changed files with 583 additions and 13 deletions
103
tests/test_accounting_adapters.py
Normal file
103
tests/test_accounting_adapters.py
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
from dataclasses import replace
|
||||
from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
|
||||
from fin_hub.accounting import (
|
||||
AccountingCapability,
|
||||
DatevDuoAdapter,
|
||||
SchemaMigrations,
|
||||
default_registry,
|
||||
)
|
||||
from fin_hub.services.billing import BillingBasisExport, BillingBasisRecord
|
||||
|
||||
|
||||
def _export(schema_version: str = "0.1") -> BillingBasisExport:
|
||||
record = BillingBasisRecord(
|
||||
billing_basis_id="billing-basis:stable",
|
||||
cost_attribution_key="client:acme|app:portal|instance:prod-01",
|
||||
client_id="acme",
|
||||
application_id="portal",
|
||||
app_instance_id="prod-01",
|
||||
period_month="2026-07",
|
||||
currency="EUR",
|
||||
price_id="price:agreement-v1",
|
||||
price_revision_of=None,
|
||||
price_source="agreement-v1",
|
||||
revenue=Decimal("200.00"),
|
||||
direct_cost=Decimal("20.00"),
|
||||
allocated_cost=Decimal("80.00"),
|
||||
total_cost=Decimal("100.00"),
|
||||
margin=Decimal("100.00"),
|
||||
financial_fact_ids=("fact:1",),
|
||||
financial_fact_corrections=(),
|
||||
allocation_ids=("allocation:1",),
|
||||
allocation_revisions=(),
|
||||
provenance=("agreement-v1",),
|
||||
)
|
||||
return BillingBasisExport(
|
||||
schema_version=schema_version,
|
||||
artifact_type="billing_basis_report",
|
||||
records=(record,),
|
||||
exceptions=(),
|
||||
disclaimer="Reporting basis only; not an invoice.",
|
||||
)
|
||||
|
||||
|
||||
def test_datev_duo_is_default_and_prepares_only_an_incomplete_draft():
|
||||
transfer = default_registry().prepare(_export())
|
||||
|
||||
assert transfer.adapter_id == "datev-duo-v1"
|
||||
assert transfer.provider == "DATEV Unternehmen online"
|
||||
assert transfer.records[0]["external_reference"] == "billing-basis:stable"
|
||||
assert transfer.records[0]["net_billing_basis"] == Decimal("200.00")
|
||||
assert transfer.records[0]["requires_legal_and_tax_completion"] is True
|
||||
assert "Provider draft only" in transfer.disclaimer
|
||||
assert not any(
|
||||
key in transfer.records[0]
|
||||
for key in ("invoice_number", "issue_date", "payment_status")
|
||||
)
|
||||
|
||||
|
||||
def test_qonto_requires_explicit_selection_and_keeps_datev_as_destination():
|
||||
transfer = default_registry().prepare(
|
||||
_export(),
|
||||
adapter_id="qonto-invoice-draft-v1",
|
||||
required_capabilities=frozenset(
|
||||
{AccountingCapability.INVOICE_DRAFT_WORKFLOW}
|
||||
),
|
||||
)
|
||||
|
||||
assert transfer.provider == "Qonto"
|
||||
assert transfer.records[0]["bookkeeping_destination"] == (
|
||||
"DATEV Unternehmen online"
|
||||
)
|
||||
|
||||
|
||||
def test_capability_mismatch_never_silently_falls_back_to_qonto():
|
||||
with pytest.raises(ValueError, match="lacks capabilities: invoice_draft_workflow"):
|
||||
default_registry().prepare(
|
||||
_export(),
|
||||
required_capabilities=frozenset(
|
||||
{AccountingCapability.INVOICE_DRAFT_WORKFLOW}
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def test_breaking_schema_requires_an_explicit_migration():
|
||||
with pytest.raises(ValueError, match="no explicit billing-basis schema migration"):
|
||||
default_registry().prepare(_export("1.0"))
|
||||
|
||||
|
||||
def test_registered_schema_migration_must_produce_declared_version():
|
||||
migrations = SchemaMigrations()
|
||||
migrations.register("1.0", "0.1", lambda export: replace(export, schema_version="0.1"))
|
||||
from fin_hub.accounting.adapters import AdapterRegistry
|
||||
|
||||
registry = AdapterRegistry(
|
||||
(DatevDuoAdapter(),),
|
||||
default_adapter_id="datev-duo-v1",
|
||||
migrations=migrations,
|
||||
)
|
||||
|
||||
assert registry.prepare(_export("1.0")).source_schema_version == "0.1"
|
||||
Loading…
Add table
Add a link
Reference in a new issue