feat: multi-dim comparison + clustering + per-supplier PDF export
Backend: - BizComparisonVO: add composite score fields (priceScore, deliveryScore, qualityScore, serviceScore, compositeScore, rankBadge) + SupplierPlan cluster VO - Mapper: join biz_supplier_evaluation for quality/service history scores - Service: weighted scoring (price 40%/delivery 25%/quality 20%/service 15%), greedy clustering assigns each item to best-score supplier, groups into plans - Controller: returns CompareResult with items + recommendedPlans Frontend: - Tab 1 (多维度比价): supplier rank cards with 4-dim progress bars - Tab 2 (智能推荐方案): per-supplier cluster cards with explanation + PDF export - PDF: logo header, score legend, items table, cluster reason per supplier
This commit is contained in:
@@ -3,52 +3,167 @@ package com.ruoyi.system.domain.bid;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 比价结果 VO (含多维评分 + 聚类推荐方案)
|
||||
*/
|
||||
public class BizComparisonVO {
|
||||
|
||||
// ---------- 物料行 ----------
|
||||
private Long rfqItemId;
|
||||
private String materialName;
|
||||
private String spec;
|
||||
private String unit;
|
||||
private BigDecimal quantity;
|
||||
private List<BizComparisonVO.SupplierPrice> prices;
|
||||
private List<SupplierPrice> prices;
|
||||
|
||||
// ---------- 单个供应商在该物料上的报价 ----------
|
||||
public static class SupplierPrice {
|
||||
private Long supplierId;
|
||||
private String supplierName;
|
||||
private Long quotationId;
|
||||
private String quoteNo;
|
||||
|
||||
// 原始数据
|
||||
private BigDecimal unitPrice;
|
||||
private BigDecimal totalPrice;
|
||||
private Integer deliveryDays;
|
||||
private boolean lowestPrice;
|
||||
|
||||
public Long getSupplierId() { return supplierId; }
|
||||
public void setSupplierId(Long supplierId) { this.supplierId = supplierId; }
|
||||
public String getSupplierName() { return supplierName; }
|
||||
public void setSupplierName(String supplierName) { this.supplierName = supplierName; }
|
||||
public Long getQuotationId() { return quotationId; }
|
||||
public void setQuotationId(Long quotationId) { this.quotationId = quotationId; }
|
||||
public String getQuoteNo() { return quoteNo; }
|
||||
public void setQuoteNo(String quoteNo) { this.quoteNo = quoteNo; }
|
||||
public BigDecimal getUnitPrice() { return unitPrice; }
|
||||
public void setUnitPrice(BigDecimal unitPrice) { this.unitPrice = unitPrice; }
|
||||
public BigDecimal getTotalPrice() { return totalPrice; }
|
||||
public void setTotalPrice(BigDecimal totalPrice) { this.totalPrice = totalPrice; }
|
||||
public Integer getDeliveryDays() { return deliveryDays; }
|
||||
public void setDeliveryDays(Integer deliveryDays) { this.deliveryDays = deliveryDays; }
|
||||
public boolean isLowestPrice() { return lowestPrice; }
|
||||
public void setLowestPrice(boolean lowestPrice) { this.lowestPrice = lowestPrice; }
|
||||
// 多维评分 (均 0-100)
|
||||
private double priceScore; // 价格得分
|
||||
private double deliveryScore; // 交期得分
|
||||
private double qualityScore; // 质量历史得分
|
||||
private double serviceScore; // 服务历史得分
|
||||
private double compositeScore; // 综合得分 = 0.40P+0.25D+0.20Q+0.15S
|
||||
|
||||
// 辅助字段
|
||||
private boolean lowestPrice;
|
||||
private String rankBadge; // "最优综合", "价格最低", "交期最快"
|
||||
private double evalAvgScore; // 历史平均评分(原始1-5)
|
||||
private int historyCount; // 历史成交次数
|
||||
|
||||
// ---- getters/setters ----
|
||||
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 Long getQuotationId(){return quotationId;}
|
||||
public void setQuotationId(Long v){quotationId=v;}
|
||||
public String getQuoteNo(){return quoteNo;}
|
||||
public void setQuoteNo(String v){quoteNo=v;}
|
||||
public BigDecimal getUnitPrice(){return unitPrice;}
|
||||
public void setUnitPrice(BigDecimal v){unitPrice=v;}
|
||||
public BigDecimal getTotalPrice(){return totalPrice;}
|
||||
public void setTotalPrice(BigDecimal v){totalPrice=v;}
|
||||
public Integer getDeliveryDays(){return deliveryDays;}
|
||||
public void setDeliveryDays(Integer v){deliveryDays=v;}
|
||||
public double getPriceScore(){return priceScore;}
|
||||
public void setPriceScore(double v){priceScore=v;}
|
||||
public double getDeliveryScore(){return deliveryScore;}
|
||||
public void setDeliveryScore(double v){deliveryScore=v;}
|
||||
public double getQualityScore(){return qualityScore;}
|
||||
public void setQualityScore(double v){qualityScore=v;}
|
||||
public double getServiceScore(){return serviceScore;}
|
||||
public void setServiceScore(double v){serviceScore=v;}
|
||||
public double getCompositeScore(){return compositeScore;}
|
||||
public void setCompositeScore(double v){compositeScore=v;}
|
||||
public boolean isLowestPrice(){return lowestPrice;}
|
||||
public void setLowestPrice(boolean v){lowestPrice=v;}
|
||||
public String getRankBadge(){return rankBadge;}
|
||||
public void setRankBadge(String v){rankBadge=v;}
|
||||
public double getEvalAvgScore(){return evalAvgScore;}
|
||||
public void setEvalAvgScore(double v){evalAvgScore=v;}
|
||||
public int getHistoryCount(){return historyCount;}
|
||||
public void setHistoryCount(int v){historyCount=v;}
|
||||
}
|
||||
|
||||
public Long getRfqItemId() { return rfqItemId; }
|
||||
public void setRfqItemId(Long rfqItemId) { this.rfqItemId = rfqItemId; }
|
||||
public String getMaterialName() { return materialName; }
|
||||
public void setMaterialName(String materialName) { this.materialName = materialName; }
|
||||
public String getSpec() { return spec; }
|
||||
public void setSpec(String spec) { this.spec = spec; }
|
||||
public String getUnit() { return unit; }
|
||||
public void setUnit(String unit) { this.unit = unit; }
|
||||
public BigDecimal getQuantity() { return quantity; }
|
||||
public void setQuantity(BigDecimal quantity) { this.quantity = quantity; }
|
||||
public List<SupplierPrice> getPrices() { return prices; }
|
||||
public void setPrices(List<SupplierPrice> prices) { this.prices = prices; }
|
||||
// ---------- 聚类推荐方案:每个供应商一个采购计划 ----------
|
||||
public static class SupplierPlan {
|
||||
private Long supplierId;
|
||||
private String supplierName;
|
||||
private String contact;
|
||||
private String phone;
|
||||
private BigDecimal totalAmount;
|
||||
private double avgCompositeScore;
|
||||
private String clusterReason;
|
||||
private List<PlanItem> items;
|
||||
|
||||
public static class PlanItem {
|
||||
private String materialName;
|
||||
private String spec;
|
||||
private String unit;
|
||||
private BigDecimal quantity;
|
||||
private BigDecimal unitPrice;
|
||||
private BigDecimal totalPrice;
|
||||
private Integer deliveryDays;
|
||||
private double compositeScore;
|
||||
private String rankBadge;
|
||||
|
||||
public String getMaterialName(){return materialName;}
|
||||
public void setMaterialName(String v){materialName=v;}
|
||||
public String getSpec(){return spec;}
|
||||
public void setSpec(String v){spec=v;}
|
||||
public String getUnit(){return unit;}
|
||||
public void setUnit(String v){unit=v;}
|
||||
public BigDecimal getQuantity(){return quantity;}
|
||||
public void setQuantity(BigDecimal v){quantity=v;}
|
||||
public BigDecimal getUnitPrice(){return unitPrice;}
|
||||
public void setUnitPrice(BigDecimal v){unitPrice=v;}
|
||||
public BigDecimal getTotalPrice(){return totalPrice;}
|
||||
public void setTotalPrice(BigDecimal v){totalPrice=v;}
|
||||
public Integer getDeliveryDays(){return deliveryDays;}
|
||||
public void setDeliveryDays(Integer v){deliveryDays=v;}
|
||||
public double getCompositeScore(){return compositeScore;}
|
||||
public void setCompositeScore(double v){compositeScore=v;}
|
||||
public String getRankBadge(){return rankBadge;}
|
||||
public void setRankBadge(String v){rankBadge=v;}
|
||||
}
|
||||
|
||||
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 getContact(){return contact;}
|
||||
public void setContact(String v){contact=v;}
|
||||
public String getPhone(){return phone;}
|
||||
public void setPhone(String v){phone=v;}
|
||||
public BigDecimal getTotalAmount(){return totalAmount;}
|
||||
public void setTotalAmount(BigDecimal v){totalAmount=v;}
|
||||
public double getAvgCompositeScore(){return avgCompositeScore;}
|
||||
public void setAvgCompositeScore(double v){avgCompositeScore=v;}
|
||||
public String getClusterReason(){return clusterReason;}
|
||||
public void setClusterReason(String v){clusterReason=v;}
|
||||
public List<PlanItem> getItems(){return items;}
|
||||
public void setItems(List<PlanItem> v){items=v;}
|
||||
}
|
||||
|
||||
// ---------- 最终返回:包裹物料列表 + 推荐方案 ----------
|
||||
public static class CompareResult {
|
||||
private List<BizComparisonVO> items;
|
||||
private List<SupplierPlan> recommendedPlans;
|
||||
private String rfqTitle;
|
||||
private String rfqNo;
|
||||
|
||||
public List<BizComparisonVO> getItems(){return items;}
|
||||
public void setItems(List<BizComparisonVO> v){items=v;}
|
||||
public List<SupplierPlan> getRecommendedPlans(){return recommendedPlans;}
|
||||
public void setRecommendedPlans(List<SupplierPlan> v){recommendedPlans=v;}
|
||||
public String getRfqTitle(){return rfqTitle;}
|
||||
public void setRfqTitle(String v){rfqTitle=v;}
|
||||
public String getRfqNo(){return rfqNo;}
|
||||
public void setRfqNo(String v){rfqNo=v;}
|
||||
}
|
||||
|
||||
// ---- BizComparisonVO getters/setters ----
|
||||
public Long getRfqItemId(){return rfqItemId;}
|
||||
public void setRfqItemId(Long v){rfqItemId=v;}
|
||||
public String getMaterialName(){return materialName;}
|
||||
public void setMaterialName(String v){materialName=v;}
|
||||
public String getSpec(){return spec;}
|
||||
public void setSpec(String v){spec=v;}
|
||||
public String getUnit(){return unit;}
|
||||
public void setUnit(String v){unit=v;}
|
||||
public BigDecimal getQuantity(){return quantity;}
|
||||
public void setQuantity(BigDecimal v){quantity=v;}
|
||||
public List<SupplierPrice> getPrices(){return prices;}
|
||||
public void setPrices(List<SupplierPrice> v){prices=v;}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.ruoyi.system.mapper.bid;
|
||||
|
||||
import com.ruoyi.system.domain.bid.BizComparisonVO;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
public interface BizComparisonMapper {
|
||||
List<Map<String, Object>> selectComparisonData(@Param("rfqId") Long rfqId, @Param("tenantId") Long tenantId);
|
||||
List<Map<String, Object>> selectComparisonData(@Param("rfqId") Long rfqId);
|
||||
Map<String, Object> selectRfqInfo(@Param("rfqId") Long rfqId);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.ruoyi.system.service.bid;
|
||||
|
||||
import com.ruoyi.system.domain.bid.BizComparisonVO;
|
||||
import java.util.List;
|
||||
|
||||
public interface IBizComparisonService {
|
||||
List<BizComparisonVO> compareRfq(Long rfqId, Long tenantId);
|
||||
/** 多维比价 + 聚类推荐,返回完整结果 */
|
||||
BizComparisonVO.CompareResult compareRfq(Long rfqId, Long tenantId);
|
||||
}
|
||||
|
||||
@@ -5,18 +5,41 @@ import com.ruoyi.system.mapper.bid.BizComparisonMapper;
|
||||
import com.ruoyi.system.service.bid.IBizComparisonService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 智慧比价服务
|
||||
*
|
||||
* 评分维度权重:
|
||||
* 价格 40 % — 最低价=100, 其他按比例递减
|
||||
* 交期 25 % — 最短天数=100, 其他按比例递减
|
||||
* 质量 20 % — 历史 quality_score (1-5) 归一化到 0-100
|
||||
* 服务 15 % — 历史 service_score (1-5) 归一化到 0-100
|
||||
*
|
||||
* 聚类算法: 对每个物料行选出综合评分最高的供应商,
|
||||
* 再把相同供应商的推荐行合并为一个采购方案。
|
||||
*/
|
||||
@Service
|
||||
public class BizComparisonServiceImpl implements IBizComparisonService {
|
||||
|
||||
@Autowired private BizComparisonMapper mapper;
|
||||
|
||||
@Override
|
||||
public List<BizComparisonVO> compareRfq(Long rfqId, Long tenantId) {
|
||||
List<Map<String, Object>> rows = mapper.selectComparisonData(rfqId, tenantId);
|
||||
Map<Long, BizComparisonVO> itemMap = new LinkedHashMap<>();
|
||||
private static final double W_PRICE = 0.40;
|
||||
private static final double W_DELIVERY = 0.25;
|
||||
private static final double W_QUALITY = 0.20;
|
||||
private static final double W_SERVICE = 0.15;
|
||||
|
||||
@Override
|
||||
public BizComparisonVO.CompareResult compareRfq(Long rfqId, Long tenantId) {
|
||||
List<Map<String, Object>> rows = mapper.selectComparisonData(rfqId);
|
||||
Map<String, Object> rfqInfo = mapper.selectRfqInfo(rfqId);
|
||||
|
||||
// ── Step 1: 按物料行归组 ────────────────────────────────────────────
|
||||
Map<Long, BizComparisonVO> itemMap = new LinkedHashMap<>();
|
||||
for (Map<String, Object> row : rows) {
|
||||
Long rfqItemId = toLong(row.get("rfqItemId"));
|
||||
BizComparisonVO vo = itemMap.computeIfAbsent(rfqItemId, k -> {
|
||||
@@ -38,27 +61,158 @@ public class BizComparisonServiceImpl implements IBizComparisonService {
|
||||
sp.setQuoteNo(str(row.get("quoteNo")));
|
||||
sp.setUnitPrice(toBD(row.get("unitPrice")));
|
||||
sp.setTotalPrice(toBD(row.get("totalPrice")));
|
||||
sp.setDeliveryDays(row.get("deliveryDays") == null ? 0 : ((Number) row.get("deliveryDays")).intValue());
|
||||
sp.setDeliveryDays(row.get("deliveryDays") == null ? 30
|
||||
: ((Number) row.get("deliveryDays")).intValue());
|
||||
sp.setEvalAvgScore(toDouble(row.get("avgQuality")));
|
||||
sp.setHistoryCount(row.get("historyCount") == null ? 0
|
||||
: ((Number) row.get("historyCount")).intValue());
|
||||
// Store avgService temporarily in qualityScore slot; will be replaced after normalization
|
||||
sp.setQualityScore(toDouble(row.get("avgQuality")));
|
||||
sp.setServiceScore(toDouble(row.get("avgService")));
|
||||
vo.getPrices().add(sp);
|
||||
}
|
||||
}
|
||||
|
||||
// mark lowest price per item
|
||||
// ── Step 2: 多维归一化评分 ───────────────────────────────────────────
|
||||
for (BizComparisonVO vo : itemMap.values()) {
|
||||
BigDecimal min = vo.getPrices().stream()
|
||||
List<BizComparisonVO.SupplierPrice> prices = vo.getPrices();
|
||||
if (prices.isEmpty()) continue;
|
||||
|
||||
// min price & min delivery
|
||||
BigDecimal minPrice = prices.stream()
|
||||
.filter(p -> p.getUnitPrice() != null)
|
||||
.map(BizComparisonVO.SupplierPrice::getUnitPrice)
|
||||
.min(BigDecimal::compareTo).orElse(null);
|
||||
if (min != null) {
|
||||
for (BizComparisonVO.SupplierPrice p : vo.getPrices()) {
|
||||
p.setLowestPrice(min.compareTo(p.getUnitPrice()) == 0);
|
||||
.min(BigDecimal::compareTo).orElse(BigDecimal.ONE);
|
||||
|
||||
int minDays = prices.stream()
|
||||
.mapToInt(p -> p.getDeliveryDays() == null ? 30 : p.getDeliveryDays())
|
||||
.min().orElse(1);
|
||||
if (minDays <= 0) minDays = 1;
|
||||
|
||||
for (BizComparisonVO.SupplierPrice sp : prices) {
|
||||
// 价格得分
|
||||
double priceS = 0;
|
||||
if (sp.getUnitPrice() != null && sp.getUnitPrice().compareTo(BigDecimal.ZERO) > 0) {
|
||||
priceS = minPrice.doubleValue() / sp.getUnitPrice().doubleValue() * 100.0;
|
||||
}
|
||||
sp.setPriceScore(round2(priceS));
|
||||
|
||||
// 交期得分 (最短=100, 1.5倍最短=50)
|
||||
int days = sp.getDeliveryDays() == null ? 30 : sp.getDeliveryDays();
|
||||
double deliveryS = days <= 0 ? 100 : Math.min(100.0, (double) minDays / days * 100.0);
|
||||
sp.setDeliveryScore(round2(deliveryS));
|
||||
|
||||
// 质量/服务得分 (历史均分 1-5 → 0-100; 无历史给 60 分中性分)
|
||||
double rawQ = sp.getQualityScore(); // avgQuality stored temporarily
|
||||
double rawS = sp.getServiceScore(); // avgService stored temporarily
|
||||
double qualityS = sp.getHistoryCount() == 0 ? 60.0 : (rawQ - 1) / 4.0 * 100.0;
|
||||
double serviceS = sp.getHistoryCount() == 0 ? 60.0 : (rawS - 1) / 4.0 * 100.0;
|
||||
sp.setQualityScore(round2(qualityS));
|
||||
sp.setServiceScore(round2(serviceS));
|
||||
|
||||
// 综合得分
|
||||
double composite = W_PRICE * priceS + W_DELIVERY * deliveryS
|
||||
+ W_QUALITY * qualityS + W_SERVICE * serviceS;
|
||||
sp.setCompositeScore(round2(composite));
|
||||
|
||||
// 最低价标记
|
||||
sp.setLowestPrice(minPrice.compareTo(sp.getUnitPrice()) == 0);
|
||||
}
|
||||
|
||||
// ── 排序 + 标记徽章 ──
|
||||
prices.sort(Comparator.comparingDouble(BizComparisonVO.SupplierPrice::getCompositeScore).reversed());
|
||||
if (!prices.isEmpty()) {
|
||||
BizComparisonVO.SupplierPrice best = prices.get(0);
|
||||
best.setRankBadge("综合最优");
|
||||
}
|
||||
// 价格最低标记
|
||||
prices.stream().filter(BizComparisonVO.SupplierPrice::isLowestPrice)
|
||||
.findFirst().ifPresent(p -> {
|
||||
if (p.getRankBadge() == null) p.setRankBadge("价格最低");
|
||||
});
|
||||
// 交期最快标记
|
||||
prices.stream().min(Comparator.comparingInt(p -> p.getDeliveryDays() == null ? 999 : p.getDeliveryDays()))
|
||||
.ifPresent(p -> {
|
||||
if (p.getRankBadge() == null) p.setRankBadge("交期最快");
|
||||
});
|
||||
}
|
||||
return new ArrayList<>(itemMap.values());
|
||||
|
||||
List<BizComparisonVO> items = new ArrayList<>(itemMap.values());
|
||||
|
||||
// ── Step 3: 聚类 — 每个物料选最优供应商,按供应商合并为采购方案 ────────
|
||||
// planMap: supplierId -> SupplierPlan
|
||||
Map<Long, BizComparisonVO.SupplierPlan> planMap = new LinkedHashMap<>();
|
||||
|
||||
for (BizComparisonVO vo : items) {
|
||||
if (vo.getPrices() == null || vo.getPrices().isEmpty()) continue;
|
||||
// 选综合分最高的供应商
|
||||
BizComparisonVO.SupplierPrice best = vo.getPrices().get(0); // already sorted desc
|
||||
if (best.getSupplierId() == null) continue;
|
||||
|
||||
BizComparisonVO.SupplierPlan plan = planMap.computeIfAbsent(best.getSupplierId(), k -> {
|
||||
BizComparisonVO.SupplierPlan p = new BizComparisonVO.SupplierPlan();
|
||||
p.setSupplierId(best.getSupplierId());
|
||||
p.setSupplierName(best.getSupplierName());
|
||||
p.setItems(new ArrayList<>());
|
||||
p.setTotalAmount(BigDecimal.ZERO);
|
||||
return p;
|
||||
});
|
||||
|
||||
BizComparisonVO.SupplierPlan.PlanItem pi = new BizComparisonVO.SupplierPlan.PlanItem();
|
||||
pi.setMaterialName(vo.getMaterialName());
|
||||
pi.setSpec(vo.getSpec());
|
||||
pi.setUnit(vo.getUnit());
|
||||
pi.setQuantity(vo.getQuantity());
|
||||
pi.setUnitPrice(best.getUnitPrice());
|
||||
pi.setDeliveryDays(best.getDeliveryDays());
|
||||
pi.setCompositeScore(best.getCompositeScore());
|
||||
pi.setRankBadge(best.getRankBadge());
|
||||
|
||||
BigDecimal lineTotal = best.getTotalPrice() != null ? best.getTotalPrice()
|
||||
: (vo.getQuantity() != null && best.getUnitPrice() != null
|
||||
? vo.getQuantity().multiply(best.getUnitPrice()).setScale(2, RoundingMode.HALF_UP)
|
||||
: BigDecimal.ZERO);
|
||||
pi.setTotalPrice(lineTotal);
|
||||
|
||||
plan.getItems().add(pi);
|
||||
plan.setTotalAmount(plan.getTotalAmount().add(lineTotal));
|
||||
}
|
||||
|
||||
// 计算每个方案的平均综合分 + 聚类原因
|
||||
for (BizComparisonVO.SupplierPlan plan : planMap.values()) {
|
||||
double avg = plan.getItems().stream()
|
||||
.mapToDouble(BizComparisonVO.SupplierPlan.PlanItem::getCompositeScore)
|
||||
.average().orElse(0);
|
||||
plan.setAvgCompositeScore(round2(avg));
|
||||
|
||||
long priceWins = plan.getItems().stream()
|
||||
.filter(i -> "价格最低".equals(i.getRankBadge()) || "综合最优".equals(i.getRankBadge())).count();
|
||||
String reason;
|
||||
if (avg >= 80) reason = "综合评分优秀,推荐优先采购";
|
||||
else if (avg >= 65) reason = "综合评分良好,性价比较优";
|
||||
else reason = "部分物料为唯一报价供应商";
|
||||
if (priceWins > 0) reason += "," + priceWins + "项价格具有竞争力";
|
||||
plan.setClusterReason(reason);
|
||||
}
|
||||
|
||||
List<BizComparisonVO.SupplierPlan> plans = new ArrayList<>(planMap.values());
|
||||
plans.sort(Comparator.comparingDouble(BizComparisonVO.SupplierPlan::getAvgCompositeScore).reversed());
|
||||
|
||||
// ── 组装最终结果 ────────────────────────────────────────────────────
|
||||
BizComparisonVO.CompareResult result = new BizComparisonVO.CompareResult();
|
||||
result.setItems(items);
|
||||
result.setRecommendedPlans(plans);
|
||||
if (rfqInfo != null) {
|
||||
result.setRfqNo(str(rfqInfo.get("rfqNo")));
|
||||
result.setRfqTitle(str(rfqInfo.get("rfqTitle")));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ── Utilities ─────────────────────────────────────────────────────────────
|
||||
private Long toLong(Object v) { return v == null ? null : ((Number)v).longValue(); }
|
||||
private BigDecimal toBD(Object v) { return v == null ? null : new BigDecimal(v.toString()); }
|
||||
private String str(Object v) { return v == null ? "" : v.toString(); }
|
||||
private double toDouble(Object v) { return v == null ? 3.0 : ((Number)v).doubleValue(); }
|
||||
private double round2(double v) { return Math.round(v * 100) / 100.0; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user