feat(back-end) entities for shop

This commit is contained in:
2026-03-09 19:50:00 +01:00
parent 2f34c52b6f
commit c953232f3c
23 changed files with 1611 additions and 0 deletions

View File

@@ -62,6 +62,7 @@ public class QuoteSessionController {
public ResponseEntity<QuoteSession> createSession() { public ResponseEntity<QuoteSession> createSession() {
QuoteSession session = new QuoteSession(); QuoteSession session = new QuoteSession();
session.setStatus("ACTIVE"); session.setStatus("ACTIVE");
session.setSessionType("PRINT_QUOTE");
session.setPricingVersion("v1"); session.setPricingVersion("v1");
session.setMaterialCode("PLA"); session.setMaterialCode("PLA");
session.setSupportsEnabled(false); session.setSupportsEnabled(false);

View File

@@ -7,6 +7,7 @@ import java.util.UUID;
public class AdminQuoteSessionDto { public class AdminQuoteSessionDto {
private UUID id; private UUID id;
private String status; private String status;
private String sessionType;
private String materialCode; private String materialCode;
private OffsetDateTime createdAt; private OffsetDateTime createdAt;
private OffsetDateTime expiresAt; private OffsetDateTime expiresAt;
@@ -32,6 +33,14 @@ public class AdminQuoteSessionDto {
this.status = status; this.status = status;
} }
public String getSessionType() {
return sessionType;
}
public void setSessionType(String sessionType) {
this.sessionType = sessionType;
}
public String getMaterialCode() { public String getMaterialCode() {
return materialCode; return materialCode;
} }

View File

@@ -8,6 +8,7 @@ import java.util.UUID;
public class OrderDto { public class OrderDto {
private UUID id; private UUID id;
private String orderNumber; private String orderNumber;
private String sourceType;
private String status; private String status;
private String paymentStatus; private String paymentStatus;
private String paymentMethod; private String paymentMethod;
@@ -45,6 +46,9 @@ public class OrderDto {
public String getOrderNumber() { return orderNumber; } public String getOrderNumber() { return orderNumber; }
public void setOrderNumber(String orderNumber) { this.orderNumber = orderNumber; } public void setOrderNumber(String orderNumber) { this.orderNumber = orderNumber; }
public String getSourceType() { return sourceType; }
public void setSourceType(String sourceType) { this.sourceType = sourceType; }
public String getStatus() { return status; } public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; } public void setStatus(String status) { this.status = status; }

View File

@@ -5,10 +5,19 @@ import java.util.UUID;
public class OrderItemDto { public class OrderItemDto {
private UUID id; private UUID id;
private String itemType;
private String originalFilename; private String originalFilename;
private String displayName;
private String materialCode; private String materialCode;
private String colorCode; private String colorCode;
private Long filamentVariantId; private Long filamentVariantId;
private UUID shopProductId;
private UUID shopProductVariantId;
private String shopProductSlug;
private String shopProductName;
private String shopVariantLabel;
private String shopVariantColorName;
private String shopVariantColorHex;
private String filamentVariantDisplayName; private String filamentVariantDisplayName;
private String filamentColorName; private String filamentColorName;
private String filamentColorHex; private String filamentColorHex;
@@ -28,9 +37,15 @@ public class OrderItemDto {
public UUID getId() { return id; } public UUID getId() { return id; }
public void setId(UUID id) { this.id = id; } public void setId(UUID id) { this.id = id; }
public String getItemType() { return itemType; }
public void setItemType(String itemType) { this.itemType = itemType; }
public String getOriginalFilename() { return originalFilename; } public String getOriginalFilename() { return originalFilename; }
public void setOriginalFilename(String originalFilename) { this.originalFilename = originalFilename; } public void setOriginalFilename(String originalFilename) { this.originalFilename = originalFilename; }
public String getDisplayName() { return displayName; }
public void setDisplayName(String displayName) { this.displayName = displayName; }
public String getMaterialCode() { return materialCode; } public String getMaterialCode() { return materialCode; }
public void setMaterialCode(String materialCode) { this.materialCode = materialCode; } public void setMaterialCode(String materialCode) { this.materialCode = materialCode; }
@@ -40,6 +55,27 @@ public class OrderItemDto {
public Long getFilamentVariantId() { return filamentVariantId; } public Long getFilamentVariantId() { return filamentVariantId; }
public void setFilamentVariantId(Long filamentVariantId) { this.filamentVariantId = filamentVariantId; } public void setFilamentVariantId(Long filamentVariantId) { this.filamentVariantId = filamentVariantId; }
public UUID getShopProductId() { return shopProductId; }
public void setShopProductId(UUID shopProductId) { this.shopProductId = shopProductId; }
public UUID getShopProductVariantId() { return shopProductVariantId; }
public void setShopProductVariantId(UUID shopProductVariantId) { this.shopProductVariantId = shopProductVariantId; }
public String getShopProductSlug() { return shopProductSlug; }
public void setShopProductSlug(String shopProductSlug) { this.shopProductSlug = shopProductSlug; }
public String getShopProductName() { return shopProductName; }
public void setShopProductName(String shopProductName) { this.shopProductName = shopProductName; }
public String getShopVariantLabel() { return shopVariantLabel; }
public void setShopVariantLabel(String shopVariantLabel) { this.shopVariantLabel = shopVariantLabel; }
public String getShopVariantColorName() { return shopVariantColorName; }
public void setShopVariantColorName(String shopVariantColorName) { this.shopVariantColorName = shopVariantColorName; }
public String getShopVariantColorHex() { return shopVariantColorHex; }
public void setShopVariantColorHex(String shopVariantColorHex) { this.shopVariantColorHex = shopVariantColorHex; }
public String getFilamentVariantDisplayName() { return filamentVariantDisplayName; } public String getFilamentVariantDisplayName() { return filamentVariantDisplayName; }
public void setFilamentVariantDisplayName(String filamentVariantDisplayName) { this.filamentVariantDisplayName = filamentVariantDisplayName; } public void setFilamentVariantDisplayName(String filamentVariantDisplayName) { this.filamentVariantDisplayName = filamentVariantDisplayName; }

View File

@@ -20,6 +20,10 @@ public class Order {
@JoinColumn(name = "source_quote_session_id") @JoinColumn(name = "source_quote_session_id")
private QuoteSession sourceQuoteSession; private QuoteSession sourceQuoteSession;
@ColumnDefault("'CALCULATOR'")
@Column(name = "source_type", nullable = false, length = Integer.MAX_VALUE)
private String sourceType;
@Column(name = "status", nullable = false, length = Integer.MAX_VALUE) @Column(name = "status", nullable = false, length = Integer.MAX_VALUE)
private String status; private String status;
@@ -151,6 +155,34 @@ public class Order {
@Column(name = "paid_at") @Column(name = "paid_at")
private OffsetDateTime paidAt; private OffsetDateTime paidAt;
@PrePersist
private void onCreate() {
OffsetDateTime now = OffsetDateTime.now();
if (createdAt == null) {
createdAt = now;
}
if (updatedAt == null) {
updatedAt = now;
}
if (shippingSameAsBilling == null) {
shippingSameAsBilling = true;
}
if (sourceType == null || sourceType.isBlank()) {
sourceType = "CALCULATOR";
}
}
@PreUpdate
private void onUpdate() {
updatedAt = OffsetDateTime.now();
if (shippingSameAsBilling == null) {
shippingSameAsBilling = true;
}
if (sourceType == null || sourceType.isBlank()) {
sourceType = "CALCULATOR";
}
}
public UUID getId() { public UUID getId() {
return id; return id;
} }
@@ -177,6 +209,14 @@ public class Order {
this.sourceQuoteSession = sourceQuoteSession; this.sourceQuoteSession = sourceQuoteSession;
} }
public String getSourceType() {
return sourceType;
}
public void setSourceType(String sourceType) {
this.sourceType = sourceType;
}
public String getStatus() { public String getStatus() {
return status; return status;
} }

View File

@@ -23,9 +23,16 @@ public class OrderItem {
@JoinColumn(name = "order_id", nullable = false) @JoinColumn(name = "order_id", nullable = false)
private Order order; private Order order;
@ColumnDefault("'PRINT_FILE'")
@Column(name = "item_type", nullable = false, length = Integer.MAX_VALUE)
private String itemType;
@Column(name = "original_filename", nullable = false, length = Integer.MAX_VALUE) @Column(name = "original_filename", nullable = false, length = Integer.MAX_VALUE)
private String originalFilename; private String originalFilename;
@Column(name = "display_name", length = Integer.MAX_VALUE)
private String displayName;
@Column(name = "stored_relative_path", nullable = false, length = Integer.MAX_VALUE) @Column(name = "stored_relative_path", nullable = false, length = Integer.MAX_VALUE)
private String storedRelativePath; private String storedRelativePath;
@@ -66,6 +73,29 @@ public class OrderItem {
@JoinColumn(name = "filament_variant_id") @JoinColumn(name = "filament_variant_id")
private FilamentVariant filamentVariant; private FilamentVariant filamentVariant;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "shop_product_id")
private ShopProduct shopProduct;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "shop_product_variant_id")
private ShopProductVariant shopProductVariant;
@Column(name = "shop_product_slug", length = Integer.MAX_VALUE)
private String shopProductSlug;
@Column(name = "shop_product_name", length = Integer.MAX_VALUE)
private String shopProductName;
@Column(name = "shop_variant_label", length = Integer.MAX_VALUE)
private String shopVariantLabel;
@Column(name = "shop_variant_color_name", length = Integer.MAX_VALUE)
private String shopVariantColorName;
@Column(name = "shop_variant_color_hex", length = Integer.MAX_VALUE)
private String shopVariantColorHex;
@Column(name = "color_code", length = Integer.MAX_VALUE) @Column(name = "color_code", length = Integer.MAX_VALUE)
private String colorCode; private String colorCode;
@@ -106,6 +136,14 @@ public class OrderItem {
if (quantity == null) { if (quantity == null) {
quantity = 1; quantity = 1;
} }
if (itemType == null || itemType.isBlank()) {
itemType = "PRINT_FILE";
}
if ((displayName == null || displayName.isBlank()) && originalFilename != null && !originalFilename.isBlank()) {
displayName = originalFilename;
} else if ((displayName == null || displayName.isBlank()) && shopProductName != null && !shopProductName.isBlank()) {
displayName = shopProductName;
}
} }
public UUID getId() { public UUID getId() {
@@ -124,6 +162,14 @@ public class OrderItem {
this.order = order; this.order = order;
} }
public String getItemType() {
return itemType;
}
public void setItemType(String itemType) {
this.itemType = itemType;
}
public String getOriginalFilename() { public String getOriginalFilename() {
return originalFilename; return originalFilename;
} }
@@ -132,6 +178,14 @@ public class OrderItem {
this.originalFilename = originalFilename; this.originalFilename = originalFilename;
} }
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getStoredRelativePath() { public String getStoredRelativePath() {
return storedRelativePath; return storedRelativePath;
} }
@@ -236,6 +290,62 @@ public class OrderItem {
this.filamentVariant = filamentVariant; this.filamentVariant = filamentVariant;
} }
public ShopProduct getShopProduct() {
return shopProduct;
}
public void setShopProduct(ShopProduct shopProduct) {
this.shopProduct = shopProduct;
}
public ShopProductVariant getShopProductVariant() {
return shopProductVariant;
}
public void setShopProductVariant(ShopProductVariant shopProductVariant) {
this.shopProductVariant = shopProductVariant;
}
public String getShopProductSlug() {
return shopProductSlug;
}
public void setShopProductSlug(String shopProductSlug) {
this.shopProductSlug = shopProductSlug;
}
public String getShopProductName() {
return shopProductName;
}
public void setShopProductName(String shopProductName) {
this.shopProductName = shopProductName;
}
public String getShopVariantLabel() {
return shopVariantLabel;
}
public void setShopVariantLabel(String shopVariantLabel) {
this.shopVariantLabel = shopVariantLabel;
}
public String getShopVariantColorName() {
return shopVariantColorName;
}
public void setShopVariantColorName(String shopVariantColorName) {
this.shopVariantColorName = shopVariantColorName;
}
public String getShopVariantColorHex() {
return shopVariantColorHex;
}
public void setShopVariantColorHex(String shopVariantColorHex) {
this.shopVariantColorHex = shopVariantColorHex;
}
public String getColorCode() { public String getColorCode() {
return colorCode; return colorCode;
} }

View File

@@ -30,9 +30,16 @@ public class QuoteLineItem {
@Column(name = "status", nullable = false, length = Integer.MAX_VALUE) @Column(name = "status", nullable = false, length = Integer.MAX_VALUE)
private String status; private String status;
@ColumnDefault("'PRINT_FILE'")
@Column(name = "line_item_type", nullable = false, length = Integer.MAX_VALUE)
private String lineItemType;
@Column(name = "original_filename", nullable = false, length = Integer.MAX_VALUE) @Column(name = "original_filename", nullable = false, length = Integer.MAX_VALUE)
private String originalFilename; private String originalFilename;
@Column(name = "display_name", length = Integer.MAX_VALUE)
private String displayName;
@ColumnDefault("1") @ColumnDefault("1")
@Column(name = "quantity", nullable = false) @Column(name = "quantity", nullable = false)
private Integer quantity; private Integer quantity;
@@ -45,6 +52,31 @@ public class QuoteLineItem {
@com.fasterxml.jackson.annotation.JsonIgnore @com.fasterxml.jackson.annotation.JsonIgnore
private FilamentVariant filamentVariant; private FilamentVariant filamentVariant;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "shop_product_id")
@com.fasterxml.jackson.annotation.JsonIgnore
private ShopProduct shopProduct;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "shop_product_variant_id")
@com.fasterxml.jackson.annotation.JsonIgnore
private ShopProductVariant shopProductVariant;
@Column(name = "shop_product_slug", length = Integer.MAX_VALUE)
private String shopProductSlug;
@Column(name = "shop_product_name", length = Integer.MAX_VALUE)
private String shopProductName;
@Column(name = "shop_variant_label", length = Integer.MAX_VALUE)
private String shopVariantLabel;
@Column(name = "shop_variant_color_name", length = Integer.MAX_VALUE)
private String shopVariantColorName;
@Column(name = "shop_variant_color_hex", length = Integer.MAX_VALUE)
private String shopVariantColorHex;
@Column(name = "material_code", length = Integer.MAX_VALUE) @Column(name = "material_code", length = Integer.MAX_VALUE)
private String materialCode; private String materialCode;
@@ -102,6 +134,41 @@ public class QuoteLineItem {
@Column(name = "updated_at", nullable = false) @Column(name = "updated_at", nullable = false)
private OffsetDateTime updatedAt; private OffsetDateTime updatedAt;
@PrePersist
private void onCreate() {
OffsetDateTime now = OffsetDateTime.now();
if (createdAt == null) {
createdAt = now;
}
if (updatedAt == null) {
updatedAt = now;
}
if (quantity == null) {
quantity = 1;
}
if (lineItemType == null || lineItemType.isBlank()) {
lineItemType = "PRINT_FILE";
}
if ((displayName == null || displayName.isBlank()) && originalFilename != null && !originalFilename.isBlank()) {
displayName = originalFilename;
} else if ((displayName == null || displayName.isBlank()) && shopProductName != null && !shopProductName.isBlank()) {
displayName = shopProductName;
}
}
@PreUpdate
private void onUpdate() {
updatedAt = OffsetDateTime.now();
if (lineItemType == null || lineItemType.isBlank()) {
lineItemType = "PRINT_FILE";
}
if ((displayName == null || displayName.isBlank()) && originalFilename != null && !originalFilename.isBlank()) {
displayName = originalFilename;
} else if ((displayName == null || displayName.isBlank()) && shopProductName != null && !shopProductName.isBlank()) {
displayName = shopProductName;
}
}
public UUID getId() { public UUID getId() {
return id; return id;
} }
@@ -126,6 +193,14 @@ public class QuoteLineItem {
this.status = status; this.status = status;
} }
public String getLineItemType() {
return lineItemType;
}
public void setLineItemType(String lineItemType) {
this.lineItemType = lineItemType;
}
public String getOriginalFilename() { public String getOriginalFilename() {
return originalFilename; return originalFilename;
} }
@@ -134,6 +209,14 @@ public class QuoteLineItem {
this.originalFilename = originalFilename; this.originalFilename = originalFilename;
} }
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public Integer getQuantity() { public Integer getQuantity() {
return quantity; return quantity;
} }
@@ -158,6 +241,62 @@ public class QuoteLineItem {
this.filamentVariant = filamentVariant; this.filamentVariant = filamentVariant;
} }
public ShopProduct getShopProduct() {
return shopProduct;
}
public void setShopProduct(ShopProduct shopProduct) {
this.shopProduct = shopProduct;
}
public ShopProductVariant getShopProductVariant() {
return shopProductVariant;
}
public void setShopProductVariant(ShopProductVariant shopProductVariant) {
this.shopProductVariant = shopProductVariant;
}
public String getShopProductSlug() {
return shopProductSlug;
}
public void setShopProductSlug(String shopProductSlug) {
this.shopProductSlug = shopProductSlug;
}
public String getShopProductName() {
return shopProductName;
}
public void setShopProductName(String shopProductName) {
this.shopProductName = shopProductName;
}
public String getShopVariantLabel() {
return shopVariantLabel;
}
public void setShopVariantLabel(String shopVariantLabel) {
this.shopVariantLabel = shopVariantLabel;
}
public String getShopVariantColorName() {
return shopVariantColorName;
}
public void setShopVariantColorName(String shopVariantColorName) {
this.shopVariantColorName = shopVariantColorName;
}
public String getShopVariantColorHex() {
return shopVariantColorHex;
}
public void setShopVariantColorHex(String shopVariantColorHex) {
this.shopVariantColorHex = shopVariantColorHex;
}
public String getMaterialCode() { public String getMaterialCode() {
return materialCode; return materialCode;
} }

View File

@@ -22,6 +22,10 @@ public class QuoteSession {
@Column(name = "status", nullable = false, length = Integer.MAX_VALUE) @Column(name = "status", nullable = false, length = Integer.MAX_VALUE)
private String status; private String status;
@ColumnDefault("'PRINT_QUOTE'")
@Column(name = "session_type", nullable = false, length = Integer.MAX_VALUE)
private String sessionType;
@Column(name = "pricing_version", nullable = false, length = Integer.MAX_VALUE) @Column(name = "pricing_version", nullable = false, length = Integer.MAX_VALUE)
private String pricingVersion; private String pricingVersion;
@@ -70,6 +74,19 @@ public class QuoteSession {
@Column(name = "cad_hourly_rate_chf", precision = 10, scale = 2) @Column(name = "cad_hourly_rate_chf", precision = 10, scale = 2)
private BigDecimal cadHourlyRateChf; private BigDecimal cadHourlyRateChf;
@PrePersist
private void onCreate() {
if (sessionType == null || sessionType.isBlank()) {
sessionType = "PRINT_QUOTE";
}
if (supportsEnabled == null) {
supportsEnabled = false;
}
if (createdAt == null) {
createdAt = OffsetDateTime.now();
}
}
public UUID getId() { public UUID getId() {
return id; return id;
} }
@@ -86,6 +103,14 @@ public class QuoteSession {
this.status = status; this.status = status;
} }
public String getSessionType() {
return sessionType;
}
public void setSessionType(String sessionType) {
this.sessionType = sessionType;
}
public String getPricingVersion() { public String getPricingVersion() {
return pricingVersion; return pricingVersion;
} }

View File

@@ -0,0 +1,221 @@
package com.printcalculator.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate;
import jakarta.persistence.Table;
import org.hibernate.annotations.ColumnDefault;
import java.time.OffsetDateTime;
import java.util.UUID;
@Entity
@Table(name = "shop_category", indexes = {
@Index(name = "ix_shop_category_parent_sort", columnList = "parent_category_id, sort_order"),
@Index(name = "ix_shop_category_active_sort", columnList = "is_active, sort_order")
})
public class ShopCategory {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "shop_category_id", nullable = false)
private UUID id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_category_id")
private ShopCategory parentCategory;
@Column(name = "slug", nullable = false, unique = true, length = Integer.MAX_VALUE)
private String slug;
@Column(name = "name", nullable = false, length = Integer.MAX_VALUE)
private String name;
@Column(name = "description", length = Integer.MAX_VALUE)
private String description;
@Column(name = "seo_title", length = Integer.MAX_VALUE)
private String seoTitle;
@Column(name = "seo_description", length = Integer.MAX_VALUE)
private String seoDescription;
@Column(name = "og_title", length = Integer.MAX_VALUE)
private String ogTitle;
@Column(name = "og_description", length = Integer.MAX_VALUE)
private String ogDescription;
@ColumnDefault("true")
@Column(name = "indexable", nullable = false)
private Boolean indexable;
@ColumnDefault("true")
@Column(name = "is_active", nullable = false)
private Boolean isActive;
@ColumnDefault("0")
@Column(name = "sort_order", nullable = false)
private Integer sortOrder;
@ColumnDefault("now()")
@Column(name = "created_at", nullable = false)
private OffsetDateTime createdAt;
@ColumnDefault("now()")
@Column(name = "updated_at", nullable = false)
private OffsetDateTime updatedAt;
@PrePersist
private void onCreate() {
OffsetDateTime now = OffsetDateTime.now();
if (createdAt == null) {
createdAt = now;
}
if (updatedAt == null) {
updatedAt = now;
}
if (indexable == null) {
indexable = true;
}
if (isActive == null) {
isActive = true;
}
if (sortOrder == null) {
sortOrder = 0;
}
}
@PreUpdate
private void onUpdate() {
updatedAt = OffsetDateTime.now();
if (indexable == null) {
indexable = true;
}
if (isActive == null) {
isActive = true;
}
if (sortOrder == null) {
sortOrder = 0;
}
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public ShopCategory getParentCategory() {
return parentCategory;
}
public void setParentCategory(ShopCategory parentCategory) {
this.parentCategory = parentCategory;
}
public String getSlug() {
return slug;
}
public void setSlug(String slug) {
this.slug = slug;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getSeoTitle() {
return seoTitle;
}
public void setSeoTitle(String seoTitle) {
this.seoTitle = seoTitle;
}
public String getSeoDescription() {
return seoDescription;
}
public void setSeoDescription(String seoDescription) {
this.seoDescription = seoDescription;
}
public String getOgTitle() {
return ogTitle;
}
public void setOgTitle(String ogTitle) {
this.ogTitle = ogTitle;
}
public String getOgDescription() {
return ogDescription;
}
public void setOgDescription(String ogDescription) {
this.ogDescription = ogDescription;
}
public Boolean getIndexable() {
return indexable;
}
public void setIndexable(Boolean indexable) {
this.indexable = indexable;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean active) {
isActive = active;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
public OffsetDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(OffsetDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -0,0 +1,250 @@
package com.printcalculator.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate;
import jakarta.persistence.Table;
import org.hibernate.annotations.ColumnDefault;
import java.time.OffsetDateTime;
import java.util.UUID;
@Entity
@Table(name = "shop_product", indexes = {
@Index(name = "ix_shop_product_category_active_sort", columnList = "shop_category_id, is_active, sort_order"),
@Index(name = "ix_shop_product_featured_sort", columnList = "is_featured, is_active, sort_order")
})
public class ShopProduct {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "shop_product_id", nullable = false)
private UUID id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "shop_category_id", nullable = false)
private ShopCategory category;
@Column(name = "slug", nullable = false, unique = true, length = Integer.MAX_VALUE)
private String slug;
@Column(name = "name", nullable = false, length = Integer.MAX_VALUE)
private String name;
@Column(name = "excerpt", length = Integer.MAX_VALUE)
private String excerpt;
@Column(name = "description", length = Integer.MAX_VALUE)
private String description;
@Column(name = "seo_title", length = Integer.MAX_VALUE)
private String seoTitle;
@Column(name = "seo_description", length = Integer.MAX_VALUE)
private String seoDescription;
@Column(name = "og_title", length = Integer.MAX_VALUE)
private String ogTitle;
@Column(name = "og_description", length = Integer.MAX_VALUE)
private String ogDescription;
@ColumnDefault("true")
@Column(name = "indexable", nullable = false)
private Boolean indexable;
@ColumnDefault("false")
@Column(name = "is_featured", nullable = false)
private Boolean isFeatured;
@ColumnDefault("true")
@Column(name = "is_active", nullable = false)
private Boolean isActive;
@ColumnDefault("0")
@Column(name = "sort_order", nullable = false)
private Integer sortOrder;
@ColumnDefault("now()")
@Column(name = "created_at", nullable = false)
private OffsetDateTime createdAt;
@ColumnDefault("now()")
@Column(name = "updated_at", nullable = false)
private OffsetDateTime updatedAt;
@PrePersist
private void onCreate() {
OffsetDateTime now = OffsetDateTime.now();
if (createdAt == null) {
createdAt = now;
}
if (updatedAt == null) {
updatedAt = now;
}
if (indexable == null) {
indexable = true;
}
if (isFeatured == null) {
isFeatured = false;
}
if (isActive == null) {
isActive = true;
}
if (sortOrder == null) {
sortOrder = 0;
}
}
@PreUpdate
private void onUpdate() {
updatedAt = OffsetDateTime.now();
if (indexable == null) {
indexable = true;
}
if (isFeatured == null) {
isFeatured = false;
}
if (isActive == null) {
isActive = true;
}
if (sortOrder == null) {
sortOrder = 0;
}
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public ShopCategory getCategory() {
return category;
}
public void setCategory(ShopCategory category) {
this.category = category;
}
public String getSlug() {
return slug;
}
public void setSlug(String slug) {
this.slug = slug;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getExcerpt() {
return excerpt;
}
public void setExcerpt(String excerpt) {
this.excerpt = excerpt;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getSeoTitle() {
return seoTitle;
}
public void setSeoTitle(String seoTitle) {
this.seoTitle = seoTitle;
}
public String getSeoDescription() {
return seoDescription;
}
public void setSeoDescription(String seoDescription) {
this.seoDescription = seoDescription;
}
public String getOgTitle() {
return ogTitle;
}
public void setOgTitle(String ogTitle) {
this.ogTitle = ogTitle;
}
public String getOgDescription() {
return ogDescription;
}
public void setOgDescription(String ogDescription) {
this.ogDescription = ogDescription;
}
public Boolean getIndexable() {
return indexable;
}
public void setIndexable(Boolean indexable) {
this.indexable = indexable;
}
public Boolean getIsFeatured() {
return isFeatured;
}
public void setIsFeatured(Boolean featured) {
isFeatured = featured;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean active) {
isActive = active;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
public OffsetDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(OffsetDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -0,0 +1,189 @@
package com.printcalculator.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate;
import jakarta.persistence.Table;
import org.hibernate.annotations.ColumnDefault;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
import java.util.UUID;
@Entity
@Table(name = "shop_product_model_asset", indexes = {
@Index(name = "ix_shop_product_model_asset_product", columnList = "shop_product_id")
})
public class ShopProductModelAsset {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "shop_product_model_asset_id", nullable = false)
private UUID id;
@OneToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "shop_product_id", nullable = false, unique = true)
private ShopProduct product;
@Column(name = "original_filename", nullable = false, length = Integer.MAX_VALUE)
private String originalFilename;
@Column(name = "stored_relative_path", nullable = false, length = Integer.MAX_VALUE)
private String storedRelativePath;
@Column(name = "stored_filename", nullable = false, length = Integer.MAX_VALUE)
private String storedFilename;
@Column(name = "file_size_bytes")
private Long fileSizeBytes;
@Column(name = "mime_type", length = Integer.MAX_VALUE)
private String mimeType;
@Column(name = "sha256_hex", length = Integer.MAX_VALUE)
private String sha256Hex;
@Column(name = "bounding_box_x_mm", precision = 10, scale = 3)
private BigDecimal boundingBoxXMm;
@Column(name = "bounding_box_y_mm", precision = 10, scale = 3)
private BigDecimal boundingBoxYMm;
@Column(name = "bounding_box_z_mm", precision = 10, scale = 3)
private BigDecimal boundingBoxZMm;
@ColumnDefault("now()")
@Column(name = "created_at", nullable = false)
private OffsetDateTime createdAt;
@ColumnDefault("now()")
@Column(name = "updated_at", nullable = false)
private OffsetDateTime updatedAt;
@PrePersist
private void onCreate() {
OffsetDateTime now = OffsetDateTime.now();
if (createdAt == null) {
createdAt = now;
}
if (updatedAt == null) {
updatedAt = now;
}
}
@PreUpdate
private void onUpdate() {
updatedAt = OffsetDateTime.now();
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public ShopProduct getProduct() {
return product;
}
public void setProduct(ShopProduct product) {
this.product = product;
}
public String getOriginalFilename() {
return originalFilename;
}
public void setOriginalFilename(String originalFilename) {
this.originalFilename = originalFilename;
}
public String getStoredRelativePath() {
return storedRelativePath;
}
public void setStoredRelativePath(String storedRelativePath) {
this.storedRelativePath = storedRelativePath;
}
public String getStoredFilename() {
return storedFilename;
}
public void setStoredFilename(String storedFilename) {
this.storedFilename = storedFilename;
}
public Long getFileSizeBytes() {
return fileSizeBytes;
}
public void setFileSizeBytes(Long fileSizeBytes) {
this.fileSizeBytes = fileSizeBytes;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
public String getSha256Hex() {
return sha256Hex;
}
public void setSha256Hex(String sha256Hex) {
this.sha256Hex = sha256Hex;
}
public BigDecimal getBoundingBoxXMm() {
return boundingBoxXMm;
}
public void setBoundingBoxXMm(BigDecimal boundingBoxXMm) {
this.boundingBoxXMm = boundingBoxXMm;
}
public BigDecimal getBoundingBoxYMm() {
return boundingBoxYMm;
}
public void setBoundingBoxYMm(BigDecimal boundingBoxYMm) {
this.boundingBoxYMm = boundingBoxYMm;
}
public BigDecimal getBoundingBoxZMm() {
return boundingBoxZMm;
}
public void setBoundingBoxZMm(BigDecimal boundingBoxZMm) {
this.boundingBoxZMm = boundingBoxZMm;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
public OffsetDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(OffsetDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -0,0 +1,218 @@
package com.printcalculator.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate;
import jakarta.persistence.Table;
import org.hibernate.annotations.ColumnDefault;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
import java.util.UUID;
@Entity
@Table(name = "shop_product_variant", indexes = {
@Index(name = "ix_shop_product_variant_product_active_sort", columnList = "shop_product_id, is_active, sort_order"),
@Index(name = "ix_shop_product_variant_sku", columnList = "sku")
})
public class ShopProductVariant {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "shop_product_variant_id", nullable = false)
private UUID id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "shop_product_id", nullable = false)
private ShopProduct product;
@Column(name = "sku", unique = true, length = Integer.MAX_VALUE)
private String sku;
@Column(name = "variant_label", nullable = false, length = Integer.MAX_VALUE)
private String variantLabel;
@Column(name = "color_name", nullable = false, length = Integer.MAX_VALUE)
private String colorName;
@Column(name = "color_hex", length = Integer.MAX_VALUE)
private String colorHex;
@Column(name = "internal_material_code", nullable = false, length = Integer.MAX_VALUE)
private String internalMaterialCode;
@ColumnDefault("0.00")
@Column(name = "price_chf", nullable = false, precision = 12, scale = 2)
private BigDecimal priceChf;
@ColumnDefault("false")
@Column(name = "is_default", nullable = false)
private Boolean isDefault;
@ColumnDefault("true")
@Column(name = "is_active", nullable = false)
private Boolean isActive;
@ColumnDefault("0")
@Column(name = "sort_order", nullable = false)
private Integer sortOrder;
@ColumnDefault("now()")
@Column(name = "created_at", nullable = false)
private OffsetDateTime createdAt;
@ColumnDefault("now()")
@Column(name = "updated_at", nullable = false)
private OffsetDateTime updatedAt;
@PrePersist
private void onCreate() {
OffsetDateTime now = OffsetDateTime.now();
if (createdAt == null) {
createdAt = now;
}
if (updatedAt == null) {
updatedAt = now;
}
if (priceChf == null) {
priceChf = BigDecimal.ZERO;
}
if (isDefault == null) {
isDefault = false;
}
if (isActive == null) {
isActive = true;
}
if (sortOrder == null) {
sortOrder = 0;
}
}
@PreUpdate
private void onUpdate() {
updatedAt = OffsetDateTime.now();
if (priceChf == null) {
priceChf = BigDecimal.ZERO;
}
if (isDefault == null) {
isDefault = false;
}
if (isActive == null) {
isActive = true;
}
if (sortOrder == null) {
sortOrder = 0;
}
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public ShopProduct getProduct() {
return product;
}
public void setProduct(ShopProduct product) {
this.product = product;
}
public String getSku() {
return sku;
}
public void setSku(String sku) {
this.sku = sku;
}
public String getVariantLabel() {
return variantLabel;
}
public void setVariantLabel(String variantLabel) {
this.variantLabel = variantLabel;
}
public String getColorName() {
return colorName;
}
public void setColorName(String colorName) {
this.colorName = colorName;
}
public String getColorHex() {
return colorHex;
}
public void setColorHex(String colorHex) {
this.colorHex = colorHex;
}
public String getInternalMaterialCode() {
return internalMaterialCode;
}
public void setInternalMaterialCode(String internalMaterialCode) {
this.internalMaterialCode = internalMaterialCode;
}
public BigDecimal getPriceChf() {
return priceChf;
}
public void setPriceChf(BigDecimal priceChf) {
this.priceChf = priceChf;
}
public Boolean getIsDefault() {
return isDefault;
}
public void setIsDefault(Boolean aDefault) {
isDefault = aDefault;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean active) {
isActive = active;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
public OffsetDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(OffsetDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -0,0 +1,16 @@
package com.printcalculator.repository;
import com.printcalculator.entity.ShopCategory;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface ShopCategoryRepository extends JpaRepository<ShopCategory, UUID> {
Optional<ShopCategory> findBySlug(String slug);
boolean existsBySlugIgnoreCase(String slug);
List<ShopCategory> findAllByOrderBySortOrderAscNameAsc();
}

View File

@@ -0,0 +1,11 @@
package com.printcalculator.repository;
import com.printcalculator.entity.ShopProductModelAsset;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
import java.util.UUID;
public interface ShopProductModelAssetRepository extends JpaRepository<ShopProductModelAsset, UUID> {
Optional<ShopProductModelAsset> findByProduct_Id(UUID productId);
}

View File

@@ -0,0 +1,18 @@
package com.printcalculator.repository;
import com.printcalculator.entity.ShopProduct;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface ShopProductRepository extends JpaRepository<ShopProduct, UUID> {
Optional<ShopProduct> findBySlug(String slug);
boolean existsBySlugIgnoreCase(String slug);
List<ShopProduct> findAllByOrderBySortOrderAscNameAsc();
List<ShopProduct> findByCategory_IdOrderBySortOrderAscNameAsc(UUID categoryId);
}

View File

@@ -0,0 +1,16 @@
package com.printcalculator.repository;
import com.printcalculator.entity.ShopProductVariant;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface ShopProductVariantRepository extends JpaRepository<ShopProductVariant, UUID> {
List<ShopProductVariant> findByProduct_IdOrderBySortOrderAscColorNameAsc(UUID productId);
Optional<ShopProductVariant> findFirstByProduct_IdAndIsDefaultTrue(UUID productId);
boolean existsBySkuIgnoreCase(String sku);
}

View File

@@ -104,6 +104,7 @@ public class OrderService {
Order order = new Order(); Order order = new Order();
order.setSourceQuoteSession(session); order.setSourceQuoteSession(session);
order.setSourceType(resolveOrderSourceType(session));
order.setCustomer(customer); order.setCustomer(customer);
order.setCustomerEmail(request.getCustomer().getEmail()); order.setCustomerEmail(request.getCustomer().getEmail());
order.setCustomerPhone(request.getCustomer().getPhone()); order.setCustomerPhone(request.getCustomer().getPhone());
@@ -172,11 +173,24 @@ public class OrderService {
for (QuoteLineItem qItem : quoteItems) { for (QuoteLineItem qItem : quoteItems) {
OrderItem oItem = new OrderItem(); OrderItem oItem = new OrderItem();
oItem.setOrder(order); oItem.setOrder(order);
oItem.setItemType(qItem.getLineItemType() != null ? qItem.getLineItemType() : "PRINT_FILE");
oItem.setOriginalFilename(qItem.getOriginalFilename()); oItem.setOriginalFilename(qItem.getOriginalFilename());
oItem.setDisplayName(
qItem.getDisplayName() != null && !qItem.getDisplayName().isBlank()
? qItem.getDisplayName()
: qItem.getOriginalFilename()
);
int quantity = qItem.getQuantity() != null && qItem.getQuantity() > 0 ? qItem.getQuantity() : 1; int quantity = qItem.getQuantity() != null && qItem.getQuantity() > 0 ? qItem.getQuantity() : 1;
oItem.setQuantity(quantity); oItem.setQuantity(quantity);
oItem.setColorCode(qItem.getColorCode()); oItem.setColorCode(qItem.getColorCode());
oItem.setFilamentVariant(qItem.getFilamentVariant()); oItem.setFilamentVariant(qItem.getFilamentVariant());
oItem.setShopProduct(qItem.getShopProduct());
oItem.setShopProductVariant(qItem.getShopProductVariant());
oItem.setShopProductSlug(qItem.getShopProductSlug());
oItem.setShopProductName(qItem.getShopProductName());
oItem.setShopVariantLabel(qItem.getShopVariantLabel());
oItem.setShopVariantColorName(qItem.getShopVariantColorName());
oItem.setShopVariantColorHex(qItem.getShopVariantColorHex());
if (qItem.getFilamentVariant() != null if (qItem.getFilamentVariant() != null
&& qItem.getFilamentVariant().getFilamentMaterialType() != null && qItem.getFilamentVariant().getFilamentMaterialType() != null
&& qItem.getFilamentVariant().getFilamentMaterialType().getMaterialCode() != null) { && qItem.getFilamentVariant().getFilamentMaterialType().getMaterialCode() != null) {
@@ -319,6 +333,13 @@ public class OrderService {
} }
} }
private String resolveOrderSourceType(QuoteSession session) {
if (session != null && "SHOP_CART".equalsIgnoreCase(session.getSessionType())) {
return "SHOP";
}
return "CALCULATOR";
}
private String getDisplayOrderNumber(Order order) { private String getDisplayOrderNumber(Order order) {
String orderNumber = order.getOrderNumber(); String orderNumber = order.getOrderNumber();
if (orderNumber != null && !orderNumber.isBlank()) { if (orderNumber != null && !orderNumber.isBlank()) {

View File

@@ -301,6 +301,7 @@ public class AdminOperationsControllerService {
} else { } else {
session = new QuoteSession(); session = new QuoteSession();
session.setStatus("CAD_ACTIVE"); session.setStatus("CAD_ACTIVE");
session.setSessionType("PRINT_QUOTE");
session.setPricingVersion("v1"); session.setPricingVersion("v1");
session.setMaterialCode("PLA"); session.setMaterialCode("PLA");
session.setNozzleDiameterMm(BigDecimal.valueOf(0.4)); session.setNozzleDiameterMm(BigDecimal.valueOf(0.4));
@@ -398,6 +399,7 @@ public class AdminOperationsControllerService {
AdminQuoteSessionDto dto = new AdminQuoteSessionDto(); AdminQuoteSessionDto dto = new AdminQuoteSessionDto();
dto.setId(session.getId()); dto.setId(session.getId());
dto.setStatus(session.getStatus()); dto.setStatus(session.getStatus());
dto.setSessionType(session.getSessionType() != null ? session.getSessionType() : "PRINT_QUOTE");
dto.setMaterialCode(session.getMaterialCode()); dto.setMaterialCode(session.getMaterialCode());
dto.setCreatedAt(session.getCreatedAt()); dto.setCreatedAt(session.getCreatedAt());
dto.setExpiresAt(session.getExpiresAt()); dto.setExpiresAt(session.getExpiresAt());

View File

@@ -197,6 +197,7 @@ public class AdminOrderControllerService {
OrderDto dto = new OrderDto(); OrderDto dto = new OrderDto();
dto.setId(order.getId()); dto.setId(order.getId());
dto.setOrderNumber(getDisplayOrderNumber(order)); dto.setOrderNumber(getDisplayOrderNumber(order));
dto.setSourceType(order.getSourceType() != null ? order.getSourceType() : "CALCULATOR");
dto.setStatus(order.getStatus()); dto.setStatus(order.getStatus());
paymentRepo.findByOrder_Id(order.getId()).ifPresent(payment -> { paymentRepo.findByOrder_Id(order.getId()).ifPresent(payment -> {
@@ -260,9 +261,26 @@ public class AdminOrderControllerService {
List<OrderItemDto> itemDtos = items.stream().map(item -> { List<OrderItemDto> itemDtos = items.stream().map(item -> {
OrderItemDto itemDto = new OrderItemDto(); OrderItemDto itemDto = new OrderItemDto();
itemDto.setId(item.getId()); itemDto.setId(item.getId());
itemDto.setItemType(item.getItemType() != null ? item.getItemType() : "PRINT_FILE");
itemDto.setOriginalFilename(item.getOriginalFilename()); itemDto.setOriginalFilename(item.getOriginalFilename());
itemDto.setDisplayName(
item.getDisplayName() != null && !item.getDisplayName().isBlank()
? item.getDisplayName()
: item.getOriginalFilename()
);
itemDto.setMaterialCode(item.getMaterialCode()); itemDto.setMaterialCode(item.getMaterialCode());
itemDto.setColorCode(item.getColorCode()); itemDto.setColorCode(item.getColorCode());
if (item.getShopProduct() != null) {
itemDto.setShopProductId(item.getShopProduct().getId());
}
if (item.getShopProductVariant() != null) {
itemDto.setShopProductVariantId(item.getShopProductVariant().getId());
}
itemDto.setShopProductSlug(item.getShopProductSlug());
itemDto.setShopProductName(item.getShopProductName());
itemDto.setShopVariantLabel(item.getShopVariantLabel());
itemDto.setShopVariantColorName(item.getShopVariantColorName());
itemDto.setShopVariantColorHex(item.getShopVariantColorHex());
if (item.getFilamentVariant() != null) { if (item.getFilamentVariant() != null) {
itemDto.setFilamentVariantId(item.getFilamentVariant().getId()); itemDto.setFilamentVariantId(item.getFilamentVariant().getId());
itemDto.setFilamentVariantDisplayName(item.getFilamentVariant().getVariantDisplayName()); itemDto.setFilamentVariantDisplayName(item.getFilamentVariant().getVariantDisplayName());

View File

@@ -255,6 +255,7 @@ public class OrderControllerService {
OrderDto dto = new OrderDto(); OrderDto dto = new OrderDto();
dto.setId(order.getId()); dto.setId(order.getId());
dto.setOrderNumber(getDisplayOrderNumber(order)); dto.setOrderNumber(getDisplayOrderNumber(order));
dto.setSourceType(order.getSourceType() != null ? order.getSourceType() : "CALCULATOR");
dto.setStatus(order.getStatus()); dto.setStatus(order.getStatus());
paymentRepo.findByOrder_Id(order.getId()).ifPresent(payment -> { paymentRepo.findByOrder_Id(order.getId()).ifPresent(payment -> {
@@ -314,9 +315,26 @@ public class OrderControllerService {
List<OrderItemDto> itemDtos = items.stream().map(item -> { List<OrderItemDto> itemDtos = items.stream().map(item -> {
OrderItemDto itemDto = new OrderItemDto(); OrderItemDto itemDto = new OrderItemDto();
itemDto.setId(item.getId()); itemDto.setId(item.getId());
itemDto.setItemType(item.getItemType() != null ? item.getItemType() : "PRINT_FILE");
itemDto.setOriginalFilename(item.getOriginalFilename()); itemDto.setOriginalFilename(item.getOriginalFilename());
itemDto.setDisplayName(
item.getDisplayName() != null && !item.getDisplayName().isBlank()
? item.getDisplayName()
: item.getOriginalFilename()
);
itemDto.setMaterialCode(item.getMaterialCode()); itemDto.setMaterialCode(item.getMaterialCode());
itemDto.setColorCode(item.getColorCode()); itemDto.setColorCode(item.getColorCode());
if (item.getShopProduct() != null) {
itemDto.setShopProductId(item.getShopProduct().getId());
}
if (item.getShopProductVariant() != null) {
itemDto.setShopProductVariantId(item.getShopProductVariant().getId());
}
itemDto.setShopProductSlug(item.getShopProductSlug());
itemDto.setShopProductName(item.getShopProductName());
itemDto.setShopVariantLabel(item.getShopVariantLabel());
itemDto.setShopVariantColorName(item.getShopVariantColorName());
itemDto.setShopVariantColorHex(item.getShopVariantColorHex());
if (item.getFilamentVariant() != null) { if (item.getFilamentVariant() != null) {
itemDto.setFilamentVariantId(item.getFilamentVariant().getId()); itemDto.setFilamentVariantId(item.getFilamentVariant().getId());
itemDto.setFilamentVariantDisplayName(item.getFilamentVariant().getVariantDisplayName()); itemDto.setFilamentVariantDisplayName(item.getFilamentVariant().getVariantDisplayName());

View File

@@ -237,7 +237,9 @@ public class QuoteSessionItemService {
Path convertedPersistentPath) { Path convertedPersistentPath) {
QuoteLineItem item = new QuoteLineItem(); QuoteLineItem item = new QuoteLineItem();
item.setQuoteSession(session); item.setQuoteSession(session);
item.setLineItemType("PRINT_FILE");
item.setOriginalFilename(originalFilename); item.setOriginalFilename(originalFilename);
item.setDisplayName(originalFilename);
item.setStoredPath(quoteStorageService.toStoredPath(persistentPath)); item.setStoredPath(quoteStorageService.toStoredPath(persistentPath));
item.setQuantity(normalizeQuantity(settings.getQuantity())); item.setQuantity(normalizeQuantity(settings.getQuantity()));
item.setColorCode(selectedVariant.getColorName()); item.setColorCode(selectedVariant.getColorName());

View File

@@ -46,12 +46,26 @@ public class QuoteSessionResponseAssembler {
private Map<String, Object> toItemDto(QuoteLineItem item, QuoteSessionTotalsService.QuoteSessionTotals totals) { private Map<String, Object> toItemDto(QuoteLineItem item, QuoteSessionTotalsService.QuoteSessionTotals totals) {
Map<String, Object> dto = new HashMap<>(); Map<String, Object> dto = new HashMap<>();
dto.put("id", item.getId()); dto.put("id", item.getId());
dto.put("lineItemType", item.getLineItemType() != null ? item.getLineItemType() : "PRINT_FILE");
dto.put("originalFilename", item.getOriginalFilename()); dto.put("originalFilename", item.getOriginalFilename());
dto.put(
"displayName",
item.getDisplayName() != null && !item.getDisplayName().isBlank()
? item.getDisplayName()
: item.getOriginalFilename()
);
dto.put("quantity", item.getQuantity()); dto.put("quantity", item.getQuantity());
dto.put("printTimeSeconds", item.getPrintTimeSeconds()); dto.put("printTimeSeconds", item.getPrintTimeSeconds());
dto.put("materialGrams", item.getMaterialGrams()); dto.put("materialGrams", item.getMaterialGrams());
dto.put("colorCode", item.getColorCode()); dto.put("colorCode", item.getColorCode());
dto.put("filamentVariantId", item.getFilamentVariant() != null ? item.getFilamentVariant().getId() : null); dto.put("filamentVariantId", item.getFilamentVariant() != null ? item.getFilamentVariant().getId() : null);
dto.put("shopProductId", item.getShopProduct() != null ? item.getShopProduct().getId() : null);
dto.put("shopProductVariantId", item.getShopProductVariant() != null ? item.getShopProductVariant().getId() : null);
dto.put("shopProductSlug", item.getShopProductSlug());
dto.put("shopProductName", item.getShopProductName());
dto.put("shopVariantLabel", item.getShopVariantLabel());
dto.put("shopVariantColorName", item.getShopVariantColorName());
dto.put("shopVariantColorHex", item.getShopVariantColorHex());
dto.put("materialCode", item.getMaterialCode()); dto.put("materialCode", item.getMaterialCode());
dto.put("quality", item.getQuality()); dto.put("quality", item.getQuality());
dto.put("nozzleDiameterMm", item.getNozzleDiameterMm()); dto.put("nozzleDiameterMm", item.getNozzleDiameterMm());

233
db.sql
View File

@@ -1007,6 +1007,239 @@ ALTER TABLE media_usage
ALTER TABLE media_usage ALTER TABLE media_usage
ADD COLUMN IF NOT EXISTS alt_text_fr text; ADD COLUMN IF NOT EXISTS alt_text_fr text;
CREATE TABLE IF NOT EXISTS shop_category
(
shop_category_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
parent_category_id uuid REFERENCES shop_category (shop_category_id) ON DELETE SET NULL,
slug text NOT NULL UNIQUE,
name text NOT NULL,
description text,
seo_title text,
seo_description text,
og_title text,
og_description text,
indexable boolean NOT NULL DEFAULT true,
is_active boolean NOT NULL DEFAULT true,
sort_order integer NOT NULL DEFAULT 0,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT chk_shop_category_not_self_parent CHECK (
parent_category_id IS NULL OR parent_category_id <> shop_category_id
)
);
CREATE INDEX IF NOT EXISTS ix_shop_category_parent_sort
ON shop_category (parent_category_id, sort_order, created_at DESC);
CREATE INDEX IF NOT EXISTS ix_shop_category_active_sort
ON shop_category (is_active, sort_order, created_at DESC);
CREATE TABLE IF NOT EXISTS shop_product
(
shop_product_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
shop_category_id uuid NOT NULL REFERENCES shop_category (shop_category_id),
slug text NOT NULL UNIQUE,
name text NOT NULL,
excerpt text,
description text,
seo_title text,
seo_description text,
og_title text,
og_description text,
indexable boolean NOT NULL DEFAULT true,
is_featured boolean NOT NULL DEFAULT false,
is_active boolean NOT NULL DEFAULT true,
sort_order integer NOT NULL DEFAULT 0,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS ix_shop_product_category_active_sort
ON shop_product (shop_category_id, is_active, sort_order, created_at DESC);
CREATE INDEX IF NOT EXISTS ix_shop_product_featured_sort
ON shop_product (is_featured, is_active, sort_order, created_at DESC);
CREATE TABLE IF NOT EXISTS shop_product_variant
(
shop_product_variant_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
shop_product_id uuid NOT NULL REFERENCES shop_product (shop_product_id) ON DELETE CASCADE,
sku text UNIQUE,
variant_label text NOT NULL,
color_name text NOT NULL,
color_hex text,
internal_material_code text NOT NULL,
price_chf numeric(12, 2) NOT NULL DEFAULT 0.00 CHECK (price_chf >= 0),
is_default boolean NOT NULL DEFAULT false,
is_active boolean NOT NULL DEFAULT true,
sort_order integer NOT NULL DEFAULT 0,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS ix_shop_product_variant_product_active_sort
ON shop_product_variant (shop_product_id, is_active, sort_order, created_at DESC);
CREATE INDEX IF NOT EXISTS ix_shop_product_variant_sku
ON shop_product_variant (sku);
CREATE TABLE IF NOT EXISTS shop_product_model_asset
(
shop_product_model_asset_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
shop_product_id uuid NOT NULL UNIQUE REFERENCES shop_product (shop_product_id) ON DELETE CASCADE,
original_filename text NOT NULL,
stored_relative_path text NOT NULL,
stored_filename text NOT NULL,
file_size_bytes bigint CHECK (file_size_bytes >= 0),
mime_type text,
sha256_hex text,
bounding_box_x_mm numeric(10, 3),
bounding_box_y_mm numeric(10, 3),
bounding_box_z_mm numeric(10, 3),
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS ix_shop_product_model_asset_product
ON shop_product_model_asset (shop_product_id);
ALTER TABLE quote_sessions
ADD COLUMN IF NOT EXISTS session_type text NOT NULL DEFAULT 'PRINT_QUOTE';
CREATE INDEX IF NOT EXISTS ix_quote_sessions_session_type
ON quote_sessions (session_type);
ALTER TABLE quote_sessions
DROP CONSTRAINT IF EXISTS quote_sessions_session_type_check;
ALTER TABLE quote_sessions
ADD CONSTRAINT quote_sessions_session_type_check
CHECK (session_type IN ('PRINT_QUOTE', 'SHOP_CART'));
ALTER TABLE quote_line_items
ADD COLUMN IF NOT EXISTS line_item_type text NOT NULL DEFAULT 'PRINT_FILE';
ALTER TABLE quote_line_items
ADD COLUMN IF NOT EXISTS display_name text;
ALTER TABLE quote_line_items
ADD COLUMN IF NOT EXISTS shop_product_id uuid;
ALTER TABLE quote_line_items
ADD COLUMN IF NOT EXISTS shop_product_variant_id uuid;
ALTER TABLE quote_line_items
ADD COLUMN IF NOT EXISTS shop_product_slug text;
ALTER TABLE quote_line_items
ADD COLUMN IF NOT EXISTS shop_product_name text;
ALTER TABLE quote_line_items
ADD COLUMN IF NOT EXISTS shop_variant_label text;
ALTER TABLE quote_line_items
ADD COLUMN IF NOT EXISTS shop_variant_color_name text;
ALTER TABLE quote_line_items
ADD COLUMN IF NOT EXISTS shop_variant_color_hex text;
ALTER TABLE quote_line_items
ADD COLUMN IF NOT EXISTS stored_path text;
CREATE INDEX IF NOT EXISTS ix_quote_line_items_shop_product
ON quote_line_items (shop_product_id);
CREATE INDEX IF NOT EXISTS ix_quote_line_items_shop_product_variant
ON quote_line_items (shop_product_variant_id);
ALTER TABLE quote_line_items
DROP CONSTRAINT IF EXISTS quote_line_items_line_item_type_check;
ALTER TABLE quote_line_items
ADD CONSTRAINT quote_line_items_line_item_type_check
CHECK (line_item_type IN ('PRINT_FILE', 'SHOP_PRODUCT'));
ALTER TABLE quote_line_items
DROP CONSTRAINT IF EXISTS fk_quote_line_items_shop_product;
ALTER TABLE quote_line_items
ADD CONSTRAINT fk_quote_line_items_shop_product
FOREIGN KEY (shop_product_id) REFERENCES shop_product (shop_product_id);
ALTER TABLE quote_line_items
DROP CONSTRAINT IF EXISTS fk_quote_line_items_shop_product_variant;
ALTER TABLE quote_line_items
ADD CONSTRAINT fk_quote_line_items_shop_product_variant
FOREIGN KEY (shop_product_variant_id) REFERENCES shop_product_variant (shop_product_variant_id);
ALTER TABLE orders
ADD COLUMN IF NOT EXISTS source_type text NOT NULL DEFAULT 'CALCULATOR';
CREATE INDEX IF NOT EXISTS ix_orders_source_type
ON orders (source_type);
ALTER TABLE orders
DROP CONSTRAINT IF EXISTS orders_source_type_check;
ALTER TABLE orders
ADD CONSTRAINT orders_source_type_check
CHECK (source_type IN ('CALCULATOR', 'SHOP'));
ALTER TABLE order_items
ADD COLUMN IF NOT EXISTS item_type text NOT NULL DEFAULT 'PRINT_FILE';
ALTER TABLE order_items
ADD COLUMN IF NOT EXISTS display_name text;
ALTER TABLE order_items
ADD COLUMN IF NOT EXISTS shop_product_id uuid;
ALTER TABLE order_items
ADD COLUMN IF NOT EXISTS shop_product_variant_id uuid;
ALTER TABLE order_items
ADD COLUMN IF NOT EXISTS shop_product_slug text;
ALTER TABLE order_items
ADD COLUMN IF NOT EXISTS shop_product_name text;
ALTER TABLE order_items
ADD COLUMN IF NOT EXISTS shop_variant_label text;
ALTER TABLE order_items
ADD COLUMN IF NOT EXISTS shop_variant_color_name text;
ALTER TABLE order_items
ADD COLUMN IF NOT EXISTS shop_variant_color_hex text;
CREATE INDEX IF NOT EXISTS ix_order_items_shop_product
ON order_items (shop_product_id);
CREATE INDEX IF NOT EXISTS ix_order_items_shop_product_variant
ON order_items (shop_product_variant_id);
ALTER TABLE order_items
DROP CONSTRAINT IF EXISTS order_items_item_type_check;
ALTER TABLE order_items
ADD CONSTRAINT order_items_item_type_check
CHECK (item_type IN ('PRINT_FILE', 'SHOP_PRODUCT'));
ALTER TABLE order_items
DROP CONSTRAINT IF EXISTS fk_order_items_shop_product;
ALTER TABLE order_items
ADD CONSTRAINT fk_order_items_shop_product
FOREIGN KEY (shop_product_id) REFERENCES shop_product (shop_product_id);
ALTER TABLE order_items
DROP CONSTRAINT IF EXISTS fk_order_items_shop_product_variant;
ALTER TABLE order_items
ADD CONSTRAINT fk_order_items_shop_product_variant
FOREIGN KEY (shop_product_variant_id) REFERENCES shop_product_variant (shop_product_variant_id);
ALTER TABLE quote_sessions ALTER TABLE quote_sessions
DROP CONSTRAINT IF EXISTS fk_quote_sessions_source_request; DROP CONSTRAINT IF EXISTS fk_quote_sessions_source_request;