card.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. 'use strict';
  2. const { CrudService } = require('naf-framework-mongoose/lib/service');
  3. const { BusinessError, ErrorCode } = require('naf-core').Error;
  4. const { ObjectId } = require('mongoose').Types;
  5. const _ = require('lodash');
  6. const moment = require('moment');
  7. /**
  8. * 核心逻辑
  9. * . A
  10. * /_\ B
  11. * /_ _\ C crime
  12. * /_ _ _\ D crime
  13. * /_ _ _ _\ E crime
  14. */
  15. class CardService extends CrudService {
  16. constructor(ctx) {
  17. super(ctx, 'card');
  18. this.model = this.ctx.model.Card;
  19. this.record = this.ctx.model.Record;
  20. this.set = this.ctx.model.Set;
  21. /**
  22. * @constant Number 车奖的积分 default:131419
  23. */
  24. this.car_point = 131419;
  25. /**
  26. * @constant Number 股东的积分 default:10
  27. */
  28. this.stockholder_point = 10;
  29. /**
  30. * @constant Number 股东的单数界限,前xxxx单不是股东 default: 9999
  31. */
  32. this.stockholder_limit = 20;
  33. /**
  34. * @constant Number 股东的等级界限,前x级不是股东 default:6
  35. */
  36. this.stockholder_limit_level = 6;
  37. /**
  38. * @constant Number 车奖的等级界限,前x级不是能获得车奖 default:4
  39. */
  40. this.car_show_limit_level = 4;
  41. /**
  42. * @constant Number 车奖的B梯队等级界限 default:4
  43. */
  44. this.car_show_b_limit_level = 4;
  45. /**
  46. * @constant Number 车奖的B梯队等级界限的人数 default:5
  47. */
  48. this.car_show_b_limit_person = 5;
  49. }
  50. /**
  51. * 1创建卡用户;2检查推荐人
  52. * @param {Object} data 参数
  53. */
  54. async create(data) {
  55. // 先创建用户
  56. // 1,检查手机号是否存在
  57. const { password, mobile, set, r_mobile } = data;
  58. const is_exists = await this.model.count({ mobile });
  59. if (is_exists) { throw new BusinessError(ErrorCode.DATA_EXISTED, '手机号已存在'); }
  60. // 2,创建用户
  61. let user;
  62. try {
  63. data.password = { secret: password };
  64. user = await this.model.create({ ...data, create_time: moment().format('YYYY-MM-DD HH:mm:ss') });
  65. user = _.omit(user, [ 'password' ]);
  66. } catch (e) {
  67. this.ctx.logger.error(e);
  68. throw new BusinessError(
  69. ErrorCode.SERVICE_FAULT,
  70. '输入信息有误,用户创建失败!'
  71. );
  72. }
  73. return user;
  74. // 2021-04-02 12:00 修改,添加用户审核,需要将非创建用户部分移至通过审核后进行
  75. }
  76. async check({ id }, { status }) {
  77. // 2021-04-02 12:00 修改,将积分计算部分移至用户审核后进行
  78. const user = await this.model.findById(id);
  79. if (!user) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到用户信息');
  80. if (status === '-1') {
  81. // 审核不通过,直接删除用户
  82. await this.model.deleteOne({ _id: ObjectId(id) });
  83. return '审核未通过,用户已删除';
  84. }
  85. user.status = status;
  86. await user.save();
  87. const { set, r_mobile } = user;
  88. // 3,检查有没有推荐人,如果没有推荐人,那就完事了,有推荐人,还需要检查给推荐人加多少分
  89. if (!r_mobile) return user;
  90. const recommender = await this.model.findOne({ mobile: r_mobile });
  91. if (!recommender) {
  92. await this.model.deleteOne({ _id: user._id });
  93. throw new BusinessError(
  94. ErrorCode.SERVICE_FAULT,
  95. '推荐人信息错误,用户创建失败!'
  96. );
  97. }
  98. // 4,判断是套餐有没有推荐制度(此处因为套餐可维护,修改了)
  99. const setInfo = await this.set.findById(set);
  100. if (!setInfo) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到选择的套餐信息!');
  101. const { has_group } = setInfo;
  102. if (!has_group) return user;
  103. // 5,根据等级,找下推荐人应该加多少分
  104. const { stockholder, level } = recommender;
  105. // 6,重新计算推荐人用户等级
  106. const nlevel = await this.reComputedLevel(recommender);
  107. recommender.level = nlevel;
  108. const rank = this.getRank().find(f => f.rank === nlevel);
  109. if (!rank) {
  110. // TODO 告知推荐人,等级信息有错误,中断,返回用户添加成功
  111. return user;
  112. }
  113. let { score } = rank;
  114. // 7,需要检查股东奖,是否需要+10
  115. if (stockholder) score = score + this.stockholder_point;
  116. else {
  117. // 去写检查是不是股东的问题
  118. const result = await this.checkStockholder(recommender);
  119. if (result === undefined) {
  120. // TODO 告知推荐人,股东检测有错误,中断,返回用户添加成功
  121. return user;
  122. }
  123. // 这次加上这个用户就是股东了
  124. if (result) {
  125. // 此情况主要讨论的是 9999 单时,是否加=>9999单时,不加
  126. recommender.stockholder = true;
  127. score = score + 10;
  128. }
  129. }
  130. // 8,加积分
  131. recommender.points = `${recommender.points + score}`;
  132. await recommender.save();
  133. const record = _.pick(recommender, [ 'mobile', 'name' ]);
  134. record.points = score;
  135. record.opera = '1';
  136. record.params = { name: user.name, mobile: user.mobile, level: user.level };
  137. try {
  138. await this.record.create({ ...record, create_time: moment().format('YYYY-MM-DD HH:mm:ss') });
  139. } catch (error) {
  140. this.logger.error(`line-121-积分记录添加失败${JSON.stringify(record)}`);
  141. }
  142. // 9,以上为用户创建,计算推荐人等级=>计算推荐人积分=>生成记录
  143. // 先算等级,避免分数基础部队
  144. // 推荐人升级,进行推荐人等级检查与同级加分(原等级和现等级不同,说明升级了,需要检查下)
  145. if (level !== nlevel) this.toCheckRecommender(recommender._id);
  146. // 10,给推荐人的推荐人反佣金
  147. this.rtrScore(recommender._id, level);
  148. return user;
  149. }
  150. /**
  151. * 计算推荐人信息的等级;找到@property的2个变量
  152. * @param {Object} user 推荐人信息
  153. * @property r_number A推出去的人数 B级总数
  154. * @property b_r_number B级推出去的人数 C级总数
  155. * @property group_number 团队完成单数: r_number + b_r_number
  156. * @return {Number} level 等级
  157. */
  158. async reComputedLevel(user) {
  159. const result = await this.getEchelon(user);
  160. if (!result) return;
  161. const { b = [], c = 0 } = result;
  162. /**
  163. * @constant Number group_number 团队完成单数
  164. * @type Number
  165. */
  166. const group_number = b.length + c;
  167. // 先用1阶条件过滤,是否满足推荐人数要求 + 是否满足团队单数要求
  168. let ranks = this.getRank().filter(f => b.length >= f.person && group_number >= f.group);
  169. // 按rank 倒序
  170. ranks = _.orderBy(ranks, [ 'rank' ], [ 'desc' ]);
  171. // 使用find查找,因为倒序排列,所以按照条件挨个比,取的就是结果
  172. const rank = ranks.find(f => {
  173. const { n_rank, n_r_person } = f;
  174. // 查找B梯队里,满足当前阶级要求的 B梯队职位 的人数
  175. const list = b.filter(bl => bl.level === n_rank);
  176. // 如果人数也满足要求,推荐人就应该是这个阶级了,反之继续找
  177. return list >= n_r_person;
  178. });
  179. if (rank) return rank.rank;
  180. return user.level;
  181. }
  182. /**
  183. * 检查该用户是否满足股东身份
  184. * @param {Object} user 用户
  185. */
  186. async checkStockholder(user) {
  187. try {
  188. const level = _.get(user, 'level', 1) * 1;
  189. if (level < this.stockholder_limit_level) return false;
  190. const result = await this.getEchelon(user);
  191. if (!result) return;
  192. const { b = [], c = 0 } = result;
  193. const group_number = b.length + c;
  194. return group_number > this.stockholder_limit;
  195. } catch (error) {
  196. this.ctx.logger.error(`line-178-${moment().format('YYYY-MM-DD HH:mm:ss')}-${user.name}-${user.mobile}:股东检查出错`);
  197. return undefined;
  198. }
  199. }
  200. /**
  201. * 检查该用户是否满足车奖
  202. */
  203. async checkCarshow() {
  204. const list = await this.model.find({ level: { $gte: this.car_show_limit_level }, car_show: false });
  205. try {
  206. for (const user of list) {
  207. const b = await this.model.count({ r_mobile: user.mobile, level: { $gte: this.car_show_b_limit_level } });
  208. if (b >= this.car_show_b_limit_person) {
  209. user.car_show = true;
  210. user.points = user.points + this.car_point;
  211. await user.save();
  212. // 添加积分记录
  213. const record = _.pick(user, [ 'mobile', 'name' ]);
  214. record.points = this.car_point;
  215. record.opera = '2';
  216. try {
  217. await this.record.create({ ...record, create_time: moment().format('YYYY-MM-DD HH:mm:ss') });
  218. } catch (error) {
  219. this.logger.error(`line-202-积分记录添加失败${JSON.stringify(record)}`);
  220. }
  221. }
  222. }
  223. } catch (error) {
  224. this.ctx.logger.error(`line-207-${moment().format('YYYY-MM-DD HH:mm:ss')}-车奖检查出错`);
  225. return undefined;
  226. }
  227. }
  228. /**
  229. * 查找用户的梯队
  230. * @param {Object} user 用户(其实指的就是推荐人)
  231. * @return {Object} {b:b梯队列表,c:c梯队数据}
  232. */
  233. async getEchelon(user) {
  234. // 推荐的人 B梯队
  235. const b_echelon = await this.model.find({ r_mobile: user.mobile });
  236. // c梯队的总人数
  237. const c_echelon_number = await this.model.count({ r_mobile: b_echelon.map(i => i.mobile) });
  238. return { b: b_echelon, c: c_echelon_number };
  239. }
  240. /**
  241. * 添加用户后,检查推荐人是否和他自己的推荐人同级了
  242. * @param {String} id 该用户的推荐人id
  243. */
  244. async toCheckRecommender(id) {
  245. try {
  246. const rec = await this.model.findById(id);
  247. if (!rec) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到推荐人信息');
  248. const { r_mobile, level } = rec;
  249. // 查出推荐人的推荐人
  250. const r_rec = await this.model.findOne({ mobile: r_mobile });
  251. if (!r_rec) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到推荐人的推荐人信息');
  252. const { level: rlevel } = r_rec;
  253. // 推荐人等级已经计算过了,直接比较
  254. if (level === rlevel) {
  255. rec.points = rec.points + 20;
  256. await rec.save();
  257. }
  258. const record = _.pick(rec, [ 'mobile', 'name' ]);
  259. try {
  260. record.points = 20;
  261. record.opera = '3';
  262. await this.record.create({ ...record, create_time: moment().format('YYYY-MM-DD HH:mm:ss') });
  263. } catch (error) {
  264. this.logger.error(`line-250-积分记录添加失败${JSON.stringify(record)}`);
  265. }
  266. } catch (error) {
  267. this.logger.error(`line-253-捕获检查用户推荐人的推荐人失败:${JSON.stringify(error)}`);
  268. }
  269. }
  270. /**
  271. * 给该用户的推荐人的推荐人反佣金
  272. * @param {String} id 该用户的推荐人id
  273. * @param {Number} level 该用户的推荐人原等级,需要按这个等级去反佣
  274. */
  275. async rtrScore(id, level) {
  276. try {
  277. const rec = await this.model.findById(id);
  278. if (!rec) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到推荐人信息');
  279. const { r_mobile } = rec;
  280. // 查出推荐人的推荐人
  281. const r_rec = await this.model.findOne({ mobile: r_mobile });
  282. if (!r_rec) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到推荐人的推荐人信息');
  283. // r_level:推荐人的推荐人的等级
  284. const { level: r_level } = r_rec;
  285. const points = this.getReturns(level, r_level);
  286. r_rec.points = r_rec.points + points;
  287. await r_rec.save();
  288. const record = _.pick(r_rec, [ 'mobile', 'name' ]);
  289. try {
  290. record.points = points;
  291. record.opera = '4';
  292. await this.record.create({ ...record, create_time: moment().format('YYYY-MM-DD HH:mm:ss') });
  293. } catch (error) {
  294. this.logger.error(`line-282-积分记录添加失败${JSON.stringify(record)}`);
  295. }
  296. } catch (error) {
  297. this.logger.error(`line-285-捕获检查用户推荐人的推荐人失败:${JSON.stringify(error)}`);
  298. }
  299. }
  300. /**
  301. * 等级设置
  302. * @property rank 等级:1,业务员;2,经理;3一星经理;4,二星经理;5,三星经理;6,四星经理;
  303. * @property person 该等级人数底线要求
  304. * @property group 该等级团队单数底线要求
  305. * @property n_rank 该团队中,要求B梯队中的等级
  306. * @property n_r_person 该团队中,要求B梯队指定等级的人数
  307. */
  308. getRank() {
  309. return [
  310. {
  311. rank: 1,
  312. person: 1,
  313. group: 0,
  314. score: 600,
  315. n_rank: undefined,
  316. n_r_person: 0,
  317. },
  318. {
  319. rank: 2,
  320. person: 3,
  321. group: 11,
  322. score: 600 + 100,
  323. n_rank: undefined,
  324. n_r_person: 0,
  325. },
  326. {
  327. rank: 3,
  328. person: 10,
  329. group: 111,
  330. score: 600 + 150,
  331. n_rank: 2,
  332. n_r_person: 1,
  333. },
  334. {
  335. rank: 4,
  336. person: 20,
  337. group: 555,
  338. score: 600 + 200,
  339. n_rank: 3,
  340. n_r_person: 2,
  341. },
  342. {
  343. rank: 5,
  344. person: 25,
  345. group: 1111,
  346. score: 600 + 250,
  347. n_rank: 4,
  348. n_r_person: 2,
  349. },
  350. {
  351. rank: 6,
  352. person: 30,
  353. group: 5555,
  354. score: 600 + 300,
  355. n_rank: 5,
  356. n_r_person: 3,
  357. },
  358. ];
  359. }
  360. /**
  361. * 根据推荐人和推荐人的推荐人等级判断,该如何反給推荐人的推荐人佣金
  362. * @param {Number} level 推荐人等级
  363. * @param {Number} r_level 推荐人的推荐人等级
  364. */
  365. getReturns(level, r_level) {
  366. // 同级固定反20
  367. if (level === r_level) return 20;
  368. const levels = [
  369. { rank: 1, 2: 100, 3: 150, 4: 200, 5: 250, 6: 300 },
  370. { rank: 2, 3: 50, 4: 100, 5: 150, 6: 200 },
  371. { rank: 3, 4: 50, 5: 100, 6: 150 },
  372. { rank: 4, 5: 50, 6: 100 },
  373. { rank: 5, 6: 50 },
  374. ];
  375. // 根据推荐人等级,获取价格表的指定行
  376. const obj = levels.find(f => f.rank === level);
  377. if (!obj) throw new BusinessError(ErrorCode.SERVICE_FAULT, '为找到推荐人适合的规则');
  378. return _.get(obj, r_level);
  379. }
  380. /**
  381. * 修改密码
  382. * @param {Object} param 参数
  383. */
  384. async passwd({ id, password }) {
  385. password = { secret: password };
  386. const res = await this.model.update({ _id: ObjectId(id) }, { password });
  387. return res;
  388. }
  389. /**
  390. * 根据条件查询该用户的团队人员
  391. * @param {Object} query 查询条件
  392. */
  393. async group(query) {
  394. const condition = this.ctx.service.util.queryReset(query);
  395. const { filter } = condition;
  396. const bTerm = await this.model.find(filter);
  397. const cTerm = await this.model.find({ r_mobile: bTerm.map(i => i.mobile) });
  398. return { list: _.uniqBy([ ...bTerm, ...cTerm ], '_id'), total: bTerm.length + cTerm.length };
  399. }
  400. }
  401. module.exports = CardService;