matchSign.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  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 moment = require('moment');
  7. //
  8. class MatchSignService extends CrudService {
  9. constructor(ctx) {
  10. super(ctx, 'matchsign');
  11. this.model = this.ctx.model.Race.MatchSign;
  12. this.matchModel = this.ctx.model.Race.Match;
  13. this.matchGroupModel = this.ctx.model.Race.MatchGroup;
  14. this.matchProjectModel = this.ctx.model.Race.MatchProject;
  15. this.billModel = this.ctx.model.Race.Bill;
  16. this.userModel = this.ctx.model.Race.User;
  17. this.baseUserModel = this.ctx.model.Base.User;
  18. }
  19. /**
  20. * 检查是否已经有该赛事,该组别,该项目的报名
  21. * @param {Object} body 参数体
  22. */
  23. async beforeCreate(body) {
  24. const { match_id } = body;
  25. const num = await this.matchModel.count({ _id: match_id, status: '1' });
  26. if (num <= 0) throw new BusinessError(ErrorCode.SERVICE_FAULT, '当前赛事无法报名');
  27. await this.checkHas(body);
  28. return body;
  29. }
  30. async beforeUpdate(filter, update) {
  31. // 比赛结束后不能退款, match 0,1,2往后的状态不能退赛
  32. const { pay_status } = update;
  33. if (pay_status !== '-2') return { filter, update };
  34. const { id } = filter;
  35. const data = await this.model.findById(id);
  36. const { match_id } = data;
  37. const num = await this.matchModel.count({ _id: match_id, status: { $nin: ['0', '1', '2'] } });
  38. if (num > 0) throw new BusinessError(ErrorCode.DATA_INVALID, '赛事已经处于准备开赛或开赛中,不能退赛');
  39. return { filter, update };
  40. }
  41. async afterQuery(filter, data) {
  42. for (const i of data) {
  43. const { user_id: raceUser_id } = i;
  44. const raceUser = await this.userModel.findById(raceUser_id);
  45. if (!raceUser) continue;
  46. const { user_id } = raceUser;
  47. const user = await this.baseUserModel.findById(user_id);
  48. if (user) i.user_name = user.name;
  49. }
  50. return data;
  51. }
  52. // 退赛
  53. async afterUpdate(filter, body, data) {
  54. const { pay_status } = data;
  55. if (pay_status !== '-3') return data;
  56. // 线下管理人员允许退款,生成账单
  57. const obj = _.pick(data, ['match_id', 'group_id', 'project_id']);
  58. obj.payer_id = data.user_id;
  59. obj.time = moment().format('YYYY-MM-DD HH:mm:ss');
  60. obj.type = '-1';
  61. await this.billModel.create(obj);
  62. return data;
  63. }
  64. /**
  65. * 检查有没有报过名
  66. * @param {Object} body 参数体
  67. * @return {Boolean} true 可以继续进行
  68. */
  69. async checkHas(body) {
  70. const { match_id, group_id, project_id, user_id } = body;
  71. const query = { match_id, group_id, project_id, user_id, pay_status: ['0', '1'] };
  72. const num = await this.model.count(query);
  73. if (num > 0) throw new BusinessError(ErrorCode.DATA_EXISTED, '您已报名');
  74. return true;
  75. }
  76. async checkFit(body) {
  77. const { match_id, group_id, project_id, user_id, card } = body;
  78. await this.checkHas(body);
  79. const { pass, msg, gender: cardGender, age: cardAge } = this.idCodeValid(card);
  80. if (!pass) throw new BusinessError(ErrorCode.SERVICE_FAULT, msg);
  81. // 1.看 match 状态 是否为报名中
  82. const mnum = await this.matchModel.count({ _id: match_id, status: '1' });
  83. if (mnum <= 0) throw new BusinessError(ErrorCode.DATA_INVALID, '当前赛事不处于报名期');
  84. // 2.看matchGroup 年龄限制是否符合要求
  85. const matchGroup = await this.matchGroupModel.findById(group_id);
  86. if (!matchGroup) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到当前组别');
  87. const { age } = matchGroup;
  88. let next = this.checkAgeInRange(age, cardAge);
  89. // 3.看 matchProject 年龄限制和性别限制,人数限制
  90. if (!next) throw new BusinessError(ErrorCode.DATA_INVALID, '年龄不符合组别条件');
  91. const matchProject = await this.matchProjectModel.findById(project_id);
  92. if (!matchProject) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到组别项目');
  93. const { age: projectAge, gender, num } = matchProject;
  94. const joinNum = await this.matchProjectModel.count({ match_id, group_id });
  95. if (joinNum >= num) throw new BusinessError(ErrorCode.DATA_INVALID, '该项目已经到达人数限制,无法报名');
  96. next = this.checkAgeInRange(projectAge, cardAge);
  97. if (!next) throw new BusinessError(ErrorCode.DATA_INVALID, '年龄不符合组别项目条件');
  98. if (gender !== '2') {
  99. if (gender !== cardGender) throw new BusinessError(ErrorCode.DATA_INVALID, '性别不符合组别条件');
  100. }
  101. return true;
  102. }
  103. checkAgeInRange(age, cardAge) {
  104. let next = true;
  105. if (_.isString(age) && age.indexOf('-') > -1) {
  106. const arr = age.split('-');
  107. const start = parseInt(_.head(arr));
  108. const end = parseInt(_.last(arr));
  109. // inRange 是左闭右开区间,所以加1,形成闭区间
  110. const r = _.inRange(cardAge, start, end + 1);
  111. next = r;
  112. } else if (!age || age === '不限') next = true;
  113. return next;
  114. }
  115. // 身份证验证
  116. idCodeValid(code) {
  117. // 身份证号合法性验证
  118. // 支持15位和18位身份证号
  119. // 支持地址编码、出生日期、校验位验证
  120. const city = {
  121. 11: '北京',
  122. 12: '天津',
  123. 13: '河北',
  124. 14: '山西',
  125. 15: '内蒙古',
  126. 21: '辽宁',
  127. 22: '吉林',
  128. 23: '黑龙江 ',
  129. 31: '上海',
  130. 32: '江苏',
  131. 33: '浙江',
  132. 34: '安徽',
  133. 35: '福建',
  134. 36: '江西',
  135. 37: '山东',
  136. 41: '河南',
  137. 42: '湖北 ',
  138. 43: '湖南',
  139. 44: '广东',
  140. 45: '广西',
  141. 46: '海南',
  142. 50: '重庆',
  143. 51: '四川',
  144. 52: '贵州',
  145. 53: '云南',
  146. 54: '西藏 ',
  147. 61: '陕西',
  148. 62: '甘肃',
  149. 63: '青海',
  150. 64: '宁夏',
  151. 65: '新疆',
  152. 71: '台湾',
  153. 81: '香港',
  154. 82: '澳门',
  155. 91: '国外 ',
  156. };
  157. let row = {
  158. pass: true,
  159. msg: '验证成功',
  160. };
  161. if (!code || !/^\d{6}(18|19|20)?\d{2}(0[1-9]|1[012])(0[1-9]|[12]\d|3[01])\d{3}(\d|[xX])$/.test(code)) {
  162. row = {
  163. pass: false,
  164. msg: '身份证号格式错误',
  165. };
  166. } else if (!city[code.substr(0, 2)]) {
  167. row = {
  168. pass: false,
  169. msg: '身份证号地址编码错误',
  170. };
  171. }
  172. // else {
  173. // // 18位身份证需要验证最后一位校验位
  174. // if (code.length === 18) {
  175. // code = code.split('');
  176. // // ∑(ai×Wi)(mod 11)
  177. // // 加权因子
  178. // const factor = [ 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 ];
  179. // // 校验位
  180. // const parity = [ 1, 0, 'X', 9, 8, 7, 6, 5, 4, 3, 2 ];
  181. // let sum = 0;
  182. // let ai = 0;
  183. // let wi = 0;
  184. // for (let i = 0; i < 17; i++) {
  185. // ai = code[i];
  186. // wi = factor[i];
  187. // sum += ai * wi;
  188. // }
  189. // if (parity[sum % 11] !== code[17].toUpperCase()) {
  190. // row = {
  191. // pass: false,
  192. // msg: '身份证号校验位错误',
  193. // };
  194. // }
  195. // }
  196. // }
  197. if (row.pass) {
  198. row.gender = this.maleOrFemalByIdCard(code);
  199. row.age = this.getAge(code);
  200. }
  201. return row;
  202. }
  203. // 性别
  204. maleOrFemalByIdCard(idCard) {
  205. if (_.isArray(idCard)) idCard = idCard.join('');
  206. idCard = _.trim(idCard.replace(/ /g, '')); // 对身份证号码做处理。包括字符间有空格。
  207. if (idCard.length === 15) {
  208. if (idCard.substring(14, 15) % 2 === 0) {
  209. return '1';
  210. }
  211. return '0';
  212. } else if (idCard.length === 18) {
  213. if (idCard.substring(14, 17) % 2 === 0) {
  214. return '1';
  215. }
  216. return '0';
  217. }
  218. return null;
  219. }
  220. // 年龄
  221. getAge(idCard) {
  222. if (_.isArray(idCard)) idCard = idCard.join('');
  223. const ageDate = new Date();
  224. const month = ageDate.getMonth() + 1;
  225. const day = ageDate.getDate();
  226. let age = ageDate.getFullYear() - idCard.substring(6, 10) - 1;
  227. if (idCard.substring(10, 12) < month || (idCard.substring(10, 12) === month && idCard.substring(12, 14) <= day)) {
  228. age++;
  229. }
  230. if (age <= 0) {
  231. age = 1;
  232. }
  233. return age;
  234. }
  235. }
  236. module.exports = MatchSignService;