import { Router } from "express"; import { db } from "../db.ts"; import type { AuthedRequest } from "../lib/auth.ts"; import { err, ownerOnly } from "../lib/auth.ts"; export const cashRouter = Router(); cashRouter.use(ownerOnly); function sessionStats(sessionId: number, businessId: number) { const row = db .prepare( `SELECT COALESCE(SUM(CASE WHEN type='income' THEN amount ELSE 0 END),0) income, COALESCE(SUM(CASE WHEN type='expense' THEN amount ELSE 0 END),0) expense, COUNT(*) n FROM cash_entries WHERE session_id = ? AND business_id = ?` ) .get(sessionId, businessId) as { income: number; expense: number; n: number }; const tickets = db .prepare( `SELECT COALESCE(SUM(amount),0) s, COUNT(*) n FROM cash_entries WHERE session_id = ? AND business_id = ? AND ticket_id IS NOT NULL` ) .get(sessionId, businessId) as { s: number; n: number }; return { income: Math.round(row.income * 100) / 100, expense: Math.round(row.expense * 100) / 100, entries: row.n, ticket_income: Math.round(tickets.s * 100) / 100, ticket_count: tickets.n, }; } cashRouter.get("/session", (req: AuthedRequest, res) => { const bid = req.user!.business_id as number; const s = db .prepare(`SELECT * FROM cash_sessions WHERE business_id = ? AND status = 'open' ORDER BY opened_at DESC LIMIT 1`) .get(bid) as any; if (!s) return res.json({ session: null }); res.json({ session: s, stats: sessionStats(s.id, bid) }); }); cashRouter.get("/sessions", (req: AuthedRequest, res) => { const bid = req.user!.business_id as number; const limit = Math.min(60, Number(req.query.limit) || 30); const rows = db .prepare(`SELECT * FROM cash_sessions WHERE business_id = ? ORDER BY opened_at DESC LIMIT ?`) .all(bid, limit) as any[]; const out = rows.map((s) => ({ session: s, stats: sessionStats(s.id, bid) })); res.json({ sessions: out }); }); cashRouter.post("/open", (req: AuthedRequest, res) => { const bid = req.user!.business_id as number; const open = db.prepare(`SELECT id FROM cash_sessions WHERE business_id = ? AND status = 'open'`).get(bid); if (open) return err(res, 400, "Ya hay una caja abierta — ciérrala primero"); const { opening_amount = 0, note } = req.body ?? {}; const s = db .prepare( `INSERT INTO cash_sessions (business_id, opened_by_user_id, opening_amount, note) VALUES (?, ?, ?, ?) RETURNING *` ) .get(bid, req.user!.id, Number(opening_amount) || 0, note || null) as any; res.status(201).json({ session: s, stats: sessionStats(s.id, bid) }); }); cashRouter.post("/close", (req: AuthedRequest, res) => { const bid = req.user!.business_id as number; const s = db.prepare(`SELECT * FROM cash_sessions WHERE business_id = ? AND status = 'open'`).get(bid) as any; if (!s) return err(res, 400, "No hay caja abierta"); const { closing_amount, note } = req.body ?? {}; const stats = sessionStats(s.id, bid); const expected = Number(s.opening_amount) + stats.income - stats.expense; db.prepare(`UPDATE cash_sessions SET status='closed', closed_at=datetime('now'), closing_amount=?, expected_amount=?, note=COALESCE(?,note) WHERE id=?`).run( Number(closing_amount) || 0, Math.round(expected * 100) / 100, note || null, s.id ); const updated = db.prepare(`SELECT * FROM cash_sessions WHERE id=?`).get(s.id) as any; res.json({ session: updated, stats, expected: Math.round(expected * 100) / 100 }); }); cashRouter.get("/entries", (req: AuthedRequest, res) => { const bid = req.user!.business_id as number; const sessionId = req.query.session_id ? Number(req.query.session_id) : null; let rows: any[]; if (sessionId) { rows = db.prepare(`SELECT * FROM cash_entries WHERE business_id = ? AND session_id = ? ORDER BY created_at DESC`).all(bid, sessionId); } else { const s = db.prepare(`SELECT id FROM cash_sessions WHERE business_id = ? AND status='open'`).get(bid) as any; rows = s ? db.prepare(`SELECT * FROM cash_entries WHERE business_id = ? AND session_id = ? ORDER BY created_at DESC`).all(bid, s.id) : []; } res.json({ entries: rows }); }); cashRouter.post("/entries", (req: AuthedRequest, res) => { const bid = req.user!.business_id as number; const s = db.prepare(`SELECT id FROM cash_sessions WHERE business_id = ? AND status='open'`).get(bid) as any; if (!s) return err(res, 400, "No hay caja abierta"); const { type, amount, concept, payment_method } = req.body ?? {}; if (!["income", "expense"].includes(type)) return err(res, 400, "Tipo inválido (income/expense)"); if (!amount || !concept) return err(res, 400, "Monto y concepto son obligatorios"); const r = db .prepare( `INSERT INTO cash_entries (business_id, session_id, type, amount, concept, payment_method) VALUES (?, ?, ?, ?, ?, ?) RETURNING *` ) .get(bid, s.id, type, Number(amount), concept, payment_method || null) as any; res.status(201).json({ entry: r, stats: sessionStats(s.id, bid) }); }); cashRouter.delete("/entries/:id", (req: AuthedRequest, res) => { const bid = req.user!.business_id as number; const id = Number(req.params.id); const e = db.prepare(`SELECT session_id FROM cash_entries WHERE id = ? AND business_id = ?`).get(id, bid) as any; if (!e) return err(res, 404, "Movimiento no encontrado"); db.prepare(`DELETE FROM cash_entries WHERE id = ?`).run(id); res.json({ ok: true, stats: sessionStats(e.session_id, bid) }); });