Files
Autoparts-DB/scripts/apply_facturapi_to_all_tenants.py
consultoria-as d67887284d feat(pos/facturapi): finalize Horux-to-Facturapi migration
- Normalize Facturapi key/org_id resolution (supports both cfdi_ prefixed
  tenant_config keys and short names used by invoicing_bp).
- Add CSD upload end-to-end (backend + frontend).
- Add helper scripts: setup_facturapi_orgs.py and check_facturapi_tenants.py.
- Add 20 unit tests with mocks (pos/tests/test_facturapi_service.py).
- Add CI workflow for lint + console tests on Python 3.11/3.13.
- Add pyproject.toml and requirements-dev.txt with ruff/pytest config.
- Update FASES_IMPLEMENTADAS.md with FASE 8 documentation.

Tests: 81 passing (61 console + 20 Facturapi).
2026-06-15 04:58:42 +00:00

96 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()