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