71dba5da09354187ab9fbc35a2954491cfcfa011
[odoo/odoo.git] / addons / web_gantt / static / src / js / gantt.js
1 /*---------------------------------------------------------
2  * OpenERP web_gantt
3  *---------------------------------------------------------*/
4 openerp.web_gantt = function (openerp) {
5 var _t = openerp.web._t;
6 var QWeb = openerp.web.qweb;
7 openerp.web.views.add('gantt', 'openerp.web_gantt.GanttView');
8 openerp.web_gantt.GanttView = openerp.web.View.extend({
9
10     init: function(parent, dataset, view_id) {
11         this._super(parent);
12         this.view_manager = parent || new openerp.web.NullViewManager();
13         this.dataset = dataset;
14         this.model = dataset.model;
15         this.view_id = view_id;
16         this.domain = this.dataset.domain || [];
17         this.context = this.dataset.context || {};
18         this.has_been_loaded = $.Deferred();
19     },
20
21     start: function() {
22         this._super();
23         return this.rpc("/web/view/load", {"model": this.model, "view_id": this.view_id, "view_type": "gantt"}, this.on_loaded);
24     },
25
26     on_loaded: function(data) {
27
28         this.fields_view = data,
29         this.name =  this.fields_view.arch.attrs.string,
30         this.view_id = this.fields_view.view_id,
31         this.fields = this.fields_view.fields;
32         
33         this.date_start = this.fields_view.arch.attrs.date_start,
34         this.date_delay = this.fields_view.arch.attrs.date_delay,
35         this.date_stop = this.fields_view.arch.attrs.date_stop,
36         this.day_length = this.fields_view.arch.attrs.day_length || 8;
37
38         this.color_field = this.fields_view.arch.attrs.color,
39         this.colors = this.fields_view.arch.attrs.colors;
40         
41         if (this.fields_view.arch.children.length) {
42             var level = this.fields_view.arch.children[0];
43             this.parent = level.attrs.link, this.text = level.children.length ? level.children[0].attrs.name : level.attrs.name;
44         } else {
45             this.text = 'name';
46         }
47         
48         if (!this.date_start) {
49             return self.do_warn(_t("date_start is not defined "))
50         }
51         
52         this.$element.html(QWeb.render("GanttView", {'height': $('.oe-application-container').height(), 'width': $('.oe-application-container').width()}));
53         this.has_been_loaded.resolve();
54     },
55
56     init_gantt_view: function() {
57
58         ganttChartControl = this.ganttChartControl = new GanttChart(this.day_length);
59         ganttChartControl.setImagePath("/web_gantt/static/lib/dhtmlxGantt/codebase/imgs/");
60         ganttChartControl.setEditable(true);
61         ganttChartControl.showTreePanel(true);
62         ganttChartControl.showContextMenu(false);
63         ganttChartControl.showDescTask(true,'d,s-f');
64         ganttChartControl.showDescProject(true,'n,d');
65
66     },
67     
68     project_starting_date : function() {
69         var self = this,
70             projects = this.database_projects,
71             min_date = _.min(projects, function(prj) {
72                 return self.format_date(prj[self.date_start]);
73             });
74         if (min_date) this.project_start_date = this.format_date(min_date[self.date_start]);
75         else 
76             this.project_start_date = Date.today();
77         return $.Deferred().resolve().promise();
78     },
79     
80     format_date : function(date) {
81         var datetime_regex = /^(\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d)(?:\.\d+)?$/,
82             date_regex = /^\d\d\d\d-\d\d-\d\d$/,
83             time_regex = /^(\d\d:\d\d:\d\d)(?:\.\d+)?$/,
84             def = $.Deferred();
85         if(date_regex.exec(date)) {
86             this.date_format = "yyyy-MM-dd";
87         } else if(time_regex.exec(date)) {
88             this.date_format = "HH:mm:ss";
89         } else {
90             this.date_format = "yyyy-MM-dd HH:mm:ss";
91         }
92         if(typeof date === 'string')
93             return openerp.web.auto_str_to_date(date);
94         return date;
95     },
96
97     on_project_loaded: function(events) {
98         
99         if(!events.length) return;
100         var self = this,
101             started_projects = _.filter(events, function(res) {
102             return res[self.date_start];
103         });
104         
105         this.database_projects = started_projects;
106         
107         if(!started_projects.length)
108             return self.do_warn(_t("date_start is not defined"));
109             
110         if(!self.name && started_projects.length) {
111             var name = started_projects[0][self.parent];
112             self.name = name instanceof Array? name[name.length - 1] : name;
113         }
114         this.$element.find('#add_task').click(function(){
115             self.editTask();
116         });
117         
118         $.when(this.project_starting_date())
119             .then(function() {
120                 if(self.ganttChartControl) {
121                     self.ganttChartControl.clearAll();
122                     self.$element.find('#GanttView').empty();
123                 }
124             })
125             .done(this.init_gantt_view());
126         
127         var self = this;
128         var show_event = started_projects;
129         _.each(show_event, function(evt) {evt[self.date_start] = self.format_date(evt[self.date_start])});
130         this.project = new GanttProjectInfo("_1", self.name, this.project_start_date);
131         self.ganttChartControl.addProject(this.project);
132         //create child
133         var k = 0;
134         var color_box = {};
135         var parents = {};
136         var all_events = {};
137         var child_event = {};
138         var temp_id = "";
139         var final_events = [];
140         for (var i in show_event) {
141
142             var res = show_event[i];
143
144             var id = res['id'];
145             var text = res[this.text];
146             var start_date = res[this.date_start];
147
148             if (this.date_stop != undefined){
149                 if (res[this.date_stop] != false){
150                     var stop_date = this.convert_str_date(res[this.date_stop]);
151                     var duration= self.hours_between(start_date, stop_date);
152                 }
153                 else{
154                     var duration = 0;
155                 }
156             }
157             else{
158                 var duration = res[this.date_delay];
159             }
160             if (!duration)
161                 duration = 0;
162
163             if (this.group_by.length){
164                 for (var j in self.group_by){
165                     var grp_key = res[self.group_by[j]];
166                     if (typeof(grp_key) == "object"){
167                         grp_key = res[self.group_by[j]][1];
168                     }
169                     else{
170                         grp_key = res[self.group_by[j]];
171                     }
172
173                     if (!grp_key){
174                         grp_key = "Undefined";
175                     }
176
177                     if (j == 0){
178                         if (parents[grp_key] == undefined){
179                             var mod_id = i+ "_" +j;
180                             parents[grp_key] = mod_id;
181                             child_event[mod_id] = {};
182                             all_events[mod_id] = {'parent': "", 'evt':[mod_id , grp_key, start_date, start_date, 100, ""]};
183                         }
184                         else{
185                             mod_id = parents[grp_key];
186                         }
187                         temp_id = mod_id;
188                     }else{
189                         if (child_event[mod_id][grp_key] == undefined){
190                             var ch_mod_id = i+ "_" +j;
191                             child_event[mod_id][grp_key] = ch_mod_id;
192                             child_event[ch_mod_id] = {};
193                             temp_id = ch_mod_id;
194                             all_events[ch_mod_id] = {'parent': mod_id, 'evt':[ch_mod_id , grp_key, start_date, start_date, 100, ""]};
195                             mod_id = ch_mod_id;
196                         }
197                         else{
198                             mod_id = child_event[mod_id][grp_key];
199                             temp_id = mod_id;
200                         }
201                     }
202                 }
203                 all_events[id] = {'parent': temp_id, 'evt':[id , text, start_date, duration, 100, ""]};
204                 final_events.push(id);
205             }
206             else {
207                 if (i == 0) {
208                     var mod_id = "_" + i;
209                     all_events[mod_id] = {'parent': "", 'evt': [mod_id, this.name, start_date, start_date, 100, ""]};
210                 }
211                 all_events[id] = {'parent': mod_id, 'evt':[id , text, start_date, duration, 100, ""]};
212                 final_events.push(id);
213             }
214         }
215
216         for (var i in final_events){
217             var evt_id = final_events[i];
218             var evt_date = all_events[evt_id]['evt'][2];
219             while (all_events[evt_id]['parent'] != "") {
220                var parent_id =all_events[evt_id]['parent'];
221                if (all_events[parent_id]['evt'][2] > evt_date){
222                     all_events[parent_id]['evt'][2] = evt_date;
223                }
224                evt_id = parent_id;
225             }
226         }
227         var evt_id = [];
228         var evt_date = "";
229         var evt_duration = "";
230         var evt_end_date = "";
231         var project_tree_field = [];
232         for (var i in final_events){
233             evt_id = final_events[i];
234             evt_date = all_events[evt_id]['evt'][2];
235             evt_duration = all_events[evt_id]['evt'][3];
236
237             var evt_str_date = this.convert_date_str(evt_date);
238             evt_end_date = this.end_date(evt_str_date, evt_duration);
239
240             while (all_events[evt_id]['parent'] != "") {
241                var parent_id =all_events[evt_id]['parent'];
242                if (all_events[parent_id]['evt'][3] < evt_end_date){
243                     all_events[parent_id]['evt'][3] = evt_end_date;
244                }
245                evt_id = parent_id;
246             }
247         }
248
249         for (var j in self.group_by) {
250             self.render_events(all_events, j);
251         }
252
253         if (!self.group_by.length) {
254             self.render_events(all_events, 0);
255         }
256
257         for (var i in final_events) {
258             evt_id = final_events[i];
259             res = all_events[evt_id];
260             task=new GanttTaskInfo(res['evt'][0], res['evt'][1], res['evt'][2], res['evt'][3], res['evt'][4], "");
261             prt = self.project.getTaskById(res['parent']);
262             prt.addChildTask(task);
263         }
264
265         var oth_hgt = 264;
266         var min_hgt = 150;
267         var name_min_wdt = 150;
268         var gantt_hgt = $(window).height() - oth_hgt;
269         var search_wdt = $("#oe_app_search").width();
270
271         if (gantt_hgt > min_hgt) {
272             $('#GanttView').height(gantt_hgt).width(search_wdt);
273         } else{
274             $('#GanttView').height(min_hgt).width(search_wdt);
275         }
276
277         self.ganttChartControl.create("GanttView");
278         
279         // Setup Events
280         self.ganttChartControl.attachEvent("onTaskStartDrag", function(task) {
281             if (task.parentTask) {
282                 var task_date = task.getEST();
283                 if (task_date.getHours()) {
284                     task_date.set({
285                         hour: task_date.getHours(),
286                         minute: task_date.getMinutes(),
287                         second: 0
288                     });
289                 }
290             }
291         });
292         self.ganttChartControl.attachEvent("onTaskEndResize", function(task) {return self.resizeTask(task);});
293         self.ganttChartControl.attachEvent("onTaskEndDrag", function(task) {return self.resizeTask(task);});
294         
295         var taskdiv = $("div.taskPanel").parent();
296         taskdiv.addClass('ganttTaskPanel');
297         taskdiv.prev().addClass('ganttDayPanel');
298         var $gantt_panel = $(".ganttTaskPanel , .ganttDayPanel");
299
300         var ganttrow = $('.taskPanel').closest('tr');
301         var gtd =  ganttrow.children(':first-child');
302         gtd.children().addClass('task-name');
303
304         $(".toggle-sidebar").click(function(e) {
305             self.set_width();
306         });
307
308         $(window).bind('resize',function() {
309             window.clearTimeout(self.ganttChartControl._resize_timer);
310             self.ganttChartControl._resize_timer = window.setTimeout(function(){
311                 self.reloadView();
312             }, 200);
313         });
314         
315         var project = self.ganttChartControl.getProjectById("_1");
316         if (project) {
317             $(project.projectItem[0]).hide();
318             $(project.projectNameItem).hide();
319             $(project.descrProject).hide();
320             
321             _.each(final_events, function(id) {
322                 var Task = project.getTaskById(id);
323                 $(Task.cTaskNameItem[0]).click(function() {
324                     self.editTask(Task);
325                 })
326             });
327         }
328     },
329     
330     resizeTask: function(task) {
331         var self = this,
332             event_id = task.getId();
333         if(task.childTask.length)
334             return;
335         
336         var data = {};
337         data[this.date_start] = task.getEST().toString(this.date_format);
338         
339         if(this.date_stop) {
340             var diff = task.getDuration() % this.day_length,
341                 finished_date = task.getFinishDate().add({hours: diff});
342             data[this.date_stop] = finished_date.toString(this.date_format);
343         } else {
344             data[this.date_delay] = task.getDuration();
345         }
346         this.dataset
347             .write(event_id, data, {})
348             .done(function() {
349                 var get_project = _.find(self.database_projects, function(project){ return project.id == event_id});
350                 _.extend(get_project,data);
351                 self.reloadView();
352             });
353     },
354     
355     editTask: function(task) {
356         var self = this,
357             event_id;
358         if (!task)
359             event_id = 0;
360         else {
361             event_id = task.getId();
362             if(!event_id || !task.parentTask)
363                 return false;
364         }
365         if(event_id) event_id = parseInt(event_id, 10);
366         
367         var pop = new openerp.web.form.FormOpenPopup(this);
368         
369         pop.show_element(this.model, event_id, this.context || this.dataset.context, {});
370         
371         pop.on_write.add(function(id, data) {
372             var get_project = _.find(self.database_projects, function(project){ return project.id == id});
373             if (get_project) {
374                 _.extend(get_project, data);
375             } else {
376                 _.extend(self.database_projects, _.extend(data, {'id': id}));
377             }
378             self.reloadView();
379         });
380     },
381
382     set_width: function() {
383         $gantt_panel.width(1);
384         jQuery(".ganttTaskPanel").parent().width(1);
385
386         var search_wdt = jQuery("#oe_app_search").width();
387         var day_wdt = jQuery(".ganttDayPanel").children().children().width();
388         jQuery('#GanttView').css('width','100%');
389
390         if (search_wdt - day_wdt <= name_min_wdt){
391             jQuery(".ganttTaskPanel").parent().width(search_wdt - name_min_wdt);
392             jQuery(".ganttTaskPanel").width(search_wdt - name_min_wdt);
393             jQuery(".ganttDayPanel").width(search_wdt - name_min_wdt - 14);
394             jQuery('.task-name').width(name_min_wdt);
395             jQuery('.task-name').children().width(name_min_wdt);
396         }else{
397             jQuery(".ganttTaskPanel").parent().width(day_wdt);
398             jQuery(".ganttTaskPanel").width(day_wdt);
399             jQuery(".taskPanel").width(day_wdt - 16);
400             jQuery(".ganttDayPanel").width(day_wdt -16);
401             jQuery('.task-name').width(search_wdt - day_wdt);
402             jQuery('.task-name').children().width(search_wdt - day_wdt);
403         }
404
405     },
406
407     end_date: function(dat, duration) {
408
409          var self = this;
410
411          var dat = this.convert_str_date(dat);
412
413          var day = Math.floor(duration/self.day_length);
414          var hrs = duration % self.day_length;
415
416          dat.add(day).days();
417          dat.add(hrs).hour();
418
419          return dat;
420     },
421
422     hours_between: function(date1, date2, parent_task) {
423
424         var ONE_DAY = 1000 * 60 * 60 * 24;
425         var date1_ms = date1.getTime();
426         var date2_ms = date2.getTime();
427         var difference_ms = Math.abs(date1_ms - date2_ms);
428
429         var d = parent_task? Math.ceil(difference_ms / ONE_DAY) : Math.floor(difference_ms / ONE_DAY);
430         var h = (difference_ms % ONE_DAY)/(1000 * 60 * 60);
431         var num = (d * this.day_length) + h;
432         return parseFloat(num.toFixed(2));
433
434     },
435
436     render_events : function(all_events, j) {
437
438         var self = this;
439         for (var i in all_events){
440             var res = all_events[i];
441             if ((typeof(res['evt'][3])) == "object"){
442                 res['evt'][3] = self.hours_between(res['evt'][2],res['evt'][3], true);
443             }
444
445             k = res['evt'][0].toString().indexOf('_');
446
447             if (k != -1) {
448                 if (res['evt'][0].substring(k) == "_"+j){
449                     if (j == 0){
450                         task = new GanttTaskInfo(res['evt'][0], res['evt'][1], res['evt'][2], res['evt'][3], res['evt'][4], "");
451                         self.project.addTask(task);
452                     } else {
453                         task = new GanttTaskInfo(res['evt'][0], res['evt'][1], res['evt'][2], res['evt'][3], res['evt'][4], "");
454                         prt = self.project.getTaskById(res['parent']);
455                         prt.addChildTask(task);
456                     }
457                 }
458             }
459         }
460     },
461
462     do_show: function () {
463         this.$element.show();
464     },
465
466     do_hide: function () {
467         this.$element.hide();
468     },
469
470     convert_str_date: function (str) {
471         if (typeof str == 'string') {
472             if (str.length == 19) {
473                 this.date_format = "yyyy-MM-dd HH:mm:ss";
474                 return openerp.web.str_to_datetime(str);
475             }
476             else 
477                 if (str.length == 10) {
478                     this.date_format = "yyyy-MM-dd";
479                     return openerp.web.str_to_date(str);
480                 }
481                 else 
482                     if (str.length == 8) {
483                         this.date_format = "HH:mm:ss";
484                         return openerp.web.str_to_time(str);
485                     }
486             throw "Unrecognized date/time format";
487         } else {
488             return str;
489         }
490     },
491
492     convert_date_str: function(full_date) {
493         if (this.date_format == "yyyy-MM-dd HH:mm:ss"){
494             return openerp.web.datetime_to_str(full_date);
495         } else if (this.date_format == "yyyy-MM-dd"){
496             return openerp.web.date_to_str(full_date);
497         } else if (this.date_format == "HH:mm:ss"){
498             return openerp.web.time_to_str(full_date);
499         }
500         throw "Unrecognized date/time format";
501     },
502     
503     reloadView: function() {
504        return this.on_project_loaded(this.database_projects);
505     },
506
507     do_search: function (domains, contexts, groupbys) {
508         var self = this;
509         this.group_by = [];
510         if (this.fields_view.arch.attrs.default_group_by) {
511             this.group_by = this.fields_view.arch.attrs.default_group_by.split(',');
512         }
513         
514         if (groupbys.length) {
515             this.group_by = groupbys;
516         }
517         
518         $.when(this.has_been_loaded).then(function() {
519                 self.dataset.read_slice([], {
520                     domain: domains,
521                     context: contexts
522                 }).done(function(projects){
523                     self.on_project_loaded(projects);
524                 });
525         });
526     }
527
528 });
529
530 // here you may tweak globals object, if any, and play with on_* or do_* callbacks on them
531
532 };
533 // vim:et fdc=0 fdl=0: