import CommongroupClient from './../client/commongroup' import TeambitionClient from '../client/teambition' import { DbCollection, App } from '../../types' import { Context } from 'koa' import * as moment from 'moment' import * as _ from 'lodash' import Base from './base' import { IUProjectModel } from '../../model/project' import { IUTasklistModel } from '../../model/tasklist' interface Opts { project: IUProjectModel, tasklist: IUTasklistModel, scenariofieldconfig: any, row?: number, _creatorId: string, } export default class TaskChecker extends Base { private teambitionClient: TeambitionClient private commongroupClient: CommongroupClient private PRIORITY_NORMAL: string = '普通' private PRIORITY_HIGH: string = '紧急' private PRIORITY_URGENT: string = '非常紧急' private stagesMap = new Map() private tagsMap = new Map() private sprintMap = new Map() private lookUpMap = new Map() private taskflowstatusMap = new Map() constructor (app: App, ctx: Context) { super(app, ctx) this.teambitionClient = new TeambitionClient(app, ctx) this.commongroupClient = new CommongroupClient(app, ctx) } /** * content 实际类型不确定 */ content(content: string, row: number) { if (content === null || content === undefined) { this.pushError('err.excel.content.required', row, 'content', content) } return String(content) } worktimes(content, row) { if (!content) { return null } content = Number(content).toFixed(1) if (!content) { this.pushError('err.excel.invalid.worktimes', row, 'worktimes', content) return null } else { // 导入接口默认是分钟为单位 return content * 60 } } storyPoint(content, row) { if (!content) { return null } content = Number(content).toFixed(1) if (!content) { this.pushError('err.excel.invalid.storyPoint', row, 'storyPoint', content) return null } else { return `${content}` } } progress(content, row) { if (!content) { return null } content = Number(content) if (!_.isInteger(content) || content < 0 || content > 100) { this.pushError('err.excel.invalid.taskProgress', row, 'taskProgress', content) return null } else { return content } } async tags(content: string, _projectId, row: number): Promise { if (!content) { return null } const tags: string[] = content.split('|') const tagIds: string[] = [] if (!tags.length) { this.pushError('err.excel.invalid.tag', row, 'tag', content) return [] } if (this.tagsMap.size) { tags.forEach((tag: string) => { if (this.tagsMap.has(tag)) { tagIds.push(this.tagsMap.get(tag)) } }) } else { const tagList: Object[] = await this.teambitionClient.getTagListByProjectId(_projectId) tagList.forEach((tag: any) => { if (tags.includes(tag.name)) { tagIds.push(tag._id) } this.tagsMap.set(tag.name, tag._id) }) } if (!tagIds.length) { this.pushError('err.excel.invalid.tag', row, 'tag', content) return [] } return tagIds } async sprint(content: string, _projectId: string, row: number): Promise { if (!content) { return null } let _sprintId = null if (this.sprintMap.has(content)) { _sprintId = this.sprintMap.get(content) } else { const sprints: DbCollection.Sptrint[] = await this.teambitionClient.getSprintListByProjectId(_projectId) sprints.forEach((sprint) => { if (sprint.status !== 'complete') { this.sprintMap.set(sprint.name, sprint._id) if (sprint.name === content) { _sprintId = sprint._id } } }) } if (!_sprintId) { this.pushError('err.excel.invalid.sprint', row, 'sprint', content) return null } return _sprintId } async taskflowstatus(name, opts: Opts): Promise { const { row, scenariofieldconfig, project } = opts if ((project.proTemplateType !== 'scrum') && (project.normalType !== 'taskflow')) { return null } if (!name) { this.pushError('err.excel.invalid.taskflowstatus', row, 'taskflowstatus', name) return null } let _taskflowstatusId if (this.taskflowstatusMap.has(name)) { _taskflowstatusId = this.taskflowstatusMap.get(name) } else { const conds = { _taskflowId: scenariofieldconfig._taskflowId, // 只能导入到开始状态 kind: 'start' } const taskflowstatuses: DbCollection.Taskflowstatus[] = await this.dbClient.getTaskflowstatusByCounds(conds) taskflowstatuses.map((taskflowstatus) => { this.taskflowstatusMap.set(taskflowstatus.name, `${taskflowstatus._id}`) if (taskflowstatus.name === name) { _taskflowstatusId = `${taskflowstatus._id}` } }) } if (!_taskflowstatusId) { this.pushError('err.excel.invalid.taskflowstatus', row, 'taskflowstatus', name) return null } return _taskflowstatusId } // 敏捷模板不需要 stageId async stage(name: string, _tasklistId: string, opts: Opts): Promise { const { row, project, tasklist } = opts let stageId if (project.proTemplateType === 'scrum') return null if (!name) { this.pushError('err.excel.invalid.stage', row, 'stage', name) return null } if (this.stagesMap.size && this.stagesMap.has(name)) { stageId = this.stagesMap.get(name) } else { const conds = { _tasklistId, _projectId: project._id, } const stages = await this.dbClient.getStageByConds(conds) stages.forEach((stage) => { if (stage.name === name) { stageId = stage._id } this.stagesMap.set(stage.name, stage._id) }) } if (!stageId) { this.pushError('err.excel.invalid.stage', row, 'stage', name) return null } if (tasklist.lock && (String(stageId) === tasklist.lock.split('$')[1])) { this.pushError('err.excel.can.not.import.to.lock.stage', row, 'stage', name) return null } return stageId } /** * @author jiangwei * @desc 格式化日期 */ parseDate(str: string, row: number, celName, hour?: number) { if (!str) return null let date = moment(str) if (!date.isValid()) { this.pushError('error.excel.date.invalid', row, celName, str) } if (date.get('hour') === 0 && date.get('minute') === 0) { date = date.startOf('d').hours(hour || 9) } return date.toISOString() } startAndDueDate(task, row) { if (!moment(task.startDate).isBefore(task.dueDate)) { return this.pushError('err.excel.date.wrongorder', row, 'dueDate', moment(task.dueDate).format('YYYY-MM-DD HH:mm:ss')) } if (moment(task.startDate).isSame(task.dueDate)) { task.startDate = moment(task.startDate).startOf('d').hours(9).toDate() task.dueDate = moment(task.dueDate).startOf('d').hours(18).toDate() } } priority(priority: string, row: number): number { if (!priority) return null switch (priority) { case this.PRIORITY_NORMAL: return 0 case this.PRIORITY_HIGH: return 1 case this.PRIORITY_URGENT: return 2 default: this.pushError('err.excel.priority', row, 'priority', priority) } } async commongroup(_projectId: string, name: string, row: number, _creatorId: string): Promise { if (!name) { return null } let _commongroupId: string let _commongroupName: string const commongroupList: Object[] = await this.commongroupClient.getCommongroupStoryList(_creatorId, _projectId) if (commongroupList && commongroupList.length) { commongroupList.forEach((commongroup: DbCollection.Commongroup) => { if (commongroup.name === name) { _commongroupId = commongroup._id _commongroupName = commongroup.name } }) } if (!_commongroupId) { this.pushError('err.excel.commongroup.story', row, 'commongroup.story', name) } return { _id: _commongroupId, name: _commongroupName } } async lookup(project, content, _customfieldId, row) { if (!content) { return null } const values: string[] = content.split('|') let lookupList = this.lookUpMap.get(_customfieldId) if (!lookupList) { lookupList = await this.teambitionClient.getLookUpChoiceList(project._id, project._organizationId, _customfieldId) if (!lookupList) { return } this.lookUpMap.set(_customfieldId, lookupList) } const result = lookupList.options.filter((lookup) => { return values.includes(lookup.title) }) return result } date(content, celName, row) { if (!moment(content).isValid()) { this.pushError('err.excel.customfield.invalid', row, celName, content) } return [moment(content).toISOString()] } multipleChoice(choices, content) { const values: string[] = content.split('|') const result = [] choices.forEach((choice) => { if (values.includes(choice.value)) { result.push(choice._id) } }) return result } dropDown(choices, content, row: number, celName: string) { const result = [] choices.forEach((choice) => { if (content === choice.value) { result.push(choice._id) } }) if (!result.length) { this.pushError('err.excel.customfield.invalid', row, celName, content) } return result } number(content, row, celName: string): Number { content = Number(content) if (!content) { this.pushError('err.excel.customfield.invalid', row, celName, content) } return content } // 预判断只能检测名字 parent(parentTask: string, parentTasksSet: Set, row: number): string { if (!parentTasksSet.has(parentTask)) { this.pushError('err.excel.parenttask.content', row, 'parentTask', parentTask) return '' // 父任务不存在则清空 } return parentTask } }