aeat.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 (composition-service-no-parallel-write-path); 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 (single-subject-mutation-is-idempotent-guarded): 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 BulkInvoiceImportRow(**data)[source]

Bases: BaseModel

One parsed row from a bulk invoice import CSV/XLSX file.

Mirrors the operator fields aeat app ledger invoice catalogue create accepts one at a time; taxable_base and iva_rate synthesise the single line item exactly as build_catalogue_invoice() does for the single-invoice verb, so a bulk row produces an identical Invoice shape.

Parameters:
  • counterparty_nif (str)

  • counterparty_name (str)

  • invoice_number (str)

  • invoice_date (date)

  • taxable_base (Decimal)

  • iva_rate (Decimal | None)

  • currency (str)

  • country_code (str)

  • notes (str)

counterparty_nif: str
counterparty_name: str
invoice_number: str
invoice_date: date
taxable_base: Decimal
iva_rate: Decimal | None
currency: str
country_code: str
notes: str
class BulkInvoiceImportRowFailure(**data)[source]

Bases: BaseModel

One row that could not be parsed or persisted during a bulk import.

Parameters:
row_number: int
field: str
reason: str
class BulkInvoiceImportResult(**data)[source]

Bases: BaseModel

Aggregate 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 derived invoice_id already exists in the catalogue are counted in skipped_duplicate (the idempotent-guarded re-import no-op) rather than raised as an error.

Parameters:
rows: int
created: int
skipped_duplicate: int
refused: tuple[BulkInvoiceImportRowFailure, ...]
created_invoice_ids: tuple[str, ...]
read_bulk_invoice_import_rows(path)[source]

Yield (row_number, raw_row) pairs from a CSV or XLSX bulk-import file.

row_number is 1-based against the file’s own rows (the header is row 1), so a refusal names exactly the row an operator would count by opening the file in a spreadsheet application.

Raises:

InvoiceValidationError – When the file extension is unsupported, or the header carries unknown columns or omits a required column.

Return type:

Iterator[tuple[int, Mapping[str, object]]]

Parameters:

path (Path)

import_invoices_from_rows(rows, *, bucket_id, kind, repository=None)[source]

Create one catalogue Invoice per valid row in rows.

Every accepted row is handed to create_catalogue_invoice() – this function never persists a row itself (composition-service-no-parallel-write-path). Because Invoice identity 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-catalogued invoice_id and reports it in skipped_duplicate – no second write, no raised error (single-subject-mutation-is-idempotent-guarded). 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 refused with its row number and the failing field name; the remaining valid rows still import (partial-success semantics).

Return type:

BulkInvoiceImportResult

Parameters: