Files
ERP/sophal/js/view/view.gantt.js

836 lines
23 KiB
JavaScript

/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// localization
(function () {
/* global gantt: true */
"use strict";
$(function () {
$('<script>')
.attr('type', 'text/javascript')
.attr('src', 'https://export.dhtmlx.com/gantt/api.js').appendTo('head');
});
var regional = {
month_full: [
_t('January'),
_t('February'),
_t('March'),
_t('April'),
_t('May'),
_t('June'),
_t('July'),
_t('August'),
_t('September'),
_t('October'),
_t('November'),
_t('December')],
month_short: [
_t('Jan'),
_t('Feb'),
_t('Mar'),
_t('Apr'),
_t('May'),
_t('Jun'),
_t('Jul'),
_t('Aug'),
_t('Sep'),
_t('Oct'),
_t('Nov'),
_t('Dec')],
day_full: [
_t('Sunday'),
_t('Monday'),
_t('Tuesday'),
_t('Wednesday'),
_t('Thursday'),
_t('Friday'),
_t('Saturday')],
day_short : [_t('Sun'), _t('Mon'), _t('Tue'), _t('Wed'), _t('Thu'), _t('Fri'), _t('Sat')]
};
gantt.locale = {
date: regional,
labels:{
new_task: _t("New task"),
icon_save: _t("Save"),
icon_cancel: _t("Cancel"),
icon_details: _t("Details"),
icon_edit: _t("Edit"),
icon_delete: _t("Delete"),
confirm_closing:"",// Your changes will be lost, are your sure ?
confirm_deleting: _t("Task will be deleted permanently, are you sure?"),
section_description: _t("Description"),
section_time: _t("Time period"),
section_type: _t("Type"),
/* grid columns */
column_text : _t("Task name"),
column_start_date : _t("Start time"),
column_duration : _t("Duration"),
column_add : "",
/* link confirmation */
link: _t("Link"),
confirm_link_deleting: _t("will be deleted"),
link_start: " " + _t("(start)"),
link_end: " " + _t("(end)"),
type_task: _t("Task"),
type_project: _t("Project"),
type_milestone: _t("Milestone"),
minutes: _t("Minutes"),
hours: _t("Hours"),
days: _t("Days"),
weeks: _t("Week"),
months: _t("Months"),
years: _t("Years")
}
};
var ui = angular.module('axelor.ui');
ui.controller('GanttViewCtrl', GanttViewCtrl);
GanttViewCtrl.$inject = ['$scope', '$element'];
function GanttViewCtrl($scope, $element) {
ui.DSViewCtrl('gantt', $scope, $element);
var ds = $scope._dataSource;
var view = $scope._views.gantt;
var initialized = false;
$scope.onShow = function(viewPromise) {
if (initialized) {
return $scope.refresh();
}
viewPromise.then(function(){
var schema = $scope.schema;
initialized = true;
$scope._viewResolver.resolve(schema, $element);
$scope.updateRoute();
});
};
$scope.select = function() {
};
$scope.fetchItems = function(callback) {
var schema = $scope.schema;
var searchFields = _.pluck(this.fields, "name");
searchFields.push(schema.taskStart);
var optionalFields = [schema.taskProgress,
schema.taskEnd,
schema.taskDuration,
schema.taskParent,
schema.taskSequence,
schema.taskProgress,
schema.finishToStart,
schema.startToStart,
schema.finishToFinish,
schema.startToFinish,
schema.taskUser
];
_.each(optionalFields,function(optField){
if(optField){
searchFields.push(optField);
}
});
var opts = {
fields: searchFields,
filter: false,
domain: this._domain,
store: false
};
ds.search(opts).success(function(records) {
callback(records);
});
};
$scope.getContext = function() {
return _.extend({}, $scope._context);
};
$scope.getRouteOptions = function() {
return {
mode: 'gantt',
args: []
};
};
$scope.setRouteOptions = function(options) {
var opts = options || {};
if (opts.mode === "gantt") {
return $scope.updateRoute();
}
var params = $scope._viewParams;
if (params.viewType !== "calendar") {
return $scope.show();
}
};
$scope.doSave = function(task, callback){
var record = _.clone(task.record);
return ds.save(record).success(function(res){
callback(task, res);
});
};
$scope.doRemove = function(id, task){
var record = _.clone(task.record);
return ds.remove(record).success(function(res){
return true;
});
};
}
ui.directive('uiViewGantt', ['ViewService', 'ActionService', function(ViewService, ActionService) {
function link(scope, element, attrs, controller) {
var main = element.children(".gantt-main");
var schema = scope.schema;
var fields = scope.fields;
var fieldNames = _.pluck(schema.items, "name");
var firstField = fields[fieldNames[0]];
var mode = schema.mode || "week";
var editor = null;
ganttInit();
function byId(list, id) {
for (var i = 0; i < list.length; i++) {
if (list[i].key == id)
return list[i].label || "";
}
return "";
}
function setScaleConfig(value){
switch (value) {
case "day":
gantt.config.scale_unit = "day";
gantt.config.date_scale = "%d/%m/%Y";
gantt.config.subscales = [{unit:"hour", step:1, date:"%H:%i"}];
gantt.templates.date_scale = null;
gantt.config.min_column_width = 50;
break;
case "week":
var weekScaleTemplate = function(date){
var dateToStr = gantt.date.date_to_str("%d/%m/%Y");
var endDate = gantt.date.add(gantt.date.add(date, 1, "week"), -1, "day");
return gantt.date.date_to_str("%W")(date) + "(" + dateToStr(date) + " - " + dateToStr(endDate) + ")";
};
gantt.config.scale_unit = "week";
gantt.templates.date_scale = weekScaleTemplate;
gantt.config.min_column_width = 50;
gantt.config.subscales = [
{unit:"day", step:1, date:"%D %d" }];
break;
case "month":
gantt.config.scale_unit = "month";
gantt.config.date_scale = "%F, %Y";
gantt.config.subscales = [
{unit:"week", step:1, date:"%W" }
];
gantt.templates.date_scale = null;
gantt.config.min_column_width = 50;
break;
case "year":
gantt.config.scale_unit = "year";
gantt.config.date_scale = "%Y";
gantt.templates.date_scale = null;
gantt.config.min_column_width = 100;
gantt.config.subscales = [
{unit:"month", step:1, date:"%M" }
];
break;
}
}
function getGanttColumns() {
var colHeader = '<div class="gantt_grid_head_cell gantt_grid_head_add" onclick="gantt.createTask()"></div>';
var colContent = function(task){
return '<i class="fa gantt_button_grid gantt_grid_add fa-plus" onclick="gantt.createTask(null, '+task.id+')"></i>'+
'<i class="fa gantt_button_grid gantt_grid_delete fa-times" onclick="gantt.confirm({ ' +
'title: gantt.locale.labels.confirm_deleting_title,'+
'text: gantt.locale.labels.confirm_deleting,'+
'callback: function(res){ '+
' if(res)'+
' gantt.deleteTask('+task.id+');'+
'}})"></i>';
};
var columns = [];
if (schema.taskUser) {
columns.push({name: "users", label: fields[schema.taskUser].title, align: "center", template: function (item) {
return byId(gantt.serverList("users"), item.user_id);
}});
}
var isTree = true;
_.each(fieldNames, function(fname){
var field = fields[fname];
if (columns.length == 0) {
columns.push({ name:"text", label:field.title, tree:isTree,
template: function(item){
if(moment(item[fname], moment.ISO_8601, true).isValid()) {
return moment(item[fname], moment.ISO_8601, true).format("MM/DD/YYYY h:mm:ss");
}
return item.text;
}});
}
else {
columns.push({ name:field.name, label:field.title, tree:isTree,
template: function(item){
if (!item.label) {
if(moment(item[fname], moment.ISO_8601, true).isValid()) {
return moment(item[fname], moment.ISO_8601, true).format("MM/DD/YYYY h:mm:ss");
}
return item[fname];
}
return "";
}
});
}
isTree = false;
});
columns.push({ name:"buttons", label:colHeader, width:30, template:colContent });
return columns;
}
function setChildTaskDisplay() {
function createBox(sizes, class_name){
var box = document.createElement('div');
box.style.cssText = [
"height:" + sizes.height + "px",
"line-height:" + sizes.height + "px",
"width:" + sizes.width + "px",
"top:" + sizes.top + 'px',
"left:" + sizes.left + "px",
"position:absolute"
].join(";");
box.className = class_name;
return box;
}
gantt.templates.grid_row_class = gantt.templates.task_class=function(start, end, task){
var css = [];
if(gantt.hasChild(task.id)){
css.push("task-parent");
}
if (!task.$open && gantt.hasChild(task.id)) {
css.push("task-collapsed");
}
if (task.$virtual || task.type == gantt.config.types.project)
css.push("summary-bar");
if(task.user_id){
css.push("gantt_resource_task gantt_resource_" + task.user_id);
}
return css.join(" ");
};
}
function ganttInit(){
gantt = main.dhx_gantt();
setScaleConfig("week");
gantt.templates.leftside_text = function(start, end, task){
if (!task.progress){
return "";
}
return "<span style='text-align:left;'>"+Math.round(task.progress*100)+ "% </span>";
};
gantt.config.step = 1;
gantt.config.duration_unit = "hour";
gantt.config.duration_step = 1;
gantt.config.scale_height = 75;
gantt.config.grid_width = 400;
gantt.config.fit_tasks = true;
gantt.config.columns = getGanttColumns();
gantt._onTaskIdChange = null;
gantt._onLinkIdChange = null;
gantt.config.autosize = "x";
gantt.config.grid_resize = true;
gantt.config.order_branch = true;
gantt.config.date_grid = "%d/%m/%Y %H %i";
gantt.serverList("users", []);
gantt.eachSuccessor = function(callback, root){
if(!this.isTaskExists(root))
return;
// remember tasks we've already iterated in order to avoid infinite loops
var traversedTasks = arguments[2] || {};
if(traversedTasks[root])
return;
traversedTasks[root] = true;
var rootTask = this.getTask(root);
var links = rootTask.$source;
if(links){
for(var i=0; i < links.length; i++){
var link = this.getLink(links[i]);
if(this.isTaskExists(link.target)){
callback.call(this, this.getTask(link.target));
// iterate the whole branch, not only first-level dependencies
this.eachSuccessor(callback, link.target, traversedTasks);
}
}
}
};
gantt.templates.task_class=function(start, end, task){
if(task.$virtual)
return "summary-bar";
};
ganttAttachEvents();
setChildTaskDisplay();
fetchRecords();
}
function ganttAttachEvents(){
gantt.templates.rightside_text = function(start, end, task){
return byId(gantt.serverList("users"), task.user_id);
};
if (schema.taskUser) {
gantt.attachEvent("onParse", function(){
var styleId = "dynamicGanttStyles";
var element = document.getElementById(styleId);
if(!element){
element = document.createElement("style");
element.id = styleId;
document.querySelector("head").appendChild(element);
}
var html = [".gantt_cell:nth-child(1) .gantt_tree_content{" +
" border-radius: 16px;" +
" width: 100%;" +
" height: 70%;" +
" margin: 5% 0;" +
" line-height: 230%;}"];
var resources = gantt.serverList("users");
resources.forEach(function(r){
html.push(".gantt_task_line.gantt_resource_" + r.key + "{" +
"background-color:"+r.backgroundColor+"; " +
"color:"+r.textColor+";" +
"}");
html.push(".gantt_row.gantt_resource_" + r.key + " .gantt_cell:nth-child(1) .gantt_tree_content{" +
"background-color:"+r.backgroundColor+"; " +
"color:"+r.textColor+";" +
"}");
});
element.innerHTML = html.join("");
});
}
gantt.attachEvent("onAfterTaskAdd", updateRecord);
gantt.attachEvent("onAfterTaskUpdate", updateRecord);
gantt.attachEvent("onAfterTaskDelete", scope.doRemove);
gantt.attachEvent("onAfterLinkAdd", updateLink);
gantt.attachEvent("onAfterLinkUpdate", updateLink);
gantt.attachEvent("onAfterLinkDelete", deleteLink);
gantt.attachEvent("onTaskCreated",function(task){
scope.showEditor(task, true);
return false;
});
gantt.attachEvent("onBeforeLightbox", function(id) {
var task = gantt.getTask(id);
scope.showEditor(task, false);
return false;
});
var diff = 0;
gantt.attachEvent("onBeforeTaskChanged", function(id, mode, originalTask){
var modes = gantt.config.drag_mode;
if(mode == modes.move ){
var modifiedTask = gantt.getTask(id);
diff = modifiedTask.start_date - originalTask.start_date;
}
return true;
});
//rounds positions of the child items to scale
gantt.attachEvent("onAfterTaskDrag", function(id, mode, e){
var modes = gantt.config.drag_mode;
if(mode == modes.move ){
gantt.eachSuccessor(function(child){
child.start_date = gantt.roundDate(new Date(child.start_date.valueOf() + diff));
child.end_date = gantt.calculateEndDate(child.start_date, child.duration);
gantt.updateTask(child.id);
},id );
}
});
}
function fetchRecords() {
scope.fetchItems(function(records) {
var data = [];
var links = [];
_.each(records, function(rec) {
addData(data, rec);
addLinks(links, rec);
});
gantt.parse({ "data":data, "links":links });
});
}
function updateRecordItem(id,link,toRemove){
var linkMap = {
"0":"finishToStart",
"1":"startToStart",
"2":"finishToFinish",
"3":"startToFinish"
};
var linkField = schema[linkMap[link.type]];
var task = gantt.getTask(link.target);
var record = task.record;
if(record && linkField){
var endRecord = gantt.getTask(link.source).record;
if(endRecord){
var recordList = record[linkField];
recordList = recordList.filter(function(item, idx) {
return item.id != endRecord.id;
});
if(!toRemove){
recordList.push(endRecord);
}
record[linkField] = recordList;
task.record = record;
scope.doSave(task, updateTaskRecord);
}
}
}
function updateLink(id,link){
updateRecordItem(id, link, false);
}
function updateTaskRecord(task, rec){
task.record = rec;
}
function deleteLink(id, link){
updateRecordItem(id, link, true);
}
function updateRecord(id, item){
var record = item.record;
if(!record){ record = {}; }
var duration = item.duration || 1;
record[schema.taskStart] = item.start_date.toJSON();
record[firstField.name] = item.text;
if(schema.taskProgress){
record[schema.taskProgress] = item.progress*100;
}
if(schema.taskSequence){
record[schema.taskSequence] = item.order;
}
if(schema.taskDuration){
record[schema.taskDuration] = duration;
}
if(schema.taskEnd){
record[schema.taskEnd] = item.end_date.toJSON();
}
if(schema.taskParent && item.parent && !record[schema.taskParent]){
var parentTask = gantt.getTask(item.parent);
var parentRecord = parentTask.record;
if(parentRecord){
record[schema.taskParent] = parentRecord;
}
}
return scope.doSave(item, updateTaskRecord);
}
function addData(data, rec){
if(rec[schema.taskStart]){
var dict = {
id:rec.id,
open:true,
isNew:true
};
dict = updateData(dict, rec);
dict.isNew = false;
if(dict.start_date){
data.push(dict);
}
}
}
function addLinkDict(links, targetRecordId, sourceRecords, linkType){
_.each(sourceRecords, function(sourceRec){
links.push({
id:targetRecordId+"-"+sourceRec.id,
target:targetRecordId,
source:sourceRec.id,
type:linkType
});
});
}
function addLinks(links,record){
var linkMap = {
"finishToStart":"0",
"startToStart":"1",
"finishToFinish":"2",
"startToFinish":"3"
};
_.each(_.keys(linkMap), function(key) {
if(schema[key]){
addLinkDict(links, record.id, record[schema[key]], linkMap[key]);
}
});
}
function updateTask(task, rec){
task = updateData(task, rec);
if(!task.isNew){
gantt.refreshTask(task.id);
}
task.isNew = false;
return task;
}
function updateData(task, rec){
task.record = rec;
var name = firstField.targetName ? rec[firstField.targetName] : rec[firstField.name];
task.text = "";
if(name){
task.text = name;
}
_.each(fields,function(field){
var val = rec[field.name];
if(_.isObject(val) && field.targetName){
val = val[field.targetName];
}
task[field.name] = val || "";
});
var endDate = null;
if(schema.taskEnd && rec[schema.taskEnd]){
endDate = moment(rec[schema.taskEnd]);
task.end_date = endDate.toDate();
if(task.isNew){
task.end_date = endDate.format("DD-MM-YYYY HH:mm:SS");
}
}
var startDate = moment(rec[schema.taskStart]);
if(task.isNew){
task.start_date = startDate.format("DD-MM-YYYY HH:mm:SS");
}
else{
task.start_date = startDate.toDate();
}
if(schema.taskDuration && rec[schema.taskDuration]){
task.duration = rec[schema.taskDuration];
}
else if(endDate){
task.duration = gantt.calculateDuration(startDate.toDate(), endDate);
}
else{
task.duration = "1";
}
if(!endDate){
task.end_date = gantt.calculateEndDate(startDate.toDate(), task.duration);
}
if(schema.taskProgress){
task.progress = rec[schema.taskProgress]/100;
}
if(schema.taskParent){
if(rec[schema.taskParent] && rec[schema.taskParent].id != task.id){
task.parent = rec[schema.taskParent].id;
}
else{
task.parent = 0;
}
}
if(schema.taskSequence){
task.sortorder = rec[schema.taskSequence];
}
if(schema.taskUser && rec[schema.taskUser]) {
task.user_id = rec[schema.taskUser].id;
if(!byId(gantt.serverList("users"), task.user_id)) {
gantt.serverList("users").push({key:task.user_id,
label:rec[schema.taskUser][fields[schema.taskUser].targetName],
backgroundColor: get_random_color(),
textColor:"#FFF"
});
}
}
return task;
}
function get_random_color() {
function c() {
var hex = Math.floor(Math.random()*256).toString(16);
return ("0"+String(hex)).substr(-2); // pad with zero
}
return "#"+c()+c()+c();
}
scope.onMode = function(name) {
setScaleConfig(name);
mode = name;
gantt.render();
};
scope.isMode = function(name) {
return mode === name;
};
scope.onRefresh = function () {
gantt.clearAll();
fetchRecords();
};
scope.onPrint = function () {
gantt.exportToPDF({
name: "Gantt.pdf",
callback: function(result){
window.open(result.url , '_self');
}});
};
scope.$on('$destroy', function() {
gantt.clearAll();
gantt.detachAllEvents();
});
scope.showEditor = function(task, isNew) {
var record = _.extend({}, task.record);
if (!editor) {
editor = ViewService.compile('<div ui-editor-popup></div>')(scope.$new());
editor.data('$target', element);
}
var popup = editor.isolateScope();
popup.setEditable(true);
if(isNew && schema.taskParent && task.parent && !record[schema.taskParent]){
var parentTask = gantt.getTask(task.parent);
var parentRecord = parentTask.record;
if(parentRecord){
record[schema.taskParent] = parentRecord;
}
}
popup.show(record, function(result) {
task.isNew = isNew;
task = updateTask(task, result);
if(isNew){
gantt.addTask(task);
}
else {
gantt.updateTask(task.id);
}
});
if (!record || !record.id) {
popup.waitForActions(function() {
popup.$broadcast("on:new");
});
}
};
}
return {
link:function(scope, element, attrs, controller) {
scope._viewPromise.then(function(){
link(scope, element, attrs, controller);
});
},
replace:true,
template:
'<div>'+
'<div class="gantt-main"></div>'+
'</div>'
};
}]);
})();