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