| | |
| | | package com.talentcard.server.service; |
| | | |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | 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.request.DependentCardAuditRequest; |
| | | import com.talentcard.server.response.DependentCardApplyAdminResponse; |
| | | import com.talentcard.server.response.DependentCardApplyStatusLogResponse; |
| | | import org.springframework.jdbc.core.JdbcTemplate; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | |
| | | |
| | | private final DependentCardApplyMapper mapper; |
| | | private final DependentCardApplyStatusLogMapper statusLogMapper; |
| | | private final JdbcTemplate jdbcTemplate; |
| | | |
| | | public DependentCardApplyAdminService( |
| | | DependentCardApplyMapper mapper, |
| | | DependentCardApplyStatusLogMapper statusLogMapper |
| | | DependentCardApplyStatusLogMapper statusLogMapper, |
| | | JdbcTemplate jdbcTemplate |
| | | ) { |
| | | this.mapper = mapper; |
| | | this.statusLogMapper = statusLogMapper; |
| | | this.jdbcTemplate = jdbcTemplate; |
| | | } |
| | | |
| | | @Transactional(readOnly = true) |
| | | public PageResult<DependentCardApplyAdminResponse> page( |
| | | Long talentCardId, |
| | | String familyKeyword, |
| | | String talentKeyword, |
| | | String cardLevel, |
| | | Integer status, |
| | | LocalDate applyStart, |
| | | LocalDate applyEnd, |
| | | boolean management, |
| | | int page, |
| | | int size |
| | | ) { |
| | | validateStatusFilter(status); |
| | | if (management && status != null |
| | | && status != DependentCardApply.STATUS_APPROVED |
| | | && status != DependentCardApply.STATUS_CANCELLED) { |
| | | throw new BusinessException(ErrorCode.BAD_REQUEST, "家属卡管理状态只能为正常或已解绑"); |
| | | } |
| | | if (applyStart != null && applyEnd != null && applyStart.isAfter(applyEnd)) { |
| | | throw new BusinessException(ErrorCode.BAD_REQUEST, |
| | | management ? "注册开始时间不能晚于结束时间" : "申请开始时间不能晚于结束时间"); |
| | | } |
| | | int safePage = Math.max(page, 1); |
| | | int safeSize = Math.min(Math.max(size, 1), 100); |
| | | LambdaQueryWrapper<DependentCardApply> query = Wrappers.<DependentCardApply>lambdaQuery() |
| | | .eq(talentCardId != null, DependentCardApply::getTalentCardId, talentCardId) |
| | | .eq(status != null, DependentCardApply::getStatus, status); |
| | | .eq(status != null, DependentCardApply::getStatus, status) |
| | | .in(management && status == null, DependentCardApply::getStatus, |
| | | DependentCardApply.STATUS_APPROVED, DependentCardApply.STATUS_CANCELLED) |
| | | .eq(cardLevel != null && !cardLevel.isBlank(), DependentCardApply::getCardLevel, trimToNull(cardLevel)) |
| | | .and(familyKeyword != null && !familyKeyword.isBlank(), wrapper -> wrapper |
| | | .like(DependentCardApply::getDependentName, familyKeyword.trim()) |
| | | .or() |
| | | .like(DependentCardApply::getDependentMobile, familyKeyword.trim()) |
| | | .or() |
| | | .like(DependentCardApply::getDependentIdCardNo, familyKeyword.trim())) |
| | | .and(talentKeyword != null && !talentKeyword.isBlank(), wrapper -> wrapper |
| | | .like(DependentCardApply::getTalentName, talentKeyword.trim()) |
| | | .or() |
| | | .like(DependentCardApply::getTalentMobile, talentKeyword.trim()) |
| | | .or() |
| | | .like(DependentCardApply::getTalentCardNo, talentKeyword.trim()) |
| | | .or() |
| | | .apply("exists (select 1 from talent_user u where u.id = dependent_card_apply.talent_id and u.deleted = 0 and u.id_card_no like concat('%', {0}, '%'))", talentKeyword.trim())) |
| | | .ge(!management && applyStart != null, DependentCardApply::getApplyTime, |
| | | applyStart == null ? null : applyStart.atStartOfDay()) |
| | | .lt(!management && applyEnd != null, DependentCardApply::getApplyTime, |
| | | applyEnd == null ? null : applyEnd.plusDays(1).atStartOfDay()) |
| | | .ge(management && applyStart != null, DependentCardApply::getAuditTime, |
| | | applyStart == null ? null : applyStart.atStartOfDay()) |
| | | .lt(management && applyEnd != null, DependentCardApply::getAuditTime, |
| | | applyEnd == null ? null : applyEnd.plusDays(1).atStartOfDay()); |
| | | long total = mapper.selectCount(query); |
| | | if (total == 0) { |
| | | return new PageResult<>(List.of(), safePage, safeSize, 0); |
| | | } |
| | | long offset = (long) (safePage - 1) * safeSize; |
| | | List<DependentCardApplyAdminResponse> records = mapper.selectList( |
| | | query.orderByDesc(DependentCardApply::getApplyTime) |
| | | query.orderByDesc(management, DependentCardApply::getAuditTime) |
| | | .orderByDesc(!management, DependentCardApply::getApplyTime) |
| | | .orderByDesc(DependentCardApply::getId) |
| | | .last("limit " + safeSize + " offset " + offset) |
| | | ).stream() |
| | | .map(DependentCardApplyAdminResponse::from) |
| | | .map(apply -> DependentCardApplyAdminResponse.from(apply, applicationExtra(apply))) |
| | | .toList(); |
| | | return new PageResult<>(records, safePage, safeSize, total); |
| | | } |
| | | |
| | | @Transactional(readOnly = true) |
| | | public DependentCardApplyAdminResponse detail(Long id) { |
| | | return DependentCardApplyAdminResponse.from(requireApplication(id)); |
| | | DependentCardApply apply = requireApplication(id); |
| | | return DependentCardApplyAdminResponse.from(apply, applicationExtra(apply)); |
| | | } |
| | | |
| | | @Transactional(readOnly = true) |
| | |
| | | int targetStatus = request.status(); |
| | | if (targetStatus != DependentCardApply.STATUS_APPROVED |
| | | && targetStatus != DependentCardApply.STATUS_REJECTED) { |
| | | throw new BusinessException(ErrorCode.BAD_REQUEST, "审核状态只能为1通过或2驳回"); |
| | | throw new BusinessException(ErrorCode.BAD_REQUEST, "审核状态只能为通过或驳回"); |
| | | } |
| | | String auditRemark = trimToNull(request.auditRemark()); |
| | | if (targetStatus == DependentCardApply.STATUS_REJECTED && auditRemark == null) { |
| | |
| | | } |
| | | DependentCardApply apply = mapper.selectByIdForUpdate(id); |
| | | if (apply == null) { |
| | | throw new BusinessException(ErrorCode.NOT_FOUND, "家属附属卡申请不存在"); |
| | | throw new BusinessException(ErrorCode.NOT_FOUND, "家属卡申请不存在"); |
| | | } |
| | | if (!Integer.valueOf(DependentCardApply.STATUS_PENDING_REVIEW).equals(apply.getStatus())) { |
| | | throw new BusinessException(ErrorCode.CONFLICT, "该申请已审核或已解绑"); |
| | | throw new BusinessException(ErrorCode.CONFLICT, "该申请已审核或已取消"); |
| | | } |
| | | if (targetStatus == DependentCardApply.STATUS_APPROVED) { |
| | | ensureApprovalSlot(apply); |
| | |
| | | )) != 1) { |
| | | throw new BusinessException(ErrorCode.INTERNAL_ERROR, "审核状态记录保存失败"); |
| | | } |
| | | return DependentCardApplyAdminResponse.from(mapper.selectById(id)); |
| | | DependentCardApply updatedApply = mapper.selectById(id); |
| | | return DependentCardApplyAdminResponse.from(updatedApply, applicationExtra(updatedApply)); |
| | | } |
| | | |
| | | @Transactional |
| | | public DependentCardApplyAdminResponse unbind(Long id, Long operatorId) { |
| | | DependentCardApply apply = mapper.selectByIdForUpdate(id); |
| | | if (apply == null) { |
| | | throw new BusinessException(ErrorCode.NOT_FOUND, "家属卡不存在"); |
| | | } |
| | | if (!Integer.valueOf(DependentCardApply.STATUS_APPROVED).equals(apply.getStatus())) { |
| | | throw new BusinessException(ErrorCode.CONFLICT, "仅正常状态的家属卡可以解除绑定"); |
| | | } |
| | | int updated = mapper.update( |
| | | null, |
| | | Wrappers.<DependentCardApply>lambdaUpdate() |
| | | .eq(DependentCardApply::getId, id) |
| | | .eq(DependentCardApply::getStatus, DependentCardApply.STATUS_APPROVED) |
| | | .set(DependentCardApply::getStatus, DependentCardApply.STATUS_CANCELLED) |
| | | ); |
| | | if (updated != 1) { |
| | | throw new BusinessException(ErrorCode.CONFLICT, "家属卡状态已变化,请刷新后重试"); |
| | | } |
| | | if (statusLogMapper.insert(new DependentCardApplyStatusLog( |
| | | apply.getId(), |
| | | apply.getApplyNo(), |
| | | DependentCardApply.STATUS_APPROVED, |
| | | DependentCardApply.STATUS_CANCELLED, |
| | | "ADMIN", |
| | | operatorId, |
| | | "管理后台解除绑定" |
| | | )) != 1) { |
| | | throw new BusinessException(ErrorCode.INTERNAL_ERROR, "解绑状态记录保存失败"); |
| | | } |
| | | DependentCardApply updatedApply = mapper.selectById(id); |
| | | return DependentCardApplyAdminResponse.from(updatedApply, applicationExtra(updatedApply)); |
| | | } |
| | | |
| | | private Map<String, Object> applicationExtra(DependentCardApply apply) { |
| | | if (apply == null || apply.getTalentCardId() == null) { |
| | | return Map.of(); |
| | | } |
| | | List<Map<String, Object>> rows = jdbcTemplate.queryForList(""" |
| | | select u.id_card_no as talentIdCardNo, |
| | | u.gender as gender, |
| | | u.contact_address as contactAddress, |
| | | u.work_address as workAddress, |
| | | ao.display_name as auditorName, |
| | | c.valid_start as validStart, |
| | | c.valid_end as validEnd, |
| | | c.issue_time as issueTime, |
| | | c.card_level as cardLevel, |
| | | (select count(*) from dependent_card_apply approved |
| | | where approved.deleted = 0 |
| | | and approved.talent_card_id = a.talent_card_id |
| | | and approved.status = 1) as currentFamilyCardCount |
| | | from dependent_card_apply a |
| | | left join talent_user u on u.id = a.talent_id and u.deleted = 0 |
| | | left join talent_card c on c.id = a.talent_card_id and c.deleted = 0 |
| | | left join admin_operator ao on ao.id = a.audit_by |
| | | where a.id = ? and a.deleted = 0 |
| | | limit 1 |
| | | """, apply.getId()); |
| | | if (rows.isEmpty()) { |
| | | return Map.of(); |
| | | } |
| | | Map<String, Object> extra = new LinkedHashMap<>(rows.get(0)); |
| | | // 名额按真正生效的规则算:审核/申请侧也是这个口径。之前直接读 talent_card.family_card_limit, |
| | | // 那一列在库里基本是 0(按卡配置那条路没启用),页面就成了「1/0」。 |
| | | extra.put("familyCardLimit", familyCardLimit(string(extra.get("cardLevel")))); |
| | | return extra; |
| | | } |
| | | |
| | | /** |
| | | * 家属附属卡名额:卓越人才卡 2 个({@link #MAX_FAMILY_CARD_COUNT}),其它等级没有名额。 |
| | | * 展示与限流都用这一个口径,避免「页面显示 0、实际能绑 2 个」。 |
| | | */ |
| | | private int familyCardLimit(String cardLevel) { |
| | | return cardLevel != null && ELITE_CARD_LEVEL.equals(cardLevel.trim()) ? MAX_FAMILY_CARD_COUNT : 0; |
| | | } |
| | | |
| | | private String string(Object value) { |
| | | return value == null ? "" : String.valueOf(value).trim(); |
| | | } |
| | | |
| | | private void ensureApprovalSlot(DependentCardApply apply) { |
| | |
| | | if (ownerCard == null) { |
| | | throw new BusinessException(ErrorCode.CONFLICT, "主卡不存在或已失效,不能审核通过"); |
| | | } |
| | | if (!ELITE_CARD_LEVEL.equals(ownerCard.getCardLevel()) |
| | | || !Integer.valueOf(1).equals(ownerCard.getIsFamilyCardEnabled())) { |
| | | if (!ELITE_CARD_LEVEL.equals(ownerCard.getCardLevel())) { |
| | | 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); |
| | | // Integer configuredLimit = ownerCard.getFamilyCardLimit(); |
| | | // if (configuredLimit == null || configuredLimit <= 0) { |
| | | // throw new BusinessException(ErrorCode.CONFLICT, "该主卡未配置家属附属卡名额"); |
| | | // } |
| | | int familyCardLimit = familyCardLimit(ownerCard.getCardLevel()); |
| | | long approvedCount = mapper.selectCount( |
| | | Wrappers.<DependentCardApply>lambdaQuery() |
| | | .eq(DependentCardApply::getTalentCardId, apply.getTalentCardId()) |
| | |
| | | } |
| | | DependentCardApply apply = mapper.selectById(id); |
| | | if (apply == null) { |
| | | throw new BusinessException(ErrorCode.NOT_FOUND, "家属附属卡申请不存在"); |
| | | throw new BusinessException(ErrorCode.NOT_FOUND, "家属卡申请不存在"); |
| | | } |
| | | return apply; |
| | | } |
| | |
| | | } |
| | | return value.trim(); |
| | | } |
| | | } |
| | | } |