From 1709316ce101c053debc1a5537ba46c3cda25e5d Mon Sep 17 00:00:00 2001 From: Timi Date: Thu, 6 Aug 2026 10:52:50 +0800 Subject: [PATCH 01/12] refactor gao module --- .idea/sqldialects.xml | 1 + pom.xml | 4 +- .../controller/AttachmentController.java | 26 - .../api/modules/common/entity/Attachment.java | 15 +- .../common/mapper/AttachmentMapper.java | 4 +- .../common/service/AttachmentService.java | 20 +- .../implement/AttachmentServiceImplement.java | 232 +++--- .../common/vo/attach/BizDeleteReq.java | 20 - .../common/vo/attach/BizUpdateReq.java | 26 - .../modules/gao/bean/GaoPermissionCode.java | 29 +- .../gao/controller/GaoCustomerController.java | 230 +++--- ...ontroller.java => GaoEventController.java} | 53 +- .../controller/GaoEventRecordController.java | 152 ++++ .../GaoRegistrationEventRecordController.java | 201 ----- .../gao/controller/GaoRoleController.java | 7 +- .../gao/controller/GaoUserController.java | 5 +- .../api/modules/gao/entity/GaoCustomer.java | 29 +- ...aoRegistrationEvent.java => GaoEvent.java} | 33 +- ...onEventRecord.java => GaoEventRecord.java} | 10 +- ...elation.java => GaoEventRoleRelation.java} | 4 +- .../modules/gao/mapper/GaoCustomerMapper.java | 31 +- .../modules/gao/mapper/GaoEventMapper.java | 19 + .../gao/mapper/GaoEventRecordMapper.java | 37 + .../mapper/GaoEventRoleRelationMapper.java | 16 + .../mapper/GaoRegistrationEventMapper.java | 19 - .../GaoRegistrationEventRecordMapper.java | 157 ---- ...aoRegistrationEventRoleRelationMapper.java | 36 - .../gao/service/GaoCustomerService.java | 53 +- .../gao/service/GaoEventRecordService.java | 35 + .../modules/gao/service/GaoEventService.java | 36 + .../GaoRegistrationEventRecordService.java | 127 --- .../service/GaoRegistrationEventService.java | 38 - .../GaoCustomerServiceImplement.java | 525 ++++-------- .../GaoEventRecordServiceImplement.java | 252 ++++++ .../implement/GaoEventServiceImplement.java | 215 +++++ ...gistrationEventRecordServiceImplement.java | 761 ------------------ .../GaoRegistrationEventServiceImplement.java | 260 ------ .../modules/gao/util/CustomerXSSFBuilder.java | 67 ++ .../gao/util/EventRecordXSSFBuilder.java | 53 ++ ...ew.java => GaoCustomerDailyStateView.java} | 2 +- ...iew.java => GaoCustomerEventRankView.java} | 19 +- .../gao/vo/GaoCustomerInviteUpdateReq.java | 18 - .../gao/vo/GaoCustomerRegisterStatView.java | 30 - .../GaoCustomerRegistrationDayStatView.java | 33 - .../modules/gao/vo/GaoCustomerSearchReq.java | 18 - ...Req.java => GaoCustomerTrendStateReq.java} | 2 +- ...dRangeReq.java => GaoEventRecordPage.java} | 18 +- .../gao/vo/GaoEventRecordRankPage.java | 28 + .../GaoQuickRegistrationEventRecordReq.java | 27 - .../GaoRegistrationEventPeriodStatView.java | 21 - ...aoRegistrationEventRecordCalendarView.java | 30 - .../GaoRegistrationEventRecordCreateReq.java | 30 - ...oRegistrationEventRecordDailyStatView.java | 21 - ...aoRegistrationEventRecordListItemView.java | 33 - .../GaoRegistrationEventRecordPeriodReq.java | 35 - .../vo/GaoRegistrationEventRecordStatReq.java | 21 - ...aoRegistrationEventRecordTrendStatReq.java | 24 - .../user/controller/UserController.java | 7 +- .../com/imyeyu/api/util/BaseXSSFBuilder.java | 65 ++ .../V23__rename_gao_event_tables.sql | 54 ++ .../mapper/timi-server/AttachmentMapper.xml | 33 +- .../mapper/timi-server/GaoCustomerMapper.xml | 78 +- ...tionEventMapper.xml => GaoEventMapper.xml} | 18 +- .../timi-server/GaoEventRecordMapper.xml | 171 ++++ .../GaoEventRoleRelationMapper.xml | 22 + .../GaoRegistrationEventRecordMapper.xml | 389 --------- .../mapper/timi-server/UserMapper.xml | 4 +- 67 files changed, 1770 insertions(+), 3319 deletions(-) delete mode 100644 src/main/java/com/imyeyu/api/modules/common/vo/attach/BizDeleteReq.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/vo/attach/BizUpdateReq.java rename src/main/java/com/imyeyu/api/modules/gao/controller/{GaoRegistrationEventController.java => GaoEventController.java} (57%) create mode 100644 src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventRecordController.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gao/controller/GaoRegistrationEventRecordController.java rename src/main/java/com/imyeyu/api/modules/gao/entity/{GaoRegistrationEvent.java => GaoEvent.java} (77%) rename src/main/java/com/imyeyu/api/modules/gao/entity/{GaoRegistrationEventRecord.java => GaoEventRecord.java} (81%) rename src/main/java/com/imyeyu/api/modules/gao/entity/{GaoRegistrationEventRoleRelation.java => GaoEventRoleRelation.java} (76%) create mode 100644 src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventMapper.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventRecordMapper.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventRoleRelationMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventRecordMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventRoleRelationMapper.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/service/GaoEventRecordService.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/service/GaoEventService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gao/service/GaoRegistrationEventRecordService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gao/service/GaoRegistrationEventService.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoEventRecordServiceImplement.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoEventServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoRegistrationEventRecordServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoRegistrationEventServiceImplement.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/util/CustomerXSSFBuilder.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/util/EventRecordXSSFBuilder.java rename src/main/java/com/imyeyu/api/modules/gao/vo/{GaoCustomerDailyStatView.java => GaoCustomerDailyStateView.java} (89%) rename src/main/java/com/imyeyu/api/modules/gao/vo/{GaoCustomerRegistrationRankView.java => GaoCustomerEventRankView.java} (52%) delete mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerInviteUpdateReq.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerRegisterStatView.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerRegistrationDayStatView.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerSearchReq.java rename src/main/java/com/imyeyu/api/modules/gao/vo/{GaoCustomerTrendStatReq.java => GaoCustomerTrendStateReq.java} (86%) rename src/main/java/com/imyeyu/api/modules/gao/vo/{GaoRegistrationEventRecordRangeReq.java => GaoEventRecordPage.java} (58%) create mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordRankPage.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoQuickRegistrationEventRecordReq.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventPeriodStatView.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordCalendarView.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordCreateReq.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordDailyStatView.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordListItemView.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordPeriodReq.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordStatReq.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordTrendStatReq.java create mode 100644 src/main/java/com/imyeyu/api/util/BaseXSSFBuilder.java create mode 100644 src/main/resources/db/migration/timiserver/V23__rename_gao_event_tables.sql rename src/main/resources/mapper/timi-server/{GaoRegistrationEventMapper.xml => GaoEventMapper.xml} (63%) create mode 100644 src/main/resources/mapper/timi-server/GaoEventRecordMapper.xml create mode 100644 src/main/resources/mapper/timi-server/GaoEventRoleRelationMapper.xml delete mode 100644 src/main/resources/mapper/timi-server/GaoRegistrationEventRecordMapper.xml 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..a77ebe1 100644 --- a/pom.xml +++ b/pom.xml @@ -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/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/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..f1b1bf5 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; @@ -43,7 +41,7 @@ import java.io.ByteArrayOutputStream; 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 +69,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 +346,12 @@ public class AttachmentServiceImplement extends AbstractEntityService items = req.getItems(); - + public void updateByBizId(Attachment.BizType bizType, String bizId, List list) { // 数据库现有附件 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 +359,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/bean/GaoPermissionCode.java b/src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java index cfc07be..3f25cb5 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 模块权限枚举 @@ -20,23 +21,23 @@ public enum GaoPermissionCode implements BuiltinPermissionCode { CUSTOMER_DELETE("CUSTOMER:DELETE", "客户删除"), - REGISTRATION_EVENT_CREATE("REGISTRATION_EVENT:CREATE", "登记事件创建"), + EVENT_CREATE("EVENT:CREATE", "登记事件创建"), - REGISTRATION_EVENT_READ("REGISTRATION_EVENT:READ", "登记事件读取"), + EVENT_READ("EVENT:READ", "登记事件读取"), - REGISTRATION_EVENT_UPDATE("REGISTRATION_EVENT:UPDATE", "登记事件修改"), + EVENT_UPDATE("EVENT:UPDATE", "登记事件修改"), - REGISTRATION_EVENT_DELETE("REGISTRATION_EVENT:DELETE", "登记事件删除"), + EVENT_DELETE("EVENT:DELETE", "登记事件删除"), - REGISTRATION_EVENT_RECORD_CREATE("REGISTRATION_EVENT_RECORD:CREATE", "登记事件记录创建"), + EVENT_RECORD_CREATE("EVENT_RECORD:CREATE", "登记事件记录创建"), - REGISTRATION_EVENT_RECORD_READ("REGISTRATION_EVENT_RECORD:READ", "登记事件记录读取"), + EVENT_RECORD_READ("EVENT_RECORD:READ", "登记事件记录读取"), - REGISTRATION_EVENT_RECORD_EXPORT("REGISTRATION_EVENT_RECORD:EXPORT", "登记事件记录导出", true), + EVENT_RECORD_EXPORT("EVENT_RECORD:EXPORT", "登记事件记录导出", true), - REGISTRATION_EVENT_RECORD_UPDATE("REGISTRATION_EVENT_RECORD:UPDATE", "登记事件记录修改"), + EVENT_RECORD_UPDATE("EVENT_RECORD:UPDATE", "登记事件记录修改"), - REGISTRATION_EVENT_RECORD_DELETE("REGISTRATION_EVENT_RECORD:DELETE", "登记事件记录删除"), + EVENT_RECORD_DELETE("EVENT_RECORD:DELETE", "登记事件记录删除"), STAT_READ("STAT:READ", "统计读取"), @@ -52,6 +53,9 @@ public enum GaoPermissionCode implements BuiltinPermissionCode { final String value; final String defaultName; + + /// `true` 为初始化时授权给店长角色 + @Getter final boolean grantToStoreManager; GaoPermissionCode(String value, String defaultName) { @@ -88,11 +92,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/controller/GaoCustomerController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerController.java index 480a64b..ee984b0 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,29 @@ 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.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.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.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.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.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 +33,22 @@ 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 +@RequireGaoRole(value = {GaoRoleCode.STORE_MANAGER, GaoRoleCode.EMPLOYEE}, mode = RoleMatchMode.ANY) @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 AttachmentService attachmentService; private final GaoCustomerService service; - private final GaoRegistrationEventRecordService registrationEventRecordService; - private final GaoRegistrationEventService registrationEventService; @AOPLog @RequestRateLimit @@ -63,74 +57,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 +90,9 @@ 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) { 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 @@ -169,27 +113,66 @@ 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/code") + @JsonView(ResponseView.Public.class) + public GaoCustomer findByCode(@RequestParam String code) { + GaoCustomer customer = service.getByCode(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("/invite/detail") + @PostMapping("/find/name") @JsonView(ResponseView.Public.class) - public GaoCustomer inviteDetail(@RequestParam String customerId) { - return service.get(customerId); + public GaoCustomer findByName(@RequestParam String name) { + GaoCustomer customer = service.getByName(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 +183,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() { + return service.stateGender(); } } diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRegistrationEventController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventController.java similarity index 57% rename from src/main/java/com/imyeyu/api/modules/gao/controller/GaoRegistrationEventController.java rename to src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventController.java index 0063ad6..6103e94 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRegistrationEventController.java +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventController.java @@ -1,12 +1,12 @@ package com.imyeyu.api.modules.gao.controller; +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.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.api.modules.gao.entity.GaoEvent; +import com.imyeyu.api.modules.gao.service.GaoEventService; import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.RequestRateLimit; import com.imyeyu.spring.bean.Page; @@ -19,62 +19,67 @@ 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-27 +/// @author 夜雨 +/// @since 2026-07-27 14:13 @RestController -@RequiredArgsConstructor -@RequestMapping("/gao/registration-event") @RequireGaoRole(value = {GaoRoleCode.STORE_MANAGER, GaoRoleCode.EMPLOYEE}, mode = RoleMatchMode.ANY) -public class GaoRegistrationEventController { +@RequiredArgsConstructor +@RequestMapping("/gao/event") +public class GaoEventController { - private final GaoRegistrationEventService service; + private final GaoEventService service; @AOPLog @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_READ) + @RequireGaoPermission(GaoPermissionCode.EVENT_READ) @PostMapping("/list") - public PageResult list(@RequestBody Page page) { - return service.pageWithRegistrationEventLimit(page); + 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("/enabled/list") - public List enabledList() { - return service.listEnabled(); + @PostMapping("/list/valid") + public List listValid() { + return service.listValid(); } @AOPLog @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_READ) + @RequireGaoPermission(GaoPermissionCode.EVENT_READ) @PostMapping("/detail") - public GaoRegistrationEvent detail(@RequestParam String id) { + public GaoEvent detail(@RequestParam String id) { return service.get(id); } @AOPLog @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_CREATE) + @RequireGaoPermission(GaoPermissionCode.EVENT_CREATE) @PostMapping("/create") - public void create(@RequestBody GaoRegistrationEvent registrationEvent) { + public void create(@RequestBody GaoEvent registrationEvent) { service.create(registrationEvent); } @AOPLog @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_UPDATE) + @RequireGaoPermission(GaoPermissionCode.EVENT_UPDATE) @PostMapping("/update") - public void update(@RequestBody GaoRegistrationEvent registrationEvent) { + public void update(@RequestBody GaoEvent registrationEvent) { service.update(registrationEvent); } @AOPLog @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_UPDATE) + @RequireGaoPermission(GaoPermissionCode.EVENT_UPDATE) @PostMapping("/sort") public void sort(@RequestBody List idList) { service.sort(idList); @@ -82,7 +87,7 @@ public class GaoRegistrationEventController { @AOPLog @RequestRateLimit - @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_DELETE) + @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..e79352b --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventRecordController.java @@ -0,0 +1,152 @@ +package com.imyeyu.api.modules.gao.controller; + +import com.fasterxml.jackson.annotation.JsonView; +import com.imyeyu.api.bean.RoleMatchMode; +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.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.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.UserService; +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 +@RequireGaoRole(value = {GaoRoleCode.STORE_MANAGER, GaoRoleCode.EMPLOYEE}, mode = RoleMatchMode.ANY) +@RequiredArgsConstructor +@RequestMapping("/gao/event/record") +public class GaoEventRecordController { + + private final UserService userService; + private final GaoEventService eventService; + private final AttachmentService attachmentService; + private final GaoCustomerService customerService; + private final GaoEventRecordService service; + + @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 + @PostMapping("/create") + @JsonView(ResponseView.Public.class) + public void create(@RequestBody GaoEventRecord record) { + service.create(record); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.EVENT_RECORD_UPDATE) + @PostMapping("/update") + public void update(@RequestBody GaoEventRecord record) { + 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/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..cbc4d27 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 @@ -14,15 +14,14 @@ 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; 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..cf26252 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 @@ -24,11 +24,10 @@ import org.springframework.web.bind.annotation.RestController; import java.util.List; -/// /// GAO 用户接口 /// -/// @author Codex -/// @since 2026-07-30 +/// @author 夜雨 +/// @since 2026-07-30 14:15 @RestController @RequiredArgsConstructor @RequestMapping("/gao/user") 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..9a5b573 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; @@ -55,7 +55,7 @@ public class GaoCustomer extends UUIDEntity { private Gender gender; /// 年龄 - private Integer age; + private Long age; /// 出生日期,数据库只保存公历日期 @JsonFormat(pattern = "yyyy-MM-dd") @@ -64,15 +64,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 +82,24 @@ 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; + + 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 77% 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..1142178 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,33 +38,36 @@ 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; } /// 登记事件名称 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 81% 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..4dacd05 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 { /// /// 登记事件记录附件类型 @@ -32,7 +32,7 @@ public class GaoRegistrationEventRecord extends UUIDEntity { private String customerId; /// 登记登记事件 ID - private String registrationEventId; + private String eventId; /// 操作管理员用户 ID private String operatorUserId; @@ -47,7 +47,7 @@ public class GaoRegistrationEventRecord extends UUIDEntity { private GaoCustomer customer; @Transient - private GaoRegistrationEvent registrationEvent; + private GaoEvent event; @Transient private User operator; @@ -55,7 +55,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/mapper/GaoCustomerMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerMapper.java index 3c6aa26..1ee6b93 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,11 @@ 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 +15,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(Long beginCreatedAt, Long endCreatedAt); /// 统计开始时间前客户总数 /// /// @param beginCreatedAt 开始创建时间 /// @return 客户总数 - Long countCreatedBefore(@Param("beginCreatedAt") Long beginCreatedAt); + Long countCreatedBefore(Long beginCreatedAt); /// 统计客户性别分布 /// /// @return 性别统计列表 - List statGender(); + List stateGender(); } 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..56040d3 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventMapper.java @@ -0,0 +1,19 @@ +package com.imyeyu.api.modules.gao.mapper; + +import com.imyeyu.api.modules.gao.entity.GaoEvent; +import com.imyeyu.spring.mapper.BaseMapper; + +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(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..460900a --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventRecordMapper.java @@ -0,0 +1,37 @@ +package com.imyeyu.api.modules.gao.mapper; + +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.spring.mapper.BaseMapper; + +import java.util.List; + +/// 登记事件记录 Mapper +/// +/// @author 夜雨 +/// @since 2026-07-27 14:11 +public interface GaoEventRecordMapper extends BaseMapper { + + Long count(String customerId, String eventId, Long beginAt, Long endAt); + + long countByRange(GaoEventRecordPage page); + + /// 按任意时间范围查询登记事件记录 + /// + /// @return 登记事件记录列表 + List selectByRange(GaoEventRecordPage page); + + /// 统计客户登记排行榜总数 + /// + /// @param page 排行榜分页请求 + /// @return 客户数 + long countRankByRange(GaoEventRecordRankPage page); + + /// 分页查询客户登记排行榜 + /// + /// @param page 排行榜分页请求 + /// @return 客户登记排行榜 + List selectRankByRange(GaoEventRecordRankPage page); +} 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/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/service/GaoCustomerService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerService.java index 928e23f..f544164 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,23 +1,22 @@ 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 { /// 按编码查询客户 @@ -26,35 +25,18 @@ public interface GaoCustomerService extends BaseService { /// @return 客户 GaoCustomer getByCode(String code); - /// 按编码查询客户,不存在时返回 null - /// - /// @param code 客户编码 - /// @return 客户 - GaoCustomer getNullableByCode(String code); + GaoCustomer getByName(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 +46,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 +59,10 @@ public interface GaoCustomerService extends BaseService { /// /// @param req 趋势统计请求 /// @return 每日趋势列表 - List statDailyTrend(GaoCustomerTrendStatReq req); + List stateDailyTrend(GaoCustomerTrendStateReq req); /// 统计客户性别分布 /// /// @return 性别统计列表 - List statGender(); + List stateGender(); } 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..3ad884f --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoEventRecordService.java @@ -0,0 +1,35 @@ +package com.imyeyu.api.modules.gao.service; + +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.spring.bean.PageResult; +import com.imyeyu.spring.service.BaseService; + +import java.io.IOException; + +/// 登记事件记录服务 +/// +/// @author 夜雨 +/// @since 2026-07-27 14:09 +public interface GaoEventRecordService extends BaseService { + + /// 按任意时间范围查询登记事件记录 + /// + /// @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..f37f2db --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoEventService.java @@ -0,0 +1,36 @@ +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); + + /// 查询启用的登记事件 + /// + /// @return 登记事件列表 + List listValid(); + + /// 查询登记事件要求的角色 + /// + /// @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/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/implement/GaoCustomerServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java index 471526e..f268ac7 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java @@ -4,54 +4,39 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.service.AttachmentService; -import com.imyeyu.api.modules.common.vo.attach.BizUpdateReq; import com.imyeyu.api.modules.gao.entity.GaoCustomer; import com.imyeyu.api.modules.gao.mapper.GaoCustomerMapper; import com.imyeyu.api.modules.gao.service.GaoCustomerService; -import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStatView; +import com.imyeyu.api.modules.gao.util.CustomerXSSFBuilder; +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.api.modules.system.entity.Logger; import com.imyeyu.api.modules.system.service.LoggerService; 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.TimiCode; import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.spring.TimiSpring; import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.service.AbstractEntityService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.poi.ss.usermodel.CellStyle; -import org.apache.poi.ss.usermodel.Font; -import org.apache.poi.ss.usermodel.Row; -import org.apache.poi.ss.usermodel.Sheet; -import org.apache.poi.ss.usermodel.Workbook; -import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.time.Instant; import java.time.LocalDate; -import java.time.Period; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; -/// /// 客户服务实现 /// /// @author Codex @@ -62,14 +47,16 @@ import java.util.stream.Collectors; public class GaoCustomerServiceImplement extends AbstractEntityService implements GaoCustomerService { private static final DateTimeFormatter DAY_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd"); - private static final DateTimeFormatter EXPORT_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault()); - private final GaoCustomerMapper mapper; private final ObjectMapper jackson; - private final AttachmentService attachmentService; + private final LoggerService loggerService; private final UserLoginService userLoginService; - private final UserService userService; + private final AttachmentService attachmentService; + + private final GaoCustomerMapper mapper; + + private final CustomerXSSFBuilder customerXSSFBuilder; @Override protected BaseMapper mapper() { @@ -77,26 +64,41 @@ public class GaoCustomerServiceImplement extends AbstractEntityService page(Page page) { - PageResult result = super.page(page); - loadListTransient(result.getList()); - return result; - } + public void create(GaoCustomer customer) { + TimiException.required(customer.getName(), "not found customer.name"); - @Override - public void create(GaoCustomer entity) { Logger logger = new Logger(Logger.Module.GAO, "GAO_CUSTOMER_CREATE"); try { - logger.setContent(jackson.writeValueAsString(entity)); - checkEntity(entity, false); - entity.setCreatorUserId(userLoginService.getRequireLoginUserId()); - entity.setInvitedCount(0L); - super.create(entity); - saveAttachment(entity.getId(), resolveAttachmentList(entity)); - changeInvitedCount(entity.getIntroducerCustomerId(), 1L); - loadDetailTransient(entity); + logger.setContent(jackson.writeValueAsString(customer)); + { + GaoCustomer byName = getByName(customer.getName()); + TimiException.requiredNull(byName, "该姓名客户已登记"); + } + if (TimiJava.isNotEmpty(customer.getCode())) { + GaoCustomer byCode = getByCode(customer.getCode()); + TimiException.requiredNull(byCode, "该编码客户已登记"); + } + if (TimiJava.isNotEmpty(customer.getIntroducerCustomerId())) { + TimiException.requiredTrue(!customer.getIntroducerCustomerId().equals(customer.getId()), "customer.introducerCustomerId invalid"); + TimiException.required(super.get(customer.getIntroducerCustomerId()), "not found introducer customer"); + } + + customer.setBirthdateCalendar(TimiJava.defaultIfNull(customer.getBirthdateCalendar(), GaoCustomer.BirthdateCalendar.SOLAR)); + if (TimiJava.isNotEmpty(customer.getBirthdate())) { + customer.setAge(ChronoUnit.YEARS.between(customer.getBirthdate(), LocalDate.now())); + } + customer.setInvitedCount(0L); + if (customer.hasIntroducer()) { + customer.setInvitedCount(1L); + } + customer.setCreatorUserId(userLoginService.getRequireLoginUserId()); + + super.create(customer); + // 附件 + attachmentService.updateByBizId(Attachment.BizType.GAO_CUSTOMER, customer.getId(), customer.getAttachmentList()); + logger.setLevel(Logger.Level.INFO); - logger.setResult(entity.getId()); + logger.setResult(customer.getId()); } catch (TimiException e) { logger.setLevel(Logger.Level.WARN); logger.setException(e.getMessage()); @@ -112,23 +114,44 @@ public class GaoCustomerServiceImplement extends AbstractEntityService search(GaoCustomerSearchReq req) { - req = resolveSearchReq(req); - List list = mapper.search(req.getKeyword(), req.getIntroducerCustomerId()); - loadListTransient(list); - return list; + public List listByIdList(Collection idList) { + return mapper.selectByIdList(idList); } @Override - public PageResult searchPage(Page page) { - TimiException.required(page, "not found page"); - TimiException.required(page.getIndex(), "not found page.index"); - TimiException.required(page.getSize(), "not found page.size"); - TimiException.requiredTrue(0 <= page.getIndex(), "page.index lt 0"); - TimiException.requiredTrue(0 < page.getSize(), "page.size lte 0"); - GaoCustomerSearchReq req = resolveSearchReq(page.getEqualsExample()); - Long total = TimiJava.defaultIfNull(mapper.countSearch(req.getKeyword(), req.getIntroducerCustomerId()), 0L); - List list = mapper.searchPage(req.getKeyword(), req.getIntroducerCustomerId(), page.getIndex() * page.getSize(), page.getSize()); - loadListTransient(list); - PageResult result = new PageResult<>(); - result.setTotal(total); - result.setList(list); - return result; - } - - @Override - public byte[] exportSearchExcel(GaoCustomerSearchReq req) throws IOException { - List list = search(req); - loadIntroducerCustomer(list); - try (Workbook workbook = new XSSFWorkbook(); ByteArrayOutputStream output = new ByteArrayOutputStream()) { - Sheet sheet = workbook.createSheet("客户列表"); - CellStyle titleStyle = workbook.createCellStyle(); - Font titleFont = workbook.createFont(); - titleFont.setBold(true); - titleFont.setFontHeightInPoints((short) 14); - titleStyle.setFont(titleFont); - CellStyle headerStyle = workbook.createCellStyle(); - Font headerFont = workbook.createFont(); - headerFont.setBold(true); - headerStyle.setFont(headerFont); - - appendRow(sheet, 0, titleStyle, "客户列表导出"); - appendRow(sheet, 1, null, "生成时间", EXPORT_TIME_FORMATTER.format(Instant.now())); - appendRow(sheet, 2, null, "关键词", req == null || req.getKeyword() == null || req.getKeyword().isBlank() ? "全部" : req.getKeyword().trim()); - appendRow(sheet, 3, null, "记录总数", String.valueOf(list.size())); - appendRow(sheet, 5, headerStyle, "客户编码", "姓名", "性别", "出生日期(公历)", "出生日期历法", "年龄", "电话", "地址", "病症", "备注", "邀请人", "邀请客户数", "创建时间"); - int rowIndex = 6; + public byte[] export(Page page) throws IOException { + page.setIndex(0); + page.setSize(Long.MAX_VALUE); + List list = mapper.selectByPage(page); + { + List idList = list.stream().map(GaoCustomer::getIntroducerCustomerId).filter(TimiJava::isNotEmpty).distinct().toList(); + Map map = mapByIdList(idList); for (GaoCustomer customer : list) { - appendRow(sheet, rowIndex, null, - text(customer.getCode()), - text(customer.getName()), - formatGender(customer), - text(customer.getBirthdate()), - formatBirthdateCalendar(customer), - customer.getAge() == null ? "" : String.valueOf(customer.getAge()), - text(customer.getTelephone()), - text(customer.getAddress()), - text(customer.getDisease()), - text(customer.getRemark()), - customer.getIntroducerCustomer() == null ? "" : text(customer.getIntroducerCustomer().getName()), - customer.getInvitedCount() == null ? "0" : String.valueOf(customer.getInvitedCount()), - formatExportTime(customer.getCreatedAt()) - ); - rowIndex++; + customer.setIntroducerCustomer(map.get(customer.getIntroducerCustomerId())); } - for (int index = 0; index < 13; index++) { - sheet.autoSizeColumn(index); - sheet.setColumnWidth(index, Math.min(Math.max(sheet.getColumnWidth(index), 10 * 256), 36 * 256)); - } - workbook.write(output); - return output.toByteArray(); } + return customerXSSFBuilder.build(list); } @Override public List listInvited(String introducerCustomerId) { TimiException.required(introducerCustomerId, "not found introducerCustomerId"); - List list = mapper.selectInvitedList(introducerCustomerId); - loadListTransient(list); - return list; + GaoCustomer example = new GaoCustomer(); + example.setIntroducerCustomerId(introducerCustomerId); + return mapper.selectAllByExample(example); } @Transactional(TimiServerDBConfig.ROLLBACKER) @Override - public void saveInviteRelation(GaoCustomerInviteUpdateReq req) { + public void saveInviteRelation(String introducerCustomerId, String customerId) { + TimiException.required(customerId, "not found customerId"); + TimiException.required(introducerCustomerId, "not found introducerCustomerId"); + Logger logger = new Logger(Logger.Module.GAO, "GAO_CUSTOMER_SAVE_INVITE_RELATION"); try { - logger.setContent(jackson.writeValueAsString(req)); - TimiException.required(req, "not found invite req"); - TimiException.required(req.getCustomerId(), "not found invite req.customerId"); - GaoCustomer customer = super.get(req.getCustomerId()); - String oldIntroducerCustomerId = customer.getIntroducerCustomerId(); - customer.setIntroducerCustomerId(req.getIntroducerCustomerId()); - checkEntity(customer, true); - super.update(customer); - syncIntroducerInvitedCount(oldIntroducerCustomerId, customer.getIntroducerCustomerId()); + logger.setContent("introducerCustomerId: %s, customerId: %s".formatted(introducerCustomerId, customerId)); + GaoCustomer customer = get(customerId); + TimiException.required(customer, "not found customer"); + GaoCustomer introducerCustomer = get(introducerCustomerId); + TimiException.required(introducerCustomer, "not found introducerCustomer"); + if (introducerCustomer.getIntroducerCustomerId().equals(customer.getIntroducerCustomerId())) { + throw new TimiException(TimiCode.ARG_BAD, "customer can not upper be introducer customer"); + } + // 存在旧的邀请来源 + if (TimiJava.isNotEmpty(customer.getIntroducerCustomerId())) { + if (customer.getIntroducerCustomerId().equals(introducerCustomerId)) { + return; + } + changeInvitedCount(customer.getIntroducerCustomerId(), -1); + } + customer.setIntroducerCustomerId(introducerCustomerId); + mapper.update(customer); + changeInvitedCount(introducerCustomerId, +1); logger.setLevel(Logger.Level.INFO); - logger.setResult(req.getCustomerId()); } catch (TimiException e) { logger.setLevel(Logger.Level.WARN); logger.setException(e.getMessage()); @@ -321,15 +279,16 @@ public class GaoCustomerServiceImplement extends AbstractEntityService statDailyTrend(GaoCustomerTrendStatReq req) { + public List stateDailyTrend(GaoCustomerTrendStateReq req) { TimiException.required(req, "not found req"); TimiException.required(req.getBeginCreatedAt(), "not found req.beginCreatedAt"); TimiException.required(req.getEndCreatedAt(), "not found req.endCreatedAt"); @@ -355,20 +314,23 @@ public class GaoCustomerServiceImplement extends AbstractEntityService newCustomerCountMap = new HashMap<>(); - for (GaoCustomerDailyStatView view : mapper.statDailyNewCustomer(beginCreatedAt, endCreatedAt)) { + for (GaoCustomerDailyStateView view : mapper.stateDailyNewCustomer(beginCreatedAt, endCreatedAt)) { newCustomerCountMap.put(view.getDateValue(), TimiJava.defaultIfNull(view.getNewCustomerCount(), 0L)); } long totalCustomerCount = TimiJava.defaultIfNull(mapper.countCreatedBefore(beginCreatedAt), 0L); - List list = new ArrayList<>(); + List list = new ArrayList<>(); for (LocalDate date = beginDate; !date.isAfter(endDate); date = date.plusDays(1)) { int dateValue = Integer.parseInt(DAY_FORMATTER.format(date)); long newCustomerCount = TimiJava.defaultIfNull(newCustomerCountMap.get(dateValue), 0L); totalCustomerCount += newCustomerCount; - GaoCustomerDailyStatView view = new GaoCustomerDailyStatView(); + GaoCustomerDailyStateView view = new GaoCustomerDailyStateView(); view.setDateValue(dateValue); view.setNewCustomerCount(newCustomerCount); view.setTotalCustomerCount(totalCustomerCount); @@ -378,219 +340,16 @@ public class GaoCustomerServiceImplement extends AbstractEntityService statGender() { - return mapper.statGender(); - } - - private void checkDailyStatRange(LocalDate beginDate, LocalDate endDate) { - long days = ChronoUnit.DAYS.between(beginDate, endDate) + 1; - TimiException.requiredTrue(days <= 366, "daily stat range too large"); - } - - private GaoCustomerSearchReq resolveSearchReq(GaoCustomerSearchReq req) { - return req == null ? new GaoCustomerSearchReq() : req; - } - - private GaoCustomer checkEntity(GaoCustomer entity, boolean requireId) { - TimiException.required(entity, "not found customer"); - GaoCustomer dbCustomer = null; - entity.setCode(normalizeText(entity.getCode())); - entity.setIntroducerCustomerId(normalizeText(entity.getIntroducerCustomerId())); - if (entity.getBirthdateCalendar() == null) { - entity.setBirthdateCalendar(GaoCustomer.BirthdateCalendar.SOLAR); - } - refreshAge(entity); - if (requireId) { - TimiException.required(entity.getId(), "not found customer.id"); - dbCustomer = super.get(entity.getId()); - entity.setCreatorUserId(dbCustomer.getCreatorUserId()); - } - TimiException.required(entity.getName(), "not found customer.name"); - entity.setName(entity.getName().trim()); - TimiException.requiredTrue(!entity.getName().isEmpty(), "not found customer.name"); - GaoCustomer duplicateCustomer = mapper.selectByName(entity.getName()); - if (duplicateCustomer != null && !duplicateCustomer.getId().equals(entity.getId())) { - throw new TimiException(TimiCode.ARG_BAD, "客户姓名已存在"); - } - if (TimiJava.isNotEmpty(entity.getCode())) { - duplicateCustomer = mapper.selectByCode(entity.getCode()); - if (duplicateCustomer != null && !duplicateCustomer.getId().equals(entity.getId())) { - throw new TimiException(TimiCode.ARG_BAD, "客户编码已存在"); - } - } - if (TimiJava.isNotEmpty(entity.getIntroducerCustomerId())) { - TimiException.requiredTrue(!entity.getIntroducerCustomerId().equals(entity.getId()), "customer.introducerCustomerId invalid"); - TimiException.required(super.get(entity.getIntroducerCustomerId()), "not found introducer customer"); - } - return dbCustomer; - } - - private void loadDetailTransient(GaoCustomer customer) { - if (customer == null) { - return; - } - refreshAge(customer); - loadIntroducerCustomer(List.of(customer)); - if (TimiJava.isNotEmpty(customer.getCreatorUserId())) { - customer.setCreatorUser(userService.get(customer.getCreatorUserId())); - } - List detailCustomerList = new ArrayList<>(); - detailCustomerList.add(customer); - if (customer.getIntroducerCustomer() != null) { - detailCustomerList.add(customer.getIntroducerCustomer()); - } - loadAttachmentList(detailCustomerList); - } - - private void loadListTransient(List customerList) { - if (customerList != null) { - customerList.forEach(this::refreshAge); - } - loadAttachmentList(customerList); - } - - /// 根据出生日期计算当前周岁。出生日期为空时保留旧数据中的手工年龄。 - private void refreshAge(GaoCustomer customer) { - if (customer.getBirthdate() == null) { - return; - } - LocalDate today = LocalDate.now(); - TimiException.requiredTrue(!customer.getBirthdate().isAfter(today), "customer.birthdate invalid"); - customer.setAge(Period.between(customer.getBirthdate(), today).getYears()); - } - - private void loadIntroducerCustomer(List customerList) { - if (customerList == null || customerList.isEmpty()) { - return; - } - List introducerCustomerIdList = customerList.stream() - .map(GaoCustomer::getIntroducerCustomerId) - .filter(TimiJava::isNotEmpty) - .distinct() - .toList(); - if (introducerCustomerIdList.isEmpty()) { - return; - } - Map introducerCustomerMap = mapper.selectByIdList(new ArrayList<>(introducerCustomerIdList)).stream() - .collect(Collectors.toMap(GaoCustomer::getId, item -> item, (left, right) -> left)); - for (GaoCustomer customer : customerList) { - customer.setIntroducerCustomer(introducerCustomerMap.get(customer.getIntroducerCustomerId())); - } - } - - private void loadAttachmentList(List customerList) { - if (customerList == null || customerList.isEmpty()) { - return; - } - List customerIdList = customerList.stream() - .map(GaoCustomer::getId) - .filter(TimiJava::isNotEmpty) - .distinct() - .toList(); - Map> attachmentMap = attachmentService.mapByBizIdList(Attachment.BizType.GAO_CUSTOMER, customerIdList, GaoCustomer.AttachType.PHOTO.name()); - for (GaoCustomer customer : customerList) { - customer.setAttachmentList(attachmentMap.getOrDefault(customer.getId(), List.of())); - } - } - - private void syncIntroducerInvitedCount(String oldIntroducerCustomerId, String newIntroducerCustomerId) { - String normalizedOldIntroducerCustomerId = normalizeText(oldIntroducerCustomerId); - String normalizedNewIntroducerCustomerId = normalizeText(newIntroducerCustomerId); - if (Objects.equals(normalizedOldIntroducerCustomerId, normalizedNewIntroducerCustomerId)) { - return; - } - changeInvitedCount(normalizedOldIntroducerCustomerId, -1L); - changeInvitedCount(normalizedNewIntroducerCustomerId, 1L); + public List stateGender() { + return mapper.stateGender(); } private void changeInvitedCount(String customerId, long delta) { - String normalizedCustomerId = normalizeText(customerId); - if (normalizedCustomerId == null || delta == 0) { + if (delta == 0) { return; } - mapper.updateInvitedCountDelta(normalizedCustomerId, delta); - } - - private String normalizeText(String value) { - if (value == null) { - return null; - } - String trimmedValue = value.trim(); - return trimmedValue.isEmpty() ? null : trimmedValue; - } - - private void saveAttachment(String customerId, List attachmentList) { - if (attachmentList == null) { - return; - } - for (Attachment attachment : attachmentList) { - attachment.setBizType(Attachment.BizType.GAO_CUSTOMER); - attachment.setBizId(customerId); - attachment.setAttachType(GaoCustomer.AttachType.PHOTO.name()); - } - BizUpdateReq req = new BizUpdateReq(); - req.setBizType(Attachment.BizType.GAO_CUSTOMER); - req.setBizId(customerId); - req.setItems(attachmentList); - attachmentService.updateByBiz(req); - } - - private List resolveAttachmentList(GaoCustomer customer) { - if (customer.getAttachmentIdList() != null) { - return buildPhotoAttachmentList(customer.getAttachmentIdList()); - } - return customer.getAttachmentList(); - } - - private List buildPhotoAttachmentList(List attachmentIdList) { - List list = new ArrayList<>(); - for (String attachmentId : attachmentIdList) { - if (attachmentId == null || attachmentId.isBlank()) { - continue; - } - Attachment attachment = new Attachment(); - attachment.setTempFileId(attachmentId.trim()); - attachment.setAttachType(GaoCustomer.AttachType.PHOTO.name()); - list.add(attachment); - } - return list; - } - - private void appendRow(Sheet sheet, int rowIndex, CellStyle style, String... values) { - Row row = sheet.createRow(rowIndex); - for (int index = 0; index < values.length; index++) { - row.createCell(index).setCellValue(values[index]); - if (style != null) { - row.getCell(index).setCellStyle(style); - } - } - } - - private String formatGender(GaoCustomer customer) { - if (customer.getGender() == null) { - return ""; - } - return switch (customer.getGender()) { - case MALE -> "男"; - case FEMALE -> "女"; - }; - } - - private String formatBirthdateCalendar(GaoCustomer customer) { - if (customer.getBirthdateCalendar() == null) { - return "公历"; - } - return customer.getBirthdateCalendar() == GaoCustomer.BirthdateCalendar.LUNAR ? "农历" : "公历"; - } - - private String formatExportTime(Long value) { - if (value == null) { - return ""; - } - return EXPORT_TIME_FORMATTER.format(Instant.ofEpochMilli(value)); - } - - private String text(Object value) { - return value == null ? "" : value.toString(); + GaoCustomer customer = get(customerId); + customer.setInvitedCount(customer.getInvitedCount() + delta); + mapper.update(customer); } } diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoEventRecordServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoEventRecordServiceImplement.java new file mode 100644 index 0000000..5f15c5c --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoEventRecordServiceImplement.java @@ -0,0 +1,252 @@ +package com.imyeyu.api.modules.gao.service.implement; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.config.dbsource.TimiServerDBConfig; +import com.imyeyu.api.modules.common.entity.Attachment; +import com.imyeyu.api.modules.common.service.AttachmentService; +import com.imyeyu.api.modules.gao.entity.GaoEvent; +import com.imyeyu.api.modules.gao.entity.GaoEventRecord; +import com.imyeyu.api.modules.gao.mapper.GaoEventRecordMapper; +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.util.EventRecordXSSFBuilder; +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.system.entity.Logger; +import com.imyeyu.api.modules.system.service.LoggerService; +import com.imyeyu.api.modules.user.service.RoleChecker; +import com.imyeyu.api.modules.user.service.UserLoginService; +import com.imyeyu.java.TimiJava; +import com.imyeyu.java.bean.timi.TimiCode; +import com.imyeyu.java.bean.timi.TimiException; +import com.imyeyu.spring.bean.PageResult; +import com.imyeyu.spring.mapper.BaseMapper; +import com.imyeyu.spring.service.AbstractEntityService; +import com.imyeyu.utils.Time; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.IOException; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAdjusters; +import java.util.ArrayList; +import java.util.List; + +/// 登记事件记录服务实现 +/// +/// @author 夜雨 +/// @since 2026-07-27 14:11 +@Slf4j +@Service +@RequiredArgsConstructor +public class GaoEventRecordServiceImplement extends AbstractEntityService implements GaoEventRecordService { + + private final ObjectMapper jackson; + private final RoleChecker roleChecker; + private final LoggerService loggerService; + private final GaoEventService eventService; + private final UserLoginService userLoginService; + private final AttachmentService attachmentService; + private final GaoCustomerService customerService; + + private final GaoEventRecordMapper mapper; + + private final EventRecordXSSFBuilder xssfBuilder; + + @Override + protected BaseMapper mapper() { + return mapper; + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void create(GaoEventRecord record) { + TimiException.required(record, "not found record"); + + Logger logger = new Logger(Logger.Module.GAO, "GAO_EVENT_RECORD_BATCH_CREATE"); + try { + logger.setContent(jackson.writeValueAsString(record)); + + String loginUserId = userLoginService.getRequireLoginUserId(); + Long registeredAt = TimiJava.defaultIfNull(record.getRegisteredAt(), Time.now()); + GaoEvent event = eventService.get(record.getEventId()); + // 检查权限 + checkEventPermission(event); + + List customerIdList = TimiJava.defaultIfEmpty(record.getCustomerIdList(), List.of(record.getCustomerId())); + if (TimiJava.isNotEmpty(record.getCustomer())) { + // 客户未创建,补录客户后登记 + customerService.create(record.getCustomer()); + customerIdList.add(record.getCustomer().getId()); + } + TimiException.required(customerIdList, "not found customerId or empty customerIdList"); + + GaoEventRecord firstRecord = null; + List logIdList = new ArrayList<>(); + for (int i = 0; i < customerIdList.size(); i++) { + String customerId = customerIdList.get(i); + // 检查事件可登记 + checkEventCreatable(customerId, event, registeredAt); + + GaoEventRecord dbRecord = new GaoEventRecord(); + dbRecord.setCustomerId(customerId); + dbRecord.setEventId(record.getEventId()); + dbRecord.setOperatorUserId(loginUserId); + dbRecord.setRemark(record.getRemark()); + dbRecord.setRegisteredAt(registeredAt); + super.create(dbRecord); + // 附件 + if (i == 0) { + // 第一个记录储存附件 + firstRecord = dbRecord; + attachmentService.updateByBizId(Attachment.BizType.GAO_EVENT_RECORD, dbRecord.getId(), record.getAttachmentList()); + } else { + // 其他记录克隆第一个记录附件 + List attachmentList = attachmentService.listByBizId(Attachment.BizType.GAO_EVENT_RECORD, firstRecord.getId()); + for (Attachment attachment : attachmentList) { + Attachment clone = attachmentService.clone(attachment.getId()); + clone.setId(dbRecord.getId()); + attachmentService.update(clone); + } + } + logIdList.add(dbRecord.getId()); + } + logger.setLevel(Logger.Level.INFO); + logger.setResult(String.join(",", logIdList)); + } catch (TimiException e) { + logger.setLevel(Logger.Level.WARN); + logger.setException(e.getMessage()); + throw e; + } catch (Exception e) { + logger.setLevel(Logger.Level.ERROR); + logger.setException(TimiJava.serializeThrowable(e)); + log.error("create event record error", e); + throw new TimiException(TimiCode.ERROR, "create event record error", e); + } finally { + loggerService.create(logger); + } + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void update(GaoEventRecord record) { + TimiException.required(record, "not found record"); + TimiException.required(record.getId(), "not found record.id"); + + Logger logger = new Logger(Logger.Module.GAO, "GAO_EVENT_RECORD_UPDATE"); + try { + logger.setContent(jackson.writeValueAsString(record)); + TimiException.required(record.getCustomerId(), "not found record.customerId"); + TimiException.required(record.getEventId(), "not found record.eventId"); + + record.setRegisteredAt(TimiJava.defaultIfNull(record.getRegisteredAt(), Time.now())); + + GaoEvent event = eventService.get(record.getEventId()); + checkEventPermission(event); + checkEventCreatable(record.getCustomerId(), event, record.getRegisteredAt()); + + super.update(record); + attachmentService.updateByBizId(Attachment.BizType.GAO_EVENT_RECORD, record.getId(), record.getAttachmentList()); + logger.setLevel(Logger.Level.INFO); + logger.setResult(record.getId()); + } catch (TimiException e) { + logger.setLevel(Logger.Level.WARN); + logger.setException(e.getMessage()); + throw e; + } catch (Exception e) { + logger.setLevel(Logger.Level.ERROR); + logger.setException(TimiJava.serializeThrowable(e)); + log.error("修改 GAO 登记记录失败", e); + throw new TimiException(TimiCode.ERROR, "修改 GAO 登记记录失败", e); + } finally { + loggerService.create(logger); + } + } + + @Override + public PageResult pageByRange(GaoEventRecordPage page) { + PageResult result = new PageResult<>(); + result.setTotal(mapper.countByRange(page)); + result.setList(mapper.selectByRange(page)); + return result; + } + + @Override + public byte[] export(GaoEventRecordPage page) throws IOException { + List list = mapper.selectByRange(page); + return xssfBuilder.build(list); + } + + @Override + public PageResult pageRankByRange(GaoEventRecordRankPage page) { + PageResult result = new PageResult<>(); + result.setTotal(mapper.countRankByRange(page)); + result.setList(mapper.selectRankByRange(page)); + return result; + } + + private void checkEventPermission(GaoEvent event) { + List roleCodeList = eventService.listRoleCode(event.getId()); + TimiException.requiredTrue(roleCodeList != null && !roleCodeList.isEmpty(), "登记事件未配置可登记角色"); + roleChecker.checkAny(ModuleCode.GAO, roleCodeList.toArray(String[]::new)); + } + + private void checkEventCreatable(String customerId, GaoEvent event, long registeredAt) { + TimiException.requiredTrue(event.getStatus() == null || event.getStatus() == GaoEvent.Status.ACTIVE, "登记事件不可登记"); + TimiException.requiredTrue(event.getBeginAt() == null || event.getBeginAt() <= registeredAt, "登记事件未到可登记时间"); + TimiException.requiredTrue(event.getEndAt() == null || registeredAt <= event.getEndAt(), "登记事件已过可登记时间"); + GaoEvent.LimitType limitType = TimiJava.defaultIfNull(event.getLimitType(), GaoEvent.LimitType.NONE); + switch (limitType) { + case DAILY_HALF_DAY -> { + long today = Time.today(registeredAt); + long noon = today + Time.D / 2; + long beginAt = today, endAt; + if (today + registeredAt < noon) { + // 上半日 + endAt = noon; + } else { + // 下半日 + beginAt = noon; + endAt = today + Time.D; + } + Long count = mapper.count(customerId, event.getId(), beginAt, endAt); + TimiException.requiredTrue(TimiJava.defaultIfNull(count, 0L) < 1, "该客户当前半日已登记该事件"); + } + case NATURAL_DAY -> { + long beginAt = Time.today(registeredAt); + long endAt = beginAt + Time.D; + Long count = mapper.count(customerId, event.getId(), beginAt, endAt); + TimiException.requiredTrue(TimiJava.defaultIfNull(count, 0L) < 1, "该客户当前半日已登记该事件"); + } + case INTERVAL_HOUR -> { + long beginAt = registeredAt - event.getLimitValue() * Time.H; + Long count = mapper.count(customerId, event.getId(), beginAt, registeredAt); + TimiException.requiredTrue(TimiJava.defaultIfNull(count, 0L) < 1, "该客户登记该事件间隔不足"); + } + case NATURAL_MONTH -> { + ZoneId zone = ZoneId.systemDefault(); + ZonedDateTime zdt = Instant.ofEpochMilli(registeredAt).atZone(zone); + long beginAt = zdt.with(TemporalAdjusters.firstDayOfMonth()).truncatedTo(ChronoUnit.DAYS).toInstant().toEpochMilli(); + long endAt = zdt.with(TemporalAdjusters.firstDayOfNextMonth()).truncatedTo(ChronoUnit.DAYS).toInstant().toEpochMilli(); + Long count = mapper.count(customerId, event.getId(), beginAt, endAt); + TimiException.requiredTrue(TimiJava.defaultIfNull(count, 0L) < 1, "该客户当月已登记该事件"); + } + case TOTAL_LIMIT -> { + Long count = mapper.count(null, event.getId(), null, null); + TimiException.requiredTrue(TimiJava.defaultIfNull(count, 0L) < event.getLimitValue(), "登记事件总次数已达上限"); + } + case CUSTOMER_TOTAL_LIMIT -> { + Long count = mapper.count(customerId, event.getId(), null, null); + TimiException.requiredTrue(TimiJava.defaultIfNull(count, 0L) < event.getLimitValue(), "该客户登记该事件次数已达上限"); + } + } + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoEventServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoEventServiceImplement.java new file mode 100644 index 0000000..6f42c65 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoEventServiceImplement.java @@ -0,0 +1,215 @@ +package com.imyeyu.api.modules.gao.service.implement; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.imyeyu.api.config.dbsource.TimiServerDBConfig; +import com.imyeyu.api.modules.gao.entity.GaoEvent; +import com.imyeyu.api.modules.gao.entity.GaoEventRoleRelation; +import com.imyeyu.api.modules.gao.mapper.GaoEventMapper; +import com.imyeyu.api.modules.gao.mapper.GaoEventRoleRelationMapper; +import com.imyeyu.api.modules.gao.service.GaoEventService; +import com.imyeyu.api.modules.system.entity.Logger; +import com.imyeyu.api.modules.system.service.LoggerService; +import com.imyeyu.java.TimiJava; +import com.imyeyu.java.bean.timi.TimiCode; +import com.imyeyu.java.bean.timi.TimiException; +import com.imyeyu.spring.mapper.BaseMapper; +import com.imyeyu.spring.service.AbstractEntityService; +import com.imyeyu.utils.Time; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +/// +/// 登记事件服务实现 +/// +/// @author 夜雨 +/// @since 2026-07-27 09:00 +@Slf4j +@Service +@RequiredArgsConstructor +public class GaoEventServiceImplement extends AbstractEntityService implements GaoEventService { + + private final ObjectMapper jackson; + + private final LoggerService loggerService; + + private final GaoEventMapper mapper; + private final GaoEventRoleRelationMapper roleRelationMapper; + + @Override + protected BaseMapper mapper() { + return mapper; + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void create(GaoEvent event) { + TimiException.required(event, "not found event"); + TimiException.required(event.getName(), "not found event.name"); + + Logger logger = new Logger(Logger.Module.GAO, "GAO_EVENT_CREATE"); + try { + logger.setContent(jackson.writeValueAsString(event)); + + event.setStatus(TimiJava.defaultIfNull(event.getStatus(), GaoEvent.Status.DRAFT)); + event.setRequirePhoto(TimiJava.defaultIfNull(event.getRequirePhoto(), false)); + event.setLimitType(TimiJava.defaultIfNull(event.getLimitType(), GaoEvent.LimitType.NONE)); + if (TimiJava.isNotEmpty(event.getLimitType())) { + TimiException.requiredTrue(event.getLimitType().getMin() < event.getLimitValue(), "约束值最低 %s".formatted(event.getLimitType().getMin())); + } + if (event.getBeginAt() != null || event.getEndAt() != null) { + TimiException.requiredTrue(event.getBeginAt() == null || event.getEndAt() == null || event.getBeginAt() <= event.getEndAt(), "invalid beginAt or endAt"); + } + event.setSort(TimiJava.defaultIfNull(event.getSort(), 0)); + super.create(event); + + saveRoleRelation(event.getId(), event.getRoleCodeList()); + logger.setLevel(Logger.Level.INFO); + logger.setResult(event.getId()); + } catch (TimiException e) { + logger.setLevel(Logger.Level.WARN); + logger.setException(e.getMessage()); + throw e; + } catch (Exception e) { + logger.setLevel(Logger.Level.ERROR); + logger.setException(TimiJava.serializeThrowable(e)); + log.error("create event error", e); + throw new TimiException(TimiCode.ERROR, "create event error", e); + } finally { + loggerService.create(logger); + } + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void update(GaoEvent entity) { + Logger logger = new Logger(Logger.Module.GAO, "GAO_EVENT_UPDATE"); + try { + logger.setContent(jackson.writeValueAsString(entity)); + GaoEvent dbEvent = get(entity.getId()); + dbEvent.setName(entity.getName()); + dbEvent.setDescription(entity.getDescription()); + dbEvent.setColor(entity.getColor()); + dbEvent.setStatus(entity.getStatus()); + dbEvent.setRequirePhoto(entity.getRequirePhoto()); + dbEvent.setLimitType(entity.getLimitType()); + dbEvent.setLimitValue(entity.getLimitValue()); + dbEvent.setBeginAt(entity.getBeginAt()); + dbEvent.setEndAt(entity.getEndAt()); + mapper.update(dbEvent); + saveRoleRelation(entity.getId(), entity.getRoleCodeList()); + logger.setLevel(Logger.Level.INFO); + logger.setResult(entity.getId()); + } catch (TimiException e) { + logger.setLevel(Logger.Level.WARN); + logger.setException(e.getMessage()); + throw e; + } catch (Exception e) { + logger.setLevel(Logger.Level.ERROR); + logger.setException(TimiJava.serializeThrowable(e)); + log.error("update event error", e); + throw new TimiException(TimiCode.ERROR, "update event error", e); + } finally { + loggerService.create(logger); + } + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void delete(String id) { + Logger logger = new Logger(Logger.Module.GAO, "GAO_EVENT_DELETE"); + try { + logger.setContent(id); + GaoEvent event = get(id); + event.setStatus(GaoEvent.Status.DELETED); + super.update(event); + logger.setLevel(Logger.Level.INFO); + logger.setResult(id); + } catch (TimiException e) { + logger.setLevel(Logger.Level.WARN); + logger.setException(e.getMessage()); + throw e; + } catch (Exception e) { + logger.setLevel(Logger.Level.ERROR); + logger.setException(TimiJava.serializeThrowable(e)); + log.error("delete event error", e); + throw new TimiException(TimiCode.ERROR, "delete event error", e); + } finally { + loggerService.create(logger); + } + } + + @Override + public Map mapByIdList(Collection idList) { + return mapper.selectByIdList(new HashSet<>(idList)).stream().collect(Collectors.toMap(GaoEvent::getId, Function.identity())); + } + + @Override + public Map> mapRoleCodeListByEventIdList(Collection eventIdList) { + return roleRelationMapper.selectAllByEventIdList(new HashSet<>(eventIdList)) + .stream() + .collect(Collectors.groupingBy(GaoEventRoleRelation::getEventId, Collectors.mapping(GaoEventRoleRelation::getRoleCode, Collectors.toList()))); + } + + @Override + public List listValid() { + return mapper.selectValidList(Time.now()); + } + + @Override + public List listRoleCode(String eventId) { + GaoEventRoleRelation example = new GaoEventRoleRelation(); + example.setEventId(eventId); + return roleRelationMapper.selectAllByExample(example).stream().map(GaoEventRoleRelation::getRoleCode).toList(); + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void sort(List idList) { + if (TimiJava.isEmpty(idList)) { + return; + } + idList = new ArrayList<>(new LinkedHashSet<>(idList)); + Map eventMap = mapper.selectByIdList(idList).stream().collect(Collectors.toMap(GaoEvent::getId, Function.identity())); + for (int i = 0; i < idList.size(); i++) { + GaoEvent event = eventMap.get(idList.get(i)); + event.setSort(i); + mapper.update(event); + } + } + + private void saveRoleRelation(String eventId, List roleCodeList) { + List dbRoleRelationList = roleRelationMapper.selectAllByEventIdList(List.of(eventId)); + Set roleCodeSet = TimiJava.defaultIfEmpty(roleCodeList, List.of()).stream().filter(TimiJava::isNotEmpty).collect(Collectors.toSet()); + Set retainedRoleCodeSet = new HashSet<>(); + for (GaoEventRoleRelation dbEventRole : dbRoleRelationList) { + String roleCode = dbEventRole.getRoleCode(); + if (roleCode != null) { + roleCode = roleCode.trim(); + } + if (!roleCodeSet.contains(roleCode) || !retainedRoleCodeSet.add(roleCode)) { + roleRelationMapper.delete(dbEventRole.getId()); + } + } + for (String roleCode : roleCodeSet) { + if (retainedRoleCodeSet.contains(roleCode)) { + continue; + } + GaoEventRoleRelation relation = new GaoEventRoleRelation(); + relation.setEventId(eventId); + relation.setRoleCode(roleCode); + roleRelationMapper.insert(relation); + } + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoRegistrationEventRecordServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoRegistrationEventRecordServiceImplement.java deleted file mode 100644 index 0a00508..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoRegistrationEventRecordServiceImplement.java +++ /dev/null @@ -1,761 +0,0 @@ -package com.imyeyu.api.modules.gao.service.implement; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.imyeyu.api.config.dbsource.TimiServerDBConfig; -import com.imyeyu.api.bean.ModuleCode; -import com.imyeyu.api.modules.common.entity.Attachment; -import com.imyeyu.api.modules.common.service.AttachmentService; -import com.imyeyu.api.modules.common.vo.attach.BizUpdateReq; -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.mapper.GaoCustomerMapper; -import com.imyeyu.api.modules.gao.mapper.GaoRegistrationEventMapper; -import com.imyeyu.api.modules.gao.mapper.GaoRegistrationEventRecordMapper; -import com.imyeyu.api.modules.gao.service.GaoCustomerService; -import com.imyeyu.api.modules.gao.service.GaoRegistrationEventService; -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.GaoCustomerRegistrationDayStatView; -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.api.modules.system.entity.Logger; -import com.imyeyu.api.modules.system.service.LoggerService; -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.api.modules.user.mapper.UserMapper; -import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.spring.TimiSpring; -import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.bean.PageResult; -import com.imyeyu.spring.mapper.BaseMapper; -import com.imyeyu.spring.service.AbstractEntityService; -import com.imyeyu.utils.Time; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.apache.poi.ss.usermodel.CellStyle; -import org.apache.poi.ss.usermodel.Font; -import org.apache.poi.ss.usermodel.Row; -import org.apache.poi.ss.usermodel.Sheet; -import org.apache.poi.ss.usermodel.Workbook; -import org.apache.poi.xssf.usermodel.XSSFWorkbook; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.io.ByteArrayOutputStream; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.time.Instant; -import java.time.LocalDate; -import java.time.ZoneId; -import java.time.temporal.ChronoUnit; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.StringJoiner; -import java.util.function.Function; -import java.util.stream.Collectors; - -/// -/// 登记事件记录服务实现 -/// -/// @author Codex -/// @since 2026-07-27 -@Slf4j -@Service -@RequiredArgsConstructor -public class GaoRegistrationEventRecordServiceImplement extends AbstractEntityService implements GaoRegistrationEventRecordService { - - private static final DateTimeFormatter DAY_FORMATTER = DateTimeFormatter.BASIC_ISO_DATE; - private static final DateTimeFormatter EXPORT_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault()); - - private final GaoRegistrationEventRecordMapper mapper; - private final ObjectMapper jackson; - private final GaoCustomerMapper customerMapper; - private final GaoRegistrationEventMapper registrationEventMapper; - private final UserMapper userMapper; - private final AttachmentService attachmentService; - private final LoggerService loggerService; - private final RoleChecker roleChecker; - private final UserLoginService userLoginService; - private final UserService userService; - private final GaoCustomerService customerService; - private final GaoRegistrationEventService registrationEventService; - - @Override - protected BaseMapper mapper() { - return mapper; - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public List createRecords(GaoRegistrationEventRecordCreateReq req) { - Logger logger = new Logger(Logger.Module.GAO, "GAO_REGISTRATION_EVENT_RECORD_BATCH_CREATE"); - try { - TimiException.required(req, "not found req"); - TimiException.requiredTrue(req.getCustomerIdList() != null && !req.getCustomerIdList().isEmpty(), "customerIdList is empty"); - logger.setContent(jackson.writeValueAsString(req)); - List recordList = new ArrayList<>(); - for (int index = 0; index < req.getCustomerIdList().size(); index++) { - String customerId = req.getCustomerIdList().get(index); - GaoRegistrationEventRecordCreateReq customerReq = copyCreateReq(req); - customerReq.setAttachmentIdList(0 == index && canUseSourceTempFileList(req.getAttachmentIdList()) - ? req.getAttachmentIdList() - : duplicateTempFileList(req.getAttachmentIdList())); - recordList.add(doCreateRecord(customerReq, customerId)); - } - logger.setLevel(Logger.Level.INFO); - logger.setResult(recordList.stream().map(GaoRegistrationEventRecord::getId).collect(Collectors.joining(","))); - return recordList; - } catch (TimiException e) { - logger.setLevel(Logger.Level.WARN); - logger.setException(e.getMessage()); - throw e; - } catch (Exception e) { - logger.setLevel(Logger.Level.ERROR); - logger.setException(TimiJava.serializeThrowable(e)); - log.error("批量新增 GAO 登记记录失败", e); - throw new TimiException(TimiCode.ERROR, "批量新增 GAO 登记记录失败", e); - } finally { - loggerService.create(logger); - } - } - - private List duplicateTempFileList(List sourceIdList) { - if (sourceIdList == null) { - return null; - } - List targetIdList = new ArrayList<>(); - for (String sourceId : sourceIdList) { - if (sourceId == null || sourceId.isBlank()) { - continue; - } - Attachment source = attachmentService.get(sourceId.trim()); - TimiException.required(source, "not found attachment"); - TimiException.required(source.getMongoId(), "not found attachment.mongoId"); - Attachment target = new Attachment(); - target.setBizType(Attachment.BizType.TEMP_FILE); - target.setName(source.getName()); - target.setInputStream(new ByteArrayInputStream(attachmentService.readAllByMongoId(source.getMongoId()))); - attachmentService.create(target); - targetIdList.add(target.getId()); - } - return targetIdList; - } - - private boolean canUseSourceTempFileList(List sourceIdList) { - if (sourceIdList == null || sourceIdList.isEmpty()) { - return true; - } - for (String sourceId : sourceIdList) { - if (sourceId == null || sourceId.isBlank()) { - continue; - } - Attachment source = attachmentService.get(sourceId.trim()); - if (source == null || source.getBizType() != Attachment.BizType.TEMP_FILE) { - return false; - } - } - return true; - } - - private GaoRegistrationEventRecordCreateReq copyCreateReq(GaoRegistrationEventRecordCreateReq source) { - GaoRegistrationEventRecordCreateReq target = new GaoRegistrationEventRecordCreateReq(); - target.setRegistrationEventId(source.getRegistrationEventId()); - target.setRemark(source.getRemark()); - target.setRegisteredAt(source.getRegisteredAt()); - return target; - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public GaoRegistrationEventRecord quickRegister(GaoQuickRegistrationEventRecordReq req) { - Logger logger = new Logger(Logger.Module.GAO, "GAO_REGISTRATION_EVENT_RECORD_QUICK_REGISTER"); - try { - logger.setContent(jackson.writeValueAsString(req)); - TimiException.required(req, "not found req"); - TimiException.required(req.getCustomerCode(), "not found req.customerCode"); - TimiException.required(req.getRegistrationEventId(), "not found req.registrationEventId"); - GaoCustomer customer = customerService.getOrQuickCreateByCode(req.getCustomerCode()); - GaoRegistrationEventRecordCreateReq recordReq = new GaoRegistrationEventRecordCreateReq(); - recordReq.setCustomerIdList(List.of(customer.getId())); - recordReq.setRegistrationEventId(req.getRegistrationEventId()); - recordReq.setRemark(req.getRemark()); - recordReq.setAttachmentIdList(req.getAttachmentIdList()); - GaoRegistrationEventRecord record = createRecords(recordReq).get(0); - logger.setLevel(Logger.Level.INFO); - logger.setResult(record.getId()); - return record; - } catch (TimiException e) { - logger.setLevel(Logger.Level.WARN); - logger.setException(e.getMessage()); - throw e; - } catch (Exception e) { - logger.setLevel(Logger.Level.ERROR); - logger.setException(TimiJava.serializeThrowable(e)); - log.error("GAO 快速登记失败", e); - throw new TimiException(TimiCode.ERROR, "GAO 快速登记失败", e); - } finally { - loggerService.create(logger); - } - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void update(GaoRegistrationEventRecord entity) { - Logger logger = new Logger(Logger.Module.GAO, "GAO_REGISTRATION_EVENT_RECORD_UPDATE"); - try { - logger.setContent(jackson.writeValueAsString(entity)); - TimiException.required(entity, "not found record"); - TimiException.required(entity.getId(), "not found record.id"); - TimiException.required(entity.getCustomerId(), "not found record.customerId"); - TimiException.required(entity.getRegistrationEventId(), "not found record.registrationEventId"); - customerService.get(entity.getCustomerId()); - GaoRegistrationEvent registrationEvent = registrationEventService.get(entity.getRegistrationEventId()); - checkRegistrationEventPermission(registrationEvent); - entity.setRegisteredAt(TimiJava.defaultIfNull(entity.getRegisteredAt(), Time.now())); - super.update(entity); - saveAttachment(entity.getId(), resolveAttachmentList(entity)); - logger.setLevel(Logger.Level.INFO); - logger.setResult(entity.getId()); - } catch (TimiException e) { - logger.setLevel(Logger.Level.WARN); - logger.setException(e.getMessage()); - throw e; - } catch (Exception e) { - logger.setLevel(Logger.Level.ERROR); - logger.setException(TimiJava.serializeThrowable(e)); - log.error("修改 GAO 登记记录失败", e); - throw new TimiException(TimiCode.ERROR, "修改 GAO 登记记录失败", e); - } finally { - loggerService.create(logger); - } - } - - @Override - public GaoRegistrationEventRecord get(String id) { - GaoRegistrationEventRecord record = super.get(id); - loadTransient(List.of(record)); - return record; - } - - @Override - public List listByRegistrationEventAndPeriod(GaoRegistrationEventRecordPeriodReq req) { - TimiException.required(req, "not found req"); - TimiException.required(req.getRegistrationEventId(), "not found req.registrationEventId"); - TimiException.required(req.getPeriodType(), "not found req.periodType"); - TimiException.required(req.getPeriodValue(), "not found req.periodValue"); - List list = mapper.selectByRegistrationEventAndPeriod(req.getRegistrationEventId(), req.getPeriodType(), req.getPeriodValue()); - loadTransient(list); - return list; - } - - @Override - public List listByRange(GaoRegistrationEventRecordRangeReq req) { - TimiException.required(req, "not found req"); - checkStatRange(req.getBeginRegisteredAt(), req.getEndRegisteredAt()); - List list = mapper.selectByRange(req); - loadTransient(list); - return list; - } - - @Override - public List listCalendarByRange(GaoRegistrationEventRecordRangeReq req) { - TimiException.required(req, "not found req"); - checkStatRange(req.getBeginRegisteredAt(), req.getEndRegisteredAt()); - return mapper.selectCalendarByRange(req); - } - - @Override - public PageResult pageByRange(Page page) { - TimiException.required(page, "not found page"); - TimiException.required(page.getIndex(), "not found page.index"); - TimiException.required(page.getSize(), "not found page.size"); - TimiException.requiredTrue(0 <= page.getIndex(), "page.index lt 0"); - TimiException.requiredTrue(0 < page.getSize(), "page.size lte 0"); - GaoRegistrationEventRecordRangeReq req = TimiJava.defaultIfNull(page.getEqualsExample(), new GaoRegistrationEventRecordRangeReq()); - checkStatRange(req.getBeginRegisteredAt(), req.getEndRegisteredAt()); - Long total = TimiJava.defaultIfNull(mapper.countByRange(req), 0L); - List list = mapper.selectPageByRange(req, page.getIndex() * page.getSize(), page.getSize()); - PageResult result = new PageResult<>(); - result.setTotal(total); - result.setList(list); - return result; - } - - @Override - public byte[] exportRangeExcel(GaoRegistrationEventRecordRangeReq req) throws IOException { - List list = listByRange(req); - try (Workbook workbook = new XSSFWorkbook(); ByteArrayOutputStream output = new ByteArrayOutputStream()) { - Sheet sheet = workbook.createSheet("登记事件记录"); - CellStyle titleStyle = workbook.createCellStyle(); - Font titleFont = workbook.createFont(); - titleFont.setBold(true); - titleFont.setFontHeightInPoints((short) 14); - titleStyle.setFont(titleFont); - CellStyle headerStyle = workbook.createCellStyle(); - Font headerFont = workbook.createFont(); - headerFont.setBold(true); - headerStyle.setFont(headerFont); - - appendRow(sheet, 0, titleStyle, "登记事件记录导出"); - appendRow(sheet, 1, null, "生成时间", EXPORT_TIME_FORMATTER.format(Instant.now())); - appendRow(sheet, 2, null, "时间范围", "%s ~ %s".formatted(formatExportTime(req.getBeginRegisteredAt()), formatExportTime(req.getEndRegisteredAt()))); - appendRow(sheet, 3, null, "事件筛选", buildEventFilterText(req)); - appendRow(sheet, 4, null, "关键词", req.getKeyword() == null || req.getKeyword().isBlank() ? "全部" : req.getKeyword().trim()); - appendRow(sheet, 5, null, "记录总数", String.valueOf(list.size())); - appendRow(sheet, 7, headerStyle, "客户编码", "客户姓名", "登记事件", "登记时间", "登记人", "备注"); - int rowIndex = 8; - for (GaoRegistrationEventRecord record : list) { - appendRow(sheet, rowIndex, null, - record.getCustomer() == null ? "" : text(record.getCustomer().getCode()), - record.getCustomer() == null ? "" : text(record.getCustomer().getName()), - record.getRegistrationEvent() == null ? "" : text(record.getRegistrationEvent().getName()), - formatExportTime(record.getRegisteredAt()), - getOperatorName(record), - text(record.getRemark()) - ); - rowIndex++; - } - for (int index = 0; index < 6; index++) { - sheet.autoSizeColumn(index); - sheet.setColumnWidth(index, Math.min(Math.max(sheet.getColumnWidth(index), 12 * 256), 40 * 256)); - } - workbook.write(output); - return output.toByteArray(); - } - } - - @Override - public List statByRegistrationEventAndPeriodType(String registrationEventId, GaoRegistrationEventRecordPeriodReq.PeriodType periodType) { - TimiException.required(registrationEventId, "not found registrationEventId"); - TimiException.required(periodType, "not found periodType"); - return mapper.statByRegistrationEventAndPeriodType(registrationEventId, periodType); - } - - @Override - public List listLatestByCustomerIdListAndRegistrationEventId(List customerIdList, String registrationEventId) { - TimiException.required(registrationEventId, "not found registrationEventId"); - if (TimiJava.isEmpty(customerIdList)) { - return Collections.emptyList(); - } - List list = mapper.selectLatestByCustomerIdListAndRegistrationEventId(customerIdList, registrationEventId); - loadTransient(list); - return list; - } - - @Override - public List statByCustomerId(String customerId) { - TimiException.required(customerId, "not found customerId"); - return mapper.statByCustomerId(customerId); - } - - @Override - public List statByCustomerAndRange(GaoRegistrationEventRecordStatReq req) { - TimiException.required(req, "not found req"); - TimiException.required(req.getCustomerId(), "not found req.customerId"); - checkStatRange(req.getBeginRegisteredAt(), req.getEndRegisteredAt()); - return mapper.statByCustomerIdAndRange(req.getCustomerId(), req.getBeginRegisteredAt(), req.getEndRegisteredAt()); - } - - @Override - public List statAllCustomerByRange(GaoRegistrationEventRecordStatReq req) { - TimiException.required(req, "not found req"); - checkStatRange(req.getBeginRegisteredAt(), req.getEndRegisteredAt()); - return mapper.statAllCustomerByRange(req.getBeginRegisteredAt(), req.getEndRegisteredAt()); - } - - @Override - public List listCustomerRegistrationRank(GaoRegistrationEventRecordRangeReq req) { - GaoRegistrationEventRecordRangeReq rangeReq = TimiJava.defaultIfNull(req, new GaoRegistrationEventRecordRangeReq()); - if (rangeReq.getBeginRegisteredAt() != null || rangeReq.getEndRegisteredAt() != null) { - checkStatRange(rangeReq.getBeginRegisteredAt(), rangeReq.getEndRegisteredAt()); - } - List dayStatList = mapper.statCustomerRegistrationDay(rangeReq); - Map> customerDayStatMap = dayStatList.stream() - .collect(Collectors.groupingBy(GaoCustomerRegistrationDayStatView::getCustomerId)); - List rankList = new ArrayList<>(); - for (Map.Entry> entry : customerDayStatMap.entrySet()) { - rankList.add(buildCustomerRegistrationRank(entry.getValue())); - } - rankList.sort(Comparator - .comparing(GaoCustomerRegistrationRankView::getTotalRegistrationDays, Comparator.nullsLast(Comparator.reverseOrder())) - .thenComparing(GaoCustomerRegistrationRankView::getTotalRegistrationCount, Comparator.nullsLast(Comparator.reverseOrder())) - .thenComparing(GaoCustomerRegistrationRankView::getLatestRegisteredAt, Comparator.nullsLast(Comparator.reverseOrder()))); - for (int index = 0; index < rankList.size(); index++) { - rankList.get(index).setRank(index + 1); - } - return rankList; - } - - @Override - public List statDailyByCustomerAndRegistrationEvent(GaoRegistrationEventRecordTrendStatReq req) { - TimiException.required(req, "not found req"); - TimiException.required(req.getCustomerId(), "not found req.customerId"); - customerService.get(req.getCustomerId()); - return buildDailyTrend(req, true); - } - - @Override - public List statDailyByRegistrationEvent(GaoRegistrationEventRecordTrendStatReq req) { - TimiException.required(req, "not found req"); - return buildDailyTrend(req, false); - } - - @Override - public List listLatestByCustomerId(String customerId) { - TimiException.required(customerId, "not found customerId"); - List list = mapper.selectLatestByCustomerId(customerId); - loadTransient(list); - return list; - } - - private String resolveCustomerId(String customerId) { - TimiException.required(customerId, "not found customerId"); - customerService.get(customerId); - return customerId; - } - - private GaoRegistrationEventRecord doCreateRecord(GaoRegistrationEventRecordCreateReq req, String customerId) { - TimiException.required(req, "not found req"); - TimiException.required(req.getRegistrationEventId(), "not found req.registrationEventId"); - customerId = resolveCustomerId(customerId); - GaoRegistrationEvent registrationEvent = registrationEventService.get(req.getRegistrationEventId()); - checkRegistrationEventPermission(registrationEvent); - long registeredAt = TimiJava.defaultIfNull(req.getRegisteredAt(), Time.now()); - checkRegistrationEventCreatable(registrationEvent, customerId, registeredAt); - if (Boolean.TRUE.equals(registrationEvent.getRequirePhoto())) { - TimiException.requiredTrue(req.getAttachmentIdList() != null && !req.getAttachmentIdList().isEmpty(), "required record photo"); - } - - GaoRegistrationEventRecord record = new GaoRegistrationEventRecord(); - record.setCustomerId(customerId); - record.setRegistrationEventId(req.getRegistrationEventId()); - record.setOperatorUserId(userLoginService.getRequireLoginUserId()); - record.setRemark(req.getRemark()); - record.setRegisteredAt(registeredAt); - super.create(record); - saveAttachment(record.getId(), buildPhotoAttachmentList(req.getAttachmentIdList())); - return get(record.getId()); - } - - private void checkRegistrationEventPermission(GaoRegistrationEvent registrationEvent) { - List roleCodeList = registrationEventService.listRoleCode(registrationEvent.getId()); - TimiException.requiredTrue(roleCodeList != null && !roleCodeList.isEmpty(), "登记事件未配置可登记角色"); - roleChecker.checkAny(ModuleCode.GAO, roleCodeList.toArray(String[]::new)); - } - - private void checkRegistrationEventCreatable(GaoRegistrationEvent registrationEvent, String customerId, long registeredAt) { - TimiException.requiredTrue(registrationEvent.getStatus() == null || registrationEvent.getStatus() == GaoRegistrationEvent.Status.ACTIVE, "登记事件不可登记"); - TimiException.requiredTrue(registrationEvent.getBeginAt() == null || registrationEvent.getBeginAt() <= registeredAt, "登记事件未到可登记时间"); - TimiException.requiredTrue(registrationEvent.getEndAt() == null || registeredAt <= registrationEvent.getEndAt(), "登记事件已过可登记时间"); - GaoRegistrationEvent.LimitType limitType = TimiJava.defaultIfNull(registrationEvent.getLimitType(), GaoRegistrationEvent.LimitType.NONE); - switch (limitType) { - case DAILY_HALF_DAY -> checkRegistrationLimitRange(customerId, registrationEvent.getId(), getHalfDayBegin(registeredAt), getHalfDayEnd(registeredAt), "该客户当前半日已登记该事件"); - case NATURAL_DAY -> checkRegistrationLimitRange(customerId, registrationEvent.getId(), getDayBegin(registeredAt), getDayEnd(registeredAt), "该客户当日已登记该事件"); - case INTERVAL_HOUR -> checkIntervalHourRegistrationLimit(registrationEvent, customerId, registeredAt); - case NATURAL_MONTH -> checkRegistrationLimitRange(customerId, registrationEvent.getId(), getMonthBegin(registeredAt), getMonthEnd(registeredAt), "该客户当月已登记该事件"); - case TOTAL_LIMIT -> checkTotalRegistrationLimit(registrationEvent); - case CUSTOMER_TOTAL_LIMIT -> checkCustomerTotalRegistrationLimit(registrationEvent, customerId); - default -> { - } - } - } - - private void checkRegistrationLimitRange(String customerId, String registrationEventId, long beginRegisteredAt, long endRegisteredAt, String message) { - Long count = mapper.countByCustomerIdAndRegistrationEventIdRange(customerId, registrationEventId, beginRegisteredAt, endRegisteredAt); - TimiException.requiredTrue(TimiJava.defaultIfNull(count, 0L) < 1, message); - } - - private void checkIntervalHourRegistrationLimit(GaoRegistrationEvent registrationEvent, String customerId, long registeredAt) { - Integer limitValue = registrationEvent.getLimitValue(); - TimiException.requiredTrue(limitValue != null && 0 < limitValue, "登记事件间隔小时配置错误"); - long beginRegisteredAt = registeredAt - limitValue * 60L * 60L * 1000L + 1; - checkRegistrationLimitRange(customerId, registrationEvent.getId(), beginRegisteredAt, registeredAt, "该客户登记该事件间隔不足"); - } - - private void checkCustomerTotalRegistrationLimit(GaoRegistrationEvent registrationEvent, String customerId) { - Integer limitValue = registrationEvent.getLimitValue(); - TimiException.requiredTrue(limitValue != null && 0 < limitValue, "登记事件单客户总次数配置错误"); - Long count = mapper.countByCustomerIdAndRegistrationEventId(customerId, registrationEvent.getId()); - TimiException.requiredTrue(TimiJava.defaultIfNull(count, 0L) < limitValue, "该客户登记该事件次数已达上限"); - } - - private void checkTotalRegistrationLimit(GaoRegistrationEvent registrationEvent) { - Integer limitValue = registrationEvent.getLimitValue(); - TimiException.requiredTrue(limitValue != null && 0 < limitValue, "登记事件总次数配置错误"); - Long count = mapper.countByRegistrationEventId(registrationEvent.getId()); - TimiException.requiredTrue(TimiJava.defaultIfNull(count, 0L) < limitValue, "登记事件总次数已达上限"); - } - - private long getDayBegin(long registeredAt) { - ZoneId zoneId = ZoneId.systemDefault(); - return Instant.ofEpochMilli(registeredAt).atZone(zoneId).toLocalDate().atStartOfDay(zoneId).toInstant().toEpochMilli(); - } - - private long getDayEnd(long registeredAt) { - ZoneId zoneId = ZoneId.systemDefault(); - return Instant.ofEpochMilli(registeredAt).atZone(zoneId).toLocalDate().plusDays(1).atStartOfDay(zoneId).toInstant().toEpochMilli() - 1; - } - - private long getHalfDayBegin(long registeredAt) { - ZoneId zoneId = ZoneId.systemDefault(); - int hour = Instant.ofEpochMilli(registeredAt).atZone(zoneId).getHour(); - long dayBegin = getDayBegin(registeredAt); - return hour < 12 ? dayBegin : dayBegin + 12L * 60L * 60L * 1000L; - } - - private long getHalfDayEnd(long registeredAt) { - ZoneId zoneId = ZoneId.systemDefault(); - int hour = Instant.ofEpochMilli(registeredAt).atZone(zoneId).getHour(); - long dayBegin = getDayBegin(registeredAt); - return hour < 12 ? dayBegin + 12L * 60L * 60L * 1000L - 1 : getDayEnd(registeredAt); - } - - private long getMonthBegin(long registeredAt) { - ZoneId zoneId = ZoneId.systemDefault(); - LocalDate date = Instant.ofEpochMilli(registeredAt).atZone(zoneId).toLocalDate(); - return date.withDayOfMonth(1).atStartOfDay(zoneId).toInstant().toEpochMilli(); - } - - private long getMonthEnd(long registeredAt) { - ZoneId zoneId = ZoneId.systemDefault(); - LocalDate date = Instant.ofEpochMilli(registeredAt).atZone(zoneId).toLocalDate(); - return date.withDayOfMonth(1).plusMonths(1).atStartOfDay(zoneId).toInstant().toEpochMilli() - 1; - } - - private void checkStatRange(Long beginRegisteredAt, Long endRegisteredAt) { - TimiException.required(beginRegisteredAt, "not found beginRegisteredAt"); - TimiException.required(endRegisteredAt, "not found endRegisteredAt"); - TimiException.requiredTrue(endRegisteredAt >= beginRegisteredAt, "endRegisteredAt lt beginRegisteredAt"); - } - - private List buildDailyTrend(GaoRegistrationEventRecordTrendStatReq req, boolean byCustomer) { - TimiException.required(req.getRegistrationEventId(), "not found req.registrationEventId"); - registrationEventService.get(req.getRegistrationEventId()); - checkStatRange(req.getBeginRegisteredAt(), req.getEndRegisteredAt()); - ZoneId zoneId = ZoneId.systemDefault(); - LocalDate beginDate = Instant.ofEpochMilli(req.getBeginRegisteredAt()).atZone(zoneId).toLocalDate(); - LocalDate endDate = Instant.ofEpochMilli(req.getEndRegisteredAt()).atZone(zoneId).toLocalDate(); - checkDailyStatRange(beginDate, endDate); - long beginRegisteredAt = beginDate.atStartOfDay(zoneId).toInstant().toEpochMilli(); - long endRegisteredAt = endDate.plusDays(1).atStartOfDay(zoneId).toInstant().toEpochMilli() - 1; - Map dailyCountMap = new HashMap<>(); - List dbList = byCustomer - ? mapper.statDailyCountByCustomerIdAndRegistrationEventIdRange(req.getCustomerId(), req.getRegistrationEventId(), beginRegisteredAt, endRegisteredAt) - : mapper.statDailyCountByRegistrationEventIdRange(req.getRegistrationEventId(), beginRegisteredAt, endRegisteredAt); - for (GaoRegistrationEventRecordDailyStatView view : dbList) { - dailyCountMap.put(view.getDateValue(), TimiJava.defaultIfNull(view.getDailyCount(), 0L)); - } - long totalCount = byCustomer - ? TimiJava.defaultIfNull(mapper.countBeforeByCustomerIdAndRegistrationEventId(req.getCustomerId(), req.getRegistrationEventId(), beginRegisteredAt), 0L) - : TimiJava.defaultIfNull(mapper.countBeforeByRegistrationEventId(req.getRegistrationEventId(), beginRegisteredAt), 0L); - List list = new ArrayList<>(); - for (LocalDate date = beginDate; !date.isAfter(endDate); date = date.plusDays(1)) { - int dateValue = Integer.parseInt(DAY_FORMATTER.format(date)); - long dailyCount = TimiJava.defaultIfNull(dailyCountMap.get(dateValue), 0L); - totalCount += dailyCount; - GaoRegistrationEventRecordDailyStatView view = new GaoRegistrationEventRecordDailyStatView(); - view.setDateValue(dateValue); - view.setDailyCount(dailyCount); - view.setTotalCount(totalCount); - list.add(view); - } - return list; - } - - private GaoCustomerRegistrationRankView buildCustomerRegistrationRank(List dayStatList) { - List sortedDayStatList = dayStatList.stream() - .sorted(Comparator.comparing(GaoCustomerRegistrationDayStatView::getDateValue, Comparator.nullsLast(Comparator.reverseOrder()))) - .toList(); - GaoCustomerRegistrationDayStatView first = sortedDayStatList.get(0); - GaoCustomerRegistrationRankView view = new GaoCustomerRegistrationRankView(); - view.setCustomerId(first.getCustomerId()); - view.setCustomerCode(first.getCustomerCode()); - view.setCustomerName(first.getCustomerName()); - view.setCustomerPhotoAttachmentId(first.getCustomerPhotoAttachmentId()); - view.setTotalRegistrationDays((long) sortedDayStatList.size()); - view.setTotalRegistrationCount(sortedDayStatList.stream().mapToLong(item -> TimiJava.defaultIfNull(item.getDailyCount(), 0L)).sum()); - view.setLatestRegisteredAt(sortedDayStatList.stream().map(GaoCustomerRegistrationDayStatView::getLatestRegisteredAt).filter(Objects::nonNull).max(Long::compareTo).orElse(null)); - view.setRecentContinuousRegistrationDays(countRecentContinuousRegistrationDays(sortedDayStatList)); - return view; - } - - private long countRecentContinuousRegistrationDays(List sortedDayStatList) { - if (sortedDayStatList.isEmpty() || sortedDayStatList.get(0).getDateValue() == null) { - return 0; - } - LocalDate expectedDate = LocalDate.parse(String.valueOf(sortedDayStatList.get(0).getDateValue()), DAY_FORMATTER); - long count = 0; - for (GaoCustomerRegistrationDayStatView item : sortedDayStatList) { - if (item.getDateValue() == null) { - continue; - } - LocalDate date = LocalDate.parse(String.valueOf(item.getDateValue()), DAY_FORMATTER); - if (!date.equals(expectedDate)) { - break; - } - count++; - expectedDate = expectedDate.minusDays(1); - } - return count; - } - - private void loadTransient(List recordList) { - if (recordList == null || recordList.isEmpty()) { - return; - } - List customerIdList = recordList.stream() - .map(GaoRegistrationEventRecord::getCustomerId) - .filter(TimiJava::isNotEmpty) - .distinct() - .toList(); - Map customerMap = customerMapper.selectByIdList(new ArrayList<>(customerIdList)).stream() - .collect(Collectors.toMap(GaoCustomer::getId, Function.identity(), (left, right) -> left)); - - List registrationEventIdList = recordList.stream() - .map(GaoRegistrationEventRecord::getRegistrationEventId) - .filter(TimiJava::isNotEmpty) - .distinct() - .toList(); - Map registrationEventMap = registrationEventMapper.selectByIdList(new ArrayList<>(registrationEventIdList)).stream() - .collect(Collectors.toMap(GaoRegistrationEvent::getId, Function.identity(), (left, right) -> left)); - - List operatorUserIdList = recordList.stream() - .map(GaoRegistrationEventRecord::getOperatorUserId) - .filter(TimiJava::isNotEmpty) - .distinct() - .toList(); - Map userMap = userMapper.listByIdList(new ArrayList<>(operatorUserIdList)).stream() - .collect(Collectors.toMap(com.imyeyu.api.modules.user.entity.User::getId, Function.identity(), (left, right) -> left)); - - List recordIdList = recordList.stream() - .map(GaoRegistrationEventRecord::getId) - .filter(TimiJava::isNotEmpty) - .distinct() - .toList(); - Map> attachmentMap = attachmentService.mapByBizIdList( - Attachment.BizType.GAO_REGISTRATION_EVENT_RECORD, - recordIdList, - GaoRegistrationEventRecord.AttachType.PHOTO.name() - ); - - for (GaoRegistrationEventRecord record : recordList) { - record.setCustomer(customerMap.get(record.getCustomerId())); - record.setRegistrationEvent(registrationEventMap.get(record.getRegistrationEventId())); - record.setOperator(userMap.get(record.getOperatorUserId())); - record.setAttachmentList(attachmentMap.getOrDefault(record.getId(), List.of())); - } - } - - private void checkDailyStatRange(LocalDate beginDate, LocalDate endDate) { - long days = ChronoUnit.DAYS.between(beginDate, endDate) + 1; - TimiException.requiredTrue(days <= 366, "daily stat range too large"); - } - - private void saveAttachment(String recordId, List attachmentList) { - if (attachmentList == null) { - return; - } - for (Attachment attachment : attachmentList) { - attachment.setBizType(Attachment.BizType.GAO_REGISTRATION_EVENT_RECORD); - attachment.setBizId(recordId); - attachment.setAttachType(GaoRegistrationEventRecord.AttachType.PHOTO.name()); - } - BizUpdateReq req = new BizUpdateReq(); - req.setBizType(Attachment.BizType.GAO_REGISTRATION_EVENT_RECORD); - req.setBizId(recordId); - req.setItems(attachmentList); - attachmentService.updateByBiz(req); - } - - private List resolveAttachmentList(GaoRegistrationEventRecord record) { - if (record.getAttachmentIdList() != null) { - return buildPhotoAttachmentList(record.getAttachmentIdList()); - } - return record.getAttachmentList(); - } - - private List buildPhotoAttachmentList(List attachmentIdList) { - if (attachmentIdList == null) { - return null; - } - List list = new ArrayList<>(); - for (String attachmentId : attachmentIdList) { - if (attachmentId == null || attachmentId.isBlank()) { - continue; - } - Attachment attachment = new Attachment(); - attachment.setTempFileId(attachmentId.trim()); - attachment.setAttachType(GaoRegistrationEventRecord.AttachType.PHOTO.name()); - list.add(attachment); - } - return list; - } - - private void appendRow(Sheet sheet, int rowIndex, CellStyle style, String... values) { - Row row = sheet.createRow(rowIndex); - for (int index = 0; index < values.length; index++) { - row.createCell(index).setCellValue(values[index]); - if (style != null) { - row.getCell(index).setCellStyle(style); - } - } - } - - private String buildEventFilterText(GaoRegistrationEventRecordRangeReq req) { - if ((req.getRegistrationEventId() == null || req.getRegistrationEventId().isBlank()) && (req.getRegistrationEventIdList() == null || req.getRegistrationEventIdList().isEmpty())) { - return "全部事件"; - } - if (req.getRegistrationEventId() != null && !req.getRegistrationEventId().isBlank()) { - return "事件 ID:" + req.getRegistrationEventId(); - } - StringJoiner joiner = new StringJoiner("、"); - req.getRegistrationEventIdList().stream() - .filter(Objects::nonNull) - .filter(item -> !item.isBlank()) - .forEach(joiner::add); - return "事件 ID:" + joiner; - } - - private String formatExportTime(Long value) { - if (value == null) { - return ""; - } - return EXPORT_TIME_FORMATTER.format(Instant.ofEpochMilli(value)); - } - - private String getOperatorName(GaoRegistrationEventRecord record) { - if (record.getOperator() == null) { - return text(record.getOperatorUserId()); - } - if (record.getOperator().getNick() != null && !record.getOperator().getNick().isBlank()) { - return record.getOperator().getNick(); - } - if (record.getOperator().getName() != null && !record.getOperator().getName().isBlank()) { - return record.getOperator().getName(); - } - return text(record.getOperatorUserId()); - } - - private String text(Object value) { - return value == null ? "" : value.toString(); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoRegistrationEventServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoRegistrationEventServiceImplement.java deleted file mode 100644 index eda704e..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoRegistrationEventServiceImplement.java +++ /dev/null @@ -1,260 +0,0 @@ -package com.imyeyu.api.modules.gao.service.implement; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.imyeyu.api.config.dbsource.TimiServerDBConfig; -import com.imyeyu.api.modules.gao.entity.GaoRegistrationEvent; -import com.imyeyu.api.modules.gao.entity.GaoRegistrationEventRoleRelation; -import com.imyeyu.api.modules.gao.mapper.GaoRegistrationEventMapper; -import com.imyeyu.api.modules.gao.mapper.GaoRegistrationEventRoleRelationMapper; -import com.imyeyu.api.modules.gao.service.GaoRegistrationEventService; -import com.imyeyu.api.modules.system.entity.Logger; -import com.imyeyu.api.modules.system.service.LoggerService; -import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.bean.PageResult; -import com.imyeyu.spring.mapper.BaseMapper; -import com.imyeyu.spring.service.AbstractEntityService; -import com.imyeyu.utils.Time; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -/// -/// 登记事件服务实现 -/// -/// @author 夜雨 -/// @since 2026-07-27 09:00 -@Slf4j -@Service -@RequiredArgsConstructor -public class GaoRegistrationEventServiceImplement extends AbstractEntityService implements GaoRegistrationEventService { - - private final ObjectMapper jackson; - - private final LoggerService loggerService; - - private final GaoRegistrationEventMapper mapper; - private final GaoRegistrationEventRoleRelationMapper roleRelationMapper; - - @Override - protected BaseMapper mapper() { - return mapper; - } - - @Override - public PageResult pageWithRegistrationEventLimit(Page page) { - PageResult result = page(page); - result.setList(result.getList().stream().filter(item -> item.getStatus() != GaoRegistrationEvent.Status.DELETED).toList()); - loadRegistrationEventLimit(result.getList()); - return result; - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void create(GaoRegistrationEvent entity) { - Logger logger = new Logger(Logger.Module.GAO, "GAO_REGISTRATION_EVENT_CREATE"); - try { - logger.setContent(jackson.writeValueAsString(entity)); - checkEntity(entity, false); - super.create(entity); - saveRoleRelation(entity.getId(), entity.getRoleCodeList()); - logger.setLevel(Logger.Level.INFO); - logger.setResult(entity.getId()); - } catch (TimiException e) { - logger.setLevel(Logger.Level.WARN); - logger.setException(e.getMessage()); - throw e; - } catch (Exception e) { - logger.setLevel(Logger.Level.ERROR); - logger.setException(TimiJava.serializeThrowable(e)); - log.error("新增 GAO 登记事件失败", e); - throw new TimiException(TimiCode.ERROR, "新增 GAO 登记事件失败", e); - } finally { - loggerService.create(logger); - } - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void update(GaoRegistrationEvent entity) { - Logger logger = new Logger(Logger.Module.GAO, "GAO_REGISTRATION_EVENT_UPDATE"); - try { - logger.setContent(jackson.writeValueAsString(entity)); - Integer sort = entity.getSort(); - checkEntity(entity, true); - GaoRegistrationEvent current = get(entity.getId()); - current.setName(entity.getName()); - current.setDescription(entity.getDescription()); - current.setColor(entity.getColor()); - current.setStatus(entity.getStatus()); - current.setRequirePhoto(entity.getRequirePhoto()); - current.setLimitType(entity.getLimitType()); - current.setLimitValue(entity.getLimitValue()); - current.setBeginAt(entity.getBeginAt()); - current.setEndAt(entity.getEndAt()); - if (sort != null) { - current.setSort(sort); - } - mapper.update(current); - saveRoleRelation(entity.getId(), entity.getRoleCodeList()); - logger.setLevel(Logger.Level.INFO); - logger.setResult(entity.getId()); - } catch (TimiException e) { - logger.setLevel(Logger.Level.WARN); - logger.setException(e.getMessage()); - throw e; - } catch (Exception e) { - logger.setLevel(Logger.Level.ERROR); - logger.setException(TimiJava.serializeThrowable(e)); - log.error("修改 GAO 登记事件失败", e); - throw new TimiException(TimiCode.ERROR, "修改 GAO 登记事件失败", e); - } finally { - loggerService.create(logger); - } - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void delete(String id) { - Logger logger = new Logger(Logger.Module.GAO, "GAO_REGISTRATION_EVENT_DELETE"); - try { - logger.setContent(id); - GaoRegistrationEvent entity = get(id); - entity.setStatus(GaoRegistrationEvent.Status.DELETED); - super.update(entity); - logger.setLevel(Logger.Level.INFO); - logger.setResult(id); - } catch (TimiException e) { - logger.setLevel(Logger.Level.WARN); - logger.setException(e.getMessage()); - throw e; - } catch (Exception e) { - logger.setLevel(Logger.Level.ERROR); - logger.setException(TimiJava.serializeThrowable(e)); - log.error("删除 GAO 登记事件失败", e); - throw new TimiException(TimiCode.ERROR, "删除 GAO 登记事件失败", e); - } finally { - loggerService.create(logger); - } - } - - @Override - public GaoRegistrationEvent get(String id) { - GaoRegistrationEvent registrationEvent = super.get(id); - registrationEvent.setRoleCodeList(roleRelationMapper.selectRoleCodeListByRegistrationEventId(id)); - return registrationEvent; - } - - @Override - public List listEnabled() { - List list = mapper.selectEnabledList(Time.now()); - loadRegistrationEventLimit(list); - list = list.stream().filter(item -> !TimiJava.isEmpty(item.getRoleCodeList())).toList(); - return list; - } - - @Override - public List listRoleCode(String registrationEventId) { - return roleRelationMapper.selectRoleCodeListByRegistrationEventId(registrationEventId); - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void sort(List idList) { - if (idList == null || idList.isEmpty()) { - return; - } - List distinctIdList = new ArrayList<>(new LinkedHashSet<>(idList)); - Map eventMap = mapper.selectByIdList(distinctIdList).stream().collect(Collectors.toMap(GaoRegistrationEvent::getId, item -> item)); - TimiException.requiredTrue(eventMap.size() == distinctIdList.size(), "not found registrationEvent"); - for (int index = 0; index < distinctIdList.size(); index++) { - GaoRegistrationEvent registrationEvent = eventMap.get(distinctIdList.get(index)); - registrationEvent.setSort(index); - mapper.update(registrationEvent); - } - } - - private void checkEntity(GaoRegistrationEvent entity, boolean requireId) { - TimiException.required(entity, "not found registrationEvent"); - if (requireId) { - TimiException.required(entity.getId(), "not found registrationEvent.id"); - } - TimiException.required(entity.getName(), "not found registrationEvent.name"); - if (entity.getStatus() == null) { - entity.setStatus(GaoRegistrationEvent.Status.ACTIVE); - } - if (entity.getRequirePhoto() == null) { - entity.setRequirePhoto(false); - } - if (entity.getLimitType() == null) { - entity.setLimitType(GaoRegistrationEvent.LimitType.NONE); - } - TimiException.requiredTrue(entity.getRoleCodeList() != null && entity.getRoleCodeList() - .stream() - .anyMatch(item -> item != null && !item.isBlank()), "not found registrationEvent.roleCodeList"); - checkRegistrationLimit(entity); - checkRegisterablePeriod(entity); - if (entity.getColor() != null) { - entity.setColor(entity.getColor().trim()); - } - if (entity.getSort() == null) { - entity.setSort(0); - } - } - - private void checkRegistrationLimit(GaoRegistrationEvent entity) { - if (entity.getLimitType() == GaoRegistrationEvent.LimitType.INTERVAL_HOUR || entity.getLimitType() == GaoRegistrationEvent.LimitType.TOTAL_LIMIT || entity.getLimitType() == GaoRegistrationEvent.LimitType.CUSTOMER_TOTAL_LIMIT) { - TimiException.required(entity.getLimitValue(), "not found registrationEvent.limitValue"); - TimiException.requiredTrue(0 < entity.getLimitValue(), "registrationEvent.limitValue lte 0"); - return; - } - entity.setLimitValue(null); - } - - private void checkRegisterablePeriod(GaoRegistrationEvent entity) { - if (entity.getBeginAt() == null && entity.getEndAt() == null) { - return; - } - TimiException.requiredTrue(entity.getBeginAt() == null || entity.getEndAt() == null || entity.getBeginAt() <= entity.getEndAt(), "registrationEvent.beginAt gt registrationEvent.endAt"); - } - - private void saveRoleRelation(String registrationEventId, List roleCodeList) { - roleRelationMapper.deleteByRegistrationEventId(registrationEventId); - if (roleCodeList == null || roleCodeList.isEmpty()) { - return; - } - Set distinctCodeSet = new LinkedHashSet<>(roleCodeList); - for (String roleCode : distinctCodeSet) { - if (roleCode == null || roleCode.isBlank()) { - continue; - } - GaoRegistrationEventRoleRelation relation = new GaoRegistrationEventRoleRelation(); - relation.setRegistrationEventId(registrationEventId); - relation.setRoleCode(roleCode.trim()); - roleRelationMapper.insert(relation); - } - } - - private void loadRegistrationEventLimit(List registrationEventList) { - if (registrationEventList == null || registrationEventList.isEmpty()) { - return; - } - List registrationEventIdList = registrationEventList.stream().map(GaoRegistrationEvent::getId).distinct().toList(); - Map> roleCodeMap = roleRelationMapper.selectByRegistrationEventIdList(new ArrayList<>(registrationEventIdList)) - .stream() - .collect(Collectors.groupingBy(GaoRegistrationEventRoleRelation::getRegistrationEventId, Collectors.mapping(GaoRegistrationEventRoleRelation::getRoleCode, Collectors.toList()))); - for (GaoRegistrationEvent registrationEvent : registrationEventList) { - registrationEvent.setRoleCodeList(roleCodeMap.getOrDefault(registrationEvent.getId(), List.of())); - } - } -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/util/CustomerXSSFBuilder.java b/src/main/java/com/imyeyu/api/modules/gao/util/CustomerXSSFBuilder.java new file mode 100644 index 0000000..eed8e8b --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/util/CustomerXSSFBuilder.java @@ -0,0 +1,67 @@ +package com.imyeyu.api.modules.gao.util; + +import com.imyeyu.api.modules.gao.entity.GaoCustomer; +import com.imyeyu.api.util.BaseXSSFBuilder; +import com.imyeyu.utils.Time; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.stereotype.Component; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; + +/// +/// +/// @author 夜雨 +/// @since 2026-08-04 15:59 +@Component +public class CustomerXSSFBuilder extends BaseXSSFBuilder { + + public byte[] build(List list) throws IOException { + try (Workbook workbook = new XSSFWorkbook(); ByteArrayOutputStream output = new ByteArrayOutputStream()) { + Sheet sheet = workbook.createSheet("客户列表"); + CellStyle titleStyle = createTitleStyle(workbook); + CellStyle headerStyle = createHeaderStyle(workbook); + + appendRow(sheet, 0, titleStyle, "客户列表导出"); + appendRow(sheet, 1, null, "生成时间", Time.nowString()); + appendRow(sheet, 2, null, "记录总数", String.valueOf(list.size())); + appendRow(sheet, 3, headerStyle, "客户编码", "姓名", "性别", "出生日期(公历)", "出生日期历法", "年龄", "电话", "地址", "病症", "备注", "邀请人", "邀请客户数", "创建时间"); + int rowIndex = 4; + for (GaoCustomer customer : list) { + appendRow(sheet, rowIndex, null, + text(customer.getCode()), + text(customer.getName()), + gender(customer.getGender()), + text(customer.getBirthdate()), + formatBirthdateCalendar(customer), + customer.getAge() == null ? "" : String.valueOf(customer.getAge()), + text(customer.getTelephone()), + text(customer.getAddress()), + text(customer.getDisease()), + text(customer.getRemark()), + customer.getIntroducerCustomer() == null ? "" : text(customer.getIntroducerCustomer().getName()), + String.valueOf(customer.getInvitedCount()), + Time.toDateTime(customer.getCreatedAt()) + ); + rowIndex++; + } + for (int index = 0; index < 13; index++) { + sheet.autoSizeColumn(index); + sheet.setColumnWidth(index, Math.clamp(sheet.getColumnWidth(index), 10 * 256, 36 * 256)); + } + workbook.write(output); + return output.toByteArray(); + } + } + + private String formatBirthdateCalendar(GaoCustomer customer) { + if (customer.getBirthdateCalendar() == null) { + return "公历"; + } + return customer.getBirthdateCalendar() == GaoCustomer.BirthdateCalendar.LUNAR ? "农历" : "公历"; + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/util/EventRecordXSSFBuilder.java b/src/main/java/com/imyeyu/api/modules/gao/util/EventRecordXSSFBuilder.java new file mode 100644 index 0000000..ce4db1f --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/util/EventRecordXSSFBuilder.java @@ -0,0 +1,53 @@ +package com.imyeyu.api.modules.gao.util; + +import com.imyeyu.api.modules.gao.entity.GaoEventRecord; +import com.imyeyu.api.util.BaseXSSFBuilder; +import com.imyeyu.utils.Time; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.stereotype.Component; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; + +/// +/// +/// @author 夜雨 +/// @since 2026-08-05 11:03 +@Component +public class EventRecordXSSFBuilder extends BaseXSSFBuilder { + + public byte[] build(List list) throws IOException { + try (Workbook workbook = new XSSFWorkbook(); ByteArrayOutputStream output = new ByteArrayOutputStream()) { + Sheet sheet = workbook.createSheet("登记事件记录"); + CellStyle titleStyle = createTitleStyle(workbook); + CellStyle headerStyle = createHeaderStyle(workbook); + + appendRow(sheet, 0, titleStyle, "登记事件记录导出"); + appendRow(sheet, 1, null, "生成时间", Time.nowString()); + appendRow(sheet, 2, null, "记录总数", String.valueOf(list.size())); + appendRow(sheet, 3, headerStyle, "客户编码", "客户姓名", "登记事件", "登记时间", "登记人", "备注"); + int rowIndex = 4; + for (GaoEventRecord record : list) { + appendRow(sheet, rowIndex, null, + record.getCustomer() == null ? "" : text(record.getCustomer().getCode()), + record.getCustomer() == null ? "" : text(record.getCustomer().getName()), + record.getEvent() == null ? "" : text(record.getEvent().getName()), + Time.toDateTime(record.getRegisteredAt()), + record.getOperator().getName(), + text(record.getRemark()) + ); + rowIndex++; + } + for (int i = 0; i < 6; i++) { + sheet.autoSizeColumn(i); + sheet.setColumnWidth(i, Math.clamp(sheet.getColumnWidth(i), 12 * 256, 40 * 256)); + } + workbook.write(output); + return output.toByteArray(); + } + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerDailyStatView.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerDailyStateView.java similarity index 89% rename from src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerDailyStatView.java rename to src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerDailyStateView.java index 3a75c4c..8198c95 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerDailyStatView.java +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerDailyStateView.java @@ -8,7 +8,7 @@ import lombok.Data; /// @author Codex /// @since 2026-07-28 @Data -public class GaoCustomerDailyStatView { +public class GaoCustomerDailyStateView { /// 日期值,格式为 yyyyMMdd private Integer dateValue; diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerRegistrationRankView.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerEventRankView.java similarity index 52% rename from src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerRegistrationRankView.java rename to src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerEventRankView.java index ee9fb11..db3a492 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerRegistrationRankView.java +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerEventRankView.java @@ -1,5 +1,6 @@ package com.imyeyu.api.modules.gao.vo; +import com.imyeyu.api.modules.gao.entity.GaoCustomer; import lombok.Data; /// @@ -8,7 +9,7 @@ import lombok.Data; /// @author Codex /// @since 2026-08-03 @Data -public class GaoCustomerRegistrationRankView { +public class GaoCustomerEventRankView { /// 排名 private Integer rank; @@ -16,23 +17,17 @@ public class GaoCustomerRegistrationRankView { /// 客户 ID private String customerId; - /// 客户编码 - private String customerCode; - - /// 客户姓名 - private String customerName; - - /// 客户头像附件 ID - private String customerPhotoAttachmentId; + /// 客户 + private GaoCustomer customer; /// 累计登记天数 - private Long totalRegistrationDays; + private Long totalEventDays; /// 累计登记次数 - private Long totalRegistrationCount; + private Long totalEventCount; /// 最近连续登记天数 - private Long recentContinuousRegistrationDays; + private Long recentContinuousEventDays; /// 最后登记时间 private Long latestRegisteredAt; diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerInviteUpdateReq.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerInviteUpdateReq.java deleted file mode 100644 index fe21f48..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerInviteUpdateReq.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.imyeyu.api.modules.gao.vo; - -import lombok.Data; - -/// -/// 客户邀请关系更新请求 -/// -/// @author Codex -/// @since 2026-07-27 -@Data -public class GaoCustomerInviteUpdateReq { - - /// 客户 ID - private String customerId; - - /// 介绍人客户 ID - private String introducerCustomerId; -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerRegisterStatView.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerRegisterStatView.java deleted file mode 100644 index 741b448..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerRegisterStatView.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.imyeyu.api.modules.gao.vo; - -import lombok.Data; - -/// -/// 客户登记统计视图 -/// -/// @author Codex -/// @since 2026-07-27 -@Data -public class GaoCustomerRegisterStatView { - - /// 客户 ID - private String customerId; - - /// 客户姓名 - private String customerName; - - /// 登记事件 ID - private String registrationEventId; - - /// 登记事件名称 - private String registrationEventName; - - /// 数量 - private Long count; - - /// 最后登记时间 - private Long latestRegisteredAt; -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerRegistrationDayStatView.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerRegistrationDayStatView.java deleted file mode 100644 index 902977c..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerRegistrationDayStatView.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.imyeyu.api.modules.gao.vo; - -import lombok.Data; - -/// -/// 客户登记日期统计视图 -/// -/// @author Codex -/// @since 2026-08-03 -@Data -public class GaoCustomerRegistrationDayStatView { - - /// 客户 ID - private String customerId; - - /// 客户编码 - private String customerCode; - - /// 客户姓名 - private String customerName; - - /// 客户头像附件 ID - private String customerPhotoAttachmentId; - - /// 登记日期,格式 yyyyMMdd - private Integer dateValue; - - /// 当日登记次数 - private Long dailyCount; - - /// 当日最后登记时间 - private Long latestRegisteredAt; -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerSearchReq.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerSearchReq.java deleted file mode 100644 index 244c1f9..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerSearchReq.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.imyeyu.api.modules.gao.vo; - -import lombok.Data; - -/// -/// 客户搜索请求 -/// -/// @author Codex -/// @since 2026-07-27 -@Data -public class GaoCustomerSearchReq { - - /// 关键字,匹配编码、姓名、手机号、电话、住址、病症、条例部位 - private String keyword; - - /// 介绍人客户 ID - private String introducerCustomerId; -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerTrendStatReq.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerTrendStateReq.java similarity index 86% rename from src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerTrendStatReq.java rename to src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerTrendStateReq.java index b0ca66a..2763f5e 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerTrendStatReq.java +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerTrendStateReq.java @@ -8,7 +8,7 @@ import lombok.Data; /// @author Codex /// @since 2026-07-28 @Data -public class GaoCustomerTrendStatReq { +public class GaoCustomerTrendStateReq { /// 开始创建时间 private Long beginCreatedAt; diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordRangeReq.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordPage.java similarity index 58% rename from src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordRangeReq.java rename to src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordPage.java index d579c34..d64917d 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordRangeReq.java +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordPage.java @@ -1,32 +1,32 @@ package com.imyeyu.api.modules.gao.vo; +import com.imyeyu.java.bean.BasePage; import lombok.Data; +import lombok.EqualsAndHashCode; import java.util.List; /// /// 登记事件记录范围查询请求 /// -/// @author Codex -/// @since 2026-07-28 +/// @author 夜雨 +/// @since 2026-07-28 11:01 @Data -public class GaoRegistrationEventRecordRangeReq { +@EqualsAndHashCode(callSuper = true) +public class GaoEventRecordPage extends BasePage { /// 客户 ID,为空查询全部客户 private String customerId; - /// 登记事件 ID,为空查询全部登记事件 - private String registrationEventId; - /// 登记事件 ID 列表,为空查询全部登记事件 - private List registrationEventIdList; + private List eventIdList; /// 关键词,匹配客户、登记事件、备注、登记人 private String keyword; /// 开始登记时间 - private Long beginRegisteredAt; + private Long beginAt; /// 结束登记时间 - private Long endRegisteredAt; + private Long endAt; } diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordRankPage.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordRankPage.java new file mode 100644 index 0000000..7073947 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordRankPage.java @@ -0,0 +1,28 @@ +package com.imyeyu.api.modules.gao.vo; + +import com.imyeyu.java.bean.BasePage; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/// +/// +/// @author 夜雨 +/// @since 2026-08-05 12:44 +@Data +@EqualsAndHashCode(callSuper = true) +public class GaoEventRecordRankPage extends BasePage { + + /// 客户 ID,为空查询全部客户 + private String customerId; + + /// 登记事件 ID 列表,为空查询全部登记事件 + private List eventIdList; + + /// 开始登记时间 + private Long beginAt; + + /// 结束登记时间 + private Long endAt; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoQuickRegistrationEventRecordReq.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoQuickRegistrationEventRecordReq.java deleted file mode 100644 index 96d7dc6..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoQuickRegistrationEventRecordReq.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.imyeyu.api.modules.gao.vo; - -import lombok.Data; - -import java.util.ArrayList; -import java.util.List; - -/// -/// 快速录入并登记请求 -/// -/// @author Codex -/// @since 2026-07-28 -@Data -public class GaoQuickRegistrationEventRecordReq { - - /// 客户编码 - private String customerCode; - - /// 登记登记事件 ID - private String registrationEventId; - - /// 备注 - private String remark; - - /// 临时照片附件 ID 列表 - private List attachmentIdList = new ArrayList<>(); -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventPeriodStatView.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventPeriodStatView.java deleted file mode 100644 index c461e9f..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventPeriodStatView.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.imyeyu.api.modules.gao.vo; - -import lombok.Data; - -/// -/// 登记事件周期统计视图 -/// -/// @author Codex -/// @since 2026-07-27 -@Data -public class GaoRegistrationEventPeriodStatView { - - /// 登记事件 ID - private String registrationEventId; - - /// 周期值 - private Integer periodValue; - - /// 数量 - private Long count; -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordCalendarView.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordCalendarView.java deleted file mode 100644 index 3ee29a9..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordCalendarView.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.imyeyu.api.modules.gao.vo; - -import lombok.Data; - -/// -/// 登记事件记录日历轻量视图 -/// -/// @author Codex -/// @since 2026-08-01 -@Data -public class GaoRegistrationEventRecordCalendarView { - - /// 记录 ID - private String id; - - /// 客户 ID - private String customerId; - - /// 客户编码 - private String customerCode; - - /// 客户姓名 - private String customerName; - - /// 登记事件 ID - private String registrationEventId; - - /// 登记时间 - private Long registeredAt; -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordCreateReq.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordCreateReq.java deleted file mode 100644 index 72d8b81..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordCreateReq.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.imyeyu.api.modules.gao.vo; - -import lombok.Data; - -import java.util.ArrayList; -import java.util.List; - -/// -/// 登记登记事件记录创建请求 -/// -/// @author Codex -/// @since 2026-07-27 -@Data -public class GaoRegistrationEventRecordCreateReq { - - /// 客户 ID 列表,批量登记时使用 - private List customerIdList = new ArrayList<>(); - - /// 登记事件 ID - private String registrationEventId; - - /// 备注 - private String remark; - - /// 登记时间 - private Long registeredAt; - - /// 临时照片附件 ID 列表 - private List attachmentIdList = new ArrayList<>(); -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordDailyStatView.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordDailyStatView.java deleted file mode 100644 index 477b902..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordDailyStatView.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.imyeyu.api.modules.gao.vo; - -import lombok.Data; - -/// -/// 登记事件记录每日统计视图 -/// -/// @author Codex -/// @since 2026-07-28 -@Data -public class GaoRegistrationEventRecordDailyStatView { - - /// 日期值,格式为 yyyyMMdd - private Integer dateValue; - - /// 当日登记数 - private Long dailyCount; - - /// 截止当日累计登记数 - private Long totalCount; -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordListItemView.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordListItemView.java deleted file mode 100644 index 5bca512..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordListItemView.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.imyeyu.api.modules.gao.vo; - -import lombok.Data; - -/// -/// 登记事件记录列表项视图 -/// -/// @author Codex -/// @since 2026-08-01 -@Data -public class GaoRegistrationEventRecordListItemView { - - /// 记录 ID - private String id; - - /// 客户 ID - private String customerId; - - /// 客户姓名 - private String customerName; - - /// 客户头像附件 ID - private String customerPhotoAttachmentId; - - /// 登记事件 ID - private String registrationEventId; - - /// 登记事件名称 - private String registrationEventName; - - /// 登记时间 - private Long registeredAt; -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordPeriodReq.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordPeriodReq.java deleted file mode 100644 index 5fe8e2a..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordPeriodReq.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.imyeyu.api.modules.gao.vo; - -import lombok.Data; - -/// -/// 登记事件记录按周期查询请求 -/// -/// @author Codex -/// @since 2026-07-27 -@Data -public class GaoRegistrationEventRecordPeriodReq { - - /// - /// 周期类型 - /// - /// @author Codex - /// @since 2026-07-27 - public enum PeriodType { - - DAY, - - MONTH, - - YEAR - } - - /// 登记事件 ID - private String registrationEventId; - - /// 周期类型 - private PeriodType periodType; - - /// 周期值,日为 yyyyMMdd,月为 yyyyMM,年为 yyyy - private Integer periodValue; -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordStatReq.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordStatReq.java deleted file mode 100644 index 51214bf..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordStatReq.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.imyeyu.api.modules.gao.vo; - -import lombok.Data; - -/// -/// 登记事件记录统计请求 -/// -/// @author Codex -/// @since 2026-07-28 -@Data -public class GaoRegistrationEventRecordStatReq { - - /// 客户 ID,全部客户统计时不传 - private String customerId; - - /// 开始登记时间 - private Long beginRegisteredAt; - - /// 结束登记时间 - private Long endRegisteredAt; -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordTrendStatReq.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordTrendStatReq.java deleted file mode 100644 index b83be89..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordTrendStatReq.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.imyeyu.api.modules.gao.vo; - -import lombok.Data; - -/// -/// 登记事件记录趋势统计请求 -/// -/// @author Codex -/// @since 2026-07-28 -@Data -public class GaoRegistrationEventRecordTrendStatReq { - - /// 客户 ID,全部客户趋势时不传 - private String customerId; - - /// 登记事件 ID - private String registrationEventId; - - /// 开始登记时间 - private Long beginRegisteredAt; - - /// 结束登记时间 - private Long endRegisteredAt; -} diff --git a/src/main/java/com/imyeyu/api/modules/user/controller/UserController.java b/src/main/java/com/imyeyu/api/modules/user/controller/UserController.java index 5ff2101..c5315e2 100644 --- a/src/main/java/com/imyeyu/api/modules/user/controller/UserController.java +++ b/src/main/java/com/imyeyu/api/modules/user/controller/UserController.java @@ -7,7 +7,6 @@ import com.imyeyu.api.bean.CorePermissionCode; import com.imyeyu.api.bean.CoreRoleCode; import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.service.AttachmentService; -import com.imyeyu.api.modules.common.vo.attach.BizUpdateReq; import com.imyeyu.api.modules.user.entity.User; import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.api.modules.user.service.UserService; @@ -245,9 +244,6 @@ public class UserController { if (tempFileIdList == null || tempFileIdList.isEmpty()) { return; } - BizUpdateReq req = new BizUpdateReq(); - req.setBizType(Attachment.BizType.USER); - req.setBizId(userId); List items = attachmentService.listByBizId(Attachment.BizType.USER, userId).stream() .filter(item -> !User.AttachType.AVATAR.name().equals(item.getAttachType())) .map(item -> { @@ -264,7 +260,6 @@ public class UserController { }).toList(); items = new ArrayList<>(items); items.addAll(avatarItems); - req.setItems(items); - attachmentService.updateByBiz(req); + attachmentService.updateByBizId(Attachment.BizType.USER, userId, items); } } diff --git a/src/main/java/com/imyeyu/api/util/BaseXSSFBuilder.java b/src/main/java/com/imyeyu/api/util/BaseXSSFBuilder.java new file mode 100644 index 0000000..74d9afb --- /dev/null +++ b/src/main/java/com/imyeyu/api/util/BaseXSSFBuilder.java @@ -0,0 +1,65 @@ +package com.imyeyu.api.util; + +import com.imyeyu.api.modules.user.bean.Gender; +import com.imyeyu.java.TimiJava; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; + +import java.io.IOException; +import java.util.List; + +/// +/// +/// @author 夜雨 +/// @since 2026-08-04 15:57 +public abstract class BaseXSSFBuilder { + + public abstract byte[] build(List list) throws IOException; + + protected CellStyle createTitleStyle(Workbook workbook) { + CellStyle style = workbook.createCellStyle(); + Font font = workbook.createFont(); + font.setBold(true); + font.setFontHeightInPoints((short) 14); + style.setFont(font); + return style; + } + + protected CellStyle createHeaderStyle(Workbook workbook) { + CellStyle style = workbook.createCellStyle(); + Font font = workbook.createFont(); + font.setBold(true); + style.setFont(font); + return style; + } + + protected void appendRow(Sheet sheet, int rowIndex, CellStyle style, String... values) { + Row row = sheet.createRow(rowIndex); + for (int index = 0; index < values.length; index++) { + row.createCell(index).setCellValue(values[index]); + if (style != null) { + row.getCell(index).setCellStyle(style); + } + } + } + + protected String text(Object obj) { + if (obj == null) { + return ""; + } + return TimiJava.defaultIfEmpty(obj.toString(), ""); + } + + protected String gender(Gender gender) { + if (gender == null) { + return ""; + } + return switch (gender) { + case MALE -> "男"; + case FEMALE -> "女"; + }; + } +} diff --git a/src/main/resources/db/migration/timiserver/V23__rename_gao_event_tables.sql b/src/main/resources/db/migration/timiserver/V23__rename_gao_event_tables.sql new file mode 100644 index 0000000..2e4ec83 --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V23__rename_gao_event_tables.sql @@ -0,0 +1,54 @@ +RENAME TABLE `gao_registration_event` TO `gao_event`; + +ALTER TABLE `gao_event` + DROP KEY `idx_gao_registration_event_status`, + ADD KEY `idx_gao_event_status` (`status`, `deleted_at`, `sort`, `created_at`); + +RENAME TABLE `gao_registration_event_role_relation` TO `gao_event_role_relation`; + +ALTER TABLE `gao_event_role_relation` + DROP KEY `idx_gao_registration_event_role_relation_registration_event_id`, + CHANGE COLUMN `registration_event_id` `event_id` VARCHAR(36) NOT NULL COMMENT '登记事件 ID', + ADD KEY `idx_gao_event_role_relation_event_id` (`event_id`); + +RENAME TABLE `gao_registration_event_record` TO `gao_event_record`; + +ALTER TABLE `gao_event_record` + DROP KEY `idx_gao_registration_event_record_customer_id`, + DROP KEY `idx_gao_registration_event_record_registration_event_id`, + DROP KEY `idx_gao_registration_event_record_registered_at`, + CHANGE COLUMN `registration_event_id` `event_id` VARCHAR(36) NOT NULL COMMENT '登记事件 ID', + ADD KEY `idx_gao_event_record_customer_id` (`customer_id`), + ADD KEY `idx_gao_event_record_event_id` (`event_id`), + ADD KEY `idx_gao_event_record_registered_at` (`registered_at`); + +UPDATE `attachment` +SET `biz_type` = 'GAO_EVENT_RECORD' +WHERE `biz_type` = 'GAO_REGISTRATION_EVENT_RECORD'; + +UPDATE `permission` +SET `code` = CASE `code` + WHEN 'REGISTRATION_EVENT:CREATE' THEN 'EVENT:CREATE' + WHEN 'REGISTRATION_EVENT:READ' THEN 'EVENT:READ' + WHEN 'REGISTRATION_EVENT:UPDATE' THEN 'EVENT:UPDATE' + WHEN 'REGISTRATION_EVENT:DELETE' THEN 'EVENT:DELETE' + WHEN 'REGISTRATION_EVENT_RECORD:CREATE' THEN 'EVENT_RECORD:CREATE' + WHEN 'REGISTRATION_EVENT_RECORD:READ' THEN 'EVENT_RECORD:READ' + WHEN 'REGISTRATION_EVENT_RECORD:EXPORT' THEN 'EVENT_RECORD:EXPORT' + WHEN 'REGISTRATION_EVENT_RECORD:UPDATE' THEN 'EVENT_RECORD:UPDATE' + WHEN 'REGISTRATION_EVENT_RECORD:DELETE' THEN 'EVENT_RECORD:DELETE' + ELSE `code` +END +WHERE + `module_code` = 'GAO' + AND `code` IN ( + 'REGISTRATION_EVENT:CREATE', + 'REGISTRATION_EVENT:READ', + 'REGISTRATION_EVENT:UPDATE', + 'REGISTRATION_EVENT:DELETE', + 'REGISTRATION_EVENT_RECORD:CREATE', + 'REGISTRATION_EVENT_RECORD:READ', + 'REGISTRATION_EVENT_RECORD:EXPORT', + 'REGISTRATION_EVENT_RECORD:UPDATE', + 'REGISTRATION_EVENT_RECORD:DELETE' + ); diff --git a/src/main/resources/mapper/timi-server/AttachmentMapper.xml b/src/main/resources/mapper/timi-server/AttachmentMapper.xml index e06d291..65bbae8 100644 --- a/src/main/resources/mapper/timi-server/AttachmentMapper.xml +++ b/src/main/resources/mapper/timi-server/AttachmentMapper.xml @@ -14,7 +14,7 @@ - AND 1 = 0 + AND FALSE AND `deleted_at` IS NULL AND (`destroy_at` IS NULL OR UNIX_TIMESTAMP() < `destroy_at`) @@ -24,6 +24,35 @@ #{attachType} - ORDER BY `created_at` ASC + ORDER BY `created_at` + + diff --git a/src/main/resources/mapper/timi-server/GaoCustomerMapper.xml b/src/main/resources/mapper/timi-server/GaoCustomerMapper.xml index b7db501..6cbed41 100644 --- a/src/main/resources/mapper/timi-server/GaoCustomerMapper.xml +++ b/src/main/resources/mapper/timi-server/GaoCustomerMapper.xml @@ -1,33 +1,6 @@ - - 1 = 1 - AND `deleted_at` IS NULL - - AND ( - `code` LIKE CONCAT('%', #{keyword}, '%') - OR `name` LIKE CONCAT('%', #{keyword}, '%') - OR `telephone` LIKE CONCAT('%', #{keyword}, '%') - OR `address` LIKE CONCAT('%', #{keyword}, '%') - OR `disease` LIKE CONCAT('%', #{keyword}, '%') - OR CAST(`conditioning_part` AS CHAR) LIKE CONCAT('%', #{keyword}, '%') - ) - - - AND `introducer_customer_id` = #{introducerCustomerId} - - - - - - - - - - - - - - - UPDATE `gao_customer` - SET - `invited_count` = GREATEST(IFNULL(`invited_count`, 0) + #{delta}, 0), - `updated_at` = UNIX_TIMESTAMP() * 1000 - WHERE - `id` = #{customerId} - AND `deleted_at` IS NULL - - - SELECT CAST(DATE_FORMAT(FROM_UNIXTIME(`created_at` / 1000), '%Y%m%d') AS UNSIGNED) AS `dateValue`, COUNT(1) AS `newCustomerCount` @@ -109,7 +35,7 @@ AND `deleted_at` IS NULL - SELECT IFNULL(CAST(`gender` AS CHAR), 'UNKNOWN') AS `genderCode`, COUNT(1) AS `count` diff --git a/src/main/resources/mapper/timi-server/GaoRegistrationEventMapper.xml b/src/main/resources/mapper/timi-server/GaoEventMapper.xml similarity index 63% rename from src/main/resources/mapper/timi-server/GaoRegistrationEventMapper.xml rename to src/main/resources/mapper/timi-server/GaoEventMapper.xml index 9709039..8750dcd 100644 --- a/src/main/resources/mapper/timi-server/GaoRegistrationEventMapper.xml +++ b/src/main/resources/mapper/timi-server/GaoEventMapper.xml @@ -1,12 +1,12 @@ - - SELECT * - FROM `gao_registration_event` + FROM `gao_event` WHERE - 1 = 1 + TRUE AND `id` IN @@ -14,20 +14,20 @@ - AND 1 = 0 + AND FALSE AND `deleted_at` IS NULL - SELECT * - FROM `gao_registration_event` + FROM `gao_event` WHERE - (`status` IS NULL OR `status` = 'ACTIVE') + `status` = '${@com.imyeyu.api.modules.gao.entity.GaoEvent$Status@ACTIVE.name()}' AND (`begin_at` IS NULL OR `begin_at` <= #{now}) AND (`end_at` IS NULL OR #{now} <= `end_at`) AND `deleted_at` IS NULL - ORDER BY `sort` ASC, `created_at` ASC + ORDER BY `sort`, `created_at` diff --git a/src/main/resources/mapper/timi-server/GaoEventRecordMapper.xml b/src/main/resources/mapper/timi-server/GaoEventRecordMapper.xml new file mode 100644 index 0000000..aaea72d --- /dev/null +++ b/src/main/resources/mapper/timi-server/GaoEventRecordMapper.xml @@ -0,0 +1,171 @@ + + + + + + + FROM `gao_event_record` r + LEFT JOIN `gao_customer` c ON r.`customer_id` = c.`id` + + + r.`registered_at` >= #{beginAt} + AND r.`registered_at` <= #{endAt} + AND r.`deleted_at` IS NULL + + AND r.`customer_id` = #{customerId} + + + AND r.`event_id` IN + + #{eventId} + + + + AND ( + c.`code` LIKE CONCAT('%', #{keyword}, '%') + OR c.`name` LIKE CONCAT('%', #{keyword}, '%') + OR r.`remark` LIKE CONCAT('%', #{keyword}, '%') + OR r.`operator_user_id` LIKE CONCAT('%', #{keyword}, '%') + ) + + + + + + + r.`deleted_at` IS NULL + AND c.`deleted_at` IS NULL + + AND r.`customer_id` = #{customerId} + + + AND r.`registered_at` >= #{beginAt} + + + AND r.`registered_at` <= #{endAt} + + + AND r.`event_id` IN + + #{eventId} + + + + + + diff --git a/src/main/resources/mapper/timi-server/GaoEventRoleRelationMapper.xml b/src/main/resources/mapper/timi-server/GaoEventRoleRelationMapper.xml new file mode 100644 index 0000000..410bb9e --- /dev/null +++ b/src/main/resources/mapper/timi-server/GaoEventRoleRelationMapper.xml @@ -0,0 +1,22 @@ + + + + + diff --git a/src/main/resources/mapper/timi-server/GaoRegistrationEventRecordMapper.xml b/src/main/resources/mapper/timi-server/GaoRegistrationEventRecordMapper.xml deleted file mode 100644 index 12e0902..0000000 --- a/src/main/resources/mapper/timi-server/GaoRegistrationEventRecordMapper.xml +++ /dev/null @@ -1,389 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - r.`registered_at` >= #{req.beginRegisteredAt} - AND r.`registered_at` <= #{req.endRegisteredAt} - AND r.`deleted_at` IS NULL - - AND r.`customer_id` = #{req.customerId} - - - AND r.`registration_event_id` = #{req.registrationEventId} - - - AND r.`registration_event_id` IN - - #{registrationEventId} - - - - AND ( - c.`code` LIKE CONCAT('%', #{req.keyword}, '%') - OR c.`name` LIKE CONCAT('%', #{req.keyword}, '%') - OR e.`name` LIKE CONCAT('%', #{req.keyword}, '%') - OR r.`remark` LIKE CONCAT('%', #{req.keyword}, '%') - OR u.`name` LIKE CONCAT('%', #{req.keyword}, '%') - OR u.`nick` LIKE CONCAT('%', #{req.keyword}, '%') - OR r.`operator_user_id` LIKE CONCAT('%', #{req.keyword}, '%') - ) - - - - - FROM `gao_registration_event_record` r - LEFT JOIN `gao_customer` c ON r.`customer_id` = c.`id` - LEFT JOIN `gao_registration_event` e ON r.`registration_event_id` = e.`id` - LEFT JOIN `user` u ON r.`operator_user_id` = u.`id` - - - - - - - - - - diff --git a/src/main/resources/mapper/timi-server/UserMapper.xml b/src/main/resources/mapper/timi-server/UserMapper.xml index fd49dd3..2440a33 100644 --- a/src/main/resources/mapper/timi-server/UserMapper.xml +++ b/src/main/resources/mapper/timi-server/UserMapper.xml @@ -6,7 +6,7 @@ * FROM `user` WHERE - 1 = 1 + TRUE AND `id` IN @@ -14,7 +14,7 @@ - AND 1 = 0 + AND FALSE AND `deleted_at` IS NULL -- 2.54.0 From 7ffdd715ee8b142dfb63e4dbd90a0b588b250e14 Mon Sep 17 00:00:00 2001 From: Timi Date: Thu, 6 Aug 2026 13:16:52 +0800 Subject: [PATCH 02/12] refactor gao module --- .../gao/controller/GaoCustomerController.java | 2 +- .../controller/GaoEventRecordController.java | 15 +++++++- .../api/modules/gao/entity/GaoCustomer.java | 3 ++ .../gao/service/GaoCustomerService.java | 2 ++ .../gao/service/GaoEventRecordService.java | 11 ++++++ .../GaoCustomerServiceImplement.java | 10 ++++-- .../GaoEventRecordServiceImplement.java | 34 ++++++++++++++++--- 7 files changed, 67 insertions(+), 10 deletions(-) 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 ee984b0..260ea13 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 @@ -100,7 +100,7 @@ public class GaoCustomerController { @RequireGaoPermission(GaoPermissionCode.CUSTOMER_UPDATE) @PostMapping("/update") public void update(@RequestBody GaoCustomer customer) { - service.update(customer); + service.updateWithAttachment(customer); } @AOPLog 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 index e79352b..427c534 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventRecordController.java +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventRecordController.java @@ -93,11 +93,24 @@ public class GaoEventRecordController { @AOPLog @RequestRateLimit @PostMapping("/create") - @JsonView(ResponseView.Public.class) public void create(@RequestBody GaoEventRecord record) { service.create(record); } + @AOPLog + @RequestRateLimit + @PostMapping("/create/batch") + public void createBatch(@RequestBody List recordList) { + service.createBatch(recordList); + } + + @AOPLog + @RequestRateLimit + @PostMapping("/create/customer") + public void createWithCustomer(@RequestBody GaoCustomer customer) { + service.createWithCustomer(customer); + } + @AOPLog @RequestRateLimit @RequireGaoPermission(GaoPermissionCode.EVENT_RECORD_UPDATE) 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 9a5b573..af788d8 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 @@ -99,6 +99,9 @@ public class GaoCustomer extends UUIDEntity { @Transient 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/service/GaoCustomerService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerService.java index f544164..b9b0ca0 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 @@ -19,6 +19,8 @@ import java.util.stream.Collectors; /// @since 2026-07-27 14:12 public interface GaoCustomerService extends BaseService { + void updateWithAttachment(GaoCustomer customer); + /// 按编码查询客户 /// /// @param code 客户编码 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 index 3ad884f..3fb4fb8 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/GaoEventRecordService.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoEventRecordService.java @@ -1,13 +1,16 @@ 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; /// 登记事件记录服务 /// @@ -15,6 +18,14 @@ import java.io.IOException; /// @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 范围查询请求 diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java index f268ac7..9053b0a 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java @@ -146,10 +146,8 @@ public class GaoCustomerServiceImplement extends AbstractEntityService customerIdList = TimiJava.defaultIfEmpty(record.getCustomerIdList(), List.of(record.getCustomerId())); - if (TimiJava.isNotEmpty(record.getCustomer())) { - // 客户未创建,补录客户后登记 - customerService.create(record.getCustomer()); - customerIdList.add(record.getCustomer().getId()); - } TimiException.required(customerIdList, "not found customerId or empty customerIdList"); GaoEventRecord firstRecord = null; @@ -171,6 +167,34 @@ public class GaoEventRecordServiceImplement extends AbstractEntityService pageByRange(GaoEventRecordPage page) { PageResult result = new PageResult<>(); -- 2.54.0 From 7e40e0202ec5a1d54ad9479cc2d91611c1f5b443 Mon Sep 17 00:00:00 2001 From: Timi Date: Fri, 7 Aug 2026 11:37:54 +0800 Subject: [PATCH 03/12] add store --- .idea/data_source_mapping.xml | 7 - .../annotation/RequireGaoRoleInterceptor.java | 4 + .../modules/gao/bean/GaoPermissionCode.java | 21 +- .../api/modules/gao/bean/GaoRoleCode.java | 12 +- .../gao/controller/GaoCustomerController.java | 25 ++- .../gao/controller/GaoEventController.java | 4 +- .../GaoStoreBusinessDayController.java | 82 +++++++ .../gao/controller/GaoStoreController.java | 103 +++++++++ .../gao/controller/GaoUserController.java | 64 ++++-- .../api/modules/gao/entity/GaoCustomer.java | 3 + .../api/modules/gao/entity/GaoEvent.java | 3 + .../modules/gao/entity/GaoEventRecord.java | 9 + .../api/modules/gao/entity/GaoStore.java | 57 +++++ .../gao/entity/GaoStoreBusinessDay.java | 44 ++++ .../api/modules/gao/entity/GaoUser.java | 12 +- .../modules/gao/mapper/GaoCustomerMapper.java | 7 +- .../modules/gao/mapper/GaoEventMapper.java | 3 +- .../gao/mapper/GaoEventRecordMapper.java | 17 +- .../gao/mapper/GaoStoreBusinessDayMapper.java | 49 +++++ .../modules/gao/mapper/GaoStoreMapper.java | 19 ++ .../api/modules/gao/mapper/GaoUserMapper.java | 3 + .../gao/service/GaoCustomerService.java | 13 +- .../modules/gao/service/GaoEventService.java | 3 +- .../service/GaoStoreBusinessDayService.java | 33 +++ .../modules/gao/service/GaoStoreService.java | 27 +++ .../modules/gao/service/GaoUserService.java | 15 +- .../GaoCustomerServiceImplement.java | 42 ++-- .../GaoEventRecordServiceImplement.java | 45 +++- .../implement/GaoEventServiceImplement.java | 7 +- .../GaoStoreBusinessDayServiceImplement.java | 135 ++++++++++++ .../implement/GaoStoreServiceImplement.java | 160 ++++++++++++++ .../implement/GaoUserServiceImplement.java | 202 ++++++------------ .../modules/gao/util/StoreXSSFBuilder.java | 61 ++++++ .../gao/vo/GaoCustomerTrendStateReq.java | 3 + .../modules/gao/vo/GaoEventRecordPage.java | 3 + .../gao/vo/GaoEventRecordRankPage.java | 3 + .../user/controller/RoleController.java | 16 +- .../imyeyu/api/modules/user/entity/User.java | 9 + .../api/modules/user/mapper/RoleMapper.java | 8 +- .../api/modules/user/mapper/UserMapper.java | 3 +- .../modules/user/mapper/UserRoleMapper.java | 4 + .../api/modules/user/service/RoleService.java | 57 +---- .../modules/user/service/UserRoleService.java | 69 +++++- .../api/modules/user/service/UserService.java | 9 + .../implement/PermissionCheckerImplement.java | 5 +- .../implement/RoleCheckerImplement.java | 6 +- .../implement/RoleServiceImplement.java | 115 +--------- .../implement/UserLoginServiceImplement.java | 6 +- .../implement/UserRoleServiceImplement.java | 117 ++++++++++ .../implement/UserServiceImplement.java | 33 ++- ...Req.java => UserRoleAuthorizeRequest.java} | 4 +- .../com/imyeyu/api/util/InitBuiltinUser.java | 4 + .../timiserver/V24__add_gao_store_scope.sql | 192 +++++++++++++++++ ...V25__restrict_gao_user_to_single_store.sql | 8 + .../mapper/timi-server/GaoCustomerMapper.xml | 23 +- .../mapper/timi-server/GaoEventMapper.xml | 3 +- .../timi-server/GaoEventRecordMapper.xml | 53 ++++- .../mapper/timi-server/GaoStoreMapper.xml | 17 ++ .../mapper/timi-server/RoleMapper.xml | 25 +-- .../mapper/timi-server/UserRoleMapper.xml | 17 ++ 60 files changed, 1657 insertions(+), 446 deletions(-) delete mode 100644 .idea/data_source_mapping.xml create mode 100644 src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreBusinessDayController.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreController.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/entity/GaoStore.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/entity/GaoStoreBusinessDay.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/mapper/GaoStoreBusinessDayMapper.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/mapper/GaoStoreMapper.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreBusinessDayService.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreService.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreBusinessDayServiceImplement.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreServiceImplement.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/util/StoreXSSFBuilder.java rename src/main/java/com/imyeyu/api/modules/user/vo/role/{UserRoleAuthorizeReq.java => UserRoleAuthorizeRequest.java} (79%) create mode 100644 src/main/resources/db/migration/timiserver/V24__add_gao_store_scope.sql create mode 100644 src/main/resources/db/migration/timiserver/V25__restrict_gao_user_to_single_store.sql create mode 100644 src/main/resources/mapper/timi-server/GaoStoreMapper.xml 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/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 3f25cb5..845e349 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 @@ -11,6 +11,16 @@ import lombok.Getter; /// @since 2026-07-26 public enum GaoPermissionCode implements BuiltinPermissionCode { + STORE_CREATE("STORE:CREATE", "门店创建", false, true), + + STORE_READ("STORE:READ", "门店读取", true, true), + + STORE_EXPORT("STORE:EXPORT", "门店导出", false, true), + + STORE_UPDATE("STORE:UPDATE", "门店修改", true, true), + + STORE_DELETE("STORE:DELETE", "门店删除", false, true), + CUSTOMER_CREATE("CUSTOMER:CREATE", "客户创建"), CUSTOMER_READ("CUSTOMER:READ", "客户读取"), @@ -58,14 +68,23 @@ public enum GaoPermissionCode implements BuiltinPermissionCode { @Getter final boolean grantToStoreManager; + /// `true` 为初始化时授权给全局管理员角色 + @Getter + final boolean grantToGlobalManager; + GaoPermissionCode(String value, String defaultName) { - this(value, defaultName, false); + this(value, defaultName, false, false); } GaoPermissionCode(String value, String defaultName, boolean grantToStoreManager) { + this(value, defaultName, grantToStoreManager, false); + } + + GaoPermissionCode(String value, String defaultName, boolean grantToStoreManager, boolean grantToGlobalManager) { this.value = value; this.defaultName = defaultName; this.grantToStoreManager = grantToStoreManager; + this.grantToGlobalManager = grantToGlobalManager; } @Override 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..04e0b9b 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,16 @@ import lombok.AllArgsConstructor; @AllArgsConstructor public enum GaoRoleCode implements BuiltinRoleCode { - /// 店长 - STORE_MANAGER("店长"), + /// 全局管理员 + GLOBAL_MANAGER("全局管理员", null), - EMPLOYEE("员工"); + /// 店长 + STORE_MANAGER("店长", GLOBAL_MANAGER.name()), + + EMPLOYEE("员工", STORE_MANAGER.name()); final String defaultName; + final String parentCode; @Override public ModuleCode getModuleCode() { @@ -36,6 +40,6 @@ 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/GaoCustomerController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerController.java index 260ea13..d237a78 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 @@ -15,6 +15,7 @@ import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView; import com.imyeyu.api.modules.gao.vo.GaoCustomerTrendStateReq; 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; @@ -111,13 +112,25 @@ public class GaoCustomerController { service.delete(id); } + @AOPLog + @RequestRateLimit + @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("/find/code") @JsonView(ResponseView.Public.class) - public GaoCustomer findByCode(@RequestParam String code) { - GaoCustomer customer = service.getByCode(code); + public GaoCustomer findByCode(@RequestParam String storeId, @RequestParam String code) { + GaoCustomer customer = service.getByCode(storeId, code); if (customer == null) { return null; } @@ -130,8 +143,8 @@ public class GaoCustomerController { @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) @PostMapping("/find/name") @JsonView(ResponseView.Public.class) - public GaoCustomer findByName(@RequestParam String name) { - GaoCustomer customer = service.getByName(name); + public GaoCustomer findByName(@RequestParam String storeId, @RequestParam String name) { + GaoCustomer customer = service.getByName(storeId, name); if (customer == null) { return null; } @@ -197,7 +210,7 @@ public class GaoCustomerController { @RequestRateLimit @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) @PostMapping("/state/gender") - public List genderStat() { - return service.stateGender(); + 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 index 6103e94..08a19d2 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventController.java +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventController.java @@ -49,8 +49,8 @@ public class GaoEventController { @AOPLog @RequestRateLimit @PostMapping("/list/valid") - public List listValid() { - return service.listValid(); + public List listValid(@RequestParam String storeId) { + return service.listValid(storeId); } @AOPLog 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/GaoStoreController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreController.java new file mode 100644 index 0000000..86f6229 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreController.java @@ -0,0 +1,103 @@ +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.api.bean.RoleMatchMode; +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; + +/// GAO 门店接口 +/// +/// @author Codex +/// @since 2026-08-06 +@RestController +@RequiredArgsConstructor +@RequestMapping("/gao/store") +@RequireGaoRole(GaoRoleCode.GLOBAL_MANAGER) +public class GaoStoreController { + + private final GaoStoreService service; + private final UserLoginService userLoginService; + + @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("/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 + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STORE_UPDATE) + @PostMapping("/update") + public void update(@RequestBody GaoStore store) { + service.update(store); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STORE_DELETE) + @PostMapping("/delete") + public void delete(@RequestParam String id) { + service.delete(id); + } + + @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 + @RequireGaoRole(value = {GaoRoleCode.GLOBAL_MANAGER, GaoRoleCode.STORE_MANAGER, GaoRoleCode.EMPLOYEE}, mode = RoleMatchMode.ANY) + @PostMapping("/detail/user") + public GaoStore getByLoginUser() { + 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 cf26252..dbc0e8b 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,6 +32,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.List; +import java.util.Map; /// GAO 用户接口 /// @@ -34,8 +44,14 @@ import java.util.List; @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 @@ -43,7 +59,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 @@ -52,16 +83,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 @@ -84,6 +113,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 af788d8..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 @@ -48,6 +48,9 @@ public class GaoCustomer extends UUIDEntity { /// 客户编码 private String code; + /// 门店 ID + private String storeId; + /// 姓名 private String name; diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEvent.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEvent.java index 1142178..0a354b1 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEvent.java +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEvent.java @@ -73,6 +73,9 @@ public class GaoEvent extends UUIDEntity { /// 登记事件名称 private String name; + /// 门店 ID + private String storeId; + /// 说明 private String description; diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEventRecord.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEventRecord.java index 4dacd05..9a2a53b 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEventRecord.java +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEventRecord.java @@ -31,6 +31,9 @@ public class GaoEventRecord extends UUIDEntity { /// 客户 ID private String customerId; + /// 门店 ID + private String storeId; + /// 登记登记事件 ID private String eventId; @@ -43,6 +46,12 @@ public class GaoEventRecord extends UUIDEntity { /// 登记时间 private Long registeredAt; + /// 登记日期值,格式为 yyyyMMdd + private Integer registeredDateValue; + + /// 营业日递增序号 + private Long businessDayNo; + @Transient private GaoCustomer customer; 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 1ee6b93..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 @@ -4,6 +4,7 @@ import com.imyeyu.api.modules.gao.entity.GaoCustomer; 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 java.util.Collection; import java.util.List; @@ -22,16 +23,16 @@ public interface GaoCustomerMapper extends BaseMapper { /// @param beginCreatedAt 开始创建时间 /// @param endCreatedAt 结束创建时间 /// @return 每日新增客户数 - List stateDailyNewCustomer(Long beginCreatedAt, Long endCreatedAt); + List stateDailyNewCustomer(@Param("storeId") String storeId, @Param("beginCreatedAt") Long beginCreatedAt, @Param("endCreatedAt") Long endCreatedAt); /// 统计开始时间前客户总数 /// /// @param beginCreatedAt 开始创建时间 /// @return 客户总数 - Long countCreatedBefore(Long beginCreatedAt); + Long countCreatedBefore(@Param("storeId") String storeId, @Param("beginCreatedAt") Long beginCreatedAt); /// 统计客户性别分布 /// /// @return 性别统计列表 - List stateGender(); + 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 index 56040d3..259b345 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventMapper.java +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventMapper.java @@ -2,6 +2,7 @@ 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; @@ -15,5 +16,5 @@ public interface GaoEventMapper extends BaseMapper { List selectByIdList(Collection idList); - List selectValidList(Long now); + 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 index 460900a..f5ac28f 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventRecordMapper.java +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventRecordMapper.java @@ -5,6 +5,7 @@ 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.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; import java.util.List; @@ -14,7 +15,21 @@ import java.util.List; /// @since 2026-07-27 14:11 public interface GaoEventRecordMapper extends BaseMapper { - Long count(String customerId, String eventId, Long beginAt, Long endAt); + 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); 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..36eaf89 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoStoreMapper.java @@ -0,0 +1,19 @@ +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; + +/// +/// GAO 门店 Mapper +/// +/// @author Codex +/// @since 2026-08-06 +public interface GaoStoreMapper extends BaseMapper { + + /// 查询用户所属的可用门店 + /// + /// @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..157595a 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 @@ -14,4 +14,7 @@ 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); + + @Select("SELECT * FROM `gao_user` WHERE `store_id` = #{storeId} AND `user_id` = #{userId} AND `deleted_at` IS NULL LIMIT 1") + GaoUser selectByStoreIdAndUserId(@Param("storeId") String storeId, @Param("userId") String userId); } 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 b9b0ca0..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 @@ -23,11 +23,17 @@ public interface GaoCustomerService extends BaseService { /// 按编码查询客户 /// + /// @param storeId 门店 ID /// @param code 客户编码 /// @return 客户 - GaoCustomer getByCode(String code); + GaoCustomer getByCode(String storeId, String code); - GaoCustomer getByName(String name); + /// 按姓名查询客户 + /// + /// @param storeId 门店 ID + /// @param name 姓名 + /// @return 客户 + GaoCustomer getByName(String storeId, String name); List listByIdList(Collection idList); @@ -65,6 +71,7 @@ public interface GaoCustomerService extends BaseService { /// 统计客户性别分布 /// + /// @param storeId 门店 ID /// @return 性别统计列表 - List stateGender(); + List stateGender(String storeId); } 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 index f37f2db..6331a21 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/GaoEventService.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoEventService.java @@ -20,8 +20,9 @@ public interface GaoEventService extends BaseService { /// 查询启用的登记事件 /// + /// @param storeId 门店 ID /// @return 登记事件列表 - List listValid(); + List listValid(String storeId); /// 查询登记事件要求的角色 /// 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/GaoStoreService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreService.java new file mode 100644 index 0000000..5036e71 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreService.java @@ -0,0 +1,27 @@ +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; + +/// +/// GAO 门店服务 +/// +/// @author Codex +/// @since 2026-08-06 +public interface GaoStoreService extends BaseService { + + /// 查询用户所属门店 + /// + /// @param userId 用户 ID + /// @return 门店 + GaoStore getByUserId(String userId); + + /// 导出门店列表 + /// + /// @param page 分页查询条件 + /// @return xlsx 文件字节 + byte[] export(Page page) throws IOException; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoUserService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoUserService.java index a7d3efd..b3de23b 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/GaoUserService.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoUserService.java @@ -1,8 +1,6 @@ package com.imyeyu.api.modules.gao.service; import com.imyeyu.api.modules.gao.entity.GaoUser; -import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.service.BaseService; /// @@ -12,15 +10,8 @@ import com.imyeyu.spring.service.BaseService; /// @since 2026-07-30 public interface GaoUserService extends BaseService { - /// 按用户 ID 查询 GAO 用户 + /// 调整 GAO 用户所属门店 /// - /// @param userId 用户 ID - /// @return GAO 用户 - GaoUser getByUserId(String userId); - - /// 分页查询并填充用户详情 - /// - /// @param page 分页请求 - /// @return GAO 用户分页 - PageResult pageWithUser(Page page); + /// @param gaoUser GAO 用户,必须包含 ID 和目标门店 ID + void transferStore(GaoUser gaoUser); } diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java index 9053b0a..66cf3e5 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java @@ -34,8 +34,10 @@ import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; /// 客户服务实现 /// @@ -66,21 +68,24 @@ public class GaoCustomerServiceImplement extends AbstractEntityService listByIdList(Collection idList) { - return mapper.selectByIdList(idList); + return mapper.selectByIdList(new HashSet<>(idList)); } @Override @@ -252,6 +263,7 @@ public class GaoCustomerServiceImplement extends AbstractEntityService stateDailyTrend(GaoCustomerTrendStateReq req) { TimiException.required(req, "not found req"); + TimiException.required(req.getStoreId(), "not found req.storeId"); TimiException.required(req.getBeginCreatedAt(), "not found req.beginCreatedAt"); TimiException.required(req.getEndCreatedAt(), "not found req.endCreatedAt"); TimiException.requiredTrue(req.getEndCreatedAt() >= req.getBeginCreatedAt(), "req.endCreatedAt lt req.beginCreatedAt"); @@ -325,10 +338,10 @@ public class GaoCustomerServiceImplement extends AbstractEntityService newCustomerCountMap = new HashMap<>(); - for (GaoCustomerDailyStateView view : mapper.stateDailyNewCustomer(beginCreatedAt, endCreatedAt)) { + for (GaoCustomerDailyStateView view : mapper.stateDailyNewCustomer(req.getStoreId(), beginCreatedAt, endCreatedAt)) { newCustomerCountMap.put(view.getDateValue(), TimiJava.defaultIfNull(view.getNewCustomerCount(), 0L)); } - long totalCustomerCount = TimiJava.defaultIfNull(mapper.countCreatedBefore(beginCreatedAt), 0L); + long totalCustomerCount = TimiJava.defaultIfNull(mapper.countCreatedBefore(req.getStoreId(), beginCreatedAt), 0L); List list = new ArrayList<>(); for (LocalDate date = beginDate; !date.isAfter(endDate); date = date.plusDays(1)) { int dateValue = Integer.parseInt(DAY_FORMATTER.format(date)); @@ -344,8 +357,9 @@ public class GaoCustomerServiceImplement extends AbstractEntityService stateGender() { - return mapper.stateGender(); + public List stateGender(String storeId) { + TimiException.required(storeId, "not found storeId"); + return mapper.stateGender(storeId); } private void changeInvitedCount(String customerId, long delta) { diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoEventRecordServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoEventRecordServiceImplement.java index 7f1c93a..e3af9a0 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoEventRecordServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoEventRecordServiceImplement.java @@ -12,6 +12,7 @@ import com.imyeyu.api.modules.gao.mapper.GaoEventRecordMapper; 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.GaoStoreBusinessDayService; import com.imyeyu.api.modules.gao.util.EventRecordXSSFBuilder; import com.imyeyu.api.modules.gao.vo.GaoCustomerEventRankView; import com.imyeyu.api.modules.gao.vo.GaoEventRecordPage; @@ -34,8 +35,10 @@ import org.springframework.transaction.annotation.Transactional; import java.io.IOException; import java.time.Instant; +import java.time.LocalDate; import java.time.ZoneId; import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalAdjusters; import java.util.ArrayList; @@ -50,6 +53,8 @@ import java.util.List; @RequiredArgsConstructor public class GaoEventRecordServiceImplement extends AbstractEntityService implements GaoEventRecordService { + private static final DateTimeFormatter DAY_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd"); + private final ObjectMapper jackson; private final RoleChecker roleChecker; private final LoggerService loggerService; @@ -57,6 +62,7 @@ public class GaoEventRecordServiceImplement extends AbstractEntityService customerIdList = TimiJava.defaultIfEmpty(record.getCustomerIdList(), List.of(record.getCustomerId())); TimiException.required(customerIdList, "not found customerId or empty customerIdList"); @@ -91,13 +100,19 @@ public class GaoEventRecordServiceImplement extends AbstractEntityService pageByRange(GaoEventRecordPage page) { + TimiException.required(page.getStoreId(), "not found page.storeId"); PageResult result = new PageResult<>(); result.setTotal(mapper.countByRange(page)); result.setList(mapper.selectByRange(page)); @@ -205,12 +229,14 @@ public class GaoEventRecordServiceImplement extends AbstractEntityService list = mapper.selectByRange(page); return xssfBuilder.build(list); } @Override public PageResult pageRankByRange(GaoEventRecordRankPage page) { + TimiException.required(page.getStoreId(), "not found page.storeId"); PageResult result = new PageResult<>(); result.setTotal(mapper.countRankByRange(page)); result.setList(mapper.selectRankByRange(page)); @@ -241,18 +267,18 @@ public class GaoEventRecordServiceImplement extends AbstractEntityService { long beginAt = Time.today(registeredAt); long endAt = beginAt + Time.D; - Long count = mapper.count(customerId, event.getId(), beginAt, endAt); + Long count = mapper.count(event.getStoreId(), customerId, event.getId(), beginAt, endAt); TimiException.requiredTrue(TimiJava.defaultIfNull(count, 0L) < 1, "该客户当前半日已登记该事件"); } case INTERVAL_HOUR -> { long beginAt = registeredAt - event.getLimitValue() * Time.H; - Long count = mapper.count(customerId, event.getId(), beginAt, registeredAt); + Long count = mapper.count(event.getStoreId(), customerId, event.getId(), beginAt, registeredAt); TimiException.requiredTrue(TimiJava.defaultIfNull(count, 0L) < 1, "该客户登记该事件间隔不足"); } case NATURAL_MONTH -> { @@ -260,17 +286,22 @@ public class GaoEventRecordServiceImplement extends AbstractEntityService { - Long count = mapper.count(null, event.getId(), null, null); + Long count = mapper.count(event.getStoreId(), null, event.getId(), null, null); TimiException.requiredTrue(TimiJava.defaultIfNull(count, 0L) < event.getLimitValue(), "登记事件总次数已达上限"); } case CUSTOMER_TOTAL_LIMIT -> { - Long count = mapper.count(customerId, event.getId(), null, null); + Long count = mapper.count(event.getStoreId(), customerId, event.getId(), null, null); TimiException.requiredTrue(TimiJava.defaultIfNull(count, 0L) < event.getLimitValue(), "该客户登记该事件次数已达上限"); } } } + + private int dateValue(long time) { + LocalDate date = Instant.ofEpochMilli(time).atZone(ZoneId.systemDefault()).toLocalDate(); + return Integer.parseInt(DAY_FORMATTER.format(date)); + } } diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoEventServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoEventServiceImplement.java index 6f42c65..cac18ba 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoEventServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoEventServiceImplement.java @@ -56,6 +56,7 @@ public class GaoEventServiceImplement extends AbstractEntityService listValid() { - return mapper.selectValidList(Time.now()); + public List listValid(String storeId) { + TimiException.required(storeId, "not found storeId"); + return mapper.selectValidList(storeId, Time.now()); } @Override diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreBusinessDayServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreBusinessDayServiceImplement.java new file mode 100644 index 0000000..a359323 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreBusinessDayServiceImplement.java @@ -0,0 +1,135 @@ +package com.imyeyu.api.modules.gao.service.implement; + +import com.imyeyu.api.config.dbsource.TimiServerDBConfig; +import com.imyeyu.api.modules.gao.entity.GaoStoreBusinessDay; +import com.imyeyu.api.modules.gao.mapper.GaoEventRecordMapper; +import com.imyeyu.api.modules.gao.mapper.GaoStoreBusinessDayMapper; +import com.imyeyu.api.modules.gao.service.GaoStoreBusinessDayService; +import com.imyeyu.java.TimiJava; +import com.imyeyu.java.bean.timi.TimiException; +import com.imyeyu.spring.mapper.BaseMapper; +import com.imyeyu.spring.service.AbstractEntityService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/// +/// GAO 门店营业日服务实现 +/// +/// @author Codex +/// @since 2026-08-06 +@Service +@RequiredArgsConstructor +public class GaoStoreBusinessDayServiceImplement extends AbstractEntityService implements GaoStoreBusinessDayService { + + private final GaoStoreBusinessDayMapper mapper; + private final GaoEventRecordMapper eventRecordMapper; + + @Override + protected BaseMapper mapper() { + return mapper; + } + + @Override + @Transactional(TimiServerDBConfig.ROLLBACKER) + public void create(GaoStoreBusinessDay businessDay) { + prepareBusinessDay(businessDay); + super.create(businessDay); + if (businessDay.getOpenStatus() == GaoStoreBusinessDay.OpenStatus.OPEN) { + rebuildBusinessDayNo(businessDay.getStoreId(), businessDay.getDateValue()); + } + } + + @Override + @Transactional(TimiServerDBConfig.ROLLBACKER) + public void update(GaoStoreBusinessDay businessDay) { + TimiException.required(businessDay, "not found businessDay"); + TimiException.required(businessDay.getId(), "not found businessDay.id"); + GaoStoreBusinessDay dbBusinessDay = get(businessDay.getId()); + TimiException.required(dbBusinessDay, "not found dbBusinessDay"); + TimiException.requiredTrue(dbBusinessDay.getStoreId().equals(businessDay.getStoreId()), "businessDay.storeId invalid"); + TimiException.requiredTrue(dbBusinessDay.getDateValue().equals(businessDay.getDateValue()), "businessDay.dateValue invalid"); + if (businessDay.getOpenStatus() == GaoStoreBusinessDay.OpenStatus.CLOSED) { + checkNoRecord(dbBusinessDay); + } + prepareBusinessDay(businessDay); + super.update(businessDay); + rebuildBusinessDayNo(businessDay.getStoreId(), businessDay.getDateValue()); + } + + @Override + @Transactional(TimiServerDBConfig.ROLLBACKER) + public void delete(String id) { + GaoStoreBusinessDay businessDay = get(id); + TimiException.required(businessDay, "not found businessDay"); + if (businessDay.getOpenStatus() == GaoStoreBusinessDay.OpenStatus.OPEN) { + checkNoRecord(businessDay); + } + super.delete(id); + rebuildBusinessDayNo(businessDay.getStoreId(), businessDay.getDateValue()); + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public GaoStoreBusinessDay resolveOpenBusinessDay(String storeId, Integer dateValue) { + TimiException.required(storeId, "not found storeId"); + TimiException.required(dateValue, "not found dateValue"); + GaoStoreBusinessDay businessDay = mapper.selectByStoreIdAndDateValue(storeId, dateValue); + if (businessDay != null) { + TimiException.requiredTrue(businessDay.getOpenStatus() == GaoStoreBusinessDay.OpenStatus.OPEN, "门店当日休息,不能登记"); + TimiException.required(businessDay.getBusinessDayNo(), "not found businessDay.businessDayNo"); + return businessDay; + } + businessDay = new GaoStoreBusinessDay(); + businessDay.setStoreId(storeId); + businessDay.setDateValue(dateValue); + businessDay.setOpenStatus(GaoStoreBusinessDay.OpenStatus.OPEN); + super.create(businessDay); + rebuildBusinessDayNo(storeId, dateValue); + return mapper.selectByStoreIdAndDateValue(storeId, dateValue); + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void rebuildBusinessDayNo(String storeId, Integer beginDateValue) { + TimiException.required(storeId, "not found storeId"); + TimiException.required(beginDateValue, "not found beginDateValue"); + long businessDayNo = TimiJava.defaultIfNull(mapper.selectLastBusinessDayNoBefore(storeId, beginDateValue), 0L); + for (GaoStoreBusinessDay businessDay : mapper.selectOpenListFromDate(storeId, beginDateValue)) { + businessDayNo++; + if (!Long.valueOf(businessDayNo).equals(businessDay.getBusinessDayNo())) { + businessDay.setBusinessDayNo(businessDayNo); + mapper.update(businessDay); + } + eventRecordMapper.updateBusinessDayNoByStoreIdAndRegisteredDateValue(storeId, businessDay.getDateValue(), businessDayNo); + } + } + + @Override + public List listByDateRange(String storeId, Integer beginDateValue, Integer endDateValue) { + TimiException.required(storeId, "not found storeId"); + TimiException.required(beginDateValue, "not found beginDateValue"); + TimiException.required(endDateValue, "not found endDateValue"); + TimiException.requiredTrue(beginDateValue <= endDateValue, "endDateValue lt beginDateValue"); + return mapper.selectByStoreIdAndDateRange(storeId, beginDateValue, endDateValue); + } + + private void prepareBusinessDay(GaoStoreBusinessDay businessDay) { + TimiException.required(businessDay, "not found businessDay"); + TimiException.required(businessDay.getStoreId(), "not found businessDay.storeId"); + TimiException.required(businessDay.getDateValue(), "not found businessDay.dateValue"); + businessDay.setOpenStatus(TimiJava.defaultIfNull(businessDay.getOpenStatus(), GaoStoreBusinessDay.OpenStatus.OPEN)); + if (businessDay.getOpenStatus() == GaoStoreBusinessDay.OpenStatus.CLOSED) { + businessDay.setBusinessDayNo(null); + return; + } + businessDay.setBusinessDayNo(null); + } + + private void checkNoRecord(GaoStoreBusinessDay businessDay) { + Long count = eventRecordMapper.countByStoreIdAndRegisteredDateValue(businessDay.getStoreId(), businessDay.getDateValue()); + TimiException.requiredTrue(TimiJava.defaultIfNull(count, 0L) < 1, "该营业日已有登记记录,不能改为休息或删除"); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreServiceImplement.java new file mode 100644 index 0000000..9d66219 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreServiceImplement.java @@ -0,0 +1,160 @@ +package com.imyeyu.api.modules.gao.service.implement; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.imyeyu.api.modules.gao.entity.GaoStore; +import com.imyeyu.api.modules.gao.mapper.GaoStoreMapper; +import com.imyeyu.api.modules.gao.service.GaoStoreService; +import com.imyeyu.api.modules.gao.util.StoreXSSFBuilder; +import com.imyeyu.api.modules.system.entity.Logger; +import com.imyeyu.api.modules.system.service.LoggerService; +import com.imyeyu.java.TimiJava; +import com.imyeyu.java.bean.timi.TimiCode; +import com.imyeyu.java.bean.timi.TimiException; +import com.imyeyu.spring.bean.Page; +import com.imyeyu.spring.mapper.BaseMapper; +import com.imyeyu.spring.service.AbstractEntityService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.util.List; + +/// +/// GAO 门店服务实现 +/// +/// @author Codex +/// @since 2026-08-06 +@Slf4j +@Service +@RequiredArgsConstructor +public class GaoStoreServiceImplement extends AbstractEntityService implements GaoStoreService { + + private final ObjectMapper jackson; + + private final LoggerService loggerService; + + private final GaoStoreMapper mapper; + private final StoreXSSFBuilder storeXSSFBuilder; + + @Override + protected BaseMapper mapper() { + return mapper; + } + + @Override + public void create(GaoStore store) { + TimiException.required(store, "not found store"); + TimiException.required(store.getCode(), "not found store.code"); + TimiException.required(store.getName(), "not found store.name"); + + Logger logger = new Logger(Logger.Module.GAO, "GAO_STORE_CREATE"); + try { + logger.setContent(jackson.writeValueAsString(store)); + checkUnique(store); + store.setStatus(TimiJava.defaultIfNull(store.getStatus(), GaoStore.Status.ACTIVE)); + store.setSort(TimiJava.defaultIfNull(store.getSort(), 0)); + super.create(store); + logger.setLevel(Logger.Level.INFO); + logger.setResult(store.getId()); + } catch (TimiException e) { + logger.setLevel(Logger.Level.WARN); + logger.setException(e.getMessage()); + throw e; + } catch (Exception e) { + logger.setLevel(Logger.Level.ERROR); + logger.setException(TimiJava.serializeThrowable(e)); + log.error("新增 GAO 门店失败", e); + throw new TimiException(TimiCode.ERROR, "新增 GAO 门店失败", e); + } finally { + loggerService.create(logger); + } + } + + @Override + public void update(GaoStore store) { + TimiException.required(store, "not found store"); + TimiException.required(store.getId(), "not found store.id"); + TimiException.required(store.getCode(), "not found store.code"); + TimiException.required(store.getName(), "not found store.name"); + + Logger logger = new Logger(Logger.Module.GAO, "GAO_STORE_UPDATE"); + try { + logger.setContent(jackson.writeValueAsString(store)); + checkUnique(store); + GaoStore dbStore = get(store.getId()); + dbStore.setCode(store.getCode()); + dbStore.setName(store.getName()); + dbStore.setStatus(store.getStatus()); + dbStore.setTelephone(store.getTelephone()); + dbStore.setAddress(store.getAddress()); + dbStore.setRemark(store.getRemark()); + dbStore.setSort(store.getSort()); + super.update(dbStore); + logger.setLevel(Logger.Level.INFO); + logger.setResult(store.getId()); + } catch (TimiException e) { + logger.setLevel(Logger.Level.WARN); + logger.setException(e.getMessage()); + throw e; + } catch (Exception e) { + logger.setLevel(Logger.Level.ERROR); + logger.setException(TimiJava.serializeThrowable(e)); + log.error("修改 GAO 门店失败", e); + throw new TimiException(TimiCode.ERROR, "修改 GAO 门店失败", e); + } finally { + loggerService.create(logger); + } + } + + @Override + public void delete(String id) { + Logger logger = new Logger(Logger.Module.GAO, "GAO_STORE_DELETE"); + try { + logger.setContent(id); + GaoStore store = get(id); + store.setStatus(GaoStore.Status.DELETED); + super.update(store); + super.delete(id); + logger.setLevel(Logger.Level.INFO); + logger.setResult(id); + } catch (TimiException e) { + logger.setLevel(Logger.Level.WARN); + logger.setException(e.getMessage()); + throw e; + } catch (Exception e) { + logger.setLevel(Logger.Level.ERROR); + logger.setException(TimiJava.serializeThrowable(e)); + log.error("删除 GAO 门店失败", e); + throw new TimiException(TimiCode.ERROR, "删除 GAO 门店失败", e); + } finally { + loggerService.create(logger); + } + } + + @Override + public GaoStore getByUserId(String userId) { + TimiException.required(userId, "not found userId"); + return mapper.selectByUserId(userId); + } + + @Override + public byte[] export(Page page) throws IOException { + page.setIndex(0); + page.setSize(Long.MAX_VALUE); + List list = mapper.selectByPage(page); + return storeXSSFBuilder.build(list); + } + + private void checkUnique(GaoStore store) { + GaoStore codeExample = new GaoStore(); + codeExample.setCode(store.getCode()); + GaoStore byCode = mapper.selectByExample(codeExample); + TimiException.requiredTrue(byCode == null || byCode.getId().equals(store.getId()), "该门店编码已存在"); + + GaoStore nameExample = new GaoStore(); + nameExample.setName(store.getName()); + GaoStore byName = mapper.selectByExample(nameExample); + TimiException.requiredTrue(byName == null || byName.getId().equals(store.getId()), "该门店名称已存在"); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoUserServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoUserServiceImplement.java index 1ec583b..a77d6e2 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoUserServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoUserServiceImplement.java @@ -1,39 +1,26 @@ package com.imyeyu.api.modules.gao.service.implement; import com.fasterxml.jackson.databind.ObjectMapper; -import com.imyeyu.api.bean.ModuleCode; import com.imyeyu.api.config.dbsource.TimiServerDBConfig; +import com.imyeyu.api.modules.gao.entity.GaoStore; import com.imyeyu.api.modules.gao.entity.GaoUser; import com.imyeyu.api.modules.gao.mapper.GaoUserMapper; +import com.imyeyu.api.modules.gao.service.GaoStoreService; import com.imyeyu.api.modules.gao.service.GaoUserService; import com.imyeyu.api.modules.system.entity.Logger; import com.imyeyu.api.modules.system.service.LoggerService; -import com.imyeyu.api.modules.user.entity.Role; -import com.imyeyu.api.modules.user.entity.User; -import com.imyeyu.api.modules.user.mapper.UserMapper; import com.imyeyu.api.modules.user.service.RoleService; import com.imyeyu.api.modules.user.service.UserService; -import com.imyeyu.api.modules.user.vo.role.UserRoleAuthorizeReq; import com.imyeyu.java.TimiJava; import com.imyeyu.java.bean.timi.TimiCode; import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.spring.TimiSpring; -import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.service.AbstractEntityService; -import com.imyeyu.utils.Time; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; - /// /// GAO 用户服务实现 /// @@ -44,12 +31,13 @@ import java.util.stream.Collectors; @RequiredArgsConstructor public class GaoUserServiceImplement extends AbstractEntityService implements GaoUserService { - private final GaoUserMapper mapper; private final ObjectMapper jackson; - private final LoggerService loggerService; - private final UserMapper userMapper; private final UserService userService; private final RoleService roleService; + private final LoggerService loggerService; + private final GaoStoreService gaoStoreService; + + private final GaoUserMapper mapper; @Override protected BaseMapper mapper() { @@ -58,17 +46,28 @@ public class GaoUserServiceImplement extends AbstractEntityService pageWithUser(Page page) { - PageResult result = page(page); - loadUser(result.getList()); - return result; - } + Logger logger = new Logger(Logger.Module.GAO, "GAO_USER_TRANSFER_STORE"); + try { + logger.setContent(jackson.writeValueAsString(gaoUser)); - private void checkEntity(GaoUser entity, boolean requireId) { - TimiException.required(entity, "not found gao user"); - if (requireId) { - TimiException.required(entity.getId(), "not found gao user.id"); - } - TimiException.required(entity.getUserId(), "not found gao user.userId"); - TimiException.required(userMapper.select(entity.getUserId()), "not found user"); - GaoUser dbGaoUser = mapper.selectByUserId(entity.getUserId()); - if (dbGaoUser != null && !dbGaoUser.getId().equals(entity.getId())) { - throw new TimiException(TimiCode.ARG_BAD, "GAO 用户已存在"); - } - if (entity.getEnabled() == null) { - entity.setEnabled(true); - } - } + GaoStore store = gaoStoreService.get(gaoUser.getStoreId()); + TimiException.required(store, "not found store"); + TimiException.requiredTrue(store.isActivated(), "门店不可用"); - private void prepareUser(GaoUser entity) { - TimiException.required(entity, "not found gao user"); - if (TimiJava.isNotEmpty(entity.getUserId())) { - return; - } - User user = entity.getUser(); - TimiException.required(user, "not found gao user.user"); - user.setPhoneNoVerifyAt(TimiJava.defaultIfNull(user.getPhoneNoVerifyAt(), Time.now())); - userService.create(user); - entity.setUserId(user.getId()); - } + GaoUser dbGaoUser = super.get(gaoUser.getId()); + TimiException.required(dbGaoUser, "not found gao user"); - private void updateUser(GaoUser entity) { - User reqUser = entity.getUser(); - if (reqUser == null) { - return; - } - User user = userService.get(entity.getUserId()); - if (reqUser.getName() != null) { - user.setName(reqUser.getName()); - } - if (reqUser.getNick() != null) { - user.setNick(reqUser.getNick()); - } - if (reqUser.getPhoneNo() != null) { - user.setPhoneNo(reqUser.getPhoneNo()); - } - if (reqUser.getEmail() != null) { - user.setEmail(reqUser.getEmail()); - } - userService.update(user); - } + dbGaoUser.setStoreId(gaoUser.getStoreId()); + super.update(dbGaoUser); - private void saveUserRole(GaoUser entity) { - if (entity.getRoleIdList() == null) { - return; - } - UserRoleAuthorizeReq req = new UserRoleAuthorizeReq(); - req.setUserId(entity.getUserId()); - req.setModuleCode(ModuleCode.GAO); - req.setRoleIdList(entity.getRoleIdList()); - roleService.authorizeUserRole(req); - } - - private void loadUser(GaoUser gaoUser) { - if (gaoUser == null) { - return; - } - gaoUser.setUser(userMapper.select(gaoUser.getUserId())); - gaoUser.setRoleList(roleService.listByUserId(gaoUser.getUserId(), ModuleCode.GAO)); - } - - private void loadUser(List gaoUserList) { - if (gaoUserList == null || gaoUserList.isEmpty()) { - return; - } - List userIdList = gaoUserList.stream().map(GaoUser::getUserId).distinct().toList(); - Map userMap = userMapper.listByIdList(new ArrayList<>(userIdList)).stream() - .collect(Collectors.toMap(User::getId, Function.identity(), (left, right) -> left)); - for (GaoUser gaoUser : gaoUserList) { - gaoUser.setUser(userMap.get(gaoUser.getUserId())); - List roleList = roleService.listByUserId(gaoUser.getUserId(), ModuleCode.GAO); - gaoUser.setRoleList(roleList); + logger.setLevel(Logger.Level.INFO); + logger.setResult(gaoUser.getId()); + } catch (TimiException e) { + logger.setLevel(Logger.Level.WARN); + logger.setException(e.getMessage()); + throw e; + } catch (Exception e) { + logger.setLevel(Logger.Level.ERROR); + logger.setException(TimiJava.serializeThrowable(e)); + log.error("transfer user store error", e); + throw new TimiException(TimiCode.ERROR, "transfer user store error", e); + } finally { + loggerService.create(logger); } } } diff --git a/src/main/java/com/imyeyu/api/modules/gao/util/StoreXSSFBuilder.java b/src/main/java/com/imyeyu/api/modules/gao/util/StoreXSSFBuilder.java new file mode 100644 index 0000000..000f7eb --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/util/StoreXSSFBuilder.java @@ -0,0 +1,61 @@ +package com.imyeyu.api.modules.gao.util; + +import com.imyeyu.api.modules.gao.entity.GaoStore; +import com.imyeyu.api.util.BaseXSSFBuilder; +import com.imyeyu.utils.Time; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.stereotype.Component; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; + +/// +/// 门店 Excel 构建器 +/// +/// @author Codex +/// @since 2026-08-06 +@Component +public class StoreXSSFBuilder extends BaseXSSFBuilder { + + /// 构建门店列表 Excel + /// + /// @param list 门店列表 + /// @return xlsx 文件字节 + /// @throws IOException 写入异常 + public byte[] build(List list) throws IOException { + try (Workbook workbook = new XSSFWorkbook(); ByteArrayOutputStream output = new ByteArrayOutputStream()) { + Sheet sheet = workbook.createSheet("门店列表"); + CellStyle titleStyle = createTitleStyle(workbook); + CellStyle headerStyle = createHeaderStyle(workbook); + + appendRow(sheet, 0, titleStyle, "门店列表导出"); + appendRow(sheet, 1, null, "生成时间", Time.nowString()); + appendRow(sheet, 2, null, "记录总数", String.valueOf(list.size())); + appendRow(sheet, 3, headerStyle, "门店编码", "门店名称", "状态", "电话", "地址", "备注", "排序", "创建时间"); + int rowIndex = 4; + for (GaoStore store : list) { + appendRow(sheet, rowIndex, null, + text(store.getCode()), + text(store.getName()), + store.getStatus() == null ? "" : store.getStatus().name(), + text(store.getTelephone()), + text(store.getAddress()), + text(store.getRemark()), + store.getSort() == null ? "" : String.valueOf(store.getSort()), + Time.toDateTime(store.getCreatedAt()) + ); + rowIndex++; + } + for (int index = 0; index < 8; index++) { + sheet.autoSizeColumn(index); + sheet.setColumnWidth(index, Math.clamp(sheet.getColumnWidth(index), 10 * 256, 36 * 256)); + } + workbook.write(output); + return output.toByteArray(); + } + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerTrendStateReq.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerTrendStateReq.java index 2763f5e..881d521 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerTrendStateReq.java +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerTrendStateReq.java @@ -10,6 +10,9 @@ import lombok.Data; @Data public class GaoCustomerTrendStateReq { + /// 门店 ID + private String storeId; + /// 开始创建时间 private Long beginCreatedAt; diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordPage.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordPage.java index d64917d..870c859 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordPage.java +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordPage.java @@ -15,6 +15,9 @@ import java.util.List; @EqualsAndHashCode(callSuper = true) public class GaoEventRecordPage extends BasePage { + /// 门店 ID + private String storeId; + /// 客户 ID,为空查询全部客户 private String customerId; diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordRankPage.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordRankPage.java index 7073947..537b853 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordRankPage.java +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordRankPage.java @@ -14,6 +14,9 @@ import java.util.List; @EqualsAndHashCode(callSuper = true) public class GaoEventRecordRankPage extends BasePage { + /// 门店 ID + private String storeId; + /// 客户 ID,为空查询全部客户 private String customerId; diff --git a/src/main/java/com/imyeyu/api/modules/user/controller/RoleController.java b/src/main/java/com/imyeyu/api/modules/user/controller/RoleController.java index fc882d9..cfe1425 100644 --- a/src/main/java/com/imyeyu/api/modules/user/controller/RoleController.java +++ b/src/main/java/com/imyeyu/api/modules/user/controller/RoleController.java @@ -10,7 +10,8 @@ import com.imyeyu.api.modules.user.entity.Permission; import com.imyeyu.api.modules.user.entity.Role; import com.imyeyu.api.modules.user.service.PermissionService; import com.imyeyu.api.modules.user.service.RoleService; -import com.imyeyu.api.modules.user.vo.role.UserRoleAuthorizeReq; +import com.imyeyu.api.modules.user.service.UserRoleService; +import com.imyeyu.api.modules.user.vo.role.UserRoleAuthorizeRequest; import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.bean.Page; import com.imyeyu.spring.bean.PageResult; @@ -37,6 +38,7 @@ import java.util.List; public class RoleController { private final RoleService service; + private final UserRoleService userRoleService; private final PermissionService permissionService; @RequireCoreRole(value = {CoreRoleCode.ADMIN, CoreRoleCode.USER_ROLE_ADMIN}, mode = RoleMatchMode.ANY) @@ -86,26 +88,26 @@ public class RoleController { @RequireCoreRole(value = {CoreRoleCode.ADMIN, CoreRoleCode.USER_ROLE_ADMIN}, mode = RoleMatchMode.ANY) @RequireCorePermission(CorePermissionCode.USER_ROLE_READ) @PostMapping("/authorized/list") - public List listUserRole(@RequestParam String userId, @RequestParam(required = false) ModuleCode moduleCode) { + public List listUserRole(@RequestParam(required = false) ModuleCode moduleCode, @RequestParam String userId) { if (moduleCode != null) { - return service.listByUserId(userId, moduleCode); + return userRoleService.listRoleByUserId(moduleCode, userId); } - return service.listByUserId(userId); + return userRoleService.listRoleByUserId(userId); } @AOPLog @RequireCoreRole(value = {CoreRoleCode.ADMIN, CoreRoleCode.USER_ROLE_ADMIN}, mode = RoleMatchMode.ANY) @RequireCorePermission(value = {CorePermissionCode.USER_ROLE_CREATE, CorePermissionCode.USER_ROLE_DELETE}) @PostMapping("/authorized/create") - public void authorizeUserRole(@RequestBody UserRoleAuthorizeReq req) { - service.authorizeUserRole(req); + public void authorizeUserRole(@RequestBody UserRoleAuthorizeRequest req) { + userRoleService.authorizeUserRole(req); } @RequireCoreRole(value = {CoreRoleCode.ADMIN, CoreRoleCode.USER_ROLE_ADMIN}, mode = RoleMatchMode.ANY) @RequireCorePermission(CorePermissionCode.USER_ROLE_READ) @PostMapping("/authorized/permission") public List listAuthorizedPermission(@RequestParam String userId) { - return service.listAllPermissionByUserId(userId); + return userRoleService.listAllPermissionByUserId(userId); } private void fillRoleDetail(Role role) { diff --git a/src/main/java/com/imyeyu/api/modules/user/entity/User.java b/src/main/java/com/imyeyu/api/modules/user/entity/User.java index 2b7fe33..cd58258 100644 --- a/src/main/java/com/imyeyu/api/modules/user/entity/User.java +++ b/src/main/java/com/imyeyu/api/modules/user/entity/User.java @@ -8,6 +8,7 @@ import com.imyeyu.api.modules.user.bean.Gender; import com.imyeyu.spring.annotation.table.Transient; import com.imyeyu.spring.entity.UUIDEntity; import com.imyeyu.spring.util.ResponseView; +import com.imyeyu.utils.Regex; import com.imyeyu.utils.Time; import lombok.Data; import lombok.EqualsAndHashCode; @@ -123,6 +124,10 @@ public class User extends UUIDEntity { @JsonView(ResponseView.Public.class) protected List permissionList; + @Transient + @JsonView(ResponseView.Public.class) + protected List roleEntityList; + @Transient @JsonView(ResponseView.Public.class) protected List attachmentList; @@ -135,6 +140,10 @@ public class User extends UUIDEntity { @JsonView(ResponseView.Public.class) protected List tempFileIdList; + public boolean isValidPhone() { + return Regex.isMatch(Regex.MOBILE_PHONE, phoneNo); + } + /** @return true 为禁言中 */ public boolean isMuting() { return unmuteAt != null && Time.now() < unmuteAt; diff --git a/src/main/java/com/imyeyu/api/modules/user/mapper/RoleMapper.java b/src/main/java/com/imyeyu/api/modules/user/mapper/RoleMapper.java index 459e5f4..931b5ab 100644 --- a/src/main/java/com/imyeyu/api/modules/user/mapper/RoleMapper.java +++ b/src/main/java/com/imyeyu/api/modules/user/mapper/RoleMapper.java @@ -3,8 +3,8 @@ package com.imyeyu.api.modules.user.mapper; import com.imyeyu.api.bean.ModuleCode; import com.imyeyu.api.modules.user.entity.Role; import com.imyeyu.spring.mapper.BaseMapper; -import org.apache.ibatis.annotations.Param; +import java.util.Collection; import java.util.List; /** @@ -15,9 +15,7 @@ import java.util.List; */ public interface RoleMapper extends BaseMapper { - List selectByIdList(List idList); + List selectByIdList(Collection idList); - List selectByModuleCodeAndCodeList(@Param("moduleCode") ModuleCode moduleCode, @Param("codeList") List codeList); - - List selectByModuleCode(@Param("moduleCode") ModuleCode moduleCode); + List selectByModuleCode(ModuleCode moduleCode); } diff --git a/src/main/java/com/imyeyu/api/modules/user/mapper/UserMapper.java b/src/main/java/com/imyeyu/api/modules/user/mapper/UserMapper.java index feefe9f..eb62db4 100644 --- a/src/main/java/com/imyeyu/api/modules/user/mapper/UserMapper.java +++ b/src/main/java/com/imyeyu/api/modules/user/mapper/UserMapper.java @@ -4,6 +4,7 @@ import com.imyeyu.api.modules.user.entity.User; import com.imyeyu.spring.mapper.BaseMapper; import org.apache.ibatis.annotations.Select; +import java.util.Collection; import java.util.List; /** @@ -32,5 +33,5 @@ public interface UserMapper extends BaseMapper { * @param idList 用户 ID 列表 * @return 用户列表 */ - List listByIdList(List idList); + List listByIdList(Collection idList); } diff --git a/src/main/java/com/imyeyu/api/modules/user/mapper/UserRoleMapper.java b/src/main/java/com/imyeyu/api/modules/user/mapper/UserRoleMapper.java index 729e0fc..9314e55 100644 --- a/src/main/java/com/imyeyu/api/modules/user/mapper/UserRoleMapper.java +++ b/src/main/java/com/imyeyu/api/modules/user/mapper/UserRoleMapper.java @@ -2,7 +2,9 @@ package com.imyeyu.api.modules.user.mapper; import com.imyeyu.api.modules.user.entity.UserRole; import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; +import java.util.Collection; import java.util.List; /** @@ -15,5 +17,7 @@ public interface UserRoleMapper extends BaseMapper { void createBatch(List userRoleList); + List selectByUserIdList(@Param("userIdList") Collection userIdList); + void deleteByRoleIdList(String userId, List roleIdList); } diff --git a/src/main/java/com/imyeyu/api/modules/user/service/RoleService.java b/src/main/java/com/imyeyu/api/modules/user/service/RoleService.java index eda63dd..06bf481 100644 --- a/src/main/java/com/imyeyu/api/modules/user/service/RoleService.java +++ b/src/main/java/com/imyeyu/api/modules/user/service/RoleService.java @@ -3,9 +3,9 @@ package com.imyeyu.api.modules.user.service; import com.imyeyu.api.bean.ModuleCode; import com.imyeyu.api.modules.user.entity.Permission; import com.imyeyu.api.modules.user.entity.Role; -import com.imyeyu.api.modules.user.vo.role.UserRoleAuthorizeReq; import com.imyeyu.spring.service.BaseService; +import java.util.Collection; import java.util.List; /// 角色服务 @@ -14,60 +14,13 @@ import java.util.List; /// @since 2026-05-13 14:46 public interface RoleService extends BaseService { - /// 给用户授权角色 - /// - /// @param req 用户角色授权请求 - void authorizeUserRole(UserRoleAuthorizeReq req); - - /// 查询用户直属角色 - /// - /// @param userId 用户 ID - /// @return 角色列表 - List listByUserId(String userId); - - /// 查询用户指定模块直属角色 - /// - /// @param userId 用户 ID - /// @param moduleCode 模块代码 - /// @return 角色列表 - List listByUserId(String userId, ModuleCode moduleCode); - /// 查询模块角色 /// /// @param moduleCode 模块代码 /// @return 角色列表 List listByModuleCode(ModuleCode moduleCode); - /// 查询用户直属角色编码 - /// - /// @param userId 用户 ID - /// @return 角色编码列表,格式为 MODULE:ROLE - default List listCodeByUserId(String userId) { - return listByUserId(userId).stream().map(role -> "%s:%s".formatted(role.getModuleCode().name(), role.getCode())).toList(); - } - - /// 查询用户全部角色编码 - /// - /// @param userId 用户 ID - /// @return 包含子角色的角色编码列表,格式为 MODULE:ROLE - List listAllCodeByUserId(String userId); - - /// 查询用户全部权限 - /// - /// @param userId 用户 ID - /// @return 权限列表 - List listAllPermissionByUserId(String userId); - - /// 查询用户全部权限编码 - /// - /// @param userId 用户 ID - /// @return 权限编码列表,格式为 MODULE:PERMISSION - default List listAllPermissionCodeByUserId(String userId) { - return listAllPermissionByUserId(userId).stream() - .map(permission -> "%s:%s".formatted(permission.getModuleCode().name(), permission.getCode())) - .distinct() - .toList(); - } + List listByIdList(Collection idList); /// 查询直属子角色 /// @@ -80,4 +33,10 @@ public interface RoleService extends BaseService { /// @param roleId 角色 ID /// @return 权限列表 List listPermissionByRoleId(String roleId); + + /// 递归获取角色及其所有子角色的 ID 列表 + /// + /// @param idList 角色 ID 列表 + /// @return 包含所有子角色的 ID 列表 + List listAllRoleIdIncludingChildren(Collection idList); } diff --git a/src/main/java/com/imyeyu/api/modules/user/service/UserRoleService.java b/src/main/java/com/imyeyu/api/modules/user/service/UserRoleService.java index e2e3ee3..11423f7 100644 --- a/src/main/java/com/imyeyu/api/modules/user/service/UserRoleService.java +++ b/src/main/java/com/imyeyu/api/modules/user/service/UserRoleService.java @@ -1,9 +1,15 @@ package com.imyeyu.api.modules.user.service; +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.user.entity.Permission; +import com.imyeyu.api.modules.user.entity.Role; import com.imyeyu.api.modules.user.entity.UserRole; +import com.imyeyu.api.modules.user.vo.role.UserRoleAuthorizeRequest; import com.imyeyu.spring.service.BaseService; +import java.util.Collection; import java.util.List; +import java.util.Map; /// 用户角色绑定服务 /// @@ -16,11 +22,62 @@ public interface UserRoleService extends BaseService { /// @param list 用户角色绑定列表 void createBatch(List list); - /// 查询用户角色绑定 + /// 给用户授权角色 + /// + /// @param req 用户角色授权请求 + void authorizeUserRole(UserRoleAuthorizeRequest req); + + List listByUserId(String userId); + + /// 查询多个用户角色绑定 + /// + /// @param userIdList 用户 ID 列表 + /// @return 用户角色绑定列表 + List listByUserIdList(Collection userIdList); + + /// 查询用户直属角色 /// /// @param userId 用户 ID - /// @return 用户角色绑定列表 - List listByUserId(String userId); + /// @return 角色列表 + List listRoleByUserId(String userId); + + /// 查询用户指定模块直属角色 + /// + /// @param moduleCode 模块代码 + /// @param userId 用户 ID + /// @return 角色列表 + List listRoleByUserId(ModuleCode moduleCode, String userId); + + /// 查询用户直属角色编码 + /// + /// @param userId 用户 ID + /// @return 角色编码列表,格式为 MODULE:ROLE + default List listRoleCodeByUserId(String userId) { + return listRoleByUserId(userId).stream().map(role -> "%s:%s".formatted(role.getModuleCode().name(), role.getCode())).toList(); + } + + /// 查询用户全部角色编码 + /// + /// @param userId 用户 ID + /// @return 包含子角色的角色编码列表,格式为 MODULE:ROLE + List listAllRoleCodeByUserId(String userId); + + /// 查询用户全部权限 + /// + /// @param userId 用户 ID + /// @return 权限列表 + List listAllPermissionByUserId(String userId); + + /// 查询用户全部权限编码 + /// + /// @param userId 用户 ID + /// @return 权限编码列表,格式为 MODULE:PERMISSION + default List listAllPermissionCodeByUserId(String userId) { + return listAllPermissionByUserId(userId).stream() + .map(permission -> "%s:%s".formatted(permission.getModuleCode().name(), permission.getCode())) + .distinct() + .toList(); + } /// 查询用户直属角色 ID /// @@ -31,6 +88,12 @@ public interface UserRoleService extends BaseService { return bindingList.stream().map(UserRole::getRoleId).toList(); } + /// 按用户 ID 分组查询直属角色 + /// + /// @param idList 用户 ID 列表 + /// @return 用户角色映射 + Map> mapRoleListByUserIdList(Collection idList); + /// 删除用户指定角色绑定 /// /// @param userId 用户 ID diff --git a/src/main/java/com/imyeyu/api/modules/user/service/UserService.java b/src/main/java/com/imyeyu/api/modules/user/service/UserService.java index 465ab0e..c0b9fe2 100644 --- a/src/main/java/com/imyeyu/api/modules/user/service/UserService.java +++ b/src/main/java/com/imyeyu/api/modules/user/service/UserService.java @@ -3,6 +3,9 @@ package com.imyeyu.api.modules.user.service; import com.imyeyu.api.modules.user.entity.User; import com.imyeyu.spring.service.BaseService; +import java.util.Collection; +import java.util.Map; + /** * 用户管理服务 * @@ -20,4 +23,10 @@ public interface UserService extends BaseService { User getByPhoneNo(String phoneNo); User getByVerifiedPhoneNo(String phoneNo); + + void verifyEmail(String id); + + void verifyPhoneNo(String id); + + Map mapByIdList(Collection idList); } diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/PermissionCheckerImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/PermissionCheckerImplement.java index fd6427c..32e0f39 100644 --- a/src/main/java/com/imyeyu/api/modules/user/service/implement/PermissionCheckerImplement.java +++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/PermissionCheckerImplement.java @@ -4,6 +4,7 @@ import com.imyeyu.api.bean.ModuleCode; import com.imyeyu.api.modules.user.service.PermissionChecker; 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.java.bean.timi.TimiCode; import com.imyeyu.java.bean.timi.TimiException; import lombok.RequiredArgsConstructor; @@ -25,7 +26,7 @@ import java.util.Set; @RequiredArgsConstructor public class PermissionCheckerImplement implements PermissionChecker { - private final RoleService roleService; + private final UserRoleService userRoleService; private final UserLoginService userLoginService; public Set codeSet() { @@ -34,7 +35,7 @@ public class PermissionCheckerImplement implements PermissionChecker { @Override public Set codeSet(String userId) { - return new HashSet<>(roleService.listAllPermissionCodeByUserId(userId)); + return new HashSet<>(userRoleService.listAllPermissionCodeByUserId(userId)); } @Override diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleCheckerImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleCheckerImplement.java index 57a81f5..9fad2a1 100644 --- a/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleCheckerImplement.java +++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleCheckerImplement.java @@ -2,8 +2,8 @@ package com.imyeyu.api.modules.user.service.implement; import com.imyeyu.api.bean.ModuleCode; 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.java.bean.timi.TimiCode; import com.imyeyu.java.bean.timi.TimiException; import lombok.RequiredArgsConstructor; @@ -24,12 +24,12 @@ import java.util.Set; @RequiredArgsConstructor public class RoleCheckerImplement implements RoleChecker { - private final RoleService roleService; + private final UserRoleService userRoleService; private final UserLoginService userLoginService; @Override public Set codeSet() { - return new HashSet<>(roleService.listAllCodeByUserId(userLoginService.getRequireLoginUserId())); + return new HashSet<>(userRoleService.listAllRoleCodeByUserId(userLoginService.getRequireLoginUserId())); } @Override diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleServiceImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleServiceImplement.java index eb9aa83..6569e88 100644 --- a/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleServiceImplement.java @@ -2,23 +2,17 @@ package com.imyeyu.api.modules.user.service.implement; import com.imyeyu.api.bean.ModuleCode; import com.imyeyu.api.config.dbsource.TimiServerDBConfig; -import com.imyeyu.api.modules.common.entity.Multilingual; -import com.imyeyu.api.modules.common.mapper.MultilingualMapper; import com.imyeyu.api.modules.common.service.MultilingualService; import com.imyeyu.api.modules.user.entity.Permission; import com.imyeyu.api.modules.user.entity.Role; import com.imyeyu.api.modules.user.entity.RolePermission; import com.imyeyu.api.modules.user.entity.RoleRelation; -import com.imyeyu.api.modules.user.entity.UserRole; import com.imyeyu.api.modules.user.mapper.RoleMapper; import com.imyeyu.api.modules.user.mapper.RolePermissionMapper; import com.imyeyu.api.modules.user.mapper.RoleRelationMapper; import com.imyeyu.api.modules.user.service.PermissionService; import com.imyeyu.api.modules.user.service.RoleService; -import com.imyeyu.api.modules.user.service.UserRoleService; -import com.imyeyu.api.modules.user.vo.role.UserRoleAuthorizeReq; import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.timi.TimiCode; import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.service.AbstractEntityService; @@ -28,6 +22,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; +import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -42,9 +37,7 @@ import java.util.stream.Collectors; @RequiredArgsConstructor public class RoleServiceImplement extends AbstractEntityService implements RoleService { - private final UserRoleService userRoleService; private final PermissionService permissionService; - private final MultilingualMapper multilingualMapper; private final MultilingualService multilingualService; private final RoleMapper mapper; @@ -153,67 +146,6 @@ public class RoleServiceImplement extends AbstractEntityService im } } - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void authorizeUserRole(UserRoleAuthorizeReq req) { - TimiException.required(req, "not found req"); - TimiException.required(req.getUserId(), "not found req.userId"); - if (req.getModuleCode() == null) { - throw new TimiException(TimiCode.ARG_MISS, "moduleCode"); - } - List bindingList = userRoleService.listByUserId(req.getUserId()); - List bindingRoleIdList = bindingList.stream().map(UserRole::getRoleId).toList(); - Set dbIdSet = mapper.selectByIdList(bindingRoleIdList).stream() - .filter(role -> req.getModuleCode() == role.getModuleCode()) - .map(Role::getId) - .collect(Collectors.toSet()); - Set newIdSet = new HashSet<>(); - if (TimiJava.isNotEmpty(req.getRoleIdList())) { - newIdSet.addAll(mapper.selectByIdList(req.getRoleIdList()).stream() - .filter(role -> req.getModuleCode() == role.getModuleCode()) - .map(Role::getId) - .toList()); - } - if (TimiJava.isNotEmpty(req.getRoleCodeList())) { - newIdSet.addAll(mapper.selectByModuleCodeAndCodeList(req.getModuleCode(), req.getRoleCodeList()).stream().map(Role::getId).toList()); - } - Set deleteIdList = new HashSet<>(dbIdSet); - deleteIdList.removeAll(newIdSet); - - Set addIdList = new HashSet<>(newIdSet); - addIdList.removeAll(dbIdSet); - - if (TimiJava.isNotEmpty(deleteIdList)) { - userRoleService.deleteByRoleIdList(req.getUserId(), new ArrayList<>(deleteIdList)); - } - if (TimiJava.isNotEmpty(addIdList)) { - List roleBindingList = new ArrayList<>(); - long now = Time.now(); - for (String roleId : addIdList) { - UserRole roleBinding = new UserRole(); - roleBinding.setId(UUID.randomUUID().toString()); - roleBinding.setUserId(req.getUserId()); - roleBinding.setRoleId(roleId); - roleBinding.setCreatedAt(now); - roleBindingList.add(roleBinding); - } - userRoleService.createBatch(roleBindingList); - } - } - - @Override - public List listByUserId(String userId) { - return mapper.selectByIdList(userRoleService.listRoleIdByUserId(userId)); - } - - @Override - public List listByUserId(String userId, ModuleCode moduleCode) { - TimiException.required(moduleCode, "not found moduleCode"); - return listByUserId(userId).stream() - .filter(role -> moduleCode == role.getModuleCode()) - .toList(); - } - @Override public List listByModuleCode(ModuleCode moduleCode) { TimiException.required(moduleCode, "not found moduleCode"); @@ -221,20 +153,8 @@ public class RoleServiceImplement extends AbstractEntityService im } @Override - public List listAllCodeByUserId(String userId) { - List roleIdList = listByUserId(userId).stream().map(Role::getId).toList(); - List allRoleIdList = getAllRoleIdsIncludingChildren(roleIdList); - if (TimiJava.isEmpty(allRoleIdList)) { - return List.of(); - } - return mapper.selectByIdList(allRoleIdList).stream().map(role -> "%s:%s".formatted(role.getModuleCode().name(), role.getCode())).distinct().toList(); - } - - @Override - public List listAllPermissionByUserId(String userId) { - List roleIdList = listByUserId(userId).stream().map(Role::getId).toList(); - List allRoleIdList = getAllRoleIdsIncludingChildren(roleIdList); - return permissionService.listByRoleId(allRoleIdList); + public List listByIdList(Collection idList) { + return mapper.selectByIdList(new HashSet<>(idList)); } @Override @@ -248,20 +168,13 @@ public class RoleServiceImplement extends AbstractEntityService im @Override public List listPermissionByRoleId(String roleId) { - List allRoleIdList = getAllRoleIdsIncludingChildren(List.of(roleId)); + List allRoleIdList = listAllRoleIdIncludingChildren(List.of(roleId)); return permissionService.listByRoleId(allRoleIdList); } - /// 递归获取角色及其所有子角色的 ID 列表 - /// - /// @param roleIdList 角色 ID 列表 - /// @return 包含所有子角色的 ID 列表 - private List getAllRoleIdsIncludingChildren(List roleIdList) { - if (TimiJava.isEmpty(roleIdList)) { - return new ArrayList<>(); - } - Set allRoleIdSet = new HashSet<>(roleIdList); - List currentLevelRoleIdList = new ArrayList<>(roleIdList); + public List listAllRoleIdIncludingChildren(Collection idList) { + Set allRoleIdSet = new HashSet<>(idList); + List currentLevelRoleIdList = new ArrayList<>(idList); while (TimiJava.isNotEmpty(currentLevelRoleIdList)) { List childRoleIdList = roleRelationMapper.selectChildRoleIdListByParentRoleIdList(currentLevelRoleIdList); @@ -277,18 +190,4 @@ public class RoleServiceImplement extends AbstractEntityService im } return new ArrayList<>(allRoleIdSet); } - - private String saveNameMultilingual(String langId, String name) { - if (TimiJava.isEmpty(name)) { - return langId; - } - if (TimiJava.isEmpty(langId)) { - return multilingualService.create(name); - } - Multilingual multilingual = new Multilingual(); - multilingual.setId(langId); - multilingual.setZhCN(name); - multilingualMapper.updateSelective(multilingual); - return langId; - } } diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/UserLoginServiceImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/UserLoginServiceImplement.java index 8977985..c9d9455 100644 --- a/src/main/java/com/imyeyu/api/modules/user/service/implement/UserLoginServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/UserLoginServiceImplement.java @@ -9,6 +9,7 @@ import com.imyeyu.api.modules.user.bean.BootstrapData; import com.imyeyu.api.modules.user.entity.User; 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.api.modules.user.vo.LoginRequest; import com.imyeyu.api.modules.user.vo.LoginResponse; @@ -45,6 +46,7 @@ public class UserLoginServiceImplement implements UserLoginService, TimiJava { private final UserService userService; private final RoleService roleService; private final SettingService settingService; + private final UserRoleService userRoleService; private final AttachmentService attachmentService; private final Redis redisUserToken; @@ -86,8 +88,8 @@ public class UserLoginServiceImplement implements UserLoginService, TimiJava { result.setUser(userService.get(userId)); User user = result.getUser(); - user.setPermissionList(roleService.listAllPermissionCodeByUserId(user.getId())); - user.setRoleList(roleService.listAllCodeByUserId(user.getId())); + user.setPermissionList(userRoleService.listAllPermissionCodeByUserId(user.getId())); + user.setRoleList(userRoleService.listAllRoleCodeByUserId(user.getId())); user.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.USER, user.getId())); return result; } diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/UserRoleServiceImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/UserRoleServiceImplement.java index 1ed6dc2..769a1ea 100644 --- a/src/main/java/com/imyeyu/api/modules/user/service/implement/UserRoleServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/UserRoleServiceImplement.java @@ -1,14 +1,34 @@ package com.imyeyu.api.modules.user.service.implement; +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.config.dbsource.TimiServerDBConfig; +import com.imyeyu.api.modules.user.entity.Permission; +import com.imyeyu.api.modules.user.entity.Role; import com.imyeyu.api.modules.user.entity.UserRole; import com.imyeyu.api.modules.user.mapper.UserRoleMapper; +import com.imyeyu.api.modules.user.service.PermissionService; +import com.imyeyu.api.modules.user.service.RoleService; import com.imyeyu.api.modules.user.service.UserRoleService; +import com.imyeyu.api.modules.user.vo.role.UserRoleAuthorizeRequest; +import com.imyeyu.java.TimiJava; +import com.imyeyu.java.bean.timi.TimiCode; +import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.service.AbstractEntityService; +import com.imyeyu.utils.Time; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.function.Function; +import java.util.stream.Collectors; /// 用户角色服务实现 /// @@ -18,6 +38,9 @@ import java.util.List; @RequiredArgsConstructor public class UserRoleServiceImplement extends AbstractEntityService implements UserRoleService { + private final RoleService roleService; + private final PermissionService permissionService; + private final UserRoleMapper mapper; @Override @@ -30,6 +53,49 @@ public class UserRoleServiceImplement extends AbstractEntityService bindingList = listByUserId(req.getUserId()); + List bindingRoleIdList = bindingList.stream().map(UserRole::getRoleId).toList(); + Set dbIdSet = roleService.listByIdList(bindingRoleIdList) + .stream() + .filter(role -> req.getModuleCode() == role.getModuleCode()) + .map(Role::getId) + .collect(Collectors.toSet()); + Set newIdSet = new HashSet<>(); + if (TimiJava.isNotEmpty(req.getRoleIdList())) { + newIdSet.addAll(roleService.listByIdList(req.getRoleIdList()).stream().filter(role -> req.getModuleCode() == role.getModuleCode()).map(Role::getId).toList()); + } + Set deleteIdList = new HashSet<>(dbIdSet); + deleteIdList.removeAll(newIdSet); + + Set addIdList = new HashSet<>(newIdSet); + addIdList.removeAll(dbIdSet); + + if (TimiJava.isNotEmpty(deleteIdList)) { + deleteByRoleIdList(req.getUserId(), new ArrayList<>(deleteIdList)); + } + if (TimiJava.isNotEmpty(addIdList)) { + List roleBindingList = new ArrayList<>(); + long now = Time.now(); + for (String roleId : addIdList) { + UserRole roleBinding = new UserRole(); + roleBinding.setId(UUID.randomUUID().toString()); + roleBinding.setUserId(req.getUserId()); + roleBinding.setRoleId(roleId); + roleBinding.setCreatedAt(now); + roleBindingList.add(roleBinding); + } + createBatch(roleBindingList); + } + } + @Override public List listByUserId(String userId) { UserRole example = new UserRole(); @@ -37,6 +103,57 @@ public class UserRoleServiceImplement extends AbstractEntityService listByUserIdList(Collection userIdList) { + return mapper.selectByUserIdList(userIdList); + } + + @Override + public List listRoleByUserId(String userId) { + List userRoleList = listByUserId(userId); + return roleService.listByIdList(userRoleList.stream().map(UserRole::getRoleId).toList()); + } + + @Override + public List listRoleByUserId(ModuleCode moduleCode, String userId) { + TimiException.required(moduleCode, "not found moduleCode"); + return listRoleByUserId(userId).stream().filter(role -> moduleCode == role.getModuleCode()).toList(); + } + + @Override + public List listAllRoleCodeByUserId(String userId) { + List roleIdList = listRoleByUserId(userId).stream().map(Role::getId).toList(); + List allRoleIdList = roleService.listAllRoleIdIncludingChildren(roleIdList); + if (TimiJava.isEmpty(allRoleIdList)) { + return List.of(); + } + return roleService.listByIdList(allRoleIdList).stream().map(role -> "%s:%s".formatted(role.getModuleCode().name(), role.getCode())).distinct().toList(); + } + + @Override + public List listAllPermissionByUserId(String userId) { + List roleIdList = listRoleByUserId(userId).stream().map(Role::getId).toList(); + List allRoleIdList = roleService.listAllRoleIdIncludingChildren(roleIdList); + return permissionService.listByRoleId(allRoleIdList); + } + + @Override + public Map> mapRoleListByUserIdList(Collection idList) { + if (TimiJava.isEmpty(idList)) { + return Map.of(); + } + List userRoleList = listByUserIdList(idList); + if (TimiJava.isEmpty(userRoleList)) { + return Map.of(); + } + Map roleMap = roleService.listByIdList(userRoleList.stream().map(UserRole::getRoleId).distinct().toList()) + .stream() + .collect(Collectors.toMap(Role::getId, Function.identity())); + return userRoleList.stream() + .filter(userRole -> roleMap.containsKey(userRole.getRoleId())) + .collect(Collectors.groupingBy(UserRole::getUserId, Collectors.mapping(userRole -> roleMap.get(userRole.getRoleId()), Collectors.toList()))); + } + @Override public void deleteByRoleIdList(String userId, List roleIdList) { mapper.deleteByRoleIdList(userId, roleIdList); diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/UserServiceImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/UserServiceImplement.java index 33a79c3..0f0ba8f 100644 --- a/src/main/java/com/imyeyu/api/modules/user/service/implement/UserServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/UserServiceImplement.java @@ -11,11 +11,18 @@ import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.service.AbstractEntityService; import com.imyeyu.utils.Digest; import com.imyeyu.utils.Regex; +import com.imyeyu.utils.Time; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + /** * 用户管理服务实现 * @@ -27,8 +34,6 @@ import org.springframework.stereotype.Service; @RequiredArgsConstructor(onConstructor_ = {@Lazy}) public class UserServiceImplement extends AbstractEntityService implements UserService, TimiJava { - private static final String REGEX_PHONE_NO = "^1\\d{10}$"; - private final UserMapper mapper; @Override @@ -51,7 +56,7 @@ public class UserServiceImplement extends AbstractEntityService im } { if (TimiJava.isNotEmpty(user.getPhoneNo())) { - validatePhoneNo(user.getPhoneNo()); + TimiException.requiredTrue(user.isValidPhone(), "invalid phone"); User byPhoneNo = getByVerifiedPhoneNo(user.getPhoneNo()); TimiException.requiredNull(byPhoneNo, "existed phone number"); } @@ -65,7 +70,6 @@ public class UserServiceImplement extends AbstractEntityService im dbUser.setPassword(Digest.argon2id(user.getPassword())); dbUser.setEmail(user.getEmail()); dbUser.setPhoneNo(user.getPhoneNo()); - dbUser.setPhoneNoVerifyAt(user.getPhoneNoVerifyAt()); dbUser.setExp(TimiJava.defaultIfNull(user.getExp(), 0)); super.create(dbUser); user.setId(dbUser.getId()); @@ -88,7 +92,7 @@ public class UserServiceImplement extends AbstractEntityService im TimiException.requiredTrue(byEmail == null || byEmail.getId().equals(dbUser.getId()), "existed email"); } if (TimiJava.isNotEmpty(phoneNo)) { - validatePhoneNo(phoneNo); + TimiException.requiredTrue(user.isValidPhone(), "invalid phone"); if (!phoneNo.equals(dbUser.getPhoneNo())) { User byPhoneNo = getByVerifiedPhoneNo(phoneNo); TimiException.requiredTrue(byPhoneNo == null || byPhoneNo.getId().equals(dbUser.getId()), "existed phone number"); @@ -138,7 +142,22 @@ public class UserServiceImplement extends AbstractEntityService im return mapper.selectByVerifiedPhoneNo(phoneNo); } - private void validatePhoneNo(String phoneNo) { - TimiException.requiredTrue(Regex.isMatch(REGEX_PHONE_NO, phoneNo), "invalid phone number"); + @Override + public void verifyEmail(String id) { + User user = get(id); + user.setEmailVerifyAt(Time.now()); + update(user); + } + + @Override + public void verifyPhoneNo(String id) { + User user = get(id); + user.setPhoneNoVerifyAt(Time.now()); + update(user); + } + + @Override + public Map mapByIdList(Collection idList) { + return mapper.listByIdList(new HashSet<>(idList)).stream().collect(Collectors.toMap(User::getId, Function.identity())); } } diff --git a/src/main/java/com/imyeyu/api/modules/user/vo/role/UserRoleAuthorizeReq.java b/src/main/java/com/imyeyu/api/modules/user/vo/role/UserRoleAuthorizeRequest.java similarity index 79% rename from src/main/java/com/imyeyu/api/modules/user/vo/role/UserRoleAuthorizeReq.java rename to src/main/java/com/imyeyu/api/modules/user/vo/role/UserRoleAuthorizeRequest.java index 1187ddf..3c70a32 100644 --- a/src/main/java/com/imyeyu/api/modules/user/vo/role/UserRoleAuthorizeReq.java +++ b/src/main/java/com/imyeyu/api/modules/user/vo/role/UserRoleAuthorizeRequest.java @@ -10,13 +10,11 @@ import java.util.List; /// @author 夜雨 /// @since 2026-07-17 14:16 @Data -public class UserRoleAuthorizeReq { +public class UserRoleAuthorizeRequest { private String userId; private ModuleCode moduleCode; private List roleIdList; - - private List roleCodeList; } diff --git a/src/main/java/com/imyeyu/api/util/InitBuiltinUser.java b/src/main/java/com/imyeyu/api/util/InitBuiltinUser.java index 5850f68..6dfad63 100644 --- a/src/main/java/com/imyeyu/api/util/InitBuiltinUser.java +++ b/src/main/java/com/imyeyu/api/util/InitBuiltinUser.java @@ -192,6 +192,7 @@ public class InitBuiltinUser implements ApplicationRunner { private void bindBuiltinPermission(Map roleMap, Map permissionMap) { Role systemRole = roleMap.get(BuiltinAuthRegistry.toRoleKey(BootstrapData.SYS_ROLE_MODULE_CODE, BootstrapData.SYS_ROLE_CODE)); Role adminRole = roleMap.get(BuiltinAuthRegistry.toRoleKey(BootstrapData.ADMIN_ROLE_MODULE_CODE, BootstrapData.ADMIN_ROLE_CODE)); + Role gaoGlobalManagerRole = roleMap.get(BuiltinAuthRegistry.toRoleKey(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())); Role gaoStoreManagerRole = roleMap.get(BuiltinAuthRegistry.toRoleKey(ModuleCode.GAO, GaoRoleCode.STORE_MANAGER.name())); for (BuiltinPermissionCode item : BuiltinAuthRegistry.PERMISSION_CODE_LIST) { Permission permission = permissionMap.get(BuiltinAuthRegistry.toPermissionKey(item.getModuleCode(), item.getValue())); @@ -204,6 +205,9 @@ public class InitBuiltinUser implements ApplicationRunner { if (item.isGrantToAdmin() && adminRole != null) { createRolePermissionIfAbsent(adminRole.getId(), permission.getId()); } + if (item instanceof GaoPermissionCode gaoPermission && gaoPermission.isGrantToGlobalManager() && gaoGlobalManagerRole != null) { + createRolePermissionIfAbsent(gaoGlobalManagerRole.getId(), permission.getId()); + } if (item instanceof GaoPermissionCode gaoPermission && gaoPermission.isGrantToStoreManager() && gaoStoreManagerRole != null) { createRolePermissionIfAbsent(gaoStoreManagerRole.getId(), permission.getId()); } diff --git a/src/main/resources/db/migration/timiserver/V24__add_gao_store_scope.sql b/src/main/resources/db/migration/timiserver/V24__add_gao_store_scope.sql new file mode 100644 index 0000000..3474b47 --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V24__add_gao_store_scope.sql @@ -0,0 +1,192 @@ +-- 新增门店主表。active_code / active_name 沿用项目现有软删除唯一索引方案: +-- 未删除数据参与唯一约束,已删除数据生成 NULL,不阻塞后续重建同编码或同名门店。 +CREATE TABLE IF NOT EXISTS `gao_store` ( + `id` VARCHAR(36) NOT NULL, + `code` VARCHAR(64) NOT NULL COMMENT '门店编码', + `active_code` VARCHAR(64) GENERATED ALWAYS AS ( + CASE WHEN `deleted_at` IS NULL THEN NULLIF(TRIM(`code`), '') ELSE NULL END + ) STORED COMMENT '未删除门店编码' INVISIBLE, + `name` VARCHAR(64) NOT NULL COMMENT '门店名称', + `active_name` VARCHAR(64) GENERATED ALWAYS AS ( + CASE WHEN `deleted_at` IS NULL THEN NULLIF(TRIM(`name`), '') ELSE NULL END + ) STORED COMMENT '未删除门店名称' INVISIBLE, + `status` VARCHAR(32) NOT NULL COMMENT '门店状态', + `telephone` VARCHAR(32) NULL COMMENT '电话', + `address` VARCHAR(255) NULL COMMENT '地址', + `remark` TEXT NULL COMMENT '备注', + `sort` INT NOT NULL DEFAULT 0 COMMENT '排序值', + `created_at` BIGINT NOT NULL, + `updated_at` BIGINT NULL, + `deleted_at` BIGINT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_gao_store_active_code` (`active_code`), + UNIQUE KEY `uk_gao_store_active_name` (`active_name`), + KEY `idx_gao_store_status_sort` (`status`, `deleted_at`, `sort`, `created_at`) +) COMMENT='GAO 门店'; + +-- 旧数据来自单门店模型,迁移时全部归入固定默认门店。 +-- 使用固定 ID 而不是 UUID(),避免迁移中断后排查或手工重跑时产生多个默认门店。 +SET @gao_default_store_id = '00000000-0000-0000-0000-000000000001'; +SET @gao_now = ROUND(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000); + +-- Flyway 正常不会重复执行版本脚本;这里保留 ON DUPLICATE KEY,方便本地或预发排查时手工重放片段。 +INSERT INTO `gao_store` ( + `id`, + `code`, + `name`, + `status`, + `telephone`, + `address`, + `remark`, + `sort`, + `created_at` +) VALUES ( + @gao_default_store_id, + 'DEFAULT', + '默认门店', + 'ACTIVE', + NULL, + NULL, + '系统迁移自动创建', + 0, + @gao_now +) ON DUPLICATE KEY UPDATE + `name` = VALUES(`name`), + `status` = VALUES(`status`), + `updated_at` = @gao_now; + +-- 门店营业日历是连续登记统计的事实维度。 +-- business_day_no 只给营业日使用,休息日 business_day_no 为 NULL。 +-- 后续统计连续登记时按 business_day_no 连续,不按自然日连续。 +CREATE TABLE IF NOT EXISTS `gao_store_business_day` ( + `id` VARCHAR(36) NOT NULL, + `store_id` VARCHAR(36) NOT NULL COMMENT '门店 ID', + `date_value` INT NOT NULL COMMENT '日期值,格式为 yyyyMMdd', + `active_date_value` INT GENERATED ALWAYS AS ( + CASE WHEN `deleted_at` IS NULL THEN `date_value` ELSE NULL END + ) STORED COMMENT '未删除日期值' INVISIBLE, + `open_status` VARCHAR(32) NOT NULL COMMENT '营业状态', + `business_day_no` BIGINT NULL COMMENT '营业日递增序号,仅营业日有值', + `active_business_day_no` BIGINT GENERATED ALWAYS AS ( + CASE WHEN `deleted_at` IS NULL AND `open_status` = 'OPEN' THEN `business_day_no` ELSE NULL END + ) STORED COMMENT '未删除营业日序号' INVISIBLE, + `reason` VARCHAR(255) NULL COMMENT '原因', + `created_at` BIGINT NOT NULL, + `updated_at` BIGINT NULL, + `deleted_at` BIGINT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_gao_store_business_day_active_date` (`store_id`, `active_date_value`), + UNIQUE KEY `uk_gao_store_business_day_active_no` (`store_id`, `active_business_day_no`), + KEY `idx_gao_store_business_day_open_no` (`store_id`, `open_status`, `business_day_no`) +) COMMENT='GAO 门店营业日'; + +-- 客户归属门店。先允许 NULL,加字段后回填,再改 NOT NULL,避免已有数据直接违反约束。 +ALTER TABLE `gao_customer` + ADD COLUMN `store_id` VARCHAR(36) NULL COMMENT '门店 ID' AFTER `id`; + +UPDATE `gao_customer` +SET `store_id` = @gao_default_store_id +WHERE `store_id` IS NULL; + +ALTER TABLE `gao_customer` + MODIFY COLUMN `store_id` VARCHAR(36) NOT NULL COMMENT '门店 ID', + -- 客户编码、姓名从全局唯一改为门店内唯一。 + DROP KEY `uk_gao_customer_active_code`, + DROP KEY `uk_gao_customer_active_name`, + ADD UNIQUE KEY `uk_gao_customer_store_active_code` (`store_id`, `active_code`), + ADD UNIQUE KEY `uk_gao_customer_store_active_name` (`store_id`, `active_name`), + ADD KEY `idx_gao_customer_store_created_at` (`store_id`, `deleted_at`, `created_at`); + +-- 登记事件归属门店。历史事件统一归入默认门店。 +ALTER TABLE `gao_event` + ADD COLUMN `store_id` VARCHAR(36) NULL COMMENT '门店 ID' AFTER `id`; + +UPDATE `gao_event` +SET `store_id` = @gao_default_store_id +WHERE `store_id` IS NULL; + +ALTER TABLE `gao_event` + MODIFY COLUMN `store_id` VARCHAR(36) NOT NULL COMMENT '门店 ID', + ADD KEY `idx_gao_event_store_status` (`store_id`, `status`, `deleted_at`, `sort`, `created_at`); + +-- 登记记录是事实表,需要保存门店和营业日快照。 +-- store_id:优先跟随历史登记事件所属门店,找不到事件时兜底默认门店。 +-- registered_date_value:由 registered_at 计算 yyyyMMdd,避免查询时反复做时间函数。 +-- business_day_no:按门店营业日历回填,用于连续登记统计。 +ALTER TABLE `gao_event_record` + ADD COLUMN `store_id` VARCHAR(36) NULL COMMENT '门店 ID' AFTER `customer_id`, + ADD COLUMN `registered_date_value` INT NULL COMMENT '登记日期值,格式为 yyyyMMdd' AFTER `registered_at`, + ADD COLUMN `business_day_no` BIGINT NULL COMMENT '营业日递增序号' AFTER `registered_date_value`; + +UPDATE `gao_event_record` r +LEFT JOIN `gao_event` e ON r.`event_id` = e.`id` +SET + r.`store_id` = COALESCE(e.`store_id`, @gao_default_store_id), + r.`registered_date_value` = CAST(DATE_FORMAT(FROM_UNIXTIME(r.`registered_at` / 1000), '%Y%m%d') AS UNSIGNED) +WHERE + r.`store_id` IS NULL + OR r.`registered_date_value` IS NULL; + +-- 以历史登记记录中实际发生过登记的日期生成营业日。 +-- 这是最平滑的迁移口径:有登记的日期一定视为营业日;没有登记的历史日期不主动生成休息日或营业日。 +-- 如果门店历史休息日需要展示,可在迁移后通过营业日接口补录 CLOSED 数据,不影响已有登记记录。 +INSERT INTO `gao_store_business_day` ( + `id`, + `store_id`, + `date_value`, + `open_status`, + `business_day_no`, + `reason`, + `created_at` +) +SELECT + UUID(), + d.`store_id`, + d.`registered_date_value`, + 'OPEN', + ROW_NUMBER() OVER (PARTITION BY d.`store_id` ORDER BY d.`registered_date_value`), + '系统迁移按历史登记日期生成', + @gao_now +FROM ( + SELECT DISTINCT + `store_id`, + `registered_date_value` + FROM `gao_event_record` + WHERE + `store_id` IS NOT NULL + AND `registered_date_value` IS NOT NULL +) d; + +-- 把营业日序号快照回填到历史登记记录。 +-- 后续即使营业日历被调整,历史记录仍保留迁移时的事实快照;如需重算,应做显式重算脚本。 +UPDATE `gao_event_record` r +LEFT JOIN `gao_store_business_day` d ON r.`store_id` = d.`store_id` AND r.`registered_date_value` = d.`date_value` AND d.`deleted_at` IS NULL +SET r.`business_day_no` = d.`business_day_no` +WHERE r.`business_day_no` IS NULL; + +ALTER TABLE `gao_event_record` + MODIFY COLUMN `store_id` VARCHAR(36) NOT NULL COMMENT '门店 ID', + MODIFY COLUMN `registered_date_value` INT NOT NULL COMMENT '登记日期值,格式为 yyyyMMdd', + MODIFY COLUMN `business_day_no` BIGINT NOT NULL COMMENT '营业日递增序号', + ADD KEY `idx_gao_event_record_store_registered_at` (`store_id`, `deleted_at`, `registered_at`), + ADD KEY `idx_gao_event_record_store_date` (`store_id`, `registered_date_value`, `deleted_at`), + ADD KEY `idx_gao_event_record_store_customer_time` (`store_id`, `customer_id`, `deleted_at`, `registered_at`), + ADD KEY `idx_gao_event_record_store_event_time` (`store_id`, `event_id`, `deleted_at`, `registered_at`), + ADD KEY `idx_gao_event_record_store_customer_day` (`store_id`, `customer_id`, `business_day_no`); + +-- GAO 用户归属门店。用户可在不同门店分别存在一条 GAO 用户记录。 +-- 注意:当前用户角色关系仍是用户维度,不是门店维度;不同门店不同角色需要后续扩展 user_role 作用域。 +ALTER TABLE `gao_user` + ADD COLUMN `store_id` VARCHAR(36) NULL COMMENT '门店 ID' AFTER `id`, + ADD COLUMN `active_user_id` VARCHAR(36) GENERATED ALWAYS AS ( + CASE WHEN `deleted_at` IS NULL THEN `user_id` ELSE NULL END + ) STORED COMMENT '未删除用户 ID' INVISIBLE; + +UPDATE `gao_user` +SET `store_id` = @gao_default_store_id +WHERE `store_id` IS NULL; + +ALTER TABLE `gao_user` + MODIFY COLUMN `store_id` VARCHAR(36) NOT NULL COMMENT '门店 ID', + ADD UNIQUE KEY `uk_gao_user_store_active_user` (`store_id`, `active_user_id`), + ADD KEY `idx_gao_user_store_enabled` (`store_id`, `enabled`, `deleted_at`, `created_at`); diff --git a/src/main/resources/db/migration/timiserver/V25__restrict_gao_user_to_single_store.sql b/src/main/resources/db/migration/timiserver/V25__restrict_gao_user_to_single_store.sql new file mode 100644 index 0000000..df810d9 --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V25__restrict_gao_user_to_single_store.sql @@ -0,0 +1,8 @@ +-- V24 已经上线为“用户可在多个门店分别存在 GAO 用户记录”。 +-- 现在业务改为一个用户只能归属一个门店,所以不能再改 V24,必须通过 V25 切换唯一约束。 + +-- 如果线上已经存在同一 user_id 绑定多个未删除门店的数据,下面新增唯一索引会失败。 +-- 这是正确行为:这种数据无法自动判断保留哪个门店,必须人工先清理重复 GAO 用户记录。 +ALTER TABLE `gao_user` + DROP KEY `uk_gao_user_store_active_user`, + ADD UNIQUE KEY `uk_gao_user_active_user` (`active_user_id`); diff --git a/src/main/resources/mapper/timi-server/GaoCustomerMapper.xml b/src/main/resources/mapper/timi-server/GaoCustomerMapper.xml index 6cbed41..ae69942 100644 --- a/src/main/resources/mapper/timi-server/GaoCustomerMapper.xml +++ b/src/main/resources/mapper/timi-server/GaoCustomerMapper.xml @@ -6,10 +6,16 @@ * FROM `gao_customer` WHERE - `id` IN - - #{id} - + TRUE + + AND `id` IN + + #{id} + + + + AND FALSE + AND `deleted_at` IS NULL @@ -19,7 +25,8 @@ COUNT(1) AS `newCustomerCount` FROM `gao_customer` WHERE - `created_at` >= #{beginCreatedAt} + `store_id` = #{storeId} + AND `created_at` >= #{beginCreatedAt} AND `created_at` <= #{endCreatedAt} AND `deleted_at` IS NULL GROUP BY DATE_FORMAT(FROM_UNIXTIME(`created_at` / 1000), '%Y%m%d') @@ -31,7 +38,8 @@ COUNT(1) FROM `gao_customer` WHERE - `created_at` < #{beginCreatedAt} + `store_id` = #{storeId} + AND `created_at` < #{beginCreatedAt} AND `deleted_at` IS NULL @@ -41,7 +49,8 @@ COUNT(1) AS `count` FROM `gao_customer` WHERE - `deleted_at` IS NULL + `store_id` = #{storeId} + AND `deleted_at` IS NULL GROUP BY IFNULL(CAST(`gender` AS CHAR), 'UNKNOWN') diff --git a/src/main/resources/mapper/timi-server/GaoEventMapper.xml b/src/main/resources/mapper/timi-server/GaoEventMapper.xml index 8750dcd..9b08997 100644 --- a/src/main/resources/mapper/timi-server/GaoEventMapper.xml +++ b/src/main/resources/mapper/timi-server/GaoEventMapper.xml @@ -24,7 +24,8 @@ * FROM `gao_event` WHERE - `status` = '${@com.imyeyu.api.modules.gao.entity.GaoEvent$Status@ACTIVE.name()}' + `store_id` = #{storeId} + AND `status` = '${@com.imyeyu.api.modules.gao.entity.GaoEvent$Status@ACTIVE.name()}' AND (`begin_at` IS NULL OR `begin_at` <= #{now}) AND (`end_at` IS NULL OR #{now} <= `end_at`) AND `deleted_at` IS NULL diff --git a/src/main/resources/mapper/timi-server/GaoEventRecordMapper.xml b/src/main/resources/mapper/timi-server/GaoEventRecordMapper.xml index aaea72d..2a05053 100644 --- a/src/main/resources/mapper/timi-server/GaoEventRecordMapper.xml +++ b/src/main/resources/mapper/timi-server/GaoEventRecordMapper.xml @@ -8,6 +8,9 @@ `gao_event_record` WHERE TRUE + + AND `store_id` = #{storeId} + AND `customer_id` = #{customerId} @@ -18,19 +21,48 @@ AND `registered_at` >= #{beginAt} - AND `registered_at` >= #{endAt} + AND `registered_at` <= #{endAt} AND `deleted_at` IS NULL + + + + UPDATE `gao_event_record` + SET + `business_day_no` = #{businessDayNo}, + `updated_at` = ROUND(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000) + WHERE + `store_id` = #{storeId} + AND `registered_date_value` = #{registeredDateValue} + AND `deleted_at` IS NULL + + FROM `gao_event_record` r LEFT JOIN `gao_customer` c ON r.`customer_id` = c.`id` - r.`registered_at` >= #{beginAt} - AND r.`registered_at` <= #{endAt} + TRUE + + AND r.`registered_at` >= #{beginAt} + + + AND r.`registered_at` <= #{endAt} + AND r.`deleted_at` IS NULL + + AND r.`store_id` = #{storeId} + AND r.`customer_id` = #{customerId} @@ -69,6 +101,9 @@ r.`deleted_at` IS NULL AND c.`deleted_at` IS NULL + + AND r.`store_id` = #{storeId} + AND r.`customer_id` = #{customerId} @@ -97,7 +132,7 @@ WITH `daily_stat` AS ( SELECT r.`customer_id` AS `customerId`, - DATE(FROM_UNIXTIME(r.`registered_at` / 1000)) AS `eventDate`, + r.`business_day_no` AS `businessDayNo`, COUNT(1) AS `dailyCount`, MAX(r.`registered_at`) AS `latestRegisteredAt` FROM `gao_event_record` r @@ -106,7 +141,7 @@ GROUP BY r.`customer_id`, - DATE(FROM_UNIXTIME(r.`registered_at` / 1000)) + r.`business_day_no` ), `rank_stat` AS ( SELECT @@ -120,14 +155,14 @@ `continuous_ordered` AS ( SELECT `customerId`, - `eventDate`, - ROW_NUMBER() OVER (PARTITION BY `customerId` ORDER BY `eventDate` DESC) AS `dayIndex` + `businessDayNo`, + ROW_NUMBER() OVER (PARTITION BY `customerId` ORDER BY `businessDayNo` DESC) AS `dayIndex` FROM `daily_stat` ), `latest_date` AS ( SELECT `customerId`, - MAX(`eventDate`) AS `latestEventDate` + MAX(`businessDayNo`) AS `latestBusinessDayNo` FROM `daily_stat` GROUP BY `customerId` ), @@ -137,7 +172,7 @@ COUNT(1) AS `recentContinuousEventDays` FROM `continuous_ordered` o LEFT JOIN `latest_date` l ON o.`customerId` = l.`customerId` - WHERE DATE_ADD(o.`eventDate`, INTERVAL o.`dayIndex` DAY) = DATE_ADD(l.`latestEventDate`, INTERVAL 1 DAY) + WHERE o.`businessDayNo` + o.`dayIndex` = l.`latestBusinessDayNo` + 1 GROUP BY o.`customerId` ), `ranked` AS ( diff --git a/src/main/resources/mapper/timi-server/GaoStoreMapper.xml b/src/main/resources/mapper/timi-server/GaoStoreMapper.xml new file mode 100644 index 0000000..b30c92e --- /dev/null +++ b/src/main/resources/mapper/timi-server/GaoStoreMapper.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/src/main/resources/mapper/timi-server/RoleMapper.xml b/src/main/resources/mapper/timi-server/RoleMapper.xml index ddda0ca..736551c 100644 --- a/src/main/resources/mapper/timi-server/RoleMapper.xml +++ b/src/main/resources/mapper/timi-server/RoleMapper.xml @@ -6,7 +6,7 @@ * FROM `role` WHERE - 1 = 1 + TRUE AND `id` IN @@ -14,29 +14,10 @@ - AND 1 = 0 + AND FALSE AND `deleted_at` IS NULL - - diff --git a/src/main/resources/mapper/timi-server/UserRoleMapper.xml b/src/main/resources/mapper/timi-server/UserRoleMapper.xml index 55ce81d..e3e8a16 100644 --- a/src/main/resources/mapper/timi-server/UserRoleMapper.xml +++ b/src/main/resources/mapper/timi-server/UserRoleMapper.xml @@ -12,6 +12,23 @@ (#{item.id}, #{item.userId}, #{item.roleId}, #{item.createdAt}) + UPDATE `user_role` SET `deleted_at` = UNIX_TIMESTAMP() -- 2.54.0 From 67bc9382c5c15799ed7936af325736bc9b32dc82 Mon Sep 17 00:00:00 2001 From: Timi Date: Fri, 7 Aug 2026 11:49:17 +0800 Subject: [PATCH 04/12] add store sort api --- .../gao/controller/GaoStoreController.java | 9 +++++++ .../modules/gao/mapper/GaoStoreMapper.java | 5 ++++ .../modules/gao/service/GaoStoreService.java | 6 +++++ .../implement/GaoStoreServiceImplement.java | 26 ++++++++++++++++++- .../mapper/timi-server/GaoStoreMapper.xml | 17 ++++++++++++ 5 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreController.java index 86f6229..008bc8b 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreController.java +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreController.java @@ -25,6 +25,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; +import java.util.List; /// GAO 门店接口 /// @@ -80,6 +81,14 @@ public class GaoStoreController { service.delete(id); } + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STORE_UPDATE) + @PostMapping("/sort") + public void sort(@RequestBody List idList) { + service.sort(idList); + } + @AOPLog @RequestRateLimit @IgnoreGlobalReturn 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 index 36eaf89..0dcb790 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoStoreMapper.java +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoStoreMapper.java @@ -4,6 +4,9 @@ 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 /// @@ -11,6 +14,8 @@ import org.apache.ibatis.annotations.Param; /// @since 2026-08-06 public interface GaoStoreMapper extends BaseMapper { + List selectByIdList(@Param("idList") Collection idList); + /// 查询用户所属的可用门店 /// /// @param userId 用户 ID 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 index 5036e71..3529698 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreService.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreService.java @@ -5,6 +5,7 @@ import com.imyeyu.spring.bean.Page; import com.imyeyu.spring.service.BaseService; import java.io.IOException; +import java.util.List; /// /// GAO 门店服务 @@ -24,4 +25,9 @@ public interface GaoStoreService extends BaseService { /// @param page 分页查询条件 /// @return xlsx 文件字节 byte[] export(Page page) throws IOException; + + /// 按客户端提交的顺序更新排序 + /// + /// @param idList 门店 ID 列表 + void sort(List idList); } diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreServiceImplement.java index 9d66219..ca70e60 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreServiceImplement.java @@ -1,6 +1,7 @@ package com.imyeyu.api.modules.gao.service.implement; import com.fasterxml.jackson.databind.ObjectMapper; +import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.api.modules.gao.entity.GaoStore; import com.imyeyu.api.modules.gao.mapper.GaoStoreMapper; import com.imyeyu.api.modules.gao.service.GaoStoreService; @@ -16,9 +17,14 @@ import com.imyeyu.spring.service.AbstractEntityService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.io.IOException; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; /// /// GAO 门店服务实现 @@ -89,7 +95,6 @@ public class GaoStoreServiceImplement extends AbstractEntityService idList) { + if (TimiJava.isEmpty(idList)) { + return; + } + + List storeList = mapper.selectByIdList(new LinkedHashSet<>(idList)); + Map storeMap = storeList.stream().collect(Collectors.toMap(GaoStore::getId, Function.identity())); + for (int i = 0; i < idList.size(); i++) { + GaoStore store = storeMap.get(idList.get(i)); + if (store == null) { + continue; + } + store.setSort(i); + mapper.update(store); + } + } + private void checkUnique(GaoStore store) { GaoStore codeExample = new GaoStore(); codeExample.setCode(store.getCode()); diff --git a/src/main/resources/mapper/timi-server/GaoStoreMapper.xml b/src/main/resources/mapper/timi-server/GaoStoreMapper.xml index b30c92e..1bf3891 100644 --- a/src/main/resources/mapper/timi-server/GaoStoreMapper.xml +++ b/src/main/resources/mapper/timi-server/GaoStoreMapper.xml @@ -1,6 +1,23 @@ + + SELECT + * + FROM `permission` + WHERE + TRUE + + AND `id` IN + + #{item} + + + + AND FALSE + + AND `deleted_at` IS NULL + + + diff --git a/src/main/resources/mapper/timi-server/RoleMapper.xml b/src/main/resources/mapper/timi-server/RoleMapper.xml index 736551c..2ae6f1b 100644 --- a/src/main/resources/mapper/timi-server/RoleMapper.xml +++ b/src/main/resources/mapper/timi-server/RoleMapper.xml @@ -1,6 +1,15 @@ + + + diff --git a/src/main/resources/mapper/timi-server/RolePermissionDelegationMapper.xml b/src/main/resources/mapper/timi-server/RolePermissionDelegationMapper.xml new file mode 100644 index 0000000..5a00cd6 --- /dev/null +++ b/src/main/resources/mapper/timi-server/RolePermissionDelegationMapper.xml @@ -0,0 +1,53 @@ + + + + + + + INSERT INTO `role_permission_delegation` ( + id, + role_id, + permission_id, + created_by, + created_at + ) VALUES + + (#{item.id}, #{item.roleId}, #{item.permissionId}, #{item.createdBy}, #{item.createdAt}) + + + + UPDATE `role_permission_delegation` + SET `deleted_at` = UNIX_TIMESTAMP() + WHERE + `role_id` = #{roleId} + AND `permission_id` IN + + #{item} + + AND `deleted_at` IS NULL + + diff --git a/src/main/resources/mapper/timi-server/RoleRelationMapper.xml b/src/main/resources/mapper/timi-server/RolePermissionInheritMapper.xml similarity index 53% rename from src/main/resources/mapper/timi-server/RoleRelationMapper.xml rename to src/main/resources/mapper/timi-server/RolePermissionInheritMapper.xml index dea0b6e..8b7510a 100644 --- a/src/main/resources/mapper/timi-server/RoleRelationMapper.xml +++ b/src/main/resources/mapper/timi-server/RolePermissionInheritMapper.xml @@ -1,10 +1,10 @@ - - SELECT * - FROM `role_relation` + FROM `role_permission_inherit` WHERE `parent_role_id` = #{parentRoleId} AND `deleted_at` IS NULL @@ -12,15 +12,26 @@ + - INSERT INTO `role_relation` ( + INSERT INTO `role_permission_inherit` ( id, parent_role_id, child_role_id, + owner_type, + owner_id, + created_by, created_at ) VALUES - - (#{item.id}, #{item.parentRoleId}, #{item.childRoleId}, #{item.createdAt}) + + (#{item.id}, #{item.parentRoleId}, #{item.childRoleId}, #{item.ownerType}, #{item.ownerId}, #{item.createdBy}, #{item.createdAt}) - UPDATE `role_relation` - SET `deleted_at` = UNIX_TIMESTAMP() + UPDATE `role_permission_inherit` + SET `deleted_at` = UNIX_TIMESTAMP() * 1000 WHERE `parent_role_id` = #{parentRoleId} AND `child_role_id` IN #{item} + AND `owner_type` = #{ownerType} + + AND `owner_id` = #{ownerId} + + + AND `owner_id` IS NULL + diff --git a/src/main/resources/mapper/timi-server/RolePermissionMapper.xml b/src/main/resources/mapper/timi-server/RolePermissionMapper.xml index 8efab7c..3c223c7 100644 --- a/src/main/resources/mapper/timi-server/RolePermissionMapper.xml +++ b/src/main/resources/mapper/timi-server/RolePermissionMapper.xml @@ -13,7 +13,7 @@ AND `role_permission`.`deleted_at` IS NULL + SELECT * + FROM `gao_point_account` + WHERE `store_id` = #{storeId} + AND `customer_id` = #{customerId} + AND `deleted_at` IS NULL + LIMIT 1 + + + + INSERT INTO `gao_point_account` ( + `id`, `store_id`, `customer_id`, `balance`, `total_earned`, `total_revoked`, `total_adjusted`, `version`, `created_at`, `updated_at`, `deleted_at` + ) VALUES ( + #{account.id}, #{account.storeId}, #{account.customerId}, #{account.balance}, #{account.totalEarned}, #{account.totalRevoked}, #{account.totalAdjusted}, #{account.version}, #{account.createdAt}, #{account.updatedAt}, #{account.deletedAt} + ) + ON DUPLICATE KEY UPDATE `id` = `id` + + + + + + UPDATE `gao_point_account` + SET + `balance` = `balance` + #{delta}, + `total_earned` = `total_earned` + #{totalEarnedDelta}, + `total_revoked` = `total_revoked` + #{totalRevokedDelta}, + `total_adjusted` = `total_adjusted` + #{totalAdjustedDelta}, + `version` = `version` + 1, + `updated_at` = #{updatedAt} + WHERE `id` = #{id} + AND `balance` + #{delta} >= 0 + AND `deleted_at` IS NULL + + diff --git a/src/main/resources/mapper/timi-server/GaoPointLedgerMapper.xml b/src/main/resources/mapper/timi-server/GaoPointLedgerMapper.xml new file mode 100644 index 0000000..3a4d6ac --- /dev/null +++ b/src/main/resources/mapper/timi-server/GaoPointLedgerMapper.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + `deleted_at` IS NULL + + AND `store_id` = #{storeId} + + + AND `customer_id` = #{customerId} + + + AND `account_id` = #{accountId} + + + AND `change_type` = #{changeType} + + + AND `occurred_at` >= #{beginAt} + + + AND `occurred_at` <= #{endAt} + + + + + + + diff --git a/src/main/resources/mapper/timi-server/GaoPointRuleMapper.xml b/src/main/resources/mapper/timi-server/GaoPointRuleMapper.xml new file mode 100644 index 0000000..c5434d5 --- /dev/null +++ b/src/main/resources/mapper/timi-server/GaoPointRuleMapper.xml @@ -0,0 +1,15 @@ + + + + + -- 2.54.0 From 44af16f451361084e8af46517141a36311fb991e Mon Sep 17 00:00:00 2001 From: Timi Date: Tue, 11 Aug 2026 22:06:53 +0800 Subject: [PATCH 09/12] fix new permission D authorize fail --- ...AuthorizationBoundaryServiceImplement.java | 2 -- .../implement/RoleServiceImplement.java | 28 +++++++++++-------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/AuthorizationBoundaryServiceImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/AuthorizationBoundaryServiceImplement.java index e0c947c..30baa9c 100644 --- a/src/main/java/com/imyeyu/api/modules/user/service/implement/AuthorizationBoundaryServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/AuthorizationBoundaryServiceImplement.java @@ -173,7 +173,6 @@ public class AuthorizationBoundaryServiceImplement implements AuthorizationBound } List permissionList = permissionMapper.selectByIdList(permissionIdList.stream().toList()); checkPermissionIdListComplete(permissionIdList, permissionList); - Set allPermissionIdSet = listRoleAllPermissionId(operatorUserId, roleId); boolean hasParentRole = TimiJava.isNotEmpty(role.getParentRoleId()); Set parentDelegationPermissionIdSet = listParentDelegationPermissionId(roleId); if (!isCoreDelegationRoot(role)) { @@ -182,7 +181,6 @@ public class AuthorizationBoundaryServiceImplement implements AuthorizationBound Set operatorGrantablePermissionIdSet = listGrantablePermissionId(operatorUserId, permissionList); for (Permission permission : permissionList) { TimiException.requiredTrue(role.getModuleCode() == permission.getModuleCode() || isCoreDelegationRoot(role) || isCoreAuthorizationPermission(permission), "不能配置其他模块转授权限"); - TimiException.requiredTrue(isCoreDelegationRoot(role) || allPermissionIdSet.contains(permission.getId()), "转授权限必须先属于目标角色有效权限:%s".formatted(permission.getCode())); if (hasParentRole) { TimiException.requiredTrue(parentDelegationPermissionIdSet.contains(permission.getId()), "转授权限超过上级边界:%s".formatted(permission.getCode())); } diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleServiceImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleServiceImplement.java index 94a25ac..99a5850 100644 --- a/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleServiceImplement.java @@ -151,7 +151,6 @@ public class RoleServiceImplement extends AbstractEntityService im if (TimiJava.isNotEmpty(deleteIdList)) { rolePermissionMapper.deleteByPermissionIdList(role.getId(), new ArrayList<>(deleteIdList)); - rolePermissionDelegationMapper.deleteByPermissionIdList(role.getId(), new ArrayList<>(deleteIdList)); } if (TimiJava.isNotEmpty(addIdList)) { List rolePermissionList = new ArrayList<>(); @@ -284,19 +283,20 @@ public class RoleServiceImplement extends AbstractEntityService im String loginUserId = userLoginService.getRequireLoginUserId(); Role role = mapper.selectById(roleId); TimiException.required(role, "not found role"); - List allPermissionList = isCoreDelegationRoot(role) - ? Arrays.stream(ModuleCode.values()).flatMap(moduleCode -> permissionService.listByModuleCode(moduleCode).stream()).toList() - : listPermissionByRoleId(roleId); - Set allPermissionIdSet = allPermissionList.stream().map(Permission::getId).collect(Collectors.toSet()); - Set operatorGrantableIdSet = listOperatorGrantablePermissionId(loginUserId, allPermissionList); boolean hasParentRole = TimiJava.isNotEmpty(role.getParentRoleId()); - Set parentDelegationIdSet = listDelegationParentPermissionId(roleId); boolean systemAdmin = authorizationScopeService.isSystemAdmin(loginUserId); - return allPermissionList.stream() - .filter(permission -> allPermissionIdSet.contains(permission.getId())) - .filter(permission -> parentDelegationIdSet.contains(permission.getId()) || isCoreDelegationRoot(role) && systemAdmin && !hasParentRole) - .filter(permission -> operatorGrantableIdSet.contains(permission.getId()) || systemAdmin) - .toList(); + Set candidateIdSet = isCoreDelegationRoot(role) && systemAdmin && !hasParentRole + ? Arrays.stream(ModuleCode.values()) + .flatMap(moduleCode -> permissionService.listByModuleCode(moduleCode).stream()) + .map(Permission::getId) + .collect(Collectors.toSet()) + : listDelegationParentPermissionId(roleId); + List candidateList = listPermissionById(candidateIdSet); + Set operatorGrantableIdSet = listOperatorGrantablePermissionId(loginUserId, candidateList); + return candidateList.stream() + .filter(permission -> role.getModuleCode() == permission.getModuleCode() || isCoreDelegationRoot(role) || isCoreAuthorizationPermission(permission)) + .filter(permission -> operatorGrantableIdSet.contains(permission.getId()) || systemAdmin) + .toList(); } @Transactional(TimiServerDBConfig.ROLLBACKER) @@ -411,6 +411,10 @@ public class RoleServiceImplement extends AbstractEntityService im && (com.imyeyu.api.bean.CoreRoleCode.SYSTEM.name().equals(role.getCode()) || com.imyeyu.api.bean.CoreRoleCode.ADMIN.name().equals(role.getCode())); } + private boolean isCoreAuthorizationPermission(Permission permission) { + return permission != null && ModuleCode.CORE == permission.getModuleCode(); + } + private Set listOperatorGrantablePermissionId(String loginUserId, List permissionList) { if (TimiJava.isEmpty(permissionList)) { return Set.of(); -- 2.54.0 From 81b7356d677723311dccf7ad91e881770b4154c4 Mon Sep 17 00:00:00 2001 From: Timi Date: Tue, 11 Aug 2026 22:41:49 +0800 Subject: [PATCH 10/12] nullable point account detail api --- .../modules/gao/controller/GaoPointAccountController.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) 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 index 5d37691..537e0a1 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPointAccountController.java +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPointAccountController.java @@ -8,7 +8,6 @@ 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.java.bean.timi.TimiException; import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.RequestRateLimit; import lombok.RequiredArgsConstructor; @@ -36,9 +35,7 @@ public class GaoPointAccountController { @PostMapping("/account/detail") public GaoPointAccount detail(@RequestParam String customerId, @RequestParam(required = false) String storeId) { storeId = resolveStoreId(storeId); - GaoPointAccount account = accountService.getByCustomerId(storeId, customerId); - TimiException.required(account, "not found customer point account"); - return account; + return accountService.getByCustomerId(storeId, customerId); } private String resolveStoreId(String requestedStoreId) { -- 2.54.0 From 5ec6bc472f6a6add0f0076134a8268f0b5df9d6a Mon Sep 17 00:00:00 2001 From: Timi Date: Tue, 11 Aug 2026 23:51:05 +0800 Subject: [PATCH 11/12] add chart api --- .../GaoCustomerChartController.java | 47 ++++++++++ .../controller/GaoStoreChartController.java | 68 ++++++++++++++ .../gao/mapper/GaoEventRecordMapper.java | 28 ++++++ .../gao/service/GaoCustomerChartService.java | 20 +++++ .../gao/service/GaoStoreChartService.java | 28 ++++++ .../GaoCustomerChartServiceImplement.java | 44 +++++++++ .../GaoStoreChartServiceImplement.java | 64 +++++++++++++ .../modules/gao/vo/GaoCustomerChartReq.java | 28 ++++++ .../gao/vo/GaoEventRecordCalendarView.java | 29 ++++++ .../gao/vo/GaoEventRecordDailyStatView.java | 20 +++++ .../api/modules/gao/vo/GaoStoreChartReq.java | 25 ++++++ .../timi-server/GaoEventRecordMapper.xml | 89 +++++++++++++++++++ 12 files changed, 490 insertions(+) create mode 100644 src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerChartController.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreChartController.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerChartService.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreChartService.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerChartServiceImplement.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreChartServiceImplement.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerChartReq.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordCalendarView.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordDailyStatView.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoStoreChartReq.java 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/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/mapper/GaoEventRecordMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventRecordMapper.java index f5ac28f..cbd2f00 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventRecordMapper.java +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventRecordMapper.java @@ -1,9 +1,13 @@ 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; @@ -49,4 +53,28 @@ public interface GaoEventRecordMapper extends BaseMapper /// @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/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/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/implement/GaoCustomerChartServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerChartServiceImplement.java new file mode 100644 index 0000000..e2e1d32 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerChartServiceImplement.java @@ -0,0 +1,44 @@ +package com.imyeyu.api.modules.gao.service.implement; + +import com.imyeyu.api.modules.gao.mapper.GaoEventRecordMapper; +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.java.bean.timi.TimiException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +/// 客户报表服务实现 +/// +/// @author Codex +/// @since 2026-08-11 +@Service +@RequiredArgsConstructor +public class GaoCustomerChartServiceImplement implements GaoCustomerChartService { + + private final GaoEventRecordMapper eventRecordMapper; + + @Override + public List eventRecordDailyStat(GaoCustomerChartReq req) { + checkRangeReq(req); + return eventRecordMapper.stateCustomerDailyByRange(req); + } + + @Override + public List eventRecordCalendar(GaoCustomerChartReq req) { + checkRangeReq(req); + return eventRecordMapper.selectCustomerCalendarByRange(req); + } + + private void checkRangeReq(GaoCustomerChartReq req) { + TimiException.required(req, "not found req"); + TimiException.required(req.getStoreId(), "not found req.storeId"); + TimiException.required(req.getCustomerId(), "not found req.customerId"); + TimiException.required(req.getBeginAt(), "not found req.beginAt"); + TimiException.required(req.getEndAt(), "not found req.endAt"); + TimiException.requiredTrue(req.getBeginAt() <= req.getEndAt(), "req.endAt lt req.beginAt"); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreChartServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreChartServiceImplement.java new file mode 100644 index 0000000..00f02ff --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreChartServiceImplement.java @@ -0,0 +1,64 @@ +package com.imyeyu.api.modules.gao.service.implement; + +import com.imyeyu.api.modules.gao.mapper.GaoEventRecordMapper; +import com.imyeyu.api.modules.gao.service.GaoCustomerService; +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.GaoCustomerTrendStateReq; +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.java.bean.timi.TimiException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +/// 门店报表服务实现 +/// +/// @author Codex +/// @since 2026-08-11 +@Service +@RequiredArgsConstructor +public class GaoStoreChartServiceImplement implements GaoStoreChartService { + + private final GaoCustomerService customerService; + private final GaoEventRecordMapper eventRecordMapper; + + @Override + public List customerDailyTrend(GaoStoreChartReq req) { + checkRangeReq(req); + GaoCustomerTrendStateReq statReq = new GaoCustomerTrendStateReq(); + statReq.setStoreId(req.getStoreId()); + statReq.setBeginCreatedAt(req.getBeginAt()); + statReq.setEndCreatedAt(req.getEndAt()); + return customerService.stateDailyTrend(statReq); + } + + @Override + public List customerGender(String storeId) { + TimiException.required(storeId, "not found storeId"); + return customerService.stateGender(storeId); + } + + @Override + public List eventRecordDailyStat(GaoStoreChartReq req) { + checkRangeReq(req); + return eventRecordMapper.stateDailyByRange(req); + } + + @Override + public List eventRecordCalendar(GaoStoreChartReq req) { + checkRangeReq(req); + return eventRecordMapper.selectCalendarByRange(req); + } + + private void checkRangeReq(GaoStoreChartReq req) { + TimiException.required(req, "not found req"); + TimiException.required(req.getStoreId(), "not found req.storeId"); + TimiException.required(req.getBeginAt(), "not found req.beginAt"); + TimiException.required(req.getEndAt(), "not found req.endAt"); + TimiException.requiredTrue(req.getBeginAt() <= req.getEndAt(), "req.endAt lt req.beginAt"); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerChartReq.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerChartReq.java new file mode 100644 index 0000000..8b0c6ad --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerChartReq.java @@ -0,0 +1,28 @@ +package com.imyeyu.api.modules.gao.vo; + +import lombok.Data; + +import java.util.List; + +/// 客户报表查询请求 +/// +/// @author Codex +/// @since 2026-08-11 +@Data +public class GaoCustomerChartReq { + + /// 门店 ID + private String storeId; + + /// 客户 ID + private String customerId; + + /// 开始时间 + private Long beginAt; + + /// 结束时间 + private Long endAt; + + /// 登记事件 ID 列表 + private List eventIdList; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordCalendarView.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordCalendarView.java new file mode 100644 index 0000000..5a9722d --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordCalendarView.java @@ -0,0 +1,29 @@ +package com.imyeyu.api.modules.gao.vo; + +import lombok.Data; + +/// 登记事件日历记录视图 +/// +/// @author Codex +/// @since 2026-08-11 +@Data +public class GaoEventRecordCalendarView { + + /// 登记记录 ID + private String id; + + /// 客户 ID + private String customerId; + + /// 客户编码 + private String customerCode; + + /// 客户姓名 + private String customerName; + + /// 登记事件 ID + private String eventId; + + /// 登记时间 + private Long registeredAt; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordDailyStatView.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordDailyStatView.java new file mode 100644 index 0000000..039a326 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordDailyStatView.java @@ -0,0 +1,20 @@ +package com.imyeyu.api.modules.gao.vo; + +import lombok.Data; + +/// 登记事件每日统计视图 +/// +/// @author Codex +/// @since 2026-08-11 +@Data +public class GaoEventRecordDailyStatView { + + /// 登记事件 ID + private String eventId; + + /// 日期值,格式为 yyyyMMdd + private Integer dateValue; + + /// 当日登记数 + private Long dailyCount; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoStoreChartReq.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoStoreChartReq.java new file mode 100644 index 0000000..f3c343a --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoStoreChartReq.java @@ -0,0 +1,25 @@ +package com.imyeyu.api.modules.gao.vo; + +import lombok.Data; + +import java.util.List; + +/// 门店报表查询请求 +/// +/// @author Codex +/// @since 2026-08-11 +@Data +public class GaoStoreChartReq { + + /// 门店 ID + private String storeId; + + /// 开始时间 + private Long beginAt; + + /// 结束时间 + private Long endAt; + + /// 登记事件 ID 列表 + private List eventIdList; +} diff --git a/src/main/resources/mapper/timi-server/GaoEventRecordMapper.xml b/src/main/resources/mapper/timi-server/GaoEventRecordMapper.xml index 2a05053..796b272 100644 --- a/src/main/resources/mapper/timi-server/GaoEventRecordMapper.xml +++ b/src/main/resources/mapper/timi-server/GaoEventRecordMapper.xml @@ -203,4 +203,93 @@ ORDER BY `rank` LIMIT #{offset}, #{limit} + + + r.`store_id` = #{storeId} + AND r.`registered_at` >= #{beginAt} + AND r.`registered_at` <= #{endAt} + AND r.`deleted_at` IS NULL + + AND r.`event_id` IN + + #{eventId} + + + + + + + + r.`store_id` = #{storeId} + AND r.`customer_id` = #{customerId} + AND r.`registered_at` >= #{beginAt} + AND r.`registered_at` <= #{endAt} + AND r.`deleted_at` IS NULL + + AND r.`event_id` IN + + #{eventId} + + + + + -- 2.54.0 From 72bdb8d49b910e521f2a7d6158ca9955f7f8638a Mon Sep 17 00:00:00 2001 From: Timi Date: Tue, 11 Aug 2026 23:57:49 +0800 Subject: [PATCH 12/12] v1.0.9 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 75960a9..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 @@ -178,7 +178,7 @@ com.imyeyu.network timi-network - 0.0.13 + 0.0.14 com.imyeyu.lang -- 2.54.0