package com.talentcard.server.service; import java.time.LocalDateTime; import java.util.List; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.talentcard.server.business.PageResult; import com.talentcard.server.request.DependentCardAuditRequest; import com.talentcard.server.common.BusinessException; import com.talentcard.server.common.ErrorCode; import com.talentcard.server.entity.DependentCardApply; import com.talentcard.server.entity.DependentCardApplyStatusLog; import com.talentcard.server.mapper.DependentCardApplyMapper; import com.talentcard.server.mapper.DependentCardApplyStatusLogMapper; import com.talentcard.server.response.DependentCardApplyAdminResponse; import com.talentcard.server.response.DependentCardApplyStatusLogResponse; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class DependentCardApplyAdminService { private static final String ELITE_CARD_LEVEL = "卓越人才卡"; private static final int MAX_FAMILY_CARD_COUNT = 2; private final DependentCardApplyMapper mapper; private final DependentCardApplyStatusLogMapper statusLogMapper; public DependentCardApplyAdminService( DependentCardApplyMapper mapper, DependentCardApplyStatusLogMapper statusLogMapper ) { this.mapper = mapper; this.statusLogMapper = statusLogMapper; } @Transactional(readOnly = true) public PageResult page( Long talentCardId, Integer status, int page, int size ) { validateStatusFilter(status); int safePage = Math.max(page, 1); int safeSize = Math.min(Math.max(size, 1), 100); LambdaQueryWrapper query = Wrappers.lambdaQuery() .eq(talentCardId != null, DependentCardApply::getTalentCardId, talentCardId) .eq(status != null, DependentCardApply::getStatus, status); long total = mapper.selectCount(query); if (total == 0) { return new PageResult<>(List.of(), safePage, safeSize, 0); } long offset = (long) (safePage - 1) * safeSize; List records = mapper.selectList( query.orderByDesc(DependentCardApply::getApplyTime) .orderByDesc(DependentCardApply::getId) .last("limit " + safeSize + " offset " + offset) ).stream() .map(DependentCardApplyAdminResponse::from) .toList(); return new PageResult<>(records, safePage, safeSize, total); } @Transactional(readOnly = true) public DependentCardApplyAdminResponse detail(Long id) { return DependentCardApplyAdminResponse.from(requireApplication(id)); } @Transactional(readOnly = true) public List statusLogs(Long id) { DependentCardApply apply = requireApplication(id); return statusLogMapper.selectList( Wrappers.lambdaQuery() .eq(DependentCardApplyStatusLog::getApplyId, apply.getId()) .orderByAsc(DependentCardApplyStatusLog::getCreatedAt) .orderByAsc(DependentCardApplyStatusLog::getId) ).stream() .map(DependentCardApplyStatusLogResponse::from) .toList(); } @Transactional public DependentCardApplyAdminResponse audit( Long id, DependentCardAuditRequest request, Long operatorId ) { int targetStatus = request.status(); if (targetStatus != DependentCardApply.STATUS_APPROVED && targetStatus != DependentCardApply.STATUS_REJECTED) { throw new BusinessException(ErrorCode.BAD_REQUEST, "审核状态只能为1通过或2驳回"); } String auditRemark = trimToNull(request.auditRemark()); if (targetStatus == DependentCardApply.STATUS_REJECTED && auditRemark == null) { throw new BusinessException(ErrorCode.BAD_REQUEST, "驳回申请时必须填写审核备注"); } DependentCardApply apply = mapper.selectByIdForUpdate(id); if (apply == null) { throw new BusinessException(ErrorCode.NOT_FOUND, "家属附属卡申请不存在"); } if (!Integer.valueOf(DependentCardApply.STATUS_PENDING_REVIEW).equals(apply.getStatus())) { throw new BusinessException(ErrorCode.CONFLICT, "该申请已审核或已解绑"); } if (targetStatus == DependentCardApply.STATUS_APPROVED) { ensureApprovalSlot(apply); } LocalDateTime auditTime = LocalDateTime.now(); int updated = mapper.update( null, Wrappers.lambdaUpdate() .eq(DependentCardApply::getId, id) .eq(DependentCardApply::getStatus, DependentCardApply.STATUS_PENDING_REVIEW) .set(DependentCardApply::getStatus, targetStatus) .set(DependentCardApply::getAuditBy, operatorId) .set(DependentCardApply::getAuditTime, auditTime) .set(DependentCardApply::getAuditRemark, auditRemark) ); if (updated != 1) { throw new BusinessException(ErrorCode.CONFLICT, "申请状态已变化,请刷新后重试"); } if (statusLogMapper.insert(new DependentCardApplyStatusLog( apply.getId(), apply.getApplyNo(), DependentCardApply.STATUS_PENDING_REVIEW, targetStatus, "ADMIN", operatorId, auditRemark )) != 1) { throw new BusinessException(ErrorCode.INTERNAL_ERROR, "审核状态记录保存失败"); } return DependentCardApplyAdminResponse.from(mapper.selectById(id)); } private void ensureApprovalSlot(DependentCardApply apply) { DependentCardApply ownerCard = mapper.selectOwnerCard(apply.getTalentCardId()); if (ownerCard == null) { throw new BusinessException(ErrorCode.CONFLICT, "主卡不存在或已失效,不能审核通过"); } if (!ELITE_CARD_LEVEL.equals(ownerCard.getCardLevel()) || !Integer.valueOf(1).equals(ownerCard.getIsFamilyCardEnabled())) { throw new BusinessException(ErrorCode.CONFLICT, "该主卡当前无家属附属卡权限"); } Integer configuredLimit = ownerCard.getFamilyCardLimit(); if (configuredLimit == null || configuredLimit <= 0) { throw new BusinessException(ErrorCode.CONFLICT, "该主卡未配置家属附属卡名额"); } int familyCardLimit = Math.min(configuredLimit, MAX_FAMILY_CARD_COUNT); long approvedCount = mapper.selectCount( Wrappers.lambdaQuery() .eq(DependentCardApply::getTalentCardId, apply.getTalentCardId()) .eq(DependentCardApply::getStatus, DependentCardApply.STATUS_APPROVED) ); if (approvedCount >= familyCardLimit) { throw new BusinessException(ErrorCode.CONFLICT, "该主卡已达到家属附属卡绑定上限"); } } private DependentCardApply requireApplication(Long id) { if (id == null || id <= 0) { throw new BusinessException(ErrorCode.BAD_REQUEST, "申请ID不能为空"); } DependentCardApply apply = mapper.selectById(id); if (apply == null) { throw new BusinessException(ErrorCode.NOT_FOUND, "家属附属卡申请不存在"); } return apply; } private void validateStatusFilter(Integer status) { if (status != null && (status < DependentCardApply.STATUS_PENDING_REVIEW || status > DependentCardApply.STATUS_CANCELLED)) { throw new BusinessException(ErrorCode.BAD_REQUEST, "申请状态不正确"); } } private String trimToNull(String value) { if (value == null || value.isBlank()) { return null; } return value.trim(); } }