admin.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. 'use strict';
  2. const { CrudService } = require('naf-framework-mongoose-free/lib/service');
  3. const { BusinessError, ErrorCode } = require('naf-core').Error;
  4. const _ = require('lodash');
  5. const assert = require('assert');
  6. const { ObjectId } = require('mongoose').Types;
  7. const moment = require('moment');
  8. // 中台统计
  9. class AdminService extends CrudService {
  10. constructor(ctx) {
  11. super(ctx, 'admin');
  12. this.goods = this.ctx.model.Shop.Goods;
  13. this.goodsSpec = this.ctx.model.Shop.GoodsSpec;
  14. this.orderModel = this.ctx.model.Trade.Order;
  15. this.orderDetailModel = this.ctx.model.Trade.OrderDetail;
  16. this.afterSaleModel = this.ctx.model.Trade.AfterSale;
  17. this.tf = 'YYYY-MM-DD';
  18. }
  19. // 待办事项
  20. async todo(query) {
  21. const notPay = await this.notPay(query);
  22. const notSend = await this.notSend(query);
  23. const notDealAfterSale = await this.notDealAfterSale(query);
  24. const stockWarning = await this.stockWarning(query);
  25. const sMarkOrder = await this.makeOrder(query);
  26. const sAfterSale = await this.makeAfterSale(query);
  27. return { notPay, notSend, notDealAfterSale, stockWarning, sMarkOrder, sAfterSale };
  28. }
  29. // 待办-库存警告
  30. async stockWarning(query) {
  31. // TODO,库存警告线需要设置
  32. const warningLine = 10;
  33. const { shop } = query;
  34. // 过滤警告线以外的数据
  35. const pipline = [{ $match: { status: '0', num: { $lte: warningLine } } }];
  36. // 因为不需要数据,节省内存,将没用的字段过滤掉
  37. pipline.push({ $project: { goods_id: { $toObjectId: '$goods' }, goodsSpec_id: { $toString: '$_id' }, num: '$num' } });
  38. // 表关联
  39. pipline.push({
  40. $lookup: {
  41. from: 'goods',
  42. localField: 'goods_id',
  43. foreignField: '_id',
  44. as: 'goods',
  45. },
  46. });
  47. // 平铺
  48. pipline.push({ $unwind: '$goods' });
  49. // 再组织数据
  50. pipline.push({ $project: { goods_id: '$goods_id', goodsSpec_id: '$goodsSpec_id', num: '$num', shop: { $toString: '$goods.shop' } } });
  51. if (ObjectId.isValid(shop)) {
  52. pipline.push({ $match: { shop } });
  53. }
  54. pipline.push(this.totalPip());
  55. const result = await this.goodsSpec.aggregate(pipline);
  56. return this.getTotal(result);
  57. }
  58. // 待办-未处理售后
  59. async notDealAfterSale(query) {
  60. const { shop } = query;
  61. const pipline = [{ $match: { status: '0' } }];
  62. if (ObjectId.isValid(shop)) {
  63. pipline.push({ $match: { shop } });
  64. }
  65. pipline.push(this.totalPip());
  66. const result = await this.afterSaleModel.aggregate(pipline);
  67. return this.getTotal(result);
  68. }
  69. // 待办-未发货
  70. async notSend(query) {
  71. const { shop } = query;
  72. const pipline = [{ $match: { status: '1' } }];
  73. if (ObjectId.isValid(shop)) {
  74. pipline.push({ $match: { shop } });
  75. }
  76. pipline.push(this.totalPip());
  77. const result = await this.orderDetailModel.aggregate(pipline);
  78. return this.getTotal(result);
  79. }
  80. // 待办-未付款订单数
  81. async notPay(query) {
  82. const { shop } = query;
  83. const pipline = [{ $match: { status: '0' } }];
  84. if (ObjectId.isValid(shop)) {
  85. pipline.push({ $match: { 'goods.shop': shop } });
  86. }
  87. pipline.push(this.totalPip());
  88. const result = await this.orderModel.aggregate(pipline);
  89. return this.getTotal(result);
  90. }
  91. /**
  92. * 统计-下单数(生成order,不管支不支付)
  93. * @param {Object} query 地址参数
  94. * @return {Array} 统计数据
  95. * @property {String} type 数据类型
  96. ** 默认为从 当天/本周/本月/本年
  97. ** custom:自定义,只查这个时间段的,每天的数
  98. ** day:单数据(一个数);
  99. ** week:指定(当前)周-每天的数; (默认)
  100. ** monthWeek:指定(当前)月-每周的数;
  101. ** monthDay:指定(当前)月-每天的数;
  102. ** yearMonth:指定(当前)年-每月的数;
  103. ** yearWeek:指定(当前)年-每周的数;
  104. ** yearDay:指定(当前)年-每天的数;
  105. * @property {Array} start 开始
  106. * @property {Array} end 结束
  107. * @property {Array} range 指定的时间范围;如果没传,则根据type自动生成
  108. */
  109. async makeOrder(query) {
  110. const pipline = [];
  111. const { type = 'week', start, end, shop } = query;
  112. let timeRange = [];
  113. if (type === 'custom') {
  114. const r = moment(start).isBefore(end);
  115. if (r) timeRange.push(start, end);
  116. else timeRange.push(end, start);
  117. } else timeRange = this.getDefaultRange(type);
  118. // 先用商店过滤下
  119. if (ObjectId.isValid(shop)) {
  120. pipline.push({ $match: { shop } });
  121. }
  122. // 时间格式化: create_date:日期,用来分组统计; create_time: 时间,用来过滤数据
  123. pipline.push({
  124. $project: {
  125. create_date: { $dateToString: { date: '$meta.createdAt', ...this.pipDateFormatDateObject() } },
  126. create_time: { $dateToString: { date: '$meta.createdAt', ...this.pipDateFormatTimeObject() } },
  127. },
  128. });
  129. pipline.push({ $match: { $and: [{ create_time: { $gte: _.head(timeRange) } }, { create_time: { $lte: _.last(timeRange) } }] } });
  130. pipline.push({
  131. $group: {
  132. _id: '$create_date',
  133. num: { $sum: 1 },
  134. },
  135. });
  136. const fs = await this.orderModel.aggregate(pipline);
  137. const list = this.getRangeDateList(timeRange);
  138. const result = [];
  139. for (const d of list) {
  140. const r = fs.find(f => f._id === d);
  141. const obj = { date: d, num: 0 };
  142. if (r) obj.num = r.num;
  143. result.push(obj);
  144. }
  145. return result;
  146. }
  147. /**
  148. * 售后数
  149. * @param {Object} query 查询条件
  150. */
  151. async makeAfterSale(query) {
  152. const pipline = [];
  153. const { type = 'week', start, end, shop } = query;
  154. let timeRange = [];
  155. if (type === 'custom') {
  156. const r = moment(start).isBefore(end);
  157. if (r) timeRange.push(start, end);
  158. else timeRange.push(end, start);
  159. } else timeRange = this.getDefaultRange(type);
  160. // 先用商店过滤下
  161. if (ObjectId.isValid(shop)) {
  162. pipline.push({ $match: { shop } });
  163. }
  164. // 时间格式化: create_date:日期,用来分组统计; create_time: 时间,用来过滤数据
  165. pipline.push({
  166. $project: {
  167. create_date: { $dateToString: { date: '$meta.createdAt', ...this.pipDateFormatDateObject() } },
  168. create_time: { $dateToString: { date: '$meta.createdAt', ...this.pipDateFormatTimeObject() } },
  169. },
  170. });
  171. pipline.push({ $match: { $and: [{ create_time: { $gte: _.head(timeRange) } }, { create_time: { $lte: _.last(timeRange) } }] } });
  172. pipline.push({
  173. $group: {
  174. _id: '$create_date',
  175. num: { $sum: 1 },
  176. },
  177. });
  178. const fs = await this.afterSaleModel.aggregate(pipline);
  179. const list = this.getRangeDateList(timeRange);
  180. const result = [];
  181. for (const d of list) {
  182. const r = fs.find(f => f._id === d);
  183. const obj = { date: d, num: 0 };
  184. if (r) obj.num = r.num;
  185. result.push(obj);
  186. }
  187. return result;
  188. }
  189. // 聚合总数管道
  190. totalPip() {
  191. return {
  192. $count: 'total',
  193. };
  194. }
  195. /**
  196. * 取出聚合查询的总数
  197. * @param {Array} data 聚合查询总数的结果($count)
  198. * @param {String} key 总数的字段
  199. * @return {Number} 返回总数
  200. */
  201. getTotal(data, key = 'total') {
  202. return _.get(_.head(data), key, 0);
  203. }
  204. /**
  205. * 管道格式化时间格式--至时分秒
  206. * @return {Object}
  207. */
  208. pipDateFormatTimeObject() {
  209. return { format: '%Y-%m-%d %H:%M:%S', timezone: '+08:00' };
  210. }
  211. /**
  212. * 管道格式化室间隔是-至日期
  213. * @return {Object}
  214. */
  215. pipDateFormatDateObject() {
  216. return { format: '%Y-%m-%d', timezone: '+08:00' };
  217. }
  218. /**
  219. * 获取时间范围内的每一天
  220. * @param {Array} range 时间范围
  221. * @return {Array} 时间范围数组
  222. */
  223. getRangeDateList(range) {
  224. const start = _.head(range);
  225. const end = _.last(range);
  226. const list = [ start ];
  227. let pd = 1;
  228. while (!moment(_.last(list)).isSame(end)) {
  229. const d = moment(start).add(pd, 'day').format(this.tf);
  230. pd++;
  231. list.push(d);
  232. }
  233. return list;
  234. }
  235. /**
  236. *
  237. * @param {String} type 类型
  238. ** week:指定(当前)周-每天的数; (默认)
  239. ** monthWeek:指定(当前)月-每周的数;
  240. ** monthDay:指定(当前)月-每天的数;
  241. ** yearMonth:指定(当前)年-每月的数;
  242. ** yearWeek:指定(当前)年-每周的数;
  243. ** yearDay:指定(当前)年-每天的数;
  244. */
  245. getDefaultRange(type) {
  246. let result;
  247. if (type === 'day') {
  248. const start = moment().startOf('day').format(this.tf);
  249. const end = moment(start).add(1, 'day').format(this.tf);
  250. result = [ start, end ];
  251. } else if (type === 'week') {
  252. const start = moment().startOf('week').format(this.tf);
  253. const end = moment(start).add(1, 'week').format(this.tf);
  254. result = [ start, end ];
  255. } else if (type.includes('month')) {
  256. const start = moment().startOf('month').format(this.tf);
  257. const end = moment(start).add(1, 'month').format(this.tf);
  258. result = [ start, end ];
  259. } else if (type.includes('year')) {
  260. const start = moment().startOf('year').format(this.tf);
  261. const end = moment(start).add(1, 'year').format(this.tf);
  262. result = [ start, end ];
  263. }
  264. return result;
  265. }
  266. }
  267. module.exports = AdminService;