Files
Autoparts-DB/scripts/apply_facturapi_to_all_tenants.py
consultoria-as 8796cadb56 feat(pos): migrate CFDI timbrado from Horux to Facturapi
- Add Facturapi REST service (invoices, customers, orgs, cancel, downloads)
- Add JSON payload builder for ingreso/egreso/pago/global invoices
- Replace XML queue with Facturapi JSON queue (payload_unsigned, external_id)
- Update invoicing blueprint with Facturapi config and download endpoints
- Update global invoice service to use Facturapi payloads
- Add migration v4.3_facturapi.sql and tenant rollout script
- Update invoicing UI: payload preview, PDF/XML downloads, PAC status panel
- Add FACTURAPI_USER_KEY to .env.example
2026-06-14 09:26:42 +00:00

97 lines
2.8 KiB
Python
Executable File

#!/usr/bin/env python3
"""Apply Facturapi configuration to all active tenant databases.
Reads FACTURAPI_SECRET_KEY from the environment and stores it in each
tenant's tenant_config table as cfdi_facturapi_key. Also runs the
v4.3_facturapi.sql migration against every tenant DB.
Usage:
export MASTER_DB_URL=postgresql://...
export TENANT_DB_URL_TEMPLATE="postgresql://.../{db_name}"
export FACTURAPI_SECRET_KEY=sk_user_xxxxxxxxxxxxxxxx
python3 scripts/apply_facturapi_to_all_tenants.py
"""
import os
import sys
import psycopg2
MIGRATION_SQL = """
ALTER TABLE cfdi_queue RENAME COLUMN xml_unsigned TO payload_unsigned;
COMMENT ON COLUMN cfdi_queue.payload_unsigned IS 'Facturapi JSON payload (previously unsigned XML for Horux)';
COMMENT ON COLUMN cfdi_queue.xml_signed IS 'Signed+stamped XML returned by Facturapi';
ALTER TABLE cfdi_queue ADD COLUMN IF NOT EXISTS external_id VARCHAR(64);
COMMENT ON COLUMN cfdi_queue.external_id IS 'Facturapi invoice id';
CREATE INDEX IF NOT EXISTS idx_cfdi_queue_external_id ON cfdi_queue(external_id);
INSERT INTO tenant_config (key, value)
VALUES
('cfdi_facturapi_key', ''),
('cfdi_facturapi_org_id', ''),
('cfdi_facturapi_customer_sync', 'true')
ON CONFLICT (key) DO NOTHING;
UPDATE tenant_config SET value = %s WHERE key = 'cfdi_facturapi_key';
"""
def get_tenant_db_names(master_dsn):
conn = psycopg2.connect(master_dsn)
try:
cur = conn.cursor()
cur.execute(
"SELECT id, db_name FROM tenants WHERE is_active = true ORDER BY id"
)
rows = cur.fetchall()
cur.close()
return rows
finally:
conn.close()
def apply_to_tenant(tenant_id, db_name, template_dsn, api_key):
dsn = template_dsn.format(db_name=db_name)
conn = psycopg2.connect(dsn)
try:
cur = conn.cursor()
cur.execute(MIGRATION_SQL, (api_key,))
conn.commit()
cur.close()
print(f"[OK] tenant {tenant_id} ({db_name})")
except Exception as e:
print(f"[ERROR] tenant {tenant_id} ({db_name}): {e}")
finally:
conn.close()
def main():
master_dsn = os.environ.get("MASTER_DB_URL")
template_dsn = os.environ.get("TENANT_DB_URL_TEMPLATE")
api_key = os.environ.get("FACTURAPI_SECRET_KEY")
if not master_dsn or not template_dsn:
print("Set MASTER_DB_URL and TENANT_DB_URL_TEMPLATE", file=sys.stderr)
sys.exit(1)
if not api_key:
print("Set FACTURAPI_SECRET_KEY", file=sys.stderr)
sys.exit(1)
tenants = get_tenant_db_names(master_dsn)
if not tenants:
print("No active tenants found.")
return
print(f"Applying Facturapi config to {len(tenants)} tenant(s)...")
for tenant_id, db_name in tenants:
apply_to_tenant(tenant_id, db_name, template_dsn, api_key)
print("Done.")
if __name__ == "__main__":
main()