feat: 完成采购看板与异议管理模块功能升级

1.  新增报表下钻跳转支持,为供应商、RFQ、采购单、物料等页面添加路由参数解析
2.  优化异议管理模块:新增发货单关联、详情弹窗、审批流程优化
3.  完善采购看板功能:支持累计数据展示、图表导出、数据补全与趋势优化
4.  新增供应商评分历史趋势统计与品类分布聚合逻辑
5.  修复异议API路径与通知跳转路径问题,新增模拟测试数据
This commit is contained in:
2026-06-21 12:40:59 +08:00
parent 8bdb8d7c23
commit 896999dfeb
26 changed files with 1129 additions and 108 deletions

View File

@@ -7,6 +7,7 @@ public class BizOrderObjection {
private Long objectionId;
private Long tenantId;
private Long poId;
private Long doId;
private Long supplierId;
private String reason;
private String attachment;
@@ -19,6 +20,7 @@ public class BizOrderObjection {
private Date resolveTime;
private String supplierName;
private String poNo;
private String doNo;
public Long getObjectionId() { return objectionId; }
public void setObjectionId(Long objectionId) { this.objectionId = objectionId; }
@@ -26,6 +28,8 @@ public class BizOrderObjection {
public void setTenantId(Long tenantId) { this.tenantId = tenantId; }
public Long getPoId() { return poId; }
public void setPoId(Long poId) { this.poId = poId; }
public Long getDoId() { return doId; }
public void setDoId(Long doId) { this.doId = doId; }
public Long getSupplierId() { return supplierId; }
public void setSupplierId(Long supplierId) { this.supplierId = supplierId; }
public String getReason() { return reason; }
@@ -46,4 +50,6 @@ public class BizOrderObjection {
public void setSupplierName(String supplierName) { this.supplierName = supplierName; }
public String getPoNo() { return poNo; }
public void setPoNo(String poNo) { this.poNo = poNo; }
public String getDoNo() { return doNo; }
public void setDoNo(String doNo) { this.doNo = doNo; }
}

View File

@@ -59,6 +59,7 @@ public class ReportCostVO {
private BigDecimal expectedAmount;
private BigDecimal actualAmount;
private BigDecimal savedAmount;
private boolean overBudget;
public String getMonth() { return month; }
public void setMonth(String v) { month = v; }
@@ -68,15 +69,20 @@ public class ReportCostVO {
public void setActualAmount(BigDecimal v) { actualAmount = v; }
public BigDecimal getSavedAmount() { return savedAmount; }
public void setSavedAmount(BigDecimal v) { savedAmount = v; }
public boolean isOverBudget() { return overBudget; }
public void setOverBudget(boolean v) { overBudget = v; }
}
/** 品类分布 */
public static class CategoryDist {
private Long categoryId;
private String categoryName;
private BigDecimal amount;
private int materialCount;
private double percent;
public Long getCategoryId() { return categoryId; }
public void setCategoryId(Long v) { categoryId = v; }
public String getCategoryName() { return categoryName; }
public void setCategoryName(String v) { categoryName = v; }
public BigDecimal getAmount() { return amount; }
@@ -97,6 +103,7 @@ public class ReportCostVO {
private BigDecimal acceptedQuote;
private BigDecimal savedAmount;
private int supplierCount;
private boolean overBudget;
public Long getRfqId() { return rfqId; }
public void setRfqId(Long v) { rfqId = v; }
@@ -114,5 +121,7 @@ public class ReportCostVO {
public void setSavedAmount(BigDecimal v) { savedAmount = v; }
public int getSupplierCount() { return supplierCount; }
public void setSupplierCount(int v) { supplierCount = v; }
public boolean isOverBudget() { return overBudget; }
public void setOverBudget(boolean v) { overBudget = v; }
}
}

View File

@@ -58,6 +58,8 @@ public class ReportDashboardVO {
public static class KpiCard {
private String label;
private BigDecimal value;
/** 累计值全部历史数据汇总用于当月为0时的补充展示 */
private BigDecimal totalValue;
private double changeRate;
private String unit;
private String trend; // up / down
@@ -66,6 +68,8 @@ public class ReportDashboardVO {
public void setLabel(String v) { label = v; }
public BigDecimal getValue() { return value; }
public void setValue(BigDecimal v) { value = v; }
public BigDecimal getTotalValue() { return totalValue; }
public void setTotalValue(BigDecimal v) { totalValue = v; }
public double getChangeRate() { return changeRate; }
public void setChangeRate(double v) { changeRate = v; }
public String getUnit() { return unit; }

View File

@@ -20,6 +20,9 @@ public class ReportSupplierVO {
/** 异议统计 */
private List<ObjectionStat> objectionStats;
/** 评分历史趋势 */
private List<ScoreHistory> scoreHistory;
// ===== getters / setters =====
public List<SupplierScore> getRankings() { return rankings; }
@@ -34,6 +37,9 @@ public class ReportSupplierVO {
public List<ObjectionStat> getObjectionStats() { return objectionStats; }
public void setObjectionStats(List<ObjectionStat> v) { objectionStats = v; }
public List<ScoreHistory> getScoreHistory() { return scoreHistory; }
public void setScoreHistory(List<ScoreHistory> v) { scoreHistory = v; }
// ===== 内部类 =====
/** 供应商评分 */
@@ -41,6 +47,7 @@ public class ReportSupplierVO {
private Long supplierId;
private String supplierName;
private int evalCount;
private String evalStatus;
private double qualityAvg;
private double deliveryAvg;
private double serviceAvg;
@@ -55,6 +62,8 @@ public class ReportSupplierVO {
public void setSupplierName(String v) { supplierName = v; }
public int getEvalCount() { return evalCount; }
public void setEvalCount(int v) { evalCount = v; }
public String getEvalStatus() { return evalStatus; }
public void setEvalStatus(String v) { evalStatus = v; }
public double getQualityAvg() { return qualityAvg; }
public void setQualityAvg(double v) { qualityAvg = v; }
public double getDeliveryAvg() { return deliveryAvg; }
@@ -130,4 +139,30 @@ public class ReportSupplierVO {
public String getTopReason() { return topReason; }
public void setTopReason(String v) { topReason = v; }
}
/** 评分历史趋势 */
public static class ScoreHistory {
private Long supplierId;
private String supplierName;
private String month;
private double qualityAvg;
private double deliveryAvg;
private double serviceAvg;
private double priceAvg;
public Long getSupplierId() { return supplierId; }
public void setSupplierId(Long v) { supplierId = v; }
public String getSupplierName() { return supplierName; }
public void setSupplierName(String v) { supplierName = v; }
public String getMonth() { return month; }
public void setMonth(String v) { month = v; }
public double getQualityAvg() { return qualityAvg; }
public void setQualityAvg(double v) { qualityAvg = v; }
public double getDeliveryAvg() { return deliveryAvg; }
public void setDeliveryAvg(double v) { deliveryAvg = v; }
public double getServiceAvg() { return serviceAvg; }
public void setServiceAvg(double v) { serviceAvg = v; }
public double getPriceAvg() { return priceAvg; }
public void setPriceAvg(double v) { priceAvg = v; }
}
}

View File

@@ -18,12 +18,18 @@ public interface BizReportMapper {
/** 本月/上月采购总额 */
Map<String, Object> selectPurchaseAmount(@Param("month") String month);
/** 累计采购总额(不限月份) */
Map<String, Object> selectPurchaseAmountAll();
/** 本月/上月 RFQ 数量 */
Map<String, Object> selectRfqCount(@Param("month") String month);
/** 本月/上月 PO 数量 */
Map<String, Object> selectPoCount(@Param("month") String month);
/** 累计采购单数(不限月份) */
Map<String, Object> selectPoCountAll();
/** 活跃供应商数 */
Map<String, Object> selectActiveSupplierCount();
@@ -70,4 +76,7 @@ public interface BizReportMapper {
/** 异议统计 */
List<ReportSupplierVO.ObjectionStat> selectObjectionStats();
/** 供应商评分历史趋势 */
List<ReportSupplierVO.ScoreHistory> selectSupplierScoreHistory();
}

View File

@@ -168,6 +168,14 @@ public class BizApprovalActionServiceImpl implements IBizApprovalActionService {
sb.append(";驳回原因: ").append(reason);
}
// 订单异议额外显示处理结果
if ("ORDER_OBJECTION".equals(bizType) && detail.get("resolution") != null) {
String resolution = String.valueOf(detail.get("resolution")).trim();
if (!resolution.isEmpty()) {
sb.append(";处理结果: ").append(resolution);
}
}
return sb.toString();
}
@@ -203,6 +211,15 @@ public class BizApprovalActionServiceImpl implements IBizApprovalActionService {
}
sb.append("提交人: ").append(submitterName);
// 订单异议额外显示处理结果
if ("ORDER_OBJECTION".equals(bizType) && detail.get("resolution") != null) {
String resolution = String.valueOf(detail.get("resolution")).trim();
if (!resolution.isEmpty()) {
sb.append(";处理结果: ").append(resolution);
}
}
return sb.toString();
}
@@ -286,10 +303,10 @@ public class BizApprovalActionServiceImpl implements IBizApprovalActionService {
if (bizType == null || bizId == null) return null;
switch (bizType) {
case "PURCHASE_ORDER": return "/quote/purchaseorder?id=" + bizId;
case "CLIENT_QUOTE": return "/bid/clientquote/detail?id=" + bizId;
case "CLIENT_QUOTE": return "/bid/clientquote?id=" + bizId;
case "QUOTATION": return "/quote/quotation?quotationId=" + bizId;
case "DELIVERY_ORDER": return "/bid/order/pending?id=" + bizId;
case "ORDER_OBJECTION": return "/bid/order/objection?id=" + bizId;
case "DELIVERY_ORDER": return "/fulfill/client-delivery/pending?id=" + bizId;
case "ORDER_OBJECTION": return "/fulfill/supplierFulfill/objection?id=" + bizId;
default: return null;
}
}

View File

@@ -145,10 +145,10 @@ public class BizNotifyMessageServiceImpl implements IBizNotifyMessageService {
if (bizType == null || bizId == null) return null;
switch (bizType) {
case "PURCHASE_ORDER": return "/quote/purchaseorder?id=" + bizId;
case "CLIENT_QUOTE": return "/bid/clientquote/detail?id=" + bizId;
case "CLIENT_QUOTE": return "/bid/clientquote?id=" + bizId;
case "QUOTATION": return "/quote/quotation?quotationId=" + bizId;
case "DELIVERY_ORDER": return "/bid/order/pending?id=" + bizId;
case "ORDER_OBJECTION": return "/bid/order/objection?id=" + bizId;
case "DELIVERY_ORDER": return "/fulfill/client-delivery/pending?id=" + bizId;
case "ORDER_OBJECTION": return "/fulfill/supplierFulfill/objection?id=" + bizId;
default: return null;
}
}

View File

@@ -12,9 +12,10 @@ import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 报表统计 Service 实现
@@ -35,25 +36,34 @@ public class BizReportServiceImpl implements IBizReportService {
String curMonth = LocalDate.now().format(MONTH_FMT);
String lastMonth = LocalDate.now().minusMonths(1).format(MONTH_FMT);
// 累计值(全部历史数据)
BigDecimal totalPurchaseAll = selectAmount("totalAmount", null, true);
BigDecimal totalPoAll = selectCount("po", null, true);
// 1) KPI 卡片
vo.setTotalPurchaseAmount(buildKpi("采购总额",
ReportDashboardVO.KpiCard purchaseCard = buildKpi("采购总额",
selectAmount("totalAmount", curMonth),
selectAmount("totalAmount", lastMonth), ""));
selectAmount("totalAmount", lastMonth), "");
purchaseCard.setTotalValue(totalPurchaseAll);
vo.setTotalPurchaseAmount(purchaseCard);
vo.setTotalRfqCount(buildKpi("RFQ总数",
selectCount("rfq", curMonth),
selectCount("rfq", lastMonth), ""));
vo.setTotalPoCount(buildKpi("采购单数",
ReportDashboardVO.KpiCard poCard = buildKpi("采购单数",
selectCount("po", curMonth),
selectCount("po", lastMonth), ""));
selectCount("po", lastMonth), "");
poCard.setTotalValue(totalPoAll);
vo.setTotalPoCount(poCard);
Map<String, Object> act = mapper.selectActiveSupplierCount();
BigDecimal activeVal = toBD(act.get("totalCount"));
vo.setActiveSupplierCount(buildKpi("活跃供应商", activeVal, null, ""));
// 2) 月度趋势
vo.setMonthlyTrend(mapper.selectMonthlyTrend());
// 2) 月度趋势补全近12个月无数据月份填0
List<ReportDashboardVO.MonthTrend> rawTrend = mapper.selectMonthlyTrend();
vo.setMonthlyTrend(fillMonthlyTrend(rawTrend, 12));
// 3) RFQ 状态分布
List<ReportDashboardVO.StatusDist> dist = mapper.selectRfqStatusDist();
@@ -91,8 +101,9 @@ public class BizReportServiceImpl implements IBizReportService {
: 0);
vo.setSummary(summary);
// 月度趋势
vo.setCostTrend(mapper.selectCostTrend(startMonth, endMonth));
// 月度趋势补全月份无数据月份填0
List<ReportCostVO.CostTrend> rawTrend = mapper.selectCostTrend(startMonth, endMonth);
vo.setCostTrend(fillCostTrend(rawTrend, startMonth, endMonth));
// 品类分布
List<ReportCostVO.CategoryDist> catDist = mapper.selectCategoryDist();
@@ -108,7 +119,15 @@ public class BizReportServiceImpl implements IBizReportService {
vo.setCategoryDist(catDist);
// RFQ 比价明细
vo.setRfqDetails(mapper.selectRfqCompareDetails());
List<ReportCostVO.RfqCompareDetail> details = mapper.selectRfqCompareDetails();
if (details != null) {
for (ReportCostVO.RfqCompareDetail d : details) {
if (d.getSavedAmount() != null && d.getSavedAmount().compareTo(BigDecimal.ZERO) < 0) {
d.setOverBudget(true);
}
}
}
vo.setRfqDetails(details);
return vo;
}
@@ -121,6 +140,7 @@ public class BizReportServiceImpl implements IBizReportService {
vo.setWinRateData(mapper.selectWinRate());
vo.setRadarData(mapper.selectRadarData());
vo.setObjectionStats(mapper.selectObjectionStats());
vo.setScoreHistory(mapper.selectSupplierScoreHistory());
return vo;
}
@@ -149,15 +169,36 @@ public class BizReportServiceImpl implements IBizReportService {
}
private BigDecimal selectAmount(String column, String month) {
Map<String, Object> map = mapper.selectPurchaseAmount(month);
return selectAmount(column, month, false);
}
private BigDecimal selectAmount(String column, String month, boolean all) {
Map<String, Object> map;
if (all) {
map = mapper.selectPurchaseAmountAll();
} else {
map = mapper.selectPurchaseAmount(month);
}
return toBD(map.get(column));
}
private BigDecimal selectCount(String type, String month) {
return selectCount(type, month, false);
}
private BigDecimal selectCount(String type, String month, boolean all) {
Map<String, Object> map;
switch (type) {
case "rfq": map = mapper.selectRfqCount(month); break;
case "po": map = mapper.selectPoCount(month); break;
case "rfq":
if (all) return BigDecimal.ZERO; // RFQ无累计需求
map = mapper.selectRfqCount(month); break;
case "po":
if (all) {
map = mapper.selectPoCountAll();
} else {
map = mapper.selectPoCount(month);
}
break;
default: return BigDecimal.ZERO;
}
return toBD(map.get("totalCount"));
@@ -169,4 +210,100 @@ public class BizReportServiceImpl implements IBizReportService {
if (v instanceof Number) return BigDecimal.valueOf(((Number) v).doubleValue());
return BigDecimal.ZERO;
}
/**
* 补全月度采购趋势 — 生成近N个月的完整月份列表无数据月份填0
* 解决趋势图断层问题
*/
private List<ReportDashboardVO.MonthTrend> fillMonthlyTrend(List<ReportDashboardVO.MonthTrend> raw, int months) {
// 生成近N个月份列表从最早到当前月
List<String> allMonths = new ArrayList<>();
LocalDate now = LocalDate.now();
for (int i = months - 1; i >= 0; i--) {
allMonths.add(now.minusMonths(i).format(MONTH_FMT));
}
// 将原始数据转为Map便于查找
Map<String, ReportDashboardVO.MonthTrend> rawMap = new LinkedHashMap<>();
if (raw != null) {
for (ReportDashboardVO.MonthTrend t : raw) {
if (t.getMonth() != null) {
rawMap.put(t.getMonth(), t);
}
}
}
// 按完整月份列表补全
List<ReportDashboardVO.MonthTrend> result = new ArrayList<>();
for (String m : allMonths) {
ReportDashboardVO.MonthTrend t = rawMap.get(m);
if (t == null) {
t = new ReportDashboardVO.MonthTrend();
t.setMonth(m);
t.setAmount(BigDecimal.ZERO);
t.setCount(0);
}
result.add(t);
}
return result;
}
/**
* 补全成本趋势 — 生成完整月份列表无数据月份填0
* 解决成本趋势图断层问题
*/
private List<ReportCostVO.CostTrend> fillCostTrend(List<ReportCostVO.CostTrend> raw, String startMonth, String endMonth) {
// 确定起止月份
LocalDate now = LocalDate.now();
LocalDate start;
LocalDate end;
if (startMonth != null && !startMonth.isEmpty()) {
start = LocalDate.parse(startMonth + "-01");
} else {
start = now.minusMonths(11); // 默认近12个月
}
if (endMonth != null && !endMonth.isEmpty()) {
end = LocalDate.parse(endMonth + "-01");
} else {
end = now;
}
// 生成完整月份列表
List<String> allMonths = new ArrayList<>();
LocalDate cur = start.withDayOfMonth(1);
while (!cur.isAfter(end)) {
allMonths.add(cur.format(MONTH_FMT));
cur = cur.plusMonths(1);
}
// 将原始数据转为Map
Map<String, ReportCostVO.CostTrend> rawMap = new LinkedHashMap<>();
if (raw != null) {
for (ReportCostVO.CostTrend t : raw) {
if (t.getMonth() != null) {
rawMap.put(t.getMonth(), t);
}
}
}
// 补全
List<ReportCostVO.CostTrend> result = new ArrayList<>();
for (String m : allMonths) {
ReportCostVO.CostTrend t = rawMap.get(m);
if (t == null) {
t = new ReportCostVO.CostTrend();
t.setMonth(m);
t.setExpectedAmount(BigDecimal.ZERO);
t.setActualAmount(BigDecimal.ZERO);
t.setSavedAmount(BigDecimal.ZERO);
}
// 设置超支标识:节省金额为负表示实际超预算
if (t.getSavedAmount() != null && t.getSavedAmount().compareTo(BigDecimal.ZERO) < 0) {
t.setOverBudget(true);
}
result.add(t);
}
return result;
}
}

View File

@@ -64,11 +64,14 @@
<!-- 查询订单异议详情 -->
<select id="selectObjectionInfo" resultType="java.util.Map">
SELECT o.reason AS bizTitle, s.supplier_name AS partnerName, p.po_no AS bizNo,
SELECT o.reason AS bizTitle, s.supplier_name AS partnerName,
COALESCE(d.do_no, p.po_no) AS bizNo,
o.resolution AS resolution, o.status AS status,
o.create_time AS createTime, o.create_by AS createBy
FROM biz_order_objection o
LEFT JOIN biz_supplier s ON o.supplier_id = s.supplier_id
LEFT JOIN biz_purchase_order p ON o.po_id = p.po_id
LEFT JOIN biz_delivery_order d ON o.do_id = d.do_id
WHERE o.objection_id = #{id}
</select>
</mapper>

View File

@@ -5,6 +5,7 @@
<id property="objectionId" column="objection_id"/>
<result property="tenantId" column="tenant_id"/>
<result property="poId" column="po_id"/>
<result property="doId" column="do_id"/>
<result property="supplierId" column="supplier_id"/>
<result property="reason" column="reason"/>
<result property="attachment" column="attachment"/>
@@ -15,13 +16,15 @@
<result property="resolveTime" column="resolve_time"/>
<result property="supplierName" column="supplier_name"/>
<result property="poNo" column="po_no"/>
<result property="doNo" column="do_no"/>
</resultMap>
<select id="selectBizOrderObjectionList" resultMap="BaseRM">
SELECT o.*, s.supplier_name, p.po_no
SELECT o.*, s.supplier_name, p.po_no, d.do_no
FROM biz_order_objection o
LEFT JOIN biz_supplier s ON o.supplier_id=s.supplier_id
LEFT JOIN biz_purchase_order p ON o.po_id=p.po_id
LEFT JOIN biz_delivery_order d ON o.do_id=d.do_id
<where>
<if test="tenantId != null"> AND o.tenant_id=#{tenantId}</if>
<if test="status != null and status != ''"> AND o.status=#{status}</if>
@@ -30,15 +33,16 @@
</select>
<select id="selectBizOrderObjectionById" resultMap="BaseRM">
SELECT o.*, s.supplier_name, p.po_no FROM biz_order_objection o
SELECT o.*, s.supplier_name, p.po_no, d.do_no FROM biz_order_objection o
LEFT JOIN biz_supplier s ON o.supplier_id=s.supplier_id
LEFT JOIN biz_purchase_order p ON o.po_id=p.po_id
LEFT JOIN biz_delivery_order d ON o.do_id=d.do_id
WHERE o.objection_id=#{id}
</select>
<insert id="insertBizOrderObjection" useGeneratedKeys="true" keyProperty="objectionId">
INSERT INTO biz_order_objection(tenant_id,po_id,supplier_id,reason,attachment,status,create_by,create_time)
VALUES(#{tenantId},#{poId},#{supplierId},#{reason},#{attachment},'pending',#{createBy},NOW())
INSERT INTO biz_order_objection(tenant_id,po_id,do_id,supplier_id,reason,attachment,status,create_by,create_time)
VALUES(#{tenantId},#{poId},#{doId},#{supplierId},#{reason},#{attachment},'pending',#{createBy},NOW())
</insert>
<update id="updateBizOrderObjection">

View File

@@ -16,6 +16,13 @@
</if>
</select>
<!-- 累计采购总额(不限月份) -->
<select id="selectPurchaseAmountAll" resultType="java.util.HashMap">
SELECT COALESCE(SUM(total_amount), 0) AS totalAmount
FROM biz_purchase_order
WHERE status IN ('confirmed', 'closed', 'delivered')
</select>
<!-- 指定月份 RFQ 数量 -->
<select id="selectRfqCount" resultType="java.util.HashMap">
SELECT COUNT(*) AS totalCount
@@ -36,6 +43,13 @@
</if>
</select>
<!-- 累计采购单数(不限月份) -->
<select id="selectPoCountAll" resultType="java.util.HashMap">
SELECT COUNT(*) AS totalCount
FROM biz_purchase_order
WHERE status IN ('confirmed', 'closed', 'delivered')
</select>
<!-- 活跃供应商数(有过报价或采购的) -->
<select id="selectActiveSupplierCount" resultType="java.util.HashMap">
SELECT COUNT(DISTINCT supplier_id) AS totalCount
@@ -180,17 +194,26 @@
ORDER BY t.month ASC
</select>
<!-- 品类采购分布 -->
<!-- 品类采购分布(按一级分类聚合) -->
<select id="selectCategoryDist" resultType="com.ruoyi.system.domain.bid.ReportCostVO$CategoryDist">
SELECT COALESCE(c.category_name, '未分类') AS categoryName,
COALESCE(SUM(pi.total_price), 0) AS amount,
COUNT(DISTINCT pi.material_id) AS materialCount
SELECT
COALESCE(top_cat.category_id, 0) AS categoryId,
COALESCE(top_cat.category_name, '未分类') AS categoryName,
COALESCE(SUM(pi.total_price), 0) AS amount,
COUNT(DISTINCT pi.material_id) AS materialCount
FROM biz_purchase_order_item pi
JOIN biz_purchase_order p ON pi.po_id = p.po_id
LEFT JOIN biz_material m ON pi.material_id = m.material_id
LEFT JOIN biz_material_category c ON m.category_id = c.category_id
<!-- 通过ancestors字段找到顶级分类parent_id=0的一级分类 -->
LEFT JOIN biz_material_category top_cat ON top_cat.category_id =
CASE
WHEN c.ancestors = '0' THEN c.category_id
WHEN c.ancestors IS NULL THEN NULL
ELSE CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(c.ancestors, ',', 2), ',', -1) AS UNSIGNED)
END
WHERE p.status IN ('confirmed', 'closed', 'delivered')
GROUP BY c.category_name
GROUP BY top_cat.category_id, top_cat.category_name
ORDER BY amount DESC
</select>
@@ -249,6 +272,7 @@
s.supplier_id AS supplierId,
s.supplier_name AS supplierName,
COALESCE(e.evalCount, 0) AS evalCount,
CASE WHEN COALESCE(e.evalCount, 0) > 0 THEN 'evaluated' ELSE 'not_evaluated' END AS evalStatus,
COALESCE(e.qualityAvg, 0) AS qualityAvg,
COALESCE(e.deliveryAvg, 0) AS deliveryAvg,
COALESCE(e.serviceAvg, 0) AS serviceAvg,
@@ -276,7 +300,7 @@
WHERE status IN ('confirmed', 'closed', 'delivered')
GROUP BY supplier_id
) po ON s.supplier_id = po.supplier_id
ORDER BY totalAvg DESC
ORDER BY CASE WHEN COALESCE(e.evalCount, 0) > 0 THEN 0 ELSE 1 END, totalAvg DESC
</select>
<!-- 中标率 -->
@@ -294,7 +318,7 @@
ORDER BY winRate DESC
</select>
<!-- 雷达图数据 -->
<!-- 雷达图数据(仅含有评价数据的供应商) -->
<select id="selectRadarData" resultType="com.ruoyi.system.domain.bid.ReportSupplierVO$RadarData">
SELECT
s.supplier_name AS supplierName,
@@ -303,7 +327,7 @@
COALESCE(AVG(e.service_score), 0) AS service,
COALESCE(AVG(e.price_score), 0) AS price
FROM biz_supplier s
LEFT JOIN biz_supplier_evaluation e ON s.supplier_id = e.supplier_id
INNER JOIN biz_supplier_evaluation e ON s.supplier_id = e.supplier_id
GROUP BY s.supplier_id, s.supplier_name
ORDER BY s.supplier_id
</select>
@@ -322,4 +346,20 @@
ORDER BY objectionCount DESC
</select>
<!-- 供应商评分历史趋势(按月) -->
<select id="selectSupplierScoreHistory" resultType="com.ruoyi.system.domain.bid.ReportSupplierVO$ScoreHistory">
SELECT
e.supplier_id AS supplierId,
s.supplier_name AS supplierName,
DATE_FORMAT(e.eval_time, '%Y-%m') AS month,
AVG(e.quality_score) AS qualityAvg,
AVG(e.delivery_score) AS deliveryAvg,
AVG(e.service_score) AS serviceAvg,
AVG(e.price_score) AS priceAvg
FROM biz_supplier_evaluation e
JOIN biz_supplier s ON e.supplier_id = s.supplier_id
GROUP BY e.supplier_id, s.supplier_name, DATE_FORMAT(e.eval_time, '%Y-%m')
ORDER BY e.supplier_id, month
</select>
</mapper>