literyzjs-project/src/project/project.js
2024-09-02 21:32:54 +01:00

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;