cadrumo.application.invoices.catalogue_creation module¶
Application service for creating one rich catalogue Invoice.
The operator-facing aeat app ledger invoice add verb mints a linkable
invoice directly: the Invoice in the
InvoiceCatalogue is the only invoice record, and it
carries
linked_transaction_ids and is the reconciliation authority link targets.
create_catalogue_invoice() builds a strict Invoice from
operator-friendly fields and persists it through the required catalogue
creation port bundle (no parallel write path). A caller that
supplies no line set gets a single line synthesised from the taxable base and
IVA rate; a caller that supplies one gets those lines, which is how an invoice
carrying several IVA rates is expressed.
This is the ONE line-synthesis site. The bulk import path does not carry its
own: it routes every row through build_catalogue_invoice(), so the two
transports cannot disagree about the shape they produce. Its row model does
still admit only one rate per row, which is a limit of that file format rather
than a second synthesis to keep in step. The returned Invoice.invoice_id is the
content-addressed hash link --invoice-id resolves, closing the documented
add->link gap without collapsing the two stores.
- class CatalogueInvoiceCreateResult(**data)[source]¶
Bases:
BaseModelResult of persisting one rich catalogue invoice.
- Parameters:
data (Any)
- invoice: Invoice¶
- catalogue: InvoiceCatalogue¶
- bucket_event_ids: tuple[str, ...]¶
- emit_catalogue_invoice_event(*, invoice, bucket_id, slot, event_repository, occurred_at, actor)[source]¶
Append the creation event for a canonically-written invoice.
The canonical write paths emitted NO bucket event of any kind, while the slim store emitted six types and returned their ids to the operator. So repointing the operator’s verbs onto this store would have dropped the invoice audit trail and the event-ids field in the same change – a capability loss with no replacement, which is why it blocked the fold.
The event type is chosen by direction so this store speaks the vocabulary that already exists rather than minting a parallel one; the six members outlive the slim store that used to be their only emitter.
- resolve_iva_rate_slot(iva_rate, on_date)[source]¶
Map a printed percentage to its dated authority-backed persisted slot.
Noneresolves to the registry-declared exempt slot so a base-only invoice with no cuota is accepted. A percentage outside the governed slot vocabulary is refused with the accepted set named, never a bare “value invalid”.Public because the percentage does not only come from an operator: the ledger’s evidence-confirm path reads it off the document itself and needs the SAME refusal. Two resolvers previously split that job between them and disagreed about the outcome – one raised a localised error naming the accepted set, the other a raw English one – so which message an operator saw depended on whether their document happened to print a cuota.
- build_catalogue_invoice(*, bucket_id, kind, counterparty_name, counterparty_tax_id, counterparty_country, invoice_number, issued_at, taxable_base, iva_rate, currency, payment_status=PaymentStatus.PENDING, notes='', iva_category=None, operation_type=None, operation_date=None, retention_rate=None, retention_amount=None, invoice_class=None, series=None, rectifies_invoice_number=None, recargo_amount=None, lines=None, rate_provider, operation=None)[source]¶
Return a strict rich
Invoicefrom operator-supplied fields.When
linesis omitted a single line item is synthesised fromtaxable_baseand the resolved IVA rate slot, and the invoice totals are derived from that line so theInvoicearithmetic invariants hold. The returned invoice carries no linked transactions yet —link --invoice-idpopulates them later.When
linesIS supplied they are authoritative and the totals are summed from them, which is what lets one invoice carry several IVA rates. A real invoice mixing 21% and 10% lines is not exotic — collapsing it to one line at a single rate reports the right grand total while attributing the cuota to the wrong rate, and the per-rate breakdown is precisely what the IVA modelos declare.taxable_basemust then AGREE with the summed line subtotals, and a mismatch refuses rather than resolving. Two disagreeing sources of truth for the same base is the shape that silently mis-declares, so the caller is made to state one number, not two.iva_categorycarries the intra-community classification the M349 recapitulative resolver reads for historical goods/triangulation records.operation_typecarries the explicit Modelo 349 clave for invoice records that need a key not represented by an IVA category.retention_rate/retention_amountrecord the RIRPF art. 95 withholding a payer settles against a received invoice (or a customer against an issued one). Neither is derived here:Invoice’s own_validate_retencion_consistencyaccepts an amount alone, requires an amount whenever a rate is supplied, and refuses either that does not match the invoice’sbase_total.invoice_class,series,rectifies_invoice_numberandrecargo_amountreach axes the aggregate has always modelled and no write path could set. Until they existed here every canonically-written invoice was ordinary with no series and no recargo by construction, and a rectificativa was unrepresentable — so the aggregate claimed a vocabulary the writer could not speak.The recargo rides INSIDE
grand_total(LIVA art. 161) while a retención is settled outside it, which is why only the recargo enters the totals identity. The model re-checks that identity exactly, so a stated recargo the lines do not support refuses rather than being balanced silently.- Return type:
- Parameters:
bucket_id (str | None)
kind (InvoiceKind)
counterparty_name (str)
counterparty_tax_id (str | None)
counterparty_country (str)
invoice_number (str)
issued_at (date)
taxable_base (Decimal)
iva_rate (Decimal | None)
currency (str)
payment_status (PaymentStatus)
notes (str)
iva_category (IvaCategory | None)
operation_type (IntracomOperationType | None)
operation_date (date | None)
retention_rate (Decimal | None)
retention_amount (Decimal | None)
invoice_class (InvoiceClass | None)
series (str | None)
rectifies_invoice_number (str | None)
recargo_amount (Decimal | None)
lines (Sequence[InvoiceLine] | None)
rate_provider (CatalogueInvoiceRateProviderPort)
operation (PinnedAuthorityOperation | None)
- create_catalogue_invoice(*, invoice, ports, occurred_at=None, actor='cli')[source]¶
Persist one pre-built catalogue invoice and return the updated catalogue.
build_catalogue_invoice()is the sole construction authority for operator-supplied fields and line synthesis. This service owns only the catalogue mutation and its post-save audit event, so the construction contract cannot drift between an in-memory candidate and the written record.- Return type:
- Parameters:
invoice (Invoice)
ports (CatalogueCreationPorts)
occurred_at (datetime | None)
actor (str)