import { Request, Response } from 'express'; import { BaseController } from '../../../shared/controllers/base.controller'; import { CashMovementService, MovementQueryOptions } from '../services/cash-movement.service'; import { CashClosingService, ClosingQueryOptions } from '../services/cash-closing.service'; export class CashController extends BaseController { constructor( private readonly movementService: CashMovementService, private readonly closingService: CashClosingService ) { super(); } // ==================== MOVEMENT ENDPOINTS ==================== /** * Create a cash movement * POST /cash/movements */ createMovement = async (req: Request, res: Response): Promise => { const tenantId = req.tenantContext!.tenantId; const userId = req.userContext!.userId; const result = await this.movementService.createMovement(tenantId, req.body, userId); if (!result.success) { this.error(res, result.error!.message, 400, result.error!.code); return; } this.created(res, result.data); }; /** * List movements * GET /cash/movements */ listMovements = async (req: Request, res: Response): Promise => { const tenantId = req.tenantContext!.tenantId; const options: MovementQueryOptions = { page: Number(req.query.page) || 1, limit: Number(req.query.limit) || 20, branchId: req.query.branchId as string, sessionId: req.query.sessionId as string, registerId: req.query.registerId as string, type: req.query.type as any, reason: req.query.reason as any, status: req.query.status as any, startDate: req.query.startDate ? new Date(req.query.startDate as string) : undefined, endDate: req.query.endDate ? new Date(req.query.endDate as string) : undefined, }; const result = await this.movementService.findMovements(tenantId, options); this.paginated(res, result.data, result.total, options.page!, options.limit!); }; /** * Get movement by ID * GET /cash/movements/:id */ getMovement = async (req: Request, res: Response): Promise => { const tenantId = req.tenantContext!.tenantId; const { id } = req.params; const movement = await this.movementService.findById(tenantId, id); if (!movement) { this.notFound(res, 'Movement'); return; } this.success(res, movement); }; /** * Approve movement * POST /cash/movements/:id/approve */ approveMovement = async (req: Request, res: Response): Promise => { const tenantId = req.tenantContext!.tenantId; const userId = req.userContext!.userId; const { id } = req.params; const result = await this.movementService.approveMovement(tenantId, id, userId); if (!result.success) { this.error(res, result.error!.message, 400, result.error!.code); return; } this.success(res, result.data, 'Movement approved'); }; /** * Reject movement * POST /cash/movements/:id/reject */ rejectMovement = async (req: Request, res: Response): Promise => { const tenantId = req.tenantContext!.tenantId; const userId = req.userContext!.userId; const { id } = req.params; const { reason } = req.body; const result = await this.movementService.rejectMovement(tenantId, id, userId, reason); if (!result.success) { this.error(res, result.error!.message, 400, result.error!.code); return; } this.success(res, result.data, 'Movement rejected'); }; /** * Cancel movement * POST /cash/movements/:id/cancel */ cancelMovement = async (req: Request, res: Response): Promise => { const tenantId = req.tenantContext!.tenantId; const userId = req.userContext!.userId; const { id } = req.params; const { reason } = req.body; const result = await this.movementService.cancelMovement(tenantId, id, userId, reason); if (!result.success) { this.error(res, result.error!.message, 400, result.error!.code); return; } this.success(res, result.data, 'Movement cancelled'); }; /** * Get session summary * GET /cash/sessions/:sessionId/summary */ getSessionSummary = async (req: Request, res: Response): Promise => { const tenantId = req.tenantContext!.tenantId; const { sessionId } = req.params; const summary = await this.movementService.getSessionSummary(tenantId, sessionId); this.success(res, summary); }; // ==================== CLOSING ENDPOINTS ==================== /** * Create a cash closing * POST /cash/closings */ createClosing = async (req: Request, res: Response): Promise => { const tenantId = req.tenantContext!.tenantId; const userId = req.userContext!.userId; const result = await this.closingService.createClosing(tenantId, req.body, userId); if (!result.success) { this.error(res, result.error!.message, 400, result.error!.code); return; } this.created(res, result.data); }; /** * List closings * GET /cash/closings */ listClosings = async (req: Request, res: Response): Promise => { const tenantId = req.tenantContext!.tenantId; const options: ClosingQueryOptions = { page: Number(req.query.page) || 1, limit: Number(req.query.limit) || 20, branchId: req.query.branchId as string, sessionId: req.query.sessionId as string, status: req.query.status as any, type: req.query.type as any, startDate: req.query.startDate ? new Date(req.query.startDate as string) : undefined, endDate: req.query.endDate ? new Date(req.query.endDate as string) : undefined, }; const result = await this.closingService.findClosings(tenantId, options); this.paginated(res, result.data, result.total, options.page!, options.limit!); }; /** * Get closing by ID * GET /cash/closings/:id */ getClosing = async (req: Request, res: Response): Promise => { const tenantId = req.tenantContext!.tenantId; const { id } = req.params; const closing = await this.closingService.getClosingWithCounts(tenantId, id); if (!closing) { this.notFound(res, 'Closing'); return; } this.success(res, closing); }; /** * Submit cash count * POST /cash/closings/:id/count */ submitCashCount = async (req: Request, res: Response): Promise => { const tenantId = req.tenantContext!.tenantId; const userId = req.userContext!.userId; const { id } = req.params; const { denominations } = req.body; const result = await this.closingService.submitCashCount(tenantId, id, denominations, userId); if (!result.success) { this.error(res, result.error!.message, 400, result.error!.code); return; } this.success(res, result.data, 'Cash count submitted'); }; /** * Submit payment counts * POST /cash/closings/:id/payments */ submitPaymentCounts = async (req: Request, res: Response): Promise => { const tenantId = req.tenantContext!.tenantId; const userId = req.userContext!.userId; const { id } = req.params; const result = await this.closingService.submitPaymentCounts(tenantId, id, req.body, userId); if (!result.success) { this.error(res, result.error!.message, 400, result.error!.code); return; } this.success(res, result.data, 'Payment counts submitted'); }; /** * Approve closing * POST /cash/closings/:id/approve */ approveClosing = async (req: Request, res: Response): Promise => { const tenantId = req.tenantContext!.tenantId; const userId = req.userContext!.userId; const { id } = req.params; const { notes } = req.body; const result = await this.closingService.approveClosing(tenantId, id, userId, notes); if (!result.success) { this.error(res, result.error!.message, 400, result.error!.code); return; } this.success(res, result.data, 'Closing approved'); }; /** * Reject closing * POST /cash/closings/:id/reject */ rejectClosing = async (req: Request, res: Response): Promise => { const tenantId = req.tenantContext!.tenantId; const userId = req.userContext!.userId; const { id } = req.params; const { notes } = req.body; const result = await this.closingService.rejectClosing(tenantId, id, userId, notes); if (!result.success) { this.error(res, result.error!.message, 400, result.error!.code); return; } this.success(res, result.data, 'Closing rejected'); }; /** * Reconcile closing * POST /cash/closings/:id/reconcile */ reconcileClosing = async (req: Request, res: Response): Promise => { const tenantId = req.tenantContext!.tenantId; const userId = req.userContext!.userId; const { id } = req.params; const { depositAmount, depositReference, depositDate } = req.body; const result = await this.closingService.reconcileClosing( tenantId, id, { amount: depositAmount, reference: depositReference, date: new Date(depositDate), }, userId ); if (!result.success) { this.error(res, result.error!.message, 400, result.error!.code); return; } this.success(res, result.data, 'Closing reconciled'); }; /** * Get daily summary * GET /cash/summary/daily */ getDailySummary = async (req: Request, res: Response): Promise => { const tenantId = req.tenantContext!.tenantId; const branchId = req.branchContext?.branchId || (req.query.branchId as string); const date = req.query.date ? new Date(req.query.date as string) : new Date(); if (!branchId) { this.error(res, 'Branch ID is required', 400); return; } const summary = await this.closingService.getDailySummary(tenantId, branchId, date); this.success(res, summary); }; }