Primer commit
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
import argparse
|
||||
import os
|
||||
import sqlite3
|
||||
import sys
|
||||
|
||||
|
||||
import sys as _sys, os as _os
|
||||
_sys.path.insert(0, _os.path.dirname(_os.path.dirname(_os.path.abspath(__file__))))
|
||||
from paths import DB_PATH as DB_PATH
|
||||
BRAND_LOCATION_ID = "GbKkBpCmKu2QmloKFHy3"
|
||||
TRACKED_STATUSES = ("open", "won", "lost", "abandoned")
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Resume oportunidades por sucursal y estado usando la base SQLite local."
|
||||
)
|
||||
parser.add_argument("--location", help="ID de una sucursal especifica")
|
||||
parser.add_argument("--include-main", action="store_true", help="Incluye la cuenta de marca principal")
|
||||
parser.add_argument("--include-demo", action="store_true", help="Incluye cuentas cuyo nombre contiene 'demo'")
|
||||
parser.add_argument("--show-empty", action="store_true", help="Muestra sucursales sin oportunidades")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def status_key(status):
|
||||
return (status or "open").strip().lower() or "open"
|
||||
|
||||
|
||||
def build_accounts_query(args):
|
||||
where = []
|
||||
params = []
|
||||
|
||||
if args.location:
|
||||
where.append("a.location_id = ?")
|
||||
params.append(args.location)
|
||||
elif not args.include_main:
|
||||
where.append("a.location_id != ?")
|
||||
params.append(BRAND_LOCATION_ID)
|
||||
|
||||
if not args.include_demo:
|
||||
where.append("LOWER(a.nombre) NOT LIKE ?")
|
||||
params.append("%demo%")
|
||||
|
||||
sql = """
|
||||
SELECT a.location_id, a.nombre, a.type
|
||||
FROM accounts a
|
||||
"""
|
||||
if where:
|
||||
sql += " WHERE " + " AND ".join(where)
|
||||
sql += " ORDER BY a.nombre COLLATE NOCASE"
|
||||
return sql, params
|
||||
|
||||
|
||||
def summarize(args):
|
||||
if not os.path.exists(DB_PATH):
|
||||
print(f"Error: La base de datos local no existe en {DB_PATH}.")
|
||||
print("Ejecuta primero la sincronizacion global desde el dashboard.")
|
||||
sys.exit(1)
|
||||
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
conn.row_factory = sqlite3.Row
|
||||
|
||||
try:
|
||||
accounts_sql, account_params = build_accounts_query(args)
|
||||
accounts = conn.execute(accounts_sql, account_params).fetchall()
|
||||
if not accounts:
|
||||
print("No se encontraron sucursales con los filtros indicados.")
|
||||
return 0
|
||||
|
||||
summaries = []
|
||||
unknown_statuses = {}
|
||||
totals = {status: 0 for status in TRACKED_STATUSES}
|
||||
totals["other"] = 0
|
||||
totals["total"] = 0
|
||||
|
||||
for account in accounts:
|
||||
rows = conn.execute(
|
||||
"""
|
||||
SELECT status, COUNT(*) AS count
|
||||
FROM opportunities
|
||||
WHERE location_id = ?
|
||||
GROUP BY status
|
||||
""",
|
||||
(account["location_id"],),
|
||||
).fetchall()
|
||||
|
||||
counts = {status: 0 for status in TRACKED_STATUSES}
|
||||
other = 0
|
||||
for row in rows:
|
||||
key = status_key(row["status"])
|
||||
count = row["count"] or 0
|
||||
if key in counts:
|
||||
counts[key] += count
|
||||
else:
|
||||
other += count
|
||||
unknown_statuses[key] = unknown_statuses.get(key, 0) + count
|
||||
|
||||
total = sum(counts.values()) + other
|
||||
if total == 0 and not args.show_empty:
|
||||
continue
|
||||
|
||||
for status in TRACKED_STATUSES:
|
||||
totals[status] += counts[status]
|
||||
totals["other"] += other
|
||||
totals["total"] += total
|
||||
|
||||
summaries.append({
|
||||
"name": account["nombre"],
|
||||
"location_id": account["location_id"],
|
||||
"type": account["type"],
|
||||
"counts": counts,
|
||||
"other": other,
|
||||
"total": total,
|
||||
})
|
||||
|
||||
print("=== RESUMEN GLOBAL DE OPORTUNIDADES POR SUCURSAL ===")
|
||||
print("Fuente: SQLite local (ultima sincronizacion del dashboard)")
|
||||
print(f"Cuentas incluidas: {len(summaries)}")
|
||||
print(f"Marca incluida: {'si' if args.include_main else 'no'}")
|
||||
print(f"Demos incluidas: {'si' if args.include_demo else 'no'}")
|
||||
if args.location:
|
||||
print(f"Filtro location: {args.location}")
|
||||
print()
|
||||
|
||||
header = f"{'Sucursal':<36} {'Abiertas':>9} {'Ganadas':>9} {'Perdidas':>9} {'Abandon.':>9} {'Otros':>7} {'Total':>7}"
|
||||
print(header)
|
||||
print("-" * len(header))
|
||||
|
||||
for item in summaries:
|
||||
counts = item["counts"]
|
||||
name = item["name"][:36]
|
||||
print(
|
||||
f"{name:<36} {counts['open']:>9} {counts['won']:>9} "
|
||||
f"{counts['lost']:>9} {counts['abandoned']:>9} {item['other']:>7} {item['total']:>7}"
|
||||
)
|
||||
|
||||
print("-" * len(header))
|
||||
print(
|
||||
f"{'TOTAL':<36} {totals['open']:>9} {totals['won']:>9} "
|
||||
f"{totals['lost']:>9} {totals['abandoned']:>9} {totals['other']:>7} {totals['total']:>7}"
|
||||
)
|
||||
|
||||
if unknown_statuses:
|
||||
print("\nEstados no estandar encontrados:")
|
||||
for status, count in sorted(unknown_statuses.items(), key=lambda item: item[0]):
|
||||
print(f" {status}: {count}")
|
||||
|
||||
return 0
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if sys.platform == "win32":
|
||||
try:
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
except AttributeError:
|
||||
pass
|
||||
sys.exit(summarize(parse_args()))
|
||||
Reference in New Issue
Block a user