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.OrderMappingResponse;
import com.teterialosjuanjos.tfg.sync_service.api.dto.ProductMappingResponse; 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 com.teterialosjuanjos.tfg.sync_service.mapping.*;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping; import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ResponseStatusException;
import java.util.List; import java.util.List;
@ -17,6 +19,7 @@ import java.util.List;
* REST endpoints for inspecting the Dolibarr ↔ PrestaShop ID mappings stored locally. * REST endpoints for inspecting the Dolibarr ↔ PrestaShop ID mappings stored locally.
*/ */
@Tag(name = "Mappings", description = "Inspect Dolibarr ↔ PrestaShop ID mappings") @Tag(name = "Mappings", description = "Inspect Dolibarr ↔ PrestaShop ID mappings")
@Slf4j
@RestController @RestController
@RequestMapping("/api/mappings") @RequestMapping("/api/mappings")
@RequiredArgsConstructor @RequiredArgsConstructor
@ -24,6 +27,7 @@ public class MappingController {
private final ProductMappingRepository productMappingRepository; private final ProductMappingRepository productMappingRepository;
private final OrderMappingRepository orderMappingRepository; private final OrderMappingRepository orderMappingRepository;
private final PrestashopClient prestashopClient;
@Operation(summary = "List product mappings, optionally filtered by sync status") @Operation(summary = "List product mappings, optionally filtered by sync status")
@GetMapping("/products") @GetMapping("/products")
@ -36,6 +40,24 @@ public class MappingController {
return mappings.stream().map(MappingController::toProductResponse).toList(); 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") @Operation(summary = "List order mappings")
@GetMapping("/orders") @GetMapping("/orders")
public List<OrderMappingResponse> getOrderMappings() { public List<OrderMappingResponse> getOrderMappings() {

View File

@ -115,6 +115,18 @@ public class PrestashopClient {
return getProductByReference(dto.reference()).orElse(null); 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. */ /** Updates an existing product by its PrestaShop internal ID. */
public void updateProduct(Integer id, PrestashopProductDto dto) { public void updateProduct(Integer id, PrestashopProductDto dto) {
log.debug("Updating PrestaShop product id={}", id); log.debug("Updating PrestaShop product id={}", id);