PluginProbe
PostNL for WooCommerce / 4.4.1
PostNL for WooCommerce v4.4.1
5.9.12 5.9.11 5.9.10 5.9.9 5.9.8 5.9.7 5.9.6 trunk 2.5.0 2.5.1 2.5.2 2.5.3 2.5.4 2.5.5 3.1.4 3.1.5 3.1.6 3.1.7 4.0.0 4.0.1 4.0.2 4.3.2 4.3.3 4.4.0 4.4.1 All 72 releases
woo-postnl / includes / admin / class-wcpn-export-consignments.php

class-wcpn-export-consignments.php in PostNL for WooCommerce 4.4.1, at includes/admin/class-wcpn-export-consignments.php

475 lines 13.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 use MyParcelNL\Sdk\src\Adapter\DeliveryOptions\AbstractDeliveryOptionsAdapter as DeliveryOptions;
4 use MyParcelNL\Sdk\src\Factory\ConsignmentFactory;
5 use MyParcelNL\Sdk\src\Helper\MyParcelCollection;
6 use MyParcelNL\Sdk\src\Model\Consignment\AbstractConsignment;
7 use MyParcelNL\Sdk\src\Model\MyParcelCustomsItem;
8 use WPO\WC\PostNL\Compatibility\Order as WCX_Order;
9 use WPO\WC\PostNL\Compatibility\Product as WCX_Product;
10
11 if (! defined("ABSPATH")) {
12 exit;
13 } // Exit if accessed directly
14
15 if (class_exists("WCPN_Export_Consignments")) {
16 return;
17 }
18
19 class WCPN_Export_Consignments
20 {
21 private const DEFAULT_PRODUCT_QUANTITY = 1;
22
23 /**
24 * @var AbstractConsignment
25 */
26 private $consignment;
27
28 /**
29 * @var DeliveryOptions
30 */
31 private $deliveryOptions;
32
33 /**
34 * @var WC_Order
35 */
36 private $order;
37
38 /**
39 * @var string
40 */
41 private $apiKey;
42
43 /**
44 * @var string|null
45 */
46 private $carrier;
47
48 /**
49 * @var MyParcelCollection
50 */
51 public $myParcelCollection;
52
53 /**
54 * @var \OrderSettings
55 */
56 private $orderSettings;
57
58 /**
59 * WCPN_Export_Consignments constructor.
60 *
61 * @param WC_Order $order
62 *
63 * @throws \ErrorException
64 * @throws \JsonException
65 * @throws \Exception
66 */
67 public function __construct(WC_Order $order)
68 {
69 $this->getApiKey();
70
71 $this->order = $order;
72 $this->orderSettings = new OrderSettings($order);
73 $this->deliveryOptions = $this->orderSettings->getDeliveryOptions();
74
75 $this->carrier = $this->deliveryOptions->getCarrier() ?? WCPN_Data::DEFAULT_CARRIER;
76
77 $this->myParcelCollection = (new MyParcelCollection())->setUserAgents(
78 [
79 'Wordpress' => get_bloginfo('version'),
80 'WooCommerce' => WOOCOMMERCE_VERSION,
81 'PostNL-WooCommerce' => WC_POSTNL_VERSION,
82 ]
83 );
84
85 $this->createConsignment();
86 $this->setConsignmentData();
87 }
88
89 /**
90 * Create a new consignment
91 *
92 * @return void
93 * @throws Exception
94 */
95 public function createConsignment(): void
96 {
97 $this->consignment = ConsignmentFactory::createByCarrierName($this->carrier);
98 }
99
100 /**
101 * Set all the needed data for the consignment.
102 *
103 * @throws Exception
104 */
105 private function setConsignmentData(): void
106 {
107 $this->setBaseData();
108 $this->setRecipient();
109 $this->setShipmentOptions();
110 $this->setPickupLocation();
111 $this->setCustomsDeclaration();
112 $this->setPhysicalProperties();
113 }
114
115 /**
116 * @param string $name
117 *
118 * @return mixed
119 */
120 private function getSetting(string $name)
121 {
122 return WCPOST()->setting_collection->getByName($name);
123 }
124
125 /**
126 * @return int
127 */
128 private function getPackageType(): int
129 {
130 return WCPN_Data::getPackageTypeId($this->orderSettings->getPackageType());
131 }
132
133 /**
134 * @return int
135 */
136 private function getDeliveryType(): int
137 {
138 $deliveryTypeId = WCPN_Data::getDeliveryTypeId($this->deliveryOptions->getDeliveryType());
139
140 return $deliveryTypeId ?? AbstractConsignment::DELIVERY_TYPE_STANDARD;
141 }
142
143 /**
144 * Get date in YYYY-MM-DD HH:MM:SS format
145 *
146 * @return string
147 */
148 public function getDeliveryDate(): string
149 {
150 $date = strtotime($this->deliveryOptions->getDate());
151 $deliveryDateTime = date('Y-m-d H:i:s', $date);
152 $deliveryDate = date("Y-m-d", $date);
153 $dateOfToday = date("Y-m-d", strtotime('now'));
154 $dateOfTomorrow = date('Y-m-d H:i:s', strtotime('now +1 day'));
155
156 if ($deliveryDate <= $dateOfToday) {
157 return $dateOfTomorrow;
158 }
159
160 return $deliveryDateTime;
161 }
162
163 /**
164 * @return void
165 * @throws \MyParcelNL\Sdk\src\Exception\MissingFieldException
166 * @throws \ErrorException
167 */
168 public function setCustomItems(): void
169 {
170 foreach ($this->order->get_items() as $item) {
171 $product = $item->get_product();
172
173 if (! $product) {
174 return;
175 }
176
177 $country = $this->getCountryOfOrigin($product);
178 $description = $item["name"];
179
180 // GitHub issue https://github.com/myparcelnl/woocommerce/issues/190
181 if (strlen($description) >= WCPN_Export::ITEM_DESCRIPTION_MAX_LENGTH) {
182 $description = substr($description, 0, 47) . "...";
183 }
184
185 // Amount
186 $amount = (int) ($item["qty"] ?? self::DEFAULT_PRODUCT_QUANTITY);
187
188 // Weight (total item weight in grams)
189 $weight = WCPN_Export::convertWeightToGrams($product->get_weight());
190
191 $total = (int) $item["line_total"];
192 $tax = (int) $item["line_tax"];
193 $value = round(($total + $tax) * 100);
194
195 $this->consignment->addItem(
196 (new MyParcelCustomsItem())->setDescription($description)
197 ->setAmount($amount)
198 ->setWeight($weight)
199 ->setItemValue($value)
200 ->setCountry($country)
201 ->setClassification($this->getHsCode($product))
202 );
203 }
204 }
205
206 /**
207 * Returns the weight of the order plus the empty parcel weight.
208 *
209 * @return int
210 */
211 private function getTotalWeight(): int
212 {
213 $digitalStampRangeWeight = null;
214 $weight = $this->orderSettings->getWeight();
215
216 // Divide the consignment weight by the amount of parcels.
217 $weight /= $this->orderSettings->getColloAmount();
218
219 switch ($this->orderSettings->getPackageType()) {
220 case AbstractConsignment::PACKAGE_TYPE_PACKAGE_NAME:
221 $emptyParcelWeight = (float) $this->getSetting(WCPOST_Settings::SETTING_EMPTY_PARCEL_WEIGHT);
222 $weight += $emptyParcelWeight;
223 break;
224 case AbstractConsignment::PACKAGE_TYPE_DIGITAL_STAMP_NAME:
225 $digitalStampRangeWeight = $this->orderSettings->getDigitalStampRangeWeight();
226 break;
227 }
228
229 return $digitalStampRangeWeight ?? WCPN_Export::convertWeightToGrams($weight);
230 }
231
232 /**
233 * @param WC_Product $product
234 *
235 * @return int
236 * @throws \ErrorException
237 */
238 public function getHsCode(WC_Product $product): int
239 {
240 $defaultHsCode = $this->getSetting(WCPOST_Settings::SETTING_HS_CODE);
241 $productHsCode = WCX_Product::get_meta($product, WCPOST_Admin::META_HS_CODE, true);
242 $variationHsCode = WCX_Product::get_meta($product, WCPOST_Admin::META_HS_CODE_VARIATION, true);
243
244 $hsCode = $productHsCode ?: $defaultHsCode;
245
246 if ($variationHsCode) {
247 $hsCode = $variationHsCode;
248 }
249
250 if (! $hsCode) {
251 throw new ErrorException(__("No HS code found in PostNL settings", "woocommerce-postnl"));
252 }
253
254 return (int) $hsCode;
255 }
256
257 /**
258 * @param WC_Product $product
259 *
260 * @return string
261 * @throws \JsonException
262 */
263 public function getCountryOfOrigin(WC_Product $product): string
264 {
265 $defaultCountryOfOrigin = $this->getSetting(WCPOST_Settings::SETTING_COUNTRY_OF_ORIGIN);
266 $productCountryOfOrigin = WCX_Product::get_meta($product, WCPOST_Admin::META_COUNTRY_OF_ORIGIN, true);
267
268 return $this->getPriorityOrigin($defaultCountryOfOrigin, $productCountryOfOrigin);
269 }
270
271 /**
272 * @param string|null $defaultCountryOfOrigin
273 * @param string|null $productCountryOfOrigin
274 *
275 * @return string
276 */
277 public function getPriorityOrigin(?string $defaultCountryOfOrigin, ?string $productCountryOfOrigin): string
278 {
279 if ($productCountryOfOrigin) {
280 return $productCountryOfOrigin;
281 }
282
283 if ($defaultCountryOfOrigin) {
284 return $defaultCountryOfOrigin;
285 }
286
287 return WC()->countries->get_base_country() ?? AbstractConsignment::CC_NL;
288 }
289
290 /**
291 * @return AbstractConsignment
292 */
293 public function getConsignment(): AbstractConsignment
294 {
295 return $this->consignment;
296 }
297
298 /**
299 * @return \OrderSettings
300 */
301 public function getOrderSettings(): OrderSettings
302 {
303 return $this->orderSettings;
304 }
305
306 /**
307 * @return int
308 */
309 private function getContents(): int
310 {
311 return (int) ($this->getSetting("package_contents") ?? AbstractConsignment::PACKAGE_CONTENTS_COMMERCIAL_GOODS);
312 }
313
314 /**
315 * Gets the recipient and puts its data in the consignment.
316 *
317 * @throws Exception
318 */
319 private function setRecipient(): void
320 {
321 $recipient = WCPN_Export::getRecipientFromOrder($this->order);
322
323 $this->consignment
324 ->setCountry($recipient['cc'])
325 ->setPerson($recipient['person'])
326 ->setCompany($recipient['company'])
327 ->setStreet($recipient['street'])
328 ->setNumber($recipient['number'] ?? null)
329 ->setNumberSuffix($recipient['number_suffix'] ?? null)
330 ->setStreetAdditionalInfo($recipient['street_additional_info'] ?? null)
331 ->setPostalCode($recipient['postal_code'])
332 ->setCity($recipient['city'])
333 ->setEmail($recipient['email'])
334 ->setPhone($recipient['phone'])
335 ->setSaveRecipientAddress(false);
336 }
337
338 /**
339 * @throws ErrorException
340 */
341 private function getApiKey(): void
342 {
343 $this->apiKey = $this->getSetting(WCPOST_Settings::SETTING_API_KEY);
344
345 if (! $this->apiKey) {
346 throw new ErrorException(__("No API key found in PostNL settings", "woocommerce-postnl"));
347 }
348 }
349
350 /**
351 * Get the label description from OrderSettings and replace any variables in it.
352 *
353 * @return string
354 */
355 private function getFormattedLabelDescription(): string
356 {
357 $productIds = [];
358 $productNames = [];
359 $productSkus = [];
360
361 foreach ($this->order->get_items() as $item) {
362 if (! method_exists($item, 'get_product')) {
363 continue;
364 }
365
366 /** @var WC_Product $product */
367 $product = $item->get_product();
368 $sku = $product->get_sku();
369
370 $productIds[] = $product->get_id();
371 $productNames[] = $product->get_name();
372 $productSkus[] = empty($sku) ? '–' : $sku;
373 }
374
375 $formattedLabelDescription = strtr(
376 $this->orderSettings->getLabelDescription(),
377 [
378 '[DELIVERY_DATE]' => date('d-m-Y', strtotime($this->deliveryOptions->getDate())),
379 '[ORDER_NR]' => $this->order->get_order_number(),
380 '[PRODUCT_ID]' => implode(', ', $productIds),
381 '[PRODUCT_NAME]' => implode(', ', $productNames),
382 '[PRODUCT_QTY]' => count($this->order->get_items()),
383 '[PRODUCT_SKU]' => implode(', ', $productSkus),
384 '[CUSTOMER_NOTE]' => $this->order->get_customer_note(),
385 ]
386 );
387
388 if (strlen($formattedLabelDescription) > WCPN_Export::ORDER_DESCRIPTION_MAX_LENGTH) {
389 return substr($formattedLabelDescription, 0, 42) . "...";
390 }
391
392 return $formattedLabelDescription;
393 }
394
395 /**
396 * Set the pickup location
397 */
398 private function setPickupLocation(): void
399 {
400 $pickupLocation = $this->deliveryOptions->getPickupLocation();
401
402 if (! $this->deliveryOptions->isPickup() || ! $pickupLocation) {
403 return;
404 }
405
406 $this->consignment
407 ->setPickupCountry($pickupLocation->getCountry())
408 ->setPickupCity($pickupLocation->getCity())
409 ->setPickupLocationName($pickupLocation->getLocationName())
410 ->setPickupStreet($pickupLocation->getStreet())
411 ->setPickupNumber($pickupLocation->getNumber())
412 ->setPickupPostalCode($pickupLocation->getPostalCode())
413 ->setRetailNetworkId($pickupLocation->getRetailNetworkId())
414 ->setPickupLocationCode($pickupLocation->getLocationCode());
415 }
416
417 /**
418 * Set the shipment options.
419 *
420 * @throws Exception
421 */
422 private function setShipmentOptions(): void
423 {
424 $this->consignment
425 ->setAgeCheck($this->orderSettings->hasAgeCheck())
426 ->setInsurance($this->orderSettings->getInsuranceAmount())
427 // ->setLargeFormat($this->orderSettings->hasLargeFormat())
428 ->setOnlyRecipient($this->orderSettings->hasOnlyRecipient())
429 ->setReturn($this->orderSettings->hasReturnShipment())
430 ->setSignature($this->orderSettings->hasSignature())
431 ->setContents($this->getContents())
432 ->setInvoice($this->order->get_id());
433 }
434
435 /**
436 * Sets a customs declaration for the consignment if necessary.
437 *
438 * @throws \Exception
439 */
440 private function setCustomsDeclaration(): void
441 {
442 $shippingCountry = WCX_Order::get_prop($this->order, "shipping_country");
443
444 if (WCPN_Country_Codes::isWorldShipmentCountry($shippingCountry)) {
445 $this->setCustomItems();
446 }
447 }
448
449 /**
450 * @throws \Exception
451 */
452 private function setPhysicalProperties(): void
453 {
454 $this->consignment->setPhysicalProperties(
455 [
456 'weight' => $this->getTotalWeight(),
457 ]
458 );
459 }
460
461 /**
462 * @throws Exception
463 */
464 private function setBaseData(): void
465 {
466 $this->consignment
467 ->setApiKey($this->apiKey)
468 ->setReferenceId((string) $this->order->get_id())
469 ->setPackageType($this->getPackageType())
470 ->setDeliveryDate($this->getDeliveryDate())
471 ->setDeliveryType($this->getDeliveryType())
472 ->setLabelDescription($this->getFormattedLabelDescription());
473 }
474 }
475