feat: 路由跳转重构 + 甲方报价单 + 物料分类 + 比价选择方案 + 规格型号拆分

- 比价页改为列表→路由跳转到 comparison/detail,支持勾选物料行生成采购方案PDF
- 新增甲方报价单模块(clientquote):列表+详情路由,含成本价/报价/毛利率,导出PDF
- 新增物料分类管理(category):树形结构,CRUD,物料页面关联分类筛选
- BizMaterial 拆分 spec(规格) + modelNo(型号) 两个字段
- Logo 修复:新PNG + 内联样式确保完整显示
- sys_menu 新增 2012(物料分类)、2013(甲方报价单)、2014(报价单详情)、2015(比价详情)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-23 13:49:11 +08:00
parent 608ee0ed61
commit 54a421aa36
25 changed files with 1614 additions and 502 deletions

View File

@@ -0,0 +1,57 @@
package com.ruoyi.system.domain.bid;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
public class BizClientQuote {
private Long quoteId;
private Long tenantId;
private String quoteNo;
private String clientName;
private Long rfqId;
private String rfqNo;
private String rfqTitle;
private String status;
private Date validityDate;
private BigDecimal totalAmount;
private String currency;
private String remark;
private String createBy;
private Date createTime;
private Date updateTime;
private List<BizClientQuoteItem> items;
public Long getQuoteId(){return quoteId;}
public void setQuoteId(Long v){quoteId=v;}
public Long getTenantId(){return tenantId;}
public void setTenantId(Long v){tenantId=v;}
public String getQuoteNo(){return quoteNo;}
public void setQuoteNo(String v){quoteNo=v;}
public String getClientName(){return clientName;}
public void setClientName(String v){clientName=v;}
public Long getRfqId(){return rfqId;}
public void setRfqId(Long v){rfqId=v;}
public String getRfqNo(){return rfqNo;}
public void setRfqNo(String v){rfqNo=v;}
public String getRfqTitle(){return rfqTitle;}
public void setRfqTitle(String v){rfqTitle=v;}
public String getStatus(){return status;}
public void setStatus(String v){status=v;}
public Date getValidityDate(){return validityDate;}
public void setValidityDate(Date v){validityDate=v;}
public BigDecimal getTotalAmount(){return totalAmount;}
public void setTotalAmount(BigDecimal v){totalAmount=v;}
public String getCurrency(){return currency;}
public void setCurrency(String v){currency=v;}
public String getRemark(){return remark;}
public void setRemark(String v){remark=v;}
public String getCreateBy(){return createBy;}
public void setCreateBy(String v){createBy=v;}
public Date getCreateTime(){return createTime;}
public void setCreateTime(Date v){createTime=v;}
public Date getUpdateTime(){return updateTime;}
public void setUpdateTime(Date v){updateTime=v;}
public List<BizClientQuoteItem> getItems(){return items;}
public void setItems(List<BizClientQuoteItem> v){items=v;}
}

View File

