- F0 blindaje: Redis/Celery, pg_hba sin trust LAN, secretos a env 600, backups, webhook auth, jobs muertos - F1 caja: bugs POS, reembolsos al corte, apartados, CFDI, topes de descuento en cotizacion/apartado - F2/F7 IA de catalogo: autocategorizacion, dedup de SKUs con embeddings, OCR de facturas, descripciones, claves SAT, limpieza de fitment - F3 IA en el flujo: vision Yonke, preguntas MeLi, venta cruzada, anomalias, sinonimos, busqueda semantica (rerank/embeddings) - F4/F8 escala y cartera: /metrics+Prometheus+Grafana, pool, score de credito, churn, metricas multi-tenant - Nexus Yonke (M1-M6): modelo de datos 1-de-1, alta por donador, vitrina publica, MeLi usado, consignacion, garantia por dias, reportes, etiquetas - Marketplace B2B entre yonkes (catalogo + pedidos cross-tenant) - Seguridad: auth en /api/admin/* (35 endpoints), alertas por tenant - Migraciones v4.27/v4.28; timers expire-demos y rollup-metrics
67 lines
2.0 KiB
Python
67 lines
2.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Expire demo tenants whose trial has ended.
|
|
|
|
A tenant on plan='demo' is a time-limited full trial (no resource limits).
|
|
When its active subscription's expires_at passes, this job downgrades it to
|
|
'free' and marks the subscription as expired.
|
|
|
|
Safe by default: runs in DRY-RUN and only reports. Pass --apply to enforce.
|
|
"""
|
|
import argparse
|
|
import os
|
|
|
|
import psycopg2
|
|
|
|
MASTER_DB_URL = os.environ.get(
|
|
'MASTER_DB_URL', 'postgresql://postgres@localhost/nexus_autoparts'
|
|
)
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description='Expire ended demo tenants.')
|
|
ap.add_argument('--apply', action='store_true',
|
|
help='Actually downgrade expired demos (default: dry-run)')
|
|
args = ap.parse_args()
|
|
|
|
conn = psycopg2.connect(MASTER_DB_URL)
|
|
cur = conn.cursor()
|
|
cur.execute("""
|
|
SELECT t.id, t.name, s.expires_at
|
|
FROM tenants t
|
|
JOIN subscriptions s ON s.tenant_id = t.id
|
|
WHERE t.plan = 'demo'
|
|
AND t.is_active = true
|
|
AND s.status = 'active'
|
|
AND s.expires_at IS NOT NULL
|
|
AND s.expires_at < NOW()
|
|
ORDER BY t.id
|
|
""")
|
|
rows = cur.fetchall()
|
|
|
|
if not rows:
|
|
print('No expired demo tenants.')
|
|
else:
|
|
print(f'{len(rows)} demo tenant(s) with an expired subscription:')
|
|
for tid, name, exp in rows:
|
|
print(f' #{tid} {name} (expiró {exp:%Y-%m-%d})')
|
|
|
|
if args.apply:
|
|
for tid, _name, _exp in rows:
|
|
cur.execute("UPDATE tenants SET plan = 'free' WHERE id = %s", (tid,))
|
|
cur.execute(
|
|
"UPDATE subscriptions SET status = 'expired' "
|
|
"WHERE tenant_id = %s AND status = 'active'",
|
|
(tid,),
|
|
)
|
|
conn.commit()
|
|
print(f'Applied: {len(rows)} tenant(s) downgraded to free.')
|
|
else:
|
|
print('DRY-RUN: nothing changed. Re-run with --apply to enforce.')
|
|
|
|
cur.close()
|
|
conn.close()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|