From b963d010372072915b0b9bfbc2075e5e36ec8bd6 Mon Sep 17 00:00:00 2001 From: Erawat Chamanont Date: Wed, 20 Sep 2023 10:31:15 +0100 Subject: [PATCH 01/14] BTHAB-185: Remove unnecessary upgrader --- CRM/Civicase/Upgrader/Steps/Step0020.php | 30 ------------------------ 1 file changed, 30 deletions(-) delete mode 100644 CRM/Civicase/Upgrader/Steps/Step0020.php diff --git a/CRM/Civicase/Upgrader/Steps/Step0020.php b/CRM/Civicase/Upgrader/Steps/Step0020.php deleted file mode 100644 index 1011d2db4..000000000 --- a/CRM/Civicase/Upgrader/Steps/Step0020.php +++ /dev/null @@ -1,30 +0,0 @@ -create(); - } - catch (\Throwable $th) { - \Civi::log()->error('Error upgrading Civicase', [ - 'context' => [ - 'backtrace' => $th->getTraceAsString(), - 'message' => $th->getMessage(), - ], - ]); - } - - return TRUE; - } - -} From c897ffa20abd79eca6fda23c642af605fff3e518 Mon Sep 17 00:00:00 2001 From: Erawat Chamanont Date: Wed, 20 Sep 2023 10:35:27 +0100 Subject: [PATCH 02/14] BTHAB-185: Upgrade civix from version 22.05.2 to 23.02.1 --- CRM/Civicase/Upgrader.php | 15 +- CRM/Civicase/Upgrader/Base.php | 396 ------------------------- civicase.civix.php | 142 +-------- civicase.php | 37 --- info.xml | 6 +- mixin/entity-types-php@1.0.0.mixin.php | 36 +++ mixin/smarty-v2@1.0.1.mixin.php | 51 ++++ 7 files changed, 106 insertions(+), 577 deletions(-) delete mode 100644 CRM/Civicase/Upgrader/Base.php create mode 100644 mixin/entity-types-php@1.0.0.mixin.php create mode 100644 mixin/smarty-v2@1.0.1.mixin.php diff --git a/CRM/Civicase/Upgrader.php b/CRM/Civicase/Upgrader.php index d3de92044..d47880eeb 100644 --- a/CRM/Civicase/Upgrader.php +++ b/CRM/Civicase/Upgrader.php @@ -23,7 +23,7 @@ /** * Collection of upgrade steps. */ -class CRM_Civicase_Upgrader extends CRM_Civicase_Upgrader_Base { +class CRM_Civicase_Upgrader extends CRM_Extension_Upgrader_Base { // By convention, functions that look like "function upgrade_NNNN()" are // upgrade tasks. They are executed in order (like Drupal's hook_update_N). @@ -516,13 +516,13 @@ public function hasPendingRevisions() { * * @inheritdoc */ - public function enqueuePendingRevisions(CRM_Queue_Queue $queue) { + public function enqueuePendingRevisions() { $currentRevisionNum = (int) $this->getCurrentRevision(); foreach ($this->getRevisions() as $revisionClass => $revisionNum) { if ($revisionNum <= $currentRevisionNum) { continue; } - $title = E::ts('Upgrade %1 to revision %2', [ + $title = ts('Upgrade %1 to revision %2', [ 1 => $this->extensionName, 2 => $revisionNum, ]); @@ -531,13 +531,8 @@ public function enqueuePendingRevisions(CRM_Queue_Queue $queue) { [(new $revisionClass())], $title ); - $queue->createItem($upgradeTask); - $setRevisionTask = new CRM_Queue_Task( - [get_class($this), '_queueAdapter'], - ['setCurrentRevision', $revisionNum], - $title - ); - $queue->createItem($setRevisionTask); + $this->queue->createItem($upgradeTask); + $this->appendTask($title, 'setCurrentRevision', $revisionNum); } } diff --git a/CRM/Civicase/Upgrader/Base.php b/CRM/Civicase/Upgrader/Base.php deleted file mode 100644 index 1c8340902..000000000 --- a/CRM/Civicase/Upgrader/Base.php +++ /dev/null @@ -1,396 +0,0 @@ -ctx = array_shift($args); - $instance->queue = $instance->ctx->queue; - $method = array_shift($args); - return call_user_func_array([$instance, $method], $args); - } - - /** - * CRM_Civicase_Upgrader_Base constructor. - * - * @param $extensionName - * @param $extensionDir - */ - public function __construct($extensionName, $extensionDir) { - $this->extensionName = $extensionName; - $this->extensionDir = $extensionDir; - } - - // ******** Task helpers ******** - - /** - * Run a CustomData file. - * - * @param string $relativePath - * the CustomData XML file path (relative to this extension's dir) - * @return bool - */ - public function executeCustomDataFile($relativePath) { - $xml_file = $this->extensionDir . '/' . $relativePath; - return $this->executeCustomDataFileByAbsPath($xml_file); - } - - /** - * Run a CustomData file - * - * @param string $xml_file - * the CustomData XML file path (absolute path) - * - * @return bool - */ - protected function executeCustomDataFileByAbsPath($xml_file) { - $import = new CRM_Utils_Migrate_Import(); - $import->run($xml_file); - return TRUE; - } - - /** - * Run a SQL file. - * - * @param string $relativePath - * the SQL file path (relative to this extension's dir) - * - * @return bool - */ - public function executeSqlFile($relativePath) { - CRM_Utils_File::sourceSQLFile( - CIVICRM_DSN, - $this->extensionDir . DIRECTORY_SEPARATOR . $relativePath - ); - return TRUE; - } - - /** - * Run the sql commands in the specified file. - * - * @param string $tplFile - * The SQL file path (relative to this extension's dir). - * Ex: "sql/mydata.mysql.tpl". - * - * @return bool - * @throws \CRM_Core_Exception - */ - public function executeSqlTemplate($tplFile) { - // Assign multilingual variable to Smarty. - $upgrade = new CRM_Upgrade_Form(); - - $tplFile = CRM_Utils_File::isAbsolute($tplFile) ? $tplFile : $this->extensionDir . DIRECTORY_SEPARATOR . $tplFile; - $smarty = CRM_Core_Smarty::singleton(); - $smarty->assign('domainID', CRM_Core_Config::domainID()); - CRM_Utils_File::sourceSQLFile( - CIVICRM_DSN, $smarty->fetch($tplFile), NULL, TRUE - ); - return TRUE; - } - - /** - * Run one SQL query. - * - * This is just a wrapper for CRM_Core_DAO::executeSql, but it - * provides syntactic sugar for queueing several tasks that - * run different queries - * - * @return bool - */ - public function executeSql($query, $params = []) { - // FIXME verify that we raise an exception on error - CRM_Core_DAO::executeQuery($query, $params); - return TRUE; - } - - /** - * Syntactic sugar for enqueuing a task which calls a function in this class. - * - * The task is weighted so that it is processed - * as part of the currently-pending revision. - * - * After passing the $funcName, you can also pass parameters that will go to - * the function. Note that all params must be serializable. - */ - public function addTask($title) { - $args = func_get_args(); - $title = array_shift($args); - $task = new CRM_Queue_Task( - [get_class($this), '_queueAdapter'], - $args, - $title - ); - return $this->queue->createItem($task, ['weight' => -1]); - } - - // ******** Revision-tracking helpers ******** - - /** - * Determine if there are any pending revisions. - * - * @return bool - */ - public function hasPendingRevisions() { - $revisions = $this->getRevisions(); - $currentRevision = $this->getCurrentRevision(); - - if (empty($revisions)) { - return FALSE; - } - if (empty($currentRevision)) { - return TRUE; - } - - return ($currentRevision < max($revisions)); - } - - /** - * Add any pending revisions to the queue. - * - * @param CRM_Queue_Queue $queue - */ - public function enqueuePendingRevisions(CRM_Queue_Queue $queue) { - $this->queue = $queue; - - $currentRevision = $this->getCurrentRevision(); - foreach ($this->getRevisions() as $revision) { - if ($revision > $currentRevision) { - $title = E::ts('Upgrade %1 to revision %2', [ - 1 => $this->extensionName, - 2 => $revision, - ]); - - // note: don't use addTask() because it sets weight=-1 - - $task = new CRM_Queue_Task( - [get_class($this), '_queueAdapter'], - ['upgrade_' . $revision], - $title - ); - $this->queue->createItem($task); - - $task = new CRM_Queue_Task( - [get_class($this), '_queueAdapter'], - ['setCurrentRevision', $revision], - $title - ); - $this->queue->createItem($task); - } - } - } - - /** - * Get a list of revisions. - * - * @return array - * revisionNumbers sorted numerically - */ - public function getRevisions() { - if (!is_array($this->revisions)) { - $this->revisions = []; - - $clazz = new ReflectionClass(get_class($this)); - $methods = $clazz->getMethods(); - foreach ($methods as $method) { - if (preg_match('/^upgrade_(.*)/', $method->name, $matches)) { - $this->revisions[] = $matches[1]; - } - } - sort($this->revisions, SORT_NUMERIC); - } - - return $this->revisions; - } - - public function getCurrentRevision() { - $revision = CRM_Core_BAO_Extension::getSchemaVersion($this->extensionName); - if (!$revision) { - $revision = $this->getCurrentRevisionDeprecated(); - } - return $revision; - } - - private function getCurrentRevisionDeprecated() { - $key = $this->extensionName . ':version'; - if ($revision = \Civi::settings()->get($key)) { - $this->revisionStorageIsDeprecated = TRUE; - } - return $revision; - } - - public function setCurrentRevision($revision) { - CRM_Core_BAO_Extension::setSchemaVersion($this->extensionName, $revision); - // clean up legacy schema version store (CRM-19252) - $this->deleteDeprecatedRevision(); - return TRUE; - } - - private function deleteDeprecatedRevision() { - if ($this->revisionStorageIsDeprecated) { - $setting = new CRM_Core_BAO_Setting(); - $setting->name = $this->extensionName . ':version'; - $setting->delete(); - CRM_Core_Error::debug_log_message("Migrated extension schema revision ID for {$this->extensionName} from civicrm_setting (deprecated) to civicrm_extension.\n"); - } - } - - // ******** Hook delegates ******** - - /** - * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_install - */ - public function onInstall() { - $files = glob($this->extensionDir . '/sql/*_install.sql'); - if (is_array($files)) { - foreach ($files as $file) { - CRM_Utils_File::sourceSQLFile(CIVICRM_DSN, $file); - } - } - $files = glob($this->extensionDir . '/sql/*_install.mysql.tpl'); - if (is_array($files)) { - foreach ($files as $file) { - $this->executeSqlTemplate($file); - } - } - $files = glob($this->extensionDir . '/xml/*_install.xml'); - if (is_array($files)) { - foreach ($files as $file) { - $this->executeCustomDataFileByAbsPath($file); - } - } - if (is_callable([$this, 'install'])) { - $this->install(); - } - } - - /** - * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_postInstall - */ - public function onPostInstall() { - $revisions = $this->getRevisions(); - if (!empty($revisions)) { - $this->setCurrentRevision(max($revisions)); - } - if (is_callable([$this, 'postInstall'])) { - $this->postInstall(); - } - } - - /** - * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_uninstall - */ - public function onUninstall() { - $files = glob($this->extensionDir . '/sql/*_uninstall.mysql.tpl'); - if (is_array($files)) { - foreach ($files as $file) { - $this->executeSqlTemplate($file); - } - } - if (is_callable([$this, 'uninstall'])) { - $this->uninstall(); - } - $files = glob($this->extensionDir . '/sql/*_uninstall.sql'); - if (is_array($files)) { - foreach ($files as $file) { - CRM_Utils_File::sourceSQLFile(CIVICRM_DSN, $file); - } - } - } - - /** - * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_enable - */ - public function onEnable() { - // stub for possible future use - if (is_callable([$this, 'enable'])) { - $this->enable(); - } - } - - /** - * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_disable - */ - public function onDisable() { - // stub for possible future use - if (is_callable([$this, 'disable'])) { - $this->disable(); - } - } - - public function onUpgrade($op, CRM_Queue_Queue $queue = NULL) { - switch ($op) { - case 'check': - return [$this->hasPendingRevisions()]; - - case 'enqueue': - return $this->enqueuePendingRevisions($queue); - - default: - } - } - -} diff --git a/civicase.civix.php b/civicase.civix.php index e3932e11e..5a4fb9350 100644 --- a/civicase.civix.php +++ b/civicase.civix.php @@ -24,7 +24,7 @@ class CRM_Civicase_ExtensionUtil { * Translated text. * @see ts */ - public static function ts($text, $params = []) { + public static function ts($text, $params = []): string { if (!array_key_exists('domain', $params)) { $params['domain'] = [self::LONG_NAME, NULL]; } @@ -41,7 +41,7 @@ public static function ts($text, $params = []) { * Ex: 'http://example.org/sites/default/ext/org.example.foo'. * Ex: 'http://example.org/sites/default/ext/org.example.foo/css/foo.css'. */ - public static function url($file = NULL) { + public static function url($file = NULL): string { if ($file === NULL) { return rtrim(CRM_Core_Resources::singleton()->getUrl(self::LONG_NAME), '/'); } @@ -84,27 +84,17 @@ public static function findClass($suffix) { * * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_config */ -function _civicase_civix_civicrm_config(&$config = NULL) { +function _civicase_civix_civicrm_config($config = NULL) { static $configured = FALSE; if ($configured) { return; } $configured = TRUE; - $template = CRM_Core_Smarty::singleton(); - $extRoot = __DIR__ . DIRECTORY_SEPARATOR; - $extDir = $extRoot . 'templates'; - - if (is_array($template->template_dir)) { - array_unshift($template->template_dir, $extDir); - } - else { - $template->template_dir = [$extDir, $template->template_dir]; - } - $include_path = $extRoot . PATH_SEPARATOR . get_include_path(); set_include_path($include_path); + // Based on , this does not currently require mixin/polyfill.php. } /** @@ -114,35 +104,7 @@ function _civicase_civix_civicrm_config(&$config = NULL) { */ function _civicase_civix_civicrm_install() { _civicase_civix_civicrm_config(); - if ($upgrader = _civicase_civix_upgrader()) { - $upgrader->onInstall(); - } -} - -/** - * Implements hook_civicrm_postInstall(). - * - * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_postInstall - */ -function _civicase_civix_civicrm_postInstall() { - _civicase_civix_civicrm_config(); - if ($upgrader = _civicase_civix_upgrader()) { - if (is_callable([$upgrader, 'onPostInstall'])) { - $upgrader->onPostInstall(); - } - } -} - -/** - * Implements hook_civicrm_uninstall(). - * - * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_uninstall - */ -function _civicase_civix_civicrm_uninstall() { - _civicase_civix_civicrm_config(); - if ($upgrader = _civicase_civix_upgrader()) { - $upgrader->onUninstall(); - } + // Based on , this does not currently require mixin/polyfill.php. } /** @@ -150,58 +112,9 @@ function _civicase_civix_civicrm_uninstall() { * * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_enable */ -function _civicase_civix_civicrm_enable() { +function _civicase_civix_civicrm_enable(): void { _civicase_civix_civicrm_config(); - if ($upgrader = _civicase_civix_upgrader()) { - if (is_callable([$upgrader, 'onEnable'])) { - $upgrader->onEnable(); - } - } -} - -/** - * (Delegated) Implements hook_civicrm_disable(). - * - * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_disable - * @return mixed - */ -function _civicase_civix_civicrm_disable() { - _civicase_civix_civicrm_config(); - if ($upgrader = _civicase_civix_upgrader()) { - if (is_callable([$upgrader, 'onDisable'])) { - $upgrader->onDisable(); - } - } -} - -/** - * (Delegated) Implements hook_civicrm_upgrade(). - * - * @param $op string, the type of operation being performed; 'check' or 'enqueue' - * @param $queue CRM_Queue_Queue, (for 'enqueue') the modifiable list of pending up upgrade tasks - * - * @return mixed - * based on op. for 'check', returns array(boolean) (TRUE if upgrades are pending) - * for 'enqueue', returns void - * - * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_upgrade - */ -function _civicase_civix_civicrm_upgrade($op, CRM_Queue_Queue $queue = NULL) { - if ($upgrader = _civicase_civix_upgrader()) { - return $upgrader->onUpgrade($op, $queue); - } -} - -/** - * @return CRM_Civicase_Upgrader - */ -function _civicase_civix_upgrader() { - if (!file_exists(__DIR__ . '/CRM/Civicase/Upgrader.php')) { - return NULL; - } - else { - return CRM_Civicase_Upgrader_Base::instance(); - } + // Based on , this does not currently require mixin/polyfill.php. } /** @@ -220,8 +133,8 @@ function _civicase_civix_insert_navigation_menu(&$menu, $path, $item) { if (empty($path)) { $menu[] = [ 'attributes' => array_merge([ - 'label' => CRM_Utils_Array::value('name', $item), - 'active' => 1, + 'label' => $item['name'] ?? NULL, + 'active' => 1, ], $item), ]; return TRUE; @@ -285,40 +198,3 @@ function _civicase_civix_fixNavigationMenuItems(&$nodes, &$maxNavID, $parentID) } } } - -/** - * (Delegated) Implements hook_civicrm_entityTypes(). - * - * Find any *.entityType.php files, merge their content, and return. - * - * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_entityTypes - */ -function _civicase_civix_civicrm_entityTypes(&$entityTypes) { - $entityTypes = array_merge($entityTypes, [ - 'CRM_Civicase_DAO_CaseCategoryFeatures' => [ - 'name' => 'CaseCategoryFeatures', - 'class' => 'CRM_Civicase_DAO_CaseCategoryFeatures', - 'table' => 'civicrm_case_category_features', - ], - 'CRM_Civicase_DAO_CaseCategoryInstance' => [ - 'name' => 'CaseCategoryInstance', - 'class' => 'CRM_Civicase_DAO_CaseCategoryInstance', - 'table' => 'civicrm_case_category_instance', - ], - 'CRM_Civicase_DAO_CaseContactLock' => [ - 'name' => 'CaseContactLock', - 'class' => 'CRM_Civicase_DAO_CaseContactLock', - 'table' => 'civicase_contactlock', - ], - 'CRM_Civicase_DAO_CaseSalesOrder' => [ - 'name' => 'CaseSalesOrder', - 'class' => 'CRM_Civicase_DAO_CaseSalesOrder', - 'table' => 'civicase_sales_order', - ], - 'CRM_Civicase_DAO_CaseSalesOrderLine' => [ - 'name' => 'CaseSalesOrderLine', - 'class' => 'CRM_Civicase_DAO_CaseSalesOrderLine', - 'table' => 'civicase_sales_order_line', - ], - ]); -} diff --git a/civicase.php b/civicase.php index 89ac2b026..b0931f5e1 100644 --- a/civicase.php +++ b/civicase.php @@ -100,24 +100,6 @@ function civicase_civicrm_install() { _civicase_civix_civicrm_install(); } -/** - * Implements hook_civicrm_postInstall(). - * - * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_postInstall - */ -function civicase_civicrm_postInstall() { - _civicase_civix_civicrm_postInstall(); -} - -/** - * Implements hook_civicrm_uninstall(). - * - * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_uninstall - */ -function civicase_civicrm_uninstall() { - _civicase_civix_civicrm_uninstall(); -} - /** * Implements hook_civicrm_enable(). * @@ -127,24 +109,6 @@ function civicase_civicrm_enable() { _civicase_civix_civicrm_enable(); } -/** - * Implements hook_civicrm_disable(). - * - * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_disable - */ -function civicase_civicrm_disable() { - _civicase_civix_civicrm_disable(); -} - -/** - * Implements hook_civicrm_upgrade(). - * - * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_upgrade - */ -function civicase_civicrm_upgrade($op, CRM_Queue_Queue $queue = NULL) { - return _civicase_civix_civicrm_upgrade($op, $queue); -} - /** * Implements hook_civicrm_alterMenu(). * @@ -452,7 +416,6 @@ function civicase_civicrm_selectWhereClause($entity, &$clauses) { * Implements hook_civicrm_entityTypes(). */ function civicase_civicrm_entityTypes(&$entityTypes) { - _civicase_civix_civicrm_entityTypes($entityTypes); _civicase_add_case_category_case_type_entity($entityTypes); } diff --git a/info.xml b/info.xml index cb0addc54..713d2298b 100644 --- a/info.xml +++ b/info.xml @@ -26,10 +26,11 @@ + CRM/Civicase - 22.05.2 + 23.02.1 org.civicrm.shoreditch @@ -41,5 +42,8 @@ menu-xml@1.0.0 mgd-php@1.0.0 setting-php@1.0.0 + smarty-v2@1.0.1 + entity-types-php@1.0.0 + CRM_Civicase_Upgrader diff --git a/mixin/entity-types-php@1.0.0.mixin.php b/mixin/entity-types-php@1.0.0.mixin.php new file mode 100644 index 000000000..8ec4203df --- /dev/null +++ b/mixin/entity-types-php@1.0.0.mixin.php @@ -0,0 +1,36 @@ +addListener('hook_civicrm_entityTypes', function ($e) use ($mixInfo) { + // When deactivating on a polyfill/pre-mixin system, listeners may not cleanup automatically. + if (!$mixInfo->isActive() || !is_dir($mixInfo->getPath('xml/schema/CRM'))) { + return; + } + + $files = (array) glob($mixInfo->getPath('xml/schema/CRM/*/*.entityType.php')); + foreach ($files as $file) { + $entities = include $file; + foreach ($entities as $entity) { + $e->entityTypes[$entity['class']] = $entity; + } + } + }); + +}; diff --git a/mixin/smarty-v2@1.0.1.mixin.php b/mixin/smarty-v2@1.0.1.mixin.php new file mode 100644 index 000000000..5972dbdc5 --- /dev/null +++ b/mixin/smarty-v2@1.0.1.mixin.php @@ -0,0 +1,51 @@ +getPath('templates'); + if (!file_exists($dir)) { + return; + } + + $register = function() use ($dir) { + // This implementation has a theoretical edge-case bug on older versions of CiviCRM where a template could + // be registered more than once. + CRM_Core_Smarty::singleton()->addTemplateDir($dir); + }; + + // Let's figure out what environment we're in -- so that we know the best way to call $register(). + + if (!empty($GLOBALS['_CIVIX_MIXIN_POLYFILL'])) { + // Polyfill Loader (v<=5.45): We're already in the middle of firing `hook_config`. + if ($mixInfo->isActive()) { + $register(); + } + return; + } + + if (CRM_Extension_System::singleton()->getManager()->extensionIsBeingInstalledOrEnabled($mixInfo->longName)) { + // New Install, Standard Loader: The extension has just been enabled, and we're now setting it up. + // System has already booted. New templates may be needed for upcoming installation steps. + $register(); + return; + } + + // Typical Pageview, Standard Loader: Defer the actual registration for a moment -- to ensure that Smarty is online. + \Civi::dispatcher()->addListener('hook_civicrm_config', function() use ($mixInfo, $register) { + if ($mixInfo->isActive()) { + $register(); + } + }); + +}; From 01c345153198b5890722507405503fff2cb14aa7 Mon Sep 17 00:00:00 2001 From: Erawat Chamanont Date: Wed, 20 Sep 2023 10:41:05 +0100 Subject: [PATCH 03/14] BTHAB-185: Add new entities to case sales order Include regenerating boilerplate code --- CRM/Civicase/DAO/CaseSalesOrder.php | 60 +++++++++++++++++++++- sql/auto_install.sql | 2 + xml/schema/CRM/Civicase/CaseSalesOrder.xml | 28 ++++++++++ 3 files changed, 89 insertions(+), 1 deletion(-) diff --git a/CRM/Civicase/DAO/CaseSalesOrder.php b/CRM/Civicase/DAO/CaseSalesOrder.php index 5a85f9e39..9e2304e13 100644 --- a/CRM/Civicase/DAO/CaseSalesOrder.php +++ b/CRM/Civicase/DAO/CaseSalesOrder.php @@ -6,7 +6,7 @@ * * Generated from uk.co.compucorp.civicase/xml/schema/CRM/Civicase/CaseSalesOrder.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:555941e9f2d2bb4c81224d13c42ac043) + * (GenCodeChecksum:88cd4ea87464d1254915637375e0a5a5) */ use CRM_Civicase_ExtensionUtil as E; @@ -96,6 +96,24 @@ class CRM_Civicase_DAO_CaseSalesOrder extends CRM_Core_DAO { */ public $status_id; + /** + * One of the values of the case_sales_order_invoicing_status option group + * + * @var int|string + * (SQL type: int unsigned) + * Note that values will be retrieved from the database as a string. + */ + public $invoicing_status_id; + + /** + * One of the values of the case_sales_order_payment_status option group + * + * @var int|string + * (SQL type: int unsigned) + * Note that values will be retrieved from the database as a string. + */ + public $payment_status_id; + /** * Sales order deesctiption * @@ -313,6 +331,46 @@ public static function &fields() { ], 'add' => NULL, ], + 'invoicing_status_id' => [ + 'name' => 'invoicing_status_id', + 'type' => CRM_Utils_Type::T_INT, + 'description' => E::ts('One of the values of the case_sales_order_invoicing_status option group'), + 'required' => TRUE, + 'where' => 'civicase_sales_order.invoicing_status_id', + 'table_name' => 'civicase_sales_order', + 'entity' => 'CaseSalesOrder', + 'bao' => 'CRM_Civicase_DAO_CaseSalesOrder', + 'localizable' => 0, + 'html' => [ + 'type' => 'Select', + 'label' => E::ts("Invoicing"), + ], + 'pseudoconstant' => [ + 'optionGroupName' => 'case_sales_order_invoicing_status', + 'optionEditPath' => 'civicrm/admin/options/case_sales_order_invoicing_status', + ], + 'add' => NULL, + ], + 'payment_status_id' => [ + 'name' => 'payment_status_id', + 'type' => CRM_Utils_Type::T_INT, + 'description' => E::ts('One of the values of the case_sales_order_payment_status option group'), + 'required' => TRUE, + 'where' => 'civicase_sales_order.payment_status_id', + 'table_name' => 'civicase_sales_order', + 'entity' => 'CaseSalesOrder', + 'bao' => 'CRM_Civicase_DAO_CaseSalesOrder', + 'localizable' => 0, + 'html' => [ + 'type' => 'Select', + 'label' => E::ts("Payments"), + ], + 'pseudoconstant' => [ + 'optionGroupName' => 'case_sales_order_payment_status', + 'optionEditPath' => 'civicrm/admin/options/case_sales_order_payment_status', + ], + 'add' => NULL, + ], 'description' => [ 'name' => 'description', 'type' => CRM_Utils_Type::T_TEXT, diff --git a/sql/auto_install.sql b/sql/auto_install.sql index aa06792c5..c85f5274a 100644 --- a/sql/auto_install.sql +++ b/sql/auto_install.sql @@ -60,6 +60,8 @@ CREATE TABLE IF NOT EXISTS `civicase_sales_order` ( `case_id` int unsigned COMMENT 'FK to Case', `currency` varchar(3) DEFAULT NULL COMMENT '3 character string, value from config setting or input via user.', `status_id` int unsigned NOT NULL COMMENT 'One of the values of the case_sales_order_status option group', + `invoicing_status_id` int unsigned NOT NULL COMMENT 'One of the values of the case_sales_order_invoicing_status option group', + `payment_status_id` int unsigned NOT NULL COMMENT 'One of the values of the case_sales_order_payment_status option group', `description` text NULL COMMENT 'Sales order deesctiption', `notes` text NULL COMMENT 'Sales order notes', `total_before_tax` decimal(20,2) NULL COMMENT 'Total amount of the sales order line items before tax deduction.', diff --git a/xml/schema/CRM/Civicase/CaseSalesOrder.xml b/xml/schema/CRM/Civicase/CaseSalesOrder.xml index 13866e93e..e7a4630bb 100644 --- a/xml/schema/CRM/Civicase/CaseSalesOrder.xml +++ b/xml/schema/CRM/Civicase/CaseSalesOrder.xml @@ -110,6 +110,34 @@ + + invoicing_status_id + int unsigned + One of the values of the case_sales_order_invoicing_status option group + true + + case_sales_order_invoicing_status + + + + Select + + + + + payment_status_id + int unsigned + One of the values of the case_sales_order_payment_status option group + true + + case_sales_order_payment_status + + + + Select + + + description text From 15bc50e4d3b3e5caa3640facd41b3c787183d15a Mon Sep 17 00:00:00 2001 From: Erawat Chamanont Date: Wed, 20 Sep 2023 10:42:40 +0100 Subject: [PATCH 04/14] BTHAB-185: Add option groups/values for invoicing and payment statuses --- .../Manage/CaseSalesOrderStatusManager.php | 125 ++++++++++++++++-- CRM/Civicase/Upgrader/Steps/Step0019.php | 6 +- 2 files changed, 121 insertions(+), 10 deletions(-) diff --git a/CRM/Civicase/Setup/Manage/CaseSalesOrderStatusManager.php b/CRM/Civicase/Setup/Manage/CaseSalesOrderStatusManager.php index 71d9e6f5c..105c5bfe4 100644 --- a/CRM/Civicase/Setup/Manage/CaseSalesOrderStatusManager.php +++ b/CRM/Civicase/Setup/Manage/CaseSalesOrderStatusManager.php @@ -5,7 +5,9 @@ */ class CRM_Civicase_Setup_Manage_CaseSalesOrderStatusManager extends CRM_Civicase_Setup_Manage_AbstractManager { - const NAME = 'case_sales_order_status'; + const SALE_ORDER_STATUS_NAME = 'case_sales_order_status'; + const SALE_ORDER_INVOICING_STATUS_NANE = 'case_sales_order_invoicing_status'; + const SALE_ORDER_PAYMENT_STATUS_NANE = 'case_sales_order_payment_status'; /** * Ensures Sales Order Status option group and default option values exists. @@ -14,14 +16,23 @@ class CRM_Civicase_Setup_Manage_CaseSalesOrderStatusManager extends CRM_Civicase * for sales order. */ public function create(): void { + $this->createSaleOrderStatus(); + $this->createSaleOrderInvoicingStatus(); + $this->createSaleOrderPaymentStatus(); + } + + /** + * Creates sale order status. + */ + private function createSaleOrderStatus() { CRM_Core_BAO_OptionGroup::ensureOptionGroupExists([ - 'name' => self::NAME, + 'name' => self::SALE_ORDER_STATUS_NAME, 'title' => ts('Sales Order Status'), 'is_reserved' => 1, ]); CRM_Core_BAO_OptionValue::ensureOptionValueExists([ - 'option_group_id' => self::NAME, + 'option_group_id' => self::SALE_ORDER_STATUS_NAME, 'name' => 'new', 'label' => 'New', 'is_default' => TRUE, @@ -30,7 +41,7 @@ public function create(): void { ]); CRM_Core_BAO_OptionValue::ensureOptionValueExists([ - 'option_group_id' => self::NAME, + 'option_group_id' => self::SALE_ORDER_STATUS_NAME, 'name' => 'sent_to_client', 'label' => 'Sent to client', 'is_active' => TRUE, @@ -38,7 +49,7 @@ public function create(): void { ]); CRM_Core_BAO_OptionValue::ensureOptionValueExists([ - 'option_group_id' => self::NAME, + 'option_group_id' => self::SALE_ORDER_STATUS_NAME, 'name' => 'accepted', 'label' => 'Accepted', 'is_active' => TRUE, @@ -46,7 +57,7 @@ public function create(): void { ]); CRM_Core_BAO_OptionValue::ensureOptionValueExists([ - 'option_group_id' => self::NAME, + 'option_group_id' => self::SALE_ORDER_STATUS_NAME, 'name' => 'declined', 'label' => 'Declined', 'is_active' => TRUE, @@ -54,13 +65,105 @@ public function create(): void { ]); } + /** + * Creates sale order invoicing status. + */ + private function createSaleOrderInvoicingStatus() { + CRM_Core_BAO_OptionGroup::ensureOptionGroupExists([ + 'name' => self::SALE_ORDER_INVOICING_STATUS_NANE, + 'title' => ts('Invoicing'), + 'is_reserved' => 1, + ]); + + CRM_Core_BAO_OptionValue::ensureOptionValueExists([ + 'option_group_id' => self::SALE_ORDER_INVOICING_STATUS_NANE, + 'name' => 'no_invoices', + 'label' => 'No Invoices', + 'is_active' => TRUE, + 'is_reserved' => TRUE, + ]); + + CRM_Core_BAO_OptionValue::ensureOptionValueExists([ + 'option_group_id' => self::SALE_ORDER_INVOICING_STATUS_NANE, + 'name' => 'partially_invoiced', + 'label' => 'Partially Invoiced', + 'is_active' => TRUE, + 'is_reserved' => TRUE, + ]); + + CRM_Core_BAO_OptionValue::ensureOptionValueExists([ + 'option_group_id' => self::SALE_ORDER_INVOICING_STATUS_NANE, + 'name' => 'fully_invoiced', + 'label' => 'Fully Invoiced', + 'is_active' => TRUE, + 'is_reserved' => TRUE, + ]); + } + + /** + * Creates sale order payments status. + */ + private function createSaleOrderPaymentStatus() { + CRM_Core_BAO_OptionGroup::ensureOptionGroupExists([ + 'name' => self::SALE_ORDER_PAYMENT_STATUS_NANE, + 'title' => ts('Payments'), + 'is_reserved' => 1, + ]); + + CRM_Core_BAO_OptionValue::ensureOptionValueExists([ + 'option_group_id' => self::SALE_ORDER_PAYMENT_STATUS_NANE, + 'name' => 'no_payments', + 'label' => 'No Payments', + 'is_active' => TRUE, + 'is_reserved' => TRUE, + ]); + + CRM_Core_BAO_OptionValue::ensureOptionValueExists([ + 'option_group_id' => self::SALE_ORDER_PAYMENT_STATUS_NANE, + 'name' => 'no_payments', + 'label' => 'No Payments', + 'is_active' => TRUE, + 'is_reserved' => TRUE, + ]); + + CRM_Core_BAO_OptionValue::ensureOptionValueExists([ + 'option_group_id' => self::SALE_ORDER_PAYMENT_STATUS_NANE, + 'name' => 'partially_paid', + 'label' => 'Partially Paid', + 'is_active' => TRUE, + 'is_reserved' => TRUE, + ]); + + CRM_Core_BAO_OptionValue::ensureOptionValueExists([ + 'option_group_id' => self::SALE_ORDER_PAYMENT_STATUS_NANE, + 'name' => 'fully_paid', + 'label' => 'Fully Paid', + 'is_active' => TRUE, + 'is_reserved' => TRUE, + ]); + + CRM_Core_BAO_OptionValue::ensureOptionValueExists([ + 'option_group_id' => self::SALE_ORDER_PAYMENT_STATUS_NANE, + 'name' => 'over_paid', + 'label' => 'Over Paid', + 'is_active' => TRUE, + 'is_reserved' => TRUE, + ]); + } + /** * Removes the entity. */ public function remove(): void { civicrm_api3('OptionGroup', 'get', [ 'return' => ['id'], - 'name' => CRM_Civicase_Service_CaseTypeCategoryFeatures::NAME, + 'name' => [ + 'IN' => [ + self::SALE_ORDER_STATUS_NAME, + self::SALE_ORDER_INVOICING_STATUS_NANE, + self::SALE_ORDER_PAYMENT_STATUS_NANE, + ], + ], 'api.OptionGroup.delete' => ['id' => '$value.id'], ]); } @@ -71,7 +174,13 @@ public function remove(): void { protected function toggle($status): void { civicrm_api3('OptionGroup', 'get', [ 'sequential' => 1, - 'name' => CRM_Civicase_Service_CaseTypeCategoryFeatures::NAME, + 'name' => [ + 'IN' => [ + self::SALE_ORDER_STATUS_NAME, + self::SALE_ORDER_INVOICING_STATUS_NANE, + self::SALE_ORDER_PAYMENT_STATUS_NANE, + ], + ], 'api.OptionGroup.create' => ['id' => '$value.id', 'is_active' => $status], ]); } diff --git a/CRM/Civicase/Upgrader/Steps/Step0019.php b/CRM/Civicase/Upgrader/Steps/Step0019.php index 8139aefa2..c89186996 100644 --- a/CRM/Civicase/Upgrader/Steps/Step0019.php +++ b/CRM/Civicase/Upgrader/Steps/Step0019.php @@ -1,8 +1,9 @@ create(); (new CaseTypeCategoryManager())->create(); (new CaseSalesOrderStatusManager())->create(); + (new MembershipTypeCustomFieldManager())->create(); } catch (\Throwable $th) { \Civi::log()->error('Error upgrading Civicase', [ From 41351d0048672f2f0cc2b28851afaca0a440e9b6 Mon Sep 17 00:00:00 2001 From: Erawat Chamanont Date: Wed, 20 Sep 2023 10:45:49 +0100 Subject: [PATCH 05/14] BTHAB-185: Add CaseSaleOrderContribution service The service calculates statuses and amounts from the given sales order ID --- .../Service/CaseSaleOrderContribution.php | 239 ++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 CRM/Civicase/Service/CaseSaleOrderContribution.php diff --git a/CRM/Civicase/Service/CaseSaleOrderContribution.php b/CRM/Civicase/Service/CaseSaleOrderContribution.php new file mode 100644 index 000000000..cc6c94c39 --- /dev/null +++ b/CRM/Civicase/Service/CaseSaleOrderContribution.php @@ -0,0 +1,239 @@ +salesOrder = $this->getSalesOrder($salesOrderId); + $this->paymentStatusOptionValues = $this->getOptionValues('case_sales_order_payment_status'); + $this->invoicingStatusOptionValues = $this->getOptionValues('case_sales_order_invoicing_status'); + $this->contributions = $this->getContributions(); + $this->totalInvoicedAmount = $this->getTotalInvoicedAmount(); + $this->totalPaymentsAmount = $this->getTotalPaymentsAmount(); + } + + /** + * Calculates total invoiced amount. + * + * @return float + * Total invoiced amount. + */ + public function calculateTotalInvoicedAmount(): float { + + return $this->getTotalInvoicedAmount(); + } + + /** + * Calculates total paid amount. + * + * @return float + * Total paid amounts. + */ + public function calculateTotalPaidAmount(): float { + return $this->getTotalPaymentsAmount(); + } + + /** + * Calculates invoicing status. + * + * @return string + * Invoicing status option value's value + */ + public function calculateInvoicingStatus() { + if (empty($this->salesOrder) || empty($this->contributions)) { + return $this->getStatus('no_invoices', $this->invoicingStatusOptionValues); + } + + $quotationTotalAmount = $this->salesOrder['total_after_tax']; + if ($this->totalInvoicedAmount < $quotationTotalAmount) { + return $this->getStatus('partially_invoiced', $this->invoicingStatusOptionValues); + } + + return $this->getStatus('fully_invoiced', $this->invoicingStatusOptionValues); + } + + /** + * Calculates payment status. + * + * @return string + * Payment status option value's value + */ + public function calculatePaymentStatus() { + if (empty($this->salesOrder) || empty($this->contributions)) { + + return $this->getStatus('no_payments', $this->paymentStatusOptionValues); + } + + if ($this->totalPaymentsAmount < $this->totalInvoicedAmount) { + return $this->getStatus('partially_paid', $this->paymentStatusOptionValues); + } + + return $this->getStatus('fully_paid', $this->paymentStatusOptionValues); + } + + /** + * Gets list of contributions from the sale order. + * + * @return array + * List of contributions. + * + * @throws API_Exception + * @throws \Civi\API\Exception\UnauthorizedException + */ + private function getContributions() { + return Contribution::get(FALSE) + ->addWhere('Opportunity_Details.Quotation', '=', $this->salesOrder['id']) + ->execute() + ->getArrayCopy(); + } + + /** + * Gets Sales Order by SaleOrder ID. + * + * @param string $saleOrderId + * Sales Order ID. + * + * @return array|null + * Sales Order array or null + * + * @throws API_Exception + * @throws \Civi\API\Exception\UnauthorizedException + */ + private function getSalesOrder($saleOrderId) { + return CaseSalesOrder::get() + ->addWhere('id', '=', $saleOrderId) + ->execute() + ->first(); + } + + /** + * Gets option values by option group name. + * + * @param string $name + * Option group name. + * + * @return array + * Option values. + * + * @throws API_Exception + * @throws \Civi\API\Exception\UnauthorizedException + */ + private function getOptionValues($name) { + return OptionValue::get() + ->addSelect('*') + ->addWhere('option_group_id:name', '=', $name) + ->execute() + ->getArrayCopy(); + } + + /** + * Gets total invoiced amount. + * + * @return float + * Total invoiced amount. + */ + private function getTotalInvoicedAmount(): float { + $totalInvoicedAmount = 0.0; + foreach ($this->contributions as $contribution) { + $totalInvoicedAmount += $contribution['total_amount']; + } + + return $totalInvoicedAmount; + } + + /** + * Gets total payments amount. + * + * @return float + * Total payment amount. + */ + private function getTotalPaymentsAmount(): float { + $totalPaymentsAmount = 0.0; + foreach ($this->contributions as $contribution) { + $payments = civicrm_api3('Payment', 'get', [ + 'sequential' => 1, + 'contribution_id' => $contribution['id'], + ])['values']; + + foreach ($payments as $payment) { + $totalPaymentsAmount += $payment['total_amount']; + } + } + + return $totalPaymentsAmount; + } + + /** + * Gets status (option values' value) from the given options. + * + * @param string $needle + * Search value. + * @param array $options + * Option value. + * + * @return string + * Option values' value. + */ + private function getStatus($needle, $options) { + $key = array_search($needle, array_column($options, 'name')); + + return $options[$key]['value']; + } + +} From 4054ccb99245dd623a746ad1f9eee3a669155aa0 Mon Sep 17 00:00:00 2001 From: Erawat Chamanont Date: Wed, 20 Sep 2023 10:47:27 +0100 Subject: [PATCH 06/14] BTHAB-185: Add compu action for invoiced and paid amounts --- .../ComputeTotalAmountInvoicedAction.php | 39 +++++++++++++++++++ .../ComputeTotalAmountPaidAction.php | 38 ++++++++++++++++++ Civi/Api4/CaseSalesOrder.php | 36 +++++++++++++++-- 3 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 Civi/Api4/Action/CaseSalesOrder/ComputeTotalAmountInvoicedAction.php create mode 100644 Civi/Api4/Action/CaseSalesOrder/ComputeTotalAmountPaidAction.php diff --git a/Civi/Api4/Action/CaseSalesOrder/ComputeTotalAmountInvoicedAction.php b/Civi/Api4/Action/CaseSalesOrder/ComputeTotalAmountInvoicedAction.php new file mode 100644 index 000000000..a7be9c48c --- /dev/null +++ b/Civi/Api4/Action/CaseSalesOrder/ComputeTotalAmountInvoicedAction.php @@ -0,0 +1,39 @@ +salesOrderID) { + return; + } + $service = new \CRM_Civicase_Service_CaseSaleOrderContribution($this->salesOrderID); + $result['amount'] = $service->calculateTotalInvoicedAmount(); + } + + /** + * Sets Sales Order ID. + */ + public function setSalesOrderId(string $salesOrderId) { + $this->salesOrderID = $salesOrderId; + } + +} diff --git a/Civi/Api4/Action/CaseSalesOrder/ComputeTotalAmountPaidAction.php b/Civi/Api4/Action/CaseSalesOrder/ComputeTotalAmountPaidAction.php new file mode 100644 index 000000000..9fcf1b8a2 --- /dev/null +++ b/Civi/Api4/Action/CaseSalesOrder/ComputeTotalAmountPaidAction.php @@ -0,0 +1,38 @@ +salesOrderID) { + return; + } + $service = new \CRM_Civicase_Service_CaseSaleOrderContribution($this->salesOrderID); + $result['amount'] = $service->calculateTotalPaidAmount(); + } + + /** + * Sets Sales Order ID. + */ + public function setSalesOrderId(string $salesOrderId) { + $this->salesOrderID = $salesOrderId; + } + +} diff --git a/Civi/Api4/CaseSalesOrder.php b/Civi/Api4/CaseSalesOrder.php index 2ba0cc624..f74898f80 100644 --- a/Civi/Api4/CaseSalesOrder.php +++ b/Civi/Api4/CaseSalesOrder.php @@ -2,9 +2,11 @@ namespace Civi\Api4; -use Civi\Api4\Generic\DAOEntity; use Civi\Api4\Action\CaseSalesOrder\ComputeTotalAction; +use Civi\Api4\Action\CaseSalesOrder\ComputeTotalAmountInvoicedAction; +use Civi\Api4\Action\CaseSalesOrder\ComputeTotalAmountPaidAction; use Civi\Api4\Action\CaseSalesOrder\SalesOrderSaveAction; +use Civi\Api4\Generic\DAOEntity; /** * CaseSalesOrder entity. @@ -35,12 +37,40 @@ public static function save($checkPermissions = TRUE) { * @param bool $checkPermissions * Should permission be checked for the user. * - * @return Civi\Api4\Action\CaseSalesOrder\SalesOrderSaveAction - * returns save order action + * @return Civi\Api4\Action\CaseSalesOrder\ComputeTotalAction + * returns computed total action */ public static function computeTotal($checkPermissions = FALSE) { return (new ComputeTotalAction(__CLASS__, __FUNCTION__)) ->setCheckPermissions($checkPermissions); } + /** + * Computes the sum of amount paid. + * + * @param bool $checkPermissions + * Should permission be checked for the user. + * + * @return ComputeAmountPaidAction + * returns computed amount paid action + */ + public static function computeTotalAmountPaid(bool $checkPermissions = FALSE) { + return (new ComputeTotalAmountPaidAction(__CLASS__, __FUNCTION__)) + ->setCheckPermissions($checkPermissions); + } + + /** + * Computes the sum of amount invoiced. + * + * @param bool $checkPermissions + * Should permission be checked for the user. + * + * @return ComputeAmountInvoicedAction + * returns computed amount invoiced action + */ + public static function computeTotalAmountInvoiced(bool $checkPermissions = FALSE) { + return (new ComputeTotalAmountInvoicedAction(__CLASS__, __FUNCTION__)) + ->setCheckPermissions($checkPermissions); + } + } From a05159bf185626cd859ce5fe7a8e1b2990ff9643 Mon Sep 17 00:00:00 2001 From: Erawat Chamanont Date: Wed, 20 Sep 2023 10:55:16 +0100 Subject: [PATCH 07/14] BTHAB-185: Calculate payments/invoicing statuses when creating a contribution --- .../Post/CreateSalesOrderContribution.php | 45 ++++++++++++++++--- .../Service/CaseSaleOrderContribution.php | 2 +- civicase.php | 1 + 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/CRM/Civicase/Hook/Post/CreateSalesOrderContribution.php b/CRM/Civicase/Hook/Post/CreateSalesOrderContribution.php index 5b40d9419..c4b8a08b9 100644 --- a/CRM/Civicase/Hook/Post/CreateSalesOrderContribution.php +++ b/CRM/Civicase/Hook/Post/CreateSalesOrderContribution.php @@ -1,6 +1,7 @@ shouldRun($op, $objectName)) { + return; + } + $salesOrderId = CRM_Utils_Request::retrieve('sales_order', 'Integer'); - $salesOrderStatusId = CRM_Utils_Request::retrieve('sales_order_status_id', 'Integer'); + if (empty($salesOrderId)) { + $salesOrderId = $this->getQuotationID($objectId)['Opportunity_Details.Quotation']; + } - if (!$this->shouldRun($op, $objectName, $salesOrderId)) { + if (empty($salesOrderId)) { return; } + $salesOrderStatusId = CRM_Utils_Request::retrieve('sales_order_status_id', 'Integer'); + if (empty($salesOrderStatusId)) { + $salesOrderStatusId = CaseSalesOrder::get() + ->addSelect('status_id') + ->addWhere('id', '=', $salesOrderId) + ->execute() + ->first()['status_id']; + } + $transaction = CRM_Core_Transaction::create(); try { + $caseSaleOrderContributionService = new CRM_Civicase_Service_CaseSaleOrderContribution($salesOrderId); + $paymentStatusID = $caseSaleOrderContributionService->calculatePaymentStatus(); + $invoicingStatusID = $caseSaleOrderContributionService->calculateInvoicingStatus(); + CaseSalesOrder::update() ->addWhere('id', '=', $salesOrderId) ->addValue('status_id', $salesOrderStatusId) + ->addValue('invoicing_status_id', $invoicingStatusID) + ->addValue('payment_status_id', $paymentStatusID) ->execute(); } catch (\Throwable $th) { @@ -47,14 +69,23 @@ public function run($op, $objectName, $objectId, &$objectRef) { * The operation being performed. * @param string $objectName * Object name. - * @param string $salesOrderId - * The sales order that triggered the contribution (if any). * * @return bool * returns a boolean to determine if hook will run or not. */ - private function shouldRun($op, $objectName, $salesOrderId) { - return strtolower($objectName) == 'contribution' && !empty($salesOrderId) && $op == 'create'; + private function shouldRun($op, $objectName) { + return strtolower($objectName) == 'contribution' && $op == 'create'; + } + + /** + * Gets quotation ID by contribution ID. + */ + private function getQuotationId($id) { + return Contribution::get() + ->addSelect('Opportunity_Details.Quotation') + ->addWhere('id', '=', $id) + ->execute() + ->first(); } } diff --git a/CRM/Civicase/Service/CaseSaleOrderContribution.php b/CRM/Civicase/Service/CaseSaleOrderContribution.php index cc6c94c39..b00da3ee2 100644 --- a/CRM/Civicase/Service/CaseSaleOrderContribution.php +++ b/CRM/Civicase/Service/CaseSaleOrderContribution.php @@ -115,7 +115,7 @@ public function calculateInvoicingStatus() { * Payment status option value's value */ public function calculatePaymentStatus() { - if (empty($this->salesOrder) || empty($this->contributions)) { + if (empty($this->salesOrder) || empty($this->contributions) || !($this->totalPaymentsAmount > 0)) { return $this->getStatus('no_payments', $this->paymentStatusOptionValues); } diff --git a/civicase.php b/civicase.php index b0931f5e1..51d10a8f9 100644 --- a/civicase.php +++ b/civicase.php @@ -245,6 +245,7 @@ function civicase_civicrm_validateForm($formName, &$fields, &$files, &$form, &$e */ function civicase_civicrm_post($op, $objectName, $objectId, &$objectRef) { $hooks = [ + new CRM_Civicase_Hook_Post_CaseSalesOrderPayment(), new CRM_Civicase_Hook_Post_CreateSalesOrderContribution(), new CRM_Civicase_Hook_Post_PopulateCaseCategoryForCaseType(), new CRM_Civicase_Hook_Post_CaseCategoryCustomGroupSaver(), From 425ac434e86402d129ba3126943ba7282b022925 Mon Sep 17 00:00:00 2001 From: Erawat Chamanont Date: Wed, 20 Sep 2023 10:55:58 +0100 Subject: [PATCH 08/14] BTHAB-185: Calculate payments/invoicing statuses when creating/updating the quotation --- Civi/Api4/Action/CaseSalesOrder/SalesOrderSaveAction.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Civi/Api4/Action/CaseSalesOrder/SalesOrderSaveAction.php b/Civi/Api4/Action/CaseSalesOrder/SalesOrderSaveAction.php index 6195297fe..ce7401a9f 100644 --- a/Civi/Api4/Action/CaseSalesOrder/SalesOrderSaveAction.php +++ b/Civi/Api4/Action/CaseSalesOrder/SalesOrderSaveAction.php @@ -47,6 +47,12 @@ protected function writeRecord($items) { $total = CaseSalesOrderBAO::computeTotal($lineItems); $salesOrder['total_before_tax'] = $total['totalBeforeTax']; $salesOrder['total_after_tax'] = $total['totalAfterTax']; + + $saleOrderId = $salesOrder['id'] ?? NULL; + $caseSaleOrderContributionService = new \CRM_Civicase_Service_CaseSaleOrderContribution($saleOrderId); + $salesOrder['payment_status_id'] = $caseSaleOrderContributionService->calculateInvoicingStatus(); + $salesOrder['invoicing_status_id'] = $caseSaleOrderContributionService->calculatePaymentStatus(); + $salesOrders = $this->writeObjects([$salesOrder]); $result = array_pop($salesOrders); From b6223c675df07709f6e0f6832279774fe58b1d06 Mon Sep 17 00:00:00 2001 From: Erawat Chamanont Date: Wed, 20 Sep 2023 10:57:47 +0100 Subject: [PATCH 09/14] BTHAB-185: Display invoicing/payment amounts and statuses on the quotation view --- .../directives/quotations-view.directive.html | 28 ++++++++++++++++++- .../directives/quotations-view.directive.js | 13 +++++++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/ang/civicase-features/quotations/directives/quotations-view.directive.html b/ang/civicase-features/quotations/directives/quotations-view.directive.html index 3140e3ed6..61ffba7a9 100644 --- a/ang/civicase-features/quotations/directives/quotations-view.directive.html +++ b/ang/civicase-features/quotations/directives/quotations-view.directive.html @@ -57,6 +57,14 @@

{{ ts('View Quotation') }}

Currency {{salesOrder.currency}} + + Invoicing + {{salesOrder['invoicing_status_id:label']}} + + + Payments + {{salesOrder['payment_status_id:label']}} + @@ -113,6 +121,23 @@

Amount Summary

+
+
+
+
+
+ + + + + + + + + +
Total Amount Invoiced{{ currencySymbol }} {{ salesOrder.totalAmountInvoiced.amount }}
Total Amount Paid{{ currencySymbol }} {{salesOrder.totalAmountPaid.amount}}
+
+
@@ -133,7 +158,8 @@

Notes