diff --git a/.idea/data_source_mapping.xml b/.idea/data_source_mapping.xml deleted file mode 100644 index 22e3b6c..0000000 --- a/.idea/data_source_mapping.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml index eb4a267..1d0a883 100644 --- a/.idea/sqldialects.xml +++ b/.idea/sqldialects.xml @@ -3,6 +3,7 @@ + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 5e66368..cf9e69d 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.imyeyu.timiserverapi TimiServerAPI - 1.0.8 + 1.0.9 jar TimiServerAPI imyeyu.com API @@ -173,12 +173,12 @@ com.imyeyu.spring timi-spring - 0.0.18 + 0.0.19 com.imyeyu.network timi-network - 0.0.13 + 0.0.14 com.imyeyu.lang @@ -188,12 +188,12 @@ com.imyeyu.java timi-java - 0.0.6 + 0.0.7 com.imyeyu.utils timi-utils - 0.0.12 + 0.0.13 com.imyeyu.font.icon diff --git a/src/main/java/com/imyeyu/api/annotation/RequireCoreRole.java b/src/main/java/com/imyeyu/api/annotation/RequireCoreRole.java deleted file mode 100644 index 6e3da68..0000000 --- a/src/main/java/com/imyeyu/api/annotation/RequireCoreRole.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.imyeyu.api.annotation; - -import com.imyeyu.api.bean.RoleMatchMode; -import com.imyeyu.api.bean.CoreRoleCode; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/// -/// 要求 CORE 模块角色 -/// -/// @author Codex -/// @since 2026-07-26 -@Target({ElementType.METHOD, ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface RequireCoreRole { - - /// 所需角色 - /// - /// @return 角色列表 - CoreRoleCode[] value(); - - /// 匹配模式 - /// - /// @return 匹配模式 - RoleMatchMode mode() default RoleMatchMode.ALL; -} diff --git a/src/main/java/com/imyeyu/api/annotation/RequireCoreRoleInterceptor.java b/src/main/java/com/imyeyu/api/annotation/RequireCoreRoleInterceptor.java deleted file mode 100644 index 3342560..0000000 --- a/src/main/java/com/imyeyu/api/annotation/RequireCoreRoleInterceptor.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.imyeyu.api.annotation; - -import com.imyeyu.api.bean.ModuleCode; -import com.imyeyu.api.bean.RoleMatchMode; -import com.imyeyu.api.bean.CoreRoleCode; -import com.imyeyu.api.modules.user.service.RoleChecker; -import com.imyeyu.java.TimiJava; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.springframework.lang.NonNull; -import org.springframework.stereotype.Component; -import org.springframework.web.method.HandlerMethod; -import org.springframework.web.servlet.HandlerInterceptor; - -/// -/// CORE 角色注解处理器 -/// -/// @author Codex -/// @since 2026-07-27 -@Component -public class RequireCoreRoleInterceptor implements HandlerInterceptor { - - private final RoleChecker roleChecker; - - public RequireCoreRoleInterceptor(RoleChecker roleChecker) { - this.roleChecker = roleChecker; - } - - @Override - public boolean preHandle(@NonNull HttpServletRequest req, @NonNull HttpServletResponse resp, @NonNull Object handler) { - if (!(handler instanceof HandlerMethod handlerMethod)) { - return true; - } - RequireCoreRole annotation = handlerMethod.getMethodAnnotation(RequireCoreRole.class); - if (annotation == null || TimiJava.isEmpty(annotation.value())) { - return true; - } - String[] roleCodeList = java.util.Arrays.stream(annotation.value()).map(CoreRoleCode::name).toArray(String[]::new); - if (annotation.mode() == RoleMatchMode.ANY) { - roleChecker.checkAny(ModuleCode.CORE, roleCodeList); - } else { - roleChecker.checkAll(ModuleCode.CORE, roleCodeList); - } - return true; - } -} diff --git a/src/main/java/com/imyeyu/api/bean/CorePermissionCode.java b/src/main/java/com/imyeyu/api/bean/CorePermissionCode.java index 2de1037..fba6068 100644 --- a/src/main/java/com/imyeyu/api/bean/CorePermissionCode.java +++ b/src/main/java/com/imyeyu/api/bean/CorePermissionCode.java @@ -25,9 +25,9 @@ public enum CorePermissionCode implements BuiltinPermissionCode { ROLE_PERMISSION_READ("ROLE_PERMISSION:READ", "角色权限读取", false, true), ROLE_PERMISSION_DELETE("ROLE_PERMISSION:DELETE", "角色权限删除", false, true), - ROLE_RELATION_CREATE("ROLE_RELATION:CREATE", "角色关联创建", false, true), - ROLE_RELATION_READ("ROLE_RELATION:READ", "角色关联读取", false, true), - ROLE_RELATION_DELETE("ROLE_RELATION:DELETE", "角色关联删除", false, true), + ROLE_PERMISSION_INHERIT_CREATE("ROLE_PERMISSION_INHERIT:CREATE", "角色权限继承创建", false, true), + ROLE_PERMISSION_INHERIT_READ("ROLE_PERMISSION_INHERIT:READ", "角色权限继承读取", false, true), + ROLE_PERMISSION_INHERIT_DELETE("ROLE_PERMISSION_INHERIT:DELETE", "角色权限继承删除", false, true), SETTING_CREATE("SETTING:CREATE", "设置创建", false, true), SETTING_READ("SETTING:READ", "设置读取", false, true), diff --git a/src/main/java/com/imyeyu/api/bean/CoreRoleCode.java b/src/main/java/com/imyeyu/api/bean/CoreRoleCode.java index 64aa870..7e246b9 100644 --- a/src/main/java/com/imyeyu/api/bean/CoreRoleCode.java +++ b/src/main/java/com/imyeyu/api/bean/CoreRoleCode.java @@ -13,9 +13,7 @@ public enum CoreRoleCode implements BuiltinRoleCode { SYSTEM("系统", true, null), - ADMIN("管理员", false, SYSTEM.name()), - - USER_ROLE_ADMIN("授权管理", false, ADMIN.name()); + ADMIN("管理员", false, SYSTEM.name()); final String defaultName; final boolean protectedRole; @@ -40,4 +38,5 @@ public enum CoreRoleCode implements BuiltinRoleCode { public String getParentCode() { return parentCode; } + } diff --git a/src/main/java/com/imyeyu/api/config/WebConfig.java b/src/main/java/com/imyeyu/api/config/WebConfig.java index d457568..6be5503 100644 --- a/src/main/java/com/imyeyu/api/config/WebConfig.java +++ b/src/main/java/com/imyeyu/api/config/WebConfig.java @@ -9,7 +9,6 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.imyeyu.api.annotation.EnableSettingInterceptor; import com.imyeyu.api.annotation.RequestRateLimitInterceptor; import com.imyeyu.api.annotation.RequireCorePermissionInterceptor; -import com.imyeyu.api.annotation.RequireCoreRoleInterceptor; import com.imyeyu.api.modules.gao.annotation.RequireGaoPermissionInterceptor; import com.imyeyu.api.modules.gao.annotation.RequireGaoRoleInterceptor; import com.imyeyu.api.modules.journal.util.JournalAPIInterceptor; @@ -44,7 +43,6 @@ public class WebConfig implements WebMvcConfigurer { private final JournalAPIInterceptor journalAPIInterceptor; private final EnableSettingInterceptor enableSettingInterceptor; private final RequireGaoRoleInterceptor requireGaoRoleInterceptor; - private final RequireCoreRoleInterceptor requireCoreRoleInterceptor; private final RequestRateLimitInterceptor requestRateLimitInterceptor; private final RequireGaoPermissionInterceptor requireGaoPermissionInterceptor; private final RequireCorePermissionInterceptor requireCorePermissionInterceptor; @@ -59,7 +57,6 @@ public class WebConfig implements WebMvcConfigurer { registry.addInterceptor(systemAPIInterceptor).addPathPatterns(SystemAPIInterceptor.PATH); registry.addInterceptor(journalAPIInterceptor).addPathPatterns(JournalAPIInterceptor.PATH); registry.addInterceptor(enableSettingInterceptor).addPathPatterns("/**"); - registry.addInterceptor(requireCoreRoleInterceptor).addPathPatterns("/**"); registry.addInterceptor(requireCorePermissionInterceptor).addPathPatterns("/**"); registry.addInterceptor(requireGaoRoleInterceptor).addPathPatterns("/**"); registry.addInterceptor(requireGaoPermissionInterceptor).addPathPatterns("/**"); diff --git a/src/main/java/com/imyeyu/api/modules/common/controller/AttachmentController.java b/src/main/java/com/imyeyu/api/modules/common/controller/AttachmentController.java index 28712f7..10ed758 100644 --- a/src/main/java/com/imyeyu/api/modules/common/controller/AttachmentController.java +++ b/src/main/java/com/imyeyu/api/modules/common/controller/AttachmentController.java @@ -3,8 +3,6 @@ package com.imyeyu.api.modules.common.controller; import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.common.service.TempFileService; -import com.imyeyu.api.modules.common.vo.attach.BizDeleteReq; -import com.imyeyu.api.modules.common.vo.attach.BizUpdateReq; import com.imyeyu.api.modules.common.vo.attach.TempFileResp; import com.imyeyu.java.TimiJava; import com.imyeyu.network.Network; @@ -183,30 +181,6 @@ public class AttachmentController { return service.pageByBizId(bizType, bizId, attachTypeList, page); } - /** - * 按业务差分更新附件 - * - * @param req 更新请求 - */ - @AOPLog - @RequestRateLimit - @PostMapping("/update/biz") - public void updateByBiz(@RequestBody BizUpdateReq req) { - service.updateByBiz(req); - } - - /** - * 按业务删除全部附件 - * - * @param req 删除请求 - */ - @AOPLog - @RequestRateLimit - @PostMapping("/delete/biz") - public void deleteByBiz(@RequestBody BizDeleteReq req) { - service.deleteByBiz(req); - } - @AOPLog @IgnoreGlobalReturn @GetMapping({"/read/{id}", "/download/{id}"}) diff --git a/src/main/java/com/imyeyu/api/modules/common/controller/MultilingualController.java b/src/main/java/com/imyeyu/api/modules/common/controller/MultilingualController.java index db002a3..1fb0994 100644 --- a/src/main/java/com/imyeyu/api/modules/common/controller/MultilingualController.java +++ b/src/main/java/com/imyeyu/api/modules/common/controller/MultilingualController.java @@ -2,9 +2,7 @@ package com.imyeyu.api.modules.common.controller; import com.fasterxml.jackson.annotation.JsonView; import com.imyeyu.api.annotation.RequireCorePermission; -import com.imyeyu.api.annotation.RequireCoreRole; import com.imyeyu.api.bean.CorePermissionCode; -import com.imyeyu.api.bean.CoreRoleCode; import com.imyeyu.api.modules.common.entity.Multilingual; import com.imyeyu.api.modules.common.service.MultilingualService; import com.imyeyu.spring.annotation.AOPLog; @@ -40,7 +38,6 @@ public class MultilingualController { */ @JsonView(ResponseView.Admin.class) @RequestRateLimit - @RequireCoreRole(CoreRoleCode.ADMIN) @RequireCorePermission(CorePermissionCode.MULTILINGUAL_READ) @PostMapping("/detail") public Multilingual detail(@RequestParam String id) { @@ -56,7 +53,6 @@ public class MultilingualController { @AOPLog @JsonView(ResponseView.Admin.class) @RequestRateLimit - @RequireCoreRole(CoreRoleCode.ADMIN) @RequireCorePermission(CorePermissionCode.MULTILINGUAL_CREATE) @PostMapping("/create") public Multilingual create(@RequestBody Multilingual multilingual) { @@ -71,7 +67,6 @@ public class MultilingualController { */ @AOPLog @RequestRateLimit - @RequireCoreRole(CoreRoleCode.ADMIN) @RequireCorePermission(CorePermissionCode.MULTILINGUAL_UPDATE) @PostMapping("/update") public void update(@RequestBody Multilingual multilingual) { @@ -85,7 +80,6 @@ public class MultilingualController { */ @AOPLog @RequestRateLimit - @RequireCoreRole(CoreRoleCode.ADMIN) @RequireCorePermission(CorePermissionCode.MULTILINGUAL_DELETE) @PostMapping("/delete") public void delete(@RequestParam String id) { 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 6b1c10d..3ef00fe 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 @@ -1,9 +1,10 @@ package com.imyeyu.api.modules.common.entity; -import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.imyeyu.api.TimiServerAPI; import com.imyeyu.api.modules.common.bean.attachment.AccessKey; import com.imyeyu.api.modules.common.service.AttachmentService; +import com.imyeyu.java.TimiJava; import com.imyeyu.java.ref.Ref; import com.imyeyu.spring.TimiSpring; import com.imyeyu.spring.annotation.table.Transient; @@ -33,7 +34,7 @@ public class Attachment extends UUIDEntity { GAO_CUSTOMER, - GAO_REGISTRATION_EVENT_RECORD, + GAO_EVENT_RECORD, TEMP_FILE, @@ -54,7 +55,7 @@ public class Attachment extends UUIDEntity { protected String mimeType; - protected JsonNode metadata; + protected ObjectNode metadata; protected Long size; @@ -82,6 +83,14 @@ public class Attachment extends UUIDEntity { @Transient protected MultipartFile file; + public String buildMongoName() { + StringBuilder result = new StringBuilder(bizType.name()); + if (TimiJava.isNotEmpty(attachType)) { + result.append('_').append(attachType.toUpperCase()); + } + return result.append('_').append(name).toString(); + } + public InputStream openInputStream() { AttachmentService service = TimiServerAPI.applicationContext.getBean(AttachmentService.class); return service.getInputStreamByMongoId(mongoId); diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/AttachmentMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/AttachmentMapper.java index 60e7d61..6c5d57f 100644 --- a/src/main/java/com/imyeyu/api/modules/common/mapper/AttachmentMapper.java +++ b/src/main/java/com/imyeyu/api/modules/common/mapper/AttachmentMapper.java @@ -4,9 +4,9 @@ import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.spring.bean.Page; import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.RawMapper; -import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; +import java.util.Collection; import java.util.List; /** @@ -32,7 +32,7 @@ public interface AttachmentMapper extends BaseMapper, RawMap List selectByBizId(Attachment.BizType bizType, String bizId, List attachTypes, Page page); - List selectByBizIdList(@Param("bizType") Attachment.BizType bizType, @Param("bizIdList") List bizIdList, @Param("attachTypes") List attachTypes); + List selectByBizIdList(Attachment.BizType bizType, Collection bizIdList, List attachTypes); long countByBizId(Attachment.BizType bizType, String bizId, List attachTypes); diff --git a/src/main/java/com/imyeyu/api/modules/common/service/AttachmentService.java b/src/main/java/com/imyeyu/api/modules/common/service/AttachmentService.java index ddd367c..b89df9c 100644 --- a/src/main/java/com/imyeyu/api/modules/common/service/AttachmentService.java +++ b/src/main/java/com/imyeyu/api/modules/common/service/AttachmentService.java @@ -1,13 +1,12 @@ package com.imyeyu.api.modules.common.service; import com.imyeyu.api.modules.common.entity.Attachment; -import com.imyeyu.api.modules.common.vo.attach.BizDeleteReq; -import com.imyeyu.api.modules.common.vo.attach.BizUpdateReq; import com.imyeyu.spring.bean.Page; import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.service.BaseService; import java.io.InputStream; +import java.util.Collection; import java.util.List; import java.util.Map; @@ -19,6 +18,13 @@ import java.util.Map; */ public interface AttachmentService extends BaseService { + /// + /// 克隆附件,复制业务属性和文件内容并生成新的附件记录 + /// + /// @param id 源附件 ID + /// @return 克隆后的附件 + Attachment clone(String id); + /** * 按需获取或创建缩略图附件。 * 当附件为图片或视频时,若指定尺寸的缩略图已存在则直接返回缓存, @@ -52,14 +58,14 @@ public interface AttachmentService extends BaseService { List listByBizId(Attachment.BizType bizType, String bizId, String... attachTypes); /** - * 根据多个业务 ID 批量获取附件。 + * 根据多个业务 ID 批量获取附件 * * @param bizType 业务类型 * @param bizIdList 业务 ID 列表 * @param attachTypes 附件类型,可为 null * @return 按业务 ID 分组的附件列表 */ - Map> mapByBizIdList(Attachment.BizType bizType, List bizIdList, String... attachTypes); + Map> mapByBizIdList(Attachment.BizType bizType, Collection bizIdList, String... attachTypes); /** * 根据业务获取所有附件 @@ -84,10 +90,8 @@ public interface AttachmentService extends BaseService { * 请求有、数据库有、无 tempFileId → 仅更新元数据 * 请求有、数据库有、有 tempFileId → 更新文件与元数据 * - * - * @param req 差分更新请求 */ - void updateByBiz(BizUpdateReq req); + void updateByBizId(Attachment.BizType bizType, String bizId, List list); - void deleteByBiz(BizDeleteReq req); + void deleteByBizId(Attachment.BizType bizType, String bizId); } diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/AttachmentServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/AttachmentServiceImplement.java index 4346370..701d4db 100644 --- a/src/main/java/com/imyeyu/api/modules/common/service/implement/AttachmentServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/common/service/implement/AttachmentServiceImplement.java @@ -8,8 +8,6 @@ import com.imyeyu.api.modules.common.bean.attachment.Metadata; import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.mapper.AttachmentMapper; import com.imyeyu.api.modules.common.service.AttachmentService; -import com.imyeyu.api.modules.common.vo.attach.BizDeleteReq; -import com.imyeyu.api.modules.common.vo.attach.BizUpdateReq; import com.imyeyu.api.util.JavaCV; import com.imyeyu.io.IO; import com.imyeyu.java.TimiJava; @@ -44,6 +42,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Objects; @@ -71,6 +70,88 @@ public class AttachmentServiceImplement extends AbstractEntityService> mapByBizIdList(Attachment.BizType bizType, List bizIdList, String... attachTypes) { - if (bizIdList == null || bizIdList.isEmpty()) { - return Map.of(); - } - List normalizedBizIdList = bizIdList.stream() - .filter(TimiJava::isNotEmpty) - .distinct() - .toList(); - if (normalizedBizIdList.isEmpty()) { - return Map.of(); - } - return mapper.selectByBizIdList(bizType, new ArrayList<>(normalizedBizIdList), List.of(attachTypes)).stream() - .collect(Collectors.groupingBy(Attachment::getBizId)); + public Map> mapByBizIdList(Attachment.BizType bizType, Collection bizIdList, String... attachTypes) { + return mapper.selectByBizIdList(bizType, bizIdList, List.of(attachTypes)).stream().collect(Collectors.groupingBy(Attachment::getBizId)); } @Override @@ -348,16 +347,14 @@ public class AttachmentServiceImplement extends AbstractEntityService items = req.getItems(); + public void updateByBizId(Attachment.BizType bizType, String bizId, List list) { + list = TimiJava.defaultIfNull(list, new ArrayList<>()); // 数据库现有附件 List dbList = mapper.selectAllByBizId(bizType, bizId); Set dbIds = dbList.stream().map(Attachment::getId).collect(Collectors.toSet()); // 请求附件 ID - Set reqIds = items.stream().map(Attachment::getId).filter(Objects::nonNull).collect(Collectors.toSet()); + Set reqIds = list.stream().map(Attachment::getId).filter(Objects::nonNull).collect(Collectors.toSet()); // 数据库有、请求无:删除 for (Attachment attach : dbList) { if (!reqIds.contains(attach.getId())) { @@ -365,7 +362,7 @@ public class AttachmentServiceImplement extends AbstractEntityService attachmentList = mapper.selectAllByBizId(req.getBizType(), req.getBizId()); + public void deleteByBizId(Attachment.BizType bizType, String bizId) { + List attachmentList = mapper.selectAllByBizId(bizType, bizId); for (Attachment attachment : attachmentList) { delete(attachment.getId()); } diff --git a/src/main/java/com/imyeyu/api/modules/common/vo/attach/BizDeleteReq.java b/src/main/java/com/imyeyu/api/modules/common/vo/attach/BizDeleteReq.java deleted file mode 100644 index 7ebb47a..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/vo/attach/BizDeleteReq.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.imyeyu.api.modules.common.vo.attach; - -import com.imyeyu.api.modules.common.entity.Attachment; -import lombok.Data; - -/** - * 业务附件差分更新请求 - * - * @author 夜雨 - * @since 2026-03-11 00:00 - */ -@Data -public class BizDeleteReq { - - /** 业务类型 */ - private Attachment.BizType bizType; - - /** 业务 ID */ - private String bizId; -} diff --git a/src/main/java/com/imyeyu/api/modules/common/vo/attach/BizUpdateReq.java b/src/main/java/com/imyeyu/api/modules/common/vo/attach/BizUpdateReq.java deleted file mode 100644 index 6b90f1f..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/vo/attach/BizUpdateReq.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.imyeyu.api.modules.common.vo.attach; - -import com.imyeyu.api.modules.common.entity.Attachment; -import lombok.Data; - -import java.util.ArrayList; -import java.util.List; - -/** - * 业务附件差分更新请求 - * - * @author 夜雨 - * @since 2026-03-11 00:00 - */ -@Data -public class BizUpdateReq { - - /** 业务类型 */ - private Attachment.BizType bizType; - - /** 业务 ID */ - private String bizId; - - /** 附件项列表 */ - private List items = new ArrayList<>(); -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/annotation/RequireGaoRoleInterceptor.java b/src/main/java/com/imyeyu/api/modules/gao/annotation/RequireGaoRoleInterceptor.java index 600db09..4d24acc 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/annotation/RequireGaoRoleInterceptor.java +++ b/src/main/java/com/imyeyu/api/modules/gao/annotation/RequireGaoRoleInterceptor.java @@ -8,6 +8,7 @@ import com.imyeyu.java.TimiJava; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.lang.NonNull; +import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; @@ -32,6 +33,9 @@ public class RequireGaoRoleInterceptor implements HandlerInterceptor { return true; } RequireGaoRole annotation = handlerMethod.getMethodAnnotation(RequireGaoRole.class); + if (annotation == null) { + annotation = AnnotatedElementUtils.findMergedAnnotation(handlerMethod.getBeanType(), RequireGaoRole.class); + } if (annotation == null || TimiJava.isEmpty(annotation.value())) { return true; } diff --git a/src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java b/src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java index cfc07be..9015b38 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java +++ b/src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java @@ -2,6 +2,7 @@ package com.imyeyu.api.modules.gao.bean; import com.imyeyu.api.bean.ModuleCode; import com.imyeyu.api.modules.user.bean.BuiltinPermissionCode; +import lombok.Getter; /// /// GAO 模块权限枚举 @@ -10,58 +11,88 @@ import com.imyeyu.api.modules.user.bean.BuiltinPermissionCode; /// @since 2026-07-26 public enum GaoPermissionCode implements BuiltinPermissionCode { - CUSTOMER_CREATE("CUSTOMER:CREATE", "客户创建"), + STORE_CREATE("STORE:CREATE", "门店创建", false, true), - CUSTOMER_READ("CUSTOMER:READ", "客户读取"), + STORE_READ("STORE:READ", "门店读取", true, true), - CUSTOMER_EXPORT("CUSTOMER:EXPORT", "客户导出", true), + STORE_EXPORT("STORE:EXPORT", "门店导出", false, true), - CUSTOMER_UPDATE("CUSTOMER:UPDATE", "客户修改"), + STORE_UPDATE("STORE:UPDATE", "门店修改", true, true), - CUSTOMER_DELETE("CUSTOMER:DELETE", "客户删除"), + STORE_DELETE("STORE:DELETE", "门店删除", false, true), - REGISTRATION_EVENT_CREATE("REGISTRATION_EVENT:CREATE", "登记事件创建"), + CUSTOMER_CREATE("CUSTOMER:CREATE", "客户创建", false, false), - REGISTRATION_EVENT_READ("REGISTRATION_EVENT:READ", "登记事件读取"), + CUSTOMER_READ("CUSTOMER:READ", "客户读取", false, false), - REGISTRATION_EVENT_UPDATE("REGISTRATION_EVENT:UPDATE", "登记事件修改"), + CUSTOMER_EXPORT("CUSTOMER:EXPORT", "客户导出", true, false), - REGISTRATION_EVENT_DELETE("REGISTRATION_EVENT:DELETE", "登记事件删除"), + CUSTOMER_UPDATE("CUSTOMER:UPDATE", "客户修改", false, false), - REGISTRATION_EVENT_RECORD_CREATE("REGISTRATION_EVENT_RECORD:CREATE", "登记事件记录创建"), + CUSTOMER_DELETE("CUSTOMER:DELETE", "客户删除", false, false), - REGISTRATION_EVENT_RECORD_READ("REGISTRATION_EVENT_RECORD:READ", "登记事件记录读取"), + EVENT_CREATE("EVENT:CREATE", "登记事件创建", false, false), - REGISTRATION_EVENT_RECORD_EXPORT("REGISTRATION_EVENT_RECORD:EXPORT", "登记事件记录导出", true), + EVENT_READ("EVENT:READ", "登记事件读取", false, false), - REGISTRATION_EVENT_RECORD_UPDATE("REGISTRATION_EVENT_RECORD:UPDATE", "登记事件记录修改"), + EVENT_UPDATE("EVENT:UPDATE", "登记事件修改", false, false), - REGISTRATION_EVENT_RECORD_DELETE("REGISTRATION_EVENT_RECORD:DELETE", "登记事件记录删除"), + EVENT_DELETE("EVENT:DELETE", "登记事件删除", false, false), - STAT_READ("STAT:READ", "统计读取"), + EVENT_RECORD_CREATE("EVENT_RECORD:CREATE", "登记事件记录创建", false, false), - QR_CODE_CREATE("QR_CODE:CREATE", "二维码创建"), + EVENT_RECORD_READ("EVENT_RECORD:READ", "登记事件记录读取", false, false), - USER_CREATE("USER:CREATE", "用户创建", true), + EVENT_RECORD_EXPORT("EVENT_RECORD:EXPORT", "登记事件记录导出", true, false), - USER_READ("USER:READ", "用户读取", true), + EVENT_RECORD_UPDATE("EVENT_RECORD:UPDATE", "登记事件记录修改", false, false), - USER_UPDATE("USER:UPDATE", "用户修改", true), + EVENT_RECORD_DELETE("EVENT_RECORD:DELETE", "登记事件记录删除", false, false), - USER_DELETE("USER:DELETE", "用户删除", true); + POINT_ACCOUNT_READ("POINT_ACCOUNT:READ", "客户积分账户读取", true, true), + + POINT_LEDGER_READ("POINT_LEDGER:READ", "客户积分流水读取", true, true), + + POINT_RULE_CREATE("POINT_RULE:CREATE", "客户积分规则创建", true, true), + + POINT_RULE_READ("POINT_RULE:READ", "客户积分规则读取", true, true), + + POINT_RULE_UPDATE("POINT_RULE:UPDATE", "客户积分规则修改", true, true), + + POINT_RULE_DELETE("POINT_RULE:DELETE", "客户积分规则删除", true, true), + + POINT_ADJUST("POINT:ADJUST", "客户积分人工调整", true, true), + + POINT_SOURCE_VALUE("POINT:SOURCE_VALUE", "登记提交自定义积分值", true, true), + + STAT_READ("STAT:READ", "统计读取", false, false), + + QR_CODE_CREATE("QR_CODE:CREATE", "二维码创建", false, false), + + USER_CREATE("USER:CREATE", "用户创建", true, false), + + USER_READ("USER:READ", "用户读取", true, false), + + USER_UPDATE("USER:UPDATE", "用户修改", true, false), + + USER_DELETE("USER:DELETE", "用户删除", true, false); final String value; final String defaultName; + + /// `true` 为初始化时授权给店长角色 + @Getter final boolean grantToStoreManager; - GaoPermissionCode(String value, String defaultName) { - this(value, defaultName, false); - } + /// `true` 为初始化时授权给全局管理员角色 + @Getter + final boolean grantToGlobalManager; - GaoPermissionCode(String value, String defaultName, boolean grantToStoreManager) { + GaoPermissionCode(String value, String defaultName, boolean grantToStoreManager, boolean grantToGlobalManager) { this.value = value; this.defaultName = defaultName; this.grantToStoreManager = grantToStoreManager; + this.grantToGlobalManager = grantToGlobalManager; } @Override @@ -88,11 +119,4 @@ public enum GaoPermissionCode implements BuiltinPermissionCode { public boolean isGrantToAdmin() { return false; } - - /// true 为初始化时授权给店长角色 - /// - /// @return 是否授权给店长角色 - public boolean isGrantToStoreManager() { - return grantToStoreManager; - } } diff --git a/src/main/java/com/imyeyu/api/modules/gao/bean/GaoRoleCode.java b/src/main/java/com/imyeyu/api/modules/gao/bean/GaoRoleCode.java index b76855e..3bd2282 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/bean/GaoRoleCode.java +++ b/src/main/java/com/imyeyu/api/modules/gao/bean/GaoRoleCode.java @@ -12,12 +12,14 @@ import lombok.AllArgsConstructor; @AllArgsConstructor public enum GaoRoleCode implements BuiltinRoleCode { - /// 店长 - STORE_MANAGER("店长"), + /// 全局管理员 + GLOBAL_MANAGER("全局管理员", null), - EMPLOYEE("员工"); + /// 店长 + STORE_MANAGER("店长", GLOBAL_MANAGER.name()); final String defaultName; + final String parentCode; @Override public ModuleCode getModuleCode() { @@ -36,6 +38,7 @@ public enum GaoRoleCode implements BuiltinRoleCode { @Override public String getParentCode() { - return null; + return parentCode; } + } diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerChartController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerChartController.java new file mode 100644 index 0000000..3bec34d --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerChartController.java @@ -0,0 +1,47 @@ +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.GaoCustomerChartService; +import com.imyeyu.api.modules.gao.vo.GaoCustomerChartReq; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordCalendarView; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordDailyStatView; +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-08-11 +@RestController +@RequiredArgsConstructor +@RequestMapping("/gao/customer/chart") +public class GaoCustomerChartController { + + private final GaoCustomerChartService service; + + /// 查询客户登记每日统计,可同时支撑登记趋势和登记事件结构图表 + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STAT_READ) + @PostMapping("/event/record/daily") + public List eventRecordDaily(@RequestBody GaoCustomerChartReq req) { + return service.eventRecordDailyStat(req); + } + + /// 查询客户登记日历记录 + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STAT_READ) + @PostMapping("/event/record/calendar") + public List eventRecordCalendar(@RequestBody GaoCustomerChartReq req) { + return service.eventRecordCalendar(req); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerController.java index 480a64b..e36ffbc 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerController.java +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerController.java @@ -1,29 +1,32 @@ package com.imyeyu.api.modules.gao.controller; import com.fasterxml.jackson.annotation.JsonView; +import com.imyeyu.api.bean.ModuleCode; +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.bean.GaoRoleCode; -import com.imyeyu.api.bean.RoleMatchMode; -import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; -import com.imyeyu.api.modules.gao.annotation.RequireGaoRole; import com.imyeyu.api.modules.gao.entity.GaoCustomer; -import com.imyeyu.api.modules.gao.entity.GaoRegistrationEvent; -import com.imyeyu.api.modules.gao.entity.GaoRegistrationEventRecord; -import com.imyeyu.api.modules.gao.service.GaoRegistrationEventService; import com.imyeyu.api.modules.gao.service.GaoCustomerService; -import com.imyeyu.api.modules.gao.service.GaoRegistrationEventRecordService; -import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStatView; +import com.imyeyu.api.modules.gao.service.GaoStoreService; +import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView; import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView; -import com.imyeyu.api.modules.gao.vo.GaoCustomerInviteUpdateReq; -import com.imyeyu.api.modules.gao.vo.GaoCustomerSearchReq; -import com.imyeyu.api.modules.gao.vo.GaoCustomerTrendStatReq; -import com.imyeyu.api.modules.gao.vo.GaoQuickRegistrationEventRecordReq; +import com.imyeyu.api.modules.gao.vo.GaoCustomerTrendStateReq; +import com.imyeyu.api.modules.user.service.RoleChecker; +import com.imyeyu.api.modules.user.service.UserLoginService; +import com.imyeyu.api.modules.user.service.UserService; +import com.imyeyu.java.TimiJava; +import com.imyeyu.java.bean.timi.TimiException; +import com.imyeyu.network.Network; +import com.imyeyu.spring.TimiSpring; import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.IgnoreGlobalReturn; import com.imyeyu.spring.annotation.RequestRateLimit; import com.imyeyu.spring.bean.Page; import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.util.ResponseView; +import com.imyeyu.utils.Time; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.PostMapping; @@ -33,28 +36,24 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; -import java.net.URLEncoder; import java.util.List; import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; -/// /// GAO 客户接口 /// -/// @author Codex -/// @since 2026-07-27 +/// @author 夜雨 +/// @since 2026-07-27 14:12 @RestController @RequiredArgsConstructor @RequestMapping("/gao/customer") -@RequireGaoRole(value = {GaoRoleCode.STORE_MANAGER, GaoRoleCode.EMPLOYEE}, mode = RoleMatchMode.ANY) public class GaoCustomerController { - private static final String SIGN_IN_REGISTRATION_EVENT_NAME = "签到"; - + private final UserService userService; + private final RoleChecker roleChecker; + private final AttachmentService attachmentService; private final GaoCustomerService service; - private final GaoRegistrationEventRecordService registrationEventRecordService; - private final GaoRegistrationEventService registrationEventService; + private final GaoStoreService gaoStoreService; + private final UserLoginService userLoginService; @AOPLog @RequestRateLimit @@ -63,74 +62,32 @@ public class GaoCustomerController { @JsonView(ResponseView.Public.class) public PageResult list(@RequestBody Page page) { PageResult result = service.page(page); - fillLastSignInAt(result.getList()); + + List idList = result.getList().stream().map(GaoCustomer::getId).distinct().toList(); + Map> attachmentMap = attachmentService.mapByBizIdList(Attachment.BizType.GAO_CUSTOMER, idList); + for (GaoCustomer customer : result.getList()) { + customer.setAttachmentList(attachmentMap.get(customer.getId())); + } return result; } - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) - @PostMapping("/search") - @JsonView(ResponseView.Public.class) - public List search(@RequestBody GaoCustomerSearchReq req) { - return service.search(req); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) - @PostMapping("/search/page") - @JsonView(ResponseView.Public.class) - public PageResult searchPage(@RequestBody Page page) { - return service.searchPage(page); - } - - @AOPLog - @RequestRateLimit - @IgnoreGlobalReturn - @RequireGaoPermission(GaoPermissionCode.CUSTOMER_EXPORT) - @PostMapping("/export") - public void export(@RequestBody GaoCustomerSearchReq req, HttpServletResponse resp) throws IOException { - String fileName = URLEncoder.encode("客户列表.xlsx", "UTF-8").replace("+", "%20"); - resp.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); - resp.setHeader("Content-Disposition", "attachment; filename=\"gao-customer.xlsx\"; filename*=UTF-8''" + fileName); - resp.getOutputStream().write(service.exportSearchExcel(req)); - } - @AOPLog @RequestRateLimit @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) @PostMapping("/detail") @JsonView(ResponseView.Public.class) public GaoCustomer detail(@RequestParam String id) { - return service.get(id); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) - @PostMapping("/detail/code") - @JsonView(ResponseView.Public.class) - public GaoCustomer detailByCode(@RequestParam String code) { - return service.getByCode(code); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) - @PostMapping("/detail/code/nullable") - @JsonView(ResponseView.Public.class) - public GaoCustomer nullableDetailByCode(@RequestParam String code) { - return service.getNullableByCode(code); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) - @PostMapping("/invited/list") - @JsonView(ResponseView.Public.class) - public List invitedList(@RequestParam String introducerCustomerId) { - return service.listInvited(introducerCustomerId); + GaoCustomer customer = service.get(id); + if (TimiJava.isNotEmpty(customer.getIntroducerCustomerId())) { + GaoCustomer introducerCustomer = service.get(customer.getIntroducerCustomerId()); + introducerCustomer.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.GAO_CUSTOMER, introducerCustomer.getId())); + customer.setIntroducerCustomer(introducerCustomer); + } + if (TimiJava.isNotEmpty(customer.getCreatorUserId())) { + customer.setCreatorUser(userService.get(customer.getCreatorUserId())); + } + customer.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.GAO_CUSTOMER, customer.getId())); + return customer; } @AOPLog @@ -138,17 +95,12 @@ public class GaoCustomerController { @RequireGaoPermission(GaoPermissionCode.CUSTOMER_CREATE) @PostMapping("/create") @JsonView(ResponseView.Public.class) - public GaoCustomer create(@RequestBody GaoCustomer customer) { + public String create(@RequestBody GaoCustomer customer) { + if (!roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) { + customer.setStoreId(gaoStoreService.getByUserId(userLoginService.getRequireLoginUserId()).getId()); + } service.create(customer); - return customer; - } - - @AOPLog - @RequestRateLimit - @PostMapping("/quick/register") - @JsonView(ResponseView.Public.class) - public GaoRegistrationEventRecord quickRegister(@RequestBody GaoQuickRegistrationEventRecordReq req) { - return registrationEventRecordService.quickRegister(req); + return customer.getId(); } @AOPLog @@ -156,7 +108,10 @@ public class GaoCustomerController { @RequireGaoPermission(GaoPermissionCode.CUSTOMER_UPDATE) @PostMapping("/update") public void update(@RequestBody GaoCustomer customer) { - service.update(customer); + if (!roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) { + customer.setStoreId(gaoStoreService.getByUserId(userLoginService.getRequireLoginUserId()).getId()); + } + service.updateWithAttachment(customer); } @AOPLog @@ -169,27 +124,78 @@ public class GaoCustomerController { @AOPLog @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.CUSTOMER_UPDATE) - @PostMapping("/invite/create") - public void createInvite(@RequestBody GaoCustomerInviteUpdateReq req) { - service.saveInviteRelation(req); + @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) + @PostMapping("/find/id") + @JsonView(ResponseView.Public.class) + public GaoCustomer findById(@RequestParam String id) { + GaoCustomer customer = service.get(id); + TimiException.required(customer, "not found customer"); + customer.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.GAO_CUSTOMER, customer.getId())); + return customer; } @AOPLog @RequestRateLimit @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) - @PostMapping("/invite/detail") + @PostMapping("/find/code") @JsonView(ResponseView.Public.class) - public GaoCustomer inviteDetail(@RequestParam String customerId) { - return service.get(customerId); + public GaoCustomer findByCode(@RequestParam String storeId, @RequestParam String code) { + GaoCustomer customer = service.getByCode(storeId, code); + if (customer == null) { + return null; + } + customer.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.GAO_CUSTOMER, customer.getId())); + return customer; + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) + @PostMapping("/find/name") + @JsonView(ResponseView.Public.class) + public GaoCustomer findByName(@RequestParam String storeId, @RequestParam String name) { + GaoCustomer customer = service.getByName(storeId, name); + if (customer == null) { + return null; + } + customer.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.GAO_CUSTOMER, customer.getId())); + return customer; + } + + @AOPLog + @RequestRateLimit + @IgnoreGlobalReturn + @RequireGaoPermission(GaoPermissionCode.CUSTOMER_EXPORT) + @PostMapping("/export") + public void export(@RequestBody Page page) throws IOException { + HttpServletResponse resp = TimiSpring.getResponse(); + String contentDisposition = Network.getFileDownloadHeader("客户列表-%s.xlsx".formatted(Time.serialize.format(Time.now()))); + resp.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + resp.setHeader("Content-Disposition", contentDisposition); + resp.getOutputStream().write(service.export(page)); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) + @PostMapping("/invited/list") + @JsonView(ResponseView.Public.class) + public List listInvited(@RequestParam String introducerCustomerId) { + List result = service.listInvited(introducerCustomerId); + List idList = result.stream().map(GaoCustomer::getId).distinct().toList(); + Map> attachMap = attachmentService.mapByBizIdList(Attachment.BizType.GAO_CUSTOMER, idList); + for (GaoCustomer customer : result) { + customer.setAttachmentList(attachMap.get(customer.getId())); + } + return result; } @AOPLog @RequestRateLimit @RequireGaoPermission(GaoPermissionCode.CUSTOMER_UPDATE) - @PostMapping("/invite/update") - public void updateInvite(@RequestBody GaoCustomerInviteUpdateReq req) { - service.saveInviteRelation(req); + @PostMapping("/invite/save") + public void saveInvite(@RequestParam String introducerCustomerId, @RequestParam String customerId) { + service.saveInviteRelation(introducerCustomerId, customerId); } @AOPLog @@ -200,46 +206,21 @@ public class GaoCustomerController { service.deleteInviteRelation(customerId); } - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) - @PostMapping("/stat/daily") /// 按天统计客户新增数和累计总数 - public List dailyStat(@RequestBody GaoCustomerTrendStatReq req) { - return service.statDailyTrend(req); - } - @AOPLog @RequestRateLimit @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) - @PostMapping("/stat/gender") - /// 统计客户性别分布 - public List genderStat() { - return service.statGender(); + @PostMapping("/state/daily") + public List dailyStat(@RequestBody GaoCustomerTrendStateReq req) { + return service.stateDailyTrend(req); } - private void fillLastSignInAt(List customerList) { - if (customerList == null || customerList.isEmpty()) { - return; - } - String signInRegistrationEventId = null; - for (GaoRegistrationEvent registrationEvent : registrationEventService.listEnabled()) { - if (SIGN_IN_REGISTRATION_EVENT_NAME.equals(registrationEvent.getName())) { - signInRegistrationEventId = registrationEvent.getId(); - break; - } - } - if (signInRegistrationEventId == null) { - return; - } - List customerIdList = customerList.stream().map(GaoCustomer::getId).toList(); - Map recordMap = registrationEventRecordService.listLatestByCustomerIdListAndRegistrationEventId(customerIdList, signInRegistrationEventId).stream() - .collect(Collectors.toMap(GaoRegistrationEventRecord::getCustomerId, Function.identity(), (left, right) -> left)); - for (GaoCustomer customer : customerList) { - GaoRegistrationEventRecord record = recordMap.get(customer.getId()); - if (record != null) { - customer.setLastSignInAt(record.getRegisteredAt()); - } - } + /// 统计客户性别分布 + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) + @PostMapping("/state/gender") + public List genderStat(@RequestParam String storeId) { + return service.stateGender(storeId); } } 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 new file mode 100644 index 0000000..72234c3 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventController.java @@ -0,0 +1,105 @@ +package com.imyeyu.api.modules.gao.controller; + +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; +import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; +import com.imyeyu.api.modules.gao.bean.GaoRoleCode; +import com.imyeyu.api.modules.gao.entity.GaoEvent; +import com.imyeyu.api.modules.gao.service.GaoEventService; +import com.imyeyu.api.modules.gao.service.GaoStoreService; +import com.imyeyu.api.modules.user.service.RoleChecker; +import com.imyeyu.api.modules.user.service.UserLoginService; +import com.imyeyu.spring.annotation.AOPLog; +import com.imyeyu.spring.annotation.RequestRateLimit; +import com.imyeyu.spring.bean.Page; +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; + +/// GAO 登记事件接口 +/// +/// @author 夜雨 +/// @since 2026-07-27 14:13 +@RestController +@RequiredArgsConstructor +@RequestMapping("/gao/event") +public class GaoEventController { + + private final GaoEventService service; + private final RoleChecker roleChecker; + private final GaoStoreService gaoStoreService; + private final UserLoginService userLoginService; + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.EVENT_READ) + @PostMapping("/list") + public PageResult list(@RequestBody Page page) { + PageResult result = service.page(page); + Map> roleMap = service.mapRoleCodeListByEventIdList(result.getList().stream().map(GaoEvent::getId).toList()); + for (GaoEvent event : result.getList()) { + event.setRoleCodeList(roleMap.get(event.getId())); + } + return result; + } + + @AOPLog + @RequestRateLimit + @PostMapping("/list/valid") + public List listValid(@RequestParam String storeId) { + return service.listValid(storeId); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.EVENT_READ) + @PostMapping("/detail") + public GaoEvent detail(@RequestParam String id) { + return service.get(id); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.EVENT_CREATE) + @PostMapping("/create") + public void create(@RequestBody GaoEvent event) { + if (!roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) { + event.setStoreId(gaoStoreService.getByUserId(userLoginService.getRequireLoginUserId()).getId()); + } + service.create(event); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.EVENT_UPDATE) + @PostMapping("/update") + public void update(@RequestBody GaoEvent event) { + if (!roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) { + event.setStoreId(gaoStoreService.getByUserId(userLoginService.getRequireLoginUserId()).getId()); + } + service.update(event); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.EVENT_UPDATE) + @PostMapping("/sort") + public void sort(@RequestBody List idList) { + service.sort(idList); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.EVENT_DELETE) + @PostMapping("/delete") + public void delete(@RequestParam String id) { + service.delete(id); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventRecordController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventRecordController.java new file mode 100644 index 0000000..2a97e6c --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventRecordController.java @@ -0,0 +1,191 @@ +package com.imyeyu.api.modules.gao.controller; + +import com.fasterxml.jackson.annotation.JsonView; +import com.imyeyu.api.bean.ModuleCode; +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.bean.GaoRoleCode; +import com.imyeyu.api.modules.gao.entity.GaoCustomer; +import com.imyeyu.api.modules.gao.entity.GaoEvent; +import com.imyeyu.api.modules.gao.entity.GaoEventRecord; +import com.imyeyu.api.modules.gao.service.GaoCustomerService; +import com.imyeyu.api.modules.gao.service.GaoEventRecordService; +import com.imyeyu.api.modules.gao.service.GaoEventService; +import com.imyeyu.api.modules.gao.service.GaoStoreService; +import com.imyeyu.api.modules.gao.vo.GaoCustomerEventRankView; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordPage; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordRankPage; +import com.imyeyu.api.modules.user.service.RoleChecker; +import com.imyeyu.api.modules.user.service.UserLoginService; +import com.imyeyu.api.modules.user.service.UserService; +import com.imyeyu.java.TimiJava; +import com.imyeyu.network.Network; +import com.imyeyu.spring.TimiSpring; +import com.imyeyu.spring.annotation.AOPLog; +import com.imyeyu.spring.annotation.IgnoreGlobalReturn; +import com.imyeyu.spring.annotation.RequestRateLimit; +import com.imyeyu.spring.bean.PageResult; +import com.imyeyu.spring.util.ResponseView; +import com.imyeyu.utils.Time; +import jakarta.servlet.http.HttpServletResponse; +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.io.IOException; +import java.util.List; +import java.util.Map; + +/// GAO 登记事件记录接口 +/// +/// @author 夜雨 +/// @since 2026-07-27 14:11 +@RestController +@RequiredArgsConstructor +@RequestMapping("/gao/event/record") +public class GaoEventRecordController { + + private final UserService userService; + private final RoleChecker roleChecker; + private final GaoEventService eventService; + private final AttachmentService attachmentService; + private final GaoCustomerService customerService; + private final GaoStoreService gaoStoreService; + private final GaoEventRecordService service; + private final UserLoginService userLoginService; + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.EVENT_RECORD_READ) + @PostMapping("/list") + @JsonView(ResponseView.Public.class) + public PageResult list(@RequestBody GaoEventRecordPage page) { + PageResult result = service.pageByRange(page); + { + List customerIdList = result.getList().stream().map(GaoEventRecord::getCustomerId).toList(); + Map eventMap = eventService.mapByIdList(result.getList().stream().map(GaoEventRecord::getEventId).toList()); + Map customerMap = customerService.mapByIdList(customerIdList); + Map> attachMap = attachmentService.mapByBizIdList(Attachment.BizType.GAO_CUSTOMER, customerIdList); + for (GaoEventRecord record : result.getList()) { + record.setEvent(eventMap.get(record.getEventId())); + record.setCustomer(customerMap.get(record.getCustomerId())); + record.getCustomer().setAttachmentList(attachMap.get(record.getCustomerId())); + } + } + return result; + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.EVENT_RECORD_READ) + @PostMapping("/detail") + @JsonView(ResponseView.Public.class) + public GaoEventRecord detail(@RequestParam String id) { + GaoEventRecord record = service.get(id); + record.setCustomer(customerService.get(record.getCustomerId())); + record.getCustomer().setAttachmentList(attachmentService.listByBizId(Attachment.BizType.GAO_CUSTOMER, record.getCustomerId())); + record.setEvent(eventService.get(record.getEventId())); + record.setOperator(userService.get(record.getOperatorUserId())); + record.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.GAO_EVENT_RECORD, record.getId())); + return record; + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.EVENT_RECORD_CREATE) + @PostMapping("/create") + public void create(@RequestBody GaoEventRecord record) { + if (!roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) { + record.setStoreId(gaoStoreService.getByUserId(userLoginService.getRequireLoginUserId()).getId()); + } + service.create(record); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.EVENT_RECORD_CREATE) + @PostMapping("/create/batch") + public void createBatch(@RequestBody List recordList) { + if (!roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) { + String storeId = gaoStoreService.getBelongIdByRequiredLoginUserId(); + for (GaoEventRecord record : recordList) { + record.setStoreId(storeId); + } + } + service.createBatch(recordList); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.EVENT_RECORD_CREATE) + @PostMapping("/create/customer") + public void createWithCustomer(@RequestBody GaoCustomer customer) { + if (!roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) { + String storeId = gaoStoreService.getBelongIdByRequiredLoginUserId(); + for (GaoEventRecord record : TimiJava.safeIterable(customer.getEventRecordList())) { + record.setStoreId(storeId); + } + } + service.createWithCustomer(customer); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.EVENT_RECORD_UPDATE) + @PostMapping("/update") + public void update(@RequestBody GaoEventRecord record) { + if (!roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) { + record.setStoreId(gaoStoreService.getBelongIdByRequiredLoginUserId()); + } + service.update(record); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.EVENT_RECORD_DELETE) + @PostMapping("/delete") + public void delete(@RequestParam String id) { + service.delete(id); + } + + @AOPLog + @RequestRateLimit + @IgnoreGlobalReturn + @RequireGaoPermission(GaoPermissionCode.EVENT_RECORD_EXPORT) + @PostMapping("/export") + public void export(@RequestBody GaoEventRecordPage page) throws IOException { + HttpServletResponse resp = TimiSpring.getResponse(); + String contentDisposition = Network.getFileDownloadHeader("登记事件记录-%s.xlsx".formatted(Time.serialize.format(Time.now()))); + resp.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + resp.setHeader("Content-Disposition", contentDisposition); + resp.getOutputStream().write(service.export(page)); + } + + /// 按时间范围查询客户登记排行榜 + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STAT_READ) + @PostMapping("/rank") + public PageResult eventRank(@RequestBody GaoEventRecordRankPage page) { + PageResult result = service.pageRankByRange(page); + List customerIdList = result.getList().stream().map(GaoCustomerEventRankView::getCustomerId).distinct().toList(); + if (customerIdList.isEmpty()) { + return result; + } + Map customerMap = customerService.mapByIdList(customerIdList); + Map> attachmentMap = attachmentService.mapByBizIdList(Attachment.BizType.GAO_CUSTOMER, customerIdList); + for (GaoCustomerEventRankView view : result.getList()) { + GaoCustomer customer = customerMap.get(view.getCustomerId()); + if (customer != null) { + customer.setAttachmentList(attachmentMap.get(view.getCustomerId())); + } + view.setCustomer(customer); + } + return result; + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPointAccountController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPointAccountController.java new file mode 100644 index 0000000..537e0a1 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPointAccountController.java @@ -0,0 +1,47 @@ +package com.imyeyu.api.modules.gao.controller; + +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; +import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; +import com.imyeyu.api.modules.gao.bean.GaoRoleCode; +import com.imyeyu.api.modules.gao.entity.GaoPointAccount; +import com.imyeyu.api.modules.gao.service.GaoPointAccountService; +import com.imyeyu.api.modules.gao.service.GaoStoreService; +import com.imyeyu.api.modules.user.service.RoleChecker; +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.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/// GAO 客户积分账户接口 +/// +/// @author Codex +/// @since 2026-08-11 +@RestController +@RequiredArgsConstructor +@RequestMapping("/gao/customer/point") +public class GaoPointAccountController { + + private final RoleChecker roleChecker; + private final GaoStoreService storeService; + private final GaoPointAccountService accountService; + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.POINT_ACCOUNT_READ) + @PostMapping("/account/detail") + public GaoPointAccount detail(@RequestParam String customerId, @RequestParam(required = false) String storeId) { + storeId = resolveStoreId(storeId); + return accountService.getByCustomerId(storeId, customerId); + } + + private String resolveStoreId(String requestedStoreId) { + if (roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) { + return requestedStoreId; + } + return storeService.getBelongIdByRequiredLoginUserId(); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPointLedgerController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPointLedgerController.java new file mode 100644 index 0000000..412c761 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPointLedgerController.java @@ -0,0 +1,63 @@ +package com.imyeyu.api.modules.gao.controller; + +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; +import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; +import com.imyeyu.api.modules.gao.bean.GaoRoleCode; +import com.imyeyu.api.modules.gao.entity.GaoPointLedger; +import com.imyeyu.api.modules.gao.service.GaoPointLedgerService; +import com.imyeyu.api.modules.gao.service.GaoPointTriggerService; +import com.imyeyu.api.modules.gao.service.GaoStoreService; +import com.imyeyu.api.modules.gao.vo.GaoPointAdjustRequest; +import com.imyeyu.api.modules.gao.vo.GaoPointLedgerPage; +import com.imyeyu.api.modules.user.service.RoleChecker; +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.RestController; + +/// GAO 客户积分流水接口 +/// +/// @author Codex +/// @since 2026-08-11 +@RestController +@RequiredArgsConstructor +@RequestMapping("/gao/customer/point") +public class GaoPointLedgerController { + + private final RoleChecker roleChecker; + private final GaoStoreService storeService; + private final GaoPointLedgerService ledgerService; + private final GaoPointTriggerService triggerService; + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.POINT_LEDGER_READ) + @PostMapping("/ledger/list") + public PageResult list(@RequestBody GaoPointLedgerPage page) { + TimiException.required(page, "not found page"); + page.setStoreId(resolveStoreId(page.getStoreId())); + return ledgerService.pageByQuery(page); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.POINT_ADJUST) + @PostMapping("/adjust") + public void adjust(@RequestBody GaoPointAdjustRequest request) { + request.setStoreId(resolveStoreId(request.getStoreId())); + triggerService.adjust(request); + } + + private String resolveStoreId(String requestedStoreId) { + if (roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) { + return requestedStoreId; + } + return storeService.getBelongIdByRequiredLoginUserId(); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPointRuleController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPointRuleController.java new file mode 100644 index 0000000..02c6e9d --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPointRuleController.java @@ -0,0 +1,108 @@ +package com.imyeyu.api.modules.gao.controller; + +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; +import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; +import com.imyeyu.api.modules.gao.bean.GaoRoleCode; +import com.imyeyu.api.modules.gao.entity.GaoPointRule; +import com.imyeyu.api.modules.gao.service.GaoPointRuleService; +import com.imyeyu.api.modules.gao.service.GaoStoreService; +import com.imyeyu.api.modules.user.service.RoleChecker; +import com.imyeyu.java.TimiJava; +import com.imyeyu.java.bean.timi.TimiException; +import com.imyeyu.spring.annotation.AOPLog; +import com.imyeyu.spring.annotation.RequestRateLimit; +import com.imyeyu.spring.bean.Page; +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-08-11 +@RestController +@RequiredArgsConstructor +@RequestMapping("/gao/customer/point") +public class GaoPointRuleController { + + private final RoleChecker roleChecker; + private final GaoStoreService storeService; + private final GaoPointRuleService ruleService; + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.POINT_RULE_READ) + @PostMapping("/rule/list") + public PageResult list(@RequestBody Page page) { + TimiException.required(page, "not found page"); + if (!isGlobalManager()) { + GaoPointRule example = TimiJava.defaultIfNull(page.getEqualsExample(), new GaoPointRule()); + example.setStoreId(resolveStoreId(null)); + page.setEqualsExample(example); + } + return ruleService.page(page); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.POINT_RULE_READ) + @PostMapping("/rule/detail") + public GaoPointRule detail(@RequestParam String id) { + GaoPointRule rule = ruleService.get(id); + TimiException.required(rule, "not found point rule"); + checkStore(rule.getStoreId()); + return rule; + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.POINT_RULE_CREATE) + @PostMapping("/rule/create") + public String create(@RequestBody GaoPointRule rule) { + rule.setStoreId(resolveStoreId(rule.getStoreId())); + ruleService.create(rule); + return rule.getId(); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.POINT_RULE_UPDATE) + @PostMapping("/rule/update") + public void update(@RequestBody GaoPointRule rule) { + rule.setStoreId(resolveStoreId(rule.getStoreId())); + ruleService.update(rule); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.POINT_RULE_DELETE) + @PostMapping("/rule/delete") + public void delete(@RequestParam String id) { + GaoPointRule rule = ruleService.get(id); + TimiException.required(rule, "not found point rule"); + checkStore(rule.getStoreId()); + ruleService.delete(id); + } + + private String resolveStoreId(String requestedStoreId) { + if (isGlobalManager()) { + return requestedStoreId; + } + return storeService.getBelongIdByRequiredLoginUserId(); + } + + private void checkStore(String storeId) { + if (!isGlobalManager()) { + TimiException.requiredTrue(resolveStoreId(null).equals(storeId), "无权操作其他门店数据"); + } + } + + private boolean isGlobalManager() { + return roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name()); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRegistrationEventController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRegistrationEventController.java deleted file mode 100644 index 0063ad6..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRegistrationEventController.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.imyeyu.api.modules.gao.controller; - -import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; -import com.imyeyu.api.modules.gao.annotation.RequireGaoRole; -import com.imyeyu.api.bean.RoleMatchMode; -import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; -import com.imyeyu.api.modules.gao.bean.GaoRoleCode; -import com.imyeyu.api.modules.gao.entity.GaoRegistrationEvent; -import com.imyeyu.api.modules.gao.service.GaoRegistrationEventService; -import com.imyeyu.spring.annotation.AOPLog; -import com.imyeyu.spring.annotation.RequestRateLimit; -import com.imyeyu.spring.bean.Page; -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-07-27 -@RestController -@RequiredArgsConstructor -@RequestMapping("/gao/registration-event") -@RequireGaoRole(value = {GaoRoleCode.STORE_MANAGER, GaoRoleCode.EMPLOYEE}, mode = RoleMatchMode.ANY) -public class GaoRegistrationEventController { - - private final GaoRegistrationEventService service; - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_READ) - @PostMapping("/list") - public PageResult list(@RequestBody Page page) { - return service.pageWithRegistrationEventLimit(page); - } - - @AOPLog - @RequestRateLimit - @PostMapping("/enabled/list") - public List enabledList() { - return service.listEnabled(); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_READ) - @PostMapping("/detail") - public GaoRegistrationEvent detail(@RequestParam String id) { - return service.get(id); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_CREATE) - @PostMapping("/create") - public void create(@RequestBody GaoRegistrationEvent registrationEvent) { - service.create(registrationEvent); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_UPDATE) - @PostMapping("/update") - public void update(@RequestBody GaoRegistrationEvent registrationEvent) { - service.update(registrationEvent); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_UPDATE) - @PostMapping("/sort") - public void sort(@RequestBody List idList) { - service.sort(idList); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_DELETE) - @PostMapping("/delete") - public void delete(@RequestParam String id) { - service.delete(id); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRegistrationEventRecordController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRegistrationEventRecordController.java deleted file mode 100644 index 6cdfc4a..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRegistrationEventRecordController.java +++ /dev/null @@ -1,201 +0,0 @@ -package com.imyeyu.api.modules.gao.controller; - -import com.fasterxml.jackson.annotation.JsonView; -import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; -import com.imyeyu.api.modules.gao.bean.GaoRoleCode; -import com.imyeyu.api.bean.RoleMatchMode; -import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; -import com.imyeyu.api.modules.gao.annotation.RequireGaoRole; -import com.imyeyu.api.modules.gao.entity.GaoRegistrationEventRecord; -import com.imyeyu.api.modules.gao.service.GaoRegistrationEventRecordService; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventPeriodStatView; -import com.imyeyu.api.modules.gao.vo.GaoCustomerRegisterStatView; -import com.imyeyu.api.modules.gao.vo.GaoCustomerRegistrationRankView; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordCreateReq; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordCalendarView; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordDailyStatView; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordListItemView; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordPeriodReq; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordRangeReq; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordStatReq; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordTrendStatReq; -import com.imyeyu.spring.annotation.AOPLog; -import com.imyeyu.spring.annotation.IgnoreGlobalReturn; -import com.imyeyu.spring.annotation.RequestRateLimit; -import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.bean.PageResult; -import com.imyeyu.spring.util.ResponseView; -import jakarta.servlet.http.HttpServletResponse; -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.io.IOException; -import java.net.URLEncoder; -import java.util.List; - -/// -/// GAO 登记事件记录接口 -/// -/// @author Codex -/// @since 2026-07-27 -@RestController -@RequiredArgsConstructor -@RequestMapping("/gao/registration-event-record") -@RequireGaoRole(value = {GaoRoleCode.STORE_MANAGER, GaoRoleCode.EMPLOYEE}, mode = RoleMatchMode.ANY) -public class GaoRegistrationEventRecordController { - - private final GaoRegistrationEventRecordService service; - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_READ) - @PostMapping("/list") - @JsonView(ResponseView.Public.class) - public PageResult list(@RequestBody Page page) { - return service.page(page); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_READ) - @PostMapping("/range/page") - public PageResult rangePage(@RequestBody Page page) { - return service.pageByRange(page); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_READ) - @PostMapping("/detail") - @JsonView(ResponseView.Public.class) - public GaoRegistrationEventRecord detail(@RequestParam String id) { - return service.get(id); - } - - @AOPLog - @RequestRateLimit - @PostMapping("/create") - @JsonView(ResponseView.Public.class) - public List create(@RequestBody GaoRegistrationEventRecordCreateReq req) { - return service.createRecords(req); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_UPDATE) - @PostMapping("/update") - public void update(@RequestBody GaoRegistrationEventRecord record) { - service.update(record); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_DELETE) - @PostMapping("/delete") - public void delete(@RequestParam String id) { - service.delete(id); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_READ) - @PostMapping("/period/list") - @JsonView(ResponseView.Public.class) - public List periodList(@RequestBody GaoRegistrationEventRecordPeriodReq req) { - return service.listByRegistrationEventAndPeriod(req); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_READ) - @PostMapping("/range/list") - @JsonView(ResponseView.Public.class) - public List rangeList(@RequestBody GaoRegistrationEventRecordRangeReq req) { - return service.listByRange(req); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_READ) - @PostMapping("/range/calendar/list") - public List calendarRangeList(@RequestBody GaoRegistrationEventRecordRangeReq req) { - return service.listCalendarByRange(req); - } - - @AOPLog - @RequestRateLimit - @IgnoreGlobalReturn - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_EXPORT) - @PostMapping("/range/export") - public void rangeExport(@RequestBody GaoRegistrationEventRecordRangeReq req, HttpServletResponse resp) throws IOException { - String fileName = URLEncoder.encode("登记事件记录.xlsx", "UTF-8").replace("+", "%20"); - resp.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); - resp.setHeader("Content-Disposition", "attachment; filename=\"gao-registration-event-record.xlsx\"; filename*=UTF-8''" + fileName); - resp.getOutputStream().write(service.exportRangeExcel(req)); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.STAT_READ) - @PostMapping("/period/stat") - public List periodStat(@RequestBody GaoRegistrationEventRecordPeriodReq req) { - return service.statByRegistrationEventAndPeriodType(req.getRegistrationEventId(), req.getPeriodType()); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.STAT_READ) - @PostMapping("/customer/stat") - public List customerStat(@RequestParam String customerId) { - return service.statByCustomerId(customerId); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.STAT_READ) - @PostMapping("/customer/range/stat") - /// 按时间段统计单个客户各登记事件记录数 - public List customerRangeStat(@RequestBody GaoRegistrationEventRecordStatReq req) { - return service.statByCustomerAndRange(req); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.STAT_READ) - @PostMapping("/all-customer/range/stat") - /// 按时间段统计全部客户各登记事件记录数 - public List allCustomerRangeStat(@RequestBody GaoRegistrationEventRecordStatReq req) { - return service.statAllCustomerByRange(req); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.STAT_READ) - @PostMapping("/customer/registration-rank") - /// 按时间范围查询客户登记排行榜 - public List customerRegistrationRank(@RequestBody(required = false) GaoRegistrationEventRecordRangeReq req) { - return service.listCustomerRegistrationRank(req); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.STAT_READ) - @PostMapping("/customer/registration-event/daily/stat") - /// 按天统计单个客户单个登记事件记录趋势 - public List customerRegistrationEventDailyStat(@RequestBody GaoRegistrationEventRecordTrendStatReq req) { - return service.statDailyByCustomerAndRegistrationEvent(req); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.STAT_READ) - @PostMapping("/all-customer/registration-event/daily/stat") - /// 按天统计全部客户单个登记事件记录趋势 - public List allCustomerRegistrationEventDailyStat(@RequestBody GaoRegistrationEventRecordTrendStatReq req) { - return service.statDailyByRegistrationEvent(req); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRoleController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRoleController.java index cff4cd3..c9a1839 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRoleController.java +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRoleController.java @@ -4,7 +4,8 @@ import com.imyeyu.api.bean.ModuleCode; import com.imyeyu.api.modules.gao.annotation.RequireGaoRole; import com.imyeyu.api.modules.gao.bean.GaoRoleCode; import com.imyeyu.api.modules.user.entity.Role; -import com.imyeyu.api.modules.user.service.RoleService; +import com.imyeyu.api.modules.user.service.AuthorizationScopeService; +import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.RequestRateLimit; import lombok.RequiredArgsConstructor; @@ -14,23 +15,23 @@ import org.springframework.web.bind.annotation.RestController; import java.util.List; -/// /// GAO 角色接口 /// -/// @author Codex -/// @since 2026-07-28 +/// @author 夜雨 +/// @since 2026-07-28 14:15 @RestController +@RequireGaoRole(GaoRoleCode.STORE_MANAGER) @RequiredArgsConstructor @RequestMapping("/gao/role") -@RequireGaoRole(GaoRoleCode.STORE_MANAGER) public class GaoRoleController { - private final RoleService roleService; + private final UserLoginService userLoginService; + private final AuthorizationScopeService authorizationScopeService; @AOPLog @RequestRateLimit @PostMapping("/available/list") public List availableList() { - return roleService.listByModuleCode(ModuleCode.GAO); + return authorizationScopeService.listGrantableRole(userLoginService.getRequireLoginUserId(), ModuleCode.GAO); } } diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreBusinessDayController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreBusinessDayController.java new file mode 100644 index 0000000..68dad8a --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreBusinessDayController.java @@ -0,0 +1,82 @@ +package com.imyeyu.api.modules.gao.controller; + +import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; +import com.imyeyu.api.modules.gao.annotation.RequireGaoRole; +import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; +import com.imyeyu.api.modules.gao.bean.GaoRoleCode; +import com.imyeyu.api.modules.gao.entity.GaoStoreBusinessDay; +import com.imyeyu.api.modules.gao.service.GaoStoreBusinessDayService; +import com.imyeyu.spring.annotation.AOPLog; +import com.imyeyu.spring.annotation.RequestRateLimit; +import com.imyeyu.spring.bean.Page; +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-08-06 +@RestController +@RequiredArgsConstructor +@RequestMapping("/gao/store/business/day") +@RequireGaoRole(GaoRoleCode.GLOBAL_MANAGER) +public class GaoStoreBusinessDayController { + + private final GaoStoreBusinessDayService service; + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STORE_READ) + @PostMapping("/list") + public PageResult list(@RequestBody Page page) { + return service.page(page); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STORE_READ) + @PostMapping("/calendar") + public List calendar(@RequestParam String storeId, @RequestParam Integer beginDateValue, @RequestParam Integer endDateValue) { + return service.listByDateRange(storeId, beginDateValue, endDateValue); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STORE_READ) + @PostMapping("/detail") + public GaoStoreBusinessDay detail(@RequestParam String id) { + return service.get(id); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STORE_CREATE) + @PostMapping("/create") + public String create(@RequestBody GaoStoreBusinessDay businessDay) { + service.create(businessDay); + return businessDay.getId(); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STORE_UPDATE) + @PostMapping("/update") + public void update(@RequestBody GaoStoreBusinessDay businessDay) { + service.update(businessDay); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STORE_DELETE) + @PostMapping("/delete") + public void delete(@RequestParam String id) { + service.delete(id); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreChartController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreChartController.java new file mode 100644 index 0000000..88a004e --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreChartController.java @@ -0,0 +1,68 @@ +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.GaoStoreChartService; +import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView; +import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordCalendarView; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordDailyStatView; +import com.imyeyu.api.modules.gao.vo.GaoStoreChartReq; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/// GAO 门店报表接口 +/// +/// @author Codex +/// @since 2026-08-11 +@RestController +@RequiredArgsConstructor +@RequestMapping("/gao/store/chart") +public class GaoStoreChartController { + + private final GaoStoreChartService service; + + /// 查询客户增长趋势 + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STAT_READ) + @PostMapping("/customer/daily") + public List customerDaily(@RequestBody GaoStoreChartReq req) { + return service.customerDailyTrend(req); + } + + /// 查询客户性别统计 + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STAT_READ) + @PostMapping("/customer/gender") + public List customerGender(@RequestParam String storeId) { + return service.customerGender(storeId); + } + + /// 查询登记事件每日统计,可同时支撑登记趋势和事件结构图表 + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STAT_READ) + @PostMapping("/event/record/daily") + public List eventRecordDaily(@RequestBody GaoStoreChartReq req) { + return service.eventRecordDailyStat(req); + } + + /// 查询登记事件日历记录 + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STAT_READ) + @PostMapping("/event/record/calendar") + public List eventRecordCalendar(@RequestBody GaoStoreChartReq req) { + return service.eventRecordCalendar(req); + } +} 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 new file mode 100644 index 0000000..21b0d7e --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreController.java @@ -0,0 +1,114 @@ +package com.imyeyu.api.modules.gao.controller; + +import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; +import com.imyeyu.api.modules.gao.annotation.RequireGaoRole; +import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; +import com.imyeyu.api.modules.gao.bean.GaoRoleCode; +import com.imyeyu.api.modules.gao.entity.GaoStore; +import com.imyeyu.api.modules.gao.service.GaoStoreService; +import com.imyeyu.api.modules.user.service.UserLoginService; +import com.imyeyu.network.Network; +import com.imyeyu.spring.TimiSpring; +import com.imyeyu.spring.annotation.AOPLog; +import com.imyeyu.spring.annotation.IgnoreGlobalReturn; +import com.imyeyu.spring.annotation.RequestRateLimit; +import com.imyeyu.spring.bean.Page; +import com.imyeyu.spring.bean.PageResult; +import com.imyeyu.utils.Time; +import jakarta.servlet.http.HttpServletResponse; +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.io.IOException; +import java.util.List; + +/// GAO 门店接口 +/// +/// @author Codex +/// @since 2026-08-06 +@RestController +@RequiredArgsConstructor +@RequestMapping("/gao/store") +public class GaoStoreController { + + private final GaoStoreService service; + private final UserLoginService userLoginService; + + @AOPLog + @RequireGaoRole(GaoRoleCode.GLOBAL_MANAGER) + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STORE_READ) + @PostMapping("/list") + public PageResult list(@RequestBody Page page) { + return service.page(page); + } + + @AOPLog + @RequireGaoRole(GaoRoleCode.STORE_MANAGER) + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STORE_READ) + @PostMapping("/detail") + public GaoStore detail(@RequestParam String id) { + return service.get(id); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STORE_CREATE) + @PostMapping("/create") + public String create(@RequestBody GaoStore store) { + service.create(store); + return store.getId(); + } + + @AOPLog + @RequireGaoRole(GaoRoleCode.STORE_MANAGER) + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STORE_UPDATE) + @PostMapping("/update") + public void update(@RequestBody GaoStore store) { + service.update(store); + } + + @AOPLog + @RequireGaoRole(GaoRoleCode.GLOBAL_MANAGER) + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STORE_DELETE) + @PostMapping("/delete") + public void delete(@RequestParam String id) { + service.delete(id); + } + + @AOPLog + @RequireGaoRole(GaoRoleCode.GLOBAL_MANAGER) + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STORE_UPDATE) + @PostMapping("/sort") + public void sort(@RequestBody List idList) { + service.sort(idList); + } + + @AOPLog + @RequestRateLimit + @IgnoreGlobalReturn + @RequireGaoPermission(GaoPermissionCode.STORE_EXPORT) + @PostMapping("/export") + public void export(@RequestBody Page page) throws IOException { + HttpServletResponse resp = TimiSpring.getResponse(); + String contentDisposition = Network.getFileDownloadHeader("门店列表-%s.xlsx".formatted(Time.serialize.format(Time.now()))); + resp.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + resp.setHeader("Content-Disposition", contentDisposition); + resp.getOutputStream().write(service.export(page)); + } + + @AOPLog + @RequestRateLimit + @PostMapping("/user") + public GaoStore getLoginStore() { + return service.getByUserId(userLoginService.getRequireLoginUserId()); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoUserController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoUserController.java index 1d38644..0452932 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoUserController.java +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoUserController.java @@ -2,14 +2,23 @@ package com.imyeyu.api.modules.gao.controller; import com.fasterxml.jackson.annotation.JsonView; import com.imyeyu.api.bean.ModuleCode; +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.annotation.RequireGaoRole; import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; import com.imyeyu.api.modules.gao.bean.GaoRoleCode; import com.imyeyu.api.modules.gao.entity.GaoUser; +import com.imyeyu.api.modules.gao.service.GaoStoreService; import com.imyeyu.api.modules.gao.service.GaoUserService; import com.imyeyu.api.modules.user.entity.Role; +import com.imyeyu.api.modules.user.entity.User; +import com.imyeyu.api.modules.user.service.RoleChecker; import com.imyeyu.api.modules.user.service.RoleService; +import com.imyeyu.api.modules.user.service.UserLoginService; +import com.imyeyu.api.modules.user.service.UserRoleService; +import com.imyeyu.api.modules.user.service.UserService; +import com.imyeyu.java.TimiJava; import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.RequestRateLimit; import com.imyeyu.spring.bean.Page; @@ -23,20 +32,25 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.List; +import java.util.Map; -/// /// GAO 用户接口 /// -/// @author Codex -/// @since 2026-07-30 +/// @author 夜雨 +/// @since 2026-07-30 14:15 @RestController @RequiredArgsConstructor @RequestMapping("/gao/user") -@RequireGaoRole(GaoRoleCode.STORE_MANAGER) public class GaoUserController { - private final GaoUserService service; + private final UserService userService; private final RoleService roleService; + private final RoleChecker roleChecker; + private final GaoUserService service; + private final UserRoleService userRoleService; + private final GaoStoreService gaoStoreService; + private final UserLoginService userLoginService; + private final AttachmentService attachmentService; @AOPLog @RequestRateLimit @@ -44,7 +58,22 @@ public class GaoUserController { @PostMapping("/list") @JsonView(ResponseView.Public.class) public PageResult list(@RequestBody Page page) { - return service.pageWithUser(page); + if (!roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) { + // 非全局管理只能看所在店用户 + page.setEqualsExample(TimiJava.defaultIfNull(page.getEqualsExample(), new GaoUser())); + page.getEqualsExample().setStoreId(gaoStoreService.getByUserId(userLoginService.getRequireLoginUserId()).getId()); + } + PageResult result = service.page(page); + { + List userIdList = result.getList().stream().map(GaoUser::getUserId).toList(); + Map userMap = userService.mapByIdList(userIdList); + Map> roleMap = userRoleService.mapRoleListByUserIdList(userIdList); + for (GaoUser gaoUser : result.getList()) { + gaoUser.setUser(userMap.get(gaoUser.getUserId())); + gaoUser.getUser().setRoleEntityList(roleMap.get(gaoUser.getUserId())); + } + } + return result; } @AOPLog @@ -53,16 +82,14 @@ public class GaoUserController { @PostMapping("/detail") @JsonView(ResponseView.Public.class) public GaoUser detail(@RequestParam String id) { - return service.get(id); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.USER_READ) - @PostMapping("/detail/user") - @JsonView(ResponseView.Public.class) - public GaoUser detailByUserId(@RequestParam String userId) { - return service.getByUserId(userId); + GaoUser result = service.get(id); + { + User user = userService.get(result.getUserId()); + user.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.USER, user.getId())); + user.setRoleEntityList(userRoleService.listRoleByUserId(user.getId())); + result.setUser(user); + } + return result; } @AOPLog @@ -71,6 +98,9 @@ public class GaoUserController { @PostMapping("/create") @JsonView(ResponseView.Public.class) public GaoUser create(@RequestBody GaoUser gaoUser) { + if (!roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) { + gaoUser.setStoreId(gaoStoreService.getByUserId(userLoginService.getRequireLoginUserId()).getId()); + } service.create(gaoUser); return service.get(gaoUser.getId()); } @@ -85,6 +115,17 @@ public class GaoUserController { return service.get(gaoUser.getId()); } + @AOPLog + @RequestRateLimit + @RequireGaoRole(GaoRoleCode.GLOBAL_MANAGER) + @RequireGaoPermission(GaoPermissionCode.USER_UPDATE) + @PostMapping("/transfer/store") + @JsonView(ResponseView.Public.class) + public GaoUser transferStore(@RequestBody GaoUser gaoUser) { + service.transferStore(gaoUser); + return service.get(gaoUser.getId()); + } + @AOPLog @RequestRateLimit @RequireGaoPermission(GaoPermissionCode.USER_DELETE) diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomer.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomer.java index a37ed88..36573a3 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomer.java +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomer.java @@ -1,11 +1,11 @@ package com.imyeyu.api.modules.gao.entity; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonFormat; 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.api.modules.user.bean.Gender; +import com.imyeyu.api.modules.user.entity.User; +import com.imyeyu.java.TimiJava; import com.imyeyu.spring.annotation.table.Transient; import com.imyeyu.spring.entity.UUIDEntity; import lombok.Data; @@ -48,6 +48,9 @@ public class GaoCustomer extends UUIDEntity { /// 客户编码 private String code; + /// 门店 ID + private String storeId; + /// 姓名 private String name; @@ -55,7 +58,7 @@ public class GaoCustomer extends UUIDEntity { private Gender gender; /// 年龄 - private Integer age; + private Long age; /// 出生日期,数据库只保存公历日期 @JsonFormat(pattern = "yyyy-MM-dd") @@ -64,15 +67,6 @@ public class GaoCustomer extends UUIDEntity { /// 出生日期录入时使用的历法 private BirthdateCalendar birthdateCalendar; - @JsonIgnore - @Transient - private boolean birthdateProvided; - - public void setBirthdate(LocalDate birthdate) { - this.birthdate = birthdate; - this.birthdateProvided = true; - } - /// 电话 private String telephone; @@ -91,24 +85,27 @@ public class GaoCustomer extends UUIDEntity { /// 介绍人客户 ID private String introducerCustomerId; + private Long invitedCount; + /// 录入用户 ID private String creatorUserId; - @Transient - private Long lastSignInAt; - @Transient private GaoCustomer introducerCustomer; @Transient private User creatorUser; - private Long invitedCount; - @Transient private List attachmentList; - /// 临时照片附件 ID 列表 @Transient - private List attachmentIdList; + private GaoEventRecord eventRecord; + + @Transient + private List eventRecordList; + + public boolean hasIntroducer() { + return TimiJava.isNotEmpty(introducerCustomerId); + } } diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEvent.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEvent.java similarity index 75% rename from src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEvent.java rename to src/main/java/com/imyeyu/api/modules/gao/entity/GaoEvent.java index 40a792b..0a354b1 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEvent.java +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEvent.java @@ -2,8 +2,10 @@ package com.imyeyu.api.modules.gao.entity; import com.imyeyu.spring.annotation.table.Transient; import com.imyeyu.spring.entity.UUIDEntity; +import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.Getter; import java.util.List; @@ -14,7 +16,7 @@ import java.util.List; /// @since 2026-07-27 @Data @EqualsAndHashCode(callSuper = true) -public class GaoRegistrationEvent extends UUIDEntity { +public class GaoEvent extends UUIDEntity { /// /// 登记事件状态 @@ -36,38 +38,44 @@ public class GaoRegistrationEvent extends UUIDEntity { DELETED } - /// /// 登记限制类型 /// - /// @author Codex - /// @since 2026-08-01 + /// @author 夜雨 + /// @since 2026-08-01 14:37 + @Getter + @AllArgsConstructor public enum LimitType { /// 不限制 - NONE, + NONE(null), - /// 每天上午、下午各一次 - DAILY_HALF_DAY, + /// 每天上午、下午各 N 次 + DAILY_HALF_DAY(1L), - /// 每个自然日一次 - NATURAL_DAY, + /// 每个自然日 N 次 + NATURAL_DAY(1L), /// 每隔 N 小时一次 - INTERVAL_HOUR, + INTERVAL_HOUR(1L), - /// 每个自然月一次 - NATURAL_MONTH, + /// 每个自然月 N 次 + NATURAL_MONTH(1L), /// 事件总登记次数上限 - TOTAL_LIMIT, + TOTAL_LIMIT(1L), /// 单个客户总登记次数上限 - CUSTOMER_TOTAL_LIMIT + CUSTOMER_TOTAL_LIMIT(1L); + + final Long min; } /// 登记事件名称 private String name; + /// 门店 ID + private String storeId; + /// 说明 private String description; diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEventRecord.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEventRecord.java similarity index 66% rename from src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEventRecord.java rename to src/main/java/com/imyeyu/api/modules/gao/entity/GaoEventRecord.java index ec92999..39f4739 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEventRecord.java +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEventRecord.java @@ -16,7 +16,7 @@ import java.util.List; /// @since 2026-07-27 @Data @EqualsAndHashCode(callSuper = true) -public class GaoRegistrationEventRecord extends UUIDEntity { +public class GaoEventRecord extends UUIDEntity { /// /// 登记事件记录附件类型 @@ -31,8 +31,11 @@ public class GaoRegistrationEventRecord extends UUIDEntity { /// 客户 ID private String customerId; + /// 门店 ID + private String storeId; + /// 登记登记事件 ID - private String registrationEventId; + private String eventId; /// 操作管理员用户 ID private String operatorUserId; @@ -43,11 +46,21 @@ public class GaoRegistrationEventRecord extends UUIDEntity { /// 登记时间 private Long registeredAt; + /// 登记日期值,格式为 yyyyMMdd + private Integer registeredDateValue; + + /// 营业日递增序号 + private Long businessDayNo; + + /// 本次登记提交的积分值,只参与积分触发,不落库 + @Transient + private Long pointValue; + @Transient private GaoCustomer customer; @Transient - private GaoRegistrationEvent registrationEvent; + private GaoEvent event; @Transient private User operator; @@ -55,7 +68,7 @@ public class GaoRegistrationEventRecord extends UUIDEntity { @Transient private List attachmentList; - /// 临时照片附件 ID 列表 + /// 客户 ID 列表,批量登记时使用 @Transient - private List attachmentIdList; + private List customerIdList; } diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEventRoleRelation.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEventRoleRelation.java similarity index 76% rename from src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEventRoleRelation.java rename to src/main/java/com/imyeyu/api/modules/gao/entity/GaoEventRoleRelation.java index 0922068..e5f0ba7 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEventRoleRelation.java +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEventRoleRelation.java @@ -11,10 +11,10 @@ import lombok.EqualsAndHashCode; /// @since 2026-07-28 @Data @EqualsAndHashCode(callSuper = true) -public class GaoRegistrationEventRoleRelation extends UUIDEntity { +public class GaoEventRoleRelation extends UUIDEntity { /// 登记事件 ID - private String registrationEventId; + private String eventId; /// 角色代码 private String roleCode; diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPointAccount.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPointAccount.java new file mode 100644 index 0000000..87c06eb --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPointAccount.java @@ -0,0 +1,35 @@ +package com.imyeyu.api.modules.gao.entity; + +import com.imyeyu.spring.entity.UUIDEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/// GAO 客户积分账户 +/// +/// @author Codex +/// @since 2026-08-11 +@Data +@EqualsAndHashCode(callSuper = true) +public class GaoPointAccount extends UUIDEntity { + + /// 门店 ID + private String storeId; + + /// 客户 ID + private String customerId; + + /// 当前可用积分余额 + private Long balance; + + /// 累计获得积分 + private Long totalEarned; + + /// 累计撤销积分 + private Long totalRevoked; + + /// 累计人工调整积分,可正可负 + private Long totalAdjusted; + + /// 乐观锁版本 + private Long version; +} 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 new file mode 100644 index 0000000..550c558 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPointLedger.java @@ -0,0 +1,86 @@ +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-08-11 +@Data +@EqualsAndHashCode(callSuper = true) +public class GaoPointLedger extends UUIDEntity { + + /// 流水变化类型 + public enum ChangeType { + + /// 登记事件获得积分 + EARN, + + /// 删除登记记录撤销积分 + REVOKE, + + /// 人工调整积分 + ADJUST + } + + /// 流水方向 + public enum Direction { + + /// 增加积分 + INCREASE, + + /// 减少积分 + DECREASE + } + + /// 客户积分账户 ID + private String accountId; + + /// 门店 ID + private String storeId; + + /// 客户 ID + private String customerId; + + /// 变化类型 + private ChangeType changeType; + + /// 方向 + private Direction direction; + + /// 本次变化积分,增加为正,减少为负 + private Long pointDelta; + + /// 变化前余额 + private Long balanceBefore; + + /// 变化后余额 + private Long balanceAfter; + + /// 命中的积分规则 ID + private String ruleId; + + /// 登记事件 ID + private String eventId; + + /// 登记记录 ID + private String eventRecordId; + + /// 来源快照 + private JsonNode sourcePayload; + + /// 幂等键 + private String idempotencyKey; + + /// 备注 + private String remark; + + /// 操作用户 ID + private String operatorUserId; + + /// 发生时间 + private Long occurredAt; +} 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 new file mode 100644 index 0000000..b37d46c --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPointRule.java @@ -0,0 +1,96 @@ +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-08-11 +@Data +@EqualsAndHashCode(callSuper = true) +public class GaoPointRule extends UUIDEntity { + + /// 规则状态 + public enum Status { + + /// 草稿,不参与触发 + DRAFT, + + /// 启用,参与触发 + ACTIVE, + + /// 停用,不参与触发 + INACTIVE, + + /// 已删除,不参与触发 + DELETED + } + + /// 触发类型 + public enum TriggerType { + + /// 每登记一次触发一次 + EVERY_TIME, + + /// 客户首次登记该事件触发一次 + FIRST_TIME, + + /// 客户累计登记达到 N 次后每次触发 + AFTER_COUNT_EVERY_TIME, + + /// 使用登记接口提交的积分值触发 + SOURCE_VALUE + } + + /// 积分值模式 + public enum PointMode { + + /// 使用规则固定积分值 + FIXED, + + /// 使用登记接口提交的积分值 + SOURCE_VALUE + } + + /// 门店 ID + private String storeId; + + /// 登记事件 ID + private String eventId; + + /// 规则名称 + private String name; + + /// 规则状态 + private Status status; + + /// 触发类型 + private TriggerType triggerType; + + /// 积分值模式 + private PointMode pointMode; + + /// 固定积分值 + private Long pointValue; + + /// 条件配置 + private JsonNode conditionJson; + + /// 生效开始时间戳,毫秒,空为不限制 + private Long beginAt; + + /// 生效结束时间戳,毫秒,空为不限制 + private Long endAt; + + /// 优先级,数值小优先 + private Integer priority; + + /// true 为命中后停止继续匹配后续规则 + private Boolean exclusive; + + /// 备注 + private String remark; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoStore.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoStore.java new file mode 100644 index 0000000..f9042b0 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoStore.java @@ -0,0 +1,57 @@ +package com.imyeyu.api.modules.gao.entity; + +import com.imyeyu.spring.entity.UUIDEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/// +/// GAO 门店 +/// +/// @author Codex +/// @since 2026-08-06 +@Data +@EqualsAndHashCode(callSuper = true) +public class GaoStore extends UUIDEntity { + + /// + /// 门店状态 + /// + /// @author Codex + /// @since 2026-08-06 + public enum Status { + + /// 可用 + ACTIVE, + + /// 停用 + INACTIVE, + + /// 已删除 + DELETED + } + + /// 门店编码 + private String code; + + /// 门店名称 + private String name; + + /// 状态 + private Status status; + + /// 电话 + private String telephone; + + /// 地址 + private String address; + + /// 备注 + private String remark; + + /// 排序值 + private Integer sort; + + public boolean isActivated() { + return status == Status.ACTIVE; + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoStoreBusinessDay.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoStoreBusinessDay.java new file mode 100644 index 0000000..6192ef0 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoStoreBusinessDay.java @@ -0,0 +1,44 @@ +package com.imyeyu.api.modules.gao.entity; + +import com.imyeyu.spring.entity.UUIDEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/// +/// GAO 门店营业日 +/// +/// @author Codex +/// @since 2026-08-06 +@Data +@EqualsAndHashCode(callSuper = true) +public class GaoStoreBusinessDay extends UUIDEntity { + + /// + /// 营业状态 + /// + /// @author Codex + /// @since 2026-08-06 + public enum OpenStatus { + + /// 营业 + OPEN, + + /// 休息 + CLOSED + } + + /// 门店 ID + private String storeId; + + /// 日期值,格式为 yyyyMMdd + private Integer dateValue; + + /// 营业状态 + private OpenStatus openStatus; + + /// 营业日递增序号,仅营业日有值 + private Long businessDayNo; + + /// 原因 + private String reason; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoUser.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoUser.java index fd012ce..133ebdd 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoUser.java +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoUser.java @@ -1,14 +1,11 @@ package com.imyeyu.api.modules.gao.entity; import com.imyeyu.api.modules.user.entity.User; -import com.imyeyu.api.modules.user.entity.Role; import com.imyeyu.spring.annotation.table.Transient; import com.imyeyu.spring.entity.UUIDEntity; import lombok.Data; import lombok.EqualsAndHashCode; -import java.util.List; - /// /// GAO 用户 /// @@ -21,6 +18,9 @@ public class GaoUser extends UUIDEntity { /// 用户 ID private String userId; + /// 门店 ID + private String storeId; + /// true 为启用 private Boolean enabled; @@ -29,10 +29,4 @@ public class GaoUser extends UUIDEntity { @Transient private User user; - - @Transient - private List roleList; - - @Transient - private List roleIdList; } diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerMapper.java index 3c6aa26..cde3fac 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerMapper.java +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerMapper.java @@ -1,12 +1,12 @@ package com.imyeyu.api.modules.gao.mapper; import com.imyeyu.api.modules.gao.entity.GaoCustomer; -import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStatView; +import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView; import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView; import com.imyeyu.spring.mapper.BaseMapper; import org.apache.ibatis.annotations.Param; -import org.apache.ibatis.annotations.Select; +import java.util.Collection; import java.util.List; /// @@ -16,41 +16,23 @@ import java.util.List; /// @since 2026-07-27 public interface GaoCustomerMapper extends BaseMapper { - @Select("SELECT * FROM `gao_customer` WHERE `code` = #{code} AND `deleted_at` IS NULL LIMIT 1") - GaoCustomer selectByCode(@Param("code") String code); - - @Select("SELECT * FROM `gao_customer` WHERE `name` = #{name} AND `deleted_at` IS NULL LIMIT 1") - GaoCustomer selectByName(@Param("name") String name); - - List selectByIdList(@Param("idList") List idList); - - List search(@Param("keyword") String keyword, @Param("introducerCustomerId") String introducerCustomerId); - - Long countSearch(@Param("keyword") String keyword, @Param("introducerCustomerId") String introducerCustomerId); - - List searchPage(@Param("keyword") String keyword, @Param("introducerCustomerId") String introducerCustomerId, @Param("offset") Long offset, @Param("size") Long size); - - List selectInvitedList(@Param("introducerCustomerId") String introducerCustomerId); - - Long countInvited(@Param("introducerCustomerId") String introducerCustomerId); - - void updateInvitedCountDelta(@Param("customerId") String customerId, @Param("delta") Long delta); + List selectByIdList(Collection idList); /// 统计每日新增客户数 /// /// @param beginCreatedAt 开始创建时间 /// @param endCreatedAt 结束创建时间 /// @return 每日新增客户数 - List statDailyNewCustomer(@Param("beginCreatedAt") Long beginCreatedAt, @Param("endCreatedAt") Long endCreatedAt); + List stateDailyNewCustomer(@Param("storeId") String storeId, @Param("beginCreatedAt") Long beginCreatedAt, @Param("endCreatedAt") Long endCreatedAt); /// 统计开始时间前客户总数 /// /// @param beginCreatedAt 开始创建时间 /// @return 客户总数 - Long countCreatedBefore(@Param("beginCreatedAt") Long beginCreatedAt); + Long countCreatedBefore(@Param("storeId") String storeId, @Param("beginCreatedAt") Long beginCreatedAt); /// 统计客户性别分布 /// /// @return 性别统计列表 - List statGender(); + List stateGender(@Param("storeId") String storeId); } diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventMapper.java new file mode 100644 index 0000000..259b345 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventMapper.java @@ -0,0 +1,20 @@ +package com.imyeyu.api.modules.gao.mapper; + +import com.imyeyu.api.modules.gao.entity.GaoEvent; +import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.Collection; +import java.util.List; + +/// +/// 登记事件 Mapper +/// +/// @author 夜雨 +/// @since 2026-07-27 15:53 +public interface GaoEventMapper extends BaseMapper { + + List selectByIdList(Collection idList); + + List selectValidList(@Param("storeId") String storeId, @Param("now") Long now); +} 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 new file mode 100644 index 0000000..cbd2f00 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventRecordMapper.java @@ -0,0 +1,80 @@ +package com.imyeyu.api.modules.gao.mapper; + +import com.imyeyu.api.modules.gao.entity.GaoEventRecord; +import com.imyeyu.api.modules.gao.vo.GaoCustomerChartReq; +import com.imyeyu.api.modules.gao.vo.GaoCustomerEventRankView; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordCalendarView; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordDailyStatView; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordPage; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordRankPage; +import com.imyeyu.api.modules.gao.vo.GaoStoreChartReq; +import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/// 登记事件记录 Mapper +/// +/// @author 夜雨 +/// @since 2026-07-27 14:11 +public interface GaoEventRecordMapper extends BaseMapper { + + Long count(String storeId, String customerId, String eventId, Long beginAt, Long endAt); + + /// 按门店和登记日期统计登记记录数 + /// + /// @param storeId 门店 ID + /// @param registeredDateValue 登记日期值,格式为 yyyyMMdd + /// @return 登记记录数 + Long countByStoreIdAndRegisteredDateValue(@Param("storeId") String storeId, @Param("registeredDateValue") Integer registeredDateValue); + + /// 同步门店指定日期的登记记录营业日序号快照 + /// + /// @param storeId 门店 ID + /// @param registeredDateValue 登记日期值,格式为 yyyyMMdd + /// @param businessDayNo 营业日序号 + void updateBusinessDayNoByStoreIdAndRegisteredDateValue(@Param("storeId") String storeId, @Param("registeredDateValue") Integer registeredDateValue, @Param("businessDayNo") Long businessDayNo); + + long countByRange(GaoEventRecordPage page); + + /// 按任意时间范围查询登记事件记录 + /// + /// @return 登记事件记录列表 + List selectByRange(GaoEventRecordPage page); + + /// 统计客户登记排行榜总数 + /// + /// @param page 排行榜分页请求 + /// @return 客户数 + long countRankByRange(GaoEventRecordRankPage page); + + /// 分页查询客户登记排行榜 + /// + /// @param page 排行榜分页请求 + /// @return 客户登记排行榜 + List selectRankByRange(GaoEventRecordRankPage page); + + /// 按时间范围和事件批量统计每日登记数 + /// + /// @param req 门店报表查询请求 + /// @return 每日登记数 + List stateDailyByRange(GaoStoreChartReq req); + + /// 按时间范围查询登记日历记录 + /// + /// @param req 门店报表查询请求 + /// @return 日历记录列表 + List selectCalendarByRange(GaoStoreChartReq req); + + /// 按客户、时间范围和事件批量统计每日登记数 + /// + /// @param req 客户报表查询请求 + /// @return 每日登记数 + List stateCustomerDailyByRange(GaoCustomerChartReq req); + + /// 按客户和时间范围查询登记日历记录 + /// + /// @param req 客户报表查询请求 + /// @return 日历记录列表 + List selectCustomerCalendarByRange(GaoCustomerChartReq req); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventRoleRelationMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventRoleRelationMapper.java new file mode 100644 index 0000000..ec5cc81 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventRoleRelationMapper.java @@ -0,0 +1,16 @@ +package com.imyeyu.api.modules.gao.mapper; + +import com.imyeyu.api.modules.gao.entity.GaoEventRoleRelation; +import com.imyeyu.spring.mapper.BaseMapper; + +import java.util.Collection; +import java.util.List; + +/// 登记事件角色关联 Mapper +/// +/// @author 夜雨 +/// @since 2026-07-28 14:23 +public interface GaoEventRoleRelationMapper extends BaseMapper { + + List selectAllByEventIdList(Collection idList); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPointAccountMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPointAccountMapper.java new file mode 100644 index 0000000..4392dc3 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPointAccountMapper.java @@ -0,0 +1,41 @@ +package com.imyeyu.api.modules.gao.mapper; + +import com.imyeyu.api.modules.gao.entity.GaoPointAccount; +import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +/// GAO 客户积分账户 Mapper +/// +/// @author Codex +/// @since 2026-08-11 +public interface GaoPointAccountMapper extends BaseMapper { + + /// 查询客户当前有效积分账户 + /// + /// @param storeId 门店 ID + /// @param customerId 客户 ID + /// @return 积分账户 + GaoPointAccount selectByStoreIdAndCustomerId(@Param("storeId") String storeId, @Param("customerId") String customerId); + + /// 创建客户积分账户,已存在时保持原账户不变 + /// + /// @param account 积分账户 + void insertIfAbsent(@Param("account") GaoPointAccount account); + + /// 锁定积分账户 + /// + /// @param id 账户 ID + /// @return 积分账户 + GaoPointAccount selectForUpdate(@Param("id") String id); + + /// 原子更新积分账户余额和统计值 + /// + /// @param id 账户 ID + /// @param delta 本次余额变化 + /// @param totalEarnedDelta 累计获得积分变化 + /// @param totalRevokedDelta 累计撤销积分变化 + /// @param totalAdjustedDelta 累计人工调整积分变化 + /// @param updatedAt 更新时间 + /// @return 更新行数 + int updateBalance(@Param("id") String id, @Param("delta") long delta, @Param("totalEarnedDelta") long totalEarnedDelta, @Param("totalRevokedDelta") long totalRevokedDelta, @Param("totalAdjustedDelta") long totalAdjustedDelta, @Param("updatedAt") long updatedAt); +} 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 new file mode 100644 index 0000000..e1934fd --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPointLedgerMapper.java @@ -0,0 +1,45 @@ +package com.imyeyu.api.modules.gao.mapper; + +import com.imyeyu.api.modules.gao.entity.GaoPointLedger; +import com.imyeyu.api.modules.gao.vo.GaoPointLedgerPage; +import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/// GAO 客户积分流水 Mapper +/// +/// @author Codex +/// @since 2026-08-11 +public interface GaoPointLedgerMapper extends BaseMapper { + + /// 按幂等键查询流水 + /// + /// @param idempotencyKey 幂等键 + /// @return 流水 + GaoPointLedger selectByIdempotencyKey(@Param("idempotencyKey") String idempotencyKey); + + /// 查询登记记录产生的原始获得积分流水 + /// + /// @param eventRecordId 登记记录 ID + /// @return 获得积分流水 + List selectEarnByEventRecordId(@Param("eventRecordId") String eventRecordId); + + /// 批量查询幂等键对应的流水 + /// + /// @param idempotencyKeyList 幂等键列表 + /// @return 已存在流水 + List selectByIdempotencyKeyList(@Param("idempotencyKeyList") List idempotencyKeyList); + + /// 查询积分流水总数 + /// + /// @param page 查询条件 + /// @return 流水总数 + long countByQuery(GaoPointLedgerPage page); + + /// 分页查询积分流水 + /// + /// @param page 查询条件 + /// @return 流水列表 + List selectByQuery(GaoPointLedgerPage page); +} 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 new file mode 100644 index 0000000..15a5807 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPointRuleMapper.java @@ -0,0 +1,22 @@ +package com.imyeyu.api.modules.gao.mapper; + +import com.imyeyu.api.modules.gao.entity.GaoPointRule; +import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/// GAO 客户积分规则 Mapper +/// +/// @author Codex +/// @since 2026-08-11 +public interface GaoPointRuleMapper extends BaseMapper { + + /// 查询当前时间可匹配的启用规则 + /// + /// @param storeId 门店 ID + /// @param eventId 登记事件 ID + /// @param now 当前时间戳 + /// @return 按优先级排序的规则列表 + List selectActiveByStoreIdAndEventId(@Param("storeId") String storeId, @Param("eventId") String eventId, @Param("now") long now); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventMapper.java deleted file mode 100644 index 5ccd039..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventMapper.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.imyeyu.api.modules.gao.mapper; - -import com.imyeyu.api.modules.gao.entity.GaoRegistrationEvent; -import com.imyeyu.spring.mapper.BaseMapper; -import org.apache.ibatis.annotations.Param; - -import java.util.List; - -/// -/// 登记事件 Mapper -/// -/// @author Codex -/// @since 2026-07-27 -public interface GaoRegistrationEventMapper extends BaseMapper { - - List selectByIdList(@Param("idList") List idList); - - List selectEnabledList(@Param("now") Long now); -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventRecordMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventRecordMapper.java deleted file mode 100644 index 71c1dc3..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventRecordMapper.java +++ /dev/null @@ -1,157 +0,0 @@ -package com.imyeyu.api.modules.gao.mapper; - -import com.imyeyu.api.modules.gao.entity.GaoRegistrationEventRecord; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventPeriodStatView; -import com.imyeyu.api.modules.gao.vo.GaoCustomerRegisterStatView; -import com.imyeyu.api.modules.gao.vo.GaoCustomerRegistrationDayStatView; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordCalendarView; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordDailyStatView; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordListItemView; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordPeriodReq; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordRangeReq; -import com.imyeyu.spring.mapper.BaseMapper; -import org.apache.ibatis.annotations.Param; - -import java.util.List; - -/// -/// 登记事件记录 Mapper -/// -/// @author Codex -/// @since 2026-07-27 -public interface GaoRegistrationEventRecordMapper extends BaseMapper { - - /// 按登记事件查询客户最后登记事件记录 - /// - /// @param customerIdList 客户 ID 列表 - /// @param registrationEventId 登记事件 ID - /// @return 最后登记事件记录列表 - List selectByRegistrationEventAndPeriod(@Param("registrationEventId") String registrationEventId, @Param("periodType") GaoRegistrationEventRecordPeriodReq.PeriodType periodType, @Param("periodValue") Integer periodValue); - - /// 统计客户指定登记事件在时间范围内的登记数 - /// - /// @param customerId 客户 ID - /// @param registrationEventId 登记事件 ID - /// @param beginRegisteredAt 开始登记时间 - /// @param endRegisteredAt 结束登记时间 - /// @return 登记数 - Long countByCustomerIdAndRegistrationEventIdRange(@Param("customerId") String customerId, @Param("registrationEventId") String registrationEventId, @Param("beginRegisteredAt") Long beginRegisteredAt, @Param("endRegisteredAt") Long endRegisteredAt); - - /// 统计客户指定登记事件总登记数 - /// - /// @param customerId 客户 ID - /// @param registrationEventId 登记事件 ID - /// @return 登记数 - Long countByCustomerIdAndRegistrationEventId(@Param("customerId") String customerId, @Param("registrationEventId") String registrationEventId); - - /// 统计登记事件总登记数 - /// - /// @param registrationEventId 登记事件 ID - /// @return 登记数 - Long countByRegistrationEventId(@Param("registrationEventId") String registrationEventId); - - /// 按周期统计登记事件记录 - /// - /// @param registrationEventId 登记事件 ID - /// @param periodType 周期类型 - /// @return 统计结果 - List statByRegistrationEventAndPeriodType(@Param("registrationEventId") String registrationEventId, @Param("periodType") GaoRegistrationEventRecordPeriodReq.PeriodType periodType); - - /// 统计客户所有登记 - /// - /// @param customerId 客户 ID - /// @return 统计结果 - List statByCustomerId(@Param("customerId") String customerId); - - /// 按时间段统计单个客户登记 - /// - /// @param customerId 客户 ID - /// @param beginRegisteredAt 开始登记时间 - /// @param endRegisteredAt 结束登记时间 - /// @return 统计结果 - List statByCustomerIdAndRange(@Param("customerId") String customerId, @Param("beginRegisteredAt") Long beginRegisteredAt, @Param("endRegisteredAt") Long endRegisteredAt); - - /// 按时间段统计全部客户登记 - /// - /// @param beginRegisteredAt 开始登记时间 - /// @param endRegisteredAt 结束登记时间 - /// @return 统计结果 - List statAllCustomerByRange(@Param("beginRegisteredAt") Long beginRegisteredAt, @Param("endRegisteredAt") Long endRegisteredAt); - - /// 按时间范围统计全部客户每日登记 - /// - /// @param req 范围查询请求 - /// @return 每个有登记客户的每日统计 - List statCustomerRegistrationDay(@Param("req") GaoRegistrationEventRecordRangeReq req); - - /// 统计单个客户单个登记事件的每日登记事件记录数 - /// - /// @param customerId 客户 ID - /// @param registrationEventId 登记事件 ID - /// @param beginRegisteredAt 开始登记时间 - /// @param endRegisteredAt 结束登记时间 - /// @return 每日统计 - List statDailyCountByCustomerIdAndRegistrationEventIdRange(@Param("customerId") String customerId, @Param("registrationEventId") String registrationEventId, @Param("beginRegisteredAt") Long beginRegisteredAt, @Param("endRegisteredAt") Long endRegisteredAt); - - /// 统计全部客户单个登记事件的每日登记事件记录数 - /// - /// @param registrationEventId 登记事件 ID - /// @param beginRegisteredAt 开始登记时间 - /// @param endRegisteredAt 结束登记时间 - /// @return 每日统计 - List statDailyCountByRegistrationEventIdRange(@Param("registrationEventId") String registrationEventId, @Param("beginRegisteredAt") Long beginRegisteredAt, @Param("endRegisteredAt") Long endRegisteredAt); - - /// 统计时间段前单个客户单个登记事件累计登记事件记录数 - /// - /// @param customerId 客户 ID - /// @param registrationEventId 登记事件 ID - /// @param beginRegisteredAt 开始登记时间 - /// @return 累计登记数 - Long countBeforeByCustomerIdAndRegistrationEventId(@Param("customerId") String customerId, @Param("registrationEventId") String registrationEventId, @Param("beginRegisteredAt") Long beginRegisteredAt); - - /// 统计时间段前全部客户单个登记事件累计登记事件记录数 - /// - /// @param registrationEventId 登记事件 ID - /// @param beginRegisteredAt 开始登记时间 - /// @return 累计登记数 - Long countBeforeByRegistrationEventId(@Param("registrationEventId") String registrationEventId, @Param("beginRegisteredAt") Long beginRegisteredAt); - - /// 查询客户指定登记事件的最后登记事件记录 - /// - /// @param customerIdList 客户 ID 列表 - /// @param registrationEventId 登记事件 ID - /// @return 最后登记事件记录列表 - List selectLatestByCustomerIdListAndRegistrationEventId(@Param("customerIdList") List customerIdList, @Param("registrationEventId") String registrationEventId); - - /// 查询客户全部登记事件的最后登记事件记录 - /// - /// @param customerId 客户 ID - /// @return 最后登记事件记录列表 - List selectLatestByCustomerId(@Param("customerId") String customerId); - - /// 按任意时间范围查询登记事件记录 - /// - /// @param req 范围查询请求 - /// @return 登记事件记录列表 - List selectByRange(@Param("req") GaoRegistrationEventRecordRangeReq req); - - /// 按任意时间范围查询日历轻量记录 - /// - /// @param req 范围查询请求 - /// @return 日历轻量记录列表 - List selectCalendarByRange(@Param("req") GaoRegistrationEventRecordRangeReq req); - - /// 按任意时间范围统计登记事件记录 - /// - /// @param req 范围查询请求 - /// @return 登记事件记录数 - Long countByRange(@Param("req") GaoRegistrationEventRecordRangeReq req); - - /// 按任意时间范围分页查询登记事件记录 - /// - /// @param req 范围查询请求 - /// @param offset 偏移量 - /// @param size 每页条数 - /// @return 登记事件记录列表 - List selectPageByRange(@Param("req") GaoRegistrationEventRecordRangeReq req, @Param("offset") Long offset, @Param("size") Long size); -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventRoleRelationMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventRoleRelationMapper.java deleted file mode 100644 index 0a66c9d..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventRoleRelationMapper.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.imyeyu.api.modules.gao.mapper; - -import com.imyeyu.api.modules.gao.entity.GaoRegistrationEventRoleRelation; -import com.imyeyu.spring.mapper.BaseMapper; -import org.apache.ibatis.annotations.Param; -import org.apache.ibatis.annotations.Select; -import org.apache.ibatis.annotations.Update; - -import java.util.List; - -/// -/// 登记事件角色关联 Mapper -/// -/// @author Codex -/// @since 2026-07-28 -public interface GaoRegistrationEventRoleRelationMapper extends BaseMapper { - - @Select("SELECT `role_code` FROM `gao_registration_event_role_relation` WHERE `registration_event_id` = #{registrationEventId} AND `deleted_at` IS NULL ORDER BY `created_at` ASC") - List selectRoleCodeListByRegistrationEventId(@Param("registrationEventId") String registrationEventId); - - @Select({ - "" - }) - List selectByRegistrationEventIdList(@Param("registrationEventIdList") List registrationEventIdList); - - @Update("UPDATE `gao_registration_event_role_relation` SET `deleted_at` = UNIX_TIMESTAMP() WHERE `registration_event_id` = #{registrationEventId} AND `deleted_at` IS NULL") - void deleteByRegistrationEventId(@Param("registrationEventId") String registrationEventId); -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoStoreBusinessDayMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoStoreBusinessDayMapper.java new file mode 100644 index 0000000..0ea3a57 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoStoreBusinessDayMapper.java @@ -0,0 +1,49 @@ +package com.imyeyu.api.modules.gao.mapper; + +import com.imyeyu.api.modules.gao.entity.GaoStoreBusinessDay; +import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +/// +/// GAO 门店营业日 Mapper +/// +/// @author Codex +/// @since 2026-08-06 +public interface GaoStoreBusinessDayMapper extends BaseMapper { + + /// 按门店和日期查询营业日 + /// + /// @param storeId 门店 ID + /// @param dateValue 日期值,格式为 yyyyMMdd + /// @return 营业日 + @Select("SELECT * FROM `gao_store_business_day` WHERE `store_id` = #{storeId} AND `date_value` = #{dateValue} AND `deleted_at` IS NULL LIMIT 1") + GaoStoreBusinessDay selectByStoreIdAndDateValue(@Param("storeId") String storeId, @Param("dateValue") Integer dateValue); + + /// 查询指定日期前最后一个营业日序号 + /// + /// @param storeId 门店 ID + /// @param dateValue 日期值,格式为 yyyyMMdd + /// @return 营业日序号 + @Select("SELECT MAX(`business_day_no`) FROM `gao_store_business_day` WHERE `store_id` = #{storeId} AND `date_value` < #{dateValue} AND `open_status` = 'OPEN' AND `deleted_at` IS NULL") + Long selectLastBusinessDayNoBefore(@Param("storeId") String storeId, @Param("dateValue") Integer dateValue); + + /// 查询指定日期及之后的营业日 + /// + /// @param storeId 门店 ID + /// @param beginDateValue 开始日期值,格式为 yyyyMMdd + /// @return 营业日列表 + @Select("SELECT * FROM `gao_store_business_day` WHERE `store_id` = #{storeId} AND `date_value` >= #{beginDateValue} AND `open_status` = 'OPEN' AND `deleted_at` IS NULL ORDER BY `date_value` ASC") + List selectOpenListFromDate(@Param("storeId") String storeId, @Param("beginDateValue") Integer beginDateValue); + + /// 查询日期范围内的营业日 + /// + /// @param storeId 门店 ID + /// @param beginDateValue 开始日期值,格式为 yyyyMMdd + /// @param endDateValue 结束日期值,格式为 yyyyMMdd + /// @return 营业日列表 + @Select("SELECT * FROM `gao_store_business_day` WHERE `store_id` = #{storeId} AND `date_value` >= #{beginDateValue} AND `date_value` <= #{endDateValue} AND `deleted_at` IS NULL ORDER BY `date_value` ASC") + List selectByStoreIdAndDateRange(@Param("storeId") String storeId, @Param("beginDateValue") Integer beginDateValue, @Param("endDateValue") Integer endDateValue); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoStoreMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoStoreMapper.java new file mode 100644 index 0000000..0dcb790 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoStoreMapper.java @@ -0,0 +1,24 @@ +package com.imyeyu.api.modules.gao.mapper; + +import com.imyeyu.api.modules.gao.entity.GaoStore; +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-06 +public interface GaoStoreMapper extends BaseMapper { + + List selectByIdList(@Param("idList") Collection idList); + + /// 查询用户所属的可用门店 + /// + /// @param userId 用户 ID + /// @return 门店 + GaoStore selectByUserId(@Param("userId") String userId); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoUserMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoUserMapper.java index 88d550c..dc94f6e 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoUserMapper.java +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoUserMapper.java @@ -2,8 +2,6 @@ package com.imyeyu.api.modules.gao.mapper; import com.imyeyu.api.modules.gao.entity.GaoUser; import com.imyeyu.spring.mapper.BaseMapper; -import org.apache.ibatis.annotations.Param; -import org.apache.ibatis.annotations.Select; /// /// GAO 用户 Mapper @@ -11,7 +9,4 @@ import org.apache.ibatis.annotations.Select; /// @author Codex /// @since 2026-07-30 public interface GaoUserMapper extends BaseMapper { - - @Select("SELECT * FROM `gao_user` WHERE `user_id` = #{userId} AND `deleted_at` IS NULL LIMIT 1") - GaoUser selectByUserId(@Param("userId") String userId); } diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerChartService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerChartService.java new file mode 100644 index 0000000..252a50f --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerChartService.java @@ -0,0 +1,20 @@ +package com.imyeyu.api.modules.gao.service; + +import com.imyeyu.api.modules.gao.vo.GaoCustomerChartReq; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordCalendarView; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordDailyStatView; + +import java.util.List; + +/// 客户报表服务 +/// +/// @author Codex +/// @since 2026-08-11 +public interface GaoCustomerChartService { + + /// 查询客户登记每日统计 + List eventRecordDailyStat(GaoCustomerChartReq req); + + /// 查询客户登记日历记录 + List eventRecordCalendar(GaoCustomerChartReq req); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerService.java index 928e23f..2c15d7a 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerService.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerService.java @@ -1,60 +1,50 @@ package com.imyeyu.api.modules.gao.service; import com.imyeyu.api.modules.gao.entity.GaoCustomer; -import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStatView; +import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView; import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView; -import com.imyeyu.api.modules.gao.vo.GaoCustomerInviteUpdateReq; -import com.imyeyu.api.modules.gao.vo.GaoCustomerSearchReq; -import com.imyeyu.api.modules.gao.vo.GaoCustomerTrendStatReq; +import com.imyeyu.api.modules.gao.vo.GaoCustomerTrendStateReq; import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.service.BaseService; import java.io.IOException; +import java.util.Collection; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; -/// /// 客户服务 /// -/// @author Codex -/// @since 2026-07-27 +/// @author 夜雨 +/// @since 2026-07-27 14:12 public interface GaoCustomerService extends BaseService { + void updateWithAttachment(GaoCustomer customer); + /// 按编码查询客户 /// + /// @param storeId 门店 ID /// @param code 客户编码 /// @return 客户 - GaoCustomer getByCode(String code); + GaoCustomer getByCode(String storeId, String code); - /// 按编码查询客户,不存在时返回 null + /// 按姓名查询客户 /// - /// @param code 客户编码 + /// @param storeId 门店 ID + /// @param name 姓名 /// @return 客户 - GaoCustomer getNullableByCode(String code); + GaoCustomer getByName(String storeId, String name); - /// 按编码获取或快速创建客户 - /// - /// @param code 客户编码 - /// @return 客户 - GaoCustomer getOrQuickCreateByCode(String code); + List listByIdList(Collection idList); - /// 搜索客户 - /// - /// @param req 搜索请求 - /// @return 客户列表 - List search(GaoCustomerSearchReq req); - - /// 分页搜索客户 - /// - /// @param page 分页搜索请求 - /// @return 客户分页结果 - PageResult searchPage(Page page); + default Map mapByIdList(Collection idList) { + return listByIdList(idList).stream().collect(Collectors.toMap(GaoCustomer::getId, item -> item)); + } /// 按搜索条件导出客户 Excel /// - /// @param req 搜索请求 /// @return xlsx 文件字节 - byte[] exportSearchExcel(GaoCustomerSearchReq req) throws IOException; + byte[] export(Page page) throws IOException; /// 查询邀请的客户 /// @@ -64,8 +54,9 @@ public interface GaoCustomerService extends BaseService { /// 保存邀请关系 /// - /// @param req 邀请关系请求 - void saveInviteRelation(GaoCustomerInviteUpdateReq req); + /// @param introducerCustomerId 邀请来源客户 ID + /// @param customerId 客户 ID + void saveInviteRelation(String introducerCustomerId, String customerId); /// 删除邀请关系 /// @@ -76,10 +67,11 @@ public interface GaoCustomerService extends BaseService { /// /// @param req 趋势统计请求 /// @return 每日趋势列表 - List statDailyTrend(GaoCustomerTrendStatReq req); + List stateDailyTrend(GaoCustomerTrendStateReq req); /// 统计客户性别分布 /// + /// @param storeId 门店 ID /// @return 性别统计列表 - List statGender(); + List stateGender(String storeId); } diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoEventRecordService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoEventRecordService.java new file mode 100644 index 0000000..3fb4fb8 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoEventRecordService.java @@ -0,0 +1,46 @@ +package com.imyeyu.api.modules.gao.service; + +import com.imyeyu.api.modules.gao.entity.GaoCustomer; +import com.imyeyu.api.modules.gao.entity.GaoEventRecord; +import com.imyeyu.api.modules.gao.vo.GaoCustomerEventRankView; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordPage; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordRankPage; +import com.imyeyu.java.TimiJava; +import com.imyeyu.spring.bean.PageResult; +import com.imyeyu.spring.service.BaseService; + +import java.io.IOException; +import java.util.List; + +/// 登记事件记录服务 +/// +/// @author 夜雨 +/// @since 2026-07-27 14:09 +public interface GaoEventRecordService extends BaseService { + + default void createBatch(List recordList) { + for (GaoEventRecord record : TimiJava.safeIterable(recordList)) { + create(record); + } + } + + void createWithCustomer(GaoCustomer customer); + + /// 按任意时间范围查询登记事件记录 + /// + /// @param req 范围查询请求 + /// @return 登记事件记录列表 + PageResult pageByRange(GaoEventRecordPage req); + + /// 按时间范围查询客户登记排行榜 + /// + /// @param page 范围查询请求,不传时间为全部时间 + /// @return 客户登记排行榜 + PageResult pageRankByRange(GaoEventRecordRankPage page); + + /// 按任意时间范围导出登记事件记录 Excel + /// + /// @param page 范围查询请求 + /// @return xlsx 文件字节 + byte[] export(GaoEventRecordPage page) throws IOException; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoEventService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoEventService.java new file mode 100644 index 0000000..6331a21 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoEventService.java @@ -0,0 +1,37 @@ +package com.imyeyu.api.modules.gao.service; + +import com.imyeyu.api.modules.gao.entity.GaoEvent; +import com.imyeyu.spring.service.BaseService; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/// +/// 登记事件服务 +/// +/// @author Codex +/// @since 2026-07-27 +public interface GaoEventService extends BaseService { + + Map mapByIdList(Collection idList); + + Map> mapRoleCodeListByEventIdList(Collection eventIdList); + + /// 查询启用的登记事件 + /// + /// @param storeId 门店 ID + /// @return 登记事件列表 + List listValid(String storeId); + + /// 查询登记事件要求的角色 + /// + /// @param eventId 登记事件 ID + /// @return 角色代码列表 + List listRoleCode(String eventId); + + /// 按客户端提交的顺序更新登记事件排序 + /// + /// @param idList 登记事件 ID 列表 + void sort(List idList); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoPointAccountService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoPointAccountService.java new file mode 100644 index 0000000..c824b8a --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoPointAccountService.java @@ -0,0 +1,35 @@ +package com.imyeyu.api.modules.gao.service; + +import com.imyeyu.api.modules.gao.entity.GaoPointAccount; +import com.imyeyu.spring.service.BaseService; + +/// GAO 客户积分账户服务 +/// +/// @author Codex +/// @since 2026-08-11 +public interface GaoPointAccountService extends BaseService { + + /// 查询客户当前有效积分账户 + /// + /// @param storeId 门店 ID + /// @param customerId 客户 ID + /// @return 积分账户,不存在时返回 null + GaoPointAccount getByCustomerId(String storeId, String customerId); + + /// 获取并锁定客户积分账户,不存在时创建 + /// + /// @param storeId 门店 ID + /// @param customerId 客户 ID + /// @return 已锁定的积分账户 + GaoPointAccount getOrCreateForUpdate(String storeId, String customerId); + + /// 原子更新账户余额 + /// + /// @param accountId 账户 ID + /// @param delta 本次余额变化 + /// @param totalEarnedDelta 累计获得积分变化 + /// @param totalRevokedDelta 累计撤销积分变化 + /// @param totalAdjustedDelta 累计人工调整积分变化 + /// @return 更新行数 + int updateBalance(String accountId, long delta, long totalEarnedDelta, long totalRevokedDelta, long totalAdjustedDelta); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoPointLedgerService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoPointLedgerService.java new file mode 100644 index 0000000..e544369 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoPointLedgerService.java @@ -0,0 +1,46 @@ +package com.imyeyu.api.modules.gao.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.imyeyu.api.modules.gao.entity.GaoPointLedger; +import com.imyeyu.api.modules.gao.entity.GaoPointRule; +import com.imyeyu.api.modules.gao.entity.GaoEventRecord; +import com.imyeyu.api.modules.gao.vo.GaoPointAdjustRequest; +import com.imyeyu.api.modules.gao.vo.GaoPointLedgerPage; +import com.imyeyu.spring.bean.PageResult; +import com.imyeyu.spring.service.BaseService; + +/// GAO 客户积分流水服务 +/// +/// @author Codex +/// @since 2026-08-11 +public interface GaoPointLedgerService extends BaseService { + + /// 写入登记获得积分流水 + /// + /// @param record 登记记录 + /// @param rule 命中规则 + /// @param pointDelta 积分变化 + /// @param sourcePayload 来源快照 + /// @return 积分流水 + GaoPointLedger earn(GaoEventRecord record, GaoPointRule rule, long pointDelta, JsonNode sourcePayload); + + /// 写入删除登记记录的撤销流水 + /// + /// @param originLedger 原始获得积分流水 + /// @param record 登记记录 + /// @return 撤销流水 + GaoPointLedger revoke(GaoPointLedger originLedger, GaoEventRecord record); + + /// 写入人工调整流水 + /// + /// @param request 人工调整请求 + /// @param operatorUserId 操作用户 ID + /// @return 调整流水 + GaoPointLedger adjust(GaoPointAdjustRequest request, String operatorUserId); + + /// 分页查询积分流水 + /// + /// @param page 查询条件 + /// @return 分页结果 + PageResult pageByQuery(GaoPointLedgerPage page); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoPointRuleService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoPointRuleService.java new file mode 100644 index 0000000..5dbc7ff --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoPointRuleService.java @@ -0,0 +1,36 @@ +package com.imyeyu.api.modules.gao.service; + +import com.imyeyu.api.modules.gao.entity.GaoPointRule; +import com.imyeyu.spring.service.BaseService; + +import java.util.List; + +/// GAO 客户积分规则服务 +/// +/// @author Codex +/// @since 2026-08-11 +public interface GaoPointRuleService extends BaseService { + + /// 查询当前时间可匹配的启用规则 + /// + /// @param storeId 门店 ID + /// @param eventId 登记事件 ID + /// @param now 当前时间戳 + /// @return 规则列表 + List listActive(String storeId, String eventId, long now); + + /// 创建积分规则 + /// + /// @param rule 积分规则 + void create(GaoPointRule rule); + + /// 修改积分规则 + /// + /// @param rule 积分规则 + void update(GaoPointRule rule); + + /// 删除积分规则 + /// + /// @param id 规则 ID + void delete(String id); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoPointTriggerService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoPointTriggerService.java new file mode 100644 index 0000000..b2e47db --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoPointTriggerService.java @@ -0,0 +1,28 @@ +package com.imyeyu.api.modules.gao.service; + +import com.imyeyu.api.modules.gao.entity.GaoPointLedger; +import com.imyeyu.api.modules.gao.entity.GaoEventRecord; +import com.imyeyu.api.modules.gao.vo.GaoPointAdjustRequest; + +/// GAO 客户积分触发服务 +/// +/// @author Codex +/// @since 2026-08-11 +public interface GaoPointTriggerService { + + /// 在登记记录创建成功后触发积分 + /// + /// @param record 登记记录 + void onEventRecordCreated(GaoEventRecord record); + + /// 撤销登记记录产生的全部获得积分 + /// + /// @param record 登记记录 + void revokeByEventRecord(GaoEventRecord record); + + /// 执行人工调整 + /// + /// @param request 人工调整请求 + /// @return 调整流水 + GaoPointLedger adjust(GaoPointAdjustRequest request); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoRegistrationEventRecordService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoRegistrationEventRecordService.java deleted file mode 100644 index 6ad2a85..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/service/GaoRegistrationEventRecordService.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.imyeyu.api.modules.gao.service; - -import com.imyeyu.api.modules.gao.entity.GaoRegistrationEventRecord; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventPeriodStatView; -import com.imyeyu.api.modules.gao.vo.GaoCustomerRegisterStatView; -import com.imyeyu.api.modules.gao.vo.GaoCustomerRegistrationRankView; -import com.imyeyu.api.modules.gao.vo.GaoQuickRegistrationEventRecordReq; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordCalendarView; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordCreateReq; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordDailyStatView; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordListItemView; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordPeriodReq; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordRangeReq; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordStatReq; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordTrendStatReq; -import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.bean.PageResult; -import com.imyeyu.spring.service.BaseService; - -import java.io.IOException; -import java.util.List; - -/// -/// 登记事件记录服务 -/// -/// @author Codex -/// @since 2026-07-27 -public interface GaoRegistrationEventRecordService extends BaseService { - - /// 批量创建登记事件记录 - /// - /// @param req 批量创建请求 - /// @return 创建后的登记事件记录列表 - List createRecords(GaoRegistrationEventRecordCreateReq req); - - /// 快速录入客户并创建登记事件记录 - /// - /// @param req 快速录入请求 - /// @return 创建后的登记事件记录 - GaoRegistrationEventRecord quickRegister(GaoQuickRegistrationEventRecordReq req); - - /// 按任意时间范围查询登记事件记录 - /// - /// @param req 范围查询请求 - /// @return 登记事件记录列表 - List listByRange(GaoRegistrationEventRecordRangeReq req); - - /// 按任意时间范围查询日历轻量记录 - /// - /// @param req 范围查询请求 - /// @return 日历轻量记录列表 - List listCalendarByRange(GaoRegistrationEventRecordRangeReq req); - - /// 按任意时间范围分页查询登记事件记录 - /// - /// @param page 分页参数 - /// @return 登记事件记录分页结果 - PageResult pageByRange(Page page); - - /// 按任意时间范围导出登记事件记录 Excel - /// - /// @param req 范围查询请求 - /// @return xlsx 文件字节 - byte[] exportRangeExcel(GaoRegistrationEventRecordRangeReq req) throws IOException; - - /// 按周期查询登记事件记录 - /// - /// @param req 周期请求 - /// @return 登记事件记录列表 - List listByRegistrationEventAndPeriod(GaoRegistrationEventRecordPeriodReq req); - - /// 按周期统计登记事件记录 - /// - /// @param registrationEventId 登记事件 ID - /// @param periodType 周期类型 - /// @return 统计列表 - List statByRegistrationEventAndPeriodType(String registrationEventId, GaoRegistrationEventRecordPeriodReq.PeriodType periodType); - - /// 查询客户指定登记事件的最后登记事件记录 - /// - /// @param customerIdList 客户 ID 列表 - /// @param registrationEventId 登记事件 ID - /// @return 每个客户的最后登记事件记录 - List listLatestByCustomerIdListAndRegistrationEventId(List customerIdList, String registrationEventId); - - /// 统计客户所有登记 - /// - /// @param customerId 客户 ID - /// @return 统计结果 - List statByCustomerId(String customerId); - - /// 按时间段统计单个客户登记 - /// - /// @param req 统计请求 - /// @return 统计结果 - List statByCustomerAndRange(GaoRegistrationEventRecordStatReq req); - - /// 按时间段统计全部客户登记 - /// - /// @param req 统计请求 - /// @return 统计结果 - List statAllCustomerByRange(GaoRegistrationEventRecordStatReq req); - - /// 按时间范围查询客户登记排行榜 - /// - /// @param req 范围查询请求,不传时间为全部时间 - /// @return 客户登记排行榜 - List listCustomerRegistrationRank(GaoRegistrationEventRecordRangeReq req); - - /// 按时间段统计单个客户单个登记事件每日趋势 - /// - /// @param req 趋势统计请求 - /// @return 每日趋势列表 - List statDailyByCustomerAndRegistrationEvent(GaoRegistrationEventRecordTrendStatReq req); - - /// 按时间段统计全部客户单个登记事件每日趋势 - /// - /// @param req 趋势统计请求 - /// @return 每日趋势列表 - List statDailyByRegistrationEvent(GaoRegistrationEventRecordTrendStatReq req); - - /// 查询客户全部登记事件的最后登记事件记录 - /// - /// @param customerId 客户 ID - /// @return 每个登记事件的最后登记事件记录 - List listLatestByCustomerId(String customerId); -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoRegistrationEventService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoRegistrationEventService.java deleted file mode 100644 index f6f609c..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/service/GaoRegistrationEventService.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.imyeyu.api.modules.gao.service; - -import com.imyeyu.api.modules.gao.entity.GaoRegistrationEvent; -import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.bean.PageResult; -import com.imyeyu.spring.service.BaseService; - -import java.util.List; - -/// -/// 登记事件服务 -/// -/// @author Codex -/// @since 2026-07-27 -public interface GaoRegistrationEventService extends BaseService { - - /// 分页查询登记事件并填充可登记角色 - /// - /// @param page 分页参数 - /// @return 登记事件分页 - PageResult pageWithRegistrationEventLimit(Page page); - - /// 查询启用的登记事件 - /// - /// @return 登记事件列表 - List listEnabled(); - - /// 查询登记事件要求的角色 - /// - /// @param registrationEventId 登记事件 ID - /// @return 角色代码列表 - List listRoleCode(String registrationEventId); - - /// 按客户端提交的顺序更新登记事件排序 - /// - /// @param idList 登记事件 ID 列表 - void sort(List idList); -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreBusinessDayService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreBusinessDayService.java new file mode 100644 index 0000000..fb6eb78 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreBusinessDayService.java @@ -0,0 +1,33 @@ +package com.imyeyu.api.modules.gao.service; + +import com.imyeyu.api.modules.gao.entity.GaoStoreBusinessDay; +import com.imyeyu.spring.service.BaseService; + +/// +/// GAO 门店营业日服务 +/// +/// @author Codex +/// @since 2026-08-06 +public interface GaoStoreBusinessDayService extends BaseService { + + /// 解析营业日,不存在时按默认营业日创建 + /// + /// @param storeId 门店 ID + /// @param dateValue 日期值,格式为 yyyyMMdd + /// @return 营业日 + GaoStoreBusinessDay resolveOpenBusinessDay(String storeId, Integer dateValue); + + /// 重算门店营业日序号并同步登记记录快照 + /// + /// @param storeId 门店 ID + /// @param beginDateValue 开始日期值,格式为 yyyyMMdd + void rebuildBusinessDayNo(String storeId, Integer beginDateValue); + + /// 查询日期范围内的营业日 + /// + /// @param storeId 门店 ID + /// @param beginDateValue 开始日期值,格式为 yyyyMMdd + /// @param endDateValue 结束日期值,格式为 yyyyMMdd + /// @return 营业日列表 + java.util.List listByDateRange(String storeId, Integer beginDateValue, Integer endDateValue); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreChartService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreChartService.java new file mode 100644 index 0000000..da556d5 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreChartService.java @@ -0,0 +1,28 @@ +package com.imyeyu.api.modules.gao.service; + +import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView; +import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordCalendarView; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordDailyStatView; +import com.imyeyu.api.modules.gao.vo.GaoStoreChartReq; + +import java.util.List; + +/// 门店报表服务 +/// +/// @author Codex +/// @since 2026-08-11 +public interface GaoStoreChartService { + + /// 查询客户每日趋势 + List customerDailyTrend(GaoStoreChartReq req); + + /// 查询客户性别统计 + List customerGender(String storeId); + + /// 查询登记事件每日统计 + List eventRecordDailyStat(GaoStoreChartReq req); + + /// 查询登记事件日历记录 + List eventRecordCalendar(GaoStoreChartReq req); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreService.java new file mode 100644 index 0000000..43f4fe0 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreService.java @@ -0,0 +1,35 @@ +package com.imyeyu.api.modules.gao.service; + +import com.imyeyu.api.modules.gao.entity.GaoStore; +import com.imyeyu.spring.bean.Page; +import com.imyeyu.spring.service.BaseService; + +import java.io.IOException; +import java.util.List; + +/// +/// GAO 门店服务 +/// +/// @author Codex +/// @since 2026-08-06 +public interface GaoStoreService extends BaseService { + + /// 查询用户所属门店 + /// + /// @param userId 用户 ID + /// @return 门店 + GaoStore getByUserId(String userId); + + String getBelongIdByRequiredLoginUserId(); + + /// 导出门店列表 + /// + /// @param page 分页查询条件 + /// @return xlsx 文件字节 + byte[] export(Page
当附件为图片或视频时,若指定尺寸的缩略图已存在则直接返回缓存, @@ -52,14 +58,14 @@ public interface AttachmentService extends BaseService { List listByBizId(Attachment.BizType bizType, String bizId, String... attachTypes); /** - * 根据多个业务 ID 批量获取附件。 + * 根据多个业务 ID 批量获取附件 * * @param bizType 业务类型 * @param bizIdList 业务 ID 列表 * @param attachTypes 附件类型,可为 null * @return 按业务 ID 分组的附件列表 */ - Map> mapByBizIdList(Attachment.BizType bizType, List bizIdList, String... attachTypes); + Map> mapByBizIdList(Attachment.BizType bizType, Collection bizIdList, String... attachTypes); /** * 根据业务获取所有附件 @@ -84,10 +90,8 @@ public interface AttachmentService extends BaseService { * 请求有、数据库有、无 tempFileId → 仅更新元数据 * 请求有、数据库有、有 tempFileId → 更新文件与元数据 * - * - * @param req 差分更新请求 */ - void updateByBiz(BizUpdateReq req); + void updateByBizId(Attachment.BizType bizType, String bizId, List list); - void deleteByBiz(BizDeleteReq req); + void deleteByBizId(Attachment.BizType bizType, String bizId); } diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/AttachmentServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/AttachmentServiceImplement.java index 4346370..701d4db 100644 --- a/src/main/java/com/imyeyu/api/modules/common/service/implement/AttachmentServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/common/service/implement/AttachmentServiceImplement.java @@ -8,8 +8,6 @@ import com.imyeyu.api.modules.common.bean.attachment.Metadata; import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.mapper.AttachmentMapper; import com.imyeyu.api.modules.common.service.AttachmentService; -import com.imyeyu.api.modules.common.vo.attach.BizDeleteReq; -import com.imyeyu.api.modules.common.vo.attach.BizUpdateReq; import com.imyeyu.api.util.JavaCV; import com.imyeyu.io.IO; import com.imyeyu.java.TimiJava; @@ -44,6 +42,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Objects; @@ -71,6 +70,88 @@ public class AttachmentServiceImplement extends AbstractEntityService> mapByBizIdList(Attachment.BizType bizType, List bizIdList, String... attachTypes) { - if (bizIdList == null || bizIdList.isEmpty()) { - return Map.of(); - } - List normalizedBizIdList = bizIdList.stream() - .filter(TimiJava::isNotEmpty) - .distinct() - .toList(); - if (normalizedBizIdList.isEmpty()) { - return Map.of(); - } - return mapper.selectByBizIdList(bizType, new ArrayList<>(normalizedBizIdList), List.of(attachTypes)).stream() - .collect(Collectors.groupingBy(Attachment::getBizId)); + public Map> mapByBizIdList(Attachment.BizType bizType, Collection bizIdList, String... attachTypes) { + return mapper.selectByBizIdList(bizType, bizIdList, List.of(attachTypes)).stream().collect(Collectors.groupingBy(Attachment::getBizId)); } @Override @@ -348,16 +347,14 @@ public class AttachmentServiceImplement extends AbstractEntityService items = req.getItems(); + public void updateByBizId(Attachment.BizType bizType, String bizId, List list) { + list = TimiJava.defaultIfNull(list, new ArrayList<>()); // 数据库现有附件 List dbList = mapper.selectAllByBizId(bizType, bizId); Set dbIds = dbList.stream().map(Attachment::getId).collect(Collectors.toSet()); // 请求附件 ID - Set reqIds = items.stream().map(Attachment::getId).filter(Objects::nonNull).collect(Collectors.toSet()); + Set reqIds = list.stream().map(Attachment::getId).filter(Objects::nonNull).collect(Collectors.toSet()); // 数据库有、请求无:删除 for (Attachment attach : dbList) { if (!reqIds.contains(attach.getId())) { @@ -365,7 +362,7 @@ public class AttachmentServiceImplement extends AbstractEntityService attachmentList = mapper.selectAllByBizId(req.getBizType(), req.getBizId()); + public void deleteByBizId(Attachment.BizType bizType, String bizId) { + List attachmentList = mapper.selectAllByBizId(bizType, bizId); for (Attachment attachment : attachmentList) { delete(attachment.getId()); } diff --git a/src/main/java/com/imyeyu/api/modules/common/vo/attach/BizDeleteReq.java b/src/main/java/com/imyeyu/api/modules/common/vo/attach/BizDeleteReq.java deleted file mode 100644 index 7ebb47a..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/vo/attach/BizDeleteReq.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.imyeyu.api.modules.common.vo.attach; - -import com.imyeyu.api.modules.common.entity.Attachment; -import lombok.Data; - -/** - * 业务附件差分更新请求 - * - * @author 夜雨 - * @since 2026-03-11 00:00 - */ -@Data -public class BizDeleteReq { - - /** 业务类型 */ - private Attachment.BizType bizType; - - /** 业务 ID */ - private String bizId; -} diff --git a/src/main/java/com/imyeyu/api/modules/common/vo/attach/BizUpdateReq.java b/src/main/java/com/imyeyu/api/modules/common/vo/attach/BizUpdateReq.java deleted file mode 100644 index 6b90f1f..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/vo/attach/BizUpdateReq.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.imyeyu.api.modules.common.vo.attach; - -import com.imyeyu.api.modules.common.entity.Attachment; -import lombok.Data; - -import java.util.ArrayList; -import java.util.List; - -/** - * 业务附件差分更新请求 - * - * @author 夜雨 - * @since 2026-03-11 00:00 - */ -@Data -public class BizUpdateReq { - - /** 业务类型 */ - private Attachment.BizType bizType; - - /** 业务 ID */ - private String bizId; - - /** 附件项列表 */ - private List items = new ArrayList<>(); -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/annotation/RequireGaoRoleInterceptor.java b/src/main/java/com/imyeyu/api/modules/gao/annotation/RequireGaoRoleInterceptor.java index 600db09..4d24acc 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/annotation/RequireGaoRoleInterceptor.java +++ b/src/main/java/com/imyeyu/api/modules/gao/annotation/RequireGaoRoleInterceptor.java @@ -8,6 +8,7 @@ import com.imyeyu.java.TimiJava; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.lang.NonNull; +import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; @@ -32,6 +33,9 @@ public class RequireGaoRoleInterceptor implements HandlerInterceptor { return true; } RequireGaoRole annotation = handlerMethod.getMethodAnnotation(RequireGaoRole.class); + if (annotation == null) { + annotation = AnnotatedElementUtils.findMergedAnnotation(handlerMethod.getBeanType(), RequireGaoRole.class); + } if (annotation == null || TimiJava.isEmpty(annotation.value())) { return true; } diff --git a/src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java b/src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java index cfc07be..9015b38 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java +++ b/src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java @@ -2,6 +2,7 @@ package com.imyeyu.api.modules.gao.bean; import com.imyeyu.api.bean.ModuleCode; import com.imyeyu.api.modules.user.bean.BuiltinPermissionCode; +import lombok.Getter; /// /// GAO 模块权限枚举 @@ -10,58 +11,88 @@ import com.imyeyu.api.modules.user.bean.BuiltinPermissionCode; /// @since 2026-07-26 public enum GaoPermissionCode implements BuiltinPermissionCode { - CUSTOMER_CREATE("CUSTOMER:CREATE", "客户创建"), + STORE_CREATE("STORE:CREATE", "门店创建", false, true), - CUSTOMER_READ("CUSTOMER:READ", "客户读取"), + STORE_READ("STORE:READ", "门店读取", true, true), - CUSTOMER_EXPORT("CUSTOMER:EXPORT", "客户导出", true), + STORE_EXPORT("STORE:EXPORT", "门店导出", false, true), - CUSTOMER_UPDATE("CUSTOMER:UPDATE", "客户修改"), + STORE_UPDATE("STORE:UPDATE", "门店修改", true, true), - CUSTOMER_DELETE("CUSTOMER:DELETE", "客户删除"), + STORE_DELETE("STORE:DELETE", "门店删除", false, true), - REGISTRATION_EVENT_CREATE("REGISTRATION_EVENT:CREATE", "登记事件创建"), + CUSTOMER_CREATE("CUSTOMER:CREATE", "客户创建", false, false), - REGISTRATION_EVENT_READ("REGISTRATION_EVENT:READ", "登记事件读取"), + CUSTOMER_READ("CUSTOMER:READ", "客户读取", false, false), - REGISTRATION_EVENT_UPDATE("REGISTRATION_EVENT:UPDATE", "登记事件修改"), + CUSTOMER_EXPORT("CUSTOMER:EXPORT", "客户导出", true, false), - REGISTRATION_EVENT_DELETE("REGISTRATION_EVENT:DELETE", "登记事件删除"), + CUSTOMER_UPDATE("CUSTOMER:UPDATE", "客户修改", false, false), - REGISTRATION_EVENT_RECORD_CREATE("REGISTRATION_EVENT_RECORD:CREATE", "登记事件记录创建"), + CUSTOMER_DELETE("CUSTOMER:DELETE", "客户删除", false, false), - REGISTRATION_EVENT_RECORD_READ("REGISTRATION_EVENT_RECORD:READ", "登记事件记录读取"), + EVENT_CREATE("EVENT:CREATE", "登记事件创建", false, false), - REGISTRATION_EVENT_RECORD_EXPORT("REGISTRATION_EVENT_RECORD:EXPORT", "登记事件记录导出", true), + EVENT_READ("EVENT:READ", "登记事件读取", false, false), - REGISTRATION_EVENT_RECORD_UPDATE("REGISTRATION_EVENT_RECORD:UPDATE", "登记事件记录修改"), + EVENT_UPDATE("EVENT:UPDATE", "登记事件修改", false, false), - REGISTRATION_EVENT_RECORD_DELETE("REGISTRATION_EVENT_RECORD:DELETE", "登记事件记录删除"), + EVENT_DELETE("EVENT:DELETE", "登记事件删除", false, false), - STAT_READ("STAT:READ", "统计读取"), + EVENT_RECORD_CREATE("EVENT_RECORD:CREATE", "登记事件记录创建", false, false), - QR_CODE_CREATE("QR_CODE:CREATE", "二维码创建"), + EVENT_RECORD_READ("EVENT_RECORD:READ", "登记事件记录读取", false, false), - USER_CREATE("USER:CREATE", "用户创建", true), + EVENT_RECORD_EXPORT("EVENT_RECORD:EXPORT", "登记事件记录导出", true, false), - USER_READ("USER:READ", "用户读取", true), + EVENT_RECORD_UPDATE("EVENT_RECORD:UPDATE", "登记事件记录修改", false, false), - USER_UPDATE("USER:UPDATE", "用户修改", true), + EVENT_RECORD_DELETE("EVENT_RECORD:DELETE", "登记事件记录删除", false, false), - USER_DELETE("USER:DELETE", "用户删除", true); + POINT_ACCOUNT_READ("POINT_ACCOUNT:READ", "客户积分账户读取", true, true), + + POINT_LEDGER_READ("POINT_LEDGER:READ", "客户积分流水读取", true, true), + + POINT_RULE_CREATE("POINT_RULE:CREATE", "客户积分规则创建", true, true), + + POINT_RULE_READ("POINT_RULE:READ", "客户积分规则读取", true, true), + + POINT_RULE_UPDATE("POINT_RULE:UPDATE", "客户积分规则修改", true, true), + + POINT_RULE_DELETE("POINT_RULE:DELETE", "客户积分规则删除", true, true), + + POINT_ADJUST("POINT:ADJUST", "客户积分人工调整", true, true), + + POINT_SOURCE_VALUE("POINT:SOURCE_VALUE", "登记提交自定义积分值", true, true), + + STAT_READ("STAT:READ", "统计读取", false, false), + + QR_CODE_CREATE("QR_CODE:CREATE", "二维码创建", false, false), + + USER_CREATE("USER:CREATE", "用户创建", true, false), + + USER_READ("USER:READ", "用户读取", true, false), + + USER_UPDATE("USER:UPDATE", "用户修改", true, false), + + USER_DELETE("USER:DELETE", "用户删除", true, false); final String value; final String defaultName; + + /// `true` 为初始化时授权给店长角色 + @Getter final boolean grantToStoreManager; - GaoPermissionCode(String value, String defaultName) { - this(value, defaultName, false); - } + /// `true` 为初始化时授权给全局管理员角色 + @Getter + final boolean grantToGlobalManager; - GaoPermissionCode(String value, String defaultName, boolean grantToStoreManager) { + GaoPermissionCode(String value, String defaultName, boolean grantToStoreManager, boolean grantToGlobalManager) { this.value = value; this.defaultName = defaultName; this.grantToStoreManager = grantToStoreManager; + this.grantToGlobalManager = grantToGlobalManager; } @Override @@ -88,11 +119,4 @@ public enum GaoPermissionCode implements BuiltinPermissionCode { public boolean isGrantToAdmin() { return false; } - - /// true 为初始化时授权给店长角色 - /// - /// @return 是否授权给店长角色 - public boolean isGrantToStoreManager() { - return grantToStoreManager; - } } diff --git a/src/main/java/com/imyeyu/api/modules/gao/bean/GaoRoleCode.java b/src/main/java/com/imyeyu/api/modules/gao/bean/GaoRoleCode.java index b76855e..3bd2282 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/bean/GaoRoleCode.java +++ b/src/main/java/com/imyeyu/api/modules/gao/bean/GaoRoleCode.java @@ -12,12 +12,14 @@ import lombok.AllArgsConstructor; @AllArgsConstructor public enum GaoRoleCode implements BuiltinRoleCode { - /// 店长 - STORE_MANAGER("店长"), + /// 全局管理员 + GLOBAL_MANAGER("全局管理员", null), - EMPLOYEE("员工"); + /// 店长 + STORE_MANAGER("店长", GLOBAL_MANAGER.name()); final String defaultName; + final String parentCode; @Override public ModuleCode getModuleCode() { @@ -36,6 +38,7 @@ public enum GaoRoleCode implements BuiltinRoleCode { @Override public String getParentCode() { - return null; + return parentCode; } + } diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerChartController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerChartController.java new file mode 100644 index 0000000..3bec34d --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerChartController.java @@ -0,0 +1,47 @@ +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.GaoCustomerChartService; +import com.imyeyu.api.modules.gao.vo.GaoCustomerChartReq; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordCalendarView; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordDailyStatView; +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-08-11 +@RestController +@RequiredArgsConstructor +@RequestMapping("/gao/customer/chart") +public class GaoCustomerChartController { + + private final GaoCustomerChartService service; + + /// 查询客户登记每日统计,可同时支撑登记趋势和登记事件结构图表 + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STAT_READ) + @PostMapping("/event/record/daily") + public List eventRecordDaily(@RequestBody GaoCustomerChartReq req) { + return service.eventRecordDailyStat(req); + } + + /// 查询客户登记日历记录 + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STAT_READ) + @PostMapping("/event/record/calendar") + public List eventRecordCalendar(@RequestBody GaoCustomerChartReq req) { + return service.eventRecordCalendar(req); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerController.java index 480a64b..e36ffbc 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerController.java +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerController.java @@ -1,29 +1,32 @@ package com.imyeyu.api.modules.gao.controller; import com.fasterxml.jackson.annotation.JsonView; +import com.imyeyu.api.bean.ModuleCode; +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.bean.GaoRoleCode; -import com.imyeyu.api.bean.RoleMatchMode; -import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; -import com.imyeyu.api.modules.gao.annotation.RequireGaoRole; import com.imyeyu.api.modules.gao.entity.GaoCustomer; -import com.imyeyu.api.modules.gao.entity.GaoRegistrationEvent; -import com.imyeyu.api.modules.gao.entity.GaoRegistrationEventRecord; -import com.imyeyu.api.modules.gao.service.GaoRegistrationEventService; import com.imyeyu.api.modules.gao.service.GaoCustomerService; -import com.imyeyu.api.modules.gao.service.GaoRegistrationEventRecordService; -import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStatView; +import com.imyeyu.api.modules.gao.service.GaoStoreService; +import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView; import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView; -import com.imyeyu.api.modules.gao.vo.GaoCustomerInviteUpdateReq; -import com.imyeyu.api.modules.gao.vo.GaoCustomerSearchReq; -import com.imyeyu.api.modules.gao.vo.GaoCustomerTrendStatReq; -import com.imyeyu.api.modules.gao.vo.GaoQuickRegistrationEventRecordReq; +import com.imyeyu.api.modules.gao.vo.GaoCustomerTrendStateReq; +import com.imyeyu.api.modules.user.service.RoleChecker; +import com.imyeyu.api.modules.user.service.UserLoginService; +import com.imyeyu.api.modules.user.service.UserService; +import com.imyeyu.java.TimiJava; +import com.imyeyu.java.bean.timi.TimiException; +import com.imyeyu.network.Network; +import com.imyeyu.spring.TimiSpring; import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.IgnoreGlobalReturn; import com.imyeyu.spring.annotation.RequestRateLimit; import com.imyeyu.spring.bean.Page; import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.util.ResponseView; +import com.imyeyu.utils.Time; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.PostMapping; @@ -33,28 +36,24 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; -import java.net.URLEncoder; import java.util.List; import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; -/// /// GAO 客户接口 /// -/// @author Codex -/// @since 2026-07-27 +/// @author 夜雨 +/// @since 2026-07-27 14:12 @RestController @RequiredArgsConstructor @RequestMapping("/gao/customer") -@RequireGaoRole(value = {GaoRoleCode.STORE_MANAGER, GaoRoleCode.EMPLOYEE}, mode = RoleMatchMode.ANY) public class GaoCustomerController { - private static final String SIGN_IN_REGISTRATION_EVENT_NAME = "签到"; - + private final UserService userService; + private final RoleChecker roleChecker; + private final AttachmentService attachmentService; private final GaoCustomerService service; - private final GaoRegistrationEventRecordService registrationEventRecordService; - private final GaoRegistrationEventService registrationEventService; + private final GaoStoreService gaoStoreService; + private final UserLoginService userLoginService; @AOPLog @RequestRateLimit @@ -63,74 +62,32 @@ public class GaoCustomerController { @JsonView(ResponseView.Public.class) public PageResult list(@RequestBody Page page) { PageResult result = service.page(page); - fillLastSignInAt(result.getList()); + + List idList = result.getList().stream().map(GaoCustomer::getId).distinct().toList(); + Map> attachmentMap = attachmentService.mapByBizIdList(Attachment.BizType.GAO_CUSTOMER, idList); + for (GaoCustomer customer : result.getList()) { + customer.setAttachmentList(attachmentMap.get(customer.getId())); + } return result; } - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) - @PostMapping("/search") - @JsonView(ResponseView.Public.class) - public List search(@RequestBody GaoCustomerSearchReq req) { - return service.search(req); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) - @PostMapping("/search/page") - @JsonView(ResponseView.Public.class) - public PageResult searchPage(@RequestBody Page page) { - return service.searchPage(page); - } - - @AOPLog - @RequestRateLimit - @IgnoreGlobalReturn - @RequireGaoPermission(GaoPermissionCode.CUSTOMER_EXPORT) - @PostMapping("/export") - public void export(@RequestBody GaoCustomerSearchReq req, HttpServletResponse resp) throws IOException { - String fileName = URLEncoder.encode("客户列表.xlsx", "UTF-8").replace("+", "%20"); - resp.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); - resp.setHeader("Content-Disposition", "attachment; filename=\"gao-customer.xlsx\"; filename*=UTF-8''" + fileName); - resp.getOutputStream().write(service.exportSearchExcel(req)); - } - @AOPLog @RequestRateLimit @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) @PostMapping("/detail") @JsonView(ResponseView.Public.class) public GaoCustomer detail(@RequestParam String id) { - return service.get(id); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) - @PostMapping("/detail/code") - @JsonView(ResponseView.Public.class) - public GaoCustomer detailByCode(@RequestParam String code) { - return service.getByCode(code); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) - @PostMapping("/detail/code/nullable") - @JsonView(ResponseView.Public.class) - public GaoCustomer nullableDetailByCode(@RequestParam String code) { - return service.getNullableByCode(code); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) - @PostMapping("/invited/list") - @JsonView(ResponseView.Public.class) - public List invitedList(@RequestParam String introducerCustomerId) { - return service.listInvited(introducerCustomerId); + GaoCustomer customer = service.get(id); + if (TimiJava.isNotEmpty(customer.getIntroducerCustomerId())) { + GaoCustomer introducerCustomer = service.get(customer.getIntroducerCustomerId()); + introducerCustomer.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.GAO_CUSTOMER, introducerCustomer.getId())); + customer.setIntroducerCustomer(introducerCustomer); + } + if (TimiJava.isNotEmpty(customer.getCreatorUserId())) { + customer.setCreatorUser(userService.get(customer.getCreatorUserId())); + } + customer.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.GAO_CUSTOMER, customer.getId())); + return customer; } @AOPLog @@ -138,17 +95,12 @@ public class GaoCustomerController { @RequireGaoPermission(GaoPermissionCode.CUSTOMER_CREATE) @PostMapping("/create") @JsonView(ResponseView.Public.class) - public GaoCustomer create(@RequestBody GaoCustomer customer) { + public String create(@RequestBody GaoCustomer customer) { + if (!roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) { + customer.setStoreId(gaoStoreService.getByUserId(userLoginService.getRequireLoginUserId()).getId()); + } service.create(customer); - return customer; - } - - @AOPLog - @RequestRateLimit - @PostMapping("/quick/register") - @JsonView(ResponseView.Public.class) - public GaoRegistrationEventRecord quickRegister(@RequestBody GaoQuickRegistrationEventRecordReq req) { - return registrationEventRecordService.quickRegister(req); + return customer.getId(); } @AOPLog @@ -156,7 +108,10 @@ public class GaoCustomerController { @RequireGaoPermission(GaoPermissionCode.CUSTOMER_UPDATE) @PostMapping("/update") public void update(@RequestBody GaoCustomer customer) { - service.update(customer); + if (!roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) { + customer.setStoreId(gaoStoreService.getByUserId(userLoginService.getRequireLoginUserId()).getId()); + } + service.updateWithAttachment(customer); } @AOPLog @@ -169,27 +124,78 @@ public class GaoCustomerController { @AOPLog @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.CUSTOMER_UPDATE) - @PostMapping("/invite/create") - public void createInvite(@RequestBody GaoCustomerInviteUpdateReq req) { - service.saveInviteRelation(req); + @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) + @PostMapping("/find/id") + @JsonView(ResponseView.Public.class) + public GaoCustomer findById(@RequestParam String id) { + GaoCustomer customer = service.get(id); + TimiException.required(customer, "not found customer"); + customer.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.GAO_CUSTOMER, customer.getId())); + return customer; } @AOPLog @RequestRateLimit @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) - @PostMapping("/invite/detail") + @PostMapping("/find/code") @JsonView(ResponseView.Public.class) - public GaoCustomer inviteDetail(@RequestParam String customerId) { - return service.get(customerId); + public GaoCustomer findByCode(@RequestParam String storeId, @RequestParam String code) { + GaoCustomer customer = service.getByCode(storeId, code); + if (customer == null) { + return null; + } + customer.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.GAO_CUSTOMER, customer.getId())); + return customer; + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) + @PostMapping("/find/name") + @JsonView(ResponseView.Public.class) + public GaoCustomer findByName(@RequestParam String storeId, @RequestParam String name) { + GaoCustomer customer = service.getByName(storeId, name); + if (customer == null) { + return null; + } + customer.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.GAO_CUSTOMER, customer.getId())); + return customer; + } + + @AOPLog + @RequestRateLimit + @IgnoreGlobalReturn + @RequireGaoPermission(GaoPermissionCode.CUSTOMER_EXPORT) + @PostMapping("/export") + public void export(@RequestBody Page page) throws IOException { + HttpServletResponse resp = TimiSpring.getResponse(); + String contentDisposition = Network.getFileDownloadHeader("客户列表-%s.xlsx".formatted(Time.serialize.format(Time.now()))); + resp.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + resp.setHeader("Content-Disposition", contentDisposition); + resp.getOutputStream().write(service.export(page)); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) + @PostMapping("/invited/list") + @JsonView(ResponseView.Public.class) + public List listInvited(@RequestParam String introducerCustomerId) { + List result = service.listInvited(introducerCustomerId); + List idList = result.stream().map(GaoCustomer::getId).distinct().toList(); + Map> attachMap = attachmentService.mapByBizIdList(Attachment.BizType.GAO_CUSTOMER, idList); + for (GaoCustomer customer : result) { + customer.setAttachmentList(attachMap.get(customer.getId())); + } + return result; } @AOPLog @RequestRateLimit @RequireGaoPermission(GaoPermissionCode.CUSTOMER_UPDATE) - @PostMapping("/invite/update") - public void updateInvite(@RequestBody GaoCustomerInviteUpdateReq req) { - service.saveInviteRelation(req); + @PostMapping("/invite/save") + public void saveInvite(@RequestParam String introducerCustomerId, @RequestParam String customerId) { + service.saveInviteRelation(introducerCustomerId, customerId); } @AOPLog @@ -200,46 +206,21 @@ public class GaoCustomerController { service.deleteInviteRelation(customerId); } - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) - @PostMapping("/stat/daily") /// 按天统计客户新增数和累计总数 - public List dailyStat(@RequestBody GaoCustomerTrendStatReq req) { - return service.statDailyTrend(req); - } - @AOPLog @RequestRateLimit @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) - @PostMapping("/stat/gender") - /// 统计客户性别分布 - public List genderStat() { - return service.statGender(); + @PostMapping("/state/daily") + public List dailyStat(@RequestBody GaoCustomerTrendStateReq req) { + return service.stateDailyTrend(req); } - private void fillLastSignInAt(List customerList) { - if (customerList == null || customerList.isEmpty()) { - return; - } - String signInRegistrationEventId = null; - for (GaoRegistrationEvent registrationEvent : registrationEventService.listEnabled()) { - if (SIGN_IN_REGISTRATION_EVENT_NAME.equals(registrationEvent.getName())) { - signInRegistrationEventId = registrationEvent.getId(); - break; - } - } - if (signInRegistrationEventId == null) { - return; - } - List customerIdList = customerList.stream().map(GaoCustomer::getId).toList(); - Map recordMap = registrationEventRecordService.listLatestByCustomerIdListAndRegistrationEventId(customerIdList, signInRegistrationEventId).stream() - .collect(Collectors.toMap(GaoRegistrationEventRecord::getCustomerId, Function.identity(), (left, right) -> left)); - for (GaoCustomer customer : customerList) { - GaoRegistrationEventRecord record = recordMap.get(customer.getId()); - if (record != null) { - customer.setLastSignInAt(record.getRegisteredAt()); - } - } + /// 统计客户性别分布 + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) + @PostMapping("/state/gender") + public List genderStat(@RequestParam String storeId) { + return service.stateGender(storeId); } } 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 new file mode 100644 index 0000000..72234c3 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventController.java @@ -0,0 +1,105 @@ +package com.imyeyu.api.modules.gao.controller; + +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; +import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; +import com.imyeyu.api.modules.gao.bean.GaoRoleCode; +import com.imyeyu.api.modules.gao.entity.GaoEvent; +import com.imyeyu.api.modules.gao.service.GaoEventService; +import com.imyeyu.api.modules.gao.service.GaoStoreService; +import com.imyeyu.api.modules.user.service.RoleChecker; +import com.imyeyu.api.modules.user.service.UserLoginService; +import com.imyeyu.spring.annotation.AOPLog; +import com.imyeyu.spring.annotation.RequestRateLimit; +import com.imyeyu.spring.bean.Page; +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; + +/// GAO 登记事件接口 +/// +/// @author 夜雨 +/// @since 2026-07-27 14:13 +@RestController +@RequiredArgsConstructor +@RequestMapping("/gao/event") +public class GaoEventController { + + private final GaoEventService service; + private final RoleChecker roleChecker; + private final GaoStoreService gaoStoreService; + private final UserLoginService userLoginService; + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.EVENT_READ) + @PostMapping("/list") + public PageResult list(@RequestBody Page page) { + PageResult result = service.page(page); + Map> roleMap = service.mapRoleCodeListByEventIdList(result.getList().stream().map(GaoEvent::getId).toList()); + for (GaoEvent event : result.getList()) { + event.setRoleCodeList(roleMap.get(event.getId())); + } + return result; + } + + @AOPLog + @RequestRateLimit + @PostMapping("/list/valid") + public List listValid(@RequestParam String storeId) { + return service.listValid(storeId); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.EVENT_READ) + @PostMapping("/detail") + public GaoEvent detail(@RequestParam String id) { + return service.get(id); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.EVENT_CREATE) + @PostMapping("/create") + public void create(@RequestBody GaoEvent event) { + if (!roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) { + event.setStoreId(gaoStoreService.getByUserId(userLoginService.getRequireLoginUserId()).getId()); + } + service.create(event); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.EVENT_UPDATE) + @PostMapping("/update") + public void update(@RequestBody GaoEvent event) { + if (!roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) { + event.setStoreId(gaoStoreService.getByUserId(userLoginService.getRequireLoginUserId()).getId()); + } + service.update(event); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.EVENT_UPDATE) + @PostMapping("/sort") + public void sort(@RequestBody List idList) { + service.sort(idList); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.EVENT_DELETE) + @PostMapping("/delete") + public void delete(@RequestParam String id) { + service.delete(id); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventRecordController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventRecordController.java new file mode 100644 index 0000000..2a97e6c --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventRecordController.java @@ -0,0 +1,191 @@ +package com.imyeyu.api.modules.gao.controller; + +import com.fasterxml.jackson.annotation.JsonView; +import com.imyeyu.api.bean.ModuleCode; +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.bean.GaoRoleCode; +import com.imyeyu.api.modules.gao.entity.GaoCustomer; +import com.imyeyu.api.modules.gao.entity.GaoEvent; +import com.imyeyu.api.modules.gao.entity.GaoEventRecord; +import com.imyeyu.api.modules.gao.service.GaoCustomerService; +import com.imyeyu.api.modules.gao.service.GaoEventRecordService; +import com.imyeyu.api.modules.gao.service.GaoEventService; +import com.imyeyu.api.modules.gao.service.GaoStoreService; +import com.imyeyu.api.modules.gao.vo.GaoCustomerEventRankView; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordPage; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordRankPage; +import com.imyeyu.api.modules.user.service.RoleChecker; +import com.imyeyu.api.modules.user.service.UserLoginService; +import com.imyeyu.api.modules.user.service.UserService; +import com.imyeyu.java.TimiJava; +import com.imyeyu.network.Network; +import com.imyeyu.spring.TimiSpring; +import com.imyeyu.spring.annotation.AOPLog; +import com.imyeyu.spring.annotation.IgnoreGlobalReturn; +import com.imyeyu.spring.annotation.RequestRateLimit; +import com.imyeyu.spring.bean.PageResult; +import com.imyeyu.spring.util.ResponseView; +import com.imyeyu.utils.Time; +import jakarta.servlet.http.HttpServletResponse; +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.io.IOException; +import java.util.List; +import java.util.Map; + +/// GAO 登记事件记录接口 +/// +/// @author 夜雨 +/// @since 2026-07-27 14:11 +@RestController +@RequiredArgsConstructor +@RequestMapping("/gao/event/record") +public class GaoEventRecordController { + + private final UserService userService; + private final RoleChecker roleChecker; + private final GaoEventService eventService; + private final AttachmentService attachmentService; + private final GaoCustomerService customerService; + private final GaoStoreService gaoStoreService; + private final GaoEventRecordService service; + private final UserLoginService userLoginService; + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.EVENT_RECORD_READ) + @PostMapping("/list") + @JsonView(ResponseView.Public.class) + public PageResult list(@RequestBody GaoEventRecordPage page) { + PageResult result = service.pageByRange(page); + { + List customerIdList = result.getList().stream().map(GaoEventRecord::getCustomerId).toList(); + Map eventMap = eventService.mapByIdList(result.getList().stream().map(GaoEventRecord::getEventId).toList()); + Map customerMap = customerService.mapByIdList(customerIdList); + Map> attachMap = attachmentService.mapByBizIdList(Attachment.BizType.GAO_CUSTOMER, customerIdList); + for (GaoEventRecord record : result.getList()) { + record.setEvent(eventMap.get(record.getEventId())); + record.setCustomer(customerMap.get(record.getCustomerId())); + record.getCustomer().setAttachmentList(attachMap.get(record.getCustomerId())); + } + } + return result; + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.EVENT_RECORD_READ) + @PostMapping("/detail") + @JsonView(ResponseView.Public.class) + public GaoEventRecord detail(@RequestParam String id) { + GaoEventRecord record = service.get(id); + record.setCustomer(customerService.get(record.getCustomerId())); + record.getCustomer().setAttachmentList(attachmentService.listByBizId(Attachment.BizType.GAO_CUSTOMER, record.getCustomerId())); + record.setEvent(eventService.get(record.getEventId())); + record.setOperator(userService.get(record.getOperatorUserId())); + record.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.GAO_EVENT_RECORD, record.getId())); + return record; + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.EVENT_RECORD_CREATE) + @PostMapping("/create") + public void create(@RequestBody GaoEventRecord record) { + if (!roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) { + record.setStoreId(gaoStoreService.getByUserId(userLoginService.getRequireLoginUserId()).getId()); + } + service.create(record); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.EVENT_RECORD_CREATE) + @PostMapping("/create/batch") + public void createBatch(@RequestBody List recordList) { + if (!roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) { + String storeId = gaoStoreService.getBelongIdByRequiredLoginUserId(); + for (GaoEventRecord record : recordList) { + record.setStoreId(storeId); + } + } + service.createBatch(recordList); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.EVENT_RECORD_CREATE) + @PostMapping("/create/customer") + public void createWithCustomer(@RequestBody GaoCustomer customer) { + if (!roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) { + String storeId = gaoStoreService.getBelongIdByRequiredLoginUserId(); + for (GaoEventRecord record : TimiJava.safeIterable(customer.getEventRecordList())) { + record.setStoreId(storeId); + } + } + service.createWithCustomer(customer); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.EVENT_RECORD_UPDATE) + @PostMapping("/update") + public void update(@RequestBody GaoEventRecord record) { + if (!roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) { + record.setStoreId(gaoStoreService.getBelongIdByRequiredLoginUserId()); + } + service.update(record); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.EVENT_RECORD_DELETE) + @PostMapping("/delete") + public void delete(@RequestParam String id) { + service.delete(id); + } + + @AOPLog + @RequestRateLimit + @IgnoreGlobalReturn + @RequireGaoPermission(GaoPermissionCode.EVENT_RECORD_EXPORT) + @PostMapping("/export") + public void export(@RequestBody GaoEventRecordPage page) throws IOException { + HttpServletResponse resp = TimiSpring.getResponse(); + String contentDisposition = Network.getFileDownloadHeader("登记事件记录-%s.xlsx".formatted(Time.serialize.format(Time.now()))); + resp.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + resp.setHeader("Content-Disposition", contentDisposition); + resp.getOutputStream().write(service.export(page)); + } + + /// 按时间范围查询客户登记排行榜 + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STAT_READ) + @PostMapping("/rank") + public PageResult eventRank(@RequestBody GaoEventRecordRankPage page) { + PageResult result = service.pageRankByRange(page); + List customerIdList = result.getList().stream().map(GaoCustomerEventRankView::getCustomerId).distinct().toList(); + if (customerIdList.isEmpty()) { + return result; + } + Map customerMap = customerService.mapByIdList(customerIdList); + Map> attachmentMap = attachmentService.mapByBizIdList(Attachment.BizType.GAO_CUSTOMER, customerIdList); + for (GaoCustomerEventRankView view : result.getList()) { + GaoCustomer customer = customerMap.get(view.getCustomerId()); + if (customer != null) { + customer.setAttachmentList(attachmentMap.get(view.getCustomerId())); + } + view.setCustomer(customer); + } + return result; + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPointAccountController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPointAccountController.java new file mode 100644 index 0000000..537e0a1 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPointAccountController.java @@ -0,0 +1,47 @@ +package com.imyeyu.api.modules.gao.controller; + +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; +import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; +import com.imyeyu.api.modules.gao.bean.GaoRoleCode; +import com.imyeyu.api.modules.gao.entity.GaoPointAccount; +import com.imyeyu.api.modules.gao.service.GaoPointAccountService; +import com.imyeyu.api.modules.gao.service.GaoStoreService; +import com.imyeyu.api.modules.user.service.RoleChecker; +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.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/// GAO 客户积分账户接口 +/// +/// @author Codex +/// @since 2026-08-11 +@RestController +@RequiredArgsConstructor +@RequestMapping("/gao/customer/point") +public class GaoPointAccountController { + + private final RoleChecker roleChecker; + private final GaoStoreService storeService; + private final GaoPointAccountService accountService; + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.POINT_ACCOUNT_READ) + @PostMapping("/account/detail") + public GaoPointAccount detail(@RequestParam String customerId, @RequestParam(required = false) String storeId) { + storeId = resolveStoreId(storeId); + return accountService.getByCustomerId(storeId, customerId); + } + + private String resolveStoreId(String requestedStoreId) { + if (roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) { + return requestedStoreId; + } + return storeService.getBelongIdByRequiredLoginUserId(); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPointLedgerController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPointLedgerController.java new file mode 100644 index 0000000..412c761 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPointLedgerController.java @@ -0,0 +1,63 @@ +package com.imyeyu.api.modules.gao.controller; + +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; +import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; +import com.imyeyu.api.modules.gao.bean.GaoRoleCode; +import com.imyeyu.api.modules.gao.entity.GaoPointLedger; +import com.imyeyu.api.modules.gao.service.GaoPointLedgerService; +import com.imyeyu.api.modules.gao.service.GaoPointTriggerService; +import com.imyeyu.api.modules.gao.service.GaoStoreService; +import com.imyeyu.api.modules.gao.vo.GaoPointAdjustRequest; +import com.imyeyu.api.modules.gao.vo.GaoPointLedgerPage; +import com.imyeyu.api.modules.user.service.RoleChecker; +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.RestController; + +/// GAO 客户积分流水接口 +/// +/// @author Codex +/// @since 2026-08-11 +@RestController +@RequiredArgsConstructor +@RequestMapping("/gao/customer/point") +public class GaoPointLedgerController { + + private final RoleChecker roleChecker; + private final GaoStoreService storeService; + private final GaoPointLedgerService ledgerService; + private final GaoPointTriggerService triggerService; + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.POINT_LEDGER_READ) + @PostMapping("/ledger/list") + public PageResult list(@RequestBody GaoPointLedgerPage page) { + TimiException.required(page, "not found page"); + page.setStoreId(resolveStoreId(page.getStoreId())); + return ledgerService.pageByQuery(page); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.POINT_ADJUST) + @PostMapping("/adjust") + public void adjust(@RequestBody GaoPointAdjustRequest request) { + request.setStoreId(resolveStoreId(request.getStoreId())); + triggerService.adjust(request); + } + + private String resolveStoreId(String requestedStoreId) { + if (roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) { + return requestedStoreId; + } + return storeService.getBelongIdByRequiredLoginUserId(); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPointRuleController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPointRuleController.java new file mode 100644 index 0000000..02c6e9d --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPointRuleController.java @@ -0,0 +1,108 @@ +package com.imyeyu.api.modules.gao.controller; + +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; +import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; +import com.imyeyu.api.modules.gao.bean.GaoRoleCode; +import com.imyeyu.api.modules.gao.entity.GaoPointRule; +import com.imyeyu.api.modules.gao.service.GaoPointRuleService; +import com.imyeyu.api.modules.gao.service.GaoStoreService; +import com.imyeyu.api.modules.user.service.RoleChecker; +import com.imyeyu.java.TimiJava; +import com.imyeyu.java.bean.timi.TimiException; +import com.imyeyu.spring.annotation.AOPLog; +import com.imyeyu.spring.annotation.RequestRateLimit; +import com.imyeyu.spring.bean.Page; +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-08-11 +@RestController +@RequiredArgsConstructor +@RequestMapping("/gao/customer/point") +public class GaoPointRuleController { + + private final RoleChecker roleChecker; + private final GaoStoreService storeService; + private final GaoPointRuleService ruleService; + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.POINT_RULE_READ) + @PostMapping("/rule/list") + public PageResult list(@RequestBody Page page) { + TimiException.required(page, "not found page"); + if (!isGlobalManager()) { + GaoPointRule example = TimiJava.defaultIfNull(page.getEqualsExample(), new GaoPointRule()); + example.setStoreId(resolveStoreId(null)); + page.setEqualsExample(example); + } + return ruleService.page(page); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.POINT_RULE_READ) + @PostMapping("/rule/detail") + public GaoPointRule detail(@RequestParam String id) { + GaoPointRule rule = ruleService.get(id); + TimiException.required(rule, "not found point rule"); + checkStore(rule.getStoreId()); + return rule; + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.POINT_RULE_CREATE) + @PostMapping("/rule/create") + public String create(@RequestBody GaoPointRule rule) { + rule.setStoreId(resolveStoreId(rule.getStoreId())); + ruleService.create(rule); + return rule.getId(); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.POINT_RULE_UPDATE) + @PostMapping("/rule/update") + public void update(@RequestBody GaoPointRule rule) { + rule.setStoreId(resolveStoreId(rule.getStoreId())); + ruleService.update(rule); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.POINT_RULE_DELETE) + @PostMapping("/rule/delete") + public void delete(@RequestParam String id) { + GaoPointRule rule = ruleService.get(id); + TimiException.required(rule, "not found point rule"); + checkStore(rule.getStoreId()); + ruleService.delete(id); + } + + private String resolveStoreId(String requestedStoreId) { + if (isGlobalManager()) { + return requestedStoreId; + } + return storeService.getBelongIdByRequiredLoginUserId(); + } + + private void checkStore(String storeId) { + if (!isGlobalManager()) { + TimiException.requiredTrue(resolveStoreId(null).equals(storeId), "无权操作其他门店数据"); + } + } + + private boolean isGlobalManager() { + return roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name()); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRegistrationEventController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRegistrationEventController.java deleted file mode 100644 index 0063ad6..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRegistrationEventController.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.imyeyu.api.modules.gao.controller; - -import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; -import com.imyeyu.api.modules.gao.annotation.RequireGaoRole; -import com.imyeyu.api.bean.RoleMatchMode; -import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; -import com.imyeyu.api.modules.gao.bean.GaoRoleCode; -import com.imyeyu.api.modules.gao.entity.GaoRegistrationEvent; -import com.imyeyu.api.modules.gao.service.GaoRegistrationEventService; -import com.imyeyu.spring.annotation.AOPLog; -import com.imyeyu.spring.annotation.RequestRateLimit; -import com.imyeyu.spring.bean.Page; -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-07-27 -@RestController -@RequiredArgsConstructor -@RequestMapping("/gao/registration-event") -@RequireGaoRole(value = {GaoRoleCode.STORE_MANAGER, GaoRoleCode.EMPLOYEE}, mode = RoleMatchMode.ANY) -public class GaoRegistrationEventController { - - private final GaoRegistrationEventService service; - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_READ) - @PostMapping("/list") - public PageResult list(@RequestBody Page page) { - return service.pageWithRegistrationEventLimit(page); - } - - @AOPLog - @RequestRateLimit - @PostMapping("/enabled/list") - public List enabledList() { - return service.listEnabled(); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_READ) - @PostMapping("/detail") - public GaoRegistrationEvent detail(@RequestParam String id) { - return service.get(id); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_CREATE) - @PostMapping("/create") - public void create(@RequestBody GaoRegistrationEvent registrationEvent) { - service.create(registrationEvent); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_UPDATE) - @PostMapping("/update") - public void update(@RequestBody GaoRegistrationEvent registrationEvent) { - service.update(registrationEvent); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_UPDATE) - @PostMapping("/sort") - public void sort(@RequestBody List idList) { - service.sort(idList); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_DELETE) - @PostMapping("/delete") - public void delete(@RequestParam String id) { - service.delete(id); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRegistrationEventRecordController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRegistrationEventRecordController.java deleted file mode 100644 index 6cdfc4a..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRegistrationEventRecordController.java +++ /dev/null @@ -1,201 +0,0 @@ -package com.imyeyu.api.modules.gao.controller; - -import com.fasterxml.jackson.annotation.JsonView; -import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; -import com.imyeyu.api.modules.gao.bean.GaoRoleCode; -import com.imyeyu.api.bean.RoleMatchMode; -import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; -import com.imyeyu.api.modules.gao.annotation.RequireGaoRole; -import com.imyeyu.api.modules.gao.entity.GaoRegistrationEventRecord; -import com.imyeyu.api.modules.gao.service.GaoRegistrationEventRecordService; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventPeriodStatView; -import com.imyeyu.api.modules.gao.vo.GaoCustomerRegisterStatView; -import com.imyeyu.api.modules.gao.vo.GaoCustomerRegistrationRankView; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordCreateReq; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordCalendarView; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordDailyStatView; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordListItemView; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordPeriodReq; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordRangeReq; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordStatReq; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordTrendStatReq; -import com.imyeyu.spring.annotation.AOPLog; -import com.imyeyu.spring.annotation.IgnoreGlobalReturn; -import com.imyeyu.spring.annotation.RequestRateLimit; -import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.bean.PageResult; -import com.imyeyu.spring.util.ResponseView; -import jakarta.servlet.http.HttpServletResponse; -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.io.IOException; -import java.net.URLEncoder; -import java.util.List; - -/// -/// GAO 登记事件记录接口 -/// -/// @author Codex -/// @since 2026-07-27 -@RestController -@RequiredArgsConstructor -@RequestMapping("/gao/registration-event-record") -@RequireGaoRole(value = {GaoRoleCode.STORE_MANAGER, GaoRoleCode.EMPLOYEE}, mode = RoleMatchMode.ANY) -public class GaoRegistrationEventRecordController { - - private final GaoRegistrationEventRecordService service; - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_READ) - @PostMapping("/list") - @JsonView(ResponseView.Public.class) - public PageResult list(@RequestBody Page page) { - return service.page(page); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_READ) - @PostMapping("/range/page") - public PageResult rangePage(@RequestBody Page page) { - return service.pageByRange(page); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_READ) - @PostMapping("/detail") - @JsonView(ResponseView.Public.class) - public GaoRegistrationEventRecord detail(@RequestParam String id) { - return service.get(id); - } - - @AOPLog - @RequestRateLimit - @PostMapping("/create") - @JsonView(ResponseView.Public.class) - public List create(@RequestBody GaoRegistrationEventRecordCreateReq req) { - return service.createRecords(req); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_UPDATE) - @PostMapping("/update") - public void update(@RequestBody GaoRegistrationEventRecord record) { - service.update(record); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_DELETE) - @PostMapping("/delete") - public void delete(@RequestParam String id) { - service.delete(id); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_READ) - @PostMapping("/period/list") - @JsonView(ResponseView.Public.class) - public List periodList(@RequestBody GaoRegistrationEventRecordPeriodReq req) { - return service.listByRegistrationEventAndPeriod(req); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_READ) - @PostMapping("/range/list") - @JsonView(ResponseView.Public.class) - public List rangeList(@RequestBody GaoRegistrationEventRecordRangeReq req) { - return service.listByRange(req); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_READ) - @PostMapping("/range/calendar/list") - public List calendarRangeList(@RequestBody GaoRegistrationEventRecordRangeReq req) { - return service.listCalendarByRange(req); - } - - @AOPLog - @RequestRateLimit - @IgnoreGlobalReturn - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_EXPORT) - @PostMapping("/range/export") - public void rangeExport(@RequestBody GaoRegistrationEventRecordRangeReq req, HttpServletResponse resp) throws IOException { - String fileName = URLEncoder.encode("登记事件记录.xlsx", "UTF-8").replace("+", "%20"); - resp.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); - resp.setHeader("Content-Disposition", "attachment; filename=\"gao-registration-event-record.xlsx\"; filename*=UTF-8''" + fileName); - resp.getOutputStream().write(service.exportRangeExcel(req)); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.STAT_READ) - @PostMapping("/period/stat") - public List periodStat(@RequestBody GaoRegistrationEventRecordPeriodReq req) { - return service.statByRegistrationEventAndPeriodType(req.getRegistrationEventId(), req.getPeriodType()); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.STAT_READ) - @PostMapping("/customer/stat") - public List customerStat(@RequestParam String customerId) { - return service.statByCustomerId(customerId); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.STAT_READ) - @PostMapping("/customer/range/stat") - /// 按时间段统计单个客户各登记事件记录数 - public List customerRangeStat(@RequestBody GaoRegistrationEventRecordStatReq req) { - return service.statByCustomerAndRange(req); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.STAT_READ) - @PostMapping("/all-customer/range/stat") - /// 按时间段统计全部客户各登记事件记录数 - public List allCustomerRangeStat(@RequestBody GaoRegistrationEventRecordStatReq req) { - return service.statAllCustomerByRange(req); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.STAT_READ) - @PostMapping("/customer/registration-rank") - /// 按时间范围查询客户登记排行榜 - public List customerRegistrationRank(@RequestBody(required = false) GaoRegistrationEventRecordRangeReq req) { - return service.listCustomerRegistrationRank(req); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.STAT_READ) - @PostMapping("/customer/registration-event/daily/stat") - /// 按天统计单个客户单个登记事件记录趋势 - public List customerRegistrationEventDailyStat(@RequestBody GaoRegistrationEventRecordTrendStatReq req) { - return service.statDailyByCustomerAndRegistrationEvent(req); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.STAT_READ) - @PostMapping("/all-customer/registration-event/daily/stat") - /// 按天统计全部客户单个登记事件记录趋势 - public List allCustomerRegistrationEventDailyStat(@RequestBody GaoRegistrationEventRecordTrendStatReq req) { - return service.statDailyByRegistrationEvent(req); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRoleController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRoleController.java index cff4cd3..c9a1839 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRoleController.java +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRoleController.java @@ -4,7 +4,8 @@ import com.imyeyu.api.bean.ModuleCode; import com.imyeyu.api.modules.gao.annotation.RequireGaoRole; import com.imyeyu.api.modules.gao.bean.GaoRoleCode; import com.imyeyu.api.modules.user.entity.Role; -import com.imyeyu.api.modules.user.service.RoleService; +import com.imyeyu.api.modules.user.service.AuthorizationScopeService; +import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.RequestRateLimit; import lombok.RequiredArgsConstructor; @@ -14,23 +15,23 @@ import org.springframework.web.bind.annotation.RestController; import java.util.List; -/// /// GAO 角色接口 /// -/// @author Codex -/// @since 2026-07-28 +/// @author 夜雨 +/// @since 2026-07-28 14:15 @RestController +@RequireGaoRole(GaoRoleCode.STORE_MANAGER) @RequiredArgsConstructor @RequestMapping("/gao/role") -@RequireGaoRole(GaoRoleCode.STORE_MANAGER) public class GaoRoleController { - private final RoleService roleService; + private final UserLoginService userLoginService; + private final AuthorizationScopeService authorizationScopeService; @AOPLog @RequestRateLimit @PostMapping("/available/list") public List availableList() { - return roleService.listByModuleCode(ModuleCode.GAO); + return authorizationScopeService.listGrantableRole(userLoginService.getRequireLoginUserId(), ModuleCode.GAO); } } diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreBusinessDayController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreBusinessDayController.java new file mode 100644 index 0000000..68dad8a --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreBusinessDayController.java @@ -0,0 +1,82 @@ +package com.imyeyu.api.modules.gao.controller; + +import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; +import com.imyeyu.api.modules.gao.annotation.RequireGaoRole; +import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; +import com.imyeyu.api.modules.gao.bean.GaoRoleCode; +import com.imyeyu.api.modules.gao.entity.GaoStoreBusinessDay; +import com.imyeyu.api.modules.gao.service.GaoStoreBusinessDayService; +import com.imyeyu.spring.annotation.AOPLog; +import com.imyeyu.spring.annotation.RequestRateLimit; +import com.imyeyu.spring.bean.Page; +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-08-06 +@RestController +@RequiredArgsConstructor +@RequestMapping("/gao/store/business/day") +@RequireGaoRole(GaoRoleCode.GLOBAL_MANAGER) +public class GaoStoreBusinessDayController { + + private final GaoStoreBusinessDayService service; + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STORE_READ) + @PostMapping("/list") + public PageResult list(@RequestBody Page page) { + return service.page(page); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STORE_READ) + @PostMapping("/calendar") + public List calendar(@RequestParam String storeId, @RequestParam Integer beginDateValue, @RequestParam Integer endDateValue) { + return service.listByDateRange(storeId, beginDateValue, endDateValue); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STORE_READ) + @PostMapping("/detail") + public GaoStoreBusinessDay detail(@RequestParam String id) { + return service.get(id); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STORE_CREATE) + @PostMapping("/create") + public String create(@RequestBody GaoStoreBusinessDay businessDay) { + service.create(businessDay); + return businessDay.getId(); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STORE_UPDATE) + @PostMapping("/update") + public void update(@RequestBody GaoStoreBusinessDay businessDay) { + service.update(businessDay); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STORE_DELETE) + @PostMapping("/delete") + public void delete(@RequestParam String id) { + service.delete(id); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreChartController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreChartController.java new file mode 100644 index 0000000..88a004e --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreChartController.java @@ -0,0 +1,68 @@ +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.GaoStoreChartService; +import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView; +import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordCalendarView; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordDailyStatView; +import com.imyeyu.api.modules.gao.vo.GaoStoreChartReq; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/// GAO 门店报表接口 +/// +/// @author Codex +/// @since 2026-08-11 +@RestController +@RequiredArgsConstructor +@RequestMapping("/gao/store/chart") +public class GaoStoreChartController { + + private final GaoStoreChartService service; + + /// 查询客户增长趋势 + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STAT_READ) + @PostMapping("/customer/daily") + public List customerDaily(@RequestBody GaoStoreChartReq req) { + return service.customerDailyTrend(req); + } + + /// 查询客户性别统计 + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STAT_READ) + @PostMapping("/customer/gender") + public List customerGender(@RequestParam String storeId) { + return service.customerGender(storeId); + } + + /// 查询登记事件每日统计,可同时支撑登记趋势和事件结构图表 + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STAT_READ) + @PostMapping("/event/record/daily") + public List eventRecordDaily(@RequestBody GaoStoreChartReq req) { + return service.eventRecordDailyStat(req); + } + + /// 查询登记事件日历记录 + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STAT_READ) + @PostMapping("/event/record/calendar") + public List eventRecordCalendar(@RequestBody GaoStoreChartReq req) { + return service.eventRecordCalendar(req); + } +} 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 new file mode 100644 index 0000000..21b0d7e --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreController.java @@ -0,0 +1,114 @@ +package com.imyeyu.api.modules.gao.controller; + +import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; +import com.imyeyu.api.modules.gao.annotation.RequireGaoRole; +import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; +import com.imyeyu.api.modules.gao.bean.GaoRoleCode; +import com.imyeyu.api.modules.gao.entity.GaoStore; +import com.imyeyu.api.modules.gao.service.GaoStoreService; +import com.imyeyu.api.modules.user.service.UserLoginService; +import com.imyeyu.network.Network; +import com.imyeyu.spring.TimiSpring; +import com.imyeyu.spring.annotation.AOPLog; +import com.imyeyu.spring.annotation.IgnoreGlobalReturn; +import com.imyeyu.spring.annotation.RequestRateLimit; +import com.imyeyu.spring.bean.Page; +import com.imyeyu.spring.bean.PageResult; +import com.imyeyu.utils.Time; +import jakarta.servlet.http.HttpServletResponse; +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.io.IOException; +import java.util.List; + +/// GAO 门店接口 +/// +/// @author Codex +/// @since 2026-08-06 +@RestController +@RequiredArgsConstructor +@RequestMapping("/gao/store") +public class GaoStoreController { + + private final GaoStoreService service; + private final UserLoginService userLoginService; + + @AOPLog + @RequireGaoRole(GaoRoleCode.GLOBAL_MANAGER) + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STORE_READ) + @PostMapping("/list") + public PageResult list(@RequestBody Page page) { + return service.page(page); + } + + @AOPLog + @RequireGaoRole(GaoRoleCode.STORE_MANAGER) + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STORE_READ) + @PostMapping("/detail") + public GaoStore detail(@RequestParam String id) { + return service.get(id); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STORE_CREATE) + @PostMapping("/create") + public String create(@RequestBody GaoStore store) { + service.create(store); + return store.getId(); + } + + @AOPLog + @RequireGaoRole(GaoRoleCode.STORE_MANAGER) + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STORE_UPDATE) + @PostMapping("/update") + public void update(@RequestBody GaoStore store) { + service.update(store); + } + + @AOPLog + @RequireGaoRole(GaoRoleCode.GLOBAL_MANAGER) + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STORE_DELETE) + @PostMapping("/delete") + public void delete(@RequestParam String id) { + service.delete(id); + } + + @AOPLog + @RequireGaoRole(GaoRoleCode.GLOBAL_MANAGER) + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STORE_UPDATE) + @PostMapping("/sort") + public void sort(@RequestBody List idList) { + service.sort(idList); + } + + @AOPLog + @RequestRateLimit + @IgnoreGlobalReturn + @RequireGaoPermission(GaoPermissionCode.STORE_EXPORT) + @PostMapping("/export") + public void export(@RequestBody Page page) throws IOException { + HttpServletResponse resp = TimiSpring.getResponse(); + String contentDisposition = Network.getFileDownloadHeader("门店列表-%s.xlsx".formatted(Time.serialize.format(Time.now()))); + resp.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + resp.setHeader("Content-Disposition", contentDisposition); + resp.getOutputStream().write(service.export(page)); + } + + @AOPLog + @RequestRateLimit + @PostMapping("/user") + public GaoStore getLoginStore() { + return service.getByUserId(userLoginService.getRequireLoginUserId()); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoUserController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoUserController.java index 1d38644..0452932 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoUserController.java +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoUserController.java @@ -2,14 +2,23 @@ package com.imyeyu.api.modules.gao.controller; import com.fasterxml.jackson.annotation.JsonView; import com.imyeyu.api.bean.ModuleCode; +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.annotation.RequireGaoRole; import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; import com.imyeyu.api.modules.gao.bean.GaoRoleCode; import com.imyeyu.api.modules.gao.entity.GaoUser; +import com.imyeyu.api.modules.gao.service.GaoStoreService; import com.imyeyu.api.modules.gao.service.GaoUserService; import com.imyeyu.api.modules.user.entity.Role; +import com.imyeyu.api.modules.user.entity.User; +import com.imyeyu.api.modules.user.service.RoleChecker; import com.imyeyu.api.modules.user.service.RoleService; +import com.imyeyu.api.modules.user.service.UserLoginService; +import com.imyeyu.api.modules.user.service.UserRoleService; +import com.imyeyu.api.modules.user.service.UserService; +import com.imyeyu.java.TimiJava; import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.RequestRateLimit; import com.imyeyu.spring.bean.Page; @@ -23,20 +32,25 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.List; +import java.util.Map; -/// /// GAO 用户接口 /// -/// @author Codex -/// @since 2026-07-30 +/// @author 夜雨 +/// @since 2026-07-30 14:15 @RestController @RequiredArgsConstructor @RequestMapping("/gao/user") -@RequireGaoRole(GaoRoleCode.STORE_MANAGER) public class GaoUserController { - private final GaoUserService service; + private final UserService userService; private final RoleService roleService; + private final RoleChecker roleChecker; + private final GaoUserService service; + private final UserRoleService userRoleService; + private final GaoStoreService gaoStoreService; + private final UserLoginService userLoginService; + private final AttachmentService attachmentService; @AOPLog @RequestRateLimit @@ -44,7 +58,22 @@ public class GaoUserController { @PostMapping("/list") @JsonView(ResponseView.Public.class) public PageResult list(@RequestBody Page page) { - return service.pageWithUser(page); + if (!roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) { + // 非全局管理只能看所在店用户 + page.setEqualsExample(TimiJava.defaultIfNull(page.getEqualsExample(), new GaoUser())); + page.getEqualsExample().setStoreId(gaoStoreService.getByUserId(userLoginService.getRequireLoginUserId()).getId()); + } + PageResult result = service.page(page); + { + List userIdList = result.getList().stream().map(GaoUser::getUserId).toList(); + Map userMap = userService.mapByIdList(userIdList); + Map> roleMap = userRoleService.mapRoleListByUserIdList(userIdList); + for (GaoUser gaoUser : result.getList()) { + gaoUser.setUser(userMap.get(gaoUser.getUserId())); + gaoUser.getUser().setRoleEntityList(roleMap.get(gaoUser.getUserId())); + } + } + return result; } @AOPLog @@ -53,16 +82,14 @@ public class GaoUserController { @PostMapping("/detail") @JsonView(ResponseView.Public.class) public GaoUser detail(@RequestParam String id) { - return service.get(id); - } - - @AOPLog - @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.USER_READ) - @PostMapping("/detail/user") - @JsonView(ResponseView.Public.class) - public GaoUser detailByUserId(@RequestParam String userId) { - return service.getByUserId(userId); + GaoUser result = service.get(id); + { + User user = userService.get(result.getUserId()); + user.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.USER, user.getId())); + user.setRoleEntityList(userRoleService.listRoleByUserId(user.getId())); + result.setUser(user); + } + return result; } @AOPLog @@ -71,6 +98,9 @@ public class GaoUserController { @PostMapping("/create") @JsonView(ResponseView.Public.class) public GaoUser create(@RequestBody GaoUser gaoUser) { + if (!roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) { + gaoUser.setStoreId(gaoStoreService.getByUserId(userLoginService.getRequireLoginUserId()).getId()); + } service.create(gaoUser); return service.get(gaoUser.getId()); } @@ -85,6 +115,17 @@ public class GaoUserController { return service.get(gaoUser.getId()); } + @AOPLog + @RequestRateLimit + @RequireGaoRole(GaoRoleCode.GLOBAL_MANAGER) + @RequireGaoPermission(GaoPermissionCode.USER_UPDATE) + @PostMapping("/transfer/store") + @JsonView(ResponseView.Public.class) + public GaoUser transferStore(@RequestBody GaoUser gaoUser) { + service.transferStore(gaoUser); + return service.get(gaoUser.getId()); + } + @AOPLog @RequestRateLimit @RequireGaoPermission(GaoPermissionCode.USER_DELETE) diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomer.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomer.java index a37ed88..36573a3 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomer.java +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomer.java @@ -1,11 +1,11 @@ package com.imyeyu.api.modules.gao.entity; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonFormat; 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.api.modules.user.bean.Gender; +import com.imyeyu.api.modules.user.entity.User; +import com.imyeyu.java.TimiJava; import com.imyeyu.spring.annotation.table.Transient; import com.imyeyu.spring.entity.UUIDEntity; import lombok.Data; @@ -48,6 +48,9 @@ public class GaoCustomer extends UUIDEntity { /// 客户编码 private String code; + /// 门店 ID + private String storeId; + /// 姓名 private String name; @@ -55,7 +58,7 @@ public class GaoCustomer extends UUIDEntity { private Gender gender; /// 年龄 - private Integer age; + private Long age; /// 出生日期,数据库只保存公历日期 @JsonFormat(pattern = "yyyy-MM-dd") @@ -64,15 +67,6 @@ public class GaoCustomer extends UUIDEntity { /// 出生日期录入时使用的历法 private BirthdateCalendar birthdateCalendar; - @JsonIgnore - @Transient - private boolean birthdateProvided; - - public void setBirthdate(LocalDate birthdate) { - this.birthdate = birthdate; - this.birthdateProvided = true; - } - /// 电话 private String telephone; @@ -91,24 +85,27 @@ public class GaoCustomer extends UUIDEntity { /// 介绍人客户 ID private String introducerCustomerId; + private Long invitedCount; + /// 录入用户 ID private String creatorUserId; - @Transient - private Long lastSignInAt; - @Transient private GaoCustomer introducerCustomer; @Transient private User creatorUser; - private Long invitedCount; - @Transient private List attachmentList; - /// 临时照片附件 ID 列表 @Transient - private List attachmentIdList; + private GaoEventRecord eventRecord; + + @Transient + private List eventRecordList; + + public boolean hasIntroducer() { + return TimiJava.isNotEmpty(introducerCustomerId); + } } diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEvent.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEvent.java similarity index 75% rename from src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEvent.java rename to src/main/java/com/imyeyu/api/modules/gao/entity/GaoEvent.java index 40a792b..0a354b1 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEvent.java +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEvent.java @@ -2,8 +2,10 @@ package com.imyeyu.api.modules.gao.entity; import com.imyeyu.spring.annotation.table.Transient; import com.imyeyu.spring.entity.UUIDEntity; +import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.Getter; import java.util.List; @@ -14,7 +16,7 @@ import java.util.List; /// @since 2026-07-27 @Data @EqualsAndHashCode(callSuper = true) -public class GaoRegistrationEvent extends UUIDEntity { +public class GaoEvent extends UUIDEntity { /// /// 登记事件状态 @@ -36,38 +38,44 @@ public class GaoRegistrationEvent extends UUIDEntity { DELETED } - /// /// 登记限制类型 /// - /// @author Codex - /// @since 2026-08-01 + /// @author 夜雨 + /// @since 2026-08-01 14:37 + @Getter + @AllArgsConstructor public enum LimitType { /// 不限制 - NONE, + NONE(null), - /// 每天上午、下午各一次 - DAILY_HALF_DAY, + /// 每天上午、下午各 N 次 + DAILY_HALF_DAY(1L), - /// 每个自然日一次 - NATURAL_DAY, + /// 每个自然日 N 次 + NATURAL_DAY(1L), /// 每隔 N 小时一次 - INTERVAL_HOUR, + INTERVAL_HOUR(1L), - /// 每个自然月一次 - NATURAL_MONTH, + /// 每个自然月 N 次 + NATURAL_MONTH(1L), /// 事件总登记次数上限 - TOTAL_LIMIT, + TOTAL_LIMIT(1L), /// 单个客户总登记次数上限 - CUSTOMER_TOTAL_LIMIT + CUSTOMER_TOTAL_LIMIT(1L); + + final Long min; } /// 登记事件名称 private String name; + /// 门店 ID + private String storeId; + /// 说明 private String description; diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEventRecord.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEventRecord.java similarity index 66% rename from src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEventRecord.java rename to src/main/java/com/imyeyu/api/modules/gao/entity/GaoEventRecord.java index ec92999..39f4739 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEventRecord.java +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEventRecord.java @@ -16,7 +16,7 @@ import java.util.List; /// @since 2026-07-27 @Data @EqualsAndHashCode(callSuper = true) -public class GaoRegistrationEventRecord extends UUIDEntity { +public class GaoEventRecord extends UUIDEntity { /// /// 登记事件记录附件类型 @@ -31,8 +31,11 @@ public class GaoRegistrationEventRecord extends UUIDEntity { /// 客户 ID private String customerId; + /// 门店 ID + private String storeId; + /// 登记登记事件 ID - private String registrationEventId; + private String eventId; /// 操作管理员用户 ID private String operatorUserId; @@ -43,11 +46,21 @@ public class GaoRegistrationEventRecord extends UUIDEntity { /// 登记时间 private Long registeredAt; + /// 登记日期值,格式为 yyyyMMdd + private Integer registeredDateValue; + + /// 营业日递增序号 + private Long businessDayNo; + + /// 本次登记提交的积分值,只参与积分触发,不落库 + @Transient + private Long pointValue; + @Transient private GaoCustomer customer; @Transient - private GaoRegistrationEvent registrationEvent; + private GaoEvent event; @Transient private User operator; @@ -55,7 +68,7 @@ public class GaoRegistrationEventRecord extends UUIDEntity { @Transient private List attachmentList; - /// 临时照片附件 ID 列表 + /// 客户 ID 列表,批量登记时使用 @Transient - private List attachmentIdList; + private List customerIdList; } diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEventRoleRelation.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEventRoleRelation.java similarity index 76% rename from src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEventRoleRelation.java rename to src/main/java/com/imyeyu/api/modules/gao/entity/GaoEventRoleRelation.java index 0922068..e5f0ba7 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEventRoleRelation.java +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEventRoleRelation.java @@ -11,10 +11,10 @@ import lombok.EqualsAndHashCode; /// @since 2026-07-28 @Data @EqualsAndHashCode(callSuper = true) -public class GaoRegistrationEventRoleRelation extends UUIDEntity { +public class GaoEventRoleRelation extends UUIDEntity { /// 登记事件 ID - private String registrationEventId; + private String eventId; /// 角色代码 private String roleCode; diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPointAccount.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPointAccount.java new file mode 100644 index 0000000..87c06eb --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPointAccount.java @@ -0,0 +1,35 @@ +package com.imyeyu.api.modules.gao.entity; + +import com.imyeyu.spring.entity.UUIDEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/// GAO 客户积分账户 +/// +/// @author Codex +/// @since 2026-08-11 +@Data +@EqualsAndHashCode(callSuper = true) +public class GaoPointAccount extends UUIDEntity { + + /// 门店 ID + private String storeId; + + /// 客户 ID + private String customerId; + + /// 当前可用积分余额 + private Long balance; + + /// 累计获得积分 + private Long totalEarned; + + /// 累计撤销积分 + private Long totalRevoked; + + /// 累计人工调整积分,可正可负 + private Long totalAdjusted; + + /// 乐观锁版本 + private Long version; +} 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 new file mode 100644 index 0000000..550c558 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPointLedger.java @@ -0,0 +1,86 @@ +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-08-11 +@Data +@EqualsAndHashCode(callSuper = true) +public class GaoPointLedger extends UUIDEntity { + + /// 流水变化类型 + public enum ChangeType { + + /// 登记事件获得积分 + EARN, + + /// 删除登记记录撤销积分 + REVOKE, + + /// 人工调整积分 + ADJUST + } + + /// 流水方向 + public enum Direction { + + /// 增加积分 + INCREASE, + + /// 减少积分 + DECREASE + } + + /// 客户积分账户 ID + private String accountId; + + /// 门店 ID + private String storeId; + + /// 客户 ID + private String customerId; + + /// 变化类型 + private ChangeType changeType; + + /// 方向 + private Direction direction; + + /// 本次变化积分,增加为正,减少为负 + private Long pointDelta; + + /// 变化前余额 + private Long balanceBefore; + + /// 变化后余额 + private Long balanceAfter; + + /// 命中的积分规则 ID + private String ruleId; + + /// 登记事件 ID + private String eventId; + + /// 登记记录 ID + private String eventRecordId; + + /// 来源快照 + private JsonNode sourcePayload; + + /// 幂等键 + private String idempotencyKey; + + /// 备注 + private String remark; + + /// 操作用户 ID + private String operatorUserId; + + /// 发生时间 + private Long occurredAt; +} 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 new file mode 100644 index 0000000..b37d46c --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPointRule.java @@ -0,0 +1,96 @@ +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-08-11 +@Data +@EqualsAndHashCode(callSuper = true) +public class GaoPointRule extends UUIDEntity { + + /// 规则状态 + public enum Status { + + /// 草稿,不参与触发 + DRAFT, + + /// 启用,参与触发 + ACTIVE, + + /// 停用,不参与触发 + INACTIVE, + + /// 已删除,不参与触发 + DELETED + } + + /// 触发类型 + public enum TriggerType { + + /// 每登记一次触发一次 + EVERY_TIME, + + /// 客户首次登记该事件触发一次 + FIRST_TIME, + + /// 客户累计登记达到 N 次后每次触发 + AFTER_COUNT_EVERY_TIME, + + /// 使用登记接口提交的积分值触发 + SOURCE_VALUE + } + + /// 积分值模式 + public enum PointMode { + + /// 使用规则固定积分值 + FIXED, + + /// 使用登记接口提交的积分值 + SOURCE_VALUE + } + + /// 门店 ID + private String storeId; + + /// 登记事件 ID + private String eventId; + + /// 规则名称 + private String name; + + /// 规则状态 + private Status status; + + /// 触发类型 + private TriggerType triggerType; + + /// 积分值模式 + private PointMode pointMode; + + /// 固定积分值 + private Long pointValue; + + /// 条件配置 + private JsonNode conditionJson; + + /// 生效开始时间戳,毫秒,空为不限制 + private Long beginAt; + + /// 生效结束时间戳,毫秒,空为不限制 + private Long endAt; + + /// 优先级,数值小优先 + private Integer priority; + + /// true 为命中后停止继续匹配后续规则 + private Boolean exclusive; + + /// 备注 + private String remark; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoStore.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoStore.java new file mode 100644 index 0000000..f9042b0 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoStore.java @@ -0,0 +1,57 @@ +package com.imyeyu.api.modules.gao.entity; + +import com.imyeyu.spring.entity.UUIDEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/// +/// GAO 门店 +/// +/// @author Codex +/// @since 2026-08-06 +@Data +@EqualsAndHashCode(callSuper = true) +public class GaoStore extends UUIDEntity { + + /// + /// 门店状态 + /// + /// @author Codex + /// @since 2026-08-06 + public enum Status { + + /// 可用 + ACTIVE, + + /// 停用 + INACTIVE, + + /// 已删除 + DELETED + } + + /// 门店编码 + private String code; + + /// 门店名称 + private String name; + + /// 状态 + private Status status; + + /// 电话 + private String telephone; + + /// 地址 + private String address; + + /// 备注 + private String remark; + + /// 排序值 + private Integer sort; + + public boolean isActivated() { + return status == Status.ACTIVE; + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoStoreBusinessDay.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoStoreBusinessDay.java new file mode 100644 index 0000000..6192ef0 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoStoreBusinessDay.java @@ -0,0 +1,44 @@ +package com.imyeyu.api.modules.gao.entity; + +import com.imyeyu.spring.entity.UUIDEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/// +/// GAO 门店营业日 +/// +/// @author Codex +/// @since 2026-08-06 +@Data +@EqualsAndHashCode(callSuper = true) +public class GaoStoreBusinessDay extends UUIDEntity { + + /// + /// 营业状态 + /// + /// @author Codex + /// @since 2026-08-06 + public enum OpenStatus { + + /// 营业 + OPEN, + + /// 休息 + CLOSED + } + + /// 门店 ID + private String storeId; + + /// 日期值,格式为 yyyyMMdd + private Integer dateValue; + + /// 营业状态 + private OpenStatus openStatus; + + /// 营业日递增序号,仅营业日有值 + private Long businessDayNo; + + /// 原因 + private String reason; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoUser.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoUser.java index fd012ce..133ebdd 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoUser.java +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoUser.java @@ -1,14 +1,11 @@ package com.imyeyu.api.modules.gao.entity; import com.imyeyu.api.modules.user.entity.User; -import com.imyeyu.api.modules.user.entity.Role; import com.imyeyu.spring.annotation.table.Transient; import com.imyeyu.spring.entity.UUIDEntity; import lombok.Data; import lombok.EqualsAndHashCode; -import java.util.List; - /// /// GAO 用户 /// @@ -21,6 +18,9 @@ public class GaoUser extends UUIDEntity { /// 用户 ID private String userId; + /// 门店 ID + private String storeId; + /// true 为启用 private Boolean enabled; @@ -29,10 +29,4 @@ public class GaoUser extends UUIDEntity { @Transient private User user; - - @Transient - private List roleList; - - @Transient - private List roleIdList; } diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerMapper.java index 3c6aa26..cde3fac 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerMapper.java +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerMapper.java @@ -1,12 +1,12 @@ package com.imyeyu.api.modules.gao.mapper; import com.imyeyu.api.modules.gao.entity.GaoCustomer; -import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStatView; +import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView; import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView; import com.imyeyu.spring.mapper.BaseMapper; import org.apache.ibatis.annotations.Param; -import org.apache.ibatis.annotations.Select; +import java.util.Collection; import java.util.List; /// @@ -16,41 +16,23 @@ import java.util.List; /// @since 2026-07-27 public interface GaoCustomerMapper extends BaseMapper { - @Select("SELECT * FROM `gao_customer` WHERE `code` = #{code} AND `deleted_at` IS NULL LIMIT 1") - GaoCustomer selectByCode(@Param("code") String code); - - @Select("SELECT * FROM `gao_customer` WHERE `name` = #{name} AND `deleted_at` IS NULL LIMIT 1") - GaoCustomer selectByName(@Param("name") String name); - - List selectByIdList(@Param("idList") List idList); - - List search(@Param("keyword") String keyword, @Param("introducerCustomerId") String introducerCustomerId); - - Long countSearch(@Param("keyword") String keyword, @Param("introducerCustomerId") String introducerCustomerId); - - List searchPage(@Param("keyword") String keyword, @Param("introducerCustomerId") String introducerCustomerId, @Param("offset") Long offset, @Param("size") Long size); - - List selectInvitedList(@Param("introducerCustomerId") String introducerCustomerId); - - Long countInvited(@Param("introducerCustomerId") String introducerCustomerId); - - void updateInvitedCountDelta(@Param("customerId") String customerId, @Param("delta") Long delta); + List selectByIdList(Collection idList); /// 统计每日新增客户数 /// /// @param beginCreatedAt 开始创建时间 /// @param endCreatedAt 结束创建时间 /// @return 每日新增客户数 - List statDailyNewCustomer(@Param("beginCreatedAt") Long beginCreatedAt, @Param("endCreatedAt") Long endCreatedAt); + List stateDailyNewCustomer(@Param("storeId") String storeId, @Param("beginCreatedAt") Long beginCreatedAt, @Param("endCreatedAt") Long endCreatedAt); /// 统计开始时间前客户总数 /// /// @param beginCreatedAt 开始创建时间 /// @return 客户总数 - Long countCreatedBefore(@Param("beginCreatedAt") Long beginCreatedAt); + Long countCreatedBefore(@Param("storeId") String storeId, @Param("beginCreatedAt") Long beginCreatedAt); /// 统计客户性别分布 /// /// @return 性别统计列表 - List statGender(); + List stateGender(@Param("storeId") String storeId); } diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventMapper.java new file mode 100644 index 0000000..259b345 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventMapper.java @@ -0,0 +1,20 @@ +package com.imyeyu.api.modules.gao.mapper; + +import com.imyeyu.api.modules.gao.entity.GaoEvent; +import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.Collection; +import java.util.List; + +/// +/// 登记事件 Mapper +/// +/// @author 夜雨 +/// @since 2026-07-27 15:53 +public interface GaoEventMapper extends BaseMapper { + + List selectByIdList(Collection idList); + + List selectValidList(@Param("storeId") String storeId, @Param("now") Long now); +} 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 new file mode 100644 index 0000000..cbd2f00 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventRecordMapper.java @@ -0,0 +1,80 @@ +package com.imyeyu.api.modules.gao.mapper; + +import com.imyeyu.api.modules.gao.entity.GaoEventRecord; +import com.imyeyu.api.modules.gao.vo.GaoCustomerChartReq; +import com.imyeyu.api.modules.gao.vo.GaoCustomerEventRankView; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordCalendarView; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordDailyStatView; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordPage; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordRankPage; +import com.imyeyu.api.modules.gao.vo.GaoStoreChartReq; +import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/// 登记事件记录 Mapper +/// +/// @author 夜雨 +/// @since 2026-07-27 14:11 +public interface GaoEventRecordMapper extends BaseMapper { + + Long count(String storeId, String customerId, String eventId, Long beginAt, Long endAt); + + /// 按门店和登记日期统计登记记录数 + /// + /// @param storeId 门店 ID + /// @param registeredDateValue 登记日期值,格式为 yyyyMMdd + /// @return 登记记录数 + Long countByStoreIdAndRegisteredDateValue(@Param("storeId") String storeId, @Param("registeredDateValue") Integer registeredDateValue); + + /// 同步门店指定日期的登记记录营业日序号快照 + /// + /// @param storeId 门店 ID + /// @param registeredDateValue 登记日期值,格式为 yyyyMMdd + /// @param businessDayNo 营业日序号 + void updateBusinessDayNoByStoreIdAndRegisteredDateValue(@Param("storeId") String storeId, @Param("registeredDateValue") Integer registeredDateValue, @Param("businessDayNo") Long businessDayNo); + + long countByRange(GaoEventRecordPage page); + + /// 按任意时间范围查询登记事件记录 + /// + /// @return 登记事件记录列表 + List selectByRange(GaoEventRecordPage page); + + /// 统计客户登记排行榜总数 + /// + /// @param page 排行榜分页请求 + /// @return 客户数 + long countRankByRange(GaoEventRecordRankPage page); + + /// 分页查询客户登记排行榜 + /// + /// @param page 排行榜分页请求 + /// @return 客户登记排行榜 + List selectRankByRange(GaoEventRecordRankPage page); + + /// 按时间范围和事件批量统计每日登记数 + /// + /// @param req 门店报表查询请求 + /// @return 每日登记数 + List stateDailyByRange(GaoStoreChartReq req); + + /// 按时间范围查询登记日历记录 + /// + /// @param req 门店报表查询请求 + /// @return 日历记录列表 + List selectCalendarByRange(GaoStoreChartReq req); + + /// 按客户、时间范围和事件批量统计每日登记数 + /// + /// @param req 客户报表查询请求 + /// @return 每日登记数 + List stateCustomerDailyByRange(GaoCustomerChartReq req); + + /// 按客户和时间范围查询登记日历记录 + /// + /// @param req 客户报表查询请求 + /// @return 日历记录列表 + List selectCustomerCalendarByRange(GaoCustomerChartReq req); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventRoleRelationMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventRoleRelationMapper.java new file mode 100644 index 0000000..ec5cc81 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventRoleRelationMapper.java @@ -0,0 +1,16 @@ +package com.imyeyu.api.modules.gao.mapper; + +import com.imyeyu.api.modules.gao.entity.GaoEventRoleRelation; +import com.imyeyu.spring.mapper.BaseMapper; + +import java.util.Collection; +import java.util.List; + +/// 登记事件角色关联 Mapper +/// +/// @author 夜雨 +/// @since 2026-07-28 14:23 +public interface GaoEventRoleRelationMapper extends BaseMapper { + + List selectAllByEventIdList(Collection idList); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPointAccountMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPointAccountMapper.java new file mode 100644 index 0000000..4392dc3 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPointAccountMapper.java @@ -0,0 +1,41 @@ +package com.imyeyu.api.modules.gao.mapper; + +import com.imyeyu.api.modules.gao.entity.GaoPointAccount; +import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +/// GAO 客户积分账户 Mapper +/// +/// @author Codex +/// @since 2026-08-11 +public interface GaoPointAccountMapper extends BaseMapper { + + /// 查询客户当前有效积分账户 + /// + /// @param storeId 门店 ID + /// @param customerId 客户 ID + /// @return 积分账户 + GaoPointAccount selectByStoreIdAndCustomerId(@Param("storeId") String storeId, @Param("customerId") String customerId); + + /// 创建客户积分账户,已存在时保持原账户不变 + /// + /// @param account 积分账户 + void insertIfAbsent(@Param("account") GaoPointAccount account); + + /// 锁定积分账户 + /// + /// @param id 账户 ID + /// @return 积分账户 + GaoPointAccount selectForUpdate(@Param("id") String id); + + /// 原子更新积分账户余额和统计值 + /// + /// @param id 账户 ID + /// @param delta 本次余额变化 + /// @param totalEarnedDelta 累计获得积分变化 + /// @param totalRevokedDelta 累计撤销积分变化 + /// @param totalAdjustedDelta 累计人工调整积分变化 + /// @param updatedAt 更新时间 + /// @return 更新行数 + int updateBalance(@Param("id") String id, @Param("delta") long delta, @Param("totalEarnedDelta") long totalEarnedDelta, @Param("totalRevokedDelta") long totalRevokedDelta, @Param("totalAdjustedDelta") long totalAdjustedDelta, @Param("updatedAt") long updatedAt); +} 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 new file mode 100644 index 0000000..e1934fd --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPointLedgerMapper.java @@ -0,0 +1,45 @@ +package com.imyeyu.api.modules.gao.mapper; + +import com.imyeyu.api.modules.gao.entity.GaoPointLedger; +import com.imyeyu.api.modules.gao.vo.GaoPointLedgerPage; +import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/// GAO 客户积分流水 Mapper +/// +/// @author Codex +/// @since 2026-08-11 +public interface GaoPointLedgerMapper extends BaseMapper { + + /// 按幂等键查询流水 + /// + /// @param idempotencyKey 幂等键 + /// @return 流水 + GaoPointLedger selectByIdempotencyKey(@Param("idempotencyKey") String idempotencyKey); + + /// 查询登记记录产生的原始获得积分流水 + /// + /// @param eventRecordId 登记记录 ID + /// @return 获得积分流水 + List selectEarnByEventRecordId(@Param("eventRecordId") String eventRecordId); + + /// 批量查询幂等键对应的流水 + /// + /// @param idempotencyKeyList 幂等键列表 + /// @return 已存在流水 + List selectByIdempotencyKeyList(@Param("idempotencyKeyList") List idempotencyKeyList); + + /// 查询积分流水总数 + /// + /// @param page 查询条件 + /// @return 流水总数 + long countByQuery(GaoPointLedgerPage page); + + /// 分页查询积分流水 + /// + /// @param page 查询条件 + /// @return 流水列表 + List selectByQuery(GaoPointLedgerPage page); +} 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 new file mode 100644 index 0000000..15a5807 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPointRuleMapper.java @@ -0,0 +1,22 @@ +package com.imyeyu.api.modules.gao.mapper; + +import com.imyeyu.api.modules.gao.entity.GaoPointRule; +import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/// GAO 客户积分规则 Mapper +/// +/// @author Codex +/// @since 2026-08-11 +public interface GaoPointRuleMapper extends BaseMapper { + + /// 查询当前时间可匹配的启用规则 + /// + /// @param storeId 门店 ID + /// @param eventId 登记事件 ID + /// @param now 当前时间戳 + /// @return 按优先级排序的规则列表 + List selectActiveByStoreIdAndEventId(@Param("storeId") String storeId, @Param("eventId") String eventId, @Param("now") long now); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventMapper.java deleted file mode 100644 index 5ccd039..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventMapper.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.imyeyu.api.modules.gao.mapper; - -import com.imyeyu.api.modules.gao.entity.GaoRegistrationEvent; -import com.imyeyu.spring.mapper.BaseMapper; -import org.apache.ibatis.annotations.Param; - -import java.util.List; - -/// -/// 登记事件 Mapper -/// -/// @author Codex -/// @since 2026-07-27 -public interface GaoRegistrationEventMapper extends BaseMapper { - - List selectByIdList(@Param("idList") List idList); - - List selectEnabledList(@Param("now") Long now); -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventRecordMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventRecordMapper.java deleted file mode 100644 index 71c1dc3..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventRecordMapper.java +++ /dev/null @@ -1,157 +0,0 @@ -package com.imyeyu.api.modules.gao.mapper; - -import com.imyeyu.api.modules.gao.entity.GaoRegistrationEventRecord; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventPeriodStatView; -import com.imyeyu.api.modules.gao.vo.GaoCustomerRegisterStatView; -import com.imyeyu.api.modules.gao.vo.GaoCustomerRegistrationDayStatView; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordCalendarView; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordDailyStatView; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordListItemView; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordPeriodReq; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordRangeReq; -import com.imyeyu.spring.mapper.BaseMapper; -import org.apache.ibatis.annotations.Param; - -import java.util.List; - -/// -/// 登记事件记录 Mapper -/// -/// @author Codex -/// @since 2026-07-27 -public interface GaoRegistrationEventRecordMapper extends BaseMapper { - - /// 按登记事件查询客户最后登记事件记录 - /// - /// @param customerIdList 客户 ID 列表 - /// @param registrationEventId 登记事件 ID - /// @return 最后登记事件记录列表 - List selectByRegistrationEventAndPeriod(@Param("registrationEventId") String registrationEventId, @Param("periodType") GaoRegistrationEventRecordPeriodReq.PeriodType periodType, @Param("periodValue") Integer periodValue); - - /// 统计客户指定登记事件在时间范围内的登记数 - /// - /// @param customerId 客户 ID - /// @param registrationEventId 登记事件 ID - /// @param beginRegisteredAt 开始登记时间 - /// @param endRegisteredAt 结束登记时间 - /// @return 登记数 - Long countByCustomerIdAndRegistrationEventIdRange(@Param("customerId") String customerId, @Param("registrationEventId") String registrationEventId, @Param("beginRegisteredAt") Long beginRegisteredAt, @Param("endRegisteredAt") Long endRegisteredAt); - - /// 统计客户指定登记事件总登记数 - /// - /// @param customerId 客户 ID - /// @param registrationEventId 登记事件 ID - /// @return 登记数 - Long countByCustomerIdAndRegistrationEventId(@Param("customerId") String customerId, @Param("registrationEventId") String registrationEventId); - - /// 统计登记事件总登记数 - /// - /// @param registrationEventId 登记事件 ID - /// @return 登记数 - Long countByRegistrationEventId(@Param("registrationEventId") String registrationEventId); - - /// 按周期统计登记事件记录 - /// - /// @param registrationEventId 登记事件 ID - /// @param periodType 周期类型 - /// @return 统计结果 - List statByRegistrationEventAndPeriodType(@Param("registrationEventId") String registrationEventId, @Param("periodType") GaoRegistrationEventRecordPeriodReq.PeriodType periodType); - - /// 统计客户所有登记 - /// - /// @param customerId 客户 ID - /// @return 统计结果 - List statByCustomerId(@Param("customerId") String customerId); - - /// 按时间段统计单个客户登记 - /// - /// @param customerId 客户 ID - /// @param beginRegisteredAt 开始登记时间 - /// @param endRegisteredAt 结束登记时间 - /// @return 统计结果 - List statByCustomerIdAndRange(@Param("customerId") String customerId, @Param("beginRegisteredAt") Long beginRegisteredAt, @Param("endRegisteredAt") Long endRegisteredAt); - - /// 按时间段统计全部客户登记 - /// - /// @param beginRegisteredAt 开始登记时间 - /// @param endRegisteredAt 结束登记时间 - /// @return 统计结果 - List statAllCustomerByRange(@Param("beginRegisteredAt") Long beginRegisteredAt, @Param("endRegisteredAt") Long endRegisteredAt); - - /// 按时间范围统计全部客户每日登记 - /// - /// @param req 范围查询请求 - /// @return 每个有登记客户的每日统计 - List statCustomerRegistrationDay(@Param("req") GaoRegistrationEventRecordRangeReq req); - - /// 统计单个客户单个登记事件的每日登记事件记录数 - /// - /// @param customerId 客户 ID - /// @param registrationEventId 登记事件 ID - /// @param beginRegisteredAt 开始登记时间 - /// @param endRegisteredAt 结束登记时间 - /// @return 每日统计 - List statDailyCountByCustomerIdAndRegistrationEventIdRange(@Param("customerId") String customerId, @Param("registrationEventId") String registrationEventId, @Param("beginRegisteredAt") Long beginRegisteredAt, @Param("endRegisteredAt") Long endRegisteredAt); - - /// 统计全部客户单个登记事件的每日登记事件记录数 - /// - /// @param registrationEventId 登记事件 ID - /// @param beginRegisteredAt 开始登记时间 - /// @param endRegisteredAt 结束登记时间 - /// @return 每日统计 - List statDailyCountByRegistrationEventIdRange(@Param("registrationEventId") String registrationEventId, @Param("beginRegisteredAt") Long beginRegisteredAt, @Param("endRegisteredAt") Long endRegisteredAt); - - /// 统计时间段前单个客户单个登记事件累计登记事件记录数 - /// - /// @param customerId 客户 ID - /// @param registrationEventId 登记事件 ID - /// @param beginRegisteredAt 开始登记时间 - /// @return 累计登记数 - Long countBeforeByCustomerIdAndRegistrationEventId(@Param("customerId") String customerId, @Param("registrationEventId") String registrationEventId, @Param("beginRegisteredAt") Long beginRegisteredAt); - - /// 统计时间段前全部客户单个登记事件累计登记事件记录数 - /// - /// @param registrationEventId 登记事件 ID - /// @param beginRegisteredAt 开始登记时间 - /// @return 累计登记数 - Long countBeforeByRegistrationEventId(@Param("registrationEventId") String registrationEventId, @Param("beginRegisteredAt") Long beginRegisteredAt); - - /// 查询客户指定登记事件的最后登记事件记录 - /// - /// @param customerIdList 客户 ID 列表 - /// @param registrationEventId 登记事件 ID - /// @return 最后登记事件记录列表 - List selectLatestByCustomerIdListAndRegistrationEventId(@Param("customerIdList") List customerIdList, @Param("registrationEventId") String registrationEventId); - - /// 查询客户全部登记事件的最后登记事件记录 - /// - /// @param customerId 客户 ID - /// @return 最后登记事件记录列表 - List selectLatestByCustomerId(@Param("customerId") String customerId); - - /// 按任意时间范围查询登记事件记录 - /// - /// @param req 范围查询请求 - /// @return 登记事件记录列表 - List selectByRange(@Param("req") GaoRegistrationEventRecordRangeReq req); - - /// 按任意时间范围查询日历轻量记录 - /// - /// @param req 范围查询请求 - /// @return 日历轻量记录列表 - List selectCalendarByRange(@Param("req") GaoRegistrationEventRecordRangeReq req); - - /// 按任意时间范围统计登记事件记录 - /// - /// @param req 范围查询请求 - /// @return 登记事件记录数 - Long countByRange(@Param("req") GaoRegistrationEventRecordRangeReq req); - - /// 按任意时间范围分页查询登记事件记录 - /// - /// @param req 范围查询请求 - /// @param offset 偏移量 - /// @param size 每页条数 - /// @return 登记事件记录列表 - List selectPageByRange(@Param("req") GaoRegistrationEventRecordRangeReq req, @Param("offset") Long offset, @Param("size") Long size); -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventRoleRelationMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventRoleRelationMapper.java deleted file mode 100644 index 0a66c9d..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventRoleRelationMapper.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.imyeyu.api.modules.gao.mapper; - -import com.imyeyu.api.modules.gao.entity.GaoRegistrationEventRoleRelation; -import com.imyeyu.spring.mapper.BaseMapper; -import org.apache.ibatis.annotations.Param; -import org.apache.ibatis.annotations.Select; -import org.apache.ibatis.annotations.Update; - -import java.util.List; - -/// -/// 登记事件角色关联 Mapper -/// -/// @author Codex -/// @since 2026-07-28 -public interface GaoRegistrationEventRoleRelationMapper extends BaseMapper { - - @Select("SELECT `role_code` FROM `gao_registration_event_role_relation` WHERE `registration_event_id` = #{registrationEventId} AND `deleted_at` IS NULL ORDER BY `created_at` ASC") - List selectRoleCodeListByRegistrationEventId(@Param("registrationEventId") String registrationEventId); - - @Select({ - "" - }) - List selectByRegistrationEventIdList(@Param("registrationEventIdList") List registrationEventIdList); - - @Update("UPDATE `gao_registration_event_role_relation` SET `deleted_at` = UNIX_TIMESTAMP() WHERE `registration_event_id` = #{registrationEventId} AND `deleted_at` IS NULL") - void deleteByRegistrationEventId(@Param("registrationEventId") String registrationEventId); -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoStoreBusinessDayMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoStoreBusinessDayMapper.java new file mode 100644 index 0000000..0ea3a57 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoStoreBusinessDayMapper.java @@ -0,0 +1,49 @@ +package com.imyeyu.api.modules.gao.mapper; + +import com.imyeyu.api.modules.gao.entity.GaoStoreBusinessDay; +import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +/// +/// GAO 门店营业日 Mapper +/// +/// @author Codex +/// @since 2026-08-06 +public interface GaoStoreBusinessDayMapper extends BaseMapper { + + /// 按门店和日期查询营业日 + /// + /// @param storeId 门店 ID + /// @param dateValue 日期值,格式为 yyyyMMdd + /// @return 营业日 + @Select("SELECT * FROM `gao_store_business_day` WHERE `store_id` = #{storeId} AND `date_value` = #{dateValue} AND `deleted_at` IS NULL LIMIT 1") + GaoStoreBusinessDay selectByStoreIdAndDateValue(@Param("storeId") String storeId, @Param("dateValue") Integer dateValue); + + /// 查询指定日期前最后一个营业日序号 + /// + /// @param storeId 门店 ID + /// @param dateValue 日期值,格式为 yyyyMMdd + /// @return 营业日序号 + @Select("SELECT MAX(`business_day_no`) FROM `gao_store_business_day` WHERE `store_id` = #{storeId} AND `date_value` < #{dateValue} AND `open_status` = 'OPEN' AND `deleted_at` IS NULL") + Long selectLastBusinessDayNoBefore(@Param("storeId") String storeId, @Param("dateValue") Integer dateValue); + + /// 查询指定日期及之后的营业日 + /// + /// @param storeId 门店 ID + /// @param beginDateValue 开始日期值,格式为 yyyyMMdd + /// @return 营业日列表 + @Select("SELECT * FROM `gao_store_business_day` WHERE `store_id` = #{storeId} AND `date_value` >= #{beginDateValue} AND `open_status` = 'OPEN' AND `deleted_at` IS NULL ORDER BY `date_value` ASC") + List selectOpenListFromDate(@Param("storeId") String storeId, @Param("beginDateValue") Integer beginDateValue); + + /// 查询日期范围内的营业日 + /// + /// @param storeId 门店 ID + /// @param beginDateValue 开始日期值,格式为 yyyyMMdd + /// @param endDateValue 结束日期值,格式为 yyyyMMdd + /// @return 营业日列表 + @Select("SELECT * FROM `gao_store_business_day` WHERE `store_id` = #{storeId} AND `date_value` >= #{beginDateValue} AND `date_value` <= #{endDateValue} AND `deleted_at` IS NULL ORDER BY `date_value` ASC") + List selectByStoreIdAndDateRange(@Param("storeId") String storeId, @Param("beginDateValue") Integer beginDateValue, @Param("endDateValue") Integer endDateValue); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoStoreMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoStoreMapper.java new file mode 100644 index 0000000..0dcb790 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoStoreMapper.java @@ -0,0 +1,24 @@ +package com.imyeyu.api.modules.gao.mapper; + +import com.imyeyu.api.modules.gao.entity.GaoStore; +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-06 +public interface GaoStoreMapper extends BaseMapper { + + List selectByIdList(@Param("idList") Collection idList); + + /// 查询用户所属的可用门店 + /// + /// @param userId 用户 ID + /// @return 门店 + GaoStore selectByUserId(@Param("userId") String userId); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoUserMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoUserMapper.java index 88d550c..dc94f6e 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoUserMapper.java +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoUserMapper.java @@ -2,8 +2,6 @@ package com.imyeyu.api.modules.gao.mapper; import com.imyeyu.api.modules.gao.entity.GaoUser; import com.imyeyu.spring.mapper.BaseMapper; -import org.apache.ibatis.annotations.Param; -import org.apache.ibatis.annotations.Select; /// /// GAO 用户 Mapper @@ -11,7 +9,4 @@ import org.apache.ibatis.annotations.Select; /// @author Codex /// @since 2026-07-30 public interface GaoUserMapper extends BaseMapper { - - @Select("SELECT * FROM `gao_user` WHERE `user_id` = #{userId} AND `deleted_at` IS NULL LIMIT 1") - GaoUser selectByUserId(@Param("userId") String userId); } diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerChartService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerChartService.java new file mode 100644 index 0000000..252a50f --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerChartService.java @@ -0,0 +1,20 @@ +package com.imyeyu.api.modules.gao.service; + +import com.imyeyu.api.modules.gao.vo.GaoCustomerChartReq; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordCalendarView; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordDailyStatView; + +import java.util.List; + +/// 客户报表服务 +/// +/// @author Codex +/// @since 2026-08-11 +public interface GaoCustomerChartService { + + /// 查询客户登记每日统计 + List eventRecordDailyStat(GaoCustomerChartReq req); + + /// 查询客户登记日历记录 + List eventRecordCalendar(GaoCustomerChartReq req); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerService.java index 928e23f..2c15d7a 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerService.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerService.java @@ -1,60 +1,50 @@ package com.imyeyu.api.modules.gao.service; import com.imyeyu.api.modules.gao.entity.GaoCustomer; -import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStatView; +import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView; import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView; -import com.imyeyu.api.modules.gao.vo.GaoCustomerInviteUpdateReq; -import com.imyeyu.api.modules.gao.vo.GaoCustomerSearchReq; -import com.imyeyu.api.modules.gao.vo.GaoCustomerTrendStatReq; +import com.imyeyu.api.modules.gao.vo.GaoCustomerTrendStateReq; import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.service.BaseService; import java.io.IOException; +import java.util.Collection; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; -/// /// 客户服务 /// -/// @author Codex -/// @since 2026-07-27 +/// @author 夜雨 +/// @since 2026-07-27 14:12 public interface GaoCustomerService extends BaseService { + void updateWithAttachment(GaoCustomer customer); + /// 按编码查询客户 /// + /// @param storeId 门店 ID /// @param code 客户编码 /// @return 客户 - GaoCustomer getByCode(String code); + GaoCustomer getByCode(String storeId, String code); - /// 按编码查询客户,不存在时返回 null + /// 按姓名查询客户 /// - /// @param code 客户编码 + /// @param storeId 门店 ID + /// @param name 姓名 /// @return 客户 - GaoCustomer getNullableByCode(String code); + GaoCustomer getByName(String storeId, String name); - /// 按编码获取或快速创建客户 - /// - /// @param code 客户编码 - /// @return 客户 - GaoCustomer getOrQuickCreateByCode(String code); + List listByIdList(Collection idList); - /// 搜索客户 - /// - /// @param req 搜索请求 - /// @return 客户列表 - List search(GaoCustomerSearchReq req); - - /// 分页搜索客户 - /// - /// @param page 分页搜索请求 - /// @return 客户分页结果 - PageResult searchPage(Page page); + default Map mapByIdList(Collection idList) { + return listByIdList(idList).stream().collect(Collectors.toMap(GaoCustomer::getId, item -> item)); + } /// 按搜索条件导出客户 Excel /// - /// @param req 搜索请求 /// @return xlsx 文件字节 - byte[] exportSearchExcel(GaoCustomerSearchReq req) throws IOException; + byte[] export(Page page) throws IOException; /// 查询邀请的客户 /// @@ -64,8 +54,9 @@ public interface GaoCustomerService extends BaseService { /// 保存邀请关系 /// - /// @param req 邀请关系请求 - void saveInviteRelation(GaoCustomerInviteUpdateReq req); + /// @param introducerCustomerId 邀请来源客户 ID + /// @param customerId 客户 ID + void saveInviteRelation(String introducerCustomerId, String customerId); /// 删除邀请关系 /// @@ -76,10 +67,11 @@ public interface GaoCustomerService extends BaseService { /// /// @param req 趋势统计请求 /// @return 每日趋势列表 - List statDailyTrend(GaoCustomerTrendStatReq req); + List stateDailyTrend(GaoCustomerTrendStateReq req); /// 统计客户性别分布 /// + /// @param storeId 门店 ID /// @return 性别统计列表 - List statGender(); + List stateGender(String storeId); } diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoEventRecordService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoEventRecordService.java new file mode 100644 index 0000000..3fb4fb8 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoEventRecordService.java @@ -0,0 +1,46 @@ +package com.imyeyu.api.modules.gao.service; + +import com.imyeyu.api.modules.gao.entity.GaoCustomer; +import com.imyeyu.api.modules.gao.entity.GaoEventRecord; +import com.imyeyu.api.modules.gao.vo.GaoCustomerEventRankView; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordPage; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordRankPage; +import com.imyeyu.java.TimiJava; +import com.imyeyu.spring.bean.PageResult; +import com.imyeyu.spring.service.BaseService; + +import java.io.IOException; +import java.util.List; + +/// 登记事件记录服务 +/// +/// @author 夜雨 +/// @since 2026-07-27 14:09 +public interface GaoEventRecordService extends BaseService { + + default void createBatch(List recordList) { + for (GaoEventRecord record : TimiJava.safeIterable(recordList)) { + create(record); + } + } + + void createWithCustomer(GaoCustomer customer); + + /// 按任意时间范围查询登记事件记录 + /// + /// @param req 范围查询请求 + /// @return 登记事件记录列表 + PageResult pageByRange(GaoEventRecordPage req); + + /// 按时间范围查询客户登记排行榜 + /// + /// @param page 范围查询请求,不传时间为全部时间 + /// @return 客户登记排行榜 + PageResult pageRankByRange(GaoEventRecordRankPage page); + + /// 按任意时间范围导出登记事件记录 Excel + /// + /// @param page 范围查询请求 + /// @return xlsx 文件字节 + byte[] export(GaoEventRecordPage page) throws IOException; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoEventService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoEventService.java new file mode 100644 index 0000000..6331a21 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoEventService.java @@ -0,0 +1,37 @@ +package com.imyeyu.api.modules.gao.service; + +import com.imyeyu.api.modules.gao.entity.GaoEvent; +import com.imyeyu.spring.service.BaseService; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/// +/// 登记事件服务 +/// +/// @author Codex +/// @since 2026-07-27 +public interface GaoEventService extends BaseService { + + Map mapByIdList(Collection idList); + + Map> mapRoleCodeListByEventIdList(Collection eventIdList); + + /// 查询启用的登记事件 + /// + /// @param storeId 门店 ID + /// @return 登记事件列表 + List listValid(String storeId); + + /// 查询登记事件要求的角色 + /// + /// @param eventId 登记事件 ID + /// @return 角色代码列表 + List listRoleCode(String eventId); + + /// 按客户端提交的顺序更新登记事件排序 + /// + /// @param idList 登记事件 ID 列表 + void sort(List idList); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoPointAccountService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoPointAccountService.java new file mode 100644 index 0000000..c824b8a --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoPointAccountService.java @@ -0,0 +1,35 @@ +package com.imyeyu.api.modules.gao.service; + +import com.imyeyu.api.modules.gao.entity.GaoPointAccount; +import com.imyeyu.spring.service.BaseService; + +/// GAO 客户积分账户服务 +/// +/// @author Codex +/// @since 2026-08-11 +public interface GaoPointAccountService extends BaseService { + + /// 查询客户当前有效积分账户 + /// + /// @param storeId 门店 ID + /// @param customerId 客户 ID + /// @return 积分账户,不存在时返回 null + GaoPointAccount getByCustomerId(String storeId, String customerId); + + /// 获取并锁定客户积分账户,不存在时创建 + /// + /// @param storeId 门店 ID + /// @param customerId 客户 ID + /// @return 已锁定的积分账户 + GaoPointAccount getOrCreateForUpdate(String storeId, String customerId); + + /// 原子更新账户余额 + /// + /// @param accountId 账户 ID + /// @param delta 本次余额变化 + /// @param totalEarnedDelta 累计获得积分变化 + /// @param totalRevokedDelta 累计撤销积分变化 + /// @param totalAdjustedDelta 累计人工调整积分变化 + /// @return 更新行数 + int updateBalance(String accountId, long delta, long totalEarnedDelta, long totalRevokedDelta, long totalAdjustedDelta); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoPointLedgerService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoPointLedgerService.java new file mode 100644 index 0000000..e544369 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoPointLedgerService.java @@ -0,0 +1,46 @@ +package com.imyeyu.api.modules.gao.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.imyeyu.api.modules.gao.entity.GaoPointLedger; +import com.imyeyu.api.modules.gao.entity.GaoPointRule; +import com.imyeyu.api.modules.gao.entity.GaoEventRecord; +import com.imyeyu.api.modules.gao.vo.GaoPointAdjustRequest; +import com.imyeyu.api.modules.gao.vo.GaoPointLedgerPage; +import com.imyeyu.spring.bean.PageResult; +import com.imyeyu.spring.service.BaseService; + +/// GAO 客户积分流水服务 +/// +/// @author Codex +/// @since 2026-08-11 +public interface GaoPointLedgerService extends BaseService { + + /// 写入登记获得积分流水 + /// + /// @param record 登记记录 + /// @param rule 命中规则 + /// @param pointDelta 积分变化 + /// @param sourcePayload 来源快照 + /// @return 积分流水 + GaoPointLedger earn(GaoEventRecord record, GaoPointRule rule, long pointDelta, JsonNode sourcePayload); + + /// 写入删除登记记录的撤销流水 + /// + /// @param originLedger 原始获得积分流水 + /// @param record 登记记录 + /// @return 撤销流水 + GaoPointLedger revoke(GaoPointLedger originLedger, GaoEventRecord record); + + /// 写入人工调整流水 + /// + /// @param request 人工调整请求 + /// @param operatorUserId 操作用户 ID + /// @return 调整流水 + GaoPointLedger adjust(GaoPointAdjustRequest request, String operatorUserId); + + /// 分页查询积分流水 + /// + /// @param page 查询条件 + /// @return 分页结果 + PageResult pageByQuery(GaoPointLedgerPage page); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoPointRuleService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoPointRuleService.java new file mode 100644 index 0000000..5dbc7ff --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoPointRuleService.java @@ -0,0 +1,36 @@ +package com.imyeyu.api.modules.gao.service; + +import com.imyeyu.api.modules.gao.entity.GaoPointRule; +import com.imyeyu.spring.service.BaseService; + +import java.util.List; + +/// GAO 客户积分规则服务 +/// +/// @author Codex +/// @since 2026-08-11 +public interface GaoPointRuleService extends BaseService { + + /// 查询当前时间可匹配的启用规则 + /// + /// @param storeId 门店 ID + /// @param eventId 登记事件 ID + /// @param now 当前时间戳 + /// @return 规则列表 + List listActive(String storeId, String eventId, long now); + + /// 创建积分规则 + /// + /// @param rule 积分规则 + void create(GaoPointRule rule); + + /// 修改积分规则 + /// + /// @param rule 积分规则 + void update(GaoPointRule rule); + + /// 删除积分规则 + /// + /// @param id 规则 ID + void delete(String id); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoPointTriggerService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoPointTriggerService.java new file mode 100644 index 0000000..b2e47db --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoPointTriggerService.java @@ -0,0 +1,28 @@ +package com.imyeyu.api.modules.gao.service; + +import com.imyeyu.api.modules.gao.entity.GaoPointLedger; +import com.imyeyu.api.modules.gao.entity.GaoEventRecord; +import com.imyeyu.api.modules.gao.vo.GaoPointAdjustRequest; + +/// GAO 客户积分触发服务 +/// +/// @author Codex +/// @since 2026-08-11 +public interface GaoPointTriggerService { + + /// 在登记记录创建成功后触发积分 + /// + /// @param record 登记记录 + void onEventRecordCreated(GaoEventRecord record); + + /// 撤销登记记录产生的全部获得积分 + /// + /// @param record 登记记录 + void revokeByEventRecord(GaoEventRecord record); + + /// 执行人工调整 + /// + /// @param request 人工调整请求 + /// @return 调整流水 + GaoPointLedger adjust(GaoPointAdjustRequest request); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoRegistrationEventRecordService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoRegistrationEventRecordService.java deleted file mode 100644 index 6ad2a85..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/service/GaoRegistrationEventRecordService.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.imyeyu.api.modules.gao.service; - -import com.imyeyu.api.modules.gao.entity.GaoRegistrationEventRecord; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventPeriodStatView; -import com.imyeyu.api.modules.gao.vo.GaoCustomerRegisterStatView; -import com.imyeyu.api.modules.gao.vo.GaoCustomerRegistrationRankView; -import com.imyeyu.api.modules.gao.vo.GaoQuickRegistrationEventRecordReq; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordCalendarView; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordCreateReq; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordDailyStatView; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordListItemView; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordPeriodReq; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordRangeReq; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordStatReq; -import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordTrendStatReq; -import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.bean.PageResult; -import com.imyeyu.spring.service.BaseService; - -import java.io.IOException; -import java.util.List; - -/// -/// 登记事件记录服务 -/// -/// @author Codex -/// @since 2026-07-27 -public interface GaoRegistrationEventRecordService extends BaseService { - - /// 批量创建登记事件记录 - /// - /// @param req 批量创建请求 - /// @return 创建后的登记事件记录列表 - List createRecords(GaoRegistrationEventRecordCreateReq req); - - /// 快速录入客户并创建登记事件记录 - /// - /// @param req 快速录入请求 - /// @return 创建后的登记事件记录 - GaoRegistrationEventRecord quickRegister(GaoQuickRegistrationEventRecordReq req); - - /// 按任意时间范围查询登记事件记录 - /// - /// @param req 范围查询请求 - /// @return 登记事件记录列表 - List listByRange(GaoRegistrationEventRecordRangeReq req); - - /// 按任意时间范围查询日历轻量记录 - /// - /// @param req 范围查询请求 - /// @return 日历轻量记录列表 - List listCalendarByRange(GaoRegistrationEventRecordRangeReq req); - - /// 按任意时间范围分页查询登记事件记录 - /// - /// @param page 分页参数 - /// @return 登记事件记录分页结果 - PageResult pageByRange(Page page); - - /// 按任意时间范围导出登记事件记录 Excel - /// - /// @param req 范围查询请求 - /// @return xlsx 文件字节 - byte[] exportRangeExcel(GaoRegistrationEventRecordRangeReq req) throws IOException; - - /// 按周期查询登记事件记录 - /// - /// @param req 周期请求 - /// @return 登记事件记录列表 - List listByRegistrationEventAndPeriod(GaoRegistrationEventRecordPeriodReq req); - - /// 按周期统计登记事件记录 - /// - /// @param registrationEventId 登记事件 ID - /// @param periodType 周期类型 - /// @return 统计列表 - List statByRegistrationEventAndPeriodType(String registrationEventId, GaoRegistrationEventRecordPeriodReq.PeriodType periodType); - - /// 查询客户指定登记事件的最后登记事件记录 - /// - /// @param customerIdList 客户 ID 列表 - /// @param registrationEventId 登记事件 ID - /// @return 每个客户的最后登记事件记录 - List listLatestByCustomerIdListAndRegistrationEventId(List customerIdList, String registrationEventId); - - /// 统计客户所有登记 - /// - /// @param customerId 客户 ID - /// @return 统计结果 - List statByCustomerId(String customerId); - - /// 按时间段统计单个客户登记 - /// - /// @param req 统计请求 - /// @return 统计结果 - List statByCustomerAndRange(GaoRegistrationEventRecordStatReq req); - - /// 按时间段统计全部客户登记 - /// - /// @param req 统计请求 - /// @return 统计结果 - List statAllCustomerByRange(GaoRegistrationEventRecordStatReq req); - - /// 按时间范围查询客户登记排行榜 - /// - /// @param req 范围查询请求,不传时间为全部时间 - /// @return 客户登记排行榜 - List listCustomerRegistrationRank(GaoRegistrationEventRecordRangeReq req); - - /// 按时间段统计单个客户单个登记事件每日趋势 - /// - /// @param req 趋势统计请求 - /// @return 每日趋势列表 - List statDailyByCustomerAndRegistrationEvent(GaoRegistrationEventRecordTrendStatReq req); - - /// 按时间段统计全部客户单个登记事件每日趋势 - /// - /// @param req 趋势统计请求 - /// @return 每日趋势列表 - List statDailyByRegistrationEvent(GaoRegistrationEventRecordTrendStatReq req); - - /// 查询客户全部登记事件的最后登记事件记录 - /// - /// @param customerId 客户 ID - /// @return 每个登记事件的最后登记事件记录 - List listLatestByCustomerId(String customerId); -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoRegistrationEventService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoRegistrationEventService.java deleted file mode 100644 index f6f609c..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/service/GaoRegistrationEventService.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.imyeyu.api.modules.gao.service; - -import com.imyeyu.api.modules.gao.entity.GaoRegistrationEvent; -import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.bean.PageResult; -import com.imyeyu.spring.service.BaseService; - -import java.util.List; - -/// -/// 登记事件服务 -/// -/// @author Codex -/// @since 2026-07-27 -public interface GaoRegistrationEventService extends BaseService { - - /// 分页查询登记事件并填充可登记角色 - /// - /// @param page 分页参数 - /// @return 登记事件分页 - PageResult pageWithRegistrationEventLimit(Page page); - - /// 查询启用的登记事件 - /// - /// @return 登记事件列表 - List listEnabled(); - - /// 查询登记事件要求的角色 - /// - /// @param registrationEventId 登记事件 ID - /// @return 角色代码列表 - List listRoleCode(String registrationEventId); - - /// 按客户端提交的顺序更新登记事件排序 - /// - /// @param idList 登记事件 ID 列表 - void sort(List idList); -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreBusinessDayService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreBusinessDayService.java new file mode 100644 index 0000000..fb6eb78 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreBusinessDayService.java @@ -0,0 +1,33 @@ +package com.imyeyu.api.modules.gao.service; + +import com.imyeyu.api.modules.gao.entity.GaoStoreBusinessDay; +import com.imyeyu.spring.service.BaseService; + +/// +/// GAO 门店营业日服务 +/// +/// @author Codex +/// @since 2026-08-06 +public interface GaoStoreBusinessDayService extends BaseService { + + /// 解析营业日,不存在时按默认营业日创建 + /// + /// @param storeId 门店 ID + /// @param dateValue 日期值,格式为 yyyyMMdd + /// @return 营业日 + GaoStoreBusinessDay resolveOpenBusinessDay(String storeId, Integer dateValue); + + /// 重算门店营业日序号并同步登记记录快照 + /// + /// @param storeId 门店 ID + /// @param beginDateValue 开始日期值,格式为 yyyyMMdd + void rebuildBusinessDayNo(String storeId, Integer beginDateValue); + + /// 查询日期范围内的营业日 + /// + /// @param storeId 门店 ID + /// @param beginDateValue 开始日期值,格式为 yyyyMMdd + /// @param endDateValue 结束日期值,格式为 yyyyMMdd + /// @return 营业日列表 + java.util.List listByDateRange(String storeId, Integer beginDateValue, Integer endDateValue); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreChartService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreChartService.java new file mode 100644 index 0000000..da556d5 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreChartService.java @@ -0,0 +1,28 @@ +package com.imyeyu.api.modules.gao.service; + +import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView; +import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordCalendarView; +import com.imyeyu.api.modules.gao.vo.GaoEventRecordDailyStatView; +import com.imyeyu.api.modules.gao.vo.GaoStoreChartReq; + +import java.util.List; + +/// 门店报表服务 +/// +/// @author Codex +/// @since 2026-08-11 +public interface GaoStoreChartService { + + /// 查询客户每日趋势 + List customerDailyTrend(GaoStoreChartReq req); + + /// 查询客户性别统计 + List customerGender(String storeId); + + /// 查询登记事件每日统计 + List eventRecordDailyStat(GaoStoreChartReq req); + + /// 查询登记事件日历记录 + List eventRecordCalendar(GaoStoreChartReq req); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreService.java new file mode 100644 index 0000000..43f4fe0 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreService.java @@ -0,0 +1,35 @@ +package com.imyeyu.api.modules.gao.service; + +import com.imyeyu.api.modules.gao.entity.GaoStore; +import com.imyeyu.spring.bean.Page; +import com.imyeyu.spring.service.BaseService; + +import java.io.IOException; +import java.util.List; + +/// +/// GAO 门店服务 +/// +/// @author Codex +/// @since 2026-08-06 +public interface GaoStoreService extends BaseService { + + /// 查询用户所属门店 + /// + /// @param userId 用户 ID + /// @return 门店 + GaoStore getByUserId(String userId); + + String getBelongIdByRequiredLoginUserId(); + + /// 导出门店列表 + /// + /// @param page 分页查询条件 + /// @return xlsx 文件字节 + byte[] export(Page