import { ApiTags, ApiQuery } from '@midwayjs/swagger'; import { Validate } from '@midwayjs/validate'; import { Controller, Inject, Get, Param, Post, Body, Del, Query } from '@midwayjs/core'; import { cloneDeep, get, omit, pick } from 'lodash'; import { ServiceError, ErrorCode } from '../../error/service.error'; import { BaseController } from '../../frame/BaseController'; import { MatchExtService } from '../../service/match/matchExt.service'; import { MatchRegistrationService } from '../../service/match/matchRegistration.service'; import { UserService } from '../../service/system/user.service'; import { AliyunSmsService } from '../../service/thirdParty/aliyunSms.service'; import { MatchService } from '../../service/platform/match.service'; import dayjs = require('dayjs'); const namePrefix = '创新大赛拓展'; @ApiTags(['创新大赛拓展']) @Controller('/matchExt', { tagName: namePrefix }) export class MatchExtController implements BaseController { @Inject() service: MatchExtService; @Inject() matchRegService: MatchRegistrationService; @Inject() userService: UserService; @Inject() smsService: AliyunSmsService; @Inject() matchService: MatchService; @Get('/') @ApiTags('列表查询') @ApiQuery({ name: 'query' }) async index(@Query() query: object) { const qobj = omit(query, ['skip', 'limit']); const others: any = pick(query, ['skip', 'limit']); const result = await this.service.query(qobj, others); return result; } @Get('/:id') @ApiTags('单查询') async fetch(@Param('id') id: number) { const data = await this.service.fetch({ id }); return data; } @Post('/', { routerName: `创建${namePrefix}` }) @ApiTags('创建数据') @Validate() async create(@Body() data: object) { // 跟随赛事信息创建,不能直接创建 return false; } @Post('/:id', { routerName: `修改${namePrefix}` }) @ApiTags('修改数据') @Validate() async update(@Param('id') id: number, @Body() data: object) { if (!id) throw new ServiceError(ErrorCode.ID_NOT_FOUND); // 所有的修改,是不允许修改状态的 const newData = omit(data, ['status']); const result = await this.service.update({ id }, newData); return result; } @Del('/:id', { routerName: `删除${namePrefix}` }) @ApiTags('删除数据') @Validate() async delete(@Param('id') id: number) { if (!id) throw new ServiceError(ErrorCode.ID_NOT_FOUND); // 不允许删除非报名中的赛事 const data = await this.service.fetch({ id }); if (!data) return; const status = get(data, 'status'); if (status !== '0') { // 抛出异常:赛事不处于报名中的状态,无法删除. throw new ServiceError(ErrorCode.MATCH_EXT_IS_BEGINNING); } const result = await this.service.delete({ id }); return result; } @Get('/detail/:id') async detail(@Param('id') id: string) { const data = await this.service.fetch({ id }); return data; } /** * 进入:报名阶段-已结束 * @param data body * @property match_id 赛事id */ @Post('/step1', { routerName: '报名阶段-已结束' }) async step1(@Body() data: object) { //报名结束,需要检查 报名的创建接口是否允许创建信息----不允许再添加报名信息了 /** * 检查内容: * 赛事拓展表: * ext.status = "0" * 赛事表: * match.status = "1" * 修改内容: * 赛事拓展表: * ext.status => "1" 变为 报名阶段-已结束 * 赛事表: * match.status =>"2" 变为 进行中 * 赛事报名表: * 处于 报名阶段(ext_status='0') 且 已通过审核的报名信息(status='1') 变为 初审阶段(ext_status='1')且未通过审核(status='0')的报名信息 * reg.ext_status: "0" => "1" * reg.status: "1" => "0" */ const match_id = get(data, 'match_id'); await this.service.checkMatchStatus(match_id, '1'); await this.service.checkMatchExtStatus(match_id, '0'); await this.matchService.update({ id: match_id }, { match_status: '2' }); await this.service.update({ match_id }, { status: '1' }); await this.matchRegService.update({ match_id, ext_status: '0', status: '1' }, { ext_status: '1', status: '0' }); return 'ok'; } // 报名阶段-已结束 到 初审阶段-组织初审 过程只是变化状态码,不需要处理其他内容 /** * 进入: 初审阶段-组织初审 * @param data body * @property match_id 赛事id */ @Post('/step2', { routerName: '初审阶段-组织初审' }) async regBack(@Body() data: object) { // 只修改状态,不做时间修改,时间修改用另一个接口 /** * 检查内容: * 赛事拓展表: * ext.status = "1" * 修改内容: * 赛事拓展表: * ext.status => "2" 变为 初审阶段-组织初审 * 赛事报名表: * reg.ext_status: "1" => '2' */ const match_id = get(data, 'match_id'); await this.service.checkMatchExtStatus(match_id, '1'); await this.service.update({ match_id }, { status: '2' }); await this.matchRegService.update({ match_id, ext_status: '1' }, { ext_status: '2' }); } /** * 初审阶段-组织初审 到 初审阶段-公示名单 需要修改 参加初审人员信息的 初审开始时间 * @param data body * @property match_id 赛事id * @property start_time 初审时间 * @property ids 报名数据id */ @Post('/step2/fill', { routerName: '初审阶段-组织初审-补充初审时间信息' }) async step2Fill(@Body() data: object) { /** * 检查内容: * 赛事拓展表: * ext.status = "2" * 修改内容: * 赛事报名表: * reg.ext_status: '2' * reg.start_time: => ${start_time} */ const match_id = get(data, 'match_id'); await this.service.checkMatchExtStatus(match_id, '2'); const start_time = get(data, 'start_time'); // 验证是否是正确的时间格式 const is_time = dayjs(start_time).isValid(); if (!is_time) { // 抛出异常: 时间格式错误 throw new ServiceError(ErrorCode.TIME_FORMAT_ERROR); } // 获取报名信息数据id const ids = get(data, 'ids', []); for (const id of ids) { await this.matchRegService.update({ id: id, ext_status: '2', status: '0' }, { start_time }); } } /** * 进入: 初审阶段-公示名单, 只修改状态即可 * @param data body * @property match_id 赛事id */ @Post('/step3', { routerName: '初审阶段-公示名单' }) async step3(@Body() data: object) { /** * 检查内容: * 赛事拓展表: * ext.status = "2" * 修改内容: * 赛事拓展表: * ext.status => "3" 变为 初审阶段-公示名单 * 赛事报名表: * reg.ext_status: "2" => '3' */ const match_id = get(data, 'match_id'); await this.service.checkMatchExtStatus(match_id, '2'); await this.service.update({ match_id }, { status: '3' }); await this.matchRegService.update({ match_id, ext_status: '2' }, { ext_status: '3' }); } /** * 查询初审名单 * @param match_id 赛事id */ @Get('/step3/nameList/:match_id', { routerName: '初审阶段-公示名单-名单查询' }) async step3NameList(@Param('match_id') match_id: string) { /** * 检查内容: * 赛事拓展表: * ext.status = "3" * 查询内容: * 赛事报名表: * reg.ext_status: "3"; */ await this.service.checkMatchExtStatus(match_id, '3'); const { data } = await this.matchRegService.query({ match_id, ext_status: '3', status: '0' }); const fillList = []; for (const i of data) { const newItem = cloneDeep(i); const user_id = get(i, 'user_id'); const user = await this.userService.fetch({ id: user_id }); if (user) newItem.user_name = get(user, 'nick_name'); const match_id = get(i, 'match_id'); const match = await this.matchService.fetch({ id: match_id }); if (match) newItem.match_name = get(match, 'name'); fillList.push(newItem); } return fillList; } /** * 进入: 初审阶段-赛事进行, 只修改状态即可 * @param data body * @property match_id 赛事id */ @Post('/step4', { routerName: '初审阶段-赛事进行' }) async step4(@Body() data: object) { /** * 检查内容: * 赛事拓展表: * ext.status = "3" * 修改内容: * 赛事拓展表: * ext.status => "4" 变为 初审阶段-赛事进行 * 赛事报名表: * reg.ext_status: "3" => '4' */ const match_id = get(data, 'match_id'); await this.service.checkMatchExtStatus(match_id, '3'); await this.service.update({ match_id }, { status: '4' }); await this.matchRegService.update({ match_id, ext_status: '3' }, { ext_status: '4' }); } /** * 初审阶段-赛事进行-上传成绩, 只上传分数 * @param match_id 赛事id * @property reg_id 选手id * @property status 是否通过; 1:通过初审;2未通过初审 * @property score 分数 */ @Post('/step4/score/:match_id', { routerName: '初审阶段-赛事进行-上传成绩' }) async step4Score(@Param('match_id') match_id: string, @Body() data: object) { /** * 检查内容: * 赛事拓展表: * ext.status = "4" * 修改内容: * 赛事报名表: * reg.ext_status: "4" * reg.status: '0' * reg.score = ${score} */ await this.service.checkMatchExtStatus(match_id, '4'); const reg_id = get(data, 'reg_id'); // const status = get(data, 'status') const score = get(data, 'score'); const query = { id: reg_id, ext_status: '4', status: '0' }; const regData = await this.matchRegService.fetch(query); if (!regData) { // 抛出异常: 该选手不符合上传成绩的要求,请检查选手信息 throw new ServiceError(ErrorCode.MATCH_REG_USER_ERROR); } await this.matchRegService.update({ id: reg_id }, { score }); } /** * 决定决赛名单 * @param match_id 赛事id * @param data body * @property ids 报名信息id */ @Post('/step4/to5/:match_id', { routerName: '初审阶段-赛事进行-选择决赛名单' }) async step4To5(@Param('match_id') match_id: string, @Body() data: object) { /** * 检查内容: * 赛事拓展表: * ext.status = "4" * 修改内容: * 赛事报名表: * reg.ext_status: "4" * reg.status: '0' * reg.status: '0' => '1' */ await this.service.checkMatchExtStatus(match_id, '4'); const ids = get(data, 'ids', []); for (const id of ids) { await this.matchRegService.update({ id: id, ext_status: '4', status: '0' }, { status: '1' }); } } @Post('/step5', { routerName: '决赛阶段-组织决赛' }) async step5(@Body() data: object) { /** * 检查内容: * 赛事拓展表: * ext.status = "4" * 修改内容: * 赛事拓展表: * ext.status => "5" * 赛事报名表: * reg.ext_status: "4" * reg.status: '1' * reg.status: '1' => '0' * reg.ext_status: '4' => '5' */ const match_id = get(data, 'match_id'); await this.service.checkMatchExtStatus(match_id, '4'); await this.service.update({ match_id }, { status: '5' }); await this.matchRegService.update({ match_id, ext_status: '4', status: '1' }, { status: '0', ext_status: '5' }); } /** * 设置某些人决赛时间 * @param match_id 赛事id * @param data body * @property ids 报名数据id列表 * @property start_time 决赛开始时间 */ @Post('/step5/time/:match_id', { routerName: '决赛阶段-组织决赛-设置决赛时间' }) async step5SetTime(@Param('match_id') match_id: string, @Body() data: object) { /** * 检查内容: * 赛事拓展表: * ext.status = "5" * 修改内容: * 赛事报名表: * reg.ext_status: "5" * reg.status: '0' * reg.final_start_time => ${start_time} */ await this.service.checkMatchExtStatus(match_id, '5'); const ids = get(data, 'ids', []); const start_time = get(data, 'start_time'); for (const id of ids) { await this.matchRegService.update({ id, ext_status: '5', status: '0' }, { final_start_time: start_time }); } return 'ok'; } @Get('/step5/sms/:match_id', { routerName: '决赛阶段-组织决赛-短信通知' }) async step5Sms(@Param('match_id') match_id: string, @Body() data: object) { /** * 检查内容: * 赛事拓展表: * ext.status = "5" * 组织内容: * 赛事报名表: * reg.ext_status:'5' * reg.status: '0' * reg.final_confirm:'1' * 1.获取赛事信息----标题 * 2.获取赛事报名信息----每个人的决赛时间 * 3.获取用户信息----昵称,电话 * 4.组织短信: ${昵称} 您参加的赛事: ${标题} 将于 ${决赛时间} 开始, * 请您在网站或小程序中进行确认,以避免主办方将您误认为拒赛而采用其他选手代替 */ await this.service.checkMatchExtStatus(match_id, '5'); const matchInfo = await this.matchService.fetch({ id: match_id }); const match_name = get(matchInfo, 'name'); const { data: extList } = await this.matchRegService.query({ match_id, ext_status: '5', status: '0', final_confirm: '1' }); for (const i of extList) { const user_id = get(i, 'user_id'); if (!user_id) continue; const userInfo = await this.userService.fetch({ id: user_id }); if (!userInfo) continue; const phone = get(userInfo, 'phone'); if (!phone) continue; const nick_name = get(userInfo, 'nick_name'); if (!nick_name) continue; const start_time = get(i, 'final_start_time'); if (!start_time) continue; const smsMsg = `${nick_name} 您参加的赛事: ${match_name} 将于 ${start_time} 开始, 请您在网站或小程序中进行确认,以避免主办方将您误认为拒赛而采用其他选手代替`; // TODO: 发短信 } } /** * 用户确认参加决赛 * @param match_id 赛事id * @param data body * @property user_id 用户id */ @Post('/step5/confirm/:match_id', { routerName: '决赛阶段-组织决赛-参加决赛确认' }) async step5Confirm(@Param('match_id') match_id: string, @Body() data: object) { /** * 检查内容: * 赛事拓展表: * ext.status = "5" * 修改内容: * 赛事报名表: * reg.ext_status:'5' * reg.status: '0' * final_confirm => '0' */ await this.service.checkMatchExtStatus(match_id, '5'); const user_id = get(data, 'user_id'); // 默认规定顺序,先来后到 const { data: list } = await this.matchRegService.query({ match_id, ext_status: '5', status: '0', final_confirm: '0' }); // 做默认序号,将已经确认的人查出来,然后排查序号,如果中间有缺少的序号,就先补充到缺少的序号中;如果没有缺少,那就往后默认排 let lastOrderNum = 1; for (const i of list) { const thisOrderNum = get(i, 'final_order_no'); // 按理说,不应该出现没有排序的情况 if (!thisOrderNum) continue; // 顺序不一致,说明少了 if (lastOrderNum !== thisOrderNum) { // 少了就需要把当前的补上.会出现2种情况, 正常顺序<当前顺序: 按正常顺序进行补充; // 正常顺序>当前顺序:说明从此开始,后面的序号都是乱的,是不应该出现的情况.如果出现了,就需要人为调整了.所以只考虑第一个情况 break; } // 下一个,如果不循环了,那就直接给新数据用 lastOrderNum = lastOrderNum + 1; } await this.matchRegService.update({ match_id, user_id, ext_status: '5', status: '0' }, { final_confirm: '0', final_order_no: lastOrderNum }); } @Post('/step5/refuse/:match_id', { routerName: '决赛阶段-组织决赛-拒绝参加决赛' }) async step5Refuse(@Param('match_id') match_id: string, @Body() data: object) { /** * 检查内容: * 赛事拓展表: * ext.status = "5" * 修改内容: * 赛事报名表: * reg.ext_status:'5' * reg.status: '0' * reg.status => '2' */ await this.service.checkMatchExtStatus(match_id, '5'); const user_id = get(data, 'user_id'); await this.matchRegService.update({ match_id, user_id, ext_status: '5', status: '0' }, { status: '2' }); } /** * 选择初赛中的选手 补充到决赛中 * @param match_id 赛事id * @param data body * @property user_id 用户id */ @Post('/step5/supplement/:match_id', { routerName: '决赛阶段-组织决赛-补充决赛人员' }) async step5Supplement(@Param('match_id') match_id: string, @Body() data: object) { /** * 检查内容: * 赛事拓展表: * ext.status = "5" * 修改内容: * 赛事报名表: * reg.ext_status:'4'=>'5' * reg.status =>'0' */ await this.service.checkMatchExtStatus(match_id, '5'); const user_id = get(data, 'user_id'); await this.matchRegService.update({ match_id, user_id, ext_status: '4' }, { ext_status: '5', status: '0' }); } @Get('/step5/list/:match_id', { routerName: '决赛阶段-组织决赛-决赛人员名单' }) async step5NameList(@Param('match_id') match_id: string, @Query() query: object) { /** * 检查内容: * 组织内容: * 赛事报名表: * reg.ext_status:'5' * reg.final_confirm =>'0' */ // await this.service.checkMatchExtStatus(match_id, '5'); // 决赛名单,根据顺序升序 const final_confirm = get(query, 'final_confirm') const { data: list } = await this.matchRegService.query({ match_id, ext_status: '5', final_confirm }, { order: { final_order_no: 'ASC' } }); const newList = [] for (const i of list) { let newItem = cloneDeep(i) newItem = this.matchRegService.dealData(newItem); newList.push(newItem) } return newList; } /** * 决赛人员排序 * @param match_id 赛事id * @param data body * @property id 报名数据id * @property order_no 排名位置 */ @Post('/step5/order/:match_id', { routerName: '决赛阶段-组织决赛-人员排序' }) async step5Order(@Param('match_id') match_id: string, @Body() data: object) { /** * 检查内容: * 赛事拓展表: * ext.status = "5" * 组织内容: * 赛事报名表: * reg.final_order_no =>'0' * id,order_no */ await this.service.checkMatchExtStatus(match_id, '5'); const { data: list } = await this.matchRegService.query({ match_id, ext_status: '5', final_confirm: '0' }, { order: { final_order_no: 'ASC' } }); const id = get(data, 'id'); if (!id) { // 抛出异常:缺少报名信息 throw new ServiceError(ErrorCode.MATCH_EXT_DATA_NOT_FOUND); } const order_no = get(data, 'order_no'); // 没写,直接不该 if (!order_no) return 'ok'; /**数据原索引 */ const oldIndex = list.findIndex(f => f.id === id); // 没数据,不改 if (oldIndex < 0) { throw new ServiceError(ErrorCode.MATCH_REG_NOT_FOUND); } const oldData = list[oldIndex]; /**目标索引 */ let targetIndex = order_no - 1; if (oldIndex === targetIndex) { throw new ServiceError(ErrorCode.MATCH_REG_ORDER_NOT_CHANGE); } let removeIndex = oldIndex; if (targetIndex < oldIndex) { // 如果要移动到的索引 小于 原数据索引: 是在原数据前添加的,影响删除,需要原索引+1才能删除原数据 removeIndex = removeIndex + 1; } else { // 移动的索引要+1 targetIndex = targetIndex + 1; } list.splice(targetIndex, 0, oldData); list.splice(removeIndex, 1); // 重新排列 for (let index = 0; index < list.length; index++) { const e = list[index]; const id = get(e, 'id'); await this.matchRegService.update({ id }, { final_order_no: index + 1 }); } } @Post('/step6', { routerName: '决赛阶段-名单公示' }) async step6(@Body() data: object) { /** * 检查内容: * 赛事拓展表: * ext.status = "5" * 修改内容: * 赛事拓展表: * ext.status => "6" 变为 决赛阶段-名单公示 * 赛事报名表: * reg.final_confirm:'0' * reg.ext_status: "5" => '6' */ const match_id = get(data, 'match_id'); await this.service.checkMatchExtStatus(match_id, '5'); await this.service.update({ match_id }, { status: '6' }); await this.matchRegService.update({ match_id, ext_status: '5', final_confirm: '0' }, { ext_status: '6' }); } @Post('/step7', { routerName: '决赛阶段-赛事进行' }) async step7(@Body() data: object) { /** * 检查内容: * 赛事拓展表: * ext.status = "6" * 修改内容: * 赛事拓展表: * ext.status => "7" 变为 决赛阶段-赛事进行 * 赛事报名表: * reg.final_confirm:'0' * reg.ext_status: "6" => '7' */ const match_id = get(data, 'match_id'); await this.service.checkMatchExtStatus(match_id, '6'); await this.service.update({ match_id }, { status: '7' }); await this.matchRegService.update({ match_id, ext_status: '6', final_confirm: '0' }, { ext_status: '7' }); } // TODO:决赛大屏 /** * 评委打分,前端将评委的数据传来.然后再做总分计算,总分通过平均分方式计算 * @param match_id 赛事id * @param data body * @property id 报名数据id * @property {Object} details 决赛总分数详情 */ @Post('/step7/score/:match_id', { routerName: '决赛阶段-赛事进行-评委打分' }) async step7Score(@Param('match_id') match_id: string, @Body() data: object) { /** * 检查内容: * 赛事拓展表: * ext.status = "7" * 修改内容: * 赛事报名表: * reg.ext_status: "7" * reg.final_confirm: "0" * reg.id: ${id} * reg.final_score_details=> ${details} */ await this.service.checkMatchExtStatus(match_id, '7'); const id = get(data, 'id'); if (!id) { // 抛出异常 throw new ServiceError(ErrorCode.MATCH_REG_NOT_FOUND); } const query = { id, match_id, ext_status: '7', final_confirm: '0' }; const regData = await this.matchRegService.fetch(query); if (!regData) { throw new ServiceError(ErrorCode.MATCH_REG_NOT_FOUND); } // 合并评审记录 const oldDetails: Array = get(regData, 'final_score_details') || []; const final_score_details: object = get(data, 'details'); if (oldDetails && oldDetails.length > 0) { // 判断之前的评审记录是否存在 const alreadyGiven = oldDetails.findIndex(f => get(final_score_details, 'name') && f.name === get(final_score_details, 'name')); if (alreadyGiven >= 0) { // 已经给过了,最新的覆盖 oldDetails.splice(alreadyGiven, 0, final_score_details); } else { // 没有给,那就直接push oldDetails.push(final_score_details); } } else { // 没有给,那就直接push oldDetails.push(); } // 计算总分 const final_score_total = oldDetails.reduce((p, n) => p + get(n, 'score', 0), 0); const final_score = final_score_total / oldDetails.length; await this.matchRegService.update(query, { final_score, final_score_details: oldDetails }); } @Post('/step8', { routerName: '决赛阶段-赛事结束' }) async step8(@Body() data: object) { /** * 检查内容: * 赛事拓展表: * ext.status = "7" * 修改内容: * 赛事拓展表: * ext.status => "8" 变为 决赛阶段-赛事进行 * 赛事报名表: * reg.ext_status: "7" => '8' */ const match_id = get(data, 'match_id'); await this.service.checkMatchExtStatus(match_id, '7'); await this.service.update({ match_id }, { status: '8' }); await this.matchRegService.update({ match_id, ext_status: '7', final_confirm: '0' }, { ext_status: '8' }); } /** * 人为规定的决赛实时名单 * @param match_id 赛事id * @returns */ @Post('/lastList/:match_id', { routerName: '赛事决赛名单' }) async lastList(@Param('match_id') match_id: string) { const list = await this.matchRegService.getArrangeList(match_id) const newList = [] for (const i of list) { let newItem = cloneDeep(i) newItem = this.matchRegService.dealData(newItem); newList.push(newItem) } return newList; } }