fix: map Dolibarr tva_tx to PS id_tax_rules_group on product sync

Price with VAT was showing the same value as price without VAT because
id_tax_rules_group was hardcoded to 1 (no-tax group in this PS instance).

Now reads tva_tx from Dolibarr product, resolves the PS tax rule group
via a configurable map (integration.prestashop.tax-rule-groups), and
sets the correct group in the product XML. Defaults to
default-tax-rules-group-id when no mapping is found.

ES groups configured: 21% → 53, 10% → 54, 4% → 55.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
luklpz 2026-05-28 18:47:55 +02:00
parent c59d2048db
commit df3775d96b
7 changed files with 52 additions and 10 deletions

View File

@ -3,8 +3,11 @@ package com.teterialosjuanjos.tfg.sync_service.config;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import java.util.Map;
/** /**
* Externalized configuration for Dolibarr and PrestaShop integration endpoints. * Externalized configuration for Dolibarr and PrestaShop integration endpoints.
* Bound from {@code integration.*} properties in application.yml; validated at startup. * Bound from {@code integration.*} properties in application.yml; validated at startup.
@ -28,16 +31,23 @@ public record IntegrationProperties(
) {} ) {}
/** /**
* @param baseUrl full API base URL, e.g. {@code https://host/tienda/api} * @param baseUrl full API base URL, e.g. {@code https://host/tienda/api}
* @param apiKey webservice key sent as {@code ws_key} query param * @param apiKey webservice key sent as {@code ws_key} query param
* @param defaultCategoryId PrestaShop category ID assigned to new products (default: 2 = Home) * @param defaultCategoryId PrestaShop category ID assigned to new products (default: 2 = Home)
* @param languageId PrestaShop language ID used for all multilingual fields (name, description, etc.) * @param languageId PrestaShop language ID used for all multilingual fields (name, description, etc.)
* Check yours at GET /api/languages. Default 1 is usually English; set to Spanish ID. * Check yours at GET /api/languages. Default 1 is usually English; set to Spanish ID.
* @param defaultTaxRulesGroupId Fallback PS {@code id_tax_rules_group} when the product's Dolibarr {@code tva_tx}
* has no entry in {@code taxRuleGroups}. Defaults to 1.
* Check your PS tax groups at GET /api/tax_rule_groups.
* @param taxRuleGroups Map of Dolibarr VAT rate (integer key, e.g. "21") to PS {@code id_tax_rules_group}.
* Configure in application-dev/prod.yml. If empty, {@code defaultTaxRulesGroupId} is used.
*/ */
public record Prestashop( public record Prestashop(
@NotBlank String baseUrl, @NotBlank String baseUrl,
@NotBlank String apiKey, @NotBlank String apiKey,
int defaultCategoryId, int defaultCategoryId,
int languageId int languageId,
@DefaultValue("1") int defaultTaxRulesGroupId,
Map<String, Integer> taxRuleGroups
) {} ) {}
} }

View File

@ -26,5 +26,7 @@ public record DolibarrProductDto(
@JsonProperty("tosell") Integer toSell, @JsonProperty("tosell") Integer toSell,
/** 0 = physical product, 1 = service */ /** 0 = physical product, 1 = service */
Integer type, Integer type,
Double weight Double weight,
/** VAT rate in percent, e.g. 21.0 for 21% IVA */
@JsonProperty("tva_tx") Double tvaTx
) {} ) {}

View File

@ -371,7 +371,8 @@ public class PrestashopClient {
sb.append("<id_category_default>").append(escapeXml(dto.idCategoryDefault())).append("</id_category_default>"); sb.append("<id_category_default>").append(escapeXml(dto.idCategoryDefault())).append("</id_category_default>");
} }
sb.append("<state>1</state>"); sb.append("<state>1</state>");
sb.append("<id_tax_rules_group>1</id_tax_rules_group>"); int taxRulesGroupId = dto.taxRulesGroupId() != null ? dto.taxRulesGroupId() : 1;
sb.append("<id_tax_rules_group>").append(taxRulesGroupId).append("</id_tax_rules_group>");
sb.append("<id_shop_default>1</id_shop_default>"); sb.append("<id_shop_default>1</id_shop_default>");
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>");

