diff --git a/CRM/Activity/Page/AJAX.php b/CRM/Activity/Page/AJAX.php index 1da789c2734afd668870371312bbad87f18d9a29..6c78fb59c5b1d879c5f267da09a7487c6d6b6905 100644 --- a/CRM/Activity/Page/AJAX.php +++ b/CRM/Activity/Page/AJAX.php @@ -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 diff --git a/CRM/Case/BAO/Case.php b/CRM/Case/BAO/Case.php index aaa37f5a595ea41732b1cd28bf72c3a50e465ab6..f25f917749eebc7264bc6fac47866a9df52f198b 100644 --- a/CRM/Case/BAO/Case.php +++ b/CRM/Case/BAO/Case.php @@ -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; + } + } + } diff --git a/CRM/Case/BAO/Query.php b/CRM/Case/BAO/Query.php index e5bf54125eb7996e80fc93e4a275a0d49ce71a55..321118bf96e74613625e82dd299eff8ff4338cfe 100644 --- a/CRM/Case/BAO/Query.php +++ b/CRM/Case/BAO/Query.php @@ -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': diff --git a/CRM/Case/Form/AddToCaseAsRole.php b/CRM/Case/Form/AddToCaseAsRole.php index 20be94a95ad2be7f831c7d0a0d3bf5be7b2ef1a7..f2809047e3c97398e3caf18df8f555d3f7319307 100644 --- a/CRM/Case/Form/AddToCaseAsRole.php +++ b/CRM/Case/Form/AddToCaseAsRole.php @@ -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', diff --git a/CRM/Case/Form/CaseView.php b/CRM/Case/Form/CaseView.php index fdf587ca58dfd4284866c8c8cf5778708a487ea4..2f9eb82a0e8c5da1e3b3706d123817968dc5b489 100644 --- a/CRM/Case/Form/CaseView.php +++ b/CRM/Case/Form/CaseView.php @@ -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; } } diff --git a/CRM/Case/Info.php b/CRM/Case/Info.php index 413385c55ddfa9d9a1cae443827c35fcc2c25dc1..ce5421fd1b0d754869efd47c00da6e5f7e1844ac 100644 --- a/CRM/Case/Info.php +++ b/CRM/Case/Info.php @@ -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', diff --git a/CRM/Case/ManagedEntities.php b/CRM/Case/ManagedEntities.php index 461b51f5dafc78929b7c99b7452bf9337fd8dcbd..15bc97354f7e0448489900456c68aac7f9966ac9 100644 --- a/CRM/Case/ManagedEntities.php +++ b/CRM/Case/ManagedEntities.php @@ -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, ], diff --git a/CRM/Case/XMLProcessor.php b/CRM/Case/XMLProcessor.php index 863ea68ff5b58ca152ab9a3120fdc3f129fcafa6..245e9ace1c268058e45222cd0903df05d3c2083b 100644 --- a/CRM/Case/XMLProcessor.php +++ b/CRM/Case/XMLProcessor.php @@ -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']); } } diff --git a/CRM/Case/XMLProcessor/Process.php b/CRM/Case/XMLProcessor/Process.php index b83e11cce6637e89012059f1be4e1ab238cb538e..4cfb5c8d7e1abcf6142a5e97ab2acc580d1663c3 100644 --- a/CRM/Case/XMLProcessor/Process.php +++ b/CRM/Case/XMLProcessor/Process.php @@ -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 diff --git a/CRM/Case/XMLProcessor/Report.php b/CRM/Case/XMLProcessor/Report.php index 98534dc95ec8fe2e6aababc572f8c1334ab6def5..2915874f79e57ed5d0668c8569f25d17fb3b060e 100644 --- a/CRM/Case/XMLProcessor/Report.php +++ b/CRM/Case/XMLProcessor/Report.php @@ -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)) { diff --git a/CRM/Case/XMLRepository.php b/CRM/Case/XMLRepository.php index 54011e77e3b91aa0da88799ea7d85d06520febf7..243c74c5cc7325a38b74cb97806c61b9acc960d0 100644 --- a/CRM/Case/XMLRepository.php +++ b/CRM/Case/XMLRepository.php @@ -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() { diff --git a/CRM/Report/Form/Case/Summary.php b/CRM/Report/Form/Case/Summary.php index 1db23629ce08d7622e51cdf22a5ba0e96f4cca48..286fe3b2332b86cbf4f10a1c44293a86da69abcb 100644 --- a/CRM/Report/Form/Case/Summary.php +++ b/CRM/Report/Form/Case/Summary.php @@ -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)) { diff --git a/ang/crmCaseType.ang.php b/ang/crmCaseType.ang.php index 21596810d419bb04afdf1b7e3b1e8788e05c8a78..c71e9c6678b50fb97fd47ae9312e75babaa8d8d0 100644 --- a/ang/crmCaseType.ang.php +++ b/ang/crmCaseType.ang.php @@ -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'], diff --git a/ang/crmCaseType.js b/ang/crmCaseType.js index ee2344ece81a7fd2e4a975319e9f0283c0bb36ca..a1ff51ae5749074c93fd2d019b1e93a5950f9815 100644 --- a/ang/crmCaseType.js +++ b/ang/crmCaseType.js @@ -78,7 +78,7 @@ sequential: 1, is_active: 1, options: { - sort: CRM.crmCaseType.REL_TYPE_CNAME, + sort: 'label_a_b', limit: 0 } }]; @@ -238,11 +238,9 @@ }); 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'}); @@ -263,33 +261,56 @@ $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 + }); + } } }, []); } @@ -327,6 +348,15 @@ } }); }); + + // 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 @@ -427,18 +457,28 @@ 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(); }); } @@ -534,6 +574,13 @@ $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') { diff --git a/ang/crmCaseType/rolesTable.html b/ang/crmCaseType/rolesTable.html index cc64a60a8eb60c0170496d003411225c04559bc3..e7edee076e6ec7d905b9fb39163f2c2471764f97 100644 --- a/ang/crmCaseType/rolesTable.html +++ b/ang/crmCaseType/rolesTable.html @@ -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> diff --git a/tests/karma/unit/crmCaseTypeSpec.js b/tests/karma/unit/crmCaseTypeSpec.js index 3369579c19bdf3d99aa5563871c2f33e6d7e00a0..2cfb624e2a124bfc8b0a359eaec11ad8f9de22e1 100644 --- a/tests/karma/unit/crmCaseTypeSpec.js +++ b/tests/karma/unit/crmCaseTypeSpec.js @@ -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' }); } }, []); diff --git a/tests/phpunit/CRM/Case/BAO/CaseTypeForkTest.php b/tests/phpunit/CRM/Case/BAO/CaseTypeForkTest.php index caa23c93d3b4823413c9103a94228513f308fbdb..4fce36f37aa1c6ad2f807d5f6164594d1e71bb52 100644 --- a/tests/phpunit/CRM/Case/BAO/CaseTypeForkTest.php +++ b/tests/phpunit/CRM/Case/BAO/CaseTypeForkTest.php @@ -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')); } /**