331 lines
9.7 KiB
TypeScript
331 lines
9.7 KiB
TypeScript
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<void> => {
|
|
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<void> => {
|
|
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<void> => {
|
|
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<void> => {
|
|
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<void> => {
|
|
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<void> => {
|
|
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<void> => {
|
|
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<void> => {
|
|
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<void> => {
|
|
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<void> => {
|
|
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<void> => {
|
|
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<void> => {
|
|
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<void> => {
|
|
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<void> => {
|
|
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<void> => {
|
|
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<void> => {
|
|
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);
|
|
};
|
|
}
|