import { Response, NextFunction } from 'express'; import { BaseController } from '../../../shared/controllers/base.controller'; import { AuthenticatedRequest } from '../../../shared/types'; import { posSessionService } from '../services/pos-session.service'; import { posOrderService } from '../services/pos-order.service'; class POSController extends BaseController { // ==================== SESSION ENDPOINTS ==================== /** * POST /pos/sessions/open - Open a new session */ async openSession(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = this.getTenantId(req); const userId = this.getUserId(req); const branchId = this.getBranchId(req); if (!branchId) { return this.error(res, 'BRANCH_REQUIRED', 'X-Branch-ID header is required'); } const { registerId, openingCash, openingNotes } = req.body; if (!registerId) { return this.validationError(res, { registerId: 'Required' }); } const result = await posSessionService.openSession(tenantId, { branchId, registerId, userId, openingCash: openingCash || 0, openingNotes, }); if (!result.success) { return this.error(res, result.error.code, result.error.message); } return this.success(res, result.data, 201); } catch (error) { next(error); } } /** * POST /pos/sessions/:id/close - Close a session */ async closeSession(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = this.getTenantId(req); const userId = this.getUserId(req); const { id } = req.params; const { closingCashCounted, closingNotes, cashCountDetail } = req.body; if (closingCashCounted === undefined) { return this.validationError(res, { closingCashCounted: 'Required' }); } const result = await posSessionService.closeSession(tenantId, id, { closedBy: userId, closingCashCounted, closingNotes, cashCountDetail, }); if (!result.success) { return this.error(res, result.error.code, result.error.message); } return this.success(res, result.data); } catch (error) { next(error); } } /** * GET /pos/sessions/active - Get active session for current user */ async getActiveSession(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = this.getTenantId(req); const userId = this.getUserId(req); const session = await posSessionService.getActiveSession(tenantId, userId); if (!session) { return this.notFound(res, 'Active session'); } return this.success(res, session); } catch (error) { next(error); } } /** * GET /pos/sessions/:id - Get session by ID */ async getSession(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = this.getTenantId(req); const { id } = req.params; const { withOrders } = req.query; const session = withOrders ? await posSessionService.getSessionWithOrders(tenantId, id) : await posSessionService.findById(tenantId, id); if (!session) { return this.notFound(res, 'Session'); } return this.success(res, session); } catch (error) { next(error); } } /** * GET /pos/sessions - List sessions for branch */ async listSessions(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = this.getTenantId(req); const branchId = this.getBranchId(req); if (!branchId) { return this.error(res, 'BRANCH_REQUIRED', 'X-Branch-ID header is required'); } const pagination = this.parsePagination(req.query); const { status, userId, startDate, endDate } = req.query; const result = await posSessionService.findAll(tenantId, { pagination, filters: [ { field: 'branchId', operator: 'eq', value: branchId }, ...(status ? [{ field: 'status', operator: 'eq' as const, value: status }] : []), ...(userId ? [{ field: 'userId', operator: 'eq' as const, value: userId }] : []), ], }); return this.paginated(res, result); } catch (error) { next(error); } } /** * GET /pos/sessions/daily-summary - Get daily session summary */ async getDailySummary(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = this.getTenantId(req); const branchId = this.getBranchId(req); if (!branchId) { return this.error(res, 'BRANCH_REQUIRED', 'X-Branch-ID header is required'); } const dateParam = req.query.date as string; const date = dateParam ? new Date(dateParam) : new Date(); const summary = await posSessionService.getDailySummary(tenantId, branchId, date); return this.success(res, summary); } catch (error) { next(error); } } // ==================== ORDER ENDPOINTS ==================== /** * POST /pos/orders - Create a new order */ async createOrder(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = this.getTenantId(req); const userId = this.getUserId(req); const branchId = this.getBranchId(req); const branchCode = req.branch?.branchCode || 'UNK'; if (!branchId) { return this.error(res, 'BRANCH_REQUIRED', 'X-Branch-ID header is required'); } const { sessionId, registerId, lines, ...orderData } = req.body; if (!sessionId || !registerId || !lines || lines.length === 0) { return this.validationError(res, { sessionId: sessionId ? undefined : 'Required', registerId: registerId ? undefined : 'Required', lines: lines?.length ? undefined : 'At least one line is required', }); } const result = await posOrderService.createOrder( tenantId, { sessionId, branchId, registerId, userId, lines, ...orderData, }, branchCode ); if (!result.success) { return this.error(res, result.error.code, result.error.message); } return this.success(res, result.data, 201); } catch (error) { next(error); } } /** * POST /pos/orders/:id/payments - Add payment to order */ async addPayment(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = this.getTenantId(req); const userId = this.getUserId(req); const { id } = req.params; const { method, amount, amountReceived, ...paymentData } = req.body; if (!method || amount === undefined) { return this.validationError(res, { method: method ? undefined : 'Required', amount: amount !== undefined ? undefined : 'Required', }); } const result = await posOrderService.addPayment( tenantId, id, { method, amount, amountReceived: amountReceived || amount, ...paymentData, }, userId ); if (!result.success) { return this.error(res, result.error.code, result.error.message); } return this.success(res, result.data); } catch (error) { next(error); } } /** * POST /pos/orders/:id/void - Void an order */ async voidOrder(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = this.getTenantId(req); const userId = this.getUserId(req); const { id } = req.params; const { reason } = req.body; if (!reason) { return this.validationError(res, { reason: 'Required' }); } const result = await posOrderService.voidOrder(tenantId, id, reason, userId); if (!result.success) { return this.error(res, result.error.code, result.error.message); } return this.success(res, result.data); } catch (error) { next(error); } } /** * GET /pos/orders/:id - Get order by ID */ async getOrder(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = this.getTenantId(req); const { id } = req.params; const order = await posOrderService.getOrderWithDetails(tenantId, id); if (!order) { return this.notFound(res, 'Order'); } return this.success(res, order); } catch (error) { next(error); } } /** * GET /pos/orders - List orders */ async listOrders(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = this.getTenantId(req); const branchId = this.getBranchId(req); if (!branchId) { return this.error(res, 'BRANCH_REQUIRED', 'X-Branch-ID header is required'); } const pagination = this.parsePagination(req.query); const { sessionId, status, customerId, startDate, endDate, search } = req.query; // If search term provided, use search method if (search) { const orders = await posOrderService.searchOrders( tenantId, branchId, search as string, pagination.limit ); return this.success(res, orders); } const result = await posOrderService.findAll(tenantId, { pagination, filters: [ { field: 'branchId', operator: 'eq', value: branchId }, ...(sessionId ? [{ field: 'sessionId', operator: 'eq' as const, value: sessionId }] : []), ...(status ? [{ field: 'status', operator: 'eq' as const, value: status }] : []), ...(customerId ? [{ field: 'customerId', operator: 'eq' as const, value: customerId }] : []), ], }); return this.paginated(res, result); } catch (error) { next(error); } } /** * GET /pos/orders/session/:sessionId - Get orders by session */ async getOrdersBySession(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = this.getTenantId(req); const { sessionId } = req.params; const orders = await posOrderService.getOrdersBySession(tenantId, sessionId); return this.success(res, orders); } catch (error) { next(error); } } // ==================== REFUND ENDPOINTS ==================== /** * POST /pos/refunds - Create a refund */ async createRefund(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = this.getTenantId(req); const userId = this.getUserId(req); const branchId = this.getBranchId(req); const branchCode = req.branch?.branchCode || 'UNK'; if (!branchId) { return this.error(res, 'BRANCH_REQUIRED', 'X-Branch-ID header is required'); } const { originalOrderId, sessionId, registerId, lines, refundReason } = req.body; if (!originalOrderId || !sessionId || !registerId || !lines || lines.length === 0) { return this.validationError(res, { originalOrderId: originalOrderId ? undefined : 'Required', sessionId: sessionId ? undefined : 'Required', registerId: registerId ? undefined : 'Required', lines: lines?.length ? undefined : 'At least one line is required', }); } const result = await posOrderService.createRefund( tenantId, { originalOrderId, sessionId, branchId, registerId, userId, lines, refundReason: refundReason || 'Customer refund', }, branchCode ); if (!result.success) { return this.error(res, result.error.code, result.error.message); } return this.success(res, result.data, 201); } catch (error) { next(error); } } /** * POST /pos/refunds/:id/process - Process refund payment */ async processRefund(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = this.getTenantId(req); const userId = this.getUserId(req); const { id } = req.params; const { method, amount } = req.body; if (!method || amount === undefined) { return this.validationError(res, { method: method ? undefined : 'Required', amount: amount !== undefined ? undefined : 'Required', }); } const result = await posOrderService.processRefundPayment(tenantId, id, { method, amount }, userId); if (!result.success) { return this.error(res, result.error.code, result.error.message); } return this.success(res, result.data); } catch (error) { next(error); } } // ==================== HOLD/RECALL ENDPOINTS ==================== /** * POST /pos/orders/:id/hold - Hold an order */ async holdOrder(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = this.getTenantId(req); const { id } = req.params; const { holdName } = req.body; const result = await posOrderService.holdOrder(tenantId, id, holdName || 'Unnamed'); if (!result.success) { return this.error(res, result.error.code, result.error.message); } return this.success(res, result.data); } catch (error) { next(error); } } /** * GET /pos/orders/held - Get held orders */ async getHeldOrders(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = this.getTenantId(req); const branchId = this.getBranchId(req); if (!branchId) { return this.error(res, 'BRANCH_REQUIRED', 'X-Branch-ID header is required'); } const orders = await posOrderService.getHeldOrders(tenantId, branchId); return this.success(res, orders); } catch (error) { next(error); } } /** * POST /pos/orders/:id/recall - Recall a held order */ async recallOrder(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = this.getTenantId(req); const userId = this.getUserId(req); const { id } = req.params; const { sessionId, registerId } = req.body; if (!sessionId || !registerId) { return this.validationError(res, { sessionId: sessionId ? undefined : 'Required', registerId: registerId ? undefined : 'Required', }); } const result = await posOrderService.recallOrder(tenantId, id, sessionId, registerId, userId); if (!result.success) { return this.error(res, result.error.code, result.error.message); } return this.success(res, result.data); } catch (error) { next(error); } } // ==================== ADDITIONAL ENDPOINTS ==================== /** * POST /pos/orders/:id/coupon - Apply coupon to order */ async applyCoupon(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = this.getTenantId(req); const branchId = this.getBranchId(req); const { id } = req.params; const { couponCode } = req.body; if (!branchId) { return this.error(res, 'BRANCH_REQUIRED', 'X-Branch-ID header is required'); } if (!couponCode) { return this.validationError(res, { couponCode: 'Required' }); } const result = await posOrderService.applyCouponToOrder(tenantId, id, couponCode, branchId); if (!result.success) { return this.error(res, result.error.code, result.error.message); } return this.success(res, result.data); } catch (error) { next(error); } } /** * POST /pos/orders/:id/receipt/print - Mark receipt as printed */ async markReceiptPrinted(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = this.getTenantId(req); const { id } = req.params; await posOrderService.markReceiptPrinted(tenantId, id); return this.success(res, { message: 'Receipt marked as printed' }); } catch (error) { next(error); } } /** * POST /pos/orders/:id/receipt/send - Send receipt by email */ async sendReceipt(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = this.getTenantId(req); const { id } = req.params; const { email } = req.body; if (!email) { return this.validationError(res, { email: 'Required' }); } const result = await posOrderService.sendReceiptEmail(tenantId, id, email); if (!result.success) { return this.error(res, result.error.code, result.error.message); } return this.success(res, { message: 'Receipt sent successfully' }); } catch (error) { next(error); } } /** * GET /pos/sessions/:id/stats - Get session statistics */ async getSessionStats(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = this.getTenantId(req); const { id } = req.params; const stats = await posOrderService.getSessionStats(tenantId, id); return this.success(res, stats); } catch (error) { next(error); } } } export const posController = new POSController();