import { Request, Response, NextFunction } from 'express'; import { PriceEngineService } from '../services/price-engine.service'; import { PromotionService } from '../services/promotion.service'; import { CouponService } from '../services/coupon.service'; import { CreatePromotionInput, UpdatePromotionInput, CreateCouponInput, UpdateCouponInput, CalculatePricesInput, RedeemCouponInput, ListPromotionsQuery, ListCouponsQuery, PromotionProductInput, GenerateBulkCouponsInput, } from '../validation/pricing.schema'; import { AppDataSource } from '../../../config/database'; import { Promotion } from '../entities/promotion.entity'; import { Coupon } from '../entities/coupon.entity'; const priceEngineService = new PriceEngineService(AppDataSource); const promotionService = new PromotionService( AppDataSource, AppDataSource.getRepository(Promotion) ); const couponService = new CouponService( AppDataSource, AppDataSource.getRepository(Coupon) ); // ==================== PRICE ENGINE ==================== /** * Calculate prices for cart/order items * POST /api/pricing/calculate */ export const calculatePrices = async ( req: Request, res: Response, next: NextFunction ): Promise => { try { const tenantId = req.headers['x-tenant-id'] as string; const input = req.body as CalculatePricesInput; const context = { tenantId, branchId: input.branchId, customerId: input.customerId, customerLevelId: input.customerLevelId, isNewCustomer: input.isNewCustomer || false, isLoyaltyMember: input.isLoyaltyMember || false, isFirstOrder: input.isFirstOrder || false, channel: input.channel, }; const result = await priceEngineService.calculatePrices( context, input.lines, input.couponCode ); res.json({ success: true, data: result, }); } catch (error) { next(error); } }; /** * Validate a coupon code * POST /api/pricing/validate-coupon */ export const validateCoupon = async ( req: Request, res: Response, next: NextFunction ): Promise => { try { const tenantId = req.headers['x-tenant-id'] as string; const branchId = req.headers['x-branch-id'] as string; const { code, orderAmount } = req.body; const context = { tenantId, branchId, channel: 'pos' as const, isNewCustomer: false, isLoyaltyMember: false, isFirstOrder: false, }; const result = await priceEngineService.validateCoupon( context, code, orderAmount ); res.json({ success: true, data: result, }); } catch (error) { next(error); } }; /** * Get active promotions for a branch * GET /api/pricing/active-promotions */ export const getActivePromotions = async ( req: Request, res: Response, next: NextFunction ): Promise => { try { const tenantId = req.headers['x-tenant-id'] as string; const branchId = req.headers['x-branch-id'] as string; const channel = (req.query.channel as 'pos' | 'ecommerce' | 'mobile') || 'pos'; const promotions = await priceEngineService.getActivePromotions( tenantId, branchId, channel ); res.json({ success: true, data: promotions, meta: { count: promotions.length, }, }); } catch (error) { next(error); } }; // ==================== PROMOTIONS ==================== /** * List promotions with filters * GET /api/pricing/promotions */ export const listPromotions = async ( req: Request, res: Response, next: NextFunction ): Promise => { try { const tenantId = req.headers['x-tenant-id'] as string; const query = req.query as unknown as ListPromotionsQuery; const result = await promotionService.listPromotions(tenantId, query); res.json({ success: true, data: result.data, meta: result.meta, }); } catch (error) { next(error); } }; /** * Get promotion by ID * GET /api/pricing/promotions/:id */ export const getPromotion = async ( req: Request, res: Response, next: NextFunction ): Promise => { try { const tenantId = req.headers['x-tenant-id'] as string; const { id } = req.params; const result = await promotionService.getById(tenantId, id, { relations: ['products'], }); if (!result.success) { res.status(404).json({ success: false, error: result.error, }); return; } res.json({ success: true, data: result.data, }); } catch (error) { next(error); } }; /** * Create a new promotion * POST /api/pricing/promotions */ export const createPromotion = async ( req: Request, res: Response, next: NextFunction ): Promise => { try { const tenantId = req.headers['x-tenant-id'] as string; const userId = req.user?.id as string; const input = req.body as CreatePromotionInput; const result = await promotionService.createPromotion(tenantId, input, userId); if (!result.success) { res.status(400).json({ success: false, error: result.error, }); return; } res.status(201).json({ success: true, data: result.data, }); } catch (error) { next(error); } }; /** * Update a promotion * PUT /api/pricing/promotions/:id */ export const updatePromotion = async ( req: Request, res: Response, next: NextFunction ): Promise => { try { const tenantId = req.headers['x-tenant-id'] as string; const userId = req.user?.id as string; const { id } = req.params; const input = req.body as UpdatePromotionInput; const result = await promotionService.updatePromotion(tenantId, id, input, userId); if (!result.success) { res.status(400).json({ success: false, error: result.error, }); return; } res.json({ success: true, data: result.data, }); } catch (error) { next(error); } }; /** * Activate a promotion * POST /api/pricing/promotions/:id/activate */ export const activatePromotion = async ( req: Request, res: Response, next: NextFunction ): Promise => { try { const tenantId = req.headers['x-tenant-id'] as string; const userId = req.user?.id as string; const { id } = req.params; const result = await promotionService.activatePromotion(tenantId, id, userId); if (!result.success) { res.status(400).json({ success: false, error: result.error, }); return; } res.json({ success: true, data: result.data, }); } catch (error) { next(error); } }; /** * Pause a promotion * POST /api/pricing/promotions/:id/pause */ export const pausePromotion = async ( req: Request, res: Response, next: NextFunction ): Promise => { try { const tenantId = req.headers['x-tenant-id'] as string; const userId = req.user?.id as string; const { id } = req.params; const result = await promotionService.pausePromotion(tenantId, id, userId); if (!result.success) { res.status(400).json({ success: false, error: result.error, }); return; } res.json({ success: true, data: result.data, }); } catch (error) { next(error); } }; /** * End a promotion * POST /api/pricing/promotions/:id/end */ export const endPromotion = async ( req: Request, res: Response, next: NextFunction ): Promise => { try { const tenantId = req.headers['x-tenant-id'] as string; const userId = req.user?.id as string; const { id } = req.params; const result = await promotionService.endPromotion(tenantId, id, userId); if (!result.success) { res.status(400).json({ success: false, error: result.error, }); return; } res.json({ success: true, data: result.data, }); } catch (error) { next(error); } }; /** * Cancel a promotion * POST /api/pricing/promotions/:id/cancel */ export const cancelPromotion = async ( req: Request, res: Response, next: NextFunction ): Promise => { try { const tenantId = req.headers['x-tenant-id'] as string; const userId = req.user?.id as string; const { id } = req.params; const result = await promotionService.cancelPromotion(tenantId, id, userId); if (!result.success) { res.status(400).json({ success: false, error: result.error, }); return; } res.json({ success: true, data: result.data, }); } catch (error) { next(error); } }; /** * Add products to a promotion * POST /api/pricing/promotions/:id/products */ export const addPromotionProducts = async ( req: Request, res: Response, next: NextFunction ): Promise => { try { const tenantId = req.headers['x-tenant-id'] as string; const userId = req.user?.id as string; const { id } = req.params; const { products } = req.body as { products: PromotionProductInput[] }; const result = await promotionService.addProducts(tenantId, id, products, userId); if (!result.success) { res.status(400).json({ success: false, error: result.error, }); return; } res.status(201).json({ success: true, data: result.data, }); } catch (error) { next(error); } }; /** * Remove product from a promotion * DELETE /api/pricing/promotions/:id/products/:productId */ export const removePromotionProduct = async ( req: Request, res: Response, next: NextFunction ): Promise => { try { const tenantId = req.headers['x-tenant-id'] as string; const userId = req.user?.id as string; const { id, productId } = req.params; const result = await promotionService.removeProduct(tenantId, id, productId, userId); if (!result.success) { res.status(400).json({ success: false, error: result.error, }); return; } res.json({ success: true, message: 'Product removed from promotion', }); } catch (error) { next(error); } }; /** * Delete a promotion * DELETE /api/pricing/promotions/:id */ export const deletePromotion = async ( req: Request, res: Response, next: NextFunction ): Promise => { try { const tenantId = req.headers['x-tenant-id'] as string; const userId = req.user?.id as string; const { id } = req.params; const result = await promotionService.softDelete(tenantId, id, userId); if (!result.success) { res.status(400).json({ success: false, error: result.error, }); return; } res.json({ success: true, message: 'Promotion deleted', }); } catch (error) { next(error); } }; // ==================== COUPONS ==================== /** * List coupons with filters * GET /api/pricing/coupons */ export const listCoupons = async ( req: Request, res: Response, next: NextFunction ): Promise => { try { const tenantId = req.headers['x-tenant-id'] as string; const query = req.query as unknown as ListCouponsQuery; const result = await couponService.listCoupons(tenantId, query); res.json({ success: true, data: result.data, meta: result.meta, }); } catch (error) { next(error); } }; /** * Get coupon by ID * GET /api/pricing/coupons/:id */ export const getCoupon = async ( req: Request, res: Response, next: NextFunction ): Promise => { try { const tenantId = req.headers['x-tenant-id'] as string; const { id } = req.params; const result = await couponService.getById(tenantId, id, { relations: ['redemptions'], }); if (!result.success) { res.status(404).json({ success: false, error: result.error, }); return; } res.json({ success: true, data: result.data, }); } catch (error) { next(error); } }; /** * Get coupon by code * GET /api/pricing/coupons/code/:code */ export const getCouponByCode = async ( req: Request, res: Response, next: NextFunction ): Promise => { try { const tenantId = req.headers['x-tenant-id'] as string; const { code } = req.params; const result = await couponService.getByCode(tenantId, code); if (!result.success) { res.status(404).json({ success: false, error: result.error, }); return; } res.json({ success: true, data: result.data, }); } catch (error) { next(error); } }; /** * Create a new coupon * POST /api/pricing/coupons */ export const createCoupon = async ( req: Request, res: Response, next: NextFunction ): Promise => { try { const tenantId = req.headers['x-tenant-id'] as string; const userId = req.user?.id as string; const input = req.body as CreateCouponInput; const result = await couponService.createCoupon(tenantId, input, userId); if (!result.success) { res.status(400).json({ success: false, error: result.error, }); return; } res.status(201).json({ success: true, data: result.data, }); } catch (error) { next(error); } }; /** * Generate bulk coupons * POST /api/pricing/coupons/bulk */ export const generateBulkCoupons = async ( req: Request, res: Response, next: NextFunction ): Promise => { try { const tenantId = req.headers['x-tenant-id'] as string; const userId = req.user?.id as string; const { quantity, prefix, baseInput } = req.body as GenerateBulkCouponsInput; const result = await couponService.generateBulkCoupons( tenantId, baseInput, quantity, prefix, userId ); if (!result.success) { res.status(400).json({ success: false, error: result.error, }); return; } res.status(201).json({ success: true, data: result.data, meta: { count: result.data?.length || 0, }, }); } catch (error) { next(error); } }; /** * Update a coupon * PUT /api/pricing/coupons/:id */ export const updateCoupon = async ( req: Request, res: Response, next: NextFunction ): Promise => { try { const tenantId = req.headers['x-tenant-id'] as string; const userId = req.user?.id as string; const { id } = req.params; const input = req.body as UpdateCouponInput; const result = await couponService.updateCoupon(tenantId, id, input, userId); if (!result.success) { res.status(400).json({ success: false, error: result.error, }); return; } res.json({ success: true, data: result.data, }); } catch (error) { next(error); } }; /** * Activate a coupon * POST /api/pricing/coupons/:id/activate */ export const activateCoupon = async ( req: Request, res: Response, next: NextFunction ): Promise => { try { const tenantId = req.headers['x-tenant-id'] as string; const userId = req.user?.id as string; const { id } = req.params; const result = await couponService.activateCoupon(tenantId, id, userId); if (!result.success) { res.status(400).json({ success: false, error: result.error, }); return; } res.json({ success: true, data: result.data, }); } catch (error) { next(error); } }; /** * Deactivate a coupon * POST /api/pricing/coupons/:id/deactivate */ export const deactivateCoupon = async ( req: Request, res: Response, next: NextFunction ): Promise => { try { const tenantId = req.headers['x-tenant-id'] as string; const userId = req.user?.id as string; const { id } = req.params; const result = await couponService.deactivateCoupon(tenantId, id, userId); if (!result.success) { res.status(400).json({ success: false, error: result.error, }); return; } res.json({ success: true, data: result.data, }); } catch (error) { next(error); } }; /** * Redeem a coupon * POST /api/pricing/coupons/:id/redeem */ export const redeemCoupon = async ( req: Request, res: Response, next: NextFunction ): Promise => { try { const tenantId = req.headers['x-tenant-id'] as string; const userId = req.user?.id as string; const { id } = req.params; const input = req.body as RedeemCouponInput; // Get coupon code from ID const couponResult = await couponService.getById(tenantId, id); if (!couponResult.success || !couponResult.data) { res.status(404).json({ success: false, error: 'Coupon not found', }); return; } const result = await couponService.redeemCoupon( tenantId, couponResult.data.code, input, userId ); if (!result.success) { res.status(400).json({ success: false, error: result.error, }); return; } res.status(201).json({ success: true, data: result.data, }); } catch (error) { next(error); } }; /** * Redeem coupon by code * POST /api/pricing/coupons/redeem */ export const redeemCouponByCode = async ( req: Request, res: Response, next: NextFunction ): Promise => { try { const tenantId = req.headers['x-tenant-id'] as string; const userId = req.user?.id as string; const { code, ...input } = req.body as RedeemCouponInput & { code: string }; const result = await couponService.redeemCoupon(tenantId, code, input, userId); if (!result.success) { res.status(400).json({ success: false, error: result.error, }); return; } res.status(201).json({ success: true, data: result.data, }); } catch (error) { next(error); } }; /** * Reverse a coupon redemption * POST /api/pricing/coupons/redemptions/:redemptionId/reverse */ export const reverseRedemption = async ( req: Request, res: Response, next: NextFunction ): Promise => { try { const tenantId = req.headers['x-tenant-id'] as string; const userId = req.user?.id as string; const { redemptionId } = req.params; const { reason } = req.body as { reason: string }; const result = await couponService.reverseRedemption( tenantId, redemptionId, reason, userId ); if (!result.success) { res.status(400).json({ success: false, error: result.error, }); return; } res.json({ success: true, data: result.data, }); } catch (error) { next(error); } }; /** * Get coupon redemption history * GET /api/pricing/coupons/:id/redemptions */ export const getCouponRedemptions = async ( req: Request, res: Response, next: NextFunction ): Promise => { try { const tenantId = req.headers['x-tenant-id'] as string; const { id } = req.params; const { page = 1, limit = 20 } = req.query; const result = await couponService.getRedemptionHistory( tenantId, id, Number(page), Number(limit) ); res.json({ success: true, data: result.data, meta: result.meta, }); } catch (error) { next(error); } }; /** * Delete a coupon * DELETE /api/pricing/coupons/:id */ export const deleteCoupon = async ( req: Request, res: Response, next: NextFunction ): Promise => { try { const tenantId = req.headers['x-tenant-id'] as string; const userId = req.user?.id as string; const { id } = req.params; const result = await couponService.softDelete(tenantId, id, userId); if (!result.success) { res.status(400).json({ success: false, error: result.error, }); return; } res.json({ success: true, message: 'Coupon deleted', }); } catch (error) { next(error); } }; /** * Get coupon statistics * GET /api/pricing/coupons/:id/stats */ export const getCouponStats = async ( req: Request, res: Response, next: NextFunction ): Promise => { try { const tenantId = req.headers['x-tenant-id'] as string; const { id } = req.params; const result = await couponService.getCouponStats(tenantId, id); if (!result.success) { res.status(404).json({ success: false, error: result.error, }); return; } res.json({ success: true, data: result.data, }); } catch (error) { next(error); } };