Merge branch '2016.11-develop' into 2017.11
[tine20] / tine20 / Timetracker / js / TimesheetGridPanel.js
1 /*
2  * Tine 2.0
3  * 
4  * @package     Timetracker
5  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
6  * @author      Philipp Schüle <p.schuele@metaways.de>
7  * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
8  *
9  */
10  
11 Ext.namespace('Tine.Timetracker');
12
13 /**
14  * Timesheet grid panel
15  * 
16  * @namespace   Tine.Timetracker
17  * @class       Tine.Timetracker.TimesheetGridPanel
18  * @extends     Tine.widgets.grid.GridPanel
19  * 
20  * <p>Timesheet Grid Panel</p>
21  * <p><pre>
22  * </pre></p>
23  * 
24  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
25  * @author      Philipp Schüle <p.schuele@metaways.de>
26  * 
27  * @param       {Object} config
28  * @constructor
29  * Create a new Tine.Timetracker.TimesheetGridPanel
30  */
31 Tine.Timetracker.TimesheetGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
32     /**
33      * record class
34      * @cfg {Tine.Timetracker.Model.Timesheet} recordClass
35      */
36     recordClass: Tine.Timetracker.Model.Timesheet,
37
38     /**
39      * @private grid cfg
40      */
41     defaultSortInfo: {field: 'start_date', direction: 'DESC'},
42     gridConfig: {
43         autoExpandColumn: 'description'
44     },
45     copyEditAction: true,
46     multipleEdit: true,
47     multipleEditRequiredRight: 'manage_timeaccounts',
48
49     /**
50      * @private
51      */
52
53     /**
54      * activates copy action
55      */
56     copyEditAction: true,
57
58     /**
59      * only allow multi edit with manage_timeaccounts right (because of timeaccount handling in edit dlg)
60      */
61     multipleEditRequiredRight: 'manage_timeaccounts',
62
63     initComponent: function() {
64         this.defaultFilters = [
65             {field: 'start_date', operator: 'within', value: 'weekThis'},
66             {field: 'account_id', operator: 'equals', value: Tine.Tinebase.registry.get('currentAccount')}
67         ];
68
69         this.initDetailsPanel();
70         
71         // only eval grants in action updater if user does not have the right to manage timeaccounts
72         this.evalGrants = ! Tine.Tinebase.common.hasRight('manage', 'Timetracker', 'timeaccounts');
73
74
75         Tine.Timetracker.TimesheetGridPanel.superclass.initComponent.call(this);
76     },
77
78     /**
79      * @private
80      */
81     initDetailsPanel: function() {
82         this.detailsPanel = new Tine.widgets.grid.DetailsPanel({
83             gridpanel: this,
84             
85             // use default Tpl for default and multi view
86             defaultTpl: new Ext.XTemplate(
87                 '<div class="preview-panel-timesheet-nobreak">',
88                     '<!-- Preview timeframe -->',           
89                     '<div class="preview-panel preview-panel-timesheet-left">',
90                         '<div class="bordercorner_1"></div>',
91                         '<div class="bordercorner_2"></div>',
92                         '<div class="bordercorner_3"></div>',
93                         '<div class="bordercorner_4"></div>',
94                         '<div class="preview-panel-declaration">' /*+ this.app.i18n._('timeframe')*/ + '</div>',
95                         '<div class="preview-panel-timesheet-leftside preview-panel-left">',
96                             '<span class="preview-panel-bold">',
97                             /*'First Entry'*/'<br/>',
98                             /*'Last Entry*/'<br/>',
99                             /*'Duration*/'<br/>',
100                             '<br/>',
101                             '</span>',
102                         '</div>',
103                         '<div class="preview-panel-timesheet-rightside preview-panel-left">',
104                             '<span class="preview-panel-nonbold">',
105                             '<br/>',
106                             '<br/>',
107                             '<br/>',
108                             '<br/>',
109                             '</span>',
110                         '</div>',
111                     '</div>',
112                     '<!-- Preview summary -->',
113                     '<div class="preview-panel-timesheet-right">',
114                         '<div class="bordercorner_gray_1"></div>',
115                         '<div class="bordercorner_gray_2"></div>',
116                         '<div class="bordercorner_gray_3"></div>',
117                         '<div class="bordercorner_gray_4"></div>',
118                         '<div class="preview-panel-declaration">'/* + this.app.i18n._('summary')*/ + '</div>',
119                         '<div class="preview-panel-timesheet-leftside preview-panel-left">',
120                             '<span class="preview-panel-bold">',
121                             this.app.i18n._('Total Timesheets') + '<br/>',
122                             this.app.i18n._('Billable Timesheets') + '<br/>',
123                             this.app.i18n._('Total Time') + '<br/>',
124                             this.app.i18n._('Time of Billable Timesheets') + '<br/>',
125                             '</span>',
126                         '</div>',
127                         '<div class="preview-panel-timesheet-rightside preview-panel-left">',
128                             '<span class="preview-panel-nonbold">',
129                             '{count}<br/>',
130                             '{countbillable}<br/>',
131                             '{sum}<br/>',
132                             '{sumbillable}<br/>',
133                             '</span>',
134                         '</div>',
135                     '</div>',
136                 '</div>'            
137             ),
138             
139             showDefault: function(body) {
140                 
141                 var data = {
142                     count: this.gridpanel.store.proxy.jsonReader.jsonData.totalcount,
143                     countbillable: (this.gridpanel.store.proxy.jsonReader.jsonData.totalcountbillable) ? this.gridpanel.store.proxy.jsonReader.jsonData.totalcountbillable : 0,
144                     sum:  Tine.Tinebase.common.minutesRenderer(this.gridpanel.store.proxy.jsonReader.jsonData.totalsum),
145                     sumbillable: Tine.Tinebase.common.minutesRenderer(this.gridpanel.store.proxy.jsonReader.jsonData.totalsumbillable)
146                 };
147                 
148                 this.defaultTpl.overwrite(body, data);
149             },
150             
151             showMulti: function(sm, body) {
152                 
153                 var data = {
154                     count: sm.getCount(),
155                     countbillable: 0,
156                     sum: 0,
157                     sumbillable: 0
158                 };
159                 sm.each(function(record){
160                     
161                     data.sum = data.sum + parseInt(record.data.duration);
162                     if (record.data.is_billable_combined == '1') {
163                         data.countbillable++;
164                         data.sumbillable = data.sumbillable + parseInt(record.data.duration);
165                     }
166                 });
167                 data.sum = Tine.Tinebase.common.minutesRenderer(data.sum);
168                 data.sumbillable = Tine.Tinebase.common.minutesRenderer(data.sumbillable);
169                 
170                 this.defaultTpl.overwrite(body, data);
171             },
172             
173             tpl: new Ext.XTemplate(
174                 '<div class="preview-panel-timesheet-nobreak">',    
175                     '<!-- Preview beschreibung -->',
176                     '<div class="preview-panel preview-panel-timesheet-left">',
177                         '<div class="bordercorner_1"></div>',
178                         '<div class="bordercorner_2"></div>',
179                         '<div class="bordercorner_3"></div>',
180                         '<div class="bordercorner_4"></div>',
181                         '<div class="preview-panel-declaration">' /* + this.app.i18n._('Description') */ + '</div>',
182                         '<div class="preview-panel-timesheet-description preview-panel-left">',
183                             '<span class="preview-panel-nonbold">',
184                              '{[this.encode(values.description)]}',
185                             '<br/>',
186                             '</span>',
187                         '</div>',
188                     '</div>',
189                     '<!-- Preview detail-->',
190                     '<div class="preview-panel-timesheet-right">',
191                         '<div class="bordercorner_gray_1"></div>',
192                         '<div class="bordercorner_gray_2"></div>',
193                         '<div class="bordercorner_gray_3"></div>',
194                         '<div class="bordercorner_gray_4"></div>',
195                         '<div class="preview-panel-declaration">' /* + this.app.i18n._('Detail') */ + '</div>',
196                         '<div class="preview-panel-timesheet-leftside preview-panel-left">',
197                         // @todo add custom fields here
198                         /*
199                             '<span class="preview-panel-bold">',
200                             'Ansprechpartner<br/>',
201                             'Newsletter<br/>',
202                             'Ticketnummer<br/>',
203                             'Ticketsubjekt<br/>',
204                             '</span>',
205                         */
206                         '</div>',
207                         '<div class="preview-panel-timesheet-rightside preview-panel-left">',
208                             '<span class="preview-panel-nonbold">',
209                             '<br/>',
210                             '<br/>',
211                             '<br/>',
212                             '<br/>',
213                             '</span>',
214                         '</div>',
215                     '</div>',
216                 '</div>',{
217                 encode: function(value, type, prefix) {
218                     if (value) {
219                         if (type) {
220                             switch (type) {
221                                 case 'longtext':
222                                     value = Ext.util.Format.ellipsis(value, 150);
223                                     break;
224                                 default:
225                                     value += type;
226                             }
227                         } else {
228                             value = Ext.util.Format.htmlEncode(value);
229                         }
230                         
231                         var encoded = Ext.util.Format.htmlEncode(value);
232                         encoded = Ext.util.Format.nl2br(encoded);
233                         
234                         return encoded;
235                     } else {
236                         return '';
237                     }
238                 }
239             })
240         });
241     },
242
243     /**
244      * @private
245      */
246     initActions: function() {
247         var hiddenQuickTag = false,
248             quicktagName,
249             quicktagId;
250
251         quicktagId = Tine.Timetracker.registry.get('quicktagId');
252         quicktagName = Tine.Timetracker.registry.get('quicktagName');
253
254         if (!quicktagId || !quicktagName) {
255             hiddenQuickTag = true;
256         }
257
258         this.actions_massQuickTag = new Ext.Action({
259             hidden: hiddenQuickTag,
260             requiredGrant: 'editGrant',
261             text: String.format(
262                 this.app.i18n._('Assign \'{0}\' Tag'),
263                 quicktagName
264             ),
265             disabled: true,
266             allowMultiple: true,
267             handler: this.onApplyQuickTag.createDelegate(this),
268             iconCls: 'action_tag',
269             scope: this
270         });
271
272         this.actions_export = new Ext.Action({
273             text: this.app.i18n._('Export Timesheets'),
274             iconCls: 'action_export',
275             scope: this,
276             requiredGrant: 'exportGrant',
277             disabled: true,
278             allowMultiple: true,
279             actionUpdater: this.updateExportAction,
280             menu: {
281                 items: [
282                     new Tine.widgets.grid.ExportButton({
283                         text: this.app.i18n._('Export as ODS'),
284                         format: 'ods',
285                         iconCls: 'tinebase-action-export-ods',
286                         exportFunction: 'Timetracker.exportTimesheets',
287                         gridPanel: this
288                     }),
289                     new Tine.widgets.grid.ExportButton({
290                         text: this.app.i18n._('Export as CSV'),
291                         format: 'csv',
292                         iconCls: 'tinebase-action-export-csv',
293                         exportFunction: 'Timetracker.exportTimesheets',
294                         gridPanel: this
295                     }),
296                     new Tine.widgets.grid.ExportButton({
297                         text: this.app.i18n._('Export as ...'),
298                         iconCls: 'tinebase-action-export-xls',
299                         exportFunction: 'Timetracker.exportTimesheets',
300                         showExportDialog: true,
301                         gridPanel: this
302                     })
303                 ]
304             }
305         });
306         
307         // register actions in updater
308         this.actionUpdater.addActions([
309             this.actions_export,
310             this.actions_massQuickTag
311         ]);
312         
313         Tine.Timetracker.TimesheetGridPanel.superclass.initActions.call(this);
314     },
315
316     /**
317      * Apply quick tag to current selection
318      */
319     onApplyQuickTag: function() {
320         var quickTagId,
321             filter,
322             filterModel,
323             me;
324
325         me = this;
326
327         // Tag to assign
328         quickTagId = Tine.Timetracker.registry.get('quicktagId');
329
330         // Get filter model for current selection
331         filter = this.selectionModel.getSelectionFilter();
332         filterModel = this.recordClass.getMeta('appName') + '_Model_' +  this.recordClass.getMeta('modelName') + 'Filter';
333
334         // Send request to backend
335         Ext.Ajax.request({
336             scope: this,
337             timeout: 1800000,
338             success: function(response, options) {
339                 // In case of success, just reload grid
340                 me.getStore().reload();
341             },
342             params: {
343                 method: 'Tinebase.attachTagToMultipleRecords',
344                 filterData: filter,
345                 filterName: filterModel,
346                 tag: quickTagId
347             },
348             failure: function(response, options) {
349                 Tine.Tinebase.ExceptionHandler.handleRequestException(response, options);
350             }
351         });
352     },
353
354     /**
355      * check user exportGrant for timeaccounts
356      * NOTE: manage_timeaccounts ALWAYS allows to export
357      *
358      * @param action
359      * @param grants
360      * @param records
361      * @returns {boolean}
362      */
363     updateExportAction: function(action, grants, records) {
364         // export should be allowed always if user is allowed to manage timeaccounts
365         if (Tine.Tinebase.common.hasRight('manage', 'Timetracker', 'timeaccounts')) {
366             action.setDisabled(false);
367
368             // stop further events
369             return false;
370         }
371
372         // By default disallow export, this apply for example, if there is no selection yet
373         // E.g. filter changes and so on
374         var exportGrant = false;
375
376         // We need to go through all timeaccounts and check if the user is trying to export a timesheet of a timeaccount
377         // where he has no permission to export.
378         Ext.each(records, function (record) {
379             var timeaccount = record.get('timeaccount_id');
380             var c = timeaccount.container_id;
381             if (c.hasOwnProperty('account_grants')) {
382                 var grants = c.account_grants;
383
384                 if (!grants.exportGrant) {
385                     exportGrant = false;
386
387                     // stop loop
388                     return false;
389                 } else {
390                     // If there was at least one selection which had the exportGrant, allow to export
391                     exportGrant = true;
392                 }
393             }
394         });
395
396         var disable = !exportGrant;
397         action.setDisabled(disable);
398
399         // stop further events
400         return false;
401     },
402     
403     /**
404      * add custom items to context menu
405      * 
406      * @return {Array}
407      */
408     getContextMenuItems: function() {
409         var items = [
410             '-',
411             this.actions_massQuickTag,
412             this.actions_export
413         ];
414         
415         return items;
416     }
417 });