tVehicleRecordService2.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. 'use strict';
  2. const moment = require('moment');
  3. const Service = require('egg').Service;
  4. class TVehicleRecordService2 extends Service {
  5. async register(cond) {
  6. const { ctx } = this;
  7. const agg = ctx.helper.getCommonAggSum({ ...cond, value: 'saledNewUser' });
  8. return await ctx.model.Local.TVehicleRecordModel.aggregateFix(agg);
  9. }
  10. // 如果在用户表计算 无法算到过去的实销用户数量
  11. async saledUser(cond) {
  12. const { ctx } = this;
  13. const agg = ctx.helper.getCommonAggMax({ ...cond, value: 'saledUser' });
  14. return await ctx.model.Local.TVehicleRecordModel.aggregateFix(agg);
  15. }
  16. async saledExt() {
  17. const { ctx } = this;
  18. // 最高哪天 最高是哪月 当前总数 当前总数app 当前总数ivi
  19. // const max = await ctx.model.Local.TRegisterInfoModel.findOne().sort({ total: -1 });
  20. // let maxDay = {};
  21. // if (max) {
  22. // maxDay = { _id: { year: max.year, month: max.month, day: max.day }, count: max.total };
  23. // }
  24. // const agg = [
  25. // { $group: { _id: { year: '$year', month: '$month' }, count: { $sum: '$total' } } },
  26. // { $sort: { count: -1 } },
  27. // { $group: { _id: null, data: { $first: '$$ROOT' } } },
  28. // // 或者
  29. // // { $group: { _id: null, data: { $max: { count: '$count', _id: { year: '$_id.year', month: '$_id.month' } } } } },
  30. // ];
  31. // const result = await ctx.model.Local.TRegisterInfoModel.aggregateNGroup(agg);
  32. // const maxMonth = result.data || {};
  33. const user = await ctx.model.Local.TRbacUserModel.findOne({}, {
  34. appTotal: 1, iviTotal: 1,
  35. saledTotal: 1, saledAppTotal: 1, saledIviTotal: 1,
  36. }).sort({ create_date: -1 });
  37. let appTotal = 0;
  38. let iviTotal = 0;
  39. let saledTotal = 0;
  40. let saledAppTotal = 0;
  41. let saledIviTotal = 0;
  42. if (user) {
  43. appTotal = user.appTotal;
  44. iviTotal = user.iviTotal;
  45. saledTotal = user.saledTotal;
  46. saledAppTotal = user.saledAppTotal;
  47. saledIviTotal = user.saledIviTotal;
  48. }
  49. return { saledTotal, saledAppTotal, saledIviTotal, appTotal, iviTotal };
  50. }
  51. // 实销车辆统计分析 实销车辆百分比统计分析 入网车辆总数统计分析
  52. async index({ type, startTime, endTime, seriesCode, modelCode }) {
  53. const { ctx } = this;
  54. const cond = [{ $match: {} }];
  55. if (seriesCode) {
  56. cond[0].$match['car._id.series_code'] = seriesCode;
  57. }
  58. if (modelCode) {
  59. cond[0].$match['car._id.model_code'] = modelCode;
  60. }
  61. const agg = [
  62. { $match: ctx.helper.getTimeRangMatch(startTime, endTime) },
  63. { $sort: { create_date: -1 } },
  64. { $group: ctx.helper.getTimeGroup(type, { car: { $first: '$car' } }) },
  65. { $unwind: '$car' },
  66. ...cond,
  67. { $group: { _id: '$_id', count: { $sum: '$car.count' },
  68. saledCount: { $sum: '$car.saledCount' },
  69. } },
  70. ];
  71. return await ctx.model.Local.TVehicleRecordModel.aggregateFix(agg);
  72. }
  73. async increment({ type, startTime, endTime, seriesCode, modelCode }) {
  74. const { ctx } = this;
  75. const cond = [{ $match: {} }];
  76. if (seriesCode) {
  77. cond[0].$match['car._id.series_code'] = seriesCode;
  78. }
  79. if (modelCode) {
  80. cond[0].$match['car._id.model_code'] = modelCode;
  81. }
  82. const agg = [
  83. { $match: ctx.helper.getTimeRangMatch(startTime, endTime) },
  84. { $unwind: '$car' },
  85. ...cond,
  86. { $group: ctx.helper.getTimeGroup(type, { count: { $sum: '$car.saledNewTotal' } }) },
  87. ];
  88. return await ctx.model.Local.TVehicleRecordModel.aggregateFix(agg);
  89. }
  90. // 在线车
  91. async online({ type, startTime, endTime, seriesCode, modelCode }) {
  92. const { ctx } = this;
  93. const cond = [{ $match: {} }];
  94. if (seriesCode) {
  95. cond[0].$match['onlineCar._id.series_code'] = seriesCode;
  96. }
  97. if (modelCode) {
  98. cond[0].$match['onlineCar._id.model_code'] = modelCode;
  99. }
  100. const agg = [
  101. { $match: ctx.helper.getTimeRangMatch(startTime, endTime) },
  102. { $unwind: '$onlineCar' },
  103. ...cond,
  104. { $group: ctx.helper.getTimeGroup(type, { count: { $sum: '$onlineCar.count' } }) },
  105. ];
  106. return await ctx.model.Local.TVehicleRecordModel.aggregateFix(agg);
  107. }
  108. // TODO 暂时即时查询
  109. async saled({ seriesCode, modelCode }) {
  110. const { ctx } = this;
  111. const cond = [{ $match: { is_saled_car: 1, user_id: { $ne: null } } }];
  112. if (seriesCode) {
  113. cond[0].$match.series_code = seriesCode;
  114. }
  115. if (modelCode) {
  116. cond[0].$match.model_code = modelCode;
  117. }
  118. const agg = [
  119. ...cond,
  120. { $group: { _id: '$pro_code', count: { $sum: 1 } } },
  121. { $lookup: { from: 't_sync_province', localField: '_id', foreignField: 'out_pro_code', as: 'pro' } },
  122. { $unwind: { path: '$pro', preserveNullAndEmptyArrays: true } },
  123. { $lookup: { from: 't_sync_county', localField: '_id', foreignField: 'out_pro_code', as: 'area' } },
  124. { $project: {
  125. area_name: { $arrayElemAt: [ '$area.area_name', 0 ] },
  126. provice_name: '$pro.province_name',
  127. count: '$count',
  128. } },
  129. { $group: { _id: { area_name: '$area_name' },
  130. provinces: { $push: { provice_name: '$provice_name', count: '$count' } },
  131. count: { $sum: '$count' } } },
  132. { $group: { _id: null,
  133. areas: { $push: { area_name: '$_id.area_name', provinces: '$provinces', count: '$count' } },
  134. count: { $sum: '$count' } } },
  135. ];
  136. return await ctx.model.TVehicleRecordModel.aggregateNGroup(agg);
  137. }
  138. // TODO 暂时即时查询
  139. async saledCity({ seriesCode, modelCode }) {
  140. const { ctx } = this;
  141. const cond = [{ $match: { is_saled_car: 1, user_id: { $ne: null },
  142. pro_code: { $exists: true }, city_code: { $exists: true } } }];
  143. if (seriesCode) {
  144. cond[0].$match.series_code = seriesCode;
  145. }
  146. if (modelCode) {
  147. cond[0].$match.model_code = modelCode;
  148. }
  149. const agg = [
  150. ...cond,
  151. { $group: { _id: '$city_code', count: { $sum: 1 }, pro_code: { $first: '$pro_code' }, city_code: { $first: '$city_code' } } },
  152. { $sort: { count: -1 } },
  153. { $limit: 3 },
  154. { $lookup: { from: 't_sync_province', localField: 'pro_code', foreignField: 'out_pro_code', as: 'pro' } },
  155. { $unwind: '$pro' },
  156. { $lookup: {
  157. from: 't_sync_city',
  158. let: { city_code: '$city_code', province_id: '$pro.province_id' },
  159. pipeline: [{ $match: { $expr: { $and:
  160. [
  161. { $eq: [ '$out_city_code', '$$city_code' ] },
  162. { $eq: [ '$province_id', '$$province_id' ] },
  163. ] } } }], as: 'city',
  164. } },
  165. { $unwind: '$city' },
  166. { $project: {
  167. _id: 0,
  168. count: 1,
  169. provice_name: '$pro.province_name',
  170. city_name: '$city.city_name',
  171. } },
  172. ];
  173. return await ctx.model.TVehicleRecordModel.aggregate(agg).allowDiskUse(true);
  174. }
  175. // TODO 暂时即时查询
  176. async activeLocation({ startTime, endTime, seriesCode, modelCode }) {
  177. const { ctx } = this;
  178. // const onlineAgg = [
  179. // { $match: { ...ctx.helper.getTimeRangMatch(ctx.helper.getMonthTop(startTime), endTime,
  180. // 'online_time') } },
  181. // { $group: {
  182. // _id: '$vin',
  183. // login_count: { $sum: 1 },
  184. // } },
  185. // { $match: { login_count: { $gt: 3 } } },
  186. // ];
  187. // const driveAgg = [
  188. // { $match: ctx.helper.getTimeRangMatch(ctx.helper.getMonthTop(startTime), endTime,
  189. // 'start_time') },
  190. // { $group: { _id: '$vin',
  191. // mileage: { $sum: { $cond: [ '$mileage', { $toDouble: '$mileage' }, 0 ] } } } },
  192. // { $match: { mileage: { $gt: 50 } } },
  193. // ];
  194. // const result1 = await ctx.model.TBoxOnlineModel.aggregateFix(onlineAgg);
  195. // const result2 = await ctx.model.DrivingBehaviorInfoModel.aggregateFix(driveAgg);
  196. // const unionArray = ctx.helper.unionArray(result1.map(item => item._id), result2.map(item => item._id));
  197. const result = await ctx.model.Local.TVehicleRecordModel.findOne(
  198. { year: moment(startTime).year(), month: moment(startTime).month() + 1 }, { activeVin: 1 }
  199. ).sort({ create_date: -1 });
  200. let unionArray;
  201. if (result) {
  202. unionArray = result.activeVin || [];
  203. } else {
  204. unionArray = [];
  205. }
  206. const cond = { };
  207. if (seriesCode) {
  208. cond.series_code = seriesCode;
  209. }
  210. if (modelCode) {
  211. cond.model_code = modelCode;
  212. }
  213. const agg = [
  214. { $match: { vin: { $in: unionArray }, ...cond } },
  215. { $lookup: { from: 't_sync_province', localField: 'pro_code', foreignField: 'out_pro_code', as: 'pro' } },
  216. { $unwind: '$pro' },
  217. { $lookup: {
  218. from: 't_sync_city',
  219. let: { city_code: '$city_code', province_id: '$pro.province_id' },
  220. pipeline: [{ $match: { $expr: { $and:
  221. [
  222. { $eq: [ '$out_city_code', '$$city_code' ] },
  223. { $eq: [ '$province_id', '$$province_id' ] },
  224. ] } } }], as: 'city',
  225. } },
  226. { $unwind: '$city' },
  227. { $lookup: { from: 't_sync_county', localField: 'pro_code', foreignField: 'out_pro_code', as: 'area' } },
  228. { $project: {
  229. area_name: { $arrayElemAt: [ '$area.area_name', 0 ] },
  230. provice_name: '$pro.province_name',
  231. city_name: '$city.city_name',
  232. } },
  233. { $addFields: {
  234. count: 1,
  235. } },
  236. { $match: { area_name: { $exists: true } } },
  237. ...ctx.helper.getLocationMongo(),
  238. ];
  239. const reuslt = await ctx.model.TVehicleRecordModel.aggregateNGroup(agg);
  240. return reuslt;
  241. }
  242. // 活跃车辆
  243. async active({ type, startTime, endTime, seriesCode, modelCode }) {
  244. const { ctx } = this;
  245. const cond = [{ $match: {} }];
  246. if (seriesCode) {
  247. cond[0].$match['car._id.series_code'] = seriesCode;
  248. }
  249. if (modelCode) {
  250. cond[0].$match['car._id.model_code'] = modelCode;
  251. }
  252. let agg = [];
  253. if (type == 1) {
  254. agg = [
  255. { $match: ctx.helper.getTimeRangMatch(startTime, endTime) },
  256. { $sort: { create_date: -1 } },
  257. { $group: ctx.helper.getTimeGroup(type, { car: { $first: '$car' } }) },
  258. { $unwind: '$car' },
  259. ...cond,
  260. { $group: { _id: '$_id', count: { $sum: '$car.count' },
  261. activeCount: { $sum: '$car.activeCount' } } },
  262. ];
  263. } else if (type == 2) {
  264. agg = [
  265. { $match: ctx.helper.getTimeRangMatch(startTime, endTime) },
  266. { $sort: { create_date: -1 } },
  267. { $group: ctx.helper.getTimeGroup('1', { car: { $first: '$car' } }) },
  268. { $unwind: '$car' },
  269. ...cond,
  270. { $group: { _id: { year: '$_id.year' }, count: { $sum: '$car.count' },
  271. activeCount: { $sum: '$car.activeCount' } } },
  272. ];
  273. }
  274. return await ctx.model.Local.TVehicleRecordModel.aggregateFix(agg);
  275. }
  276. // 活跃车辆 扩展数据
  277. async activeExt({ seriesCode, modelCode }) {
  278. const { ctx } = this;
  279. const cond = [{ $match: {} }];
  280. if (seriesCode) {
  281. cond[0].$match['car._id.series_code'] = seriesCode;
  282. }
  283. if (modelCode) {
  284. cond[0].$match['car._id.model_code'] = modelCode;
  285. }
  286. const maxMonthAgg = [
  287. { $sort: { create_date: -1 } },
  288. { $group: ctx.helper.getTimeGroup('1', { car: { $first: '$car' } }) },
  289. { $unwind: '$car' },
  290. ...cond,
  291. { $group: { _id: '$_id', activeCount: { $sum: '$car.activeCount' } } },
  292. { $sort: { activeCount: -1 } },
  293. { $group: { _id: null, data: { $first: '$$ROOT' } } },
  294. ];
  295. const maxYearAgg = [
  296. { $sort: { create_date: -1 } },
  297. { $group: ctx.helper.getTimeGroup('1', { car: { $first: '$car' } }) },
  298. { $unwind: '$car' },
  299. ...cond,
  300. { $group: { _id: { year: '$_id.year' }, activeCount: { $sum: '$car.activeCount' } } },
  301. { $sort: { activeCount: -1 } },
  302. { $group: { _id: null, data: { $first: '$$ROOT' } } },
  303. ];
  304. const maxMonthResult = await ctx.model.Local.TVehicleRecordModel.aggregateNGroup(maxMonthAgg);
  305. const maxYearResult = await ctx.model.Local.TVehicleRecordModel.aggregateNGroup(maxYearAgg);
  306. const maxMonth = maxMonthResult.data || {};
  307. const maxYear = maxYearResult.data || {};
  308. return { maxMonth, maxYear };
  309. }
  310. // ———————————————————清洗数据———————————————————————————————————————————————————————————————————————————————————————————————————————————————————
  311. async statistics({ timeRangData, initData, isForceUpdate }) {
  312. const { ctx } = this;
  313. const hasData = await ctx.service.statisticsService.saveBefore(ctx.model.Local.TVehicleRecordModel,
  314. { ...initData });
  315. if (hasData && !isForceUpdate) {
  316. return;
  317. }
  318. initData.start_time = new Date();
  319. const result = await this.group(timeRangData);
  320. ctx.logger.info('任务进行group');
  321. const { activeVin, aResult } = await this.activeCount(timeRangData);
  322. ctx.logger.info('任务进行activeCount');
  323. const onlineCar = await this.onlineCount(timeRangData);
  324. const car = result.car || [];
  325. aResult.forEach(c => {
  326. const obj = car.find(item => item._id.series_code == c._id.series_code && item._id.model_code == c._id.model_code);
  327. if (obj) {
  328. obj.activeCount = c.activeCount;
  329. }
  330. });
  331. ctx.logger.info('任务进行onlineCount');
  332. const saledUser = await this.saledUserCount(timeRangData);
  333. ctx.logger.info('任务进行saledUserCount');
  334. const saledNewUser = await this.saledNewUserCount(timeRangData, saledUser);
  335. ctx.logger.info('任务进行saledNewUserCount');
  336. await ctx.service.statisticsService.save(ctx.model.Local.TVehicleRecordModel,
  337. { ...initData, car, onlineCar, saledNewUser, saledUser, activeVin }, isForceUpdate);
  338. }
  339. async group({ startTime, endTime }) {
  340. const { ctx } = this;
  341. const agg = [
  342. // { $match: { sale_date: { $lt: endTime } } },
  343. { $group: { _id: { series_code: '$series_code', model_code: '$model_code' },
  344. series_name: { $first: '$series_name' }, model_name: { $first: '$model_name' },
  345. count: { $sum: 1 },
  346. saledCount: { $sum: {
  347. $cond: [{ $and: [{ $eq: [ '$is_saled_car', 1 ] }, { $lt: [ '$sale_date', endTime ] },
  348. { $ne: [{ $ifNull: [ '$user_id', null ] }, null ] }] }, 1, 0 ],
  349. } },
  350. saledNewTotal: { $sum: {
  351. $cond: [{ $and: [{ $eq: [ '$is_saled_car', 1 ] },
  352. { $gte: [ '$sale_date', startTime ] }, { $lt: [ '$sale_date', endTime ] },
  353. { $ne: [{ $ifNull: [ '$user_id', null ] }, null ] }] }, 1, 0 ],
  354. } },
  355. } },
  356. { $group: { _id: null, car: { $push: '$$ROOT' }, saledTotal: { $sum: '$saledCount' } } },
  357. ];
  358. return await ctx.model.TVehicleRecordModel.aggregateNGroup(agg);
  359. }
  360. // TODO 本js里活跃的计算方式
  361. async activeCount({ startTime, endTime }) {
  362. const { ctx } = this;
  363. const onlineAgg = [
  364. { $match: { ...ctx.helper.getTimeRangMatch(ctx.helper.getMonthTop(startTime), endTime,
  365. 'online_time') } },
  366. { $group: {
  367. _id: '$vin',
  368. login_count: { $sum: 1 },
  369. } },
  370. { $match: { login_count: { $gt: 3 } } },
  371. ];
  372. const driveAgg = [
  373. { $match: ctx.helper.getTimeRangMatch(ctx.helper.getMonthTop(startTime), endTime,
  374. 'start_time') },
  375. { $group: { _id: '$vin',
  376. mileage: { $sum: { $cond: [ '$mileage', { $toDouble: '$mileage' }, 0 ] } } } },
  377. { $match: { mileage: { $gt: 50 } } },
  378. ];
  379. const result1 = await ctx.model.TBoxOnlineModel.aggregateFix(onlineAgg);
  380. ctx.logger.info('任务进行onlineAgg');
  381. const result2 = await ctx.model.DrivingBehaviorInfoModel.aggregateFix(driveAgg);
  382. ctx.logger.info('任务进行driveAgg');
  383. const unionArray = ctx.helper.unionArray(result1.map(item => item._id), result2.map(item => item._id));
  384. ctx.logger.info('任务进行unionArray');
  385. const agg = [
  386. { $match: { create_time: { $lt: endTime }, vin: { $in: unionArray } } },
  387. { $group: { _id: { series_code: '$series_code', model_code: '$model_code' },
  388. activeCount: { $sum: 1 },
  389. } },
  390. ];
  391. const aResult = await ctx.model.TVehicleRecordModel.aggregateFix(agg);
  392. ctx.logger.info('任务进行agg');
  393. return { activeVin: unionArray, aResult };
  394. }
  395. async onlineCount({ startTime, endTime }) {
  396. const { ctx } = this;
  397. const agg = [
  398. { $match: ctx.helper.getTimeRangMatch(startTime, endTime, 'online_time') },
  399. { $group: { _id: '$vin', count: { $sum: 1 } } },
  400. { $lookup: { from: 't_vehicle_record', localField: '_id', foreignField: 'vin', as: 'car' } },
  401. { $unwind: { path: '$car', preserveNullAndEmptyArrays: true } },
  402. { $group: { _id: { series_code: '$car.series_code', model_code: '$car.model_code' },
  403. count: { $sum: 1 } } },
  404. ];
  405. return await ctx.model.TBoxOnlineModel.aggregateFix(agg);
  406. }
  407. async saledNewUserCount({ startTime, endTime }, saledUser) {
  408. const { ctx } = this;
  409. // const beforeR = await ctx.model.Local.TVehicleRecordModel.findOne(
  410. // { }, { saledUser: 1 }
  411. // ).sort({ create_date: -1 });
  412. // if (beforeR) {
  413. // return saledUser - beforeR.saledUser;
  414. // }
  415. const agg = [
  416. { $match: { is_saled_car: 1, sale_date: { $gte: startTime, $lt: endTime }, user_id: { $ne: null } } },
  417. { $group: {
  418. _id: '$user_id',
  419. } },
  420. ];
  421. const result = await ctx.model.TVehicleRecordModel.aggregateFix(agg);
  422. return await ctx.model.TRbacUserModel.find({ role_id: ctx.helper.saledRoleId,
  423. user_id: { $in: result.map(item => item._id) } }).countDocuments();
  424. }
  425. async saledUserCount({ endTime }) {
  426. const { ctx } = this;
  427. const agg = [
  428. { $match: { is_saled_car: 1, sale_date: { $lt: endTime }, user_id: { $ne: null } } },
  429. { $group: {
  430. _id: '$user_id',
  431. } },
  432. ];
  433. const result = await ctx.model.TVehicleRecordModel.aggregateFix(agg);
  434. return await ctx.model.TRbacUserModel.find({ role_id: ctx.helper.saledRoleId,
  435. user_id: { $in: result.map(item => item._id) } }).countDocuments();
  436. }
  437. }
  438. module.exports = TVehicleRecordService2;