feat(back-end front-end): shop improvements
Some checks failed
Build and Deploy / test-backend (push) Successful in 38s
Build and Deploy / test-frontend (push) Successful in 1m4s
Build and Deploy / build-and-push (push) Failing after 1m15s
Build and Deploy / deploy (push) Has been skipped

This commit is contained in:
2026-03-13 16:16:49 +01:00
parent fcdede2dd6
commit 00af9a9701
39 changed files with 1886 additions and 299 deletions

View File

@@ -94,6 +94,10 @@ public class OptionsController {
v.getId(),
v.getVariantDisplayName(),
v.getColorName(),
v.getColorLabelIt(),
v.getColorLabelEn(),
v.getColorLabelDe(),
v.getColorLabelFr(),
resolveHexColor(v),
v.getFinishType() != null ? v.getFinishType() : "GLOSSY",
v.getStockSpools() != null ? v.getStockSpools().doubleValue() : 0d,

View File

@@ -12,6 +12,10 @@ public class AdminFilamentVariantDto {
private String materialTechnicalTypeLabel;
private String variantDisplayName;
private String colorName;
private String colorLabelIt;
private String colorLabelEn;
private String colorLabelDe;
private String colorLabelFr;
private String colorHex;
private String finishType;
private String brand;
@@ -89,6 +93,38 @@ public class AdminFilamentVariantDto {
this.colorName = colorName;
}
public String getColorLabelIt() {
return colorLabelIt;
}
public void setColorLabelIt(String colorLabelIt) {
this.colorLabelIt = colorLabelIt;
}
public String getColorLabelEn() {
return colorLabelEn;
}
public void setColorLabelEn(String colorLabelEn) {
this.colorLabelEn = colorLabelEn;
}
public String getColorLabelDe() {
return colorLabelDe;
}
public void setColorLabelDe(String colorLabelDe) {
this.colorLabelDe = colorLabelDe;
}
public String getColorLabelFr() {
return colorLabelFr;
}
public void setColorLabelFr(String colorLabelFr) {
this.colorLabelFr = colorLabelFr;
}
public String getColorHex() {
return colorHex;
}

View File

@@ -10,9 +10,25 @@ public class AdminShopCategoryDto {
private String parentCategoryName;
private String slug;
private String name;
private String nameIt;
private String nameEn;
private String nameDe;
private String nameFr;
private String description;
private String descriptionIt;
private String descriptionEn;
private String descriptionDe;
private String descriptionFr;
private String seoTitle;
private String seoTitleIt;
private String seoTitleEn;
private String seoTitleDe;
private String seoTitleFr;
private String seoDescription;
private String seoDescriptionIt;
private String seoDescriptionEn;
private String seoDescriptionDe;
private String seoDescriptionFr;
private String ogTitle;
private String ogDescription;
private Boolean indexable;
@@ -69,6 +85,38 @@ public class AdminShopCategoryDto {
this.name = name;
}
public String getNameIt() {
return nameIt;
}
public void setNameIt(String nameIt) {
this.nameIt = nameIt;
}
public String getNameEn() {
return nameEn;
}
public void setNameEn(String nameEn) {
this.nameEn = nameEn;
}
public String getNameDe() {
return nameDe;
}
public void setNameDe(String nameDe) {
this.nameDe = nameDe;
}
public String getNameFr() {
return nameFr;
}
public void setNameFr(String nameFr) {
this.nameFr = nameFr;
}
public String getDescription() {
return description;
}
@@ -77,6 +125,38 @@ public class AdminShopCategoryDto {
this.description = description;
}
public String getDescriptionIt() {
return descriptionIt;
}
public void setDescriptionIt(String descriptionIt) {
this.descriptionIt = descriptionIt;
}
public String getDescriptionEn() {
return descriptionEn;
}
public void setDescriptionEn(String descriptionEn) {
this.descriptionEn = descriptionEn;
}
public String getDescriptionDe() {
return descriptionDe;
}
public void setDescriptionDe(String descriptionDe) {
this.descriptionDe = descriptionDe;
}
public String getDescriptionFr() {
return descriptionFr;
}
public void setDescriptionFr(String descriptionFr) {
this.descriptionFr = descriptionFr;
}
public String getSeoTitle() {
return seoTitle;
}
@@ -85,6 +165,38 @@ public class AdminShopCategoryDto {
this.seoTitle = seoTitle;
}
public String getSeoTitleIt() {
return seoTitleIt;
}
public void setSeoTitleIt(String seoTitleIt) {
this.seoTitleIt = seoTitleIt;
}
public String getSeoTitleEn() {
return seoTitleEn;
}
public void setSeoTitleEn(String seoTitleEn) {
this.seoTitleEn = seoTitleEn;
}
public String getSeoTitleDe() {
return seoTitleDe;
}
public void setSeoTitleDe(String seoTitleDe) {
this.seoTitleDe = seoTitleDe;
}
public String getSeoTitleFr() {
return seoTitleFr;
}
public void setSeoTitleFr(String seoTitleFr) {
this.seoTitleFr = seoTitleFr;
}
public String getSeoDescription() {
return seoDescription;
}
@@ -93,6 +205,38 @@ public class AdminShopCategoryDto {
this.seoDescription = seoDescription;
}
public String getSeoDescriptionIt() {
return seoDescriptionIt;
}
public void setSeoDescriptionIt(String seoDescriptionIt) {
this.seoDescriptionIt = seoDescriptionIt;
}
public String getSeoDescriptionEn() {
return seoDescriptionEn;
}
public void setSeoDescriptionEn(String seoDescriptionEn) {
this.seoDescriptionEn = seoDescriptionEn;
}
public String getSeoDescriptionDe() {
return seoDescriptionDe;
}
public void setSeoDescriptionDe(String seoDescriptionDe) {
this.seoDescriptionDe = seoDescriptionDe;
}
public String getSeoDescriptionFr() {
return seoDescriptionFr;
}
public void setSeoDescriptionFr(String seoDescriptionFr) {
this.seoDescriptionFr = seoDescriptionFr;
}
public String getOgTitle() {
return ogTitle;
}

View File

@@ -9,6 +9,10 @@ public class AdminShopProductVariantDto {
private String sku;
private String variantLabel;
private String colorName;
private String colorLabelIt;
private String colorLabelEn;
private String colorLabelDe;
private String colorLabelFr;
private String colorHex;
private String internalMaterialCode;
private BigDecimal priceChf;
@@ -50,6 +54,38 @@ public class AdminShopProductVariantDto {
this.colorName = colorName;
}
public String getColorLabelIt() {
return colorLabelIt;
}
public void setColorLabelIt(String colorLabelIt) {
this.colorLabelIt = colorLabelIt;
}
public String getColorLabelEn() {
return colorLabelEn;
}
public void setColorLabelEn(String colorLabelEn) {
this.colorLabelEn = colorLabelEn;
}
public String getColorLabelDe() {
return colorLabelDe;
}
public void setColorLabelDe(String colorLabelDe) {
this.colorLabelDe = colorLabelDe;
}
public String getColorLabelFr() {
return colorLabelFr;
}
public void setColorLabelFr(String colorLabelFr) {
this.colorLabelFr = colorLabelFr;
}
public String getColorHex() {
return colorHex;
}

View File

@@ -6,6 +6,10 @@ public class AdminUpsertFilamentVariantRequest {
private Long materialTypeId;
private String variantDisplayName;
private String colorName;
private String colorLabelIt;
private String colorLabelEn;
private String colorLabelDe;
private String colorLabelFr;
private String colorHex;
private String finishType;
private String brand;
@@ -40,6 +44,38 @@ public class AdminUpsertFilamentVariantRequest {
this.colorName = colorName;
}
public String getColorLabelIt() {
return colorLabelIt;
}
public void setColorLabelIt(String colorLabelIt) {
this.colorLabelIt = colorLabelIt;
}
public String getColorLabelEn() {
return colorLabelEn;
}
public void setColorLabelEn(String colorLabelEn) {
this.colorLabelEn = colorLabelEn;
}
public String getColorLabelDe() {
return colorLabelDe;
}
public void setColorLabelDe(String colorLabelDe) {
this.colorLabelDe = colorLabelDe;
}
public String getColorLabelFr() {
return colorLabelFr;
}
public void setColorLabelFr(String colorLabelFr) {
this.colorLabelFr = colorLabelFr;
}
public String getColorHex() {
return colorHex;
}

View File

@@ -6,9 +6,25 @@ public class AdminUpsertShopCategoryRequest {
private UUID parentCategoryId;
private String slug;
private String name;
private String nameIt;
private String nameEn;
private String nameDe;
private String nameFr;
private String description;
private String descriptionIt;
private String descriptionEn;
private String descriptionDe;
private String descriptionFr;
private String seoTitle;
private String seoTitleIt;
private String seoTitleEn;
private String seoTitleDe;
private String seoTitleFr;
private String seoDescription;
private String seoDescriptionIt;
private String seoDescriptionEn;
private String seoDescriptionDe;
private String seoDescriptionFr;
private String ogTitle;
private String ogDescription;
private Boolean indexable;
@@ -39,6 +55,38 @@ public class AdminUpsertShopCategoryRequest {
this.name = name;
}
public String getNameIt() {
return nameIt;
}
public void setNameIt(String nameIt) {
this.nameIt = nameIt;
}
public String getNameEn() {
return nameEn;
}
public void setNameEn(String nameEn) {
this.nameEn = nameEn;
}
public String getNameDe() {
return nameDe;
}
public void setNameDe(String nameDe) {
this.nameDe = nameDe;
}
public String getNameFr() {
return nameFr;
}
public void setNameFr(String nameFr) {
this.nameFr = nameFr;
}
public String getDescription() {
return description;
}
@@ -47,6 +95,38 @@ public class AdminUpsertShopCategoryRequest {
this.description = description;
}
public String getDescriptionIt() {
return descriptionIt;
}
public void setDescriptionIt(String descriptionIt) {
this.descriptionIt = descriptionIt;
}
public String getDescriptionEn() {
return descriptionEn;
}
public void setDescriptionEn(String descriptionEn) {
this.descriptionEn = descriptionEn;
}
public String getDescriptionDe() {
return descriptionDe;
}
public void setDescriptionDe(String descriptionDe) {
this.descriptionDe = descriptionDe;
}
public String getDescriptionFr() {
return descriptionFr;
}
public void setDescriptionFr(String descriptionFr) {
this.descriptionFr = descriptionFr;
}
public String getSeoTitle() {
return seoTitle;
}
@@ -55,6 +135,38 @@ public class AdminUpsertShopCategoryRequest {
this.seoTitle = seoTitle;
}
public String getSeoTitleIt() {
return seoTitleIt;
}
public void setSeoTitleIt(String seoTitleIt) {
this.seoTitleIt = seoTitleIt;
}
public String getSeoTitleEn() {
return seoTitleEn;
}
public void setSeoTitleEn(String seoTitleEn) {
this.seoTitleEn = seoTitleEn;
}
public String getSeoTitleDe() {
return seoTitleDe;
}
public void setSeoTitleDe(String seoTitleDe) {
this.seoTitleDe = seoTitleDe;
}
public String getSeoTitleFr() {
return seoTitleFr;
}
public void setSeoTitleFr(String seoTitleFr) {
this.seoTitleFr = seoTitleFr;
}
public String getSeoDescription() {
return seoDescription;
}
@@ -63,6 +175,38 @@ public class AdminUpsertShopCategoryRequest {
this.seoDescription = seoDescription;
}
public String getSeoDescriptionIt() {
return seoDescriptionIt;
}
public void setSeoDescriptionIt(String seoDescriptionIt) {
this.seoDescriptionIt = seoDescriptionIt;
}
public String getSeoDescriptionEn() {
return seoDescriptionEn;
}
public void setSeoDescriptionEn(String seoDescriptionEn) {
this.seoDescriptionEn = seoDescriptionEn;
}
public String getSeoDescriptionDe() {
return seoDescriptionDe;
}
public void setSeoDescriptionDe(String seoDescriptionDe) {
this.seoDescriptionDe = seoDescriptionDe;
}
public String getSeoDescriptionFr() {
return seoDescriptionFr;
}
public void setSeoDescriptionFr(String seoDescriptionFr) {
this.seoDescriptionFr = seoDescriptionFr;
}
public String getOgTitle() {
return ogTitle;
}

View File

@@ -8,6 +8,10 @@ public class AdminUpsertShopProductVariantRequest {
private String sku;
private String variantLabel;
private String colorName;
private String colorLabelIt;
private String colorLabelEn;
private String colorLabelDe;
private String colorLabelFr;
private String colorHex;
private String internalMaterialCode;
private BigDecimal priceChf;
@@ -47,6 +51,38 @@ public class AdminUpsertShopProductVariantRequest {
this.colorName = colorName;
}
public String getColorLabelIt() {
return colorLabelIt;
}
public void setColorLabelIt(String colorLabelIt) {
this.colorLabelIt = colorLabelIt;
}
public String getColorLabelEn() {
return colorLabelEn;
}
public void setColorLabelEn(String colorLabelEn) {
this.colorLabelEn = colorLabelEn;
}
public String getColorLabelDe() {
return colorLabelDe;
}
public void setColorLabelDe(String colorLabelDe) {
this.colorLabelDe = colorLabelDe;
}
public String getColorLabelFr() {
return colorLabelFr;
}
public void setColorLabelFr(String colorLabelFr) {
this.colorLabelFr = colorLabelFr;
}
public String getColorHex() {
return colorHex;
}

View File

@@ -15,6 +15,10 @@ public record OptionsResponse(
Long id,
String name,
String colorName,
String colorLabelIt,
String colorLabelEn,
String colorLabelDe,
String colorLabelFr,
String hexColor,
String finishType,
Double stockSpools,

View File

@@ -17,9 +17,17 @@ public class OrderItemDto {
private String shopProductName;
private String shopVariantLabel;
private String shopVariantColorName;
private String shopVariantColorLabelIt;
private String shopVariantColorLabelEn;
private String shopVariantColorLabelDe;
private String shopVariantColorLabelFr;
private String shopVariantColorHex;
private String filamentVariantDisplayName;
private String filamentColorName;
private String filamentColorLabelIt;
private String filamentColorLabelEn;
private String filamentColorLabelDe;
private String filamentColorLabelFr;
private String filamentColorHex;
private String quality;
private BigDecimal nozzleDiameterMm;
@@ -73,6 +81,18 @@ public class OrderItemDto {
public String getShopVariantColorName() { return shopVariantColorName; }
public void setShopVariantColorName(String shopVariantColorName) { this.shopVariantColorName = shopVariantColorName; }
public String getShopVariantColorLabelIt() { return shopVariantColorLabelIt; }
public void setShopVariantColorLabelIt(String shopVariantColorLabelIt) { this.shopVariantColorLabelIt = shopVariantColorLabelIt; }
public String getShopVariantColorLabelEn() { return shopVariantColorLabelEn; }
public void setShopVariantColorLabelEn(String shopVariantColorLabelEn) { this.shopVariantColorLabelEn = shopVariantColorLabelEn; }
public String getShopVariantColorLabelDe() { return shopVariantColorLabelDe; }
public void setShopVariantColorLabelDe(String shopVariantColorLabelDe) { this.shopVariantColorLabelDe = shopVariantColorLabelDe; }
public String getShopVariantColorLabelFr() { return shopVariantColorLabelFr; }
public void setShopVariantColorLabelFr(String shopVariantColorLabelFr) { this.shopVariantColorLabelFr = shopVariantColorLabelFr; }
public String getShopVariantColorHex() { return shopVariantColorHex; }
public void setShopVariantColorHex(String shopVariantColorHex) { this.shopVariantColorHex = shopVariantColorHex; }
@@ -82,6 +102,18 @@ public class OrderItemDto {
public String getFilamentColorName() { return filamentColorName; }
public void setFilamentColorName(String filamentColorName) { this.filamentColorName = filamentColorName; }
public String getFilamentColorLabelIt() { return filamentColorLabelIt; }
public void setFilamentColorLabelIt(String filamentColorLabelIt) { this.filamentColorLabelIt = filamentColorLabelIt; }
public String getFilamentColorLabelEn() { return filamentColorLabelEn; }
public void setFilamentColorLabelEn(String filamentColorLabelEn) { this.filamentColorLabelEn = filamentColorLabelEn; }
public String getFilamentColorLabelDe() { return filamentColorLabelDe; }
public void setFilamentColorLabelDe(String filamentColorLabelDe) { this.filamentColorLabelDe = filamentColorLabelDe; }
public String getFilamentColorLabelFr() { return filamentColorLabelFr; }
public void setFilamentColorLabelFr(String filamentColorLabelFr) { this.filamentColorLabelFr = filamentColorLabelFr; }
public String getFilamentColorHex() { return filamentColorHex; }
public void setFilamentColorHex(String filamentColorHex) { this.filamentColorHex = filamentColorHex; }

View File

@@ -8,6 +8,7 @@ public record ShopProductVariantOptionDto(
String sku,
String variantLabel,
String colorName,
String colorLabel,
String colorHex,
BigDecimal priceChf,
Boolean isDefault

View File

@@ -24,6 +24,18 @@ public class FilamentVariant {
@Column(name = "color_name", nullable = false, length = Integer.MAX_VALUE)
private String colorName;
@Column(name = "color_label_it", length = Integer.MAX_VALUE)
private String colorLabelIt;
@Column(name = "color_label_en", length = Integer.MAX_VALUE)
private String colorLabelEn;
@Column(name = "color_label_de", length = Integer.MAX_VALUE)
private String colorLabelDe;
@Column(name = "color_label_fr", length = Integer.MAX_VALUE)
private String colorLabelFr;
@Column(name = "color_hex", length = Integer.MAX_VALUE)
private String colorHex;
@@ -93,6 +105,38 @@ public class FilamentVariant {
this.colorName = colorName;
}
public String getColorLabelIt() {
return colorLabelIt;
}
public void setColorLabelIt(String colorLabelIt) {
this.colorLabelIt = colorLabelIt;
}
public String getColorLabelEn() {
return colorLabelEn;
}
public void setColorLabelEn(String colorLabelEn) {
this.colorLabelEn = colorLabelEn;
}
public String getColorLabelDe() {
return colorLabelDe;
}
public void setColorLabelDe(String colorLabelDe) {
this.colorLabelDe = colorLabelDe;
}
public String getColorLabelFr() {
return colorLabelFr;
}
public void setColorLabelFr(String colorLabelFr) {
this.colorLabelFr = colorLabelFr;
}
public String getColorHex() {
return colorHex;
}
@@ -173,4 +217,60 @@ public class FilamentVariant {
this.createdAt = createdAt;
}
public String getColorLabelForLanguage(String language) {
return resolveLocalizedValue(
language,
colorName,
colorLabelIt,
colorLabelEn,
colorLabelDe,
colorLabelFr
);
}
private String resolveLocalizedValue(String language,
String fallback,
String valueIt,
String valueEn,
String valueDe,
String valueFr) {
String normalizedLanguage = normalizeLanguage(language);
String preferred = switch (normalizedLanguage) {
case "it" -> valueIt;
case "en" -> valueEn;
case "de" -> valueDe;
case "fr" -> valueFr;
default -> null;
};
String resolved = firstNonBlank(preferred, fallback);
if (resolved != null) {
return resolved;
}
return firstNonBlank(valueIt, valueEn, valueDe, valueFr);
}
private String normalizeLanguage(String language) {
if (language == null) {
return "";
}
String normalized = language.trim().toLowerCase();
int separatorIndex = normalized.indexOf('-');
if (separatorIndex > 0) {
normalized = normalized.substring(0, separatorIndex);
}
return normalized;
}
private String firstNonBlank(String... values) {
if (values == null) {
return null;
}
for (String value : values) {
if (value != null && !value.isBlank()) {
return value;
}
}
return null;
}
}

View File

@@ -15,6 +15,7 @@ import jakarta.persistence.Table;
import org.hibernate.annotations.ColumnDefault;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;
@Entity
@@ -23,6 +24,8 @@ import java.util.UUID;
@Index(name = "ix_shop_category_active_sort", columnList = "is_active, sort_order")
})
public class ShopCategory {
public static final List<String> SUPPORTED_LANGUAGES = List.of("it", "en", "de", "fr");
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "shop_category_id", nullable = false)
@@ -38,15 +41,63 @@ public class ShopCategory {
@Column(name = "name", nullable = false, length = Integer.MAX_VALUE)
private String name;
@Column(name = "name_it", length = Integer.MAX_VALUE)
private String nameIt;
@Column(name = "name_en", length = Integer.MAX_VALUE)
private String nameEn;
@Column(name = "name_de", length = Integer.MAX_VALUE)
private String nameDe;
@Column(name = "name_fr", length = Integer.MAX_VALUE)
private String nameFr;
@Column(name = "description", length = Integer.MAX_VALUE)
private String description;
@Column(name = "description_it", length = Integer.MAX_VALUE)
private String descriptionIt;
@Column(name = "description_en", length = Integer.MAX_VALUE)
private String descriptionEn;
@Column(name = "description_de", length = Integer.MAX_VALUE)
private String descriptionDe;
@Column(name = "description_fr", length = Integer.MAX_VALUE)
private String descriptionFr;
@Column(name = "seo_title", length = Integer.MAX_VALUE)
private String seoTitle;
@Column(name = "seo_title_it", length = Integer.MAX_VALUE)
private String seoTitleIt;
@Column(name = "seo_title_en", length = Integer.MAX_VALUE)
private String seoTitleEn;
@Column(name = "seo_title_de", length = Integer.MAX_VALUE)
private String seoTitleDe;
@Column(name = "seo_title_fr", length = Integer.MAX_VALUE)
private String seoTitleFr;
@Column(name = "seo_description", length = Integer.MAX_VALUE)
private String seoDescription;
@Column(name = "seo_description_it", length = Integer.MAX_VALUE)
private String seoDescriptionIt;
@Column(name = "seo_description_en", length = Integer.MAX_VALUE)
private String seoDescriptionEn;
@Column(name = "seo_description_de", length = Integer.MAX_VALUE)
private String seoDescriptionDe;
@Column(name = "seo_description_fr", length = Integer.MAX_VALUE)
private String seoDescriptionFr;
@Column(name = "og_title", length = Integer.MAX_VALUE)
private String ogTitle;
@@ -139,6 +190,38 @@ public class ShopCategory {
this.name = name;
}
public String getNameIt() {
return nameIt;
}
public void setNameIt(String nameIt) {
this.nameIt = nameIt;
}
public String getNameEn() {
return nameEn;
}
public void setNameEn(String nameEn) {
this.nameEn = nameEn;
}
public String getNameDe() {
return nameDe;
}
public void setNameDe(String nameDe) {
this.nameDe = nameDe;
}
public String getNameFr() {
return nameFr;
}
public void setNameFr(String nameFr) {
this.nameFr = nameFr;
}
public String getDescription() {
return description;
}
@@ -147,6 +230,38 @@ public class ShopCategory {
this.description = description;
}
public String getDescriptionIt() {
return descriptionIt;
}
public void setDescriptionIt(String descriptionIt) {
this.descriptionIt = descriptionIt;
}
public String getDescriptionEn() {
return descriptionEn;
}
public void setDescriptionEn(String descriptionEn) {
this.descriptionEn = descriptionEn;
}
public String getDescriptionDe() {
return descriptionDe;
}
public void setDescriptionDe(String descriptionDe) {
this.descriptionDe = descriptionDe;
}
public String getDescriptionFr() {
return descriptionFr;
}
public void setDescriptionFr(String descriptionFr) {
this.descriptionFr = descriptionFr;
}
public String getSeoTitle() {
return seoTitle;
}
@@ -155,6 +270,38 @@ public class ShopCategory {
this.seoTitle = seoTitle;
}
public String getSeoTitleIt() {
return seoTitleIt;
}
public void setSeoTitleIt(String seoTitleIt) {
this.seoTitleIt = seoTitleIt;
}
public String getSeoTitleEn() {
return seoTitleEn;
}
public void setSeoTitleEn(String seoTitleEn) {
this.seoTitleEn = seoTitleEn;
}
public String getSeoTitleDe() {
return seoTitleDe;
}
public void setSeoTitleDe(String seoTitleDe) {
this.seoTitleDe = seoTitleDe;
}
public String getSeoTitleFr() {
return seoTitleFr;
}
public void setSeoTitleFr(String seoTitleFr) {
this.seoTitleFr = seoTitleFr;
}
public String getSeoDescription() {
return seoDescription;
}
@@ -163,6 +310,38 @@ public class ShopCategory {
this.seoDescription = seoDescription;
}
public String getSeoDescriptionIt() {
return seoDescriptionIt;
}
public void setSeoDescriptionIt(String seoDescriptionIt) {
this.seoDescriptionIt = seoDescriptionIt;
}
public String getSeoDescriptionEn() {
return seoDescriptionEn;
}
public void setSeoDescriptionEn(String seoDescriptionEn) {
this.seoDescriptionEn = seoDescriptionEn;
}
public String getSeoDescriptionDe() {
return seoDescriptionDe;
}
public void setSeoDescriptionDe(String seoDescriptionDe) {
this.seoDescriptionDe = seoDescriptionDe;
}
public String getSeoDescriptionFr() {
return seoDescriptionFr;
}
public void setSeoDescriptionFr(String seoDescriptionFr) {
this.seoDescriptionFr = seoDescriptionFr;
}
public String getOgTitle() {
return ogTitle;
}
@@ -218,4 +397,109 @@ public class ShopCategory {
public void setUpdatedAt(OffsetDateTime updatedAt) {
this.updatedAt = updatedAt;
}
public String getNameForLanguage(String language) {
return resolveLocalizedValue(language, name, nameIt, nameEn, nameDe, nameFr);
}
public void setNameForLanguage(String language, String value) {
switch (normalizeLanguage(language)) {
case "it" -> nameIt = value;
case "en" -> nameEn = value;
case "de" -> nameDe = value;
case "fr" -> nameFr = value;
default -> {
}
}
}
public String getDescriptionForLanguage(String language) {
return resolveLocalizedValue(language, description, descriptionIt, descriptionEn, descriptionDe, descriptionFr);
}
public void setDescriptionForLanguage(String language, String value) {
switch (normalizeLanguage(language)) {
case "it" -> descriptionIt = value;
case "en" -> descriptionEn = value;
case "de" -> descriptionDe = value;
case "fr" -> descriptionFr = value;
default -> {
}
}
}
public String getSeoTitleForLanguage(String language) {
return resolveLocalizedValue(language, seoTitle, seoTitleIt, seoTitleEn, seoTitleDe, seoTitleFr);
}
public void setSeoTitleForLanguage(String language, String value) {
switch (normalizeLanguage(language)) {
case "it" -> seoTitleIt = value;
case "en" -> seoTitleEn = value;
case "de" -> seoTitleDe = value;
case "fr" -> seoTitleFr = value;
default -> {
}
}
}
public String getSeoDescriptionForLanguage(String language) {
return resolveLocalizedValue(language, seoDescription, seoDescriptionIt, seoDescriptionEn, seoDescriptionDe, seoDescriptionFr);
}
public void setSeoDescriptionForLanguage(String language, String value) {
switch (normalizeLanguage(language)) {
case "it" -> seoDescriptionIt = value;
case "en" -> seoDescriptionEn = value;
case "de" -> seoDescriptionDe = value;
case "fr" -> seoDescriptionFr = value;
default -> {
}
}
}
private String resolveLocalizedValue(String language,
String fallback,
String valueIt,
String valueEn,
String valueDe,
String valueFr) {
String normalizedLanguage = normalizeLanguage(language);
String preferred = switch (normalizedLanguage) {
case "it" -> valueIt;
case "en" -> valueEn;
case "de" -> valueDe;
case "fr" -> valueFr;
default -> null;
};
String resolved = firstNonBlank(preferred, fallback);
if (resolved != null) {
return resolved;
}
return firstNonBlank(valueIt, valueEn, valueDe, valueFr);
}
private String normalizeLanguage(String language) {
if (language == null) {
return "";
}
String normalized = language.trim().toLowerCase();
int separatorIndex = normalized.indexOf('-');
if (separatorIndex > 0) {
normalized = normalized.substring(0, separatorIndex);
}
return normalized;
}
private String firstNonBlank(String... values) {
if (values == null) {
return null;
}
for (String value : values) {
if (value != null && !value.isBlank()) {
return value;
}
}
return null;
}
}

View File

@@ -42,6 +42,18 @@ public class ShopProductVariant {
@Column(name = "color_name", nullable = false, length = Integer.MAX_VALUE)
private String colorName;
@Column(name = "color_label_it", length = Integer.MAX_VALUE)
private String colorLabelIt;
@Column(name = "color_label_en", length = Integer.MAX_VALUE)
private String colorLabelEn;
@Column(name = "color_label_de", length = Integer.MAX_VALUE)
private String colorLabelDe;
@Column(name = "color_label_fr", length = Integer.MAX_VALUE)
private String colorLabelFr;
@Column(name = "color_hex", length = Integer.MAX_VALUE)
private String colorHex;
@@ -152,6 +164,38 @@ public class ShopProductVariant {
this.colorName = colorName;
}
public String getColorLabelIt() {
return colorLabelIt;
}
public void setColorLabelIt(String colorLabelIt) {
this.colorLabelIt = colorLabelIt;
}
public String getColorLabelEn() {
return colorLabelEn;
}
public void setColorLabelEn(String colorLabelEn) {
this.colorLabelEn = colorLabelEn;
}
public String getColorLabelDe() {
return colorLabelDe;
}
public void setColorLabelDe(String colorLabelDe) {
this.colorLabelDe = colorLabelDe;
}
public String getColorLabelFr() {
return colorLabelFr;
}
public void setColorLabelFr(String colorLabelFr) {
this.colorLabelFr = colorLabelFr;
}
public String getColorHex() {
return colorHex;
}
@@ -215,4 +259,60 @@ public class ShopProductVariant {
public void setUpdatedAt(OffsetDateTime updatedAt) {
this.updatedAt = updatedAt;
}
public String getColorLabelForLanguage(String language) {
return resolveLocalizedValue(
language,
colorName,
colorLabelIt,
colorLabelEn,
colorLabelDe,
colorLabelFr
);
}
private String resolveLocalizedValue(String language,
String fallback,
String valueIt,
String valueEn,
String valueDe,
String valueFr) {
String normalizedLanguage = normalizeLanguage(language);
String preferred = switch (normalizedLanguage) {
case "it" -> valueIt;
case "en" -> valueEn;
case "de" -> valueDe;
case "fr" -> valueFr;
default -> null;
};
String resolved = firstNonBlank(preferred, fallback);
if (resolved != null) {
return resolved;
}
return firstNonBlank(valueIt, valueEn, valueDe, valueFr);
}
private String normalizeLanguage(String language) {
if (language == null) {
return "";
}
String normalized = language.trim().toLowerCase();
int separatorIndex = normalized.indexOf('-');
if (separatorIndex > 0) {
normalized = normalized.substring(0, separatorIndex);
}
return normalized;
}
private String firstNonBlank(String... values) {
if (values == null) {
return null;
}
for (String value : values) {
if (value != null && !value.isBlank()) {
return value;
}
}
return null;
}
}

View File

@@ -161,10 +161,21 @@ public class AdminFilamentControllerService {
String normalizedColorHex = normalizeAndValidateColorHex(payload.getColorHex());
String normalizedFinishType = normalizeAndValidateFinishType(payload.getFinishType(), payload.getIsMatte());
String normalizedBrand = normalizeOptional(payload.getBrand());
String fallbackColorLabel = firstNonBlank(
normalizeOptional(payload.getColorLabelIt()),
normalizeOptional(payload.getColorLabelEn()),
normalizeOptional(payload.getColorLabelDe()),
normalizeOptional(payload.getColorLabelFr()),
normalizedColorName
);
variant.setFilamentMaterialType(material);
variant.setVariantDisplayName(normalizedDisplayName);
variant.setColorName(normalizedColorName);
variant.setColorLabelIt(firstNonBlank(normalizeOptional(payload.getColorLabelIt()), fallbackColorLabel));
variant.setColorLabelEn(firstNonBlank(normalizeOptional(payload.getColorLabelEn()), fallbackColorLabel));
variant.setColorLabelDe(firstNonBlank(normalizeOptional(payload.getColorLabelDe()), fallbackColorLabel));
variant.setColorLabelFr(firstNonBlank(normalizeOptional(payload.getColorLabelFr()), fallbackColorLabel));
variant.setColorHex(normalizedColorHex);
variant.setFinishType(normalizedFinishType);
variant.setBrand(normalizedBrand);
@@ -226,6 +237,18 @@ public class AdminFilamentControllerService {
return normalized.isBlank() ? null : normalized;
}
private String firstNonBlank(String... values) {
if (values == null) {
return null;
}
for (String value : values) {
if (value != null && !value.isBlank()) {
return value;
}
}
return null;
}
private FilamentMaterialType validateAndResolveMaterial(AdminUpsertFilamentVariantRequest payload) {
if (payload == null || payload.getMaterialTypeId() == null) {
throw new ResponseStatusException(BAD_REQUEST, "Material type id is required");
@@ -306,6 +329,10 @@ public class AdminFilamentControllerService {
dto.setVariantDisplayName(variant.getVariantDisplayName());
dto.setColorName(variant.getColorName());
dto.setColorLabelIt(variant.getColorLabelIt());
dto.setColorLabelEn(variant.getColorLabelEn());
dto.setColorLabelDe(variant.getColorLabelDe());
dto.setColorLabelFr(variant.getColorLabelFr());
dto.setColorHex(variant.getColorHex());
dto.setFinishType(variant.getFinishType());
dto.setBrand(variant.getBrand());

View File

@@ -67,13 +67,13 @@ public class AdminShopCategoryControllerService {
@Transactional
public AdminShopCategoryDto createCategory(AdminUpsertShopCategoryRequest payload) {
ensurePayload(payload);
String normalizedName = normalizeRequiredName(payload.getName());
String normalizedSlug = normalizeAndValidateSlug(payload.getSlug(), normalizedName);
LocalizedCategoryContent localizedContent = normalizeLocalizedCategoryContent(payload);
String normalizedSlug = normalizeAndValidateSlug(payload.getSlug(), localizedContent.defaultName());
ensureSlugAvailable(normalizedSlug, null);
ShopCategory category = new ShopCategory();
category.setCreatedAt(OffsetDateTime.now());
applyPayload(category, payload, normalizedName, normalizedSlug, null);
applyPayload(category, payload, localizedContent, normalizedSlug, null);
ShopCategory saved = shopCategoryRepository.save(category);
return getCategory(saved.getId());
@@ -86,11 +86,11 @@ public class AdminShopCategoryControllerService {
ShopCategory category = shopCategoryRepository.findById(categoryId)
.orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Shop category not found"));
String normalizedName = normalizeRequiredName(payload.getName());
String normalizedSlug = normalizeAndValidateSlug(payload.getSlug(), normalizedName);
LocalizedCategoryContent localizedContent = normalizeLocalizedCategoryContent(payload);
String normalizedSlug = normalizeAndValidateSlug(payload.getSlug(), localizedContent.defaultName());
ensureSlugAvailable(normalizedSlug, category.getId());
applyPayload(category, payload, normalizedName, normalizedSlug, category.getId());
applyPayload(category, payload, localizedContent, normalizedSlug, category.getId());
ShopCategory saved = shopCategoryRepository.save(category);
return getCategory(saved.getId());
}
@@ -112,17 +112,33 @@ public class AdminShopCategoryControllerService {
private void applyPayload(ShopCategory category,
AdminUpsertShopCategoryRequest payload,
String normalizedName,
LocalizedCategoryContent localizedContent,
String normalizedSlug,
UUID currentCategoryId) {
ShopCategory parentCategory = resolveParentCategory(payload.getParentCategoryId(), currentCategoryId);
category.setParentCategory(parentCategory);
category.setSlug(normalizedSlug);
category.setName(normalizedName);
category.setDescription(normalizeOptional(payload.getDescription()));
category.setSeoTitle(normalizeOptional(payload.getSeoTitle()));
category.setSeoDescription(normalizeOptional(payload.getSeoDescription()));
category.setName(localizedContent.defaultName());
category.setNameIt(localizedContent.names().get("it"));
category.setNameEn(localizedContent.names().get("en"));
category.setNameDe(localizedContent.names().get("de"));
category.setNameFr(localizedContent.names().get("fr"));
category.setDescription(localizedContent.defaultDescription());
category.setDescriptionIt(localizedContent.descriptions().get("it"));
category.setDescriptionEn(localizedContent.descriptions().get("en"));
category.setDescriptionDe(localizedContent.descriptions().get("de"));
category.setDescriptionFr(localizedContent.descriptions().get("fr"));
category.setSeoTitle(localizedContent.defaultSeoTitle());
category.setSeoTitleIt(localizedContent.seoTitles().get("it"));
category.setSeoTitleEn(localizedContent.seoTitles().get("en"));
category.setSeoTitleDe(localizedContent.seoTitles().get("de"));
category.setSeoTitleFr(localizedContent.seoTitles().get("fr"));
category.setSeoDescription(localizedContent.defaultSeoDescription());
category.setSeoDescriptionIt(localizedContent.seoDescriptions().get("it"));
category.setSeoDescriptionEn(localizedContent.seoDescriptions().get("en"));
category.setSeoDescriptionDe(localizedContent.seoDescriptions().get("de"));
category.setSeoDescriptionFr(localizedContent.seoDescriptions().get("fr"));
category.setOgTitle(normalizeOptional(payload.getOgTitle()));
category.setOgDescription(normalizeOptional(payload.getOgDescription()));
category.setIndexable(payload.getIndexable() == null || payload.getIndexable());
@@ -161,14 +177,6 @@ public class AdminShopCategoryControllerService {
}
}
private String normalizeRequiredName(String name) {
String normalized = normalizeOptional(name);
if (normalized == null) {
throw new ResponseStatusException(BAD_REQUEST, "Category name is required");
}
return normalized;
}
private String normalizeAndValidateSlug(String slug, String fallbackName) {
String source = normalizeOptional(slug);
if (source == null) {
@@ -203,6 +211,103 @@ public class AdminShopCategoryControllerService {
return normalized.isBlank() ? null : normalized;
}
private String normalizeRequired(String value, String message) {
String normalized = normalizeOptional(value);
if (normalized == null) {
throw new ResponseStatusException(BAD_REQUEST, message);
}
return normalized;
}
private LocalizedCategoryContent normalizeLocalizedCategoryContent(AdminUpsertShopCategoryRequest payload) {
String legacyName = normalizeOptional(payload.getName());
String fallbackName = firstNonBlank(
legacyName,
normalizeOptional(payload.getNameIt()),
normalizeOptional(payload.getNameEn()),
normalizeOptional(payload.getNameDe()),
normalizeOptional(payload.getNameFr())
);
if (fallbackName == null) {
throw new ResponseStatusException(BAD_REQUEST, "Category name is required");
}
Map<String, String> names = new LinkedHashMap<>();
names.put("it", normalizeRequired(firstNonBlank(normalizeOptional(payload.getNameIt()), fallbackName), "Italian category name is required"));
names.put("en", normalizeRequired(firstNonBlank(normalizeOptional(payload.getNameEn()), fallbackName), "English category name is required"));
names.put("de", normalizeRequired(firstNonBlank(normalizeOptional(payload.getNameDe()), fallbackName), "German category name is required"));
names.put("fr", normalizeRequired(firstNonBlank(normalizeOptional(payload.getNameFr()), fallbackName), "French category name is required"));
String fallbackDescription = firstNonBlank(
normalizeOptional(payload.getDescription()),
normalizeOptional(payload.getDescriptionIt()),
normalizeOptional(payload.getDescriptionEn()),
normalizeOptional(payload.getDescriptionDe()),
normalizeOptional(payload.getDescriptionFr())
);
Map<String, String> descriptions = new LinkedHashMap<>();
descriptions.put("it", firstNonBlank(normalizeOptional(payload.getDescriptionIt()), fallbackDescription));
descriptions.put("en", firstNonBlank(normalizeOptional(payload.getDescriptionEn()), fallbackDescription));
descriptions.put("de", firstNonBlank(normalizeOptional(payload.getDescriptionDe()), fallbackDescription));
descriptions.put("fr", firstNonBlank(normalizeOptional(payload.getDescriptionFr()), fallbackDescription));
String fallbackSeoTitle = firstNonBlank(
normalizeOptional(payload.getSeoTitle()),
normalizeOptional(payload.getSeoTitleIt()),
normalizeOptional(payload.getSeoTitleEn()),
normalizeOptional(payload.getSeoTitleDe()),
normalizeOptional(payload.getSeoTitleFr())
);
Map<String, String> seoTitles = new LinkedHashMap<>();
seoTitles.put("it", firstNonBlank(normalizeOptional(payload.getSeoTitleIt()), fallbackSeoTitle));
seoTitles.put("en", firstNonBlank(normalizeOptional(payload.getSeoTitleEn()), fallbackSeoTitle));
seoTitles.put("de", firstNonBlank(normalizeOptional(payload.getSeoTitleDe()), fallbackSeoTitle));
seoTitles.put("fr", firstNonBlank(normalizeOptional(payload.getSeoTitleFr()), fallbackSeoTitle));
String fallbackSeoDescription = firstNonBlank(
normalizeOptional(payload.getSeoDescription()),
normalizeOptional(payload.getSeoDescriptionIt()),
normalizeOptional(payload.getSeoDescriptionEn()),
normalizeOptional(payload.getSeoDescriptionDe()),
normalizeOptional(payload.getSeoDescriptionFr())
);
Map<String, String> seoDescriptions = new LinkedHashMap<>();
seoDescriptions.put("it", validateSeoDescriptionLength(firstNonBlank(normalizeOptional(payload.getSeoDescriptionIt()), fallbackSeoDescription), "Italian"));
seoDescriptions.put("en", validateSeoDescriptionLength(firstNonBlank(normalizeOptional(payload.getSeoDescriptionEn()), fallbackSeoDescription), "English"));
seoDescriptions.put("de", validateSeoDescriptionLength(firstNonBlank(normalizeOptional(payload.getSeoDescriptionDe()), fallbackSeoDescription), "German"));
seoDescriptions.put("fr", validateSeoDescriptionLength(firstNonBlank(normalizeOptional(payload.getSeoDescriptionFr()), fallbackSeoDescription), "French"));
return new LocalizedCategoryContent(
names.get("it"),
firstNonBlank(descriptions.get("it"), fallbackDescription),
firstNonBlank(seoTitles.get("it"), fallbackSeoTitle),
firstNonBlank(seoDescriptions.get("it"), fallbackSeoDescription),
names,
descriptions,
seoTitles,
seoDescriptions
);
}
private String validateSeoDescriptionLength(String value, String languageLabel) {
if (value != null && value.length() > 160) {
throw new ResponseStatusException(BAD_REQUEST, languageLabel + " SEO description must be at most 160 characters");
}
return value;
}
private String firstNonBlank(String... values) {
if (values == null) {
return null;
}
for (String value : values) {
if (value != null && !value.isBlank()) {
return value;
}
}
return null;
}
private CategoryContext buildContext() {
List<ShopCategory> categories = shopCategoryRepository.findAllByOrderBySortOrderAscNameAsc();
List<ShopProduct> products = shopProductRepository.findAll();
@@ -278,9 +383,25 @@ public class AdminShopCategoryControllerService {
dto.setParentCategoryName(category.getParentCategory() != null ? category.getParentCategory().getName() : null);
dto.setSlug(category.getSlug());
dto.setName(category.getName());
dto.setNameIt(category.getNameIt());
dto.setNameEn(category.getNameEn());
dto.setNameDe(category.getNameDe());
dto.setNameFr(category.getNameFr());
dto.setDescription(category.getDescription());
dto.setDescriptionIt(category.getDescriptionIt());
dto.setDescriptionEn(category.getDescriptionEn());
dto.setDescriptionDe(category.getDescriptionDe());
dto.setDescriptionFr(category.getDescriptionFr());
dto.setSeoTitle(category.getSeoTitle());
dto.setSeoTitleIt(category.getSeoTitleIt());
dto.setSeoTitleEn(category.getSeoTitleEn());
dto.setSeoTitleDe(category.getSeoTitleDe());
dto.setSeoTitleFr(category.getSeoTitleFr());
dto.setSeoDescription(category.getSeoDescription());
dto.setSeoDescriptionIt(category.getSeoDescriptionIt());
dto.setSeoDescriptionEn(category.getSeoDescriptionEn());
dto.setSeoDescriptionDe(category.getSeoDescriptionDe());
dto.setSeoDescriptionFr(category.getSeoDescriptionFr());
dto.setOgTitle(category.getOgTitle());
dto.setOgDescription(category.getOgDescription());
dto.setIndexable(category.getIndexable());
@@ -331,4 +452,16 @@ public class AdminShopCategoryControllerService {
Map<UUID, Integer> descendantProductCounts
) {
}
private record LocalizedCategoryContent(
String defaultName,
String defaultDescription,
String defaultSeoTitle,
String defaultSeoDescription,
Map<String, String> names,
Map<String, String> descriptions,
Map<String, String> seoTitles,
Map<String, String> seoDescriptions
) {
}
}

View File

@@ -353,6 +353,13 @@ public class AdminShopProductControllerService {
String normalizedColorName = normalizeRequired(payload.getColorName(), "Variant colorName is required");
String normalizedVariantLabel = normalizeOptional(payload.getVariantLabel());
String normalizedSku = normalizeOptional(payload.getSku());
String fallbackColorLabel = firstNonBlank(
normalizeOptional(payload.getColorLabelIt()),
normalizeOptional(payload.getColorLabelEn()),
normalizeOptional(payload.getColorLabelDe()),
normalizeOptional(payload.getColorLabelFr()),
normalizedColorName
);
String normalizedMaterialCode = normalizeRequired(
payload.getInternalMaterialCode(),
"Variant internalMaterialCode is required"
@@ -380,6 +387,10 @@ public class AdminShopProductControllerService {
variant.setSku(normalizedSku);
variant.setVariantLabel(normalizedVariantLabel != null ? normalizedVariantLabel : normalizedColorName);
variant.setColorName(normalizedColorName);
variant.setColorLabelIt(firstNonBlank(normalizeOptional(payload.getColorLabelIt()), fallbackColorLabel));
variant.setColorLabelEn(firstNonBlank(normalizeOptional(payload.getColorLabelEn()), fallbackColorLabel));
variant.setColorLabelDe(firstNonBlank(normalizeOptional(payload.getColorLabelDe()), fallbackColorLabel));
variant.setColorLabelFr(firstNonBlank(normalizeOptional(payload.getColorLabelFr()), fallbackColorLabel));
variant.setColorHex(normalizeColorHex(payload.getColorHex()));
variant.setInternalMaterialCode(normalizedMaterialCode);
variant.setPriceChf(price);
@@ -531,6 +542,10 @@ public class AdminShopProductControllerService {
dto.setSku(variant.getSku());
dto.setVariantLabel(variant.getVariantLabel());
dto.setColorName(variant.getColorName());
dto.setColorLabelIt(variant.getColorLabelIt());
dto.setColorLabelEn(variant.getColorLabelEn());
dto.setColorLabelDe(variant.getColorLabelDe());
dto.setColorLabelFr(variant.getColorLabelFr());
dto.setColorHex(variant.getColorHex());
dto.setInternalMaterialCode(variant.getInternalMaterialCode());
dto.setPriceChf(variant.getPriceChf());

View File

@@ -280,11 +280,19 @@ public class AdminOrderControllerService {
itemDto.setShopProductName(item.getShopProductName());
itemDto.setShopVariantLabel(item.getShopVariantLabel());
itemDto.setShopVariantColorName(item.getShopVariantColorName());
itemDto.setShopVariantColorLabelIt(item.getShopProductVariant() != null ? item.getShopProductVariant().getColorLabelIt() : null);
itemDto.setShopVariantColorLabelEn(item.getShopProductVariant() != null ? item.getShopProductVariant().getColorLabelEn() : null);
itemDto.setShopVariantColorLabelDe(item.getShopProductVariant() != null ? item.getShopProductVariant().getColorLabelDe() : null);
itemDto.setShopVariantColorLabelFr(item.getShopProductVariant() != null ? item.getShopProductVariant().getColorLabelFr() : null);
itemDto.setShopVariantColorHex(item.getShopVariantColorHex());
if (item.getFilamentVariant() != null) {
itemDto.setFilamentVariantId(item.getFilamentVariant().getId());
itemDto.setFilamentVariantDisplayName(item.getFilamentVariant().getVariantDisplayName());
itemDto.setFilamentColorName(item.getFilamentVariant().getColorName());
itemDto.setFilamentColorLabelIt(item.getFilamentVariant().getColorLabelIt());
itemDto.setFilamentColorLabelEn(item.getFilamentVariant().getColorLabelEn());
itemDto.setFilamentColorLabelDe(item.getFilamentVariant().getColorLabelDe());
itemDto.setFilamentColorLabelFr(item.getFilamentVariant().getColorLabelFr());
itemDto.setFilamentColorHex(item.getFilamentVariant().getColorHex());
}
itemDto.setQuality(item.getQuality());

View File

@@ -334,11 +334,19 @@ public class OrderControllerService {
itemDto.setShopProductName(item.getShopProductName());
itemDto.setShopVariantLabel(item.getShopVariantLabel());
itemDto.setShopVariantColorName(item.getShopVariantColorName());
itemDto.setShopVariantColorLabelIt(item.getShopProductVariant() != null ? item.getShopProductVariant().getColorLabelIt() : null);
itemDto.setShopVariantColorLabelEn(item.getShopProductVariant() != null ? item.getShopProductVariant().getColorLabelEn() : null);
itemDto.setShopVariantColorLabelDe(item.getShopProductVariant() != null ? item.getShopProductVariant().getColorLabelDe() : null);
itemDto.setShopVariantColorLabelFr(item.getShopProductVariant() != null ? item.getShopProductVariant().getColorLabelFr() : null);
itemDto.setShopVariantColorHex(item.getShopVariantColorHex());
if (item.getFilamentVariant() != null) {
itemDto.setFilamentVariantId(item.getFilamentVariant().getId());
itemDto.setFilamentVariantDisplayName(item.getFilamentVariant().getVariantDisplayName());
itemDto.setFilamentColorName(item.getFilamentVariant().getColorName());
itemDto.setFilamentColorLabelIt(item.getFilamentVariant().getColorLabelIt());
itemDto.setFilamentColorLabelEn(item.getFilamentVariant().getColorLabelEn());
itemDto.setFilamentColorLabelDe(item.getFilamentVariant().getColorLabelDe());
itemDto.setFilamentColorLabelFr(item.getFilamentVariant().getColorLabelFr());
itemDto.setFilamentColorHex(item.getFilamentVariant().getColorHex());
}
itemDto.setQuality(item.getQuality());

View File

@@ -81,7 +81,15 @@ public class QuoteSessionResponseAssembler {
dto.put("shopProductName", item.getShopProductName());
dto.put("shopVariantLabel", item.getShopVariantLabel());
dto.put("shopVariantColorName", item.getShopVariantColorName());
dto.put("shopVariantColorLabelIt", item.getShopProductVariant() != null ? item.getShopProductVariant().getColorLabelIt() : null);
dto.put("shopVariantColorLabelEn", item.getShopProductVariant() != null ? item.getShopProductVariant().getColorLabelEn() : null);
dto.put("shopVariantColorLabelDe", item.getShopProductVariant() != null ? item.getShopProductVariant().getColorLabelDe() : null);
dto.put("shopVariantColorLabelFr", item.getShopProductVariant() != null ? item.getShopProductVariant().getColorLabelFr() : null);
dto.put("shopVariantColorHex", item.getShopVariantColorHex());
dto.put("filamentColorLabelIt", item.getFilamentVariant() != null ? item.getFilamentVariant().getColorLabelIt() : null);
dto.put("filamentColorLabelEn", item.getFilamentVariant() != null ? item.getFilamentVariant().getColorLabelEn() : null);
dto.put("filamentColorLabelDe", item.getFilamentVariant() != null ? item.getFilamentVariant().getColorLabelDe() : null);
dto.put("filamentColorLabelFr", item.getFilamentVariant() != null ? item.getFilamentVariant().getColorLabelFr() : null);
dto.put("materialCode", item.getMaterialCode());
dto.put("quality", item.getQuality());
dto.put("nozzleDiameterMm", item.getNozzleDiameterMm());

View File

@@ -71,7 +71,7 @@ public class PublicShopCatalogService {
public List<ShopCategoryTreeDto> getCategories(String language) {
CategoryContext categoryContext = loadCategoryContext(language);
return buildCategoryTree(null, categoryContext);
return buildCategoryTree(null, categoryContext, language);
}
public ShopCategoryDetailDto getCategory(String slug, String language) {
@@ -83,7 +83,7 @@ public class PublicShopCatalogService {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Category not found");
}
return buildCategoryDetail(category, categoryContext);
return buildCategoryDetail(category, categoryContext, language);
}
public ShopProductCatalogResponseDto getProductCatalog(String categorySlug, Boolean featuredOnly, String language) {
@@ -114,7 +114,7 @@ public class PublicShopCatalogService {
.toList();
ShopCategoryDetailDto selectedCategoryDetail = selectedCategory != null
? buildCategoryDetail(selectedCategory, categoryContext)
? buildCategoryDetail(selectedCategory, categoryContext, language)
: null;
return new ShopProductCatalogResponseDto(
@@ -316,53 +316,63 @@ public class PublicShopCatalogService {
return total;
}
private List<ShopCategoryTreeDto> buildCategoryTree(UUID parentId, CategoryContext categoryContext) {
private List<ShopCategoryTreeDto> buildCategoryTree(UUID parentId,
CategoryContext categoryContext,
String language) {
return categoryContext.childrenByParentId().getOrDefault(parentId, List.of()).stream()
.map(category -> new ShopCategoryTreeDto(
category.getId(),
category.getParentCategory() != null ? category.getParentCategory().getId() : null,
category.getSlug(),
category.getName(),
category.getDescription(),
category.getSeoTitle(),
category.getSeoDescription(),
category.getNameForLanguage(language),
category.getDescriptionForLanguage(language),
category.getSeoTitleForLanguage(language),
category.getSeoDescriptionForLanguage(language),
category.getOgTitle(),
category.getOgDescription(),
category.getIndexable(),
category.getSortOrder(),
categoryContext.descendantProductCounts().getOrDefault(category.getId(), 0),
selectPrimaryMedia(categoryContext.categoryMediaBySlug().get(categoryMediaUsageKey(category))),
buildCategoryTree(category.getId(), categoryContext)
buildCategoryTree(category.getId(), categoryContext, language)
))
.toList();
}
private ShopCategoryDetailDto buildCategoryDetail(ShopCategory category, CategoryContext categoryContext) {
private ShopCategoryDetailDto buildCategoryDetail(ShopCategory category,
CategoryContext categoryContext,
String language) {
List<PublicMediaUsageDto> images = categoryContext.categoryMediaBySlug().getOrDefault(categoryMediaUsageKey(category), List.of());
String localizedSeoTitle = category.getSeoTitleForLanguage(language);
String localizedSeoDescription = category.getSeoDescriptionForLanguage(language);
return new ShopCategoryDetailDto(
category.getId(),
category.getSlug(),
category.getName(),
category.getDescription(),
category.getSeoTitle(),
category.getSeoDescription(),
category.getNameForLanguage(language),
category.getDescriptionForLanguage(language),
localizedSeoTitle,
localizedSeoDescription,
category.getOgTitle(),
category.getOgDescription(),
category.getIndexable(),
category.getSortOrder(),
categoryContext.descendantProductCounts().getOrDefault(category.getId(), 0),
buildCategoryBreadcrumbs(category),
buildCategoryBreadcrumbs(category, language),
selectPrimaryMedia(images),
images,
buildCategoryTree(category.getId(), categoryContext)
buildCategoryTree(category.getId(), categoryContext, language)
);
}
private List<ShopCategoryRefDto> buildCategoryBreadcrumbs(ShopCategory category) {
private List<ShopCategoryRefDto> buildCategoryBreadcrumbs(ShopCategory category, String language) {
List<ShopCategoryRefDto> breadcrumbs = new ArrayList<>();
ShopCategory current = category;
while (current != null) {
breadcrumbs.add(new ShopCategoryRefDto(current.getId(), current.getSlug(), current.getName()));
breadcrumbs.add(new ShopCategoryRefDto(
current.getId(),
current.getSlug(),
current.getNameForLanguage(language)
));
current = current.getParentCategory();
}
java.util.Collections.reverse(breadcrumbs);
@@ -399,11 +409,11 @@ public class PublicShopCatalogService {
new ShopCategoryRefDto(
entry.product().getCategory().getId(),
entry.product().getCategory().getSlug(),
entry.product().getCategory().getName()
entry.product().getCategory().getNameForLanguage(language)
),
resolvePriceFrom(entry.variants()),
resolvePriceTo(entry.variants()),
toVariantDto(entry.defaultVariant(), entry.defaultVariant(), variantColorHexByMaterialAndColor),
toVariantDto(entry.defaultVariant(), entry.defaultVariant(), variantColorHexByMaterialAndColor, language),
selectPrimaryMedia(images),
toProductModelDto(entry)
);
@@ -432,14 +442,14 @@ public class PublicShopCatalogService {
new ShopCategoryRefDto(
entry.product().getCategory().getId(),
entry.product().getCategory().getSlug(),
entry.product().getCategory().getName()
entry.product().getCategory().getNameForLanguage(language)
),
buildCategoryBreadcrumbs(entry.product().getCategory()),
buildCategoryBreadcrumbs(entry.product().getCategory(), language),
resolvePriceFrom(entry.variants()),
resolvePriceTo(entry.variants()),
toVariantDto(entry.defaultVariant(), entry.defaultVariant(), variantColorHexByMaterialAndColor),
toVariantDto(entry.defaultVariant(), entry.defaultVariant(), variantColorHexByMaterialAndColor, language),
entry.variants().stream()
.map(variant -> toVariantDto(variant, entry.defaultVariant(), variantColorHexByMaterialAndColor))
.map(variant -> toVariantDto(variant, entry.defaultVariant(), variantColorHexByMaterialAndColor, language))
.toList(),
selectPrimaryMedia(images),
images,
@@ -449,7 +459,8 @@ public class PublicShopCatalogService {
private ShopProductVariantOptionDto toVariantDto(ShopProductVariant variant,
ShopProductVariant defaultVariant,
Map<String, String> variantColorHexByMaterialAndColor) {
Map<String, String> variantColorHexByMaterialAndColor,
String language) {
if (variant == null) {
return null;
}
@@ -463,6 +474,7 @@ public class PublicShopCatalogService {
variant.getSku(),
variant.getVariantLabel(),
variant.getColorName(),
variant.getColorLabelForLanguage(language),
colorHex,
variant.getPriceChf(),
defaultVariant != null && Objects.equals(defaultVariant.getId(), variant.getId())

View File

@@ -0,0 +1,55 @@
package com.printcalculator.entity;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class ShopCategoryTest {
@Test
void localizedAccessorsShouldReturnLanguageSpecificValues() {
ShopCategory category = new ShopCategory();
category.setName("Desk accessories");
category.setNameIt("Accessori da scrivania");
category.setNameEn("Desk accessories");
category.setNameDe("Schreibtischzubehor");
category.setNameFr("Accessoires de bureau");
category.setDescription("Legacy description");
category.setDescriptionIt("Organizer e accessori stampati per la scrivania.");
category.setDescriptionEn("Printed desk organizers and accessories.");
category.setDescriptionDe("Gedruckte Organizer und Zubehor fur den Schreibtisch.");
category.setDescriptionFr("Accessoires et organiseurs imprimes pour le bureau.");
category.setSeoTitle("Legacy SEO title");
category.setSeoTitleIt("Accessori da scrivania stampati in 3D");
category.setSeoTitleEn("3D printed desk accessories");
category.setSeoTitleDe("3D-gedruckte Schreibtischaccessoires");
category.setSeoTitleFr("Accessoires de bureau imprimes en 3D");
category.setSeoDescription("Legacy SEO description");
category.setSeoDescriptionIt("Accessori da scrivania personalizzati e funzionali.");
category.setSeoDescriptionEn("Functional custom desk accessories.");
category.setSeoDescriptionDe("Funktionale personalisierte Schreibtischaccessoires.");
category.setSeoDescriptionFr("Accessoires de bureau fonctionnels et personnalises.");
assertEquals("Accessori da scrivania", category.getNameForLanguage("it"));
assertEquals("Desk accessories", category.getNameForLanguage("en"));
assertEquals("Schreibtischzubehor", category.getNameForLanguage("de"));
assertEquals("Accessoires de bureau", category.getNameForLanguage("fr"));
assertEquals("Gedruckte Organizer und Zubehor fur den Schreibtisch.", category.getDescriptionForLanguage("de"));
assertEquals("3D printed desk accessories", category.getSeoTitleForLanguage("en"));
assertEquals("Accessoires de bureau fonctionnels et personnalises.", category.getSeoDescriptionForLanguage("fr"));
}
@Test
void localizedAccessorsShouldFallbackToLegacyValues() {
ShopCategory category = new ShopCategory();
category.setName("Desk accessories");
category.setDescription("Printed desk organizers and accessories.");
category.setSeoTitle("3D printed desk accessories");
category.setSeoDescription("Functional custom desk accessories.");
assertEquals("Desk accessories", category.getNameForLanguage("it"));
assertEquals("Printed desk organizers and accessories.", category.getDescriptionForLanguage("de"));
assertEquals("3D printed desk accessories", category.getSeoTitleForLanguage("fr-CH"));
assertEquals("Functional custom desk accessories.", category.getSeoDescriptionForLanguage("en-US"));
}
}