0013286: Timesheet: select duration text on focus
[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) {
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         
107         this.getForm().findField('is_billable').setDisabled(notBillable);
108         this.getForm().findField('is_cleared').setDisabled(notClearable);
109         
110         if (this.record.id == 0 && timeaccount) {
111             // set is_billable for new records according to the timeaccount setting
112             this.getForm().findField('is_billable').setValue(timeaccount.data.is_billable);
113         }
114     },
115     
116     /**
117      * Always set is_billable if timeaccount is billable. This is needed for copied sheets where the
118      * original is set to not billable
119      */
120     onAfterRecordLoad: function() {
121         Tine.Timetracker.TimesheetEditDialog.superclass.onAfterRecordLoad.call(this);
122         if (this.record.id == 0 && this.record.get('timeaccount_id') && this.record.get('timeaccount_id').is_billable) {
123             this.getForm().findField('is_billable').setValue(this.record.get('timeaccount_id').is_billable);
124         }
125
126         var focusFieldName = this.record.get('timeaccount_id') ? 'duration' : 'timeaccount_id',
127             focusField = this.getForm().findField(focusFieldName);
128
129         focusField.focus(true, 250);
130     },
131
132     /**
133      * this gets called when initializing and if cleared checkbox is changed
134      *
135      * @param {} field
136      * @param {} newValue
137      *
138      * @todo    add prompt later?
139      */
140     onClearedUpdate: function(field, checked) {
141         if (!this.useMultiple) {
142             this.getForm().findField('billed_in').setDisabled(! checked);
143         }
144     },
145
146     initComponent: function() {
147         var salesApp = Tine.Tinebase.appMgr.get('Sales');
148         this.useInvoice = Tine.Tinebase.appMgr.get('Sales')
149             && salesApp.featureEnabled('invoicesModule')
150             && Tine.Tinebase.common.hasRight('manage', 'Sales', 'invoices')
151             && Tine.Sales.Model.Invoice;
152         
153         Tine.Timetracker.TimesheetEditDialog.superclass.initComponent.call(this);
154     },
155
156     /**
157      * overwrites the isValid method on multipleEdit
158      */
159     isMultipleValid: function() {
160         var valid = true;
161         var keys = ['timeaccount_id', 'description', 'account_id'];
162         Ext.each(keys, function(key) {
163             var field = this.getForm().findField(key);
164             if(field.edited && ! field.validate()) {
165                 field.markInvalid();
166                 valid = false;
167             }
168         }, this);
169         return valid;
170     },
171
172     /**
173      * returns dialog
174      * 
175      * NOTE: when this method gets called, all initialization is done.
176      */
177     getFormItems: function() {
178         var lastRow = [new Tine.Addressbook.SearchCombo({
179             allowBlank: false,
180             forceSelection: true,
181             columnWidth: 1,
182             disabled: true,
183             useAccountRecord: true,
184             userOnly: true,
185             nameField: 'n_fileas',
186             fieldLabel: this.app.i18n._('Account'),
187             name: 'account_id'
188         }), {
189             columnWidth: .25,
190             disabled: (this.useMultiple) ? false : true,
191             boxLabel: this.app.i18n._('Billable'),
192             name: 'is_billable',
193             xtype: 'checkbox'
194         }, {
195             columnWidth: .25,
196             disabled: (this.useMultiple) ? false : true,
197             boxLabel: this.app.i18n._('Cleared'),
198             name: 'is_cleared',
199             xtype: 'checkbox',
200             listeners: {
201                 scope: this,
202                 check: this.onClearedUpdate
203             }
204         }, {
205                 columnWidth: .5,
206                 disabled: true,
207                 fieldLabel: this.app.i18n._('Cleared In'),
208                 name: 'billed_in'
209             }];
210         
211         if (this.useInvoice) {
212             lastRow.push(Tine.widgets.form.RecordPickerManager.get('Sales', 'Invoice', {
213                 columnWidth: .5,
214                 disabled: true,
215                 fieldLabel: this.app.i18n._('Invoice'),
216                 name: 'invoice_id'
217             }));
218         }
219         
220         
221         return {
222             xtype: 'tabpanel',
223             border: false,
224             plain:true,
225             activeTab: 0,
226             plugins: [{
227                 ptype : 'ux.tabpanelkeyplugin'
228             }],
229             defaults: {
230                 hideMode: 'offsets'
231             },
232             items:[
233                 {
234                 title: this.app.i18n.ngettext('Timesheet', 'Timesheets', 1),
235                 autoScroll: true,
236                 border: false,
237                 frame: true,
238                 layout: 'border',
239                 items: [{
240                     region: 'center',
241                     xtype: 'columnform',
242                     labelAlign: 'top',
243                     formDefaults: {
244                         xtype:'textfield',
245                         anchor: '100%',
246                         labelSeparator: '',
247                         columnWidth: .333
248                     },
249                     items: [[Tine.widgets.form.RecordPickerManager.get('Timetracker', 'Timeaccount', {
250                         columnWidth: 1,
251                         fieldLabel: this.app.i18n.ngettext('Time Account', 'Time Accounts', 1),
252                         emptyText: this.app.i18n._('Select Time Account...'),
253                         allowBlank: false,
254                         forceSelection: true,
255                         name: 'timeaccount_id',
256                         lazyInit: false
257                     })], [{
258                         fieldLabel: this.app.i18n._('Duration'),
259                         name: 'duration',
260                         selectOnFocus: true,
261                         allowBlank: false,
262                         xtype: 'tinedurationspinner'
263                         }, {
264                         fieldLabel: this.app.i18n._('Date'),
265                         name: 'start_date',
266                         allowBlank: false,
267                         xtype: 'datefield'
268                         }, {
269                         fieldLabel: this.app.i18n._('Start'),
270                         emptyText: this.app.i18n._('not set'),
271                         name: 'start_time',
272                         xtype: 'timefield'
273                     }], [{
274                         columnWidth: 1,
275                         fieldLabel: this.app.i18n._('Description'),
276                         emptyText: this.app.i18n._('Enter description...'),
277                         name: 'description',
278                         allowBlank: false,
279                         xtype: 'textarea',
280                         height: 150
281                     }], lastRow] 
282                 }, {
283                     // activities and tags
284                     layout: 'accordion',
285                     animate: true,
286                     region: 'east',
287                     width: 210,
288                     split: true,
289                     collapsible: true,
290                     collapseMode: 'mini',
291                     header: false,
292                     margins: '0 5 0 5',
293                     border: true,
294                     items: [
295                         new Tine.widgets.tags.TagPanel({
296                             app: 'Timetracker',
297                             border: false,
298                             bodyStyle: 'border:1px solid #B5B8C8;'
299                         })
300                     ]
301                 }]
302             }, new Tine.widgets.activities.ActivitiesTabPanel({
303                 app: this.appName,
304                 record_id: (! this.copyRecord) ? this.record.id : null,
305                 record_model: this.appName + '_Model_' + this.recordClass.getMeta('modelName')
306             })]
307         };
308     },
309     
310     /**
311      * returns additional save params
312      *
313      * @returns {{checkBusyConflicts: boolean}}
314      */
315     getAdditionalSaveParams: function() {
316         return {
317             context: this.context
318         };
319     },
320     
321     /**
322      * show error if request fails
323      * 
324      * @param {} response
325      * @param {} request
326      * @private
327      */
328     onRequestFailed: function(response, request) {
329         this.saving = false;
330         
331         if (response.code && response.code == 902) {
332             // deadline exception
333             Ext.MessageBox.alert(
334                 this.app.i18n._('Failed'), 
335                 String.format(this.app.i18n._('Could not save {0}.'), this.i18nRecordName) 
336                     + ' ( ' + this.app.i18n._('Booking deadline for this Timeaccount has been exceeded.') /* + ' ' + response.message  */ + ')'
337             );
338         } else if (response.code && response.code == 444) {
339             //Time Account is closed
340             if(Tine.Tinebase.common.hasRight('manage', 'Timetracker', 'timeaccounts')) {
341                 this.onClosedWarning.apply(this, arguments);
342             } else {
343                 Ext.MessageBox.alert(
344                     this.app.i18n._('Closed Timeaccount Warning!'), 
345                     String.format(this.app.i18n._('The selected Time Account is already closed.'))
346                 );
347             }
348         } else {
349             // call default exception handler
350             Tine.Tinebase.ExceptionHandler.handleRequestException(response);
351         }
352         this.loadMask.hide();
353     },
354     
355     onClosedWarning: function() {
356         Ext.Msg.confirm(this.app.i18n._('Closed Timeaccount Warning!'),
357             this.app.i18n._('The selected Time Account is already closed. Do you wish to continue anyway?'),
358             function(btn) {
359                 if (btn == 'yes') {
360                     this.context = { 'skipClosedCheck': true };
361                     this.onApplyChanges(true);
362                 }
363             }, this);
364     }
365 });