cadrumo.domain.invoices.models module¶
Strict immutable models for the invoice catalogue.
Defines the pydantic v2 records that back cadrumo.domain.invoices:
InvoiceLine, Invoice, and the keyed
InvoiceCatalogue. Every model is strict, frozen, and forbids
extra fields; identity-bearing fields on Invoice are
canonicalised in a model_validator and the stable
Invoice.invoice_id is derived via derive_invoice_id().
Counterparty identity validation is delegated to
cadrumo.domain.invoices.validators.
- derive_invoice_id(*, kind, invoice_number, issued_at, counterparty_tax_id, currency, grand_total)[source]¶
Return the stable invoice hash for one invoice record.
The digest is computed over a canonical JSON payload so that two invoices with equal logical identity produce identical IDs regardless of whitespace or numeric formatting.
- Parameters:
kind (
InvoiceKind) – Invoice direction (issued / received).invoice_number (
str) – AEAT-significant invoice number as printed on the document.issued_at (
date) – ISO calendar date printed on the invoice.counterparty_tax_id (
str|None) – Counterparty NIF / NIE / CIF / IVA number already validated and uppercased, orNonefor a factura simplificada issued outside the RD 1619/2012 art. 6.1.d cases that make the counterparty’s tax id mandatory.currency (
str) – ISO-4217 currency code already uppercased.grand_total (
Decimal) – Invoice grand total.
- Return type:
- Returns:
A lowercase SHA-256 digest that uniquely identifies the invoice.
- class InvoiceLine(**data)[source]¶
Bases:
BaseModelImmutable line item on an invoice.
- Parameters:
data (Any)
- description: str¶
- quantity: Decimal¶
- unit_price: Decimal¶
- subtotal: Decimal¶
- iva_rate: IvaRate¶
- iva_amount: Decimal¶
- spending_category_id: str | None¶
- oss_rate_kind: IvaRateKind | None¶
- class Invoice(**data)[source]¶
Bases:
BaseModelStrict frozen record for one issued or received invoice.
- Parameters:
data (Any)
- invoice_id: InvoiceId¶
- bucket_id: BucketId | None¶
- kind: InvoiceKind¶
- invoice_class: InvoiceClass¶
- series: str | None¶
- invoice_number: str¶
- issued_at: date¶
- operation_date: date | None¶
- operation_date_role: InvoiceOperationDateRole | None¶
- counterparty_name: str¶
- counterparty_tax_id: TaxIdIdentityToken | None¶
- counterparty_country: CountryCodeAlpha2¶
- counterparty_identification_state: EUMemberState | None¶
- issuer_address: str | None¶
- recipient_address: str | None¶
- exemption_reference: str | None¶
- legal_mentions: tuple[InvoiceLegalMention, ...]¶
- base_total: Decimal¶
- iva_total: Decimal¶
- grand_total: Decimal¶
- currency: IsoCurrencyCode¶
- lines: tuple[InvoiceLine, ...]¶
- payment_status: PaymentStatus¶
- linked_transaction_ids: tuple[str, ...]¶
- notes: str¶
- iva_category: IvaCategory | None¶
- operation_type: IntracomOperationType | None¶
- travel_agency_mediation: TravelAgencyMediationType | None¶
- collected_on_behalf_of_tax_id: TaxIdIdentityToken | None¶
- collected_on_behalf_of_name: str | None¶
- is_subvencion_ayuda: bool | None¶
- outside_economic_activity: bool | None¶
- oss_ioss_regime: OssIossRegime | None¶
- oss_transaction_kind: TransactionKind | None¶
- retention_rate: Decimal | None¶
- retention_amount: Decimal | None¶
- recargo_amount: Decimal | None¶
- suplido_amount: Decimal | None¶
- rectifies_invoice_number: str | None¶
- payment_id: str | None¶
- fx_rate: Decimal | None¶
- fx_rate_date: date | None¶
- fx_rate_source: str | None¶
- created_at: UtcInstant | None¶
- updated_at: UtcInstant | None¶
- property base_total_eur: Decimal | None¶
base_totalin euro, orNonewhen the invoice is unconverted.
- property grand_total_eur: Decimal | None¶
grand_totalin euro, orNonewhen the invoice is unconverted.
- property retention_amount_eur: Decimal | None¶
The declared retención in euro, or
Nonewhen there is none to convert.Returns
Noneboth when no retención was declared and when the invoice is unconverted, so a caller that needs to tell those apart must readretention_amountalongside it – the same shape the three total accessors already have.
- property recargo_amount_eur: Decimal | None¶
The recargo de equivalencia in euro, or
Nonewhen there is none to convert.Same shape as
retention_amount_eur:Nonecovers both “no recargo was declared” and “the invoice is unconverted”, so a caller needing to tell those apart readsrecargo_amountalongside it.
- property suplido_amount_eur: Decimal | None¶
The suplido (LIVA art. 78.Tres.3.º) in euro, or
Nonewhen there is none to convert.Same shape as
recargo_amount_eurandretention_amount_eur:Nonecovers both “no suplido was declared” and “the invoice is unconverted”.
- line_amount_eur(amount)[source]¶
Convert a LINE-level native-currency amount to euro, or
Noneif unconverted.InvoiceLinecarries no currency of its own – every line on an invoice is denominated in that invoice’s owncurrency(a single document states one currency for all its lines), so a line amount (line.subtotal,line.iva_amount) converts through the SAME rate resolution as the invoice-level totals (base_total_eurand siblings), not a separate per-line mechanism. Delegating to_in_eur()keeps that arithmetic consistent: summingline_amount_eur(line.subtotal)over every line equalsbase_total_eurexactly, the same way summingline.subtotalnatively already equalsbase_total.
- property counterparty_eu_member_state: EUMemberState | None¶
Return the substrate-typed EUMemberState for the counterparty, or
Nonefor non-EU.counterparty_countrycarries the raw uppercase ISO-3166-1 alpha-2 code (validated at construction time). This typed accessor lets downstream consumers (Modelo 369 OSS bindings, intra-community classification, OSS classifier dispatch) work with a registry-projected token without a per-call lowercase / membership check. It is anchored to the dated EU member-state fact 0131.