import type {FastifyRequest, FastifyReply} from 'fastify'; import {InvoiceService} from '../services/InvoiceService'; import {getUserId} from '../middleware/auth'; import {z} from 'zod'; const lineItemSchema = z.object({ description: z.string().min(1), quantity: z.number().min(1), unitPrice: z.number().min(0), amount: z.number().min(0), }); const createInvoiceSchema = z.object({ clientId: z.string().uuid(), issueDate: z.string().transform(str => new Date(str)), dueDate: z.string().transform(str => new Date(str)), lineItems: z.array(lineItemSchema).min(1), notes: z.string().optional(), terms: z.string().optional(), }); const updateInvoiceSchema = z.object({ issueDate: z.string().transform(str => new Date(str)).optional(), dueDate: z.string().transform(str => new Date(str)).optional(), lineItems: z.array(lineItemSchema).min(1).optional(), notes: z.string().optional(), terms: z.string().optional(), }); const updateStatusSchema = z.object({ status: z.enum(['DRAFT', 'SENT', 'PAID', 'OVERDUE', 'CANCELLED']), }); /** * Controller for Invoice endpoints * Implements Single Responsibility Principle - handles only HTTP layer */ export class InvoiceController { constructor(private invoiceService: InvoiceService) {} /** * Create a new invoice */ async create(request: FastifyRequest, reply: FastifyReply) { const userId = getUserId(request); const data = createInvoiceSchema.parse(request.body); const invoice = await this.invoiceService.create(userId, data); return reply.status(201).send({invoice}); } /** * Get all invoices for the authenticated user */ async getAll(request: FastifyRequest, reply: FastifyReply) { const userId = getUserId(request); const {clientId, status} = request.query as {clientId?: string; status?: string}; const invoices = await this.invoiceService.getAllByUser(userId, { clientId, status, }); return reply.send({invoices}); } /** * Get a single invoice by ID */ async getOne(request: FastifyRequest, reply: FastifyReply) { const userId = getUserId(request); const {id} = request.params as {id: string}; const invoice = await this.invoiceService.getById(id, userId); return reply.send({invoice}); } /** * Update an invoice */ async update(request: FastifyRequest, reply: FastifyReply) { const userId = getUserId(request); const {id} = request.params as {id: string}; const data = updateInvoiceSchema.parse(request.body); const invoice = await this.invoiceService.update(id, userId, data); return reply.send({invoice}); } /** * Update invoice status */ async updateStatus(request: FastifyRequest, reply: FastifyReply) { const userId = getUserId(request); const {id} = request.params as {id: string}; const {status} = updateStatusSchema.parse(request.body); const invoice = await this.invoiceService.updateStatus(id, userId, status); return reply.send({invoice}); } /** * Delete an invoice */ async delete(request: FastifyRequest, reply: FastifyReply) { const userId = getUserId(request); const {id} = request.params as {id: string}; await this.invoiceService.delete(id, userId); return reply.status(204).send(); } /** * Get invoice statistics */ async getStats(request: FastifyRequest, reply: FastifyReply) { const userId = getUserId(request); const stats = await this.invoiceService.getStats(userId); return reply.send({stats}); } /** * Get overdue invoices */ async getOverdue(request: FastifyRequest, reply: FastifyReply) { const userId = getUserId(request); const overdueInvoices = await this.invoiceService.getOverdueInvoices(userId); return reply.send({invoices: overdueInvoices}); } }