diff --git a/Civi/ContactCats/Processor.php b/Civi/ContactCats/Processor.php index 34effd7..a4e57fa 100644 --- a/Civi/ContactCats/Processor.php +++ b/Civi/ContactCats/Processor.php @@ -125,7 +125,7 @@ class Processor { $oldCategoryId = (int) ($lastChange[0] ?? 0); $newCategoryId = (int) ($lastChange[1] ?? 0); $oldName = $this->categories[$oldCategoryId]['label'] ?? E::ts('(Unknown)'); - $subject = "$oldName → " . $this->categories[$newCategoryId]; + $subject = "$oldName → " . $this->categories[$newCategoryId]['label']; // Create activity on the first contact $a = Activity::create(FALSE) ->addValue('target_contact_id', $batch[0]) @@ -139,18 +139,18 @@ class Processor { CRM_Core_DAO::executeQuery("INSERT INTO civicrm_activity_contact (contact_id, activity_id, record_type_id) SELECT id contact_id, $activityID activity_id, 3 record_type_id FROM civicrm_contact_category cc - WHERE cc.category = $oldCategoryId + WHERE cc.category_definition_id = $oldCategoryId AND cc.next_category = $newCategoryId - AND contact_id <> $batch[0] + AND cc.id <> $batch[0] ")->free(); Civi::log()->debug(count($batch) . " changes: $subject"); }; while ($changes->fetch()) { $n++; - if ($lastChange[0] !== $changes->category || $lastChange[1] !== $changes->next_category) { + if ($lastChange[0] !== $changes->category_definition_id || $lastChange[1] !== $changes->next_category) { $writeBatch(); - $lastChange = [$changes->category, $changes->next_category]; + $lastChange = [$changes->category_definition_id, $changes->next_category]; $batch = []; } $batch[] = $changes->id; @@ -195,7 +195,7 @@ class Processor { // Q: is it quicker to do a WHERE NOT EXISTS? A: nope. CRM_Core_DAO::executeQuery(<<free(); diff --git a/ang/afsearchCategory.aff.html b/ang/afsearchCategory.aff.html deleted file mode 100644 index 6656188..0000000 --- a/ang/afsearchCategory.aff.html +++ /dev/null @@ -1,3 +0,0 @@ -
- -
diff --git a/ang/afsearchCategory.aff.json b/ang/afsearchCategory.aff.json deleted file mode 100644 index 29dd66c..0000000 --- a/ang/afsearchCategory.aff.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "type": "search", - "title": "Category", - "placement": [ - "contact_summary_block" - ], - "icon": "fa-list-alt", - "server_route": "", - "permission": [ - "access CiviCRM" - ], - "permission_operator": "AND" -} diff --git a/ang/afsearchContactCategoryCounts.aff.html b/ang/afsearchContactCategoryCounts.aff.html new file mode 100644 index 0000000..e583026 --- /dev/null +++ b/ang/afsearchContactCategoryCounts.aff.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/ang/afsearchContactCategoryCounts.aff.json b/ang/afsearchContactCategoryCounts.aff.json new file mode 100644 index 0000000..7d31433 --- /dev/null +++ b/ang/afsearchContactCategoryCounts.aff.json @@ -0,0 +1,33 @@ +{ + "type": "search", + "title": "Contact Category Counts", + "placement": [ + "dashboard_dashlet" + ], + "icon": "fa-tags", + "server_route": "civicrm/reports/contact-category-counts", + "permission": [ + "access CiviCRM" + ], + "permission_operator": "AND", + "navigation": { + "parent": "Reports", + "label": "Contact Category Counts", + "weight": 0 + }, + "modified_date": "2025-02-27 17:32:42", + "requires": null, + "entity_type": null, + "join_entity": null, + "description": null, + "summary_contact_type": null, + "summary_weight": null, + "is_public": false, + "redirect": null, + "submit_enabled": true, + "submit_limit": null, + "create_submission": null, + "manual_processing": null, + "allow_verification_by_email": null, + "email_confirmation_template_id": null +} diff --git a/ang/crmContactcats.js b/ang/crmContactcats.js index 52bee2c..e3d69aa 100644 --- a/ang/crmContactcats.js +++ b/ang/crmContactcats.js @@ -27,6 +27,10 @@ ctrl.categoryToEdit = null; ctrl.categoryDefinitions = null; ctrl.categoryDefinitions = await crmApi4("ContactCategoryDefinition", 'get', { orderBy: { execution_order: 'ASC' }, withLabels: true }); + if (ctrl.categoryDefinitions.count === 0) { + // First time. + ctrl.categoryDefinitions = []; + } // Ensure we have the minimum of what we need. if (!ctrl.categoryDefinitions.find(c => c.search_type === 'default')) { // There's no default one. @@ -80,7 +84,7 @@ // Create a blank ctrl.categoryToEdit = { id: 0, - execution_order: ctrl.categoryDefinitions.length, + execution_order: -1, label: '', search_type: 'search', search_data: { saved_search_id: null }, @@ -139,8 +143,14 @@ edited.search_data = {saved_search_id}; } - const idx = edited.execution_order; - ctrl.categoryDefinitions[idx] = edited; + if (edited.execution_order === -1) { + // This is a new category, we need to insert it before the default one. + const newIdx = ctrl.categoryDefinitions.length - 1; + ctrl.categoryDefinitions.splice(newIdx, 0, edited); + } + else { + ctrl.categoryDefinitions[edited.execution_order] = edited; + } ctrl.categoryToEdit = null; updateOrders(); ctrl.dirty = 'dirty'; diff --git a/managed/CustomGroup_Category_changes.mgd.php b/managed/CustomGroup_Category_changes.mgd.php index d0aeb05..8c597fd 100644 --- a/managed/CustomGroup_Category_changes.mgd.php +++ b/managed/CustomGroup_Category_changes.mgd.php @@ -17,9 +17,7 @@ return [ 'style' => 'Inline', 'help_pre' => E::ts(''), 'help_post' => E::ts(''), - 'weight' => 18, 'collapse_adv_display' => TRUE, - 'created_date' => '2025-02-20 13:59:06', 'is_public' => FALSE, 'icon' => '', ], @@ -35,7 +33,7 @@ return [ 'version' => 4, 'values' => [ 'custom_group_id.name' => 'Category_changes', - 'name' => 'Previous_Category', + 'name' => 'previous_category_id', 'label' => E::ts('Previous Category'), 'data_type' => 'EntityReference', 'html_type' => 'Autocomplete-Select', @@ -44,7 +42,7 @@ return [ 'text_length' => 255, 'note_columns' => 60, 'note_rows' => 4, - 'column_name' => 'previous_category_117', + 'column_name' => 'previous_category_id', 'fk_entity' => 'ContactCategoryDefinition', ], 'match' => [ @@ -62,7 +60,7 @@ return [ 'version' => 4, 'values' => [ 'custom_group_id.name' => 'Category_changes', - 'name' => 'New_Category', + 'name' => 'new_category_id', 'label' => E::ts('New Category'), 'data_type' => 'EntityReference', 'html_type' => 'Autocomplete-Select', @@ -71,7 +69,7 @@ return [ 'text_length' => 255, 'note_columns' => 60, 'note_rows' => 4, - 'column_name' => 'new_category_118', + 'column_name' => 'new_category_id', 'fk_entity' => 'ContactCategoryDefinition', ], 'match' => [ diff --git a/managed/OptionValue_Changed_category.mgd.php b/managed/OptionValue_Changed_category.mgd.php new file mode 100644 index 0000000..cd681ad --- /dev/null +++ b/managed/OptionValue_Changed_category.mgd.php @@ -0,0 +1,26 @@ + 'OptionValue_Changed_category', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'activity_type', + 'label' => E::ts('Changed category'), + 'name' => 'changed_contact_category', + 'description' => E::ts('

This activity is automatically added when a contact is moved from one category to another.

'), + 'icon' => 'fa-tags', + ], + 'match' => [ + // Should this be option_group_id.name? + 'option_group_id', + 'name', + ], + ], + ], +]; diff --git a/managed/SavedSearch_SavedSearch_Contact_Category_Counts.mgd.php b/managed/SavedSearch_SavedSearch_Contact_Category_Counts.mgd.php new file mode 100644 index 0000000..8389819 --- /dev/null +++ b/managed/SavedSearch_SavedSearch_Contact_Category_Counts.mgd.php @@ -0,0 +1,104 @@ + 'SavedSearch_SavedSearch_Contact_Category_Counts', + 'entity' => 'SavedSearch', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'name' => 'SavedSearch_Contact_Category_Counts', + 'label' => E::ts('Category Counts query'), + 'api_entity' => 'ContactCategoryDefinition', + 'api_params' => [ + 'version' => 4, + 'select' => [ + 'id', + 'label', + 'color', + 'icon', + 'COUNT(ContactCategoryDefinition_ContactCategory_category_definition_id_01.id) AS COUNT_ContactCategoryDefinition_ContactCategory_category_definition_id_01_id', + ], + 'orderBy' => [], + 'where' => [], + 'groupBy' => ['id'], + 'join' => [ + [ + 'ContactCategory AS ContactCategoryDefinition_ContactCategory_category_definition_id_01', + 'LEFT', + [ + 'id', + '=', + 'ContactCategoryDefinition_ContactCategory_category_definition_id_01.category_definition_id', + ], + ], + ], + 'having' => [], + ], + ], + 'match' => ['name'], + ], + ], + [ + 'name' => 'SavedSearch_SavedSearch_Contact_Category_Counts_SearchDisplay_Category_counts', + 'entity' => 'SearchDisplay', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'name' => 'Category_counts', + 'label' => E::ts('Category counts'), + 'saved_search_id.name' => 'SavedSearch_Contact_Category_Counts', + 'type' => 'table', + 'settings' => [ + 'description' => E::ts('Shows a list of all categories and how many contacts are in each one.'), + 'sort' => [ + ['label', 'ASC'], + ], + 'limit' => 50, + 'pager' => FALSE, + 'placeholder' => 0, + 'columns' => [ + [ + 'type' => 'field', + 'key' => 'label', + 'dataType' => 'String', + 'label' => E::ts('Category'), + 'sortable' => TRUE, + 'rewrite' => '', + 'icons' => [ + [ + 'field' => 'icon', + 'side' => 'left', + ], + [ + 'field' => 'icon', + 'side' => 'left', + ], + ], + 'cssRules' => [], + ], + [ + 'type' => 'field', + 'key' => 'COUNT_ContactCategoryDefinition_ContactCategory_category_definition_id_01_id', + 'dataType' => 'Integer', + 'label' => E::ts('Count'), + 'sortable' => TRUE, + 'alignment' => 'text-right', + ], + ], + 'actions' => FALSE, + 'classes' => ['table', 'table-striped'], + ], + ], + 'match' => [ + 'saved_search_id', + 'name', + ], + ], + ], +]; diff --git a/schema/ContactCategory.entityType.php b/schema/ContactCategory.entityType.php index 259b556..2ae6da5 100644 --- a/schema/ContactCategory.entityType.php +++ b/schema/ContactCategory.entityType.php @@ -27,13 +27,6 @@ return [ 'description' => E::ts('Unique ID, corresponds to contact id'), 'primary_key' => TRUE, 'auto_increment' => TRUE, - ], - 'contact_id' => [ - 'title' => E::ts('Contact ID'), - 'sql_type' => 'int unsigned', - 'input_type' => 'EntityRef', - 'required' => TRUE, - 'description' => E::ts('Same as id but for FormBuilder'), 'input_attrs' => [ 'label' => E::ts('Contact'), ], @@ -64,7 +57,7 @@ return [ ], ], 'next_category' => [ - 'title' => E::ts('Next Category'), + 'title' => E::ts('Next Category - internal use'), 'sql_type' => 'int unsigned', 'required' => TRUE, 'default' => 0, diff --git a/schema/ContactCategoryDefinition.entityType.php b/schema/ContactCategoryDefinition.entityType.php index 2427abc..3c22578 100644 --- a/schema/ContactCategoryDefinition.entityType.php +++ b/schema/ContactCategoryDefinition.entityType.php @@ -66,7 +66,7 @@ return [ 'icon' => [ 'title' => E::ts('Icon'), 'sql_type' => 'varchar(64)', - 'default' => '', + 'default' => 'fa-circle-half-stroke', 'required' => TRUE, 'description' => E::ts('The name of a Font Awesome icon to use to represent this, e.g. fa-trophy'), ],