Compare commits

..
5 Commits
Author SHA1 Message Date
timi d75ab1ed5f Merge pull request 'v1.0.27' (#37) from dev into master
Reviewed-on: #37
2026-08-29 17:49:23 +00:00
Timi 4496619eeb v1.0.27
CI / build-deploy (pull_request) Successful in 23s
CI / notify-on-failure (pull_request) Skipped
2026-08-30 01:46:02 +08:00
Timi c85b400a94 fix require photo event 2026-08-30 01:25:22 +08:00
Timi 94c99e3444 fix update customer code 2026-08-29 16:05:24 +08:00
Timi 44c2095d77 add event verify 2026-08-29 15:35:11 +08:00
13 changed files with 160 additions and 5 deletions
+1 -1
View File
@@ -11,7 +11,7 @@
<groupId>com.imyeyu.timiserverapi</groupId>
<artifactId>TimiServerAPI</artifactId>
<version>1.0.26</version>
<version>1.0.27</version>
<packaging>jar</packaging>
<name>TimiServerAPI</name>
<description>imyeyu.com API</description>
@@ -43,6 +43,8 @@ public enum GaoPermissionCode implements BuiltinPermissionCode {
EVENT_RECORD_READ("EVENT_RECORD:READ", "登记事件记录读取", false, false),
EVENT_RECORD_VERIFY("EVENT_RECORD:VERIFY", "登记事件核销", true, false),
EVENT_RECORD_EXPORT("EVENT_RECORD:EXPORT", "登记事件记录导出", true, false),
EVENT_RECORD_UPDATE("EVENT_RECORD:UPDATE", "登记事件记录修改", false, false),
@@ -111,10 +111,24 @@ public class GaoEventRecordController {
record.getCustomer().setAttachmentList(attachmentService.listByBizId(Attachment.BizType.GAO_CUSTOMER, record.getCustomerId()));
record.setEvent(eventService.get(record.getEventId()));
record.setOperator(userService.get(record.getOperatorUserId()));
if (record.getVerifierUserId() != null) {
record.setVerifier(userService.get(record.getVerifierUserId()));
}
record.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.GAO_EVENT_RECORD, record.getId()));
return record;
}
@AOPLog
@RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.EVENT_RECORD_VERIFY)
@PostMapping("/verify")
public void verify(@RequestParam String id) {
GaoEventRecord record = service.get(id);
TimiException.required(record, "not found event record");
storeScopeService.checkStoreId(record.getStoreId());
service.verify(id);
}
@AOPLog
@RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.EVENT_RECORD_CREATE)
@@ -117,6 +117,9 @@ public class GaoEvent extends UUIDEntity {
/// true 为登记时要求拍照
private Boolean requirePhoto;
/// true 为登记记录需要核销
private Boolean requireVerification;
/// 登记限制类型
private LimitType limitType;
@@ -18,6 +18,23 @@ import java.util.List;
@EqualsAndHashCode(callSuper = true)
public class GaoEventRecord extends UUIDEntity {
///
/// 登记记录核销状态
///
/// @author Codex
/// @since 2026-08-29
public enum VerificationStatus {
/// 该事件不需要核销
NOT_REQUIRED,
/// 等待核销
PENDING,
/// 已核销
VERIFIED
}
///
/// 登记事件记录附件类型
///
@@ -49,6 +66,15 @@ public class GaoEventRecord extends UUIDEntity {
/// 本次登记提交的整数业务数值
private Long numberValue;
/// 核销状态
private VerificationStatus verificationStatus;
/// 核销用户 ID
private String verifierUserId;
/// 核销时间
private Long verifiedAt;
/// 备注
private String remark;
@@ -70,6 +96,9 @@ public class GaoEventRecord extends UUIDEntity {
@Transient
private User operator;
@Transient
private User verifier;
@Transient
private List<Attachment> attachmentList;
@@ -44,6 +44,14 @@ public interface GaoEventRecordMapper extends BaseMapper<GaoEventRecord, String>
/// @param earnedPointValue 获得积分总和
void updateEarnedPointValue(@Param("id") String id, @Param("earnedPointValue") Long earnedPointValue);
/// 原子更新登记记录核销状态
///
/// @param id 登记记录 ID
/// @param verifierUserId 核销用户 ID
/// @param verifiedAt 核销时间
/// @return 更新行数
int verify(@Param("id") String id, @Param("verifierUserId") String verifierUserId, @Param("verifiedAt") Long verifiedAt);
/// 按门店和登记日期统计登记记录数
///
/// @param storeId 门店 ID
@@ -24,6 +24,11 @@ public interface GaoEventRecordService extends BaseService<GaoEventRecord, Strin
/// @return 登记事件记录列表
List<GaoEventRecord> listByCustomerId(String customerId);
/// 核销登记事件记录
///
/// @param id 登记记录 ID
void verify(String id);
default void createBatch(List<GaoEventRecord> recordList) {
for (GaoEventRecord record : TimiJava.safeIterable(recordList)) {
create(record);
@@ -157,17 +157,18 @@ public class GaoCustomerServiceImplement extends AbstractEntityService<GaoCustom
logger.setContent(jackson.writeValueAsString(customer));
GaoCustomer dbCustomer = get(customer.getId());
TimiException.requiredTrue(dbCustomer.getStoreId().equals(customer.getStoreId()), "customer.storeId invalid");
dbCustomer.setCode(TimiJava.defaultIfEmpty(customer.getCode(), null));
String code = TimiJava.defaultIfEmpty(customer.getCode(), null);
{
if (!Objects.equals(dbCustomer.getCode(), customer.getCode()) && TimiJava.isNotEmpty(customer.getCode())) {
GaoCustomer byCode = getByCode(customer.getStoreId(), customer.getCode());
TimiException.requiredTrue(byCode == null || !byCode.getId().equals(dbCustomer.getId()), "姓名客户已登记");
if (!Objects.equals(dbCustomer.getCode(), code) && TimiJava.isNotEmpty(code)) {
GaoCustomer byCode = getByCode(customer.getStoreId(), code);
TimiException.requiredTrue(byCode == null || byCode.getId().equals(dbCustomer.getId()), "编码客户已登记");
}
if (!Objects.equals(dbCustomer.getName(), customer.getName())) {
GaoCustomer byName = getByName(customer.getStoreId(), customer.getName());
TimiException.requiredTrue(byName == null || !byName.getId().equals(dbCustomer.getId()), "该姓名客户已登记");
}
}
dbCustomer.setCode(code);
dbCustomer.setName(customer.getName());
fillPinyin(dbCustomer);
dbCustomer.setGender(customer.getGender());
@@ -43,7 +43,9 @@ import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/// 登记事件记录服务实现
///
@@ -83,6 +85,34 @@ public class GaoEventRecordServiceImplement extends AbstractEntityService<GaoEve
return mapper.selectAllByExample(example);
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void verify(String id) {
TimiException.required(id, "not found record.id");
Logger logger = new Logger(Logger.Module.GAO, "GAO_EVENT_RECORD_VERIFY");
try {
logger.setContent(id);
GaoEventRecord record = get(id);
TimiException.required(record, "not found event record");
TimiException.requiredTrue(record.getVerificationStatus() == GaoEventRecord.VerificationStatus.PENDING, "登记记录无需核销或已核销");
int updated = mapper.verify(id, userLoginService.getRequireLoginUserId(), Time.now());
TimiException.requiredTrue(0 < updated, "登记记录无需核销或已核销");
logger.setLevel(Logger.Level.INFO);
logger.setResult(id);
} catch (TimiException e) {
logger.setLevel(Logger.Level.WARN);
logger.setException(e.getMessage());
throw e;
} catch (Exception e) {
logger.setLevel(Logger.Level.ERROR);
logger.setException(TimiJava.serializeThrowable(e));
log.error("核销 GAO 登记记录失败", e);
throw new TimiException(TimiCode.ERROR, "核销 GAO 登记记录失败", e);
} finally {
loggerService.create(logger);
}
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void create(GaoEventRecord record) {
@@ -98,6 +128,7 @@ public class GaoEventRecordServiceImplement extends AbstractEntityService<GaoEve
TimiException.required(event, "not found event");
TimiException.required(event.getStoreId(), "not found event.storeId");
verifyRecordValue(event, record.getNumberValue());
verifyRecordAttachment(event, record.getAttachmentList());
// 检查权限
checkEventPermission(event);
int registeredDateValue = dateValue(registeredAt);
@@ -130,6 +161,9 @@ public class GaoEventRecordServiceImplement extends AbstractEntityService<GaoEve
dbRecord.setBusinessDayNo(businessDayNo);
dbRecord.setPointValue(record.getPointValue());
dbRecord.setNumberValue(record.getNumberValue());
dbRecord.setVerificationStatus(Boolean.TRUE.equals(event.getRequireVerification())
? GaoEventRecord.VerificationStatus.PENDING
: GaoEventRecord.VerificationStatus.NOT_REQUIRED);
dbRecord.setEarnedPointValue(0L);
super.create(dbRecord);
// 附件
@@ -168,11 +202,25 @@ public class GaoEventRecordServiceImplement extends AbstractEntityService<GaoEve
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void createBatch(List<GaoEventRecord> recordList) {
Set<String> usedTempFileIdSet = new HashSet<>();
for (GaoEventRecord record : TimiJava.safeIterable(recordList)) {
TimiException.required(record, "not found record");
prepareBatchAttachments(record, usedTempFileIdSet);
create(record);
}
}
private void prepareBatchAttachments(GaoEventRecord record, Set<String> usedTempFileIdSet) {
for (Attachment attachment : TimiJava.safeIterable(record.getAttachmentList())) {
if (attachment == null || TimiJava.isEmpty(attachment.getTempFileId()) || usedTempFileIdSet.add(attachment.getTempFileId())) {
continue;
}
Attachment clone = attachmentService.clone(attachment.getTempFileId());
TimiException.required(clone.getId(), "登记照片临时附件复制失败");
attachment.setTempFileId(clone.getId());
}
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void update(GaoEventRecord record) {
@@ -190,6 +238,7 @@ public class GaoEventRecordServiceImplement extends AbstractEntityService<GaoEve
GaoEvent event = eventService.get(dbRecord.getEventId());
TimiException.required(event, "not found event");
TimiException.required(event.getStoreId(), "not found event.storeId");
verifyRecordAttachment(event, record.getAttachmentList());
checkEventPermission(event);
dbRecord.setRemark(record.getRemark());
super.update(dbRecord);
@@ -383,6 +432,20 @@ public class GaoEventRecordServiceImplement extends AbstractEntityService<GaoEve
}
}
private void verifyRecordAttachment(GaoEvent event, List<Attachment> attachmentList) {
if (!Boolean.TRUE.equals(event.getRequirePhoto())) {
return;
}
boolean hasPhoto = false;
for (Attachment attachment : TimiJava.safeIterable(attachmentList)) {
if (attachment != null && (attachment.getAttachType() == null || GaoEventRecord.AttachType.PHOTO.name().equalsIgnoreCase(attachment.getAttachType()))) {
hasPhoto = true;
break;
}
}
TimiException.requiredTrue(hasPhoto, "该事件必须上传照片");
}
private void verifyImmutableRecordFields(GaoEventRecord request, GaoEventRecord dbRecord) {
TimiException.requiredTrue(request.getCustomerId() == null || request.getCustomerId().equals(dbRecord.getCustomerId()), "登记记录 customerId 创建后不可修改");
TimiException.requiredTrue(request.getEventId() == null || request.getEventId().equals(dbRecord.getEventId()), "登记记录 eventId 创建后不可修改");
@@ -69,6 +69,7 @@ public class GaoEventServiceImplement extends AbstractEntityService<GaoEvent, St
event.setValueType(TimiJava.defaultIfNull(event.getValueType(), GaoEvent.ValueType.NONE));
event.setRequireValue(TimiJava.defaultIfNull(event.getRequireValue(), false));
event.setRequirePhoto(TimiJava.defaultIfNull(event.getRequirePhoto(), false));
event.setRequireVerification(TimiJava.defaultIfNull(event.getRequireVerification(), false));
event.setLimitType(TimiJava.defaultIfNull(event.getLimitType(), GaoEvent.LimitType.NONE));
event.setSort(TimiJava.defaultIfNull(event.getSort(), 0));
verifyEvent(event);
@@ -122,6 +123,7 @@ public class GaoEventServiceImplement extends AbstractEntityService<GaoEvent, St
dbEvent.setRequireValue(dbEvent.getRequireValue());
}
dbEvent.setRequirePhoto(event.getRequirePhoto());
dbEvent.setRequireVerification(TimiJava.defaultIfNull(event.getRequireVerification(), dbEvent.getRequireVerification()));
dbEvent.setLimitType(event.getLimitType());
dbEvent.setLimitValue(event.getLimitValue());
dbEvent.setBeginAt(event.getBeginAt());
@@ -1,5 +1,6 @@
package com.imyeyu.api.modules.gao.vo;
import com.imyeyu.api.modules.gao.entity.GaoEventRecord;
import com.imyeyu.java.bean.BasePage;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -24,6 +25,9 @@ public class GaoEventRecordPage extends BasePage {
/// 登记事件 ID 列表,为空查询全部登记事件
private List<String> eventIdList;
/// 核销状态,为空查询全部状态
private GaoEventRecord.VerificationStatus verificationStatus;
/// 关键词,匹配客户、登记事件、备注、登记人
private String keyword;
@@ -0,0 +1,8 @@
ALTER TABLE `gao_event`
ADD COLUMN `require_verification` BIT(1) NOT NULL DEFAULT b'0' COMMENT 'true 为登记记录需要核销' AFTER `require_photo`;
ALTER TABLE `gao_event_record`
ADD COLUMN `verification_status` VARCHAR(32) NOT NULL DEFAULT 'NOT_REQUIRED' COMMENT '核销状态,NOT_REQUIRED 无需核销,PENDING 待核销,VERIFIED 已核销' AFTER `number_value`,
ADD COLUMN `verifier_user_id` VARCHAR(36) NULL COMMENT '核销用户 ID' AFTER `verification_status`,
ADD COLUMN `verified_at` BIGINT NULL COMMENT '核销时间,毫秒' AFTER `verifier_user_id`,
ADD KEY `idx_gao_event_record_store_verification` (`store_id`, `verification_status`, `deleted_at`, `registered_at`);
@@ -59,6 +59,19 @@
AND `deleted_at` IS NULL
</update>
<update id="verify">
UPDATE `gao_event_record`
SET
`verification_status` = 'VERIFIED',
`verifier_user_id` = #{verifierUserId},
`verified_at` = #{verifiedAt},
`updated_at` = ROUND(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000)
WHERE
`id` = #{id}
AND `verification_status` = 'PENDING'
AND `deleted_at` IS NULL
</update>
<select id="countByStoreIdAndRegisteredDateValue" resultType="java.lang.Long">
SELECT
COUNT(1)
@@ -105,6 +118,9 @@
#{eventId}
</foreach>
</if>
<if test="verificationStatus != null">
AND r.`verification_status` = #{verificationStatus}
</if>
<if test="keyword != null and keyword.trim() != ''">
AND (
c.`code` LIKE CONCAT('%', #{keyword}, '%')