matchTeamGroup.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  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. eliminateData = _.reverse(eliminateData);
  92. await this.eliminateModel.insertMany(eliminateData);
  93. }
  94. async auto({ match_id, group_id, project_id, team_number = 0 }) {
  95. // 找到比赛项目
  96. const project = await this.matchProjectModel.findById(project_id);
  97. if (!project) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到比赛项目');
  98. const { type } = project;
  99. // type 为 1 双打,是TeamApply表中的, type 为2 单打,是User表
  100. let personList = [];
  101. const obj = { match_id, group_id, project_id };
  102. if (type === '1') {
  103. // 双打,找到这个项目所有的申请,且审核通过的
  104. obj.person_type = 'Race.TeamApply';
  105. const teamApplys = await this.teamApplyModel.find({ project_id, status: '1' });
  106. personList = JSON.parse(JSON.stringify(teamApplys));
  107. } else {
  108. // 单打,找到这个项目所有申请报名的信息
  109. obj.person_type = 'Race.User';
  110. const sign = await this.matchSignModel.find({ project_id, pay_status: '1' });
  111. personList = JSON.parse(JSON.stringify(sign));
  112. }
  113. const returnData = [];
  114. // 选手数检测
  115. const number = _.floor(_.divide(personList.length, team_number));
  116. if (number <= 0) throw new BusinessError(ErrorCode.DATA_INVALID, '选手不足以分组,要分组的总人数 至少为 要分的组数');
  117. // 选手列表分组
  118. const teams = _.chunk(personList, number);
  119. // 获取 选手被均分到每个组后的余数
  120. const el = personList.length % team_number;
  121. // 最后余下的组数据
  122. let last = [];
  123. if (el !== 0) {
  124. // 如果有剩余不能被均分的选手,则将这些选手 放到 last数组中,再将多出来的这个去掉
  125. last = _.last(teams);
  126. teams.pop();
  127. }
  128. // 将正常均分的小组先分出来
  129. for (let i = 0; i < teams.length; i++) {
  130. const t = { name: `小组${i + 1}`, person: teams[i], ...obj };
  131. returnData.push(t);
  132. }
  133. // 循环剩下的选手, 依次放入每个已经分好的组中,尽可能均分人数
  134. for (let i = 0; i < last.length; i++) {
  135. returnData[i].person.push(last[i]);
  136. }
  137. // 将人的信息换回来
  138. if (type === '1') {
  139. // 双打
  140. } else {
  141. // 单打
  142. const users = [];
  143. for (const i of returnData) {
  144. const { person } = i;
  145. for (const p of person) {
  146. const { user_id } = p;
  147. const user = await this.userModel.findById(user_id).populate({
  148. path: 'user_id',
  149. select: 'name',
  150. model: this.baseUserModel,
  151. });
  152. p.user_name = _.get(user, 'user_id.name');
  153. }
  154. }
  155. }
  156. return returnData;
  157. }
  158. async beforeCreate(body) {
  159. const r = await this.checkPersonInOtherGroup(body);
  160. if (!r) throw new BusinessError(ErrorCode.DATA_INVALID, '组员已在其他组,请先将其退出原组后,再添置该组');
  161. return body;
  162. }
  163. async beforeUpdate(filter, update) {
  164. const r = await this.checkPersonInOtherGroup({ ...filter, ...update });
  165. if (!r) throw new BusinessError(ErrorCode.DATA_INVALID, '组员已在其他组,请先将其退出原组后,再添置该组');
  166. return { filter, update };
  167. }
  168. async beforeQuery(filter) {
  169. const { project_name } = filter;
  170. if (project_name) {
  171. const projectList = await this.matchProjectModel.find({ name: new RegExp(project_name) }, { _id: 1 });
  172. const project_id = projectList.map(i => i._id);
  173. filter.project_id = project_id;
  174. delete filter.project_name;
  175. }
  176. return filter;
  177. }
  178. // 检查该组的成员是否在该比赛项目中的其他组中
  179. // true:没有问题,可以操作; false:有人在其他组,不能允许操作
  180. async checkPersonInOtherGroup(data) {
  181. const { person, id, project_id } = data;
  182. // 没有person,就不需要检查
  183. if (!person) return true;
  184. const query = { _id: { $ne: id } };
  185. if (project_id) query.project_id = project_id;
  186. else {
  187. // 没有比赛项目id,也没有数据id: 是新增,但是没有比赛项目id,说明数据有错误
  188. if (!id) throw new BusinessError(ErrorCode.DATA_INVALID, '新增的数据中缺少比赛项目信息');
  189. const data = await this.model.findById(id);
  190. if (!data) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到要修改的数据');
  191. query.project_id = _.get(data, 'project_id');
  192. }
  193. let res = true;
  194. for (const p of person) {
  195. const num = await this.model.count({ ...query, person: p });
  196. if (num > 0) {
  197. res = false;
  198. break;
  199. }
  200. }
  201. return res;
  202. }
  203. async afterQuery(filter, data) {
  204. data = JSON.parse(JSON.stringify(data));
  205. for (const d of data) {
  206. const { person, person_type } = d;
  207. if (person_type === 'Race.User') {
  208. // 单人
  209. const users = await this.userModel.find({ _id: person }).populate({ path: 'user_id', model: this.baseUserModel, select: 'name' });
  210. const parr = person.map(i => {
  211. const r = users.find(f => ObjectId(f._id).equals(i));
  212. if (r) return { id: r._id, name: _.get(r, 'user_id.name') };
  213. return i;
  214. });
  215. d.person = parr;
  216. } else if (person_type === 'Race.TeamApply') {
  217. // 组队
  218. const teamApplys = await this.teamApplyModel.find({ _id: person });
  219. const parr = [];
  220. for (const p of person) {
  221. const r = teamApplys.find(f => ObjectId(f._id).equals(p));
  222. if (r) {
  223. const { one_member_name, two_member_name } = r;
  224. parr.push({ id: p, name: `${one_member_name}-${two_member_name}` });
  225. }
  226. }
  227. d.person = parr;
  228. }
  229. }
  230. return data;
  231. }
  232. async afterFetch(filter, d) {
  233. const { person = [], person_type } = d;
  234. if (person_type === 'Race.User') {
  235. // 单人
  236. const users = await this.userModel.find({ _id: person }).populate({ path: 'user_id', model: this.baseUserModel, select: 'name' });
  237. const parr = person.map(i => {
  238. const r = users.find(f => ObjectId(f._id).equals(i));
  239. if (r) return { id: r._id, name: _.get(r, 'user_id.name') };
  240. return i;
  241. });
  242. d.person = parr;
  243. } else if (person_type === 'Race.TeamApply') {
  244. // 组队
  245. const teamApplys = await this.teamApplyModel.find({ _id: person });
  246. const parr = [];
  247. for (const p of person) {
  248. const r = teamApplys.find(f => ObjectId(f._id).equals(p));
  249. if (r) {
  250. const { one_member_name, two_member_name } = r;
  251. parr.push({ id: p, name: `${one_member_name}-${two_member_name}` });
  252. }
  253. }
  254. d.person = parr;
  255. }
  256. return d;
  257. }
  258. /**
  259. * 根据该赛事状态,判断是否可以操作
  260. * 赛事状态为 1,2可以组队, 其余状态不能组队,不能修改
  261. * @param {String} match_id 赛事id
  262. * @return {Boolean} 是否可以操作
  263. */
  264. async canOpera(match_id) {
  265. const num = await this.matchModel.count({ _id: match_id, status: [ '1', '2' ] });
  266. return num > 0;
  267. }
  268. }
  269. module.exports = MatchTeamGroupService;