|
|
|
|
@ -0,0 +1,335 @@
|
|
|
|
|
package com.yau.digitalrmb.progress.application;
|
|
|
|
|
|
|
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
|
|
import com.yau.digitalrmb.institutionidentity.infrastructure.StuModuleScoreDetailsEntity;
|
|
|
|
|
import com.yau.digitalrmb.institutionidentity.infrastructure.StuModuleScoreDetailsMapper;
|
|
|
|
|
import com.yau.digitalrmb.shared.api.ErrorCode;
|
|
|
|
|
import com.yau.digitalrmb.shared.exception.BusinessException;
|
|
|
|
|
import org.springframework.jdbc.core.JdbcTemplate;
|
|
|
|
|
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.util.ArrayList;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
|
|
@Service
|
|
|
|
|
public class ModuleCompletionService {
|
|
|
|
|
|
|
|
|
|
@Resource
|
|
|
|
|
private StuModuleScoreDetailsMapper scoreDetailsMapper;
|
|
|
|
|
|
|
|
|
|
@Resource
|
|
|
|
|
private JdbcTemplate jdbcTemplate;
|
|
|
|
|
|
|
|
|
|
@Transactional(readOnly = true)
|
|
|
|
|
public ModuleCompletionResult getModuleCompletion(String userId, String moduleCode) {
|
|
|
|
|
ModuleConfig config = resolveModuleConfig(moduleCode);
|
|
|
|
|
|
|
|
|
|
if (config.usesScoreTable) {
|
|
|
|
|
return getFromScoreTable(userId, moduleCode, config);
|
|
|
|
|
}
|
|
|
|
|
return getFromTrainingTaskTable(userId, moduleCode, config);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Transactional(readOnly = true)
|
|
|
|
|
public ExperimentProgressResult getExperimentProgress(String userId) {
|
|
|
|
|
List<ExperimentProgressResult.ModuleProgressItem> modules = new ArrayList<>();
|
|
|
|
|
int completedCount = 0;
|
|
|
|
|
int inProgressCount = 0;
|
|
|
|
|
|
|
|
|
|
for (String moduleCode : new String[]{"1", "2", "3", "4", "5", "6"}) {
|
|
|
|
|
ModuleConfig config = resolveModuleConfig(moduleCode);
|
|
|
|
|
double schedule;
|
|
|
|
|
if (config.usesScoreTable) {
|
|
|
|
|
StuModuleScoreDetailsEntity detail = scoreDetailsMapper.selectOne(
|
|
|
|
|
new LambdaQueryWrapper<StuModuleScoreDetailsEntity>()
|
|
|
|
|
.eq(StuModuleScoreDetailsEntity::getUserId, userId)
|
|
|
|
|
.eq(StuModuleScoreDetailsEntity::getMoudule, config.moduleName)
|
|
|
|
|
.eq(StuModuleScoreDetailsEntity::getSerialNumber, config.serialNumber)
|
|
|
|
|
.last("LIMIT 1"));
|
|
|
|
|
schedule = detail != null && detail.getSchedule() != null ? detail.getSchedule() : 0.0;
|
|
|
|
|
} else {
|
|
|
|
|
List<String> completedStepCodes = jdbcTemplate.queryForList(
|
|
|
|
|
"SELECT step_code FROM training_task_step_progress WHERE user_id = ? AND module_code = ?",
|
|
|
|
|
String.class, userId, config.trainingModuleCode);
|
|
|
|
|
int completedSteps = completedStepCodes.size();
|
|
|
|
|
schedule = config.totalSteps > 0
|
|
|
|
|
? BigDecimal.valueOf(completedSteps)
|
|
|
|
|
.multiply(BigDecimal.valueOf(100))
|
|
|
|
|
.divide(BigDecimal.valueOf(config.totalSteps), 2, RoundingMode.HALF_UP)
|
|
|
|
|
.doubleValue()
|
|
|
|
|
: 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
schedule = BigDecimal.valueOf(schedule).setScale(2, RoundingMode.HALF_UP).doubleValue();
|
|
|
|
|
String status;
|
|
|
|
|
if (schedule >= 100.0) {
|
|
|
|
|
status = "COMPLETED";
|
|
|
|
|
completedCount++;
|
|
|
|
|
} else if (schedule > 0.0) {
|
|
|
|
|
status = "IN_PROGRESS";
|
|
|
|
|
inProgressCount++;
|
|
|
|
|
} else {
|
|
|
|
|
status = "NOT_STARTED";
|
|
|
|
|
}
|
|
|
|
|
modules.add(new ExperimentProgressResult.ModuleProgressItem(
|
|
|
|
|
moduleCode, config.moduleName, status, schedule));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int totalModules = 6;
|
|
|
|
|
int notStartedCount = totalModules - completedCount - inProgressCount;
|
|
|
|
|
|
|
|
|
|
double completedRate = BigDecimal.valueOf(completedCount)
|
|
|
|
|
.multiply(BigDecimal.valueOf(100))
|
|
|
|
|
.divide(BigDecimal.valueOf(totalModules), 2, RoundingMode.HALF_UP).doubleValue();
|
|
|
|
|
double inProgressRate = BigDecimal.valueOf(inProgressCount)
|
|
|
|
|
.multiply(BigDecimal.valueOf(100))
|
|
|
|
|
.divide(BigDecimal.valueOf(totalModules), 2, RoundingMode.HALF_UP).doubleValue();
|
|
|
|
|
double notStartedRate = BigDecimal.valueOf(notStartedCount)
|
|
|
|
|
.multiply(BigDecimal.valueOf(100))
|
|
|
|
|
.divide(BigDecimal.valueOf(totalModules), 2, RoundingMode.HALF_UP).doubleValue();
|
|
|
|
|
double totalProgress = BigDecimal.valueOf(completedRate + inProgressRate * 0.5)
|
|
|
|
|
.setScale(2, RoundingMode.HALF_UP).doubleValue();
|
|
|
|
|
|
|
|
|
|
return new ExperimentProgressResult(
|
|
|
|
|
totalProgress, completedRate, inProgressRate, notStartedRate, modules);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private ModuleCompletionResult getFromScoreTable(String userId, String moduleCode, ModuleConfig config) {
|
|
|
|
|
StuModuleScoreDetailsEntity detail = scoreDetailsMapper.selectOne(
|
|
|
|
|
new LambdaQueryWrapper<StuModuleScoreDetailsEntity>()
|
|
|
|
|
.eq(StuModuleScoreDetailsEntity::getUserId, userId)
|
|
|
|
|
.eq(StuModuleScoreDetailsEntity::getMoudule, config.moduleName)
|
|
|
|
|
.eq(StuModuleScoreDetailsEntity::getSerialNumber, config.serialNumber)
|
|
|
|
|
.last("LIMIT 1"));
|
|
|
|
|
|
|
|
|
|
double schedule = 0.0;
|
|
|
|
|
int errorCount = 0;
|
|
|
|
|
double score = 0.0;
|
|
|
|
|
|
|
|
|
|
if (detail != null) {
|
|
|
|
|
schedule = detail.getSchedule() != null ? detail.getSchedule() : 0.0;
|
|
|
|
|
errorCount = parseErrorCount(detail.getCompletionStatus());
|
|
|
|
|
score = detail.getScoreProject() != null ? detail.getScoreProject() : 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int completedSteps = calculateCompletedSteps(schedule, config.totalSteps);
|
|
|
|
|
List<ModuleStepStatus> steps = buildSteps(config, completedSteps);
|
|
|
|
|
|
|
|
|
|
return new ModuleCompletionResult(
|
|
|
|
|
moduleCode,
|
|
|
|
|
config.moduleName,
|
|
|
|
|
config.totalSteps,
|
|
|
|
|
completedSteps,
|
|
|
|
|
BigDecimal.valueOf(schedule).setScale(2, RoundingMode.HALF_UP).doubleValue(),
|
|
|
|
|
errorCount,
|
|
|
|
|
BigDecimal.valueOf(score).setScale(2, RoundingMode.HALF_UP).doubleValue(),
|
|
|
|
|
steps
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private ModuleCompletionResult getFromTrainingTaskTable(String userId, String moduleCode, ModuleConfig config) {
|
|
|
|
|
List<String> completedStepCodes = jdbcTemplate.queryForList(
|
|
|
|
|
"SELECT step_code FROM training_task_step_progress WHERE user_id = ? AND module_code = ?",
|
|
|
|
|
String.class, userId, config.trainingModuleCode);
|
|
|
|
|
|
|
|
|
|
int completedSteps = completedStepCodes.size();
|
|
|
|
|
double schedule = config.totalSteps > 0
|
|
|
|
|
? BigDecimal.valueOf(completedSteps)
|
|
|
|
|
.multiply(BigDecimal.valueOf(100))
|
|
|
|
|
.divide(BigDecimal.valueOf(config.totalSteps), 2, RoundingMode.HALF_UP)
|
|
|
|
|
.doubleValue()
|
|
|
|
|
: 0.0;
|
|
|
|
|
|
|
|
|
|
List<ModuleStepStatus> steps = buildSteps(config, completedSteps);
|
|
|
|
|
|
|
|
|
|
return new ModuleCompletionResult(
|
|
|
|
|
moduleCode,
|
|
|
|
|
config.moduleName,
|
|
|
|
|
config.totalSteps,
|
|
|
|
|
completedSteps,
|
|
|
|
|
schedule,
|
|
|
|
|
0,
|
|
|
|
|
0.0,
|
|
|
|
|
steps
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private ModuleConfig resolveModuleConfig(String moduleCode) {
|
|
|
|
|
switch (moduleCode) {
|
|
|
|
|
case "1":
|
|
|
|
|
return new ModuleConfig("中国人民银行生成数字货币", 3, 9, true, null,
|
|
|
|
|
new String[]{
|
|
|
|
|
"生成投放机构标识",
|
|
|
|
|
"生成发起货币生成请求",
|
|
|
|
|
"验证货币生成请求",
|
|
|
|
|
"生成交易信息标识",
|
|
|
|
|
"生成额度控制位请求",
|
|
|
|
|
"验证发送额度控制位请求",
|
|
|
|
|
"生成额度控制系统签名信息",
|
|
|
|
|
"生成额度控制位和数字签名",
|
|
|
|
|
"生成数字人民币"
|
|
|
|
|
},
|
|
|
|
|
new String[]{
|
|
|
|
|
"商业银行使用数字证书生成投放机构标识",
|
|
|
|
|
"商业银行发起货币生成请求",
|
|
|
|
|
"中央银行验证货币生成请求",
|
|
|
|
|
"根据已认证请求生成交易信息标识",
|
|
|
|
|
"商业银行发起额度控制位请求",
|
|
|
|
|
"中央银行验证并发送额度控制位请求",
|
|
|
|
|
"额度控制系统生成签名信息",
|
|
|
|
|
"生成最终额度控制位和数字签名",
|
|
|
|
|
"完成全流程后生成数字人民币"
|
|
|
|
|
});
|
|
|
|
|
case "2":
|
|
|
|
|
return new ModuleConfig("中国人民银行发行数字货币", 0, 7, false, "PBOC_CURRENCY_ISSUANCE",
|
|
|
|
|
new String[]{
|
|
|
|
|
"查看货币需求",
|
|
|
|
|
"生成发行申请计划",
|
|
|
|
|
"发行计划数字签名",
|
|
|
|
|
"发送标准报文",
|
|
|
|
|
"中央银行验证与业务核查",
|
|
|
|
|
"准备金通知与ACS扣减",
|
|
|
|
|
"发行并确权数字货币"
|
|
|
|
|
},
|
|
|
|
|
new String[]{
|
|
|
|
|
"查看货币需求情况",
|
|
|
|
|
"生成发行申请计划",
|
|
|
|
|
"对发行计划进行数字签名",
|
|
|
|
|
"发送标准报文",
|
|
|
|
|
"中央银行验证与业务核查",
|
|
|
|
|
"准备金通知与ACS扣减",
|
|
|
|
|
"发行并确权数字货币"
|
|
|
|
|
});
|
|
|
|
|
case "3":
|
|
|
|
|
return new ModuleConfig("开通数字货币钱包", 4, 5, true, null,
|
|
|
|
|
new String[]{
|
|
|
|
|
"商业银行钱包开立",
|
|
|
|
|
"钱包标识生成",
|
|
|
|
|
"钱包注册",
|
|
|
|
|
"智能合约签订",
|
|
|
|
|
"央行钱包激活"
|
|
|
|
|
},
|
|
|
|
|
new String[]{
|
|
|
|
|
"商业银行发起钱包开立申请",
|
|
|
|
|
"生成钱包标识和钱包ID",
|
|
|
|
|
"完成钱包注册并生成证书",
|
|
|
|
|
"签订智能合约并激活",
|
|
|
|
|
"央行最终确认并激活钱包"
|
|
|
|
|
});
|
|
|
|
|
case "4":
|
|
|
|
|
return new ModuleConfig("用户兑换数字货币", 0, 5, false, "CURRENCY_EXCHANGE",
|
|
|
|
|
new String[]{
|
|
|
|
|
"确认银行卡、钱包和商业银行库存",
|
|
|
|
|
"生成取币请求报文",
|
|
|
|
|
"钱包私钥签名",
|
|
|
|
|
"商业银行验签并扣款",
|
|
|
|
|
"央行确认币串权属"
|
|
|
|
|
},
|
|
|
|
|
new String[]{
|
|
|
|
|
"确认银行卡、钱包和商业银行库存",
|
|
|
|
|
"生成取币请求报文",
|
|
|
|
|
"钱包私钥签名",
|
|
|
|
|
"商业银行验签并扣款",
|
|
|
|
|
"央行确认币串权属"
|
|
|
|
|
});
|
|
|
|
|
case "5":
|
|
|
|
|
return new ModuleConfig("用户支付数字货币", 0, 6, false, "CURRENCY_PAYMENT",
|
|
|
|
|
new String[]{
|
|
|
|
|
"初始化付款方和收款方钱包",
|
|
|
|
|
"签名支付请求",
|
|
|
|
|
"付款行预处理",
|
|
|
|
|
"中央银行结算",
|
|
|
|
|
"收款行入账",
|
|
|
|
|
"生成支付回执与到账通知"
|
|
|
|
|
},
|
|
|
|
|
new String[]{
|
|
|
|
|
"初始化付款方和收款方钱包",
|
|
|
|
|
"签名支付请求",
|
|
|
|
|
"付款行预处理",
|
|
|
|
|
"中央银行结算",
|
|
|
|
|
"收款行入账",
|
|
|
|
|
"生成支付回执与到账通知"
|
|
|
|
|
});
|
|
|
|
|
case "6":
|
|
|
|
|
return new ModuleConfig("数字人民币创新应用场景仿真", 6, 8, true, null,
|
|
|
|
|
new String[]{
|
|
|
|
|
"工资发放批次创建",
|
|
|
|
|
"工资发放明细录入",
|
|
|
|
|
"工资发放审核",
|
|
|
|
|
"工资发放复核",
|
|
|
|
|
"工资发放确认",
|
|
|
|
|
"捐赠企业发起捐赠",
|
|
|
|
|
"受赠机构接收捐款与公示",
|
|
|
|
|
"第三方监管监督与审核"
|
|
|
|
|
},
|
|
|
|
|
new String[]{
|
|
|
|
|
"创建工资发放批次",
|
|
|
|
|
"录入工资发放明细",
|
|
|
|
|
"审核工资发放",
|
|
|
|
|
"复核工资发放",
|
|
|
|
|
"确认工资发放",
|
|
|
|
|
"捐赠企业发起捐赠流程",
|
|
|
|
|
"受赠机构接收捐款并公示",
|
|
|
|
|
"第三方监管机构监督与审核"
|
|
|
|
|
});
|
|
|
|
|
default:
|
|
|
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "未知的模块编码:" + moduleCode + ",请传入1-6的数字");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private int calculateCompletedSteps(double schedule, int totalSteps) {
|
|
|
|
|
if (schedule <= 0 || totalSteps <= 0) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
double completed = schedule / 100.0 * totalSteps;
|
|
|
|
|
return Math.min(totalSteps, (int) Math.round(completed));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private List<ModuleStepStatus> buildSteps(ModuleConfig config, int completedSteps) {
|
|
|
|
|
List<ModuleStepStatus> result = new ArrayList<>(config.stepNames.length);
|
|
|
|
|
for (int i = 0; i < config.stepNames.length; i++) {
|
|
|
|
|
String stepCode = String.format("%02d", i + 1);
|
|
|
|
|
int status = i < completedSteps ? 1 : 0;
|
|
|
|
|
String desc = i < config.stepDescriptions.length ? config.stepDescriptions[i] : "";
|
|
|
|
|
result.add(new ModuleStepStatus(stepCode, config.stepNames[i], desc, status));
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private int parseErrorCount(String completionStatus) {
|
|
|
|
|
if (completionStatus == null || completionStatus.trim().isEmpty()) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
return Math.max(0, Integer.parseInt(completionStatus.trim()));
|
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static class ModuleConfig {
|
|
|
|
|
final String moduleName;
|
|
|
|
|
final int serialNumber;
|
|
|
|
|
final int totalSteps;
|
|
|
|
|
final boolean usesScoreTable;
|
|
|
|
|
final String trainingModuleCode;
|
|
|
|
|
final String[] stepNames;
|
|
|
|
|
final String[] stepDescriptions;
|
|
|
|
|
|
|
|
|
|
ModuleConfig(String moduleName, int serialNumber, int totalSteps,
|
|
|
|
|
boolean usesScoreTable, String trainingModuleCode,
|
|
|
|
|
String[] stepNames, String[] stepDescriptions) {
|
|
|
|
|
this.moduleName = moduleName;
|
|
|
|
|
this.serialNumber = serialNumber;
|
|
|
|
|
this.totalSteps = totalSteps;
|
|
|
|
|
this.usesScoreTable = usesScoreTable;
|
|
|
|
|
this.trainingModuleCode = trainingModuleCode;
|
|
|
|
|
this.stepNames = stepNames;
|
|
|
|
|
this.stepDescriptions = stepDescriptions;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|