feat: add DELETE /api/mappings/products/{id} for broken mapping cleanup

Deletes the PS product via Webservice and removes the local mapping.
Use this to clean up incomplete PS products (missing ps_product_shop
entry) that block re-creation on next sync.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
luklpz 2026-05-17 18:07:42 +02:00
parent 9926f37015
commit 9fd11219af
2 changed files with 38 additions and 4 deletions

View File

@ -2,14 +2,16 @@ package com.teterialosjuanjos.tfg.sync_service.api.controller;
import com.teterialosjuanjos.tfg.sync_service.api.dto.OrderMappingResponse;
import com.teterialosjuanjos.tfg.sync_service.api.dto.ProductMappingResponse;
import com.teterialosjuanjos.tfg.sync_service.integration.prestashop.PrestashopClient;
import com.teterialosjuanjos.tfg.sync_service.integration.prestashop.exception.PrestashopApiException;
import com.teterialosjuanjos.tfg.sync_service.mapping.*;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
import java.util.List;
@ -17,6 +19,7 @@ import java.util.List;
* REST endpoints for inspecting the Dolibarr ↔ PrestaShop ID mappings stored locally.
*/
@Tag(name = "Mappings", description = "Inspect Dolibarr ↔ PrestaShop ID mappings")
@Slf4j
@RestController
@RequestMapping("/api/mappings")
@RequiredArgsConstructor
@ -24,6 +27,7 @@ public class MappingController {
private final ProductMappingRepository productMappingRepository;
private final OrderMappingRepository orderMappingRepository;
private final PrestashopClient prestashopClient;
@Operation(summary = "List product mappings, optionally filtered by sync status")
@GetMapping("/products")
@ -36,6 +40,24 @@ public class MappingController {
return mappings.stream().map(MappingController::toProductResponse).toList();
}
@Operation(summary = "Delete a product mapping and its corresponding PS product. Use to clean up broken/stale mappings before re-syncing.")
@DeleteMapping("/products/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteProductMapping(@PathVariable Long id) {
ProductMapping mapping = productMappingRepository.findById(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Mapping not found: " + id));
if (mapping.getPrestashopId() != null) {
try {
prestashopClient.deleteProduct(mapping.getPrestashopId());
log.info("Deleted PS product id={} (sku={})", mapping.getPrestashopId(), mapping.getSku());
} catch (PrestashopApiException e) {
log.warn("Could not delete PS product id={}: {} — removing mapping anyway", mapping.getPrestashopId(), e.getMessage());
}
}
productMappingRepository.delete(mapping);
log.info("Deleted product mapping id={} sku={}", id, mapping.getSku());
}
@Operation(summary = "List order mappings")
@GetMapping("/orders")
public List<OrderMappingResponse> getOrderMappings() {

View File

@ -115,6 +115,18 @@ public class PrestashopClient {
return getProductByReference(dto.reference()).orElse(null);
}
/** Permanently deletes a product from PrestaShop by its internal ID. */
public void deleteProduct(Integer id) {
log.debug("Deleting PrestaShop product id={}", id);
restClient.delete()
.uri(u -> u.path("/products/{id}")
.queryParam("ws_key", wsKey)
.queryParam("output_format", "JSON")
.build(id))
.retrieve()
.toBodilessEntity();
}
/** Updates an existing product by its PrestaShop internal ID. */
public void updateProduct(Integer id, PrestashopProductDto dto) {
log.debug("Updating PrestaShop product id={}", id);