PluginProbe
MBE eShip / trunk
MBE eShip vtrunk
2.8.1 trunk 1.0.0 1.1.0 1.1.3 1.2.1 1.2.2 1.3.0 1.4.0 1.4.1 1.5.0 1.5.1 1.5.2 1.6.0 1.7.0 1.7.1 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.1.0 2.1.1 2.1.2 2.2.1 All 37 releases
mail-boxes-etc / lib / Model / Carrier.php

Carrier.php in MBE eShip trunk, at lib/Model/Carrier.php

504 lines 18.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 class Mbe_Shipping_Model_Carrier {
4
5 const SHIPMENT_CONFIGURATION_MODE_ONE_SHIPMENT_PER_ITEM = 1;
6 const SHIPMENT_CONFIGURATION_MODE_ONE_SHIPMENT_PER_SHOPPING_CART_WEIGHT_MULTI_PARCEL = 2;
7 const SHIPMENT_CONFIGURATION_MODE_ONE_SHIPMENT_PER_SHOPPING_CART_ITEMS_MULTI_PARCEL = 3;
8
9 const HANDLING_TYPE_PER_SHIPMENT = "S";
10 const HANDLING_TYPE_PER_PARCEL = "P";
11
12 const HANDLING_TYPE_FIXED = "F";
13
14 const MBE_CACHE_ID = 'mbe_cache_getrates';
15
16
17 /**
18 * Carrier's code, as defined in parent class
19 *
20 * @var string
21 */
22 protected $_code = 'mbe_shipping';
23
24 /** @var $logger Mbe_Shipping_Helper_Logger */
25 protected $logger = null;
26 /** @var $shippingHelper Mbe_Shipping_Helper_Data */
27 protected $shippingHelper = null;
28
29 protected $allowedShipmentServicesArray;
30
31
32 public function __construct() {
33 $this->shippingHelper = new Mbe_Shipping_Helper_Data();
34 $this->logger = new Mbe_Shipping_Helper_Logger();
35 $this->allowedShipmentServicesArray = $this->shippingHelper->getAllowedShipmentServicesArray(); // set a local parameter to be updated (for uap) outside getRates
36 }
37
38
39 public function getRequestWeight( $request ) {
40 $result = 0;
41 foreach ( $request['contents'] as $item ) {
42 $result += ( (float) $item['data']->get_weight() * $item['quantity'] );
43 }
44
45 return $result;
46 }
47
48 public function collectRates( $request ) {
49 if ( $this->shippingHelper->isEnabledCustomMapping() ) {
50 // If custom mapping is used no MBE methods will be available
51 return [];
52 } else {
53 $this->logger->log( 'collectRates' );
54
55 if ( ! $this->shippingHelper->isEnabled() ) {
56 $this->logger->log( 'module disabled' );
57
58 return false;
59 }
60 $this->logger->log( 'module enabled' );
61
62
63 $destCountry = $request['destination']['country'];
64 $destRegion = $request['destination']['state'];
65 //TODO:verify
66 $city = $request['destination']['city'];
67
68 $destPostCode = $request['destination']['postcode'];
69
70 $this->logger->log( "Destination: COUNTRY: " . $destCountry . " - REGION CODE: " . $destRegion . " - POSTCODE: " . $destPostCode );
71
72
73 $shipmentConfigurationMode = $this->shippingHelper->getShipmentConfigurationMode();
74
75
76 $shipments = array();
77 $baseSubtotalInclTax = 0;
78 foreach ( $request['contents'] as $item ) {
79 $baseSubtotalInclTax += $item['line_total'] + $item['line_tax'];
80 }
81
82 $boxesDimensionWeight = [];
83 $boxesSingleParcelDimensionWeight = [];
84
85 $this->logger->log( "SHIPMENTCONFIGURATIONMODE: " . $shipmentConfigurationMode );
86
87 if ( $shipmentConfigurationMode == self::SHIPMENT_CONFIGURATION_MODE_ONE_SHIPMENT_PER_ITEM ) {
88
89 $iteration = 1;
90 foreach ( $request['contents'] as $item ) {
91 $boxesDimensionWeight = [];
92 $boxesSingleParcelDimensionWeight = [];
93
94 $products = $this->getProductsFromCart([$item], true);
95
96 // Retrieve the product info using the new box structure
97 $this->shippingHelper->getBoxesArray(
98 $boxesDimensionWeight,
99 $boxesSingleParcelDimensionWeight,
100 $item['data']->get_weight(),
101 $this->shippingHelper->
102 getPackageInfo( $item['data']->get_sku() )
103 );
104
105 for ( $i = 1; $i <= $item['quantity']; $i ++ ) {
106 $this->logger->log( "Product Iteration: " . $iteration );
107 // $weight = $item['data']->get_weight();
108 if ( $this->shippingHelper->getShipmentsInsuranceMode() == Mbe_Shipping_Helper_Data::MBE_INSURANCE_WITH_TAXES ) {
109 // if ( version_compare( WC()->version, '3', '>=' ) ) {
110 $insuranceValue = wc_get_price_including_tax( $item["data"] );
111 // } else {
112 // $insuranceValue = $item["data"]->get_price_including_tax();
113 // }
114 } else {
115 // if ( version_compare( WC()->version, '3', '>=' ) ) {
116 $insuranceValue = wc_get_price_excluding_tax( $item["data"] );
117 // } else {
118 // $insuranceValue = $item["data"]->get_price_excluding_tax();
119 // }
120 }
121 // $boxesDimensionWeight is used directly, since we use 1 box for each shipment and this method is run for every item (1 item - 1 box - 1 shipment)
122 $shipments = $this->getRates( $destCountry, $destRegion, $city, $destPostCode, $baseSubtotalInclTax, $boxesDimensionWeight, 1, $products, $shipments, $iteration, $insuranceValue );
123 $iteration ++;
124 }
125 }
126 } elseif ( $shipmentConfigurationMode == self::SHIPMENT_CONFIGURATION_MODE_ONE_SHIPMENT_PER_SHOPPING_CART_WEIGHT_MULTI_PARCEL ) {
127 $insuranceValue = 0;
128 $products = $this->getProductsFromCart($request['contents']);
129
130 foreach ( $request['contents'] as $item ) {
131 $packageInfo = $this->shippingHelper->getPackageInfo( $item['data']->get_sku() );
132
133 $itemQty = $item['quantity'];
134
135 for ( $i = 1; $i <= $itemQty; $i ++ ) {
136 $boxesDimensionWeight = $this->shippingHelper->getBoxesArray(
137 $boxesDimensionWeight,
138 $boxesSingleParcelDimensionWeight,
139 $item['data']->get_weight(),
140 $packageInfo
141 );
142 }
143
144 $insuranceValue += $this->getSubtotalForInsurance( $item );
145 }
146
147 $boxesMerged = $this->shippingHelper->mergeBoxesArray(
148 $boxesDimensionWeight,
149 $boxesSingleParcelDimensionWeight
150 );
151
152 $numBoxes = $this->shippingHelper->countBoxesArray( $boxesMerged );
153
154 $this->logger->log( "Num Boxes: " . $numBoxes );
155
156 $shipments = $this->getRates(
157 $destCountry, $destRegion, $city, $destPostCode, $baseSubtotalInclTax, $boxesMerged, $numBoxes, $products, [], 1, $insuranceValue
158 );
159 } elseif ( $shipmentConfigurationMode == self::SHIPMENT_CONFIGURATION_MODE_ONE_SHIPMENT_PER_SHOPPING_CART_ITEMS_MULTI_PARCEL ) {
160 $numBoxes = 0;
161 $insuranceValue = 0;
162 $products = $this->getProductsFromCart($request['contents']);
163
164 foreach ( $request['contents'] as $item ) {
165 $insuranceValue += $this->getSubtotalForInsurance( $item );
166 $numBoxes += $item['quantity'];
167 for ( $i = 1; $i <= $item['quantity']; $i ++ ) {
168 $this->shippingHelper->getBoxesArray(
169 $boxesDimensionWeight,
170 $boxesSingleParcelDimensionWeight,
171 $item['data']->get_weight(),
172 $this->shippingHelper->getPackageInfo( $item['data']->get_sku(), true )
173 );
174 }
175 }
176 $this->logger->log( "Num Boxes: " . $numBoxes );
177 // $boxesSingleParcelDimensionWeight is used directly, since we always use 1 box for each item (we're not using packages CSV)
178 $shipments = $this->getRates( $destCountry, $destRegion, $city, $destPostCode, $baseSubtotalInclTax, $boxesSingleParcelDimensionWeight, $numBoxes, $products, array(), 1, $insuranceValue );
179
180 } else {
181 $this->logger->log( 'SHIPMENT CONFIGURATION MODE - Value not set or incorrect, try to save the settings page again '.($shipmentConfigurationMode?' - '.$shipmentConfigurationMode:''), true);
182 }
183
184
185 //TODO- remove subzone if it is the same for all shipping methods
186
187 $subZones = array();
188 foreach ( $shipments as $shipment ) {
189
190 if ( ! in_array( $shipment->subzone_id, $subZones ) ) {
191 array_push( $subZones, $shipment->subzone_id );
192 }
193
194 }
195 $useSubZone = false;
196 if ( count( $subZones ) > 1 ) {
197 $useSubZone = true;
198 }
199
200 $result = array();
201 if ( empty( $shipments ) ) {
202 $errorTitle = 'Unable to retrieve shipping methods';
203 $this->logger->log( $errorTitle );
204 } else {
205 foreach ( $shipments as $shipment ) {
206
207 // Check the conditions to show delivery point service
208 $showDeliveryPointService = false;
209 if ( $shipment->method === MBE_DELIVERY_POINT_SERVICE ) {
210 $showDeliveryPointService = $this->isDeliveryPointEnabled( $this->shippingHelper->mergeBoxesArray(
211 $boxesDimensionWeight,
212 $boxesSingleParcelDimensionWeight
213 ), $request['contents'] );
214 }
215
216 if($shipment->method !== MBE_DELIVERY_POINT_SERVICE || $showDeliveryPointService) {
217 // Add Rate to the list. In case of the Delivery Point Rate, add additional info
218 if ( $useSubZone ) {
219 $currentRate = $this->_getRate( $shipment->title_full, $shipment->shipment_code, $shipment->price, $shipment->delivery_point_services ?? null, $shipment->tax_and_duties_data ?? null );
220 } else {
221 $currentRate = $this->_getRate( $shipment->title, $shipment->shipment_code, $shipment->price, $shipment->delivery_point_services ?? null, $shipment->tax_and_duties_data ?? null );
222 }
223 $result[] = $currentRate;
224 }
225 }
226 }
227 }
228
229 return $result;
230 }
231
232
233 private function getRates( $destCountry, $destRegion, $city, $destPostCode, $baseSubtotalInclTax, $weight, $boxes, $products, $oldResults = array(), $iteration = 1, $insuranceValue = 0 ) {
234 $shipmentsCache = json_decode( wp_cache_get( self::MBE_CACHE_ID ) );
235 $cachedId = md5( serialize( [
236 $destCountry,
237 $destRegion,
238 $city,
239 $destPostCode,
240 $baseSubtotalInclTax,
241 $weight,
242 $boxes
243 ] ) );
244
245 $ratesHelper = new Mbe_Shipping_Helper_Rates();
246 $ws = new Mbe_Shipping_Model_Ws();
247
248 if ( ! empty( $shipmentsCache ) && $shipmentsCache->id === $cachedId ) {
249 // return cached rates
250 $this->logger->log( "getRates from cache" );
251 $shipments = $shipmentsCache->data;
252 $molRatesforCsv = $shipmentsCache->molRatesforCsv; // retrieve cached mol rates if any for CSV Tax&Duties
253 } else {
254 // delete cache if exists but not for the same request
255 if ( ! empty( $shipmentsCache ) ) {
256 wp_cache_delete( self::MBE_CACHE_ID );
257 }
258 $this->logger->log( "getRates" );
259 $insuranceCode = $this->shippingHelper->getSelectedInsuranceCode();
260
261 if ( $ratesHelper->useCustomRates( $destCountry ) ) {
262 // Use CSV Values
263 $totalWeight = $this->shippingHelper->totalWeightBoxesArray( $weight );
264 $shipments = $ratesHelper->getCustomRates( $destCountry, $destRegion, $city, $destPostCode, $totalWeight, $insuranceValue );
265 if($this->shippingHelper->isEnabledTaxAndDuties()) $molRatesforCsv = $ws->estimateShipping( $destCountry, $destRegion, $city, $destPostCode, $weight, $boxes, $insuranceValue, $products, $insuranceCode );
266 } else {
267 $shipments = $ws->estimateShipping( $destCountry, $destRegion, $city, $destPostCode, $weight, $boxes, $insuranceValue, $products, $insuranceCode );
268 }
269 $this->logger->logVar( $shipments, 'ws estimateShipping result' );
270 // set cache
271 wp_cache_add( self::MBE_CACHE_ID, wp_json_encode( [ 'id' => $cachedId, 'data' => $shipments, 'molRatesforCsv' => $molRatesforCsv??null ] ) );
272 }
273
274 $result = null;
275 if ( $shipments ) {
276 $newResults = [];
277
278 // Adding Delivery point method to the list of allowed services if mbe services are selected
279 if(!empty(array_intersect(MBE_ESTIMATE_DELIVERY_POINT_SERVICES, $this->allowedShipmentServicesArray))) {
280 $this->allowedShipmentServicesArray[] = MBE_DELIVERY_POINT_SERVICE;
281 }
282
283 // Iterates over the shipments and generate the methods list, keeping the last one in case of duplicates
284 foreach ( $shipments as $shipment ) {
285 $shipmentMethod = $shipment->Service;
286
287 $shipmentMethodKey = $shipment->Service . "_" . $shipment->IdSubzone;
288
289
290 if ( in_array( $shipmentMethod, $this->allowedShipmentServicesArray ) ) {
291 $shipmentTitle = __( $shipment->ServiceDesc, 'mail-boxes-etc' );
292 if ( $shipment->SubzoneDesc ) {
293 $shipmentTitle .= " - " . __( $shipment->SubzoneDesc, 'mail-boxes-etc' );
294 }
295
296 $shipmentPrice = $shipment->NetShipmentTotalPrice;
297
298 $shipmentPrice = $this->applyFee( (float)$shipmentPrice, $boxes );
299
300 $shippingThreshold = $this->shippingHelper->getThresholdByShippingService( $shipmentMethod . ( $this->shippingHelper->getCountry() === $destCountry ? '_dom' : '_ww' ) );
301
302 $isFreeShipping = $shippingThreshold != '' && $baseSubtotalInclTax >= $shippingThreshold;
303
304 if ( $isFreeShipping ) {
305 $shipmentPrice = 0;
306 }
307
308
309 $customLabel = esc_html( $this->shippingHelper->getShippingMethodCustomLabel( $shipment->Service ) );
310 $current = new stdClass();
311 $current->title = $customLabel ?: ( __( $shipment->ServiceDesc, 'mail-boxes-etc' ) );
312 $current->title_full = $shipmentTitle;
313 $current->method = $shipmentMethod;
314 $current->price = $shipmentPrice;
315 $current->subzone = $shipment->SubzoneDesc;
316 $current->subzone_id = $shipment->IdSubzone;
317 $current->shipment_code = $shipmentMethodKey;
318
319 // Add all GEL couriers codes and prices to be used in the frontend map widget
320 if(MBE_DELIVERY_POINT_SERVICE === $shipmentMethod) {
321 if( $isFreeShipping ) {
322 $shipment->PriceForMap = array_fill( 0, count( $shipment->PriceForMap??[] ), 0 );
323 } else {
324 $shipment->PriceForMap = array_map(function($value) use ($boxes) {
325 return $this->applyFee($value, $boxes);
326 }, $shipment->PriceForMap??[]);
327 }
328
329 $current->delivery_point_services = [
330 'courier' => $shipment->CourierService ?? [],
331 'mol' => $shipment->MolService ?? [],
332 'prices' => $shipment->PriceForMap,
333 ];
334 }
335
336
337 // If we are using CSV CustomRates retrieve the Tax&Duties data from the WS rates
338 if($ratesHelper->useCustomRates( $destCountry ) && !empty($molRatesforCsv)) {
339 $filteredMolRates = array_filter(
340 $molRatesforCsv,
341 function ($item) use ($shipmentMethod) {
342 return isset($item->Service) && $item->Service === $shipmentMethod;
343 }
344 );
345 // Get the first match if there are multiple matches, or NULL if there is no match.
346 $molService = reset($filteredMolRates);
347
348 if(!empty($molService)) {
349 $shipment->CustomDutiesGuaranteed = $molService->CustomDutiesGuaranteed;
350 $shipment->NetTaxAndDutyTotalPrice = $molService->NetTaxAndDutyTotalPrice;
351 }
352 }
353
354 // Add MBE Easy Duty data
355 if($this->shippingHelper->isEnabledTaxAndDuties() && isset($shipment->CustomDutiesGuaranteed)) {
356 $current->tax_and_duties_data = [
357 'custom_duties_guaranteed' => $shipment->CustomDutiesGuaranteed,
358 'net_tax_and_duty_total_price' => round($shipment->NetTaxAndDutyTotalPrice, 2),
359 ];
360
361 }
362
363 $newResults[ $shipmentMethodKey ] = $current;
364
365 }
366 }
367 if ( $iteration == 1 ) {
368 $result = $newResults;
369 } else {
370
371 foreach ( $newResults as $newResultKey => $newResult ) {
372 if ( array_key_exists( $newResultKey, $oldResults ) ) {
373 $newResult->price += $oldResults[ $newResultKey ]->price;
374 $result[ $newResultKey ] = $newResult;
375 }
376
377 }
378 }
379
380 return $result;
381 }
382 return [];
383 }
384
385 public function applyFee( $value, $packages = 1 ) {
386 $handlingType = $this->shippingHelper->getHandlingType();
387 $handlingAction = $this->shippingHelper->getHandlingAction();
388 $handlingFee = (float)$this->shippingHelper->getHandlingFee();
389
390 if ( $handlingAction == self::HANDLING_TYPE_PER_SHIPMENT ) {
391 $packages = 1;
392 }
393
394 if ( self::HANDLING_TYPE_FIXED == $handlingType ) {
395 //fixed
396 $result = $value + ($handlingFee * $packages);
397 } else {
398 //percent
399 $result = $value * ( 100 + $handlingFee ) / 100;
400 }
401
402 return $result;
403
404 }
405
406 protected function _getRate( $title, $method, $price, $deliveryPointServices = [], $taxAndDutiesData = null) {
407 $price = $this->shippingHelper->round( $price );
408
409 return array( 'label' => $title, 'method' => $method, 'price' => $price, 'delivery_point_services' => $deliveryPointServices, 'tax_and_duties_data' => $taxAndDutiesData);
410 }
411
412 public function isTrackingAvailable() {
413 return true;
414 }
415
416 // TODO : Check if it's better to get items prices from the cart and not from inventory (to avoid free gift to be added to the estimate)
417 protected function getSubtotalForInsurance( $item ) {
418 if ( $this->shippingHelper->getShipmentsInsuranceMode() == Mbe_Shipping_Helper_Data::MBE_INSURANCE_WITH_TAXES ) {
419 if ( version_compare( WC()->version, '3', '>=' ) ) {
420 $insuranceValue = wc_get_price_including_tax( $item["data"], array( 'qty' => $item['quantity'] ) );
421 } else {
422 $insuranceValue = $item["data"]->get_price_including_tax() * $item['quantity'];
423 }
424 } else {
425 if ( version_compare( WC()->version, '3', '>=' ) ) {
426 $insuranceValue = wc_get_price_excluding_tax( $item["data"], array( 'qty' => $item['quantity'] ) );
427 } else {
428 $insuranceValue = $item["data"]->get_price_excluding_tax() * $item['quantity'];
429 }
430 }
431
432 return $insuranceValue;
433 }
434
435
436 /**
437 * @param $boxes
438 * If shipping mode is SHIPMENT_CONFIGURATION_MODE_ONE_SHIPMENT_PER_ITEM $boxes is the last one used for getRates, it's always a "settings" box as CSV it's not used for this mode,
439 * so it must be used for dimensions check only since it contains only 1 item
440 * In the other cases it's the proper list of boxes for the shipment
441 * @param $items
442 *
443 * @return bool
444 */
445 private function isDeliveryPointEnabled( $boxes, $items ) {
446 $oneParcel = (
447 ( $this->shippingHelper->getShipmentConfigurationMode() == self::SHIPMENT_CONFIGURATION_MODE_ONE_SHIPMENT_PER_ITEM ) ||
448 ( $this->shippingHelper->countBoxesArray( $boxes ) === 1 &&
449 in_array( $this->shippingHelper->getShipmentConfigurationMode(), [
450 self::SHIPMENT_CONFIGURATION_MODE_ONE_SHIPMENT_PER_SHOPPING_CART_WEIGHT_MULTI_PARCEL,
451 self::SHIPMENT_CONFIGURATION_MODE_ONE_SHIPMENT_PER_SHOPPING_CART_ITEMS_MULTI_PARCEL
452 ] )
453 )
454 );
455
456 // Check longest size of the last box used for getRates. This must be modified if CSV will be enabled for SHIPMENT_CONFIGURATION_MODE_ONE_SHIPMENT_PER_ITEM shipping mode
457 // Nested if to avoid useless check and operations
458 if ( $oneParcel ) {
459 // $longestSize = $this->shippingHelper->longestSizeBoxesArray( $boxes );
460 // $box = $boxes[ $this->shippingHelper->arrayKeyFirst( $boxes ) ]; // since it's one parcel we can check only the first element of the array
461 // Longest Size Ok and Total Size Ok
462 // if ( $longestSize <= MBE_UAP_LONGEST_LIMIT_97_CM
463 // && ( $longestSize + ( 2 * $box['dimensions']['width'] ) + ( 2 * $box['dimensions']['height'] ) ) <= MBE_UAP_TOTAL_SIZE_LIMIT_300_CM ) {
464
465 $weightOk = true;
466 if ( $this->shippingHelper->getShipmentConfigurationMode() == Mbe_Shipping_Model_Carrier::SHIPMENT_CONFIGURATION_MODE_ONE_SHIPMENT_PER_ITEM ) {
467 foreach ( $items as $item ) {
468 if ( $this->shippingHelper->convertWeight( $item['data']->get_weight() ) > MBE_DELIVERY_POINT_WEIGHT_LIMIT ) {
469 $weightOk = false;
470 break;
471 }
472 }
473 } else {
474 $weightOk = $this->shippingHelper->convertWeight( $this->shippingHelper->totalWeightBoxesArray( $boxes ) ) <= MBE_DELIVERY_POINT_WEIGHT_LIMIT;
475 }
476 if ( $weightOk ) {
477 // All the checks are OK
478 return true;
479 }
480 // }
481 }
482
483 return false;
484 }
485
486 protected function getProductsFromCart($cart, $single = false) {
487 $products = array();
488
489 foreach ( $cart as $item ) {
490 $product = $this->shippingHelper->getProductFromItem( $item );
491
492 $p = new stdClass;
493 $p->SKUCode = $product->get_sku();
494 $p->Description = $this->shippingHelper->getProductTitleFromItem( $item );
495 $p->Quantity = ($single?1:$item['quantity']);
496 $p->Price = $product->get_price();
497 $p->Currency = get_woocommerce_currency();
498
499 $products[] = $p;
500 }
501 return $products;
502 }
503
504 }