mirror of
https://codeberg.org/artfulrobot/contactcats.git
synced 2025-06-25 14:38:05 +02:00
Add GetFlows API action
This commit is contained in:
parent
d0ff38b0f2
commit
6cf63f5b42
1 changed files with 167 additions and 0 deletions
167
Civi/Api4/Action/ContactCategory/GetFlows.php
Normal file
167
Civi/Api4/Action/ContactCategory/GetFlows.php
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
<?php
|
||||||
|
namespace Civi\Api4\Action\ContactCategory;
|
||||||
|
|
||||||
|
use CRM_Contactcats_ExtensionUtil as E;
|
||||||
|
use Civi;
|
||||||
|
use Civi\Api4\Generic\Result;
|
||||||
|
use CRM_Core_DAO;
|
||||||
|
use CRM_Core_Exception;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get counts of contacts moving between categories between two given dates.
|
||||||
|
*
|
||||||
|
* @see \Civi\Api4\Generic\AbstractAction
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class GetFlows extends \Civi\Api4\Generic\AbstractAction {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start Date
|
||||||
|
*
|
||||||
|
* @required
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected string $startDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End Date (optional)
|
||||||
|
*
|
||||||
|
* @var string|NULL
|
||||||
|
* @default NULL
|
||||||
|
*/
|
||||||
|
protected ?string $endDate = NULL;
|
||||||
|
|
||||||
|
public function _run(Result $result) {
|
||||||
|
Civi::log()->debug('Begin', ['=start' => 'GetFlows', '=timed' => 1, 'from' => $this->startDate, 'to' => $this->endDate]);
|
||||||
|
|
||||||
|
// Check dates are valid and get a Ymd format of the day *following* the specified one.
|
||||||
|
// This is so we can find activities *before* this date and thereby capture activities that
|
||||||
|
// occurred *on* the given date.
|
||||||
|
$getValidDate = fn(?string $input) => $input ? (new DateTimeImmutable($input))->modify('+1 day')->format('Ymd'): FALSE;
|
||||||
|
|
||||||
|
$startDate = $getValidDate($this->startDate);
|
||||||
|
if (!$startDate) {
|
||||||
|
throw new CRM_Core_Exception(E::ts("Cannot parse given start date."));
|
||||||
|
}
|
||||||
|
$endDate = $getValidDate($this->endDate);
|
||||||
|
if (!$endDate) {
|
||||||
|
throw new CRM_Core_Exception(E::ts("Cannot parse given end date."));
|
||||||
|
}
|
||||||
|
if (!($endDate > $startDate)) {
|
||||||
|
throw new CRM_Core_Exception(E::ts("This version of CiviCRM does not support twisting the space time continuum. End date cannot be before start date."));
|
||||||
|
}
|
||||||
|
|
||||||
|
$result['windowFunctionsSupported'] = $this->windowFunctionsSupported();
|
||||||
|
if ($result['windowFunctionsSupported']) {
|
||||||
|
$this->solveWithWindowFunctions($result, $startDate, $endDate);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// TODO: make a slow version that doesn't need window functions.
|
||||||
|
// $this->solveWithoutWindowFunctions($result);
|
||||||
|
throw new CRM_Core_Exception("your database does not support Window functions.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Civi::log()->debug("Complete.", ['=' => 'set', 'result' => $result->getArrayCopy()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the database support window functions in the way we need?
|
||||||
|
*
|
||||||
|
* MariaDB 10.2+ does.
|
||||||
|
*/
|
||||||
|
protected function windowFunctionsSupported() {
|
||||||
|
$cache = \CRM_Utils_Cache::create(['type' => ['SqlGroup'], 'name' => 'contactcats']);
|
||||||
|
$supported = $cache->get('windowFunctionsSupported'); // Will return default if cached value expired.
|
||||||
|
$supported = null;
|
||||||
|
if ($supported === NULL) {
|
||||||
|
// Need to test for window functions.
|
||||||
|
$supported = FALSE;
|
||||||
|
// Prevent Civi handling possible execption
|
||||||
|
$handler = set_exception_handler(fn() => false);
|
||||||
|
try {
|
||||||
|
\CRM_Core_DAO::executeQuery("CREATE TEMPORARY TABLE contactcats_window_check (i int unsigned not null, x int unsigned not null)");
|
||||||
|
\CRM_Core_DAO::executeQuery("INSERT INTO contactcats_window_check VALUES (1, 1), (2, 1), (3, 2);");
|
||||||
|
$rows = \CRM_Core_DAO::executeQuery("SELECT x, ROW_NUMBER() OVER ( PARTITION BY (x) ORDER BY i DESC) rn FROM contactcats_window_check ORDER BY x, rn")->fetchAll();
|
||||||
|
$supported = ($rows === [ [ 'x' => "1", 'rn' => "1"], [ 'x' => "1", 'rn' => "2"], [ 'x' => "2", 'rn' => "1"] ]) ;
|
||||||
|
}
|
||||||
|
catch (\Exception $e) {
|
||||||
|
// Silent fail.
|
||||||
|
}
|
||||||
|
// Restore exception handler
|
||||||
|
set_exception_handler($handler);
|
||||||
|
$cache->set('windowFunctionsSupported', $supported);
|
||||||
|
}
|
||||||
|
return $supported;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected function solveWithWindowFunctions(Result $result, string $startDateYmd, string $endDateYmd) {
|
||||||
|
|
||||||
|
$sql = <<<SQL
|
||||||
|
/* Identify the relevant activities for the contacts */
|
||||||
|
WITH activities AS (
|
||||||
|
SELECT ac.contact_id, a.activity_date_time, a.id activity_id
|
||||||
|
FROM civicrm_activity a
|
||||||
|
INNER JOIN civicrm_activity_contact ac
|
||||||
|
ON a.id = ac.activity_id AND ac.record_type_id = 3
|
||||||
|
INNER JOIN civicrm_contact_category cc
|
||||||
|
ON cc.id = ac.contact_id
|
||||||
|
WHERE a.activity_type_id = 89
|
||||||
|
),
|
||||||
|
|
||||||
|
/* activities1 is the latest activity before the window's start date */
|
||||||
|
activities1 AS (
|
||||||
|
SELECT contact_id, activity_id,
|
||||||
|
ROW_NUMBER() OVER (
|
||||||
|
PARTITION BY (contact_id)
|
||||||
|
ORDER BY activity_date_time DESC
|
||||||
|
) rn
|
||||||
|
FROM activities a1
|
||||||
|
WHERE a1.activity_date_time < %1
|
||||||
|
),
|
||||||
|
|
||||||
|
/* activities1 is the latest activity before the window's end date */
|
||||||
|
activities2 AS (
|
||||||
|
SELECT contact_id, activity_id,
|
||||||
|
ROW_NUMBER() OVER (
|
||||||
|
PARTITION BY (contact_id)
|
||||||
|
ORDER BY activity_date_time DESC
|
||||||
|
) rn
|
||||||
|
FROM activities a2
|
||||||
|
WHERE a2.activity_date_time < %2
|
||||||
|
)
|
||||||
|
|
||||||
|
/* join activities1 and 2 to count changes between each shift */
|
||||||
|
SELECT cat1.new_category_id from_category_id, cat2.new_category_id to_category_id, count(*) contact_count
|
||||||
|
FROM activities2
|
||||||
|
INNER JOIN civicrm_value_category_chan_41 cat1
|
||||||
|
ON activities2.activity_id = cat1.entity_id
|
||||||
|
LEFT JOIN (
|
||||||
|
activities1
|
||||||
|
INNER JOIN civicrm_value_category_chan_41 cat2
|
||||||
|
ON activities1.activity_id = cat2.entity_id
|
||||||
|
)
|
||||||
|
ON activities1.contact_id = activities2.contact_id AND activities1.rn = 1
|
||||||
|
WHERE activities2.rn = 1
|
||||||
|
GROUP BY from_category_id, to_category_id
|
||||||
|
;
|
||||||
|
SQL;
|
||||||
|
|
||||||
|
$data = CRM_Core_DAO::executeQuery($sql, [
|
||||||
|
1 => [$endDateYmd, 'Int'],
|
||||||
|
2 => [$startDateYmd, 'Int'],
|
||||||
|
])->fetchAll();
|
||||||
|
|
||||||
|
// Don't use exchange array so as not to gazump non-array data(?)
|
||||||
|
foreach ($data as $row) {
|
||||||
|
$result[] = $row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue