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.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Typed HTTP client for the PrestaShop Webservice.
|
* Typed HTTP client for the PrestaShop Webservice.
|
||||||
|
|
@ -32,7 +33,8 @@ public class PrestashopClient {
|
||||||
|
|
||||||
private final RestClient restClient;
|
private final RestClient restClient;
|
||||||
private final String wsKey;
|
private final String wsKey;
|
||||||
private final String languageId;
|
private final String fallbackLanguageId;
|
||||||
|
private volatile List<String> cachedLanguageIds;
|
||||||
|
|
||||||
public PrestashopClient(
|
public PrestashopClient(
|
||||||
@Qualifier("prestashopRestClient") RestClient restClient,
|
@Qualifier("prestashopRestClient") RestClient restClient,
|
||||||
|
|
@ -40,7 +42,7 @@ public class PrestashopClient {
|
||||||
) {
|
) {
|
||||||
this.restClient = restClient;
|
this.restClient = restClient;
|
||||||
this.wsKey = props.prestashop().apiKey();
|
this.wsKey = props.prestashop().apiKey();
|
||||||
this.languageId = String.valueOf(props.prestashop().languageId());
|
this.fallbackLanguageId = String.valueOf(props.prestashop().languageId());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Products ──────────────────────────────────────────────────────────
|
// ── 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 ──────────────────────────────────────────────────────
|
// ── XML builders ──────────────────────────────────────────────────────
|
||||||
// PS WebService requires XML body for writes; output_format=JSON only affects the response.
|
// 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("<available_for_order>1</available_for_order>");
|
||||||
sb.append("<show_price>1</show_price>");
|
sb.append("<show_price>1</show_price>");
|
||||||
sb.append("<visibility>both</visibility>");
|
sb.append("<visibility>both</visibility>");
|
||||||
sb.append("<link_rewrite><language id=\"").append(languageId).append("\">").append(escapeXml(slug)).append("</language></link_rewrite>");
|
List<String> langIds = getActiveLanguageIds();
|
||||||
sb.append("<name><language id=\"").append(languageId).append("\">").append(escapeXml(name)).append("</language></name>");
|
sb.append("<link_rewrite>");
|
||||||
sb.append("<description><language id=\"").append(languageId).append("\">").append(escapeXml(description)).append("</language></description>");
|
for (String lid : langIds) sb.append("<language id=\"").append(lid).append("\">").append(escapeXml(slug)).append("</language>");
|
||||||
sb.append("<description_short><language id=\"").append(languageId).append("\">").append(escapeXml(descShort)).append("</language></description_short>");
|
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())
|
List<String> cats = (dto.categoryIds() != null && !dto.categoryIds().isEmpty())
|
||||||
? dto.categoryIds()
|
? dto.categoryIds()
|
||||||
: (dto.idCategoryDefault() != null ? List.of(dto.idCategoryDefault()) : List.of());
|
: (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 {
|
try {
|
||||||
List<DolibarrCategoryDto> dolibarrCats = dolibarrClient.getProductCategories(dolibarrProductId);
|
List<DolibarrCategoryDto> dolibarrCats = dolibarrClient.getProductCategories(dolibarrProductId);
|
||||||
if (dolibarrCats.isEmpty()) return List.of();
|
if (dolibarrCats.isEmpty()) return List.of();
|
||||||
List<Integer> dolibarrIds = dolibarrCats.stream().map(DolibarrCategoryDto::id).toList();
|
|
||||||
return categoryMappingRepository.findByDolibarrIdIn(dolibarrIds).stream()
|
List<String> psIds = new ArrayList<>();
|
||||||
.map(m -> String.valueOf(m.getPrestashopId()))
|
for (DolibarrCategoryDto cat : dolibarrCats) {
|
||||||
.toList();
|
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) {
|
} catch (Exception e) {
|
||||||
log.warn("ProductSync: could not resolve categories for Dolibarr product id={}: {}", dolibarrProductId, e.getMessage());
|
log.warn("ProductSync: could not resolve categories for Dolibarr product id={}: {}", dolibarrProductId, e.getMessage());
|
||||||
return List.of();
|
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.
|
* 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).
|
* Skipped when stockReel is null or zero (PS default is already 0).
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue