import { Router } from "express"; import { db } from "../db.ts"; import type { AuthedRequest } from "../lib/auth.ts"; import { authRequired } from "../lib/auth.ts"; import { bizDayBoundsSqlite, bizDayBoundsIso, toIsoUtc } from "../lib/time.ts"; export const meRouter = Router(); meRouter.use(authRequired); const DEFAULT_TZ = "America/Mexico_City"; /** Fetch the business timezone once per handler. */ function bizTz(bid: number | null): string { const row = db.prepare(`SELECT timezone t FROM businesses WHERE id = ?`).get(bid) as { t?: string } | undefined; return row?.t || DEFAULT_TZ; } const EMPTY_PERF = { appointments_today: 0, upcoming: 0, revenue_30d: 0, avg_rating: 0, share_pct: 0, next_appointment: null, }; /** Employee self-service analytics, tz-correct via the business timezone. */ meRouter.get("/performance", (req: AuthedRequest, res) => { const eid = req.user!.employee_id; const bid = req.user!.business_id; if (!eid) return res.json(EMPTY_PERF); const tz = bizTz(bid); // start_at is stored ISO-Z → pair with ISO bounds. const todayIso = bizDayBoundsIso(tz, 0); const rangeStartIso = bizDayBoundsIso(tz, -30).start; const nowIso = toIsoUtc(new Date()); // created_at is stored in a mixed format → wrap with datetime() and use SQLite bounds. const rangeStartSql = bizDayBoundsSqlite(tz, -30).start; const ratingStartSql = bizDayBoundsSqlite(tz, -90).start; const scalar = (sql: string, ...args: any[]) => (db.prepare(sql).get(...args) as any)?.v ?? 0; const appointments_today = scalar( `SELECT COUNT(*) v FROM appointments WHERE employee_id = ? AND start_at >= ? AND start_at <= ? AND status != 'cancelled'`, eid, todayIso.start, todayIso.end ); const upcoming = scalar( `SELECT COUNT(*) v FROM appointments WHERE employee_id = ? AND status = 'scheduled' AND start_at >= ?`, eid, nowIso ); const revenue_30d = scalar( `SELECT COALESCE(SUM(amount + tip), 0) v FROM tickets WHERE employee_id = ? AND datetime(created_at) >= ?`, eid, rangeStartSql ); // Avg of last-90d reviews, falling back to the employee's stored rating. const avg_rating = scalar( `SELECT COALESCE( (SELECT AVG(rating) FROM reviews WHERE employee_id = ? AND datetime(created_at) >= ?), (SELECT rating FROM employees WHERE id = ?) ) v`, eid, ratingStartSql, eid ); const empAppts = scalar( `SELECT COUNT(*) v FROM appointments WHERE employee_id = ? AND start_at >= ?`, eid, rangeStartIso ); const bizAppts = scalar( `SELECT COUNT(*) v FROM appointments WHERE business_id = ? AND start_at >= ?`, bid, rangeStartIso ); const share_pct = bizAppts > 0 ? Math.round((empAppts / bizAppts) * 100) : 0; const next = db .prepare( `SELECT a.start_at, s.name service_name, c.name client_name FROM appointments a JOIN services s ON s.id = a.service_id JOIN clients c ON c.id = a.client_id WHERE a.employee_id = ? AND a.status = 'scheduled' AND a.start_at >= ? ORDER BY a.start_at ASC LIMIT 1` ) .get(eid, nowIso) as { start_at: string; service_name: string; client_name: string } | undefined; res.json({ appointments_today, upcoming, revenue_30d: Math.round(revenue_30d * 100) / 100, avg_rating: Math.round((avg_rating ?? 0) * 10) / 10, share_pct, next_appointment: next ? { start_at: next.start_at, service_name: next.service_name, client_name: next.client_name } : null, }); }); /** Per-ticket commissions for the signed-in employee, last 30 business-local days. */ meRouter.get("/commissions", (req: AuthedRequest, res) => { const eid = req.user!.employee_id; if (!eid) return res.json({ rows: [], total: 0 }); const bid = req.user!.business_id; const tz = bizTz(bid); const rangeStartSql = bizDayBoundsSqlite(tz, -30).start; const rows = db .prepare( `SELECT t.appointment_id, s.name service_name, c.name client_name, t.amount, t.commission, t.created_at FROM tickets t JOIN services s ON s.id = t.service_id JOIN clients c ON c.id = t.client_id WHERE t.employee_id = ? AND datetime(t.created_at) >= ? ORDER BY datetime(t.created_at) DESC LIMIT 100` ) .all(eid, rangeStartSql) as any[]; const out = rows.map((r) => ({ appointment_id: r.appointment_id, service_name: r.service_name, client_name: r.client_name, amount: Math.round(Number(r.amount) * 100) / 100, commission: Math.round(Number(r.commission) * 100) / 100, created_at: r.created_at, })); const total = Math.round(out.reduce((a, r) => a + r.commission, 0) * 100) / 100; res.json({ rows: out, total }); });