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