Merge pull request #16998 from lcdservices/dev-core-1693
[civicrm-core.git] / CRM / Financial / BAO / Order.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
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 +--------------------------------------------------------------------+
10 */
11
12 /**
13 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 *
17 * Order class.
18 *
19 * This class is intended to become the object to manage orders, including via Order.api.
20 *
21 * As of writing it is in the process of having appropriate functions built up.
22 */
23 class CRM_Financial_BAO_Order {
24
25 /**
26 * Price set id.
27 *
28 * @var int
29 */
30 protected $priceSetID;
31
32 /**
33 * Selected price items in the format we see in forms.
34 *
35 * ie.
36 * [price_3 => 4, price_10 => 7]
37 * is equivalent to 'option_value 4 for radio price field 3 and
38 * a quantity of 7 for text price field 10.
39 *
40 * @var array
41 */
42 protected $priceSelection = [];
43
44 /**
45 * Override for financial type id.
46 *
47 * Used when the financial type id is to be overridden for all line items
48 * (as can happen in backoffice forms)
49 *
50 * @var int
51 */
52 protected $overrideFinancialTypeID;
53
54 /**
55 * Override for the total amount of the order.
56 *
57 * When there is a single line item the order total may be overriden.
58 *
59 * @var float
60 */
61 protected $overrideTotalAmount;
62
63 /**
64 * Line items in the order.
65 *
66 * @var array
67 */
68 protected $lineItems = [];
69
70 /**
71 * Metadata for price fields.
72 *
73 * @var array
74 */
75 protected $priceFieldMetadata = [];
76
77 /**
78 * Get Set override for total amount of the order.
79 *
80 * @return float|false
81 */
82 public function getOverrideTotalAmount() {
83 if (count($this->getPriceOptions()) !== 1) {
84 return FALSE;
85 }
86 return $this->overrideTotalAmount ?? FALSE;
87 }
88
89 /**
90 * Set override for total amount.
91 *
92 * @param float $overrideTotalAmount
93 */
94 public function setOverrideTotalAmount(float $overrideTotalAmount) {
95 $this->overrideTotalAmount = $overrideTotalAmount;
96 }
97
98 /**
99 * Get override for total amount.
100 *
101 * @return int| FALSE
102 */
103 public function getOverrideFinancialTypeID() {
104 if (count($this->getPriceOptions()) !== 1) {
105 return FALSE;
106 }
107 return $this->overrideFinancialTypeID ?? FALSE;
108 }
109
110 /**
111 * Set override for financial type ID.
112 *
113 * @param int $overrideFinancialTypeID
114 */
115 public function setOverrideFinancialTypeID(int $overrideFinancialTypeID) {
116 $this->overrideFinancialTypeID = $overrideFinancialTypeID;
117 }
118
119 /**
120 * Getter for price set id.
121 *
122 * @return int
123 */
124 public function getPriceSetID(): int {
125 return $this->priceSetID;
126 }
127
128 /**
129 * Setter for price set id.
130 *
131 * @param int $priceSetID
132 */
133 public function setPriceSetID(int $priceSetID) {
134 $this->priceSetID = $priceSetID;
135 }
136
137 /**
138 * Getter for price selection.
139 *
140 * @return array
141 */
142 public function getPriceSelection(): array {
143 return $this->priceSelection;
144 }
145
146 /**
147 * Setter for price selection.
148 *
149 * @param array $priceSelection
150 */
151 public function setPriceSelection(array $priceSelection) {
152 $this->priceSelection = $priceSelection;
153 }
154
155 /**
156 * Price options the simplified price fields selections.
157 *
158 * ie. the 'price_' is stripped off the key name and the field ID
159 * is cast to an integer.
160 *
161 * @return array
162 */
163 public function getPriceOptions():array {
164 $priceOptions = [];
165 foreach ($this->getPriceSelection() as $fieldName => $value) {
166 $fieldID = substr($fieldName, 6);
167 $priceOptions[(int) $fieldID] = $value;
168 }
169 return $priceOptions;
170 }
171
172 /**
173 * Get the metadata for the given field.
174 *
175 * @param int $id
176 *
177 * @return array
178 * @throws \CiviCRM_API3_Exception
179 */
180 public function getPriceFieldSpec(int $id) :array {
181 if (!isset($this->priceFieldMetadata[$id])) {
182 $this->priceFieldMetadata = CRM_Price_BAO_PriceSet::getCachedPriceSetDetail($this->getPriceSetID())['fields'];
183 }
184 return $this->priceFieldMetadata[$id];
185 }
186
187 /**
188 * Set the price field selection from an array of params containing price fields.
189 *
190 * This function takes the sort of 'anything & everything' parameters that come in from the
191 * form layer and filters them before assigning them to the priceSelection property.
192 *
193 * @param array $input
194 */
195 public function setPriceSelectionFromUnfilteredInput(array $input) {
196 foreach ($input as $fieldName => $value) {
197 if (strpos($fieldName, 'price_') === 0) {
198 $fieldID = substr($fieldName, 6);
199 if (is_numeric($fieldID)) {
200 $this->priceSelection[$fieldName] = $value;
201 }
202 }
203 }
204 }
205
206 /**
207 * Get line items.
208 *
209 * return array
210 *
211 * @throws \CiviCRM_API3_Exception
212 */
213 public function getLineItems():array {
214 if (empty($this->lineItems)) {
215 $this->lineItems = $this->calculateLineItems();
216 }
217 return $this->lineItems;
218 }
219
220 /**
221 * @return array
222 * @throws \CiviCRM_API3_Exception
223 */
224 protected function calculateLineItems(): array {
225 $lineItems = [];
226 $params = $this->getPriceSelection();
227 if ($this->getOverrideTotalAmount() !== FALSE) {
228 // We need to do this to keep getLine from doing weird stuff but the goal
229 // is to ditch getLine next round of refactoring
230 // and make the code more sane.
231 $params['total_amount'] = $this->getOverrideTotalAmount();
232 }
233
234 foreach ($this->getPriceOptions() as $fieldID => $valueID) {
235 $throwAwayArray = [];
236 // @todo - still using getLine for now but better to bring it to this class & do a better job.
237 $lineItems[$valueID] = CRM_Price_BAO_PriceSet::getLine($params, $throwAwayArray, $this->getPriceSetID(), $this->getPriceFieldSpec($fieldID), $fieldID, 0)[1][$valueID];
238 }
239
240 $taxRates = CRM_Core_PseudoConstant::getTaxRates();
241 foreach ($lineItems as &$lineItem) {
242 // Set any pre-calculation to zero as we will calculate.
243 $lineItem['tax_amount'] = 0;
244 if ($this->getOverrideFinancialTypeID() !== FALSE) {
245 $lineItem['financial_type_id'] = $this->getOverrideFinancialTypeID();
246 }
247 $taxRate = $taxRates[$lineItem['financial_type_id']] ?? 0;
248 if ($this->getOverrideTotalAmount() !== FALSE) {
249 if ($taxRate) {
250 // Total is tax inclusive.
251 $lineItem['tax_amount'] = ($taxRate / 100) * $this->getOverrideTotalAmount();
252 $lineItem['line_total'] = $lineItem['unit_price'] = $this->getOverrideTotalAmount() - $lineItem['tax_amount'];
253 }
254 else {
255 $lineItem['line_total'] = $lineItem['unit_price'] = $this->getOverrideTotalAmount();
256 }
257 }
258 elseif ($taxRate) {
259 $lineItem['tax_amount'] = ($taxRate / 100) * $lineItem['line_total'];
260 }
261 }
262 return $lineItems;
263 }
264
265 /**
266 * Get the total tax amount for the order.
267 *
268 * @return float
269 *
270 * @throws \CiviCRM_API3_Exception
271 */
272 public function getTotalTaxAmount() :float {
273 $amount = 0.0;
274 foreach ($this->getLineItems() as $lineItem) {
275 $amount += $lineItem['tax_amount'] ?? 0.0;
276 }
277 return $amount;
278 }
279
280 }