getdokan / dokan

Multivendor marketplace platform
https://wordpress.org/plugins/dokan-lite/
252 stars 199 forks source link

Suborder Status Incorrectly Changes from Canceled to Completed When Admin Completes Main Order #2341

Open Fayeakuzzamantonmoy opened 3 weeks ago

Fayeakuzzamantonmoy commented 3 weeks ago

Problem:
When a customer completes a purchase involving at least two vendors within one order, a single order is generated in the admin panel, which contains two suborders. Each vendor can view and manage their respective suborder in their vendor dashboard.

Issue:
If a vendor marks their suborder as canceled from the vendor dashboard, the suborder is correctly marked as canceled in the admin panel. However, if the admin later marks the entire order as completed, the previously canceled suborder is incorrectly marked as completed as well.

Steps to Reproduce

  1. Place an order as a customer, selecting products from at least two different vendors.
  2. Observe that one main order with suborders is generated in the admin panel.
  3. Each vendor can see their respective suborder in their vendor dashboard.
  4. Have one vendor mark their suborder as canceled from their dashboard.
  5. Check the admin panel to confirm the suborder is marked as canceled.
  6. Admin then marks the entire order as completed.
  7. The previously canceled suborder is incorrectly marked as completed.

Expected Behavior

Impact

Suggested Solution

Environment

mralaminahamed commented 2 weeks ago

@getdokan/product @mrabbani

Regarding the question of which statuses should be whitelisted for updating suborders when an admin updates the parent order, I propose the following whitelist:

  1. Pending Payment: Can be updated to any status
  2. On Hold: Can be updated to any status except Canceled and Refunded
  3. Processing: Can be updated to Completed, Failed, Canceled, or Refunded
  4. Completed: Can only be updated to Refunded
  5. Failed: Can be updated to any status except Completed
  6. Canceled: Cannot be automatically updated to any other status
  7. Refunded: Cannot be automatically updated to any other status

This whitelist aims to prevent issues like the one reported in #2341, where a canceled suborder was incorrectly marked as completed. By treating Canceled and Refunded as final states that can't be automatically changed, we ensure that vendor-canceled orders remain canceled regardless of parent order updates.

@Mohaiminulislam1989 vai, let me know what approach we should follow

@getdokan/product

CC: @mrabbani vai

Mohaiminulislam1989 commented 2 weeks ago

@getdokan/product @mrabbani

Regarding the question of which statuses should be whitelisted for updating suborders when an admin updates the parent order, I propose the following whitelist:

  1. Pending Payment: Can be updated to any status
  2. On Hold: Can be updated to any status except Canceled and Refunded
  3. Processing: Can be updated to Completed, Failed, Canceled, or Refunded
  4. Completed: Can only be updated to Refunded
  5. Failed: Can be updated to any status except Completed
  6. Canceled: Cannot be automatically updated to any other status
  7. Refunded: Cannot be automatically updated to any other status

This whitelist aims to prevent issues like the one reported in #2341, where a canceled suborder was incorrectly marked as completed. By treating Canceled and Refunded as final states that can't be automatically changed, we ensure that vendor-canceled orders remain canceled regardless of parent order updates.

@Mohaiminulislam1989 vai, let me know what approach we should follow

@getdokan/product

CC: @mrabbani vai

Your whitelist is correct, alhamdulillah.

mralaminahamed commented 2 weeks ago

Implementation Plan: Update Sub-order Status

Objective

Update the on_order_status_change method in the Dokan to handle sub-order status changes based on the main order status update, following a customizable whitelist and preventing incorrect status updates.

Steps

  1. Create a new private method is_status_change_allowed

    • Input: current status, new status
    • Output: boolean (true if change is allowed, false otherwise)
    • Implement the whitelist logic with a filter for customization
  2. Modify the on_order_status_change method

    • Update the existing method to use the new whitelist check
    • Implement status update logic for sub-orders
  3. Add logging for skipped status updates

    • Implement a logging mechanism to track skipped status updates

Detailed Implementation

  1. Create the new is_status_change_allowed method:
