Skip to content

Commit 0dec4fb

Browse files
erawatshahrukh-compuco
authored andcommitted
CIVIMM-542: Batch membership dashboard summary counts into two queries
PR: civicrm#36042
1 parent d036110 commit 0dec4fb

2 files changed

Lines changed: 206 additions & 16 deletions

File tree

CRM/Member/BAO/Membership.php

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1665,6 +1665,189 @@ public static function getMembershipRenewals($membershipTypeId, $startDate, $end
16651665
return (int) $memberCount;
16661666
}
16671667

1668+
/**
1669+
* Get membership dashboard summary counts for many membership types in two queries.
1670+
*
1671+
* Replaces a per-type/per-window loop in CRM_Member_Page_DashBoard (~16 calls per
1672+
* membership type) with one grouped query over the signup/renewal activity join and
1673+
* one over current memberships. Clause differences between the originals (activity-type
1674+
* guard, deleted-contact exclusion) are preserved across the two queries.
1675+
*
1676+
* Result families:
1677+
* *_new -> getMembershipJoins (signup activity in window)
1678+
* *_renew -> getMembershipRenewals (renewal activity in window)
1679+
* *_total -> getMembershipStarts (either activity)
1680+
* *_owner -> getMembershipStarts, owner_membership_id IS NULL
1681+
* current_total / total_total -> getMembershipCount
1682+
* current_owner / total_owner -> getMembershipCount, owner_membership_id IS NULL
1683+
*
1684+
* @param array $membershipTypeIds
1685+
* @param string $preMonth Previous month start, Y-m-d.
1686+
* @param string $preMonthEnd Previous month end, Y-m-d.
1687+
* @param string $monthStart Reported month start, Y-m-d.
1688+
* @param string $yearStart Reported year start, Y-m-d.
1689+
* @param string $ymd End date for month/year/total windows, Y-m-d.
1690+
* @param string $current "As of" date for current_total/current_owner, Y-m-d.
1691+
* @param bool|int $isTest
1692+
*
1693+
* @return array
1694+
* [membershipTypeId => [family => int]], zero-filled for every requested type.
1695+
*
1696+
* @throws \CRM_Core_Exception
1697+
*/
1698+
public static function getMembershipSummaryStats($membershipTypeIds, $preMonth, $preMonthEnd, $monthStart, $yearStart, $ymd, $current, $isTest = 0) {
1699+
foreach ([$preMonth, $preMonthEnd, $monthStart, $yearStart, $ymd, $current] as $date) {
1700+
if (!CRM_Utils_Rule::date($date)) {
1701+
throw new CRM_Core_Exception(ts('Invalid date "%1" (must have form yyyy-mm-dd).', [1 => $date]));
1702+
}
1703+
}
1704+
1705+
$activityFamilies = [
1706+
'premonth_new', 'premonth_renew', 'premonth_total',
1707+
'month_new', 'month_renew', 'month_total',
1708+
'year_new', 'year_renew', 'year_total',
1709+
'premonth_owner', 'month_owner', 'year_owner',
1710+
];
1711+
$countFamilies = ['current_total', 'total_total', 'current_owner', 'total_owner'];
1712+
1713+
// Pre-fill so callers can read every family without isset() guards; a GROUP BY
1714+
// query would otherwise omit types that have no matching rows.
1715+
$stats = [];
1716+
$typeIds = [];
1717+
foreach ($membershipTypeIds as $typeId) {
1718+
$typeId = (int) $typeId;
1719+
$typeIds[] = $typeId;
1720+
$stats[$typeId] = array_fill_keys(array_merge($activityFamilies, $countFamilies), 0);
1721+
}
1722+
if (empty($typeIds)) {
1723+
return $stats;
1724+
}
1725+
$typeIdList = implode(',', $typeIds);
1726+
$isTest = $isTest ? 1 : 0;
1727+
1728+
// Resolve activity type IDs once. Tests pre-set the statics to simulate a site
1729+
// where signup/renewal activity types are not configured; the legacy "refresh if
1730+
// either is falsy" check would overwrite that. Both NULL is the production
1731+
// initial state, where _getActTypes() populates both.
1732+
if (self::$_signupActType === NULL && self::$_renewalActType === NULL) {
1733+
self::_getActTypes();
1734+
}
1735+
$signupActType = self::$_signupActType;
1736+
$renewalActType = self::$_renewalActType;
1737+
$hasSignup = !empty($signupActType);
1738+
$hasRenewal = !empty($renewalActType);
1739+
1740+
// Query 1: activity-based families. The scan bound on activity_date_time prunes
1741+
// the activity table to the window union before the per-CASE filters run.
1742+
if ($hasSignup || $hasRenewal) {
1743+
$availableActTypes = [];
1744+
if ($hasSignup) {
1745+
$availableActTypes[] = (int) $signupActType;
1746+
}
1747+
if ($hasRenewal) {
1748+
$availableActTypes[] = (int) $renewalActType;
1749+
}
1750+
$actTypeList = implode(',', $availableActTypes);
1751+
1752+
$scanStart = min($preMonth, $monthStart, $yearStart);
1753+
$scanEnd = max($preMonthEnd, $ymd) . ' 23:59:59';
1754+
1755+
$preWindow = 'activity.activity_date_time >= %1 AND activity.activity_date_time <= %2';
1756+
$monthWindow = 'activity.activity_date_time >= %3 AND activity.activity_date_time <= %4';
1757+
$yearWindow = 'activity.activity_date_time >= %5 AND activity.activity_date_time <= %4';
1758+
$isSignup = 'activity.activity_type_id = %6';
1759+
$isRenewal = 'activity.activity_type_id = %7';
1760+
$isOwner = 'membership.owner_membership_id IS NULL';
1761+
1762+
$activityQuery = "
1763+
SELECT membership.membership_type_id AS membership_type_id,
1764+
COUNT(DISTINCT CASE WHEN $isSignup AND $preWindow THEN membership.id END) AS premonth_new,
1765+
COUNT(DISTINCT CASE WHEN $isRenewal AND $preWindow THEN membership.id END) AS premonth_renew,
1766+
COUNT(DISTINCT CASE WHEN $preWindow THEN membership.id END) AS premonth_total,
1767+
COUNT(DISTINCT CASE WHEN $isSignup AND $monthWindow THEN membership.id END) AS month_new,
1768+
COUNT(DISTINCT CASE WHEN $isRenewal AND $monthWindow THEN membership.id END) AS month_renew,
1769+
COUNT(DISTINCT CASE WHEN $monthWindow THEN membership.id END) AS month_total,
1770+
COUNT(DISTINCT CASE WHEN $isSignup AND $yearWindow THEN membership.id END) AS year_new,
1771+
COUNT(DISTINCT CASE WHEN $isRenewal AND $yearWindow THEN membership.id END) AS year_renew,
1772+
COUNT(DISTINCT CASE WHEN $yearWindow THEN membership.id END) AS year_total,
1773+
COUNT(DISTINCT CASE WHEN $isOwner AND $preWindow THEN membership.id END) AS premonth_owner,
1774+
COUNT(DISTINCT CASE WHEN $isOwner AND $monthWindow THEN membership.id END) AS month_owner,
1775+
COUNT(DISTINCT CASE WHEN $isOwner AND $yearWindow THEN membership.id END) AS year_owner
1776+
FROM civicrm_membership membership
1777+
INNER JOIN civicrm_activity activity ON (activity.source_record_id = membership.id AND activity.activity_type_id IN ($actTypeList))
1778+
INNER JOIN civicrm_membership_status status ON (membership.status_id = status.id AND status.is_current_member = 1)
1779+
INNER JOIN civicrm_contact contact ON (contact.id = membership.contact_id AND contact.is_deleted = 0)
1780+
WHERE membership.membership_type_id IN ($typeIdList)
1781+
AND membership.is_test = $isTest
1782+
AND activity.activity_date_time >= %8 AND activity.activity_date_time <= %9
1783+
GROUP BY membership.membership_type_id
1784+
";
1785+
$params = [
1786+
1 => [$preMonth, 'String'],
1787+
2 => [$preMonthEnd . ' 23:59:59', 'String'],
1788+
3 => [$monthStart, 'String'],
1789+
4 => [$ymd . ' 23:59:59', 'String'],
1790+
5 => [$yearStart, 'String'],
1791+
6 => [(int) $signupActType, 'Integer'],
1792+
7 => [(int) $renewalActType, 'Integer'],
1793+
8 => [$scanStart, 'String'],
1794+
9 => [$scanEnd, 'String'],
1795+
];
1796+
foreach (CRM_Core_DAO::executeQuery($activityQuery, $params)->fetchAll() as $row) {
1797+
$typeId = (int) $row['membership_type_id'];
1798+
foreach ($activityFamilies as $family) {
1799+
$stats[$typeId][$family] = (int) $row[$family];
1800+
}
1801+
}
1802+
1803+
// Per-function activity-type guard: joins needs signup, renewals needs renewal,
1804+
// starts (the *_total and *_owner families) needs both. Zero out the families the
1805+
// originals would return 0 for when a required type is missing.
1806+
if (!$hasSignup || !$hasRenewal) {
1807+
foreach ($stats as &$row) {
1808+
if (!$hasSignup) {
1809+
$row['premonth_new'] = $row['month_new'] = $row['year_new'] = 0;
1810+
}
1811+
if (!$hasRenewal) {
1812+
$row['premonth_renew'] = $row['month_renew'] = $row['year_renew'] = 0;
1813+
}
1814+
$row['premonth_total'] = $row['month_total'] = $row['year_total'] = 0;
1815+
$row['premonth_owner'] = $row['month_owner'] = $row['year_owner'] = 0;
1816+
}
1817+
unset($row);
1818+
}
1819+
}
1820+
1821+
// Query 2: current-membership families. Deleted-contact exclusion uses a NOT IN
1822+
// subquery (not an inner join) to match getMembershipCount exactly.
1823+
$countQuery = "
1824+
SELECT civicrm_membership.membership_type_id AS membership_type_id,
1825+
COUNT(CASE WHEN civicrm_membership.start_date <= %1 THEN civicrm_membership.id END) AS current_total,
1826+
COUNT(CASE WHEN civicrm_membership.start_date <= %2 THEN civicrm_membership.id END) AS total_total,
1827+
COUNT(CASE WHEN civicrm_membership.owner_membership_id IS NULL AND civicrm_membership.start_date <= %1 THEN civicrm_membership.id END) AS current_owner,
1828+
COUNT(CASE WHEN civicrm_membership.owner_membership_id IS NULL AND civicrm_membership.start_date <= %2 THEN civicrm_membership.id END) AS total_owner
1829+
FROM civicrm_membership
1830+
LEFT JOIN civicrm_membership_status ON (civicrm_membership.status_id = civicrm_membership_status.id)
1831+
WHERE civicrm_membership.membership_type_id IN ($typeIdList)
1832+
AND civicrm_membership.contact_id NOT IN (SELECT id FROM civicrm_contact WHERE is_deleted = 1)
1833+
AND civicrm_membership.is_test = $isTest
1834+
AND civicrm_membership_status.is_current_member = 1
1835+
GROUP BY civicrm_membership.membership_type_id
1836+
";
1837+
$countParams = [
1838+
1 => [$current, 'String'],
1839+
2 => [$ymd, 'String'],
1840+
];
1841+
foreach (CRM_Core_DAO::executeQuery($countQuery, $countParams)->fetchAll() as $row) {
1842+
$typeId = (int) $row['membership_type_id'];
1843+
foreach ($countFamilies as $family) {
1844+
$stats[$typeId][$family] = (int) $row[$family];
1845+
}
1846+
}
1847+
1848+
return $stats;
1849+
}
1850+
16681851
/**
16691852
* Get line items representing the default price set.
16701853
*

CRM/Member/Page/DashBoard.php

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -80,96 +80,103 @@ public function preProcess() {
8080
// added
8181
//$membership = new CRM_Member_BAO_Membership;
8282

83+
// Gather every dashboard count across all membership types in two grouped queries
84+
// rather than the ~16 per-type queries this loop previously issued.
85+
$summaryStats = CRM_Member_BAO_Membership::getMembershipSummaryStats(
86+
array_keys($membershipTypes), $preMonth, $preMonthEnd, $monthStart, $yearStart, $ymd, $current
87+
);
88+
8389
foreach ($membershipTypes as $key => $value) {
90+
$stats = $summaryStats[$key];
8491

8592
$membershipSummary[$key]['premonth']['new'] = [
86-
'count' => CRM_Member_BAO_Membership::getMembershipJoins($key, $preMonth, $preMonthEnd),
93+
'count' => $stats['premonth_new'],
8794
'name' => $value,
8895
'url' => FALSE,
8996
];
9097

9198
$membershipSummary[$key]['premonth']['renew'] = [
92-
'count' => CRM_Member_BAO_Membership::getMembershipRenewals($key, $preMonth, $preMonthEnd),
99+
'count' => $stats['premonth_renew'],
93100
'name' => $value,
94101
'url' => FALSE,
95102
];
96103

97104
$membershipSummary[$key]['premonth']['total'] = [
98-
'count' => CRM_Member_BAO_Membership::getMembershipStarts($key, $preMonth, $preMonthEnd),
105+
'count' => $stats['premonth_total'],
99106
'name' => $value,
100107
'url' => FALSE,
101108
];
102109

103110
$membershipSummary[$key]['month']['new'] = [
104-
'count' => CRM_Member_BAO_Membership::getMembershipJoins($key, $monthStart, $ymd),
111+
'count' => $stats['month_new'],
105112
'name' => $value,
106113
'url' => FALSE,
107114
];
108115

109116
$membershipSummary[$key]['month']['renew'] = [
110-
'count' => CRM_Member_BAO_Membership::getMembershipRenewals($key, $monthStart, $ymd),
117+
'count' => $stats['month_renew'],
111118
'name' => $value,
112119
'url' => FALSE,
113120
];
114121

115122
$membershipSummary[$key]['month']['total'] = [
116-
'count' => CRM_Member_BAO_Membership::getMembershipStarts($key, $monthStart, $ymd),
123+
'count' => $stats['month_total'],
117124
'name' => $value,
118125
'url' => FALSE,
119126
];
120127

121128
$membershipSummary[$key]['year']['new'] = [
122-
'count' => CRM_Member_BAO_Membership::getMembershipJoins($key, $yearStart, $ymd),
129+
'count' => $stats['year_new'],
123130
'name' => $value,
124131
'url' => FALSE,
125132
];
126133

127134
$membershipSummary[$key]['year']['renew'] = [
128-
'count' => CRM_Member_BAO_Membership::getMembershipRenewals($key, $yearStart, $ymd),
135+
'count' => $stats['year_renew'],
129136
'name' => $value,
130137
'url' => FALSE,
131138
];
132139

133140
$membershipSummary[$key]['year']['total'] = [
134-
'count' => CRM_Member_BAO_Membership::getMembershipStarts($key, $yearStart, $ymd),
141+
'count' => $stats['year_total'],
135142
'name' => $value,
136143
'url' => FALSE,
137144
];
138145

139146
$membershipSummary[$key]['current']['total'] = [
140-
'count' => CRM_Member_BAO_Membership::getMembershipCount($key, $current),
147+
'count' => $stats['current_total'],
141148
'name' => $value,
142149
'url' => FALSE,
143150
];
144151

145-
$membershipSummary[$key]['total']['total'] = ['count' => CRM_Member_BAO_Membership::getMembershipCount($key, $ymd)];
152+
$membershipSummary[$key]['total']['total'] = ['count' => $stats['total_total']];
146153

147154
//LCD also get summary stats for membership owners
148155
$membershipSummary[$key]['premonth_owner']['premonth_owner'] = [
149-
'count' => CRM_Member_BAO_Membership::getMembershipStarts($key, $preMonth, $preMonthEnd, 0, 1),
156+
'count' => $stats['premonth_owner'],
150157
'name' => $value,
151158
'url' => FALSE,
152159
];
153160

154161
$membershipSummary[$key]['month_owner']['month_owner'] = [
155-
'count' => CRM_Member_BAO_Membership::getMembershipStarts($key, $monthStart, $ymd, 0, 1),
162+
'count' => $stats['month_owner'],
156163
'name' => $value,
157164
'url' => FALSE,
158165
];
159166

160167
$membershipSummary[$key]['year_owner']['year_owner'] = [
161-
'count' => CRM_Member_BAO_Membership::getMembershipStarts($key, $yearStart, $ymd, 0, 1),
168+
'count' => $stats['year_owner'],
162169
'name' => $value,
163170
'url' => FALSE,
164171
];
165172

166173
$membershipSummary[$key]['current_owner']['current_owner'] = [
167-
'count' => CRM_Member_BAO_Membership::getMembershipCount($key, $current, 0, 1),
174+
'count' => $stats['current_owner'],
168175
'name' => $value,
169176
'url' => FALSE,
170177
];
171178

172-
$membershipSummary[$key]['total_owner']['total_owner'] = ['count' => CRM_Member_BAO_Membership::getMembershipCount($key, $ymd, 0, 1)];
179+
$membershipSummary[$key]['total_owner']['total_owner'] = ['count' => $stats['total_owner']];
173180
//LCD end
174181
}
175182

0 commit comments

Comments
 (0)