123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331 |
- '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;
|