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
当附件为图片或视频时,若指定尺寸的缩略图已存在则直接返回缓存, @@ -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