cadrumo.application.invoices.bulk_import module¶
Bulk CSV/XLSX transport for creating catalogue Invoice records.
The accountant/gestor batch case: a spreadsheet of invoice rows (counterparty
NIF, invoice number, date, taxable base, IVA rate) is turned into one
Invoice per row. This module is a typed transport
over create_catalogue_invoice() – the sole
sanctioned Invoice writer (aeat-architecture-boundaries);
it never persists a row itself.
Each row’s identity is the same content-derived
invoice_id hash the single-invoice
catalogue create verb and the evidence-confirm slice use, so a re-import of
an unchanged file is a guarded no-op per row
(aeat-cli-contract): an already-catalogued
identical row is reported skipped_duplicate, never re-written and never
raised as an error. A malformed row (missing required field, invalid NIF,
unsupported IVA rate) is collected as a refused row carrying its 1-based
CSV row number and the failing field, and the remaining valid rows are still
applied – partial-success semantics matching the ledger CSV import and bulk
classify pattern (no-silent-under-declaration: a bad row is reported, never
silently dropped).
See also
import_invoices_from_rows()Public application facade for applying validated bulk rows.
create_catalogue_invoice()Single catalogue writer invoked for every accepted row.
create_invoice_via_wizard()Manual single-invoice path with the same writer and idempotent identity.
confirm_invoice_draft_from_evidence()Evidence-confirm path that also delegates the final invoice write to the catalogue writer.
- class BulkImportSourceRow(**data)[source]¶
Bases:
BaseModelOne source row, its cells already keyed by the importer field they feed.
- Variables:
row_number – 1-based row number in the source file, header included, so a refusal names the row an operator counts in a spreadsheet.
values – One cell per importer field, in the source’s own representation. A delimited file yields text exactly as printed; a workbook yields the type the workbook itself chose, so a numeric cell is never stringified into the operator’s typed-text grammar.
- Parameters:
data (Any)
- row_number: int¶
- values: dict[str, object]¶
- class BulkInvoiceImportSource(**data)[source]¶
Bases:
BaseModelOne bulk-import file read, resolved and ready to apply.
- Variables:
rows – Every non-blank data row.
resolution – How the file’s own headers resolved onto importer fields, carrying the columns that resolved to nothing for reporting.
decimal_separator – The convention the file writes amounts in, detected once for the whole file. Amounts are converted with it at parse time rather than being rewritten in
rows.
- Parameters:
data (Any)
- rows: tuple[BulkImportSourceRow, ...]¶
- resolution: BulkImportColumnResolution¶
- decimal_separator: DecimalSeparatorValue¶
- class BulkInvoiceImportRow(**data)[source]¶
Bases:
BaseModelOne parsed row from a bulk invoice import CSV/XLSX file.
Mirrors the operator fields
aeat app ledger invoice addaccepts one at a time;taxable_baseandiva_ratesynthesise the single line item exactly asbuild_catalogue_invoice()does for the single-invoice verb, so a bulk row produces an identicalInvoiceshape.- Parameters:
data (Any)
- counterparty_nif: str¶
- counterparty_name: str¶
- invoice_number: str¶
- invoice_date: date¶
- taxable_base: Decimal¶
- iva_rate: Decimal | None¶
- retencion_amount: Decimal | None¶
- retencion_rate: Decimal | None¶
- currency: IsoCurrencyCode¶
- country_code: CountryCodeAlpha2¶
- notes: str¶
- class BulkInvoiceImportRowFailure(**data)[source]¶
Bases:
BaseModelOne row that could not be parsed or persisted during a bulk import.
- Parameters:
data (Any)
- row_number: int¶
- field: str¶
- reason: str¶
- class BulkInvoiceImportResult(**data)[source]¶
Bases:
BaseModelAggregate outcome of one bulk invoice import run.
Uses the same partial-success semantics as the ledger CSV import and bulk classify surfaces: every parseable, non-duplicate row is created; rows that fail validation are collected in
refused; rows whose derivedinvoice_idalready exists in the catalogue are counted inskipped_duplicate(the idempotent-guarded re-import no-op) rather than raised as an error.- Parameters:
data (Any)
- rows: NonNegativeInt¶
- created: NonNegativeInt¶
- skipped_duplicate: NonNegativeInt¶
- refused: tuple[BulkInvoiceImportRowFailure, ...]¶
- created_invoice_ids: tuple[str, ...]¶
- read_bulk_invoice_import_source(path, *, mapper=None)[source]¶
Read a CSV, TSV or XLSX invoice book into rows keyed by importer field.
A column whose header already names an importer field binds to it outright. Anything left over is put to
mapperonce for the whole file, and a column that still resolves to nothing is carried on the returned resolution for reporting — the file is never refused for carrying a column the importer does not know.row_numberis 1-based against the file’s own rows, so a refusal names exactly the row an operator would count in a spreadsheet application.- Parameters:
- Return type:
- Returns:
The resolved source, ready to apply.
- Raises:
InvoiceValidationError – The extension is unsupported, the file cannot be read, or no column supplies a required field.
- import_invoices_from_rows(source, *, bucket_id, kind, declared_country=None, ports)[source]¶
Create one catalogue
Invoiceper valid row in rows.Every accepted row is handed to
create_catalogue_invoice()– this function never persists a row itself (aeat-architecture-boundaries). BecauseInvoiceidentity is a content-derived hash of(kind, invoice_number, issued_at, counterparty_tax_id, currency, grand_total), re-importing the identical file a second time resolves every row to its already-cataloguedinvoice_idand reports it inskipped_duplicate– no second write, no raised error (aeat-cli-contract). A row whose resolved fields genuinely differ (a corrected amount, a new invoice number) mints a distinct record rather than overwriting one filer’s data with another’s.A malformed row (missing/blank required field, invalid date, unsupported IVA rate percentage, invalid NIF) is collected in
refusedwith its row number and the failing field name; the remaining valid rows still import (partial-success semantics).declared_countrystates one counterparty country for the whole import. It is required only when the source carries no country column at all, and a source that carries the column ignores it row by row: an operator whose book cannot express the fact declares it once and consciously, rather than having Spain inferred for a foreign counterparty.- Return type:
- Parameters:
source (BulkInvoiceImportSource)
bucket_id (str)
kind (InvoiceKind)
declared_country (str | None)
ports (CatalogueCreationPorts)