c7afd62c8abe3cc0d769fe6939115aa1124cf7fb
[odoo/odoo.git] / addons / hr_timesheet_sheet / static / src / js / timesheet.js
1
2 openerp.hr_timesheet_sheet = function(instance) {
3     var QWeb = instance.web.qweb;
4     var _t = instance.web._t;
5
6     instance.hr_timesheet_sheet.WeeklyTimesheet = instance.web.form.FormWidget.extend(instance.web.form.ReinitializeWidgetMixin, {
7         events: {
8             "click .oe_timesheet_weekly_account a": "go_to",
9         },
10         init: function() {
11             this._super.apply(this, arguments);
12             var self = this;
13             this.set({
14                 sheets: [],
15                 date_to: false,
16                 date_from: false,
17             });
18             this.updating = false;
19             this.defs = [];
20             this.field_manager.on("field_changed:timesheet_ids", this, this.query_sheets);
21             this.field_manager.on("field_changed:date_from", this, function() {
22                 this.set({"date_from": instance.web.str_to_date(this.field_manager.get_field_value("date_from"))});
23             });
24             this.field_manager.on("field_changed:date_to", this, function() {
25                 this.set({"date_to": instance.web.str_to_date(this.field_manager.get_field_value("date_to"))});
26             });
27             this.field_manager.on("field_changed:user_id", this, function() {
28                 this.set({"user_id": this.field_manager.get_field_value("user_id")});
29             });
30             this.on("change:sheets", this, this.update_sheets);
31             this.res_o2m_drop = new instance.web.DropMisordered();
32             this.render_drop = new instance.web.DropMisordered();
33             this.description_line = _t("/");
34             // Original save function is overwritten in order to wait all running deferreds to be done before actually applying the save.
35             this.view.original_save = _.bind(this.view.save, this.view);
36             this.view.save = function(prepend_on_create){
37                 self.prepend_on_create = prepend_on_create;
38                 return $.when.apply($, self.defs).then(function(){
39                     return self.view.original_save(self.prepend_on_create);
40                 });
41             };
42         },
43         go_to: function(event) {
44             var id = JSON.parse($(event.target).data("id"));
45             this.do_action({
46                 type: 'ir.actions.act_window',
47                 res_model: "account.analytic.account",
48                 res_id: id,
49                 views: [[false, 'form']],
50                 target: 'current'
51             });
52         },
53         query_sheets: function() {
54             var self = this;
55             if (self.updating)
56                 return;
57             var commands = this.field_manager.get_field_value("timesheet_ids");
58             this.res_o2m_drop.add(new instance.web.Model(this.view.model).call("resolve_2many_commands", ["timesheet_ids", commands, [], 
59                     new instance.web.CompoundContext()]))
60                 .done(function(result) {
61                 self.querying = true;
62                 self.set({sheets: result});
63                 self.querying = false;
64             });
65         },
66         update_sheets: function() {
67             var self = this;
68             if (self.querying)
69                 return;
70             self.updating = true;
71             self.field_manager.set_values({timesheet_ids: self.get("sheets")}).done(function() {
72                 self.updating = false;
73             });
74         },
75         initialize_field: function() {
76             instance.web.form.ReinitializeWidgetMixin.initialize_field.call(this);
77             var self = this;
78             self.on("change:sheets", self, self.initialize_content);
79             self.on("change:date_to", self, self.initialize_content);
80             self.on("change:date_from", self, self.initialize_content);
81             self.on("change:user_id", self, self.initialize_content);
82         },
83         initialize_content: function() {
84             var self = this;
85             if (self.setting)
86                 return;
87             // don't render anything until we have date_to and date_from
88             if (!self.get("date_to") || !self.get("date_from"))
89                 return;
90             this.destroy_content();
91
92             // it's important to use those vars to avoid race conditions
93             var dates;
94             var accounts;
95             var account_names;
96             var default_get;
97             return this.render_drop.add(new instance.web.Model("hr.analytic.timesheet").call("default_get", [
98                 ['account_id','general_account_id', 'journal_id','date','name','user_id','product_id','product_uom_id','to_invoice','amount','unit_amount'],
99                 new instance.web.CompoundContext({'user_id': self.get('user_id')})]).then(function(result) {
100                 default_get = result;
101                 // calculating dates
102                 dates = [];
103                 var start = self.get("date_from");
104                 var end = self.get("date_to");
105                 while (start <= end) {
106                     dates.push(start);
107                     start = start.clone().addDays(1);
108                 }
109                 // group by account
110                 accounts = _(self.get("sheets")).chain()
111                 .map(function(el) {
112                     // much simpler to use only the id in all cases
113                     if (typeof(el.account_id) === "object")
114                         el.account_id = el.account_id[0];
115                     return el;
116                 })
117                 .groupBy("account_id").value();
118
119                 var account_ids = _.map(_.keys(accounts), function(el) { return el === "false" ? false : Number(el) });
120
121                 return new instance.web.Model("hr.analytic.timesheet").call("multi_on_change_account_id", [[], account_ids,
122                     new instance.web.CompoundContext({'user_id': self.get('user_id')})]).then(function(accounts_defaults) {
123                     accounts = _(accounts).chain().map(function(lines, account_id) {
124                         account_defaults = _.extend({}, default_get, (accounts_defaults[account_id] || {}).value || {});
125                         // group by days
126                         account_id = account_id === "false" ? false :  Number(account_id);
127                         var index = _.groupBy(lines, "date");
128                         var days = _.map(dates, function(date) {
129                             var day = {day: date, lines: index[instance.web.date_to_str(date)] || []};
130                             // add line where we will insert/remove hours
131                             var to_add = _.find(day.lines, function(line) { return line.name === self.description_line });
132                             if (to_add) {
133                                 day.lines = _.without(day.lines, to_add);
134                                 day.lines.unshift(to_add);
135                             } else {
136                                 day.lines.unshift(_.extend(_.clone(account_defaults), {
137                                     name: self.description_line,
138                                     unit_amount: 0,
139                                     date: instance.web.date_to_str(date),
140                                     account_id: account_id,
141                                 }));
142                             }
143                             return day;
144                         });
145                         return {account: account_id, days: days, account_defaults: account_defaults};
146                     }).value();
147
148                     // we need the name_get of the analytic accounts
149                     return new instance.web.Model("account.analytic.account").call("name_get", [_.pluck(accounts, "account"),
150                         new instance.web.CompoundContext()]).then(function(result) {
151                         account_names = {};
152                         _.each(result, function(el) {
153                             account_names[el[0]] = el[1];
154                         });
155                         accounts = _.sortBy(accounts, function(el) {
156                             return account_names[el.account];
157                         });
158                     });;
159                 });
160             })).then(function(result) {
161                 // we put all the gathered data in self, then we render
162                 self.dates = dates;
163                 self.accounts = accounts;
164                 self.account_names = account_names;
165                 self.default_get = default_get;
166                 //real rendering
167                 self.display_data();
168             });
169         },
170         destroy_content: function() {
171             if (this.dfm) {
172                 this.dfm.destroy();
173                 this.dfm = undefined;
174             }
175         },
176         is_valid_value:function(value){
177             var split_value = value.split(":");
178             var valid_value = true;
179             if (split_value.length > 2)
180                 return false;
181             _.detect(split_value,function(num){
182                 if(isNaN(num)){
183                     valid_value = false;
184                 }
185             });
186             return valid_value;
187         },
188         display_data: function() {
189             var self = this;
190             self.$el.html(QWeb.render("hr_timesheet_sheet.WeeklyTimesheet", {widget: self}));
191             _.each(self.accounts, function(account) {
192                 _.each(_.range(account.days.length), function(day_count) {
193                     if (!self.get('effective_readonly')) {
194                         self.get_box(account, day_count).val(self.sum_box(account, day_count, true)).change(function() {
195                             var num = $(this).val();
196                             if (self.is_valid_value(num)){
197                                 num = (num == 0)?0:Number(self.parse_client(num));
198                             }
199                             if (isNaN(num)) {
200                                 $(this).val(self.sum_box(account, day_count, true));
201                             } else {
202                                 account.days[day_count].lines[0].unit_amount += num - self.sum_box(account, day_count);
203                                 var product = (account.days[day_count].lines[0].product_id instanceof Array) ? account.days[day_count].lines[0].product_id[0] : account.days[day_count].lines[0].product_id
204                                 var journal = (account.days[day_count].lines[0].journal_id instanceof Array) ? account.days[day_count].lines[0].journal_id[0] : account.days[day_count].lines[0].journal_id
205                                 self.defs.push(new instance.web.Model("hr.analytic.timesheet").call("on_change_unit_amount", [[], product, account.days[day_count].lines[0].unit_amount, false, false, journal]).then(function(res) {
206                                     account.days[day_count].lines[0]['amount'] = res.value.amount || 0;
207                                     self.display_totals();
208                                     self.sync();
209                                 }));
210                                 if(!isNaN($(this).val())){
211                                     $(this).val(self.sum_box(account, day_count, true));
212                                 }
213                             }
214                         });
215                     } else {
216                         self.get_box(account, day_count).html(self.sum_box(account, day_count, true));
217                     }
218                 });
219             });
220             self.display_totals();
221             self.$(".oe_timesheet_weekly_adding button").click(_.bind(this.init_add_account, this));
222         },
223         init_add_account: function() {
224             var self = this;
225             if (self.dfm)
226                 return;
227             self.$(".oe_timesheet_weekly_add_row").show();
228             self.dfm = new instance.web.form.DefaultFieldManager(self);
229             self.dfm.extend_field_desc({
230                 account: {
231                     relation: "account.analytic.account",
232                 },
233             });
234             self.account_m2o = new instance.web.form.FieldMany2One(self.dfm, {
235                 attrs: {
236                     name: "account",
237                     type: "many2one",
238                     domain: [
239                         ['type','in',['normal', 'contract']],
240                         ['state', '<>', 'close'],
241                         ['use_timesheets','=',1],
242                         ['id', 'not in', _.pluck(self.accounts, "account")],
243                     ],
244                     context: {
245                         default_use_timesheets: 1,
246                         default_type: "contract",
247                     },
248                     modifiers: '{"required": true}',
249                 },
250             });
251             self.account_m2o.prependTo(self.$(".oe_timesheet_weekly_add_row td"));
252             self.$(".oe_timesheet_weekly_add_row button").click(function() {
253                 var id = self.account_m2o.get_value();
254                 if (id === false) {
255                     self.dfm.set({display_invalid_fields: true});
256                     return;
257                 }
258                 var ops = self.generate_o2m_value();
259                 new instance.web.Model("hr.analytic.timesheet").call("on_change_account_id", [[], id]).then(function(res) {
260                     var def = _.extend({}, self.default_get, res.value, {
261                         name: self.description_line,
262                         unit_amount: 0,
263                         date: instance.web.date_to_str(self.dates[0]),
264                         account_id: id,
265                     });
266                     ops.push(def);
267                     self.set({"sheets": ops});
268                 });
269             });
270         },
271         get_box: function(account, day_count) {
272             return this.$('[data-account="' + account.account + '"][data-day-count="' + day_count + '"]');
273         },
274         get_total: function(account) {
275             return this.$('[data-account-total="' + account.account + '"]');
276         },
277         get_day_total: function(day_count) {
278             return this.$('[data-day-total="' + day_count + '"]');
279         },
280         get_super_total: function() {
281             return this.$('.oe_timesheet_weekly_supertotal');
282         },
283         sum_box: function(account, day_count, show_value_in_hour) {
284             var line_total = 0;
285             _.each(account.days[day_count].lines, function(line) {
286                 line_total += line.unit_amount;
287             });
288             return (show_value_in_hour && line_total != 0)?this.format_client(line_total):line_total;
289         },
290         display_totals: function() {
291             var self = this;
292             var day_tots = _.map(_.range(self.dates.length), function() { return 0 });
293             var super_tot = 0;
294             _.each(self.accounts, function(account) {
295                 var acc_tot = 0;
296                 _.each(_.range(self.dates.length), function(day_count) {
297                     var sum = self.sum_box(account, day_count);
298                     acc_tot += sum;
299                     day_tots[day_count] += sum;
300                     super_tot += sum;
301                 });
302                 self.get_total(account).html(self.format_client(acc_tot));
303             });
304             _.each(_.range(self.dates.length), function(day_count) {
305                 self.get_day_total(day_count).html(self.format_client(day_tots[day_count]));
306             });
307             self.get_super_total().html(self.format_client(super_tot));
308         },
309         sync: function() {
310             var self = this;
311             self.setting = true;
312             self.set({sheets: this.generate_o2m_value()});
313             self.setting = false;
314         },
315         //converts hour value to float
316         parse_client: function(value) {
317             return instance.web.parse_value(value, { type:"float_time" });
318         },
319         //converts float value to hour
320         format_client:function(value){
321             return instance.web.format_value(value, { type:"float_time" });
322         },
323         generate_o2m_value: function() {
324             var self = this;
325             var ops = [];
326             
327             _.each(self.accounts, function(account) {
328                 var auth_keys = _.extend(_.clone(account.account_defaults), {
329                     name: true, amount:true, unit_amount: true, date: true, account_id:true,
330                 });
331                 _.each(account.days, function(day) {
332                     _.each(day.lines, function(line) {
333                         if (line.unit_amount !== 0) {
334                             var tmp = _.clone(line);
335                             tmp.id = undefined;
336                             _.each(line, function(v, k) {
337                                 if (v instanceof Array) {
338                                     tmp[k] = v[0];
339                                 }
340                             });
341                             // we have to remove some keys, because analytic lines are shitty
342                             _.each(_.keys(tmp), function(key) {
343                                 if (auth_keys[key] === undefined) {
344                                     tmp[key] = undefined;
345                                 }
346                             });
347                             ops.push(tmp);
348                         }
349                     });
350                 });
351             });
352             return ops;
353         },
354     });
355
356     instance.web.form.custom_widgets.add('weekly_timesheet', 'instance.hr_timesheet_sheet.WeeklyTimesheet');
357
358 };