pm-dashboard/airtable_client.py
José Manuel Gómez 7f0b08d3dd Añade login con Google y CRUD de proyectos/tareas
Restringe el acceso a colaboradores conocidos en Airtable, añade una
vista de tareas propias filtrable por fecha, y permite crear, editar
y borrar proyectos y tareas desde el dashboard.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-05 20:39:32 +02:00

153 lines
5.9 KiB
Python

from pyairtable import Api
import config
def _collaborator(value):
if isinstance(value, dict):
return {
"id": value.get("id", ""),
"name": value.get("name") or value.get("email") or "",
"email": value.get("email", ""),
}
return None
def _collaborators(values):
if not values:
return []
return [c for c in (_collaborator(v) for v in values) if c]
class AirtableClient:
def __init__(self):
self.api = Api(config.AIRTABLE_TOKEN)
self.projects = self.api.table(config.AIRTABLE_BASE_ID, config.PROJECTS_TABLE)
self.tasks = self.api.table(config.AIRTABLE_BASE_ID, config.TASKS_TABLE)
self.clients = self.api.table(config.AIRTABLE_BASE_ID, config.CLIENTS_TABLE)
self.areas = self.api.table(config.AIRTABLE_BASE_ID, config.AREAS_TABLE)
self.products = self.api.table(config.AIRTABLE_BASE_ID, config.PRODUCTS_TABLE)
def get_projects(self) -> list[dict]:
records = self.projects.all(fields=[
"Project", "Tags", "Status", "Client", "Project lead",
"Working team", "Kickoff date", "Due date", "Budget",
"Internal", "Code", "📝 Tasks",
])
result = []
for r in records:
f = r["fields"]
project_lead = _collaborator(f.get("Project lead"))
working_team = _collaborators(f.get("Working team"))
result.append({
"id": r["id"],
"project": f.get("Project", ""),
"tags": f.get("Tags", []),
"status": f.get("Status", ""),
"client_ids": f.get("Client", []),
"project_lead": project_lead,
"project_lead_name": project_lead["name"] if project_lead else "",
"working_team": working_team,
"working_team_names": [c["name"] for c in working_team],
"kickoff_date": f.get("Kickoff date"),
"due_date": f.get("Due date"),
"budget": f.get("Budget", 0),
"internal": f.get("Internal", False),
"code": f.get("Code", ""),
"task_ids": f.get("📝 Tasks", []),
})
return result
def get_tasks(self) -> list[dict]:
records = self.tasks.all(fields=[
"Task", "Project", "Area", "Status Task", "Assigned to",
"Kick off", "Due date", "Days to complete", "Horas estimadas",
"Prioridad", "INTERNO/EXTERNO", "Recurrencia",
"Client (from Project)",
])
result = []
for r in records:
f = r["fields"]
assigned_to = _collaborators(f.get("Assigned to"))
result.append({
"id": r["id"],
"task": f.get("Task", ""),
"project_ids": f.get("Project", []),
"area_ids": f.get("Area", []),
"status": f.get("Status Task", ""),
"assigned_to": assigned_to,
"assigned_to_names": [c["name"] for c in assigned_to],
"kick_off": f.get("Kick off"),
"due_date": f.get("Due date"),
"days_to_complete": f.get("Days to complete"),
"horas_estimadas": f.get("Horas estimadas", 0),
"prioridad": f.get("Prioridad", ""),
"interno_externo": f.get("INTERNO/EXTERNO", ""),
"recurrencia": f.get("Recurrencia", ""),
"client_names": f.get("Client (from Project)", []),
})
return result
def get_clients(self) -> list[dict]:
records = self.clients.all(fields=["Display Name", "Company Name", "Projects", "Tasks"])
result = []
for r in records:
f = r["fields"]
result.append({
"id": r["id"],
"display_name": f.get("Display Name", ""),
"company_name": f.get("Company Name", ""),
"project_ids": f.get("Projects", []),
"task_ids": f.get("Tasks", []),
})
return result
def get_areas(self) -> list[dict]:
records = self.areas.all(fields=["Área", "Descripción", "Tasks"])
result = []
for r in records:
f = r["fields"]
result.append({
"id": r["id"],
"area": f.get("Área", ""),
"descripcion": f.get("Descripción", ""),
"task_ids": f.get("Tasks", []),
})
return result
@staticmethod
def get_known_collaborators(projects: list[dict], tasks: list[dict]) -> dict:
"""Colaboradores conocidos por haber aparecido en algún proyecto o tarea, indexados por email."""
known = {}
for p in projects:
candidates = list(p["working_team"])
if p["project_lead"]:
candidates.append(p["project_lead"])
for c in candidates:
if c["email"]:
known[c["email"]] = c
for t in tasks:
for c in t["assigned_to"]:
if c["email"]:
known[c["email"]] = c
return known
# ── Escritura ─────────────────────────────────────────────────────────── #
def create_project(self, fields: dict) -> dict:
return self.projects.create(fields, typecast=True)
def update_project(self, record_id: str, fields: dict) -> dict:
return self.projects.update(record_id, fields, typecast=True)
def delete_project(self, record_id: str) -> None:
self.projects.delete(record_id)
def create_task(self, fields: dict) -> dict:
return self.tasks.create(fields, typecast=True)
def update_task(self, record_id: str, fields: dict) -> dict:
return self.tasks.update(record_id, fields, typecast=True)
def delete_task(self, record_id: str) -> None:
self.tasks.delete(record_id)