diff --git a/pom.xml b/pom.xml
index 73fc325..d4e9dd9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
com.imyeyu.timiserverapi
TimiServerAPI
- 1.0.30
+ 1.0.31
jar
TimiServerAPI
imyeyu.com API
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoOrderFulfillmentItem.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoOrderFulfillmentItem.java
index 8f847a1..645efbf 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoOrderFulfillmentItem.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoOrderFulfillmentItem.java
@@ -1,5 +1,6 @@
package com.imyeyu.api.modules.gao.entity;
+import com.fasterxml.jackson.databind.JsonNode;
import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.spring.annotation.table.AutoUUID;
import com.imyeyu.spring.annotation.table.Id;
@@ -71,6 +72,9 @@ public class GaoOrderFulfillmentItem implements IDEntity, Creatable {
/// 规格摘要快照
private String specSummarySnapshot;
+ /// SKU 规格结构快照,数组元素包含 specId、specName、specValueId、specValueName
+ private JsonNode skuSpecSnapshot;
+
/// 单位快照
private String unitSnapshot;
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductSpec.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductSpec.java
index d809a67..947a1e9 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductSpec.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductSpec.java
@@ -1,5 +1,6 @@
package com.imyeyu.api.modules.gao.entity;
+import com.fasterxml.jackson.annotation.JsonProperty;
import com.imyeyu.spring.annotation.table.Transient;
import com.imyeyu.spring.entity.UUIDEntity;
import lombok.Data;
@@ -15,6 +16,11 @@ import java.util.List;
@EqualsAndHashCode(callSuper = true)
public class GaoProductSpec extends UUIDEntity {
+ /// 前端创建请求中的临时关联键
+ @Transient
+ @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
+ private String clientKey;
+
/// 产品 ID
private String productId;
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductSpecValue.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductSpecValue.java
index e6c1b40..e44ac55 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductSpecValue.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductSpecValue.java
@@ -1,5 +1,7 @@
package com.imyeyu.api.modules.gao.entity;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.imyeyu.spring.annotation.table.Transient;
import com.imyeyu.spring.entity.UUIDEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -12,6 +14,11 @@ import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
public class GaoProductSpecValue extends UUIDEntity {
+ /// 前端创建请求中的临时关联键
+ @Transient
+ @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
+ private String clientKey;
+
/// 规格项 ID
private String specId;
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductStockRecord.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductStockRecord.java
index b670e31..e1f0ded 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductStockRecord.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductStockRecord.java
@@ -1,5 +1,6 @@
package com.imyeyu.api.modules.gao.entity;
+import com.fasterxml.jackson.databind.JsonNode;
import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.user.entity.User;
import com.imyeyu.spring.annotation.table.AutoUUID;
@@ -91,6 +92,9 @@ public class GaoProductStockRecord implements IDEntity, Creatable {
/// 规格摘要快照
private String specSummarySnapshot;
+ /// SKU 规格结构快照,数组元素包含 specId、specName、specValueId、specValueName
+ private JsonNode skuSpecSnapshot;
+
/// 单位名称快照
private String unitSnapshot;
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoSalesOrderItem.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoSalesOrderItem.java
index 0920666..db2166d 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoSalesOrderItem.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoSalesOrderItem.java
@@ -1,5 +1,6 @@
package com.imyeyu.api.modules.gao.entity;
+import com.fasterxml.jackson.databind.JsonNode;
import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.spring.annotation.table.Transient;
import com.imyeyu.spring.entity.UUIDEntity;
@@ -58,6 +59,9 @@ public class GaoSalesOrderItem extends UUIDEntity {
/// 规格摘要快照
private String specSummarySnapshot;
+ /// SKU 规格结构快照,数组元素包含 specId、specName、specValueId、specValueName
+ private JsonNode skuSpecSnapshot;
+
/// 单位快照
private String unitSnapshot;
diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoProductSkuService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoProductSkuService.java
index 091b465..f7d4d21 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/service/GaoProductSkuService.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoProductSkuService.java
@@ -1,5 +1,6 @@
package com.imyeyu.api.modules.gao.service;
+import com.fasterxml.jackson.databind.JsonNode;
import com.imyeyu.api.modules.gao.entity.GaoProductSku;
import com.imyeyu.api.modules.gao.entity.GaoProductSkuUnit;
import com.imyeyu.api.modules.gao.vo.GaoProductSkuUnitSelection;
@@ -32,6 +33,18 @@ public interface GaoProductSkuService extends BaseService
/// 查询 SKU 单位
List listUnitBySkuId(String skuId);
+ /// 查询 SKU 规格结构快照
+ ///
+ /// @param skuId SKU ID
+ /// @return SKU 规格结构快照;无规格时返回空数组
+ JsonNode getSkuSpecSnapshot(String skuId);
+
+ /// 根据已加载 SKU 查询规格结构快照
+ ///
+ /// @param sku 已加载 SKU
+ /// @return SKU 规格结构快照;无规格时返回空数组
+ JsonNode getSkuSpecSnapshot(GaoProductSku sku);
+
/// 停用 SKU
void disable(String id);
}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerPackageServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerPackageServiceImplement.java
index 17178c0..d63d3e9 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerPackageServiceImplement.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerPackageServiceImplement.java
@@ -263,6 +263,7 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService fulfillmentItemList = itemMapper.selectByFulfillmentIdForUpdate(fulfillment.getId());
TimiException.requiredTrue(!fulfillmentItemList.isEmpty(), "履约单没有明细");
List stockItems = buildStockItems(order.getStoreId(), sourceList);
+ applySkuSpecSnapshots(stockItems, fulfillmentItemList);
TimiException.requiredTrue(!stockItems.isEmpty(), "履约单没有需要占用的实物库存");
TimiException.requiredTrue(productStockService.hasSufficientForFulfillment(order.getStoreId(), stockItems), "库存仍然不足,不能准备履约");
String operatorUserId = userLoginService.getRequireLoginUserId();
@@ -240,6 +242,7 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService stockItems = buildStockItems(order.getStoreId(), sourceList);
+ applySkuSpecSnapshots(stockItems, fulfillmentItemList);
String verifierUserId = userLoginService.getRequireLoginUserId();
if (!stockItems.isEmpty()) {
productStockService.outForFulfillment(order.getStoreId(), fulfillment.getId(), stockItems,
@@ -289,6 +292,7 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService stockItems = buildStockItems(order.getStoreId(), sourceList);
+ applySkuSpecSnapshots(stockItems, fulfillmentItemList);
if (!stockItems.isEmpty()) {
productStockService.releaseForFulfillment(order.getStoreId(), fulfillment.getId(), stockItems,
stockRequestNo(fulfillment, "RELEASE"), userLoginService.getRequireLoginUserId());
@@ -364,6 +368,7 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService stockItems, List fulfillmentItemList) {
+ if (stockItems == null || stockItems.isEmpty() || fulfillmentItemList == null || fulfillmentItemList.isEmpty()) {
+ return;
+ }
+ Map snapshotMap = new HashMap<>();
+ for (GaoOrderFulfillmentItem item : fulfillmentItemList) {
+ if (item.getSkuId() != null) {
+ snapshotMap.put(item.getSkuId(), item.getSkuSpecSnapshot());
+ }
+ }
+ for (GaoStockChangeItem item : stockItems) {
+ JsonNode snapshot = snapshotMap.get(item.getSkuId());
+ if (snapshot != null) {
+ item.setSkuSpecSnapshot(snapshot);
+ }
+ }
+ }
+
private List itemRequests(String fulfillmentId) {
return copyRequestItems(itemMapper.selectByFulfillmentId(fulfillmentId));
}
@@ -531,6 +555,7 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService {
prepare(product, null);
super.create(product);
+ Map specValueIdMap = saveSpecs(product, product.getSpecList());
+ normalizeSkuSpecValueIds(product.getSkuList(), specValueIdMap);
saveSkus(product, product.getSkuList());
attachmentService.updateByBizId(Attachment.BizType.GAO_PRODUCT, product.getId(), product.getAttachmentList());
return product;
@@ -196,6 +202,48 @@ public class GaoProductServiceImplement extends AbstractEntityService saveSpecs(GaoProduct product, List specList) {
+ Map specValueIdMap = new HashMap<>();
+ if (specList == null || specList.isEmpty()) {
+ return specValueIdMap;
+ }
+ for (int specIndex = 0; specIndex < specList.size(); specIndex++) {
+ GaoProductSpec spec = specList.get(specIndex);
+ spec.setId(null);
+ spec.setProductId(product.getId());
+ spec.setSort(TimiJava.defaultIfNull(spec.getSort(), specIndex));
+ specServiceProvider.getObject().createSpec(spec);
+ List valueList = spec.getValueList();
+ if (valueList == null) {
+ continue;
+ }
+ for (int valueIndex = 0; valueIndex < valueList.size(); valueIndex++) {
+ GaoProductSpecValue value = valueList.get(valueIndex);
+ String clientKey = value.getClientKey();
+ value.setId(null);
+ value.setSpecId(spec.getId());
+ value.setSort(TimiJava.defaultIfNull(value.getSort(), valueIndex));
+ specServiceProvider.getObject().createValue(value);
+ if (clientKey != null && !clientKey.isBlank()) {
+ specValueIdMap.put(clientKey, value.getId());
+ }
+ }
+ }
+ return specValueIdMap;
+ }
+
+ private void normalizeSkuSpecValueIds(List skuList, Map specValueIdMap) {
+ if (skuList == null || skuList.isEmpty() || specValueIdMap.isEmpty()) {
+ return;
+ }
+ for (GaoProductSku sku : skuList) {
+ List specValueIdList = sku.getSpecValueIdList();
+ if (specValueIdList != null) {
+ sku.setSpecValueIdList(specValueIdList.stream().map(id -> specValueIdMap.getOrDefault(id, id)).toList());
+ }
+ }
+ }
+
private void fillTransient(List list) {
if (list == null || list.isEmpty()) {
return;
diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductSkuServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductSkuServiceImplement.java
index e820abb..749979f 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductSkuServiceImplement.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductSkuServiceImplement.java
@@ -1,5 +1,9 @@
package com.imyeyu.api.modules.gao.service.implement;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.service.AttachmentService;
@@ -34,6 +38,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -120,6 +125,45 @@ public class GaoProductSkuServiceImplement extends AbstractEntityService specList = specMapper.selectByProductId(sku.getProductId());
+ if (specList.isEmpty()) {
+ return JsonNodeFactory.instance.arrayNode();
+ }
+ List specIdList = specList.stream().map(GaoProductSpec::getId).toList();
+ Map> valueMap = new LinkedHashMap<>();
+ for (GaoProductSpecValue value : valueMapper.selectBySpecIdList(specIdList)) {
+ valueMap.computeIfAbsent(value.getSpecId(), key -> new ArrayList<>()).add(value);
+ }
+ Set selectedValueIdSet = skuSpecValueMapper.selectBySkuIdList(List.of(sku.getId())).stream()
+ .map(GaoProductSkuSpecValue::getSpecValueId)
+ .collect(Collectors.toSet());
+ ArrayNode result = JsonNodeFactory.instance.arrayNode();
+ for (GaoProductSpec spec : specList) {
+ for (GaoProductSpecValue value : valueMap.getOrDefault(spec.getId(), List.of())) {
+ if (!selectedValueIdSet.contains(value.getId())) {
+ continue;
+ }
+ ObjectNode snapshot = result.addObject();
+ snapshot.put("specId", spec.getId());
+ snapshot.put("specName", spec.getName());
+ snapshot.put("specValueId", value.getId());
+ snapshot.put("specValueName", value.getName());
+ }
+ }
+ return result;
+ }
+
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void create(GaoProductSku sku) {
diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductStockRecordServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductStockRecordServiceImplement.java
index 4e96633..1cc297d 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductStockRecordServiceImplement.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductStockRecordServiceImplement.java
@@ -138,4 +138,5 @@ public class GaoProductStockRecordServiceImplement extends AbstractEntityService
record.setOperator(userMap.get(record.getOperatorUserId()));
}
}
+
}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductStockServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductStockServiceImplement.java
index 06ad1c6..b2ff198 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductStockServiceImplement.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductStockServiceImplement.java
@@ -214,6 +214,7 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
TimiException.requiredTrue(hasAttachment(request.getAttachmentList()), "库存操作必须上传照片");
TimiException.requiredTrue(hasText(request.getRemark()), "库存操作必须填写备注");
GaoStockChangeItem item = GaoStockChangeItem.of(product, sku, unit, quantity, baseQuantity);
+ item.setSkuSpecSnapshot(skuService.getSkuSpecSnapshot(sku));
List result = executeInternal(storeId, sku.getId(), List.of(item), requestNo,
userLoginService.getRequireLoginUserId(), operationType, request.getRemark(), request.getAttachmentList());
return result.getFirst();
@@ -265,6 +266,7 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
revoke.setSkuCodeSnapshot(source.getSkuCodeSnapshot());
revoke.setSkuNameSnapshot(source.getSkuNameSnapshot());
revoke.setSpecSummarySnapshot(source.getSpecSummarySnapshot());
+ revoke.setSkuSpecSnapshot(source.getSkuSpecSnapshot());
revoke.setUnitSnapshot(source.getUnitSnapshot());
revoke.setBaseQuantityPerUnitSnapshot(source.getBaseQuantityPerUnitSnapshot());
revoke.setQuantity(source.getQuantity());
@@ -341,6 +343,7 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
record.setSkuCodeSnapshot(item.getSkuCodeSnapshot());
record.setSkuNameSnapshot(item.getSkuNameSnapshot());
record.setSpecSummarySnapshot(item.getSpecSummarySnapshot());
+ record.setSkuSpecSnapshot(item.getSkuSpecSnapshot());
record.setUnitSnapshot(item.getUnitSnapshot());
record.setBaseQuantityPerUnitSnapshot(item.getBaseQuantityPerUnitSnapshot());
record.setQuantity(item.getQuantity());
diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoSalesOrderServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoSalesOrderServiceImplement.java
index 7e84042..9d16dc6 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoSalesOrderServiceImplement.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoSalesOrderServiceImplement.java
@@ -379,6 +379,7 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService