Compare commits

...
49 Commits
Author SHA1 Message Date
timi 0b98dcdfe9 Merge pull request 'v1.0.34' (#46) from dev into master
Reviewed-on: #46
2026-09-07 15:11:06 +00:00
Timi 1e62576d36 v1.0.34
CI / build-deploy (pull_request) Successful in 25s
CI / notify-on-failure (pull_request) Skipped
2026-09-07 23:10:27 +08:00
timi b373cbbe46 Merge pull request 'v1.0.33' (#45) from dev into master
Reviewed-on: #45
2026-09-07 09:40:39 +00:00
Timi bb5abcb48e v1.0.33
CI / build-deploy (pull_request) Successful in 24s
CI / notify-on-failure (pull_request) Skipped
2026-09-07 17:40:06 +08:00
timi acd97c7b9b Merge pull request 'v1.0.32' (#44) from dev into master
Reviewed-on: #44
2026-09-07 08:42:29 +00:00
Timi 4a549e77cb v1.0.32
CI / build-deploy (pull_request) Successful in 26s
CI / notify-on-failure (pull_request) Skipped
2026-09-07 16:41:53 +08:00
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
33 changed files with 405 additions and 18 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.34</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>TimiServerAPI</name> <name>TimiServerAPI</name>
<description>imyeyu.com API</description> <description>imyeyu.com API</description>
@@ -0,0 +1,66 @@
package com.imyeyu.api.modules.gao.controller;
import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission;
import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
import com.imyeyu.api.modules.gao.entity.GaoCustomerConsumptionRecord;
import com.imyeyu.api.modules.gao.service.GaoCustomerConsumptionRecordService;
import com.imyeyu.api.modules.gao.service.GaoCustomerService;
import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
import com.imyeyu.api.modules.gao.vo.GaoCustomerConsumptionRecordPage;
import com.imyeyu.api.modules.gao.vo.GaoCustomerConsumptionRecordPageResult;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.annotation.RequestRateLimit;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
/// GAO 客户消费事实接口
///
/// @author Codex
/// @since 2026-09-07
@RestController
@RequiredArgsConstructor
@RequestMapping("/gao/customer/consumption-record")
public class GaoCustomerConsumptionRecordController {
private final AttachmentService attachmentService;
private final GaoCustomerService customerService;
private final GaoStoreScopeService storeScopeService;
private final GaoCustomerConsumptionRecordService service;
@AOPLog
@RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.SALES_ORDER_READ)
@PostMapping("/list")
public GaoCustomerConsumptionRecordPageResult list(@RequestBody GaoCustomerConsumptionRecordPage page) {
TimiException.required(page, "not found consumption record page");
page.setStoreId(storeScopeService.resolveStoreId(page.getStoreId()));
GaoCustomerConsumptionRecordPageResult result = service.pageByQuery(page);
List<String> customerIdList = result.getList()
.stream()
.map(GaoCustomerConsumptionRecord::getCustomerId)
.filter(customerId -> customerId != null && !customerId.isBlank())
.distinct()
.toList();
var customerMap = customerService.mapByIdList(customerIdList);
Map<String, List<Attachment>> attachmentMap = attachmentService.mapByBizIdList(Attachment.BizType.GAO_CUSTOMER, customerIdList);
for (GaoCustomerConsumptionRecord record : result.getList()) {
var customer = customerMap.get(record.getCustomerId());
if (customer == null) {
continue;
}
customer.setAttachmentList(attachmentMap.getOrDefault(customer.getId(), List.of()));
record.setCustomer(customer);
}
return result;
}
}
@@ -12,6 +12,7 @@ import com.imyeyu.api.modules.gao.vo.GaoFundRecordPage;
import com.imyeyu.api.modules.gao.vo.GaoFundRevokeRequest; import com.imyeyu.api.modules.gao.vo.GaoFundRevokeRequest;
import com.imyeyu.api.modules.gao.vo.GaoOrderPaymentRevokeRequest; import com.imyeyu.api.modules.gao.vo.GaoOrderPaymentRevokeRequest;
import com.imyeyu.api.modules.user.entity.User; import com.imyeyu.api.modules.user.entity.User;
import com.imyeyu.api.modules.user.service.UserLoginService;
import com.imyeyu.api.modules.user.service.UserService; import com.imyeyu.api.modules.user.service.UserService;
import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.AOPLog;
@@ -42,6 +43,7 @@ public class GaoFundRecordController {
private final GaoStoreScopeService storeScopeService; private final GaoStoreScopeService storeScopeService;
private final AttachmentService attachmentService; private final AttachmentService attachmentService;
private final UserService userService; private final UserService userService;
private final UserLoginService userLoginService;
@AOPLog @AOPLog
@RequestRateLimit @RequestRateLimit
@@ -66,6 +68,33 @@ public class GaoFundRecordController {
return result; return result;
} }
@AOPLog
@RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.FUND_RECEIVE_CREATE)
@PostMapping("/my-receive/list")
public PageResult<GaoFundRecord> listMyReceive(@RequestBody GaoFundRecordPage page) {
TimiException.required(page, "not found page");
page.setStoreId(storeScopeService.resolveStoreId(page.getStoreId()));
page.setOperatorUserId(userLoginService.getRequireLoginUserId());
page.setOperationType(GaoFundRecord.OperationType.RECEIVE);
PageResult<GaoFundRecord> result = service.pageByQuery(page);
fillTransient(result.getList());
return result;
}
@AOPLog
@RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.FUND_RECEIVE_CREATE)
@PostMapping("/my-receive/detail")
public GaoFundRecord detailMyReceive(@RequestParam String id) {
GaoFundRecord result = service.detail(id);
TimiException.requiredTrue(result.getOperationType() == GaoFundRecord.OperationType.RECEIVE, "not found fund receive record");
TimiException.requiredTrue(userLoginService.getRequireLoginUserId().equals(result.getOperatorUserId()), "只能读取自己提交的收款");
storeScopeService.checkStoreId(result.getStoreId());
fillTransient(List.of(result));
return result;
}
@AOPLog @AOPLog
@RequestRateLimit @RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.FUND_REVOKE) @RequireGaoPermission(GaoPermissionCode.FUND_REVOKE)
@@ -2,6 +2,7 @@ package com.imyeyu.api.modules.gao.controller;
import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission;
import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
import com.imyeyu.api.bean.PermissionMatchMode;
import com.imyeyu.api.modules.gao.entity.GaoFundRecord; import com.imyeyu.api.modules.gao.entity.GaoFundRecord;
import com.imyeyu.api.modules.gao.entity.GaoSalesOrder; import com.imyeyu.api.modules.gao.entity.GaoSalesOrder;
import com.imyeyu.api.modules.gao.service.GaoOrderPaymentService; import com.imyeyu.api.modules.gao.service.GaoOrderPaymentService;
@@ -112,7 +113,7 @@ public class GaoSalesOrderController {
@AOPLog @AOPLog
@RequestRateLimit @RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.FUND_RECEIVE_CREATE) @RequireGaoPermission(value = {GaoPermissionCode.FUND_RECEIVE_CREATE, GaoPermissionCode.FUND_RECORD_READ}, mode = PermissionMatchMode.ANY)
@PostMapping("/payment/cancel") @PostMapping("/payment/cancel")
public GaoFundRecord cancelPayment(@RequestBody GaoOrderPaymentActionRequest request) { public GaoFundRecord cancelPayment(@RequestBody GaoOrderPaymentActionRequest request) {
return paymentService.cancel(request); return paymentService.cancel(request);
@@ -1,6 +1,7 @@
package com.imyeyu.api.modules.gao.entity; package com.imyeyu.api.modules.gao.entity;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
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;
@@ -41,4 +42,8 @@ public class GaoCustomerConsumptionRecord extends UUIDEntity {
private Long revokedAt; private Long revokedAt;
private String revokeReason; private String revokeReason;
private JsonNode sourcePayload; private JsonNode sourcePayload;
/// 客户
@Transient
private GaoCustomer customer;
} }
@@ -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,6 +1,7 @@
package com.imyeyu.api.modules.gao.mapper; package com.imyeyu.api.modules.gao.mapper;
import com.imyeyu.api.modules.gao.entity.GaoCustomerConsumptionRecord; import com.imyeyu.api.modules.gao.entity.GaoCustomerConsumptionRecord;
import com.imyeyu.api.modules.gao.vo.GaoCustomerConsumptionRecordPage;
import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
@@ -16,5 +17,11 @@ public interface GaoCustomerConsumptionRecordMapper extends BaseMapper<GaoCustom
List<GaoCustomerConsumptionRecord> selectActiveByWindow(@Param("storeId") String storeId, @Param("customerId") String customerId, @Param("beginAt") long beginAt, @Param("endAt") long endAt); List<GaoCustomerConsumptionRecord> selectActiveByWindow(@Param("storeId") String storeId, @Param("customerId") String customerId, @Param("beginAt") long beginAt, @Param("endAt") long endAt);
long countByQuery(GaoCustomerConsumptionRecordPage page);
Long sumAmountByPage(GaoCustomerConsumptionRecordPage page);
List<GaoCustomerConsumptionRecord> selectByQuery(GaoCustomerConsumptionRecordPage page);
int revokeBySource(@Param("sourceType") GaoCustomerConsumptionRecord.SourceType sourceType, @Param("sourceId") String sourceId, @Param("revokedAt") long revokedAt, @Param("revokeReason") String revokeReason); int revokeBySource(@Param("sourceType") GaoCustomerConsumptionRecord.SourceType sourceType, @Param("sourceId") String sourceId, @Param("revokedAt") long revokedAt, @Param("revokeReason") String revokeReason);
} }
@@ -2,6 +2,7 @@ package com.imyeyu.api.modules.gao.mapper;
import com.imyeyu.api.modules.gao.entity.GaoPackageConfigItem; import com.imyeyu.api.modules.gao.entity.GaoPackageConfigItem;
import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.BaseMapper;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import java.util.Collection; import java.util.Collection;
@@ -13,6 +14,12 @@ import java.util.List;
/// @since 2026-08-31 /// @since 2026-08-31
public interface GaoPackageConfigItemMapper extends BaseMapper<GaoPackageConfigItem, String> { public interface GaoPackageConfigItemMapper extends BaseMapper<GaoPackageConfigItem, String> {
/// 物理删除套餐配置的全部权益模板项
///
/// @param packageConfigId 套餐配置 ID
@Delete("DELETE FROM `gao_package_config_item` WHERE `package_config_id` = #{packageConfigId}")
void destroyByPackageConfigId(@Param("packageConfigId") String packageConfigId);
/// 按套餐配置 ID 查询产品 /// 按套餐配置 ID 查询产品
List<GaoPackageConfigItem> selectByPackageConfigId(@Param("packageConfigId") String packageConfigId); List<GaoPackageConfigItem> selectByPackageConfigId(@Param("packageConfigId") String packageConfigId);
@@ -1,6 +1,8 @@
package com.imyeyu.api.modules.gao.service; package com.imyeyu.api.modules.gao.service;
import com.imyeyu.api.modules.gao.entity.GaoCustomerConsumptionRecord; import com.imyeyu.api.modules.gao.entity.GaoCustomerConsumptionRecord;
import com.imyeyu.api.modules.gao.vo.GaoCustomerConsumptionRecordPage;
import com.imyeyu.api.modules.gao.vo.GaoCustomerConsumptionRecordPageResult;
import com.imyeyu.spring.service.BaseService; import com.imyeyu.spring.service.BaseService;
import java.util.List; import java.util.List;
@@ -11,6 +13,9 @@ import java.util.List;
/// @since 2026-09-04 /// @since 2026-09-04
public interface GaoCustomerConsumptionRecordService extends BaseService<GaoCustomerConsumptionRecord, String> { public interface GaoCustomerConsumptionRecordService extends BaseService<GaoCustomerConsumptionRecord, String> {
/// 按条件分页查询消费事实
GaoCustomerConsumptionRecordPageResult pageByQuery(GaoCustomerConsumptionRecordPage page);
/// 写入销售订单消费事实 /// 写入销售订单消费事实
/// ///
/// 幂等,重复执行返回已有事实 /// 幂等,重复执行返回已有事实
@@ -22,7 +22,7 @@ public interface GaoOrderPaymentService {
/// 拒绝收款 /// 拒绝收款
GaoFundRecord reject(GaoOrderPaymentActionRequest request); GaoFundRecord reject(GaoOrderPaymentActionRequest request);
/// 撤销自己提交的收款 /// 撤销待核销收款
GaoFundRecord cancel(GaoOrderPaymentActionRequest request); GaoFundRecord cancel(GaoOrderPaymentActionRequest request);
/// 登记退款 /// 登记退款
@@ -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);
} }
@@ -1,10 +1,14 @@
package com.imyeyu.api.modules.gao.service.implement; package com.imyeyu.api.modules.gao.service.implement;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.gao.entity.GaoCustomerConsumptionRecord; import com.imyeyu.api.modules.gao.entity.GaoCustomerConsumptionRecord;
import com.imyeyu.api.modules.gao.entity.GaoSalesOrder; import com.imyeyu.api.modules.gao.entity.GaoSalesOrder;
import com.imyeyu.api.modules.gao.mapper.GaoCustomerConsumptionRecordMapper; import com.imyeyu.api.modules.gao.mapper.GaoCustomerConsumptionRecordMapper;
import com.imyeyu.api.modules.gao.service.GaoCustomerConsumptionRecordService; import com.imyeyu.api.modules.gao.service.GaoCustomerConsumptionRecordService;
import com.imyeyu.api.modules.gao.service.GaoCustomerService;
import com.imyeyu.api.modules.gao.vo.GaoCustomerConsumptionRecordPage;
import com.imyeyu.api.modules.gao.vo.GaoCustomerConsumptionRecordPageResult;
import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService; import com.imyeyu.spring.service.AbstractEntityService;
@@ -24,12 +28,24 @@ import java.util.List;
public class GaoCustomerConsumptionRecordServiceImplement extends AbstractEntityService<GaoCustomerConsumptionRecord, String> implements GaoCustomerConsumptionRecordService { public class GaoCustomerConsumptionRecordServiceImplement extends AbstractEntityService<GaoCustomerConsumptionRecord, String> implements GaoCustomerConsumptionRecordService {
private final GaoCustomerConsumptionRecordMapper mapper; private final GaoCustomerConsumptionRecordMapper mapper;
private final GaoCustomerService customerService;
private final AttachmentService attachmentService;
@Override @Override
protected BaseMapper<GaoCustomerConsumptionRecord, String> mapper() { protected BaseMapper<GaoCustomerConsumptionRecord, String> mapper() {
return mapper; return mapper;
} }
@Override
public GaoCustomerConsumptionRecordPageResult pageByQuery(GaoCustomerConsumptionRecordPage page) {
TimiException.required(page, "not found consumption record page");
GaoCustomerConsumptionRecordPageResult result = new GaoCustomerConsumptionRecordPageResult();
result.setTotal(mapper.countByQuery(page));
result.setAmountTotal(mapper.sumAmountByPage(page));
result.setList(mapper.selectByQuery(page));
return result;
}
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public GaoCustomerConsumptionRecord createSalesOrder(String salesOrderId, String storeId, String customerId, Long amount, Long effectiveAt, GaoSalesOrder.OrderType orderType) { public GaoCustomerConsumptionRecord createSalesOrder(String salesOrderId, String storeId, String customerId, Long amount, Long effectiveAt, GaoSalesOrder.OrderType orderType) {
@@ -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;
@@ -1,10 +1,12 @@
package com.imyeyu.api.modules.gao.service.implement; package com.imyeyu.api.modules.gao.service.implement;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.bean.ModuleCode;
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;
import com.imyeyu.api.modules.gao.entity.GaoFundRecord; import com.imyeyu.api.modules.gao.entity.GaoFundRecord;
import com.imyeyu.api.modules.gao.entity.GaoSalesOrder; import com.imyeyu.api.modules.gao.entity.GaoSalesOrder;
import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
import com.imyeyu.api.modules.gao.service.GaoFundRecordService; import com.imyeyu.api.modules.gao.service.GaoFundRecordService;
import com.imyeyu.api.modules.gao.service.GaoOrderPaymentService; import com.imyeyu.api.modules.gao.service.GaoOrderPaymentService;
import com.imyeyu.api.modules.gao.service.GaoPointTriggerService; import com.imyeyu.api.modules.gao.service.GaoPointTriggerService;
@@ -17,6 +19,7 @@ import com.imyeyu.api.modules.gao.vo.GaoOrderPaymentRevokeRequest;
import com.imyeyu.api.modules.system.entity.Logger; import com.imyeyu.api.modules.system.entity.Logger;
import com.imyeyu.api.modules.system.service.LoggerService; import com.imyeyu.api.modules.system.service.LoggerService;
import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.api.modules.user.service.UserLoginService;
import com.imyeyu.api.modules.user.service.PermissionChecker;
import com.imyeyu.java.TimiJava; import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.service.AbstractEntityService; import com.imyeyu.spring.service.AbstractEntityService;
@@ -40,6 +43,7 @@ public class GaoOrderPaymentServiceImplement implements GaoOrderPaymentService {
private final GaoPointTriggerService pointTriggerService; private final GaoPointTriggerService pointTriggerService;
private final AttachmentService attachmentService; private final AttachmentService attachmentService;
private final UserLoginService userLoginService; private final UserLoginService userLoginService;
private final PermissionChecker permissionChecker;
private final LoggerService loggerService; private final LoggerService loggerService;
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@@ -110,7 +114,8 @@ public class GaoOrderPaymentServiceImplement implements GaoOrderPaymentService {
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public GaoFundRecord cancel(GaoOrderPaymentActionRequest request) { public GaoFundRecord cancel(GaoOrderPaymentActionRequest request) {
return releasePending(request, GaoFundRecord.Status.CANCELLED, true); boolean onlyOperator = !permissionChecker.hasAny(ModuleCode.GAO, GaoPermissionCode.FUND_RECORD_READ.getValue());
return releasePending(request, GaoFundRecord.Status.CANCELLED, onlyOperator);
} }
private GaoFundRecord releasePending(GaoOrderPaymentActionRequest request, GaoFundRecord.Status targetStatus, boolean onlyOperator) { private GaoFundRecord releasePending(GaoOrderPaymentActionRequest request, GaoFundRecord.Status targetStatus, boolean onlyOperator) {
@@ -132,15 +132,13 @@ public class GaoPackageConfigServiceImplement extends AbstractEntityService<GaoP
dbConfig.setEndAt(request.getEndAt()); dbConfig.setEndAt(request.getEndAt());
dbConfig.setSort(request.getSort()); dbConfig.setSort(request.getSort());
prepareConfig(dbConfig, dbConfig.getId()); prepareConfig(dbConfig, dbConfig.getId());
TimiException.required(request.getItemList(), "套餐至少配置一个产品");
verifyItems(dbConfig.getStoreId(), request.getItemList());
dbConfig.setUpdatedBy(userLoginService.getRequireLoginUserId()); dbConfig.setUpdatedBy(userLoginService.getRequireLoginUserId());
super.update(dbConfig); super.update(dbConfig);
if (request.getItemList() != null) { // 权益项仅是后续购买使用的套餐模板,历史订单已保存独立的订单明细快照
verifyItems(dbConfig.getStoreId(), request.getItemList()); itemMapper.destroyByPackageConfigId(dbConfig.getId());
GaoPackageConfigItem example = new GaoPackageConfigItem(); saveItems(dbConfig.getId(), request.getItemList());
example.setPackageConfigId(dbConfig.getId());
itemMapper.deleteAllByExample(example);
saveItems(dbConfig.getId(), request.getItemList());
}
} }
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@@ -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);
@@ -0,0 +1,33 @@
package com.imyeyu.api.modules.gao.vo;
import com.imyeyu.api.modules.gao.entity.GaoCustomerConsumptionRecord;
import com.imyeyu.java.bean.BasePage;
import lombok.Data;
import lombok.EqualsAndHashCode;
/// GAO 客户消费事实分页请求
///
/// @author Codex
/// @since 2026-09-07
@Data
@EqualsAndHashCode(callSuper = true)
public class GaoCustomerConsumptionRecordPage extends BasePage {
/// 门店 ID
private String storeId;
/// 客户 ID
private String customerId;
/// 消费来源
private GaoCustomerConsumptionRecord.SourceType sourceType;
/// 消费状态
private GaoCustomerConsumptionRecord.Status status;
/// 起始生效时间
private Long beginAt;
/// 结束生效时间
private Long endAt;
}
@@ -0,0 +1,18 @@
package com.imyeyu.api.modules.gao.vo;
import com.imyeyu.api.modules.gao.entity.GaoCustomerConsumptionRecord;
import com.imyeyu.spring.bean.PageResult;
import lombok.Data;
import lombok.EqualsAndHashCode;
/// GAO 客户消费事实分页查询结果
///
/// @author Codex
/// @since 2026-09-07
@Data
@EqualsAndHashCode(callSuper = true)
public class GaoCustomerConsumptionRecordPageResult extends PageResult<GaoCustomerConsumptionRecord> {
/// 当前筛选条件下的消费金额合计,单位分
private Long amountTotal;
}
@@ -39,4 +39,7 @@ public class GaoFundRecordPage extends BasePage {
/// 操作结束时间 /// 操作结束时间
private Long endAt; private Long endAt;
/// 操作用户 ID,仅限服务端设置的数据范围条件
private String operatorUserId;
} }
@@ -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`;
@@ -12,6 +12,26 @@
AND `effective_at` &gt;= #{beginAt} AND `effective_at` &lt;= #{endAt} AND `deleted_at` IS NULL AND `effective_at` &gt;= #{beginAt} AND `effective_at` &lt;= #{endAt} AND `deleted_at` IS NULL
ORDER BY `effective_at` ASC, `id` ASC ORDER BY `effective_at` ASC, `id` ASC
</select> </select>
<sql id="pageWhere">
r.`deleted_at` IS NULL
<if test="storeId != null and storeId != ''">AND r.`store_id` = #{storeId}</if>
<if test="customerId != null and customerId != ''">AND r.`customer_id` = #{customerId}</if>
<if test="sourceType != null">AND r.`source_type` = #{sourceType}</if>
<if test="status != null">AND r.`status` = #{status}</if>
<if test="beginAt != null">AND r.`effective_at` &gt;= #{beginAt}</if>
<if test="endAt != null">AND r.`effective_at` &lt;= #{endAt}</if>
</sql>
<select id="countByQuery" resultType="long">
SELECT COUNT(1) FROM `gao_customer_consumption_record` r WHERE <include refid="pageWhere"/>
</select>
<select id="sumAmountByPage" resultType="long">
SELECT COALESCE(SUM(r.`amount`), 0) FROM `gao_customer_consumption_record` r WHERE <include refid="pageWhere"/>
</select>
<select id="selectByQuery" resultType="com.imyeyu.api.modules.gao.entity.GaoCustomerConsumptionRecord">
SELECT r.* FROM `gao_customer_consumption_record` r WHERE <include refid="pageWhere"/>
ORDER BY r.`effective_at` DESC, r.`created_at` DESC
LIMIT #{offset}, #{limit}
</select>
<update id="revokeBySource"> <update id="revokeBySource">
UPDATE `gao_customer_consumption_record` UPDATE `gao_customer_consumption_record`
SET `status` = 'REVOKED', `revoked_at` = #{revokedAt}, `revoke_reason` = #{revokeReason}, `updated_at` = FLOOR(UNIX_TIMESTAMP(NOW(3)) * 1000) SET `status` = 'REVOKED', `revoked_at` = #{revokedAt}, `revoke_reason` = #{revokeReason}, `updated_at` = FLOOR(UNIX_TIMESTAMP(NOW(3)) * 1000)
@@ -52,6 +52,7 @@
<if test="status != null">AND f.`status` = #{status}</if> <if test="status != null">AND f.`status` = #{status}</if>
<if test="beginAt != null">AND f.`occurred_at` &gt;= #{beginAt}</if> <if test="beginAt != null">AND f.`occurred_at` &gt;= #{beginAt}</if>
<if test="endAt != null">AND f.`occurred_at` &lt;= #{endAt}</if> <if test="endAt != null">AND f.`occurred_at` &lt;= #{endAt}</if>
<if test="operatorUserId != null and operatorUserId != ''">AND f.`operator_user_id` = #{operatorUserId}</if>
</sql> </sql>
<select id="countByQuery" resultType="long"> <select id="countByQuery" resultType="long">
SELECT COUNT(1) FROM `gao_fund_record` f WHERE <include refid="queryWhere"/> SELECT COUNT(1) FROM `gao_fund_record` f WHERE <include refid="queryWhere"/>