This commit is contained in:
Mireya Serrano 2026-05-26 17:06:07 +02:00
parent 8d4ebb36e1
commit 4bf6b77c6c
4 changed files with 129 additions and 10 deletions

View File

@ -15,6 +15,7 @@ import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* Typed HTTP client for the PrestaShop Webservice.
@ -32,7 +33,8 @@ public class PrestashopClient {
private final RestClient restClient;
private final String wsKey;
private final String languageId;
private final String fallbackLanguageId;
private volatile List<String> cachedLanguageIds;
public PrestashopClient(
@Qualifier("prestashopRestClient") RestClient restClient,
@ -40,7 +42,7 @@ public class PrestashopClient {
) {
this.restClient = restClient;
this.wsKey = props.prestashop().apiKey();
this.languageId = String.valueOf(props.prestashop().languageId());
this.fallbackLanguageId = String.valueOf(props.prestashop().languageId());
}
// ── Products ──────────────────────────────────────────────────────────
@ -282,6 +284,66 @@ public class PrestashopClient {
}
}
// ── Languages / Categories ────────────────────────────────────────────
/**
* Fetches all active PrestaShop language IDs and caches them for the lifetime of the bean.
* Falls back to the configured {@code language-id} if the request fails.
* Sending content for ALL active languages ensures the value is set regardless of which
* language is the shop default.
*/
public synchronized List<String> getActiveLanguageIds() {
if (cachedLanguageIds != null) return cachedLanguageIds;
try {
PrestashopLanguageDto.ListResponse resp = restClient.get()
.uri(u -> u.path("/languages")
.queryParam("ws_key", wsKey)
.queryParam("output_format", "JSON")
.queryParam("display", "full")
.queryParam("filter[active]", "[1]")
.build())
.retrieve()
.body(PrestashopLanguageDto.ListResponse.class);
if (resp != null && resp.languages() != null && !resp.languages().isEmpty()) {
cachedLanguageIds = resp.languages().stream()
.map(l -> String.valueOf(l.id()))
.collect(Collectors.toList());
log.info("PS active language IDs: {}", cachedLanguageIds);
return cachedLanguageIds;
}
} catch (Exception e) {
log.warn("Could not fetch PS language IDs, falling back to id={}: {}", fallbackLanguageId, e.getMessage());
}
cachedLanguageIds = List.of(fallbackLanguageId);
return cachedLanguageIds;
}
/**
* Finds a PrestaShop category whose name matches (case-insensitive) the given label.
* Returns the PS category ID, or empty if not found.
*/
public Optional<Integer> findCategoryIdByName(String label) {
if (label == null || label.isBlank()) return Optional.empty();
try {
PrestashopCategoryDto.ListResponse resp = restClient.get()
.uri(u -> u.path("/categories")
.queryParam("ws_key", wsKey)
.queryParam("output_format", "JSON")
.queryParam("display", "full")
.build())
.retrieve()
.body(PrestashopCategoryDto.ListResponse.class);
if (resp == null || resp.categories() == null) return Optional.empty();
return resp.categories().stream()
.filter(c -> label.equalsIgnoreCase(c.name()))
.map(PrestashopCategoryDto::id)
.findFirst();
} catch (Exception e) {
log.warn("Could not search PS categories for label='{}': {}", label, e.getMessage());
return Optional.empty();
}
}
// ── XML builders ──────────────────────────────────────────────────────
// PS WebService requires XML body for writes; output_format=JSON only affects the response.
@ -314,10 +376,19 @@ public class PrestashopClient {
sb.append("<available_for_order>1</available_for_order>");
sb.append("<show_price>1</show_price>");
sb.append("<visibility>both</visibility>");
sb.append("<link_rewrite><language id=\"").append(languageId).append("\">").append(escapeXml(slug)).append("</language></link_rewrite>");
sb.append("<name><language id=\"").append(languageId).append("\">").append(escapeXml(name)).append("</language></name>");
sb.append("<description><language id=\"").append(languageId).append("\">").append(escapeXml(description)).append("</language></description>");
sb.append("<description_short><language id=\"").append(languageId).append("\">").append(escapeXml(descShort)).append("</language></description_short>");
List<String> langIds = getActiveLanguageIds();
sb.append("<link_rewrite>");
for (String lid : langIds) sb.append("<language id=\"").append(lid).append("\">").append(escapeXml(slug)).append("</language>");
sb.append("</link_rewrite>");
sb.append("<name>");
for (String lid : langIds) sb.append("<language id=\"").append(lid).append("\">").append(escapeXml(name)).append("</language>");
sb.append("</name>");
sb.append("<description>");
for (String lid : langIds) sb.append("<language id=\"").append(lid).append("\">").append(escapeXml(description)).append("</language>");
sb.append("</description>");
sb.append("<description_short>");
for (String lid : langIds) sb.append("<language id=\"").append(lid).append("\">").append(escapeXml(descShort)).append("</language>");
sb.append("</description_short>");
List<String> cats = (dto.categoryIds() != null && !dto.categoryIds().isEmpty())
? dto.categoryIds()
: (dto.idCategoryDefault() != null ? List.of(dto.idCategoryDefault()) : List.of());

View File

@ -0,0 +1,15 @@
package com.teterialosjuanjos.tfg.sync_service.integration.prestashop.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import java.util.List;
@JsonIgnoreProperties(ignoreUnknown = true)
public record PrestashopCategoryDto(
Integer id,
@JsonDeserialize(using = MultilangStringDeserializer.class) String name
) {
@JsonIgnoreProperties(ignoreUnknown = true)
public record ListResponse(List<PrestashopCategoryDto> categories) {}
}

View File

@ -0,0 +1,12 @@
package com.teterialosjuanjos.tfg.sync_service.integration.prestashop.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import java.util.List;
@JsonIgnoreProperties(ignoreUnknown = true)
public record PrestashopLanguageDto(Integer id, String active) {
@JsonIgnoreProperties(ignoreUnknown = true)
public record ListResponse(List<PrestashopLanguageDto> languages) {}
}

View File

@ -156,16 +156,37 @@ public class ProductSyncService {
try {
List<DolibarrCategoryDto> dolibarrCats = dolibarrClient.getProductCategories(dolibarrProductId);
if (dolibarrCats.isEmpty()) return List.of();
List<Integer> dolibarrIds = dolibarrCats.stream().map(DolibarrCategoryDto::id).toList();
return categoryMappingRepository.findByDolibarrIdIn(dolibarrIds).stream()
.map(m -> String.valueOf(m.getPrestashopId()))
.toList();
List<String> psIds = new ArrayList<>();
for (DolibarrCategoryDto cat : dolibarrCats) {
if (cat.id() == null) continue;
CategoryMapping mapping = categoryMappingRepository.findByDolibarrId(cat.id())
.orElseGet(() -> discoverAndSaveCategory(cat));
if (mapping != null) psIds.add(String.valueOf(mapping.getPrestashopId()));
}
return psIds;
} catch (Exception e) {
log.warn("ProductSync: could not resolve categories for Dolibarr product id={}: {}", dolibarrProductId, e.getMessage());
return List.of();
}
}
private CategoryMapping discoverAndSaveCategory(DolibarrCategoryDto cat) {
Optional<Integer> psId = prestashopClient.findCategoryIdByName(cat.label());
if (psId.isEmpty()) {
log.warn("ProductSync: no PS category found for Dolibarr category '{}' (id={}), skipping", cat.label(), cat.id());
return null;
}
CategoryMapping mapping = CategoryMapping.builder()
.dolibarrId(cat.id())
.prestashopId(psId.get())
.label(cat.label())
.build();
categoryMappingRepository.save(mapping);
log.info("ProductSync: auto-linked Dolibarr category '{}' (id={}) → PS id={}", cat.label(), cat.id(), psId.get());
return mapping;
}
/**
* 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).