3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
12 use Civi\Api4\PriceField
;
17 * @copyright CiviCRM LLC https://civicrm.org/licensing
20 * This class is intended to become the object to manage orders, including via Order.api.
22 * As of writing it is in the process of having appropriate functions built up.
24 class CRM_Financial_BAO_Order
{
31 protected $priceSetID;
34 * Selected price items in the format we see in forms.
37 * [price_3 => 4, price_10 => 7]
38 * is equivalent to 'option_value 4 for radio price field 3 and
39 * a quantity of 7 for text price field 10.
43 protected $priceSelection = [];
46 * Override for financial type id.
48 * Used when the financial type id is to be overridden for all line items
49 * (as can happen in backoffice forms)
53 protected $overrideFinancialTypeID;
56 * Override for the total amount of the order.
58 * When there is a single line item the order total may be overriden.
62 protected $overrideTotalAmount;
65 * Line items in the order.
69 protected $lineItems = [];
72 * Metadata for price fields.
76 protected $priceFieldMetadata = [];
79 * Get Set override for total amount of the order.
83 public function getOverrideTotalAmount() {
84 if (count($this->getPriceOptions()) !== 1) {
87 return $this->overrideTotalAmount ??
FALSE;
91 * Set override for total amount.
93 * @param float $overrideTotalAmount
95 public function setOverrideTotalAmount(float $overrideTotalAmount) {
96 $this->overrideTotalAmount
= $overrideTotalAmount;
100 * Get override for total amount.
104 public function getOverrideFinancialTypeID() {
105 if (count($this->getPriceOptions()) !== 1) {
108 return $this->overrideFinancialTypeID ??
FALSE;
112 * Set override for financial type ID.
114 * @param int $overrideFinancialTypeID
116 public function setOverrideFinancialTypeID(int $overrideFinancialTypeID) {
117 $this->overrideFinancialTypeID
= $overrideFinancialTypeID;
121 * Getter for price set id.
125 public function getPriceSetID(): int {
126 return $this->priceSetID
;
130 * Setter for price set id.
132 * @param int $priceSetID
134 public function setPriceSetID(int $priceSetID) {
135 $this->priceSetID
= $priceSetID;
139 * Getter for price selection.
143 public function getPriceSelection(): array {
144 return $this->priceSelection
;
148 * Setter for price selection.
150 * @param array $priceSelection
152 public function setPriceSelection(array $priceSelection) {
153 $this->priceSelection
= $priceSelection;
157 * Price options the simplified price fields selections.
159 * ie. the 'price_' is stripped off the key name and the field ID
160 * is cast to an integer.
164 public function getPriceOptions():array {
166 foreach ($this->getPriceSelection() as $fieldName => $value) {
167 $fieldID = substr($fieldName, 6);
168 $priceOptions[(int) $fieldID] = $value;
170 return $priceOptions;
174 * Get the metadata for the given field.
179 * @throws \CiviCRM_API3_Exception
181 public function getPriceFieldSpec(int $id) :array {
182 if (!isset($this->priceFieldMetadata
[$id])) {
183 $this->priceFieldMetadata
= CRM_Price_BAO_PriceSet
::getCachedPriceSetDetail($this->getPriceSetID())['fields'];
185 return $this->priceFieldMetadata
[$id];
189 * Set the price field selection from an array of params containing price fields.
191 * This function takes the sort of 'anything & everything' parameters that come in from the
192 * form layer and filters them before assigning them to the priceSelection property.
194 * @param array $input
196 public function setPriceSelectionFromUnfilteredInput(array $input) {
197 foreach ($input as $fieldName => $value) {
198 if (strpos($fieldName, 'price_') === 0) {
199 $fieldID = substr($fieldName, 6);
200 if (is_numeric($fieldID)) {
201 $this->priceSelection
[$fieldName] = $value;
212 * @throws \CiviCRM_API3_Exception
214 public function getLineItems():array {
215 if (empty($this->lineItems
)) {
216 $this->lineItems
= $this->calculateLineItems();
218 return $this->lineItems
;
223 * @throws \CiviCRM_API3_Exception
225 protected function calculateLineItems(): array {
227 $params = $this->getPriceSelection();
228 if ($this->getOverrideTotalAmount() !== FALSE) {
229 // We need to do this to keep getLine from doing weird stuff but the goal
230 // is to ditch getLine next round of refactoring
231 // and make the code more sane.
232 $params['total_amount'] = $this->getOverrideTotalAmount();
235 foreach ($this->getPriceOptions() as $fieldID => $valueID) {
236 if (!isset($this->priceSetID
)) {
237 $this->setPriceSetID(PriceField
::get()->addSelect('price_set_id')->addWhere('id', '=', $fieldID)->execute()->first()['price_set_id']);
239 $throwAwayArray = [];
240 // @todo - still using getLine for now but better to bring it to this class & do a better job.
241 $newLines = CRM_Price_BAO_PriceSet
::getLine($params, $throwAwayArray, $this->getPriceSetID(), $this->getPriceFieldSpec($fieldID), $fieldID)[1];
242 foreach ($newLines as $newLine) {
243 $lineItems[$newLine['price_field_value_id']] = $newLine;
247 foreach ($lineItems as &$lineItem) {
248 // Set any pre-calculation to zero as we will calculate.
249 $lineItem['tax_amount'] = 0;
250 if ($this->getOverrideFinancialTypeID() !== FALSE) {
251 $lineItem['financial_type_id'] = $this->getOverrideFinancialTypeID();
253 $taxRate = $this->getTaxRate((int) $lineItem['financial_type_id']);
254 if ($this->getOverrideTotalAmount() !== FALSE) {
256 // Total is tax inclusive.
257 $lineItem['tax_amount'] = ($taxRate / 100) * $this->getOverrideTotalAmount() / (1 +
($taxRate / 100));
258 $lineItem['line_total'] = $lineItem['unit_price'] = $this->getOverrideTotalAmount() - $lineItem['tax_amount'];
261 $lineItem['line_total'] = $lineItem['unit_price'] = $this->getOverrideTotalAmount();
265 $lineItem['tax_amount'] = ($taxRate / 100) * $lineItem['line_total'];
272 * Get the total amount for the order.
276 * @throws \CiviCRM_API3_Exception
278 public function getTotalTaxAmount() :float {
280 foreach ($this->getLineItems() as $lineItem) {
281 $amount +
= $lineItem['tax_amount'] ??
0.0;
287 * Get the total tax amount for the order.
291 * @throws \CiviCRM_API3_Exception
293 public function getTotalAmount() :float {
295 foreach ($this->getLineItems() as $lineItem) {
296 $amount +
= $lineItem['line_total'] ??
0.0;
302 * Get the tax rate for the given financial type.
304 * @param int $financialTypeID
308 public function getTaxRate(int $financialTypeID) {
309 $taxRates = CRM_Core_PseudoConstant
::getTaxRates();
310 if (!isset($taxRates[$financialTypeID])) {
313 return $taxRates[$financialTypeID];