From ee88250a149ad7f6c89cee8dd7c8124a0ad444f0 Mon Sep 17 00:00:00 2001 From: Eileen McNaughton Date: Mon, 21 Jun 2021 12:36:24 +1200 Subject: [PATCH] Make Order api easier to use for default price set This changes the order api so that it is not necessary to figure out the details of the default price set when using it to create memberships. --- CRM/Contribute/BAO/Contribution.php | 3 - CRM/Financial/BAO/Order.php | 108 ++++++++++++++++++++++ api/v3/Order.php | 38 +++----- tests/phpunit/api/v3/ContributionTest.php | 31 ++++--- 4 files changed, 142 insertions(+), 38 deletions(-) diff --git a/CRM/Contribute/BAO/Contribution.php b/CRM/Contribute/BAO/Contribution.php index 131a9fa40046..0e7136b19fa4 100644 --- a/CRM/Contribute/BAO/Contribution.php +++ b/CRM/Contribute/BAO/Contribution.php @@ -4401,9 +4401,6 @@ public static function checkLineItems(&$params) { foreach ($params['line_items'] as &$lineItems) { foreach ($lineItems['line_item'] as &$item) { - if (empty($item['financial_type_id'])) { - $item['financial_type_id'] = $params['financial_type_id']; - } $lineItemAmount += $item['line_total'] + ($item['tax_amount'] ?? 0.00); } } diff --git a/CRM/Financial/BAO/Order.php b/CRM/Financial/BAO/Order.php index 9f5df990e5aa..b419ec6195d2 100644 --- a/CRM/Financial/BAO/Order.php +++ b/CRM/Financial/BAO/Order.php @@ -58,6 +58,29 @@ class CRM_Financial_BAO_Order { */ protected $overrideFinancialTypeID; + /** + * Financial type id to use for any lines where is is not provided. + * + * @var int + */ + protected $defaultFinancialTypeID; + + /** + * @return int + */ + public function getDefaultFinancialTypeID(): int { + return $this->defaultFinancialTypeID; + } + + /** + * Set the default financial type id to be used when the line has none. + * + * @param int|null $defaultFinancialTypeID + */ + public function setDefaultFinancialTypeID(?int $defaultFinancialTypeID): void { + $this->defaultFinancialTypeID = $defaultFinancialTypeID; + } + /** * Override for the total amount of the order. * @@ -634,4 +657,89 @@ protected function setPriceSetIDFromSelectedField($fieldID): void { } } + /** + * Set the line item. + * + * This function augments the line item where possible. The calling code + * should not attempt to set taxes. This function allows minimal values + * to be passed for the default price sets - ie if only membership_type_id is + * specified the price_field_id and price_value_id will be determined. + * + * @param array $lineItem + * @param int|string $index + * + * @throws \API_Exception + * @internal tested core code usage only. + * @internal use in tested core code only. + * + */ + public function setLineItem(array $lineItem, $index): void { + if (!empty($lineItem['price_field_id']) && !isset($this->priceSetID)) { + $this->setPriceSetIDFromSelectedField($lineItem['price_field_id']); + } + if (!isset($lineItem['financial_type_id'])) { + $lineItem['financial_type_id'] = $this->getDefaultFinancialTypeID(); + } + if (!is_numeric($lineItem['financial_type_id'])) { + $lineItem['financial_type_id'] = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'financial_type_id', $lineItem['financial_type_id']); + } + $lineItem['tax_amount'] = ($this->getTaxRate($lineItem['financial_type_id']) / 100) * $lineItem['line_total']; + if (!empty($lineItem['membership_type_id'])) { + $lineItem['entity_table'] = 'civicrm_membership'; + if (empty($lineItem['price_field']) && empty($lineItem['price_field_value'])) { + // If only the membership type is passed in we use the default price field. + if (!isset($this->priceSetID)) { + $this->setPriceSetToDefault('membership'); + } + $fields = $this->getPriceFieldsMetadata(); + $field = reset($fields); + foreach ($field['options'] as $option) { + if ((int) $option['membership_type_id'] === (int) $lineItem['membership_type_id']) { + $lineItem['price_field_id'] = $field['id']; + $lineItem['price_field_value'] = $option['id']; + $lineItem['qty'] = 1; + $lineItem['unit_price'] = $lineItem['line_total'] ?? $option['amount']; + $lineItem['label'] = $option['label']; + $lineItem['field_title'] = $option['label']; + $lineItem['financial_type_id'] = $lineItem['financial_type_id'] ?: ($this->getDefaultFinancialTypeID() ?? $option['financial_type_id']); + } + } + } + } + $this->lineItems[$index] = $lineItem; + } + + /** + * Set a value on a line item. + * + * @internal only use in core tested code. + * + * @param string $name + * @param mixed $value + * @param string|int $index + */ + public function setLineItemValue(string $name, $value, $index): void { + $this->lineItems[$index][$name] = $value; + } + + /** + * @param int|string $index + * + * @return string + */ + public function getLineItemEntity($index):string { + return str_replace('civicrm_', '', $this->lineItems[$index]['entity_table']); + } + + /** + * Get the ordered line item. + * + * @param string|int $index + * + * @return array + */ + public function getLineItem($index): array { + return $this->lineItems[$index]; + } + } diff --git a/api/v3/Order.php b/api/v3/Order.php index 528a25ef38c5..54d9f31c6f62 100644 --- a/api/v3/Order.php +++ b/api/v3/Order.php @@ -76,22 +76,20 @@ function civicrm_api3_order_create(array $params): array { $entity = NULL; $entityIds = []; $params['contribution_status_id'] = 'Pending'; - $priceSetID = NULL; + $order = new CRM_Financial_BAO_Order(); + $order->setDefaultFinancialTypeID($params['financial_type_id'] ?? NULL); if (!empty($params['line_items']) && is_array($params['line_items'])) { CRM_Contribute_BAO_Contribution::checkLineItems($params); - foreach ($params['line_items'] as $lineItems) { - $entityParams = $lineItems['params'] ?? []; - if (!empty($entityParams) && !empty($lineItems['line_item'])) { - $item = reset($lineItems['line_item']); - if (!empty($item['membership_type_id'])) { - $entity = 'membership'; - } - else { - $entity = str_replace('civicrm_', '', $item['entity_table']); - } + foreach ($params['line_items'] as $index => $lineItems) { + foreach ($lineItems['line_item'] as $innerIndex => $lineItem) { + $lineIndex = $index . '+' . $innerIndex; + $order->setLineItem(reset($lineItems['line_item']), $lineIndex); } + $entityParams = $lineItems['params'] ?? []; + $entity = $order->getLineItemEntity($lineIndex); + if ($entityParams) { $supportedEntity = TRUE; switch ($entity) { @@ -118,23 +116,17 @@ function civicrm_api3_order_create(array $params): array { $entityResult = civicrm_api3($entity, 'create', $entityParams); $params['contribution_mode'] = $entity; $entityIds[] = $params[$entity . '_id'] = $entityResult['id']; - foreach ($lineItems['line_item'] as &$items) { - $items['entity_id'] = $entityResult['id']; + foreach ($lineItems['line_item'] as $innerIndex => $lineItem) { + $lineIndex = $index . '+' . $innerIndex; + $order->setLineItemValue('entity_id', $entityResult['id'], $lineIndex); } } } - - if (empty($priceSetID)) { - $item = reset($lineItems['line_item']); - $priceSetID = (int) civicrm_api3('PriceField', 'getvalue', [ - 'return' => 'price_set_id', - 'id' => $item['price_field_id'], - ]); - $params['line_item'][$priceSetID] = []; - } - $params['line_item'][$priceSetID] = array_merge($params['line_item'][$priceSetID], $lineItems['line_item']); } + $priceSetID = $order->getPriceSetID(); + $params['line_item'][$priceSetID] = $order->getLineItems(); } + $contributionParams = $params; // If this is nested we need to set sequential to 0 as sequential handling is done // in create_success & id will be miscalculated... diff --git a/tests/phpunit/api/v3/ContributionTest.php b/tests/phpunit/api/v3/ContributionTest.php index e57891b89ea1..5ad26b09bedf 100644 --- a/tests/phpunit/api/v3/ContributionTest.php +++ b/tests/phpunit/api/v3/ContributionTest.php @@ -4345,24 +4345,31 @@ protected function setUpAutoRenewMembership($generalParams = [], $recurParams = 'payment_processor_id' => $this->paymentProcessorID, ], $generalParams, $recurParams)); - $this->callAPISuccess('membership', 'create', [ - 'contact_id' => $newContact['id'], - 'contribution_recur_id' => $contributionRecur['id'], - 'financial_type_id' => 'Member Dues', - 'membership_type_id' => $membershipType['id'], - 'num_terms' => 1, - 'skipLineItem' => TRUE, - ]); - - CRM_Price_BAO_LineItem::getLineItemArray($this->_params, NULL, 'membership', $membershipType['id']); - $originalContribution = $this->callAPISuccess('contribution', 'create', array_merge( + $originalContribution = $this->callAPISuccess('Order', 'create', array_merge( $this->_params, [ 'contact_id' => $newContact['id'], 'contribution_recur_id' => $contributionRecur['id'], 'financial_type_id' => 'Member Dues', - 'contribution_status_id' => 1, + 'api.Payment.create' => ['total_amount' => 100, 'payment_instrument_id' => 'Credit card'], 'invoice_id' => 2345, + 'line_items' => [ + [ + 'line_item' => [ + [ + 'membership_type_id' => $membershipType['id'], + 'financial_type_id' => 'Member Dues', + 'line_total' => $generalParams['total_amount'] ?? 100, + ], + ], + 'params' => [ + 'contact_id' => $newContact['id'], + 'contribution_recur_id' => $contributionRecur['id'], + 'membership_type_id' => $membershipType['id'], + 'num_terms' => 1, + ], + ], + ], ], $generalParams) ); $lineItem = $this->callAPISuccess('LineItem', 'getsingle', []);