1 (function(angular
, $, _
) {
3 var crmCaseType
= angular
.module('crmCaseType', ['ngRoute', 'ui.utils', 'crmUi', 'unsavedChanges', 'crmUtil']);
5 // Note: This template will be passed to cloneDeep(), so don't put any funny stuff in here!
6 var newCaseTypeTemplate
= {
13 {name
: 'Open Case', max_instances
: 1},
21 name
: 'standard_timeline',
22 label
: 'Standard Timeline',
23 timeline
: '1', // Angular won't bind checkbox correctly with numeric 1
25 {name
: 'Open Case', status
: 'Completed' }
30 { name
: 'Case Coordinator', creator
: '1', manager
: '1'}
35 crmCaseType
.config(['$routeProvider',
36 function($routeProvider
) {
37 $routeProvider
.when('/caseType', {
38 templateUrl
: '~/crmCaseType/list.html',
39 controller
: 'CaseTypeListCtrl',
41 caseTypes: function($route
, crmApi
) {
42 return crmApi('CaseType', 'get', {options
: {limit
: 0}});
46 $routeProvider
.when('/caseType/:id', {
47 templateUrl
: '~/crmCaseType/edit.html',
48 controller
: 'CaseTypeCtrl',
50 apiCalls: function($route
, crmApi
) {
52 reqs
.actStatuses
= ['OptionValue', 'get', {
53 option_group_id
: 'activity_status',
57 reqs
.caseStatuses
= ['OptionValue', 'get', {
58 option_group_id
: 'case_status',
62 reqs
.actTypes
= ['OptionValue', 'get', {
63 option_group_id
: 'activity_type',
70 reqs
.relTypes
= ['RelationshipType', 'get', {
73 sort
: CRM
.crmCaseType
.REL_TYPE_CNAME
,
77 if ($route
.current
.params
.id
!== 'new') {
78 reqs
.caseType
= ['CaseType', 'getsingle', {
79 id
: $route
.current
.params
.id
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() {
94 template
: '<input class="add-activity crm-action-menu fa-plus" type="hidden" />',
95 link: function(scope
, element
, attrs
) {
97 var input
= $('input', element
);
99 scope
._resetSelection = function() {
100 $(input
).select2('close');
101 $(input
).select2('val', '');
102 scope
[attrs
.crmVar
] = '';
105 $(input
).crmSelect2({
106 data
: scope
[attrs
.crmOptions
],
107 createSearchChoice: function(term
) {
108 return {id
: term
, text
: term
+ ' (' + ts('new') + ')'};
110 createSearchChoicePosition
: 'bottom',
111 placeholder
: attrs
.placeholder
113 $(input
).on('select2-selecting', function(e
) {
114 scope
[attrs
.crmVar
] = e
.val
;
115 scope
.$evalAsync(attrs
.crmOnAdd
);
116 scope
.$evalAsync('_resetSelection()');
120 scope
.$watch(attrs
.crmOptions
, function(value
) {
121 $(input
).select2('data', scope
[attrs
.crmOptions
]);
122 $(input
).select2('val', '');
128 crmCaseType
.controller('CaseTypeCtrl', function($scope
, crmApi
, apiCalls
) {
129 // CRM_Case_XMLProcessor::REL_TYPE_CNAME
130 var REL_TYPE_CNAME
= CRM
.crmCaseType
.REL_TYPE_CNAME
,
132 ts
= $scope
.ts
= CRM
.ts(null);
134 $scope
.activityStatuses
= apiCalls
.actStatuses
.values
;
135 $scope
.caseStatuses
= _
.indexBy(apiCalls
.caseStatuses
.values
, 'name');
136 $scope
.activityTypes
= _
.indexBy(apiCalls
.actTypes
.values
, 'name');
137 $scope
.activityTypeOptions
= _
.map(apiCalls
.actTypes
.values
, formatActivityTypeOption
);
138 $scope
.relationshipTypeOptions
= _
.map(apiCalls
.relTypes
.values
, function(type
) {
139 return {id
: type
[REL_TYPE_CNAME
], text
: type
.label_b_a
};
141 $scope
.locks
= {caseTypeName
: true, activitySetName
: true};
144 'timeline': 'Timeline',
145 'sequence': 'Sequence'
148 $scope
.caseType
= apiCalls
.caseType
? apiCalls
.caseType
: _
.cloneDeep(newCaseTypeTemplate
);
149 $scope
.caseType
.definition
= $scope
.caseType
.definition
|| [];
150 $scope
.caseType
.definition
.activityTypes
= $scope
.caseType
.definition
.activityTypes
|| [];
151 $scope
.caseType
.definition
.activitySets
= $scope
.caseType
.definition
.activitySets
|| [];
152 $scope
.caseType
.definition
.caseRoles
= $scope
.caseType
.definition
.caseRoles
|| [];
153 $scope
.caseType
.definition
.statuses
= $scope
.caseType
.definition
.statuses
|| [];
155 $scope
.selectedStatuses
= {};
156 _
.each(apiCalls
.caseStatuses
.values
, function (status
) {
157 $scope
.selectedStatuses
[status
.name
] = !$scope
.caseType
.definition
.statuses
.length
|| $scope
.caseType
.definition
.statuses
.indexOf(status
.name
) > -1;
160 $scope
.addActivitySet = function(workflow
) {
161 var activitySet
= {};
162 activitySet
[workflow
] = '1';
163 activitySet
.activityTypes
= [];
166 var names
= _
.pluck($scope
.caseType
.definition
.activitySets
, 'name');
167 while (_
.contains(names
, workflow
+ '_' + offset
)) offset
++;
168 activitySet
.name
= workflow
+ '_' + offset
;
169 activitySet
.label
= (offset
== 1 ) ? $scope
.workflows
[workflow
] : ($scope
.workflows
[workflow
] + ' #' + offset
);
171 $scope
.caseType
.definition
.activitySets
.push(activitySet
);
173 $('.crmCaseType-acttab').tabs('refresh').tabs({active
: -1});
177 function formatActivityTypeOption(type
) {
178 return {id
: type
.name
, text
: type
.label
, icon
: type
.icon
};
181 function addActivityToSet(activitySet
, activityTypeName
) {
182 activitySet
.activityTypes
.push({
183 name
: activityTypeName
,
185 reference_activity
: 'Open Case',
186 reference_offset
: '1',
187 reference_select
: 'newest'
191 function createActivity(name
, callback
) {
192 CRM
.loadForm(CRM
.url('civicrm/admin/options/activity_type', {action
: 'add', reset
: 1, label
: name
, component_id
: 7}))
193 .on('crmFormSuccess', function(e
, data
) {
194 $scope
.activityTypes
[data
.optionValue
.name
] = data
.optionValue
;
195 $scope
.activityTypeOptions
.push(formatActivityTypeOption(data
.optionValue
));
196 callback(data
.optionValue
);
201 // Add a new activity entry to an activity-set
202 $scope
.addActivity = function(activitySet
, activityType
) {
203 if ($scope
.activityTypes
[activityType
]) {
204 addActivityToSet(activitySet
, activityType
);
206 createActivity(activityType
, function(newActivity
) {
207 addActivityToSet(activitySet
, newActivity
.name
);
212 /// Add a new top-level activity-type entry
213 $scope
.addActivityType = function(activityType
) {
214 var names
= _
.pluck($scope
.caseType
.definition
.activityTypes
, 'name');
215 if (!_
.contains(names
, activityType
)) {
216 // Add an activity type that exists
217 if ($scope
.activityTypes
[activityType
]) {
218 $scope
.caseType
.definition
.activityTypes
.push({name
: activityType
});
220 createActivity(activityType
, function(newActivity
) {
221 $scope
.caseType
.definition
.activityTypes
.push({name
: newActivity
.name
});
228 $scope
.addRole = function(roles
, roleName
) {
229 var names
= _
.pluck($scope
.caseType
.definition
.caseRoles
, 'name');
230 if (!_
.contains(names
, roleName
)) {
231 if (_
.where($scope
.relationshipTypeOptions
, {id
: roleName
}).length
) {
232 roles
.push({name
: roleName
});
234 CRM
.loadForm(CRM
.url('civicrm/admin/reltype', {action
: 'add', reset
: 1, label_a_b
: roleName
, label_b_a
: roleName
}))
235 .on('crmFormSuccess', function(e
, data
) {
236 roles
.push({name
: data
.relationshipType
[REL_TYPE_CNAME
]});
237 $scope
.relationshipTypeOptions
.push({id
: data
.relationshipType
[REL_TYPE_CNAME
], text
: data
.relationshipType
.label_b_a
});
244 $scope
.onManagerChange = function(managerRole
) {
245 angular
.forEach($scope
.caseType
.definition
.caseRoles
, function(caseRole
) {
246 if (caseRole
!= managerRole
) {
247 caseRole
.manager
= '0';
252 $scope
.removeItem = function(array
, item
) {
253 var idx
= _
.indexOf(array
, item
);
255 array
.splice(idx
, 1);
259 $scope
.isForkable = function() {
260 return !$scope
.caseType
.id
|| $scope
.caseType
.is_forkable
;
263 $scope
.newStatus = function() {
264 CRM
.loadForm(CRM
.url('civicrm/admin/options/case_status', {action
: 'add', reset
: 1}))
265 .on('crmFormSuccess', function(e
, data
) {
266 $scope
.caseStatuses
[data
.optionValue
.name
] = data
.optionValue
;
267 $scope
.selectedStatuses
[data
.optionValue
.name
] = true;
272 $scope
.isNewActivitySetAllowed = function(workflow
) {
277 return 0 === _
.where($scope
.caseType
.definition
.activitySets
, {sequence
: '1'}).length
;
279 CRM
.console('warn', 'Denied access to unrecognized workflow: (' + workflow
+ ')');
284 $scope
.isActivityRemovable = function(activitySet
, activity
) {
285 if (activitySet
.name
== 'standard_timeline' && activity
.name
== 'Open Case') {
292 $scope
.isValidName = function(name
) {
293 return !name
|| name
.match(/^[a-zA-Z0-9_]+$/);
296 $scope
.getWorkflowName = function(activitySet
) {
297 var result
= 'Unknown';
298 _
.each($scope
.workflows
, function(value
, key
) {
299 if (activitySet
[key
]) result
= value
;
305 * Determine which HTML partial to use for a particular
307 * @return string URL of the HTML partial
309 $scope
.activityTableTemplate = function(activitySet
) {
310 if (activitySet
.timeline
) {
311 return '~/crmCaseType/timelineTable.html';
312 } else if (activitySet
.sequence
) {
313 return '~/crmCaseType/sequenceTable.html';
319 $scope
.dump = function() {
320 console
.log($scope
.caseType
);
323 $scope
.save = function() {
324 // Add selected statuses
325 var selectedStatuses
= [];
326 _
.each($scope
.selectedStatuses
, function(v
, k
) {
327 if (v
) selectedStatuses
.push(k
);
329 // Ignore if ALL or NONE selected
330 $scope
.caseType
.definition
.statuses
= selectedStatuses
.length
== _
.size($scope
.selectedStatuses
) ? [] : selectedStatuses
;
331 var result
= crmApi('CaseType', 'create', $scope
.caseType
, true);
332 result
.then(function(data
) {
333 if (data
.is_error
=== 0 || data
.is_error
== '0') {
334 $scope
.caseType
.id
= data
.id
;
335 window
.location
.href
= '#/caseType';
340 $scope
.$watchCollection('caseType.definition.activitySets', function() {
342 $('.crmCaseType-acttab').tabs('refresh');
346 var updateCaseTypeName = function () {
347 if (!$scope
.caseType
.id
&& $scope
.locks
.caseTypeName
) {
348 // Should we do some filtering? Lowercase? Strip whitespace?
349 var t
= $scope
.caseType
.title
? $scope
.caseType
.title
: '';
350 $scope
.caseType
.name
= t
.replace(/ /g, '_').replace(/[^a
-zA
-Z0
-9_
]/g
, '').toLowerCase();
353 $scope
.$watch('locks.caseTypeName', updateCaseTypeName
);
354 $scope
.$watch('caseType.title', updateCaseTypeName
);
356 if (!$scope
.isForkable()) {
357 CRM
.alert(ts('The CiviCase XML file for this case-type prohibits editing the definition.'));
361 crmCaseType
.controller('CaseTypeListCtrl', function($scope
, crmApi
, caseTypes
) {
362 var ts
= $scope
.ts
= CRM
.ts(null);
364 $scope
.caseTypes
= caseTypes
.values
;
365 $scope
.toggleCaseType = function (caseType
) {
366 caseType
.is_active
= (caseType
.is_active
== '1') ? '0' : '1';
367 crmApi('CaseType', 'create', caseType
, true)
368 .catch(function (data
) {
369 caseType
.is_active
= (caseType
.is_active
== '1') ? '0' : '1'; // revert
373 $scope
.deleteCaseType = function (caseType
) {
374 crmApi('CaseType', 'delete', {id
: caseType
.id
}, {
375 error: function (data
) {
376 CRM
.alert(data
.error_message
, ts('Error'), 'error');
379 .then(function (data
) {
380 delete caseTypes
.values
[caseType
.id
];
384 $scope
.revertCaseType = function (caseType
) {
385 caseType
.definition
= 'null';
386 caseType
.is_forked
= '0';
387 crmApi('CaseType', 'create', caseType
, true)
388 .catch(function (data
) {
389 caseType
.is_forked
= '1'; // restore
395 })(angular
, CRM
.$, CRM
._
);