import Teambition from '../../client/teambition' import { Scenariofield, Task, ParsedData, DbCollection } from '../../../types' import { ilog } from 'ilog' import TaskChecker from '../../check/task' import Base from './base' import { IUTasklistModel } from '../../../model/tasklist' import { IUProjectModel } from '../../../model/project' interface Opts { project: IUProjectModel, tasklist: IUTasklistModel, scenariofieldconfig: any, row?: number, _creatorId: string parentTasksSet: Set } interface Header { type: string, value: string } export default class TaskFactory extends Base { protected headerArray: Header[] private CUSTOMFIELD: string = 'customfield' private COMMONGROUP: string = 'commongroup' private customfieldsMap = new Map() private commongroupMap = new Map() async buildHeaders (scenariofields: Scenariofield[], projectType: string) { this.headerArray = [{ type: this.NATIVE, value: 'content' }, { type: this.NATIVE, value: 'parentTask' }] switch (projectType) { case 'scrum': this.headerArray.push({ type: this.NATIVE, value: 'taskflowstatus' }) break case 'taskflow': this.headerArray.push({ type: this.NATIVE, value: 'stage' }, { type: this.NATIVE, value: 'taskflowstatus' }) break default: this.headerArray.push({ type: this.NATIVE, value: 'stage' }) break } this.headerArray = this.headerArray.concat([{ type: this.NATIVE, value: 'executor' }, { type: this.NATIVE, value: 'startDate' }, { type: this.NATIVE, value: 'dueDate' }]) let extra = '' for (let i = 0; i < scenariofields.length; i++) { const scenariofield = scenariofields[i] if (!scenariofield._customfieldId) { if (scenariofield.required) { extra = '*' } this.headerArray.push({ type: this.NATIVE, value: scenariofield.fieldType + extra }) } else { await this.buildCustomfield(scenariofield) } } this.headers = this.translate(this.headerArray) return this } private async buildCustomfield (scenariofield) { let extra = '' if (scenariofield.required) { extra = '*' } const customfield = await this.dbClient.getCustomfieldById(scenariofield._customfieldId) if (customfield.type) { switch (customfield.type) { case 'commongroup': this.headerArray.push({ type: this.COMMONGROUP, value: `${customfield.type}.${customfield.subtype}${extra}` }) break case 'lookup': this.headerArray.push({ type: this.CUSTOMFIELD, value: customfield.name + extra }) break case 'dropDown': case 'multipleChoice': case 'number': case 'date': case 'text': this.headerArray.push({ type: this.CUSTOMFIELD, value: customfield.name + extra }) break default: break } } } async transformToTasks (parsedData: Object[], scenariofieldconfig, ...rest) { let tasklist: IUTasklistModel let _creatorId: string let project: IUProjectModel [tasklist, _creatorId, project] = rest const checkClient = new TaskChecker(this.app, this.ctx) const parentTasksSet:Set = new Set() const opts:Opts = { project, tasklist, scenariofieldconfig, _creatorId, parentTasksSet } const tasks:Object[] = [] for (let index = 0; index < parsedData.length; index++) { const item = parsedData[index] opts.row = index const task = await this.checkAndTransformItems(item, checkClient, index, opts) parentTasksSet.add(task.content) tasks.push(task) } return { tasks, errorMsgs: checkClient.ErrorMsgs } } private async checkAndTransformItems(item, check: TaskChecker, row: number, opts: Opts): Promise { const { project, parentTasksSet, scenariofieldconfig, tasklist, _creatorId } = opts const body:Task = { content: check.content(item.content, row), _creatorId: _creatorId, _organizationId: project._organizationId, _tasklistId: tasklist._id, _projectId: project._id, _scenariofieldconfigId: opts.scenariofieldconfig._id, _stageId: await check.stage(item.stage, tasklist._id, opts), _taskflowstatusId: await check.taskflowstatus(item.taskflowstatus, opts), _executorId: await check.executor(item.executor, project._id, false, row), customfields: [], ancestorIds: [], parentTask: item.parentTask, } // 有父任务,则校验父任务是否存在 if (body.parentTask) { // 不存在则清空,并记录错误信息 body.parentTask = check.parent(body.parentTask, parentTasksSet, row) } // 时期单独处理,有依赖关系 body.startDate = check.parseDate(item.startDate, row, 'startDate', 9) body.dueDate = check.parseDate(item.dueDate, row, 'endDate', 18) if (body.startDate && body.dueDate) { check.startAndDueDate(body, row) } await this.transformScenariofields(body, item, check, scenariofieldconfig.scenariofields, project, row, _creatorId) return body } private async transformScenariofields(body, item, check: TaskChecker, ...rest) { const [scenariofields, project, row, _creatorId] = rest await Promise.all(scenariofields.map(async (scenariofield) => { if (scenariofield._customfieldId) { await this.transformCustomfields(scenariofield, item, body, check, project, row, _creatorId) } else { await this.transformCommonFields(scenariofield, item, body, check, project, row) } })) } private async transformCommonFields(scenariofield, item: ParsedData, body: Task, ...rest) { let check: TaskChecker let project: DbCollection.Project, row: number [check, project, row] = rest switch (scenariofield.fieldType) { case 'note': if (scenariofield.required && !item['note*']) { check.pushError('err.excel.note.required', row, 'note', null) } else { body.note = item.note || item['note*'] } break case 'priority': if (scenariofield.required && !item['priority*']) { check.pushError('err.excel.priority.required', row, 'priority', null) } else { body.priority = check.priority(item.priority || item['priority*'], row) } break case 'tag': if (scenariofield.required && !item['tag*']) { check.pushError('err.excel.tag.required', row, 'tag', null) } else { body.tagIds = await check.tags(item.tag || item['tag*'], project._id, row) } break case 'worktimes': if (scenariofield.required && !item['worktimes*']) { check.pushError('err.excel.worktimes.required', row, 'worktimes', null) } else { body.workTime = { totalTime: check.worktimes(item.worktimes || item['worktimes*'], row) } } break case 'storyPoint': if (scenariofield.required && !item['storyPoint*']) { check.pushError('err.excel.storyPoint.required', row, 'storyPoint', null) } else { body.storyPoint = check.storyPoint(item.storyPoint || item['storyPoint*'], row) } break case 'taskProgress': if (scenariofield.required && !item['taskProgress*']) { check.pushError('err.excel.taskProgress.required', row, 'taskProgress', null) } else { body.progress = check.progress(item.taskProgress || item['taskProgress*'], row) } break case 'sprint': if (scenariofield.required && !item['sprint*']) { check.pushError('err.excel.taskProgress.required', row, 'taskProgress', null) } else { body._sprintId = await check.sprint(item.sprint || item['sprint*'], project._id, row) } break default: break } } private async transformCustomfields(scenariofield, item, body, ...rest) { let check: TaskChecker let project, row:number let _creatorId: string [check, project, row, _creatorId] = rest let customfield if (this.customfieldsMap.has(scenariofield._customfieldId)) { customfield = this.customfieldsMap.get(scenariofield._customfieldId) } else { customfield = await this.dbClient.getCustomfieldById(scenariofield._customfieldId) this.customfieldsMap.set(scenariofield._customfieldId, customfield) } let extra = '' let content if (scenariofield.required) { extra = '*' } switch (customfield.type) { case 'commongroup': content = item[`${customfield.type}.${customfield.subtype}${extra}`] if (scenariofield.required && !content) { check.pushError('err.excel.customfield.required', row, customfield.name + extra, null) } else { let commongroup: DbCollection.Commongroup if (this.commongroupMap.has(content)) { commongroup = this.commongroupMap.get(content) } else { commongroup = await check.commongroup(project._id, content, row, _creatorId) } if (commongroup) { this.commongroupMap.set(commongroup.name, commongroup) body.customfields.push({ _customfieldId: customfield._id, type: 'commongroup', value: [{ _id: commongroup._id, title: commongroup.name }] }) } } break case 'lookup': content = item[customfield.name + extra] if (scenariofield.required && !content) { check.pushError('err.excel.customfield.required', row, customfield.name + extra, null) } else { if (!content) return null body.customfields.push({ _customfieldId: customfield._id, value: await check.lookup(project, content, customfield._id, row) }) } break case 'dropDown': content = item[customfield.name + extra] if (scenariofield.required && !content) { check.pushError('err.excel.customfield.required', row, customfield.name, null) } else { if (!content) { return null } body.customfields.push({ _customfieldId: customfield._id, values: check.dropDown(customfield.choices, content, row, customfield.name + extra) }) } break case 'multipleChoice': content = item[customfield.name + extra] if (scenariofield.required && !content) { check.pushError('err.excel.customfield.required', row, customfield.name + extra, null) } else { if (!content) { return null } body.customfields.push({ _customfieldId: customfield._id, values: check.multipleChoice(customfield.choices, content) }) } break case 'date': content = item[customfield.name + extra] if (scenariofield.required && !content) { check.pushError('err.excel.customfield.required', row, customfield.name + extra, null) } else { if (!content) { return null } body.customfields.push({ _customfieldId: customfield._id, type: customfield.type, values: check.date(content, customfield.name + extra, row) }) } break case 'number': content = item[customfield.name + extra] if (scenariofield.required && !content) { check.pushError('err.excel.customfield.required', row, customfield.name + extra, null) } else { if (!content) { return null } body.customfields.push({ _customfieldId: customfield._id, type: customfield.type, values: [check.number(content, row, customfield.name + extra)] }) } break case 'text': content = item[customfield.name + extra] if (scenariofield.required && !content) { check.pushError('err.excel.customfield.required', row, customfield.name + extra, null) } else { if (!content) { return null } body.customfields.push({ _customfieldId: customfield._id, type: customfield.type, values: [content] }) } break } } /** * @author jiangwei * @desc 导入创建任务,区分普通项目、专业项目 * @param {*} tasks * @memberof TaskFactory */ async import (tasks) { const tasksIdMap = new Map() const client = new Teambition(this.app, this.ctx) for (let i = 0; i < tasks.length; i++) { let newTask // 处理父子关系,目前通过 task.content 来记录父子关系 if (tasks[i].parentTask) { tasks[i]._ancestorId = tasksIdMap.get(tasks[i].parentTask) } try { newTask = await client.importTasksToProject(tasks[i]) // 同名任务覆盖之前的映射记录, 保证找到最近的一个 tasksIdMap.set(tasks[i].content, newTask._id) } catch (error) { ilog.err(error) } // 限制访问速率 await new Promise((resolve, reject) => { setTimeout(resolve, 100) }) } } }