| | |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.LinkedHashSet; |
| | | import java.util.List; |
| | | import java.util.Locale; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | import java.util.UUID; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; |
| | |
| | | import com.talentcard.server.entity.TicketPackage; |
| | | import com.talentcard.server.entity.TicketPackageClaim; |
| | | import com.talentcard.server.entity.TicketPackageCode; |
| | | import com.talentcard.server.entity.TicketPackageLevelRule; |
| | | import com.talentcard.server.entity.TicketPackageMerchant; |
| | | import com.talentcard.server.mapper.BenefitMerchantMapper; |
| | | import com.talentcard.server.mapper.TicketPackageClaimMapper; |
| | | import com.talentcard.server.mapper.TicketPackageCodeMapper; |
| | | import com.talentcard.server.mapper.TicketPackageLevelRuleMapper; |
| | | import com.talentcard.server.mapper.TicketPackageMapper; |
| | | import com.talentcard.server.mapper.TicketPackageMerchantMapper; |
| | | import com.talentcard.server.request.TicketPackageCodeImportRequest; |
| | | import com.talentcard.server.request.TicketPackageCodeGenerateRequest; |
| | | import com.talentcard.server.request.TicketPackageCodeLockRequest; |
| | | import com.talentcard.server.request.TicketPackageCodeStatusRequest; |
| | | import com.talentcard.server.request.TicketPackageSupplementRequest; |
| | |
| | | private final TicketPackageCodeMapper codeMapper; |
| | | private final TicketPackageClaimMapper claimMapper; |
| | | private final BenefitMerchantMapper merchantMapper; |
| | | private final TicketPackageMerchantMapper packageMerchantMapper; |
| | | private final TicketPackageLevelRuleMapper levelRuleMapper; |
| | | |
| | | public TicketPackageAdminService( |
| | | TicketPackageMapper packageMapper, |
| | | TicketPackageCodeMapper codeMapper, |
| | | TicketPackageClaimMapper claimMapper, |
| | | BenefitMerchantMapper merchantMapper |
| | | BenefitMerchantMapper merchantMapper, |
| | | TicketPackageMerchantMapper packageMerchantMapper, |
| | | TicketPackageLevelRuleMapper levelRuleMapper |
| | | ) { |
| | | this.packageMapper = packageMapper; |
| | | this.codeMapper = codeMapper; |
| | | this.claimMapper = claimMapper; |
| | | this.merchantMapper = merchantMapper; |
| | | this.packageMerchantMapper = packageMerchantMapper; |
| | | this.levelRuleMapper = levelRuleMapper; |
| | | } |
| | | |
| | | @Transactional(readOnly = true) |
| | | public PageResult<Map<String, Object>> packages(Long merchantId, Integer status, String keyword, int page, int size) { |
| | | public PageResult<Map<String, Object>> packages(Long merchantId, Integer status, String keyword, String merchantKeyword, int page, int size) { |
| | | int safePage = Math.max(page, 1); |
| | | int safeSize = Math.min(Math.max(size, 1), 100); |
| | | Integer normalizedStatus = normalizeStatusFilter(status); |
| | | Long total = packageMapper.countAdminPage(merchantId, normalizedStatus, clean(keyword)); |
| | | List<Map<String, Object>> records = packageMapper.selectAdminPage(merchantId, normalizedStatus, clean(keyword), safeSize, (safePage - 1) * safeSize) |
| | | Long total = packageMapper.countAdminPage(merchantId, normalizedStatus, clean(keyword), clean(merchantKeyword)); |
| | | List<Map<String, Object>> records = packageMapper.selectAdminPage(merchantId, normalizedStatus, clean(keyword), clean(merchantKeyword), safeSize, (safePage - 1) * safeSize) |
| | | .stream() |
| | | .map(this::packageView) |
| | | .toList(); |
| | |
| | | |
| | | @Transactional |
| | | public Map<String, Object> createPackage(TicketPackageSaveRequest request) { |
| | | validateMerchant(request.merchantId()); |
| | | List<Long> merchantIds = validateMerchants(request); |
| | | TicketPackage entity = new TicketPackage(); |
| | | entity.setPackageNo(BusinessIdGenerator.next("TP")); |
| | | apply(entity, request); |
| | | entity.setDeleted(0); |
| | | packageMapper.insert(entity); |
| | | savePackageMerchants(entity.getId(), merchantIds); |
| | | saveLevelRules(entity.getId(), request); |
| | | return packageDetail(entity.getId()); |
| | | } |
| | | |
| | | @Transactional |
| | | public Map<String, Object> updatePackage(Long id, TicketPackageSaveRequest request) { |
| | | Long packageId = requireId(id, "票包ID不能为空"); |
| | | validateMerchant(request.merchantId()); |
| | | List<Long> merchantIds = validateMerchants(request); |
| | | TicketPackage current = packageMapper.selectByIdForUpdate(packageId); |
| | | if (current == null) { |
| | | throw new BusinessException(ErrorCode.NOT_FOUND, "票包不存在"); |
| | |
| | | if (updated != 1) { |
| | | throw new BusinessException(ErrorCode.CONFLICT, "票包配置已变化,请刷新后重试"); |
| | | } |
| | | savePackageMerchants(packageId, merchantIds); |
| | | saveLevelRules(packageId, request); |
| | | return packageDetail(packageId); |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | @Transactional |
| | | public Map<String, Object> generateCodes(Long packageId, TicketPackageCodeGenerateRequest request) { |
| | | TicketPackage ticketPackage = requirePackage(packageId); |
| | | int target = Math.max(request.codeCount() == null ? 0 : request.codeCount(), 0); |
| | | if (target < 1) { |
| | | throw new BusinessException(ErrorCode.BAD_REQUEST, "生成数量不能为空"); |
| | | } |
| | | String batchNo = clean(request.batchNo()).isBlank() ? BusinessIdGenerator.next("TPC") : clean(request.batchNo()); |
| | | String remark = clean(request.remark()); |
| | | String codePrefix = normalizedGeneratedCodePrefix(request.codePrefix()); |
| | | Set<String> generatedCodes = new LinkedHashSet<>(); |
| | | int imported = 0; |
| | | int duplicate = 0; |
| | | int attempts = 0; |
| | | int maxAttempts = Math.max(target * 20, 100); |
| | | while (imported < target && attempts < maxAttempts) { |
| | | attempts++; |
| | | String exchangeCode = buildGeneratedExchangeCode(codePrefix); |
| | | if (!generatedCodes.add(exchangeCode)) { |
| | | duplicate++; |
| | | continue; |
| | | } |
| | | TicketPackageCode entity = new TicketPackageCode(); |
| | | entity.setPackageId(ticketPackage.getId()); |
| | | entity.setExchangeCode(exchangeCode); |
| | | entity.setBatchNo(batchNo); |
| | | entity.setStatus("AVAILABLE"); |
| | | entity.setRemark(remark); |
| | | entity.setDeleted(0); |
| | | try { |
| | | codeMapper.insert(entity); |
| | | imported++; |
| | | } catch (DuplicateKeyException exception) { |
| | | duplicate++; |
| | | generatedCodes.remove(exchangeCode); |
| | | } |
| | | } |
| | | if (imported < target) { |
| | | throw new BusinessException(ErrorCode.CONFLICT, "兑换码生成失败,请重试"); |
| | | } |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("packageId", ticketPackage.getId()); |
| | | result.put("batchNo", batchNo); |
| | | result.put("submittedCount", target); |
| | | result.put("distinctCount", generatedCodes.size()); |
| | | result.put("importedCount", imported); |
| | | result.put("duplicateCount", duplicate); |
| | | result.put("stock", stock(ticketPackage.getId())); |
| | | return result; |
| | | } |
| | | |
| | | @Transactional |
| | | public Map<String, Object> importCodeFile(Long packageId, MultipartFile file, String batchNo, String remark) { |
| | | if (file == null || file.isEmpty()) { |
| | | throw new BusinessException(ErrorCode.BAD_REQUEST, "兑换码文件不能为空"); |
| | |
| | | } |
| | | |
| | | @Transactional(readOnly = true) |
| | | public PageResult<Map<String, Object>> codes(Long packageId, String status, String batchNo, String keyword, int page, int size) { |
| | | public PageResult<Map<String, Object>> codes(Long packageId, String status, String batchNo, String keyword, String packageKeyword, String merchantKeyword, int page, int size) { |
| | | int safePage = Math.max(page, 1); |
| | | int safeSize = Math.min(Math.max(size, 1), 100); |
| | | String normalizedStatus = normalizeCodeStatusFilter(status); |
| | | Long total = codeMapper.countAdminPage(packageId, normalizedStatus, clean(batchNo), clean(keyword)); |
| | | List<Map<String, Object>> records = codeMapper.selectAdminPage(packageId, normalizedStatus, clean(batchNo), clean(keyword), safeSize, (safePage - 1) * safeSize) |
| | | Long total = codeMapper.countAdminPage(packageId, normalizedStatus, clean(batchNo), clean(keyword), clean(packageKeyword), clean(merchantKeyword)); |
| | | List<Map<String, Object>> records = codeMapper.selectAdminPage(packageId, normalizedStatus, clean(batchNo), clean(keyword), clean(packageKeyword), clean(merchantKeyword), safeSize, (safePage - 1) * safeSize) |
| | | .stream() |
| | | .map(this::codeView) |
| | | .toList(); |
| | |
| | | .eq("id", current.getIssuedClaimId()) |
| | | .eq("deleted", 0)); |
| | | } |
| | | return codeView(codeMapper.selectAdminPage(current.getPackageId(), "", "", current.getExchangeCode(), 1, 0).stream().findFirst().orElse(Map.of())); |
| | | return codeView(codeMapper.selectAdminPage(current.getPackageId(), "", "", current.getExchangeCode(), "", "", 1, 0).stream().findFirst().orElse(Map.of())); |
| | | } |
| | | |
| | | @Transactional(readOnly = true) |
| | | public PageResult<Map<String, Object>> claims(Long packageId, Long talentId, String status, String keyword, int page, int size) { |
| | | public PageResult<Map<String, Object>> claims(Long packageId, Long talentId, String status, String keyword, String packageKeyword, String talentKeyword, String cardLevel, LocalDate claimedStart, LocalDate claimedEnd, int page, int size) { |
| | | int safePage = Math.max(page, 1); |
| | | int safeSize = Math.min(Math.max(size, 1), 100); |
| | | String normalizedStatus = normalizeClaimStatusFilter(status); |
| | | Long total = claimMapper.countAdminPage(packageId, talentId, normalizedStatus, clean(keyword)); |
| | | List<Map<String, Object>> records = claimMapper.selectAdminPage(packageId, talentId, normalizedStatus, clean(keyword), safeSize, (safePage - 1) * safeSize) |
| | | Long total = claimMapper.countAdminPage(packageId, talentId, normalizedStatus, clean(keyword), clean(packageKeyword), clean(talentKeyword), clean(cardLevel), claimedStart, claimedEnd); |
| | | List<Map<String, Object>> records = claimMapper.selectAdminPage(packageId, talentId, normalizedStatus, clean(keyword), clean(packageKeyword), clean(talentKeyword), clean(cardLevel), claimedStart, claimedEnd, safeSize, (safePage - 1) * safeSize) |
| | | .stream() |
| | | .map(this::claimView) |
| | | .toList(); |
| | |
| | | String text = clean(value).toUpperCase(Locale.ROOT); |
| | | return List.of("兑换码", "券码", "码", "EXCHANGE_CODE", "CODE").contains(text); |
| | | } |
| | | |
| | | private String normalizedGeneratedCodePrefix(String codePrefix) { |
| | | String prefix = clean(codePrefix).replaceAll("[^A-Za-z0-9]", "").toUpperCase(Locale.ROOT); |
| | | if (prefix.isBlank()) { |
| | | return "EX"; |
| | | } |
| | | return prefix.length() > 16 ? prefix.substring(0, 16) : prefix; |
| | | } |
| | | |
| | | private String buildGeneratedExchangeCode(String codePrefix) { |
| | | return normalizedGeneratedCodePrefix(codePrefix) + UUID.randomUUID().toString().replace("-", "").toUpperCase(Locale.ROOT); |
| | | } |
| | | |
| | | private void apply(TicketPackage entity, TicketPackageSaveRequest request) { |
| | | entity.setPackageName(cleanRequired(request.packageName(), "票包名称不能为空")); |
| | | entity.setMerchantId(request.merchantId()); |
| | | List<Long> merchantIds = normalizedMerchantIds(request); |
| | | entity.setTotalQuantity(Math.max(request.totalQuantity() == null ? 0 : request.totalQuantity(), 0)); |
| | | entity.setMerchantId(merchantIds.isEmpty() ? null : merchantIds.get(0)); |
| | | entity.setCoverUrl(clean(request.coverUrl())); |
| | | entity.setTalentLevels(joinTalentLevels(request.talentLevels())); |
| | | entity.setTalentLevels(joinTalentLevels(levelsFromRules(request))); |
| | | entity.setClaimPrice(request.claimPrice() == null ? BigDecimal.ZERO : request.claimPrice()); |
| | | entity.setPerUserLimit(request.perUserLimit() == null ? 1 : request.perUserLimit()); |
| | | entity.setPerUserLimit(defaultPerUserLimit(request)); |
| | | entity.setValidStart(request.validStart()); |
| | | entity.setValidEnd(request.validEnd()); |
| | | if (entity.getValidStart() != null && entity.getValidEnd() != null && entity.getValidEnd().isBefore(entity.getValidStart())) { |
| | |
| | | result.put("id", value(row, "id")); |
| | | result.put("packageNo", text(row, "packageNo", "package_no")); |
| | | result.put("packageName", text(row, "packageName", "package_name")); |
| | | result.put("totalQuantity", intValue(value(row, "totalQuantity", "total_quantity"))); |
| | | result.put("merchantId", value(row, "merchantId", "merchant_id")); |
| | | result.put("merchantName", text(row, "merchantName", "merchant_name")); |
| | | List<Long> merchantIds = splitLongs(text(row, "merchantIdsText", "merchant_ids_text")); |
| | | Long legacyMerchantId = longValue(row, "merchantId", "merchant_id"); |
| | | if (merchantIds.isEmpty() && legacyMerchantId != null) { |
| | | merchantIds = List.of(legacyMerchantId); |
| | | } |
| | | result.put("merchantIds", merchantIds); |
| | | result.put("merchantNames", text(row, "merchantNames", "merchant_names")); |
| | | result.put("coverUrl", text(row, "coverUrl", "cover_url")); |
| | | result.put("talentLevels", splitLevels(text(row, "talentLevels", "talent_levels"))); |
| | | result.put("levelRules", parseLevelRules(text(row, "levelRulesText", "level_rules_text"))); |
| | | result.put("claimPrice", value(row, "claimPrice", "claim_price")); |
| | | result.put("perUserLimit", intValue(value(row, "perUserLimit", "per_user_limit"))); |
| | | result.put("validStart", value(row, "validStart", "valid_start")); |
| | |
| | | result.put("issuedCodes", intValue(value(row, "issuedCodes", "issued_codes"))); |
| | | result.put("usedCodes", intValue(value(row, "usedCodes", "used_codes"))); |
| | | result.put("voidCodes", intValue(value(row, "voidCodes", "void_codes"))); |
| | | result.put("claimedCount", intValue(value(row, "claimedCount", "claimed_count"))); |
| | | result.put("remainingCount", Math.max(intValue(value(row, "totalQuantity", "total_quantity")) - intValue(result.get("claimedCount")), 0)); |
| | | result.put("createdAt", value(row, "createdAt", "created_at")); |
| | | result.put("updatedAt", value(row, "updatedAt", "updated_at")); |
| | | return result; |
| | |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("id", value(row, "id")); |
| | | result.put("packageId", value(row, "packageId", "package_id")); |
| | | result.put("codeId", value(row, "id")); |
| | | result.put("packageNo", text(row, "packageNo", "package_no")); |
| | | result.put("packageName", text(row, "packageName", "package_name")); |
| | | result.put("merchantName", text(row, "merchantName", "merchant_name")); |
| | | result.put("merchantNames", text(row, "merchantNames", "merchant_names")); |
| | | result.put("exchangeCode", text(row, "exchangeCode", "exchange_code")); |
| | | result.put("batchNo", text(row, "batchNo", "batch_no")); |
| | | result.put("status", text(row, "status")); |
| | | result.put("statusName", codeStatusName(text(row, "status"))); |
| | | result.put("lockedByTalentId", value(row, "lockedByTalentId", "locked_by_talent_id")); |
| | | result.put("lockedByCardId", value(row, "lockedByCardId", "locked_by_card_id")); |
| | | result.put("lockedTalentName", text(row, "lockedTalentName", "locked_talent_name")); |
| | | result.put("lockedTalentMobile", text(row, "lockedTalentMobile", "locked_talent_mobile")); |
| | | result.put("lockedAt", value(row, "lockedAt", "locked_at")); |
| | | result.put("issuedClaimId", value(row, "issuedClaimId", "issued_claim_id")); |
| | | result.put("issuedAt", value(row, "issuedAt", "issued_at")); |
| | |
| | | result.put("packageName", text(row, "packageName", "package_name")); |
| | | result.put("merchantId", value(row, "merchantId", "merchant_id")); |
| | | result.put("merchantName", text(row, "merchantName", "merchant_name")); |
| | | result.put("merchantNames", text(row, "merchantNames", "merchant_names")); |
| | | result.put("talentId", value(row, "talentId", "talent_id")); |
| | | result.put("cardId", value(row, "cardId", "card_id")); |
| | | result.put("cardNo", text(row, "cardNo", "card_no")); |
| | |
| | | result.put("exchangeCode", text(row, "exchangeCode", "exchange_code")); |
| | | result.put("status", text(row, "status")); |
| | | result.put("statusName", claimStatusName(text(row, "status"))); |
| | | result.put("claimStatus", text(row, "status")); |
| | | result.put("claimStatusName", claimStatusName(text(row, "status"))); |
| | | result.put("useStatus", "USED".equalsIgnoreCase(text(row, "status")) ? "USED" : "ACTIVE"); |
| | | result.put("useStatusName", "USED".equalsIgnoreCase(text(row, "status")) ? "已使用" : "未使用"); |
| | | result.put("claimedAt", value(row, "createdAt", "created_at")); |
| | | result.put("validEnd", value(row, "validEnd", "valid_end")); |
| | | result.put("source", claimSource(text(row, "idempotencyKey"))); |
| | | result.put("sourceName", "MANUAL".equals(claimSource(text(row, "idempotencyKey"))) ? "后台补录" : "线上领取"); |
| | |
| | | return ticketPackage; |
| | | } |
| | | |
| | | private void validateMerchant(Long merchantId) { |
| | | if (merchantId == null) { |
| | | return; |
| | | private List<Long> validateMerchants(TicketPackageSaveRequest request) { |
| | | List<Long> merchantIds = normalizedMerchantIds(request); |
| | | for (Long merchantId : merchantIds) { |
| | | BenefitMerchant merchant = merchantMapper.selectOne(new QueryWrapper<BenefitMerchant>() |
| | | .eq("id", merchantId) |
| | | .eq("deleted", 0) |
| | | .last("limit 1")); |
| | | if (merchant == null) { |
| | | throw new BusinessException(ErrorCode.NOT_FOUND, "指定商家不存在"); |
| | | } |
| | | } |
| | | BenefitMerchant merchant = merchantMapper.selectOne(new QueryWrapper<BenefitMerchant>() |
| | | .eq("id", merchantId) |
| | | .eq("deleted", 0) |
| | | .last("limit 1")); |
| | | if (merchant == null) { |
| | | throw new BusinessException(ErrorCode.NOT_FOUND, "指定商家不存在"); |
| | | return merchantIds; |
| | | } |
| | | |
| | | private List<Long> normalizedMerchantIds(TicketPackageSaveRequest request) { |
| | | List<Long> result = new ArrayList<>(); |
| | | if (request.merchantIds() != null) { |
| | | for (Long merchantId : request.merchantIds()) { |
| | | if (merchantId != null && merchantId > 0 && !result.contains(merchantId)) { |
| | | result.add(merchantId); |
| | | } |
| | | } |
| | | } |
| | | if (result.isEmpty() && request.merchantId() != null && request.merchantId() > 0) { |
| | | result.add(request.merchantId()); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private void savePackageMerchants(Long packageId, List<Long> merchantIds) { |
| | | UpdateWrapper<TicketPackageMerchant> clear = new UpdateWrapper<TicketPackageMerchant>() |
| | | .set("deleted", 1) |
| | | .eq("package_id", packageId) |
| | | .eq("deleted", 0); |
| | | if (!merchantIds.isEmpty()) { |
| | | clear.notIn("merchant_id", merchantIds); |
| | | } |
| | | packageMerchantMapper.update(null, clear); |
| | | int sortNo = 0; |
| | | for (Long merchantId : merchantIds) { |
| | | TicketPackageMerchant current = packageMerchantMapper.selectOne(new QueryWrapper<TicketPackageMerchant>() |
| | | .eq("package_id", packageId) |
| | | .eq("merchant_id", merchantId) |
| | | .last("limit 1")); |
| | | if (current == null) { |
| | | TicketPackageMerchant row = new TicketPackageMerchant(); |
| | | row.setPackageId(packageId); |
| | | row.setMerchantId(merchantId); |
| | | row.setSortNo(sortNo++); |
| | | row.setDeleted(0); |
| | | packageMerchantMapper.insert(row); |
| | | } else { |
| | | packageMerchantMapper.update(null, new UpdateWrapper<TicketPackageMerchant>() |
| | | .set("sort_no", sortNo++) |
| | | .set("deleted", 0) |
| | | .eq("id", current.getId())); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void saveLevelRules(Long packageId, TicketPackageSaveRequest request) { |
| | | List<TicketPackageSaveRequest.TicketPackageLevelRuleRequest> rules = levelRules(request); |
| | | List<String> ruleLevels = rules.stream() |
| | | .map(rule -> normalizeCardLevel(rule.talentLevel())) |
| | | .filter(level -> !level.isBlank()) |
| | | .distinct() |
| | | .toList(); |
| | | UpdateWrapper<TicketPackageLevelRule> clear = new UpdateWrapper<TicketPackageLevelRule>() |
| | | .set("deleted", 1) |
| | | .eq("package_id", packageId) |
| | | .eq("deleted", 0); |
| | | if (!ruleLevels.isEmpty()) { |
| | | clear.notIn("talent_level", ruleLevels); |
| | | } |
| | | levelRuleMapper.update(null, clear); |
| | | for (TicketPackageSaveRequest.TicketPackageLevelRuleRequest rule : rules) { |
| | | String level = normalizeCardLevel(rule.talentLevel()); |
| | | if (!CARD_LEVELS.contains(level)) { |
| | | throw new BusinessException(ErrorCode.BAD_REQUEST, "适用人才卡等级仅支持卓越人才卡、杰出人才卡、菁英人才卡"); |
| | | } |
| | | int enabled = rule.enabled() == null ? 1 : normalizeStatus(rule.enabled()); |
| | | int claimableCount = Math.max(rule.claimableCount() == null ? 1 : rule.claimableCount(), 1); |
| | | String resetCycle = normalizeResetCycle(rule.resetCycle()); |
| | | TicketPackageLevelRule current = levelRuleMapper.selectOne(new QueryWrapper<TicketPackageLevelRule>() |
| | | .eq("package_id", packageId) |
| | | .eq("talent_level", level) |
| | | .last("limit 1")); |
| | | if (current == null) { |
| | | TicketPackageLevelRule entity = new TicketPackageLevelRule(); |
| | | entity.setPackageId(packageId); |
| | | entity.setTalentLevel(level); |
| | | entity.setEnabled(enabled); |
| | | entity.setClaimableCount(claimableCount); |
| | | entity.setResetCycle(resetCycle); |
| | | entity.setDeleted(0); |
| | | levelRuleMapper.insert(entity); |
| | | continue; |
| | | } |
| | | TicketPackageLevelRule entity = new TicketPackageLevelRule(); |
| | | entity.setEnabled(enabled); |
| | | entity.setClaimableCount(claimableCount); |
| | | entity.setResetCycle(resetCycle); |
| | | entity.setDeleted(0); |
| | | levelRuleMapper.update(entity, new UpdateWrapper<TicketPackageLevelRule>().eq("id", current.getId())); |
| | | } |
| | | } |
| | | |
| | | private List<TicketPackageSaveRequest.TicketPackageLevelRuleRequest> levelRules(TicketPackageSaveRequest request) { |
| | | if (request.levelRules() != null && !request.levelRules().isEmpty()) { |
| | | return request.levelRules(); |
| | | } |
| | | List<String> levels = request.talentLevels() == null || request.talentLevels().isEmpty() ? CARD_LEVELS : request.talentLevels(); |
| | | List<TicketPackageSaveRequest.TicketPackageLevelRuleRequest> result = new ArrayList<>(); |
| | | for (String level : CARD_LEVELS) { |
| | | int enabled = levels.stream().anyMatch(item -> normalizeCardLevel(item).equals(level)) ? 1 : 0; |
| | | result.add(new TicketPackageSaveRequest.TicketPackageLevelRuleRequest(level, enabled, |
| | | request.perUserLimit() == null ? 1 : request.perUserLimit(), "每天")); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private List<String> levelsFromRules(TicketPackageSaveRequest request) { |
| | | List<String> result = new ArrayList<>(); |
| | | for (TicketPackageSaveRequest.TicketPackageLevelRuleRequest rule : levelRules(request)) { |
| | | if (rule.enabled() == null || rule.enabled() == 1) { |
| | | String level = normalizeCardLevel(rule.talentLevel()); |
| | | if (!result.contains(level)) { |
| | | result.add(level); |
| | | } |
| | | } |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private int defaultPerUserLimit(TicketPackageSaveRequest request) { |
| | | int limit = request.perUserLimit() == null ? 1 : request.perUserLimit(); |
| | | for (TicketPackageSaveRequest.TicketPackageLevelRuleRequest rule : levelRules(request)) { |
| | | if (rule.enabled() == null || rule.enabled() == 1) { |
| | | limit = Math.max(rule.claimableCount() == null ? limit : rule.claimableCount(), 1); |
| | | break; |
| | | } |
| | | } |
| | | return Math.max(limit, 1); |
| | | } |
| | | |
| | | private String joinTalentLevels(List<String> levels) { |
| | |
| | | if (value.isBlank()) { |
| | | continue; |
| | | } |
| | | value = normalizeCardLevel(value); |
| | | if (!CARD_LEVELS.contains(value)) { |
| | | throw new BusinessException(ErrorCode.BAD_REQUEST, "适用人才卡等级仅支持卓越人才卡、杰出人才卡、菁英人才卡"); |
| | | } |
| | |
| | | return List.of(); |
| | | } |
| | | return List.of(text.split("[、,,]")); |
| | | } |
| | | |
| | | private List<Long> splitLongs(String value) { |
| | | String text = clean(value); |
| | | if (text.isBlank()) { |
| | | return List.of(); |
| | | } |
| | | List<Long> result = new ArrayList<>(); |
| | | for (String part : text.split(",")) { |
| | | try { |
| | | Long id = Long.valueOf(part.trim()); |
| | | if (id > 0 && !result.contains(id)) { |
| | | result.add(id); |
| | | } |
| | | } catch (NumberFormatException ignored) { |
| | | // Ignore dirty historical data in aggregate text. |
| | | } |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private List<Map<String, Object>> parseLevelRules(String value) { |
| | | Map<String, Map<String, Object>> byLevel = new HashMap<>(); |
| | | String text = clean(value); |
| | | if (!text.isBlank()) { |
| | | for (String item : text.split(";;")) { |
| | | String[] parts = item.split("\\|", -1); |
| | | if (parts.length < 4) { |
| | | continue; |
| | | } |
| | | String level = normalizeCardLevel(parts[0]); |
| | | if (!CARD_LEVELS.contains(level)) { |
| | | continue; |
| | | } |
| | | Map<String, Object> row = new LinkedHashMap<>(); |
| | | row.put("talentLevel", level); |
| | | row.put("enabled", parseInt(parts[1], 1)); |
| | | row.put("claimableCount", Math.max(parseInt(parts[2], 1), 1)); |
| | | row.put("resetCycle", normalizeResetCycle(parts[3])); |
| | | byLevel.put(level, row); |
| | | } |
| | | } |
| | | List<Map<String, Object>> result = new ArrayList<>(); |
| | | for (String level : CARD_LEVELS) { |
| | | result.add(byLevel.getOrDefault(level, defaultLevelRule(level))); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private Map<String, Object> defaultLevelRule(String level) { |
| | | Map<String, Object> row = new LinkedHashMap<>(); |
| | | row.put("talentLevel", level); |
| | | row.put("enabled", 1); |
| | | row.put("claimableCount", 1); |
| | | row.put("resetCycle", "每天"); |
| | | return row; |
| | | } |
| | | |
| | | private int parseInt(String value, int fallback) { |
| | | try { |
| | | return Integer.parseInt(clean(value)); |
| | | } catch (NumberFormatException exception) { |
| | | return fallback; |
| | | } |
| | | } |
| | | |
| | | private String normalizeCardLevel(String level) { |
| | | String value = clean(level); |
| | | String upper = value.toUpperCase(Locale.ROOT); |
| | | if (upper.contains("EXCELLENT") || value.contains("卓越")) { |
| | | return "卓越人才卡"; |
| | | } |
| | | if (upper.contains("OUTSTANDING") || value.contains("杰出")) { |
| | | return "杰出人才卡"; |
| | | } |
| | | if (upper.contains("ELITE") || value.contains("菁英")) { |
| | | return "菁英人才卡"; |
| | | } |
| | | return value; |
| | | } |
| | | |
| | | private String normalizeResetCycle(String resetCycle) { |
| | | String value = clean(resetCycle); |
| | | if (value.isBlank()) { |
| | | return "每天"; |
| | | } |
| | | String upper = value.toUpperCase(Locale.ROOT); |
| | | if (upper.contains("DAY") || value.contains("天") || value.contains("日")) { |
| | | return "每天"; |
| | | } |
| | | if (upper.contains("WEEK") || value.contains("周") || value.contains("星期")) { |
| | | return "每周"; |
| | | } |
| | | if (upper.contains("MONTH") || value.contains("月")) { |
| | | return "每月"; |
| | | } |
| | | if (upper.contains("YEAR") || upper.contains("ANNUAL") || value.contains("年")) { |
| | | return "每年"; |
| | | } |
| | | return value; |
| | | } |
| | | |
| | | private int normalizeStatus(Integer status) { |
| | |
| | | private String clean(String value) { |
| | | return value == null ? "" : value.trim(); |
| | | } |
| | | } |
| | | } |