Skip to content

Commit

Permalink
BTHAB-186: Update case opportunity details
Browse files Browse the repository at this point in the history
Update case opportunity statuses, and total amounts when:
* Create or update sales order contribution
* Create or update sales order
* Create a payment
  • Loading branch information
erawat committed Sep 22, 2023
1 parent 8c46cbb commit e94cc28
Show file tree
Hide file tree
Showing 6 changed files with 388 additions and 78 deletions.
76 changes: 62 additions & 14 deletions CRM/Civicase/Hook/Post/CaseSalesOrderPayment.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,28 +21,37 @@ class CRM_Civicase_Hook_Post_CaseSalesOrderPayment {
* Object reference.
*/
public function run($op, $objectName, $objectId, &$objectRef) {
if (!$this->shouldRun($op, $objectName)) {
if (!$this->shouldRun($op, $objectName, $objectRef)) {
return;
}

$entityFinancialTrxn = civicrm_api3('EntityFinancialTrxn', 'get', [
'sequential' => 1,
'entity_table' => 'civicrm_contribution',
'financial_trxn_id' => $objectRef->financial_trxn_id,
]);

if (empty($entityFinancialTrxn['values'][0])) {
$financialTrnxId = $objectRef->financial_trxn_id;
if (empty($financialTrnxId)) {
return;
}

$contributionId = $entityFinancialTrxn['values'][0]['entity_id'];
$contributionId = $this->getContributionId($financialTrnxId);
if (empty($contributionId)) {
return;
}

$salesOrderID = Contribution::get()
->addSelect('Opportunity_Details.Quotation')
$contribution = Contribution::get()
->addSelect('Opportunity_Details.Case_Opportunity', 'Opportunity_Details.Quotation')
->addWhere('id', '=', $contributionId)
->execute()
->first()['Opportunity_Details.Quotation'];
->first();

$this->updateQuotationFinancialStatuses($contribution['Opportunity_Details.Quotation']);
$this->updateCaseOpportunityFinancialDetails($contribution['Opportunity_Details.Case_Opportunity']);
}

/**
* Updates CaseSalesOrder financial statuses.
*
* @param int $salesOrderID
* CaseSalesOrder ID.
*/
private function updateQuotationFinancialStatuses(int $salesOrderID): void {
if (empty($salesOrderID)) {
return;
}
Expand All @@ -68,19 +77,58 @@ public function run($op, $objectName, $objectId, &$objectRef) {
}
}

/**
* Updates Case financial statuses.
*
* @param int? $caseId
* CaseSalesOrder ID.
*/
private function updateCaseOpportunityFinancialDetails(?int $caseId) {
if (empty($caseId)) {
return;
}

try {
$calculator = new CRM_Civicase_Service_CaseSalesOrderOpportunityCalculator($caseId);
$calculator->updateOpportunityFinancialDetails();
}
catch (\Throwable $th) {
CRM_Core_Error::statusBounce(ts('Error updating opportunity details'));
}
}

/**
* Gets Contribution ID by Financial Transaction ID.
*/
private function getContributionId($financialTrxnId) {
$entityFinancialTrxn = civicrm_api3('EntityFinancialTrxn', 'get', [
'sequential' => 1,
'entity_table' => 'civicrm_contribution',
'financial_trxn_id' => $financialTrxnId,
]);

if (empty($entityFinancialTrxn['values'][0])) {
return NULL;
}

return $entityFinancialTrxn['values'][0]['entity_id'];
}

/**
* Determines if the hook should run or not.
*
* @param string $op
* The operation being performed.
* @param string $objectName
* Object name.
* @param string $objectRef
* The hook object reference.
*
* @return bool
* returns a boolean to determine if hook will run or not.
*/
private function shouldRun($op, $objectName) {
return $objectName == 'EntityFinancialTrxn' && $op == 'create';
private function shouldRun($op, $objectName, $objectRef) {
return $objectName == 'EntityFinancialTrxn' && $op == 'create' && property_exists($objectRef, 'financial_trxn_id');
}

}
19 changes: 14 additions & 5 deletions CRM/Civicase/Hook/Post/CreateSalesOrderContribution.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,15 @@ public function run($op, $objectName, $objectId, &$objectRef) {
return;
}

$salesOrder = CaseSalesOrder::get()
->addSelect('status_id', 'case_id')
->addWhere('id', '=', $salesOrderId)
->execute()
->first();

$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'];
$salesOrder = $salesOrder['status_id'];
}

$transaction = CRM_Core_Transaction::create();
Expand All @@ -55,6 +57,13 @@ public function run($op, $objectName, $objectId, &$objectRef) {
->addValue('invoicing_status_id', $invoicingStatusID)
->addValue('payment_status_id', $paymentStatusID)
->execute();

$caseId = $salesOrder['case_id'];
if (empty($caseId)) {
return;
}
$calculator = new CRM_Civicase_Service_CaseSalesOrderOpportunityCalculator($caseId);
$calculator->updateOpportunityFinancialDetails();
}
catch (\Throwable $th) {
$transaction->rollback();
Expand Down
94 changes: 94 additions & 0 deletions CRM/Civicase/Service/AbstractBaseSalesOrderCalculator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

use Civi\Api4\OptionValue;

/**
* An Abstract class for SalesOrder and Opportunity statuses and amounts.
*/
abstract class CRM_Civicase_Service_AbstractBaseSalesOrderCalculator {

const
INVOICING_STATUS_NO_INVOICES = 'no_invoices',
INVOICING_STATUS_PARTIALLY_INVOICED = 'partially_invoiced',
INVOICING_STATUS_FULLY_INVOICED = 'fully_invoiced',
PAYMENT_STATUS_NO_PAYMENTS = 'no_payments',
PAYMENT_STATUS_PARTIALLY_PAID = 'partially_paid',
PAYMENT_STATUS_OVERPAID = 'overpaid',
PAYMENT_STATUS_FULLY_PAID = 'fully_paid';

/**
* Case Sales Order payment status option values.
*
* @var array
*/
protected array $paymentStatusOptionValues;
/**
* Case Sales Order Invoicing status option values.
*
* @var array
*/
protected array $invoicingStatusOptionValues;

/**
* AbstractBaseSalesOrderCalculator constructor.
*/
public function __construct() {
$this->paymentStatusOptionValues = $this->getOptionValues('case_sales_order_payment_status');
$this->invoicingStatusOptionValues = $this->getOptionValues('case_sales_order_invoicing_status');
}

/**
* 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
*/
protected function getOptionValues($name) {
return OptionValue::get(FALSE)
->addSelect('*')
->addWhere('option_group_id:name', '=', $name)
->execute()
->getArrayCopy();
}

/**
* Gets status (option values' value) from the given options.
*
* @param string $needle
* Search value.
* @param array $options
* Option value.
*
* @return string
* Option values' value.
*/
protected function getValueFromOptionValues($needle, $options) {
$key = array_search($needle, array_column($options, 'name'));

return $options[$key]['value'];
}

/**
* Gets status (option values' label) from the given options.
*
* @param string $needle
* Search value.
* @param array $options
* Option value.
*
* @return string
* Option values' value.
*/
protected function getLabelFromOptionValues($needle, $options) {
$key = array_search($needle, array_column($options, 'name'));

return $options[$key]['label'];
}

}
80 changes: 21 additions & 59 deletions CRM/Civicase/Service/CaseSalesOrderContributionCalculator.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,22 @@

use Civi\Api4\CaseSalesOrder;
use Civi\Api4\Contribution;
use Civi\Api4\OptionValue;

/**
* Case Sale Order Contribution Service.
*
* This class provides calculations for payment and invoices that
* attached to the sale order.
*/
class CRM_Civicase_Service_CaseSalesOrderContributionCalculator {
class CRM_Civicase_Service_CaseSalesOrderContributionCalculator extends CRM_Civicase_Service_AbstractBaseSalesOrderCalculator {

/**
* Case Sales Order object.
*
* @var array|null
*/
private ?array $salesOrder;
/**
* Case Sales Order payment status option values.
*
* @var array
*/
private array $paymentStatusOptionValues;
/**
* Case Sales Order Invoicing status option values.
*
* @var array
*/
private array $invoicingStatusOptionValues;

/**
* List of contributions that links to the sales order.
*
Expand Down Expand Up @@ -60,12 +48,12 @@ class CRM_Civicase_Service_CaseSalesOrderContributionCalculator {
* @throws \Civi\API\Exception\UnauthorizedException
*/
public function __construct($salesOrderId) {
parent::__construct();
$this->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();

}

/**
Expand All @@ -89,6 +77,13 @@ public function calculateTotalPaidAmount(): float {
return $this->getTotalPaymentsAmount();
}

/**
* Gets SalesOrder Total amount after tax.
*/
public function getQuotedAmount(): float {
return $this->salesOrder['total_after_tax'];
}

/**
* Calculates invoicing status.
*
Expand All @@ -97,15 +92,15 @@ public function calculateTotalPaidAmount(): float {
*/
public function calculateInvoicingStatus() {
if (empty($this->salesOrder) || empty($this->contributions)) {
return $this->getStatus('no_invoices', $this->invoicingStatusOptionValues);
return $this->getValueFromOptionValues(parent::INVOICING_STATUS_NO_INVOICES, $this->invoicingStatusOptionValues);
}

$quotationTotalAmount = $this->salesOrder['total_after_tax'];
if ($this->totalInvoicedAmount < $quotationTotalAmount) {
return $this->getStatus('partially_invoiced', $this->invoicingStatusOptionValues);
return $this->getValueFromOptionValues(parent::INVOICING_STATUS_PARTIALLY_INVOICED, $this->invoicingStatusOptionValues);
}

return $this->getStatus('fully_invoiced', $this->invoicingStatusOptionValues);
return $this->getValueFromOptionValues(parent::INVOICING_STATUS_FULLY_INVOICED, $this->invoicingStatusOptionValues);
}

/**
Expand All @@ -117,14 +112,18 @@ public function calculateInvoicingStatus() {
public function calculatePaymentStatus() {
if (empty($this->salesOrder) || empty($this->contributions) || !($this->totalPaymentsAmount > 0)) {

return $this->getStatus('no_payments', $this->paymentStatusOptionValues);
return $this->getValueFromOptionValues(parent::PAYMENT_STATUS_NO_PAYMENTS, $this->paymentStatusOptionValues);
}

if ($this->totalPaymentsAmount < $this->totalInvoicedAmount) {
return $this->getStatus('partially_paid', $this->paymentStatusOptionValues);
return $this->getValueFromOptionValues(parent::PAYMENT_STATUS_PARTIALLY_PAID, $this->paymentStatusOptionValues);
}

return $this->getStatus('fully_paid', $this->paymentStatusOptionValues);
if ($this->totalPaymentsAmount > $this->totalInvoicedAmount) {
return $this->getValueFromOptionValues(parent::PAYMENT_STATUS_OVERPAID, $this->paymentStatusOptionValues);
}

return $this->getValueFromOptionValues(parent::PAYMENT_STATUS_FULLY_PAID, $this->paymentStatusOptionValues);
}

/**
Expand Down Expand Up @@ -166,26 +165,6 @@ private function getSalesOrder($saleOrderId) {
->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.
*
Expand Down Expand Up @@ -223,21 +202,4 @@ private function getTotalPaymentsAmount(): float {
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'];
}

}
Loading

0 comments on commit e94cc28

Please sign in to comment.