View File

@ -34,7 +34,9 @@ public record PrestashopProductDto(
@JsonProperty("description_short") @JsonProperty("description_short")
@JsonDeserialize(using = MultilangStringDeserializer.class) String descriptionShort, @JsonDeserialize(using = MultilangStringDeserializer.class) String descriptionShort,
/** PS category IDs to set on write; not present in PS JSON responses (ignored on read). */ /** PS category IDs to set on write; not present in PS JSON responses (ignored on read). */
@JsonIgnore List<String> categoryIds @JsonIgnore List<String> categoryIds,
/** PS tax rule group ID; resolved from Dolibarr tva_tx at sync time, write-only. */
@JsonIgnore Integer taxRulesGroupId
) { ) {
/** Envelope for {@code GET /api/products?display=full} */ /** Envelope for {@code GET /api/products?display=full} */

View File

@ -19,6 +19,7 @@ import org.springframework.web.client.RestClientException;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -249,6 +250,7 @@ public class ProductSyncService {
String description = src.description() != null ? src.description() : ""; String description = src.description() != null ? src.description() : "";
String descriptionShort = src.notePublic() != null ? src.notePublic() : ""; String descriptionShort = src.notePublic() != null ? src.notePublic() : "";
String active = (src.toSell() != null && src.toSell() == 0) ? "0" : "1"; String active = (src.toSell() != null && src.toSell() == 0) ? "0" : "1";
Integer taxRulesGroupId = resolveTaxRulesGroup(src.tvaTx());
return new PrestashopProductDto( return new PrestashopProductDto(
prestashopId, prestashopId,
@ -259,7 +261,22 @@ public class ProductSyncService {
label, label,
description, description,
descriptionShort, descriptionShort,
allCategoryIds allCategoryIds,
taxRulesGroupId
); );
} }
/**
* Resolves the PrestaShop {@code id_tax_rules_group} from the Dolibarr VAT rate.
* Looks up {@code tvaTx} (truncated to int, e.g. 21.0 → "21") in the configured map.
* Falls back to {@code defaultTaxRulesGroupId} when no mapping exists.
*/
private Integer resolveTaxRulesGroup(Double tvaTx) {
Map<String, Integer> map = integrationProperties.prestashop().taxRuleGroups();
if (tvaTx != null && map != null && !map.isEmpty()) {
Integer groupId = map.get(String.valueOf(tvaTx.intValue()));
if (groupId != null) return groupId;
}
return integrationProperties.prestashop().defaultTaxRulesGroupId();
}
} }

View File

@ -29,6 +29,11 @@ integration:
api-key: DOLIBARR_API_KEY_AQUI api-key: DOLIBARR_API_KEY_AQUI
prestashop: prestashop:
api-key: PRESTASHOP_API_KEY_AQUI api-key: PRESTASHOP_API_KEY_AQUI
default-tax-rules-group-id: 53 # ES Standard rate (21%) — fallback
tax-rule-groups:
21: 53 # ES Standard rate (21%)
10: 54 # ES Reduced Rate (10%)
4: 55 # ES Super Reduced Rate (4%)
logging: logging:
level: level:

View File

@ -17,6 +17,11 @@ integration:
api-key: ${PRESTASHOP_API_KEY} api-key: ${PRESTASHOP_API_KEY}
default-category-id: 2 default-category-id: 2
language-id: ${PRESTASHOP_LANGUAGE_ID:1} # set to your Spanish language ID (check GET /api/languages) language-id: ${PRESTASHOP_LANGUAGE_ID:1} # set to your Spanish language ID (check GET /api/languages)
default-tax-rules-group-id: 53 # ES Standard rate (21%) — fallback
tax-rule-groups:
21: 53 # ES Standard rate (21%)
10: 54 # ES Reduced Rate (10%)
4: 55 # ES Super Reduced Rate (4%)
api: api:
security: security: