• Resolved HelgaTheViking

    (@helgatheviking)


    I am the author of WooCommerce Mix and Match Products. My plugin creates bundles/packs of similar products. For example, a 6 pack of wine, where a customer can choose which 6 bottles of wine.

    In the cart you would see
    6 pack
    3 x bottles red wine
    3 x bottles white wine

    Frequently, these items are shipped together as a unit and that’s controlled by a setting in our plugin. In the cart, we set the individual bottles to be virtual so most shippers ignore them. Your plugin is generating the shipping label from the order and so is trying to add costs for the bottles that are already accounted for.

    If I am reading it correctly, this is the code responsible:

    <?php
    
    defined('ABSPATH') || die('Restricted Access');
    
    class LpcLabelOutwardGenerateAction extends LpcComponent {
        const AJAX_TASK_NAME = 'label/outward/create';
        const ACTION_ID_PARAM_NAME = 'lpc_label_outward_id';
    
        /** @var LpcAjax */
        protected $ajaxDispatcher;
        /** @var LpcLabelGenerationOutward */
        protected $labelGenerationOutward;
    
        public function __construct(
            LpcAjax $ajaxDispatcher = null,
            LpcLabelGenerationOutward $labelGenerationOutward = null
        ) {
            $this->ajaxDispatcher         = LpcRegister::get('ajaxDispatcher', $ajaxDispatcher);
            $this->labelGenerationOutward = LpcRegister::get('labelGenerationOutward', $labelGenerationOutward);
        }
    
        public function getDependencies() {
            return ['ajaxDispatcher', 'labelGenerationOutward'];
        }
    
        public function init() {
            $this->listenToAjaxAction();
        }
    
        protected function listenToAjaxAction() {
            $this->ajaxDispatcher->register(self::AJAX_TASK_NAME, [$this, 'control']);
        }
    
        public function generateUrl($oneOrderId) {
            return $this->ajaxDispatcher->getUrlForTask(self::AJAX_TASK_NAME) .
                   '&' . self::ACTION_ID_PARAM_NAME . '=' . (int) $oneOrderId;
        }
    
        public function control() {
            if (!current_user_can('lpc_manage_labels')) {
                header('HTTP/1.0 401 Unauthorized');
    
                return $this->ajaxDispatcher->makeAndLogError(
                    [
                        'message' => 'unauthorized access to create new outward label',
                    ]
                );
            }
            $urlRedirection = admin_url('admin.php?page=wc_colissimo_view');
            $orderId        = LpcHelper::getVar(self::ACTION_ID_PARAM_NAME);
            $order          = new WC_Order($orderId);
    
            $this->labelGenerationOutward->generate($order, ['items' => $order->get_items()], true);
            wp_redirect($urlRedirection);
        }
    }
    

    so specifically this line:

    $this->labelGenerationOutward->generate($order, ['items' => $order->get_items()], true);

    (though I see several locations for generating labels) Would you be able to add an action hook before generating the label? Specifically, before you call $order->get_items()

    We do have some functions we can sometimes use to restore the shipping configuration of bundled products but would need an appropriate place to attach those hooks.

    As an example, my ShipStation compatibility adds the appropriate filters only when we know we are in the context of a ShipStation request:

    /**
     * WC_MNM_Shipstation_Compatibility Class.
     *
     * Adds compatibility with WooCommerce ShipStation.
     */
    class WC_MNM_Shipstation_Compatibility {
    
    	public static function init() {
    
    		// Shipstation compatibility.
    		add_action( 'woocommerce_api_wc_shipstation', array( __CLASS__, 'add_filters' ), 5 );
    	}
    
    	/**
    	 * Modify the returned order items and products to return the correct items/weights/values for shipping.
    	 */
    	public static function add_filters() {
    		add_filter( 'woocommerce_order_get_items', array( WC_Mix_and_Match()->order, 'get_order_items' ), 10, 2 );
    		add_filter( 'woocommerce_order_item_product', array( WC_Mix_and_Match()->order, 'get_product_from_item' ), 10, 2 );
    	}
    }
    
    WC_MNM_Shipstation_Compatibility::init();
    

    I am thinking perhaps on the same hook you are using for ajax callbacks, but

    add_action('wp_ajax_' . LPC_COMPONENT, [$this, 'dispatch']); // Logged in users

    has a constant that I am not sure of yet.

    Thanks!

    I can read replies in French if that helps… but my written French is mediocre. ??

