feat: Add Phase 11 analytics and production readiness features
CI / test (push) Successful in 10m56s
CI / test (pull_request) Successful in 11m1s

- Introduced new endpoints for account overview, analytics, and metrics.
- Implemented authorization matrix and backup/restore documentation.
- Added smoke test script and updated package dependencies.
- Created detailed production checklist and runbook for deployment.
- Established cron operations and notification event matrix documentation.
- Enhanced security and validation audits for analytics and metrics services.
- Added unit tests for analytics and metrics functionalities.
This commit is contained in:
Sathira Sri Sathara
2026-09-16 10:59:19 +05:30
parent f920ca8920
commit 5158c52db5
25 changed files with 1677 additions and 2266 deletions
+2
View File
@@ -29,4 +29,6 @@ module.exports = {
SHIPMENTS_READ:"shipments.read",SHIPMENTS_CREATE:"shipments.create",SHIPMENTS_MANAGE:"shipments.manage",SHIPMENTS_ASSIGN:"shipments.assign",RIDERS_READ:"riders.read",RIDERS_MANAGE:"riders.manage",DISPATCH_READ:"dispatch.read",DISPATCH_MANAGE:"dispatch.manage",DELIVERY_PROOF_READ:"delivery.proof.read",RETURNS_LOGISTICS_READ:"returns.logistics.read",RETURNS_LOGISTICS_MANAGE:"returns.logistics.manage",
LOYALTY_ACCOUNTS_READ:"loyalty.accounts.read",LOYALTY_POINTS_ADJUST:"loyalty.points.adjust",LOYALTY_MANAGE:"loyalty.manage",LOYALTY_REFERRALS_READ:"loyalty.referrals.read",WHOLESALE_CREDIT_READ:"wholesale.credit.read",WHOLESALE_CREDIT_MANAGE:"wholesale.credit.manage",WHOLESALE_SETTLEMENTS_READ:"wholesale.settlements.read",WHOLESALE_SETTLEMENTS_MANAGE:"wholesale.settlements.manage",WHOLESALE_ANALYTICS_READ:"wholesale.analytics.read",
SUPPORT_TICKETS_READ:"support.tickets.read",SUPPORT_TICKETS_ASSIGN:"support.tickets.assign",SUPPORT_TICKETS_REPLY:"support.tickets.reply",SUPPORT_TICKETS_STATUS:"support.tickets.status",SUPPORT_TICKETS_PRIORITY:"support.tickets.priority",SUPPORT_TICKETS_INTERNAL_NOTES:"support.tickets.internal_notes",SUPPORT_TICKETS_ESCALATE:"support.tickets.escalate",SUPPORT_CATEGORIES_MANAGE:"support.categories.manage",SUPPORT_SLA_MANAGE:"support.sla.manage",HELP_READ:"help.read",HELP_MANAGE:"help.manage",HELP_PUBLISH:"help.publish",NEWSLETTER_SUBSCRIBERS_READ:"newsletter.subscribers.read",NEWSLETTER_SUBSCRIBERS_EXPORT:"newsletter.subscribers.export",NEWSLETTER_SUBSCRIBERS_MANAGE:"newsletter.subscribers.manage",RECOMMENDATIONS_READ:"recommendations.read",RECOMMENDATIONS_MANAGE:"recommendations.manage",
ANALYTICS_DASHBOARD_READ:"analytics.dashboard.read",ANALYTICS_SALES_READ:"analytics.sales.read",ANALYTICS_ORDERS_READ:"analytics.orders.read",ANALYTICS_CUSTOMERS_READ:"analytics.customers.read",ANALYTICS_PRODUCTS_READ:"analytics.products.read",ANALYTICS_INVENTORY_READ:"analytics.inventory.read",ANALYTICS_PAYMENTS_READ:"analytics.payments.read",ANALYTICS_DELIVERY_READ:"analytics.delivery.read",ANALYTICS_LOYALTY_READ:"analytics.loyalty.read",ANALYTICS_WHOLESALE_READ:"analytics.wholesale.read",ANALYTICS_SUPPORT_READ:"analytics.support.read",ANALYTICS_RECOMMENDATIONS_READ:"analytics.recommendations.read",
SYSTEM_METRICS_READ:"system.metrics.read",
};
@@ -0,0 +1 @@
const db=require("../models");exports.overview=async(req,res,next)=>{try{const userId=req.user.id,[recentOrders,orderCount,wishlistCount,defaultAddress,loyalty,openTickets,vouchers]=await Promise.all([db.Order.findAll({where:{user_id:userId},attributes:["id","order_number","status","payment_status","grand_total","currency","placed_at"],order:[["placed_at","DESC"]],limit:5}),db.Order.count({where:{user_id:userId}}),db.WishlistItem.count({where:{user_id:userId}}),db.Address.findOne({where:{user_id:userId,is_default:true},attributes:{exclude:["user_id"]}}),db.LoyaltyAccount.findOne({where:{user_id:userId},attributes:["available_points","current_tier_id","lifetime_points_earned"]}),db.SupportTicket.count({where:{customer_user_id:userId,status:["OPEN","ASSIGNED","WAITING_FOR_CUSTOMER","WAITING_FOR_SUPPORT"]}}),db.CustomerCouponEntitlement.count({where:{user_id:userId,status:"ACTIVE"}})]);res.json({success:true,data:{recentOrders,orderCount,wishlistCount,defaultAddress,loyalty:loyalty&&{availablePoints:loyalty.available_points,lifetimePointsEarned:loyalty.lifetime_points_earned,tierId:loyalty.current_tier_id},availableVoucherCount:vouchers,openSupportTicketCount:openTickets}});}catch(e){next(e);}};
+1
View File
@@ -0,0 +1 @@
const a=require("../services/analytics/analytics.service");const run=fn=>async(req,res,next)=>{try{res.json({success:true,data:await fn(req.query)});}catch(e){next(e);}};exports.overview=run(a.overview);exports.sales=run(a.sales);exports.orders=run(a.orders);exports.customers=run(a.customers);exports.products=run(a.products);exports.inventory=run(a.inventory);exports.payments=run(a.payments);exports.delivery=run(a.delivery);exports.loyalty=run(a.loyalty);exports.support=run(a.support);exports.recommendations=run(a.recommendations);
+1
View File
@@ -0,0 +1 @@
const metrics=require("../services/metrics.service");exports.read=(_req,res)=>res.type("text/plain; version=0.0.4").send(metrics.render());
+1
View File
@@ -0,0 +1 @@
const r=require("express").Router(),c=require("../controllers/accountOverview.controller"),{authenticate}=require("../middleware/auth.middleware");r.get("/account/overview",authenticate,c.overview);module.exports=r;
+1
View File
@@ -0,0 +1 @@
const r=require("express").Router(),c=require("../controllers/analytics.controller"),{authenticate}=require("../middleware/auth.middleware"),{checkPermission}=require("../middleware/permission.middleware");r.use(authenticate);const p=(name,handler)=>r.get(name==="dashboard"?"/dashboard/overview":`/analytics/${name}`,checkPermission(`analytics.${name}.read`,{custom:true}),handler);p("dashboard",c.overview);p("sales",c.sales);p("orders",c.orders);p("customers",c.customers);p("products",c.products);p("inventory",c.inventory);p("payments",c.payments);p("delivery",c.delivery);p("loyalty",c.loyalty);p("support",c.support);p("recommendations",c.recommendations);module.exports=r;
+6
View File
@@ -49,6 +49,9 @@ const helpRoutes = require("./help.routes");
const helpAdminRoutes = require("./helpAdmin.routes");
const newsletterRoutes = require("./newsletter.routes");
const recommendationRoutes = require("./recommendation.routes");
const analyticsRoutes = require("./analytics.routes");
const accountOverviewRoutes = require("./accountOverview.routes");
const metricsRoutes = require("./metrics.routes");
const router = express.Router();
@@ -90,5 +93,8 @@ router.use("/", helpRoutes);
router.use("/admin", helpAdminRoutes);
router.use("/", newsletterRoutes);
router.use("/", recommendationRoutes);
router.use("/admin", analyticsRoutes);
router.use("/", accountOverviewRoutes);
router.use("/admin", metricsRoutes);
module.exports = router;
+1
View File
@@ -0,0 +1 @@
const r=require("express").Router(),c=require("../controllers/metrics.controller"),{authenticate}=require("../middleware/auth.middleware"),{checkPermission}=require("../middleware/permission.middleware");r.get("/metrics",authenticate,checkPermission("system.metrics.read",{custom:true}),c.read);module.exports=r;
@@ -0,0 +1,12 @@
const{Op,fn,col,literal}=require("sequelize"),db=require("../../models");const PAID=["PAID","PARTIALLY_REFUNDED","REFUNDED"],range=(query={},maxDays=366)=>{const to=query.to?new Date(`${query.to}T23:59:59.999Z`):new Date(),from=query.from?new Date(`${query.from}T00:00:00.000Z`):new Date(to.getTime()-29*86400000);if(Number.isNaN(+from)||Number.isNaN(+to)||from>to||to-from>maxDays*86400000)throw Object.assign(new Error(`Date range must be valid and at most ${maxDays} days`),{status:400,code:"INVALID_DATE_RANGE"});return{from,to,where:{[Op.between]:[from,to]}};},sum=x=>String(x||"0.00"),countBy=async(Model,column,where)=>Model.findAll({where,attributes:[column,[fn("COUNT",col(column)),"count"]],group:[column],raw:true});
async function overview(){const[orders,customers,businesses,products,openTickets,deliveries,lowStock]=await Promise.all([db.Order.count({where:{status:{[Op.ne]:"CANCELLED"}}}),db.User.count({where:{accountType:"customer",accountStatus:"ACTIVE"}}),db.BusinessCustomer.count({where:{status:"ACTIVE"}}),db.Product.count({where:{status:"ACTIVE",visibility:"PUBLIC"}}),db.SupportTicket.count({where:{status:{[Op.notIn]:["RESOLVED","CLOSED","CANCELLED"]}}}),db.Shipment.count({where:{status:{[Op.in]:["ASSIGNED","PICKUP_PENDING","PICKED_UP","IN_TRANSIT","OUT_FOR_DELIVERY"]}}}),db.InventoryBalance.count({where:{[Op.and]:[literal("on_hand - reserved > 0"),literal("on_hand - reserved <= low_stock_threshold")]}})]);const paid=await db.Payment.sum("amount",{where:{status:{[Op.in]:PAID}}});return{orders,paidRevenue:sum(paid),activeCustomers:customers,activeBusinessCustomers:businesses,activeProducts:products,lowStockProducts:lowStock,openSupportTickets:openTickets,activeDeliveries:deliveries};}
async function sales(q){const r=range(q),where={status:{[Op.in]:PAID},paid_at:r.where},[gross,refunds,orderCount,units]=await Promise.all([db.Payment.sum("amount",{where}),db.Refund.sum("amount",{where:{status:"COMPLETED",processed_at:r.where}}),db.Payment.count({where}),db.OrderItem.sum("quantity",{where:{createdAt:r.where}})]),g=Number(gross||0),f=Number(refunds||0);return{range:{from:r.from,to:r.to,timezone:"UTC"},grossPaidSales:g.toFixed(2),refunds:f.toFixed(2),netSales:(g-f).toFixed(2),paidPaymentCount:orderCount,averageOrderValue:(orderCount?g/orderCount:0).toFixed(2),unitsSold:Number(units||0),formula:"paid payment amount - completed refund amount"};}
async function orders(q){const r=range(q),where={createdAt:r.where},[byStatus,total,cancelled,returns,refunds]=await Promise.all([countBy(db.Order,"status",where),db.Order.count({where}),db.Order.count({where:{...where,status:"CANCELLED"}}),db.ReturnRequest.count({where:{requested_at:r.where}}),db.Refund.count({where:{requested_at:r.where}})]);return{range:r,byStatus,total,cancelled,cancellationRate:total?cancelled/total:0,returns,returnRate:total?returns/total:0,refunds,refundRate:total?refunds/total:0};}
async function customers(q){const r=range(q),[newCustomers,activeCustomers,retail,business]=await Promise.all([db.User.count({where:{createdAt:r.where,accountType:"customer"}}),db.Order.count({where:{createdAt:r.where,status:{[Op.ne]:"CANCELLED"},user_id:{[Op.ne]:null}},distinct:true,col:"user_id"}),db.User.count({where:{accountType:"customer",accountStatus:"ACTIVE"}}),db.BusinessCustomer.count({where:{status:"ACTIVE"}})]);return{range:r,newCustomers,activeCustomers,activeDefinition:"unique customers with a non-cancelled order in range",activeRetailAccounts:retail,activeBusinessAccounts:business};}
async function products(q){const r=range(q);return{range:r,topSelling:await db.OrderItem.findAll({where:{createdAt:r.where},attributes:["product_id",[fn("SUM",col("quantity")),"units"],[fn("SUM",col("line_total")),"revenue"]],group:["product_id"],order:[[literal("units"),"DESC"]],limit:20,raw:true}),lowStock:await db.InventoryBalance.findAll({where:{[Op.and]:[literal("on_hand - reserved > 0"),literal("on_hand - reserved <= low_stock_threshold")]},attributes:["variant_id","warehouse_id","on_hand","reserved","low_stock_threshold"],limit:100,raw:true})};}
async function inventory(){return{byWarehouse:await db.InventoryBalance.findAll({attributes:["warehouse_id",[fn("SUM",col("on_hand")),"onHand"],[fn("SUM",col("reserved")),"reserved"]],group:["warehouse_id"],limit:100,raw:true}),outOfStock:await db.InventoryBalance.count({where:literal("on_hand - reserved <= 0")}),valuation:null,valuationReason:"Cost of goods is not modeled"};}
async function payments(q){const r=range(q),[byStatus,byProvider,successfulAmount,refundAmount]=await Promise.all([countBy(db.Payment,"status",{createdAt:r.where}),countBy(db.Payment,"provider",{createdAt:r.where}),db.Payment.sum("amount",{where:{paid_at:r.where,status:{[Op.in]:PAID}}}),db.Refund.sum("amount",{where:{processed_at:r.where,status:"COMPLETED"}})]);return{range:r,byStatus,byProvider,successfulAmount:sum(successfulAmount),refundAmount:sum(refundAmount)};}
async function delivery(q){const r=range(q);return{range:r,byStatus:await countBy(db.Shipment,"status",{createdAt:r.where}),riderWorkload:await db.Shipment.findAll({where:{assigned_rider_id:{[Op.ne]:null},status:{[Op.notIn]:["DELIVERED","CANCELLED"]}},attributes:["assigned_rider_id",[fn("COUNT",col("id")),"active"]],group:["assigned_rider_id"],limit:100,raw:true})};}
async function loyalty(q){const r=range(q),[issued,redeemed,expired,accounts,tiers,rewards]=await Promise.all([db.LoyaltyLedgerEntry.sum("points_delta",{where:{occurred_at:r.where,type:"EARN"}}),db.LoyaltyLedgerEntry.sum("points_delta",{where:{occurred_at:r.where,type:"REDEEM"}}),db.LoyaltyLedgerEntry.sum("points_delta",{where:{occurred_at:r.where,type:"EXPIRE"}}),db.LoyaltyAccount.count({where:{status:"ACTIVE"}}),countBy(db.LoyaltyAccount,"current_tier_id",{}),db.LoyaltyRedemption.count({where:{redeemed_at:r.where,status:"COMPLETED"}})]);return{range:r,pointsIssued:Number(issued||0),pointsRedeemed:Math.abs(Number(redeemed||0)),pointsExpired:Math.abs(Number(expired||0)),activeAccounts:accounts,membershipDistribution:tiers,rewardRedemptions:rewards};}
async function support(q){const r=range(q),[byStatus,byPriority,breaches,agents]=await Promise.all([countBy(db.SupportTicket,"status",{createdAt:r.where}),countBy(db.SupportTicket,"priority",{createdAt:r.where}),db.SupportEscalation.count({where:{createdAt:r.where}}),db.SupportTicket.findAll({where:{assigned_agent_id:{[Op.ne]:null},status:{[Op.notIn]:["CLOSED","CANCELLED"]}},attributes:["assigned_agent_id",[fn("COUNT",col("id")),"tickets"]],group:["assigned_agent_id"],limit:100,raw:true})]);return{range:r,byStatus,byPriority,slaBreaches:breaches,agentWorkload:agents};}
async function recommendations(q){const r=range(q);return{range:r,byEventType:await countBy(db.ProductInteractionEvent,"event_type",{occurred_at:r.where}),topViewed:await db.ProductInteractionEvent.findAll({where:{occurred_at:r.where,event_type:"PRODUCT_VIEW"},attributes:["product_id",[fn("COUNT",col("id")),"views"]],group:["product_id"],order:[[literal("views"),"DESC"]],limit:20,raw:true}),conversionAttribution:null};}module.exports={range,overview,sales,orders,customers,products,inventory,payments,delivery,loyalty,support,recommendations};
+1
View File
@@ -0,0 +1 @@
const started=Date.now(),counts=new Map(),durations=new Map();const observe=(method,status,duration)=>{const key=`${method}:${Math.floor(status/100)}xx`;counts.set(key,(counts.get(key)||0)+1);durations.set(key,(durations.get(key)||0)+duration);};const middleware=(req,res,next)=>{const begin=process.hrtime.bigint();res.on("finish",()=>observe(req.method,res.statusCode,Number(process.hrtime.bigint()-begin)/1e9));next();};const render=()=>{const lines=["# HELP zumri_process_uptime_seconds Process uptime","# TYPE zumri_process_uptime_seconds gauge",`zumri_process_uptime_seconds ${Math.floor((Date.now()-started)/1000)}`,"# HELP zumri_http_requests_total HTTP requests by method and status class","# TYPE zumri_http_requests_total counter"];for(const[k,v]of counts){const[method,status_class]=k.split(":");lines.push(`zumri_http_requests_total{method="${method}",status_class="${status_class}"} ${v}`);}lines.push("# HELP zumri_http_request_duration_seconds_sum HTTP request duration","# TYPE zumri_http_request_duration_seconds_sum counter");for(const[k,v]of durations){const[method,status_class]=k.split(":");lines.push(`zumri_http_request_duration_seconds_sum{method="${method}",status_class="${status_class}"} ${v}`);}lines.push(`# TYPE zumri_process_resident_memory_bytes gauge`,`zumri_process_resident_memory_bytes ${process.memoryUsage().rss}`);return `${lines.join("\n")}\n`;};module.exports={observe,middleware,render};