• Resolved webairstudio

    (@webairstudio)


    Affected: 3.0.1 (reproduced), still present after 3.0.2

    When updating stock via the public endpoint /?slw-api&…&action=set&item=stock, the plugin should:

    • Update _stock_at_{location_id} only if the new value differs from the current one.
    • Recalculate and persist the product/variation Total Stock as the sum across locations, and only when a real change occurred.

    Currently, the API can trigger recalculation even when the value is unchanged, and the variation’s WC stock quantity may not be refreshed to match the summed location stock.

    Expected behavior

    • No DB writes or recalculation when the provided value equals the current _stock_at_{location_id}.
    • On change, write the meta, then recompute the Total Stock for that specific product/variation and sync WC stock/status (and parent variable product if applicable).

    Steps to reproduce

    1) Ensure SLW API is enabled and the caller is validated.

    2) Call: /?slw-api&value=<current_value>&action=set&item=stock&product_id=<variation_or_product_id>&location_id=<term_id>&format=json

    3) Observe that recalculation runs despite no effective change, and/or Total Stock on the variation is not refreshed to the sum.

    Proposed fix

    • File: wp-content/plugins/stock-locations-for-woocommerce/inc/functions-api.php
    • In case ‘set’ → item ‘stock’, update meta only on change; then recalc and sync stock/status for the specific product, and finally sync the parent if it is a variation.
    <?php

    add_action('init', function(){



    if(isset($_GET['slw-api'])){

    global $slw_api_valid_keys;

    $response = array('response'=>false);
    $recalc_required = false;


    $data = array('action'=>'','format'=>'');
    if(!empty($_GET)){

    $received = sanitize_slw_data($_GET);

    if ( empty( $slw_api_valid_keys ) || ! is_array( $slw_api_valid_keys ) ) {
    $slw_api_valid_keys = [];
    }
    foreach($received as $k=>$v){
    if(array_key_exists($k, $slw_api_valid_keys)){
    $data[$k] = $v;
    }
    }
    }

    if(get_option('slw_api_status')==true){

    $current_source = ($_SERVER['REMOTE_ADDR'].'/'.$_SERVER['SERVER_NAME']);

    $validated_requests = get_option('slw_api_request_validated', array());

    $validated_requests = (is_array($validated_requests)?$validated_requests:array());

    $all_requests = get_option('slw_api_request_sources', array());

    $all_requests = (is_array($all_requests)?$all_requests:array());



    $all_requests[time()] = $current_source;


    $all_requests = array_unique($all_requests);

    update_option('slw_api_request_sources', $all_requests);


    if(!in_array($current_source, $validated_requests)){

    _e('Sorry, you are not allowed to proceed.', 'stock-locations-for-woocommerce');
    exit;
    }



    switch($data['action']){
    case 'read':
    case 'get':
    case 'fetch':
    case 'pull':

    switch($data['item']){
    case 'location':
    if($data['id']){
    $response = get_term_by('id', $data['id'], 'location');
    }else{
    $response = slw_get_locations();
    }
    break;
    case 'stock':
    if($data['product_id'] && $data['location_id']){
    $response['stock_value'] = get_post_meta($data['product_id'], '_stock_at_' . $data['location_id'], true);
    }
    break;
    case 'product':
    //pree($data['id']);
    if($data['id']){
    $response[$data['id']] = wc_get_product($data['id']);
    }else{
    $products = get_posts( array('post_type'=>'product') );
    if(!empty($products)){
    foreach($products as $product){ if(!is_object($product)){ continue; }
    //pree($product->ID);
    $response[$product->ID] = wc_get_product($product->ID);
    }
    }
    }
    break;
    case 'price':
    $response['price'] = get_post_meta($data['product_id'], '_price', true);
    break;
    }

    break;
    case 'replace':
    case 'update':
    case 'put':
    case 'set':
    switch($data['item']){
    case 'location':
    if($data['product_id'] && $data['location_id']){
    $product_locations = wp_get_object_terms($data['product_id'], 'location' );
    $paux = array(intval($data['location_id']));
    foreach($product_locations as $termVal) {
    if ($termVal -> term_id != $paux[0]) $paux[] = $termVal -> term_id;
    }
    $response['response'] = wp_set_object_terms($data['product_id'], $paux, 'location');
    }

    break;
    case 'stock':
    if($data['product_id'] && $data['location_id'] && $data['value']>=0){
    // Update only if value actually changed
    $current_value = get_post_meta($data['product_id'], '_stock_at_' . $data['location_id'], true);
    if((string)$current_value !== (string)$data['value']){
    $product_locations = wp_get_object_terms($data['product_id'], 'location' );
    $paux = array(intval($data['location_id']));
    foreach($product_locations as $termVal) {
    if ($termVal->term_id != $paux[0]) $paux[] = $termVal->term_id;
    }
    wp_set_object_terms($data['product_id'], $paux, 'location');
    $response['response'] = update_post_meta($data['product_id'], '_stock_at_' . $data['location_id'], $data['value']);
    $recalc_required = true;
    }else{
    $response['response'] = false; // no change
    }
    }
    break;
    case 'product':

    break;
    case 'price':
    if($data['product_id'] && (isset($data['value']) && is_numeric($data['value']) && $data['value']>=0)){
    update_post_meta($data['product_id'], '_regular_price', $data['value']);
    $response['response'] = update_post_meta($data['product_id'], '_price', $data['value']);
    }
    break;
    }
    break;
    }

    }else{
    _e('Sorry, API is not enabled.', 'stock-locations-for-woocommerce');
    exit;
    }

    if($data['product_id']){
    $response['product_id'] = $data['product_id'];
    // Recalculate stock totals only if there was an actual change
    if($recalc_required){
    // 1) Update the stock quantity on the specific product/variation directly
    $locations_total = \SLW\SRC\Helpers\SlwProductHelper::get_product_locations_stock_total($data['product_id']);
    if($locations_total !== null){
    slw_update_product_stock_status($data['product_id'], (int)$locations_total);
    }
    // 2) Refresh parent variable product (if applicable) via existing aggregator
    $recalc_parent_id = $data['product_id'];
    $wc_product_obj = wc_get_product($recalc_parent_id);
    if($wc_product_obj && $wc_product_obj->is_type('variation')){
    $recalc_parent_id = $wc_product_obj->get_parent_id();
    }
    // Lightweight status/quantity sync for parent
    \SLW\SRC\Helpers\SlwProductHelper::update_wc_stock_status($recalc_parent_id, null, true);
    }
    }

    switch($data['format']){
    default:
    case 'default':
    pree($response);
    break;
    case 'json':
    echo json_encode($response);
    break;
    }

    exit;
    }
    }, 11);

    Rationale

    • Prevents unnecessary DB writes and hook churn when the value is unchanged.
    • Ensures the WC stock quantity for the target product/variation immediately reflects the sum across locations.
    • Keeps parent variable product’s status/quantity consistent after a variation’s stock change.
    • This topic was modified 7 months, 2 weeks ago by webairstudio.
Viewing 1 replies (of 1 total)
  • Plugin Author Fahad Mahmood

    (@fahadmahmood)

    Thanks for your report! The API has been updated:

    • Stock and price values are only updated if they actually change, preventing unnecessary database writes.
    • Total Stock is recalculated only on real changes, and parent variable products are synced automatically.
    • If the same value is posted, it won’t be rewritten to the DB — the response will show false. This doesn’t mean a failure; it just indicates there was no need to update.

    These changes address all the points you mentioned.

Viewing 1 replies (of 1 total)

You must be logged in to reply to this topic.