Merge pull request #18782 from demeritcowboy/logging-binary2
[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 use Civi\Api4\PriceField;
13
14 /**
15 *
16 * @package CRM
17 * @copyright CiviCRM LLC https://civicrm.org/licensing
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 */
180 public function getPriceFieldSpec(int $id) :array {
181 return $this->getPriceFieldsMetadata()[$id];
182 }
183
184 /**
185 * Get the metadata for the fields in the price set.
186 *
187 * @return array
188 */
189 public function getPriceFieldsMetadata(): array {
190 if (empty($this->priceFieldMetadata)) {
191 $this->priceFieldMetadata = CRM_Price_BAO_PriceSet::getCachedPriceSetDetail($this->getPriceSetID())['fields'];
192 }
193 return $this->priceFieldMetadata;
194 }
195
196 /**
197 * Set the price field selection from an array of params containing price fields.
198 *
199 * This function takes the sort of 'anything & everything' parameters that come in from the
200 * form layer and filters them before assigning them to the priceSelection property.
201 *
202 * @param array $input
203 */
204 public function setPriceSelectionFromUnfilteredInput(array $input): void {
205 foreach ($input as $fieldName => $value) {
206 if (strpos($fieldName, 'price_') === 0) {
207 $fieldID = substr($fieldName, 6);
208 if (is_numeric($fieldID)) {
209 $this->priceSelection[$fieldName] = $value;
210 }
211 }
212 }
213 }
214
215 /**
216 * Get line items.
217 *
218 * return array
219 *
220 * @throws \CiviCRM_API3_Exception
221 */
222 public function getLineItems():array {
223 if (empty($this->lineItems)) {
224 $this->lineItems = $this->calculateLineItems();
225 }
226 return $this->lineItems;
227 }
228
229 /**
230 * @return array
231 * @throws \CiviCRM_API3_Exception
232 */
233 protected function calculateLineItems(): array {
234 $lineItems = [];
235 $params = $this->getPriceSelection();
236 if ($this->getOverrideTotalAmount() !== FALSE) {
237 // We need to do this to keep getLine from doing weird stuff but the goal
238 // is to ditch getLine next round of refactoring
239 // and make the code more sane.
240 $params['total_amount'] = $this->getOverrideTotalAmount();
241 }
242
243 foreach ($this->getPriceOptions() as $fieldID => $valueID) {
244 if (!isset($this->priceSetID)) {
245 $this->setPriceSetID(PriceField::get()->addSelect('price_set_id')->addWhere('id', '=', $fieldID)->execute()->first()['price_set_id']);
246 }
247 $throwAwayArray = [];
248 // @todo - still using getLine for now but better to bring it to this class & do a better job.
249 $newLines = CRM_Price_BAO_PriceSet::getLine($params, $throwAwayArray, $this->getPriceSetID(), $this->getPriceFieldSpec($fieldID), $fieldID)[1];
250 foreach ($newLines as $newLine) {
251 $lineItems[$newLine['price_field_value_id']] = $newLine;
252 }
253 }
254
255 foreach ($lineItems as &$lineItem) {
256 // Set any pre-calculation to zero as we will calculate.
257 $lineItem['tax_amount'] = 0;
258 if ($this->getOverrideFinancialTypeID() !== FALSE) {
259 $lineItem['financial_type_id'] = $this->getOverrideFinancialTypeID();
260 }
261 $taxRate = $this->getTaxRate((int) $lineItem['financial_type_id']);
262 if ($this->getOverrideTotalAmount() !== FALSE) {
263 if ($taxRate) {
264 // Total is tax inclusive.
265 $lineItem['tax_amount'] = ($taxRate / 100) * $this->getOverrideTotalAmount() / (1 + ($taxRate / 100));
266 $lineItem['line_total'] = $lineItem['unit_price'] = $this->getOverrideTotalAmount() - $lineItem['tax_amount'];
267 }
268 else {
269 $lineItem['line_total'] = $lineItem['unit_price'] = $this->getOverrideTotalAmount();
270 }
271 }
272 elseif ($taxRate) {
273 $lineItem['tax_amount'] = ($taxRate / 100) * $lineItem['line_total'];
274 }
275 }
276 return $lineItems;
277 }
278
279 /**
280 * Get the total amount for the order.
281 *
282 * @return float
283 *
284 * @throws \CiviCRM_API3_Exception
285 */
286 public function getTotalTaxAmount() :float {
287 $amount = 0.0;
288 foreach ($this->getLineItems() as $lineItem) {
289 $amount += $lineItem['tax_amount'] ?? 0.0;
290 }
291 return $amount;
292 }
293
294 /**
295 * Get the total tax amount for the order.
296 *
297 * @return float
298 *
299 * @throws \CiviCRM_API3_Exception
300 */
301 public function getTotalAmount() :float {
302 $amount = 0.0;
303 foreach ($this->getLineItems() as $lineItem) {
304 $amount += $lineItem['line_total'] ?? 0.0;
305 }
306 return $amount;
307 }
308
309 /**
310 * Get the tax rate for the given financial type.
311 *
312 * @param int $financialTypeID
313 *
314 * @return float
315 */
316 public function getTaxRate(int $financialTypeID) {
317 $taxRates = CRM_Core_PseudoConstant::getTaxRates();
318 if (!isset($taxRates[$financialTypeID])) {
319 return 0;
320 }
321 return $taxRates[$financialTypeID];
322 }
323
324 }