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).
This commit is contained in:
2026-06-15 04:58:42 +00:00
parent 6aff32f93b
commit d67887284d
15 changed files with 1559 additions and 481 deletions

View File

@@ -4,6 +4,7 @@
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from tenant_db import get_master_conn, get_tenant_conn_by_dbname
@@ -12,43 +13,43 @@ MIGRATIONS_DIR = os.path.dirname(os.path.abspath(__file__))
# Migration registry: version -> filename
MIGRATIONS = {
'v1.0': 'v1.0_initial.sql',
'v1.1': 'v1.1_pos_tables.sql',
'v1.2': 'v1.2_subdomain.sql',
'v1.3': 'v1.3_fleet.sql',
'v1.4': 'v1.4_whatsapp.sql',
'v1.5': 'v1.5_returns.sql',
'v1.6': 'v1.6_marketplace.sql',
'v1.7': 'v1.7_plates.sql',
'v1.8': 'v1.8_performance_indexes.sql',
'v1.9': 'v1.9_redis_cache.sql',
'v2.0': 'v2.0_multi_currency.sql',
'v2.1': 'v2.1_suppliers.sql',
'v2.2': 'v2.2_alerts_warranty.sql',
'v2.3': 'v2.3_metabase.sql',
'v2.4': 'v2.4_crm_enhanced.sql',
'v2.5': 'v2.5_service_orders.sql',
'v2.6': 'v2.6_bnpl_erp.sql',
'v2.7': 'v2.7_notifications.sql',
'v2.8': 'v2.8_savings.sql',
'v2.9': 'v2.9_logistics.sql',
'v3.0': 'v3.0_public_api.sql',
'v3.1': 'v3.1_inventory_vehicle_compat.sql',
'v3.2': 'v3.2_db_performance.sql',
'v3.2.1': 'v3.2_qwen_vehicle_compat.sql',
'v3.3': 'v3.3_marketplace_any_part.sql',
'v3.3.1': 'v3.3_materialized_view.sql',
'v3.4': 'v3.4_meli_integration.sql',
'v3.5': 'v3.5_meli_questions.sql',
'v3.5.1': 'v3.5_whatsapp_state_machine.sql',
'v3.6': 'v3.6_dropshipping.sql',
'v3.7': 'v3.7_sku_aliases.sql',
'v3.8': 'v3.8_supplier_catalog.sql',
'v3.9': 'v3.9_supplier_catalog_prices.sql',
'v4.0': 'v4.0_multi_branch.sql',
'v4.1': 'v4.1_global_invoice.sql',
'v4.2': 'v4.2_meli_sync_queue.sql',
'v4.3': 'v4.3_facturapi.sql',
"v1.0": "v1.0_initial.sql",
"v1.1": "v1.1_pos_tables.sql",
"v1.2": "v1.2_subdomain.sql",
"v1.3": "v1.3_fleet.sql",
"v1.4": "v1.4_whatsapp.sql",
"v1.5": "v1.5_returns.sql",
"v1.6": "v1.6_marketplace.sql",
"v1.7": "v1.7_plates.sql",
"v1.8": "v1.8_performance_indexes.sql",
"v1.9": "v1.9_redis_cache.sql",
"v2.0": "v2.0_multi_currency.sql",
"v2.1": "v2.1_suppliers.sql",
"v2.2": "v2.2_alerts_warranty.sql",
"v2.3": "v2.3_metabase.sql",
"v2.4": "v2.4_crm_enhanced.sql",
"v2.5": "v2.5_service_orders.sql",
"v2.6": "v2.6_bnpl_erp.sql",
"v2.7": "v2.7_notifications.sql",
"v2.8": "v2.8_savings.sql",
"v2.9": "v2.9_logistics.sql",
"v3.0": "v3.0_public_api.sql",
"v3.1": "v3.1_inventory_vehicle_compat.sql",
"v3.2": "v3.2_db_performance.sql",
"v3.2.1": "v3.2_qwen_vehicle_compat.sql",
"v3.3": "v3.3_marketplace_any_part.sql",
"v3.3.1": "v3.3_materialized_view.sql",
"v3.4": "v3.4_meli_integration.sql",
"v3.5": "v3.5_meli_questions.sql",
"v3.5.1": "v3.5_whatsapp_state_machine.sql",
"v3.6": "v3.6_dropshipping.sql",
"v3.7": "v3.7_sku_aliases.sql",
"v3.8": "v3.8_supplier_catalog.sql",
"v3.9": "v3.9_supplier_catalog_prices.sql",
"v4.0": "v4.0_multi_branch.sql",
"v4.1": "v4.1_global_invoice.sql",
"v4.2": "v4.2_meli_sync_queue.sql",
"v4.3": "v4.3_facturapi.sql",
}
@@ -81,9 +82,9 @@ def apply_migration(db_name, version):
sql = f.read()
# Skip migrations marked for manual/non-tenant execution
first_line = sql.splitlines()[0].strip() if sql.strip() else ''
if first_line.startswith(': SKIP') or first_line.startswith('-- : SKIP'):
print(f" SKIP (manual/non-tenant migration)")
first_line = sql.splitlines()[0].strip() if sql.strip() else ""
if first_line.startswith(": SKIP") or first_line.startswith("-- : SKIP"):
print(" SKIP (manual/non-tenant migration)")
return True
conn = get_tenant_conn_by_dbname(db_name)
@@ -116,16 +117,19 @@ def run_migrations():
if version <= current_version:
continue
print(f" Applying {version}...", end=' ')
print(f" Applying {version}...", end=" ")
if apply_migration(db_name, version):
# Update version in master
master_conn = get_master_conn()
master_cur = master_conn.cursor()
master_cur.execute("""
master_cur.execute(
"""
INSERT INTO tenant_schema_version (tenant_id, version)
VALUES (%s, %s)
ON CONFLICT (tenant_id) DO UPDATE SET version = %s, updated_at = NOW()
""", (tenant_id, version, version))
""",
(tenant_id, version, version),
)
master_conn.commit()
master_cur.close()
master_conn.close()
@@ -137,5 +141,5 @@ def run_migrations():
print("\nDone.")
if __name__ == '__main__':
if __name__ == "__main__":
run_migrations()