diff --git a/src/app/api/reports/accounts/[id]/entries/route.ts b/src/app/api/reports/accounts/[id]/entries/route.ts new file mode 100644 index 0000000..9d0fd0d --- /dev/null +++ b/src/app/api/reports/accounts/[id]/entries/route.ts @@ -0,0 +1,52 @@ +/** + * GET /api/reports/accounts/[id]/entries + * + * Drill-down: all POSTED journal entry lines for a specific account. + * Shows entry details with running balance. + * + * Query params: + * startDate? - ISO date string + * endDate? - ISO date string + * + * Access: ADMIN, OFFICE_STAFF. + */ +import { NextRequest, NextResponse } from "next/server"; +import { withPermission } from "@/lib/middleware/authorize"; +import { withTenantContext } from "@/lib/prisma-tenant"; +import { FinancialReportService } from "@/lib/services/financial-report-service"; + +export function GET( + req: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + return withPermission("read", "Report")( + async (innerReq: NextRequest, { user }) => { + if (!user.tenantId) { + return NextResponse.json( + { error: "No tenant context -- super-admins must use the admin API" }, + { status: 400 } + ); + } + + const { id: accountId } = await params; + + const { searchParams } = new URL(innerReq.url); + const startDateParam = searchParams.get("startDate"); + const endDateParam = searchParams.get("endDate"); + + const tenantPrisma = withTenantContext(user.tenantId); + + try { + const report = await FinancialReportService.getAccountEntries(tenantPrisma, { + accountId, + startDate: startDateParam ? new Date(startDateParam) : undefined, + endDate: endDateParam ? new Date(endDateParam) : undefined, + }); + return NextResponse.json(report); + } catch (err) { + const message = err instanceof Error ? err.message : "Failed to get account entries"; + return NextResponse.json({ error: message }, { status: 400 }); + } + } + )(req); +} diff --git a/src/app/api/reports/balance-sheet/route.ts b/src/app/api/reports/balance-sheet/route.ts new file mode 100644 index 0000000..d7a74ab --- /dev/null +++ b/src/app/api/reports/balance-sheet/route.ts @@ -0,0 +1,57 @@ +/** + * GET /api/reports/balance-sheet + * + * Balance Sheet -- assets = liabilities + equity as of a date. + * Derived entirely from POSTED journal entry lines. + * + * Query params: + * asOfDate - ISO date string (required) + * + * Access: ADMIN only. + */ +import { NextRequest, NextResponse } from "next/server"; +import { withPermission } from "@/lib/middleware/authorize"; +import { withTenantContext } from "@/lib/prisma-tenant"; +import { FinancialReportService } from "@/lib/services/financial-report-service"; + +export const GET = withPermission("read", "Report")( + async (req: NextRequest, { user }) => { + if (!user.tenantId) { + return NextResponse.json( + { error: "No tenant context -- super-admins must use the admin API" }, + { status: 400 } + ); + } + + const { searchParams } = new URL(req.url); + const asOfDateParam = searchParams.get("asOfDate"); + + if (!asOfDateParam) { + return NextResponse.json( + { error: "asOfDate query parameter is required" }, + { status: 400 } + ); + } + + const asOfDate = new Date(asOfDateParam); + + if (isNaN(asOfDate.getTime())) { + return NextResponse.json( + { error: "asOfDate must be a valid ISO date string" }, + { status: 400 } + ); + } + + const tenantPrisma = withTenantContext(user.tenantId); + + try { + const report = await FinancialReportService.getBalanceSheet(tenantPrisma, { + asOfDate, + }); + return NextResponse.json(report); + } catch (err) { + const message = err instanceof Error ? err.message : "Failed to generate balance sheet"; + return NextResponse.json({ error: message }, { status: 500 }); + } + } +); diff --git a/src/app/api/reports/income-statement/route.ts b/src/app/api/reports/income-statement/route.ts new file mode 100644 index 0000000..84c4e48 --- /dev/null +++ b/src/app/api/reports/income-statement/route.ts @@ -0,0 +1,61 @@ +/** + * GET /api/reports/income-statement + * + * Income Statement -- revenue minus expenses for a date range. + * Derived entirely from POSTED journal entry lines. + * + * Query params: + * startDate - ISO date string (required) + * endDate - ISO date string (required) + * + * Access: ADMIN only. + */ +import { NextRequest, NextResponse } from "next/server"; +import { withPermission } from "@/lib/middleware/authorize"; +import { withTenantContext } from "@/lib/prisma-tenant"; +import { FinancialReportService } from "@/lib/services/financial-report-service"; + +export const GET = withPermission("read", "Report")( + async (req: NextRequest, { user }) => { + if (!user.tenantId) { + return NextResponse.json( + { error: "No tenant context -- super-admins must use the admin API" }, + { status: 400 } + ); + } + + const { searchParams } = new URL(req.url); + const startDateParam = searchParams.get("startDate"); + const endDateParam = searchParams.get("endDate"); + + if (!startDateParam || !endDateParam) { + return NextResponse.json( + { error: "startDate and endDate query parameters are required" }, + { status: 400 } + ); + } + + const startDate = new Date(startDateParam); + const endDate = new Date(endDateParam); + + if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) { + return NextResponse.json( + { error: "startDate and endDate must be valid ISO date strings" }, + { status: 400 } + ); + } + + const tenantPrisma = withTenantContext(user.tenantId); + + try { + const report = await FinancialReportService.getIncomeStatement(tenantPrisma, { + startDate, + endDate, + }); + return NextResponse.json(report); + } catch (err) { + const message = err instanceof Error ? err.message : "Failed to generate income statement"; + return NextResponse.json({ error: message }, { status: 500 }); + } + } +); diff --git a/src/app/api/reports/trial-balance/route.ts b/src/app/api/reports/trial-balance/route.ts new file mode 100644 index 0000000..070c9d1 --- /dev/null +++ b/src/app/api/reports/trial-balance/route.ts @@ -0,0 +1,41 @@ +/** + * GET /api/reports/trial-balance + * + * Trial Balance report -- all account balances, verifies debits = credits. + * Derived entirely from POSTED journal entry lines. + * + * Query params: + * asOfDate? - ISO date string (default: now) + * + * Access: ADMIN only. + */ +import { NextRequest, NextResponse } from "next/server"; +import { withPermission } from "@/lib/middleware/authorize"; +import { withTenantContext } from "@/lib/prisma-tenant"; +import { FinancialReportService } from "@/lib/services/financial-report-service"; + +export const GET = withPermission("read", "Report")( + async (req: NextRequest, { user }) => { + if (!user.tenantId) { + return NextResponse.json( + { error: "No tenant context -- super-admins must use the admin API" }, + { status: 400 } + ); + } + + const { searchParams } = new URL(req.url); + const asOfDateParam = searchParams.get("asOfDate"); + + const tenantPrisma = withTenantContext(user.tenantId); + + try { + const report = await FinancialReportService.getTrialBalance(tenantPrisma, { + asOfDate: asOfDateParam ? new Date(asOfDateParam) : undefined, + }); + return NextResponse.json(report); + } catch (err) { + const message = err instanceof Error ? err.message : "Failed to generate trial balance"; + return NextResponse.json({ error: message }, { status: 500 }); + } + } +);