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