Compare commits

..
2 Commits
Author SHA1 Message Date
timi da288338cd Merge pull request 'v1.0.7' (#12) from dev into master
Reviewed-on: #12
2026-08-01 09:34:18 +00:00
Timi 2f366fe219 v1.0.7
CI / build-deploy (pull_request) Successful in 20s
CI / notify-on-failure (pull_request) Has been skipped
2026-08-01 17:34:06 +08:00
9 changed files with 201 additions and 26 deletions
@@ -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;
@@ -16,6 +16,6 @@ public interface GaoRegistrationEventMapper extends BaseMapper<GaoRegistrationEv
List<GaoRegistrationEvent> selectByIdList(@Param("idList") List<String> 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<GaoRegistrationEvent> selectEnabledList();
}
@@ -27,6 +27,22 @@ public interface GaoRegistrationEventRecordMapper extends BaseMapper<GaoRegistra
/// @return 最后登记事件记录列表
List<GaoRegistrationEventRecord> 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
@@ -71,6 +71,7 @@ public class GaoCustomerServiceImplement extends AbstractEntityService<GaoCustom
public void create(GaoCustomer entity) {
checkEntity(entity, false);
entity.setCreatorUserId(userLoginService.getRequireLoginUserId());
entity.setInvitedCount(0L);
super.create(entity);
saveAttachment(entity.getId(), resolveAttachmentList(entity));
changeInvitedCount(entity.getIntroducerCustomerId(), 1L);
@@ -99,6 +99,8 @@ public class GaoRegistrationEventRecordServiceImplement extends AbstractEntitySe
String customerId = resolveCustomerId(req);
GaoRegistrationEvent registrationEvent = registrationEventService.get(req.getRegistrationEventId());
checkRegistrationEventPermission(registrationEvent);
long registeredAt = TimiJava.defaultIfNull(req.getRegisteredAt(), Time.now());
checkRegistrationEventCreatable(registrationEvent, customerId, registeredAt);
if (Boolean.TRUE.equals(registrationEvent.getRequirePhoto())) {
TimiException.requiredTrue(req.getAttachmentIdList() != null && !req.getAttachmentIdList().isEmpty(), "required record photo");
}
@@ -108,7 +110,7 @@ public class GaoRegistrationEventRecordServiceImplement extends AbstractEntitySe
record.setRegistrationEventId(req.getRegistrationEventId());
record.setOperatorUserId(userLoginService.getRequireLoginUserId());
record.setRemark(req.getRemark());
record.setRegisteredAt(TimiJava.defaultIfNull(req.getRegisteredAt(), Time.now()));
record.setRegisteredAt(registeredAt);
super.create(record);
saveAttachment(record.getId(), buildPhotoAttachmentList(req.getAttachmentIdList()));
return get(record.getId());
@@ -316,6 +318,76 @@ public class GaoRegistrationEventRecordServiceImplement extends AbstractEntitySe
}
}
private void checkRegistrationEventCreatable(GaoRegistrationEvent registrationEvent, String customerId, long registeredAt) {
TimiException.requiredTrue(Boolean.TRUE.equals(registrationEvent.getEnabled()), "登记事件未启用");
TimiException.requiredTrue(registrationEvent.getStatus() == null || registrationEvent.getStatus() == GaoRegistrationEvent.Status.ACTIVE, "登记事件不可登记");
GaoRegistrationEvent.RegistrationLimitType limitType = TimiJava.defaultIfNull(registrationEvent.getLimitType(), GaoRegistrationEvent.RegistrationLimitType.NONE);
switch (limitType) {
case DAILY_HALF_DAY -> checkRegistrationLimitRange(customerId, registrationEvent.getId(), getHalfDayBegin(registeredAt), getHalfDayEnd(registeredAt), "该客户当前半日已登记该事件");
case NATURAL_DAY -> checkRegistrationLimitRange(customerId, registrationEvent.getId(), getDayBegin(registeredAt), getDayEnd(registeredAt), "该客户当日已登记该事件");
case INTERVAL_HOUR -> checkIntervalHourRegistrationLimit(registrationEvent, customerId, registeredAt);
case NATURAL_MONTH -> checkRegistrationLimitRange(customerId, registrationEvent.getId(), getMonthBegin(registeredAt), getMonthEnd(registeredAt), "该客户当月已登记该事件");
case TOTAL_LIMIT -> checkTotalRegistrationLimit(registrationEvent, 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");
@@ -42,6 +42,9 @@ public class GaoRegistrationEventServiceImplement extends AbstractEntityService<
@Override
public PageResult<GaoRegistrationEvent> pageWithRegistrationEventLimit(Page<GaoRegistrationEvent> page) {
PageResult<GaoRegistrationEvent> 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<String> roleCodeList) {
roleRelationMapper.deleteByRegistrationEventId(registrationEventId);
if (roleCodeList == null || roleCodeList.isEmpty()) {
@@ -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<String> roleCodeList = new ArrayList<>();
}
@@ -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`);
@@ -20,6 +20,28 @@
ORDER BY `registered_at` DESC
</select>
<select id="countByCustomerIdAndRegistrationEventIdRange" resultType="java.lang.Long">
SELECT
COUNT(1)
FROM `gao_registration_event_record`
WHERE
`customer_id` = #{customerId}
AND `registration_event_id` = #{registrationEventId}
AND `registered_at` &gt;= #{beginRegisteredAt}
AND `registered_at` &lt;= #{endRegisteredAt}
AND `deleted_at` IS NULL
</select>
<select id="countByCustomerIdAndRegistrationEventId" resultType="java.lang.Long">
SELECT
COUNT(1)
FROM `gao_registration_event_record`
WHERE
`customer_id` = #{customerId}
AND `registration_event_id` = #{registrationEventId}
AND `deleted_at` IS NULL
</select>
<select id="statByRegistrationEventAndPeriodType" resultType="com.imyeyu.api.modules.gao.vo.GaoRegistrationEventPeriodStatView">
SELECT
`registration_event_id` AS `registrationEventId`,