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