Merge pull request #13916 from alifrumin/caserelstocore
authorcolemanw <coleman@civicrm.org>
Wed, 12 Jun 2019 15:19:46 +0000 (11:19 -0400)
committerGitHub <noreply@github.com>
Wed, 12 Jun 2019 15:19:46 +0000 (11:19 -0400)
dev/core#530 Make a_b relationships available as case roles

17 files changed:
CRM/Activity/Page/AJAX.php
CRM/Case/BAO/Case.php
CRM/Case/BAO/Query.php
CRM/Case/Form/AddToCaseAsRole.php
CRM/Case/Form/CaseView.php
CRM/Case/Info.php
CRM/Case/ManagedEntities.php
CRM/Case/XMLProcessor.php
CRM/Case/XMLProcessor/Process.php
CRM/Case/XMLProcessor/Report.php
CRM/Case/XMLRepository.php
CRM/Report/Form/Case/Summary.php
ang/crmCaseType.ang.php
ang/crmCaseType.js
ang/crmCaseType/rolesTable.html
tests/karma/unit/crmCaseTypeSpec.js
tests/phpunit/CRM/Case/BAO/CaseTypeForkTest.php

index 1da789c2734afd668870371312bbad87f18d9a29..6c78fb59c5b1d879c5f267da09a7487c6d6b6905 100644 (file)
@@ -161,7 +161,7 @@ class CRM_Activity_Page_AJAX {
 
     foreach ($caseRelationships as $key => $value) {
       // This role has been filled
-      unset($caseRoles[$value['relation_type']]);
+      unset($caseRoles[$value['relation_type'] . '_' . $value['relationship_direction']]);
       // mark original case relationships record to use on setting edit links below
       $caseRelationships[$key]['source'] = 'caseRel';
     }
@@ -209,7 +209,7 @@ class CRM_Activity_Page_AJAX {
     foreach ($caseRelationships as $key => &$row) {
       $typeLabel = $row['relation'];
       // Add "<br />(Case Manager)" to label
-      if (!empty($row['relation_type']) && $row['relation_type'] == $managerRoleId) {
+      if (!empty($row['relation_type']) && !empty($row['relationship_direction']) && $row['relation_type'] . '_' . $row['relationship_direction'] == $managerRoleId) {
         $row['relation'] .= '<br />' . '(' . ts('Case Manager') . ')';
       }
       // view user links
index aaa37f5a595ea41732b1cd28bf72c3a50e465ab6..f25f917749eebc7264bc6fac47866a9df52f198b 100644 (file)
@@ -434,7 +434,7 @@ WHERE cc.contact_id = %1 AND civicrm_case_type.name = '{$caseType}'";
       'civicrm_case.status_id as case_status_id',
       't_act.status_id as status_id',
       'civicrm_case.start_date as case_start_date',
-      'case_relation_type.label_b_a as case_role',
+      "GROUP_CONCAT(DISTINCT IF(case_relationship.contact_id_b = $userID, case_relation_type.label_a_b, case_relation_type.label_b_a) SEPARATOR ', ') as case_role",
       't_act.activity_date_time as activity_date_time',
       't_act.id as activity_id',
     );
@@ -475,8 +475,8 @@ HERESQL;
           ON civicrm_phone.contact_id = civicrm_contact.id
             AND civicrm_phone.is_primary = 1
         LEFT JOIN civicrm_relationship case_relationship
-          ON case_relationship.contact_id_a = civicrm_case_contact.contact_id
-            AND case_relationship.contact_id_b = {$userID}
+          ON ((case_relationship.contact_id_a = civicrm_case_contact.contact_id AND case_relationship.contact_id_b = {$userID})
+          OR (case_relationship.contact_id_b = civicrm_case_contact.contact_id AND case_relationship.contact_id_a = {$userID}))
             AND case_relationship.is_active
             AND case_relationship.case_id = civicrm_case.id
         LEFT JOIN civicrm_relationship_type case_relation_type
@@ -535,10 +535,11 @@ HERESQL;
     $whereClauses = array('civicrm_case.is_deleted = 0 AND civicrm_contact.is_deleted <> 1');
 
     if (!$allCases) {
-      $whereClauses[] .= " case_relationship.contact_id_b = {$userID} AND case_relationship.is_active ";
+      $whereClauses[] = "(case_relationship.contact_id_b = {$userID} OR case_relationship.contact_id_a = {$userID})";
+      $whereClauses[] = 'case_relationship.is_active';
     }
     if (empty($params['status_id']) && ($type == 'upcoming' || $type == 'any')) {
-      $whereClauses[] = " civicrm_case.status_id != " . CRM_Core_PseudoConstant::getKey('CRM_Case_BAO_Case', 'case_status_id', 'Closed');
+      $whereClauses[] = "civicrm_case.status_id != " . CRM_Core_PseudoConstant::getKey('CRM_Case_BAO_Case', 'case_status_id', 'Closed');
     }
 
     foreach (array('case_type_id', 'status_id') as $column) {
@@ -703,26 +704,28 @@ HERESQL;
 
     // build rows with actual data
     $rows = array();
-    $myGroupByClause = $mySelectClause = $myCaseFromClause = $myCaseWhereClause = '';
+    $myGroupByClause = $mySelectClause = $myCaseFromClause = $myCaseWhereClauseA = $myCaseWhereClauseB = '';
 
     if ($allCases) {
       $userID = 'null';
       $all = 1;
       $case_owner = 1;
-      $myGroupByClause = ' GROUP BY civicrm_case.id';
+      $myGroupByClauseB = ' GROUP BY civicrm_case.id';
     }
     else {
       $all = 0;
       $case_owner = 2;
-      $myCaseWhereClause = " AND case_relationship.contact_id_b = {$userID} AND case_relationship.is_active ";
-      $myGroupByClause = " GROUP BY CONCAT(case_relationship.case_id,'-',case_relationship.contact_id_b)";
+      $myCaseWhereClauseA = " AND case_relationship.contact_id_a = {$userID} AND case_relationship.is_active ";
+      $myGroupByClauseA = " GROUP BY CONCAT(civicrm_case.id,'-',case_relationship.contact_id_a)";
+      $myCaseWhereClauseB = " AND case_relationship.contact_id_b = {$userID} AND case_relationship.is_active ";
+      $myGroupByClauseB = " GROUP BY CONCAT(civicrm_case.id,'-',case_relationship.contact_id_b)";
     }
-    $myGroupByClause .= ", case_status.label, status_id, case_type_id";
-
+    $myGroupByClauseB .= ", case_status.label, status_id, case_type_id, civicrm_case.id";
+    $myGroupByClauseA = $myGroupByClauseB;
     // FIXME: This query could be a lot more efficient if it used COUNT() instead of returning all rows and then counting them with php
     $query = "
-SELECT case_status.label AS case_status, status_id, civicrm_case_type.title AS case_type,
- case_type_id, case_relationship.contact_id_b
+SELECT civicrm_case.id, case_status.label AS case_status, status_id, civicrm_case_type.title AS case_type,
+ case_type_id, case_relationship.contact_id_b as case_contact
  FROM civicrm_case
  INNER JOIN civicrm_case_contact cc on cc.case_id = civicrm_case.id
  LEFT JOIN civicrm_case_type ON civicrm_case.case_type_id = civicrm_case_type.id
@@ -732,7 +735,20 @@ SELECT case_status.label AS case_status, status_id, civicrm_case_type.title AS c
  LEFT JOIN civicrm_relationship case_relationship ON ( case_relationship.case_id  = civicrm_case.id
  AND case_relationship.contact_id_b = {$userID} AND case_relationship.is_active )
  WHERE is_deleted = 0 AND cc.contact_id IN (SELECT id FROM civicrm_contact WHERE is_deleted <> 1)
-{$myCaseWhereClause} {$myGroupByClause}";
+{$myCaseWhereClauseB} {$myGroupByClauseB}
+UNION
+SELECT civicrm_case.id, case_status.label AS case_status, status_id, civicrm_case_type.title AS case_type,
+ case_type_id, case_relationship.contact_id_a as case_contact
+ FROM civicrm_case
+ INNER JOIN civicrm_case_contact cc on cc.case_id = civicrm_case.id
+ LEFT JOIN civicrm_case_type ON civicrm_case.case_type_id = civicrm_case_type.id
+ LEFT JOIN civicrm_option_group option_group_case_status ON ( option_group_case_status.name = 'case_status' )
+ LEFT JOIN civicrm_option_value case_status ON ( civicrm_case.status_id = case_status.value
+ AND option_group_case_status.id = case_status.option_group_id )
+ LEFT JOIN civicrm_relationship case_relationship ON ( case_relationship.case_id  = civicrm_case.id
+ AND case_relationship.contact_id_a = {$userID})
+ WHERE is_deleted = 0 AND cc.contact_id IN (SELECT id FROM civicrm_contact WHERE is_deleted <> 1)
+{$myCaseWhereClauseA} {$myGroupByClauseA}";
 
     $res = CRM_Core_DAO::executeQuery($query);
     while ($res->fetch()) {
@@ -1204,29 +1220,53 @@ SELECT case_status.label AS case_status, status_id, civicrm_case_type.title AS c
       $caseInfo = civicrm_api3('Case', 'getsingle', array(
         'id' => $caseID,
         // Most efficient way of retrieving definition is to also include case type id and name so the api doesn't have to look it up separately
-        'return' => array('case_type_id', 'case_type_id.name', 'case_type_id.definition'),
+        'return' => array('case_type_id', 'case_type_id.name', 'case_type_id.definition', 'contact_id'),
       ));
       if (!empty($caseInfo['case_type_id.definition']['caseRoles'])) {
         $caseRoles = CRM_Utils_Array::rekey($caseInfo['case_type_id.definition']['caseRoles'], 'name');
       }
     }
-    $values = array();
-    $query = '
-      SELECT cc.display_name as name, cc.sort_name as sort_name, cc.id, cr.relationship_type_id, crt.label_b_a as role, crt.name_b_a, ce.email, cp.phone
-      FROM civicrm_relationship cr
-      LEFT JOIN civicrm_relationship_type crt
-        ON crt.id = cr.relationship_type_id
-      LEFT JOIN civicrm_contact cc
-        ON cc.id = cr.contact_id_b
-      LEFT JOIN civicrm_email ce
-        ON ce.contact_id = cc.id
-        AND ce.is_primary= 1
-      LEFT JOIN civicrm_phone cp
-        ON cp.contact_id = cc.id
-        AND cp.is_primary= 1
-      WHERE cr.case_id =  %1 AND cr.is_active AND cc.is_deleted <> 1';
 
-    $params = array(1 => array($caseID, 'Integer'));
+    $values = array();
+    $query = <<<HERESQL
+    SELECT cc.display_name as name, cc.sort_name as sort_name, cc.id, cr.relationship_type_id, crt.label_b_a as role, crt.name_b_a as role_name, ce.email, cp.phone
+    FROM civicrm_relationship cr
+    JOIN civicrm_relationship_type crt
+     ON crt.id = cr.relationship_type_id
+    JOIN civicrm_contact cc
+     ON cc.id = cr.contact_id_a
+     AND cc.is_deleted <> 1
+    LEFT JOIN civicrm_email ce
+     ON ce.contact_id = cc.id
+     AND ce.is_primary= 1
+    LEFT JOIN civicrm_phone cp
+     ON cp.contact_id = cc.id
+     AND cp.is_primary= 1
+    WHERE cr.case_id =  %1
+     AND cr.is_active
+     AND cc.id NOT IN (%2)
+    UNION
+    SELECT cc.display_name as name, cc.sort_name as sort_name, cc.id, cr.relationship_type_id, crt.label_a_b as role, crt.name_a_b as role_name, ce.email, cp.phone
+    FROM civicrm_relationship cr
+    JOIN civicrm_relationship_type crt
+     ON crt.id = cr.relationship_type_id
+    JOIN civicrm_contact cc
+     ON cc.id = cr.contact_id_b
+     AND cc.is_deleted <> 1
+    LEFT JOIN civicrm_email ce
+     ON ce.contact_id = cc.id
+     AND ce.is_primary= 1
+    LEFT JOIN civicrm_phone cp
+     ON cp.contact_id = cc.id
+     AND cp.is_primary= 1
+    WHERE cr.case_id =  %1
+     AND cr.is_active
+     AND cc.id NOT IN (%2)
+HERESQL;
+    $params = array(
+      1 => array($caseID, 'Integer'),
+      2 => array(implode(',', $caseInfo['client_id']), 'String'),
+    );
     $dao = CRM_Core_DAO::executeQuery($query, $params);
 
     while ($dao->fetch()) {
@@ -1244,7 +1284,7 @@ SELECT case_status.label AS case_status, status_id, civicrm_case_type.title AS c
           'phone' => $dao->phone,
         );
         // Add more info about the role (creator, manager)
-        $role = CRM_Utils_Array::value($dao->name_b_a, $caseRoles);
+        $role = CRM_Utils_Array::value($dao->role_name, $caseRoles);
         if ($role) {
           unset($role['name']);
           $details += $role;
@@ -1848,16 +1888,27 @@ SELECT case_status.label AS case_status, status_id, civicrm_case_type.title AS c
     $managerRoleId = $xmlProcessor->getCaseManagerRoleId($caseType);
 
     if (!empty($managerRoleId)) {
-      $managerRoleQuery = "
-SELECT civicrm_contact.id as casemanager_id,
-       civicrm_contact.sort_name as casemanager
- FROM civicrm_contact
- LEFT JOIN civicrm_relationship ON (civicrm_relationship.contact_id_b = civicrm_contact.id AND civicrm_relationship.relationship_type_id = %1) AND civicrm_relationship.is_active
- LEFT JOIN civicrm_case ON civicrm_case.id = civicrm_relationship.case_id
- WHERE civicrm_case.id = %2 AND is_active = 1";
+      if (substr($managerRoleId, -4) == '_a_b') {
+        $managerRoleQuery = "
+          SELECT civicrm_contact.id as casemanager_id,
+                 civicrm_contact.sort_name as casemanager
+           FROM civicrm_contact
+           LEFT JOIN civicrm_relationship ON (civicrm_relationship.contact_id_b = civicrm_contact.id AND civicrm_relationship.relationship_type_id = %1) AND civicrm_relationship.is_active
+           LEFT JOIN civicrm_case ON civicrm_case.id = civicrm_relationship.case_id
+           WHERE civicrm_case.id = %2 AND is_active = 1";
+      }
+      if (substr($managerRoleId, -4) == '_b_a') {
+        $managerRoleQuery = "
+          SELECT civicrm_contact.id as casemanager_id,
+                 civicrm_contact.sort_name as casemanager
+           FROM civicrm_contact
+           LEFT JOIN civicrm_relationship ON (civicrm_relationship.contact_id_a = civicrm_contact.id AND civicrm_relationship.relationship_type_id = %1) AND civicrm_relationship.is_active
+           LEFT JOIN civicrm_case ON civicrm_case.id = civicrm_relationship.case_id
+           WHERE civicrm_case.id = %2 AND is_active = 1";
+      }
 
       $managerRoleParams = array(
-        1 => array($managerRoleId, 'Integer'),
+        1 => array(substr($managerRoleId, 0, -4), 'Integer'),
         2 => array($caseId, 'Integer'),
       );
 
@@ -3214,4 +3265,58 @@ WHERE id IN (' . implode(',', $copiedActivityIds) . ')';
     return $filters;
   }
 
+  /**
+   * Fetch Case Role direction from Case Type
+   */
+  public static function getCaseRoleDirection($caseId, $roleTypeId = NULL) {
+    try {
+      $case = civicrm_api3('Case', 'getsingle', array('id' => $caseId));
+    }
+    catch (CiviCRM_API3_Exception $e) {
+      // Lack of permissions will throw an exception
+      return 0;
+    }
+    if (!empty($case['case_type_id'])) {
+      try {
+        $caseType = civicrm_api3('CaseType', 'getsingle', array('id' => $case['case_type_id'], 'return' => array('definition')));
+      }
+      catch (CiviCRM_API3_Exception $e) {
+        // Lack of permissions will throw an exception
+        return 'no case type found';
+      }
+      if (!empty($caseType['definition']['caseRoles'])) {
+        $caseRoles = array();
+        foreach ($caseType['definition']['caseRoles'] as $key => $roleDetails) {
+          // Check if its an a_b label
+          try {
+            $relType = civicrm_api3('RelationshipType', 'getsingle', array('label_a_b' => $roleDetails['name']));
+          }
+          catch (CiviCRM_API3_Exception $e) {
+          }
+          if (!empty($relType['id'])) {
+            $roleDetails['id'] = $relType['id'];
+            $roleDetails['direction'] = 'b_a';
+          }
+          // Check if its a b_a label
+          try {
+            $relTypeBa = civicrm_api3('RelationshipType', 'getsingle', array('label_b_a' => $roleDetails['name']));
+          }
+          catch (CiviCRM_API3_Exception $e) {
+          }
+          if (!empty($relTypeBa['id'])) {
+            if (!empty($roleDetails['direction'])) {
+              $roleDetails['direction'] = 'bidrectional';
+            }
+            else {
+              $roleDetails['id'] = $relTypeBa['id'];
+              $roleDetails['direction'] = 'a_b';
+            }
+          }
+          $caseRoles[$roleDetails['id']] = $roleDetails;
+        }
+      }
+      return $caseRoles;
+    }
+  }
+
 }
index e5bf54125eb7996e80fc93e4a275a0d49ce71a55..321118bf96e74613625e82dd299eff8ff4338cfe 100644 (file)
@@ -110,7 +110,7 @@ class CRM_Case_BAO_Query extends CRM_Core_BAO_Query {
     }
 
     if (!empty($query->_returnProperties['case_role'])) {
-      $query->_select['case_role'] = "case_relation_type.label_b_a as case_role";
+      $query->_select['case_role'] = "IF(case_relationship.contact_id_b = contact_a.id, case_relation_type.label_b_a, case_relation_type.label_a_b) as case_role";
       $query->_element['case_role'] = 1;
       $query->_tables['case_relationship'] = $query->_whereTables['case_relationship'] = 1;
       $query->_tables['case_relation_type'] = $query->_whereTables['case_relation_type'] = 1;
@@ -296,7 +296,7 @@ class CRM_Case_BAO_Query extends CRM_Core_BAO_Query {
           if ($value == 2) {
             $session = CRM_Core_Session::singleton();
             $userID = $session->get('userID');
-            $query->_where[$grouping][] = ' ( ' . CRM_Contact_BAO_Query::buildClause("case_relationship.contact_id_b", $op, $userID, 'Int') . ' AND ' . CRM_Contact_BAO_Query::buildClause("case_relationship.is_active", '<>', 0, 'Int') . ' ) ';
+            $query->_where[$grouping][] = ' (( ' . CRM_Contact_BAO_Query::buildClause("case_relationship.contact_id_b", $op, $userID, 'Int') . ' AND ' . CRM_Contact_BAO_Query::buildClause("case_relationship.is_active", '<>', 0, 'Int') . ' ) OR ( ' . CRM_Contact_BAO_Query::buildClause("case_relationship.contact_id_a", $op, $userID, 'Int') . ' AND ' . CRM_Contact_BAO_Query::buildClause("case_relationship.is_active", '<>', 0, 'Int') . ' ))';
             $query->_qill[$grouping][] = ts('Case %1 My Cases', [1 => $op]);
             $query->_tables['case_relationship'] = $query->_whereTables['case_relationship'] = 1;
           }
@@ -434,7 +434,6 @@ class CRM_Case_BAO_Query extends CRM_Core_BAO_Query {
       // adding where clause for case_role
 
       case 'case_role':
-        $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause("case_relation_type.name_b_a", $op, $value, 'String');
         $query->_qill[$grouping][] = ts("Role in Case  %1 '%2'", [1 => $op, 2 => $value]);
         $query->_tables['case_relation_type'] = $query->_whereTables['case_relationship_type'] = 1;
         $query->_tables['civicrm_case'] = $query->_whereTables['civicrm_case'] = 1;
@@ -549,7 +548,7 @@ class CRM_Case_BAO_Query extends CRM_Core_BAO_Query {
       case 'case_relationship':
         $session = CRM_Core_Session::singleton();
         $userID = $session->get('userID');
-        $from .= " $side JOIN civicrm_relationship case_relationship ON ( case_relationship.contact_id_a = civicrm_case_contact.contact_id AND case_relationship.contact_id_b = {$userID} AND case_relationship.case_id = civicrm_case.id )";
+        $from .= " $side JOIN civicrm_relationship case_relationship ON ( case_relationship.contact_id_a = civicrm_case_contact.contact_id AND case_relationship.contact_id_b = {$userID} AND case_relationship.case_id = civicrm_case.id OR case_relationship.contact_id_b = civicrm_case_contact.contact_id AND case_relationship.contact_id_a = {$userID} AND case_relationship.case_id = civicrm_case.id )";
         break;
 
       case 'case_relation_type':
index 20be94a95ad2be7f831c7d0a0d3bf5be7b2ef1a7..f2809047e3c97398e3caf18df8f555d3f7319307 100644 (file)
@@ -68,15 +68,23 @@ class CRM_Case_Form_AddToCaseAsRole extends CRM_Contact_Form_Task {
     $contacts = $this->_contactIds;
 
     $clients = CRM_Case_BAO_Case::getCaseClients($caseId);
+    $caseRole = CRM_Case_BAO_Case::getCaseRoleDirection($caseId, $roleTypeId);
 
     $params = [
-      'contact_id_a' => $clients[0],
-      'contact_id_b' => $contacts,
       'case_id' => $caseId,
       'relationship_type_id' => $roleTypeId,
     ];
 
-    CRM_Contact_BAO_Relationship::createMultiple($params, 'a');
+    if ($caseRole[$roleTypeId]['direction'] == 'b_a') {
+      $params['contact_id_b'] = $clients[0];
+      $params['contact_id_a'] = $contacts;
+      CRM_Contact_BAO_Relationship::createMultiple($params, 'b');
+    }
+    elseif ($caseRole[$roleTypeId]['direction'] == 'a_b'  || $caseRole[$roleTypeId]['direction'] = 'bidirectional') {
+      $params['contact_id_a'] = $clients[0];
+      $params['contact_id_b'] = $contacts;
+      CRM_Contact_BAO_Relationship::createMultiple($params, 'a');
+    }
 
     $url = CRM_Utils_System::url(
       'civicrm/contact/view/case',
index fdf587ca58dfd4284866c8c8cf5778708a487ea4..2f9eb82a0e8c5da1e3b3706d123817968dc5b489 100644 (file)
@@ -289,7 +289,7 @@ class CRM_Case_Form_CaseView extends CRM_Core_Form {
 
     foreach ($caseRelationships as $key => & $value) {
       if (!empty($managerRoleId)) {
-        if ($managerRoleId == $value['relation_type']) {
+        if (substr($managerRoleId, 0, -4) == $value['relation_type'] && substr($managerRoleId, -3) == $value['relationship_direction']) {
           $value['relation'] = $managerLabel;
         }
       }
index 413385c55ddfa9d9a1cae443827c35fcc2c25dc1..ce5421fd1b0d754869efd47c00da6e5f7e1844ac 100644 (file)
@@ -144,8 +144,14 @@ class CRM_Case_Info extends CRM_Core_Component_Info {
     }
     elseif ($dao instanceof CRM_Contact_DAO_RelationshipType) {
       /** @var $dao CRM_Contact_DAO_RelationshipType */
-      $count = CRM_Case_XMLRepository::singleton()
-        ->getRelationshipReferenceCount($dao->{CRM_Case_XMLProcessor::REL_TYPE_CNAME});
+
+      // Need to look both directions, but no need to translate case role
+      // direction from XML perspective to client-based perspective
+      $xmlRepo = CRM_Case_XMLRepository::singleton();
+      $count = $xmlRepo->getRelationshipReferenceCount($dao->label_a_b);
+      if ($dao->label_a_b != $dao->label_b_a) {
+        $count += $xmlRepo->getRelationshipReferenceCount($dao->label_b_a);
+      }
       if ($count > 0) {
         $result[] = [
           'name' => 'casetypexml:relationships',
index 461b51f5dafc78929b7c99b7452bf9337fd8dcbd..15bc97354f7e0448489900456c68aac7f9966ac9 100644 (file)
@@ -112,12 +112,23 @@ class CRM_Case_ManagedEntities {
 
     if (!isset(Civi::$statics[__CLASS__]['reltypes'])) {
       $relationshipInfo = CRM_Core_PseudoConstant::relationshipType('label', TRUE, NULL);
-      Civi::$statics[__CLASS__]['reltypes'] = CRM_Utils_Array::collect(CRM_Case_XMLProcessor::REL_TYPE_CNAME, $relationshipInfo);
+      foreach ($relationshipInfo as $id => $relTypeDetails) {
+        Civi::$statics[__CLASS__]['reltypes']["{$id}_a_b"] = $relTypeDetails['label_a_b'];
+        if ($relTypeDetails['label_a_b'] != $relTypeDetails['label_b_a']) {
+          Civi::$statics[__CLASS__]['reltypes']["{$id}_b_a"] = $relTypeDetails['label_b_a'];
+        }
+      }
     }
     $validRelTypes = Civi::$statics[__CLASS__]['reltypes'];
 
     $relTypes = $xmlRepo->getAllDeclaredRelationshipTypes();
     foreach ($relTypes as $relType) {
+      // Making assumption that client is the A side of the relationship.
+      // Relationship label coming from XML, meaning from perspective of
+      // non-client.
+
+      // These assumptions only apply if a case type is introduced without the
+      // relationship types already existing.
       $managed = [
         'module' => 'civicrm',
         'name' => "civicase:rel:$relType",
@@ -131,8 +142,8 @@ class CRM_Case_ManagedEntities {
           'label_a_b' => "$relType is",
           'label_b_a' => $relType,
           'description' => $relType,
-          'contact_type_a' => 'Individual',
-          'contact_type_b' => 'Individual',
+          'contact_type_a' => NULL,
+          'contact_type_b' => NULL,
           'contact_sub_type_a' => NULL,
           'contact_sub_type_b' => NULL,
         ],
index 863ea68ff5b58ca152ab9a3120fdc3f129fcafa6..245e9ace1c268058e45222cd0903df05d3c2083b 100644 (file)
@@ -42,26 +42,6 @@ class CRM_Case_XMLProcessor {
    */
   public static $activityTypes = NULL;
 
-  /**
-   * FIXME: This does *NOT* belong in a static property, but we're too late in
-   * the 4.5-cycle to do the necessary cleanup.
-   *
-   * Format is array(int $id => string $relTypeCname).
-   *
-   * @var array|null
-   */
-  public static $relationshipTypes = NULL;
-
-  /**
-   * Relationship-types have four name fields (name_a_b, name_b_a, label_a_b,
-   * label_b_a), but CiviCase XML refers to reltypes by a single name.
-   * REL_TYPE_CNAME identifies the canonical name field as used by CiviCase XML.
-   *
-   * This appears to be "label_b_a", but IMHO "name_b_a" would be more
-   * sensible.
-   */
-  const REL_TYPE_CNAME = 'label_b_a';
-
   /**
    * @param $caseType
    *
@@ -111,19 +91,33 @@ class CRM_Case_XMLProcessor {
   }
 
   /**
+   * Get all relationship type labels
+   *
+   * TODO: These should probably be names, but under legacy behavior this has
+   * been labels.
+   *
+   * @param bool $fromXML
+   *   Is this to be used for lookup of values from XML?
+   *   Relationships are recorded in XML from the perspective of the non-client
+   *   while relationships in the UI and everywhere else are from the
+   *   perspective of the client.  Since the XML can't be expected to be
+   *   switched, the direction needs to be translated.
    * @return array
    */
-  public function &allRelationshipTypes() {
-    if (self::$relationshipTypes === NULL) {
+  public function &allRelationshipTypes($fromXML = FALSE) {
+    if (!isset(Civi::$statics[__CLASS__]['reltypes'][$fromXML])) {
       $relationshipInfo = CRM_Core_PseudoConstant::relationshipType('label', TRUE);
 
-      self::$relationshipTypes = [];
+      Civi::$statics[__CLASS__]['reltypes'][$fromXML] = [];
       foreach ($relationshipInfo as $id => $info) {
-        self::$relationshipTypes[$id] = $info[CRM_Case_XMLProcessor::REL_TYPE_CNAME];
+        Civi::$statics[__CLASS__]['reltypes'][$fromXML][$id . '_b_a'] = ($fromXML) ? $info['label_a_b'] : $info['label_b_a'];
+        if ($info['label_b_a'] !== $info['label_a_b']) {
+          Civi::$statics[__CLASS__]['reltypes'][$fromXML][$id . '_a_b'] = ($fromXML) ? $info['label_b_a'] : $info['label_a_b'];
+        }
       }
     }
 
-    return self::$relationshipTypes;
+    return Civi::$statics[__CLASS__]['reltypes'][$fromXML];
   }
 
   /**
@@ -131,7 +125,7 @@ class CRM_Case_XMLProcessor {
    */
   public static function flushStaticCaches() {
     self::$activityTypes = NULL;
-    self::$relationshipTypes = NULL;
+    unset(Civi::$statics[__CLASS__]['reltypes']);
   }
 
 }
index b83e11cce6637e89012059f1be4e1ab238cb538e..4cfb5c8d7e1abcf6142a5e97ab2acc580d1663c3 100644 (file)
@@ -181,7 +181,11 @@ class CRM_Case_XMLProcessor_Process extends CRM_Case_XMLProcessor {
    * @return array|mixed
    */
   public function &caseRoles($caseRolesXML, $isCaseManager = FALSE) {
-    $relationshipTypes = &$this->allRelationshipTypes();
+    // Look up relationship types according to the XML convention (described
+    // from perspective of non-client) but return the labels according to the UI
+    // convention (described from perspective of client)
+    $relationshipTypes = &$this->allRelationshipTypes(TRUE);
+    $relationshipTypesToReturn = &$this->allRelationshipTypes(FALSE);
 
     $result = [];
     foreach ($caseRolesXML as $caseRoleXML) {
@@ -195,7 +199,7 @@ class CRM_Case_XMLProcessor_Process extends CRM_Case_XMLProcessor {
         }
 
         if (!$isCaseManager) {
-          $result[$relationshipTypeID] = $relationshipTypeName;
+          $result[$relationshipTypeID] = $relationshipTypesToReturn[$relationshipTypeID];
         }
         elseif ($relationshipTypeXML->manager == 1) {
           return $relationshipTypeID;
@@ -213,11 +217,13 @@ class CRM_Case_XMLProcessor_Process extends CRM_Case_XMLProcessor {
    * @throws Exception
    */
   public function createRelationships($relationshipTypeName, &$params) {
-    $relationshipTypes = &$this->allRelationshipTypes();
-    // get the relationship id
-    $relationshipTypeID = array_search($relationshipTypeName, $relationshipTypes);
+    // The relationshipTypeName is coming from XML, so the argument should be
+    // `TRUE`
+    $relationshipTypes = &$this->allRelationshipTypes(TRUE);
+    // get the relationship
+    $relationshipType = array_search($relationshipTypeName, $relationshipTypes);
 
-    if ($relationshipTypeID === FALSE) {
+    if ($relationshipType === FALSE) {
       $docLink = CRM_Utils_System::docURL2("user/case-management/set-up");
       CRM_Core_Error::fatal(ts('Relationship type %1, found in case configuration file, is not present in the database %2',
         [1 => $relationshipTypeName, 2 => $docLink]
@@ -232,15 +238,22 @@ class CRM_Case_XMLProcessor_Process extends CRM_Case_XMLProcessor {
 
     foreach ($client as $key => $clientId) {
       $relationshipParams = [
-        'relationship_type_id' => $relationshipTypeID,
-        'contact_id_a' => $clientId,
-        'contact_id_b' => $params['creatorID'],
+        'relationship_type_id' => substr($relationshipType, 0, -4),
         'is_active' => 1,
         'case_id' => $params['caseID'],
         'start_date' => date("Ymd"),
         'end_date' => CRM_Utils_Array::value('relationship_end_date', $params),
       ];
 
+      if (substr($relationshipType, -4) == '_b_a') {
+        $relationshipParams['contact_id_b'] = $clientId;
+        $relationshipParams['contact_id_a'] = $params['creatorID'];
+      }
+      if (substr($relationshipType, -4) == '_a_b') {
+        $relationshipParams['contact_id_a'] = $clientId;
+        $relationshipParams['contact_id_b'] = $params['creatorID'];
+      }
+
       if (!$this->createRelationship($relationshipParams)) {
         CRM_Core_Error::fatal();
         return FALSE;
@@ -343,6 +356,8 @@ class CRM_Case_XMLProcessor_Process extends CRM_Case_XMLProcessor {
   }
 
   /**
+   * Relationships are straight from XML, described from perspective of non-client
+   *
    * @param SimpleXMLElement $caseTypeXML
    *
    * @return array<string> symbolic relationship-type names
index 98534dc95ec8fe2e6aababc572f8c1334ab6def5..2915874f79e57ed5d0668c8569f25d17fb3b060e 100644 (file)
@@ -824,8 +824,8 @@ LIMIT  1
     $xmlProcessor = new CRM_Case_XMLProcessor_Process();
     $caseRoles = $xmlProcessor->get($caseType, 'CaseRoles');
     foreach ($caseRelationships as $key => & $value) {
-      if (!empty($caseRoles[$value['relation_type']])) {
-        unset($caseRoles[$value['relation_type']]);
+      if (!empty($caseRoles[$value['relation_type'] . '_' . $value['relationship_direction']])) {
+        unset($caseRoles[$value['relation_type'] . '_' . $value['relationship_direction']]);
       }
       if ($isRedact) {
         if (!array_key_exists($value['name'], $report->_redactionStringRules)) {
index 54011e77e3b91aa0da88799ea7d85d06520febf7..243c74c5cc7325a38b74cb97806c61b9acc960d0 100644 (file)
@@ -258,6 +258,8 @@ class CRM_Case_XMLRepository {
   }
 
   /**
+   * Relationships are straight from XML, described from perspective of non-client
+   *
    * @return array<string> symbolic-names of relationship-types
    */
   public function getAllDeclaredRelationshipTypes() {
index 1db23629ce08d7622e51cdf22a5ba0e96f4cca48..286fe3b2332b86cbf4f10a1c44293a86da69abcb 100644 (file)
@@ -229,6 +229,9 @@ class CRM_Report_Form_Case_Summary extends CRM_Report_Form {
             if ($fieldName == 'duration') {
               $select[] = "IF({$table['fields']['end_date']['dbAlias']} Is Null, '', DATEDIFF({$table['fields']['end_date']['dbAlias']}, {$table['fields']['start_date']['dbAlias']})) as {$tableName}_{$fieldName}";
             }
+            elseif ($tableName == 'civicrm_relationship_type') {
+              $select[] = "  IF(contact_civireport.id = relationship_civireport.contact_id_a, relationship_type_civireport.label_b_a, relationship_type_civireport.label_a_b) as civicrm_relationship_type_label_b_a";
+            }
             else {
               $select[] = "{$field['dbAlias']} as {$tableName}_{$fieldName}";
             }
@@ -290,7 +293,7 @@ class CRM_Report_Form_Case_Summary extends CRM_Report_Form {
     if ($this->_relField) {
       $this->_from = "
             FROM civicrm_contact $c
-inner join civicrm_relationship $cr on {$c}.id = ${cr}.contact_id_b
+inner join civicrm_relationship $cr on {$c}.id = ${cr}.contact_id_b OR {$c}.id = ${cr}.contact_id_a
 inner join civicrm_case $cc on ${cc}.id = ${cr}.case_id
 inner join civicrm_relationship_type $crt on ${crt}.id=${cr}.relationship_type_id
 inner join civicrm_case_contact $ccc on ${ccc}.case_id = ${cc}.id
@@ -308,6 +311,9 @@ inner join civicrm_contact $c2 on ${c2}.id=${ccc}.contact_id
 
   public function where() {
     $clauses = [];
+    if (!empty($this->_params['fields']['label_b_a']) && $this->_params['fields']['label_b_a'] == 1) {
+      $clauses[] = 'contact_civireport.sort_name !=  c2_civireport.sort_name';
+    }
     $this->_having = '';
     foreach ($this->_columns as $tableName => $table) {
       if (array_key_exists('filters', $table)) {
index 21596810d419bb04afdf1b7e3b1e8788e05c8a78..c71e9c6678b50fb97fd47ae9312e75babaa8d8d0 100644 (file)
@@ -5,12 +5,6 @@
 
 // ODDITY: This only loads if CiviCase is active.
 
-CRM_Core_Resources::singleton()->addSetting([
-  'crmCaseType' => [
-    'REL_TYPE_CNAME' => CRM_Case_XMLProcessor::REL_TYPE_CNAME,
-  ],
-]);
-
 return [
   'ext' => 'civicrm',
   'js' => ['ang/crmCaseType.js'],
index ee2344ece81a7fd2e4a975319e9f0283c0bb36ca..a1ff51ae5749074c93fd2d019b1e93a5950f9815 100644 (file)
@@ -78,7 +78,7 @@
               sequential: 1,
               is_active: 1,
               options: {
-                sort: CRM.crmCaseType.REL_TYPE_CNAME,
+                sort: 'label_a_b',
                 limit: 0
               }
             }];
   });
 
   crmCaseType.controller('CaseTypeCtrl', function($scope, crmApi, apiCalls, crmUiHelp) {
-    var REL_TYPE_CNAME, defaultAssigneeDefaultValue, ts;
+    var defaultAssigneeDefaultValue, ts;
 
     (function init () {
-      // CRM_Case_XMLProcessor::REL_TYPE_CNAME
-      REL_TYPE_CNAME = CRM.crmCaseType.REL_TYPE_CNAME;
 
       ts = $scope.ts = CRM.ts(null);
       $scope.hs = crmUiHelp({file: 'CRM/Case/CaseType'});
       $scope.activityTypes = _.indexBy(apiCalls.actTypes.values, 'name');
       $scope.activityTypeOptions = _.map(apiCalls.actTypes.values, formatActivityTypeOption);
       $scope.defaultAssigneeTypes = apiCalls.defaultAssigneeTypes.values;
-      $scope.relationshipTypeOptions = _.map(apiCalls.relTypes.values, function(type) {
-        return {id: type[REL_TYPE_CNAME], text: type.label_b_a};
-      });
-      $scope.defaultRelationshipTypeOptions = getDefaultRelationshipTypeOptions();
+      $scope.relationshipTypeOptions = getRelationshipTypeOptions(false);
+      $scope.defaultRelationshipTypeOptions = getRelationshipTypeOptions(true);
       // stores the default assignee values indexed by their option name:
       $scope.defaultAssigneeTypeValues = _.chain($scope.defaultAssigneeTypes)
         .indexBy('name').mapValues('value').value();
     }
 
-    /// Returns the default relationship type options. If the relationship is
-    /// bidirectional (Ex: Spouse of) it adds a single option otherwise it adds
-    /// two options representing the relationship type directions
-    /// (Ex: Employee of, Employer is)
-    function getDefaultRelationshipTypeOptions() {
+    // Returns the relationship type options. If the relationship is
+    // bidirectional (Ex: Spouse of) it adds a single option otherwise it adds
+    // two options representing the relationship type directions (Ex: Employee
+    // of, Employer of).
+    //
+    // The default relationship field needs values that are IDs with direction,
+    // while the role field needs values that are names (with implicit
+    // direction).
+    //
+    // At any rate, the labels should follow the convention in the UI of
+    // describing case roles from the perspective of the client, while the
+    // values must follow the convention in the XML of describing case roles
+    // from the perspective of the non-client.
+    function getRelationshipTypeOptions($isDefault) {
       return _.transform(apiCalls.relTypes.values, function(result, relType) {
         var isBidirectionalRelationship = relType.label_a_b === relType.label_b_a;
-
-        result.push({
-          label: relType.label_b_a,
-          value: relType.id + '_b_a'
-        });
-
-        if (!isBidirectionalRelationship) {
+        if ($isDefault) {
           result.push({
-            label: relType.label_a_b,
+            label: relType.label_b_a,
             value: relType.id + '_a_b'
           });
+
+          if (!isBidirectionalRelationship) {
+            result.push({
+              label: relType.label_a_b,
+              value: relType.id + '_b_a'
+            });
+          }
+        }
+        // TODO The ids below really should use names not labels see
+        //  https://lab.civicrm.org/dev/core/issues/774
+        else {
+          result.push({
+            text: relType.label_b_a,
+            id: relType.label_a_b
+          });
+
+          if (!isBidirectionalRelationship) {
+            result.push({
+              text: relType.label_a_b,
+              id: relType.label_b_a
+            });
+          }
         }
       }, []);
     }
           }
         });
       });
+
+      // go lookup and add client-perspective labels for $scope.caseType.definition.caseRoles
+      _.each($scope.caseType.definition.caseRoles, function (set) {
+        _.each($scope.relationshipTypeOptions, function (relTypes) {
+          if (relTypes.text == set.name) {
+            set.displaylabel = relTypes.id;
+          }
+        });
+      });
     }
 
     /// initializes the selected statuses
       activity.default_assignee_contact = null;
     };
 
+    // TODO roleName passed to addRole is a misnomer, its passed as the
+    // label HOWEVER it should be saved to xml as the name see
+    // https://lab.civicrm.org/dev/core/issues/774
+
     /// Add a new role
     $scope.addRole = function(roles, roleName) {
       var names = _.pluck($scope.caseType.definition.caseRoles, 'name');
       if (!_.contains(names, roleName)) {
-        if (_.where($scope.relationshipTypeOptions, {id: roleName}).length) {
-          roles.push({name: roleName});
+        var matchingRoles = _.filter($scope.relationshipTypeOptions, {id: roleName});
+        if (matchingRoles.length) {
+          var matchingRole = matchingRoles.shift();
+          roles.push({name: roleName, displaylabel: matchingRole.text});
         } else {
-          CRM.loadForm(CRM.url('civicrm/admin/reltype', {action: 'add', reset: 1, label_a_b: roleName, label_b_a: roleName}))
+           CRM.loadForm(CRM.url('civicrm/admin/reltype', {action: 'add', reset: 1, label_a_b: roleName}))
             .on('crmFormSuccess', function(e, data) {
               var newType = _.values(data.relationshipType)[0];
-              roles.push({name: newType[REL_TYPE_CNAME]});
-              $scope.relationshipTypeOptions.push({id: newType[REL_TYPE_CNAME], text: newType.label_b_a});
+              roles.push({name: newType.label_b_a, displaylabel: newType.label_a_b});
+              // Assume that the case role should be A-B but add both directions as options.
+              $scope.relationshipTypeOptions.push({id: newType.label_a_b, text: newType.label_a_b});
+              if (newType.label_a_b != newType.label_b_a) {
+                $scope.relationshipTypeOptions.push({id: newType.label_b_a, text: newType.label_b_a});
+              }
               $scope.$digest();
             });
         }
         $scope.caseType.definition.activityAsgmtGrps = $scope.caseType.definition.activityAsgmtGrps.toString().split(",");
       }
 
+      function dropDisplaylabel (v) {
+        delete v.displaylabel;
+      }
+
+      // strip out labels from $scope.caseType.definition.caseRoles
+      _.map($scope.caseType.definition.caseRoles, dropDisplaylabel);
+
       var result = crmApi('CaseType', 'create', $scope.caseType, true);
       result.then(function(data) {
         if (data.is_error === 0 || data.is_error == '0') {
index cc64a60a8eb60c0170496d003411225c04559bc3..e7edee076e6ec7d905b9fb39163f2c2471764f97 100644 (file)
@@ -13,7 +13,8 @@ Required vars: caseType
   </thead>
   <tbody>
          <tr ng-repeat="relType in caseType.definition.caseRoles | orderBy:'name'" ng-class-even="'crm-entity even-row even'" ng-class-odd="'crm-entity odd-row odd'">
-           <td>{{relType.name}}</td>
+      <!-- display label (client-perspective) -->
+           <td>{{relType.displaylabel}}</td>
            <td><input type="checkbox" ng-model="relType.creator" ng-true-value="'1'" ng-false-value="'0'"></td>
            <td><input type="radio" ng-model="relType.manager" value="1" ng-change="onManagerChange(relType)"></td>
            <td>
index 3369579c19bdf3d99aa5563871c2f33e6d7e00a0..2cfb624e2a124bfc8b0a359eaec11ad8f9de22e1 100644 (file)
@@ -18,10 +18,6 @@ describe('crmCaseType', function() {
     CRM.resourceUrls = {
       'civicrm': ''
     };
-    // CRM_Case_XMLProcessor::REL_TYPE_CNAME
-    CRM.crmCaseType = {
-      'REL_TYPE_CNAME': 'label_b_a'
-    };
     module('crmCaseType');
     module('crmJsonComparator');
     inject(function(crmJsonComparator) {
@@ -203,6 +199,30 @@ describe('crmCaseType', function() {
             }
           ]
         },
+        relTypesForm: {
+          values: [
+            {
+                "key": "14_b_a",
+                "value": "Benefits Specialist"
+            },
+            {
+                "key": "14_a_b",
+                "value": "Benefits Specialist is"
+            },
+            {
+                "key": "9_b_a",
+                "value": "Case Coordinator"
+            },
+            {
+                "key": "9_a_b",
+                "value": "Case Coordinator is"
+            },
+            {
+                "key": "2_b_a",
+                "value": "Spouse of"
+            }
+          ]
+        },
         caseType: {
           "id": "1",
           "name": "housing_support",
@@ -332,13 +352,13 @@ describe('crmCaseType', function() {
 
         result.push({
           label: relType.label_b_a,
-          value: relType.id + '_b_a'
+          value: relType.id + '_a_b'
         });
 
         if (!isBidirectionalRelationship) {
           result.push({
             label: relType.label_a_b,
-            value: relType.id + '_a_b'
+            value: relType.id + '_b_a'
           });
         }
       }, []);
index caa23c93d3b4823413c9103a94228513f308fbdb..4fce36f37aa1c6ad2f807d5f6164594d1e71bb52 100644 (file)
@@ -39,7 +39,7 @@ class CRM_Case_BAO_CaseTypeForkTest extends CiviCaseTestCase {
     ]);
     //Check if manager is correctly retrieved from xml processor.
     $xmlProcessor = new CRM_Case_XMLProcessor_Process();
-    $this->assertEquals($relTypeID, $xmlProcessor->getCaseManagerRoleId('ForkableCaseType'));
+    $this->assertEquals($relTypeID . '_a_b', $xmlProcessor->getCaseManagerRoleId('ForkableCaseType'));
   }
 
   /**