Viewing 4 replies - 1 through 4 (of 4 total)
  • Plugin Author Colissimo

    (@iscpcolissimo)

    Hello Helga,

    I can add a hook if necessary, but the products are used in multiple places (when generating a label, when building the invoice, when collecting the CN23 information, when calculating the price to show for the shipping methods, etc…).

    If your plugin sets the products as virtual in the order, it may be even better that our plugin ignores the virtual products (regardless of any integration, it should be the case as it makes no sense to take them into account).

    Just to be sure, would this code be enough for both our plugins to be compatible when we handle cart/order products in various places in our plugin?

    foreach ($order->get_items() as $item) {
        $product = $item->get_product();
    
        if ($product->is_virtual()) {
            continue;
        }
    
        [...]
    }

    Would it be ok for you to wait a few weeks for us to modify our plugin in order to ignore the virtual products?
    If you need to modify a client’s site to add a hook, here are all the files that get the order’s products to use them:

    • admin/labels/generate/lpc_label_inward_generate_action.php
    • admin/labels/generate/lpc_label_outward_generate_action.php
    • admin/orders/lpc_admin_order_banner.php
    • admin/orders/lpc_orders_table.php
    • admin/orders/lpc_woo_orders_table_action.php
    • admin/orders/lpc_woo_orders_table_bulk_actions.php
    • includes/label/lpc_label_generation_auto.php
    • includes/label/lpc_label_generation_outward.php
    • includes/label/lpc_label_generation_payload.php
    Thread Starter HelgaTheViking

    (@helgatheviking)

    Thank you for this comprehensive reply! Are all of those files different places where a paid label/étiquette is generated?

    The key piece for compatibility is that Colissimo does not add a weight/cost to items that do not need shipping. needs_shipping() is more appropriate than is_virtual() in my opinion as it’s what WooCommerce core uses. I don’t see either method in your plugin… are you not already checking that? Or did I just miss it?

    If not, there’s no way to be compatible with any plugin like mine that creates a complicated product type (see also Product Bundles and Composite Products).

    I have worked with the Shipstation plugin which also creates labels from the order and its order items… And they do something like the following, which is pretty ideal:

    $order_items = $order->get_items() + $order->get_items( 'fee' );
    foreach ( $order_items as $item_id => $item ) {
    	$product                = is_callable( array( $item, 'get_product' ) ) ? $item->get_product() : false;
    	$item_needs_no_shipping = ! $product || ! $product->needs_shipping();
    	$item_not_a_fee         = 'fee' !== $item->get_type();
    	if ( apply_filters( 'woocommerce_shipstation_no_shipping_item', $item_needs_no_shipping && $item_not_a_fee, $product, $item ) ) {
    		continue;
    	}
    
    	// truncated for brevity.
    }

    They’ve also got a hook woocommerce_api_wc_shipstation that runs before every one of their API requests so it’s easy to hook in to modify things only in the Shipstation context (I was attempting to do that via your ajax action). I restore the parent/child weights in orders on a case-by-case basis…. largely because the code that handles this is 10 years old, complicated, and not written by me and is also not really needed in the regular WC order admin.

    As a followup question, if the child items (the bottles in my example) are skipped there is still a need to mark on the label which bottles were selected. I do that typically by adding a meta key/value to the container box’s order item. Order item meta is frequently printed on the labels… is that the case in your plugin too? (Here’s an example in an email template and Shipstation also exports the order item’s get_formatted_meta_data() for printing later.

    I will be on congé annuel next month so I won’t be able to pick this up. But I will let my customer know about this thread so they can track it and potentially look for a developer to add compatibility.

    Thread Starter HelgaTheViking

    (@helgatheviking)

    One last thing (I hope)… It looks like the lpc_label_generation_payload.php file calculates the total weight of a box?

    public function withPackage(WC_Order $order, $customParams = []) {
        if (isset($customParams['totalWeight'])) {
            $totalWeight = wc_get_weight($customParams['totalWeight'], 'kg');
        } else {
            $totalWeight = 0;
            foreach ($order->get_items() as $item) {
                $data    = $item->get_data();
                $product = $item->get_product();
                if (empty($product)) {
                    throw new Exception(
                        __('The product couldn\'t be found.', 'wc_colissimo')
                    );
                }
                $productWeight = $product->get_weight() < 0.01 ? 0.01 : $product->get_weight();
                $weight        = (float) $productWeight * $data['quantity'];
    
                if ($weight < 0) {
                    throw new Exception(
                        __('Weight cannot be negative!', 'wc_colissimo')
                    );
                }
    
                $weightInKg  = wc_get_weight($weight, 'kg');
                $totalWeight += $weightInKg;
            }
    
            $packagingWeight = wc_get_weight(LpcHelper::get_option('lpc_packaging_weight', '0'), 'kg');
            $totalWeight     += $packagingWeight;
        }
    
        if ($totalWeight < 0.01) {
            $totalWeight = 0.01;
        }
    
    //truncated...

    In my compatibility code I do set the weight to a null string… though not really sure why it’s null instead of 0:

    $product->set_virtual( 'yes' );
    $product->set_weight( '' );

    And presumably there’s a box packer that is looking at dimensions?? I might be able to set 0/null dimensions if that would help your side not charge for the child items.

    Plugin Author Colissimo

    (@iscpcolissimo)

    Hello,

    Are all of those files different places where a paid label/étiquette is generated?
    Most of them (there are multiple ways to generate labels, and outward/inward labels).

    needs_shipping() is more appropriate than is_virtual() in my opinion
    I’ll use it then, I have started the development of this modification.

    are you not already checking that? Or did I just miss it?
    No, the plugin doesn’t check this yet.

    Order item meta is frequently printed on the labels… is that the case in your plugin too?
    No, the label is generated on the Colissimo side and only contains the sending and shipping addresses.

    Maybe you’re talking about the CN23 form? It contains the shipped products list.
    For each product, the information shown is the product’s name (in a “description” field), SKU, price, currency, HS code, origin country, quantity and weight.

    I guess we could try to add the meta data in the description field, but the Colissimo API only accepts up to 64 characters in this field.

    The Colissimo API will refuse to generate the label if one of the products specified in the CN23 form has no weight.

    And presumably there’s a box packer that is looking at dimensions?
    The dimensions are only necessary when shipping with Delivery Duty Paid. Although if the products has a “needs_shipping” to false, we will ignore it in the package definition after my modifications. The same goes for the weight of the product.

Viewing 4 replies - 1 through 4 (of 4 total)
  • The topic ‘Compatibilite avec Mix and Match Products’ is closed to new replies.