diff --git a/src/Automation.php b/src/Automation.php index 033a9ee4..e39a30a1 100644 --- a/src/Automation.php +++ b/src/Automation.php @@ -225,8 +225,6 @@ public static function create_shipments( $order ) { if ( $order_shipment->needs_shipping() ) { if ( $order_shipment->has_auto_packing() ) { - $items_to_pack = $order_shipment->get_items_to_pack_left_for_shipping(); - if ( $method = $order_shipment->get_builtin_shipping_method() ) { $packaging_boxes = $method->get_method()->get_available_packaging_boxes(); } else { @@ -239,48 +237,47 @@ public static function create_shipments( $order ) { $packaging_boxes = Helper::get_packaging_boxes( $available_packaging ); } - foreach ( $items_to_pack as $group => $items ) { - $packed_boxes = Helper::pack( $items, $packaging_boxes, 'order' ); + $items = $order_shipment->get_items_to_pack_left_for_shipping(); + $packed_boxes = Helper::pack( $items, $packaging_boxes, 'order' ); + + foreach ( $packed_boxes as $box ) { + $packaging = $box->getBox(); + $items = $box->getItems(); + $shipment_items = array(); - foreach ( $packed_boxes as $box ) { - $packaging = $box->getBox(); - $items = $box->getItems(); - $shipment_items = array(); + foreach ( $items as $item ) { + $order_item = $item->getItem(); - foreach ( $items as $item ) { - $order_item = $item->getItem(); + if ( ! isset( $shipment_items[ $order_item->get_id() ] ) ) { + $shipment_items[ $order_item->get_id() ] = 1; + } else { + $shipment_items[ $order_item->get_id() ]++; + } + } - if ( ! isset( $shipment_items[ $order_item->get_id() ] ) ) { - $shipment_items[ $order_item->get_id() ] = 1; - } else { - $shipment_items[ $order_item->get_id() ]++; - } + $shipment = wc_gzd_create_shipment( + $order_shipment, + array( + 'items' => $shipment_items, + 'props' => array( + 'packaging_id' => $packaging->get_id(), + 'status' => $shipment_status, + ), + ) + ); + + if ( ! is_wp_error( $shipment ) ) { + $order_shipment->add_shipment( $shipment ); + + $shipments_created[ $shipment->get_id() ] = $shipment; + } else { + foreach ( $shipments_created as $id => $shipment_created ) { + $shipment_created->delete( true ); + $order_shipment->remove_shipment( $id ); } - $shipment = wc_gzd_create_shipment( - $order_shipment, - array( - 'items' => $shipment_items, - 'props' => array( - 'packaging_id' => $packaging->get_id(), - 'status' => $shipment_status, - ), - ) - ); - - if ( ! is_wp_error( $shipment ) ) { - $order_shipment->add_shipment( $shipment ); - - $shipments_created[ $shipment->get_id() ] = $shipment; - } else { - foreach ( $shipments_created as $id => $shipment_created ) { - $shipment_created->delete( true ); - $order_shipment->remove_shipment( $id ); - } - - foreach ( $shipment->get_error_messages() as $code => $message ) { - $errors->add( $code, $message ); - } + foreach ( $shipment->get_error_messages() as $code => $message ) { + $errors->add( $code, $message ); } } } diff --git a/src/Order.php b/src/Order.php index ff5803fd..4f957359 100644 --- a/src/Order.php +++ b/src/Order.php @@ -2,9 +2,9 @@ namespace Vendidero\Germanized\Shipments; -use DVDoug\BoxPacker\ItemList; use Exception; use Vendidero\Germanized\Shipments\Packing\Helper; +use Vendidero\Germanized\Shipments\Packing\ItemList; use Vendidero\Germanized\Shipments\Packing\OrderItem; use Vendidero\Germanized\Shipments\ShippingMethod\MethodHelper; use Vendidero\Germanized\Shipments\ShippingMethod\ProviderMethod; @@ -542,29 +542,38 @@ public function get_item_quantity_left_for_returning( $order_item_id, $args = ar /** * @param false $legacy_group_by_product_group * - * @return OrderItem[] + * @return ItemList|OrderItem[] */ public function get_items_to_pack_left_for_shipping( $legacy_group_by_product_group = null ) { $items = $this->get_available_items_for_shipment(); - $items_to_be_packed = array(); + $items_to_be_packed = ! is_null( $legacy_group_by_product_group ) ? array() : new ItemList(); foreach ( $items as $order_item_id => $item ) { if ( ! $order_item = $this->get_order()->get_item( $order_item_id ) ) { continue; } - $product_group = ''; + $box_item = new Packing\OrderItem( $order_item ); - if ( $product = $order_item->get_product() ) { - $product_group = Helper::get_product_packing_group( $product ); - } + if ( ! is_null( $legacy_group_by_product_group ) ) { + $product_group = ''; - if ( ! array_key_exists( $product_group, $items_to_be_packed ) ) { - $items_to_be_packed[ $product_group ] = new ItemList(); - } + if ( $product = $order_item->get_product() ) { + $product_group = ''; - $box_item = new Packing\OrderItem( $order_item ); - $items_to_be_packed[ $product_group ]->insert( $box_item, $item['max_quantity'] ); + if ( 'yes' === get_option( 'woocommerce_gzd_shipments_packing_group_by_shipping_class' ) ) { + $product_group = $product->get_shipping_class(); + } + } + + if ( ! array_key_exists( $product_group, $items_to_be_packed ) ) { + $items_to_be_packed[ $product_group ] = new ItemList(); + } + + $items_to_be_packed[ $product_group ]->insert( $box_item, $item['max_quantity'] ); + } else { + $items_to_be_packed->insert( $box_item, $item['max_quantity'] ); + } } return apply_filters( 'woocommerce_gzd_shipment_order_items_to_pack_left_for_shipping', $items_to_be_packed ); diff --git a/src/Packing/Helper.php b/src/Packing/Helper.php index 1804843c..871960cb 100644 --- a/src/Packing/Helper.php +++ b/src/Packing/Helper.php @@ -76,22 +76,7 @@ public static function enable_auto_packing() { } /** - * @param \WC_Product $product - * - * @return string - */ - public static function get_product_packing_group( $product ) { - $group = ''; - - if ( 'yes' === get_option( 'woocommerce_gzd_shipments_packing_group_by_shipping_class' ) ) { - $group = $product->get_shipping_class(); - } - - return apply_filters( 'woocommerce_gzd_product_packing_group', $group, $product ); - } - - /** - * @param PackingItem[] $items + * @param \Vendidero\Germanized\Shipments\Packing\ItemList $items * @param PackingBox[]|BoxList $boxes * * @return PackedBoxList diff --git a/src/Packing/Item.php b/src/Packing/Item.php index 00316c72..65688f67 100644 --- a/src/Packing/Item.php +++ b/src/Packing/Item.php @@ -53,6 +53,21 @@ public function canBePacked( $box, $already_packed_items, int $proposed_x, int $ $fits = false; } } + + /** + * In case grouping is activated make sure that a new item with a different shipping class + * is not being packed within the same already existing package. + */ + if ( $fits && 'yes' === get_option( 'woocommerce_gzd_shipments_packing_group_by_shipping_class' ) ) { + $count = $already_packed_items->count(); + $last_item = $count > 0 ? $already_packed_items->getIterator()[ $count - 1 ]->getItem() : false; + + if ( $last_item && ( $last_product = $last_item->get_product() ) ) { + if ( $last_product->get_shipping_class_id() !== $shipping_class ) { + $fits = false; + } + } + } } return apply_filters( 'woocommerce_gzd_shipments_item_fits_packaging', $fits, $this, $box->get_packaging(), $already_packed_items, $args ); diff --git a/src/Packing/ItemList.php b/src/Packing/ItemList.php new file mode 100644 index 00000000..bd02f5b7 --- /dev/null +++ b/src/Packing/ItemList.php @@ -0,0 +1,12 @@ + $content ) { - $items = array(); + $items = new ItemList(); foreach ( $content['contents'] as $content_key => $data ) { - $cart_item = new CartItem( $data, wc()->cart->display_prices_including_tax() ); - $product_group = ''; - - if ( $product = $cart_item->get_product() ) { - $product_group = \Vendidero\Germanized\Shipments\Packing\Helper::get_product_packing_group( $product ); - } - - if ( ! array_key_exists( $product_group, $items ) ) { - $items[ $product_group ] = new ItemList(); - } - - $items[ $product_group ]->insert( $cart_item, $data['quantity'] ); + $cart_item = new CartItem( $data, wc()->cart->display_prices_including_tax() ); + $items->insert( $cart_item, $data['quantity'] ); } $cart_contents[ $index ]['items_to_pack'] = $items; diff --git a/src/ShippingMethod/ShippingMethod.php b/src/ShippingMethod/ShippingMethod.php index d76919f8..a07dbf71 100644 --- a/src/ShippingMethod/ShippingMethod.php +++ b/src/ShippingMethod/ShippingMethod.php @@ -387,54 +387,81 @@ public function calculate_shipping( $package = array() ) { $packaging_ids = array(); $total_packed_item_map = array(); $total_packed_items = 0; + $packed_boxes = Helper::pack( $package['items_to_pack'], $boxes, 'cart' ); + + foreach ( $packed_boxes as $box ) { + $packaging = $box->getBox(); + $items = $box->getItems(); + $total_weight = wc_get_weight( $items->getWeight(), strtolower( get_option( 'woocommerce_weight_unit' ) ), 'g' ); + $volume = wc_get_dimension( $items->getVolume(), strtolower( get_option( 'woocommerce_dimension_unit' ) ), 'mm' ); + $item_count = $items->count(); + $total = 0; + $subtotal = 0; + $products = array(); + $shipping_classes = array(); + + foreach ( $items as $item ) { + $cart_item = $item->getItem(); + $total += $cart_item->getTotal(); + $subtotal += $cart_item->getSubtotal(); + $product = $cart_item->get_product(); + + if ( $product && ! array_key_exists( $product->get_id(), $products ) ) { + $products[ $product->get_id() ] = $product; + + if ( ! empty( $product->get_shipping_class_id() ) ) { + $shipping_classes[] = $product->get_shipping_class_id(); + } + } + } - foreach ( $package['items_to_pack'] as $product_group => $items_to_pack ) { - $packed_boxes = Helper::pack( $items_to_pack, $boxes, 'cart' ); - - foreach ( $packed_boxes as $box ) { - $packaging = $box->getBox(); - $items = $box->getItems(); - $total_weight = wc_get_weight( $items->getWeight(), strtolower( get_option( 'woocommerce_weight_unit' ) ), 'g' ); - $volume = wc_get_dimension( $items->getVolume(), strtolower( get_option( 'woocommerce_dimension_unit' ) ), 'mm' ); - $item_count = $items->count(); - $total = 0; - $subtotal = 0; - $products = array(); - $shipping_classes = array(); + $total = wc_remove_number_precision( $total ); + $subtotal = wc_remove_number_precision( $subtotal ); + $shipping_classes = array_unique( $shipping_classes ); + $package_data = array( + 'total' => $total, + 'subtotal' => $subtotal, + 'weight' => $total_weight, + 'volume' => $volume, + 'item_count' => $item_count, + 'packaging_id' => $packaging->get_id(), + 'products' => $products, + 'shipping_classes' => $shipping_classes, + ); - foreach ( $items as $item ) { - $cart_item = $item->getItem(); - $total += $cart_item->getTotal(); - $subtotal += $cart_item->getSubtotal(); - $product = $cart_item->get_product(); + $package_applied_rules = array(); + $applicable_rule_costs = array(); - if ( $product && ! array_key_exists( $product->get_id(), $products ) ) { - $products[ $product->get_id() ] = $product; + foreach ( array_reverse( $this->get_shipping_rules_by_packaging( $packaging->get_id() ) ) as $rule ) { + $rule = $this->parse_rule( $rule ); + $rule_applies = $this->rule_applies( $rule, $package_data ); - if ( ! empty( $product->get_shipping_class_id() ) ) { - $shipping_classes[] = $product->get_shipping_class_id(); - } - } + if ( $rule_applies ) { + $applicable_rule_costs[] = $rule['costs']; + $package_applied_rules[] = $rule['rule_id']; } - $total = wc_remove_number_precision( $total ); - $subtotal = wc_remove_number_precision( $subtotal ); - $shipping_classes = array_unique( $shipping_classes ); - $package_data = array( - 'total' => $total, - 'subtotal' => $subtotal, - 'weight' => $total_weight, - 'volume' => $volume, - 'item_count' => $item_count, - 'packaging_id' => $packaging->get_id(), - 'products' => $products, - 'shipping_classes' => $shipping_classes, - ); + /** + * In case a free shipping option is detected, stop + reset. + */ + if ( $rule_applies && 0.0 === $rule['costs'] ) { + $applicable_rule_costs = array( + $rule['costs'], + ); + + $package_applied_rules = array( + $rule['rule_id'], + ); - $package_applied_rules = array(); - $applicable_rule_costs = array(); + break; + } + } - foreach ( array_reverse( $this->get_shipping_rules_by_packaging( $packaging->get_id() ) ) as $rule ) { + /** + * In case no applicable rule has been found, parse fallback rules. + */ + if ( empty( $package_applied_rules ) ) { + foreach ( array_reverse( $this->get_fallback_shipping_rules() ) as $rule ) { $rule = $this->parse_rule( $rule ); $rule_applies = $this->rule_applies( $rule, $package_data ); @@ -458,114 +485,84 @@ public function calculate_shipping( $package = array() ) { break; } } + } - /** - * In case no applicable rule has been found, parse fallback rules. - */ - if ( empty( $package_applied_rules ) ) { - foreach ( array_reverse( $this->get_fallback_shipping_rules() ) as $rule ) { - $rule = $this->parse_rule( $rule ); - $rule_applies = $this->rule_applies( $rule, $package_data ); - - if ( $rule_applies ) { - $applicable_rule_costs[] = $rule['costs']; - $package_applied_rules[] = $rule['rule_id']; - } - - /** - * In case a free shipping option is detected, stop + reset. - */ - if ( $rule_applies && 0.0 === $rule['costs'] ) { - $applicable_rule_costs = array( - $rule['costs'], - ); + if ( ! empty( $package_applied_rules ) ) { + $applicable_rules_total_cost = 0.0; - $package_applied_rules = array( - $rule['rule_id'], - ); + if ( 'sum' === $multiple_rules_calculation_mode ) { + $applicable_rules_total_cost = array_sum( $applicable_rule_costs ); + } elseif ( 'min' === $multiple_rules_calculation_mode ) { + $applicable_rules_total_cost = min( $applicable_rule_costs ); + } elseif ( 'max' === $multiple_rules_calculation_mode ) { + $applicable_rules_total_cost = max( $applicable_rule_costs ); + } - break; - } + if ( 'min' === $cost_calculation_mode ) { + if ( $applicable_rules_total_cost <= $total_cost || 0.0 === $total_cost ) { + $total_cost = $applicable_rules_total_cost; } + } elseif ( 'max' === $cost_calculation_mode ) { + if ( $applicable_rules_total_cost >= $total_cost ) { + $total_cost = $applicable_rules_total_cost; + } + } else { + $total_cost += $applicable_rules_total_cost; } - if ( ! empty( $package_applied_rules ) ) { - $applicable_rules_total_cost = 0.0; + /** + * Build an item map which contains a map of the cart items + * included within the package. + */ + $item_map = array(); + + foreach ( $items as $item ) { + $cart_item_wrapper = $item->getItem(); + $product = $cart_item_wrapper->get_product(); + $product_key = $product->get_parent_id() . '_' . $product->get_id(); - if ( 'sum' === $multiple_rules_calculation_mode ) { - $applicable_rules_total_cost = array_sum( $applicable_rule_costs ); - } elseif ( 'min' === $multiple_rules_calculation_mode ) { - $applicable_rules_total_cost = min( $applicable_rule_costs ); - } elseif ( 'max' === $multiple_rules_calculation_mode ) { - $applicable_rules_total_cost = max( $applicable_rule_costs ); + if ( array_key_exists( $product_key, $item_map ) ) { + $item_map[ $product_key ]++; + } else { + $item_map[ $product_key ] = 1; } - if ( 'min' === $cost_calculation_mode ) { - if ( $applicable_rules_total_cost <= $total_cost || 0.0 === $total_cost ) { - $total_cost = $applicable_rules_total_cost; - } - } elseif ( 'max' === $cost_calculation_mode ) { - if ( $applicable_rules_total_cost >= $total_cost ) { - $total_cost = $applicable_rules_total_cost; - } + if ( array_key_exists( $product_key, $total_packed_item_map ) ) { + $total_packed_item_map[ $product_key ]++; } else { - $total_cost += $applicable_rules_total_cost; + $total_packed_item_map[ $product_key ] = 1; } - /** - * Build an item map which contains a map of the cart items - * included within the package. - */ - $item_map = array(); - - foreach ( $items as $item ) { - $cart_item_wrapper = $item->getItem(); - $product = $cart_item_wrapper->get_product(); - $product_key = $product->get_parent_id() . '_' . $product->get_id(); - - if ( array_key_exists( $product_key, $item_map ) ) { - $item_map[ $product_key ]++; - } else { - $item_map[ $product_key ] = 1; - } - - if ( array_key_exists( $product_key, $total_packed_item_map ) ) { - $total_packed_item_map[ $product_key ]++; - } else { - $total_packed_item_map[ $product_key ] = 1; - } - - $total_packed_items++; - } + $total_packed_items++; + } - $applied_rules[] = array( - 'packaging_id' => $packaging->get_id(), - 'rules' => $package_applied_rules, - 'items' => $item_map, - ); + $applied_rules[] = array( + 'packaging_id' => $packaging->get_id(), + 'rules' => $package_applied_rules, + 'items' => $item_map, + ); - $rule_ids = array_unique( array_merge( $rule_ids, $package_applied_rules ) ); - $packaging_ids = array_unique( array_merge( $packaging_ids, array( $packaging->get_id() ) ) ); - } + $rule_ids = array_unique( array_merge( $rule_ids, $package_applied_rules ) ); + $packaging_ids = array_unique( array_merge( $packaging_ids, array( $packaging->get_id() ) ) ); } } + } - if ( ! empty( $applied_rules ) ) { - $this->add_rate( - array( - 'cost' => $total_cost, - 'label' => $this->get_rate_label( $total_cost ), - 'package' => $package, - 'meta_data' => array( - '_packed_items' => $total_packed_items, - '_packed_item_map' => $total_packed_item_map, - '_packaging_ids' => $packaging_ids, - '_rule_ids' => $rule_ids, - '_packages' => $applied_rules, - ), - ) - ); - } + if ( ! empty( $applied_rules ) ) { + $this->add_rate( + array( + 'cost' => $total_cost, + 'label' => $this->get_rate_label( $total_cost ), + 'package' => $package, + 'meta_data' => array( + '_packed_items' => $total_packed_items, + '_packed_item_map' => $total_packed_item_map, + '_packaging_ids' => $packaging_ids, + '_rule_ids' => $rule_ids, + '_packages' => $applied_rules, + ), + ) + ); } }