devexpress-gantt
Version:
DevExpress Gantt Control
314 lines (294 loc) • 18 kB
text/typescript
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);
}
}