@@ -0,0 +1,43 @@
package com.ruoyi.system.domain.bid;
import java.math.BigDecimal;
public class BizClientQuoteItem {
private Long itemId;
private Long quoteId;
private String materialName;
private String spec;
private String modelNo;
private String unit;
private BigDecimal quantity;
private BigDecimal costPrice;
private BigDecimal unitPrice;
private BigDecimal totalPrice;
private Integer deliveryDays;
private String remark;
public Long getItemId(){return itemId;}
public void setItemId(Long v){itemId=v;}
public Long getQuoteId(){return quoteId;}
public void setQuoteId(Long v){quoteId=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 getModelNo(){return modelNo;}
public void setModelNo(String v){modelNo=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 getCostPrice(){return costPrice;}
public void setCostPrice(BigDecimal v){costPrice=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 String getRemark(){return remark;}
public void setRemark(String v){remark=v;}
}

View File

@@ -9,6 +9,7 @@ public class BizMaterial extends BaseEntity {
private String materialCode;
private String materialName;
private String spec;
private String modelNo;
private String unit;
private String brand;
private String description;
@@ -28,6 +29,8 @@ public class BizMaterial extends BaseEntity {
public void setMaterialName(String materialName) { this.materialName = materialName; }
public String getSpec() { return spec; }
public void setSpec(String spec) { this.spec = spec; }
public String getModelNo() { return modelNo; }
public void setModelNo(String modelNo) { this.modelNo = modelNo; }
public String getUnit() { return unit; }
public void setUnit(String unit) { this.unit = unit; }
public String getBrand() { return brand; }

View File

@@ -0,0 +1,38 @@
package com.ruoyi.system.domain.bid;
import java.util.Date;
import java.util.List;
public class BizMaterialCategory {
private Long categoryId;
private Long tenantId;
private String categoryName;
private Long parentId;
private String ancestors;
private Integer sort;
private String status;
private String createBy;
private Date createTime;
private List<BizMaterialCategory> children;
public Long getCategoryId(){return categoryId;}
public void setCategoryId(Long v){categoryId=v;}
public Long getTenantId(){return tenantId;}
public void setTenantId(Long v){tenantId=v;}
public String getCategoryName(){return categoryName;}
public void setCategoryName(String v){categoryName=v;}
public Long getParentId(){return parentId;}
public void setParentId(Long v){parentId=v;}
public String getAncestors(){return ancestors;}
public void setAncestors(String v){ancestors=v;}
public Integer getSort(){return sort;}
public void setSort(Integer v){sort=v;}
public String getStatus(){return status;}
public void setStatus(String v){status=v;}
public String getCreateBy(){return createBy;}
public void setCreateBy(String v){createBy=v;}
public Date getCreateTime(){return createTime;}
public void setCreateTime(Date v){createTime=v;}
public List<BizMaterialCategory> getChildren(){return children;}
public void setChildren(List<BizMaterialCategory> v){children=v;}
}

View File

@@ -0,0 +1,18 @@
package com.ruoyi.system.mapper.bid;
import com.ruoyi.system.domain.bid.BizClientQuote;
import com.ruoyi.system.domain.bid.BizClientQuoteItem;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface BizClientQuoteMapper {
List<BizClientQuote> selectClientQuoteList(@Param("q") BizClientQuote query);
BizClientQuote selectClientQuoteById(@Param("quoteId") Long quoteId);
List<BizClientQuoteItem> selectItemsByQuoteId(@Param("quoteId") Long quoteId);
int insertClientQuote(BizClientQuote quote);
int insertClientQuoteItem(BizClientQuoteItem item);
int updateClientQuote(BizClientQuote quote);
int deleteClientQuoteById(@Param("quoteId") Long quoteId);
int deleteItemsByQuoteId(@Param("quoteId") Long quoteId);
String selectNextQuoteNo();
}

View File

@@ -0,0 +1,13 @@
package com.ruoyi.system.mapper.bid;
import com.ruoyi.system.domain.bid.BizMaterialCategory;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface BizMaterialCategoryMapper {
List<BizMaterialCategory> selectCategoryList();
BizMaterialCategory selectCategoryById(@Param("categoryId") Long categoryId);
int insertCategory(BizMaterialCategory category);
int updateCategory(BizMaterialCategory category);
int deleteCategory(@Param("categoryId") Long categoryId);
}

View File

@@ -0,0 +1,12 @@
package com.ruoyi.system.service.bid;
import com.ruoyi.system.domain.bid.BizClientQuote;
import java.util.List;
public interface IBizClientQuoteService {
List<BizClientQuote> selectClientQuoteList(BizClientQuote query);
BizClientQuote selectClientQuoteById(Long quoteId);
int insertClientQuote(BizClientQuote quote);
int updateClientQuote(BizClientQuote quote);
int deleteClientQuoteById(Long quoteId);
}

View File

@@ -0,0 +1,11 @@
package com.ruoyi.system.service.bid;
import com.ruoyi.system.domain.bid.BizMaterialCategory;
import java.util.List;
public interface IBizMaterialCategoryService {
List<BizMaterialCategory> selectCategoryList();
int insertCategory(BizMaterialCategory category);
int updateCategory(BizMaterialCategory category);
int deleteCategory(Long categoryId);
}

View File

@@ -0,0 +1,88 @@
package com.ruoyi.system.service.bid.impl;
import com.ruoyi.system.domain.bid.BizClientQuote;
import com.ruoyi.system.domain.bid.BizClientQuoteItem;
import com.ruoyi.system.mapper.bid.BizClientQuoteMapper;
import com.ruoyi.system.service.bid.IBizClientQuoteService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
@Service
public class BizClientQuoteServiceImpl implements IBizClientQuoteService {
@Autowired private BizClientQuoteMapper mapper;
@Override
public List<BizClientQuote> selectClientQuoteList(BizClientQuote query) {
return mapper.selectClientQuoteList(query);
}
@Override
public BizClientQuote selectClientQuoteById(Long quoteId) {
BizClientQuote q = mapper.selectClientQuoteById(quoteId);
if (q != null) q.setItems(mapper.selectItemsByQuoteId(quoteId));
return q;
}
@Override
@Transactional
public int insertClientQuote(BizClientQuote quote) {
if (quote.getQuoteNo() == null || quote.getQuoteNo().isBlank()) {
quote.setQuoteNo(mapper.selectNextQuoteNo());
}
if (quote.getStatus() == null) quote.setStatus("draft");
if (quote.getCurrency() == null) quote.setCurrency("CNY");
// Calculate total
if (quote.getItems() != null) {
BigDecimal total = BigDecimal.ZERO;
for (BizClientQuoteItem item : quote.getItems()) {
if (item.getUnitPrice() != null && item.getQuantity() != null) {
BigDecimal line = item.getUnitPrice().multiply(item.getQuantity()).setScale(2, RoundingMode.HALF_UP);
item.setTotalPrice(line);
total = total.add(line);
}
}
quote.setTotalAmount(total);
}
int rows = mapper.insertClientQuote(quote);
if (quote.getItems() != null) {
for (BizClientQuoteItem item : quote.getItems()) {
item.setQuoteId(quote.getQuoteId());
mapper.insertClientQuoteItem(item);
}
}
return rows;
}
@Override
@Transactional
public int updateClientQuote(BizClientQuote quote) {
if (quote.getItems() != null) {
BigDecimal total = BigDecimal.ZERO;
for (BizClientQuoteItem item : quote.getItems()) {
if (item.getUnitPrice() != null && item.getQuantity() != null) {
BigDecimal line = item.getUnitPrice().multiply(item.getQuantity()).setScale(2, RoundingMode.HALF_UP);
item.setTotalPrice(line);
total = total.add(line);
}
}
quote.setTotalAmount(total);
mapper.deleteItemsByQuoteId(quote.getQuoteId());
for (BizClientQuoteItem item : quote.getItems()) {
item.setQuoteId(quote.getQuoteId());
mapper.insertClientQuoteItem(item);
}
}
return mapper.updateClientQuote(quote);
}
@Override
@Transactional
public int deleteClientQuoteById(Long quoteId) {
mapper.deleteItemsByQuoteId(quoteId);
return mapper.deleteClientQuoteById(quoteId);
}
}

View File

@@ -0,0 +1,58 @@
package com.ruoyi.system.service.bid.impl;
import com.ruoyi.system.domain.bid.BizMaterialCategory;
import com.ruoyi.system.mapper.bid.BizMaterialCategoryMapper;
import com.ruoyi.system.service.bid.IBizMaterialCategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
@Service
public class BizMaterialCategoryServiceImpl implements IBizMaterialCategoryService {
@Autowired private BizMaterialCategoryMapper mapper;
@Override
public List<BizMaterialCategory> selectCategoryList() {
List<BizMaterialCategory> all = mapper.selectCategoryList();
return buildTree(all);
}
@Override
public int insertCategory(BizMaterialCategory cat) {
if (cat.getParentId() == null) cat.setParentId(0L);
if (cat.getSort() == null) cat.setSort(0);
if (cat.getStatus() == null) cat.setStatus("0");
// Build ancestors string
if (cat.getParentId() == 0L) {
cat.setAncestors("0");
} else {
BizMaterialCategory parent = mapper.selectCategoryById(cat.getParentId());
cat.setAncestors(parent == null ? "0" : parent.getAncestors() + "," + cat.getParentId());
}
return mapper.insertCategory(cat);
}
@Override
public int updateCategory(BizMaterialCategory cat) { return mapper.updateCategory(cat); }
@Override
public int deleteCategory(Long categoryId) { return mapper.deleteCategory(categoryId); }
private List<BizMaterialCategory> buildTree(List<BizMaterialCategory> all) {
Map<Long, BizMaterialCategory> map = new LinkedHashMap<>();
for (BizMaterialCategory c : all) map.put(c.getCategoryId(), c);
List<BizMaterialCategory> roots = new ArrayList<>();
for (BizMaterialCategory c : all) {
Long pid = c.getParentId() == null ? 0L : c.getParentId();
if (pid == 0L) { roots.add(c); }
else {
BizMaterialCategory parent = map.get(pid);
if (parent != null) {
if (parent.getChildren() == null) parent.setChildren(new ArrayList<>());
parent.getChildren().add(c);
} else roots.add(c);
}
}
return roots;
}
}