feat(dashboard): 优化大帐统计展示

- 大帐统计改为显示账户余额,移除出入库卡片
- 柱状图展示收入和支出数据,按月份正序排列
- 奖惩记录从数据库真实查询,区分奖励和惩罚
- 修复惩罚记录显示问题(类型匹配)
- 新增查询方法:selectRecentRewardsPunishments、selectLatestBalance
- PrisonerDashboardStatsRespVO新增balance字段
- ConsumptionMapper新增selectLatestBalance方法
This commit is contained in:
tangweijie 2026-01-26 18:16:43 +08:00
parent ff09efa216
commit cea9ed7335
4 changed files with 172 additions and 36 deletions

View File

@ -93,6 +93,9 @@ public class PrisonerDashboardStatsRespVO {
@Schema(description = "月度消费数据")
private List<MonthlyConsumptionData> consumptionMonthlyData;
@Schema(description = "账户余额")
private Integer balance;
@Schema(description = "消费汇总")
private ConsumptionSummary consumptionSummary;

View File

@ -123,4 +123,18 @@ public interface ConsumptionMapper extends BaseMapperX<ConsumptionDO> {
@ResultMap("ConsumptionDetailResultMap")
List<ConsumptionRespVO> selectConsumptionDetailPage(@Param("reqVO") ConsumptionPageReqVO reqVO);
/**
* 查询罪犯最新账户余额
*/
@Select("""
SELECT balance
FROM prison_consumption
WHERE deleted = 0
AND status = 1
AND prisoner_id = #{prisonerId}
ORDER BY id DESC
LIMIT 1
""")
java.math.BigDecimal selectLatestBalance(@Param("prisonerId") Long prisonerId);
}

View File

@ -9,6 +9,7 @@ import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import java.util.List;
import java.util.Map;
/**
* 监管看板 Mapper
@ -18,6 +19,63 @@ import java.util.List;
@Mapper
public interface PrisonDashboardMapper {
/**
* 查询最近6个月罪犯的汇款记录收入
*/
@Select("""
SELECT
DATE_FORMAT(arrive_date, '%Y-%m') AS month,
SUM(amount) AS total_amount
FROM prison_remittance
WHERE deleted = 0
AND status = 1
AND prisoner_id = #{prisonerId}
AND arrive_date >= DATE_SUB(CURDATE(), INTERVAL 6 MONTH)
GROUP BY DATE_FORMAT(arrive_date, '%Y-%m')
ORDER BY month DESC
""")
List<Map<String, Object>> selectRecentRemittances(@Param("prisonerId") Long prisonerId);
/**
* 查询最近6个月罪犯的汇款详情用于展示列表
*/
@Select("""
SELECT
arrive_date,
remitter_name,
amount,
relationship,
remark
FROM prison_remittance
WHERE deleted = 0
AND status = 1
AND prisoner_id = #{prisonerId}
AND arrive_date >= DATE_SUB(CURDATE(), INTERVAL 6 MONTH)
ORDER BY arrive_date DESC
LIMIT 10
""")
List<Map<String, Object>> selectRecentRemittanceDetails(@Param("prisonerId") Long prisonerId);
/**
* 查询最近6个月罪犯的奖惩记录
*/
@Select("""
SELECT
id,
occur_date,
type,
category,
content
FROM prison_rewards_punishments
WHERE deleted = 0
AND status = 1
AND prisoner_id = #{prisonerId}
AND occur_date >= DATE_SUB(CURDATE(), INTERVAL 6 MONTH)
ORDER BY occur_date DESC
LIMIT 10
""")
List<Map<String, Object>> selectRecentRewardsPunishments(@Param("prisonerId") Long prisonerId);
/**
* 查询核心指标卡片数据
*

View File

@ -20,7 +20,6 @@ import cn.iocoder.yudao.module.prison.dal.mysql.situation.SituationMapper;
import cn.iocoder.yudao.module.prison.enums.GenderEnum;
import cn.iocoder.yudao.module.prison.service.dashboard.PrisonDashboardService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
@ -30,7 +29,6 @@ import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Period;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
@ -215,8 +213,13 @@ public class PrisonDashboardServiceImpl implements PrisonDashboardService {
vo.setRemainingDays(null);
}
// 监区信息简单拼接
vo.setPrisonArea(prisoner.getAreaId() != null ? "监区" + prisoner.getAreaId() : "未分配");
// 监区信息查询实际监区名称
if (prisoner.getAreaId() != null) {
AreaDO area = areaMapper.selectById(prisoner.getAreaId());
vo.setPrisonArea(area != null ? area.getName() : "监区" + prisoner.getAreaId());
} else {
vo.setPrisonArea("未分配");
}
// ==================== 查询关联数据 ====================
@ -238,7 +241,7 @@ public class PrisonDashboardServiceImpl implements PrisonDashboardService {
List<ConsumptionDO> monthlyConsumptions = consumptionMapper.selectList(new LambdaQueryWrapper<ConsumptionDO>()
.eq(ConsumptionDO::getPrisonerId, prisonerId)
.eq(ConsumptionDO::getStatus, 1)
.eq(ConsumptionDO::getType, 1) // 消费类型
// type: 1-购物 2-餐饮 3-医疗 4-通讯 5-其他都属于消费
.apply("YEAR(trade_time) = {0} AND MONTH(trade_time) = {1}", currentYear, currentMonth)
.orderByDesc(ConsumptionDO::getTradeTime));
@ -288,6 +291,9 @@ public class PrisonDashboardServiceImpl implements PrisonDashboardService {
.orderByDesc(ConsumptionDO::getTradeTime)
.last("LIMIT 20"));
// 5.2 查询最近6个月汇款记录收入
List<Map<String, Object>> recentRemittances = dashboardMapper.selectRecentRemittances(prisonerId);
// 6. 查询最近6个月的危险评估
List<RiskAssessmentDO> recentAssessments = riskAssessmentMapper.selectList(new LambdaQueryWrapper<RiskAssessmentDO>()
.eq(RiskAssessmentDO::getPrisonerId, prisonerId)
@ -295,17 +301,17 @@ public class PrisonDashboardServiceImpl implements PrisonDashboardService {
.orderByDesc(RiskAssessmentDO::getAssessmentDate)
.last("LIMIT 6"));
// 7. 查询狱情收集心理访谈记录按监区查询
// 8. 查询狱情收集心理访谈记录按监区查询
List<SituationDO> situations = situationMapper.selectList(new LambdaQueryWrapper<SituationDO>()
.eq(SituationDO::getAreaId, prisoner.getAreaId())
.eq(SituationDO::getStatus, 3) // 已处理
.eq(SituationDO::getCategory, 2) // 教育改造类型
.eq(SituationDO::getCategory, 1) // 心理访谈类型
.orderByDesc(SituationDO::getOccurTime)
.last("LIMIT 10"));
// 8. 查询累计违规次数狱情收集-监管安全类型
Long violationCount = situationMapper.selectCount(new LambdaQueryWrapper<SituationDO>()
.eq(SituationDO::getPrisonerId, prisonerId)
.eq(SituationDO::getAreaId, prisoner.getAreaId())
.eq(SituationDO::getStatus, 3) // 已处理
.eq(SituationDO::getCategory, 1)); // 监管安全类型
vo.setViolationCount(violationCount != null ? violationCount.intValue() : 0);
@ -334,7 +340,7 @@ public class PrisonDashboardServiceImpl implements PrisonDashboardService {
double monthlyPenalty = 0;
if (currentMonthScore != null) {
monthlyReward = currentMonthScore.getRewardScore() != null ? currentMonthScore.getRewardScore().doubleValue() : 0;
monthlyPenalty = currentMonthScore.getPenaltyScore() != null ? currentMonthScore.getPenaltyScore().doubleValue() : 0;
monthlyPenalty = currentMonthScore.getPenaltyScore() != null ? Math.abs(currentMonthScore.getPenaltyScore().doubleValue()) : 0;
}
vo.setCenterLeftData(PrisonerDashboardStatsRespVO.CenterLeftData.builder()
.topValue(String.valueOf((int) monthlyTotal))
@ -368,7 +374,7 @@ public class PrisonDashboardServiceImpl implements PrisonDashboardService {
.middleLeftLabel("基础分")
.middleRightValue(String.valueOf((int) monthlyReward))
.middleRightLabel("加分项")
.bottomLeftValue(String.valueOf((int) monthlyPenalty))
.bottomLeftValue(String.valueOf((int) Math.abs(monthlyPenalty)))
.bottomLeftLabel("扣分项")
.bottomRightValue(levelText)
.bottomRightLabel("考核等级")
@ -390,23 +396,59 @@ public class PrisonDashboardServiceImpl implements PrisonDashboardService {
// ==================== 构建消费月度数据 ====================
List<PrisonerDashboardStatsRespVO.MonthlyConsumptionData> monthlyDataList = new ArrayList<>();
// 按月份汇总消费
// 按月份汇总消费支出和汇款收入
Map<String, Double> monthlyConsumptionMap = recentConsumptions.stream()
.filter(c -> c.getTradeTime() != null)
.collect(Collectors.groupingBy(
c -> c.getTradeTime().getYear() + "-" + String.format("%02d", c.getTradeTime().getMonthValue()),
Collectors.summingDouble(c -> c.getTotalAmount() != null ? c.getTotalAmount().doubleValue() : 0)
));
// 解析汇款数据收入
Map<String, Double> monthlyIncomeMap = new java.util.HashMap<>();
for (Map<String, Object> remittance : recentRemittances) {
Object monthObj = remittance.get("month");
Object amountObj = remittance.get("total_amount");
if (monthObj != null && amountObj != null) {
String month = monthObj.toString();
double amount = Double.parseDouble(amountObj.toString());
monthlyIncomeMap.merge(month, amount, Double::sum);
}
}
// 构建月度数据monthlyStandard=支出perCapita=收入
for (Map.Entry<String, Double> entry : monthlyConsumptionMap.entrySet()) {
Double consumption = entry.getValue();
String month = entry.getKey();
double consumption = entry.getValue();
double income = monthlyIncomeMap.getOrDefault(month, 0.0);
monthlyDataList.add(PrisonerDashboardStatsRespVO.MonthlyConsumptionData.builder()
.category(entry.getKey())
.monthlyStandard(consumption.intValue()) // 支出 = 该月实际消费总额
.perCapita(consumption.intValue()) // 人均消费 = 该月实际消费总额
.category(month)
.monthlyStandard((int) consumption) // 支出
.perCapita((int) income) // 收入
.build());
}
// 如果某月只有收入没有消费也添加进去
for (Map.Entry<String, Double> entry : monthlyIncomeMap.entrySet()) {
String month = entry.getKey();
if (!monthlyConsumptionMap.containsKey(month)) {
monthlyDataList.add(PrisonerDashboardStatsRespVO.MonthlyConsumptionData.builder()
.category(month)
.monthlyStandard(0) // 支出
.perCapita(entry.getValue().intValue()) // 收入
.build());
}
}
// 按月份排序从左到右时间正序 1月2月3月...
monthlyDataList.sort((a, b) -> a.getCategory().compareTo(b.getCategory()));
vo.setConsumptionMonthlyData(monthlyDataList);
// ==================== 查询账户余额 ====================
java.math.BigDecimal latestBalance = consumptionMapper.selectLatestBalance(prisonerId);
vo.setBalance(latestBalance != null ? latestBalance.intValue() : 0);
// ==================== 构建计分考核记录 ====================
List<PrisonerDashboardStatsRespVO.ScoreRecord> scoreRecords = recentScores.stream()
.map(s -> {
@ -433,11 +475,24 @@ public class PrisonDashboardServiceImpl implements PrisonDashboardService {
List<PrisonerDashboardStatsRespVO.ConsumptionRecord> consumptionRecords = recentConsumptions.stream()
.limit(10)
.map(c -> {
String typeName = c.getType() == 1 ? "消费" : "存款";
// type: 1-购物 2-餐饮 3-医疗 4-通讯 5-其他
String typeName = "消费";
String nameColor = "#f56c6c"; // 红色-消费
if (c.getType() == null) {
typeName = "未知";
} else if (c.getType() == 2) {
typeName = "餐饮";
} else if (c.getType() == 3) {
typeName = "医疗";
} else if (c.getType() == 4) {
typeName = "通讯";
} else if (c.getType() == 5) {
typeName = "其他";
}
return PrisonerDashboardStatsRespVO.ConsumptionRecord.builder()
.date(c.getTradeTime() != null ? c.getTradeTime().toLocalDate().toString() : "")
.name(typeName)
.nameColor(c.getType() == 1 ? "#f56c6c" : "#67c23a")
.nameColor(nameColor)
.category("普通消费")
.amount(c.getTotalAmount() != null ? c.getTotalAmount().intValue() : 0)
.build();
@ -445,16 +500,17 @@ public class PrisonDashboardServiceImpl implements PrisonDashboardService {
.collect(Collectors.toList());
vo.setConsumptionRecords(consumptionRecords);
// ==================== 构建危险评估记录 ====================
List<PrisonerDashboardStatsRespVO.RewardsPunishment> rewardsPunishments = recentAssessments.stream()
.map(a -> {
// ==================== 构建奖惩记录 ====================
List<Map<String, Object>> rewardsPunishmentsList = dashboardMapper.selectRecentRewardsPunishments(prisonerId);
List<PrisonerDashboardStatsRespVO.RewardsPunishment> rewardsPunishments = rewardsPunishmentsList.stream()
.map(rp -> {
Object typeObj = rp.get("type");
int type = typeObj != null ? Integer.parseInt(typeObj.toString()) : 1;
return PrisonerDashboardStatsRespVO.RewardsPunishment.builder()
.date(a.getAssessmentDate() != null ? a.getAssessmentDate().toString() : "")
.type("danger")
.typeText("危险评估")
.content("暴力倾向:" + (a.getViolenceScore() != null ? a.getViolenceScore() : 0) +
" | 脱逃倾向:" + (a.getEscapeScore() != null ? a.getEscapeScore() : 0) +
" | 自杀倾向:" + (a.getSuicideScore() != null ? a.getSuicideScore() : 0))
.date(rp.get("occur_date") != null ? rp.get("occur_date").toString().substring(0, 10) : "")
.type(type == 1 ? "reward" : "punishment")
.typeText(rp.get("category") != null ? rp.get("category").toString() : (type == 1 ? "表扬奖励" : "惩罚"))
.content(rp.get("content") != null ? rp.get("content").toString() : "")
.build();
})
.collect(Collectors.toList());
@ -471,16 +527,21 @@ public class PrisonDashboardServiceImpl implements PrisonDashboardService {
vo.setInterviewRecords(interviewRecords);
// ==================== 构建汇款记录 ====================
List<PrisonerDashboardStatsRespVO.RemittanceRecord> remittanceRecords = recentConsumptions.stream()
.filter(c -> c.getType() == 2) // 存款类型
.limit(10)
.map(c -> PrisonerDashboardStatsRespVO.RemittanceRecord.builder()
.date(c.getTradeTime() != null ? c.getTradeTime().toLocalDate().toString() : "")
.name("存款")
.nameColor("#409eff")
.category("亲属汇款")
.amount(c.getTotalAmount() != null ? c.getTotalAmount().intValue() : 0)
.build())
// 查询最近6个月的汇款记录
List<Map<String, Object>> remittanceList = dashboardMapper.selectRecentRemittances(prisonerId);
List<PrisonerDashboardStatsRespVO.RemittanceRecord> remittanceRecords = remittanceList.stream()
.map(r -> {
Object dateObj = r.get("arrive_date");
Object nameObj = r.get("remitter_name");
Object amountObj = r.get("amount");
return PrisonerDashboardStatsRespVO.RemittanceRecord.builder()
.date(dateObj != null ? dateObj.toString() : "")
.name(nameObj != null ? nameObj.toString() : "")
.nameColor("#67c23a")
.category("汇款")
.amount(amountObj != null ? Integer.parseInt(amountObj.toString()) : 0)
.build();
})
.collect(Collectors.toList());
vo.setRemittanceRecords(remittanceRecords);