Merge branch '2016.11-develop' into 2017.02
[tine20] / tine20 / Timetracker / js / TimesheetEditDialog.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-2016 Metaways Infosystems GmbH (http://www.metaways.de)
8  *
9  */
10  
11 Ext.namespace('Tine.Timetracker');
12
13 /**
14  * Timetracker Edit Dialog
15  */
16 Tine.Timetracker.TimesheetEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
17
18     /**
19      * @private
20      */
21     windowNamePrefix: 'TimesheetEditWindow_',
22     appName: 'Timetracker',
23     recordClass: Tine.Timetracker.Model.Timesheet,
24     recordProxy: Tine.Timetracker.timesheetBackend,
25     tbarItems: null,
26     evalGrants: false,
27     useInvoice: false,
28     displayNotes: true,
29     context: { 'skipClosedCheck': false },
30
31     windowWidth: 800,
32     windowHeight: 500,
33
34     
35     /**
36      * overwrite update toolbars function (we don't have record grants yet)
37      */
38     updateToolbars: function(record) {
39         this.onTimeaccountUpdate();
40         Tine.Timetracker.TimesheetEditDialog.superclass.updateToolbars.call(this, record, 'timeaccount_id');
41     },
42
43     onRecordLoad: function() {
44         // interrupt process flow until dialog is rendered
45         if (! this.rendered) {
46             this.onRecordLoad.defer(250, this);
47             return;
48         }
49
50         if (! this.record.id) {
51             // @todo: this should be handled by default values
52             this.record.set('account_id', Tine.Tinebase.registry.get('currentAccount'));
53             this.record.set('start_date', new Date());
54         }
55
56         Tine.Timetracker.TimesheetEditDialog.superclass.onRecordLoad.call(this);
57
58         // TODO get timeaccount from filter if set
59         var timeaccount = this.record.get('timeaccount_id');
60         if (timeaccount) {
61             this.onTimeaccountUpdate(null, new Tine.Timetracker.Model.Timeaccount(timeaccount));
62         }
63     },
64
65     /**
66      * this gets called when initializing and if a new timeaccount is chosen
67      * 
68      * @param {} field
69      * @param {} timeaccount
70      */
71     onTimeaccountUpdate: function(field, timeaccount) {
72         // check for manage_timeaccounts right
73         var manageRight = Tine.Tinebase.common.hasRight('manage', 'Timetracker', 'timeaccounts');
74         
75         var notBillable = false;
76         var notClearable = false;
77
78         // TODO timeaccount.get('account_grants') contains [Object object] -> why is that so? this should be fixed
79         var grants = this.record.get('timeaccount_id')
80             ? this.record.get('timeaccount_id').account_grants
81             : (timeaccount && timeaccount.get('container_id') && timeaccount.get('container_id').account_grants
82                 ? timeaccount.get('container_id').account_grants
83                 :  {});
84         
85         if (grants) {
86             var setDisabled = ! (grants.bookAllGrant || grants.adminGrant || manageRight);
87             var accountField = this.getForm().findField('account_id');
88             accountField.setDisabled(setDisabled);
89             // set account id to the current user, if he doesn't have the right to edit other users timesheets
90             if (setDisabled) {
91                 if (this.copyRecord && (this.record.get('account_id') != Tine.Tinebase.registry.get('currentAccount').accountId)) {
92                     accountField.setValue(Tine.Tinebase.registry.get('currentAccount'));
93                 }
94             }
95             notBillable = ! (grants.manageBillableGrant || grants.adminGrant || manageRight);
96             notClearable = ! (grants.adminGrant || manageRight);
97             this.getForm().findField('billed_in').setDisabled(! (grants.adminGrant || manageRight));
98         }
99
100         if (timeaccount && timeaccount.data) {
101             notBillable = notBillable || timeaccount.data.is_billable == "0" || timeaccount.get('is_billable') == "0";
102             
103             // clearable depends on timeaccount is_billable as well (changed by ps / 2009-09-01, behaviour was inconsistent)
104             notClearable = notClearable || timeaccount.data.is_billable == "0" || timeaccount.get('is_billable') == "0";
105
106             if (timeaccount.data.is_billable == "0" || timeaccount.get('is_billable') == "0") {
107                 this.getForm().findField('is_billable').setValue(false);
108             }
109         }
110
111         this.getForm().findField('is_billable').setDisabled(notBillable);
112         this.getForm().findField('is_cleared').setDisabled(notClearable);
113         
114         if (this.record.id == 0 && timeaccount) {
115             // set is_billable for new records according to the timeaccount setting
116             this.getForm().findField('is_billable').setValue(timeaccount.data.is_billable);
117         }
118     },
119     
120     /**
121      * Always set is_billable if timeaccount is billable. This is needed for copied sheets where the
122      * original is set to not billable
123      */
124     onAfterRecordLoad: function() {
125         Tine.Timetracker.TimesheetEditDialog.superclass.onAfterRecordLoad.call(this);
126         if (this.record.id == 0 && this.record.get('timeaccount_id') && this.record.get('timeaccount_id').is_billable) {
127             this.getForm().findField('is_billable').setValue(this.record.get('timeaccount_id').is_billable);
128         }
129
130         var focusFieldName = this.record.get('timeaccount_id') ? 'duration' : 'timeaccount_id',
131             focusField = this.getForm().findField(focusFieldName);
132
133         focusField.focus(true, 250);
134     },
135
136     /**
137      * this gets called when initializing and if cleared checkbox is changed
138      *
139      * @param {} field
140      * @param {} newValue
141      *
142      * @todo    add prompt later?
143      */
144     onClearedUpdate: function(field, checked) {
145         if (!this.useMultiple) {
146             this.getForm().findField('billed_in').setDisabled(! checked);
147         }
148     },
149
150     initComponent: function() {
151         var salesApp = Tine.Tinebase.appMgr.get('Sales');
152         this.useInvoice = Tine.Tinebase.appMgr.get('Sales')
153             && salesApp.featureEnabled('invoicesModule')
154             && Tine.Tinebase.common.hasRight('manage', 'Sales', 'invoices')
155             && Tine.Sales.Model.Invoice;
156         
157         Tine.Timetracker.TimesheetEditDialog.superclass.initComponent.call(this);
158     },
159
160     /**
161      * overwrites the isValid method on multipleEdit
162      */
163     isMultipleValid: function() {
164         var valid = true;
165         var keys = ['timeaccount_id', 'description', 'account_id'];
166         Ext.each(keys, function(key) {
167             var field = this.getForm().findField(key);
168             if(field.edited && ! field.validate()) {
169                 field.markInvalid();
170                 valid = false;
171             }
172         }, this);
173         return valid;
174     },
175
176     /**
177      * returns dialog
178      * 
179      * NOTE: when this method gets called, all initialization is done.
180      */
181     getFormItems: function() {
182         var lastRow = [new Tine.Addressbook.SearchCombo({
183             allowBlank: false,
184             forceSelection: true,
185             columnWidth: 1,
186             disabled: true,
187             useAccountRecord: true,
188             userOnly: true,
189             nameField: 'n_fileas',
190             fieldLabel: this.app.i18n._('Account'),
191             name: 'account_id'
192         }), {
193             columnWidth: .25,
194             disabled: (this.useMultiple) ? false : true,
195             boxLabel: this.app.i18n._('Billable'),
196             name: 'is_billable',
197             xtype: 'checkbox'
198         }, {
199             columnWidth: .25,
200             disabled: (this.useMultiple) ? false : true,
201             boxLabel: this.app.i18n._('Cleared'),
202             name: 'is_cleared',
203             xtype: 'checkbox',
204             listeners: {
205                 scope: this,
206                 check: this.onClearedUpdate
207             }
208         }, {
209                 columnWidth: .5,
210                 disabled: true,
211                 fieldLabel: this.app.i18n._('Cleared In'),
212                 name: 'billed_in'
213             }];
214         
215         if (this.useInvoice) {
216             lastRow.push(Tine.widgets.form.RecordPickerManager.get('Sales', 'Invoice', {
217                 columnWidth: .5,
218                 disabled: true,
219                 fieldLabel: this.app.i18n._('Invoice'),
220                 name: 'invoice_id'
221             }));
222         }
223         
224         
225         return {
226             xtype: 'tabpanel',
227             border: false,
228             plain:true,
229             activeTab: 0,
230             plugins: [{
231                 ptype : 'ux.tabpanelkeyplugin'
232             }],
233             defaults: {
234                 hideMode: 'offsets'
235             },
236             items:[
237                 {
238                 title: this.app.i18n.ngettext('Timesheet', 'Timesheets', 1),
239                 autoScroll: true,
240                 border: false,
241                 frame: true,
242                 layout: 'border',
243                 items: [{
244                     region: 'center',
245                     xtype: 'columnform',
246                     labelAlign: 'top',
247                     formDefaults: {
248                         xtype:'textfield',
249                         anchor: '100%',
250                         labelSeparator: '',
251                         columnWidth: .333
252                     },
253                     items: [[Tine.widgets.form.RecordPickerManager.get('Timetracker', 'Timeaccount', {
254                         columnWidth: 1,
255                         fieldLabel: this.app.i18n.ngettext('Time Account', 'Time Accounts', 1),
256                         emptyText: this.app.i18n._('Select Time Account...'),
257                         allowBlank: false,
258                         forceSelection: true,
259                         name: 'timeaccount_id',
260                         lazyInit: false
261                     })], [{
262                         fieldLabel: this.app.i18n._('Duration'),
263                         name: 'duration',
264                         selectOnFocus: true,
265                         allowBlank: false,
266                         xtype: 'tinedurationspinner'
267                         }, {
268                         fieldLabel: this.app.i18n._('Date'),
269                         name: 'start_date',
270                         allowBlank: false,
271                         xtype: 'datefield'
272                         }, {
273                         fieldLabel: this.app.i18n._('Start'),
274                         emptyText: this.app.i18n._('not set'),
275                         name: 'start_time',
276                         xtype: 'timefield'
277                     }], [{
278                         columnWidth: 1,
279                         fieldLabel: this.app.i18n._('Description'),
280                         emptyText: this.app.i18n._('Enter description...'),
281                         name: 'description',
282                         allowBlank: false,
283                         xtype: 'textarea',
284                         height: 150
285                     }], lastRow] 
286                 }, {
287                     // activities and tags
288                     layout: 'accordion',
289                     animate: true,
290                     region: 'east',
291                     width: 210,
292                     split: true,
293                     collapsible: true,
294                     collapseMode: 'mini',
295                     header: false,
296                     margins: '0 5 0 5',
297                     border: true,
298                     items: [
299                         new Tine.widgets.tags.TagPanel({
300                             app: 'Timetracker',
301                             border: false,
302                             bodyStyle: 'border:1px solid #B5B8C8;'
303                         })
304                     ]
305                 }]
306             }, new Tine.widgets.activities.ActivitiesTabPanel({
307                 app: this.appName,
308                 record_id: (! this.copyRecord) ? this.record.id : null,
309                 record_model: this.appName + '_Model_' + this.recordClass.getMeta('modelName')
310             })]
311         };
312     },
313     
314     /**
315      * returns additional save params
316      *
317      * @returns {{checkBusyConflicts: boolean}}
318      */
319     getAdditionalSaveParams: function() {
320         return {
321             context: this.context
322         };
323     },
324     
325     /**
326      * show error if request fails
327      * 
328      * @param {} response
329      * @param {} request
330      * @private
331      */
332     onRequestFailed: function(response, request) {
333         this.saving = false;
334         
335         if (response.code && response.code == 902) {
336             // deadline exception
337             Ext.MessageBox.alert(
338                 this.app.i18n._('Failed'), 
339                 String.format(this.app.i18n._('Could not save {0}.'), this.i18nRecordName) 
340                     + ' ( ' + this.app.i18n._('Booking deadline for this Timeaccount has been exceeded.') /* + ' ' + response.message  */ + ')'
341             );
342         } else if (response.code && response.code == 444) {
343             //Time Account is closed
344             if(Tine.Tinebase.common.hasRight('manage', 'Timetracker', 'timeaccounts')) {
345                 this.onClosedWarning.apply(this, arguments);
346             } else {
347                 Ext.MessageBox.alert(
348                     this.app.i18n._('Closed Timeaccount Warning!'), 
349                     String.format(this.app.i18n._('The selected Time Account is already closed.'))
350                 );
351             }
352         } else {
353             // call default exception handler
354             Tine.Tinebase.ExceptionHandler.handleRequestException(response);
355         }
356         this.loadMask.hide();
357     },
358     
359     onClosedWarning: function() {
360         Ext.Msg.confirm(this.app.i18n._('Closed Timeaccount Warning!'),
361             this.app.i18n._('The selected Time Account is already closed. Do you wish to continue anyway?'),
362             function(btn) {
363                 if (btn == 'yes') {
364                     this.context = { 'skipClosedCheck': true };
365                     this.onApplyChanges(true);
366                 }
367             }, this);
368     }
369 });