4 * Class CRM_Case_BAO_CaseTest
7 class CRM_Case_BAO_CaseTest
extends CiviUnitTestCase
{
9 public function setUp(): void
{
12 $this->tablesToTruncate
= [
15 'civicrm_custom_group',
16 'civicrm_custom_field',
18 'civicrm_case_contact',
19 'civicrm_case_activity',
21 'civicrm_activity_contact',
23 'civicrm_relationship',
24 'civicrm_relationship_type',
27 $this->quickCleanup($this->tablesToTruncate
);
29 $this->loadAllFixtures();
31 // I don't understand why need to disable but if don't then only one
32 // case type is defined on 2nd and subsequent dataprovider runs.
33 CRM_Core_BAO_ConfigSetting
::disableComponent('CiviCase');
34 CRM_Core_BAO_ConfigSetting
::enableComponent('CiviCase');
38 * Make sure that the latest case activity works accurately.
40 public function testCaseActivity() {
41 $userID = $this->createLoggedInUser();
43 $addTimeline = civicrm_api3('Case', 'addtimeline', [
45 'timeline' => "standard_timeline",
48 $query = CRM_Case_BAO_Case
::getCaseActivityQuery('recent', $userID, ' civicrm_case.id IN( 1 )');
49 $res = CRM_Core_DAO
::executeQuery($query);
50 $openCaseType = CRM_Core_PseudoConstant
::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Open Case');
51 while ($res->fetch()) {
52 $message = 'Failed asserting that the case activity query has a activity_type_id property:';
53 $this->assertObjectHasAttribute('activity_type_id', $res, $message . PHP_EOL
. print_r($res, TRUE));
54 $message = 'Failed asserting that the latest activity from Case ID 1 was "Open Case":';
55 $this->assertEquals($openCaseType, $res->activity_type_id
, $message . PHP_EOL
. print_r($res, TRUE));
59 protected function tearDown(): void
{
61 $this->quickCleanup($this->tablesToTruncate
, TRUE);
64 public function testAddCaseToContact() {
69 CRM_Case_BAO_CaseContact
::create($params);
71 $recent = CRM_Utils_Recent
::get();
72 $this->assertEquals('Test Contact - Housing Support', $recent[0]['title']);
76 * Create case role relationship between given contacts for provided case ID.
81 * @param bool $isActive
83 private function createCaseRoleRelationship($contactIdA, $contactIdB, $caseId, $isActive = TRUE) {
84 $relationshipType = $this->relationshipTypeCreate([
85 'contact_type_b' => 'Individual',
88 $this->callAPISuccess('Relationship', 'create', [
89 'contact_id_a' => $contactIdA,
90 'contact_id_b' => $contactIdB,
91 'relationship_type_id' => $relationshipType,
93 'is_active' => $isActive,
98 * Asserts number of cases for given logged in user.
100 * @param $loggedInUser
104 private function assertCasesOfUser($loggedInUser, $caseId, $caseCount) {
105 $summary = CRM_Case_BAO_Case
::getCasesSummary(FALSE);
106 $upcomingCases = CRM_Case_BAO_Case
::getCases(FALSE, [], 'dashboard', TRUE);
107 $caseRoles = CRM_Case_BAO_Case
::getCaseRoles($loggedInUser, $caseId);
109 $this->assertEquals($caseCount, $upcomingCases, 'Upcoming case count must be ' . $caseCount);
110 if ($caseCount === 0) {
111 // If there really are 0 cases then there won't be any subelements for
112 // status and count, so we get a false error if we use the assertEquals
113 // check since it tries to get a subelement on type int. In this case
114 // the summary rows are just the case type pseudoconstant list.
115 $this->assertSame(array_flip(CRM_Case_PseudoConstant
::caseType()), $summary['rows']);
118 $this->assertEquals($caseCount, $summary['rows']['Housing Support']['Ongoing']['count'], 'Housing Support Ongoing case summary must be ' . $caseCount);
120 $this->assertEquals($caseCount, count($caseRoles), 'Total case roles for logged in users must be ' . $caseCount);
124 * core/issue-1623: My Case dashlet doesn't sort by name but contact_id instead
126 * @throws \CRM_Core_Exception
128 public function testSortByCaseContact() {
129 // delete any cases if present
130 $this->callAPISuccess('Case', 'get', ['api.Case.delete' => ['id' => '$value.id']]);
132 // create three contacts with different name, later used in respective cases
134 $this->individualCreate(['first_name' => 'Antonia', 'last_name' => 'D`souza']),
135 $this->individualCreate(['first_name' => 'Darric', 'last_name' => 'Roy']),
136 $this->individualCreate(['first_name' => 'Adam', 'last_name' => 'Pitt']),
138 $loggedInUser = $this->createLoggedInUser();
139 $relationshipType = $this->relationshipTypeCreate([
140 'contact_type_b' => 'Individual',
143 // create cases for each contact
145 foreach ($contacts as $contactID) {
146 $cases[] = $caseID = $this->createCase($contactID)->id
;
147 $this->callAPISuccess('Relationship', 'create', [
148 'contact_id_a' => $contactID,
149 'contact_id_b' => $loggedInUser,
150 'relationship_type_id' => $relationshipType,
151 'case_id' => $caseID,
156 // USECASE A: fetch all cases using the AJAX fn without any sorting criteria, and match the result
168 CRM_Case_Page_AJAX
::getCases();
170 catch (CRM_Core_Exception_PrematureExitException
$e) {
171 $cases = $e->errorData
['data'];
174 // list of expected sorted names in order the respective cases were created
175 $unsortedExpectedContactNames = [
180 $unsortedActualContactNames = CRM_Utils_Array
::collect('sort_name', $cases);
181 foreach ($unsortedExpectedContactNames as $key => $name) {
182 // Something has changed recently that has exposed one of the problems with queries that are not full-groupby-compliant. Temporarily commenting this out until figure out what to do since this exact query doesn't seem to come up anywhere on common screens.
183 //$this->assertContains($name, $unsortedActualContactNames[$key]);
186 // USECASE B: fetch all cases using the AJAX fn based any 'Contact' sorting criteria, and match the result against expected sequence of names
195 'data' => 'sort_name',
197 'searchable' => TRUE,
215 CRM_Case_Page_AJAX
::getCases();
217 catch (CRM_Core_Exception_PrematureExitException
$e) {
218 $cases = $e->errorData
['data'];
221 // list of expected sorted names in ASC order
222 $sortedExpectedContactNames = [
227 $sortedActualContactNames = CRM_Utils_Array
::collect('sort_name', $cases);
228 foreach ($sortedExpectedContactNames as $key => $name) {
229 $this->assertStringContainsString($name, $sortedActualContactNames[$key]);
234 * Test that Case count is exactly one for logged in user for user's active role.
236 * @throws \CRM_Core_Exception
238 public function testActiveCaseRole() {
239 $individual = $this->individualCreate();
240 $caseObj = $this->createCase($individual);
241 $caseId = $caseObj->id
;
242 $loggedInUser = $this->createLoggedInUser();
243 $this->createCaseRoleRelationship($individual, $loggedInUser, $caseId);
244 $this->assertCasesOfUser($loggedInUser, $caseId, 1);
248 * Test that case count is zero for logged in user for user's inactive role.
250 public function testInactiveCaseRole() {
251 $individual = $this->individualCreate();
252 $caseObj = $this->createCase($individual);
253 $caseId = $caseObj->id
;
254 $loggedInUser = $this->createLoggedInUser();
255 $this->createCaseRoleRelationship($individual, $loggedInUser, $caseId, FALSE);
256 $this->assertCasesOfUser($loggedInUser, $caseId, 0);
259 public function testGetCaseType() {
260 $caseTypeLabel = CRM_Case_BAO_Case
::getCaseType(1);
261 $this->assertEquals('Housing Support', $caseTypeLabel);
264 public function testRetrieveCaseIdsByContactId() {
265 $caseIds = CRM_Case_BAO_Case
::retrieveCaseIdsByContactId(3, FALSE, 'housing_support');
266 $this->assertEquals([1], $caseIds);
270 * Test that all custom files are migrated to new case when case is assigned to new client.
272 public function testCaseReassignForCustomFiles() {
273 $individual = $this->individualCreate();
274 $customGroup = $this->customGroupCreate(array(
277 $customGroup = $customGroup['values'][$customGroup['id']];
279 $customFileFieldA = $this->customFieldCreate(array(
280 'custom_group_id' => $customGroup['id'],
281 'html_type' => 'File',
283 'default_value' => 'null',
284 'label' => 'Custom File A',
285 'data_type' => 'File',
288 $customFileFieldB = $this->customFieldCreate(array(
289 'custom_group_id' => $customGroup['id'],
290 'html_type' => 'File',
292 'default_value' => 'null',
293 'label' => 'Custom File B',
294 'data_type' => 'File',
297 // Create two files to attach to the new case
298 $filepath = Civi
::paths()->getPath('[civicrm.files]/custom');
300 CRM_Utils_File
::createFakeFile($filepath, 'Bananas do not bend themselves without a little help.', 'i_bend_bananas.txt');
301 $fileA = $this->callAPISuccess('File', 'create', ['uri' => "$filepath/i_bend_bananas.txt"]);
303 CRM_Utils_File
::createFakeFile($filepath, 'Wombats will bite your ankles if you run from them.', 'wombats_bite_your_ankles.txt');
304 $fileB = $this->callAPISuccess('File', 'create', ['uri' => "$filepath/wombats_bite_your_ankles.txt"]);
306 $caseObj = $this->createCase($individual);
308 $this->callAPISuccess('Case', 'create', array(
309 'id' => $caseObj->id
,
310 'custom_' . $customFileFieldA['id'] => $fileA['id'],
311 'custom_' . $customFileFieldB['id'] => $fileB['id'],
314 $reassignIndividual = $this->individualCreate();
315 $this->createLoggedInUser();
316 $newCase = CRM_Case_BAO_Case
::mergeCases($reassignIndividual, $caseObj->id
, $individual, NULL, TRUE);
318 $entityFiles = new CRM_Core_DAO_EntityFile();
319 $entityFiles->entity_id
= $newCase[0];
320 $entityFiles->entity_table
= $customGroup['table_name'];
321 $entityFiles->find();
323 $totalEntityFiles = 0;
324 while ($entityFiles->fetch()) {
328 $this->assertEquals(2, $totalEntityFiles, 'Two files should be attached with new case.');
332 * FIXME: need to create an activity to run this test
333 * function testGetCases() {
334 * $cases = CRM_Case_BAO_Case::getCases(TRUE, 3);
335 * $this->assertEquals('Housing Support', $cases[1]['case_type']);
336 * $this->assertEquals(1, $cases[1]['case_type_id']);
339 public function testGetCasesSummary() {
340 $cases = CRM_Case_BAO_Case
::getCasesSummary();
341 $this->assertEquals(1, $cases['rows']['Housing Support']['Ongoing']['count']);
345 * Test that getRelatedCases() returns the other case when you create a
346 * Link Cases activity on one of the cases.
348 public function testGetRelatedCases() {
349 $loggedInUser = $this->createLoggedInUser();
351 $client_id_1 = $this->individualCreate([], 0);
352 $caseObj_1 = $this->createCase($client_id_1, $loggedInUser);
353 $case_id_1 = $caseObj_1->id
;
354 $client_id_2 = $this->individualCreate([], 1);
355 $caseObj_2 = $this->createCase($client_id_2, $loggedInUser);
356 $case_id_2 = $caseObj_2->id
;
358 // Create link case activity. We could go thru the whole form processes
359 // but we really just want to test the BAO function so just need the
360 // activity to exist.
361 $result = $this->callAPISuccess('activity', 'create', [
362 'activity_type_id' => 'Link Cases',
363 'subject' => 'Test Link Cases',
364 'status_id' => 'Completed',
365 'source_contact_id' => $loggedInUser,
366 'target_contact_id' => $client_id_1,
367 'case_id' => $case_id_1,
370 // Put it in the format needed for endPostProcess
371 $activity = new StdClass();
372 $activity->id
= $result['id'];
374 'link_to_case_id' => $case_id_2,
376 CRM_Case_Form_Activity_LinkCases
::endPostProcess(NULL, $params, $activity);
378 // Get related cases for case 1
379 $cases = CRM_Case_BAO_Case
::getRelatedCases($case_id_1);
380 // It should have case 2
381 $this->assertEquals($case_id_2, $cases[$case_id_2]['case_id']);
383 // Ditto but reverse the cases
384 $cases = CRM_Case_BAO_Case
::getRelatedCases($case_id_2);
385 $this->assertEquals($case_id_1, $cases[$case_id_1]['case_id']);
389 * Test various things after a case is closed.
391 * This annotation is not ideal, but without it there is some kind of
392 * messup that happens to quickform that persists between tests, e.g.
393 * it can't add maxfilesize validation rules.
394 * @runInSeparateProcess
395 * @preserveGlobalState disabled
397 public function testCaseClosure() {
398 $loggedInUser = $this->createLoggedInUser();
399 $client_id = $this->individualCreate();
400 $caseObj = $this->createCase($client_id, $loggedInUser);
401 $case_id = $caseObj->id
;
403 // Get the case status option value for "Resolved" (name="Closed").
404 $closed_status = $this->callAPISuccess('OptionValue', 'getValue', [
406 'option_group_id' => 'case_status',
409 $this->assertNotEmpty($closed_status);
411 // Get the activity status option value for "Completed"
412 $completed_status = $this->callAPISuccess('OptionValue', 'getValue', [
414 'option_group_id' => 'activity_status',
415 'name' => 'Completed',
417 $this->assertNotEmpty($completed_status);
419 // Get the value for the activity type id we need to create
420 $atype = CRM_Core_PseudoConstant
::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Change Case Status');
422 // Now it gets weird. There doesn't seem to be a good way to test this, so we simulate a form and the various bits that go with it.
424 // HTTP vars needed because that's how the form determines stuff
425 $oldMETHOD = empty($_SERVER['REQUEST_METHOD']) ?
NULL : $_SERVER['REQUEST_METHOD'];
426 $oldGET = empty($_GET) ?
[] : $_GET;
427 $oldREQUEST = empty($_REQUEST) ?
[] : $_REQUEST;
428 $_SERVER['REQUEST_METHOD'] = 'GET';
429 $_GET['caseid'] = $case_id;
430 $_REQUEST['caseid'] = $case_id;
431 $_GET['cid'] = $client_id;
432 $_REQUEST['cid'] = $client_id;
433 $_GET['action'] = 'add';
434 $_REQUEST['action'] = 'add';
436 $_REQUEST['reset'] = 1;
437 $_GET['atype'] = $atype;
438 $_REQUEST['atype'] = $atype;
440 $form = new CRM_Case_Form_Activity();
441 $form->controller
= new CRM_Core_Controller_Simple('CRM_Case_Form_Activity', 'Case Activity');
442 $form->_activityTypeId
= $atype;
443 $form->_activityTypeName
= 'Change Case Status';
444 $form->_activityTypeFile
= 'ChangeCaseStatus';
447 $form->buildQuickForm();
448 $form->setDefaultValues();
450 // Now submit the form. Store the date used so we can check it later.
453 $now_date = date('Y-m-d H:i:s', $t);
454 $now_date_date_only = date('Y-m-d', $t);
456 'is_unittest' => TRUE,
457 'case_status_id' => $closed_status,
458 'activity_date_time' => $now_date,
459 'target_contact_id' => $client_id,
460 'source_contact_id' => $loggedInUser,
461 // yeah this is extra weird, but without it you get the wrong subject
465 $form->postProcess($actParams);
467 // Ok now let's check some things
469 $result = $this->callAPISuccess('Case', 'get', [
473 $caseData = array_shift($result['values']);
475 $this->assertEquals($caseData['end_date'], $now_date_date_only);
476 $this->assertEquals($caseData['status_id'], $closed_status);
478 // now get the latest activity and check some things for it
480 $actId = max($caseData['activities']);
481 $this->assertNotEmpty($actId);
483 $result = $this->callAPISuccess('Activity', 'get', [
487 $activity = array_shift($result['values']);
489 $this->assertEquals($activity['subject'], 'Case status changed from Ongoing to Resolved');
490 $this->assertEquals($activity['activity_date_time'], $now_date);
491 $this->assertEquals($activity['status_id'], $completed_status);
493 // Now replace old globals
494 if (is_null($oldMETHOD)) {
495 unset($_SERVER['REQUEST_METHOD']);
498 $_SERVER['REQUEST_METHOD'] = $oldMETHOD;
501 $_REQUEST = $oldREQUEST;
505 * Test getGlobalContacts
507 public function testGetGlobalContacts() {
508 //Add contact to case resource.
509 $caseResourceContactID = $this->individualCreate();
510 $this->callAPISuccess('GroupContact', 'create', [
511 'group_id' => "Case_Resources",
512 'contact_id' => $caseResourceContactID,
515 //No contact should be returned.
516 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= [];
518 $groupContacts = CRM_Case_BAO_Case
::getGlobalContacts($groupInfo);
519 $this->assertEquals(0, count($groupContacts));
521 //Verify if contact is returned correctly.
522 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= [
527 $groupContacts = CRM_Case_BAO_Case
::getGlobalContacts($groupInfo);
528 $this->assertEquals(1, count($groupContacts));
529 $this->assertEquals($caseResourceContactID, key($groupContacts));
535 public function testMaxInstances() {
536 $loggedInUser = $this->createLoggedInUser();
537 $client_id = $this->individualCreate();
538 $caseObj = $this->createCase($client_id, $loggedInUser);
539 $case_id = $caseObj->id
;
541 // Sanity check to make sure we'll be testing what we think we're testing.
542 $this->assertEquals($caseObj->case_type_id
, 1);
545 $result = $this->callAPISuccess('CaseType', 'get', [
549 $caseType = array_shift($result['values']);
550 $activityTypeName = $caseType['definition']['activityTypes'][1]['name'];
551 // Sanity check to make sure we'll be testing what we think we're testing.
552 $this->assertEquals($activityTypeName, "Medical evaluation");
554 // Look up the activity type label - we need it later
555 $result = $this->callAPISuccess('OptionValue', 'get', [
557 'option_group_id' => 'activity_type',
558 'name' => $activityTypeName,
560 $optionValue = array_shift($result['values']);
561 $activityTypeLabel = $optionValue['label'];
562 $this->assertNotEmpty($activityTypeLabel);
564 // Locate the existing activity independently so we can check it
565 $result = $this->callAPISuccess('Activity', 'get', [
567 // this sometimes confuses me - pass in the name for the id
568 'activity_type_id' => $activityTypeName,
570 // There should be only one in the database at this point so this should be the id.
571 $activity_id = $result['id'];
572 $this->assertNotEmpty($activity_id);
573 $this->assertGreaterThan(0, $activity_id);
574 $activityArr = array_shift($result['values']);
576 // At the moment everything should be happy, although there's nothing to test because if max_instances has no value then nothing gets called, which is correct since it means unlimited. But we don't have a way to test that right now. For fun we could test max_instances=0 but that isn't the same as "not set". 0 would actually mean 0 are allowed, which is pointless, since then why would you even add the activity type to the config.
578 // Update max instances for the activity type
579 // We're not really checking that the tested code has retrieved the new case type definition, just that given some numbers as input it returns the right thing as output, so these lines are mostly symbolic at the moment.
580 $caseType['definition']['activityTypes'][1]['max_instances'] = 1;
581 $this->callAPISuccess('CaseType', 'create', $caseType);
583 // Now we should get a link back
584 $editUrl = CRM_Case_Form_Activity
::checkMaxInstances(
586 $activityArr['activity_type_id'],
591 // existing activity count
594 $this->assertNotNull($editUrl);
596 $expectedUrl = CRM_Utils_System
::url(
597 'civicrm/case/activity',
598 "reset=1&cid={$client_id}&caseid={$case_id}&action=update&id={$activity_id}"
600 $this->assertEquals($editUrl, $expectedUrl);
602 // And also a bounce message is expected
603 $bounceMessage = CRM_Case_Form_Activity
::getMaxInstancesBounceMessage(
608 // existing activity count
611 $this->assertNotEmpty($bounceMessage);
613 // Now check with max_instances = 2
614 $caseType['definition']['activityTypes'][1]['max_instances'] = 2;
615 $this->callAPISuccess('CaseType', 'create', $caseType);
617 // So it should now be back to being happy
618 $editUrl = CRM_Case_Form_Activity
::checkMaxInstances(
620 $activityArr['activity_type_id'],
625 // existing activity count
628 $this->assertNull($editUrl);
629 $bounceMessage = CRM_Case_Form_Activity
::getMaxInstancesBounceMessage(
634 // existing activity count
637 $this->assertEmpty($bounceMessage);
639 // Add new activity check again
641 'case_id' => $case_id,
642 'activity_type_id' => $activityArr['activity_type_id'],
643 'status_id' => $activityArr['status_id'],
644 'subject' => "A different subject",
645 'activity_date_time' => date('Y-m-d H:i:s'),
646 'source_contact_id' => $loggedInUser,
647 'target_id' => $client_id,
649 $this->callAPISuccess('Activity', 'create', $newActivity);
651 $editUrl = CRM_Case_Form_Activity
::checkMaxInstances(
653 $activityArr['activity_type_id'],
658 // existing activity count
661 // There should be no url here.
662 $this->assertNull($editUrl);
664 // But there should be a warning message still.
665 $bounceMessage = CRM_Case_Form_Activity
::getMaxInstancesBounceMessage(
670 // existing activity count
673 $this->assertNotEmpty($bounceMessage);
677 * Test changing the label for the case manager role and then creating
679 * At the time this test was written this test would fail, demonstrating
680 * one problem with name vs label.
682 public function testCreateCaseWithChangedManagerLabel() {
683 // We could just assume the relationship that gets created has
684 // relationship_type_id = 1, but let's create a case, see what the
685 // id is, then do our actual test.
686 $loggedInUser = $this->createLoggedInUser();
687 $client_id = $this->individualCreate();
688 $caseObj = $this->createCase($client_id, $loggedInUser);
689 $case_id = $caseObj->id
;
691 // Going to assume the stock case type has what it currently has at the
692 // time of writing, which is the autocreated case manager relationship for
693 // the logged in user.
695 'contact_id_b' => $loggedInUser,
696 'case_id' => $case_id,
698 $result = $this->callAPISuccess('Relationship', 'get', $getParams);
699 // as noted above assume this is the only one
700 $relationship_type_id = $result['values'][$result['id']]['relationship_type_id'];
702 // Save the old labels first so we can put back at end of test.
704 'id' => $relationship_type_id,
706 $oldValues = $this->callAPISuccess('RelationshipType', 'get', $oldParams);
707 // Now change the label of the relationship type.
709 'id' => $relationship_type_id,
710 'label_a_b' => 'Best ' . $oldValues['values'][$relationship_type_id]['label_a_b'],
711 'label_b_a' => 'Best ' . $oldValues['values'][$relationship_type_id]['label_b_a'],
713 $this->callAPISuccess('RelationshipType', 'create', $changeParams);
715 // Now try creating another case.
716 $caseObj2 = $this->createCase($client_id, $loggedInUser);
717 $case_id2 = $caseObj2->id
;
720 'contact_id_b' => $loggedInUser,
721 'case_id' => $case_id2,
723 $result = $this->callAPISuccess('Relationship', 'get', $checkParams);
724 // Main thing is the above createCase call doesn't fail, but let's check
725 // the relationship type id is what we expect too while we're here.
726 // See note above about assuming this is the only relationship autocreated.
727 $this->assertEquals($relationship_type_id, $result['values'][$result['id']]['relationship_type_id']);
729 // Now put relationship type back to the way it was.
731 'id' => $relationship_type_id,
732 'label_a_b' => $oldValues['values'][$relationship_type_id]['label_a_b'],
733 'label_b_a' => $oldValues['values'][$relationship_type_id]['label_b_a'],
735 $this->callAPISuccess('RelationshipType', 'create', $changeParams);
739 * Test change case status with linked cases choosing the option to
740 * update the linked cases.
742 public function testChangeCaseStatusLinkedCases() {
743 $loggedInUser = $this->createLoggedInUser();
744 $clientId1 = $this->individualCreate();
745 $clientId2 = $this->individualCreate();
746 $case1 = $this->createCase($clientId1, $loggedInUser);
747 $case2 = $this->createCase($clientId2, $loggedInUser);
748 $linkActivity = $this->callAPISuccess('Activity', 'create', [
749 'case_id' => $case1->id
,
750 'source_contact_id' => $loggedInUser,
751 'target_contact' => $clientId1,
752 'activity_type_id' => 'Link Cases',
753 'subject' => 'Test Link Cases',
754 'status_id' => 'Completed',
757 // Put it in the format needed for endPostProcess
758 $activity = new StdClass();
759 $activity->id
= $linkActivity['id'];
760 $params = ['link_to_case_id' => $case2->id
];
761 CRM_Case_Form_Activity_LinkCases
::endPostProcess(NULL, $params, $activity);
763 // Get the option_value.value for case status Closed
764 $closedStatusResult = $this->callAPISuccess('OptionValue', 'get', [
765 'option_group_id' => 'case_status',
767 'return' => ['value'],
769 $closedStatus = $closedStatusResult['values'][$closedStatusResult['id']]['value'];
771 // Go thru the motions to change case status
772 $form = new CRM_Case_Form_Activity_ChangeCaseStatus();
773 $form->_caseId
= [$case1->id
];
774 $form->_oldCaseStatus
= [$case1->status_id
];
777 'case_status_id' => $closedStatus,
778 'updateLinkedCases' => '1',
781 CRM_Case_Form_Activity_ChangeCaseStatus
::beginPostProcess($form, $params);
782 // Check that the second case is now also in the form member.
783 $this->assertEquals([$case1->id
, $case2->id
], $form->_caseId
);
785 // We need to pass in an actual activity later
786 $result = $this->callAPISuccess('Activity', 'create', [
787 'case_id' => $case1->id
,
788 'source_contact_id' => $loggedInUser,
789 'target_contact' => $clientId1,
790 'activity_type_id' => 'Change Case Status',
791 'subject' => 'Status changed',
792 'status_id' => 'Completed',
794 $changeStatusActivity = new CRM_Activity_DAO_Activity();
795 $changeStatusActivity->id
= $result['id'];
796 $changeStatusActivity->find(TRUE);
799 'case_id' => $case1->id
,
800 'target_contact_id' => [$clientId1],
801 'case_status_id' => $closedStatus,
802 'activity_date_time' => $changeStatusActivity->activity_date_time
,
805 CRM_Case_Form_Activity_ChangeCaseStatus
::endPostProcess($form, $params, $changeStatusActivity);
807 // @todo Check other case got closed.
809 * We can't do this here because it doesn't happen until the parent
810 * activity does its thing.
811 $linkedCase = $this->callAPISuccess('Case', 'get', ['id' => $case2->id]);
812 $this->assertEquals($closedStatus, $linkedCase['values'][$linkedCase['id']]['status_id']);
813 $this->assertEquals(date('Y-m-d', strtotime($changeStatusActivity->activity_date_time)), $linkedCase['values'][$linkedCase['id']]['end_date']);
818 * test getCaseActivityQuery
819 * @dataProvider caseActivityQueryProvider
820 * @param array $input
821 * @param array $expected
823 public function testGetCaseActivityQuery(array $input, array $expected): void
{
824 $activity_type_map = array_flip(CRM_Activity_BAO_Activity
::buildOptions('activity_type_id', 'validate'));
826 $loggedInUser = $this->createLoggedInUser();
827 $individual[1] = $this->individualCreate();
828 $caseObj[1] = $this->createCase($individual[1], $loggedInUser, [
829 // Unfortunately the query we're testing is not full-group-by compliant
830 // and does not have a well-defined sort order either. If we use the
831 // default casetype then there are two activities with the same date
832 // and sometimes you get one returned and sometimes the other. If the
833 // second case type timeline is altered in future it's possible the
834 // same problem could intermittently occur.
836 'case_type' => 'adult_day_care_referral',
838 $individual[2] = $this->individualCreate([], 1);
840 // create a second case with a different start date
841 $other_date = strtotime($input['other_date']);
842 $caseObj[2] = $this->createCase($individual[2], $loggedInUser, [
844 'case_type' => 'adult_day_care_referral',
845 'start_date' => date('Y-m-d', $other_date),
846 'start_date_time' => date('YmdHis', $other_date),
849 foreach (['upcoming', 'recent'] as $type) {
850 // See note above about the query being ill-defined, so this only works
851 // on mysql 5.7 and 8, similar to other tests in this file that call
852 // getCases which also uses this query.
853 $sql = CRM_Case_BAO_Case
::getCaseActivityQuery($type, $loggedInUser);
854 $dao = CRM_Core_DAO
::executeQuery($sql);
857 while ($dao->fetch()) {
858 $activities[$counter] = [
859 'case_id' => $dao->case_id
,
860 'case_subject' => $dao->case_subject
,
861 'contact_id' => (int) $dao->contact_id
,
862 'phone' => $dao->phone
,
863 'contact_type' => $dao->contact_type
,
864 'activity_type_id' => (int) $dao->activity_type_id
,
865 'case_type_id' => (int) $dao->case_type_id
,
866 'case_status_id' => (int) $dao->case_status_id
,
867 // This is activity status
868 'status_id' => (int) $dao->status_id
,
869 'case_start_date' => $dao->case_start_date
,
870 'case_role' => $dao->case_role
,
871 'activity_date_time' => $dao->activity_date_time
,
874 // Need to replace some placeholders since we don't know what they
875 // are at the time the dataprovider is evaluated.
876 $offset = $expected[$type][$counter]['which_case_offset'];
877 unset($expected[$type][$counter]['which_case_offset']);
878 $expected[$type][$counter]['case_id'] = $caseObj[$offset]->id
;
879 $expected[$type][$counter]['contact_id'] = $individual[$offset];
880 $expected[$type][$counter]['activity_type_id'] = $activity_type_map[$expected[$type][$counter]['activity_type_id']];
881 $expected[$type][$counter]['case_start_date'] = $caseObj[$offset]->start_date
;
882 // To avoid a millisecond rollover bug, where e.g. the dataprovider
883 // runs a whole second before this test, we make this relative to the
884 // case start date, which it is anyway in the timeline.
885 $expected[$type][$counter]['activity_date_time'] = date('Y-m-d H:i:s', strtotime($caseObj[$offset]->start_date
. $expected[$type][$counter]['activity_date_time']));
889 $this->assertEquals($expected[$type], $activities);
894 * dataprovider for testGetCaseActivityQuery
897 public function caseActivityQueryProvider(): array {
901 'other_date' => '-1 day',
906 'which_case_offset' => 2,
907 'case_id' => 'REPLACE_ME',
908 'case_subject' => 'Case Subject',
909 'contact_id' => 'REPLACE_ME',
911 'contact_type' => 'Individual',
912 'activity_type_id' => 'Medical evaluation',
914 'case_status_id' => 1,
916 'case_start_date' => 'REPLACE_ME',
917 'case_role' => 'Senior Services Coordinator is',
918 'activity_date_time' => ' +3 day',
921 'which_case_offset' => 1,
922 // REPLACE_ME's will get replaced in the test since the values haven't been created yet at the time dataproviders get evaluated.
923 'case_id' => 'REPLACE_ME',
924 'case_subject' => 'Case Subject',
925 'contact_id' => 'REPLACE_ME',
927 'contact_type' => 'Individual',
928 'activity_type_id' => 'Medical evaluation',
930 'case_status_id' => 1,
932 'case_start_date' => 'REPLACE_ME',
933 'case_role' => 'Senior Services Coordinator is',
934 'activity_date_time' => ' +3 day',
939 'which_case_offset' => 2,
940 'case_id' => 'REPLACE_ME',
941 'case_subject' => 'Case Subject',
942 'contact_id' => 'REPLACE_ME',
944 'contact_type' => 'Individual',
945 'activity_type_id' => 'Open Case',
947 'case_status_id' => 1,
949 'case_start_date' => 'REPLACE_ME',
950 'case_role' => 'Senior Services Coordinator is',
951 // means no offset from case start date
952 'activity_date_time' => '',
955 'which_case_offset' => 1,
956 'case_id' => 'REPLACE_ME',
957 'case_subject' => 'Case Subject',
958 'contact_id' => 'REPLACE_ME',
960 'contact_type' => 'Individual',
961 'activity_type_id' => 'Open Case',
963 'case_status_id' => 1,
965 'case_start_date' => 'REPLACE_ME',
966 'case_role' => 'Senior Services Coordinator is',
967 // means no offset from case start date
968 'activity_date_time' => '',
976 'other_date' => '-7 day',
981 'which_case_offset' => 2,
982 'case_id' => 'REPLACE_ME',
983 'case_subject' => 'Case Subject',
984 'contact_id' => 'REPLACE_ME',
986 'contact_type' => 'Individual',
987 'activity_type_id' => 'Medical evaluation',
989 'case_status_id' => 1,
991 'case_start_date' => 'REPLACE_ME',
992 'case_role' => 'Senior Services Coordinator is',
993 'activity_date_time' => ' +3 day',
996 'which_case_offset' => 1,
997 'case_id' => 'REPLACE_ME',
998 'case_subject' => 'Case Subject',
999 'contact_id' => 'REPLACE_ME',
1001 'contact_type' => 'Individual',
1002 'activity_type_id' => 'Medical evaluation',
1003 'case_type_id' => 2,
1004 'case_status_id' => 1,
1006 'case_start_date' => 'REPLACE_ME',
1007 'case_role' => 'Senior Services Coordinator is',
1008 'activity_date_time' => ' +3 day',
1013 'which_case_offset' => 2,
1014 'case_id' => 'REPLACE_ME',
1015 'case_subject' => 'Case Subject',
1016 'contact_id' => 'REPLACE_ME',
1018 'contact_type' => 'Individual',
1019 'activity_type_id' => 'Open Case',
1020 'case_type_id' => 2,
1021 'case_status_id' => 1,
1023 'case_start_date' => 'REPLACE_ME',
1024 'case_role' => 'Senior Services Coordinator is',
1025 // means no offset from case start date
1026 'activity_date_time' => '',
1029 'which_case_offset' => 1,
1030 'case_id' => 'REPLACE_ME',
1031 'case_subject' => 'Case Subject',
1032 'contact_id' => 'REPLACE_ME',
1034 'contact_type' => 'Individual',
1035 'activity_type_id' => 'Open Case',
1036 'case_type_id' => 2,
1037 'case_status_id' => 1,
1039 'case_start_date' => 'REPLACE_ME',
1040 'case_role' => 'Senior Services Coordinator is',
1041 // means no offset from case start date
1042 'activity_date_time' => '',
1050 'other_date' => '-14 day',
1055 'which_case_offset' => 2,
1056 'case_id' => 'REPLACE_ME',
1057 'case_subject' => 'Case Subject',
1058 'contact_id' => 'REPLACE_ME',
1060 'contact_type' => 'Individual',
1061 'activity_type_id' => 'Medical evaluation',
1062 'case_type_id' => 2,
1063 'case_status_id' => 1,
1065 'case_start_date' => 'REPLACE_ME',
1066 'case_role' => 'Senior Services Coordinator is',
1067 'activity_date_time' => ' +3 day',
1070 'which_case_offset' => 1,
1071 'case_id' => 'REPLACE_ME',
1072 'case_subject' => 'Case Subject',
1073 'contact_id' => 'REPLACE_ME',
1075 'contact_type' => 'Individual',
1076 'activity_type_id' => 'Medical evaluation',
1077 'case_type_id' => 2,
1078 'case_status_id' => 1,
1080 'case_start_date' => 'REPLACE_ME',
1081 'case_role' => 'Senior Services Coordinator is',
1082 'activity_date_time' => ' +3 day',
1087 'which_case_offset' => 1,
1088 'case_id' => 'REPLACE_ME',
1089 'case_subject' => 'Case Subject',
1090 'contact_id' => 'REPLACE_ME',
1092 'contact_type' => 'Individual',
1093 'activity_type_id' => 'Open Case',
1094 'case_type_id' => 2,
1095 'case_status_id' => 1,
1097 'case_start_date' => 'REPLACE_ME',
1098 'case_role' => 'Senior Services Coordinator is',
1099 // means no offset from case start date
1100 'activity_date_time' => '',
1108 'other_date' => '-21 day',
1113 'which_case_offset' => 2,
1114 'case_id' => 'REPLACE_ME',
1115 'case_subject' => 'Case Subject',
1116 'contact_id' => 'REPLACE_ME',
1118 'contact_type' => 'Individual',
1119 'activity_type_id' => 'Medical evaluation',
1120 'case_type_id' => 2,
1121 'case_status_id' => 1,
1123 'case_start_date' => 'REPLACE_ME',
1124 'case_role' => 'Senior Services Coordinator is',
1125 'activity_date_time' => ' +3 day',
1128 'which_case_offset' => 1,
1129 'case_id' => 'REPLACE_ME',
1130 'case_subject' => 'Case Subject',
1131 'contact_id' => 'REPLACE_ME',
1133 'contact_type' => 'Individual',
1134 'activity_type_id' => 'Medical evaluation',
1135 'case_type_id' => 2,
1136 'case_status_id' => 1,
1138 'case_start_date' => 'REPLACE_ME',
1139 'case_role' => 'Senior Services Coordinator is',
1140 'activity_date_time' => ' +3 day',
1145 'which_case_offset' => 1,
1146 'case_id' => 'REPLACE_ME',
1147 'case_subject' => 'Case Subject',
1148 'contact_id' => 'REPLACE_ME',
1150 'contact_type' => 'Individual',
1151 'activity_type_id' => 'Open Case',
1152 'case_type_id' => 2,
1153 'case_status_id' => 1,
1155 'case_start_date' => 'REPLACE_ME',
1156 'case_role' => 'Senior Services Coordinator is',
1157 // means no offset from case start date
1158 'activity_date_time' => '',
1167 * Test that if you only have "my cases" permission you can still view
1168 * Manage Case for **closed** cases of yours.
1170 public function testCanViewClosedCaseAsNonAdmin() {
1171 $loggedInUser = $this->createLoggedInUser();
1172 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= [
1174 'view all contacts',
1175 'edit all contacts',
1177 // this is one important part we're testing
1178 'access my cases and activities',
1180 $individual = $this->individualCreate();
1181 $caseObj = $this->createCase($individual, $loggedInUser);
1182 $caseId = $caseObj->id
;
1184 // This isn't everything needed to close a case but is good enough for
1186 $this->callAPISuccess('Case', 'create', [
1188 'status_id' => 'Closed',
1191 // Manage Case goes thru this tab even when not visiting from the tab.
1192 $tab = new CRM_Case_Page_Tab();
1193 $tab->set('action', 'view');
1194 $tab->set('cid', $individual);
1195 $tab->set('id', $caseId);
1196 $tab->set('context', 'standalone');
1198 // At this point it would have thrown PrematureExitException if we didn't have access.
1199 // Let's assert something while we're here. This is also what would have
1200 // failed, but by itself doesn't depend on permissions.
1201 $this->assertArrayHasKey($caseId, CRM_Case_BAO_Case
::getCases(FALSE, ['type' => 'any']));
1205 * Test a high number of assigned case roles.
1207 public function testGoingTo11() {
1208 $loggedInUser = $this->createLoggedInUser();
1209 $individual = $this->individualCreate();
1210 $caseObj = $this->createCase($individual, $loggedInUser);
1211 $caseId = $caseObj->id
;
1213 // Create lots of assigned roles
1214 for ($i = 1; $i <= 30; $i++
) {
1215 // create a new type
1216 $relationship_type_id = $this->callAPISuccess('RelationshipType', 'create', [
1217 'name_a_b' => "has as Wizard level $i",
1218 'name_b_a' => "is Wizard level $i for",
1221 // Now make a new person and give them the role
1222 $contact_id = $this->individualCreate([], 0, TRUE);
1223 $this->callAPISuccess('Relationship', 'create', [
1224 'case_id' => $caseId,
1225 'contact_id_a' => $individual,
1226 'contact_id_b' => $contact_id,
1227 'relationship_type_id' => $relationship_type_id,
1231 // Note the stock case type adds a manager role for the logged in user so it's 31 not 30.
1232 $this->assertCount(31, CRM_Case_BAO_Case
::getCaseRoles($individual, $caseId), 'Why not just make ten louder?');