1 (function(angular
, $, _
) {
3 var crmCaseType
= angular
.module('crmCaseType', CRM
.angRequires('crmCaseType'));
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',
65 reqs
.actTypes
= ['OptionValue', 'get', {
66 option_group_id
: 'activity_type',
73 reqs
.defaultAssigneeTypes
= ['OptionValue', 'get', {
74 option_group_id
: 'activity_default_assignee',
80 reqs
.relTypes
= ['RelationshipType', 'get', {
87 if ($route
.current
.params
.id
!== 'new') {
88 reqs
.caseType
= ['CaseType', 'getsingle', {
89 id
: $route
.current
.params
.id
99 // Add a new record by name.
100 // Ex: <crmAddName crm-options="['Alpha','Beta','Gamma']" crm-var="newItem" crm-on-add="callMyCreateFunction(newItem)" />
101 crmCaseType
.directive('crmAddName', function() {
104 template
: '<input class="add-activity crm-action-menu fa-plus" type="hidden" />',
105 link: function(scope
, element
, attrs
) {
107 var input
= $('input', element
);
109 scope
._resetSelection = function() {
110 $(input
).select2('close');
111 $(input
).select2('val', '');
112 scope
[attrs
.crmVar
] = '';
115 $(input
).crmSelect2({
117 return { results
: scope
[attrs
.crmOptions
] };
119 createSearchChoice: function(term
) {
120 return {id
: term
, text
: term
+ ' (' + ts('new') + ')'};
122 createSearchChoicePosition
: 'bottom',
123 placeholder
: attrs
.placeholder
125 $(input
).on('select2-selecting', function(e
) {
126 scope
[attrs
.crmVar
] = e
.val
;
127 scope
.$evalAsync(attrs
.crmOnAdd
);
128 scope
.$evalAsync('_resetSelection()');
135 crmCaseType
.directive('crmEditableTabTitle', function($timeout
) {
138 link: function(scope
, element
, attrs
) {
139 element
.addClass('crm-editable crm-editable-enabled');
140 var titleLabel
= $(element
).find('span');
141 var penIcon
= $('<i class="crm-i fa-pencil crm-editable-placeholder" aria-hidden="true"></i>').prependTo(element
);
142 var saveButton
= $('<button type="button"><i class="crm-i fa-check" aria-hidden="true"></i></button>').appendTo(element
);
143 var cancelButton
= $('<button type="cancel"><i class="crm-i fa-times" aria-hidden="true"></i></button>').appendTo(element
);
144 $('button', element
).wrapAll('<div class="crm-editable-form" style="display:none" />');
145 var buttons
= $('.crm-editable-form', element
);
146 titleLabel
.on('click', startEditMode
);
147 penIcon
.on('click', startEditMode
);
149 function detectEscapeKeyPress (event
) {
150 var isEscape
= false;
152 if ("key" in event
) {
153 isEscape
= (event
.key
== "Escape" || event
.key
== "Esc");
155 isEscape
= (event
.keyCode
== 27);
161 function detectEnterKeyPress (event
) {
164 if ("key" in event
) {
165 isEnter
= (event
.key
== "Enter");
167 isEnter
= (event
.keyCode
== 13);
173 function startEditMode () {
174 if (titleLabel
.is(":focus")) {
181 saveButton
.click(function () {
186 cancelButton
.click(function () {
191 $(element
).addClass('crm-editable-editing');
194 .attr("contenteditable", "true")
196 .focusout(function (event
) {
197 $timeout(function () {
202 .keydown(function(event
) {
203 event
.stopImmediatePropagation();
205 if(detectEscapeKeyPress(event
)) {
208 } else if(detectEnterKeyPress(event
)) {
209 event
.preventDefault();
216 function stopEditMode () {
217 titleLabel
.removeAttr("contenteditable").off("focusout");
218 titleLabel
.off("keydown");
219 saveButton
.off("click");
220 cancelButton
.off("click");
221 $(element
).removeClass('crm-editable-editing');
227 function revertTextValue () {
228 titleLabel
.text(scope
.activitySet
.label
);
231 function updateTextValue () {
232 var updatedTitle
= titleLabel
.text();
234 scope
.$evalAsync(function () {
235 scope
.activitySet
.label
= updatedTitle
;
242 crmCaseType
.controller('CaseTypeCtrl', function($scope
, $timeout
, crmApi
, apiCalls
, crmUiHelp
) {
243 var defaultAssigneeDefaultValue
, ts
;
247 ts
= $scope
.ts
= CRM
.ts(null);
248 $scope
.hs
= crmUiHelp({file
: 'CRM/Case/CaseType'});
249 $scope
.locks
= { caseTypeName
: true, activitySetName
: true };
250 $scope
.workflows
= { timeline
: 'Timeline', sequence
: 'Sequence' };
251 defaultAssigneeDefaultValue
= _
.find(apiCalls
.defaultAssigneeTypes
.values
, { is_default
: '1' }) || {};
253 storeApiCallsResults();
255 initCaseTypeDefinition();
256 initSelectedStatuses();
258 $timeout(function() {
259 $('form[name=editCaseTypeForm] .crmCaseType-acttab').tabs({show
: true, hide
: true});
263 /// Stores the api calls results in the $scope object
264 function storeApiCallsResults() {
265 $scope
.activityStatuses
= apiCalls
.actStatuses
.values
;
266 $scope
.caseStatuses
= _
.indexBy(apiCalls
.caseStatuses
.values
, 'name');
267 $scope
.activityTypes
= _
.indexBy(apiCalls
.actTypes
.values
, 'name');
268 $scope
.activityTypeOptions
= _
.map(apiCalls
.actTypes
.values
, formatActivityTypeOption
);
269 $scope
.defaultAssigneeTypes
= apiCalls
.defaultAssigneeTypes
.values
;
270 // for dropdown lists, only include enabled choices
271 $scope
.relationshipTypeOptions
= getRelationshipTypeOptions(true);
272 // for comparisons, include disabled
273 $scope
.relationshipTypeOptionsAll
= getRelationshipTypeOptions(false);
274 // stores the default assignee values indexed by their option name:
275 $scope
.defaultAssigneeTypeValues
= _
.chain($scope
.defaultAssigneeTypes
)
276 .indexBy('name').mapValues('value').value();
279 // Returns the relationship type options. If the relationship is
280 // bidirectional (Ex: Spouse of) it adds a single option otherwise it adds
281 // two options representing the relationship type directions (Ex: Employee
284 // The relationship dropdown needs to be given IDs with direction,
285 // while the role name in the xml needs values that are names (with
286 // implicit direction).
288 // At any rate, the labels should follow the convention in the UI of
289 // describing case roles from the perspective of the client, while the
290 // names must follow the convention in the XML of describing case roles
291 // from the perspective of the non-client.
293 // @param onlyActive bool
294 // If true, only include enabled relationship types.
295 // @return array[object]
297 // xmlName: The name corresponding to what's stored in xml/caseRoles.
298 // text: The text in dropdowns, i.e. <option value="id">text</option>
299 // It's called text because that is what select2 is expecting.
300 // id: The id value in dropdowns, i.e. <option value="id">text</option>
301 // Is the concatenation of id+direction, e.g. 2_a_b.
302 // It's called id because that is what select2 is expecting.
304 function getRelationshipTypeOptions(onlyActive
) {
305 var relationshipTypesToUse
;
307 relationshipTypesToUse
= _
.filter(apiCalls
.relTypes
.values
, {is_active
: "1"});
309 relationshipTypesToUse
= apiCalls
.relTypes
.values
;
311 return _
.transform(relationshipTypesToUse
, function(result
, relType
) {
312 var isBidirectionalRelationship
= relType
.label_a_b
=== relType
.label_b_a
;
314 // The order here of a's and b's here is important regarding
315 // unidirectional and bidirectional. Because the xml spec DOES support
316 // direction for activity auto-assignees, if this is changed you might
317 // end up with activity assignees in existing xml that then come
318 // through as blank in the dropdown on the timelines tab if it's a
319 // bidirectional relationship. E.g. if spouse is stored in an existing
320 // xml definition as 2_a_b, but we only push 2_b_a, then it won't match
323 // This has some implications for when we add a new type on the fly
324 // later, but it works out ok as long as we also do it in the same
325 // direction there. See notes in addRoleOnTheFly().
327 // This is what we want to store in the caseRoles.name field,
328 // which corresponds to the xml file, when we send it back to
330 xmlName
: relType
.name_a_b
,
331 // This has to be called text because select2 is expecting it.
332 // And yes it's the opposite direction from name.
333 text
: relType
.label_b_a
,
334 // This has to be called id because select2 is expecting it.
335 id
: relType
.id
+ '_a_b'
338 if (!isBidirectionalRelationship
) {
340 xmlName
: relType
.name_b_a
,
341 text
: relType
.label_a_b
,
342 id
: relType
.id
+ '_b_a'
348 /// initializes the case type object
349 function initCaseType() {
350 var isNewCaseType
= !apiCalls
.caseType
;
353 $scope
.caseType
= _
.cloneDeep(newCaseTypeTemplate
);
355 $scope
.caseType
= apiCalls
.caseType
;
359 /// initializes the case type definition object
360 function initCaseTypeDefinition() {
361 $scope
.caseType
.definition
= $scope
.caseType
.definition
|| [];
362 $scope
.caseType
.definition
.activityTypes
= $scope
.caseType
.definition
.activityTypes
|| [];
363 $scope
.caseType
.definition
.activitySets
= $scope
.caseType
.definition
.activitySets
|| [];
364 $scope
.caseType
.definition
.caseRoles
= $scope
.caseType
.definition
.caseRoles
|| [];
365 $scope
.caseType
.definition
.statuses
= $scope
.caseType
.definition
.statuses
|| [];
366 $scope
.caseType
.definition
.timelineActivityTypes
= $scope
.caseType
.definition
.timelineActivityTypes
|| [];
367 $scope
.caseType
.definition
.restrictActivityAsgmtToCmsUser
= $scope
.caseType
.definition
.restrictActivityAsgmtToCmsUser
|| 0;
368 $scope
.caseType
.definition
.activityAsgmtGrps
= $scope
.caseType
.definition
.activityAsgmtGrps
|| [];
370 _
.each($scope
.caseType
.definition
.activitySets
, function (set) {
371 _
.each(set.activityTypes
, function (type
, name
) {
372 var isDefaultAssigneeTypeUndefined
= _
.isUndefined(type
.default_assignee_type
);
373 var typeDefinition
= $scope
.activityTypes
[type
.name
];
374 type
.label
= (typeDefinition
&& typeDefinition
.label
) || type
.name
;
376 if (isDefaultAssigneeTypeUndefined
) {
377 type
.default_assignee_type
= defaultAssigneeDefaultValue
.value
;
382 // Go lookup and add client-perspective labels for
383 // $scope.caseType.definition.caseRoles, since the xml doesn't have them
384 // and we need to display them in the roles table.
385 _
.each($scope
.caseType
.definition
.caseRoles
, function (set) {
386 _
.each($scope
.relationshipTypeOptionsAll
, function (relationshipTypeOption
) {
387 if (relationshipTypeOption
.xmlName
== set.name
) {
388 // relationshipTypeOption.text here corresponds to one of the
389 // apiCalls.relTypes label fields (i.e. civicrm_relationship_type
390 // label database fields). It has to be called text because
391 // it's used in select2 which expects it to be called text.
392 set.displayLabel
= relationshipTypeOption
.text
;
393 // break out of inner `each` loop
400 /// initializes the selected statuses
401 function initSelectedStatuses() {
402 $scope
.selectedStatuses
= {};
404 _
.each(apiCalls
.caseStatuses
.values
, function (status
) {
405 $scope
.selectedStatuses
[status
.name
] = !$scope
.caseType
.definition
.statuses
.length
|| $scope
.caseType
.definition
.statuses
.indexOf(status
.name
) > -1;
409 $scope
.addActivitySet = function(workflow
) {
410 var activitySet
= {};
411 activitySet
[workflow
] = '1';
412 activitySet
.activityTypes
= [];
415 var names
= _
.pluck($scope
.caseType
.definition
.activitySets
, 'name');
416 while (_
.contains(names
, workflow
+ '_' + offset
)) offset
++;
417 activitySet
.name
= workflow
+ '_' + offset
;
418 activitySet
.label
= (offset
== 1 ) ? $scope
.workflows
[workflow
] : ($scope
.workflows
[workflow
] + ' #' + offset
);
420 $scope
.caseType
.definition
.activitySets
.push(activitySet
);
422 $('.crmCaseType-acttab').tabs('refresh').tabs({active
: -1});
426 function formatActivityTypeOption(type
) {
427 return {id
: type
.name
, text
: type
.label
, icon
: type
.icon
};
430 function addActivityToSet(activitySet
, activityTypeName
) {
431 activitySet
.activityTypes
= activitySet
.activityTypes
|| [];
433 name
: activityTypeName
,
434 label
: $scope
.activityTypes
[activityTypeName
].label
,
436 reference_activity
: 'Open Case',
437 reference_offset
: '1',
438 reference_select
: 'newest',
439 default_assignee_type
: $scope
.defaultAssigneeTypeValues
.NONE
441 activitySet
.activityTypes
.push(activity
);
442 if(typeof activitySet
.timeline
!== "undefined" && activitySet
.timeline
== "1") {
443 $scope
.caseType
.definition
.timelineActivityTypes
.push(activity
);
447 function resetTimelineActivityTypes() {
448 $scope
.caseType
.definition
.timelineActivityTypes
= [];
449 angular
.forEach($scope
.caseType
.definition
.activitySets
, function(activitySet
) {
450 angular
.forEach(activitySet
.activityTypes
, function(activityType
) {
451 $scope
.caseType
.definition
.timelineActivityTypes
.push(activityType
);
456 function createActivity(name
, callback
) {
457 CRM
.loadForm(CRM
.url('civicrm/admin/options/activity_type', {action
: 'add', reset
: 1, label
: name
, component_id
: 7}))
458 .on('crmFormSuccess', function(e
, data
) {
459 $scope
.activityTypes
[data
.optionValue
.name
] = data
.optionValue
;
460 $scope
.activityTypeOptions
.push(formatActivityTypeOption(data
.optionValue
));
461 callback(data
.optionValue
);
466 // Add a new activity entry to an activity-set
467 $scope
.addActivity = function(activitySet
, activityType
) {
468 if ($scope
.activityTypes
[activityType
]) {
469 addActivityToSet(activitySet
, activityType
);
471 createActivity(activityType
, function(newActivity
) {
472 addActivityToSet(activitySet
, newActivity
.name
);
477 /// Add a new top-level activity-type entry
478 $scope
.addActivityType = function(activityType
) {
479 var names
= _
.pluck($scope
.caseType
.definition
.activityTypes
, 'name');
480 if (!_
.contains(names
, activityType
)) {
481 // Add an activity type that exists
482 if ($scope
.activityTypes
[activityType
]) {
483 $scope
.caseType
.definition
.activityTypes
.push({name
: activityType
});
485 createActivity(activityType
, function(newActivity
) {
486 $scope
.caseType
.definition
.activityTypes
.push({name
: newActivity
.name
});
492 /// Clears the activity's default assignee values for relationship and contact
493 $scope
.clearActivityDefaultAssigneeValues = function(activity
) {
494 activity
.default_assignee_relationship
= null;
495 activity
.default_assignee_contact
= null;
499 // Called from the select2 dropdown when a selection is made.
501 // @param roles array
502 // The roles currently in the table.
503 // @param roleIdOrLabel string
504 // The trick here is that since you can add roles on the fly, the
505 // roleIdOrLabel parameter can be two different types of things. It can be
506 // the id, like '2_a_b' if it's an existing choice that was selected, or
507 // it can be a LABEL if they typed something that isn't in the list. If
508 // the latter the select2 has no choice but to give us a label because
509 // there is no id yet.
510 $scope
.addRole = function(roles
, roleIdOrLabel
) {
512 // First check does what we've been given match up to any relationship
513 // type, based on id, which is the id from the select2 (i.e. html
514 // <option value="id">)
515 var matchingRoles
= _
.filter($scope
.relationshipTypeOptions
, {id
: roleIdOrLabel
});
516 if (matchingRoles
.length
) {
517 matchingRole
= matchingRoles
.shift();
519 // If found, is the corresponding machine name in the list of existing
520 // roles for the case type. Unfortunately, caseRoles only stores name,
521 // which doesn't indicate the id or direction, because the xml spec
522 // doesn't support those.
523 var names
= _
.pluck($scope
.caseType
.definition
.caseRoles
, 'name');
525 // If it's not in the table already, add it, otherwise do nothing since
526 // don't want to add it twice.
527 if (!_
.contains(names
, matchingRole
.xmlName
)) {
528 roles
.push({name
: matchingRole
.xmlName
, displayLabel
: matchingRole
.text
});
531 // Not a known relationship type, so create on-the-fly.
532 // At this point roleIdOrLabel must be the new label they just typed.
533 CRM
.loadForm(CRM
.url('civicrm/admin/reltype', {action
: 'add', reset
: 1, label_a_b
: roleIdOrLabel
}))
534 .on('crmFormSuccess', function(e
, data
) {
535 var newType
= _
.values(data
.relationshipType
)[0];
536 $scope
.$apply(function() {
537 $scope
.addRoleOnTheFly(roles
, newType
);
543 // Add a newly created relationship type as a role to the table and
544 // update the list of options.
546 // @param roles array
547 // The roles currently in the table.
548 // @param newType array
549 // The array returned from the api call that created the new type
551 $scope
.addRoleOnTheFly = function(roles
, newType
) {
552 // Add it to the roles table. Assume they want the A-B direction since
553 // that's what they would have typed.
554 // Name and label are opposites here because name represents the value
555 // in the xml here which historically is the opposite.
556 roles
.push({name
: newType
.name_b_a
, displayLabel
: newType
.label_a_b
});
558 // But now add both directions as option choices for future dropdown
560 // Note that to keep in line with the original population on init,
561 // we're pushing a different direction here than we just added to the
562 // table, but there's only two possibilities:
563 // 1. Labels are the same, and since it's a new type name_a_b and
564 // name_b_a will therefore be the same, and so it doesn't matter which
565 // name is stored in caseRoles.
566 // 2. Labels are different, in which case we're also going to push the
567 // other direction below.
568 // So either way we're covered.
569 // See also note in getRelationshipTypeOptions().
570 var newRelTypeOption
= {
571 xmlName
: newType
.name_a_b
,
572 // Yes text is the opposite direction from name here.
573 text
: newType
.label_b_a
,
574 id
: newType
.id
+ '_a_b'
576 $scope
.relationshipTypeOptions
.push(newRelTypeOption
);
577 $scope
.relationshipTypeOptionsAll
.push(newRelTypeOption
);
578 // Add the other direction if different.
579 if (newType
.label_a_b
!= newType
.label_b_a
) {
581 xmlName
: newType
.name_b_a
,
582 text
: newType
.label_a_b
,
583 id
: newType
.id
+ '_b_a'
585 $scope
.relationshipTypeOptions
.push(newRelTypeOption
);
586 $scope
.relationshipTypeOptionsAll
.push(newRelTypeOption
);
590 $scope
.onManagerChange = function(managerRole
) {
591 angular
.forEach($scope
.caseType
.definition
.caseRoles
, function(caseRole
) {
592 if (caseRole
!= managerRole
) {
593 caseRole
.manager
= '0';
598 $scope
.removeItem = function(array
, item
) {
599 var idx
= _
.indexOf(array
, item
);
601 array
.splice(idx
, 1);
602 resetTimelineActivityTypes();
606 $scope
.isForkable = function() {
607 return !$scope
.caseType
.id
|| $scope
.caseType
.is_forkable
;
610 $scope
.newStatus = function() {
611 CRM
.loadForm(CRM
.url('civicrm/admin/options/case_status', {action
: 'add', reset
: 1}))
612 .on('crmFormSuccess', function(e
, data
) {
613 $scope
.caseStatuses
[data
.optionValue
.name
] = data
.optionValue
;
614 $scope
.selectedStatuses
[data
.optionValue
.name
] = true;
619 $scope
.isNewActivitySetAllowed = function(workflow
) {
624 return 0 === _
.where($scope
.caseType
.definition
.activitySets
, {sequence
: '1'}).length
;
626 CRM
.console('warn', 'Denied access to unrecognized workflow: (' + workflow
+ ')');
631 $scope
.isActivityRemovable = function(activitySet
, activity
) {
635 $scope
.isValidName = function(name
) {
636 return !name
|| name
.match(/^[a-zA-Z0-9_]+$/);
639 $scope
.getWorkflowName = function(activitySet
) {
640 var result
= 'Unknown';
641 _
.each($scope
.workflows
, function(value
, key
) {
642 if (activitySet
[key
]) result
= value
;
648 * Determine which HTML partial to use for a particular
650 * @return string URL of the HTML partial
652 $scope
.activityTableTemplate = function(activitySet
) {
653 if (activitySet
.timeline
) {
654 return '~/crmCaseType/timelineTable.html';
655 } else if (activitySet
.sequence
) {
656 return '~/crmCaseType/sequenceTable.html';
662 $scope
.dump = function() {
663 console
.log($scope
.caseType
);
666 $scope
.save = function() {
667 // Add selected statuses
668 var selectedStatuses
= [];
669 _
.each($scope
.selectedStatuses
, function(v
, k
) {
670 if (v
) selectedStatuses
.push(k
);
672 // Ignore if ALL or NONE selected
673 $scope
.caseType
.definition
.statuses
= selectedStatuses
.length
== _
.size($scope
.selectedStatuses
) ? [] : selectedStatuses
;
675 if ($scope
.caseType
.definition
.activityAsgmtGrps
) {
676 $scope
.caseType
.definition
.activityAsgmtGrps
= $scope
.caseType
.definition
.activityAsgmtGrps
.toString().split(",");
679 function dropDisplaylabel (v
) {
680 delete v
.displayLabel
;
683 // strip out labels from $scope.caseType.definition.caseRoles
684 _
.map($scope
.caseType
.definition
.caseRoles
, dropDisplaylabel
);
686 var result
= crmApi('CaseType', 'create', $scope
.caseType
, true);
687 result
.then(function(data
) {
688 if (data
.is_error
=== 0 || data
.is_error
== '0') {
689 $scope
.caseType
.id
= data
.id
;
690 window
.location
.href
= '#/caseType';
695 $scope
.$watchCollection('caseType.definition.activitySets', function() {
697 $('.crmCaseType-acttab').tabs('refresh');
701 var updateCaseTypeName = function () {
702 if (!$scope
.caseType
.id
&& $scope
.locks
.caseTypeName
) {
703 // Should we do some filtering? Lowercase? Strip whitespace?
704 var t
= $scope
.caseType
.title
? $scope
.caseType
.title
: '';
705 $scope
.caseType
.name
= t
.replace(/ /g, '_').replace(/[^a
-zA
-Z0
-9_
]/g
, '').toLowerCase();
708 $scope
.$watch('locks.caseTypeName', updateCaseTypeName
);
709 $scope
.$watch('caseType.title', updateCaseTypeName
);
711 if (!$scope
.isForkable()) {
712 CRM
.alert(ts('The CiviCase XML file for this case-type prohibits editing the definition.'));
717 crmCaseType
.controller('CaseTypeListCtrl', function($scope
, crmApi
, caseTypes
) {
718 var ts
= $scope
.ts
= CRM
.ts(null);
720 $scope
.caseTypes
= caseTypes
.values
;
721 $scope
.toggleCaseType = function (caseType
) {
722 caseType
.is_active
= (caseType
.is_active
== '1') ? '0' : '1';
723 crmApi('CaseType', 'create', caseType
, true)
724 .catch(function (data
) {
725 caseType
.is_active
= (caseType
.is_active
== '1') ? '0' : '1'; // revert
729 $scope
.deleteCaseType = function (caseType
) {
730 crmApi('CaseType', 'delete', {id
: caseType
.id
}, {
731 error: function (data
) {
732 CRM
.alert(data
.error_message
, ts('Error'), 'error');
735 .then(function (data
) {
736 delete caseTypes
.values
[caseType
.id
];
739 $scope
.revertCaseType = function (caseType
) {
740 caseType
.definition
= 'null';
741 caseType
.is_forked
= '0';
742 crmApi('CaseType', 'create', caseType
, true)
743 .catch(function (data
) {
744 caseType
.is_forked
= '1'; // restore
750 })(angular
, CRM
.$, CRM
._
);