mirror of
https://codeberg.org/artfulrobot/contactcats.git
synced 2025-06-25 14:48:04 +02:00
update tests, fix stuff, implement bespoke save, create actions for contactcategory
This commit is contained in:
parent
78e5b83c1d
commit
2ae781f6e3
6 changed files with 273 additions and 29 deletions
|
@ -8,12 +8,15 @@
|
|||
*
|
||||
* This stub provides compatibility. It is not intended to be modified in a
|
||||
* substantive way. Property annotations may be added, but are not required.
|
||||
* @property string $id
|
||||
* @property string $contact_id
|
||||
* @property string $category
|
||||
* @property string $next_category
|
||||
*
|
||||
* TODO: these are wrong, if it matters:
|
||||
*
|
||||
* @property string $id
|
||||
* @property string $contact_id
|
||||
* @property string $category
|
||||
* @property string $next_category
|
||||
*/
|
||||
class CRM_Contactcats_DAO_ContactCategory extends CRM_Contactcats_DAO_Base {
|
||||
class CRM_Contactcats_DAO_ContactCategory extends CRM_Core_DAO_Base {
|
||||
|
||||
/**
|
||||
* Required by older versions of CiviCRM (<5.74).
|
||||
|
|
28
Civi/Api4/Action/ContactCategory/Create.php
Normal file
28
Civi/Api4/Action/ContactCategory/Create.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
46
Civi/Api4/Action/ContactCategory/Save.php
Normal file
46
Civi/Api4/Action/ContactCategory/Save.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -3,6 +3,8 @@ namespace Civi\Api4;
|
|||
|
||||
use Civi\Api4\Action\ContactCategory\GetFlows;
|
||||
use Civi\Api4\Action\ContactCategory\Sync;
|
||||
use Civi\Api4\Action\ContactCategory\Create;
|
||||
use Civi\Api4\Action\ContactCategory\Save;
|
||||
|
||||
/**
|
||||
* ContactCategory entity.
|
||||
|
@ -13,6 +15,24 @@ use Civi\Api4\Action\ContactCategory\Sync;
|
|||
*/
|
||||
class ContactCategory extends Generic\DAOEntity {
|
||||
|
||||
/**
|
||||
* @param bool $checkPermissions
|
||||
* @return \Civi\Api4\Action\ContactCategory\Create
|
||||
*/
|
||||
public static function create($checkPermissions = TRUE) {
|
||||
return (new Create(static::getEntityName(), __FUNCTION__))
|
||||
->setCheckPermissions($checkPermissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $checkPermissions
|
||||
* @return \Civi\Api4\Action\ContactCategory\Save
|
||||
*/
|
||||
public static function save($checkPermissions = TRUE) {
|
||||
return (new Save(static::getEntityName(), __FUNCTION__))
|
||||
->setCheckPermissions($checkPermissions);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
|
|
@ -96,15 +96,24 @@ class GetFlowsTest extends \PHPUnit\Framework\TestCase implements HeadlessInterf
|
|||
])
|
||||
->execute()->column('id');
|
||||
|
||||
// Create a contact
|
||||
$ctID = Contact::create(FALSE)
|
||||
->setValues([
|
||||
'contact_type' => 'Individual',
|
||||
'display_name' => 'Wilma',
|
||||
])->execute()->first()['id'];
|
||||
// print "xxxxx created ct $ctID\n";
|
||||
[$ctID, $ctID2] = Contact::save(FALSE)
|
||||
->setDefaults(['contact_type' => 'Individual'])
|
||||
->setRecords([
|
||||
['display_name' => 'Wilma'],
|
||||
['display_name' => 'Fred'],
|
||||
])->execute()->column('id');
|
||||
|
||||
// Create two activities.
|
||||
// Put them in meh category. This is not required for the test,
|
||||
// but makes the data realistic.
|
||||
$ccIDs = ContactCategory::save(FALSE)
|
||||
->setRecords([
|
||||
['id' => $ctID, 'category_definition_id' => $mehCatID],
|
||||
['id' => $ctID2, 'category_definition_id' => $mehCatID],
|
||||
])->execute()->column('id');
|
||||
$this->assertEquals([$ctID, $ctID2], $ccIDs);
|
||||
// print "ccs\n" . implode("\n", array_map('json_encode', CRM_Core_DAO::executeQuery("SELECT * from civicrm_contact_category cc")->fetchAll())) . "\n---\n";
|
||||
|
||||
// Create two activities for ct1 and just one for ct2
|
||||
$activityIDs = Activity::save(FALSE)
|
||||
->setDefaults([
|
||||
'activity_type_id:name' => 'changed_contact_category',
|
||||
|
@ -115,23 +124,33 @@ class GetFlowsTest extends \PHPUnit\Framework\TestCase implements HeadlessInterf
|
|||
[
|
||||
'activity_date_time' => '2025-01-01',
|
||||
'Category_changes.new_category_id' => $amazingCatID,
|
||||
'subject' => "Fake subject: $ctID change to amazing from nothing",
|
||||
],
|
||||
[
|
||||
'activity_date_time' => '2025-01-01',
|
||||
'Category_changes.new_category_id' => $mehCatID,
|
||||
'target_contact_id' => $ctID2,
|
||||
'subject' => "Fake subject: $ctID2 change to meh from nothing",
|
||||
],
|
||||
[
|
||||
'activity_date_time' => '2025-02-01',
|
||||
'Category_changes.new_category_id' => $mehCatID,
|
||||
'subject' => "Fake subject: $ctID change to meh from amazing",
|
||||
],
|
||||
])->execute()->column('id');
|
||||
|
||||
// $acts = Activity::get(FALSE)->addWhere('id', 'IN', $activityIDs)->execute()->getArrayCopy(); print_r($acts);
|
||||
|
||||
// FIXME: this is returning backwards.
|
||||
// ================
|
||||
// At 3 Jan, we had 1 amazing, 1 meh
|
||||
// Later, 1 amazing moved to meh.
|
||||
// Flows should return non-changes, too.
|
||||
$flows = ContactCategory::getFlows()
|
||||
->setStartDate('2025-01-03')
|
||||
->execute()->getArrayCopy();
|
||||
// print_r($flows);
|
||||
$this->assertEquals([
|
||||
['from_category_id' => $amazingCatID, 'to_category_id' => $mehCatID, 'contact_count' => 1],
|
||||
['from_category_id' => $mehCatID, 'to_category_id' => $mehCatID, 'contact_count' => 1],
|
||||
], $flows);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue