| | |
| | | import org.springframework.scheduling.annotation.Scheduled; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.time.format.DateTimeFormatter; |
| | |
| | | public class PointsSettlementService { |
| | | |
| | | private static final DateTimeFormatter ORDER_NO_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); |
| | | private static final String SETTLEMENT_CLEAR_SOURCE_PREFIX = "config-clear-"; |
| | | private static final String SETTLEMENT_CLEAR_REFERENCE_PREFIX = "POINTS_CONFIG-"; |
| | | private static final Logger log = LoggerFactory.getLogger(PointsSettlementService.class); |
| | | |
| | | private final PointsConfigMapper pointsConfigMapper; |
| | | private final UserPointsAccountMapper userPointsAccountMapper; |
| | |
| | | .eq(PointsConfig::getSettlementStatus, 0) |
| | | .le(PointsConfig::getActivityEndAt, now) |
| | | .orderByAsc(PointsConfig::getId)); |
| | | if (!configs.isEmpty()) { |
| | | log.info("points settlement scan found {} expired pending configs", configs.size()); |
| | | } |
| | | for (PointsConfig config : configs) { |
| | | settleConfig(config.getId()); |
| | | } |
| | |
| | | return; |
| | | } |
| | | |
| | | log.info("points settlement start, configId={}, activityEndAt={}, requiredPoints={}", |
| | | config.getId(), config.getActivityEndAt(), config.getRequiredPoints()); |
| | | List<PointsSettlementCandidateRow> candidates = userPointsAccountMapper.selectSettlementCandidates(); |
| | | List<PointsExchangeOrderItem> createdItems = new ArrayList<PointsExchangeOrderItem>(); |
| | | LocalDateTime now = LocalDateTime.now(); |
| | | int qualifiedCount = 0; |
| | | int clearedOnlyCount = 0; |
| | | for (PointsSettlementCandidateRow candidate : candidates) { |
| | | int balance = candidate.getPointsBalance() == null ? 0 : candidate.getPointsBalance(); |
| | | if (balance <= 0) { |
| | |
| | | PointsExchangeOrderItem item = buildOrderItem(config, candidate, now); |
| | | pointsExchangeOrderItemMapper.insert(item); |
| | | createdItems.add(item); |
| | | qualifiedCount++; |
| | | } else { |
| | | pointsLedgerAppService.createSettlementClearLedger( |
| | | buildSettlementClearSourceId(config.getId(), candidate.getAppUserId()), |
| | | candidate.getAppUserId(), |
| | | candidate.getUserNickname(), |
| | | candidate.getUserPhone(), |
| | | candidate.getClinicId(), |
| | | candidate.getClinicName(), |
| | | candidate.getProvinceCode(), |
| | | candidate.getProvinceName(), |
| | | candidate.getCityCode(), |
| | | candidate.getCityName(), |
| | | candidate.getDistrictCode(), |
| | | candidate.getDistrictName(), |
| | | config.getGiftName(), |
| | | config.getId(), |
| | | buildSettlementReferenceNo(config.getId()), |
| | | balance, |
| | | now |
| | | ); |
| | | clearedOnlyCount++; |
| | | } |
| | | UserPointsAccount account = userPointsAccountMapper.selectById(candidate.getAccountId()); |
| | | if (account != null) { |
| | |
| | | config.setSettlementStatus(1); |
| | | config.setSettledAt(now); |
| | | pointsConfigMapper.updateById(config); |
| | | log.info("points settlement completed, configId={}, qualifiedCount={}, clearedOnlyCount={}, createdItemCount={}, settledAt={}", |
| | | config.getId(), qualifiedCount, clearedOnlyCount, createdItems.size(), now); |
| | | } |
| | | |
| | | private PointsExchangeOrderItem buildOrderItem(PointsConfig config, PointsSettlementCandidateRow candidate, LocalDateTime now) { |
| | |
| | | } |
| | | return "-"; |
| | | } |
| | | |
| | | /** |
| | | * 构建活动结束清零流水的唯一来源标识,确保重复调度不会重复入账。 |
| | | */ |
| | | private Long buildSettlementClearSourceId(Long configId, Long appUserId) { |
| | | String compositeId = SETTLEMENT_CLEAR_SOURCE_PREFIX + (configId == null ? "0" : configId) |
| | | + "-" + (appUserId == null ? "0" : appUserId); |
| | | return Long.valueOf(Math.abs((long) compositeId.hashCode())); |
| | | } |
| | | |
| | | /** |
| | | * 构建活动结算流水引用编号,便于后台追溯到积分活动。 |
| | | */ |
| | | private String buildSettlementReferenceNo(Long configId) { |
| | | return SETTLEMENT_CLEAR_REFERENCE_PREFIX + (configId == null ? 0L : configId.longValue()); |
| | | } |
| | | } |