/**
 * Check if a status change is allowed for a sub-order.
 *
 * @since x.x.x
 *
 * @param string $current_status The current status of the sub-order (should include 'wc-' prefix).
 * @param string $new_status     The new status to check (should include 'wc-' prefix).
 *
 * @return bool True if the status change is allowed, false otherwise.
 */
private function is_status_change_allowed( $current_status, $new_status ) {
    // Ensure both statuses have 'wc-' prefix
    $current_status = $this->maybe_add_wc_prefix( $current_status );
    $new_status     = $this->maybe_add_wc_prefix( $new_status );

    // Define the default whitelist of allowed status transitions
    $default_whitelist = [
        'wc-pending'    => ['any'],
        'wc-on-hold'    => ['wc-pending', 'wc-on-hold', 'wc-processing', 'wc-completed', 'wc-failed'],
        'wc-processing' => ['wc-completed', 'wc-failed', 'wc-cancelled', 'wc-refunded'],
        'wc-completed'  => ['wc-refunded'],
        'wc-failed'     => ['wc-pending', 'wc-on-hold', 'wc-processing', 'wc-failed', 'wc-cancelled'],
        'wc-cancelled'  => [],
        'wc-refunded'   => []
    ];

    /**
     * Filter the whitelist of allowed status transitions for sub-orders.
     *
     * @since x.x.x
     *
     * @param array $default_whitelist The default whitelist of allowed status transitions.
     */
    $whitelist = apply_filters( 'dokan_sub_order_status_update_whitelist', $default_whitelist );

    // If the current status is not in the whitelist, status change is not allowed
    if ( ! isset( $whitelist[ $current_status ] ) ) {
        return false;
    }

    // If 'any' is allowed for the current status, all transitions are allowed
    if ( in_array( 'any', $whitelist[ $current_status ], true ) ) {
        return true;
    }

    // Check if the new status is in the list of allowed transitions
    return in_array( $new_status, $whitelist[ $current_status ], true );
}

/**
 * Ensure a status string has the 'wc-' prefix.
 *
 * @since x.x.x
 *
 * @param string $status The status string to check.
 *
 * @return string The status string with 'wc-' prefix added if it was missing.
 */
private function maybe_add_wc_prefix( $status ) {
    return strpos( $status, 'wc-' ) === 0 ? $status : 'wc-' . $status;
}
  1. Modify the on_order_status_change method:
/**
 * Update the child order status when a parent order status is changed.
 *
 * @since x.x.x
 *
 * @param int      $order_id
 * @param string   $old_status
 * @param string   $new_status
 * @param WC_Order $order
 *
 * @return void
 */
public function on_order_status_change( $order_id, $old_status, $new_status, $order ) {
    global $wpdb;

    // Existing code for splitting orders remains unchanged

    // Update status in dokan_orders table
    $wpdb->update(
        $wpdb->dokan_orders,
        [ 'order_status' => $new_status ],
        [ 'order_id' => $order_id ],
        [ '%s' ],
        [ '%d' ]
    );

    // Update sub-orders
    $sub_orders = dokan()->order->get_child_orders( $order_id );
    if ( $sub_orders ) {
        foreach ( $sub_orders as $sub_order ) {
            $sub_order_status = $sub_order->get_status();
            if ( $this->is_status_change_allowed( $sub_order_status, $new_status ) ) {
                $update_status = apply_filters( 'dokan_update_sub_order_status', $new_status, $sub_order, $order );
                if ( $update_status !== $sub_order_status ) {
                    $sub_order->update_status( $update_status );
                }
            } else {
                $this->log_skipped_status_update( $sub_order->get_id(), $sub_order_status, $new_status );
            }
        }
    }

    // Existing code for updating vendor balance remains unchanged
}
  1. Implement the logging method:
/**
 * Log skipped status updates for sub-orders.
 *
 * @since x.x.x
 *
 * @param int    $order_id       The ID of the sub-order.
 * @param string $current_status The current status of the sub-order.
 * @param string $new_status     The new status that was not applied.
 *
 * @return void
 */
private function log_skipped_status_update( $order_id, $current_status, $new_status ) {
    dokan_log( sprintf( 'Skipped status update for sub-order %d from %s to %s', $order_id, $current_status, $new_status ) );
}

@mrabbani vai, would you kindly review it and share your feedback?