Compare commits

..
43 Commits
Author SHA1 Message Date
timi 118bdb1666 Merge pull request 'v1.0.31' (#43) from dev into master
Reviewed-on: #43
2026-09-07 07:40:03 +00:00
Timi 461af4d581 v1.0.31
CI / build-deploy (pull_request) Successful in 39s
CI / notify-on-failure (pull_request) Skipped
2026-09-07 15:33:29 +08:00
timi 944de228f7 Merge pull request 'v1.0.30' (#42) from dev into master
Reviewed-on: #42
2026-09-01 17:44:48 +00:00
timi 25c9cd4dfc Merge pull request 'v1.0.30' (#41) from dev into master
Reviewed-on: #41
2026-09-01 17:42:44 +00:00
timi 882cd226ec Merge pull request 'v1.0.30' (#40) from dev into master
Reviewed-on: #40
2026-09-01 17:38:50 +00:00
timi feb4d7af66 Merge pull request 'v1.0.28' (#38) from dev into master
Reviewed-on: #38
2026-08-30 15:51:37 +00:00
timi d75ab1ed5f Merge pull request 'v1.0.27' (#37) from dev into master
Reviewed-on: #37
2026-08-29 17:49:23 +00:00
timi a501eb7443 Merge pull request 'v1.0.26' (#36) from dev into master
Reviewed-on: #36
2026-08-28 16:52:15 +00:00
timi 19941d9480 Merge pull request 'v1.0.25' (#35) from dev into master
Reviewed-on: #35
2026-08-23 17:50:25 +00:00
timi cd3d522d4f Merge pull request 'v1.0.24' (#34) from dev into master
Reviewed-on: #34
2026-08-22 09:56:13 +00:00
timi 181c5078e9 Merge pull request 'v1.0.23' (#33) from dev into master
Reviewed-on: #33
2026-08-22 09:51:37 +00:00
timi e97f46e6b4 Merge pull request 'v1.0.22' (#32) from dev into master
Reviewed-on: #32
2026-08-21 17:31:33 +00:00
timi c0f89deb83 Merge pull request 'v1.0.22' (#31) from dev into master
Reviewed-on: #31
2026-08-21 17:28:32 +00:00
timi ce695463d3 Merge pull request 'v1.0.21' (#30) from dev into master
Reviewed-on: #30
2026-08-21 07:29:34 +00:00
timi 83c683d473 Merge pull request 'v1.0.20' (#29) from dev into master
Reviewed-on: #29
2026-08-21 07:25:32 +00:00
timi 9555474b0b Merge pull request 'v1.0.19' (#28) from dev into master
Reviewed-on: #28
2026-08-21 05:52:36 +00:00
timi a150f9e017 Merge pull request 'v1.0.18' (#27) from dev into master
Reviewed-on: #27
2026-08-20 17:40:22 +00:00
timi ecd2fa1c33 Merge pull request 'v1.0.17' (#26) from dev into master
Reviewed-on: #26
2026-08-19 17:30:35 +00:00
timi e3236d262d Merge pull request 'v1.0.16' (#25) from dev into master
Reviewed-on: #25
2026-08-16 03:37:08 +00:00
timi c8e2e16ba5 Merge pull request 'v1.0.15' (#24) from dev into master
Reviewed-on: #24
2026-08-15 17:49:51 +00:00
timi 99b44faf09 Merge pull request 'v1.0.15' (#23) from dev into master
Reviewed-on: #23
2026-08-15 17:45:26 +00:00
timi b3e3cbf84b Merge pull request 'v1.0.14' (#22) from dev into master
Reviewed-on: #22
2026-08-14 17:31:11 +00:00
timi 0aee11d824 Merge pull request 'v1.0.14' (#21) from dev into master
Reviewed-on: #21
2026-08-14 17:26:10 +00:00
timi fcf358a768 Merge pull request 'v1.0.13' (#20) from dev into master
Reviewed-on: #20
2026-08-13 09:52:26 +00:00
timi 619369104c Merge pull request 'v1.0.12' (#19) from dev into master
Reviewed-on: #19
2026-08-12 17:02:43 +00:00
timi 4f1408b9d0 Merge pull request 'v1.0.11' (#18) from dev into master
Reviewed-on: #18
2026-08-11 16:49:08 +00:00
timi 029eea70cc Merge pull request 'v1.0.10' (#17) from dev into master
Reviewed-on: #17
2026-08-11 16:45:36 +00:00
timi 22b1e65385 Merge pull request 'v1.0.9' (#16) from dev into master
Reviewed-on: #16
2026-08-11 16:01:10 +00:00
timi 2170c48763 Merge pull request 'v1.0.8' (#15) from dev into master
Reviewed-on: #15
2026-08-03 16:36:19 +00:00
timi c2d1c48aae Merge pull request 'v0.0.18' (#14) from dev into master
Reviewed-on: #14
2026-08-03 11:59:20 +00:00
timi e14958c643 Merge pull request 'v0.0.18' (#13) from dev into master
Reviewed-on: #13
2026-08-03 11:58:16 +00:00
timi da288338cd Merge pull request 'v1.0.7' (#12) from dev into master
Reviewed-on: #12
2026-08-01 09:34:18 +00:00
timi ea430f2468 Merge pull request 'v1.0.6' (#11) from dev into master
Reviewed-on: #11
2026-08-01 09:03:48 +00:00
timi f76f79744a Merge pull request 'v1.0.5' (#10) from dev into master
Reviewed-on: #10
2026-08-01 08:52:52 +00:00
timi 19daaff907 Merge pull request 'v1.0.4' (#9) from dev into master
Reviewed-on: #9
2026-08-01 03:13:00 +00:00
timi db94d8f932 Merge pull request 'v1.0.3' (#8) from dev into master
Reviewed-on: #8
2026-07-31 17:19:48 +00:00
timi 53618fe362 Merge pull request 'v1.0.3' (#7) from dev into master
Reviewed-on: #7
2026-07-31 17:13:37 +00:00
timi 523edffc4e Merge pull request 'v1.0.2' (#6) from dev into master
Reviewed-on: #6
2026-04-13 10:28:11 +00:00
timi c7ddb1a8b0 Merge pull request 'v1.0.1' (#5) from dev into master
Reviewed-on: #5
2026-04-12 16:17:02 +00:00
timi 45c9fc814a Merge pull request 'v1.0.0' (#4) from dev into master
Reviewed-on: #4
2026-04-09 05:16:04 +00:00
timi 407dc13ac4 Merge pull request 'v1.0.0' (#3) from dev into master
Reviewed-on: #3
2026-04-09 04:09:16 +00:00
timi 0c06bf16c2 Merge pull request 'v1.0.0' (#2) from dev into master
Reviewed-on: #2
2026-04-09 04:03:43 +00:00
timi 1db39e77d3 Merge pull request 'v1.0.0' (#1) from dev into master
Reviewed-on: #1
2026-04-08 08:48:12 +00:00
17 changed files with 181 additions and 8 deletions
+1 -1
View File
@@ -11,7 +11,7 @@
<groupId>com.imyeyu.timiserverapi</groupId> <groupId>com.imyeyu.timiserverapi</groupId>
<artifactId>TimiServerAPI</artifactId> <artifactId>TimiServerAPI</artifactId>
<version>1.0.30</version> <version>1.0.31</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>TimiServerAPI</name> <name>TimiServerAPI</name>
<description>imyeyu.com API</description> <description>imyeyu.com API</description>
@@ -1,5 +1,6 @@
package com.imyeyu.api.modules.gao.entity; 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.common.entity.Attachment;
import com.imyeyu.spring.annotation.table.AutoUUID; import com.imyeyu.spring.annotation.table.AutoUUID;
import com.imyeyu.spring.annotation.table.Id; import com.imyeyu.spring.annotation.table.Id;
@@ -71,6 +72,9 @@ public class GaoOrderFulfillmentItem implements IDEntity<String>, Creatable {
/// 规格摘要快照 /// 规格摘要快照
private String specSummarySnapshot; private String specSummarySnapshot;
/// SKU 规格结构快照,数组元素包含 specId、specName、specValueId、specValueName
private JsonNode skuSpecSnapshot;
/// 单位快照 /// 单位快照
private String unitSnapshot; private String unitSnapshot;
@@ -1,5 +1,6 @@
package com.imyeyu.api.modules.gao.entity; package com.imyeyu.api.modules.gao.entity;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.imyeyu.spring.annotation.table.Transient; import com.imyeyu.spring.annotation.table.Transient;
import com.imyeyu.spring.entity.UUIDEntity; import com.imyeyu.spring.entity.UUIDEntity;
import lombok.Data; import lombok.Data;
@@ -15,6 +16,11 @@ import java.util.List;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class GaoProductSpec extends UUIDEntity { public class GaoProductSpec extends UUIDEntity {
/// 前端创建请求中的临时关联键
@Transient
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String clientKey;
/// 产品 ID /// 产品 ID
private String productId; private String productId;
@@ -1,5 +1,7 @@
package com.imyeyu.api.modules.gao.entity; 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 com.imyeyu.spring.entity.UUIDEntity;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@@ -12,6 +14,11 @@ import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class GaoProductSpecValue extends UUIDEntity { public class GaoProductSpecValue extends UUIDEntity {
/// 前端创建请求中的临时关联键
@Transient
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String clientKey;
/// 规格项 ID /// 规格项 ID
private String specId; private String specId;
@@ -1,5 +1,6 @@
package com.imyeyu.api.modules.gao.entity; 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.common.entity.Attachment;
import com.imyeyu.api.modules.user.entity.User; import com.imyeyu.api.modules.user.entity.User;
import com.imyeyu.spring.annotation.table.AutoUUID; import com.imyeyu.spring.annotation.table.AutoUUID;
@@ -91,6 +92,9 @@ public class GaoProductStockRecord implements IDEntity<String>, Creatable {
/// 规格摘要快照 /// 规格摘要快照
private String specSummarySnapshot; private String specSummarySnapshot;
/// SKU 规格结构快照,数组元素包含 specId、specName、specValueId、specValueName
private JsonNode skuSpecSnapshot;
/// 单位名称快照 /// 单位名称快照
private String unitSnapshot; private String unitSnapshot;
@@ -1,5 +1,6 @@
package com.imyeyu.api.modules.gao.entity; 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.common.entity.Attachment;
import com.imyeyu.spring.annotation.table.Transient; import com.imyeyu.spring.annotation.table.Transient;
import com.imyeyu.spring.entity.UUIDEntity; import com.imyeyu.spring.entity.UUIDEntity;
@@ -58,6 +59,9 @@ public class GaoSalesOrderItem extends UUIDEntity {
/// 规格摘要快照 /// 规格摘要快照
private String specSummarySnapshot; private String specSummarySnapshot;
/// SKU 规格结构快照,数组元素包含 specId、specName、specValueId、specValueName
private JsonNode skuSpecSnapshot;
/// 单位快照 /// 单位快照
private String unitSnapshot; private String unitSnapshot;
@@ -1,5 +1,6 @@
package com.imyeyu.api.modules.gao.service; 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.GaoProductSku;
import com.imyeyu.api.modules.gao.entity.GaoProductSkuUnit; import com.imyeyu.api.modules.gao.entity.GaoProductSkuUnit;
import com.imyeyu.api.modules.gao.vo.GaoProductSkuUnitSelection; import com.imyeyu.api.modules.gao.vo.GaoProductSkuUnitSelection;
@@ -32,6 +33,18 @@ public interface GaoProductSkuService extends BaseService<GaoProductSku, String>
/// 查询 SKU 单位 /// 查询 SKU 单位
List<GaoProductSkuUnit> listUnitBySkuId(String skuId); List<GaoProductSkuUnit> 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 /// 停用 SKU
void disable(String id); void disable(String id);
} }
@@ -263,6 +263,7 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
item.setSkuCodeSnapshot(source.getSkuCodeSnapshot()); item.setSkuCodeSnapshot(source.getSkuCodeSnapshot());
item.setSkuNameSnapshot(source.getSkuNameSnapshot()); item.setSkuNameSnapshot(source.getSkuNameSnapshot());
item.setSpecSummarySnapshot(source.getSpecSummarySnapshot()); item.setSpecSummarySnapshot(source.getSpecSummarySnapshot());
item.setSkuSpecSnapshot(skuService.getSkuSpecSnapshot(source.getSkuId()));
item.setUnitSnapshot(source.getUnitSnapshot()); item.setUnitSnapshot(source.getUnitSnapshot());
item.setBaseQuantityPerUnitSnapshot(source.getBaseQuantityPerUnitSnapshot()); item.setBaseQuantityPerUnitSnapshot(source.getBaseQuantityPerUnitSnapshot());
item.setQuantity(source.getTotalQuantity()); item.setQuantity(source.getTotalQuantity());
@@ -1,5 +1,6 @@
package com.imyeyu.api.modules.gao.service.implement; package com.imyeyu.api.modules.gao.service.implement;
import com.fasterxml.jackson.databind.JsonNode;
import com.imyeyu.api.bean.ModuleCode; import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.entity.Attachment;
@@ -202,6 +203,7 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
List<GaoOrderFulfillmentItem> fulfillmentItemList = itemMapper.selectByFulfillmentIdForUpdate(fulfillment.getId()); List<GaoOrderFulfillmentItem> fulfillmentItemList = itemMapper.selectByFulfillmentIdForUpdate(fulfillment.getId());
TimiException.requiredTrue(!fulfillmentItemList.isEmpty(), "履约单没有明细"); TimiException.requiredTrue(!fulfillmentItemList.isEmpty(), "履约单没有明细");
List<GaoStockChangeItem> stockItems = buildStockItems(order.getStoreId(), sourceList); List<GaoStockChangeItem> stockItems = buildStockItems(order.getStoreId(), sourceList);
applySkuSpecSnapshots(stockItems, fulfillmentItemList);
TimiException.requiredTrue(!stockItems.isEmpty(), "履约单没有需要占用的实物库存"); TimiException.requiredTrue(!stockItems.isEmpty(), "履约单没有需要占用的实物库存");
TimiException.requiredTrue(productStockService.hasSufficientForFulfillment(order.getStoreId(), stockItems), "库存仍然不足,不能准备履约"); TimiException.requiredTrue(productStockService.hasSufficientForFulfillment(order.getStoreId(), stockItems), "库存仍然不足,不能准备履约");
String operatorUserId = userLoginService.getRequireLoginUserId(); String operatorUserId = userLoginService.getRequireLoginUserId();
@@ -240,6 +242,7 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
increaseFulfilled(source); increaseFulfilled(source);
} }
List<GaoStockChangeItem> stockItems = buildStockItems(order.getStoreId(), sourceList); List<GaoStockChangeItem> stockItems = buildStockItems(order.getStoreId(), sourceList);
applySkuSpecSnapshots(stockItems, fulfillmentItemList);
String verifierUserId = userLoginService.getRequireLoginUserId(); String verifierUserId = userLoginService.getRequireLoginUserId();
if (!stockItems.isEmpty()) { if (!stockItems.isEmpty()) {
productStockService.outForFulfillment(order.getStoreId(), fulfillment.getId(), stockItems, productStockService.outForFulfillment(order.getStoreId(), fulfillment.getId(), stockItems,
@@ -289,6 +292,7 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
TimiException.requiredTrue(!fulfillmentItemList.isEmpty(), "履约单没有明细"); TimiException.requiredTrue(!fulfillmentItemList.isEmpty(), "履约单没有明细");
if (fulfillment.getStatus() == GaoOrderFulfillment.Status.PENDING) { if (fulfillment.getStatus() == GaoOrderFulfillment.Status.PENDING) {
List<GaoStockChangeItem> stockItems = buildStockItems(order.getStoreId(), sourceList); List<GaoStockChangeItem> stockItems = buildStockItems(order.getStoreId(), sourceList);
applySkuSpecSnapshots(stockItems, fulfillmentItemList);
if (!stockItems.isEmpty()) { if (!stockItems.isEmpty()) {
productStockService.releaseForFulfillment(order.getStoreId(), fulfillment.getId(), stockItems, productStockService.releaseForFulfillment(order.getStoreId(), fulfillment.getId(), stockItems,
stockRequestNo(fulfillment, "RELEASE"), userLoginService.getRequireLoginUserId()); stockRequestNo(fulfillment, "RELEASE"), userLoginService.getRequireLoginUserId());
@@ -364,6 +368,7 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
source.getId(), item.getSkuId(), GaoProductStockRecord.OperationType.ORDER_OUT); source.getId(), item.getSkuId(), GaoProductStockRecord.OperationType.ORDER_OUT);
if (stockRecord != null) { if (stockRecord != null) {
item.setSourceRecordId(stockRecord.getId()); item.setSourceRecordId(stockRecord.getId());
item.setSkuSpecSnapshot(stockRecord.getSkuSpecSnapshot());
} }
} }
productStockService.revokeForFulfillment(order.getStoreId(), reverse.getId(), stockItems, productStockService.revokeForFulfillment(order.getStoreId(), reverse.getId(), stockItems,
@@ -452,16 +457,16 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
TimiException.requiredTrue(order.getStoreId().equals(product.getStoreId()), "履约来源产品不属于当前门店"); TimiException.requiredTrue(order.getStoreId().equals(product.getStoreId()), "履约来源产品不属于当前门店");
BigDecimal baseQuantityPerUnit = requiredBaseQuantityPerUnit(source.getBaseQuantityPerUnitSnapshot()); BigDecimal baseQuantityPerUnit = requiredBaseQuantityPerUnit(source.getBaseQuantityPerUnitSnapshot());
GaoProductSkuUnit unit = snapshotUnit(source.getSkuUnitId(), source.getUnitSnapshot(), baseQuantityPerUnit); GaoProductSkuUnit unit = snapshotUnit(source.getSkuUnitId(), source.getUnitSnapshot(), baseQuantityPerUnit);
return buildSource(order, source.getId(), source.getSkuId(), source.getSkuUnitId(), source.getProductIdSnapshot(), source.getProductNameSnapshot(), source.getSkuCodeSnapshot(), source.getSkuNameSnapshot(), source.getSpecSummarySnapshot(), JsonNode skuSpecSnapshot = source.getSkuSpecSnapshot();
return buildSource(order, source.getId(), source.getSkuId(), source.getSkuUnitId(), source.getProductIdSnapshot(), source.getProductNameSnapshot(), source.getSkuCodeSnapshot(), source.getSkuNameSnapshot(), source.getSpecSummarySnapshot(), skuSpecSnapshot,
source.getUnitSnapshot(), baseQuantityPerUnit, quantity, source.getQuantity(), source.getReservedQuantity(), source.getFulfilledQuantity(), source.getUnitSnapshot(), baseQuantityPerUnit, quantity, source.getQuantity(), source.getReservedQuantity(), source.getFulfilledQuantity(),
product, unit, checkAvailable); product, sku, unit, checkAvailable);
} }
private SourceEntry buildSource(GaoSalesOrder order, String orderItemId, String skuId, String unitId, String productId, private SourceEntry buildSource(GaoSalesOrder order, String orderItemId, String skuId, String unitId, String productId,
String productName, String skuCode, String skuName, String specSummary, String unitName, BigDecimal baseQuantityPerUnit, BigDecimal quantity, BigDecimal totalQuantity, String productName, String skuCode, String skuName, String specSummary, JsonNode skuSpecSnapshot, String unitName, BigDecimal baseQuantityPerUnit, BigDecimal quantity, BigDecimal totalQuantity,
BigDecimal reservedQuantity, BigDecimal fulfilledQuantity, GaoProduct product, GaoProductSkuUnit unit, BigDecimal reservedQuantity, BigDecimal fulfilledQuantity, GaoProduct product, GaoProductSku sku, GaoProductSkuUnit unit,
boolean checkAvailable) { boolean checkAvailable) {
GaoProductSku sku = skuService.get(skuId);
if (!Boolean.TRUE.equals(sku.getAllowDecimalQuantity())) { if (!Boolean.TRUE.equals(sku.getAllowDecimalQuantity())) {
TimiException.requiredTrue(quantity.stripTrailingZeros().scale() <= 0, "该产品不允许小数数量"); TimiException.requiredTrue(quantity.stripTrailingZeros().scale() <= 0, "该产品不允许小数数量");
} }
@@ -471,7 +476,7 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
BigDecimal available = decimal(totalQuantity).subtract(reservedBefore).subtract(fulfilledBefore); BigDecimal available = decimal(totalQuantity).subtract(reservedBefore).subtract(fulfilledBefore);
TimiException.requiredTrue(0 <= available.compareTo(quantity), "来源产品可履约数量不足"); TimiException.requiredTrue(0 <= available.compareTo(quantity), "来源产品可履约数量不足");
} }
return new SourceEntry(orderItemId, skuId, unitId, productId, productName, skuCode, skuName, specSummary, unitName, baseQuantityPerUnit, return new SourceEntry(orderItemId, skuId, unitId, productId, productName, skuCode, skuName, specSummary, skuSpecSnapshot, unitName, baseQuantityPerUnit,
quantity, decimal(totalQuantity), reservedBefore, fulfilledBefore, product, sku, unit); quantity, decimal(totalQuantity), reservedBefore, fulfilledBefore, product, sku, unit);
} }
@@ -488,6 +493,7 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
GaoStockChangeItem item = itemMap.get(source.sku().getId()); GaoStockChangeItem item = itemMap.get(source.sku().getId());
if (item == null) { if (item == null) {
item = GaoStockChangeItem.of(source.product(), source.sku(), baseUnit, baseQuantity, baseQuantity); item = GaoStockChangeItem.of(source.product(), source.sku(), baseUnit, baseQuantity, baseQuantity);
item.setSkuSpecSnapshot(source.skuSpecSnapshot());
itemMap.put(source.sku().getId(), item); itemMap.put(source.sku().getId(), item);
} else { } else {
item.setQuantity(item.getQuantity().add(baseQuantity)); item.setQuantity(item.getQuantity().add(baseQuantity));
@@ -504,6 +510,24 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
return result; return result;
} }
private void applySkuSpecSnapshots(List<GaoStockChangeItem> stockItems, List<GaoOrderFulfillmentItem> fulfillmentItemList) {
if (stockItems == null || stockItems.isEmpty() || fulfillmentItemList == null || fulfillmentItemList.isEmpty()) {
return;
}
Map<String, JsonNode> 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<GaoOrderFulfillmentItemRequest> itemRequests(String fulfillmentId) { private List<GaoOrderFulfillmentItemRequest> itemRequests(String fulfillmentId) {
return copyRequestItems(itemMapper.selectByFulfillmentId(fulfillmentId)); return copyRequestItems(itemMapper.selectByFulfillmentId(fulfillmentId));
} }
@@ -531,6 +555,7 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
result.setSkuCodeSnapshot(source.getSkuCodeSnapshot()); result.setSkuCodeSnapshot(source.getSkuCodeSnapshot());
result.setSkuNameSnapshot(source.getSkuNameSnapshot()); result.setSkuNameSnapshot(source.getSkuNameSnapshot());
result.setSpecSummarySnapshot(source.getSpecSummarySnapshot()); result.setSpecSummarySnapshot(source.getSpecSummarySnapshot());
result.setSkuSpecSnapshot(source.getSkuSpecSnapshot());
result.setUnitSnapshot(source.getUnitSnapshot()); result.setUnitSnapshot(source.getUnitSnapshot());
result.setBaseQuantityPerUnitSnapshot(source.getBaseQuantityPerUnitSnapshot()); result.setBaseQuantityPerUnitSnapshot(source.getBaseQuantityPerUnitSnapshot());
return result; return result;
@@ -644,7 +669,7 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
} }
private record SourceEntry(String sourceOrderItemId, String skuId, String skuUnitId, String productIdSnapshot, private record SourceEntry(String sourceOrderItemId, String skuId, String skuUnitId, String productIdSnapshot,
String productNameSnapshot, String skuCodeSnapshot, String skuNameSnapshot, String specSummarySnapshot, String productNameSnapshot, String skuCodeSnapshot, String skuNameSnapshot, String specSummarySnapshot, JsonNode skuSpecSnapshot,
String unitSnapshot, BigDecimal baseQuantityPerUnitSnapshot, BigDecimal quantity, String unitSnapshot, BigDecimal baseQuantityPerUnitSnapshot, BigDecimal quantity,
BigDecimal totalQuantity, BigDecimal reservedBefore, BigDecimal fulfilledBefore, GaoProduct product, GaoProductSku sku, BigDecimal totalQuantity, BigDecimal reservedBefore, BigDecimal fulfilledBefore, GaoProduct product, GaoProductSku sku,
GaoProductSkuUnit unit) { GaoProductSkuUnit unit) {
@@ -670,6 +695,7 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
result.setSkuCodeSnapshot(skuCodeSnapshot); result.setSkuCodeSnapshot(skuCodeSnapshot);
result.setSkuNameSnapshot(skuNameSnapshot); result.setSkuNameSnapshot(skuNameSnapshot);
result.setSpecSummarySnapshot(specSummarySnapshot); result.setSpecSummarySnapshot(specSummarySnapshot);
result.setSkuSpecSnapshot(skuSpecSnapshot);
result.setUnitSnapshot(unitSnapshot); result.setUnitSnapshot(unitSnapshot);
result.setBaseQuantityPerUnitSnapshot(baseQuantityPerUnitSnapshot); result.setBaseQuantityPerUnitSnapshot(baseQuantityPerUnitSnapshot);
return result; return result;
@@ -5,6 +5,9 @@ import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.gao.entity.GaoProduct; import com.imyeyu.api.modules.gao.entity.GaoProduct;
import com.imyeyu.api.modules.gao.entity.GaoProductCategory; import com.imyeyu.api.modules.gao.entity.GaoProductCategory;
import com.imyeyu.api.modules.gao.entity.GaoProductSpec;
import com.imyeyu.api.modules.gao.entity.GaoProductSpecValue;
import com.imyeyu.api.modules.gao.entity.GaoProductSku;
import com.imyeyu.api.modules.gao.mapper.GaoProductMapper; import com.imyeyu.api.modules.gao.mapper.GaoProductMapper;
import com.imyeyu.api.modules.gao.service.GaoProductCategoryService; import com.imyeyu.api.modules.gao.service.GaoProductCategoryService;
import com.imyeyu.api.modules.gao.service.GaoProductService; import com.imyeyu.api.modules.gao.service.GaoProductService;
@@ -28,6 +31,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -102,6 +106,8 @@ public class GaoProductServiceImplement extends AbstractEntityService<GaoProduct
loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_CREATE", product, () -> { loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_CREATE", product, () -> {
prepare(product, null); prepare(product, null);
super.create(product); super.create(product);
Map<String, String> specValueIdMap = saveSpecs(product, product.getSpecList());
normalizeSkuSpecValueIds(product.getSkuList(), specValueIdMap);
saveSkus(product, product.getSkuList()); saveSkus(product, product.getSkuList());
attachmentService.updateByBizId(Attachment.BizType.GAO_PRODUCT, product.getId(), product.getAttachmentList()); attachmentService.updateByBizId(Attachment.BizType.GAO_PRODUCT, product.getId(), product.getAttachmentList());
return product; return product;
@@ -196,6 +202,48 @@ public class GaoProductServiceImplement extends AbstractEntityService<GaoProduct
} }
} }
private Map<String, String> saveSpecs(GaoProduct product, List<GaoProductSpec> specList) {
Map<String, String> 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<GaoProductSpecValue> 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<GaoProductSku> skuList, Map<String, String> specValueIdMap) {
if (skuList == null || skuList.isEmpty() || specValueIdMap.isEmpty()) {
return;
}
for (GaoProductSku sku : skuList) {
List<String> specValueIdList = sku.getSpecValueIdList();
if (specValueIdList != null) {
sku.setSpecValueIdList(specValueIdList.stream().map(id -> specValueIdMap.getOrDefault(id, id)).toList());
}
}
}
private void fillTransient(List<GaoProduct> list) { private void fillTransient(List<GaoProduct> list) {
if (list == null || list.isEmpty()) { if (list == null || list.isEmpty()) {
return; return;
@@ -1,5 +1,9 @@
package com.imyeyu.api.modules.gao.service.implement; 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.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.common.service.AttachmentService;
@@ -34,6 +38,7 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -120,6 +125,45 @@ public class GaoProductSkuServiceImplement extends AbstractEntityService<GaoProd
return unitMapper.selectBySkuId(skuId); return unitMapper.selectBySkuId(skuId);
} }
@Override
public JsonNode getSkuSpecSnapshot(String skuId) {
TimiException.required(skuId, "not found skuId");
GaoProductSku sku = get(skuId);
TimiException.required(sku, "not found product sku");
return getSkuSpecSnapshot(sku);
}
@Override
public JsonNode getSkuSpecSnapshot(GaoProductSku sku) {
TimiException.required(sku, "not found product sku");
List<GaoProductSpec> specList = specMapper.selectByProductId(sku.getProductId());
if (specList.isEmpty()) {
return JsonNodeFactory.instance.arrayNode();
}
List<String> specIdList = specList.stream().map(GaoProductSpec::getId).toList();
Map<String, List<GaoProductSpecValue>> valueMap = new LinkedHashMap<>();
for (GaoProductSpecValue value : valueMapper.selectBySpecIdList(specIdList)) {
valueMap.computeIfAbsent(value.getSpecId(), key -> new ArrayList<>()).add(value);
}
Set<String> 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) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public void create(GaoProductSku sku) { public void create(GaoProductSku sku) {
@@ -138,4 +138,5 @@ public class GaoProductStockRecordServiceImplement extends AbstractEntityService
record.setOperator(userMap.get(record.getOperatorUserId())); record.setOperator(userMap.get(record.getOperatorUserId()));
} }
} }
} }
@@ -214,6 +214,7 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
TimiException.requiredTrue(hasAttachment(request.getAttachmentList()), "库存操作必须上传照片"); TimiException.requiredTrue(hasAttachment(request.getAttachmentList()), "库存操作必须上传照片");
TimiException.requiredTrue(hasText(request.getRemark()), "库存操作必须填写备注"); TimiException.requiredTrue(hasText(request.getRemark()), "库存操作必须填写备注");
GaoStockChangeItem item = GaoStockChangeItem.of(product, sku, unit, quantity, baseQuantity); GaoStockChangeItem item = GaoStockChangeItem.of(product, sku, unit, quantity, baseQuantity);
item.setSkuSpecSnapshot(skuService.getSkuSpecSnapshot(sku));
List<GaoProductStock> result = executeInternal(storeId, sku.getId(), List.of(item), requestNo, List<GaoProductStock> result = executeInternal(storeId, sku.getId(), List.of(item), requestNo,
userLoginService.getRequireLoginUserId(), operationType, request.getRemark(), request.getAttachmentList()); userLoginService.getRequireLoginUserId(), operationType, request.getRemark(), request.getAttachmentList());
return result.getFirst(); return result.getFirst();
@@ -265,6 +266,7 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
revoke.setSkuCodeSnapshot(source.getSkuCodeSnapshot()); revoke.setSkuCodeSnapshot(source.getSkuCodeSnapshot());
revoke.setSkuNameSnapshot(source.getSkuNameSnapshot()); revoke.setSkuNameSnapshot(source.getSkuNameSnapshot());
revoke.setSpecSummarySnapshot(source.getSpecSummarySnapshot()); revoke.setSpecSummarySnapshot(source.getSpecSummarySnapshot());
revoke.setSkuSpecSnapshot(source.getSkuSpecSnapshot());
revoke.setUnitSnapshot(source.getUnitSnapshot()); revoke.setUnitSnapshot(source.getUnitSnapshot());
revoke.setBaseQuantityPerUnitSnapshot(source.getBaseQuantityPerUnitSnapshot()); revoke.setBaseQuantityPerUnitSnapshot(source.getBaseQuantityPerUnitSnapshot());
revoke.setQuantity(source.getQuantity()); revoke.setQuantity(source.getQuantity());
@@ -341,6 +343,7 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
record.setSkuCodeSnapshot(item.getSkuCodeSnapshot()); record.setSkuCodeSnapshot(item.getSkuCodeSnapshot());
record.setSkuNameSnapshot(item.getSkuNameSnapshot()); record.setSkuNameSnapshot(item.getSkuNameSnapshot());
record.setSpecSummarySnapshot(item.getSpecSummarySnapshot()); record.setSpecSummarySnapshot(item.getSpecSummarySnapshot());
record.setSkuSpecSnapshot(item.getSkuSpecSnapshot());
record.setUnitSnapshot(item.getUnitSnapshot()); record.setUnitSnapshot(item.getUnitSnapshot());
record.setBaseQuantityPerUnitSnapshot(item.getBaseQuantityPerUnitSnapshot()); record.setBaseQuantityPerUnitSnapshot(item.getBaseQuantityPerUnitSnapshot());
record.setQuantity(item.getQuantity()); record.setQuantity(item.getQuantity());
@@ -379,6 +379,7 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
item.setSkuCodeSnapshot(sku.getCode()); item.setSkuCodeSnapshot(sku.getCode());
item.setSkuNameSnapshot(sku.getName()); item.setSkuNameSnapshot(sku.getName());
item.setSpecSummarySnapshot(sku.getSpecSummary()); item.setSpecSummarySnapshot(sku.getSpecSummary());
item.setSkuSpecSnapshot(skuService.getSkuSpecSnapshot(sku));
item.setUnitSnapshot(unit.getName()); item.setUnitSnapshot(unit.getName());
item.setBaseQuantityPerUnitSnapshot(unit.getBaseQuantityPerUnit()); item.setBaseQuantityPerUnitSnapshot(unit.getBaseQuantityPerUnit());
item.setQuantity(quantity); item.setQuantity(quantity);
@@ -1,5 +1,6 @@
package com.imyeyu.api.modules.gao.vo; package com.imyeyu.api.modules.gao.vo;
import com.fasterxml.jackson.databind.JsonNode;
import com.imyeyu.api.modules.gao.entity.GaoProduct; import com.imyeyu.api.modules.gao.entity.GaoProduct;
import com.imyeyu.api.modules.gao.entity.GaoProductSku; import com.imyeyu.api.modules.gao.entity.GaoProductSku;
import com.imyeyu.api.modules.gao.entity.GaoProductSkuUnit; import com.imyeyu.api.modules.gao.entity.GaoProductSkuUnit;
@@ -35,6 +36,9 @@ public class GaoStockChangeItem {
/// 规格摘要快照 /// 规格摘要快照
private String specSummarySnapshot; private String specSummarySnapshot;
/// SKU 规格结构快照,数组元素包含 specId、specName、specValueId、specValueName
private JsonNode skuSpecSnapshot;
/// 单位名称快照 /// 单位名称快照
private String unitSnapshot; private String unitSnapshot;
@@ -0,0 +1,5 @@
ALTER TABLE `gao_product_stock_record`
ADD COLUMN `sku_spec_snapshot` JSON NOT NULL COMMENT 'SKU 规格结构快照' AFTER `spec_summary_snapshot`;
ALTER TABLE `gao_order_fulfillment_item`
ADD COLUMN `sku_spec_snapshot` JSON NOT NULL COMMENT 'SKU 规格结构快照' AFTER `spec_summary_snapshot`;
@@ -0,0 +1,2 @@
ALTER TABLE `gao_sales_order_item`
ADD COLUMN `sku_spec_snapshot` JSON NOT NULL COMMENT 'SKU 规格结构快照' AFTER `spec_summary_snapshot`;