7aa78908 |
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 | |
58315149 |
12 | use Civi\Api4\PriceField; |
13 | |
7aa78908 |
14 | /** |
15 | * |
16 | * @package CRM |
17 | * @copyright CiviCRM LLC https://civicrm.org/licensing |
7aa78908 |
18 | * Order class. |
19 | * |
20 | * This class is intended to become the object to manage orders, including via Order.api. |
21 | * |
22 | * As of writing it is in the process of having appropriate functions built up. |
23 | */ |
24 | class CRM_Financial_BAO_Order { |
25 | |
26 | /** |
27 | * Price set id. |
28 | * |
29 | * @var int |
30 | */ |
31 | protected $priceSetID; |
32 | |
33 | /** |
34 | * Selected price items in the format we see in forms. |
35 | * |
36 | * ie. |
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. |
40 | * |
41 | * @var array |
42 | */ |
43 | protected $priceSelection = []; |
44 | |
45 | /** |
46 | * Override for financial type id. |
47 | * |
48 | * Used when the financial type id is to be overridden for all line items |
49 | * (as can happen in backoffice forms) |
50 | * |
51 | * @var int |
52 | */ |
53 | protected $overrideFinancialTypeID; |
54 | |
55 | /** |
56 | * Override for the total amount of the order. |
57 | * |
58 | * When there is a single line item the order total may be overriden. |
59 | * |
60 | * @var float |
61 | */ |
62 | protected $overrideTotalAmount; |
63 | |
64 | /** |
65 | * Line items in the order. |
66 | * |
67 | * @var array |
68 | */ |
69 | protected $lineItems = []; |
70 | |
71 | /** |
72 | * Metadata for price fields. |
73 | * |
74 | * @var array |
75 | */ |
76 | protected $priceFieldMetadata = []; |
77 | |
78 | /** |
79 | * Get Set override for total amount of the order. |
80 | * |
81 | * @return float|false |
82 | */ |
83 | public function getOverrideTotalAmount() { |
84 | if (count($this->getPriceOptions()) !== 1) { |
85 | return FALSE; |
86 | } |
87 | return $this->overrideTotalAmount ?? FALSE; |
88 | } |
89 | |
90 | /** |
91 | * Set override for total amount. |
92 | * |
93 | * @param float $overrideTotalAmount |
94 | */ |
95 | public function setOverrideTotalAmount(float $overrideTotalAmount) { |
96 | $this->overrideTotalAmount = $overrideTotalAmount; |
97 | } |
98 | |
99 | /** |
100 | * Get override for total amount. |
101 | * |
102 | * @return int| FALSE |
103 | */ |
104 | public function getOverrideFinancialTypeID() { |
105 | if (count($this->getPriceOptions()) !== 1) { |
106 | return FALSE; |
107 | } |
108 | return $this->overrideFinancialTypeID ?? FALSE; |
109 | } |
110 | |
111 | /** |
112 | * Set override for financial type ID. |
113 | * |
114 | * @param int $overrideFinancialTypeID |
115 | */ |
116 | public function setOverrideFinancialTypeID(int $overrideFinancialTypeID) { |
117 | $this->overrideFinancialTypeID = $overrideFinancialTypeID; |
118 | } |
119 | |
120 | /** |
121 | * Getter for price set id. |
122 | * |
123 | * @return int |
124 | */ |
125 | public function getPriceSetID(): int { |
126 | return $this->priceSetID; |
127 | } |
128 | |
129 | /** |
130 | * Setter for price set id. |
131 | * |
132 | * @param int $priceSetID |
133 | */ |
134 | public function setPriceSetID(int $priceSetID) { |
135 | $this->priceSetID = $priceSetID; |
136 | } |
137 | |
138 | /** |
139 | * Getter for price selection. |
140 | * |
141 | * @return array |
142 | */ |
143 | public function getPriceSelection(): array { |
144 | return $this->priceSelection; |
145 | } |
146 | |
147 | /** |
148 | * Setter for price selection. |
149 | * |
150 | * @param array $priceSelection |
151 | */ |
152 | public function setPriceSelection(array $priceSelection) { |
153 | $this->priceSelection = $priceSelection; |
154 | } |
155 | |
156 | /** |
157 | * Price options the simplified price fields selections. |
158 | * |
159 | * ie. the 'price_' is stripped off the key name and the field ID |
160 | * is cast to an integer. |
161 | * |
162 | * @return array |
163 | */ |
164 | public function getPriceOptions():array { |
165 | $priceOptions = []; |
166 | foreach ($this->getPriceSelection() as $fieldName => $value) { |
167 | $fieldID = substr($fieldName, 6); |
168 | $priceOptions[(int) $fieldID] = $value; |
169 | } |
170 | return $priceOptions; |
171 | } |
172 | |
173 | /** |
174 | * Get the metadata for the given field. |
175 | * |
176 | * @param int $id |
177 | * |
178 | * @return array |
179 | * @throws \CiviCRM_API3_Exception |
180 | */ |
181 | public function getPriceFieldSpec(int $id) :array { |
182 | if (!isset($this->priceFieldMetadata[$id])) { |
183 | $this->priceFieldMetadata = CRM_Price_BAO_PriceSet::getCachedPriceSetDetail($this->getPriceSetID())['fields']; |
184 | } |
185 | return $this->priceFieldMetadata[$id]; |
186 | } |
187 | |
188 | /** |
189 | * Set the price field selection from an array of params containing price fields. |
190 | * |
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. |
193 | * |
194 | * @param array $input |
195 | */ |
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; |
202 | } |
203 | } |
204 | } |
205 | } |
206 | |
207 | /** |
208 | * Get line items. |
209 | * |
210 | * return array |
211 | * |
212 | * @throws \CiviCRM_API3_Exception |
213 | */ |
214 | public function getLineItems():array { |
215 | if (empty($this->lineItems)) { |
216 | $this->lineItems = $this->calculateLineItems(); |
217 | } |
218 | return $this->lineItems; |
219 | } |
220 | |
221 | /** |
222 | * @return array |
223 | * @throws \CiviCRM_API3_Exception |
224 | */ |
225 | protected function calculateLineItems(): array { |
226 | $lineItems = []; |
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(); |
233 | } |
234 | |
235 | foreach ($this->getPriceOptions() as $fieldID => $valueID) { |
58315149 |
236 | if (!isset($this->priceSetID)) { |
237 | $this->setPriceSetID(PriceField::get()->addSelect('price_set_id')->addWhere('id', '=', $fieldID)->execute()->first()['price_set_id']); |
238 | } |
7aa78908 |
239 | $throwAwayArray = []; |
240 | // @todo - still using getLine for now but better to bring it to this class & do a better job. |
737bd87c |
241 | $newLines = CRM_Price_BAO_PriceSet::getLine($params, $throwAwayArray, $this->getPriceSetID(), $this->getPriceFieldSpec($fieldID), $fieldID)[1]; |
58315149 |
242 | foreach ($newLines as $newLine) { |
243 | $lineItems[$newLine['price_field_value_id']] = $newLine; |
244 | } |
7aa78908 |
245 | } |
246 | |
7aa78908 |
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(); |
252 | } |
dd118b15 |
253 | $taxRate = $this->getTaxRate((int) $lineItem['financial_type_id']); |
7aa78908 |
254 | if ($this->getOverrideTotalAmount() !== FALSE) { |
255 | if ($taxRate) { |
256 | // Total is tax inclusive. |
dd118b15 |
257 | $lineItem['tax_amount'] = ($taxRate / 100) * $this->getOverrideTotalAmount() / (1 + ($taxRate / 100)); |
7aa78908 |
258 | $lineItem['line_total'] = $lineItem['unit_price'] = $this->getOverrideTotalAmount() - $lineItem['tax_amount']; |
259 | } |
260 | else { |
261 | $lineItem['line_total'] = $lineItem['unit_price'] = $this->getOverrideTotalAmount(); |
262 | } |
263 | } |
264 | elseif ($taxRate) { |
265 | $lineItem['tax_amount'] = ($taxRate / 100) * $lineItem['line_total']; |
266 | } |
267 | } |
268 | return $lineItems; |
269 | } |
270 | |
271 | /** |
58315149 |
272 | * Get the total amount for the order. |
7aa78908 |
273 | * |
274 | * @return float |
275 | * |
276 | * @throws \CiviCRM_API3_Exception |
277 | */ |
278 | public function getTotalTaxAmount() :float { |
279 | $amount = 0.0; |
280 | foreach ($this->getLineItems() as $lineItem) { |
281 | $amount += $lineItem['tax_amount'] ?? 0.0; |
282 | } |
283 | return $amount; |
284 | } |
285 | |
58315149 |
286 | /** |
287 | * Get the total tax amount for the order. |
288 | * |
289 | * @return float |
290 | * |
291 | * @throws \CiviCRM_API3_Exception |
292 | */ |
293 | public function getTotalAmount() :float { |
294 | $amount = 0.0; |
295 | foreach ($this->getLineItems() as $lineItem) { |
296 | $amount += $lineItem['line_total'] ?? 0.0; |
297 | } |
298 | return $amount; |
299 | } |
300 | |
dd118b15 |
301 | /** |
302 | * Get the tax rate for the given financial type. |
303 | * |
304 | * @param int $financialTypeID |
305 | * |
306 | * @return float |
307 | */ |
308 | public function getTaxRate(int $financialTypeID) { |
309 | $taxRates = CRM_Core_PseudoConstant::getTaxRates(); |
310 | if (!isset($taxRates[$financialTypeID])) { |
311 | return 0; |
312 | } |
313 | return $taxRates[$financialTypeID]; |
314 | } |
315 | |
7aa78908 |
316 | } |