fix(sync): push stock immediately on new product creation
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 <noreply@anthropic.com>
This commit is contained in:
parent
a5feff6a1a
commit
e501d83c73
|
|
@ -156,6 +156,7 @@ public class PrestashopClient {
|
||||||
* @param productId PrestaShop product internal ID
|
* @param productId PrestaShop product internal ID
|
||||||
*/
|
*/
|
||||||
public Optional<PrestashopStockAvailableDto> getStockAvailableForProduct(Integer productId) {
|
public Optional<PrestashopStockAvailableDto> getStockAvailableForProduct(Integer productId) {
|
||||||
|
try {
|
||||||
PrestashopStockAvailableDto.ListResponse response = restClient.get()
|
PrestashopStockAvailableDto.ListResponse response = restClient.get()
|
||||||
.uri(u -> u.path("/stock_availables")
|
.uri(u -> u.path("/stock_availables")
|
||||||
.queryParam("ws_key", wsKey)
|
.queryParam("ws_key", wsKey)
|
||||||
|
|
@ -171,6 +172,14 @@ public class PrestashopClient {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
return Optional.of(response.stockAvailables().get(0));
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Updates the stock quantity for a stock_available record. */
|
/** Updates the stock quantity for a stock_available record. */
|
||||||
|
|
|
||||||
|
|
@ -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.dolibarr.exception.DolibarrApiException;
|
||||||
import com.teterialosjuanjos.tfg.sync_service.integration.prestashop.PrestashopClient;
|
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.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.integration.prestashop.exception.PrestashopApiException;
|
||||||
import com.teterialosjuanjos.tfg.sync_service.mapping.*;
|
import com.teterialosjuanjos.tfg.sync_service.mapping.*;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
@ -110,10 +111,37 @@ public class ProductSyncService {
|
||||||
"Product created in PS but not found via GET (reference=" + sku + ")");
|
"Product created in PS but not found via GET (reference=" + sku + ")");
|
||||||
}
|
}
|
||||||
upsertMapping(sku, dolProduct.id(), created.id());
|
upsertMapping(sku, dolProduct.id(), created.id());
|
||||||
|
pushInitialStock(dolProduct, created.id(), sku);
|
||||||
log.debug("ProductSync: created PS product reference={} id={}", sku, created.id());
|
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<PrestashopStockAvailableDto> 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) {
|
private void upsertMapping(String sku, Integer dolibarrId, Integer prestashopId) {
|
||||||
ProductMapping mapping = productMappingRepository.findBySku(sku)
|
ProductMapping mapping = productMappingRepository.findBySku(sku)
|
||||||
.orElseGet(() -> ProductMapping.builder().sku(sku).build());
|
.orElseGet(() -> ProductMapping.builder().sku(sku).build());
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue