card.js 13 KB

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