PluginProbe
Moloni / 5.0.09
Moloni v5.0.09
5.0.10 5.0.09 5.0.08 5.0.07 5.0.06 trunk 0.4.0.00 0.4.8.1 3.0.10 3.0.11 3.0.35 3.0.36 3.0.37 3.0.38 3.0.40 3.0.41 3.0.42 3.0.43 3.0.44 3.0.45 3.0.46 3.0.47 3.0.48 3.0.49 3.0.50 All 98 releases
moloni / src / Controllers / OrderProduct.php

OrderProduct.php in Moloni 5.0.09, at src/Controllers/OrderProduct.php

492 lines 13.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Moloni\Controllers;
4
5 use Moloni\Curl;
6 use Moloni\Exceptions\APIException;
7 use Moloni\Exceptions\GenericException;
8 use Moloni\Tools;
9 use WC_Order;
10 use WC_Order_Item_Product;
11 use WC_Product;
12 use WC_Tax;
13
14 class OrderProduct
15 {
16 /** @var int */
17 public $product_id = 0;
18
19 /** @var int */
20 private $order;
21
22 /**
23 * @var WC_Order_Item_Product
24 */
25 private $product;
26
27 /** @var Product */
28 private $moloniProduct;
29
30 /** @var WC_Order */
31 private $wc_order;
32
33 /** @var array */
34 protected $taxes = [];
35
36 /** @var float */
37 public $qty;
38
39 /** @var float */
40 public $price;
41
42 /** @var string */
43 protected $exemption_reason;
44
45 /** @var string */
46 private $name;
47
48 /** @var string */
49 private $summary;
50
51 /** @var float */
52 private $discount;
53
54 /** @var int */
55 private $warehouse_id = 0;
56
57 /** @var bool */
58 private $hasIVA = false;
59
60 /** @var array */
61 private $fiscalData;
62
63 /** @var int */
64 public $composition_type = 0;
65
66 /** @var false|array */
67 public $child_products = false;
68
69 /**
70 * OrderProduct constructor.
71 * @param WC_Order_Item_Product $product
72 * @param WC_Order $wcOrder
73 * @param int $order
74 */
75 public function __construct($product, $wcOrder, $order = 0, $fiscalData = [])
76 {
77 $this->product = $product;
78 $this->wc_order = $wcOrder;
79 $this->order = $order;
80 $this->fiscalData = $fiscalData;
81 }
82
83 /**
84 * @return $this
85 *
86 * @throws APIException
87 * @throws GenericException
88 */
89 public function create()
90 {
91 $this
92 ->setName()
93 ->setQty()
94 ->setPrice()
95 ->setSummary()
96 ->setProductId()
97 ->setDiscount()
98 ->setTaxes()
99 ->setWarehouse()
100 ->setChildProducts();
101
102 return $this;
103 }
104
105 public function setName()
106 {
107 $this->name = $this->product->get_name();
108 return $this;
109 }
110
111 /**
112 * Set product summary
113 *
114 * @param string|null $summary
115 *
116 * @return $this
117 */
118 public function setSummary(?string $summary = '')
119 {
120 $summary = apply_filters('moloni_before_order_item_setSummary', $summary, $this->product);
121
122 if (empty($summary)) {
123 $variationAttributes = $this->getSummaryVariationAttributes();
124 $extraOptions = $this->getSummaryExtraProductOptions();
125
126 switch (true) {
127 case !empty($variationAttributes) && !empty($extraOptions):
128 $summary = $variationAttributes . '\n' . $extraOptions;
129
130 break;
131 case !empty($variationAttributes):
132 $summary = $variationAttributes;
133
134 break;
135 case !empty($extraOptions):
136 $summary = $extraOptions;
137
138 break;
139 default:
140 $summary = '';
141
142 break;
143 }
144 }
145
146 $this->summary = apply_filters('moloni_after_order_item_setSummary', $summary, $this->product);
147
148 return $this;
149 }
150
151 /**
152 * @return string
153 */
154 private function getSummaryVariationAttributes()
155 {
156 $summary = '';
157
158 if ($this->product->get_variation_id() > 0) {
159 $product = wc_get_product($this->product->get_variation_id());
160 $attributes = $product->get_attributes();
161 if (is_array($attributes) && !empty($attributes)) {
162 $summary = wc_get_formatted_variation($attributes, true);
163 }
164 }
165
166 return $summary;
167 }
168
169 /**
170 * @return string
171 */
172 private function getSummaryExtraProductOptions()
173 {
174 $summary = '';
175 $checkEPO = $this->product->get_meta('_tmcartepo_data', true);
176 $extraProductOptions = maybe_unserialize($checkEPO);
177
178 if ($extraProductOptions && is_array($extraProductOptions)) {
179 foreach ($extraProductOptions as $extraProductOption) {
180 if (isset($extraProductOption['name'], $extraProductOption['value'])) {
181
182 if (!empty($summary)) {
183 $summary .= "\n";
184 }
185
186 $summary .= $extraProductOption['name'] . ' ' . $extraProductOption['value'];
187 }
188 }
189 }
190
191 return $summary;
192 }
193
194 /**
195 * Set order product price
196 *
197 * @return OrderProduct
198 */
199 public function setPrice()
200 {
201 $price = 0;
202
203 if ($this->qty > 0) {
204 $price = (float)$this->product->get_subtotal() / $this->qty;
205
206 $refundedValue = $this->wc_order->get_total_refunded_for_item($this->product->get_id());
207
208 if ($refundedValue !== 0) {
209 $refundedValue /= $this->qty;
210
211 $price -= $refundedValue;
212 }
213
214 if ($price < 0) {
215 $price = 0;
216 }
217 }
218
219 $this->price = $price;
220
221 return $this;
222 }
223
224 /**
225 * @return OrderProduct
226 */
227 public function setQty()
228 {
229 $this->qty = (float)$this->product->get_quantity();
230
231 $refundedQty = absint($this->wc_order->get_qty_refunded_for_item($this->product->get_id()));
232
233 if ($refundedQty !== 0) {
234 $this->qty -= $refundedQty;
235 }
236
237 return $this;
238 }
239
240 /**
241 * Set product id
242 *
243 * @return $this
244 *
245 * @throws APIException
246 * @throws GenericException
247 */
248 private function setProductId()
249 {
250 $wcProduct = $this->product->get_product();
251
252 if (!($wcProduct instanceof WC_Product)) {
253 throw new GenericException(__('Artigo da encomenda já não existe: ') . $this->name);
254 }
255
256 $this->moloniProduct = $this->makeMoloniProduct($wcProduct);
257
258 if (!$this->moloniProduct->loadByReference()) {
259 $this->moloniProduct->fiscalZone = $this->fiscalData['code'];
260 $this->moloniProduct->create();
261 } elseif (defined('USE_MOLONI_PRODUCT_DETAILS') && USE_MOLONI_PRODUCT_DETAILS) {
262 $this->name = $this->moloniProduct->name;
263 $this->summary = $this->moloniProduct->summary;
264 }
265
266 if ($this->moloniProduct->visibility_id === 0) {
267 throw new GenericException('Produto com referência ' . $this->moloniProduct->reference . ' tem de estar ativo.');
268 }
269
270 $this->composition_type = $this->moloniProduct->composition_type;
271 $this->product_id = $this->moloniProduct->getProductId();
272
273 return $this;
274 }
275
276 /**
277 * Build the Moloni product controller for this order line.
278 *
279 * Overridden by specialized lines (e.g. {@see OrderSdr}) to create a
280 * specialized product.
281 *
282 * @param WC_Product $wcProduct
283 *
284 * @return Product
285 */
286 protected function makeMoloniProduct($wcProduct): Product
287 {
288 return new Product($wcProduct);
289 }
290
291 /**
292 * Set the discount in percentage
293 * @return $this
294 */
295 private function setDiscount()
296 {
297 if ((float)$this->product->get_subtotal() === 0.0) {
298 return $this;
299 }
300
301 $this->discount = (100 - (((float)$this->product->get_total() * 100) / (float)$this->product->get_subtotal()));
302
303 if ($this->discount > 100) {
304 $this->discount = 100;
305 }
306
307 if ($this->discount < 0) {
308 $this->discount = 0;
309 }
310
311 return $this;
312 }
313
314 /**
315 * Set the taxes of a product
316 *
317 * @return OrderProduct
318 *
319 * @throws APIException
320 */
321 protected function setTaxes()
322 {
323 $taxes = $this->product->get_taxes();
324 foreach ($taxes['subtotal'] as $taxId => $value) {
325 if (!empty($value)) {
326 $taxRate = preg_replace('/[^0-9.]/', '', WC_Tax::get_rate_percent($taxId));
327
328 if ((float)$taxRate > 0) {
329 $this->taxes[] = $this->setTax($taxRate);
330 }
331 }
332 }
333
334 if (!$this->hasIVA) {
335 $exemptionReason = '';
336
337 if ($this->isCountryIntraCommunity()) {
338 $exemptionReason = defined('EXEMPTION_REASON') ? EXEMPTION_REASON : '';
339 } else {
340 if (defined('EXEMPTION_REASON_EXTRA_COMMUNITY')) {
341 $exemptionReason = EXEMPTION_REASON_EXTRA_COMMUNITY;
342 } elseif (defined('EXEMPTION_REASON')) {
343 $exemptionReason = EXEMPTION_REASON;
344 }
345 }
346
347 if (empty($exemptionReason)) {
348 $this->taxes[] = $this->moloniProduct->getDefaultTax();
349 } else {
350 $this->exemption_reason = $exemptionReason;
351 }
352 }
353
354 return $this;
355 }
356
357 /**
358 * @param float $taxRate Tax Rate in percentage
359 * @return array
360 * @throws APIException
361 */
362 private function setTax($taxRate)
363 {
364 $moloniTax = Tools::getTaxFromRate((float)$taxRate, $this->fiscalData['code']);
365
366 $tax = [];
367 $tax['tax_id'] = $moloniTax['tax_id'];
368 $tax['value'] = $taxRate;
369 $tax['order'] = is_array($this->taxes) ? count($this->taxes) : 0;
370 $tax['cumulative'] = 0;
371
372 if ((int)$moloniTax['saft_type'] === 1) {
373 $this->hasIVA = true;
374 }
375
376 return $tax;
377 }
378
379 /**
380 * @param bool|int $warehouseId
381 * @return OrderProduct
382 */
383 private function setWarehouse($warehouseId = false)
384 {
385 if ((int)$warehouseId > 0) {
386 $this->warehouse_id = $warehouseId;
387 return $this;
388 }
389
390 if (defined('MOLONI_PRODUCT_WAREHOUSE') && (int)MOLONI_PRODUCT_WAREHOUSE > 0) {
391 $this->warehouse_id = (int)MOLONI_PRODUCT_WAREHOUSE;
392 }
393
394
395 return $this;
396 }
397
398 /**
399 * @return $this
400 *
401 * @throws APIException
402 */
403 private function setChildProducts()
404 {
405 if ($this->composition_type === 1 && is_array($this->moloniProduct->child_products) && !empty($this->moloniProduct->child_products)) {
406 if ($this->moloniProduct->price > 0) {
407 $priceChangePercent = $this->price / $this->moloniProduct->price;
408 $this->price = 0;
409
410 $this->child_products = [];
411
412 foreach ($this->moloniProduct->child_products as $index => $childProduct) {
413 $moloniChildProduct = Curl::simple('products/getOne', ['product_id' => $childProduct['product_child_id']]);
414
415 $this->child_products[$index] = $moloniChildProduct;
416 $this->child_products[$index]['discount'] = (float)$this->discount > 0 ? $this->discount : 0;
417 $this->child_products[$index]['price'] = $childProduct['price'] * $priceChangePercent;
418 $this->child_products[$index]['qty'] = $childProduct['qty'] * $this->qty;
419
420 $this->price += (($childProduct['price'] * $priceChangePercent) * $childProduct['qty']);
421
422 //If billing country is not PT, change child products taxes to match order taxes
423 if ($this->wc_order->get_billing_country() !== 'PT') {
424 if (!empty($this->exemption_reason)) {
425 //Delete moloni taxes data
426 unset($this->child_products[$index]['taxes']);
427
428 //Keep this order defined exemption reason
429 $this->child_products[$index]['exemption_reason'] = $this->exemption_reason;
430 } else {
431 //Delete moloni exemption reason data
432 unset($this->child_products[$index]['exemption_reason']);
433
434 //Keep this order defined taxes
435 $this->child_products[$index]['taxes'] = $this->taxes;
436 }
437 } else {
438 //If billing country is PT, use Moloni defined taxes and/or exemption reason
439 if (empty($this->taxes)) {
440 unset($this->child_products[$index]['taxes']);
441
442 $this->child_products[$index]['exemption_reason'] = $this->exemption_reason;
443 }
444 }
445 }
446 }
447 }
448
449 return $this;
450 }
451
452 /**
453 * @return array
454 */
455 public function mapPropsToValues()
456 {
457 $values = [];
458
459 $values['product_id'] = $this->product_id;
460 $values['name'] = $this->name;
461 $values['summary'] = $this->summary;
462 $values['qty'] = $this->qty;
463 $values['price'] = $this->price;
464 $values['discount'] = $this->discount;
465 $values['order'] = $this->order;
466 $values['exemption_reason'] = $this->exemption_reason;
467 $values['taxes'] = $this->taxes;
468 $values['warehouse_id'] = $this->warehouse_id;
469 $values['child_products'] = $this->child_products;
470
471 return $values;
472 }
473
474 /**
475 * Check if country is intra community
476 *
477 * @return bool
478 */
479 private function isCountryIntraCommunity(): bool
480 {
481 if (!isset(Tools::$europeanCountryCodes[$this->fiscalData['code']])) {
482 return false;
483 }
484
485 if ($this->fiscalData['code'] === 'ES' && in_array($this->fiscalData['state'], ['TF', 'GC'])) {
486 return false;
487 }
488
489 return true;
490 }
491 }
492