From 896999dfeb1e3ca7e673bfba44a8d8083103512d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=96=87=E6=98=8A?= Date: Sun, 21 Jun 2026 12:40:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E9=87=87=E8=B4=AD?= =?UTF-8?q?=E7=9C=8B=E6=9D=BF=E4=B8=8E=E5=BC=82=E8=AE=AE=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=8A=9F=E8=83=BD=E5=8D=87=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 新增报表下钻跳转支持,为供应商、RFQ、采购单、物料等页面添加路由参数解析 2. 优化异议管理模块:新增发货单关联、详情弹窗、审批流程优化 3. 完善采购看板功能:支持累计数据展示、图表导出、数据补全与趋势优化 4. 新增供应商评分历史趋势统计与品类分布聚合逻辑 5. 修复异议API路径与通知跳转路径问题,新增模拟测试数据 --- .../bid/BizOrderObjectionController.java | 53 ++++- .../system/domain/bid/BizOrderObjection.java | 6 + .../ruoyi/system/domain/bid/ReportCostVO.java | 9 + .../system/domain/bid/ReportDashboardVO.java | 4 + .../system/domain/bid/ReportSupplierVO.java | 35 +++ .../system/mapper/bid/BizReportMapper.java | 9 + .../impl/BizApprovalActionServiceImpl.java | 23 +- .../bid/impl/BizNotifyMessageServiceImpl.java | 6 +- .../bid/impl/BizReportServiceImpl.java | 163 +++++++++++-- .../mapper/bid/BizApprovalActionMapper.xml | 5 +- .../mapper/bid/BizOrderObjectionMapper.xml | 12 +- .../resources/mapper/bid/BizReportMapper.xml | 56 ++++- ruoyi-ui/src/api/bid/objection.js | 2 +- ruoyi-ui/src/router/index.js | 61 +++++ ruoyi-ui/src/views/bid/material/index.vue | 16 +- ruoyi-ui/src/views/bid/objection/index.vue | 127 ++++++++--- .../src/views/bid/purchaseorder/index.vue | 11 +- .../views/bid/report/components/KpiCard.vue | 44 +++- ruoyi-ui/src/views/bid/report/cost.vue | 152 +++++++++++-- ruoyi-ui/src/views/bid/report/dashboard.vue | 41 +++- ruoyi-ui/src/views/bid/report/supplier.vue | 214 ++++++++++++++++-- ruoyi-ui/src/views/bid/rfq/index.vue | 4 + ruoyi-ui/src/views/bid/supplier/index.vue | 4 + sql/mock_order_objections.sql | 41 ++++ sql/mock_report_data.sql | 55 +++++ sql/mock_supplier_evaluations.sql | 84 +++++++ 26 files changed, 1129 insertions(+), 108 deletions(-) create mode 100644 sql/mock_order_objections.sql create mode 100644 sql/mock_report_data.sql create mode 100644 sql/mock_supplier_evaluations.sql diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/bid/BizOrderObjectionController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/bid/BizOrderObjectionController.java index dc8f01f7..2c0fadad 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/bid/BizOrderObjectionController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/bid/BizOrderObjectionController.java @@ -8,13 +8,19 @@ import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.system.domain.bid.BizApprovalConfig; +import com.ruoyi.system.domain.bid.BizNotifyMessage; import com.ruoyi.system.domain.bid.BizOrderObjection; +import com.ruoyi.system.service.bid.IBizApprovalConfigService; +import com.ruoyi.system.service.bid.IBizNotifyMessageService; import com.ruoyi.system.service.bid.IBizOrderObjectionService; @RestController @RequestMapping("/bid/objection") public class BizOrderObjectionController extends BaseController { @Autowired private IBizOrderObjectionService service; + @Autowired private IBizNotifyMessageService notifyMessageService; + @Autowired private IBizApprovalConfigService approvalConfigService; @PreAuthorize("@ss.hasPermi('bid:objection:list')") @GetMapping("/list") @@ -32,15 +38,58 @@ public class BizOrderObjectionController extends BaseController { @Log(title = "订单异议", businessType = BusinessType.INSERT) @PostMapping public AjaxResult add(@RequestBody BizOrderObjection record) { + Long tenantId = getDeptId(); + if (tenantId == null) tenantId = 1L; + record.setTenantId(tenantId); record.setCreateBy(getUsername()); - return toAjax(service.insertBizOrderObjection(record)); + int rows = service.insertBizOrderObjection(record); + // 通知审批人有新异议待处理 + if (rows > 0 && record.getObjectionId() != null) { + sendNewObjectionNotification(record); + } + return toAjax(rows); + } + + /** + * 新建异议后通知审批人 + */ + private void sendNewObjectionNotification(BizOrderObjection record) { + try { + BizApprovalConfig config = approvalConfigService.selectByBizType("ORDER_OBJECTION"); + if (config == null || config.getUserIds() == null || config.getUserIds().isEmpty()) return; + + String title = "新异议待处理: 订单异议 " + (record.getDoId() != null ? "发货单#" + record.getDoId() : ""); + StringBuilder content = new StringBuilder(); + content.append("有新的订单异议提交,等待处理。"); + if (record.getSupplierId() != null) content.append("供应商ID: ").append(record.getSupplierId()).append(";"); + if (record.getReason() != null) content.append("异议原因: ").append(record.getReason()).append(";"); + content.append("提交人: ").append(getUsername()); + + for (Long approverUserId : config.getUserIds()) { + if (approverUserId == null) continue; + BizNotifyMessage msg = new BizNotifyMessage(); + msg.setUserId(approverUserId); + msg.setNoticeType("approval"); + msg.setPriority(1); + msg.setBizType("ORDER_OBJECTION"); + msg.setBizId(record.getObjectionId()); + msg.setBizUrl("/fulfill/supplierFulfill/objection?id=" + record.getObjectionId()); + msg.setCreateBy(getUsername()); + msg.setTitle(title); + msg.setContent(content.toString()); + msg.setIsRead("0"); + notifyMessageService.insertNotifyMessage(msg); + } + } catch (Exception e) { + // 通知发送失败不影响业务操作 + } } @PreAuthorize("@ss.hasPermi('bid:objection:edit')") @Log(title = "处理异议", businessType = BusinessType.UPDATE) @PutMapping("/resolve") public AjaxResult resolve(@RequestBody BizOrderObjection record) { - record.setStatus("resolved"); + // 只保存处理结果,不直接修改状态,状态由审批流程控制 return toAjax(service.updateBizOrderObjection(record)); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/bid/BizOrderObjection.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/bid/BizOrderObjection.java index 8c57cdee..3248ab26 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/domain/bid/BizOrderObjection.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/bid/BizOrderObjection.java @@ -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; } } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/bid/ReportCostVO.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/bid/ReportCostVO.java index ba9e0513..cc35a564 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/domain/bid/ReportCostVO.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/bid/ReportCostVO.java @@ -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; } } } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/bid/ReportDashboardVO.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/bid/ReportDashboardVO.java index b4272fa8..476f31d4 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/domain/bid/ReportDashboardVO.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/bid/ReportDashboardVO.java @@ -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; } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/bid/ReportSupplierVO.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/bid/ReportSupplierVO.java index 78b96e20..8b133c37 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/domain/bid/ReportSupplierVO.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/bid/ReportSupplierVO.java @@ -20,6 +20,9 @@ public class ReportSupplierVO { /** 异议统计 */ private List objectionStats; + /** 评分历史趋势 */ + private List scoreHistory; + // ===== getters / setters ===== public List getRankings() { return rankings; } @@ -34,6 +37,9 @@ public class ReportSupplierVO { public List getObjectionStats() { return objectionStats; } public void setObjectionStats(List v) { objectionStats = v; } + public List getScoreHistory() { return scoreHistory; } + public void setScoreHistory(List 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; } + } } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/bid/BizReportMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/bid/BizReportMapper.java index 767483d0..27b4909f 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/bid/BizReportMapper.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/bid/BizReportMapper.java @@ -18,12 +18,18 @@ public interface BizReportMapper { /** 本月/上月采购总额 */ Map selectPurchaseAmount(@Param("month") String month); + /** 累计采购总额(不限月份) */ + Map selectPurchaseAmountAll(); + /** 本月/上月 RFQ 数量 */ Map selectRfqCount(@Param("month") String month); /** 本月/上月 PO 数量 */ Map selectPoCount(@Param("month") String month); + /** 累计采购单数(不限月份) */ + Map selectPoCountAll(); + /** 活跃供应商数 */ Map selectActiveSupplierCount(); @@ -70,4 +76,7 @@ public interface BizReportMapper { /** 异议统计 */ List selectObjectionStats(); + + /** 供应商评分历史趋势 */ + List selectSupplierScoreHistory(); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/bid/impl/BizApprovalActionServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/bid/impl/BizApprovalActionServiceImpl.java index c4a73228..c8dc52e9 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/bid/impl/BizApprovalActionServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/bid/impl/BizApprovalActionServiceImpl.java @@ -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; } } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/bid/impl/BizNotifyMessageServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/bid/impl/BizNotifyMessageServiceImpl.java index e5b1acf4..1de1e36f 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/bid/impl/BizNotifyMessageServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/bid/impl/BizNotifyMessageServiceImpl.java @@ -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; } } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/bid/impl/BizReportServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/bid/impl/BizReportServiceImpl.java index 2e449a4e..d1a74a0f 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/bid/impl/BizReportServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/bid/impl/BizReportServiceImpl.java @@ -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 act = mapper.selectActiveSupplierCount(); BigDecimal activeVal = toBD(act.get("totalCount")); vo.setActiveSupplierCount(buildKpi("活跃供应商", activeVal, null, "家")); - // 2) 月度趋势 - vo.setMonthlyTrend(mapper.selectMonthlyTrend()); + // 2) 月度趋势(补全近12个月,无数据月份填0) + List rawTrend = mapper.selectMonthlyTrend(); + vo.setMonthlyTrend(fillMonthlyTrend(rawTrend, 12)); // 3) RFQ 状态分布 List dist = mapper.selectRfqStatusDist(); @@ -91,8 +101,9 @@ public class BizReportServiceImpl implements IBizReportService { : 0); vo.setSummary(summary); - // 月度趋势 - vo.setCostTrend(mapper.selectCostTrend(startMonth, endMonth)); + // 月度趋势(补全月份,无数据月份填0) + List rawTrend = mapper.selectCostTrend(startMonth, endMonth); + vo.setCostTrend(fillCostTrend(rawTrend, startMonth, endMonth)); // 品类分布 List catDist = mapper.selectCategoryDist(); @@ -108,7 +119,15 @@ public class BizReportServiceImpl implements IBizReportService { vo.setCategoryDist(catDist); // RFQ 比价明细 - vo.setRfqDetails(mapper.selectRfqCompareDetails()); + List 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 map = mapper.selectPurchaseAmount(month); + return selectAmount(column, month, false); + } + + private BigDecimal selectAmount(String column, String month, boolean all) { + Map 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 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 fillMonthlyTrend(List raw, int months) { + // 生成近N个月份列表(从最早到当前月) + List 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 rawMap = new LinkedHashMap<>(); + if (raw != null) { + for (ReportDashboardVO.MonthTrend t : raw) { + if (t.getMonth() != null) { + rawMap.put(t.getMonth(), t); + } + } + } + + // 按完整月份列表补全 + List 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 fillCostTrend(List 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 allMonths = new ArrayList<>(); + LocalDate cur = start.withDayOfMonth(1); + while (!cur.isAfter(end)) { + allMonths.add(cur.format(MONTH_FMT)); + cur = cur.plusMonths(1); + } + + // 将原始数据转为Map + Map rawMap = new LinkedHashMap<>(); + if (raw != null) { + for (ReportCostVO.CostTrend t : raw) { + if (t.getMonth() != null) { + rawMap.put(t.getMonth(), t); + } + } + } + + // 补全 + List 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; + } } diff --git a/ruoyi-system/src/main/resources/mapper/bid/BizApprovalActionMapper.xml b/ruoyi-system/src/main/resources/mapper/bid/BizApprovalActionMapper.xml index ecf6118c..d161cce8 100644 --- a/ruoyi-system/src/main/resources/mapper/bid/BizApprovalActionMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/bid/BizApprovalActionMapper.xml @@ -64,11 +64,14 @@ diff --git a/ruoyi-system/src/main/resources/mapper/bid/BizOrderObjectionMapper.xml b/ruoyi-system/src/main/resources/mapper/bid/BizOrderObjectionMapper.xml index 44103e05..ec013dec 100644 --- a/ruoyi-system/src/main/resources/mapper/bid/BizOrderObjectionMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/bid/BizOrderObjectionMapper.xml @@ -5,6 +5,7 @@ + @@ -15,13 +16,15 @@ + - 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()) diff --git a/ruoyi-system/src/main/resources/mapper/bid/BizReportMapper.xml b/ruoyi-system/src/main/resources/mapper/bid/BizReportMapper.xml index 6699a2c1..dd175541 100644 --- a/ruoyi-system/src/main/resources/mapper/bid/BizReportMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/bid/BizReportMapper.xml @@ -16,6 +16,13 @@ + + + + + + - + @@ -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 @@ -294,7 +318,7 @@ ORDER BY winRate DESC - + @@ -322,4 +346,20 @@ ORDER BY objectionCount DESC + + + diff --git a/ruoyi-ui/src/api/bid/objection.js b/ruoyi-ui/src/api/bid/objection.js index 19195aee..af828f64 100644 --- a/ruoyi-ui/src/api/bid/objection.js +++ b/ruoyi-ui/src/api/bid/objection.js @@ -3,5 +3,5 @@ const baseUrl = '/bid/objection' export const listObjection = (params) => request({ url: baseUrl + '/list', method: 'get', params }) export const getObjection = (id) => request({ url: baseUrl + '/' + id, method: 'get' }) export const addObjection = (data) => request({ url: baseUrl, method: 'post', data }) -export const updateObjection = (data) => request({ url: baseUrl, method: 'put', data }) +export const updateObjection = (data) => request({ url: baseUrl + '/resolve', method: 'put', data }) export const delObjection = (ids) => request({ url: baseUrl + '/' + ids, method: 'delete' }) diff --git a/ruoyi-ui/src/router/index.js b/ruoyi-ui/src/router/index.js index 9db2f6cf..848cb248 100644 --- a/ruoyi-ui/src/router/index.js +++ b/ruoyi-ui/src/router/index.js @@ -127,6 +127,67 @@ export const dynamicRoutes = [ } ] }, + // ── 列表页路由(供报表下钻跳转) ── + { + path: '/bid/purchaseorder', + component: Layout, + hidden: true, + permissions: ['bid:purchaseorder:list'], + children: [{ + path: '', + component: () => import('@/views/bid/purchaseorder/index'), + name: 'PurchaseOrderList', + meta: { title: '采购单', activeMenu: '/quote/purchaseorder' } + }] + }, + { + path: '/bid/rfq', + component: Layout, + hidden: true, + permissions: ['bid:rfq:list'], + children: [{ + path: '', + component: () => import('@/views/bid/rfq/index'), + name: 'RfqList', + meta: { title: '报价请求', activeMenu: '/quote/rfq' } + }] + }, + { + path: '/bid/supplier', + component: Layout, + hidden: true, + permissions: ['bid:supplier:list'], + children: [{ + path: '', + component: () => import('@/views/bid/supplier/index'), + name: 'SupplierList', + meta: { title: '供应商管理', activeMenu: '/basedata/bidSupplier' } + }] + }, + { + path: '/bid/material', + component: Layout, + hidden: true, + permissions: ['bid:material:list'], + children: [{ + path: '', + component: () => import('@/views/bid/material/index'), + name: 'MaterialList', + meta: { title: '物料管理', activeMenu: '/basedata/material' } + }] + }, + { + path: '/bid/comparison/detail', + component: Layout, + hidden: true, + permissions: ['bid:comparison:list'], + children: [{ + path: '', + component: () => import('@/views/bid/comparison/detail'), + name: 'ComparisonDetail', + meta: { title: '比价详情', activeMenu: '/quote/comparison' } + }] + }, // ── 统计分析 路由 ── { path: '/bid/report/dashboard', diff --git a/ruoyi-ui/src/views/bid/material/index.vue b/ruoyi-ui/src/views/bid/material/index.vue index c5874770..fd603ed5 100644 --- a/ruoyi-ui/src/views/bid/material/index.vue +++ b/ruoyi-ui/src/views/bid/material/index.vue @@ -15,7 +15,7 @@ size="mini" placeholder="搜索分类" clearable - style="width:140px" /> + class="tree-filter-input" /> - + @@ -32,10 +32,10 @@ - + @@ -45,13 +45,15 @@ - - - - + + + - + + + +
取消 @@ -61,53 +63,126 @@ - - - - 已解决 - 拒绝 - + + {{ resolveForm.reason || '—' }} + + +
取消 - 确认 + 提交审批 +
+
+ + + +
+ + + + {{ detailData.doNo || '-' }} + + + {{ detailData.poNo || '-' }} + + + {{ detailData.supplierName || '-' }} + {{ detailData.reason || '-' }} + + + {{ { pending:'待处理', '10':'审批中', resolved:'已解决', rejected:'已拒绝' }[detailData.status] || detailData.status }} + + + {{ detailData.resolution }} + {{ detailData.createBy || '-' }} + {{ parseTime(detailData.createTime) }} + {{ parseTime(detailData.resolveTime) }} + +