feat: Implement loyalty and wholesale services with models, routes, and validation
CI / test (push) Successful in 10m23s
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:
@@ -0,0 +1,2 @@
|
||||
"use strict";module.exports={async up(q,S){const ID={type:S.STRING,primaryKey:true,allowNull:false},REQ={type:S.STRING,allowNull:false},STR={type:S.STRING},DATE={type:S.DATE},INT={type:S.INTEGER,allowNull:false,defaultValue:0},DEC={type:S.DECIMAL(15,2)},TS={createdAt:{type:S.DATE,allowNull:false},updatedAt:{type:S.DATE,allowNull:false}};await q.createTable("loyalty_accounts",{id:ID,user_id:{type:S.STRING,allowNull:false,unique:true},status:REQ,current_tier_id:STR,lifetime_points_earned:INT,lifetime_points_redeemed:INT,available_points:INT,pending_points:INT,points_debt:INT,last_activity_at:DATE,...TS});await q.createTable("loyalty_tiers",{id:ID,code:{type:S.STRING(60),allowNull:false,unique:true},name:REQ,status:REQ,rank:{type:S.INTEGER,allowNull:false},qualification_threshold:{type:S.INTEGER,allowNull:false},qualification_metric:REQ,benefits_json:{type:S.JSON},sort_order:INT,...TS});await q.addIndex("loyalty_tiers",["status","rank"]);await q.createTable("loyalty_tier_history",{id:ID,loyalty_account_id:REQ,from_tier_id:STR,to_tier_id:REQ,reason:REQ,effective_at:{type:S.DATE,allowNull:false},...TS});await q.createTable("loyalty_earn_rules",{id:ID,source_type:REQ,status:REQ,points_per_amount:{type:S.INTEGER},amount_unit:DEC,fixed_points:{type:S.INTEGER},minimum_amount:DEC,maximum_points:{type:S.INTEGER},expiry_days:{type:S.INTEGER},effective_from:{type:S.DATE,allowNull:false},effective_to:DATE,...TS});await q.createTable("loyalty_ledger_entries",{id:ID,event_id:{type:S.STRING(180),allowNull:false,unique:true},loyalty_account_id:REQ,type:REQ,source_type:REQ,source_id:STR,points_delta:{type:S.INTEGER,allowNull:false},balance_after:{type:S.INTEGER,allowNull:false},status:REQ,expires_at:DATE,description_code:STR,metadata:{type:S.JSON},occurred_at:{type:S.DATE,allowNull:false},createdAt:{type:S.DATE,allowNull:false}});await q.addIndex("loyalty_ledger_entries",["loyalty_account_id","occurred_at"]);await q.addIndex("loyalty_ledger_entries",["source_type","source_id"]);await q.addIndex("loyalty_ledger_entries",["expires_at"]);await q.createTable("loyalty_point_allocations",{id:ID,earn_ledger_id:REQ,loyalty_account_id:REQ,original_points:{type:S.INTEGER,allowNull:false},remaining_points:{type:S.INTEGER,allowNull:false},expires_at:DATE,...TS});await q.createTable("loyalty_rewards",{id:ID,code:{type:S.STRING(60),allowNull:false,unique:true},name:REQ,description:{type:S.TEXT},type:REQ,points_cost:{type:S.INTEGER,allowNull:false},status:REQ,starts_at:DATE,ends_at:DATE,stock_limit:{type:S.INTEGER},per_user_limit:{type:S.INTEGER},configuration:{type:S.JSON},...TS});await q.addIndex("loyalty_rewards",["status","starts_at","ends_at"]);await q.createTable("loyalty_redemptions",{id:ID,redemption_number:{type:S.STRING(80),allowNull:false,unique:true},idempotency_key:{type:S.STRING(160),allowNull:false},loyalty_account_id:REQ,reward_id:REQ,points_cost:{type:S.INTEGER,allowNull:false},status:REQ,fulfilled_reference:STR,redeemed_at:{type:S.DATE,allowNull:false},...TS});await q.addConstraint("loyalty_redemptions",{fields:["loyalty_account_id","idempotency_key"],type:"unique",name:"uq_loyalty_redemption_idempotency"});await q.createTable("referrals",{id:ID,referrer_user_id:REQ,referred_user_id:{type:S.STRING,unique:true},referral_code:{type:S.STRING(32),allowNull:false,unique:true},status:REQ,qualified_order_id:STR,qualified_at:DATE,rewarded_at:DATE,...TS});await q.addIndex("referrals",["referrer_user_id","status"]);await q.createTable("customer_coupon_entitlements",{id:ID,user_id:REQ,coupon_id:REQ,source_type:REQ,source_id:REQ,status:REQ,issued_at:{type:S.DATE,allowNull:false},used_at:DATE,expires_at:DATE,...TS});await q.addConstraint("customer_coupon_entitlements",{fields:["user_id","coupon_id","source_type","source_id"],type:"unique",name:"uq_coupon_entitlement_source"});
|
||||
await q.createTable("business_credit_ledger_entries",{id:ID,event_id:{type:S.STRING(180),allowNull:false,unique:true},business_credit_account_id:REQ,business_customer_id:REQ,order_id:STR,payment_id:STR,type:REQ,amount:{...DEC,allowNull:false},currency:{type:S.STRING(3),allowNull:false},balance_delta:{...DEC,allowNull:false},balance_after:{...DEC,allowNull:false},reference:STR,occurred_at:{type:S.DATE,allowNull:false},metadata:{type:S.JSON},createdAt:{type:S.DATE,allowNull:false}});await q.addIndex("business_credit_ledger_entries",["business_credit_account_id","occurred_at"]);await q.createTable("business_settlements",{id:ID,settlement_number:{type:S.STRING(80),allowNull:false,unique:true},business_customer_id:REQ,period_start:{type:S.DATEONLY,allowNull:false},period_end:{type:S.DATEONLY,allowNull:false},due_date:{type:S.DATEONLY,allowNull:false},status:REQ,currency:{type:S.STRING(3),allowNull:false},charge_total:{...DEC,allowNull:false},payment_total:{...DEC,allowNull:false},balance_due:{...DEC,allowNull:false},issued_at:DATE,paid_at:DATE,...TS});await q.addConstraint("business_settlements",{fields:["business_customer_id","period_start","period_end"],type:"unique",name:"uq_business_settlement_period"});await q.addIndex("business_settlements",["business_customer_id","status","due_date"]);await q.createTable("business_settlement_items",{id:ID,business_settlement_id:REQ,order_id:STR,invoice_id:STR,credit_ledger_entry_id:STR,description:REQ,amount:{...DEC,allowNull:false},currency:{type:S.STRING(3),allowNull:false},...TS});await q.createTable("payment_allocations",{id:ID,event_id:{type:S.STRING(180),allowNull:false,unique:true},payment_id:REQ,business_settlement_id:REQ,amount:{...DEC,allowNull:false},currency:{type:S.STRING(3),allowNull:false},allocated_at:{type:S.DATE,allowNull:false},allocated_by:REQ,...TS});await q.addIndex("payment_allocations",["payment_id","business_settlement_id"]);},async down(){throw new Error("Phase 9 migration is forward-only; restore from a verified backup instead");}};
|
||||
Reference in New Issue
Block a user