'use strict'; const { CrudService } = require('naf-framework-mongoose-free/lib/service'); const { BusinessError, ErrorCode } = require('naf-core').Error; const _ = require('lodash'); const assert = require('assert'); const moment = require('moment'); // class MatchSignService extends CrudService { constructor(ctx) { super(ctx, 'matchsign'); this.model = this.ctx.model.Race.MatchSign; this.matchModel = this.ctx.model.Race.Match; this.matchGroupModel = this.ctx.model.Race.MatchGroup; this.matchProjectModel = this.ctx.model.Race.MatchProject; this.billModel = this.ctx.model.Race.Bill; this.userModel = this.ctx.model.Race.User; this.baseUserModel = this.ctx.model.Base.User; } /** * 检查是否已经有该赛事,该组别,该项目的报名 * @param {Object} body 参数体 */ async beforeCreate(body) { const { match_id } = body; const num = await this.matchModel.count({ _id: match_id, status: '1' }); if (num <= 0) throw new BusinessError(ErrorCode.SERVICE_FAULT, '当前赛事无法报名'); await this.checkHas(body); return body; } async beforeUpdate(filter, update) { // 比赛结束后不能退款, match 0,1,2往后的状态不能退赛 const { pay_status } = update; if (pay_status !== '-2') return { filter, update }; const { id } = filter; const data = await this.model.findById(id); const { match_id } = data; const num = await this.matchModel.count({ _id: match_id, status: { $nin: ['0', '1', '2'] } }); if (num > 0) throw new BusinessError(ErrorCode.DATA_INVALID, '赛事已经处于准备开赛或开赛中,不能退赛'); return { filter, update }; } async afterQuery(filter, data) { for (const i of data) { const { user_id: raceUser_id } = i; const raceUser = await this.userModel.findById(raceUser_id); if (!raceUser) continue; const { user_id } = raceUser; const user = await this.baseUserModel.findById(user_id); if (user) i.user_name = user.name; } return data; } // 退赛 async afterUpdate(filter, body, data) { const { pay_status } = data; if (pay_status !== '-3') return data; // 线下管理人员允许退款,生成账单 const obj = _.pick(data, ['match_id', 'group_id', 'project_id']); obj.payer_id = data.user_id; obj.time = moment().format('YYYY-MM-DD HH:mm:ss'); obj.type = '-1'; await this.billModel.create(obj); return data; } /** * 检查有没有报过名 * @param {Object} body 参数体 * @return {Boolean} true 可以继续进行 */ async checkHas(body) { const { match_id, group_id, project_id, user_id } = body; const query = { match_id, group_id, project_id, user_id, pay_status: ['0', '1'] }; const num = await this.model.count(query); if (num > 0) throw new BusinessError(ErrorCode.DATA_EXISTED, '您已报名'); return true; } async checkFit(body) { const { match_id, group_id, project_id, user_id, card } = body; await this.checkHas(body); const { pass, msg, gender: cardGender, age: cardAge } = this.idCodeValid(card); if (!pass) throw new BusinessError(ErrorCode.SERVICE_FAULT, msg); // 1.看 match 状态 是否为报名中 const mnum = await this.matchModel.count({ _id: match_id, status: '1' }); if (mnum <= 0) throw new BusinessError(ErrorCode.DATA_INVALID, '当前赛事不处于报名期'); // 2.看matchGroup 年龄限制是否符合要求 const matchGroup = await this.matchGroupModel.findById(group_id); if (!matchGroup) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到当前组别'); const { age } = matchGroup; let next = this.checkAgeInRange(age, cardAge); // 3.看 matchProject 年龄限制和性别限制,人数限制 if (!next) throw new BusinessError(ErrorCode.DATA_INVALID, '年龄不符合组别条件'); const matchProject = await this.matchProjectModel.findById(project_id); if (!matchProject) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到组别项目'); const { age: projectAge, gender, num } = matchProject; const joinNum = await this.matchProjectModel.count({ match_id, group_id }); if (joinNum >= num) throw new BusinessError(ErrorCode.DATA_INVALID, '该项目已经到达人数限制,无法报名'); next = this.checkAgeInRange(projectAge, cardAge); if (!next) throw new BusinessError(ErrorCode.DATA_INVALID, '年龄不符合组别项目条件'); if (gender !== '2') { if (gender !== cardGender) throw new BusinessError(ErrorCode.DATA_INVALID, '性别不符合组别条件'); } return true; } checkAgeInRange(age, cardAge) { let next = true; if (_.isString(age) && age.indexOf('-') > -1) { const arr = age.split('-'); const start = parseInt(_.head(arr)); const end = parseInt(_.last(arr)); // inRange 是左闭右开区间,所以加1,形成闭区间 const r = _.inRange(cardAge, start, end + 1); next = r; } else if (!age || age === '不限') next = true; return next; } // 身份证验证 idCodeValid(code) { // 身份证号合法性验证 // 支持15位和18位身份证号 // 支持地址编码、出生日期、校验位验证 const city = { 11: '北京', 12: '天津', 13: '河北', 14: '山西', 15: '内蒙古', 21: '辽宁', 22: '吉林', 23: '黑龙江 ', 31: '上海', 32: '江苏', 33: '浙江', 34: '安徽', 35: '福建', 36: '江西', 37: '山东', 41: '河南', 42: '湖北 ', 43: '湖南', 44: '广东', 45: '广西', 46: '海南', 50: '重庆', 51: '四川', 52: '贵州', 53: '云南', 54: '西藏 ', 61: '陕西', 62: '甘肃', 63: '青海', 64: '宁夏', 65: '新疆', 71: '台湾', 81: '香港', 82: '澳门', 91: '国外 ', }; let row = { pass: true, msg: '验证成功', }; 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)) { row = { pass: false, msg: '身份证号格式错误', }; } else if (!city[code.substr(0, 2)]) { row = { pass: false, msg: '身份证号地址编码错误', }; } // else { // // 18位身份证需要验证最后一位校验位 // if (code.length === 18) { // code = code.split(''); // // ∑(ai×Wi)(mod 11) // // 加权因子 // const factor = [ 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 ]; // // 校验位 // const parity = [ 1, 0, 'X', 9, 8, 7, 6, 5, 4, 3, 2 ]; // let sum = 0; // let ai = 0; // let wi = 0; // for (let i = 0; i < 17; i++) { // ai = code[i]; // wi = factor[i]; // sum += ai * wi; // } // if (parity[sum % 11] !== code[17].toUpperCase()) { // row = { // pass: false, // msg: '身份证号校验位错误', // }; // } // } // } if (row.pass) { row.gender = this.maleOrFemalByIdCard(code); row.age = this.getAge(code); } return row; } // 性别 maleOrFemalByIdCard(idCard) { if (_.isArray(idCard)) idCard = idCard.join(''); idCard = _.trim(idCard.replace(/ /g, '')); // 对身份证号码做处理。包括字符间有空格。 if (idCard.length === 15) { if (idCard.substring(14, 15) % 2 === 0) { return '1'; } return '0'; } else if (idCard.length === 18) { if (idCard.substring(14, 17) % 2 === 0) { return '1'; } return '0'; } return null; } // 年龄 getAge(idCard) { if (_.isArray(idCard)) idCard = idCard.join(''); const ageDate = new Date(); const month = ageDate.getMonth() + 1; const day = ageDate.getDate(); let age = ageDate.getFullYear() - idCard.substring(6, 10) - 1; if (idCard.substring(10, 12) < month || (idCard.substring(10, 12) === month && idCard.substring(12, 14) <= day)) { age++; } if (age <= 0) { age = 1; } return age; } } module.exports = MatchSignService;