matchTeamGroup.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  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 user = require('../model/race/user');
  7. const { ObjectId } = require('mongoose').Types;
  8. //
  9. class MatchTeamGroupService extends CrudService {
  10. constructor(ctx) {
  11. super(ctx, 'matchteamgroup');
  12. this.model = this.ctx.model.Race.MatchTeamGroup;
  13. this.matchProjectModel = this.ctx.model.Race.MatchProject;
  14. this.teamApplyModel = this.ctx.model.Race.TeamApply;
  15. this.matchSignModel = this.ctx.model.Race.MatchSign;
  16. this.baseUserModel = this.ctx.model.Base.User;
  17. this.userModel = this.ctx.model.Race.User;
  18. this.matchModel = this.ctx.model.Race.Match;
  19. this.matchProjectModel = this.ctx.model.Race.MatchProject;
  20. this.eliminateModel = this.ctx.model.Race.Eliminate;
  21. }
  22. /**
  23. * 根据项目和选手类型查询可选择的分组人员
  24. * @param {Object} query 地址栏查询条件
  25. * @property {String} project_id 比赛项目id
  26. * @property {String} person_type 选手类型
  27. * @property {String} team_id 小组id
  28. */
  29. async findGroupPersonSelects({ project_id, person_type, team_id }) {
  30. // 1.查找所有报名的选手/小队
  31. let allPerson;
  32. // 2.查找该项目下已经分完组的成员, 如果是针对某组的修改,则不查该组
  33. const teamGroups = await this.model.find({ project_id, _id: { $ne: team_id } }, { person: 1 });
  34. // 已经有分组的成员id集合
  35. const personList = _.flattenDeep(teamGroups.map(i => i.person));
  36. if (person_type === 'Race.TeamApply') {
  37. allPerson = await this.teamApplyModel.find({ project_id, status: '1', _id: { $nin: personList } });
  38. } else {
  39. allPerson = await this.matchSignModel.find({ project_id, pay_status: '1', user_id: { $nin: personList } }).populate({
  40. path: 'user_id',
  41. select: 'user_id',
  42. model: this.userModel,
  43. populate: {
  44. path: 'user_id',
  45. select: 'name',
  46. model: this.baseUserModel,
  47. },
  48. });
  49. allPerson = JSON.parse(JSON.stringify(allPerson));
  50. allPerson = allPerson.map(i => {
  51. i.user_name = _.get(i, 'user_id.user_id.name');
  52. i.user_id = _.get(i, 'user_id._id');
  53. return i;
  54. });
  55. }
  56. // 过滤掉 allPerson 中 在personList中的数据
  57. if (person_type === 'Race.TeamApply') {
  58. allPerson = allPerson.filter(f => !personList.includes(f._id));
  59. return allPerson;
  60. }
  61. return allPerson;
  62. }
  63. // 自动建组
  64. async saveAll(data) {
  65. // 确保大家的project_id都是一个
  66. const match_id = _.get(_.head(data), 'match_id');
  67. const canOpera = await this.canOpera(match_id);
  68. if (!canOpera) throw new BusinessError(ErrorCode.SERVICE_FAULT, '当前赛事不处于可更改赛事相关信息状态');
  69. const project_id = _.get(_.head(data), 'project_id');
  70. const belongOneProject = data.every(e => e.project_id === project_id);
  71. if (!belongOneProject) throw new BusinessError(ErrorCode.DATA_INVALID, '自动创建的小组并不全都属于同一个比赛项目,无法创建小组');
  72. // 先删除该比赛项目的所有小组
  73. await this.model.deleteMany({ project_id });
  74. await this.model.insertMany(data);
  75. // 检查并添加淘汰赛信息
  76. const group_id = _.get(_.head(data), 'group_id');
  77. await this.checkEliminate(match_id, group_id, project_id);
  78. }
  79. // 检查并创建淘汰赛赛程
  80. async checkEliminate(match_id, group_id, project_id) {
  81. // 有赛程就删了,重新创建
  82. await this.eliminateModel.deleteMany({ match_id, group_id, project_id });
  83. const groups = await this.model.find({ match_id, group_id, project_id });
  84. const personTotal = groups.reduce((p, n) => p + (parseInt(n.rise) || 0), 0);
  85. const level = this.ctx.service.eliminate.getLevel(personTotal);
  86. const chartData = this.ctx.service.eliminate.getChartData(level);
  87. let eliminateData = this.ctx.service.eliminate.getEliminateData(chartData);
  88. const head = _.head(groups);
  89. const player_type = _.get(head, 'person_type');
  90. eliminateData = eliminateData.map(i => ({ ...i, player_type, match_id, group_id, project_id }));
  91. await this.eliminateModel.insertMany(eliminateData);
  92. }
  93. async auto({ match_id, group_id, project_id, team_number = 0 }) {
  94. // 找到比赛项目
  95. const project = await this.matchProjectModel.findById(project_id);
  96. if (!project) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到比赛项目');
  97. const { type } = project;
  98. // type 为 1 双打,是TeamApply表中的, type 为2 单打,是User表
  99. let personList = [];
  100. const obj = { match_id, group_id, project_id };
  101. if (type === '1') {
  102. // 双打,找到这个项目所有的申请,且审核通过的
  103. obj.person_type = 'Race.TeamApply';
  104. const teamApplys = await this.teamApplyModel.find({ project_id, status: '1' });
  105. personList = JSON.parse(JSON.stringify(teamApplys));
  106. } else {
  107. // 单打,找到这个项目所有申请报名的信息
  108. obj.person_type = 'Race.User';
  109. const sign = await this.matchSignModel.find({ project_id, pay_status: '1' });
  110. personList = JSON.parse(JSON.stringify(sign));
  111. }
  112. const returnData = [];
  113. // 选手数检测
  114. const number = _.floor(_.divide(personList.length, team_number));
  115. if (number <= 0) throw new BusinessError(ErrorCode.DATA_INVALID, '选手不足以分组,要分组的总人数 至少为 要分的组数');
  116. // 选手列表分组
  117. const teams = _.chunk(personList, number);
  118. // 获取 选手被均分到每个组后的余数
  119. const el = personList.length % team_number;
  120. // 最后余下的组数据
  121. let last = [];
  122. if (el !== 0) {
  123. // 如果有剩余不能被均分的选手,则将这些选手 放到 last数组中,再将多出来的这个去掉
  124. last = _.last(teams);
  125. teams.pop();
  126. }
  127. // 将正常均分的小组先分出来
  128. for (let i = 0; i < teams.length; i++) {
  129. const t = { name: `小组${i + 1}`, person: teams[i], ...obj };
  130. returnData.push(t);
  131. }
  132. // 循环剩下的选手, 依次放入每个已经分好的组中,尽可能均分人数
  133. for (let i = 0; i < last.length; i++) {
  134. returnData[i].person.push(last[i]);
  135. }
  136. // 将人的信息换回来
  137. if (type === '1') {
  138. // 双打
  139. } else {
  140. // 单打
  141. const users = [];
  142. for (const i of returnData) {
  143. const { person } = i;
  144. for (const p of person) {
  145. const { user_id } = p;
  146. const user = await this.userModel.findById(user_id).populate({
  147. path: 'user_id',
  148. select: 'name',
  149. model: this.baseUserModel,
  150. });
  151. p.user_name = _.get(user, 'user_id.name');
  152. }
  153. }
  154. }
  155. return returnData;
  156. }
  157. async beforeCreate(body) {
  158. const r = await this.checkPersonInOtherGroup(body);
  159. if (!r) throw new BusinessError(ErrorCode.DATA_INVALID, '组员已在其他组,请先将其退出原组后,再添置该组');
  160. return body;
  161. }
  162. async beforeUpdate(filter, update) {
  163. const r = await this.checkPersonInOtherGroup({ ...filter, ...update });
  164. if (!r) throw new BusinessError(ErrorCode.DATA_INVALID, '组员已在其他组,请先将其退出原组后,再添置该组');
  165. return { filter, update };
  166. }
  167. async beforeQuery(filter) {
  168. const { project_name } = filter;
  169. if (project_name) {
  170. const projectList = await this.matchProjectModel.find({ name: new RegExp(project_name) }, { _id: 1 });
  171. const project_id = projectList.map(i => i._id);
  172. filter.project_id = project_id;
  173. delete filter.project_name;
  174. }
  175. return filter;
  176. }
  177. // 检查该组的成员是否在该比赛项目中的其他组中
  178. // true:没有问题,可以操作; false:有人在其他组,不能允许操作
  179. async checkPersonInOtherGroup(data) {
  180. const { person, id, project_id } = data;
  181. // 没有person,就不需要检查
  182. if (!person) return true;
  183. const query = { _id: { $ne: id } };
  184. if (project_id) query.project_id = project_id;
  185. else {
  186. // 没有比赛项目id,也没有数据id: 是新增,但是没有比赛项目id,说明数据有错误
  187. if (!id) throw new BusinessError(ErrorCode.DATA_INVALID, '新增的数据中缺少比赛项目信息');
  188. const data = await this.model.findById(id);
  189. if (!data) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到要修改的数据');
  190. query.project_id = _.get(data, 'project_id');
  191. }
  192. let res = true;
  193. for (const p of person) {
  194. const num = await this.model.count({ ...query, person: p });
  195. if (num > 0) {
  196. res = false;
  197. break;
  198. }
  199. }
  200. return res;
  201. }
  202. async afterQuery(filter, data) {
  203. data = JSON.parse(JSON.stringify(data));
  204. for (const d of data) {
  205. const { person, person_type } = d;
  206. if (person_type === 'Race.User') {
  207. // 单人
  208. const users = await this.userModel.find({ _id: person }).populate({ path: 'user_id', model: this.baseUserModel, select: 'name' });
  209. const parr = person.map(i => {
  210. const r = users.find(f => ObjectId(f._id).equals(i));
  211. if (r) return { id: r._id, name: _.get(r, 'user_id.name') };
  212. return i;
  213. });
  214. d.person = parr;
  215. } else if (person_type === 'Race.TeamApply') {
  216. // 组队
  217. const teamApplys = await this.teamApplyModel.find({ _id: person });
  218. const parr = [];
  219. for (const p of person) {
  220. const r = teamApplys.find(f => ObjectId(f._id).equals(p));
  221. if (r) {
  222. const { one_member_name, two_member_name } = r;
  223. parr.push({ id: p, name: `${one_member_name}-${two_member_name}` });
  224. }
  225. }
  226. d.person = parr;
  227. }
  228. }
  229. return data;
  230. }
  231. async afterFetch(filter, d) {
  232. const { person = [], person_type } = d;
  233. if (person_type === 'Race.User') {
  234. // 单人
  235. const users = await this.userModel.find({ _id: person }).populate({ path: 'user_id', model: this.baseUserModel, select: 'name' });
  236. const parr = person.map(i => {
  237. const r = users.find(f => ObjectId(f._id).equals(i));
  238. if (r) return { id: r._id, name: _.get(r, 'user_id.name') };
  239. return i;
  240. });
  241. d.person = parr;
  242. } else if (person_type === 'Race.TeamApply') {
  243. // 组队
  244. const teamApplys = await this.teamApplyModel.find({ _id: person });
  245. const parr = [];
  246. for (const p of person) {
  247. const r = teamApplys.find(f => ObjectId(f._id).equals(p));
  248. if (r) {
  249. const { one_member_name, two_member_name } = r;
  250. parr.push({ id: p, name: `${one_member_name}-${two_member_name}` });
  251. }
  252. }
  253. d.person = parr;
  254. }
  255. return d;
  256. }
  257. /**
  258. * 根据该赛事状态,判断是否可以操作
  259. * 赛事状态为 1,2可以组队, 其余状态不能组队,不能修改
  260. * @param {String} match_id 赛事id
  261. * @return {Boolean} 是否可以操作
  262. */
  263. async canOpera(match_id) {
  264. const num = await this.matchModel.count({ _id: match_id, status: [ '1', '2' ] });
  265. return num > 0;
  266. }
  267. }
  268. module.exports = MatchTeamGroupService;