Files
erp-next/ruoyi-system/src/main/resources/mapper/bid/BizReportMapper.xml
王文昊 ba74618bea feat(bid): 新增投标报表统计分析模块
本次提交新增了完整的投标报表统计分析功能,包括:

添加用于数据检查与菜单初始化的 SQL 脚本

实现采购概览仪表板、采购成本分析及供应商绩效报告的后端服务、Mapper、Controller 及 VO 类

添加前端 API、路由配置以及使用 ECharts 可视化图表的页面组件

为仪表板添加通用的 KPI 卡片组件
2026-06-03 14:26:25 +08:00

326 lines
14 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.bid.BizReportMapper">
<!-- ================================================================ -->
<!-- 采 购 总 览 看 板 -->
<!-- ================================================================ -->
<!-- 指定月份采购总额 -->
<select id="selectPurchaseAmount" resultType="java.util.HashMap">
SELECT COALESCE(SUM(total_amount), 0) AS totalAmount
FROM biz_purchase_order
WHERE status IN ('confirmed', 'closed', 'delivered')
<if test="month != null and month != ''">
AND DATE_FORMAT(create_time, '%Y-%m') = #{month}
</if>
</select>
<!-- 指定月份 RFQ 数量 -->
<select id="selectRfqCount" resultType="java.util.HashMap">
SELECT COUNT(*) AS totalCount
FROM biz_rfq
WHERE 1=1
<if test="month != null and month != ''">
AND DATE_FORMAT(create_time, '%Y-%m') = #{month}
</if>
</select>
<!-- 指定月份 PO 数量 -->
<select id="selectPoCount" resultType="java.util.HashMap">
SELECT COUNT(*) AS totalCount
FROM biz_purchase_order
WHERE status IN ('confirmed', 'closed', 'delivered')
<if test="month != null and month != ''">
AND DATE_FORMAT(create_time, '%Y-%m') = #{month}
</if>
</select>
<!-- 活跃供应商数(有过报价或采购的) -->
<select id="selectActiveSupplierCount" resultType="java.util.HashMap">
SELECT COUNT(DISTINCT supplier_id) AS totalCount
FROM (
SELECT supplier_id FROM biz_quotation WHERE status IN ('submitted', 'accepted')
UNION
SELECT supplier_id FROM biz_purchase_order
) t
</select>
<!-- 月度采购趋势 -->
<select id="selectMonthlyTrend" resultType="com.ruoyi.system.domain.bid.ReportDashboardVO$MonthTrend">
SELECT DATE_FORMAT(create_time, '%Y-%m') AS month,
COALESCE(SUM(total_amount), 0) AS amount,
COUNT(*) AS count
FROM biz_purchase_order
WHERE status IN ('confirmed', 'closed', 'delivered')
AND create_time >= DATE_SUB(NOW(), INTERVAL 12 MONTH)
GROUP BY DATE_FORMAT(create_time, '%Y-%m')
ORDER BY month ASC
</select>
<!-- RFQ 状态分布 -->
<select id="selectRfqStatusDist" resultType="com.ruoyi.system.domain.bid.ReportDashboardVO$StatusDist">
SELECT status,
CASE status
WHEN 'draft' THEN '草稿'
WHEN 'published' THEN '已发布'
WHEN 'completed' THEN '已完成'
WHEN 'cancelled' THEN '已取消'
ELSE status
END AS statusLabel,
COUNT(*) AS count,
ROUND(COUNT(*) / (SELECT COUNT(*) FROM biz_rfq) * 100, 1) AS percent
FROM biz_rfq
GROUP BY status
</select>
<!-- Top 供应商排名 -->
<select id="selectTopSuppliers" resultType="com.ruoyi.system.domain.bid.ReportDashboardVO$SupplierRank">
SELECT p.supplier_id AS supplierId,
s.supplier_name AS supplierName,
COALESCE(SUM(p.total_amount), 0) AS totalAmount,
COUNT(*) AS poCount,
COALESCE(AVG(e.total_score), 0) AS avgScore
FROM biz_purchase_order p
LEFT JOIN biz_supplier s ON p.supplier_id = s.supplier_id
LEFT JOIN biz_supplier_evaluation e ON p.po_id = e.po_id
WHERE p.status IN ('confirmed', 'closed', 'delivered')
GROUP BY p.supplier_id, s.supplier_name
ORDER BY totalAmount DESC
LIMIT 5
</select>
<!-- 最近动态 -->
<select id="selectRecentActivities" resultType="com.ruoyi.system.domain.bid.ReportDashboardVO$RecentActivity">
SELECT DATE_FORMAT(create_time, '%Y-%m-%d %H:%i') AS time,
'PO' AS type,
CONCAT('创建采购单 ', po_no, '(¥', total_amount, '') AS `desc`,
po_id AS linkId
FROM biz_purchase_order
UNION ALL
SELECT DATE_FORMAT(create_time, '%Y-%m-%d %H:%i') AS time,
'QUOTE' AS type,
CONCAT('供应商报价 ', quote_no, '(¥', total_amount, '') AS `desc`,
quotation_id AS linkId
FROM biz_quotation
WHERE status IN ('submitted', 'accepted')
UNION ALL
SELECT DATE_FORMAT(eval_time, '%Y-%m-%d %H:%i') AS time,
'EVAL' AS type,
CONCAT('评价供应商:', s.supplier_name, '', e.total_score, '分)') AS `desc`,
e.eval_id AS linkId
FROM biz_supplier_evaluation e
LEFT JOIN biz_supplier s ON e.supplier_id = s.supplier_id
UNION ALL
SELECT DATE_FORMAT(create_time, '%Y-%m-%d %H:%i') AS time,
'OBJECTION' AS type,
CONCAT('订单异议:', LEFT(o.reason, 30)) AS `desc`,
o.objection_id AS linkId
FROM biz_order_objection o
ORDER BY time DESC
LIMIT 10
</select>
<!-- ================================================================ -->
<!-- 采 购 成 本 分 析 -->
<!-- ================================================================ -->
<!-- 预算总额 -->
<select id="selectTotalExpected" resultType="java.util.HashMap">
SELECT COALESCE(SUM(ri.quantity * ri.expected_price), 0) AS totalExpected
FROM biz_rfq r
JOIN biz_rfq_item ri ON r.rfq_id = ri.rfq_id
WHERE r.status = 'completed'
</select>
<!-- 实际采购总额 -->
<select id="selectTotalActual" resultType="java.util.HashMap">
SELECT COALESCE(SUM(total_amount), 0) AS totalActual
FROM biz_purchase_order
WHERE status IN ('confirmed', 'closed', 'delivered')
</select>
<!-- 月度成本趋势 -->
<select id="selectCostTrend" resultType="com.ruoyi.system.domain.bid.ReportCostVO$CostTrend">
SELECT
t.month,
COALESCE(e.expectedAmount, 0) AS expectedAmount,
COALESCE(a.actualAmount, 0) AS actualAmount,
COALESCE(e.expectedAmount, 0) - COALESCE(a.actualAmount, 0) AS savedAmount
FROM (
SELECT DISTINCT DATE_FORMAT(create_time, '%Y-%m') AS month
FROM biz_purchase_order
WHERE status IN ('confirmed', 'closed', 'delivered')
UNION
SELECT DISTINCT DATE_FORMAT(create_time, '%Y-%m') FROM biz_rfq
) t
LEFT JOIN (
SELECT DATE_FORMAT(create_time, '%Y-%m') AS month,
SUM(ri.quantity * ri.expected_price) AS expectedAmount
FROM biz_rfq r
JOIN biz_rfq_item ri ON r.rfq_id = ri.rfq_id
WHERE r.status = 'completed'
GROUP BY DATE_FORMAT(create_time, '%Y-%m')
) e ON t.month = e.month
LEFT JOIN (
SELECT DATE_FORMAT(create_time, '%Y-%m') AS month,
SUM(total_amount) AS actualAmount
FROM biz_purchase_order
WHERE status IN ('confirmed', 'closed', 'delivered')
GROUP BY DATE_FORMAT(create_time, '%Y-%m')
) a ON t.month = a.month
<where>
<if test="startMonth != null and startMonth != ''">
AND t.month >= #{startMonth}
</if>
<if test="endMonth != null and endMonth != ''">
AND t.month &lt;= #{endMonth}
</if>
</where>
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
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
WHERE p.status IN ('confirmed', 'closed', 'delivered')
GROUP BY c.category_name
ORDER BY amount DESC
</select>
<!-- RFQ 比价明细 -->
<select id="selectRfqCompareDetails" resultType="com.ruoyi.system.domain.bid.ReportCostVO$RfqCompareDetail">
SELECT
r.rfq_id AS rfqId,
r.rfq_no AS rfqNo,
r.rfq_title AS rfqTitle,
ri.expectedTotal,
COALESCE(lowest.lowestQuote, 0) AS lowestQuote,
COALESCE(accepted.acceptedQuote, 0) AS acceptedQuote,
ri.expectedTotal - COALESCE(accepted.acceptedQuote, 0) AS savedAmount,
COALESCE(sup.supplierCount, 0) AS supplierCount
FROM biz_rfq r
JOIN (
SELECT rfq_id,
COALESCE(SUM(quantity * expected_price), 0) AS expectedTotal
FROM biz_rfq_item
GROUP BY rfq_id
) ri ON r.rfq_id = ri.rfq_id
LEFT JOIN (
SELECT q.rfq_id,
MIN(q.total_amount) AS lowestQuote
FROM biz_quotation q
WHERE q.status IN ('submitted', 'accepted', 'rejected')
AND q.total_amount > 0
GROUP BY q.rfq_id
) lowest ON r.rfq_id = lowest.rfq_id
LEFT JOIN (
SELECT q.rfq_id,
MAX(q.total_amount) AS acceptedQuote
FROM biz_quotation q
WHERE q.status = 'accepted'
GROUP BY q.rfq_id
) accepted ON r.rfq_id = accepted.rfq_id
LEFT JOIN (
SELECT q.rfq_id,
COUNT(DISTINCT q.supplier_id) AS supplierCount
FROM biz_quotation q
WHERE q.status IN ('submitted', 'accepted', 'rejected')
AND q.total_amount > 0
GROUP BY q.rfq_id
) sup ON r.rfq_id = sup.rfq_id
WHERE r.status = 'completed'
ORDER BY r.create_time DESC
</select>
<!-- ================================================================ -->
<!-- 供 应 商 绩 效 -->
<!-- ================================================================ -->
<!-- 供应商评分排名 -->
<select id="selectSupplierScores" resultType="com.ruoyi.system.domain.bid.ReportSupplierVO$SupplierScore">
SELECT
s.supplier_id AS supplierId,
s.supplier_name AS supplierName,
COALESCE(e.evalCount, 0) AS evalCount,
COALESCE(e.qualityAvg, 0) AS qualityAvg,
COALESCE(e.deliveryAvg, 0) AS deliveryAvg,
COALESCE(e.serviceAvg, 0) AS serviceAvg,
COALESCE(e.priceAvg, 0) AS priceAvg,
COALESCE(e.totalAvg, 0) AS totalAvg,
COALESCE(po.poCount, 0) AS poCount,
COALESCE(po.poAmount, 0) AS poAmount
FROM biz_supplier s
LEFT JOIN (
SELECT supplier_id,
COUNT(*) AS evalCount,
AVG(quality_score) AS qualityAvg,
AVG(delivery_score) AS deliveryAvg,
AVG(service_score) AS serviceAvg,
AVG(price_score) AS priceAvg,
AVG(total_score) AS totalAvg
FROM biz_supplier_evaluation
GROUP BY supplier_id
) e ON s.supplier_id = e.supplier_id
LEFT JOIN (
SELECT supplier_id,
COUNT(*) AS poCount,
SUM(total_amount) AS poAmount
FROM biz_purchase_order
WHERE status IN ('confirmed', 'closed', 'delivered')
GROUP BY supplier_id
) po ON s.supplier_id = po.supplier_id
ORDER BY totalAvg DESC
</select>
<!-- 中标率 -->
<select id="selectWinRate" resultType="com.ruoyi.system.domain.bid.ReportSupplierVO$WinRateData">
SELECT
q.supplier_id AS supplierId,
s.supplier_name AS supplierName,
COUNT(*) AS totalQuotes,
SUM(CASE WHEN q.status = 'accepted' THEN 1 ELSE 0 END) AS winCount,
ROUND(SUM(CASE WHEN q.status = 'accepted' THEN 1 ELSE 0 END) / COUNT(*) * 100, 1) AS winRate
FROM biz_quotation q
JOIN biz_supplier s ON q.supplier_id = s.supplier_id
WHERE q.status IN ('accepted', 'rejected')
GROUP BY q.supplier_id, s.supplier_name
ORDER BY winRate DESC
</select>
<!-- 雷达图数据 -->
<select id="selectRadarData" resultType="com.ruoyi.system.domain.bid.ReportSupplierVO$RadarData">
SELECT
s.supplier_name AS supplierName,
COALESCE(AVG(e.quality_score), 0) AS quality,
COALESCE(AVG(e.delivery_score), 0) AS delivery,
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
GROUP BY s.supplier_id, s.supplier_name
ORDER BY s.supplier_id
</select>
<!-- 异议统计 -->
<select id="selectObjectionStats" resultType="com.ruoyi.system.domain.bid.ReportSupplierVO$ObjectionStat">
SELECT
o.supplier_id AS supplierId,
s.supplier_name AS supplierName,
COUNT(*) AS objectionCount,
SUM(CASE WHEN o.status = 'resolved' THEN 1 ELSE 0 END) AS resolvedCount,
SUBSTRING_INDEX(GROUP_CONCAT(o.reason ORDER BY o.create_time DESC SEPARATOR ''), '', 1) AS topReason
FROM biz_order_objection o
JOIN biz_supplier s ON o.supplier_id = s.supplier_id
GROUP BY o.supplier_id, s.supplier_name
ORDER BY objectionCount DESC
</select>
</mapper>