Merge pull request #12031 from mukeshcompucorp/fix-template-structure-issues
[civicrm-core.git] / ang / crmCaseType.js
1 (function(angular, $, _) {
2
3 var crmCaseType = angular.module('crmCaseType', CRM.angRequires('crmCaseType'));
4
5 // Note: This template will be passed to cloneDeep(), so don't put any funny stuff in here!
6 var newCaseTypeTemplate = {
7 title: "",
8 name: "",
9 is_active: "1",
10 weight: "1",
11 definition: {
12 activityTypes: [
13 {name: 'Open Case', max_instances: 1},
14 {name: 'Email'},
15 {name: 'Follow up'},
16 {name: 'Meeting'},
17 {name: 'Phone Call'}
18 ],
19 activitySets: [
20 {
21 name: 'standard_timeline',
22 label: 'Standard Timeline',
23 timeline: '1', // Angular won't bind checkbox correctly with numeric 1
24 activityTypes: [
25 {name: 'Open Case', status: 'Completed' }
26 ]
27 }
28 ],
29 caseRoles: [
30 { name: 'Case Coordinator', creator: '1', manager: '1'}
31 ]
32 }
33 };
34
35 crmCaseType.config(['$routeProvider',
36 function($routeProvider) {
37 $routeProvider.when('/caseType', {
38 templateUrl: '~/crmCaseType/list.html',
39 controller: 'CaseTypeListCtrl',
40 resolve: {
41 caseTypes: function($route, crmApi) {
42 return crmApi('CaseType', 'get', {options: {limit: 0}});
43 }
44 }
45 });
46 $routeProvider.when('/caseType/:id', {
47 templateUrl: '~/crmCaseType/edit.html',
48 controller: 'CaseTypeCtrl',
49 resolve: {
50 apiCalls: function($route, crmApi) {
51 var reqs = {};
52 reqs.actStatuses = ['OptionValue', 'get', {
53 option_group_id: 'activity_status',
54 sequential: 1,
55 options: {limit: 0}
56 }];
57 reqs.caseStatuses = ['OptionValue', 'get', {
58 option_group_id: 'case_status',
59 sequential: 1,
60 options: {limit: 0}
61 }];
62 reqs.actTypes = ['OptionValue', 'get', {
63 option_group_id: 'activity_type',
64 sequential: 1,
65 options: {
66 sort: 'name',
67 limit: 0
68 }
69 }];
70 reqs.relTypes = ['RelationshipType', 'get', {
71 sequential: 1,
72 options: {
73 sort: CRM.crmCaseType.REL_TYPE_CNAME,
74 limit: 0
75 }
76 }];
77 if ($route.current.params.id !== 'new') {
78 reqs.caseType = ['CaseType', 'getsingle', {
79 id: $route.current.params.id
80 }];
81 }
82 return crmApi(reqs);
83 }
84 }
85 });
86 }
87 ]);
88
89 // Add a new record by name.
90 // Ex: <crmAddName crm-options="['Alpha','Beta','Gamma']" crm-var="newItem" crm-on-add="callMyCreateFunction(newItem)" />
91 crmCaseType.directive('crmAddName', function() {
92 return {
93 restrict: 'AE',
94 template: '<input class="add-activity crm-action-menu fa-plus" type="hidden" />',
95 link: function(scope, element, attrs) {
96
97 var input = $('input', element);
98
99 scope._resetSelection = function() {
100 $(input).select2('close');
101 $(input).select2('val', '');
102 scope[attrs.crmVar] = '';
103 };
104
105 $(input).crmSelect2({
106 data: function () {
107 return { results: scope[attrs.crmOptions] };
108 },
109 createSearchChoice: function(term) {
110 return {id: term, text: term + ' (' + ts('new') + ')'};
111 },
112 createSearchChoicePosition: 'bottom',
113 placeholder: attrs.placeholder
114 });
115 $(input).on('select2-selecting', function(e) {
116 scope[attrs.crmVar] = e.val;
117 scope.$evalAsync(attrs.crmOnAdd);
118 scope.$evalAsync('_resetSelection()');
119 e.preventDefault();
120 });
121 }
122 };
123 });
124
125 crmCaseType.controller('CaseTypeCtrl', function($scope, crmApi, apiCalls) {
126 // CRM_Case_XMLProcessor::REL_TYPE_CNAME
127 var REL_TYPE_CNAME = CRM.crmCaseType.REL_TYPE_CNAME,
128
129 ts = $scope.ts = CRM.ts(null);
130
131 $scope.activityStatuses = apiCalls.actStatuses.values;
132 $scope.caseStatuses = _.indexBy(apiCalls.caseStatuses.values, 'name');
133 $scope.activityTypes = _.indexBy(apiCalls.actTypes.values, 'name');
134 $scope.activityTypeOptions = _.map(apiCalls.actTypes.values, formatActivityTypeOption);
135 $scope.relationshipTypeOptions = _.map(apiCalls.relTypes.values, function(type) {
136 return {id: type[REL_TYPE_CNAME], text: type.label_b_a};
137 });
138 $scope.locks = {caseTypeName: true, activitySetName: true};
139
140 $scope.workflows = {
141 'timeline': 'Timeline',
142 'sequence': 'Sequence'
143 };
144
145 $scope.caseType = apiCalls.caseType ? apiCalls.caseType : _.cloneDeep(newCaseTypeTemplate);
146 $scope.caseType.definition = $scope.caseType.definition || [];
147 $scope.caseType.definition.activityTypes = $scope.caseType.definition.activityTypes || [];
148 $scope.caseType.definition.activitySets = $scope.caseType.definition.activitySets || [];
149 _.each($scope.caseType.definition.activitySets, function (set) {
150 _.each(set.activityTypes, function (type, name) {
151 type.label = $scope.activityTypes[type.name].label;
152 });
153 });
154 $scope.caseType.definition.caseRoles = $scope.caseType.definition.caseRoles || [];
155 $scope.caseType.definition.statuses = $scope.caseType.definition.statuses || [];
156
157 $scope.selectedStatuses = {};
158 _.each(apiCalls.caseStatuses.values, function (status) {
159 $scope.selectedStatuses[status.name] = !$scope.caseType.definition.statuses.length || $scope.caseType.definition.statuses.indexOf(status.name) > -1;
160 });
161
162 $scope.addActivitySet = function(workflow) {
163 var activitySet = {};
164 activitySet[workflow] = '1';
165 activitySet.activityTypes = [];
166
167 var offset = 1;
168 var names = _.pluck($scope.caseType.definition.activitySets, 'name');
169 while (_.contains(names, workflow + '_' + offset)) offset++;
170 activitySet.name = workflow + '_' + offset;
171 activitySet.label = (offset == 1 ) ? $scope.workflows[workflow] : ($scope.workflows[workflow] + ' #' + offset);
172
173 $scope.caseType.definition.activitySets.push(activitySet);
174 _.defer(function() {
175 $('.crmCaseType-acttab').tabs('refresh').tabs({active: -1});
176 });
177 };
178
179 function formatActivityTypeOption(type) {
180 return {id: type.name, text: type.label, icon: type.icon};
181 }
182
183 function addActivityToSet(activitySet, activityTypeName) {
184 activitySet.activityTypes.push({
185 name: activityTypeName,
186 label: $scope.activityTypes[activityTypeName].label,
187 status: 'Scheduled',
188 reference_activity: 'Open Case',
189 reference_offset: '1',
190 reference_select: 'newest'
191 });
192 }
193
194 function createActivity(name, callback) {
195 CRM.loadForm(CRM.url('civicrm/admin/options/activity_type', {action: 'add', reset: 1, label: name, component_id: 7}))
196 .on('crmFormSuccess', function(e, data) {
197 $scope.activityTypes[data.optionValue.name] = data.optionValue;
198 $scope.activityTypeOptions.push(formatActivityTypeOption(data.optionValue));
199 callback(data.optionValue);
200 $scope.$digest();
201 });
202 }
203
204 // Add a new activity entry to an activity-set
205 $scope.addActivity = function(activitySet, activityType) {
206 if ($scope.activityTypes[activityType]) {
207 addActivityToSet(activitySet, activityType);
208 } else {
209 createActivity(activityType, function(newActivity) {
210 addActivityToSet(activitySet, newActivity.name);
211 });
212 }
213 };
214
215 /// Add a new top-level activity-type entry
216 $scope.addActivityType = function(activityType) {
217 var names = _.pluck($scope.caseType.definition.activityTypes, 'name');
218 if (!_.contains(names, activityType)) {
219 // Add an activity type that exists
220 if ($scope.activityTypes[activityType]) {
221 $scope.caseType.definition.activityTypes.push({name: activityType});
222 } else {
223 createActivity(activityType, function(newActivity) {
224 $scope.caseType.definition.activityTypes.push({name: newActivity.name});
225 });
226 }
227 }
228 };
229
230 /// Add a new role
231 $scope.addRole = function(roles, roleName) {
232 var names = _.pluck($scope.caseType.definition.caseRoles, 'name');
233 if (!_.contains(names, roleName)) {
234 if (_.where($scope.relationshipTypeOptions, {id: roleName}).length) {
235 roles.push({name: roleName});
236 } else {
237 CRM.loadForm(CRM.url('civicrm/admin/reltype', {action: 'add', reset: 1, label_a_b: roleName, label_b_a: roleName}))
238 .on('crmFormSuccess', function(e, data) {
239 roles.push({name: data.relationshipType[REL_TYPE_CNAME]});
240 $scope.relationshipTypeOptions.push({id: data.relationshipType[REL_TYPE_CNAME], text: data.relationshipType.label_b_a});
241 $scope.$digest();
242 });
243 }
244 }
245 };
246
247 $scope.onManagerChange = function(managerRole) {
248 angular.forEach($scope.caseType.definition.caseRoles, function(caseRole) {
249 if (caseRole != managerRole) {
250 caseRole.manager = '0';
251 }
252 });
253 };
254
255 $scope.removeItem = function(array, item) {
256 var idx = _.indexOf(array, item);
257 if (idx != -1) {
258 array.splice(idx, 1);
259 }
260 };
261
262 $scope.isForkable = function() {
263 return !$scope.caseType.id || $scope.caseType.is_forkable;
264 };
265
266 $scope.newStatus = function() {
267 CRM.loadForm(CRM.url('civicrm/admin/options/case_status', {action: 'add', reset: 1}))
268 .on('crmFormSuccess', function(e, data) {
269 $scope.caseStatuses[data.optionValue.name] = data.optionValue;
270 $scope.selectedStatuses[data.optionValue.name] = true;
271 $scope.$digest();
272 });
273 };
274
275 $scope.isNewActivitySetAllowed = function(workflow) {
276 switch (workflow) {
277 case 'timeline':
278 return true;
279 case 'sequence':
280 return 0 === _.where($scope.caseType.definition.activitySets, {sequence: '1'}).length;
281 default:
282 CRM.console('warn', 'Denied access to unrecognized workflow: (' + workflow + ')');
283 return false;
284 }
285 };
286
287 $scope.isActivityRemovable = function(activitySet, activity) {
288 return true;
289 };
290
291 $scope.isValidName = function(name) {
292 return !name || name.match(/^[a-zA-Z0-9_]+$/);
293 };
294
295 $scope.getWorkflowName = function(activitySet) {
296 var result = 'Unknown';
297 _.each($scope.workflows, function(value, key) {
298 if (activitySet[key]) result = value;
299 });
300 return result;
301 };
302
303 /**
304 * Determine which HTML partial to use for a particular
305 *
306 * @return string URL of the HTML partial
307 */
308 $scope.activityTableTemplate = function(activitySet) {
309 if (activitySet.timeline) {
310 return '~/crmCaseType/timelineTable.html';
311 } else if (activitySet.sequence) {
312 return '~/crmCaseType/sequenceTable.html';
313 } else {
314 return '';
315 }
316 };
317
318 $scope.dump = function() {
319 console.log($scope.caseType);
320 };
321
322 $scope.save = function() {
323 // Add selected statuses
324 var selectedStatuses = [];
325 _.each($scope.selectedStatuses, function(v, k) {
326 if (v) selectedStatuses.push(k);
327 });
328 // Ignore if ALL or NONE selected
329 $scope.caseType.definition.statuses = selectedStatuses.length == _.size($scope.selectedStatuses) ? [] : selectedStatuses;
330 var result = crmApi('CaseType', 'create', $scope.caseType, true);
331 result.then(function(data) {
332 if (data.is_error === 0 || data.is_error == '0') {
333 $scope.caseType.id = data.id;
334 window.location.href = '#/caseType';
335 }
336 });
337 };
338
339 $scope.$watchCollection('caseType.definition.activitySets', function() {
340 _.defer(function() {
341 $('.crmCaseType-acttab').tabs('refresh');
342 });
343 });
344
345 var updateCaseTypeName = function () {
346 if (!$scope.caseType.id && $scope.locks.caseTypeName) {
347 // Should we do some filtering? Lowercase? Strip whitespace?
348 var t = $scope.caseType.title ? $scope.caseType.title : '';
349 $scope.caseType.name = t.replace(/ /g, '_').replace(/[^a-zA-Z0-9_]/g, '').toLowerCase();
350 }
351 };
352 $scope.$watch('locks.caseTypeName', updateCaseTypeName);
353 $scope.$watch('caseType.title', updateCaseTypeName);
354
355 if (!$scope.isForkable()) {
356 CRM.alert(ts('The CiviCase XML file for this case-type prohibits editing the definition.'));
357 }
358 });
359
360 crmCaseType.controller('CaseTypeListCtrl', function($scope, crmApi, caseTypes) {
361 var ts = $scope.ts = CRM.ts(null);
362
363 $scope.caseTypes = caseTypes.values;
364 $scope.toggleCaseType = function (caseType) {
365 caseType.is_active = (caseType.is_active == '1') ? '0' : '1';
366 crmApi('CaseType', 'create', caseType, true)
367 .catch(function (data) {
368 caseType.is_active = (caseType.is_active == '1') ? '0' : '1'; // revert
369 $scope.$digest();
370 });
371 };
372 $scope.deleteCaseType = function (caseType) {
373 crmApi('CaseType', 'delete', {id: caseType.id}, {
374 error: function (data) {
375 CRM.alert(data.error_message, ts('Error'), 'error');
376 }
377 })
378 .then(function (data) {
379 delete caseTypes.values[caseType.id];
380 });
381 };
382 $scope.revertCaseType = function (caseType) {
383 caseType.definition = 'null';
384 caseType.is_forked = '0';
385 crmApi('CaseType', 'create', caseType, true)
386 .catch(function (data) {
387 caseType.is_forked = '1'; // restore
388 $scope.$digest();
389 });
390 };
391 });
392
393 })(angular, CRM.$, CRM._);