From e501d83c7314daaa22760f4379de70195534f842 Mon Sep 17 00:00:00 2001 From: luklpz Date: Mon, 18 May 2026 18:27:17 +0200 Subject: [PATCH] fix(sync): push stock immediately on new product creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ProductSyncService now calls pushInitialStock() after creating a new product in PrestaShop, so stock is correct without waiting for the next StockSyncService run. Failures are caught and logged as warnings — the product mapping is saved as SYNCED regardless, and StockSyncService will correct any discrepancy on its next scheduled run. Also applies the PS 8.2.5 false-instead-of-empty-array workaround to getStockAvailableForProduct(), consistent with getProductByReference(). Co-Authored-By: Claude Sonnet 4.6 --- .../prestashop/PrestashopClient.java | 33 ++++++++++++------- .../sync_service/sync/ProductSyncService.java | 28 ++++++++++++++++ 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/sync-service/src/main/java/com/teterialosjuanjos/tfg/sync_service/integration/prestashop/PrestashopClient.java b/sync-service/src/main/java/com/teterialosjuanjos/tfg/sync_service/integration/prestashop/PrestashopClient.java index cb755f2..95f9303 100644 --- a/sync-service/src/main/java/com/teterialosjuanjos/tfg/sync_service/integration/prestashop/PrestashopClient.java +++ b/sync-service/src/main/java/com/teterialosjuanjos/tfg/sync_service/integration/prestashop/PrestashopClient.java @@ -156,21 +156,30 @@ public class PrestashopClient { * @param productId PrestaShop product internal ID */ public Optional getStockAvailableForProduct(Integer productId) { - PrestashopStockAvailableDto.ListResponse response = restClient.get() - .uri(u -> u.path("/stock_availables") - .queryParam("ws_key", wsKey) - .queryParam("output_format", "JSON") - .queryParam("display", "full") - .queryParam("filter[id_product]", productId) - .queryParam("filter[id_product_attribute]", "0") - .build()) - .retrieve() - .body(PrestashopStockAvailableDto.ListResponse.class); + try { + PrestashopStockAvailableDto.ListResponse response = restClient.get() + .uri(u -> u.path("/stock_availables") + .queryParam("ws_key", wsKey) + .queryParam("output_format", "JSON") + .queryParam("display", "full") + .queryParam("filter[id_product]", productId) + .queryParam("filter[id_product_attribute]", "0") + .build()) + .retrieve() + .body(PrestashopStockAvailableDto.ListResponse.class); - if (response == null || response.stockAvailables() == null || response.stockAvailables().isEmpty()) { + if (response == null || response.stockAvailables() == null || response.stockAvailables().isEmpty()) { + return Optional.empty(); + } + return Optional.of(response.stockAvailables().get(0)); + } catch (PrestashopApiException e) { + if (e.getStatusCode() != null && e.getStatusCode().value() == 404) return Optional.empty(); + throw e; + } catch (HttpMessageConversionException | RestClientException e) { + // PS 8.x returns {"stock_availables": false} instead of [] when filter has no results + log.debug("PS returned unparseable body for stock_available productId={}, treating as not found: {}", productId, e.getMessage()); return Optional.empty(); } - return Optional.of(response.stockAvailables().get(0)); } /** Updates the stock quantity for a stock_available record. */ diff --git a/sync-service/src/main/java/com/teterialosjuanjos/tfg/sync_service/sync/ProductSyncService.java b/sync-service/src/main/java/com/teterialosjuanjos/tfg/sync_service/sync/ProductSyncService.java index 94e07b9..35e7a32 100644 --- a/sync-service/src/main/java/com/teterialosjuanjos/tfg/sync_service/sync/ProductSyncService.java +++ b/sync-service/src/main/java/com/teterialosjuanjos/tfg/sync_service/sync/ProductSyncService.java @@ -6,6 +6,7 @@ import com.teterialosjuanjos.tfg.sync_service.integration.dolibarr.dto.DolibarrP import com.teterialosjuanjos.tfg.sync_service.integration.dolibarr.exception.DolibarrApiException; import com.teterialosjuanjos.tfg.sync_service.integration.prestashop.PrestashopClient; import com.teterialosjuanjos.tfg.sync_service.integration.prestashop.dto.PrestashopProductDto; +import com.teterialosjuanjos.tfg.sync_service.integration.prestashop.dto.PrestashopStockAvailableDto; import com.teterialosjuanjos.tfg.sync_service.integration.prestashop.exception.PrestashopApiException; import com.teterialosjuanjos.tfg.sync_service.mapping.*; import lombok.RequiredArgsConstructor; @@ -110,10 +111,37 @@ public class ProductSyncService { "Product created in PS but not found via GET (reference=" + sku + ")"); } upsertMapping(sku, dolProduct.id(), created.id()); + pushInitialStock(dolProduct, created.id(), sku); log.debug("ProductSync: created PS product reference={} id={}", sku, created.id()); } } + /** + * Pushes Dolibarr stock to the PS stock_available record immediately after product creation. + * Skipped when stockReel is null or zero (PS default is already 0). + * Failures are logged as warnings but do not abort the product sync — StockSyncService + * will correct any discrepancy on its next run. + */ + private void pushInitialStock(DolibarrProductDto dolProduct, Integer psProductId, String sku) { + if (dolProduct.stockReel() == null || dolProduct.stockReel() == 0) return; + int qty = dolProduct.stockReel().intValue(); + try { + Optional stockOpt = + prestashopClient.getStockAvailableForProduct(psProductId); + if (stockOpt.isEmpty()) { + log.warn("ProductSync: no stock_available found for new product SKU={}, stock will sync on next StockSync run", sku); + return; + } + PrestashopStockAvailableDto current = stockOpt.get(); + prestashopClient.updateStockAvailable(current.id(), + new PrestashopStockAvailableDto(current.id(), current.idProduct(), + current.idProductAttribute(), String.valueOf(qty))); + log.debug("ProductSync: initial stock pushed SKU={} qty={}", sku, qty); + } catch (PrestashopApiException | RestClientException | HttpMessageConversionException e) { + log.warn("ProductSync: could not push initial stock for SKU={}: {}", sku, e.getMessage()); + } + } + private void upsertMapping(String sku, Integer dolibarrId, Integer prestashopId) { ProductMapping mapping = productMappingRepository.findBySku(sku) .orElseGet(() -> ProductMapping.builder().sku(sku).build());