Merge pull request #6339 from cividesk/Fortify_Fixes
[civicrm-core.git] / ang / crmCaseType.js
CommitLineData
4c58e251
TO
1(function(angular, $, _) {
2
a8e65974 3 var crmCaseType = angular.module('crmCaseType', ['ngRoute', 'ui.utils', 'crmUi', 'unsavedChanges', 'crmUtil']);
4c58e251 4
506cd414 5 // Note: This template will be passed to cloneDeep(), so don't put any funny stuff in here!
9be5fc34
TO
6 var newCaseTypeTemplate = {
7 title: "",
8 name: "",
9 is_active: "1",
10 weight: "1",
11 definition: {
12 activityTypes: [
4d8bbcf6
TO
13 {name: 'Open Case', max_instances: 1},
14 {name: 'Email'},
15 {name: 'Follow up'},
16 {name: 'Meeting'},
17 {name: 'Phone Call'}
9be5fc34
TO
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 }
4d74de55
TO
33 };
34
4c58e251
TO
35 crmCaseType.config(['$routeProvider',
36 function($routeProvider) {
b75c2546 37 $routeProvider.when('/caseType', {
ef5d18a1 38 templateUrl: '~/crmCaseType/list.html',
b75c2546
TO
39 controller: 'CaseTypeListCtrl',
40 resolve: {
41 caseTypes: function($route, crmApi) {
a214ce43 42 return crmApi('CaseType', 'get', {options: {limit: 0}});
b75c2546
TO
43 }
44 }
45 });
4c58e251 46 $routeProvider.when('/caseType/:id', {
ef5d18a1 47 templateUrl: '~/crmCaseType/edit.html',
4d74de55
TO
48 controller: 'CaseTypeCtrl',
49 resolve: {
9625aad1
TO
50 apiCalls: function($route, crmApi) {
51 var reqs = {};
52 reqs.actStatuses = ['OptionValue', 'get', {
53 option_group_id: 'activity_status'
54 }];
55 reqs.actTypes = ['OptionValue', 'get', {
56 option_group_id: 'activity_type',
57 options: {
58 sort: 'name',
59 limit: 0
60 }
61 }];
62 reqs.relTypes = ['RelationshipType', 'get', {
63 options: {
64 sort: CRM.crmCaseType.REL_TYPE_CNAME,
65 limit: 0
66 }
67 }];
68 if ($route.current.params.id !== 'new') {
69 reqs.caseType = ['CaseType', 'getsingle', {
70 id: $route.current.params.id
71 }];
87dcd909 72 }
9625aad1 73 return crmApi(reqs);
4d74de55
TO
74 }
75 }
4c58e251
TO
76 });
77 }
78 ]);
79
95fd24c0
TO
80 // Add a new record by name.
81 // Ex: <crmAddName crm-options="['Alpha','Beta','Gamma']" crm-var="newItem" crm-on-add="callMyCreateFunction(newItem)" />
8fc6fba7 82 crmCaseType.directive('crmAddName', function() {
95fd24c0
TO
83 return {
84 restrict: 'AE',
00eee619 85 template: '<input class="add-activity crm-action-menu action-icon-plus" type="hidden" />',
bafce1db
TO
86 link: function(scope, element, attrs) {
87 /// Format list of options for select2's "data"
88 var getFormattedOptions = function() {
89 return {
90 results: _.map(scope[attrs.crmOptions], function(option){
91 return {id: option, text: option};
92 })
93 };
94 };
95
96 var input = $('input', element);
97
98 scope._resetSelection = function() {
99 $(input).select2('close');
100 $(input).select2('val', '');
101 scope[attrs.crmVar] = '';
102 };
103
104 $(input).select2({
105 data: getFormattedOptions,
106 createSearchChoice: function(term) {
107 return {id: term, text: term};
00eee619
CW
108 },
109 placeholder: attrs.placeholder
bafce1db
TO
110 });
111 $(input).on('select2-selecting', function(e) {
112 scope[attrs.crmVar] = e.val;
113 scope.$evalAsync(attrs.crmOnAdd);
114 scope.$evalAsync('_resetSelection()');
115 e.preventDefault();
116 });
60dd172b
TO
117
118 scope.$watch(attrs.crmOptions, function(value) {
119 $(input).select2('data', getFormattedOptions);
120 $(input).select2('val', '');
121 });
bafce1db 122 }
95fd24c0
TO
123 };
124 });
125
9625aad1 126 crmCaseType.controller('CaseTypeCtrl', function($scope, crmApi, apiCalls) {
5d8901af 127 var ts = $scope.ts = CRM.ts(null);
76e4acb8 128
9625aad1
TO
129 $scope.activityStatuses = _.values(apiCalls.actStatuses.values);
130 $scope.activityTypes = apiCalls.actTypes.values;
131 $scope.activityTypeNames = _.pluck(apiCalls.actTypes.values, 'name');
00eee619 132 $scope.activityTypes = apiCalls.actTypes.values;
9625aad1 133 $scope.relationshipTypeNames = _.pluck(apiCalls.relTypes.values, CRM.crmCaseType.REL_TYPE_CNAME); // CRM_Case_XMLProcessor::REL_TYPE_CNAME
1317b266 134 $scope.locks = {caseTypeName: true, activitySetName: true};
dc7a657e 135
76e4acb8
TO
136 $scope.workflows = {
137 'timeline': 'Timeline',
b387506c 138 'sequence': 'Sequence'
76e4acb8
TO
139 };
140
9625aad1 141 $scope.caseType = apiCalls.caseType ? apiCalls.caseType : _.cloneDeep(newCaseTypeTemplate);
87dcd909 142 $scope.caseType.definition = $scope.caseType.definition || [];
4d74de55
TO
143 $scope.caseType.definition.activityTypes = $scope.caseType.definition.activityTypes || [];
144 $scope.caseType.definition.activitySets = $scope.caseType.definition.activitySets || [];
145 $scope.caseType.definition.caseRoles = $scope.caseType.definition.caseRoles || [];
dc7a657e 146 window.ct = $scope.caseType;
4c58e251 147
76e4acb8
TO
148 $scope.addActivitySet = function(workflow) {
149 var activitySet = {};
150 activitySet[workflow] = '1';
151 activitySet.activityTypes = [];
152
153 var offset = 1;
154 var names = _.pluck($scope.caseType.definition.activitySets, 'name');
155 while (_.contains(names, workflow + '_' + offset)) offset++;
156 activitySet.name = workflow + '_' + offset;
157 activitySet.label = (offset == 1 ) ? $scope.workflows[workflow] : ($scope.workflows[workflow] + ' #' + offset);
158
159 $scope.caseType.definition.activitySets.push(activitySet);
160 _.defer(function() {
161 $('.crmCaseType-acttab').tabs('refresh').tabs({active: -1});
162 });
163 };
164
d7c25f6c
TO
165 /// Add a new activity entry to an activity-set
166 $scope.addActivity = function(activitySet, activityType) {
167 activitySet.activityTypes.push({
183241d8 168 name: activityType,
169 status: 'Scheduled',
170 reference_activity: 'Open Case',
171 reference_offset: '1',
172 reference_select: 'newest'
d7c25f6c 173 });
60dd172b
TO
174 if (!_.contains($scope.activityTypeNames, activityType)) {
175 $scope.activityTypeNames.push(activityType);
176 }
d7c25f6c
TO
177 };
178
179 /// Add a new top-level activity-type entry
180 $scope.addActivityType = function(activityType) {
181 var names = _.pluck($scope.caseType.definition.activityTypes, 'name');
182 if (!_.contains(names, activityType)) {
183 $scope.caseType.definition.activityTypes.push({
184 name: activityType
185 });
60dd172b
TO
186
187 }
188 if (!_.contains($scope.activityTypeNames, activityType)) {
189 $scope.activityTypeNames.push(activityType);
d7c25f6c
TO
190 }
191 };
192
8c7e0ae8
TO
193 /// Add a new role
194 $scope.addRole = function(roles, roleName) {
bafce1db
TO
195 var names = _.pluck($scope.caseType.definition.caseRoles, 'name');
196 if (!_.contains(names, roleName)) {
197 roles.push({
198 name: roleName
199 });
200 }
60dd172b
TO
201 if (!_.contains($scope.relationshipTypeNames, roleName)) {
202 $scope.relationshipTypeNames.push(roleName);
203 }
8c7e0ae8
TO
204 };
205
4c58e251
TO
206 $scope.onManagerChange = function(managerRole) {
207 angular.forEach($scope.caseType.definition.caseRoles, function(caseRole) {
208 if (caseRole != managerRole) {
209 caseRole.manager = '0';
210 }
211 });
212 };
213
214 $scope.removeItem = function(array, item) {
215 var idx = _.indexOf(array, item);
216 if (idx != -1) {
217 array.splice(idx, 1);
218 }
219 };
220
b40b4114 221 $scope.isForkable = function() {
f2bad133 222 return !$scope.caseType.id || $scope.caseType.is_forkable;
b40b4114
TO
223 };
224
5d973e24
TO
225 $scope.isNewActivitySetAllowed = function(workflow) {
226 switch (workflow) {
227 case 'timeline':
228 return true;
b387506c 229 case 'sequence':
b04e5ffb 230 return 0 === _.where($scope.caseType.definition.activitySets, {sequence: '1'}).length;
5d973e24 231 default:
bba9b4f0 232 CRM.console('warn', 'Denied access to unrecognized workflow: (' + workflow + ')');
5d973e24
TO
233 return false;
234 }
235 };
236
259a7652
TO
237 $scope.isActivityRemovable = function(activitySet, activity) {
238 if (activitySet.name == 'standard_timeline' && activity.name == 'Open Case') {
239 return false;
240 } else {
241 return true;
242 }
243 };
244
f42b448f
TO
245 $scope.isValidName = function(name) {
246 return !name || name.match(/^[a-zA-Z0-9_]+$/);
247 };
248
4c58e251 249 $scope.getWorkflowName = function(activitySet) {
76e4acb8
TO
250 var result = 'Unknown';
251 _.each($scope.workflows, function(value, key) {
252 if (activitySet[key]) result = value;
253 });
254 return result;
4c58e251
TO
255 };
256
257 /**
258 * Determine which HTML partial to use for a particular
259 *
260 * @return string URL of the HTML partial
261 */
262 $scope.activityTableTemplate = function(activitySet) {
263 if (activitySet.timeline) {
ef5d18a1 264 return '~/crmCaseType/timelineTable.html';
b387506c 265 } else if (activitySet.sequence) {
ef5d18a1 266 return '~/crmCaseType/sequenceTable.html';
4c58e251
TO
267 } else {
268 return '';
269 }
270 };
271
272 $scope.dump = function() {
273 console.log($scope.caseType);
76e4acb8
TO
274 };
275
aa1a7c2e 276 $scope.save = function() {
c7bccb5f 277 var result = crmApi('CaseType', 'create', $scope.caseType, true);
3140a415 278 result.then(function(data) {
b04e5ffb 279 if (data.is_error === 0 || data.is_error == '0') {
c7bccb5f 280 $scope.caseType.id = data.id;
1ab5b88e 281 window.location.href = '#/caseType';
c7bccb5f 282 }
283 });
aa1a7c2e
TO
284 };
285
76e4acb8
TO
286 $scope.$watchCollection('caseType.definition.activitySets', function() {
287 _.defer(function() {
8fc6fba7 288 $('.crmCaseType-acttab').tabs('refresh');
76e4acb8
TO
289 });
290 });
685acae4 291
292 var updateCaseTypeName = function () {
293 if (!$scope.caseType.id && $scope.locks.caseTypeName) {
294 // Should we do some filtering? Lowercase? Strip whitespace?
a5ca1f48 295 var t = $scope.caseType.title ? $scope.caseType.title : '';
296 $scope.caseType.name = t.replace(/ /g, '_').replace(/[^a-zA-Z0-9_]/g, '').toLowerCase();
685acae4 297 }
298 };
299 $scope.$watch('locks.caseTypeName', updateCaseTypeName);
300 $scope.$watch('caseType.title', updateCaseTypeName);
b40b4114
TO
301
302 if (!$scope.isForkable()) {
303 CRM.alert(ts('The CiviCase XML file for this case-type prohibits editing the definition.'));
304 }
4c58e251
TO
305 });
306
b75c2546 307 crmCaseType.controller('CaseTypeListCtrl', function($scope, crmApi, caseTypes) {
7abbf317
CW
308 var ts = $scope.ts = CRM.ts(null);
309
b75c2546 310 $scope.caseTypes = caseTypes.values;
4b8c8b42
TO
311 $scope.toggleCaseType = function (caseType) {
312 caseType.is_active = (caseType.is_active == '1') ? '0' : '1';
313 crmApi('CaseType', 'create', caseType, true)
c99f1a0a
TO
314 .catch(function (data) {
315 caseType.is_active = (caseType.is_active == '1') ? '0' : '1'; // revert
316 $scope.$digest();
4b8c8b42
TO
317 });
318 };
319 $scope.deleteCaseType = function (caseType) {
eb8e4c2d
TO
320 crmApi('CaseType', 'delete', {id: caseType.id}, {
321 error: function (data) {
7abbf317 322 CRM.alert(data.error_message, ts('Error'), 'error');
eb8e4c2d
TO
323 }
324 })
4b8c8b42 325 .then(function (data) {
c99f1a0a
TO
326 delete caseTypes.values[caseType.id];
327 $scope.$digest();
4b8c8b42
TO
328 });
329 };
470a458e
TO
330 $scope.revertCaseType = function (caseType) {
331 caseType.definition = 'null';
332 caseType.is_forked = '0';
333 crmApi('CaseType', 'create', caseType, true)
c99f1a0a
TO
334 .catch(function (data) {
335 caseType.is_forked = '1'; // restore
336 $scope.$digest();
470a458e
TO
337 });
338 };
b75c2546
TO
339 });
340
bba9b4f0 341})(angular, CRM.$, CRM._);