360 lines
7.7 KiB
JavaScript
360 lines
7.7 KiB
JavaScript
import ProjectTaskModel from './project-task-model.js';
|
|
import ProjectNodeModel from './project-node-model.js';
|
|
|
|
import '../references/extensions.dist.js';
|
|
|
|
|
|
class Project {
|
|
constructor(options) {
|
|
const a = this;
|
|
const _options = Object.assign(a.DefaultOptions, options);
|
|
|
|
a.Debug = false;
|
|
a.Project = _options;
|
|
a.Tasks = [];
|
|
}
|
|
|
|
|
|
get StartDate() {
|
|
const a = this;
|
|
|
|
return new Date(a.Project.StartDate);
|
|
}
|
|
|
|
get FinishDate() {
|
|
const a = this;
|
|
|
|
let result = new Date(a.Project.StartDate);
|
|
|
|
a.ExportTasks().forEach(e => {
|
|
if (e.FinishDate == null) {
|
|
return;
|
|
}
|
|
|
|
result = Date.max(result, e.FinishDate);
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
get Duration() {
|
|
const a = this;
|
|
|
|
return Date.diffDays(a.StartDate, a.FinishDate);
|
|
}
|
|
|
|
get DefaultOptions() {
|
|
return {
|
|
Name: "",
|
|
Description: "",
|
|
StartDate: Date.today(),
|
|
Tag: null,
|
|
StartOfWeek: 1, // Monday
|
|
WorkHours: [0, 7.5, 7.5, 7.5, 7.5, 7.5, 0] // 0 = Sunday
|
|
};
|
|
}
|
|
|
|
|
|
AddTask(task) {
|
|
const a = this;
|
|
const newTask = new ProjectTaskModel(task);
|
|
const newTaskNode = new ProjectNodeModel(newTask);
|
|
|
|
if ((newTaskNode.PredecessorTaskID == null) && (newTaskNode.CollatedTaskID == null)) {
|
|
a.Tasks.push(newTaskNode);
|
|
} else if (newTaskNode.PredecessorTaskID != null) {
|
|
let node = a.FindTask(newTaskNode.PredecessorTaskID);
|
|
|
|
if (node != null) {
|
|
node.Tasks.push(newTaskNode);
|
|
} else {
|
|
a.#log("Task not found (" + newTaskNode.PredecessorTaskID + ")");
|
|
}
|
|
} else if (newTaskNode.CollatedTaskID != null) {
|
|
let node = a.FindTask(newTaskNode.CollatedTaskID);
|
|
|
|
if (node != null) {
|
|
node.Tasks.push(newTaskNode);
|
|
} else {
|
|
a.#log("Task not found (" + newTaskNode.CollatedTaskID + ")");
|
|
}
|
|
} else {
|
|
a.#log("Task not found (" + newTaskNode.ID + ")");
|
|
}
|
|
}
|
|
|
|
Clear() {
|
|
const a = this;
|
|
|
|
a.Tasks = [];
|
|
}
|
|
|
|
ExportTasks() {
|
|
const a = this;
|
|
|
|
let result = a.Tasks.copy().flatten("Tasks");
|
|
for (var i=0; i<result.length; i++) {
|
|
result[i].Order = (i + 1);
|
|
|
|
delete result[i].Tasks;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
FindTask(id) {
|
|
const a = this;
|
|
|
|
let result = null;
|
|
|
|
a.Tasks.forEachTree("Tasks", function(e) {
|
|
if (e.ID == id) {
|
|
result = e;
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Get node type.
|
|
* @returns {int} enum
|
|
*
|
|
* 0 = orphan
|
|
* 1 = task no-parent/root
|
|
* 2 = task no-parent/root, collated/group
|
|
* 3 = task with parent
|
|
* 4 = task with parent, collated/group
|
|
* 5 = sub-task
|
|
* 6 = sub-task, collated/group
|
|
*
|
|
*/
|
|
GetNodeType(node) {
|
|
if (node == null) {
|
|
// 0 = orphan
|
|
return 0;
|
|
}
|
|
|
|
if ((node.PredecessorTaskID == null) && (node.CollatedTaskID == null) && (node.IsCollated == false)) {
|
|
// 1 = task no-parent/root
|
|
return 1;
|
|
}
|
|
|
|
if ((node.PredecessorTaskID == null) && (node.CollatedTaskID == null) && (node.IsCollated == true)) {
|
|
// 2 = task no-parent/root, collated/group
|
|
return 2;
|
|
}
|
|
|
|
if ((node.PredecessorTaskID != null) && (node.CollatedTaskID == null) && (node.IsCollated == false)) {
|
|
// 3 = task with parent
|
|
return 3;
|
|
}
|
|
|
|
if ((node.PredecessorTaskID != null) && (node.CollatedTaskID == null) && (node.IsCollated == true)) {
|
|
// 4 = task with parent, collated/group
|
|
return 4;
|
|
}
|
|
|
|
if ((node.CollatedTaskID != null) && (node.IsCollated == false)) {
|
|
// 5 = sub-task
|
|
return 5;
|
|
}
|
|
|
|
if ((node.CollatedTaskID != null) && (node.IsCollated == true)) {
|
|
// 6 = sub-task, collated/group
|
|
return 6;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
Invalidate() {
|
|
const a = this;
|
|
let isSuccess = false;
|
|
|
|
if (!a.Recalculate()) {
|
|
isSuccess = false;
|
|
}
|
|
|
|
return isSuccess;
|
|
}
|
|
|
|
Recalculate() {
|
|
const a = this;
|
|
let isSuccess = false;
|
|
|
|
a.Sort();
|
|
|
|
// Get flat references
|
|
let allTasks = a.Tasks.flatten("Tasks");
|
|
|
|
// Reset calculated values
|
|
for (var i=0; i<allTasks.length; i++) {
|
|
allTasks[i].Order = (i + 1);
|
|
allTasks[i].StartDate = null;
|
|
allTasks[i].FinishDate = null;
|
|
allTasks[i].CalcWorkHours = null;
|
|
}
|
|
|
|
for (var x=0; x<16; x++) {
|
|
let pendingCount = allTasks.countMany(
|
|
{ propName: "StartDate", value: null },
|
|
{ propName: "FinishDate", value: null }
|
|
);
|
|
|
|
// Done
|
|
if (pendingCount <= 0) {
|
|
isSuccess = true;
|
|
break;
|
|
}
|
|
|
|
a.#log("Round " + (x + 1));
|
|
|
|
a.#recalculateTask(allTasks, a.Tasks);
|
|
}
|
|
|
|
// Calculate work-hours
|
|
a.#recalculateWorkHours(allTasks);
|
|
|
|
return isSuccess;
|
|
}
|
|
|
|
Sort() {
|
|
const a = this;
|
|
|
|
a.#sortNode(a);
|
|
}
|
|
|
|
#log(message) {
|
|
const a = this;
|
|
|
|
if (a.Debug) {
|
|
console.log(message);
|
|
}
|
|
}
|
|
|
|
#recalculateWorkHours(array) {
|
|
const a = this;
|
|
|
|
for (var i=0; i<array.length; i++) {
|
|
if (array[i].StartDate == null) {
|
|
continue;
|
|
}
|
|
|
|
if (array[i].FinishDate == null) {
|
|
continue;
|
|
}
|
|
|
|
if (array[i].Duration <= 0) {
|
|
continue;
|
|
}
|
|
|
|
let workHours = 0;
|
|
let date = new Date(array[i].StartDate);
|
|
for (let x=0; x<array[i].Duration; x++) {
|
|
const d = date.getDay();
|
|
|
|
workHours += a.Project.WorkHours[d];
|
|
}
|
|
|
|
array[i].CalcWorkHours = workHours;
|
|
}
|
|
}
|
|
|
|
#recalculateTask(allTasks, tasks) {
|
|
const a = this;
|
|
|
|
for (let i=0; i<tasks.length; i++) {
|
|
const task = tasks[i];
|
|
|
|
a.#log(task.Name);
|
|
|
|
if (task.PredecessorTaskID != null) {
|
|
const parentTask = allTasks.first("ID", task.PredecessorTaskID);
|
|
if (parentTask != null) {
|
|
if (parentTask.FinishDate != null) {
|
|
task.StartDate = Date.addDays(parentTask.FinishDate, task.StartDelay);
|
|
|
|
a.#log("> Set StartDate from Predecessor");
|
|
}
|
|
}
|
|
} else if (task.CollatedTaskID != null) {
|
|
const groupTask = allTasks.first("ID", task.CollatedTaskID);
|
|
if (groupTask != null) {
|
|
if (groupTask.StartDate != null) {
|
|
task.StartDate = Date.addDays(groupTask.StartDate, task.StartDelay);
|
|
|
|
a.#log("> Set StartDate from Group");
|
|
}
|
|
}
|
|
} else {
|
|
task.StartDate = Date.addDays(a.Project.StartDate, task.StartDelay);
|
|
|
|
a.#log("> Set StartDate from Project");
|
|
}
|
|
|
|
// if (task.IsCollated) {
|
|
if (task.Tasks.length > 0) {
|
|
a.#recalculateTask(allTasks, task.Tasks);
|
|
|
|
a.#log("> Calc inner");
|
|
}
|
|
// }
|
|
|
|
if (task.StartDate != null) {
|
|
if (task.IsCollated) {
|
|
|
|
const childTasks = task.Tasks.select("CollatedTaskID", task.ID);
|
|
if (childTasks.length <= 0) {
|
|
task.FinishDate = task.StartDate;
|
|
task.Duration = 0;
|
|
|
|
a.#log("> Set FinishDate from Group (0)");
|
|
} else {
|
|
|
|
if (!childTasks.any("FinishDate", null)) {
|
|
task.FinishDate = childTasks.orderByDesc("FinishDate")[0].FinishDate;
|
|
task.Duration = Date.diffDays(task.StartDate, task.FinishDate);
|
|
|
|
a.#log("> Set FinishDate from Group");
|
|
}
|
|
}
|
|
|
|
} else {
|
|
task.FinishDate = Date.addDays(task.StartDate, task.Duration);
|
|
|
|
a.#log("> Set FinishDate");
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
#sortNode(node) {
|
|
const a = this;
|
|
|
|
if (Array.isEmpty(node.Tasks)) {
|
|
return;
|
|
}
|
|
|
|
node.Tasks.orderBy("StartDelay");
|
|
|
|
const childDependentTasks = node.Tasks.notSelect("PredecessorTaskID", null);
|
|
if (childDependentTasks.length > 0) {
|
|
node.Tasks.removeRange(childDependentTasks);
|
|
node.Tasks.addRange(childDependentTasks);
|
|
}
|
|
|
|
node.Tasks.forEach(e => {
|
|
a.#sortNode(e);
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
|
|
export default Project; |