update tests, fix stuff, implement bespoke save, create actions for contactcategory

This commit is contained in:
Rich Lott / Artful Robot 2025-03-26 10:13:00 +00:00
parent 78e5b83c1d
commit 2ae781f6e3
6 changed files with 273 additions and 29 deletions

View file

@ -0,0 +1,28 @@
<?php
namespace Civi\Api4\Action\ContactCategory;
use Civi\Api4\Generic\Result;
use Civi\Api4\ContactCategory;
use CRM_Contactcats_ExtensionUtil as E;
/**
* Create ContactCategory action
*
* This entity's ID is a unique primary key that references
* contact_id. Here we overwrite the validateValues which normally
*
*/
class Create extends \Civi\Api4\Generic\DAOCreateAction {
/**
* @inheritDoc
*/
public function _run(Result $result) {
// Wrap Save API
$saveResults = ContactCategory::save($this->getCheckPermissions())
->addRecord($this->values)
->execute()->getArrayCopy();
$result->exchangeArray($saveResults);
}
}

View file

@ -72,6 +72,7 @@ class GetFlows extends \Civi\Api4\Generic\AbstractAction {
// $result['windowFunctionsSupported'] = $this->windowFunctionsSupported();
// if ($result['windowFunctionsSupported']) {
if ($this->windowFunctionsSupported()) {
// $this->debug1($startDate, $endDate);
$this->solveWithWindowFunctions($result, $startDate, $endDate);
}
else {
@ -114,19 +115,75 @@ class GetFlows extends \Civi\Api4\Generic\AbstractAction {
return $supported;
}
/**
*
*/
protected function solveWithWindowFunctions(Result $result, string $startDateYmd, ?string $endDateYmd) {
protected function debug1(string $startDateYmd, ?string $endDateYmd) {
$sql = <<<SQL
if (!$endDateYmd) {
$endDateYmd = date('Ymd', strtotime('tomorrow'));
}
$params = [1 => [$startDateYmd, 'Int'], 2 => [$endDateYmd, 'Int']];
$params1 = [1 => [$startDateYmd, 'Int']];
$this->dump("all activities", <<<SQL
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
WHERE a.activity_type_id = $this->activityTypeId
SQL);
$this->dump("all start activities", <<<SQL
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
WHERE a.activity_type_id = $this->activityTypeId
),
/* startActivity is the latest activity before the window's start date */
startActivity 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
)
select * from startActivity;
SQL, $params1);
$this->dump("all end activities", <<<SQL
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
WHERE a.activity_type_id = $this->activityTypeId
),
/* endActivity is the latest activity before the window's end date */
endActivity 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
)
select * from endActivity;
SQL, $params);
$this->dump("all 2", <<<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 = $this->activityTypeId
),
@ -149,7 +206,68 @@ class GetFlows extends \Civi\Api4\Generic\AbstractAction {
ORDER BY activity_date_time DESC
) rn
FROM activities a2
WHERE a2.activity_date_time BETWEEN %1 AND %2
WHERE a2.activity_date_time < %2
)
SELECT /*startCat.new_category_id from_category_id, endCat.new_category_id to_category_id,*/
endActivity.contact_id endCtID, endActivity.activity_id endAcID,
startActivity.contact_id startCtID, startActivity.activity_id startAcID
FROM endActivity
/* INNER JOIN $this->catChangesTableName endCat
ON endActivity.activity_id = endCat.entity_id */
LEFT JOIN (
startActivity
/* INNER JOIN $this->catChangesTableName startCat
ON startActivity.activity_id = startCat.entity_id */
)
ON startActivity.contact_id = endActivity.contact_id AND startActivity.rn = 1
WHERE endActivity.rn = 1
/*ORDER BY from_category_id, to_category_id*/
;
SQL, $params);
}
/**
* This SQL uses the change activities to determine the flows between two dates.
*
* It does so by finding the latest activity before the start date and using it's new_category_id
* as the category for that contact on that date, and likewise for the end date.
*
* It is assumed that a change activity is always present.
*/
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
WHERE a.activity_type_id = $this->activityTypeId
),
/* startActivity is the latest activity before the window's start date */
startActivity 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
),
/* endActivity is the latest activity before the window's end date */
endActivity 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 startActivity and endActivity to count changes between each shift */
@ -188,11 +306,21 @@ class GetFlows extends \Civi\Api4\Generic\AbstractAction {
}
}
protected function dump(string $msg, string $sql) {
print "\n$msg ===============================\n$sql\n";
$data = CRM_Core_DAO::executeQuery($sql)->fetchAll();
print_r($data);
print "\n ===============================\n";
/**
* useful in debugging phpunit tests only; unused in normal operation.
*/
protected function dump(string $msg, string $sql, $params = []) {
try {
$data = CRM_Core_DAO::executeQuery($sql, $params)->fetchAll();
}
catch (\Exception $e) {
print $e->getCause()->userinfo;
}
print "\n$msg (" . count($data) . ") records ===============================\n";
foreach ($data as $row) {
print json_encode($row) . "\n";
}
print "===============================\n";
}
}

View file

@ -0,0 +1,46 @@
<?php
namespace Civi\Api4\Action\ContactCategory;
use Civi\Api4\Generic\Result;
use CRM_Contactcats_ExtensionUtil as E;
use CRM_Core_DAO;
/**
* Create ContactCategory action
*
* This entity's ID is a unique primary key that references
* contact_id. Here we overwrite the validateValues which normally
*
* @see \Civi\Api4\Generic\AbstractAction
*
*/
class Save extends \Civi\Api4\Generic\DAOSaveAction {
/**
* Allow creating records with ids.
*
* @inheritDoc
*/
public function _run(Result $result) {
$ids = [];
foreach (array_column($this->records, 'id') as $id) {
if ($id) {
$ids[] = (int) $id;
}
}
if ($ids) {
$idsList = implode(",", array_filter($ids));
$preExisting = array_column(CRM_Core_DAO::executeQuery(
"SELECT id FROM civicrm_contact_category WHERE id IN ($idsList)"
)->fetchAll(), 'id');
// Any where we were given an ID that doesn't exist are important.
foreach (array_diff($ids, $preExisting) as $unknownID) {
CRM_Core_DAO::executeQuery("INSERT INTO civicrm_contact_category (id) VALUES ($unknownID)");
}
}
// Now let it proceed as normal.
parent::_run($result);
}
}