diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEvent.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEvent.java index 83ca9ae..f6a290a 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEvent.java +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEvent.java @@ -16,18 +16,73 @@ import java.util.List; @EqualsAndHashCode(callSuper = true) public class GaoRegistrationEvent extends UUIDEntity { + /// + /// 登记事件状态 + /// + /// @author Codex + /// @since 2026-08-01 + public enum Status { + + /// 可登记 + ACTIVE, + + /// 无效,不可登记但保留统计 + INACTIVE, + + /// 已删除,不可登记但保留统计 + DELETED + } + + /// + /// 登记限制类型 + /// + /// @author Codex + /// @since 2026-08-01 + public enum RegistrationLimitType { + + /// 不限制 + NONE, + + /// 每天上午、下午各一次 + DAILY_HALF_DAY, + + /// 每个自然日一次 + NATURAL_DAY, + + /// 每隔 N 小时一次 + INTERVAL_HOUR, + + /// 每个自然月一次 + NATURAL_MONTH, + + /// 累计限 N 次 + TOTAL_LIMIT + } + /// 登记事件名称 private String name; /// 说明 private String description; + /// 统计图表颜色 + private String color; + + /// 状态 + private Status status; + /// true 为启用 private Boolean enabled; /// true 为登记时要求拍照 private Boolean requirePhoto; + /// 登记限制类型 + private RegistrationLimitType limitType; + + /// 登记限制数值,按限制类型表示小时数或次数 + private Integer limitValue; + /// 排序值 private Integer sort; 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 index 3fd8c2c..196ae83 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventMapper.java +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventMapper.java @@ -16,6 +16,6 @@ public interface GaoRegistrationEventMapper extends BaseMapper selectByIdList(@Param("idList") List idList); - @Select("SELECT * FROM `gao_registration_event` WHERE `enabled` = TRUE AND `deleted_at` IS NULL ORDER BY `sort` ASC, `created_at` ASC") + @Select("SELECT * FROM `gao_registration_event` WHERE `enabled` = TRUE AND (`status` IS NULL OR `status` = 'ACTIVE') AND `deleted_at` IS NULL ORDER BY `sort` ASC, `created_at` ASC") List selectEnabledList(); } 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 index 63adbc8..31df65a 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventRecordMapper.java +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventRecordMapper.java @@ -27,6 +27,22 @@ public interface GaoRegistrationEventRecordMapper extends BaseMapper 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 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 dbcc4fd..d8b0fcd 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 @@ -71,6 +71,7 @@ public class GaoCustomerServiceImplement extends AbstractEntityService checkRegistrationLimitRange(customerId, registrationEvent.getId(), getHalfDayBegin(registeredAt), getHalfDayEnd(registeredAt), "该客户当前半日已登记该事件"); + case NATURAL_DAY -> checkRegistrationLimitRange(customerId, registrationEvent.getId(), getDayBegin(registeredAt), getDayEnd(registeredAt), "该客户当日已登记该事件"); + case INTERVAL_HOUR -> checkIntervalHourRegistrationLimit(registrationEvent, customerId, registeredAt); + case NATURAL_MONTH -> checkRegistrationLimitRange(customerId, registrationEvent.getId(), getMonthBegin(registeredAt), getMonthEnd(registeredAt), "该客户当月已登记该事件"); + case TOTAL_LIMIT -> checkTotalRegistrationLimit(registrationEvent, customerId); + default -> { + } + } + } + + private void checkRegistrationLimitRange(String customerId, String registrationEventId, long beginRegisteredAt, long endRegisteredAt, String message) { + Long count = mapper.countByCustomerIdAndRegistrationEventIdRange(customerId, registrationEventId, beginRegisteredAt, endRegisteredAt); + TimiException.requiredTrue(TimiJava.defaultIfNull(count, 0L) < 1, message); + } + + private void checkIntervalHourRegistrationLimit(GaoRegistrationEvent registrationEvent, String customerId, long registeredAt) { + Integer limitValue = registrationEvent.getLimitValue(); + TimiException.requiredTrue(limitValue != null && 0 < limitValue, "登记事件间隔小时配置错误"); + long beginRegisteredAt = registeredAt - limitValue * 60L * 60L * 1000L + 1; + checkRegistrationLimitRange(customerId, registrationEvent.getId(), beginRegisteredAt, registeredAt, "该客户登记该事件间隔不足"); + } + + private void checkTotalRegistrationLimit(GaoRegistrationEvent registrationEvent, String customerId) { + Integer limitValue = registrationEvent.getLimitValue(); + TimiException.requiredTrue(limitValue != null && 0 < limitValue, "登记事件累计次数配置错误"); + Long count = mapper.countByCustomerIdAndRegistrationEventId(customerId, registrationEvent.getId()); + TimiException.requiredTrue(TimiJava.defaultIfNull(count, 0L) < limitValue, "该客户登记该事件次数已达上限"); + } + + private long getDayBegin(long registeredAt) { + ZoneId zoneId = ZoneId.systemDefault(); + return Instant.ofEpochMilli(registeredAt).atZone(zoneId).toLocalDate().atStartOfDay(zoneId).toInstant().toEpochMilli(); + } + + private long getDayEnd(long registeredAt) { + ZoneId zoneId = ZoneId.systemDefault(); + return Instant.ofEpochMilli(registeredAt).atZone(zoneId).toLocalDate().plusDays(1).atStartOfDay(zoneId).toInstant().toEpochMilli() - 1; + } + + private long getHalfDayBegin(long registeredAt) { + ZoneId zoneId = ZoneId.systemDefault(); + int hour = Instant.ofEpochMilli(registeredAt).atZone(zoneId).getHour(); + long dayBegin = getDayBegin(registeredAt); + return hour < 12 ? dayBegin : dayBegin + 12L * 60L * 60L * 1000L; + } + + private long getHalfDayEnd(long registeredAt) { + ZoneId zoneId = ZoneId.systemDefault(); + int hour = Instant.ofEpochMilli(registeredAt).atZone(zoneId).getHour(); + long dayBegin = getDayBegin(registeredAt); + return hour < 12 ? dayBegin + 12L * 60L * 60L * 1000L - 1 : getDayEnd(registeredAt); + } + + private long getMonthBegin(long registeredAt) { + ZoneId zoneId = ZoneId.systemDefault(); + LocalDate date = Instant.ofEpochMilli(registeredAt).atZone(zoneId).toLocalDate(); + return date.withDayOfMonth(1).atStartOfDay(zoneId).toInstant().toEpochMilli(); + } + + private long getMonthEnd(long registeredAt) { + ZoneId zoneId = ZoneId.systemDefault(); + LocalDate date = Instant.ofEpochMilli(registeredAt).atZone(zoneId).toLocalDate(); + return date.withDayOfMonth(1).plusMonths(1).atStartOfDay(zoneId).toInstant().toEpochMilli() - 1; + } + private void checkStatRange(Long beginRegisteredAt, Long endRegisteredAt) { TimiException.required(beginRegisteredAt, "not found beginRegisteredAt"); TimiException.required(endRegisteredAt, "not found endRegisteredAt"); diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoRegistrationEventServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoRegistrationEventServiceImplement.java index 0cfbdc8..9e5b922 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoRegistrationEventServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoRegistrationEventServiceImplement.java @@ -42,6 +42,9 @@ public class GaoRegistrationEventServiceImplement extends AbstractEntityService< @Override public PageResult pageWithRegistrationEventLimit(Page page) { PageResult result = page(page); + result.setList(result.getList().stream() + .filter(item -> item.getStatus() != GaoRegistrationEvent.Status.DELETED) + .toList()); loadRegistrationEventLimit(result.getList()); return result; } @@ -65,8 +68,10 @@ public class GaoRegistrationEventServiceImplement extends AbstractEntityService< @Transactional(TimiServerDBConfig.ROLLBACKER) @Override public void delete(String id) { - roleRelationMapper.deleteByRegistrationEventId(id); - super.delete(id); + GaoRegistrationEvent entity = get(id); + entity.setStatus(GaoRegistrationEvent.Status.DELETED); + entity.setEnabled(false); + super.update(entity); } @Override @@ -94,17 +99,37 @@ public class GaoRegistrationEventServiceImplement extends AbstractEntityService< TimiException.required(entity.getId(), "not found registrationEvent.id"); } TimiException.required(entity.getName(), "not found registrationEvent.name"); + if (entity.getStatus() == null) { + entity.setStatus(GaoRegistrationEvent.Status.ACTIVE); + } if (entity.getEnabled() == null) { entity.setEnabled(true); } if (entity.getRequirePhoto() == null) { entity.setRequirePhoto(false); } + if (entity.getLimitType() == null) { + entity.setLimitType(GaoRegistrationEvent.RegistrationLimitType.NONE); + } + checkRegistrationLimit(entity); + if (entity.getColor() != null) { + entity.setColor(entity.getColor().trim()); + } if (entity.getSort() == null) { entity.setSort(0); } } + private void checkRegistrationLimit(GaoRegistrationEvent entity) { + if (entity.getLimitType() == GaoRegistrationEvent.RegistrationLimitType.INTERVAL_HOUR + || entity.getLimitType() == GaoRegistrationEvent.RegistrationLimitType.TOTAL_LIMIT) { + TimiException.required(entity.getLimitValue(), "not found registrationEvent.limitValue"); + TimiException.requiredTrue(0 < entity.getLimitValue(), "registrationEvent.limitValue lte 0"); + return; + } + entity.setLimitValue(null); + } + private void saveRoleRelation(String registrationEventId, List roleCodeList) { roleRelationMapper.deleteByRegistrationEventId(registrationEventId); if (roleCodeList == null || roleCodeList.isEmpty()) { diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventSaveReq.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventSaveReq.java deleted file mode 100644 index dfe8bd1..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventSaveReq.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.imyeyu.api.modules.gao.vo; - -import com.imyeyu.api.modules.gao.entity.GaoRegistrationEvent; -import lombok.Data; - -import java.util.ArrayList; -import java.util.List; - -/// -/// 登记事件保存请求 -/// -/// @author Codex -/// @since 2026-07-27 -@Data -public class GaoRegistrationEventSaveReq { - - /// 登记事件 - private GaoRegistrationEvent registrationEvent; - - /// 可登记角色代码列表 - private List roleCodeList = new ArrayList<>(); -} diff --git a/src/main/resources/db/migration/timiserver/V20__extend_gao_registration_event_rule.sql b/src/main/resources/db/migration/timiserver/V20__extend_gao_registration_event_rule.sql new file mode 100644 index 0000000..46be432 --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V20__extend_gao_registration_event_rule.sql @@ -0,0 +1,6 @@ +ALTER TABLE `gao_registration_event` + ADD COLUMN `color` VARCHAR(32) NULL COMMENT '统计图表颜色' AFTER `id`, + ADD COLUMN `status` VARCHAR(32) NOT NULL DEFAULT 'ACTIVE' COMMENT '状态,ACTIVE 可登记,INACTIVE 无效,DELETED 已删除' AFTER `sort`, + ADD COLUMN `limit_type` VARCHAR(32) NOT NULL DEFAULT 'NONE' COMMENT '登记限制类型' AFTER `require_photo`, + ADD COLUMN `limit_value` INT NULL COMMENT '登记限制数值,按限制类型表示小时数或次数' AFTER `limit_type`, + ADD KEY `idx_gao_registration_event_status` (`status`, `enabled`, `deleted_at`, `sort`); diff --git a/src/main/resources/mapper/timi-server/GaoRegistrationEventRecordMapper.xml b/src/main/resources/mapper/timi-server/GaoRegistrationEventRecordMapper.xml index 0ba640f..ae8ec61 100644 --- a/src/main/resources/mapper/timi-server/GaoRegistrationEventRecordMapper.xml +++ b/src/main/resources/mapper/timi-server/GaoRegistrationEventRecordMapper.xml @@ -20,6 +20,28 @@ ORDER BY `registered_at` DESC + + + +