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