import express from 'express'; import { invoiceService } from '../services/invoiceService.js'; import { authenticate, requireTenant } from '../middleware/auth.js'; import { validateRequest } from '../middleware/validation.js'; import { body, param, query } from 'express-validator'; import { logger } from '../utils/logger.js'; const router = express.Router(); // Create invoice router.post('/', authenticate, requireTenant, validateRequest([ body('subscriptionId').optional().isMongoId(), body('lineItems').isArray(), body('lineItems.*.description').notEmpty(), body('lineItems.*.quantity').isInt({ min: 1 }), body('lineItems.*.unitPrice').isFloat({ min: 0 }), body('dueDate').optional().isISO8601() ]), async (req, res) => { try { const invoice = await invoiceService.createInvoice( req.tenantId, { ...req.body, customerId: req.tenant.billing?.customerId, customer: { name: req.tenant.name, email: req.tenant.owner.email, address: req.tenant.billing?.billingAddress } } ); res.status(201).json({ success: true, invoice }); } catch (error) { logger.error('Create invoice error:', error); res.status(500).json({ success: false, error: 'Failed to create invoice' }); } } ); // Get invoices router.get('/', authenticate, requireTenant, validateRequest([ query('status').optional().isIn(['draft', 'open', 'paid', 'void', 'uncollectible']), query('startDate').optional().isISO8601(), query('endDate').optional().isISO8601(), query('limit').optional().isInt({ min: 1, max: 100 }) ]), async (req, res) => { try { const invoices = await invoiceService.getInvoices( req.tenantId, req.query ); res.json({ success: true, invoices }); } catch (error) { logger.error('Get invoices error:', error); res.status(500).json({ success: false, error: 'Failed to get invoices' }); } } ); // Get unpaid invoices router.get('/unpaid', authenticate, requireTenant, async (req, res) => { try { const invoices = await invoiceService.getUnpaidInvoices(req.tenantId); res.json({ success: true, invoices, totalDue: invoices.reduce((sum, inv) => sum + inv.amountDue, 0) }); } catch (error) { logger.error('Get unpaid invoices error:', error); res.status(500).json({ success: false, error: 'Failed to get unpaid invoices' }); } } ); // Get overdue invoices router.get('/overdue', authenticate, requireTenant, async (req, res) => { try { const invoices = await invoiceService.getOverdueInvoices(req.tenantId); res.json({ success: true, invoices, totalOverdue: invoices.reduce((sum, inv) => sum + inv.amountDue, 0) }); } catch (error) { logger.error('Get overdue invoices error:', error); res.status(500).json({ success: false, error: 'Failed to get overdue invoices' }); } } ); // Get invoice by ID router.get('/:id', authenticate, requireTenant, validateRequest([ param('id').isMongoId() ]), async (req, res) => { try { const invoice = await invoiceService.getInvoice( req.tenantId, req.params.id ); if (!invoice) { return res.status(404).json({ success: false, error: 'Invoice not found' }); } res.json({ success: true, invoice }); } catch (error) { logger.error('Get invoice error:', error); res.status(500).json({ success: false, error: 'Failed to get invoice' }); } } ); // Update invoice (draft only) router.patch('/:id', authenticate, requireTenant, validateRequest([ param('id').isMongoId(), body('lineItems').optional().isArray(), body('dueDate').optional().isISO8601() ]), async (req, res) => { try { const invoice = await invoiceService.updateInvoice( req.tenantId, req.params.id, req.body ); res.json({ success: true, invoice }); } catch (error) { logger.error('Update invoice error:', error); res.status(500).json({ success: false, error: 'Failed to update invoice' }); } } ); // Finalize invoice router.post('/:id/finalize', authenticate, requireTenant, validateRequest([ param('id').isMongoId() ]), async (req, res) => { try { const invoice = await invoiceService.finalizeInvoice( req.tenantId, req.params.id ); res.json({ success: true, invoice, message: 'Invoice finalized and sent' }); } catch (error) { logger.error('Finalize invoice error:', error); res.status(500).json({ success: false, error: 'Failed to finalize invoice' }); } } ); // Pay invoice router.post('/:id/pay', authenticate, requireTenant, validateRequest([ param('id').isMongoId(), body('paymentMethodId').optional().isString() ]), async (req, res) => { try { const invoice = await invoiceService.payInvoice( req.tenantId, req.params.id, { paymentMethodId: req.body.paymentMethodId } ); res.json({ success: true, invoice, message: 'Invoice paid successfully' }); } catch (error) { logger.error('Pay invoice error:', error); res.status(500).json({ success: false, error: 'Failed to pay invoice' }); } } ); // Void invoice router.post('/:id/void', authenticate, requireTenant, validateRequest([ param('id').isMongoId(), body('reason').notEmpty() ]), async (req, res) => { try { const invoice = await invoiceService.voidInvoice( req.tenantId, req.params.id, req.body.reason ); res.json({ success: true, invoice, message: 'Invoice voided' }); } catch (error) { logger.error('Void invoice error:', error); res.status(500).json({ success: false, error: 'Failed to void invoice' }); } } ); // Download invoice PDF router.get('/:id/pdf', authenticate, requireTenant, validateRequest([ param('id').isMongoId() ]), async (req, res) => { try { const invoice = await invoiceService.getInvoice( req.tenantId, req.params.id ); if (!invoice) { return res.status(404).json({ success: false, error: 'Invoice not found' }); } // Generate PDF if not exists if (!invoice.pdfUrl) { await invoiceService.generatePDF(invoice); } // In production, redirect to cloud storage URL res.redirect(invoice.pdfUrl); } catch (error) { logger.error('Download invoice PDF error:', error); res.status(500).json({ success: false, error: 'Failed to download invoice' }); } } ); // Send invoice reminder router.post('/:id/remind', authenticate, requireTenant, validateRequest([ param('id').isMongoId() ]), async (req, res) => { try { await invoiceService.sendInvoiceReminder( req.tenantId, req.params.id ); res.json({ success: true, message: 'Invoice reminder sent' }); } catch (error) { logger.error('Send reminder error:', error); res.status(500).json({ success: false, error: 'Failed to send reminder' }); } } ); export default router;