You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
309 lines
18 KiB
Java
309 lines
18 KiB
Java
package com.yau.digitalrmb.institutionidentity.application;
|
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
import com.yau.digitalrmb.institutionidentity.infrastructure.QuotaControlBitEntity;
|
|
import com.yau.digitalrmb.institutionidentity.infrastructure.QuotaControlBitMapper;
|
|
import com.yau.digitalrmb.institutionidentity.infrastructure.StandardCurrencyBatchEntity;
|
|
import com.yau.digitalrmb.institutionidentity.infrastructure.StandardCurrencyBatchMapper;
|
|
import com.yau.digitalrmb.institutionidentity.infrastructure.StandardCurrencyEntity;
|
|
import com.yau.digitalrmb.institutionidentity.infrastructure.StandardCurrencyMapper;
|
|
import com.yau.digitalrmb.shared.api.ErrorCode;
|
|
import com.yau.digitalrmb.shared.exception.BusinessException;
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
import javax.annotation.Resource;
|
|
import java.math.BigDecimal;
|
|
import java.math.RoundingMode;
|
|
import java.security.SecureRandom;
|
|
import java.time.LocalDateTime;
|
|
import java.time.format.DateTimeFormatter;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
import java.util.stream.Collectors;
|
|
|
|
@Service
|
|
public class StandardCurrencyService {
|
|
public static final String ID_RULE = "批次号+枚序号+随机校验码";
|
|
private static final List<BigDecimal> DENOMINATIONS = Arrays.asList(
|
|
new BigDecimal("100.00"), new BigDecimal("50.00"), new BigDecimal("20.00"),
|
|
new BigDecimal("10.00"), new BigDecimal("5.00"), new BigDecimal("1.00"),
|
|
new BigDecimal("0.50"), new BigDecimal("0.20"), new BigDecimal("0.10"),
|
|
new BigDecimal("0.05"), new BigDecimal("0.01"));
|
|
private static final DateTimeFormatter DATE = DateTimeFormatter.ofPattern("yyyyMMdd");
|
|
private static final char[] CHECK_CODE = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
|
|
|
|
@Resource private QuotaControlBitMapper quotaControlBitMapper;
|
|
@Resource private StandardCurrencyBatchMapper batchMapper;
|
|
@Resource private StandardCurrencyMapper currencyMapper;
|
|
@Resource private InstitutionTrainingErrorRecorder errorRecorder;
|
|
@Resource private InstitutionTrainingScoreService scoreService;
|
|
|
|
@Transactional
|
|
public StandardCurrencyBatchResult decompose(BigDecimal amount, InstitutionKeySubject subject, String operator) {
|
|
QuotaControlBitEntity source = requireStepEight(subject);
|
|
if (amount == null || source.getAmount().compareTo(amount) != 0) {
|
|
throw inputError(subject, "用户输入的货币总额与步骤八额度控制位金额不一致");
|
|
}
|
|
StandardCurrencyBatchEntity current = latest(subject);
|
|
List<DenominationBreakdownResult> breakdown = decomposeAmount(source.getAmount());
|
|
if (current != null) {
|
|
if (!current.getQuotaControlBitId().equals(source.getId())) {
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR,
|
|
"当前实训已有步骤九记录,如需重新开始请先调用重新实训接口");
|
|
}
|
|
currencyMapper.update(null, new LambdaUpdateWrapper<StandardCurrencyEntity>()
|
|
.eq(StandardCurrencyEntity::getBatchId, current.getId())
|
|
.eq(StandardCurrencyEntity::getDeleted, false)
|
|
.set(StandardCurrencyEntity::getDeleted, true)
|
|
.set(StandardCurrencyEntity::getUpdatedAt, LocalDateTime.now())
|
|
.set(StandardCurrencyEntity::getUpdatedBy, operator));
|
|
int updated = batchMapper.update(null, new LambdaUpdateWrapper<StandardCurrencyBatchEntity>()
|
|
.eq(StandardCurrencyBatchEntity::getId, current.getId())
|
|
.eq(StandardCurrencyBatchEntity::getDeleted, false)
|
|
.set(StandardCurrencyBatchEntity::getTotalAmount,
|
|
source.getAmount().setScale(2, RoundingMode.UNNECESSARY))
|
|
.set(StandardCurrencyBatchEntity::getTotalCount,
|
|
breakdown.stream().mapToInt(DenominationBreakdownResult::getCount).sum())
|
|
.set(StandardCurrencyBatchEntity::getDenominationSummary, encode(breakdown))
|
|
.set(StandardCurrencyBatchEntity::getPrefix, null)
|
|
.set(StandardCurrencyBatchEntity::getIdRule, null)
|
|
.set(StandardCurrencyBatchEntity::getCurrencyStatus, null)
|
|
.set(StandardCurrencyBatchEntity::getStatus, "DECOMPOSED")
|
|
.set(StandardCurrencyBatchEntity::getUpdatedAt, LocalDateTime.now())
|
|
.set(StandardCurrencyBatchEntity::getUpdatedBy, operator));
|
|
if (updated != 1) throw stateChanged();
|
|
return result(requireLatest(subject));
|
|
}
|
|
StandardCurrencyBatchEntity batch = new StandardCurrencyBatchEntity();
|
|
batch.setQuotaControlBitId(source.getId());
|
|
batch.setBatchNumber(LocalDateTime.now().format(DATE) + "_"
|
|
+ String.format("%03d", source.getId() % 1000));
|
|
batch.setTotalAmount(source.getAmount().setScale(2, RoundingMode.UNNECESSARY));
|
|
batch.setTotalCount(breakdown.stream().mapToInt(DenominationBreakdownResult::getCount).sum());
|
|
batch.setDenominationSummary(encode(breakdown));
|
|
batch.setStatus("DECOMPOSED");
|
|
batch.setUserId(subject.getUserId());
|
|
batch.setSchoolId(subject.getSchoolId());
|
|
batch.setClassId(subject.getClassId());
|
|
batch.setCreatedAt(LocalDateTime.now());
|
|
batch.setUpdatedAt(batch.getCreatedAt());
|
|
batch.setCreatedBy(operator);
|
|
batch.setUpdatedBy(operator);
|
|
batch.setDeleted(false);
|
|
batchMapper.insert(batch);
|
|
return result(batch);
|
|
}
|
|
|
|
@Transactional
|
|
public StandardCurrencyBatchResult confirmRule(String prefix, String idRule, String currencyStatus,
|
|
InstitutionKeySubject subject, String operator) {
|
|
StandardCurrencyBatchEntity batch = requireLatest(subject);
|
|
if (batch.getDenominationSummary() == null || batch.getTotalAmount() == null) {
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先执行面额拆解");
|
|
}
|
|
if (!"DC".equals(prefix == null ? null : prefix.trim().toUpperCase())) {
|
|
throw inputError(subject, "币串前缀必须为DC");
|
|
}
|
|
if (!ID_RULE.equals(idRule == null ? null : idRule.trim())) {
|
|
throw inputError(subject, "币串ID规则必须为“批次号+枚序号+随机校验码”");
|
|
}
|
|
if (!"待生效".equals(currencyStatus == null ? null : currencyStatus.trim())) {
|
|
throw inputError(subject, "币串初始状态必须为“待生效”");
|
|
}
|
|
int updated = batchMapper.update(null, new LambdaUpdateWrapper<StandardCurrencyBatchEntity>()
|
|
.eq(StandardCurrencyBatchEntity::getId, batch.getId())
|
|
.eq(StandardCurrencyBatchEntity::getStatus, batch.getStatus())
|
|
.eq(StandardCurrencyBatchEntity::getDeleted, false)
|
|
.set(StandardCurrencyBatchEntity::getPrefix, "DC")
|
|
.set(StandardCurrencyBatchEntity::getIdRule, ID_RULE)
|
|
.set(StandardCurrencyBatchEntity::getCurrencyStatus, "待生效")
|
|
.set(StandardCurrencyBatchEntity::getStatus, "RULE_CONFIRMED")
|
|
.set(StandardCurrencyBatchEntity::getUpdatedAt, LocalDateTime.now())
|
|
.set(StandardCurrencyBatchEntity::getUpdatedBy, operator));
|
|
if (updated != 1) throw stateChanged();
|
|
return result(requireLatest(subject));
|
|
}
|
|
|
|
@Transactional
|
|
public StandardCurrencyBatchResult generate(InstitutionKeySubject subject, String operator) {
|
|
StandardCurrencyBatchEntity batch = requireLatest(subject);
|
|
if (batch.getPrefix() == null || batch.getIdRule() == null || batch.getCurrencyStatus() == null) {
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先确认币串生成规则");
|
|
}
|
|
QuotaControlBitEntity source = requireStepEight(subject);
|
|
if (!source.getId().equals(batch.getQuotaControlBitId()) || source.getBankSignature() == null
|
|
|| source.getCentralBankSignatureSegment() == null) {
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "步骤八额度控制位签名数据不完整");
|
|
}
|
|
List<BigDecimal> values = expand(decode(batch.getDenominationSummary()));
|
|
List<StandardCurrencyEntity> existing = currencyMapper.selectList(
|
|
new LambdaQueryWrapper<StandardCurrencyEntity>()
|
|
.eq(StandardCurrencyEntity::getBatchId, batch.getId())
|
|
.eq(StandardCurrencyEntity::getDeleted, false)
|
|
.orderByAsc(StandardCurrencyEntity::getSequenceNumber));
|
|
SecureRandom random = new SecureRandom();
|
|
for (int i = 0; i < values.size(); i++) {
|
|
int sequence = i + 1;
|
|
String sequenceText = String.format("%03d", sequence);
|
|
String checkCode = checkCode(random);
|
|
String currencyId = "DC_" + batch.getBatchNumber() + "_" + sequenceText + "_" + checkCode;
|
|
String denomination = values.get(i).setScale(2).toPlainString();
|
|
if (i < existing.size()) {
|
|
StandardCurrencyEntity currency = existing.get(i);
|
|
currencyMapper.update(null, new LambdaUpdateWrapper<StandardCurrencyEntity>()
|
|
.eq(StandardCurrencyEntity::getId, currency.getId())
|
|
.eq(StandardCurrencyEntity::getDeleted, false)
|
|
.set(StandardCurrencyEntity::getSequenceNumber, sequence)
|
|
.set(StandardCurrencyEntity::getDenomination, values.get(i))
|
|
.set(StandardCurrencyEntity::getRandomCheckCode, checkCode)
|
|
.set(StandardCurrencyEntity::getCurrencyId, currencyId)
|
|
.set(StandardCurrencyEntity::getInstitutionIdentifier, source.getInstitutionIdentifier())
|
|
.set(StandardCurrencyEntity::getBankSignatureSegment, source.getBankSignature())
|
|
.set(StandardCurrencyEntity::getCentralBankSignatureSegment,
|
|
source.getCentralBankSignatureSegment())
|
|
.set(StandardCurrencyEntity::getCompleteCurrency,
|
|
currencyId + "_" + source.getInstitutionIdentifier() + "_" + denomination
|
|
+ "_" + source.getBankSignature() + "_"
|
|
+ source.getCentralBankSignatureSegment())
|
|
.set(StandardCurrencyEntity::getStatus, "待生效")
|
|
.set(StandardCurrencyEntity::getUpdatedAt, LocalDateTime.now())
|
|
.set(StandardCurrencyEntity::getUpdatedBy, operator));
|
|
} else {
|
|
StandardCurrencyEntity currency = new StandardCurrencyEntity();
|
|
currency.setBatchId(batch.getId());
|
|
currency.setSequenceNumber(sequence);
|
|
currency.setDenomination(values.get(i));
|
|
currency.setRandomCheckCode(checkCode);
|
|
currency.setCurrencyId(currencyId);
|
|
currency.setInstitutionIdentifier(source.getInstitutionIdentifier());
|
|
currency.setBankSignatureSegment(source.getBankSignature());
|
|
currency.setCentralBankSignatureSegment(source.getCentralBankSignatureSegment());
|
|
currency.setCompleteCurrency(currencyId + "_" + source.getInstitutionIdentifier() + "_" + denomination
|
|
+ "_" + source.getBankSignature() + "_" + source.getCentralBankSignatureSegment());
|
|
currency.setStatus("待生效");
|
|
currency.setCreatedAt(LocalDateTime.now());
|
|
currency.setUpdatedAt(currency.getCreatedAt());
|
|
currency.setCreatedBy(operator);
|
|
currency.setUpdatedBy(operator);
|
|
currency.setDeleted(false);
|
|
currencyMapper.insert(currency);
|
|
}
|
|
}
|
|
int updated = batchMapper.update(null, new LambdaUpdateWrapper<StandardCurrencyBatchEntity>()
|
|
.eq(StandardCurrencyBatchEntity::getId, batch.getId())
|
|
.eq(StandardCurrencyBatchEntity::getStatus, batch.getStatus())
|
|
.eq(StandardCurrencyBatchEntity::getDeleted, false)
|
|
.set(StandardCurrencyBatchEntity::getStatus, "GENERATED")
|
|
.set(StandardCurrencyBatchEntity::getUpdatedAt, LocalDateTime.now())
|
|
.set(StandardCurrencyBatchEntity::getUpdatedBy, operator));
|
|
if (updated != 1) throw stateChanged();
|
|
scoreService.markStepCompleted(subject.getUserId(), 9);
|
|
return result(requireLatest(subject));
|
|
}
|
|
|
|
@Transactional(readOnly = true)
|
|
public StandardCurrencyBatchResult detail(InstitutionKeySubject subject) {
|
|
return result(requireLatest(subject));
|
|
}
|
|
|
|
private List<DenominationBreakdownResult> decomposeAmount(BigDecimal amount) {
|
|
long remaining = amount.movePointRight(2).longValueExact();
|
|
List<DenominationBreakdownResult> result = new ArrayList<>();
|
|
for (BigDecimal denomination : DENOMINATIONS) {
|
|
long cents = denomination.movePointRight(2).longValueExact();
|
|
int count = Math.toIntExact(remaining / cents);
|
|
remaining %= cents;
|
|
result.add(new DenominationBreakdownResult(denomination, count));
|
|
}
|
|
if (remaining != 0) throw new BusinessException(ErrorCode.VALIDATION_ERROR, "货币金额无法按标准面额完整拆解");
|
|
return result;
|
|
}
|
|
|
|
private String encode(List<DenominationBreakdownResult> values) {
|
|
return values.stream().map(v -> v.getDenomination().setScale(2).toPlainString() + ":" + v.getCount())
|
|
.collect(Collectors.joining(","));
|
|
}
|
|
|
|
private List<DenominationBreakdownResult> decode(String summary) {
|
|
List<DenominationBreakdownResult> result = new ArrayList<>();
|
|
for (String item : summary.split(",")) {
|
|
String[] parts = item.split(":", 2);
|
|
result.add(new DenominationBreakdownResult(new BigDecimal(parts[0]), Integer.parseInt(parts[1])));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private List<BigDecimal> expand(List<DenominationBreakdownResult> breakdown) {
|
|
List<BigDecimal> result = new ArrayList<>();
|
|
for (DenominationBreakdownResult item : breakdown) {
|
|
result.addAll(Collections.nCopies(item.getCount(), item.getDenomination()));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private String checkCode(SecureRandom random) {
|
|
StringBuilder value = new StringBuilder(6);
|
|
for (int i = 0; i < 6; i++) value.append(CHECK_CODE[random.nextInt(CHECK_CODE.length)]);
|
|
return value.toString();
|
|
}
|
|
|
|
private QuotaControlBitEntity requireStepEight(InstitutionKeySubject subject) {
|
|
QuotaControlBitEntity value = quotaControlBitMapper.selectOne(new LambdaQueryWrapper<QuotaControlBitEntity>()
|
|
.eq(QuotaControlBitEntity::getUserId, subject.getUserId())
|
|
.eq(QuotaControlBitEntity::getSchoolId, subject.getSchoolId())
|
|
.eq(QuotaControlBitEntity::getClassId, subject.getClassId())
|
|
.eq(QuotaControlBitEntity::getStatus, "RECEIVED")
|
|
.eq(QuotaControlBitEntity::getDeleted, false)
|
|
.orderByDesc(QuotaControlBitEntity::getCreatedAt).last("LIMIT 1"));
|
|
if (value == null) throw new BusinessException(ErrorCode.VALIDATION_ERROR,
|
|
"步骤八尚未完成央行接收,不能进入步骤九");
|
|
return value;
|
|
}
|
|
|
|
private StandardCurrencyBatchEntity requireStatus(InstitutionKeySubject subject, String status, String message) {
|
|
StandardCurrencyBatchEntity value = requireLatest(subject);
|
|
if (!status.equals(value.getStatus())) {
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, message + ",当前状态:" + value.getStatus());
|
|
}
|
|
return value;
|
|
}
|
|
|
|
private StandardCurrencyBatchEntity requireLatest(InstitutionKeySubject subject) {
|
|
StandardCurrencyBatchEntity value = latest(subject);
|
|
if (value == null) throw new BusinessException(ErrorCode.RESOURCE_NOT_FOUND, "当前没有步骤九标准币串记录");
|
|
return value;
|
|
}
|
|
|
|
private StandardCurrencyBatchEntity latest(InstitutionKeySubject subject) {
|
|
return batchMapper.selectOne(new LambdaQueryWrapper<StandardCurrencyBatchEntity>()
|
|
.eq(StandardCurrencyBatchEntity::getUserId, subject.getUserId())
|
|
.eq(StandardCurrencyBatchEntity::getSchoolId, subject.getSchoolId())
|
|
.eq(StandardCurrencyBatchEntity::getClassId, subject.getClassId())
|
|
.eq(StandardCurrencyBatchEntity::getDeleted, false)
|
|
.orderByDesc(StandardCurrencyBatchEntity::getCreatedAt).last("LIMIT 1"));
|
|
}
|
|
|
|
private StandardCurrencyBatchResult result(StandardCurrencyBatchEntity batch) {
|
|
List<StandardCurrencyEntity> currencies = currencyMapper.selectList(
|
|
new LambdaQueryWrapper<StandardCurrencyEntity>()
|
|
.eq(StandardCurrencyEntity::getBatchId, batch.getId())
|
|
.eq(StandardCurrencyEntity::getDeleted, false)
|
|
.orderByAsc(StandardCurrencyEntity::getSequenceNumber));
|
|
return new StandardCurrencyBatchResult(batch, decode(batch.getDenominationSummary()), currencies);
|
|
}
|
|
|
|
private BusinessException inputError(InstitutionKeySubject subject, String message) {
|
|
int sequence = errorRecorder.recordScoreError(subject.getUserId());
|
|
return new BusinessException(ErrorCode.VALIDATION_ERROR,
|
|
message + ",本次实训第" + sequence + "次错误");
|
|
}
|
|
|
|
private BusinessException stateChanged() {
|
|
return new BusinessException(ErrorCode.VALIDATION_ERROR, "步骤九状态已变化,不能重复或越级操作");
|
|
}
|
|
}
|