diff --git a/src/http/handlers.ts b/src/http/handlers.ts index 4f1354a..d90a582 100644 --- a/src/http/handlers.ts +++ b/src/http/handlers.ts @@ -12,7 +12,6 @@ import { import { isValidDate } from '../domain/calendar.ts'; import { limaTime, type Clock } from '../domain/clock.ts'; import { quotePriceCents } from '../domain/pricing.ts'; -import { dailySummary } from '../domain/report.ts'; import type { Notifier } from '../notifications/notifier.ts'; import type { BookingService } from '../services/booking-service.ts'; import type { BookingRow } from '../store/booking-repository.ts'; @@ -159,8 +158,7 @@ export function dailyReport(deps: AppDependencies): Handler { if (!isValidDate(date)) { throw new HttpError(400, 'INVALID_DATE', 'Indiquen la fecha con el formato AAAA-MM-DD (?date=2026-03-14).'); } - const bookings = deps.service.bookingsOn(date).map(toBookingJson); - sendJson(res, 200, dailySummary(date, deps.service.listCourts(), bookings)); + sendJson(res, 200, deps.service.dailySummary(date)); }; } diff --git a/src/services/booking-service.ts b/src/services/booking-service.ts index 55e62cf..e196bbf 100644 --- a/src/services/booking-service.ts +++ b/src/services/booking-service.ts @@ -4,14 +4,18 @@ import type { Member } from '../domain/member.ts'; import { BookingRepository, type BookingRow, type NewBookingRow } from '../store/booking-repository.ts'; import { CourtRepository } from '../store/court-repository.ts'; import { MemberRepository } from '../store/member-repository.ts'; +import { dailySummarySql } from '../store/report-sql.ts'; +import type { DailySummary } from '../domain/report.ts'; // Punto de entrada a los datos de reservas para la capa HTTP. export class BookingService { #courts: CourtRepository; #members: MemberRepository; #bookings: BookingRepository; + #db: DatabaseSync; constructor(db: DatabaseSync) { + this.#db = db; this.#courts = new CourtRepository(db); this.#members = new MemberRepository(db); this.#bookings = new BookingRepository(db); @@ -53,6 +57,10 @@ export class BookingService { return this.#bookings.insert(row); } + dailySummary(date: string): DailySummary { + return dailySummarySql(this.#db, date); + } + markCancelled(id: number, cancelledAt: string): void { this.#bookings.updateStatus(id, 'cancelled', cancelledAt); } diff --git a/src/store/report-sql.ts b/src/store/report-sql.ts new file mode 100644 index 0000000..9b30fae --- /dev/null +++ b/src/store/report-sql.ts @@ -0,0 +1,25 @@ +import type { DatabaseSync } from 'node:sqlite'; +import type { CourtSummary, DailySummary } from '../domain/report.ts'; + +export function dailySummarySql(db: DatabaseSync, date: string): DailySummary { + const courts = db + .prepare( + `SELECT c.id AS courtId, c.name AS courtName, + COUNT(*) AS bookings, + SUM(b.hours) AS hours, + SUM(b.price_cents) AS revenueCents + FROM bookings b + JOIN courts c ON c.id = b.court_id + WHERE b.date = ? AND b.status = 'confirmed' + GROUP BY c.id + ORDER BY c.id`, + ) + .all(date) as CourtSummary[]; + return { + date, + bookings: courts.reduce((total, c) => total + c.bookings, 0), + hours: courts.reduce((total, c) => total + c.hours, 0), + revenueCents: courts.reduce((total, c) => total + c.revenueCents, 0), + courts: courts.map((c) => ({ ...c })), + }; +}