Merge branch '2014.11-develop' into 2014.09
[tine20] / tine20 / Crm / js / LeadEditDialog.js
1 /*
2  * Tine 2.0
3  * 
4  * @package     Crm
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.Crm');
12
13 /**
14  * @namespace   Tine.Crm
15  * @class       Tine.Crm.LeadEditDialog
16  * @extends     Tine.widgets.dialog.EditDialog
17  * 
18  * <p>Lead Edit Dialog</p>
19  * <p>
20  * TODO         simplify relation handling (move init of stores to relation grids and get data from there later?)
21  * TODO         make marking of invalid fields work again
22  * TODO         add export button
23  * TODO         disable link grids if user has no run right for the app (adb/tasks/sales)
24  * </p>
25  * 
26  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
27  * 
28  * @param       {Object} config
29  * @constructor
30  * Create a new Tine.Crm.LeadEditDialog
31  */
32 Tine.Crm.LeadEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
33     
34     /**
35      * linked contacts grid
36      * 
37      * @type Tine.Crm.Contact.GridPanel
38      * @property contactGrid
39      */
40     contactGrid: null,
41     
42     /**
43      * linked tasks grid
44      * 
45      * @type Tine.Crm.Task.GridPanel
46      * @property tasksGrid
47      */
48     tasksGrid: null,
49     
50     /**
51      * @private
52      */
53     windowNamePrefix: 'LeadEditWindow_',
54     appName: 'Crm',
55     recordClass: Tine.Crm.Model.Lead,
56     recordProxy: Tine.Crm.leadBackend,
57     showContainerSelector: true,
58     displayNotes: true,
59
60     /**
61      * ignore these models in relation grid
62      * @type {Array}
63      */
64     ignoreRelatedModels: ['Sales_Model_Product', 'Addressbook_Model_Contact', 'Tasks_Model_Task'],
65     
66     /**
67      * executed after record got updated from proxy
68      * 
69      * @private
70      */
71     onAfterRecordLoad: function() {
72         Tine.Crm.LeadEditDialog.superclass.onAfterRecordLoad.call(this);
73         // load contacts/tasks/products into link grid (only first time this function gets called/store is empty)
74         if (this.contactGrid && this.tasksGrid && this.productsGrid 
75             && this.contactGrid.store.getCount() == 0 
76             && (! this.tasksGrid.store || this.tasksGrid.store.getCount() == 0) 
77             && (! this.productsGrid.store || this.productsGrid.store.getCount() == 0)) {
78             
79             var relations = this.splitRelations();
80             
81             this.contactGrid.store.loadData(relations.contacts, true);
82             
83             if (this.tasksGrid.store) {
84                 this.tasksGrid.store.loadData(relations.tasks, true);
85             }
86             if (this.productsGrid.store) {
87                 this.productsGrid.store.loadData(relations.products, true);
88             }
89         }
90     },
91
92     /**
93      * is called from onApplyChanges
94      * @param {Boolean} closeWindow
95      */
96     doApplyChanges: function(closeWindow) {
97         this.getAdditionalData();
98         
99         var relations = [].concat(this.record.get('relations'));
100         this.record.data.relations = relations;
101         
102         Tine.Crm.LeadEditDialog.superclass.doApplyChanges.call(this, closeWindow);
103     },
104     
105     /**
106      * getRelationData
107      * get the record relation data (switch relation and related record)
108      * 
109      * @param   Object record with relation data
110      * @return  Object relation with record data
111      */
112     getRelationData: function(record) {
113         var relation = null;
114         
115         if (record.data.relation) {
116             relation = record.data.relation;
117         } else {
118             // empty relation for new record
119             relation = {};
120         }
121
122         // set the relation type
123         if (!relation.type) {
124             relation.type = record.data.relation_type.toUpperCase();
125         }
126         
127         // do not do recursion!
128         record.data.relation = null;
129         delete record.data.relation;
130         
131         // save record data
132         relation.related_record = record.data;
133         
134         // add remark values
135         relation.remark = {};
136         if (record.data.remark_price) {
137             relation.remark.price = record.data.remark_price;
138         }
139         if (record.data.remark_description) {
140             relation.remark.description = record.data.remark_description;
141         }
142         if (record.data.remark_quantity) {
143             relation.remark.quantity = record.data.remark_quantity;
144         }
145         
146         Tine.log.debug('Tine.Crm.LeadEditDialog::getRelationData() -> relation:');
147         Tine.log.debug(relation);
148         
149         return relation;
150     },
151
152     /**
153      * getAdditionalData
154      * collects additional data (start/end dates, linked contacts, ...)
155      */
156     getAdditionalData: function() {
157         var relations = this.record.get('relations'),
158             grids = [this.contactGrid, this.tasksGrid, this.productsGrid];
159             
160         Ext.each(grids, function(grid) {
161             if (grid.store) {
162                 grid.store.each(function(record) {
163                     relations.push(this.getRelationData(record.copy()));
164                 }, this);
165             }
166         }, this);
167         
168         this.record.data.relations = relations;
169     },
170     
171     /**
172      * split the relations array in contacts and tasks and switch related_record and relation objects
173      * 
174      * @return {Array}
175      */
176     splitRelations: function() {
177         
178         var contacts = [],
179             tasks = [],
180             products = [];
181         
182         var relations = this.record.get('relations');
183         
184         for (var i=0; i < relations.length; i++) {
185             var newLinkObject = relations[i]['related_record'];
186             relations[i]['related_record']['relation'] = null;
187             delete relations[i]['related_record']['relation'];
188             newLinkObject.relation = relations[i];
189             newLinkObject.relation_type = relations[i]['type'].toLowerCase();
190             
191             if ((newLinkObject.relation_type === 'responsible' 
192               || newLinkObject.relation_type === 'customer' 
193               || newLinkObject.relation_type === 'partner')) {
194                 contacts.push(newLinkObject);
195             } else if (newLinkObject.relation_type === 'task') {
196                 tasks.push(newLinkObject);
197             } else if (newLinkObject.relation_type === 'product') {
198                 newLinkObject.remark_description = (relations[i].remark) ? relations[i].remark.description : '';
199                 newLinkObject.remark_price = (relations[i].remark) ? relations[i].remark.price : 0;
200                 newLinkObject.remark_quantity = (relations[i].remark) ? relations[i].remark.quantity : 1;
201                 products.push(newLinkObject);
202             }
203         }
204         
205         return {
206             contacts: contacts,
207             tasks: tasks,
208             products: products
209         };
210     },
211
212     /**
213      * returns dialog
214      * 
215      * NOTE: when this method gets called, all initalisation is done.
216      * 
217      * @return {Object}
218      * @private
219      */
220     getFormItems: function() {
221         
222         this.combo_probability = new Ext.ux.PercentCombo({
223             fieldLabel: this.app.i18n._('Probability'), 
224             id: 'combo_probability',
225             anchor:'95%',
226             name:'probability'
227         });
228         
229         this.date_end = new Ext.ux.form.ClearableDateField({
230             fieldLabel: this.app.i18n._('End'), 
231             id: 'end',
232             anchor: '95%'
233         });
234         
235         this.contactGrid = new Tine.Crm.Contact.GridPanel({
236             record: this.record,
237             anchor: '100% 98%'
238         });
239
240         if (Tine.Tasks && Tine.Tinebase.common.hasRight('run', 'Tasks')) {
241             this.tasksGrid = new Tine.Crm.Task.GridPanel({
242                 record: this.record
243             });
244         } else {
245             this.tasksGrid = new Ext.Panel({
246                 title: this.app.i18n._('Tasks'),
247                 html: this.app.i18n._('You do not have the run right for the Tasks application or it is not activated.')
248             })
249         }
250         
251         if (Tine.Sales && Tine.Tinebase.common.hasRight('run', 'Sales')) {
252             this.productsGrid = new Tine.Crm.Product.GridPanel({
253                 record: this.record
254             });
255         } else {
256             this.productsGrid = new Ext.Panel({
257                 title: this.app.i18n._('Products'),
258                 html: this.app.i18n._('You do not have the run right for the Sales application or it is not activated.')
259             })
260         }
261
262         // Don't show item if it's a archived source!
263         var sourceStore = Tine.Crm.LeadSource.getStore();
264
265         var preserveRecords = [];
266
267         sourceStore.each(function(record) {
268             preserveRecords.push(record.copy());
269         });
270
271         var copiedStore = new Ext.data.Store({
272             recordType: sourceStore.recordType
273         });
274
275         copiedStore.add(preserveRecords);
276
277         sourceStore.each(function(item) {
278             if (item.get('archived') == true) {
279                 sourceStore.remove(item);
280             }
281         });
282
283         var setdeffered = function (combo) {
284             var rawValue = parseInt(combo.getRawValue());
285
286             if (Ext.isNumber(rawValue)) {
287                 combo.setRawValue(copiedStore.getById(combo.getValue()).get('leadsource'));
288             }
289         };
290
291         return {
292             xtype: 'tabpanel',
293             border: false,
294             plain:true,
295             plugins: [{
296                 ptype : 'ux.tabpanelkeyplugin'
297             }],
298             defaults: {
299                 hideMode: 'offsets'
300             },
301             activeTab: 0,
302             border: false,
303             items:[{
304                 title: this.app.i18n._('Lead'),
305                 autoScroll: true,
306                 border: true,
307                 frame: true,
308                 layout: 'border',
309                 id: 'editCenterPanel',
310                 defaults: {
311                     border: true,
312                     frame: true
313                 },
314                 items: [{
315                     region: 'center',
316                     layout: 'border',
317                     items: [{
318                         region: 'north',
319                         height: 40,
320                         layout: 'form',
321                         labelAlign: 'top',
322                         defaults: {
323                             anchor: '100%',
324                             labelSeparator: '',
325                             columnWidth: 1
326                         },
327                         items: [{
328                             xtype: 'textfield',
329                             hideLabel: true,
330                             id: 'lead_name',
331                             emptyText: this.app.i18n._('Enter short name'),
332                             name: 'lead_name',
333                             allowBlank: false,
334                             selectOnFocus: true,
335                             maxLength: 255,
336                             // TODO make this work
337                             listeners: {render: function(field){field.focus(false, 2000);}}
338                         }]
339                     }, {
340                         region: 'center',
341                         layout: 'form',
342                         items: [ this.contactGrid ]
343                     }, {
344                         region: 'south',
345                         height: 390,
346                         split: true,
347                         collapseMode: 'mini',
348                         header: false,
349                         collapsible: true,
350                         items: [{
351                             xtype: 'panel',
352                             layout:'column',
353                             height: 140,
354                             id: 'lead_combos',
355                             anchor:'100%',
356                             labelAlign: 'top',
357                             items: [{
358                                 columnWidth: 0.33,
359                                 items:[{
360                                     layout: 'form',
361                                     defaults: {
362                                         valueField:'id',
363                                         typeAhead: true,
364                                         mode: 'local',
365                                         triggerAction: 'all',
366                                         editable: false,
367                                         allowBlank: false,
368                                         forceSelection: true,
369                                         anchor:'95%',
370                                         xtype: 'combo'
371                                     },
372                                     items: [{
373                                         fieldLabel: this.app.i18n._('Leadstate'), 
374                                         id:'leadstatus',
375                                         name:'leadstate_id',
376                                         store: Tine.Crm.LeadState.getStore(),
377                                         displayField:'leadstate',
378                                         lazyInit: false,
379                                         value: (Tine.Crm.LeadState.getStore().getCount() > 0) ? Tine.Crm.LeadState.getStore().getAt(0).id : null,
380                                         listeners: {
381                                             'select': function(combo, record, index) {
382                                                 if (this.record.data.probability !== null) {
383                                                     this.combo_probability.setValue(record.data.probability);
384                                                 }
385                                                 if (record.data.endslead == '1') {
386                                                     this.date_end.setValue(new Date());
387                                                 }
388                                             },
389                                             scope: this
390                                         }
391                                     }, {
392                                         fieldLabel: this.app.i18n._('Leadtype'), 
393                                         id:'leadtype',
394                                         name:'leadtype_id',
395                                         store: Tine.Crm.LeadType.getStore(),
396                                         value: (Tine.Crm.LeadType.getStore().getCount() > 0) ? Tine.Crm.LeadType.getStore().getAt(0).id : null,
397                                         displayField:'leadtype'
398                                     }, {
399                                         fieldLabel: this.app.i18n._('Leadsource'), 
400                                         id:'leadsource',
401                                         name:'leadsource_id',
402                                         store: sourceStore,
403                                         displayField:'leadsource',
404                                         value: (sourceStore.getCount() > 0) ? sourceStore.getAt(0).id : null,
405                                         listeners: {
406                                             scope: this,
407                                             // When loading
408                                             'beforerender': function (combo) {
409                                                 setdeffered.defer(5, this, [combo]);
410                                             },
411                                             // When focus changed
412                                             'blur': function(combo) {
413                                                 setdeffered.defer(5, this, [combo]);
414                                             }
415                                         }
416                                     }]
417                                 }]
418                             }, {
419                                 columnWidth: 0.33,
420                                 items:[{
421                                     layout: 'form',
422                                     border:false,
423                                     items: [
424                                     {
425                                         xtype:'extuxnumberfield',
426                                         suffix: ' €',
427                                         fieldLabel: this.app.i18n._('Expected turnover'), 
428                                         name: 'turnover',
429                                         selectOnFocus: true,
430                                         anchor: '95%',
431                                         minValue: 0,
432                                         decimalSeparator: Tine.Tinebase.registry.get('decimalSeparator')
433                                     },  
434                                         this.combo_probability,
435                                         new Ext.ux.form.ClearableDateField({
436                                             fieldLabel: this.app.i18n._('Resubmission Date'), 
437                                             id: 'resubmission_date',
438                                             anchor: '95%'
439                                         })
440                                     ]
441                                 }]
442                             }, {
443                                 columnWidth: 0.33,
444                                 items:[{
445                                     layout: 'form',
446                                     border:false,
447                                     items: [
448                                         new Ext.form.DateField({
449                                             fieldLabel: this.app.i18n._('Start'), 
450                                             allowBlank: false,
451                                             id: 'start',
452                                             anchor: '95%'
453                                         }),
454                                         new Ext.ux.form.ClearableDateField({
455                                             fieldLabel: this.app.i18n._('Estimated end'), 
456                                             id: 'end_scheduled',
457                                             anchor: '95%'
458                                         }),
459                                         this.date_end   
460                                     ]
461                                 }]
462                             }]
463                         }, {
464                             xtype: 'tabpanel',
465                             id: 'linkPanelBottom',
466                             activeTab: 0,
467                             height: 250,
468                             items: [
469                                 this.tasksGrid,
470                                 this.productsGrid
471                             ]
472                         }]
473                     }] // end of center lead panel with border layout
474                     }, {
475                         layout: 'accordion',
476                         animate: true,
477                         region: 'east',
478                         width: 210,
479                         split: true,
480                         collapsible: true,
481                         collapseMode: 'mini',
482                         header: false,
483                         margins: '0 5 0 5',
484                         border: true,
485                         items: [
486                             new Ext.Panel({
487                                 title: this.app.i18n._('Description'),
488                                 iconCls: 'descriptionIcon',
489                                 layout: 'form',
490                                 labelAlign: 'top',
491                                 border: false,
492                                 items: [{
493                                     style: 'margin-top: -4px; border 0px;',
494                                     labelSeparator: '',
495                                     xtype:'textarea',
496                                     name: 'description',
497                                     hideLabel: true,
498                                     grow: false,
499                                     preventScrollbars:false,
500                                     anchor:'100% 100%',
501                                     emptyText: this.app.i18n._('Enter description')
502                                 }]
503                             }),
504                             new Tine.widgets.tags.TagPanel({
505                                 app: 'Crm',
506                                 border: false,
507                                 bodyStyle: 'border:1px solid #B5B8C8;'
508                             })
509                         ]} // end of accordion panel (east)
510                     ] // end of lead tabpanel items
511             }, new Tine.widgets.activities.ActivitiesTabPanel({
512                 app: this.appName,
513                 record_id: this.record.id,
514                 record_model: this.appName + '_Model_' + this.recordClass.getMeta('modelName')
515             })] // end of main tabpanel items
516         }; // end of return
517     } // end of getFormItems
518 });
519
520 /**
521  * Crm Edit Popup
522  * 
523  * @param   {Object} config
524  * @return  {Ext.ux.Window}
525  */
526 Tine.Crm.LeadEditDialog.openWindow = function (config) {
527     var id = (config.record && config.record.id) ? config.record.id : 0;
528     var window = Tine.WindowFactory.getWindow({
529         width: 800,
530         height: 750,
531         name: Tine.Crm.LeadEditDialog.prototype.windowNamePrefix + id,
532         contentPanelConstructor: 'Tine.Crm.LeadEditDialog',
533         contentPanelConstructorConfig: config
534     });
535     return window;
536 };