diff --git a/pom.xml b/pom.xml
index d7cfc82..d4e9dd9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
com.imyeyu.timiserverapi
TimiServerAPI
- 1.0.30
+ 1.0.31
jar
TimiServerAPI
imyeyu.com API
@@ -180,6 +180,11 @@
+
+ org.apache.groovy
+ groovy
+ 4.0.29
+
com.imyeyu.spring
timi-spring
diff --git a/src/main/java/com/imyeyu/api/modules/common/bean/GroovyScriptExecutionResult.java b/src/main/java/com/imyeyu/api/modules/common/bean/GroovyScriptExecutionResult.java
new file mode 100644
index 0000000..22de318
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/bean/GroovyScriptExecutionResult.java
@@ -0,0 +1,9 @@
+package com.imyeyu.api.modules.common.bean;
+
+/// Groovy 脚本执行结果
+///
+/// @param 脚本返回值类型
+/// @author Codex
+/// @since 2026-09-04
+public record GroovyScriptExecutionResult(String scriptId, String versionId, Integer version, String checksum, T result) {
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/Attachment.java b/src/main/java/com/imyeyu/api/modules/common/entity/Attachment.java
index 79489ed..deabf72 100644
--- a/src/main/java/com/imyeyu/api/modules/common/entity/Attachment.java
+++ b/src/main/java/com/imyeyu/api/modules/common/entity/Attachment.java
@@ -38,12 +38,20 @@ public class Attachment extends UUIDEntity {
GAO_CUSTOMER,
+ GAO_PRODUCT,
+
+ GAO_PRODUCT_SKU,
+
GAO_EVENT_RECORD,
- GAO_PACKAGE_DELIVERY,
-
GAO_FUND_RECORD,
+ GAO_STOCK_RECORD,
+
+ GAO_ORDER_FULFILLMENT,
+
+ GAO_SALES_ORDER,
+
TEMP_FILE,
MIRROR
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/GroovyScript.java b/src/main/java/com/imyeyu/api/modules/common/entity/GroovyScript.java
new file mode 100644
index 0000000..a6c3d02
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/entity/GroovyScript.java
@@ -0,0 +1,42 @@
+package com.imyeyu.api.modules.common.entity;
+
+import com.imyeyu.spring.entity.UUIDEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/// Groovy 脚本
+///
+/// @author Codex
+/// @since 2026-09-04
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class GroovyScript extends UUIDEntity {
+
+ ///
+ ///
+ /// @author 夜雨
+ /// @since 2026/9/4 14:54
+ public enum BizType {
+
+ /// GAO 积分规则
+ GAO_POINT_RULE
+ }
+
+ /// 业务归属
+ private BizType bizType;
+
+ /// 业务内唯一代码
+ private String code;
+
+ /// 名称
+ private String name;
+
+ /// 说明
+ private String description;
+
+ /// true 为允许执行已发布版本
+ private Boolean enabled;
+
+ /// 当前发布版本 ID
+ private String publishedVersionId;
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/GroovyScriptVersion.java b/src/main/java/com/imyeyu/api/modules/common/entity/GroovyScriptVersion.java
new file mode 100644
index 0000000..f8c57f1
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/entity/GroovyScriptVersion.java
@@ -0,0 +1,26 @@
+package com.imyeyu.api.modules.common.entity;
+
+import com.imyeyu.spring.entity.UUIDEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/// Groovy 脚本版本
+///
+/// @author Codex
+/// @since 2026-09-04
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class GroovyScriptVersion extends UUIDEntity {
+
+ /// 脚本 ID
+ private String scriptId;
+
+ /// 版本号
+ private Integer version;
+
+ /// 脚本内容
+ private String content;
+
+ /// 脚本内容 SHA-256
+ private String checksum;
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/GroovyScriptMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/GroovyScriptMapper.java
new file mode 100644
index 0000000..b7e16f6
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/mapper/GroovyScriptMapper.java
@@ -0,0 +1,11 @@
+package com.imyeyu.api.modules.common.mapper;
+
+import com.imyeyu.api.modules.common.entity.GroovyScript;
+import com.imyeyu.spring.mapper.BaseMapper;
+
+/// Groovy 脚本 Mapper
+///
+/// @author Codex
+/// @since 2026-09-04
+public interface GroovyScriptMapper extends BaseMapper {
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/GroovyScriptVersionMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/GroovyScriptVersionMapper.java
new file mode 100644
index 0000000..f563e21
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/mapper/GroovyScriptVersionMapper.java
@@ -0,0 +1,11 @@
+package com.imyeyu.api.modules.common.mapper;
+
+import com.imyeyu.api.modules.common.entity.GroovyScriptVersion;
+import com.imyeyu.spring.mapper.BaseMapper;
+
+/// Groovy 脚本版本 Mapper
+///
+/// @author Codex
+/// @since 2026-09-04
+public interface GroovyScriptVersionMapper extends BaseMapper {
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/NotifyMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/NotifyMapper.java
index 10ff654..5636f9f 100644
--- a/src/main/java/com/imyeyu/api/modules/common/mapper/NotifyMapper.java
+++ b/src/main/java/com/imyeyu/api/modules/common/mapper/NotifyMapper.java
@@ -3,6 +3,7 @@ package com.imyeyu.api.modules.common.mapper;
import com.imyeyu.api.modules.common.entity.Notify;
import com.imyeyu.api.modules.common.vo.NotifyManagementItem;
import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
import com.imyeyu.spring.mapper.BaseMapper;
import java.util.List;
@@ -15,6 +16,10 @@ import java.util.List;
*/
public interface NotifyMapper extends BaseMapper {
+ /// 查询业务提醒是否已创建
+ @Select("SELECT EXISTS (SELECT 1 FROM `notify` WHERE `biz_type` = #{bizType} AND `biz_reminded_type` = #{bizRemindedType} AND `biz_reminded_id` = #{bizRemindedId} AND `deleted_at` IS NULL)")
+ boolean existsByBizReminded(@Param("bizType") String bizType, @Param("bizRemindedType") String bizRemindedType, @Param("bizRemindedId") String bizRemindedId);
+
/// 分页查询管理端通知
///
/// @param userId 管理员用户 ID
diff --git a/src/main/java/com/imyeyu/api/modules/common/service/ArticleService.java b/src/main/java/com/imyeyu/api/modules/common/service/ArticleService.java
index 8590a20..270deb6 100644
--- a/src/main/java/com/imyeyu/api/modules/common/service/ArticleService.java
+++ b/src/main/java/com/imyeyu/api/modules/common/service/ArticleService.java
@@ -3,6 +3,8 @@ package com.imyeyu.api.modules.common.service;
import com.imyeyu.api.modules.common.entity.Article;
import com.imyeyu.spring.service.BaseService;
+import java.util.List;
+
/**
* 文章服务
*
@@ -10,4 +12,10 @@ import com.imyeyu.spring.service.BaseService;
* @since 2021-02-23 21:33
*/
public interface ArticleService extends BaseService {
+
+ /// 按示例查询文章
+ ///
+ /// @param example 查询示例
+ /// @return 文章列表
+ List listByExample(Article example);
}
diff --git a/src/main/java/com/imyeyu/api/modules/common/service/GroovyScriptService.java b/src/main/java/com/imyeyu/api/modules/common/service/GroovyScriptService.java
new file mode 100644
index 0000000..eee5ef4
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/service/GroovyScriptService.java
@@ -0,0 +1,120 @@
+package com.imyeyu.api.modules.common.service;
+
+import com.imyeyu.api.modules.common.entity.GroovyScript;
+import com.imyeyu.api.modules.common.entity.GroovyScriptVersion;
+import com.imyeyu.api.modules.common.bean.GroovyScriptExecutionResult;
+import com.imyeyu.spring.service.BaseService;
+
+import java.util.List;
+import java.util.Map;
+
+/// Groovy 脚本服务
+///
+/// 脚本仅限可信管理员维护,不能作为不可信用户代码的执行环境
+///
+/// @author Codex
+/// @since 2026-09-04
+public interface GroovyScriptService extends BaseService {
+
+ /// 按业务归属和代码查询脚本
+ ///
+ /// @param bizType 业务归属
+ /// @param code 业务内唯一代码
+ /// @return 脚本,不存在时返回 null
+ GroovyScript getByCode(GroovyScript.BizType bizType, String code);
+
+ /// 创建脚本元数据
+ ///
+ /// 新建脚本默认停用,且没有已发布版本
+ ///
+ /// @param script 脚本元数据
+ void create(GroovyScript script);
+
+ /// 更新脚本元数据
+ ///
+ /// 业务归属和代码是脚本稳定标识,创建后不可变更
+ ///
+ /// @param script 脚本元数据
+ void update(GroovyScript script);
+
+ /// 设置脚本启用状态
+ ///
+ /// @param id 脚本 ID
+ /// @param enabled true 为允许执行已发布版本
+ void setEnabled(String id, boolean enabled);
+
+ /// 创建不可变的脚本版本
+ ///
+ /// @param scriptId 脚本 ID
+ /// @param content 脚本内容
+ /// @return 新版本
+ GroovyScriptVersion createVersion(String scriptId, String content);
+
+ /// 查询脚本的全部版本
+ ///
+ /// @param scriptId 脚本 ID
+ /// @return 按版本号升序的版本列表
+ List listVersion(String scriptId);
+
+ /// 发布指定版本
+ ///
+ /// @param scriptId 脚本 ID
+ /// @param versionId 脚本版本 ID
+ void publish(String scriptId, String versionId);
+
+ /// 取消当前发布版本
+ ///
+ /// @param scriptId 脚本 ID
+ void unpublish(String scriptId);
+
+ /// 删除脚本
+ ///
+ /// @param id 脚本 ID
+ void delete(String id);
+
+ /// 执行已发布且启用的脚本
+ ///
+ /// @param bizType 业务归属
+ /// @param code 业务内唯一代码
+ /// @param bindings 传给脚本的变量
+ /// @return 脚本返回值
+ Object execute(GroovyScript.BizType bizType, String code, Map bindings);
+
+ /// 执行已发布且启用的脚本并返回实际执行版本
+ ///
+ /// @param bizType 业务归属
+ /// @param code 业务内唯一代码
+ /// @param bindings 传给脚本的变量
+ /// @param resultType 返回值类型
+ /// @param 返回值类型
+ /// @return 脚本执行结果
+ GroovyScriptExecutionResult executePublished(GroovyScript.BizType bizType, String code, Map bindings, Class resultType);
+
+ /// 按指定历史版本执行脚本
+ ///
+ /// 用于按积分发生时的脚本版本复算,脚本当前是否发布或启用不影响执行;脚本返回 null 表示未命中,属合法结果
+ ///
+ /// @param versionId 脚本版本 ID
+ /// @param bindings 传给脚本的变量
+ /// @param resultType 返回值类型
+ /// @param 返回值类型
+ /// @return 脚本执行结果
+ GroovyScriptExecutionResult executeVersion(String versionId, Map bindings, Class resultType);
+
+ /// 执行已发布且启用的脚本并验证返回值类型
+ ///
+ /// @param bizType 业务归属
+ /// @param code 业务内唯一代码
+ /// @param bindings 传给脚本的变量
+ /// @param resultType 返回值类型
+ /// @param 返回值类型
+ /// @return 脚本返回值
+ T execute(GroovyScript.BizType bizType, String code, Map bindings, Class resultType);
+
+ /// 清除脚本的本地编译缓存
+ ///
+ /// 多实例部署时,其他节点在收到脚本变更通知后调用此方法
+ ///
+ /// @param scriptId 脚本 ID
+ void invalidate(String scriptId);
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/service/NotifyService.java b/src/main/java/com/imyeyu/api/modules/common/service/NotifyService.java
index 4b41082..4b4bd09 100644
--- a/src/main/java/com/imyeyu/api/modules/common/service/NotifyService.java
+++ b/src/main/java/com/imyeyu/api/modules/common/service/NotifyService.java
@@ -14,6 +14,14 @@ import com.imyeyu.spring.service.BaseService;
*/
public interface NotifyService extends BaseService {
+ /// 查询业务提醒是否已创建
+ ///
+ /// @param bizType 业务类型
+ /// @param bizRemindedType 被提醒业务类型
+ /// @param bizRemindedId 被提醒业务 ID
+ /// @return true 为已创建
+ boolean existsByBizReminded(String bizType, String bizRemindedType, String bizRemindedId);
+
/// 分页查询当前用户创建的通知
///
/// @param page 分页参数
diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/ArticleServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/ArticleServiceImplement.java
index 9f8b58a..945b7a1 100644
--- a/src/main/java/com/imyeyu/api/modules/common/service/implement/ArticleServiceImplement.java
+++ b/src/main/java/com/imyeyu/api/modules/common/service/implement/ArticleServiceImplement.java
@@ -16,6 +16,8 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
+import java.util.List;
+
/**
* 文章服务实现
*
@@ -37,6 +39,11 @@ public class ArticleServiceImplement extends AbstractEntityService listByExample(Article example) {
+ return mapper.selectAllByExample(example);
+ }
+
@Override
public void create(Article article) {
Logger logger = new Logger(Logger.Module.COMMON, "COMMON_ARTICLE_CREATE");
diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/GroovyScriptServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/GroovyScriptServiceImplement.java
new file mode 100644
index 0000000..890571c
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/service/implement/GroovyScriptServiceImplement.java
@@ -0,0 +1,293 @@
+package com.imyeyu.api.modules.common.service.implement;
+
+import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
+import com.imyeyu.api.modules.common.entity.GroovyScript;
+import com.imyeyu.api.modules.common.entity.GroovyScriptVersion;
+import com.imyeyu.api.modules.common.bean.GroovyScriptExecutionResult;
+import com.imyeyu.api.modules.common.mapper.GroovyScriptMapper;
+import com.imyeyu.api.modules.common.mapper.GroovyScriptVersionMapper;
+import com.imyeyu.api.modules.common.service.GroovyScriptService;
+import com.imyeyu.java.bean.timi.TimiCode;
+import com.imyeyu.java.bean.timi.TimiException;
+import com.imyeyu.spring.mapper.BaseMapper;
+import com.imyeyu.spring.service.AbstractEntityService;
+import groovy.lang.Binding;
+import groovy.lang.GroovyClassLoader;
+import groovy.lang.Script;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.support.TransactionSynchronization;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/// Groovy 脚本服务实现
+///
+/// 每个已编译版本使用独立类加载器,缓存失效后可释放对应类及其元数据
+///
+/// @author Codex
+/// @since 2026-09-04
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class GroovyScriptServiceImplement extends AbstractEntityService implements GroovyScriptService {
+
+ private final GroovyScriptMapper mapper;
+ private final GroovyScriptVersionMapper versionMapper;
+ private final Map compiledScriptMap = new ConcurrentHashMap<>();
+
+ @Override
+ protected BaseMapper mapper() {
+ return mapper;
+ }
+
+ @Override
+ public GroovyScript getByCode(GroovyScript.BizType bizType, String code) {
+ GroovyScript example = new GroovyScript();
+ example.setBizType(bizType);
+ example.setCode(code);
+ return mapper.selectByExample(example);
+ }
+
+ @Transactional(TimiServerDBConfig.ROLLBACKER)
+ @Override
+ public void create(GroovyScript script) {
+ TimiException.required(script, "未找到脚本");
+ TimiException.required(script.getBizType(), "未找到脚本业务归属");
+ TimiException.required(script.getCode(), "未找到脚本代码");
+ TimiException.required(script.getName(), "未找到脚本名称");
+ TimiException.requiredNull(getByCode(script.getBizType(), script.getCode()), "Groovy 脚本已存在");
+ script.setEnabled(false);
+ script.setPublishedVersionId(null);
+ super.create(script);
+ }
+
+ @Transactional(TimiServerDBConfig.ROLLBACKER)
+ @Override
+ public void update(GroovyScript script) {
+ TimiException.required(script, "未找到脚本");
+ TimiException.required(script.getId(), "未找到脚本 ID");
+ TimiException.required(script.getName(), "未找到脚本名称");
+ GroovyScript dbScript = get(script.getId());
+ TimiException.required(dbScript, "未找到脚本");
+ dbScript.setName(script.getName());
+ dbScript.setDescription(script.getDescription());
+ mapper.update(dbScript);
+ }
+
+ @Transactional(TimiServerDBConfig.ROLLBACKER)
+ @Override
+ public void setEnabled(String id, boolean enabled) {
+ GroovyScript script = requireScript(id);
+ script.setEnabled(enabled);
+ mapper.update(script);
+ invalidateAfterCommit(script.getId());
+ }
+
+ @Transactional(TimiServerDBConfig.ROLLBACKER)
+ @Override
+ public GroovyScriptVersion createVersion(String scriptId, String content) {
+ requireScript(scriptId);
+ TimiException.required(content, "未找到脚本内容");
+ TimiException.requiredTrue(!content.isBlank(), "脚本内容不能为空");
+
+ GroovyScriptVersion version = new GroovyScriptVersion();
+ version.setScriptId(scriptId);
+ version.setVersion(nextVersion(scriptId));
+ version.setContent(content);
+ version.setChecksum(checksum(content));
+ versionMapper.insert(version);
+ return version;
+ }
+
+ @Override
+ public List listVersion(String scriptId) {
+ TimiException.required(scriptId, "未找到脚本 ID");
+ GroovyScriptVersion example = new GroovyScriptVersion();
+ example.setScriptId(scriptId);
+ List versionList = new ArrayList<>(versionMapper.selectAllByExample(example));
+ versionList.sort(Comparator.comparing(GroovyScriptVersion::getVersion));
+ return versionList;
+ }
+
+ @Transactional(TimiServerDBConfig.ROLLBACKER)
+ @Override
+ public void publish(String scriptId, String versionId) {
+ GroovyScript script = requireScript(scriptId);
+ GroovyScriptVersion version = requireVersion(versionId);
+ TimiException.requiredTrue(script.getId().equals(version.getScriptId()), "脚本版本不属于当前脚本");
+ script.setPublishedVersionId(version.getId());
+ mapper.update(script);
+ invalidateAfterCommit(script.getId());
+ }
+
+ @Transactional(TimiServerDBConfig.ROLLBACKER)
+ @Override
+ public void unpublish(String scriptId) {
+ GroovyScript script = requireScript(scriptId);
+ script.setPublishedVersionId(null);
+ mapper.update(script);
+ invalidateAfterCommit(script.getId());
+ }
+
+ @Transactional(TimiServerDBConfig.ROLLBACKER)
+ @Override
+ public void delete(String id) {
+ requireScript(id);
+ super.delete(id);
+ invalidateAfterCommit(id);
+ }
+
+ @Override
+ public Object execute(GroovyScript.BizType bizType, String code, Map bindings) {
+ return executePublished(bizType, code, bindings, Object.class).result();
+ }
+
+ @Override
+ public GroovyScriptExecutionResult executePublished(GroovyScript.BizType bizType, String code, Map bindings, Class resultType) {
+ TimiException.required(bizType, "未找到脚本业务归属");
+ TimiException.required(code, "未找到脚本代码");
+ TimiException.required(resultType, "未找到返回值类型");
+ GroovyScript script = getByCode(bizType, code);
+ TimiException.required(script, "未找到 Groovy 脚本");
+ TimiException.requiredTrue(Boolean.TRUE.equals(script.getEnabled()), "Groovy 脚本已停用");
+ TimiException.required(script.getPublishedVersionId(), "Groovy 脚本尚未发布");
+ GroovyScriptExecutionResult result = executeVersion(script.getPublishedVersionId(), bindings, resultType);
+ TimiException.requiredTrue(script.getId().equals(result.scriptId()), "Groovy 脚本已发布版本无效");
+ return result;
+ }
+
+ @Override
+ public T execute(GroovyScript.BizType bizType, String code, Map bindings, Class resultType) {
+ return executePublished(bizType, code, bindings, resultType).result();
+ }
+
+ @Override
+ public GroovyScriptExecutionResult executeVersion(String versionId, Map bindings, Class resultType) {
+ TimiException.required(resultType, "未找到返回值类型");
+ GroovyScriptVersion version = requireVersion(versionId);
+ CompiledScript compiledScript = compiledScriptMap.computeIfAbsent(cacheKey(version), key -> compile(version.getScriptId(), version));
+ try {
+ Object result = compiledScript.execute(bindings);
+ TimiException.requiredTrue(result == null || resultType.isInstance(result), "Groovy 脚本返回值类型不匹配");
+ return new GroovyScriptExecutionResult<>(version.getScriptId(), version.getId(), version.getVersion(), version.getChecksum(), resultType.cast(result));
+ } catch (TimiException e) {
+ throw e;
+ } catch (RuntimeException e) {
+ log.warn("Groovy 脚本执行失败,scriptId={}, versionId={}", version.getScriptId(), version.getId(), e);
+ throw new TimiException(TimiCode.ERROR, "Groovy 脚本执行失败");
+ }
+ }
+
+ @Override
+ public void invalidate(String scriptId) {
+ if (scriptId == null) {
+ return;
+ }
+ compiledScriptMap.entrySet().removeIf(entry -> {
+ if (!scriptId.equals(entry.getValue().scriptId())) {
+ return false;
+ }
+ entry.getValue().close();
+ return true;
+ });
+ }
+
+ private GroovyScript requireScript(String id) {
+ TimiException.required(id, "未找到脚本 ID");
+ GroovyScript script = get(id);
+ TimiException.required(script, "未找到脚本");
+ return script;
+ }
+
+ private GroovyScriptVersion requireVersion(String id) {
+ TimiException.required(id, "未找到脚本版本 ID");
+ GroovyScriptVersion version = versionMapper.select(id);
+ TimiException.required(version, "未找到脚本版本");
+ return version;
+ }
+
+ private int nextVersion(String scriptId) {
+ return listVersion(scriptId).stream()
+ .map(GroovyScriptVersion::getVersion)
+ .max(Integer::compareTo)
+ .orElse(0) + 1;
+ }
+
+ private String cacheKey(GroovyScriptVersion version) {
+ return "%s:%s".formatted(version.getId(), version.getChecksum());
+ }
+
+ private CompiledScript compile(String scriptId, GroovyScriptVersion version) {
+ GroovyClassLoader classLoader = new GroovyClassLoader(getClass().getClassLoader());
+ try {
+ Class> type = classLoader.parseClass(version.getContent(), "GroovyScript_%s_%d.groovy".formatted(version.getId().replace('-', '_'), version.getVersion()));
+ return new CompiledScript(scriptId, type.asSubclass(Script.class), classLoader);
+ } catch (RuntimeException e) {
+ close(classLoader);
+ log.warn("Groovy 脚本编译失败,scriptId={}, versionId={}", scriptId, version.getId(), e);
+ throw new TimiException(TimiCode.ERROR, "Groovy 脚本编译失败");
+ }
+ }
+
+ private String checksum(String content) {
+ try {
+ return java.util.HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(content.getBytes(StandardCharsets.UTF_8)));
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalStateException("JVM 缺少 SHA-256 算法", e);
+ }
+ }
+
+ private void invalidateAfterCommit(String scriptId) {
+ if (!TransactionSynchronizationManager.isSynchronizationActive()) {
+ invalidate(scriptId);
+ return;
+ }
+ TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
+
+ @Override
+ public void afterCommit() {
+ invalidate(scriptId);
+ }
+ });
+ }
+
+ private void close(GroovyClassLoader classLoader) {
+ try {
+ classLoader.close();
+ } catch (IOException e) {
+ log.warn("关闭 Groovy 脚本类加载器失败", e);
+ }
+ }
+
+ private record CompiledScript(String scriptId, Class extends Script> type, GroovyClassLoader classLoader) {
+
+ private Object execute(Map bindings) {
+ try {
+ Script script = type.getDeclaredConstructor().newInstance();
+ script.setBinding(new Binding(bindings == null ? Map.of() : new HashMap<>(bindings)));
+ return script.run();
+ } catch (ReflectiveOperationException e) {
+ throw new IllegalStateException("Groovy 脚本实例化失败", e);
+ }
+ }
+
+ private void close() {
+ try {
+ classLoader.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/NotifyServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/NotifyServiceImplement.java
index 85ae375..fc631bd 100644
--- a/src/main/java/com/imyeyu/api/modules/common/service/implement/NotifyServiceImplement.java
+++ b/src/main/java/com/imyeyu/api/modules/common/service/implement/NotifyServiceImplement.java
@@ -46,6 +46,11 @@ public class NotifyServiceImplement extends AbstractEntityService result = service.pageByQuery(page);
- if (result.getList().isEmpty()) {
- return result;
- }
- List customerIdList = result.getList().stream()
- .map(GaoCustomerPackage::getCustomerId)
- .filter(value -> value != null && !value.isBlank())
- .distinct()
- .toList();
- Map customerMap = customerService.mapByIdList(customerIdList);
- Map> attachmentMap = attachmentService.mapByBizIdList(Attachment.BizType.GAO_CUSTOMER, customerIdList, GaoCustomer.AttachType.PHOTO.name());
- for (GaoCustomerPackage packageEntity : result.getList()) {
- GaoCustomer customer = customerMap.get(packageEntity.getCustomerId());
- if (customer != null) {
- customer.setAttachmentList(attachmentMap.get(customer.getId()));
- }
- packageEntity.setCustomer(customer);
- }
+ fillTransient(result.getList());
return result;
}
@@ -102,15 +74,7 @@ public class GaoCustomerPackageController {
customer.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.GAO_CUSTOMER, customer.getId()));
}
result.setCustomer(customer);
- GaoEventRecord purchaseRecord = eventRecordService.get(result.getPurchaseEventRecordId());
- if (purchaseRecord != null) {
- purchaseRecord.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.GAO_EVENT_RECORD, purchaseRecord.getId()));
- }
- result.setPurchaseEventRecord(purchaseRecord);
- result.setDeliveryList(deliveryService.listByCustomerPackageId(result.getId()));
- fillDeliveryTransient(result.getDeliveryList());
- result.setFundRecordList(fundRecordService.listByCustomerPackageId(result.getId()));
- fillFundTransient(result.getFundRecordList());
+ result.setFulfillmentList(fulfillmentService.listBySalesOrderId(result.getSalesOrderId()));
return result;
}
@@ -134,141 +98,46 @@ public class GaoCustomerPackageController {
return result;
}
- @AOPLog
- @RequestRateLimit
- @RequireGaoPermission(GaoPermissionCode.FUND_RECEIVE_CREATE)
- @PostMapping("/payment/receive")
- public GaoCustomerPackage receive(@RequestBody GaoFundReceiveRequest request) {
- fundRecordService.receive(request);
- return packageResponse(request.getCustomerPackageId());
- }
-
- @AOPLog
- @RequestRateLimit
- @RequireGaoPermission(GaoPermissionCode.FUND_RECEIVE_VERIFY)
- @PostMapping("/payment/verify")
- public GaoCustomerPackage verifyPayment(@RequestParam String id) {
- fundRecordService.verify(id);
- return packageAfterFundAction(id);
- }
-
- @AOPLog
- @RequestRateLimit
- @RequireGaoPermission(GaoPermissionCode.FUND_RECEIVE_VERIFY)
- @PostMapping("/payment/reject")
- public GaoCustomerPackage rejectPayment(@RequestBody GaoFundActionRequest request) {
- fundRecordService.reject(request);
- return packageAfterFundAction(request.getFundRecordId());
- }
-
- @AOPLog
- @RequestRateLimit
- @RequireGaoPermission(GaoPermissionCode.FUND_RECEIVE_CREATE)
- @PostMapping("/payment/cancel")
- public GaoCustomerPackage cancelPayment(@RequestBody GaoFundActionRequest request) {
- fundRecordService.cancel(request);
- return packageAfterFundAction(request.getFundRecordId());
- }
-
- @AOPLog
- @RequestRateLimit
- @RequireGaoPermission(GaoPermissionCode.FUND_REFUND)
- @PostMapping("/refund")
- public GaoCustomerPackage refund(@RequestBody GaoFundRefundRequest request) {
- fundRecordService.refund(request);
- return packageResponse(request.getCustomerPackageId());
- }
-
- @AOPLog
- @RequestRateLimit
- @RequireGaoPermission(GaoPermissionCode.FUND_REFUND)
- @PostMapping("/refund/confirm-none")
- public GaoCustomerPackage confirmNoRefund(@RequestBody GaoFundConfirmNoRefundRequest request) {
- fundRecordService.confirmNoRefund(request);
- return packageResponse(request.getCustomerPackageId());
- }
-
- private GaoCustomerPackage packageAfterFundAction(String fundRecordId) {
- var record = fundRecordService.get(fundRecordId);
- TimiException.required(record, "not found fund record");
- return packageResponse(record.getBizId());
- }
-
- private GaoCustomerPackage packageResponse(String packageId) {
- GaoCustomerPackage result = service.get(packageId);
- TimiException.required(result, "not found customer package");
- fillTransient(List.of(result));
- return result;
- }
-
private void fillTransient(List packageList) {
if (packageList == null || packageList.isEmpty()) {
return;
}
- Map> itemMap = service.mapItemListByCustomerPackageIdList(packageList.stream().map(GaoCustomerPackage::getId).toList());
+ Map orderMap = salesOrderService.mapByIdList(packageList.stream()
+ .map(GaoCustomerPackage::getSalesOrderId)
+ .filter(value -> value != null && !value.isBlank())
+ .distinct()
+ .toList());
+ List customerIdList = packageList.stream()
+ .map(GaoCustomerPackage::getCustomerId)
+ .filter(value -> value != null && !value.isBlank())
+ .distinct()
+ .toList();
+ Map customerMap = customerIdList.isEmpty() ? Map.of() : customerService.mapByIdList(customerIdList);
+ Map> attachmentMap = customerIdList.isEmpty()
+ ? Map.of() : attachmentService.mapByBizIdList(Attachment.BizType.GAO_CUSTOMER, customerIdList, GaoCustomer.AttachType.PHOTO.name());
List userIdList = packageList.stream()
- .flatMap(packageEntity -> Stream.of(packageEntity.getOperatorUserId(), packageEntity.getCancelOperatorUserId()))
+ .map(packageEntity -> orderMap.get(packageEntity.getSalesOrderId()))
+ .filter(order -> order != null)
+ .map(GaoSalesOrder::getCancelOperatorUserId)
.filter(value -> value != null && !value.isBlank())
.distinct()
.toList();
Map userMap = userIdList.isEmpty() ? Map.of() : userService.mapByIdList(userIdList);
for (GaoCustomerPackage packageEntity : packageList) {
- List itemList = itemMap.getOrDefault(packageEntity.getId(), List.of());
- for (GaoCustomerPackageItem item : itemList) {
- fillDerived(item);
+ GaoSalesOrder order = orderMap.get(packageEntity.getSalesOrderId());
+ packageEntity.setSalesOrder(order);
+ packageEntity.setStatus(order == null ? null : order.getStatus());
+ packageEntity.setCancelledAt(order == null ? null : order.getCancelledAt());
+ packageEntity.setCancelOperatorUserId(order == null ? null : order.getCancelOperatorUserId());
+ packageEntity.setCancelReason(order == null ? null : order.getCancelReason());
+ packageEntity.setItemList(order == null ? List.of() : order.getItemList());
+ GaoCustomer customer = customerMap.get(packageEntity.getCustomerId());
+ if (customer != null) {
+ customer.setAttachmentList(attachmentMap.getOrDefault(customer.getId(), List.of()));
}
- packageEntity.setItemList(itemList);
- packageEntity.setOperator(userMap.get(packageEntity.getOperatorUserId()));
- packageEntity.setCancelOperator(userMap.get(packageEntity.getCancelOperatorUserId()));
+ packageEntity.setCustomer(customer);
+ String cancelOperatorUserId = order == null ? null : order.getCancelOperatorUserId();
+ packageEntity.setCancelOperator(cancelOperatorUserId == null ? null : userMap.get(cancelOperatorUserId));
}
}
-
- private void fillDeliveryTransient(List deliveryList) {
- if (deliveryList == null || deliveryList.isEmpty()) {
- return;
- }
- List idList = deliveryList.stream().map(GaoPackageDelivery::getId).toList();
- Map> itemMap = deliveryService.mapItemListByDeliveryIdList(idList);
- Map> attachmentMap = attachmentService.mapByBizIdList(Attachment.BizType.GAO_PACKAGE_DELIVERY, idList);
- List userIdList = deliveryList.stream()
- .flatMap(delivery -> Stream.of(delivery.getOperatorUserId(), delivery.getVerifierUserId()))
- .filter(value -> value != null && !value.isBlank())
- .distinct()
- .toList();
- Map userMap = userIdList.isEmpty() ? Map.of() : userService.mapByIdList(userIdList);
- for (GaoPackageDelivery delivery : deliveryList) {
- delivery.setItemList(itemMap.getOrDefault(delivery.getId(), List.of()));
- delivery.setAttachmentList(attachmentMap.getOrDefault(delivery.getId(), List.of()));
- delivery.setOperator(userMap.get(delivery.getOperatorUserId()));
- delivery.setVerifier(userMap.get(delivery.getVerifierUserId()));
- }
- }
-
- private void fillFundTransient(List recordList) {
- if (recordList == null || recordList.isEmpty()) {
- return;
- }
- List idList = recordList.stream().map(GaoFundRecord::getId).toList();
- Map> attachmentMap = attachmentService.mapByBizIdList(Attachment.BizType.GAO_FUND_RECORD, idList);
- List userIdList = recordList.stream()
- .flatMap(record -> Stream.of(record.getOperatorUserId(), record.getVerifierUserId()))
- .filter(value -> value != null && !value.isBlank())
- .distinct()
- .toList();
- Map userMap = userIdList.isEmpty() ? Map.of() : userService.mapByIdList(userIdList);
- for (GaoFundRecord record : recordList) {
- record.setAttachmentList(attachmentMap.getOrDefault(record.getId(), List.of()));
- record.setOperator(userMap.get(record.getOperatorUserId()));
- record.setVerifier(userMap.get(record.getVerifierUserId()));
- }
- }
-
- private void fillDerived(GaoCustomerPackageItem item) {
- BigDecimal reserved = item.getReservedQuantity() == null ? BigDecimal.ZERO : item.getReservedQuantity();
- BigDecimal delivered = item.getDeliveredQuantity() == null ? BigDecimal.ZERO : item.getDeliveredQuantity();
- BigDecimal total = item.getTotalQuantity() == null ? BigDecimal.ZERO : item.getTotalQuantity();
- item.setPendingQuantity(reserved);
- item.setAvailableQuantity(total.subtract(reserved).subtract(delivered));
- item.setRemainingQuantity(total.subtract(delivered));
- }
}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventController.java
index 1cd87f1..4b306fe 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventController.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventController.java
@@ -127,6 +127,7 @@ public class GaoEventController {
@PostMapping("/create")
public void create(@RequestBody GaoEvent event) {
event.setStoreId(storeScopeService.resolveStoreId(event.getStoreId()));
+ validatePackageConfigBinding(event);
service.create(event);
}
@@ -141,9 +142,21 @@ public class GaoEventController {
TimiException.required(dbEvent, "not found event");
storeScopeService.checkStoreId(dbEvent.getStoreId());
event.setStoreId(dbEvent.getStoreId());
+ validatePackageConfigBinding(event);
service.update(event);
}
+ private void validatePackageConfigBinding(GaoEvent event) {
+ if (event.getBizType() == GaoEvent.BizType.PACKAGE_PURCHASE) {
+ TimiException.required(event.getPackageConfigId(), "购买套餐事件必须绑定套餐配置");
+ GaoPackageConfig packageConfig = packageConfigService.get(event.getPackageConfigId());
+ TimiException.required(packageConfig, "购买套餐事件绑定的套餐配置不存在");
+ TimiException.requiredTrue(event.getStoreId().equals(packageConfig.getStoreId()), "购买事件和套餐配置不属于同一门店");
+ } else {
+ TimiException.requiredNull(event.getPackageConfigId(), "普通事件不能绑定套餐配置");
+ }
+ }
+
@AOPLog
@RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.EVENT_UPDATE)
diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoFundRecordController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoFundRecordController.java
index 11686dc..30d1ae3 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoFundRecordController.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoFundRecordController.java
@@ -6,9 +6,11 @@ import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission;
import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
import com.imyeyu.api.modules.gao.entity.GaoFundRecord;
import com.imyeyu.api.modules.gao.service.GaoFundRecordService;
+import com.imyeyu.api.modules.gao.service.GaoOrderPaymentService;
import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
import com.imyeyu.api.modules.gao.vo.GaoFundRecordPage;
import com.imyeyu.api.modules.gao.vo.GaoFundRevokeRequest;
+import com.imyeyu.api.modules.gao.vo.GaoOrderPaymentRevokeRequest;
import com.imyeyu.api.modules.user.entity.User;
import com.imyeyu.api.modules.user.service.UserService;
import com.imyeyu.java.bean.timi.TimiException;
@@ -36,6 +38,7 @@ import java.util.stream.Stream;
public class GaoFundRecordController {
private final GaoFundRecordService service;
+ private final GaoOrderPaymentService orderPaymentService;
private final GaoStoreScopeService storeScopeService;
private final AttachmentService attachmentService;
private final UserService userService;
@@ -57,8 +60,7 @@ public class GaoFundRecordController {
@RequireGaoPermission(GaoPermissionCode.FUND_RECORD_READ)
@PostMapping("/detail")
public GaoFundRecord detail(@RequestParam String id) {
- GaoFundRecord result = service.get(id);
- TimiException.required(result, "not found fund record");
+ GaoFundRecord result = service.detail(id);
storeScopeService.checkStoreId(result.getStoreId());
fillTransient(List.of(result));
return result;
@@ -69,7 +71,14 @@ public class GaoFundRecordController {
@RequireGaoPermission(GaoPermissionCode.FUND_REVOKE)
@PostMapping("/revoke")
public GaoFundRecord revoke(@RequestBody GaoFundRevokeRequest request) {
- GaoFundRecord result = service.revoke(request);
+ GaoOrderPaymentRevokeRequest orderRequest = new GaoOrderPaymentRevokeRequest();
+ orderRequest.setRequestNo(request.getRequestNo());
+ orderRequest.setFundRecordId(request.getFundRecordId());
+ orderRequest.setOccurredAt(request.getOccurredAt());
+ orderRequest.setReason(request.getReason());
+ orderRequest.setAttachmentList(request.getAttachmentList());
+ orderRequest.setRemark(request.getRemark());
+ GaoFundRecord result = orderPaymentService.revoke(orderRequest);
fillTransient(List.of(result));
return result;
}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoOrderFulfillmentController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoOrderFulfillmentController.java
new file mode 100644
index 0000000..940c519
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoOrderFulfillmentController.java
@@ -0,0 +1,163 @@
+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.GaoCustomer;
+import com.imyeyu.api.modules.gao.entity.GaoOrderFulfillment;
+import com.imyeyu.api.modules.gao.service.GaoCustomerService;
+import com.imyeyu.api.modules.gao.service.GaoOrderFulfillmentService;
+import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
+import com.imyeyu.api.modules.gao.vo.GaoOrderFulfillmentActionRequest;
+import com.imyeyu.api.modules.gao.vo.GaoOrderFulfillmentCreateRequest;
+import com.imyeyu.api.modules.gao.vo.GaoOrderFulfillmentPage;
+import com.imyeyu.api.modules.gao.vo.GaoOrderFulfillmentPrepareStockRequest;
+import com.imyeyu.api.modules.gao.vo.GaoOrderFulfillmentRevokeRequest;
+import com.imyeyu.api.modules.user.entity.User;
+import com.imyeyu.api.modules.user.service.UserService;
+import com.imyeyu.java.bean.timi.TimiException;
+import com.imyeyu.spring.annotation.AOPLog;
+import com.imyeyu.spring.annotation.RequestRateLimit;
+import com.imyeyu.spring.bean.PageResult;
+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.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+
+/// GAO 统一履约接口
+///
+/// @author Codex
+/// @since 2026-09-02
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/gao/order/fulfillment")
+public class GaoOrderFulfillmentController {
+
+ private final GaoOrderFulfillmentService service;
+ private final GaoStoreScopeService storeScopeService;
+ private final AttachmentService attachmentService;
+ private final GaoCustomerService customerService;
+ private final UserService userService;
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.ORDER_FULFILLMENT_READ)
+ @PostMapping("/list")
+ public PageResult list(@RequestBody GaoOrderFulfillmentPage page) {
+ TimiException.required(page, "not found fulfillment page");
+ page.setStoreId(storeScopeService.resolveStoreId(page.getStoreId()));
+ PageResult result = service.pageByQuery(page);
+ fillTransient(result.getList());
+ return result;
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.ORDER_FULFILLMENT_READ)
+ @PostMapping("/detail")
+ public GaoOrderFulfillment detail(@RequestParam String id) {
+ GaoOrderFulfillment result = service.detail(id);
+ fillTransient(List.of(result));
+ return result;
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.ORDER_FULFILLMENT_CREATE)
+ @PostMapping("/create")
+ public GaoOrderFulfillment create(@RequestBody GaoOrderFulfillmentCreateRequest request) {
+ GaoOrderFulfillment result = service.create(request);
+ fillTransient(List.of(result));
+ return result;
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.ORDER_FULFILLMENT_VERIFY)
+ @PostMapping("/prepare-stock")
+ public GaoOrderFulfillment prepareStock(@RequestBody GaoOrderFulfillmentPrepareStockRequest request) {
+ GaoOrderFulfillment result = service.prepareStock(request);
+ fillTransient(List.of(result));
+ return result;
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.ORDER_FULFILLMENT_VERIFY)
+ @PostMapping("/verify")
+ public GaoOrderFulfillment verify(@RequestBody GaoOrderFulfillmentActionRequest request) {
+ GaoOrderFulfillment result = service.verify(request);
+ fillTransient(List.of(result));
+ return result;
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.ORDER_FULFILLMENT_VERIFY)
+ @PostMapping("/reject")
+ public GaoOrderFulfillment reject(@RequestBody GaoOrderFulfillmentActionRequest request) {
+ GaoOrderFulfillment result = service.reject(request);
+ fillTransient(List.of(result));
+ return result;
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.ORDER_FULFILLMENT_CREATE)
+ @PostMapping("/cancel")
+ public GaoOrderFulfillment cancel(@RequestBody GaoOrderFulfillmentActionRequest request) {
+ GaoOrderFulfillment result = service.cancel(request);
+ fillTransient(List.of(result));
+ return result;
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.ORDER_FULFILLMENT_REVOKE)
+ @PostMapping("/revoke")
+ public GaoOrderFulfillment revoke(@RequestBody GaoOrderFulfillmentRevokeRequest request) {
+ GaoOrderFulfillment result = service.revoke(request);
+ fillTransient(List.of(result));
+ return result;
+ }
+
+ private void fillTransient(List fulfillmentList) {
+ if (fulfillmentList == null || fulfillmentList.isEmpty()) {
+ return;
+ }
+ List idList = fulfillmentList.stream().map(GaoOrderFulfillment::getId).toList();
+ Map> attachmentMap = attachmentService.mapByBizIdList(Attachment.BizType.GAO_ORDER_FULFILLMENT, idList);
+ List customerIdList = fulfillmentList.stream()
+ .map(GaoOrderFulfillment::getCustomerId)
+ .filter(value -> value != null && !value.isBlank())
+ .distinct()
+ .toList();
+ Map customerMap = customerIdList.isEmpty() ? Map.of() : customerService.mapByIdList(customerIdList);
+ Map> customerAttachmentMap = customerIdList.isEmpty()
+ ? Map.of()
+ : attachmentService.mapByBizIdList(Attachment.BizType.GAO_CUSTOMER, customerIdList, GaoCustomer.AttachType.PHOTO.name());
+ List userIdList = fulfillmentList.stream()
+ .flatMap(item -> Stream.of(item.getOperatorUserId(), item.getVerifierUserId()))
+ .filter(value -> value != null && !value.isBlank())
+ .distinct()
+ .toList();
+ Map userMap = userIdList.isEmpty() ? Map.of() : userService.mapByIdList(userIdList);
+ for (GaoOrderFulfillment fulfillment : fulfillmentList) {
+ fulfillment.setAttachmentList(attachmentMap.getOrDefault(fulfillment.getId(), List.of()));
+ GaoCustomer customer = customerMap.get(fulfillment.getCustomerId());
+ if (customer != null) {
+ customer.setAttachmentList(customerAttachmentMap.getOrDefault(customer.getId(), List.of()));
+ }
+ fulfillment.setCustomer(customer);
+ fulfillment.setOperator(userMap.get(fulfillment.getOperatorUserId()));
+ fulfillment.setVerifier(userMap.get(fulfillment.getVerifierUserId()));
+ }
+ }
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPackageConfigController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPackageConfigController.java
index a638a19..840152d 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPackageConfigController.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPackageConfigController.java
@@ -4,7 +4,10 @@ import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission;
import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
import com.imyeyu.api.modules.gao.entity.GaoPackageConfig;
import com.imyeyu.api.modules.gao.entity.GaoPackageConfigItem;
+import com.imyeyu.api.modules.gao.entity.GaoProductSku;
+import com.imyeyu.api.modules.gao.service.GaoEventService;
import com.imyeyu.api.modules.gao.service.GaoPackageConfigService;
+import com.imyeyu.api.modules.gao.service.GaoProductSkuService;
import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
import com.imyeyu.api.modules.gao.vo.GaoPackageConfigPage;
import com.imyeyu.api.modules.user.entity.User;
@@ -33,6 +36,8 @@ import java.util.Map;
public class GaoPackageConfigController {
private final GaoPackageConfigService service;
+ private final GaoEventService eventService;
+ private final GaoProductSkuService skuService;
private final GaoStoreScopeService storeScopeService;
private final UserService userService;
@@ -44,7 +49,7 @@ public class GaoPackageConfigController {
TimiException.required(page, "not found page");
page.setStoreId(storeScopeService.resolveStoreId(page.getStoreId()));
PageResult result = service.pageByQuery(page);
- fillTransient(result.getList());
+ fillTransient(result.getList(), false);
return result;
}
@@ -54,7 +59,7 @@ public class GaoPackageConfigController {
@PostMapping("/list/valid")
public List listValid(@RequestParam String storeId) {
List result = service.listValid(storeScopeService.resolveStoreId(storeId));
- fillTransient(result);
+ fillTransient(result, false);
return result;
}
@@ -66,11 +71,11 @@ public class GaoPackageConfigController {
GaoPackageConfig config = service.get(id);
TimiException.required(config, "not found package config");
storeScopeService.checkStoreId(config.getStoreId());
- fillTransient(List.of(config));
+ fillTransient(List.of(config), true);
return config;
}
- private void fillTransient(List configList) {
+ private void fillTransient(List configList, boolean includeProductDetail) {
if (configList == null || configList.isEmpty()) {
return;
}
@@ -81,6 +86,28 @@ public class GaoPackageConfigController {
config.setItemList(itemMap.getOrDefault(config.getId(), List.of()));
config.setUpdater(updaterMap.get(config.getUpdatedBy()));
}
+ if (includeProductDetail) {
+ fillProductDetails(configList);
+ }
+ }
+
+ private void fillProductDetails(List configList) {
+ List skuIdList = configList.stream()
+ .flatMap(config -> config.getItemList().stream())
+ .map(GaoPackageConfigItem::getSkuId)
+ .filter(value -> value != null && !value.isBlank())
+ .distinct()
+ .toList();
+ if (skuIdList.isEmpty()) {
+ return;
+ }
+ Map skuMap = skuIdList.stream()
+ .collect(java.util.stream.Collectors.toMap(item -> item, skuService::detail));
+ for (GaoPackageConfig config : configList) {
+ for (GaoPackageConfigItem item : config.getItemList()) {
+ item.setSku(skuMap.get(item.getSkuId()));
+ }
+ }
}
@AOPLog
@@ -116,6 +143,7 @@ public class GaoPackageConfigController {
GaoPackageConfig config = service.get(id);
TimiException.required(config, "not found package config");
storeScopeService.checkStoreId(config.getStoreId());
+ TimiException.requiredTrue(0 == eventService.countByPackageConfigId(id), "套餐配置已绑定购买事件,不能删除");
service.delete(id);
}
}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPackageDeliveryController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPackageDeliveryController.java
deleted file mode 100644
index 716e04b..0000000
--- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPackageDeliveryController.java
+++ /dev/null
@@ -1,140 +0,0 @@
-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.GaoPackageDelivery;
-import com.imyeyu.api.modules.gao.entity.GaoPackageDeliveryItem;
-import com.imyeyu.api.modules.gao.service.GaoPackageDeliveryService;
-import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
-import com.imyeyu.api.modules.gao.vo.GaoPackageDeliveryActionRequest;
-import com.imyeyu.api.modules.gao.vo.GaoPackageDeliveryCreateRequest;
-import com.imyeyu.api.modules.gao.vo.GaoPackageDeliveryPage;
-import com.imyeyu.api.modules.gao.vo.GaoPackageDeliveryRevokeRequest;
-import com.imyeyu.api.modules.user.entity.User;
-import com.imyeyu.api.modules.user.service.UserService;
-import com.imyeyu.java.bean.timi.TimiException;
-import com.imyeyu.spring.annotation.AOPLog;
-import com.imyeyu.spring.annotation.RequestRateLimit;
-import com.imyeyu.spring.bean.PageResult;
-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.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Stream;
-
-/// GAO 套餐发放接口
-///
-/// @author Codex
-/// @since 2026-08-31
-@RestController
-@RequiredArgsConstructor
-@RequestMapping("/gao/package/delivery")
-public class GaoPackageDeliveryController {
-
- private final GaoPackageDeliveryService service;
- private final GaoStoreScopeService storeScopeService;
- private final AttachmentService attachmentService;
- private final UserService userService;
-
- @AOPLog
- @RequestRateLimit
- @RequireGaoPermission(GaoPermissionCode.PACKAGE_DELIVERY_READ)
- @PostMapping("/list")
- public PageResult list(@RequestBody GaoPackageDeliveryPage page) {
- TimiException.required(page, "not found page");
- page.setStoreId(storeScopeService.resolveStoreId(page.getStoreId()));
- PageResult result = service.pageByQuery(page);
- fillTransient(result.getList());
- return result;
- }
-
- @AOPLog
- @RequestRateLimit
- @RequireGaoPermission(GaoPermissionCode.PACKAGE_DELIVERY_READ)
- @PostMapping("/detail")
- public GaoPackageDelivery detail(@RequestParam String id) {
- GaoPackageDelivery result = service.get(id);
- TimiException.required(result, "not found delivery");
- storeScopeService.checkStoreId(result.getStoreId());
- fillTransient(List.of(result));
- return result;
- }
-
- @AOPLog
- @RequestRateLimit
- @RequireGaoPermission(GaoPermissionCode.PACKAGE_DELIVERY_CREATE)
- @PostMapping("/create")
- public GaoPackageDelivery create(@RequestBody GaoPackageDeliveryCreateRequest request) {
- GaoPackageDelivery result = service.createDelivery(request);
- fillTransient(List.of(result));
- return result;
- }
-
- @AOPLog
- @RequestRateLimit
- @RequireGaoPermission(GaoPermissionCode.PACKAGE_DELIVERY_VERIFY)
- @PostMapping("/verify")
- public GaoPackageDelivery verify(@RequestParam String id) {
- GaoPackageDelivery result = service.verify(id);
- fillTransient(List.of(result));
- return result;
- }
-
- @AOPLog
- @RequestRateLimit
- @RequireGaoPermission(GaoPermissionCode.PACKAGE_DELIVERY_VERIFY)
- @PostMapping("/reject")
- public GaoPackageDelivery reject(@RequestBody GaoPackageDeliveryActionRequest request) {
- GaoPackageDelivery result = service.reject(request);
- fillTransient(List.of(result));
- return result;
- }
-
- @AOPLog
- @RequestRateLimit
- @RequireGaoPermission(GaoPermissionCode.PACKAGE_DELIVERY_CREATE)
- @PostMapping("/cancel")
- public GaoPackageDelivery cancel(@RequestBody GaoPackageDeliveryActionRequest request) {
- GaoPackageDelivery result = service.cancel(request);
- fillTransient(List.of(result));
- return result;
- }
-
- @AOPLog
- @RequestRateLimit
- @RequireGaoPermission(GaoPermissionCode.PACKAGE_DELIVERY_REVOKE)
- @PostMapping("/revoke")
- public GaoPackageDelivery revoke(@RequestBody GaoPackageDeliveryRevokeRequest request) {
- GaoPackageDelivery result = service.revoke(request);
- fillTransient(List.of(result));
- return result;
- }
-
- private void fillTransient(List deliveryList) {
- if (deliveryList == null || deliveryList.isEmpty()) {
- return;
- }
- List idList = deliveryList.stream().map(GaoPackageDelivery::getId).toList();
- Map> itemMap = service.mapItemListByDeliveryIdList(idList);
- Map> attachmentMap = attachmentService.mapByBizIdList(Attachment.BizType.GAO_PACKAGE_DELIVERY, idList);
- List userIdList = deliveryList.stream()
- .flatMap(delivery -> Stream.of(delivery.getOperatorUserId(), delivery.getVerifierUserId()))
- .filter(value -> value != null && !value.isBlank())
- .distinct()
- .toList();
- Map userMap = userIdList.isEmpty() ? Map.of() : userService.mapByIdList(userIdList);
- for (GaoPackageDelivery delivery : deliveryList) {
- delivery.setItemList(itemMap.getOrDefault(delivery.getId(), List.of()));
- delivery.setAttachmentList(attachmentMap.getOrDefault(delivery.getId(), List.of()));
- delivery.setOperator(userMap.get(delivery.getOperatorUserId()));
- delivery.setVerifier(userMap.get(delivery.getVerifierUserId()));
- }
- }
-}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoProductChartController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoProductChartController.java
new file mode 100644
index 0000000..5381646
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoProductChartController.java
@@ -0,0 +1,126 @@
+package com.imyeyu.api.modules.gao.controller;
+
+import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission;
+import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
+import com.imyeyu.api.modules.gao.service.GaoProductChartService;
+import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
+import com.imyeyu.api.modules.gao.vo.GaoProductCategoryStatView;
+import com.imyeyu.api.modules.gao.vo.GaoProductChartReq;
+import com.imyeyu.api.modules.gao.vo.GaoProductFulfillmentDailyStatView;
+import com.imyeyu.api.modules.gao.vo.GaoProductFulfillmentRankView;
+import com.imyeyu.api.modules.gao.vo.GaoProductSalesDailyStatView;
+import com.imyeyu.api.modules.gao.vo.GaoProductSalesRankView;
+import com.imyeyu.api.modules.gao.vo.GaoProductStockHealthView;
+import com.imyeyu.api.modules.gao.vo.GaoProductStockMovementDailyStatView;
+import com.imyeyu.api.modules.gao.vo.GaoProductTypeStatView;
+import com.imyeyu.api.modules.gao.vo.GaoProductUnitSalesRankView;
+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;
+
+/// GAO 产品图表接口
+///
+/// @author Codex
+/// @since 2026-09-06
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/gao/product/chart")
+public class GaoProductChartController {
+
+ private final GaoProductChartService service;
+ private final GaoStoreScopeService storeScopeService;
+
+ /// 查询产品每日销售统计
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/sales/daily")
+ public List salesDaily(@RequestBody GaoProductChartReq req) {
+ return service.salesDaily(normalize(req));
+ }
+
+ /// 查询产品销售排行
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/sales/rank")
+ public List salesRank(@RequestBody GaoProductChartReq req) {
+ return service.salesRank(normalize(req));
+ }
+
+ /// 查询产品每日履约统计
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/fulfillment/daily")
+ public List fulfillmentDaily(@RequestBody GaoProductChartReq req) {
+ return service.fulfillmentDaily(normalize(req));
+ }
+
+ /// 查询产品履约进度排行
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/fulfillment/rank")
+ public List fulfillmentRank(@RequestBody GaoProductChartReq req) {
+ return service.fulfillmentRank(normalize(req));
+ }
+
+ /// 查询产品每日库存变动统计
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/stock/movement/daily")
+ public List stockMovementDaily(@RequestBody GaoProductChartReq req) {
+ return service.stockMovementDaily(normalize(req));
+ }
+
+ /// 查询产品库存健康度
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/stock/health")
+ public List stockHealth(@RequestBody GaoProductChartReq req) {
+ return service.stockHealth(normalize(req));
+ }
+
+ /// 查询产品类型统计
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/structure/type")
+ public List type(@RequestBody GaoProductChartReq req) {
+ return service.type(normalize(req));
+ }
+
+ /// 查询产品分类统计
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/structure/category")
+ public List category(@RequestBody GaoProductChartReq req) {
+ return service.category(normalize(req));
+ }
+
+ /// 查询产品销售单位排行
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/sales/unit/rank")
+ public List unitSalesRank(@RequestBody GaoProductChartReq req) {
+ return service.unitSalesRank(normalize(req));
+ }
+
+ private GaoProductChartReq normalize(GaoProductChartReq req) {
+ TimiException.required(req, "not found product chart req");
+ req.setStoreId(storeScopeService.resolveStoreId(req.getStoreId()));
+ return req;
+ }
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoProductController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoProductController.java
new file mode 100644
index 0000000..101003b
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoProductController.java
@@ -0,0 +1,248 @@
+package com.imyeyu.api.modules.gao.controller;
+
+import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission;
+import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
+import com.imyeyu.api.modules.gao.entity.GaoProduct;
+import com.imyeyu.api.modules.gao.entity.GaoProductCategory;
+import com.imyeyu.api.modules.gao.entity.GaoProductSku;
+import com.imyeyu.api.modules.gao.service.GaoProductCategoryService;
+import com.imyeyu.api.modules.gao.service.GaoProductService;
+import com.imyeyu.api.modules.gao.service.GaoProductSkuService;
+import com.imyeyu.api.modules.gao.service.GaoProductSpecService;
+import com.imyeyu.api.modules.gao.entity.GaoProductSpec;
+import com.imyeyu.api.modules.gao.entity.GaoProductSpecValue;
+import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
+import com.imyeyu.api.modules.gao.vo.GaoProductCategoryPage;
+import com.imyeyu.api.modules.gao.vo.GaoProductPage;
+import com.imyeyu.java.bean.timi.TimiException;
+import com.imyeyu.spring.annotation.AOPLog;
+import com.imyeyu.spring.annotation.RequestRateLimit;
+import com.imyeyu.spring.bean.PageResult;
+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.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/// GAO 产品库接口
+///
+/// @author Codex
+/// @since 2026-09-02
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/gao/product")
+public class GaoProductController {
+
+ private final GaoProductCategoryService categoryService;
+ private final GaoProductService productService;
+ private final GaoProductSkuService skuService;
+ private final GaoProductSpecService specService;
+ private final GaoStoreScopeService storeScopeService;
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_CATEGORY_READ)
+ @PostMapping("/category/list")
+ public PageResult categoryList(@RequestBody GaoProductCategoryPage page) {
+ TimiException.required(page, "not found category page");
+ page.setStoreId(storeScopeService.resolveStoreId(page.getStoreId()));
+ return categoryService.pageByQuery(page);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_CATEGORY_CREATE)
+ @PostMapping("/category/create")
+ public String categoryCreate(@RequestBody GaoProductCategory category) {
+ TimiException.required(category, "not found category");
+ category.setStoreId(storeScopeService.resolveStoreId(category.getStoreId()));
+ categoryService.create(category);
+ return category.getId();
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_CATEGORY_UPDATE)
+ @PostMapping("/category/update")
+ public void categoryUpdate(@RequestBody GaoProductCategory request) {
+ TimiException.required(request, "not found category");
+ TimiException.required(request.getId(), "not found category.id");
+ GaoProductCategory category = categoryService.get(request.getId());
+ TimiException.required(category, "not found category");
+ storeScopeService.checkStoreId(category.getStoreId());
+ request.setStoreId(category.getStoreId());
+ categoryService.update(request);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_CATEGORY_UPDATE)
+ @PostMapping("/category/sort")
+ public void categorySort(@RequestBody List idList) {
+ categoryService.sort(idList);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_CATEGORY_UPDATE)
+ @PostMapping("/category/disable")
+ public void categoryDisable(@RequestParam String id) {
+ categoryService.disable(id);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_READ)
+ @PostMapping("/list")
+ public PageResult list(@RequestBody GaoProductPage page) {
+ TimiException.required(page, "not found product page");
+ page.setStoreId(storeScopeService.resolveStoreId(page.getStoreId()));
+ return productService.pageByQuery(page);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_READ)
+ @PostMapping("/detail")
+ public GaoProduct detail(@RequestParam String id) {
+ GaoProduct result = productService.detail(id);
+ storeScopeService.checkStoreId(result.getStoreId());
+ return result;
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_CREATE)
+ @PostMapping("/create")
+ public String create(@RequestBody GaoProduct product) {
+ TimiException.required(product, "not found product");
+ product.setStoreId(storeScopeService.resolveStoreId(product.getStoreId()));
+ productService.create(product);
+ return product.getId();
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_UPDATE)
+ @PostMapping("/update")
+ public void update(@RequestBody GaoProduct request) {
+ TimiException.required(request, "not found product");
+ TimiException.required(request.getId(), "not found product.id");
+ GaoProduct product = productService.get(request.getId());
+ TimiException.required(product, "not found product");
+ storeScopeService.checkStoreId(product.getStoreId());
+ request.setStoreId(product.getStoreId());
+ productService.update(request);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_UPDATE)
+ @PostMapping("/disable")
+ public void disable(@RequestParam String id) {
+ GaoProduct product = productService.get(id);
+ TimiException.required(product, "not found product");
+ storeScopeService.checkStoreId(product.getStoreId());
+ productService.disable(id);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_READ)
+ @PostMapping("/sku/detail")
+ public GaoProductSku skuDetail(@RequestParam String id) {
+ GaoProductSku result = skuService.detail(id);
+ storeScopeService.checkStoreId(result.getStoreId());
+ return result;
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_CREATE)
+ @PostMapping("/sku/create")
+ public String skuCreate(@RequestBody GaoProductSku sku) {
+ TimiException.required(sku, "not found product sku");
+ GaoProduct product = productService.get(sku.getProductId());
+ TimiException.required(product, "not found product");
+ storeScopeService.checkStoreId(product.getStoreId());
+ skuService.create(sku);
+ return sku.getId();
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_UPDATE)
+ @PostMapping("/sku/update")
+ public void skuUpdate(@RequestBody GaoProductSku request) {
+ TimiException.required(request, "not found product sku");
+ TimiException.required(request.getId(), "not found product sku.id");
+ GaoProductSku sku = skuService.get(request.getId());
+ TimiException.required(sku, "not found product sku");
+ storeScopeService.checkStoreId(sku.getStoreId());
+ request.setProductId(sku.getProductId());
+ skuService.update(request);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_UPDATE)
+ @PostMapping("/sku/disable")
+ public void skuDisable(@RequestParam String id) {
+ GaoProductSku sku = skuService.get(id);
+ TimiException.required(sku, "not found product sku");
+ storeScopeService.checkStoreId(sku.getStoreId());
+ skuService.disable(id);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_READ)
+ @PostMapping("/spec/list")
+ public List specList(@RequestParam String productId) {
+ GaoProduct product = productService.get(productId);
+ TimiException.required(product, "not found product");
+ storeScopeService.checkStoreId(product.getStoreId());
+ return specService.listByProductId(productId);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_UPDATE)
+ @PostMapping("/spec/create")
+ public String specCreate(@RequestBody GaoProductSpec spec) {
+ TimiException.required(spec, "not found product spec");
+ GaoProduct product = productService.get(spec.getProductId());
+ TimiException.required(product, "not found product");
+ storeScopeService.checkStoreId(product.getStoreId());
+ specService.createSpec(spec);
+ return spec.getId();
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_UPDATE)
+ @PostMapping("/spec/value/create")
+ public String specValueCreate(@RequestBody GaoProductSpecValue value) {
+ TimiException.required(value, "not found product spec value");
+ String productId = specService.getProductIdBySpecId(value.getSpecId());
+ TimiException.required(productId, "规格项不存在");
+ GaoProduct product = productService.get(productId);
+ TimiException.required(product, "not found product");
+ storeScopeService.checkStoreId(product.getStoreId());
+ specService.createValue(value);
+ return value.getId();
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_READ)
+ @PostMapping("/sellable/list")
+ public List sellableList(@RequestBody GaoProductPage page) {
+ TimiException.required(page, "not found product page");
+ page.setStoreId(storeScopeService.resolveStoreId(page.getStoreId()));
+ return productService.listSellable(page);
+ }
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoProductSkuChartController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoProductSkuChartController.java
new file mode 100644
index 0000000..7f6d42c
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoProductSkuChartController.java
@@ -0,0 +1,106 @@
+package com.imyeyu.api.modules.gao.controller;
+
+import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission;
+import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
+import com.imyeyu.api.modules.gao.service.GaoProductSkuChartService;
+import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
+import com.imyeyu.api.modules.gao.vo.GaoProductChartReq;
+import com.imyeyu.api.modules.gao.vo.GaoProductSkuFulfillmentDailyStatView;
+import com.imyeyu.api.modules.gao.vo.GaoProductSkuFulfillmentRankView;
+import com.imyeyu.api.modules.gao.vo.GaoProductSkuSalesDailyStatView;
+import com.imyeyu.api.modules.gao.vo.GaoProductSkuSalesRankView;
+import com.imyeyu.api.modules.gao.vo.GaoProductSkuStockHealthView;
+import com.imyeyu.api.modules.gao.vo.GaoProductSkuStockMovementDailyStatView;
+import com.imyeyu.api.modules.gao.vo.GaoProductSkuUnitSalesRankView;
+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;
+
+/// GAO SKU 图表接口
+///
+/// @author Codex
+/// @since 2026-09-07
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/gao/product/sku/chart")
+public class GaoProductSkuChartController {
+
+ private final GaoProductSkuChartService service;
+ private final GaoStoreScopeService storeScopeService;
+
+ /// 查询 SKU 每日销售统计
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/sales/daily")
+ public List salesDaily(@RequestBody GaoProductChartReq req) {
+ return service.salesDaily(normalize(req));
+ }
+
+ /// 查询 SKU 销售排行
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/sales/rank")
+ public List salesRank(@RequestBody GaoProductChartReq req) {
+ return service.salesRank(normalize(req));
+ }
+
+ /// 查询 SKU 单位销售排行
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/sales/unit/rank")
+ public List unitSalesRank(@RequestBody GaoProductChartReq req) {
+ return service.unitSalesRank(normalize(req));
+ }
+
+ /// 查询 SKU 每日履约统计
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/fulfillment/daily")
+ public List fulfillmentDaily(@RequestBody GaoProductChartReq req) {
+ return service.fulfillmentDaily(normalize(req));
+ }
+
+ /// 查询 SKU 履约进度排行
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/fulfillment/rank")
+ public List fulfillmentRank(@RequestBody GaoProductChartReq req) {
+ return service.fulfillmentRank(normalize(req));
+ }
+
+ /// 查询 SKU 每日库存变动统计
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/stock/movement/daily")
+ public List stockMovementDaily(@RequestBody GaoProductChartReq req) {
+ return service.stockMovementDaily(normalize(req));
+ }
+
+ /// 查询 SKU 库存健康度
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/stock/health")
+ public List stockHealth(@RequestBody GaoProductChartReq req) {
+ return service.stockHealth(normalize(req));
+ }
+
+ private GaoProductChartReq normalize(GaoProductChartReq req) {
+ TimiException.required(req, "SKU 图表请求不能为空");
+ req.setStoreId(storeScopeService.resolveStoreId(req.getStoreId()));
+ return req;
+ }
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoProductStockController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoProductStockController.java
new file mode 100644
index 0000000..b8e4777
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoProductStockController.java
@@ -0,0 +1,140 @@
+package com.imyeyu.api.modules.gao.controller;
+
+import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission;
+import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
+import com.imyeyu.api.modules.gao.entity.GaoProductStock;
+import com.imyeyu.api.modules.gao.entity.GaoProductStockRecord;
+import com.imyeyu.api.modules.gao.service.GaoProductStockService;
+import com.imyeyu.api.modules.gao.service.GaoProductStockRecordService;
+import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
+import com.imyeyu.api.modules.gao.vo.GaoProductStockChangeRequest;
+import com.imyeyu.api.modules.gao.vo.GaoProductStockManualRevokeRequest;
+import com.imyeyu.api.modules.gao.vo.GaoProductStockPage;
+import com.imyeyu.api.modules.gao.vo.GaoProductStockProductView;
+import com.imyeyu.api.modules.gao.vo.GaoProductStockRecordPage;
+import com.imyeyu.java.bean.timi.TimiException;
+import com.imyeyu.spring.annotation.AOPLog;
+import com.imyeyu.spring.annotation.RequestRateLimit;
+import com.imyeyu.spring.bean.PageResult;
+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.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+/// GAO 产品库存接口
+///
+/// @author Codex
+/// @since 2026-09-02
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/gao/product/stock")
+public class GaoProductStockController {
+
+ private final GaoProductStockService stockService;
+ private final GaoProductStockRecordService recordService;
+ private final GaoStoreScopeService storeScopeService;
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_STOCK_READ)
+ @PostMapping("/list")
+ public PageResult list(@RequestBody GaoProductStockPage page) {
+ TimiException.required(page, "not found stock page");
+ page.setStoreId(storeScopeService.resolveStoreId(page.getStoreId()));
+ return stockService.pageByQuery(page);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_STOCK_READ)
+ @PostMapping("/product-list")
+ public PageResult productList(@RequestBody GaoProductStockPage page) {
+ TimiException.required(page, "not found stock product page");
+ page.setStoreId(storeScopeService.resolveStoreId(page.getStoreId()));
+ return stockService.pageProductByQuery(page);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_STOCK_READ)
+ @PostMapping("/detail")
+ public GaoProductStock detail(@RequestParam(required = false) String storeId, @RequestParam String skuId) {
+ return stockService.detail(storeScopeService.resolveStoreId(storeId), skuId);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_STOCK_CREATE)
+ @PostMapping("/initial-in")
+ public GaoProductStock initialIn(@RequestBody GaoProductStockChangeRequest request) {
+ return stockService.initialIn(normalize(request));
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_STOCK_CREATE)
+ @PostMapping("/manual-in")
+ public GaoProductStock manualIn(@RequestBody GaoProductStockChangeRequest request) {
+ return stockService.manualIn(normalize(request));
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_STOCK_CREATE)
+ @PostMapping("/manual-out")
+ public GaoProductStock manualOut(@RequestBody GaoProductStockChangeRequest request) {
+ return stockService.manualOut(normalize(request));
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_STOCK_MANUAL_REVOKE)
+ @PostMapping("/manual-revoke")
+ public GaoProductStock manualRevoke(@RequestBody GaoProductStockManualRevokeRequest request) {
+ return stockService.manualRevoke(request);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_STOCK_CREATE)
+ @PostMapping("/stocktake-gain")
+ public GaoProductStock stocktakeGain(@RequestBody GaoProductStockChangeRequest request) {
+ return stockService.stocktakeGain(normalize(request));
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_STOCK_CREATE)
+ @PostMapping("/stocktake-loss")
+ public GaoProductStock stocktakeLoss(@RequestBody GaoProductStockChangeRequest request) {
+ return stockService.stocktakeLoss(normalize(request));
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_STOCK_READ)
+ @PostMapping("/record/list")
+ public PageResult recordList(@RequestBody GaoProductStockRecordPage page) {
+ TimiException.required(page, "not found stock record page");
+ page.setStoreId(storeScopeService.resolveStoreId(page.getStoreId()));
+ return recordService.pageByQuery(page);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.PRODUCT_STOCK_READ)
+ @PostMapping("/record/detail")
+ public GaoProductStockRecord recordDetail(@RequestParam String id) {
+ GaoProductStockRecord result = recordService.detail(id);
+ storeScopeService.checkStoreId(result.getStoreId());
+ return result;
+ }
+
+ private GaoProductStockChangeRequest normalize(GaoProductStockChangeRequest request) {
+ TimiException.required(request, "not found stock change request");
+ request.setStoreId(storeScopeService.resolveStoreId(request.getStoreId()));
+ return request;
+ }
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoSalesOrderChartController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoSalesOrderChartController.java
new file mode 100644
index 0000000..a01ec67
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoSalesOrderChartController.java
@@ -0,0 +1,156 @@
+package com.imyeyu.api.modules.gao.controller;
+
+import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission;
+import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
+import com.imyeyu.api.modules.gao.service.GaoSalesOrderChartService;
+import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
+import com.imyeyu.api.modules.gao.vo.GaoCustomerOrderProductRankView;
+import com.imyeyu.api.modules.gao.vo.GaoCustomerOrderRankView;
+import com.imyeyu.api.modules.gao.vo.GaoCustomerOrderSkuRankView;
+import com.imyeyu.api.modules.gao.vo.GaoCustomerRepeatDailyStatView;
+import com.imyeyu.api.modules.gao.vo.GaoCustomerRepeatIntervalStatView;
+import com.imyeyu.api.modules.gao.vo.GaoSalesOrderChartReq;
+import com.imyeyu.api.modules.gao.vo.GaoSalesOrderCalendarStatView;
+import com.imyeyu.api.modules.gao.vo.GaoSalesOrderConsumptionDailyStatView;
+import com.imyeyu.api.modules.gao.vo.GaoSalesOrderDailyStatView;
+import com.imyeyu.api.modules.gao.vo.GaoSalesOrderPaymentStatView;
+import com.imyeyu.api.modules.gao.vo.GaoSalesOrderReceivableStatView;
+import com.imyeyu.api.modules.gao.vo.GaoSalesOrderStatusStatView;
+import com.imyeyu.api.modules.gao.vo.GaoSalesOrderTypeStatView;
+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;
+
+/// GAO 销售订单图表接口
+///
+/// @author Codex
+/// @since 2026-09-06
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/gao/sales/order/chart")
+public class GaoSalesOrderChartController {
+
+ private final GaoSalesOrderChartService service;
+ private final GaoStoreScopeService storeScopeService;
+
+ /// 查询销售订单每日统计
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/daily")
+ public List daily(@RequestBody GaoSalesOrderChartReq req) {
+ return service.daily(normalize(req));
+ }
+
+ /// 查询销售订单类型统计
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/type")
+ public List type(@RequestBody GaoSalesOrderChartReq req) {
+ return service.type(normalize(req));
+ }
+
+ /// 查询销售订单付款状态统计
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/payment")
+ public List payment(@RequestBody GaoSalesOrderChartReq req) {
+ return service.payment(normalize(req));
+ }
+
+ /// 查询客户订单排行
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/customer/rank")
+ public List customerRank(@RequestBody GaoSalesOrderChartReq req) {
+ return service.customerRank(normalize(req));
+ }
+
+ /// 查询指定客户订单产品排行
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/customer/product/rank")
+ public List customerProductRank(@RequestBody GaoSalesOrderChartReq req) {
+ return service.customerProductRank(normalize(req));
+ }
+
+ /// 查询指定客户订单 SKU 排行
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/customer/sku/rank")
+ public List customerSkuRank(@RequestBody GaoSalesOrderChartReq req) {
+ return service.customerSkuRank(normalize(req));
+ }
+
+ /// 查询销售订单状态统计
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/status")
+ public List status(@RequestBody GaoSalesOrderChartReq req) {
+ return service.status(normalize(req));
+ }
+
+ /// 查询销售订单应收统计
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/receivable")
+ public GaoSalesOrderReceivableStatView receivable(@RequestBody GaoSalesOrderChartReq req) {
+ return service.receivable(normalize(req));
+ }
+
+ /// 查询客户每日有效消费统计
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/customer/consumption/daily")
+ public List consumptionDaily(@RequestBody GaoSalesOrderChartReq req) {
+ return service.consumptionDaily(normalize(req));
+ }
+
+ /// 查询客户订单日历统计
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/customer/calendar")
+ public List calendar(@RequestBody GaoSalesOrderChartReq req) {
+ return service.calendar(normalize(req));
+ }
+
+ /// 查询客户每日复购统计
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/customer/repeat/daily")
+ public List repeatDaily(@RequestBody GaoSalesOrderChartReq req) {
+ return service.repeatDaily(normalize(req));
+ }
+
+ /// 查询客户复购间隔统计
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/customer/repeat/interval")
+ public List repeatInterval(@RequestBody GaoSalesOrderChartReq req) {
+ return service.repeatInterval(normalize(req));
+ }
+
+ private GaoSalesOrderChartReq normalize(GaoSalesOrderChartReq req) {
+ TimiException.required(req, "not found sales order chart req");
+ req.setStoreId(storeScopeService.resolveStoreId(req.getStoreId()));
+ return req;
+ }
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoSalesOrderController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoSalesOrderController.java
new file mode 100644
index 0000000..1701140
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoSalesOrderController.java
@@ -0,0 +1,144 @@
+package com.imyeyu.api.modules.gao.controller;
+
+import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission;
+import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
+import com.imyeyu.api.modules.gao.entity.GaoFundRecord;
+import com.imyeyu.api.modules.gao.entity.GaoSalesOrder;
+import com.imyeyu.api.modules.gao.service.GaoOrderPaymentService;
+import com.imyeyu.api.modules.gao.service.GaoSalesOrderService;
+import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
+import com.imyeyu.api.modules.gao.vo.GaoDirectProductOrderCreateRequest;
+import com.imyeyu.api.modules.gao.vo.GaoOrderPaymentActionRequest;
+import com.imyeyu.api.modules.gao.vo.GaoOrderPaymentNoRefundRequest;
+import com.imyeyu.api.modules.gao.vo.GaoOrderPaymentReceiveRequest;
+import com.imyeyu.api.modules.gao.vo.GaoOrderPaymentRefundRequest;
+import com.imyeyu.api.modules.gao.vo.GaoOrderPaymentRevokeRequest;
+import com.imyeyu.api.modules.gao.vo.GaoSalesOrderCancelRequest;
+import com.imyeyu.api.modules.gao.vo.GaoSalesOrderPage;
+import com.imyeyu.api.modules.gao.vo.GaoSalesOrderUpdateRequest;
+import com.imyeyu.java.bean.timi.TimiException;
+import com.imyeyu.spring.annotation.AOPLog;
+import com.imyeyu.spring.annotation.RequestRateLimit;
+import com.imyeyu.spring.bean.PageResult;
+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.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+/// GAO 销售订单和资金接口
+///
+/// @author Codex
+/// @since 2026-09-02
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/gao/sales/order")
+public class GaoSalesOrderController {
+
+ private final GaoSalesOrderService service;
+ private final GaoOrderPaymentService paymentService;
+ private final GaoStoreScopeService storeScopeService;
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.SALES_ORDER_READ)
+ @PostMapping("/list")
+ public PageResult list(@RequestBody GaoSalesOrderPage page) {
+ TimiException.required(page, "not found sales order page");
+ page.setStoreId(storeScopeService.resolveStoreId(page.getStoreId()));
+ return service.pageByQuery(page);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.SALES_ORDER_READ)
+ @PostMapping("/detail")
+ public GaoSalesOrder detail(@RequestParam String id) {
+ return service.detail(id);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.SALES_ORDER_CREATE)
+ @PostMapping("/direct-product/create")
+ public GaoSalesOrder createDirectProduct(@RequestBody GaoDirectProductOrderCreateRequest request) {
+ TimiException.required(request, "not found direct product order request");
+ request.setStoreId(storeScopeService.resolveStoreId(request.getStoreId()));
+ return service.createDirectProductOrder(request);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.SALES_ORDER_UPDATE)
+ @PostMapping("/update")
+ public GaoSalesOrder update(@RequestBody GaoSalesOrderUpdateRequest request) {
+ TimiException.required(request, "not found sales order update request");
+ TimiException.required(request.getId(), "not found sales order.id");
+ return service.updateRemarkAndAttachments(request);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.SALES_ORDER_CANCEL)
+ @PostMapping("/cancel")
+ public GaoSalesOrder cancel(@RequestBody GaoSalesOrderCancelRequest request) {
+ return service.cancel(request);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.FUND_RECEIVE_CREATE)
+ @PostMapping("/payment/receive")
+ public GaoFundRecord receive(@RequestBody GaoOrderPaymentReceiveRequest request) {
+ return paymentService.receive(request);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.FUND_RECEIVE_VERIFY)
+ @PostMapping("/payment/verify")
+ public GaoFundRecord verify(@RequestParam String id) {
+ return paymentService.verify(id);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.FUND_RECEIVE_VERIFY)
+ @PostMapping("/payment/reject")
+ public GaoFundRecord reject(@RequestBody GaoOrderPaymentActionRequest request) {
+ return paymentService.reject(request);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.FUND_RECEIVE_CREATE)
+ @PostMapping("/payment/cancel")
+ public GaoFundRecord cancelPayment(@RequestBody GaoOrderPaymentActionRequest request) {
+ return paymentService.cancel(request);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.FUND_REFUND)
+ @PostMapping("/refund")
+ public GaoFundRecord refund(@RequestBody GaoOrderPaymentRefundRequest request) {
+ return paymentService.refund(request);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.FUND_REFUND)
+ @PostMapping("/refund/confirm-none")
+ public GaoFundRecord confirmNoRefund(@RequestBody GaoOrderPaymentNoRefundRequest request) {
+ return paymentService.confirmNoRefund(request);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.FUND_REVOKE)
+ @PostMapping("/fund/revoke")
+ public GaoFundRecord revoke(@RequestBody GaoOrderPaymentRevokeRequest request) {
+ return paymentService.revoke(request);
+ }
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreController.java
index 85616cc..bf1086d 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreController.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreController.java
@@ -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.bean.GaoPermissionCode;
+import com.imyeyu.api.modules.gao.entity.GaoEvent;
import com.imyeyu.api.modules.gao.entity.GaoStore;
import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
import com.imyeyu.api.modules.gao.service.GaoStoreService;
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomerConsumptionRecord.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomerConsumptionRecord.java
new file mode 100644
index 0000000..74d3ff0
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomerConsumptionRecord.java
@@ -0,0 +1,44 @@
+package com.imyeyu.api.modules.gao.entity;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.imyeyu.spring.entity.UUIDEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/// GAO 客户有效消费事实
+///
+/// @author Codex
+/// @since 2026-09-04
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class GaoCustomerConsumptionRecord extends UUIDEntity {
+
+ /// 消费来源类型
+ public enum SourceType {
+ /// 迁移的旧消费事件
+ LEGACY_EVENT,
+ /// 销售订单全额实收
+ SALES_ORDER
+ }
+
+ /// 消费事实状态
+ public enum Status {
+ /// 有效并参与累计
+ ACTIVE,
+ /// 已失效,不参与累计
+ REVOKED
+ }
+
+ private String storeId;
+ private String customerId;
+ private SourceType sourceType;
+ private String sourceId;
+ private String salesOrderId;
+ private GaoSalesOrder.OrderType orderType;
+ private Long amount;
+ private Long effectiveAt;
+ private Status status;
+ private Long revokedAt;
+ private String revokeReason;
+ private JsonNode sourcePayload;
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomerPackage.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomerPackage.java
index facd096..2425485 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomerPackage.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomerPackage.java
@@ -16,100 +16,38 @@ import java.util.List;
@EqualsAndHashCode(callSuper = true)
public class GaoCustomerPackage extends UUIDEntity {
- /// 客户套餐状态
- public enum Status {
-
- /// 履约中
- ACTIVE,
-
- /// 已全部履约
- FULFILLED,
-
- /// 已取消
- CANCELLED
- }
-
- /// 付款状态
- public enum PaymentStatus {
-
- /// 未付款
- UNPAID,
-
- /// 部分付款
- PARTIALLY_PAID,
-
- /// 已付清
- PAID
- }
-
- /// 退款状态
- public enum RefundStatus {
-
- /// 无需退款
- NOT_REQUIRED,
-
- /// 待退款或待确认无需退款
- PENDING,
-
- /// 已完成退款处理
- REFUNDED
- }
-
/// 门店 ID
private String storeId;
+ /// 销售订单 ID
+ private String salesOrderId;
+
/// 客户 ID
private String customerId;
/// 来源套餐配置 ID
private String packageConfigId;
- /// 购买事件登记 ID
- private String purchaseEventRecordId;
-
- /// 购买幂等请求号
- private String requestNo;
-
/// 套餐名称快照
private String packageNameSnapshot;
/// 标准价快照,单位分
private Long standardAmountSnapshot;
- /// 实际成交价,单位分
- private Long saleAmount;
+ /// 订单投影状态
+ @Transient
+ private GaoSalesOrder.Status status;
- /// 待核销收款合计,单位分
- private Long pendingPaidAmount;
-
- /// 累计有效收款,单位分
- private Long paidAmount;
-
- /// 累计有效退款,单位分
- private Long refundedAmount;
-
- /// 套餐状态
- private Status status;
-
- /// 付款状态
- private PaymentStatus paymentStatus;
-
- /// 退款状态
- private RefundStatus refundStatus;
-
- /// 购买时间
- private Long purchasedAt;
-
- /// 购买登记人
- private String operatorUserId;
-
- /// 取消时间
+ /// 订单投影取消时间
+ @Transient
private Long cancelledAt;
- /// 取消操作人
+ /// 订单投影取消操作人 ID
+ @Transient
private String cancelOperatorUserId;
- /// 取消原因
+ /// 订单投影取消原因
+ @Transient
private String cancelReason;
/// 备注
@@ -119,27 +57,19 @@ public class GaoCustomerPackage extends UUIDEntity {
@Transient
private GaoCustomer customer;
- /// 购买事件登记
- @Transient
- private GaoEventRecord purchaseEventRecord;
-
- /// 购买操作人
- @Transient
- private User operator;
-
/// 取消操作人
@Transient
private User cancelOperator;
- /// 产品权益列表
+ /// 关联销售订单
@Transient
- private List itemList;
+ private GaoSalesOrder salesOrder;
- /// 发放历史
+ /// 关联订单可履约明细投影
@Transient
- private List deliveryList;
+ private List itemList;
- /// 资金历史
+ /// 统一履约历史
@Transient
- private List fundRecordList;
+ private List fulfillmentList;
}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomerPackageItem.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomerPackageItem.java
deleted file mode 100644
index 450f06a..0000000
--- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomerPackageItem.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package com.imyeyu.api.modules.gao.entity;
-
-import com.imyeyu.spring.annotation.table.Transient;
-import com.imyeyu.spring.entity.UUIDEntity;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-
-import java.math.BigDecimal;
-
-/// GAO 客户套餐产品权益快照
-///
-/// @author Codex
-/// @since 2026-08-31
-@Data
-@EqualsAndHashCode(callSuper = true)
-public class GaoCustomerPackageItem extends UUIDEntity {
-
- /// 客户套餐 ID
- private String customerPackageId;
-
- /// 产品名称快照
- private String productNameSnapshot;
-
- /// 产品单位快照
- private String unitSnapshot;
-
- /// 总权益数量
- private BigDecimal totalQuantity;
-
- /// 待核销占用数量
- private BigDecimal reservedQuantity;
-
- /// 已核销净发放数量
- private BigDecimal deliveredQuantity;
-
- /// 排序值
- private Integer sort;
-
- /// 备注
- private String remark;
-
- /// 待核销数量
- @Transient
- private BigDecimal pendingQuantity;
-
- /// 当前可发数量
- @Transient
- private BigDecimal availableQuantity;
-
- /// 权益剩余数量
- @Transient
- private BigDecimal remainingQuantity;
-}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEvent.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEvent.java
index c48b270..f9c087b 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEvent.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEvent.java
@@ -25,7 +25,10 @@ public class GaoEvent extends UUIDEntity {
GENERAL,
/// 购买产品套餐
- PACKAGE_PURCHASE
+ PACKAGE_PURCHASE,
+
+ /// 已退役的历史消费事件,仅保留归档和溯源
+ LEGACY_CONSUMPTION
}
///
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoFundRecord.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoFundRecord.java
index 7292ca0..20390d0 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoFundRecord.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoFundRecord.java
@@ -9,7 +9,7 @@ import lombok.EqualsAndHashCode;
import java.util.List;
-/// GAO 通用资金操作流水
+/// GAO 销售订单资金操作流水
///
/// @author Codex
/// @since 2026-08-31
@@ -20,8 +20,8 @@ public class GaoFundRecord extends UUIDEntity {
/// 资金业务类型
public enum BizType {
- /// 客户套餐
- CUSTOMER_PACKAGE
+ /// 销售订单
+ SALES_ORDER
}
/// 资金操作类型
@@ -101,6 +101,17 @@ public class GaoFundRecord extends UUIDEntity {
/// 备注
private String remark;
+ /// 关联的冲正流水 ID
+ @Transient
+ private String revokeRecordId;
+
+ /// 关联销售订单总价,单位分
+ @Transient
+ private Long salesOrderAmount;
+
+ /// 流水发生时关联销售订单剩余待收款金额快照,单位分
+ private Long salesOrderUnpaidAmountSnapshot;
+
/// 操作人
@Transient
private User operator;
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPackageDelivery.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoOrderFulfillment.java
similarity index 53%
rename from src/main/java/com/imyeyu/api/modules/gao/entity/GaoPackageDelivery.java
rename to src/main/java/com/imyeyu/api/modules/gao/entity/GaoOrderFulfillment.java
index 34f2b6e..48f260d 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPackageDelivery.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoOrderFulfillment.java
@@ -2,34 +2,29 @@ package com.imyeyu.api.modules.gao.entity;
import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.user.entity.User;
+import com.imyeyu.spring.annotation.table.AutoUUID;
+import com.imyeyu.spring.annotation.table.Id;
import com.imyeyu.spring.annotation.table.Transient;
-import com.imyeyu.spring.entity.UUIDEntity;
+import com.imyeyu.spring.entity.Creatable;
+import com.imyeyu.spring.entity.IDEntity;
+import com.imyeyu.spring.entity.Updatable;
import lombok.Data;
-import lombok.EqualsAndHashCode;
import java.util.List;
-/// GAO 套餐发放操作
+/// GAO 统一履约单
///
/// @author Codex
-/// @since 2026-08-31
+/// @since 2026-09-02
@Data
-@EqualsAndHashCode(callSuper = true)
-public class GaoPackageDelivery extends UUIDEntity {
+public class GaoOrderFulfillment implements IDEntity, Creatable, Updatable {
- /// 发放操作类型
- public enum OperationType {
-
- /// 正常发放
- DELIVER,
-
- /// 整单冲正
- REVOKE
- }
-
- /// 发放状态
+ /// 履约状态
public enum Status {
+ /// 等待库存
+ WAITING_STOCK,
+
/// 待核销
PENDING,
@@ -42,12 +37,16 @@ public class GaoPackageDelivery extends UUIDEntity {
/// 提交人已撤销
CANCELLED,
- /// 已核销后被整单冲正
+ /// 已核销后冲正
REVOKED
}
- /// 客户套餐 ID
- private String customerPackageId;
+ @Id
+ @AutoUUID
+ private String id;
+
+ /// 销售订单 ID
+ private String salesOrderId;
/// 门店 ID
private String storeId;
@@ -55,10 +54,7 @@ public class GaoPackageDelivery extends UUIDEntity {
/// 客户 ID
private String customerId;
- /// 操作类型
- private OperationType operationType;
-
- /// 发放状态
+ /// 履约状态
private Status status;
/// 客户端幂等请求号
@@ -67,7 +63,7 @@ public class GaoPackageDelivery extends UUIDEntity {
/// 提交人
private String operatorUserId;
- /// 实际发放时间
+ /// 实际履约时间
private Long deliveredAt;
/// 核销或拒绝操作人
@@ -76,8 +72,8 @@ public class GaoPackageDelivery extends UUIDEntity {
/// 核销或拒绝时间
private Long verifiedAt;
- /// 冲正单指向原发放单
- private String sourceDeliveryId;
+ /// 冲正单指向原履约单
+ private String sourceFulfillmentId;
/// 拒绝、撤销或冲正原因
private String reason;
@@ -85,21 +81,33 @@ public class GaoPackageDelivery extends UUIDEntity {
/// 备注
private String remark;
- /// 操作人
+ private Long createdAt;
+
+ private Long updatedAt;
+
+ /// 提交人
@Transient
private User operator;
+ /// 客户
+ @Transient
+ private GaoCustomer customer;
+
/// 核销或拒绝操作人
@Transient
private User verifier;
- /// 原发放单
+ /// 原履约单
@Transient
- private GaoPackageDelivery sourceDelivery;
+ private GaoOrderFulfillment sourceFulfillment;
- /// 发放明细
+ /// 履约明细
@Transient
- private List itemList;
+ private List itemList;
+
+ /// 库存处理流水
+ @Transient
+ private List stockRecordList;
/// 附件
@Transient
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoOrderFulfillmentItem.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoOrderFulfillmentItem.java
new file mode 100644
index 0000000..645efbf
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoOrderFulfillmentItem.java
@@ -0,0 +1,85 @@
+package com.imyeyu.api.modules.gao.entity;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.imyeyu.api.modules.common.entity.Attachment;
+import com.imyeyu.spring.annotation.table.AutoUUID;
+import com.imyeyu.spring.annotation.table.Id;
+import com.imyeyu.spring.annotation.table.Transient;
+import com.imyeyu.spring.entity.Creatable;
+import com.imyeyu.spring.entity.IDEntity;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/// GAO 统一履约明细
+///
+/// @author Codex
+/// @since 2026-09-02
+@Data
+public class GaoOrderFulfillmentItem implements IDEntity, Creatable {
+
+ @Id
+ @AutoUUID
+ private String id;
+
+ /// 履约单 ID
+ private String fulfillmentId;
+
+ /// SKU ID
+ private String skuId;
+
+ /// SKU 单位 ID
+ private String skuUnitId;
+
+ /// 产品图片附件
+ @Transient
+ private List attachmentList;
+
+ /// 来源销售订单明细 ID
+ private String sourceOrderItemId;
+
+ /// 本次履约数量
+ private BigDecimal quantity;
+
+ /// 基础单位数量
+ private BigDecimal baseQuantity;
+
+ /// 操作前待履约数量
+ private BigDecimal reservedBefore;
+
+ /// 操作后待履约数量
+ private BigDecimal reservedAfter;
+
+ /// 操作前已履约数量
+ private BigDecimal fulfilledBefore;
+
+ /// 操作后已履约数量
+ private BigDecimal fulfilledAfter;
+
+ /// 产品名称快照
+ private String productNameSnapshot;
+
+ /// 产品 ID 快照
+ private String productIdSnapshot;
+
+ /// SKU 编码快照
+ private String skuCodeSnapshot;
+
+ /// SKU 名称快照
+ private String skuNameSnapshot;
+
+ /// 规格摘要快照
+ private String specSummarySnapshot;
+
+ /// SKU 规格结构快照,数组元素包含 specId、specName、specValueId、specValueName
+ private JsonNode skuSpecSnapshot;
+
+ /// 单位快照
+ private String unitSnapshot;
+
+ /// 基础单位数量系数快照
+ private BigDecimal baseQuantityPerUnitSnapshot;
+
+ private Long createdAt;
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPackageConfigItem.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPackageConfigItem.java
index 205d250..cb40d94 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPackageConfigItem.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPackageConfigItem.java
@@ -1,5 +1,6 @@
package com.imyeyu.api.modules.gao.entity;
+import com.imyeyu.spring.annotation.table.Transient;
import com.imyeyu.spring.entity.UUIDEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -17,11 +18,32 @@ public class GaoPackageConfigItem extends UUIDEntity {
/// 套餐配置 ID
private String packageConfigId;
- /// 产品名称
- private String productName;
+ /// SKU ID
+ private String skuId;
- /// 产品单位
- private String unit;
+ /// SKU 单位 ID
+ private String skuUnitId;
+
+ /// 产品名称快照
+ private String productNameSnapshot;
+
+ /// 产品 ID 快照
+ private String productIdSnapshot;
+
+ /// SKU 编码快照
+ private String skuCodeSnapshot;
+
+ /// SKU 名称快照
+ private String skuNameSnapshot;
+
+ /// 规格摘要快照
+ private String specSummarySnapshot;
+
+ /// 单位快照
+ private String unitSnapshot;
+
+ /// 基础单位数量系数快照
+ private BigDecimal baseQuantityPerUnitSnapshot;
/// 默认数量
private BigDecimal defaultQuantity;
@@ -31,4 +53,8 @@ public class GaoPackageConfigItem extends UUIDEntity {
/// 备注
private String remark;
+
+ /// SKU 详情
+ @Transient
+ private GaoProductSku sku;
}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPackageDeliveryItem.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPackageDeliveryItem.java
deleted file mode 100644
index f1fab81..0000000
--- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPackageDeliveryItem.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.imyeyu.api.modules.gao.entity;
-
-import com.imyeyu.spring.entity.UUIDEntity;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-
-import java.math.BigDecimal;
-
-/// GAO 套餐发放产品明细
-///
-/// @author Codex
-/// @since 2026-08-31
-@Data
-@EqualsAndHashCode(callSuper = true)
-public class GaoPackageDeliveryItem extends UUIDEntity {
-
- /// 发放单 ID
- private String deliveryId;
-
- /// 客户套餐产品 ID
- private String customerPackageItemId;
-
- /// 本次数量
- private BigDecimal quantity;
-
- /// 操作前占用数量快照
- private BigDecimal reservedBefore;
-
- /// 操作后占用数量快照
- private BigDecimal reservedAfter;
-
- /// 操作前已发数量快照
- private BigDecimal deliveredBefore;
-
- /// 操作后已发数量快照
- private BigDecimal deliveredAfter;
-
- /// 产品名称快照
- private String productNameSnapshot;
-
- /// 产品单位快照
- private String unitSnapshot;
-}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPointLedger.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPointLedger.java
index f0e6826..97a385c 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPointLedger.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPointLedger.java
@@ -17,13 +17,13 @@ public class GaoPointLedger extends UUIDEntity {
/// 流水变化类型
public enum ChangeType {
- /// 登记事件发放积分
+ /// 规则发放积分
EARN,
- /// 登记事件消费积分
+ /// 规则扣减积分
CONSUME,
- /// 删除登记记录撤销积分
+ /// 撤销积分
REVOKE,
/// 人工调整积分
@@ -40,6 +40,16 @@ public class GaoPointLedger extends UUIDEntity {
DECREASE
}
+ /// 直接触发来源类型
+ public enum TriggerSourceType {
+
+ /// 事件登记
+ EVENT_RECORD,
+
+ /// 订单首次全额实收
+ SALES_ORDER_PAID
+ }
+
/// 客户积分账户 ID
private String accountId;
@@ -67,11 +77,20 @@ public class GaoPointLedger extends UUIDEntity {
/// 命中的积分规则 ID
private String ruleId;
- /// 登记事件 ID
- private String eventId;
+ /// 直接触发来源类型
+ private TriggerSourceType triggerSourceType;
- /// 登记记录 ID
- private String eventRecordId;
+ /// 直接触发来源 ID
+ private String triggerSourceId;
+
+ /// 关联销售订单 ID
+ private String salesOrderId;
+
+ /// 实际执行 Groovy 脚本版本 ID,简单规则为空
+ private String groovyScriptVersionId;
+
+ /// 命中时的规则快照
+ private JsonNode ruleSnapshot;
/// 来源快照
private JsonNode sourcePayload;
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPointRule.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPointRule.java
index a17a27c..eba636a 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPointRule.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPointRule.java
@@ -1,6 +1,5 @@
package com.imyeyu.api.modules.gao.entity;
-import com.fasterxml.jackson.databind.JsonNode;
import com.imyeyu.spring.entity.UUIDEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -29,43 +28,47 @@ public class GaoPointRule extends UUIDEntity {
DELETED
}
- /// 触发类型
- public enum TriggerType {
+ /// 规则模式
+ public enum RuleMode {
- /// 每登记一次触发一次
- EVERY_TIME,
+ /// 简单规则,固定积分值
+ SIMPLE,
- /// 客户首次登记该事件触发一次
- FIRST_TIME,
-
- /// 客户累计登记达到 N 次后每次触发
- AFTER_COUNT_EVERY_TIME,
-
- /// 使用登记接口提交的积分值触发
- SOURCE_VALUE,
-
- /// 按来源事件数值累计分档触发
- CUSTOMER_EVENT_VALUE_TIER
+ /// Groovy 脚本规则
+ GROOVY
}
- /// 积分值模式
- public enum PointMode {
+ /// 直接触发来源类型
+ public enum TriggerSourceType {
- /// 使用规则固定积分值
- FIXED,
+ /// 事件登记
+ EVENT_RECORD,
- /// 使用登记接口提交的积分值
- SOURCE_VALUE,
+ /// 订单首次全额实收
+ SALES_ORDER_PAID
+ }
- /// 使用条件配置中的分档积分值
- TIER
+ /// 触发对象筛选类型
+ public enum TriggerRefType {
+
+ /// GAO 登记事件
+ EVENT
}
/// 门店 ID
private String storeId;
- /// 登记事件 ID
- private String eventId;
+ /// 规则模式
+ private RuleMode ruleMode;
+
+ /// 直接触发来源类型
+ private TriggerSourceType triggerSourceType;
+
+ /// 触发对象筛选类型
+ private TriggerRefType triggerRefType;
+
+ /// 触发对象筛选 ID
+ private String triggerRefId;
/// 规则名称
private String name;
@@ -73,17 +76,11 @@ public class GaoPointRule extends UUIDEntity {
/// 规则状态
private Status status;
- /// 触发类型
- private TriggerType triggerType;
-
- /// 积分值模式
- private PointMode pointMode;
-
- /// 固定积分值
+ /// 固定积分值,简单规则使用
private Long pointValue;
- /// 条件配置
- private JsonNode conditionJson;
+ /// Groovy 脚本 ID,GROOVY 模式使用
+ private String groovyScriptId;
/// 生效开始时间戳,毫秒,空为不限制
private Long beginAt;
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProduct.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProduct.java
new file mode 100644
index 0000000..6186823
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProduct.java
@@ -0,0 +1,97 @@
+package com.imyeyu.api.modules.gao.entity;
+
+import com.imyeyu.api.modules.common.entity.Attachment;
+import com.imyeyu.spring.annotation.table.Transient;
+import com.imyeyu.spring.entity.UUIDEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+/// GAO 产品
+///
+/// @author Codex
+/// @since 2026-09-02
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class GaoProduct extends UUIDEntity {
+
+ /// 产品类型
+ public enum ProductType {
+
+ /// 服务,不管理库存
+ SERVICE,
+
+ /// 实物,默认管理库存
+ GOODS,
+
+ /// 消耗品,可选择是否管理库存
+ CONSUMABLE
+ }
+
+ /// 产品附件类型
+ public enum AttachType {
+
+ /// 产品图片
+ PHOTO,
+
+ /// 其他产品文件
+ OTHER
+ }
+
+ /// 产品状态
+ public enum Status {
+
+ /// 启用
+ ACTIVE,
+
+ /// 停用
+ INACTIVE
+ }
+
+ /// 门店 ID
+ private String storeId;
+
+ /// 分类 ID
+ private String categoryId;
+
+ /// 门店内产品编码
+ private String code;
+
+ /// 产品名称
+ private String name;
+
+ /// 产品类型
+ private ProductType productType;
+
+ /// true 为启用库存管理
+ private Boolean stockEnabled;
+
+ /// 状态
+ private Status status;
+
+ /// 排序值
+ private Integer sort;
+
+ /// 备注
+ private String remark;
+
+ /// 最后修改用户 ID
+ private String updatedBy;
+
+ /// 产品分类
+ @Transient
+ private GaoProductCategory category;
+
+ /// 产品规格列表
+ @Transient
+ private List specList;
+
+ /// 产品 SKU 列表
+ @Transient
+ private List skuList;
+
+ /// 产品附件列表
+ @Transient
+ private List attachmentList;
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductCategory.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductCategory.java
new file mode 100644
index 0000000..f50546c
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductCategory.java
@@ -0,0 +1,36 @@
+package com.imyeyu.api.modules.gao.entity;
+
+import com.imyeyu.spring.entity.UUIDEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/// GAO 产品分类
+///
+/// @author Codex
+/// @since 2026-09-02
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class GaoProductCategory extends UUIDEntity {
+
+ /// 分类状态
+ public enum Status {
+
+ /// 启用
+ ACTIVE,
+
+ /// 停用
+ INACTIVE
+ }
+
+ /// 门店 ID
+ private String storeId;
+
+ /// 分类名称
+ private String name;
+
+ /// 排序值
+ private Integer sort;
+
+ /// 状态
+ private Status status;
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductSku.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductSku.java
new file mode 100644
index 0000000..732cc79
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductSku.java
@@ -0,0 +1,83 @@
+package com.imyeyu.api.modules.gao.entity;
+
+import com.imyeyu.api.modules.common.entity.Attachment;
+import com.imyeyu.spring.annotation.table.Transient;
+import com.imyeyu.spring.entity.UUIDEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/// GAO 产品 SKU
+///
+/// @author Codex
+/// @since 2026-09-06
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class GaoProductSku extends UUIDEntity {
+
+ /// SKU 状态
+ public enum Status {
+
+ /// 启用
+ ACTIVE,
+
+ /// 停用
+ INACTIVE
+ }
+
+ /// 门店 ID
+ private String storeId;
+
+ /// 产品 ID
+ private String productId;
+
+ /// 门店内 SKU 编码
+ private String code;
+
+ /// 条码
+ private String barcode;
+
+ /// SKU 展示名称
+ private String name;
+
+ /// 规格摘要
+ private String specSummary;
+
+ /// 规格值组合键
+ private String specValueKey;
+
+ /// true 为启用库存管理
+ private Boolean stockEnabled;
+
+ /// true 为允许业务数量保留小数
+ private Boolean allowDecimalQuantity;
+
+ /// 最低库存数量,基础单位
+ private BigDecimal minStockQuantity;
+
+ /// 状态
+ private Status status;
+
+ /// 排序值
+ private Integer sort;
+
+ /// 备注
+ private String remark;
+
+ /// 最后修改用户 ID
+ private String updatedBy;
+
+ /// 规格值 ID 列表
+ @Transient
+ private List specValueIdList;
+
+ /// SKU 单位列表
+ @Transient
+ private List unitList;
+
+ /// SKU 附件列表
+ @Transient
+ private List attachmentList;
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductSkuSpecValue.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductSkuSpecValue.java
new file mode 100644
index 0000000..fc5d847
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductSkuSpecValue.java
@@ -0,0 +1,29 @@
+package com.imyeyu.api.modules.gao.entity;
+
+import com.imyeyu.spring.annotation.table.AutoUUID;
+import com.imyeyu.spring.annotation.table.Id;
+import com.imyeyu.spring.entity.Creatable;
+import com.imyeyu.spring.entity.IDEntity;
+import com.imyeyu.spring.entity.UUIDEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/// GAO SKU 规格值关联
+///
+/// @author Codex
+/// @since 2026-09-06
+@Data
+public class GaoProductSkuSpecValue implements IDEntity, Creatable {
+
+ @Id
+ @AutoUUID
+ private String id;
+
+ /// SKU ID
+ private String skuId;
+
+ /// 规格值 ID
+ private String specValueId;
+
+ private Long createdAt;
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductSkuUnit.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductSkuUnit.java
new file mode 100644
index 0000000..af09197
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductSkuUnit.java
@@ -0,0 +1,40 @@
+package com.imyeyu.api.modules.gao.entity;
+
+import com.imyeyu.spring.entity.UUIDEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.math.BigDecimal;
+
+/// GAO 产品 SKU 业务单位
+///
+/// @author Codex
+/// @since 2026-09-06
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class GaoProductSkuUnit extends UUIDEntity {
+
+ /// SKU ID
+ private String skuId;
+
+ /// 单位名称
+ private String name;
+
+ /// 每个业务单位对应的基础单位数量
+ private BigDecimal baseQuantityPerUnit;
+
+ /// 该业务单位建议售价,单位分
+ private Long salePrice;
+
+ /// true 为基础库存单位
+ private Boolean isBase;
+
+ /// true 为允许采购入库
+ private Boolean allowPurchase;
+
+ /// true 为允许销售或套餐配置
+ private Boolean allowSale;
+
+ /// 排序值
+ private Integer sort;
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductSpec.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductSpec.java
new file mode 100644
index 0000000..947a1e9
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductSpec.java
@@ -0,0 +1,36 @@
+package com.imyeyu.api.modules.gao.entity;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.imyeyu.spring.annotation.table.Transient;
+import com.imyeyu.spring.entity.UUIDEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+/// GAO 产品规格项
+///
+/// @author Codex
+/// @since 2026-09-06
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class GaoProductSpec extends UUIDEntity {
+
+ /// 前端创建请求中的临时关联键
+ @Transient
+ @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
+ private String clientKey;
+
+ /// 产品 ID
+ private String productId;
+
+ /// 规格名称
+ private String name;
+
+ /// 排序值
+ private Integer sort;
+
+ /// 规格值列表
+ @Transient
+ private List valueList;
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductSpecValue.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductSpecValue.java
new file mode 100644
index 0000000..e44ac55
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductSpecValue.java
@@ -0,0 +1,33 @@
+package com.imyeyu.api.modules.gao.entity;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.imyeyu.spring.annotation.table.Transient;
+import com.imyeyu.spring.entity.UUIDEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/// GAO 产品规格值
+///
+/// @author Codex
+/// @since 2026-09-06
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class GaoProductSpecValue extends UUIDEntity {
+
+ /// 前端创建请求中的临时关联键
+ @Transient
+ @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
+ private String clientKey;
+
+ /// 规格项 ID
+ private String specId;
+
+ /// 规格值名称
+ private String name;
+
+ /// true 为启用
+ private Boolean enabled;
+
+ /// 排序值
+ private Integer sort;
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductStock.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductStock.java
new file mode 100644
index 0000000..df352b6
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductStock.java
@@ -0,0 +1,40 @@
+package com.imyeyu.api.modules.gao.entity;
+
+import com.imyeyu.spring.annotation.table.Transient;
+import com.imyeyu.spring.entity.UUIDEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.math.BigDecimal;
+
+/// GAO 产品库存余额
+///
+/// @author Codex
+/// @since 2026-09-02
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class GaoProductStock extends UUIDEntity {
+
+ /// 门店 ID
+ private String storeId;
+
+ /// SKU ID
+ private String skuId;
+
+ /// 实际库存,基础单位
+ private BigDecimal onHandQuantity;
+
+ /// 已预占库存,基础单位
+ private BigDecimal reservedQuantity;
+
+ /// 最后操作人
+ private String updatedBy;
+
+ /// SKU
+ @Transient
+ private GaoProductSku sku;
+
+ /// 可用库存,基础单位
+ @Transient
+ private BigDecimal availableQuantity;
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductStockRecord.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductStockRecord.java
new file mode 100644
index 0000000..e1f0ded
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductStockRecord.java
@@ -0,0 +1,165 @@
+package com.imyeyu.api.modules.gao.entity;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.imyeyu.api.modules.common.entity.Attachment;
+import com.imyeyu.api.modules.user.entity.User;
+import com.imyeyu.spring.annotation.table.AutoUUID;
+import com.imyeyu.spring.annotation.table.Id;
+import com.imyeyu.spring.annotation.table.Transient;
+import com.imyeyu.spring.entity.Creatable;
+import com.imyeyu.spring.entity.IDEntity;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/// GAO 库存变更流水
+///
+/// @author Codex
+/// @since 2026-09-02
+@Data
+public class GaoProductStockRecord implements IDEntity, Creatable {
+
+ /// 库存流水业务类型
+ public enum BizType {
+
+ /// 人工库存操作
+ PRODUCT_STOCK,
+
+ /// 订单履约库存操作
+ ORDER_FULFILLMENT
+ }
+
+ /// 库存操作类型
+ public enum OperationType {
+
+ /// 期初入库
+ INITIAL_IN,
+
+ /// 手工入库
+ MANUAL_IN,
+
+ /// 手工出库
+ MANUAL_OUT,
+
+ /// 手工流水冲正
+ MANUAL_REVOKE,
+
+ /// 履约预占
+ ORDER_RESERVE,
+
+ /// 履约释放预占
+ ORDER_RELEASE,
+
+ /// 履约出库
+ ORDER_OUT,
+
+ /// 履约冲正入库
+ ORDER_REVOKE,
+
+ /// 盘盈
+ STOCKTAKE_GAIN,
+
+ /// 盘亏
+ STOCKTAKE_LOSS
+ }
+
+ @Id
+ @AutoUUID
+ private String id;
+
+ /// 门店 ID
+ private String storeId;
+
+ /// SKU ID
+ private String skuId;
+
+ /// SKU 单位 ID
+ private String skuUnitId;
+
+ /// 产品名称快照
+ private String productNameSnapshot;
+
+ /// 产品 ID 快照
+ private String productIdSnapshot;
+
+ /// SKU 编码快照
+ private String skuCodeSnapshot;
+
+ /// SKU 名称快照
+ private String skuNameSnapshot;
+
+ /// 规格摘要快照
+ private String specSummarySnapshot;
+
+ /// SKU 规格结构快照,数组元素包含 specId、specName、specValueId、specValueName
+ private JsonNode skuSpecSnapshot;
+
+ /// 单位名称快照
+ private String unitSnapshot;
+
+ /// 基础单位数量系数快照
+ private BigDecimal baseQuantityPerUnitSnapshot;
+
+ /// 业务数量
+ private BigDecimal quantity;
+
+ /// 基础单位数量
+ private BigDecimal baseQuantity;
+
+ /// 操作类型
+ private OperationType operationType;
+
+ /// 实际库存增量
+ private BigDecimal onHandDelta;
+
+ /// 预占库存增量
+ private BigDecimal reservedDelta;
+
+ /// 操作前实际库存
+ private BigDecimal onHandBefore;
+
+ /// 操作后实际库存
+ private BigDecimal onHandAfter;
+
+ /// 操作前预占库存
+ private BigDecimal reservedBefore;
+
+ /// 操作后预占库存
+ private BigDecimal reservedAfter;
+
+ /// 来源业务类型
+ private BizType bizType;
+
+ /// 来源业务 ID
+ private String bizId;
+
+ /// 冲正来源流水 ID
+ private String sourceRecordId;
+
+ /// 关联的冲正流水 ID
+ @Transient
+ private String revokeRecordId;
+
+ /// 幂等请求号
+ private String requestNo;
+
+ /// 操作用户 ID
+ private String operatorUserId;
+
+ /// 发生时间
+ private Long occurredAt;
+
+ /// 备注
+ private String remark;
+
+ private Long createdAt;
+
+ /// 操作用户
+ @Transient
+ private User operator;
+
+ /// 附件
+ @Transient
+ private List attachmentList;
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoSalesOrder.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoSalesOrder.java
new file mode 100644
index 0000000..4e9e1f4
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoSalesOrder.java
@@ -0,0 +1,160 @@
+package com.imyeyu.api.modules.gao.entity;
+
+import com.imyeyu.api.modules.common.entity.Attachment;
+import com.imyeyu.api.modules.user.entity.User;
+import com.imyeyu.spring.annotation.table.Transient;
+import com.imyeyu.spring.entity.UUIDEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+/// GAO 销售订单
+///
+/// @author Codex
+/// @since 2026-09-02
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class GaoSalesOrder extends UUIDEntity {
+
+ /// 订单类型
+ public enum OrderType {
+
+ /// 套餐订单
+ PACKAGE,
+
+ /// 直接产品订单
+ DIRECT_PRODUCT
+ }
+
+ /// 订单状态
+ public enum Status {
+
+ /// 进行中
+ ACTIVE,
+
+ /// 全部履约
+ FULFILLED,
+
+ /// 已取消
+ CANCELLED
+ }
+
+ /// 付款状态
+ public enum PaymentStatus {
+
+ /// 未付款
+ UNPAID,
+
+ /// 部分付款
+ PARTIALLY_PAID,
+
+ /// 已付清
+ PAID
+ }
+
+ /// 退款状态
+ public enum RefundStatus {
+
+ /// 无需退款
+ NOT_REQUIRED,
+
+ /// 待退款或待确认无需退款
+ PENDING,
+
+ /// 已完成退款
+ REFUNDED
+ }
+
+ /// 门店 ID
+ private String storeId;
+
+ /// 客户 ID
+ private String customerId;
+
+ /// 门店内订单号
+ private String orderNo;
+
+ /// 订单类型
+ private OrderType orderType;
+
+ /// 订单状态
+ private Status status;
+
+ /// 付款状态
+ private PaymentStatus paymentStatus;
+
+ /// 退款状态
+ private RefundStatus refundStatus;
+
+ /// 成交金额,单位分
+ private Long saleAmount;
+
+ /// 待核销收款,单位分
+ private Long pendingPaidAmount;
+
+ /// 有效收款,单位分
+ private Long paidAmount;
+
+ /// 首次全额实收时间
+ private Long paidAt;
+
+ /// 有效退款,单位分
+ private Long refundedAmount;
+
+ /// 来源事件记录 ID
+ private String sourceEventRecordId;
+
+ /// 创建订单幂等号
+ private String requestNo;
+
+ /// 下单时间
+ private Long purchasedAt;
+
+ /// 下单操作人
+ private String operatorUserId;
+
+ /// 取消时间
+ private Long cancelledAt;
+
+ /// 取消操作人
+ private String cancelOperatorUserId;
+
+ /// 取消原因
+ private String cancelReason;
+
+ /// 备注
+ private String remark;
+
+ /// 客户
+ @Transient
+ private GaoCustomer customer;
+
+ /// 下单操作人
+ @Transient
+ private User operator;
+
+ /// 取消操作人
+ @Transient
+ private User cancelOperator;
+
+ /// 销售订单明细
+ @Transient
+ private List itemList;
+
+ /// 资金流水
+ @Transient
+ private List fundRecordList;
+
+ /// 来源事件记录
+ @Transient
+ private GaoEventRecord sourceEventRecord;
+
+ /// 未收金额,单位分
+ @Transient
+ private Long unpaidAmount;
+
+ /// 订单附件
+ @Transient
+ private List attachmentList;
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoSalesOrderItem.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoSalesOrderItem.java
new file mode 100644
index 0000000..db2166d
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoSalesOrderItem.java
@@ -0,0 +1,103 @@
+package com.imyeyu.api.modules.gao.entity;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.imyeyu.api.modules.common.entity.Attachment;
+import com.imyeyu.spring.annotation.table.Transient;
+import com.imyeyu.spring.entity.UUIDEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/// GAO 销售订单通用可履约明细
+///
+/// @author Codex
+/// @since 2026-09-02
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class GaoSalesOrderItem extends UUIDEntity {
+
+ /// 订单项类型
+ public enum ItemType {
+
+ /// 直接产品
+ DIRECT_PRODUCT,
+
+ /// 套餐权益
+ PACKAGE_ENTITLEMENT
+ }
+
+ /// 销售订单 ID
+ private String salesOrderId;
+
+ /// 订单项类型
+ private ItemType itemType;
+
+ /// SKU ID
+ private String skuId;
+
+ /// SKU 单位 ID
+ private String skuUnitId;
+
+ /// 产品图片附件
+ @Transient
+ private List attachmentList;
+
+ /// 产品名称快照
+ private String productNameSnapshot;
+
+ /// 产品 ID 快照
+ private String productIdSnapshot;
+
+ /// SKU 编码快照
+ private String skuCodeSnapshot;
+
+ /// SKU 名称快照
+ private String skuNameSnapshot;
+
+ /// 规格摘要快照
+ private String specSummarySnapshot;
+
+ /// SKU 规格结构快照,数组元素包含 specId、specName、specValueId、specValueName
+ private JsonNode skuSpecSnapshot;
+
+ /// 单位快照
+ private String unitSnapshot;
+
+ /// 基础单位数量系数快照
+ private BigDecimal baseQuantityPerUnitSnapshot;
+
+ /// 购买数量
+ private BigDecimal quantity;
+
+ /// 待履约数量
+ private BigDecimal reservedQuantity;
+
+ /// 已履约数量
+ private BigDecimal fulfilledQuantity;
+
+ /// 实际单价,单位分
+ private Long unitPrice;
+
+ /// 行金额,单位分
+ private Long lineAmount;
+
+ /// 排序值
+ private Integer sort;
+
+ /// 备注
+ private String remark;
+
+ /// 待核销数量
+ @Transient
+ private BigDecimal pendingQuantity;
+
+ /// 当前可履约数量
+ @Transient
+ private BigDecimal availableQuantity;
+
+ /// 权益剩余数量
+ @Transient
+ private BigDecimal remainingQuantity;
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerConsumptionRecordMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerConsumptionRecordMapper.java
new file mode 100644
index 0000000..a9c7261
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerConsumptionRecordMapper.java
@@ -0,0 +1,20 @@
+package com.imyeyu.api.modules.gao.mapper;
+
+import com.imyeyu.api.modules.gao.entity.GaoCustomerConsumptionRecord;
+import com.imyeyu.spring.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/// GAO 客户有效消费事实 Mapper
+///
+/// @author Codex
+/// @since 2026-09-04
+public interface GaoCustomerConsumptionRecordMapper extends BaseMapper {
+
+ GaoCustomerConsumptionRecord selectBySource(@Param("sourceType") GaoCustomerConsumptionRecord.SourceType sourceType, @Param("sourceId") String sourceId);
+
+ List selectActiveByWindow(@Param("storeId") String storeId, @Param("customerId") String customerId, @Param("beginAt") long beginAt, @Param("endAt") long endAt);
+
+ int revokeBySource(@Param("sourceType") GaoCustomerConsumptionRecord.SourceType sourceType, @Param("sourceId") String sourceId, @Param("revokedAt") long revokedAt, @Param("revokeReason") String revokeReason);
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerPackageItemMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerPackageItemMapper.java
deleted file mode 100644
index 79f9ba4..0000000
--- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerPackageItemMapper.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.imyeyu.api.modules.gao.mapper;
-
-import com.imyeyu.api.modules.gao.entity.GaoCustomerPackageItem;
-import com.imyeyu.spring.mapper.BaseMapper;
-import org.apache.ibatis.annotations.Param;
-
-import java.util.Collection;
-import java.util.List;
-
-/// GAO 客户套餐产品权益 Mapper
-///
-/// @author Codex
-/// @since 2026-08-31
-public interface GaoCustomerPackageItemMapper extends BaseMapper {
-
- /// 按客户套餐查询产品权益
- List selectByCustomerPackageId(@Param("customerPackageId") String customerPackageId);
-
- /// 批量查询客户套餐产品权益
- List selectByCustomerPackageIdList(@Param("customerPackageIdList") Collection customerPackageIdList);
-
- /// 锁定客户套餐产品
- GaoCustomerPackageItem selectForUpdate(@Param("id") String id);
-
- /// 调整待核销占用数量
- int updateReservedQuantity(@Param("id") String id, @Param("delta") java.math.BigDecimal delta);
-
- /// 调整已核销发放数量
- int updateDeliveredQuantity(@Param("id") String id, @Param("delta") java.math.BigDecimal delta);
-}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerPackageMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerPackageMapper.java
index 39ac742..1e8155b 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerPackageMapper.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerPackageMapper.java
@@ -11,17 +11,8 @@ import org.apache.ibatis.annotations.Param;
/// @since 2026-08-31
public interface GaoCustomerPackageMapper extends BaseMapper {
- /// 按门店和请求号查询购买结果
- GaoCustomerPackage selectByRequestNo(@Param("storeId") String storeId, @Param("requestNo") String requestNo);
-
- /// 按请求号查询购买结果,用于配置删除后的幂等重试
- GaoCustomerPackage selectByRequestNoAnyStore(@Param("requestNo") String requestNo);
-
- /// 锁定客户套餐
- GaoCustomerPackage selectForUpdate(@Param("id") String id);
-
- /// 按购买事件登记查询客户套餐
- GaoCustomerPackage selectByPurchaseEventRecordId(@Param("purchaseEventRecordId") String purchaseEventRecordId);
+ /// 按销售订单查询客户套餐
+ GaoCustomerPackage selectBySalesOrderId(@Param("salesOrderId") String salesOrderId);
/// 按条件统计客户套餐
long countByQuery(GaoCustomerPackagePage page);
@@ -29,12 +20,4 @@ public interface GaoCustomerPackageMapper extends BaseMapper selectByQuery(GaoCustomerPackagePage page);
- /// 原子调整待核销收款
- int updatePendingPaidAmountDelta(@Param("id") String id, @Param("delta") Long delta);
-
- /// 原子调整有效收款
- int updatePaidAmountDelta(@Param("id") String id, @Param("delta") Long delta);
-
- /// 带状态条件更新套餐状态
- int updateStatusIf(@Param("id") String id, @Param("fromStatus") GaoCustomerPackage.Status fromStatus, @Param("toStatus") GaoCustomerPackage.Status toStatus, @Param("cancelledAt") Long cancelledAt, @Param("cancelOperatorUserId") String cancelOperatorUserId, @Param("cancelReason") String cancelReason, @Param("refundStatus") GaoCustomerPackage.RefundStatus refundStatus);
}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventRecordMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventRecordMapper.java
index 6b473da..a71e9af 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventRecordMapper.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventRecordMapper.java
@@ -29,6 +29,10 @@ public interface GaoEventRecordMapper extends BaseMapper
@Select("SELECT COUNT(*) FROM `gao_event_record` WHERE `event_id` = #{eventId} AND `deleted_at` IS NULL")
long countByEventId(String eventId);
+ /// 查询事件记录是否被订单引用
+ @Select("SELECT COUNT(*) FROM `gao_sales_order` WHERE `source_event_record_id` = #{recordId}")
+ long countBySourceEventRecordId(@Param("recordId") String recordId);
+
Long count(String storeId, String customerId, String eventId, Long beginAt, Long endAt);
/// 查询事件中已携带数值的登记记录数量
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventSQLProvider.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventSQLProvider.java
index 6777a3e..ce14ce9 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventSQLProvider.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventSQLProvider.java
@@ -26,7 +26,7 @@ public class GaoEventSQLProvider extends SQLProvider {
private String appendWithoutDeleted(String sql) {
return sql.replace(
"WHERE 1 = 1",
- "WHERE 1 = 1 AND (`status` IS NULL OR `status` <> '%s')".formatted(GaoEvent.Status.DELETED.name())
+ "WHERE 1 = 1 AND (`status` IS NULL OR `status` <> '%s') AND (`biz_type` IS NULL OR `biz_type` <> '%s')".formatted(GaoEvent.Status.DELETED.name(), GaoEvent.BizType.LEGACY_CONSUMPTION.name())
);
}
}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoFundRecordMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoFundRecordMapper.java
index 4f130b0..31d1289 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoFundRecordMapper.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoFundRecordMapper.java
@@ -19,6 +19,9 @@ public interface GaoFundRecordMapper extends BaseMapper {
/// 锁定资金记录
GaoFundRecord selectForUpdate(@Param("id") String id);
+ /// 查询资金记录及关联销售订单资金摘要
+ GaoFundRecord selectWithSalesOrderSummary(@Param("id") String id);
+
/// 查询资金冲正记录
GaoFundRecord selectRevokeBySourceRecordId(@Param("sourceRecordId") String sourceRecordId);
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoOrderFulfillmentItemMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoOrderFulfillmentItemMapper.java
new file mode 100644
index 0000000..513d47b
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoOrderFulfillmentItemMapper.java
@@ -0,0 +1,23 @@
+package com.imyeyu.api.modules.gao.mapper;
+
+import com.imyeyu.api.modules.gao.entity.GaoOrderFulfillmentItem;
+import com.imyeyu.spring.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/// GAO 统一履约明细 Mapper
+///
+/// @author Codex
+/// @since 2026-09-02
+public interface GaoOrderFulfillmentItemMapper extends BaseMapper {
+
+ /// 按履约单查询明细
+ List selectByFulfillmentId(@Param("fulfillmentId") String fulfillmentId);
+
+ /// 批量查询履约明细
+ List selectByFulfillmentIdList(@Param("fulfillmentIdList") List fulfillmentIdList);
+
+ /// 锁定履约明细
+ List selectByFulfillmentIdForUpdate(@Param("fulfillmentId") String fulfillmentId);
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoOrderFulfillmentMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoOrderFulfillmentMapper.java
new file mode 100644
index 0000000..55fc95c
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoOrderFulfillmentMapper.java
@@ -0,0 +1,44 @@
+package com.imyeyu.api.modules.gao.mapper;
+
+import com.imyeyu.api.modules.gao.entity.GaoOrderFulfillment;
+import com.imyeyu.api.modules.gao.vo.GaoOrderFulfillmentPage;
+import com.imyeyu.spring.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/// GAO 统一履约 Mapper
+///
+/// @author Codex
+/// @since 2026-09-02
+public interface GaoOrderFulfillmentMapper extends BaseMapper {
+
+ /// 按门店和幂等号查询履约单
+ GaoOrderFulfillment selectByRequestNo(@Param("storeId") String storeId, @Param("requestNo") String requestNo);
+
+ /// 按 ID 查询履约单
+ GaoOrderFulfillment selectById(@Param("id") String id);
+
+ /// 锁定履约单
+ GaoOrderFulfillment selectForUpdate(@Param("id") String id);
+
+ /// 按订单查询履约单
+ List selectBySalesOrderId(@Param("salesOrderId") String salesOrderId);
+
+ /// 按冲正来源查询履约单
+ GaoOrderFulfillment selectBySourceFulfillmentId(@Param("sourceFulfillmentId") String sourceFulfillmentId);
+
+ /// 统计订单中仍占用权益的履约单
+ long countPendingBySalesOrderId(@Param("salesOrderId") String salesOrderId);
+
+ /// 按条件统计履约单
+ long countByQuery(GaoOrderFulfillmentPage page);
+
+ /// 按条件查询履约单
+ List selectByQuery(GaoOrderFulfillmentPage page);
+
+ /// 更新履约状态
+ int updateStatus(@Param("id") String id, @Param("status") GaoOrderFulfillment.Status status,
+ @Param("verifierUserId") String verifierUserId, @Param("verifiedAt") Long verifiedAt,
+ @Param("reason") String reason);
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoOrderReminderMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoOrderReminderMapper.java
new file mode 100644
index 0000000..4a05dd2
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoOrderReminderMapper.java
@@ -0,0 +1,27 @@
+package com.imyeyu.api.modules.gao.mapper;
+
+import com.imyeyu.api.modules.gao.vo.GaoOrderReminderCustomer;
+
+import java.util.List;
+
+/// GAO 订单提醒 Mapper
+///
+/// @author Codex
+/// @since 2026-09-06
+public interface GaoOrderReminderMapper {
+
+ /// 查询需要收款的订单客户
+ ///
+ /// @return 订单客户列表
+ List selectOrderReceiveCustomers();
+
+ /// 查询待核销收款客户
+ ///
+ /// @return 收款客户列表
+ List selectReceiveVerifyCustomers();
+
+ /// 查询待核销履约客户
+ ///
+ /// @return 履约客户列表
+ List selectFulfillmentVerifyCustomers();
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPackageDeliveryItemMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPackageDeliveryItemMapper.java
deleted file mode 100644
index ebee2fb..0000000
--- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPackageDeliveryItemMapper.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.imyeyu.api.modules.gao.mapper;
-
-import com.imyeyu.api.modules.gao.entity.GaoPackageDeliveryItem;
-import com.imyeyu.spring.mapper.BaseMapper;
-import org.apache.ibatis.annotations.Param;
-
-import java.util.Collection;
-import java.util.List;
-
-/// GAO 套餐发放明细 Mapper
-///
-/// @author Codex
-/// @since 2026-08-31
-public interface GaoPackageDeliveryItemMapper extends BaseMapper {
-
- /// 按发放单查询明细
- List selectByDeliveryId(@Param("deliveryId") String deliveryId);
-
- /// 批量查询发放明细
- List selectByDeliveryIdList(@Param("deliveryIdList") Collection deliveryIdList);
-}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPackageDeliveryMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPackageDeliveryMapper.java
deleted file mode 100644
index 7ee4923..0000000
--- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPackageDeliveryMapper.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.imyeyu.api.modules.gao.mapper;
-
-import com.imyeyu.api.modules.gao.entity.GaoPackageDelivery;
-import com.imyeyu.api.modules.gao.vo.GaoPackageDeliveryPage;
-import com.imyeyu.spring.mapper.BaseMapper;
-import org.apache.ibatis.annotations.Param;
-
-import java.util.List;
-
-/// GAO 套餐发放 Mapper
-///
-/// @author Codex
-/// @since 2026-08-31
-public interface GaoPackageDeliveryMapper extends BaseMapper {
-
- /// 按门店和请求号查询发放结果
- GaoPackageDelivery selectByRequestNo(@Param("storeId") String storeId, @Param("requestNo") String requestNo);
-
- /// 锁定发放单
- GaoPackageDelivery selectForUpdate(@Param("id") String id);
-
- /// 查询原发放单的冲正记录
- GaoPackageDelivery selectRevokeBySourceDeliveryId(@Param("sourceDeliveryId") String sourceDeliveryId);
-
- /// 查询客户套餐待核销发放数量
- long countPendingByCustomerPackageId(@Param("customerPackageId") String customerPackageId);
-
- /// 查询客户套餐已核销正常发放数量
- long countVerifiedByCustomerPackageId(@Param("customerPackageId") String customerPackageId);
-
- /// 查询客户套餐发放历史
- java.util.List selectByCustomerPackageId(@Param("customerPackageId") String customerPackageId);
-
- /// 按条件统计发放单
- long countByQuery(GaoPackageDeliveryPage page);
-
- /// 按条件查询发放单
- List selectByQuery(GaoPackageDeliveryPage page);
-
- /// 带状态条件更新发放单
- int updateStatusIf(@Param("id") String id, @Param("fromStatus") GaoPackageDelivery.Status fromStatus, @Param("toStatus") GaoPackageDelivery.Status toStatus, @Param("verifierUserId") String verifierUserId, @Param("verifiedAt") Long verifiedAt, @Param("reason") String reason);
-}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPointLedgerConsumptionRecordMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPointLedgerConsumptionRecordMapper.java
new file mode 100644
index 0000000..9e486ed
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPointLedgerConsumptionRecordMapper.java
@@ -0,0 +1,38 @@
+package com.imyeyu.api.modules.gao.mapper;
+
+import org.apache.ibatis.annotations.Delete;
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+/// GAO 积分流水消费事实依赖 Mapper
+///
+/// @author Codex
+/// @since 2026-09-04
+public interface GaoPointLedgerConsumptionRecordMapper {
+
+ /// 写入积分流水对消费事实的依赖
+ ///
+ /// @param pointLedgerId 积分流水 ID
+ /// @param consumptionRecordId 消费事实 ID
+ /// @param createdAt 创建时间
+ /// @return 写入行数
+ @Insert("INSERT IGNORE INTO `gao_point_ledger_consumption_record` (`point_ledger_id`, `consumption_record_id`, `created_at`) VALUES (#{pointLedgerId}, #{consumptionRecordId}, #{createdAt})")
+ int insert(@Param("pointLedgerId") String pointLedgerId, @Param("consumptionRecordId") String consumptionRecordId, @Param("createdAt") long createdAt);
+
+ /// 查询积分流水依赖的消费事实 ID
+ ///
+ /// @param pointLedgerId 积分流水 ID
+ /// @return 消费事实 ID 列表
+ @Select("SELECT `consumption_record_id` FROM `gao_point_ledger_consumption_record` WHERE `point_ledger_id` = #{pointLedgerId}")
+ List selectConsumptionRecordIdList(@Param("pointLedgerId") String pointLedgerId);
+
+ /// 删除积分流水的依赖关系
+ ///
+ /// @param pointLedgerId 积分流水 ID
+ /// @return 删除行数
+ @Delete("DELETE FROM `gao_point_ledger_consumption_record` WHERE `point_ledger_id` = #{pointLedgerId}")
+ int deleteByPointLedgerId(@Param("pointLedgerId") String pointLedgerId);
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPointLedgerMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPointLedgerMapper.java
index 197349d..4a8e7c8 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPointLedgerMapper.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPointLedgerMapper.java
@@ -19,11 +19,18 @@ public interface GaoPointLedgerMapper extends BaseMapper
/// @return 流水
GaoPointLedger selectByIdempotencyKey(@Param("idempotencyKey") String idempotencyKey);
- /// 查询登记记录产生的原始规则积分流水
+ /// 查询触发来源产生的原始规则积分流水
///
- /// @param eventRecordId 登记记录 ID
+ /// @param triggerSourceType 直接触发来源类型
+ /// @param triggerSourceId 直接触发来源 ID
/// @return 原始规则积分流水
- List selectRuleChangeByEventRecordId(@Param("eventRecordId") String eventRecordId);
+ List selectRuleChangeByTriggerSource(@Param("triggerSourceType") GaoPointLedger.TriggerSourceType triggerSourceType, @Param("triggerSourceId") String triggerSourceId);
+
+ /// 查询依赖指定消费事实的原始规则积分流水
+ ///
+ /// @param consumptionRecordId 消费事实 ID
+ /// @return 原始规则积分流水
+ List selectRuleChangeByConsumptionRecordId(@Param("consumptionRecordId") String consumptionRecordId);
/// 批量查询幂等键对应的流水
///
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPointRuleMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPointRuleMapper.java
index 97a1fe1..af230c4 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPointRuleMapper.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPointRuleMapper.java
@@ -18,8 +18,9 @@ public interface GaoPointRuleMapper extends BaseMapper {
/// 查询当前时间可匹配的启用规则
///
/// @param storeId 门店 ID
- /// @param eventId 登记事件 ID
+ /// @param triggerSourceType 直接触发来源类型
+ /// @param triggerRefId 触发对象筛选 ID,空为不限
/// @param now 当前时间戳
/// @return 按优先级排序的规则列表
- List selectActiveByStoreIdAndEventId(@Param("storeId") String storeId, @Param("eventId") String eventId, @Param("now") long now);
+ List selectActiveByTrigger(@Param("storeId") String storeId, @Param("triggerSourceType") GaoPointRule.TriggerSourceType triggerSourceType, @Param("triggerRefId") String triggerRefId, @Param("now") long now);
}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductCategoryMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductCategoryMapper.java
new file mode 100644
index 0000000..8b83fdc
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductCategoryMapper.java
@@ -0,0 +1,37 @@
+package com.imyeyu.api.modules.gao.mapper;
+
+import com.imyeyu.api.modules.gao.entity.GaoProductCategory;
+import com.imyeyu.api.modules.gao.vo.GaoProductCategoryPage;
+import com.imyeyu.spring.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Collection;
+import java.util.List;
+
+/// GAO 产品分类 Mapper
+///
+/// @author Codex
+/// @since 2026-09-02
+public interface GaoProductCategoryMapper extends BaseMapper {
+
+ /// 查询分类
+ GaoProductCategory selectById(@Param("id") String id);
+
+ /// 按 ID 批量查询分类
+ List selectByIdList(@Param("idList") Collection idList);
+
+ /// 一次性更新分类排序
+ void updateSort(@Param("idList") List idList, @Param("updatedAt") long updatedAt);
+
+ /// 按条件统计产品分类
+ long countByQuery(GaoProductCategoryPage page);
+
+ /// 按条件查询产品分类
+ List selectByQuery(GaoProductCategoryPage page);
+
+ /// 查询门店分类
+ List selectByStoreId(@Param("storeId") String storeId);
+
+ /// 查询门店内同名分类
+ GaoProductCategory selectByStoreIdAndName(@Param("storeId") String storeId, @Param("name") String name, @Param("excludeId") String excludeId);
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductChartMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductChartMapper.java
new file mode 100644
index 0000000..f5e0924
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductChartMapper.java
@@ -0,0 +1,48 @@
+package com.imyeyu.api.modules.gao.mapper;
+
+import com.imyeyu.api.modules.gao.vo.GaoProductChartReq;
+import com.imyeyu.api.modules.gao.vo.GaoProductCategoryStatView;
+import com.imyeyu.api.modules.gao.vo.GaoProductFulfillmentDailyStatView;
+import com.imyeyu.api.modules.gao.vo.GaoProductFulfillmentRankView;
+import com.imyeyu.api.modules.gao.vo.GaoProductSalesDailyStatView;
+import com.imyeyu.api.modules.gao.vo.GaoProductSalesRankView;
+import com.imyeyu.api.modules.gao.vo.GaoProductStockHealthView;
+import com.imyeyu.api.modules.gao.vo.GaoProductStockMovementDailyStatView;
+import com.imyeyu.api.modules.gao.vo.GaoProductTypeStatView;
+import com.imyeyu.api.modules.gao.vo.GaoProductUnitSalesRankView;
+
+import java.util.List;
+
+/// GAO 产品图表 Mapper
+///
+/// @author Codex
+/// @since 2026-09-06
+public interface GaoProductChartMapper {
+
+ /// 查询产品每日销售统计
+ List selectSalesDaily(GaoProductChartReq req);
+
+ /// 查询产品销售排行
+ List selectSalesRank(GaoProductChartReq req);
+
+ /// 查询产品每日履约统计
+ List selectFulfillmentDaily(GaoProductChartReq req);
+
+ /// 查询产品履约进度排行
+ List selectFulfillmentRank(GaoProductChartReq req);
+
+ /// 查询产品每日库存变动统计
+ List selectStockMovementDaily(GaoProductChartReq req);
+
+ /// 查询产品库存健康度
+ List selectStockHealth(GaoProductChartReq req);
+
+ /// 查询产品类型统计
+ List selectType(GaoProductChartReq req);
+
+ /// 查询产品分类统计
+ List selectCategory(GaoProductChartReq req);
+
+ /// 查询产品销售单位排行
+ List selectUnitSalesRank(GaoProductChartReq req);
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductMapper.java
new file mode 100644
index 0000000..5841a47
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductMapper.java
@@ -0,0 +1,31 @@
+package com.imyeyu.api.modules.gao.mapper;
+
+import com.imyeyu.api.modules.gao.entity.GaoProduct;
+import com.imyeyu.api.modules.gao.vo.GaoProductPage;
+import com.imyeyu.spring.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Collection;
+import java.util.List;
+
+/// GAO 产品 Mapper
+///
+/// @author Codex
+/// @since 2026-09-02
+public interface GaoProductMapper extends BaseMapper {
+
+ /// 按条件统计产品
+ long countByQuery(GaoProductPage page);
+
+ /// 按条件查询产品
+ List selectByQuery(GaoProductPage page);
+
+ /// 批量查询产品
+ List selectByIdList(@Param("idList") Collection idList);
+
+ /// 查询产品编码
+ GaoProduct selectByStoreIdAndCode(@Param("storeId") String storeId, @Param("code") String code, @Param("excludeId") String excludeId);
+
+ /// 锁定产品
+ GaoProduct selectForUpdate(@Param("id") String id);
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductSkuChartMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductSkuChartMapper.java
new file mode 100644
index 0000000..171750a
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductSkuChartMapper.java
@@ -0,0 +1,40 @@
+package com.imyeyu.api.modules.gao.mapper;
+
+import com.imyeyu.api.modules.gao.vo.GaoProductChartReq;
+import com.imyeyu.api.modules.gao.vo.GaoProductSkuFulfillmentDailyStatView;
+import com.imyeyu.api.modules.gao.vo.GaoProductSkuFulfillmentRankView;
+import com.imyeyu.api.modules.gao.vo.GaoProductSkuSalesDailyStatView;
+import com.imyeyu.api.modules.gao.vo.GaoProductSkuSalesRankView;
+import com.imyeyu.api.modules.gao.vo.GaoProductSkuStockHealthView;
+import com.imyeyu.api.modules.gao.vo.GaoProductSkuStockMovementDailyStatView;
+import com.imyeyu.api.modules.gao.vo.GaoProductSkuUnitSalesRankView;
+
+import java.util.List;
+
+/// GAO SKU 图表 Mapper
+///
+/// @author Codex
+/// @since 2026-09-07
+public interface GaoProductSkuChartMapper {
+
+ /// 查询 SKU 每日销售统计
+ List selectSalesDaily(GaoProductChartReq req);
+
+ /// 查询 SKU 销售排行
+ List selectSalesRank(GaoProductChartReq req);
+
+ /// 查询 SKU 单位销售排行
+ List selectUnitSalesRank(GaoProductChartReq req);
+
+ /// 查询 SKU 每日履约统计
+ List selectFulfillmentDaily(GaoProductChartReq req);
+
+ /// 查询 SKU 履约进度排行
+ List selectFulfillmentRank(GaoProductChartReq req);
+
+ /// 查询 SKU 每日库存变动统计
+ List selectStockMovementDaily(GaoProductChartReq req);
+
+ /// 查询 SKU 库存健康度
+ List selectStockHealth(GaoProductChartReq req);
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductSkuMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductSkuMapper.java
new file mode 100644
index 0000000..a14e681
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductSkuMapper.java
@@ -0,0 +1,33 @@
+package com.imyeyu.api.modules.gao.mapper;
+
+import com.imyeyu.api.modules.gao.entity.GaoProductSku;
+import com.imyeyu.spring.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Collection;
+import java.util.List;
+
+/// GAO 产品 SKU Mapper
+///
+/// @author Codex
+/// @since 2026-09-06
+public interface GaoProductSkuMapper extends BaseMapper {
+
+ /// 按产品查询 SKU
+ List selectByProductId(@Param("productId") String productId);
+
+ /// 批量按产品查询 SKU
+ List selectByProductIdList(@Param("productIdList") Collection productIdList);
+
+ /// 查询 SKU 编码
+ GaoProductSku selectByStoreIdAndCode(@Param("storeId") String storeId, @Param("code") String code, @Param("excludeId") String excludeId);
+
+ /// 查询 SKU 条码
+ GaoProductSku selectByStoreIdAndBarcode(@Param("storeId") String storeId, @Param("barcode") String barcode, @Param("excludeId") String excludeId);
+
+ /// 查询产品下相同规格值组合的 SKU
+ GaoProductSku selectByProductIdAndSpecValueKey(@Param("productId") String productId, @Param("specValueKey") String specValueKey, @Param("excludeId") String excludeId);
+
+ /// 锁定 SKU
+ GaoProductSku selectForUpdate(@Param("id") String id);
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductSkuSpecValueMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductSkuSpecValueMapper.java
new file mode 100644
index 0000000..ed78800
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductSkuSpecValueMapper.java
@@ -0,0 +1,21 @@
+package com.imyeyu.api.modules.gao.mapper;
+
+import com.imyeyu.api.modules.gao.entity.GaoProductSkuSpecValue;
+import com.imyeyu.spring.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Collection;
+import java.util.List;
+
+/// GAO SKU 规格值关联 Mapper
+///
+/// @author Codex
+/// @since 2026-09-06
+public interface GaoProductSkuSpecValueMapper extends BaseMapper {
+
+ /// 批量查询 SKU 规格值关联
+ List selectBySkuIdList(@Param("skuIdList") Collection skuIdList);
+
+ /// 删除 SKU 的规格值关联
+ void deleteBySkuId(@Param("skuId") String skuId);
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductSkuUnitMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductSkuUnitMapper.java
new file mode 100644
index 0000000..4163550
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductSkuUnitMapper.java
@@ -0,0 +1,27 @@
+package com.imyeyu.api.modules.gao.mapper;
+
+import com.imyeyu.api.modules.gao.entity.GaoProductSkuUnit;
+import com.imyeyu.spring.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Collection;
+import java.util.List;
+
+/// GAO 产品 SKU 单位 Mapper
+///
+/// @author Codex
+/// @since 2026-09-06
+public interface GaoProductSkuUnitMapper extends BaseMapper {
+
+ /// 按 SKU 查询有效单位
+ List selectBySkuId(@Param("skuId") String skuId);
+
+ /// 批量查询 SKU 有效单位
+ List selectBySkuIdList(@Param("skuIdList") Collection skuIdList);
+
+ /// 锁定单位
+ GaoProductSkuUnit selectForUpdate(@Param("id") String id);
+
+ /// 更新有效单位
+ int updateUnit(GaoProductSkuUnit unit);
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductSpecMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductSpecMapper.java
new file mode 100644
index 0000000..bc5eef0
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductSpecMapper.java
@@ -0,0 +1,27 @@
+package com.imyeyu.api.modules.gao.mapper;
+
+import com.imyeyu.api.modules.gao.entity.GaoProductSpec;
+import com.imyeyu.spring.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Collection;
+import java.util.List;
+
+/// GAO 产品规格项 Mapper
+///
+/// @author Codex
+/// @since 2026-09-06
+public interface GaoProductSpecMapper extends BaseMapper {
+
+ /// 按产品查询规格项
+ List selectByProductId(@Param("productId") String productId);
+
+ /// 批量按产品查询规格项
+ List selectByProductIdList(@Param("productIdList") Collection productIdList);
+
+ /// 查询规格值所属产品
+ String selectProductIdBySpecValueId(@Param("specValueId") String specValueId);
+
+ /// 查询规格项所属产品
+ String selectProductIdBySpecId(@Param("specId") String specId);
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductSpecValueMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductSpecValueMapper.java
new file mode 100644
index 0000000..0caba09
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductSpecValueMapper.java
@@ -0,0 +1,21 @@
+package com.imyeyu.api.modules.gao.mapper;
+
+import com.imyeyu.api.modules.gao.entity.GaoProductSpecValue;
+import com.imyeyu.spring.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Collection;
+import java.util.List;
+
+/// GAO 产品规格值 Mapper
+///
+/// @author Codex
+/// @since 2026-09-06
+public interface GaoProductSpecValueMapper extends BaseMapper {
+
+ /// 按 ID 查询有效规格值
+ GaoProductSpecValue selectById(@Param("id") String id);
+
+ /// 按规格项查询规格值
+ List selectBySpecIdList(@Param("specIdList") Collection specIdList);
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductStockMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductStockMapper.java
new file mode 100644
index 0000000..f9f8f7f
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductStockMapper.java
@@ -0,0 +1,46 @@
+package com.imyeyu.api.modules.gao.mapper;
+
+import com.imyeyu.api.modules.gao.entity.GaoProductStock;
+import com.imyeyu.api.modules.gao.vo.GaoProductStockPage;
+import com.imyeyu.api.modules.gao.vo.GaoProductStockProductView;
+import com.imyeyu.spring.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/// GAO 产品库存 Mapper
+///
+/// @author Codex
+/// @since 2026-09-02
+public interface GaoProductStockMapper extends BaseMapper {
+
+ /// 锁定库存余额
+ GaoProductStock selectForUpdate(@Param("storeId") String storeId, @Param("skuId") String skuId);
+
+ /// 按条件统计库存余额
+ long countByQuery(GaoProductStockPage page);
+
+ /// 按条件查询库存余额
+ List selectByQuery(GaoProductStockPage page);
+
+ /// 按产品汇总库存数量
+ long countProductByQuery(GaoProductStockPage page);
+
+ /// 按产品分页查询库存汇总
+ List selectProductByQuery(GaoProductStockPage page);
+
+ /// 按门店和产品查询库存余额
+ GaoProductStock selectByStoreIdAndSkuId(@Param("storeId") String storeId, @Param("skuId") String skuId);
+
+ /// 条件更新库存余额
+ int updateQuantity(@Param("id") String id, @Param("onHandDelta") java.math.BigDecimal onHandDelta, @Param("reservedDelta") java.math.BigDecimal reservedDelta, @Param("updatedBy") String updatedBy);
+
+ /// 条件增加预占库存
+ int reserve(@Param("id") String id, @Param("quantity") java.math.BigDecimal quantity, @Param("updatedBy") String updatedBy);
+
+ /// 条件释放预占库存
+ int release(@Param("id") String id, @Param("quantity") java.math.BigDecimal quantity, @Param("updatedBy") String updatedBy);
+
+ /// 插入库存余额,已存在时忽略
+ int insertIgnore(GaoProductStock stock);
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductStockRecordMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductStockRecordMapper.java
new file mode 100644
index 0000000..bd5ea37
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductStockRecordMapper.java
@@ -0,0 +1,36 @@
+package com.imyeyu.api.modules.gao.mapper;
+
+import com.imyeyu.api.modules.gao.entity.GaoProductStockRecord;
+import com.imyeyu.api.modules.gao.vo.GaoProductStockRecordPage;
+import com.imyeyu.spring.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/// GAO 库存流水 Mapper
+///
+/// @author Codex
+/// @since 2026-09-02
+public interface GaoProductStockRecordMapper extends BaseMapper {
+
+ /// 按门店和请求号查询库存流水
+ List selectByRequestNo(@Param("storeId") String storeId, @Param("requestNo") String requestNo);
+
+ /// 锁定库存流水
+ GaoProductStockRecord selectForUpdate(@Param("id") String id);
+
+ /// 查询库存冲正流水
+ GaoProductStockRecord selectRevokeBySourceRecordId(@Param("sourceRecordId") String sourceRecordId);
+
+ /// 按业务查询库存流水
+ List selectByBiz(@Param("bizType") GaoProductStockRecord.BizType bizType, @Param("bizId") String bizId);
+
+ /// 查询指定业务和产品的库存流水
+ GaoProductStockRecord selectByBizSku(@Param("bizType") GaoProductStockRecord.BizType bizType, @Param("bizId") String bizId, @Param("skuId") String skuId, @Param("operationType") GaoProductStockRecord.OperationType operationType);
+
+ /// 按条件统计库存流水
+ long countByQuery(GaoProductStockRecordPage page);
+
+ /// 按条件查询库存流水
+ List selectByQuery(GaoProductStockRecordPage page);
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoSalesOrderChartMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoSalesOrderChartMapper.java
new file mode 100644
index 0000000..43b831c
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoSalesOrderChartMapper.java
@@ -0,0 +1,60 @@
+package com.imyeyu.api.modules.gao.mapper;
+
+import com.imyeyu.api.modules.gao.vo.GaoCustomerOrderProductRankView;
+import com.imyeyu.api.modules.gao.vo.GaoCustomerOrderRankView;
+import com.imyeyu.api.modules.gao.vo.GaoCustomerOrderSkuRankView;
+import com.imyeyu.api.modules.gao.vo.GaoCustomerRepeatDailyStatView;
+import com.imyeyu.api.modules.gao.vo.GaoCustomerRepeatIntervalStatView;
+import com.imyeyu.api.modules.gao.vo.GaoSalesOrderChartReq;
+import com.imyeyu.api.modules.gao.vo.GaoSalesOrderCalendarStatView;
+import com.imyeyu.api.modules.gao.vo.GaoSalesOrderConsumptionDailyStatView;
+import com.imyeyu.api.modules.gao.vo.GaoSalesOrderDailyStatView;
+import com.imyeyu.api.modules.gao.vo.GaoSalesOrderPaymentStatView;
+import com.imyeyu.api.modules.gao.vo.GaoSalesOrderReceivableStatView;
+import com.imyeyu.api.modules.gao.vo.GaoSalesOrderStatusStatView;
+import com.imyeyu.api.modules.gao.vo.GaoSalesOrderTypeStatView;
+
+import java.util.List;
+
+/// GAO 销售订单图表 Mapper
+///
+/// @author Codex
+/// @since 2026-09-06
+public interface GaoSalesOrderChartMapper {
+
+ /// 查询销售订单每日统计
+ List selectDaily(GaoSalesOrderChartReq req);
+
+ /// 查询销售订单类型统计
+ List selectType(GaoSalesOrderChartReq req);
+
+ /// 查询销售订单付款状态统计
+ List selectPayment(GaoSalesOrderChartReq req);
+
+ /// 查询客户订单排行
+ List selectCustomerRank(GaoSalesOrderChartReq req);
+
+ /// 查询客户订单产品排行
+ List selectCustomerProductRank(GaoSalesOrderChartReq req);
+
+ /// 查询客户订单 SKU 排行
+ List selectCustomerSkuRank(GaoSalesOrderChartReq req);
+
+ /// 查询销售订单状态统计
+ List selectStatus(GaoSalesOrderChartReq req);
+
+ /// 查询销售订单应收统计
+ GaoSalesOrderReceivableStatView selectReceivable(GaoSalesOrderChartReq req);
+
+ /// 查询客户每日有效消费统计
+ List selectConsumptionDaily(GaoSalesOrderChartReq req);
+
+ /// 查询客户订单日历统计
+ List selectCalendar(GaoSalesOrderChartReq req);
+
+ /// 查询客户每日复购统计
+ List