feat: Implement loyalty and wholesale services with models, routes, and validation
CI / test (push) Successful in 10m23s

- Added models for loyalty points allocation, redemption, rewards, tiers, tier history, and referrals.
- Created business credit ledger entries, settlements, and settlement items models.
- Developed services for loyalty operations including account management, point allocation, redemption, and referral handling.
- Implemented wholesale credit management services for transactions and settlements.
- Established routes for loyalty and wholesale admin and customer operations with appropriate middleware for authentication and permission checks.
- Introduced validation schemas for loyalty and wholesale operations.
- Set up cron jobs for loyalty reconciliation tasks such as birthday rewards and point expirations.
- Created migration scripts to set up the database schema for loyalty and wholesale features.
- Added unit tests for validation and policy enforcement in loyalty and wholesale services.
This commit is contained in:
Sathira Sri Sathara
2026-09-09 13:56:37 +05:30
parent 222483d194
commit abb760cc19
45 changed files with 482 additions and 58 deletions
@@ -0,0 +1 @@
const s=require("../../app/validation/loyaltyWholesale.schemas"),settlements=require("../../app/services/wholesale/settlement.service"),{checkPermission}=require("../../app/middleware/permission.middleware");const input=body=>({body,params:{},query:{}});describe("Phase 9 security and state boundaries",()=>{test("client cannot submit a loyalty balance",()=>expect(()=>s.redeem.parse(input({rewardId:"r",availablePoints:999999}))).toThrow());test("credit amount must be decimal string",()=>expect(()=>s.credit.parse(input({businessCustomerId:"b",type:"CAPTURE",amount:1.1,currency:"LKR"}))).toThrow());test("settlement terminal state has no transitions",()=>expect(settlements.transitions.PAID).toEqual([]));test("admin without loyalty adjustment permission is denied",()=>{const req={user:{accountType:"admin",permissions:[]},method:"POST"},res={status:jest.fn().mockReturnThis(),json:jest.fn()},next=jest.fn();checkPermission("loyalty.points.adjust",{custom:true})(req,res,next);expect(res.status).toHaveBeenCalledWith(403);});});
+1
View File
@@ -0,0 +1 @@
const loyalty=require("../../app/services/loyalty/loyalty.service"),referrals=require("../../app/services/loyalty/referral.service");describe("Phase 9 loyalty policies",()=>{test("money-to-points floors incomplete units without float arithmetic",()=>expect(loyalty.calculatePurchasePoints("2499.99","1000.00",10)).toBe(20));test("purchase points respect configured maximum",()=>expect(loyalty.calculatePurchasePoints("99999.00","100.00",10,500)).toBe(500));test("referral codes are opaque and normalized",()=>{const code=referrals.code();expect(code).toMatch(/^[A-Z0-9_-]+$/);expect(code.length).toBeGreaterThanOrEqual(8);});});