Skip to content

Error Handling

Exception types

PacError

Defined per-domain:

Raised when Alanube returns a non-success status that we can't recover from. Carries the HTTP status and Alanube's error message.

MissingRetentionCodeError

Raised by InvoicePacService when an invoice for a retention-eligible client lacks a retention code. Lookup order is: invoice override → client default → error.

DGI / file-import exceptions

app/wrappers/dgi/dgi_exceptions.py

Used by the createInvoiceFromFile flow:

  • QrCodeNotFoundError — uploaded file had no QR.
  • QrCodeNotReadyError — QR present but not yet processed.
  • QrCodeUrlNotValidError — QR points at something invalid.
  • UnknownQrCodeError, UnknownAiError — fallthrough cases.

Retry policy

Where Attempts Backoff Retry on
PacService HTTP methods 2 2s → 10s exponential aiohttp.ClientResponseError
RucVerificationService.verify_ruc 2 2s → 10s exponential aiohttp.ClientError
pac_status_poll_task 60 polls × 5s fixed 5s always (until terminal status)

There is no broker-level retry for taskiq tasks beyond what the task itself implements.

Where errors get logged

Loguru is the logger throughout the codebase. The PAC code uses these conventions:

  • logger.info(...) — successful submissions, PDF attachments, status changes.
  • logger.warning(...) — recoverable oddities (missing PDF URL, polling timeout).
  • logger.error(...) — Alanube returned a 4xx/5xx with a body we want to keep.
  • logger.exception(...) — uncaught exception, includes traceback.

In production, logs are shipped to Logfire (see logfire deps in pyproject.toml). Filter by service.name=cifras-backend and search for "PAC" to see PAC-specific events.

What gets persisted on failure

Failure Are inputs saved? pac_response saved? legal_status set?
RUC verification failure No No No (transaction rolled back)
Missing retention code No No No
POST /invoices 4xx Depends on call site Sometimes Sometimes (PAC_REJECTED)
Network error after retries No No No
Webhook signature mismatch n/a n/a n/a (rejected before any DB work)
Webhook for unknown doc n/a n/a n/a (logged, returned 200)

When debugging a "this invoice was supposed to go through but didn't", the audit trail is pac_input + pac_response JSONB columns. They're often the only record of what happened.

No dead-letter queue (yet)

If a polling task crashes mid-loop, the document state is left wherever it was. There is no automatic re-enqueue. Manual recovery via attachPacPdf is the current workaround. This is a known gap — see IDEAS.md for any backlog notes.