UNPKG

devexpress-gantt

Version:

DevExpress Gantt Control

314 lines (294 loc) 18 kB
import { isDefined } from "@devexpress/utils/lib/utils/common"; import { ValidationControllerSettings } from "../../View/Settings/InitSettings/ValidationControllerSettings"; import { ValidationSettings } from "../../View/Settings/ValidationSettings"; import { DateUtils } from "../../View/Utils/DateUtils"; import { DependencyType } from "../Entities/Enums"; import { UpdateTaskHistoryItem } from "../History/HistoryItems/Task/UpdateTaskHistoryItem"; import { IHistory } from "../History/IHistory"; import { ModelManipulator } from "../Manipulators/ModelManipulator"; import { ViewVisualModelItem } from "../VisualModel/ViewVisualModelItem"; import { ViewVisualModel } from "../VisualModel/VisualModel"; import { DateRange } from "../WorkingTime/DateRange"; import { DateTimeUtils } from "../WorkingTime/DateTimeUtils"; import { ValidationError } from "./ValidationError"; export class ValidationController { settings: ValidationControllerSettings; get viewModel(): ViewVisualModel { return this.settings.getViewModel(); } get history(): IHistory { return this.settings.getHistory(); } get modelManipulator(): ModelManipulator { return this.settings.getModelManipulator(); } get range(): DateRange { return this.settings.getRange(); } get validationSettings(): ValidationSettings { return this.settings.getValidationSettings(); } get _parentAutoCalc(): boolean { return this.viewModel.parentAutoCalc; } get enablePredecessorGap(): boolean { return this.viewModel.enablePredecessorGap; } get isValidateDependenciesRequired(): boolean { return this.settings.getIsValidateDependenciesRequired(); } private lockPredecessorToSuccessor: boolean = true; private updateOwnerInAutoParentMode(): void { this.settings.updateOwnerInAutoParentMode(); } constructor(settings: ValidationControllerSettings) { this.settings = settings; } checkStartDependencies(taskId: string, date: Date): ValidationError[] { const result = []; const task = this.viewModel.tasks.getItemById(taskId); const dependencies = this.viewModel.dependencies.items.filter(d => d.successorId === taskId); dependencies.forEach((dep) => { const predecessorTask = this.viewModel.tasks.getItemById(dep.predecessorId); if(dep.type === DependencyType.FS && predecessorTask.end > date || dep.type === DependencyType.SS && predecessorTask.start > date) result.push(new ValidationError(dep.internalId, true)); if(dep.type === DependencyType.FS && predecessorTask.end.valueOf() === task.start.valueOf() && date > predecessorTask.end || dep.type === DependencyType.SS && predecessorTask.start.valueOf() === task.start.valueOf() && date > predecessorTask.start) result.push(new ValidationError(dep.internalId)); }); return result; } checkEndDependencies(taskId: string, date: Date): ValidationError[] { const result = []; const task = this.viewModel.tasks.getItemById(taskId); const dependencies = this.viewModel.dependencies.items.filter(d => d.successorId === taskId); dependencies.forEach((dep) => { const predecessorTask = this.viewModel.tasks.getItemById(dep.predecessorId); if(dep.type === DependencyType.SF && predecessorTask.start > date || dep.type === DependencyType.FF && predecessorTask.end > date) result.push(new ValidationError(dep.internalId, true)); if((dep.type === DependencyType.SF && predecessorTask.start.valueOf() === task.end.valueOf() && date > predecessorTask.start) || (dep.type === DependencyType.FF && predecessorTask.end.valueOf() === task.end.valueOf() && date > predecessorTask.end)) result.push(new ValidationError(dep.internalId)); }); return result; } public moveEndDependTasks(predecessorTaskId: string, predecessorPreviousEndDate: Date, moveInitiatorId: string = null): void { const dependencies = this.viewModel.dependencies.items.filter(d => d.predecessorId === predecessorTaskId && !d.isStartDependency); const predecessorTask = this.viewModel.tasks.getItemById(predecessorTaskId); dependencies.forEach((dependency) => { const successorTask = this.viewModel.tasks.getItemById(dependency.successorId); const isMoveNotRequired = !successorTask || (moveInitiatorId && this.viewModel.checkParent(successorTask.internalId, moveInitiatorId)) || (successorTask.parentId === predecessorTask.id); if(isMoveNotRequired) return; const successorRangeBeforeMove = new DateRange(new Date(successorTask.start.getTime()), new Date(successorTask.end.getTime())); const validTaskRange = new DateRange(new Date(successorTask.start.getTime()), new Date(successorTask.end.getTime())); const delta = predecessorTask.end.getTime() - predecessorPreviousEndDate.getTime(); const predecessorEnd = this.enablePredecessorGap ? predecessorTask.end : predecessorPreviousEndDate; if(dependency.type === DependencyType.FS && (successorTask.start < predecessorEnd || (this.lockPredecessorToSuccessor && successorTask.start.getTime() === predecessorPreviousEndDate.getTime()))) { validTaskRange.start.setTime(predecessorTask.end.getTime()); validTaskRange.end.setTime(validTaskRange.start.getTime() + (successorTask.end.getTime() - successorTask.start.getTime())); this.correctMoving(successorTask.internalId, validTaskRange); } else if(dependency.type === DependencyType.FF && (successorTask.end < predecessorEnd || (this.lockPredecessorToSuccessor && successorTask.end.getTime() === predecessorPreviousEndDate.getTime()))) { validTaskRange.start.setTime(predecessorTask.end.getTime() - (successorTask.end.getTime() - successorTask.start.getTime())); validTaskRange.end.setTime(predecessorTask.end.getTime()); this.correctMoving(successorTask.internalId, validTaskRange); } else if(!this.enablePredecessorGap) { validTaskRange.start.setTime(successorTask.start.getTime() + delta); validTaskRange.end.setTime(successorTask.end.getTime() + delta); } if(!successorRangeBeforeMove.equal(validTaskRange)) { const data = { start: validTaskRange.start, end: validTaskRange.end }; this.history.addAndRedo(new UpdateTaskHistoryItem(this.modelManipulator, dependency.successorId, data)); this.moveRelatedTasks(dependency, successorRangeBeforeMove, successorTask, validTaskRange); } }); } public moveStartDependTasks(predecessorTaskId: string, predecessorPreviousStartDate: Date, moveInitiatorId: string = null): void { const dependencies = this.viewModel.dependencies.items.filter(d => d.predecessorId === predecessorTaskId && d.isStartDependency); const predecessorTask = this.viewModel.tasks.getItemById(predecessorTaskId); dependencies.forEach((dependency) => { const successorTask = this.viewModel.tasks.getItemById(dependency.successorId); const isMoveNotRequired = !successorTask || (moveInitiatorId && this.viewModel.checkParent(successorTask.internalId, moveInitiatorId)) || (successorTask.parentId === predecessorTask.id); if(isMoveNotRequired) return; const successorRangeBeforeMove = new DateRange(new Date(successorTask.start.getTime()), new Date(successorTask.end.getTime())); const validTaskRange = new DateRange(new Date(successorTask.start.getTime()), new Date(successorTask.end.getTime())); const delta = predecessorTask.start.getTime() - predecessorPreviousStartDate.getTime(); const predecessorStart = this.enablePredecessorGap ? predecessorTask.start : predecessorPreviousStartDate; if(dependency.type === DependencyType.SF && (successorTask.end < predecessorStart || (this.lockPredecessorToSuccessor && successorTask.end.getTime() === predecessorPreviousStartDate.getTime()))) { validTaskRange.start.setTime(predecessorTask.start.getTime() - (successorTask.end.getTime() - successorTask.start.getTime())); validTaskRange.end.setTime(predecessorTask.start.getTime()); this.correctMoving(successorTask.internalId, validTaskRange); } else if(dependency.type === DependencyType.SS && (successorTask.start < predecessorStart || (this.lockPredecessorToSuccessor && successorTask.start.getTime() === predecessorPreviousStartDate.getTime()))) { validTaskRange.start.setTime(predecessorTask.start.getTime()); validTaskRange.end.setTime(predecessorTask.start.getTime() + (successorTask.end.getTime() - successorTask.start.getTime())); this.correctMoving(successorTask.internalId, validTaskRange); } else if(!this.enablePredecessorGap) { validTaskRange.start.setTime(successorTask.start.getTime() + delta); validTaskRange.end.setTime(successorTask.end.getTime() + delta); } if(!successorRangeBeforeMove.equal(validTaskRange)) { const data = { start: validTaskRange.start, end: validTaskRange.end }; this.history.addAndRedo(new UpdateTaskHistoryItem(this.modelManipulator, dependency.successorId, data)); this.moveRelatedTasks(dependency, successorRangeBeforeMove, successorTask, validTaskRange); } }); } private moveRelatedTasks(dependency, successorRangeBeforeMove, successorTask, validTaskRange) { const delta = validTaskRange.start.getTime() - successorRangeBeforeMove.start.getTime(); this.correctParentsOnChildMoving(successorTask.internalId, delta); this.moveStartDependTasks(dependency.successorId, successorRangeBeforeMove.start); this.moveEndDependTasks(dependency.successorId, successorRangeBeforeMove.end); } public getCorrectDateRange(taskId: any, startDate: Date, endDate: Date): DateRange { const dateRange = new DateRange(new Date(startDate), new Date(endDate)); const validationErrors = [...this.checkStartDependencies(taskId, dateRange.start), ...this.checkEndDependencies(taskId, dateRange.end)]; const criticalErrors = validationErrors.filter(e => e.critical); criticalErrors.forEach(error => { const dependency = this.viewModel.dependencies.getItemById(error.dependencyId); const predecessorTask = this.viewModel.tasks.getItemById(dependency.predecessorId); if(dependency.type === DependencyType.FS) if(dateRange.start < predecessorTask.end) dateRange.start.setTime(predecessorTask.end.getTime()); if(dependency.type === DependencyType.SS) if(dateRange.start < predecessorTask.start) dateRange.start.setTime(predecessorTask.start.getTime()); if(dependency.type === DependencyType.FF) if(dateRange.end < predecessorTask.end) dateRange.end.setTime(predecessorTask.end.getTime()); if(dependency.type === DependencyType.SF) if(dateRange.end < predecessorTask.start) dateRange.end.setTime(predecessorTask.start.getTime()); }); return dateRange; } private correctMoving(taskId, dateRange: DateRange): DateRange { const deltaDate = dateRange.end.getTime() - dateRange.start.getTime(); const validationErrors = [...this.checkStartDependencies(taskId, dateRange.start), ...this.checkEndDependencies(taskId, dateRange.end)]; const criticalErrors = validationErrors.filter(e => e.critical); criticalErrors.forEach(error => { const dependency = this.viewModel.dependencies.getItemById(error.dependencyId); const predecessorTask = this.viewModel.tasks.getItemById(dependency.predecessorId); if(dependency.type === DependencyType.FS) if(dateRange.start < predecessorTask.end) { dateRange.start.setTime(predecessorTask.end.getTime()); dateRange.end.setTime(dateRange.start.getTime() + deltaDate); } if(dependency.type === DependencyType.SS) if(dateRange.start < predecessorTask.start) { dateRange.start.setTime(predecessorTask.start.getTime()); dateRange.end.setTime(dateRange.start.getTime() + deltaDate); } if(dependency.type === DependencyType.FF) if(dateRange.end < predecessorTask.end) { dateRange.end.setTime(predecessorTask.end.getTime()); dateRange.start.setTime(dateRange.end.getTime() - deltaDate); } if(dependency.type === DependencyType.SF) if(dateRange.end < predecessorTask.start) { dateRange.end.setTime(predecessorTask.start.getTime()); dateRange.start.setTime(dateRange.end.getTime() - deltaDate); } }); return dateRange; } public recalculateParents(child: ViewVisualModelItem, calcStepCallback: (data: any) => any): void { let parent = child && child.parent; while(parent && parent.task) { const children = parent.children; let start = this.range.end; let end = this.range.start; let progress = 0; let totalDuration = 0; const data = { id: parent.task.internalId }; for(let i = 0; i < children.length; i++) { const childTask = children[i].task; if(!childTask.isValid()) continue; start = DateTimeUtils.getMinDate(start, childTask.start); end = DateTimeUtils.getMaxDate(end, childTask.end); const duration = childTask.getDuration(); progress += childTask.progress * duration; totalDuration += duration; } if(!DateTimeUtils.areDatesEqual(parent.task.start, start)) data["start"] = start; if(!DateTimeUtils.areDatesEqual(parent.task.end, end)) data["end"] = end; data["oldStart"] = parent.task.start; data["oldEnd"] = parent.task.end; progress = totalDuration > 0 ? Math.round(progress / totalDuration) : 0; if(progress !== parent.task.progress) data["progress"] = progress; calcStepCallback(data); parent = parent.parent; } } public updateParentsRangeByChild(taskId: string): void { this.recalculateParents(this.viewModel.findItem(taskId), (data: any) => { if(!isDefined(data.id)) return; const history = this.history; const manipulator = this.modelManipulator; if(isDefined(data.start)) { history.addAndRedo(new UpdateTaskHistoryItem(manipulator, data.id, { start: data.start })); this.moveStartDependTasks(data.id, data.oldStart); } if(isDefined(data.end)) { history.addAndRedo(new UpdateTaskHistoryItem(manipulator, data.id, { end: data.end })); this.moveEndDependTasks(data.id, data.oldEnd); } if(isDefined(data.progress)) history.addAndRedo(new UpdateTaskHistoryItem(manipulator, data.id, { progress: data.progress })); }); } public updateChildRangeByParent(parentId: string, delta: number, changedTasks: Array<any>): void { const item = this.viewModel.findItem(parentId); if(!item || item.children.length === 0) return; const children = item.children; for(let i = 0; i < children.length; i++) { const childTask = children[i].task; const newStart = new Date(childTask.start.getTime() + delta); const taskPeriod = DateUtils.getRangeMSPeriod(childTask.start, childTask.end); const newEnd = DateUtils.getDSTCorrectedTaskEnd(newStart, taskPeriod); changedTasks.push({ id: childTask.internalId, start: childTask.start, end: childTask.end }); this.history.addAndRedo(new UpdateTaskHistoryItem(this.modelManipulator, childTask.internalId, { start: newStart, end: newEnd })); this.updateChildRangeByParent(childTask.internalId, delta, changedTasks); } } public updateParentsIfRequired(childId: string): void { if(this._parentAutoCalc) { this.updateParentsRangeByChild(childId); this.updateOwnerInAutoParentMode(); } } public correctParentsOnChildMoving(taskId: string, delta: number): void { if(this._parentAutoCalc && delta !== 0) { this.updateParentsRangeByChild(taskId); const changedTasks = []; this.updateChildRangeByParent(taskId, delta, changedTasks); if(this.isValidateDependenciesRequired) changedTasks.forEach(i => { this.moveStartDependTasks(i.id, i.start, taskId); this.moveEndDependTasks(i.id, i.end, taskId); }); this.updateOwnerInAutoParentMode(); } } public canCreateDependency(predecessorId: string, successorId: string): boolean { return this.viewModel.canCreateDependency(predecessorId, successorId); } }