products
This commit is contained in:
parent
8d4ebb36e1
commit
4bf6b77c6c
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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) {}
|
||||
}
|
||||
|
|
@ -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) {}
|
||||
}
|
||||
|
|
@ -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).
|
||||
|
|
|
|||
Loading…
Reference in New Issue