Data Model¶
This page is the canonical reference for which database columns are PAC-related, what they mean, and where they're written.
tenants¶
| Column | Type | Purpose |
|---|---|---|
pac_company_id |
String(255) |
Alanube's company ID for this tenant. Returned by POST /pan/v1/companies during onboarding. The webhook URL /webhooks/pac/{company_id} matches against this column. |
If this is NULL, the tenant cannot issue PAC documents at all.
offices¶
app/graphql/companies/models.py
| Column | Type | Purpose |
|---|---|---|
pac_office_id |
String(50) |
Alanube's office ID. Returned by POST /pan/v1/companies/{id}/offices. |
code |
String(4) |
4-digit office code, e.g. 0001. |
coordinates |
String(22) |
"+9.0017832,-79.5275519" |
address |
String(100) |
Physical address. |
telephone |
String(12) |
Phone for the office. |
type |
String(20) |
"main" or "associated". |
email |
String(50) |
Office contact email. |
location |
String(255) |
DGI province/district/corregimiento code, e.g. 8-8-11. |
A tenant must have at least one office before issuing invoices.
invoices¶
app/graphql/invoices/models/invoice.py
PAC-relevant columns:
| Column | Type | Written by | Meaning |
|---|---|---|---|
invoice_number |
String(255) |
factory + PAC response | The CUFE. Set after Alanube authorizes. |
document_id |
String(255) |
PAC response | Alanube's internal ID. Used for all subsequent GET/DELETE calls. |
legal_status |
String(50) |
webhook / poller | One of PAC_AUTHORIZED, DGI_AUTHORIZED, PAC_REJECTED, DGI_REJECTED. |
pac_input |
JSONB |
InvoicePacService |
Full payload sent to PAC (audit / replay). |
pac_response |
JSONB |
InvoicePacService |
Full response from PAC. |
qr_code_url |
Text |
webhook / poller | DGI QR code link. |
invoice_generation_mode |
IntEnum |
mutation input | PROFORM, DGI, or DGI_IMPORT. |
office_id |
UUID FK |
mutation input | Which office issued the invoice; maps to pac_office_id. |
adhoc_client_name |
String(255) |
mutation input | Override the registered client's name on the PAC payload. |
adhoc_client_ruc |
String(255) |
mutation input | Override the registered client's RUC. |
emails_ad_hoc |
Array[String] |
mutation input | Override receiver emails. |
credits¶
app/graphql/credits/models/credit.py
Same shape as invoices, but the CUFE column is named credit_number:
| Column | Type | Meaning |
|---|---|---|
credit_number |
String(255) |
Credit note CUFE. |
document_id |
String(255) |
Alanube's internal ID. |
legal_status |
String(50) |
Same enum as invoices. |
pac_input, pac_response |
JSONB |
Audit. |
qr_code_url |
Text |
DGI QR. |
credit_generation_mode |
IntEnum |
PROFORM, DGI, DGI_IMPORT. |
office_id |
UUID FK |
Issuing office. |
A credit also references the source invoice — see Credit Notes.
Migration history¶
The pac_input / pac_response columns were added in alembic/versions/20260215_add_pac_input_and_response_to_invoices_and_credits.py. Older invoices in the DB may have these as NULL; consumers must tolerate that.
Why both document_id and CUFE?¶
document_idexists from the moment Alanube acknowledges the submission, even if the document is later rejected. It's the lookup key for all PAC API calls.- CUFE only exists once the document is at least PAC-authorized. It's printed on the PDF and used by the receiver.
For lookups in our own code, always prefer document_id — it's stable and unique. Use CUFE only when matching webhook payloads that lack a document_id (rare).