'use strict'; const { CrudService } = require('naf-framework-mongoose-free/lib/service'); const { BusinessError, ErrorCode } = require('naf-core').Error; const _ = require('lodash'); const assert = require('assert'); const { ObjectId } = require('mongoose').Types; const moment = require('moment'); // 中台统计 class AdminService extends CrudService { constructor(ctx) { super(ctx, 'admin'); this.goods = this.ctx.model.Shop.Goods; this.goodsSpec = this.ctx.model.Shop.GoodsSpec; this.orderModel = this.ctx.model.Trade.Order; this.orderDetailModel = this.ctx.model.Trade.OrderDetail; this.afterSaleModel = this.ctx.model.Trade.AfterSale; this.tf = 'YYYY-MM-DD'; } // 待办事项 async todo(query) { const notPay = await this.notPay(query); const notSend = await this.notSend(query); const notDealAfterSale = await this.notDealAfterSale(query); const stockWarning = await this.stockWarning(query); const sMarkOrder = await this.makeOrder(query); const sAfterSale = await this.makeAfterSale(query); return { notPay, notSend, notDealAfterSale, stockWarning, sMarkOrder, sAfterSale }; } // 待办-库存警告 async stockWarning(query) { // TODO,库存警告线需要设置 const warningLine = 10; const { shop } = query; // 过滤警告线以外的数据 const pipline = [{ $match: { status: '0', num: { $lte: warningLine } } }]; // 因为不需要数据,节省内存,将没用的字段过滤掉 pipline.push({ $project: { goods_id: { $toObjectId: '$goods' }, goodsSpec_id: { $toString: '$_id' }, num: '$num' } }); // 表关联 pipline.push({ $lookup: { from: 'goods', localField: 'goods_id', foreignField: '_id', as: 'goods', }, }); // 平铺 pipline.push({ $unwind: '$goods' }); // 再组织数据 pipline.push({ $project: { goods_id: '$goods_id', goodsSpec_id: '$goodsSpec_id', num: '$num', shop: { $toString: '$goods.shop' } } }); if (ObjectId.isValid(shop)) { pipline.push({ $match: { shop } }); } pipline.push(this.totalPip()); const result = await this.goodsSpec.aggregate(pipline); return this.getTotal(result); } // 待办-未处理售后 async notDealAfterSale(query) { const { shop } = query; const pipline = [{ $match: { status: '0' } }]; if (ObjectId.isValid(shop)) { pipline.push({ $match: { shop } }); } pipline.push(this.totalPip()); const result = await this.afterSaleModel.aggregate(pipline); return this.getTotal(result); } // 待办-未发货 async notSend(query) { const { shop } = query; const pipline = [{ $match: { status: '1' } }]; if (ObjectId.isValid(shop)) { pipline.push({ $match: { shop } }); } pipline.push(this.totalPip()); const result = await this.orderDetailModel.aggregate(pipline); return this.getTotal(result); } // 待办-未付款订单数 async notPay(query) { const { shop } = query; const pipline = [{ $match: { status: '0' } }]; if (ObjectId.isValid(shop)) { pipline.push({ $match: { 'goods.shop': shop } }); } pipline.push(this.totalPip()); const result = await this.orderModel.aggregate(pipline); return this.getTotal(result); } /** * 统计-下单数(生成order,不管支不支付) * @param {Object} query 地址参数 * @return {Array} 统计数据 * @property {String} type 数据类型 ** 默认为从 当天/本周/本月/本年 ** custom:自定义,只查这个时间段的,每天的数 ** day:单数据(一个数); ** week:指定(当前)周-每天的数; (默认) ** monthWeek:指定(当前)月-每周的数; ** monthDay:指定(当前)月-每天的数; ** yearMonth:指定(当前)年-每月的数; ** yearWeek:指定(当前)年-每周的数; ** yearDay:指定(当前)年-每天的数; * @property {Array} start 开始 * @property {Array} end 结束 * @property {Array} range 指定的时间范围;如果没传,则根据type自动生成 * @property {String} status 订单状态(1为,支付单) */ async makeOrder(query) { const pipline = []; const { type = 'week', start, end, shop, status } = query; let timeRange = []; if (type === 'custom') { const r = moment(start).isBefore(end); if (r) timeRange.push(start, end); else timeRange.push(end, start); } else timeRange = this.getDefaultRange(type); // 先用商店过滤下 if (ObjectId.isValid(shop)) { pipline.push({ $match: { 'goods.shop': shop } }); } if (status) { pipline.push({ $match: { status } }); } // 时间格式化: create_date:日期,用来分组统计; create_time: 时间,用来过滤数据 pipline.push({ $addFields: { create_date: { $dateToString: { date: '$meta.createdAt', ...this.pipDateFormatDateObject() } }, create_time: { $dateToString: { date: '$meta.createdAt', ...this.pipDateFormatTimeObject() } }, }, }); pipline.push({ $match: { $and: [{ buy_time: { $gte: _.head(timeRange) } }, { buy_time: { $lte: _.last(timeRange) } }] } }); pipline.push({ $group: { _id: '$create_date', num: { $sum: 1 }, }, }); const fs = await this.orderModel.aggregate(pipline); const list = this.getRangeDateList(timeRange); const result = []; for (const d of list) { const r = fs.find(f => f._id === d); const obj = { date: d, num: 0 }; if (r) obj.num = r.num; result.push(obj); } return result; } /** * 售后数 * @param {Object} query 查询条件 */ async makeAfterSale(query) { const pipline = []; const { type = 'week', start, end, shop, status } = query; let timeRange = []; if (type === 'custom') { const r = moment(start).isBefore(end); if (r) timeRange.push(start, end); else timeRange.push(end, start); } else timeRange = this.getDefaultRange(type); // 先用商店过滤下 if (ObjectId.isValid(shop)) { pipline.push({ $match: { shop } }); } if (status) { pipline.push({ $match: { status } }); } // 时间格式化: create_date:日期,用来分组统计; create_time: 时间,用来过滤数据 pipline.push({ $project: { create_date: { $dateToString: { date: '$meta.createdAt', ...this.pipDateFormatDateObject() } }, create_time: { $dateToString: { date: '$meta.createdAt', ...this.pipDateFormatTimeObject() } }, }, }); pipline.push({ $match: { $and: [{ create_time: { $gte: _.head(timeRange) } }, { create_time: { $lte: _.last(timeRange) } }] } }); pipline.push({ $group: { _id: '$create_date', num: { $sum: 1 }, }, }); const fs = await this.afterSaleModel.aggregate(pipline); const list = this.getRangeDateList(timeRange); const result = []; for (const d of list) { const r = fs.find(f => f._id === d); const obj = { date: d, num: 0 }; if (r) obj.num = r.num; result.push(obj); } return result; } /** * 查询销售额 * @param {Object} query 查询条件 */ async sellTotal(query) { const pipline = []; const { type = 'week', start, end, shop } = query; let timeRange = []; if (type === 'custom') { const r = moment(start).isBefore(end); if (r) timeRange.push(start, end); else timeRange.push(end, start); } else timeRange = this.getDefaultRange(type); // 先用商店过滤下 if (ObjectId.isValid(shop)) { pipline.push({ $match: { 'goods.shop': shop } }); } pipline.push({ $match: { status: '1' } }); // 时间格式化: create_date:日期,用来分组统计; create_time: 时间,用来过滤数据 pipline.push({ $project: { create_date: { $dateToString: { date: '$meta.createdAt', ...this.pipDateFormatDateObject() } }, create_time: { $dateToString: { date: '$meta.createdAt', ...this.pipDateFormatTimeObject() } }, money: '$pay.pay_money', }, }); pipline.push({ $match: { $and: [{ create_time: { $gte: _.head(timeRange) } }, { create_time: { $lte: _.last(timeRange) } }] } }); pipline.push({ $group: { _id: '$create_date', money: { $sum: '$money' }, }, }); const fs = await this.orderModel.aggregate(pipline); const list = this.getRangeDateList(timeRange); const result = []; for (const d of list) { const r = fs.find(f => f._id === d); const obj = { date: d, money: 0 }; if (r) obj.money = this.ctx.toNumber(r.money); result.push(obj); if (type === 'day') break; } return result; } // 聚合总数管道 totalPip() { return { $count: 'total', }; } /** * 取出聚合查询的总数 * @param {Array} data 聚合查询总数的结果($count) * @param {String} key 总数的字段 * @return {Number} 返回总数 */ getTotal(data, key = 'total') { return _.get(_.head(data), key, 0); } /** * 管道格式化时间格式--至时分秒 * @return {Object} */ pipDateFormatTimeObject() { return { format: '%Y-%m-%d %H:%M:%S', timezone: '+08:00' }; } /** * 管道格式化室间隔是-至日期 * @return {Object} */ pipDateFormatDateObject() { return { format: '%Y-%m-%d', timezone: '+08:00' }; } /** * 获取时间范围内的每一天 * @param {Array} range 时间范围 * @return {Array} 时间范围数组 */ getRangeDateList(range) { const start = _.head(range); const end = _.last(range); const list = [ start ]; let pd = 1; while (!moment(_.last(list)).isSame(end)) { const d = moment(start).add(pd, 'day').format(this.tf); pd++; list.push(d); } return list; } /** * * @param {String} type 类型 ** week:指定(当前)周-每天的数; (默认) ** monthWeek:指定(当前)月-每周的数; ** monthDay:指定(当前)月-每天的数; ** yearMonth:指定(当前)年-每月的数; ** yearWeek:指定(当前)年-每周的数; ** yearDay:指定(当前)年-每天的数; */ getDefaultRange(type) { let result; if (type === 'day') { const start = moment().startOf('day').format(`${this.tf}`); const end = moment(start).add(1, 'day').format(`${this.tf}`); result = [ start, end ]; } else if (type === 'week') { const start = moment().startOf('week').format(this.tf); const end = moment(start).add(1, 'week').format(this.tf); result = [ start, end ]; } else if (type.includes('month')) { const start = moment().startOf('month').format(this.tf); const end = moment(start).add(1, 'month').format(this.tf); result = [ start, end ]; } else if (type.includes('year')) { const start = moment().startOf('year').format(this.tf); const end = moment(start).add(1, 'year').subtract(1, 'day') .format(this.tf); result = [ start, end ]; } return result; } } module.exports = AdminService;