feat(bid): 新增基于甲方报价快速创建RFQ功能

本次提交完成以下核心变更:
1. 新增RFQ编号自动生成逻辑,添加selectNextRfqNo方法获取月度递增的RFQ编号
2. 在biz_rfq表新增client_quote_id关联字段,添加索引并完善实体类映射
3. 实现基于甲方报价复制物料快速创建RFQ的业务逻辑,包括事务处理和明细复制
4. 新增RFQ列表页关联甲方报价展示,支持点击跳转查看甲方报价详情
5. 在RFQ编辑页新增甲方报价选择器,选中后自动填充对应物料和标题
6. 优化甲方报价单页面,新增生成RFQ按钮和已生成RFQ列表展示
7. 调整RFQ详情页,新增编辑模式支持草稿状态修改
8. 修复路由跳转路径,统一RFQ相关页面路由到/bid/rfq路径组
This commit is contained in:
2026-06-02 18:44:44 +08:00
parent a75589018f
commit 9db84336bc
12 changed files with 514 additions and 70 deletions

View File

@@ -13,6 +13,9 @@ public class BizRfq extends BaseEntity {
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date deadline;
private String deliveryAddr;
private Long clientQuoteId;
private String clientQuoteNo;
private String clientName;
private String status;
private List<BizRfqItem> items;
private List<Long> supplierIds;
@@ -29,6 +32,12 @@ public class BizRfq extends BaseEntity {
public void setDeadline(Date deadline) { this.deadline = deadline; }
public String getDeliveryAddr() { return deliveryAddr; }
public void setDeliveryAddr(String deliveryAddr) { this.deliveryAddr = deliveryAddr; }
public Long getClientQuoteId() { return clientQuoteId; }
public void setClientQuoteId(Long clientQuoteId) { this.clientQuoteId = clientQuoteId; }
public String getClientQuoteNo() { return clientQuoteNo; }
public void setClientQuoteNo(String clientQuoteNo) { this.clientQuoteNo = clientQuoteNo; }
public String getClientName() { return clientName; }
public void setClientName(String clientName) { this.clientName = clientName; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public List<BizRfqItem> getItems() { return items; }

View File

@@ -9,5 +9,6 @@ public interface BizRfqMapper {
int insertBizRfq(BizRfq record);
int updateBizRfq(BizRfq record);
int deleteBizRfqById(Long id);
String selectNextRfqNo();
int deleteBizRfqByIds(Long[] ids);
}

View File

@@ -13,4 +13,6 @@ public interface IBizRfqService {
int deleteBizRfqByIds(Long[] ids);
int publishRfq(Long rfqId, Long[] supplierIds);
List<BizRfqItem> selectItemsByRfqId(Long rfqId);
/** 基于甲方报价快速创建 RFQ复制物料明细 */
BizRfq createRfqFromClientQuote(Long clientQuoteId, Long tenantId);
}

View File

@@ -1,20 +1,24 @@
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.domain.bid.BizRfq;
import com.ruoyi.system.domain.bid.BizRfqItem;
import com.ruoyi.system.mapper.bid.BizClientQuoteMapper;
import com.ruoyi.system.mapper.bid.BizRfqItemMapper;
import com.ruoyi.system.mapper.bid.BizRfqMapper;
import com.ruoyi.system.service.bid.IBizRfqService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class BizRfqServiceImpl implements IBizRfqService {
@Autowired private BizRfqMapper rfqMapper;
@Autowired private BizRfqItemMapper itemMapper;
@Autowired private BizClientQuoteMapper clientQuoteMapper;
@Override
public List<BizRfq> selectBizRfqList(BizRfq query) { return rfqMapper.selectBizRfqList(query); }
@@ -29,6 +33,9 @@ public class BizRfqServiceImpl implements IBizRfqService {
@Override
@Transactional
public int insertBizRfq(BizRfq rfq) {
if (rfq.getRfqNo() == null || rfq.getRfqNo().isBlank()) {
rfq.setRfqNo(rfqMapper.selectNextRfqNo());
}
rfq.setStatus("draft");
int rows = rfqMapper.insertBizRfq(rfq);
if (rfq.getItems() != null) {
@@ -71,4 +78,50 @@ public class BizRfqServiceImpl implements IBizRfqService {
public List<BizRfqItem> selectItemsByRfqId(Long rfqId) {
return itemMapper.selectItemsByRfqId(rfqId);
}
@Override
@Transactional
public BizRfq createRfqFromClientQuote(Long clientQuoteId, Long tenantId) {
// 加载甲方报价
BizClientQuote quote = clientQuoteMapper.selectClientQuoteById(clientQuoteId);
if (quote == null) {
throw new RuntimeException("甲方报价单不存在: " + clientQuoteId);
}
// 创建 RFQ
BizRfq rfq = new BizRfq();
rfq.setRfqNo(rfqMapper.selectNextRfqNo());
rfq.setTenantId(tenantId);
rfq.setRfqTitle("采购需求: " + quote.getClientName() + " - " + quote.getQuoteNo());
rfq.setClientQuoteId(quote.getQuoteId());
rfq.setDeliveryAddr("");
rfq.setStatus("draft");
rfq.setRemark("源自甲方报价单: " + quote.getQuoteNo());
// 复制物料明细
List<BizClientQuoteItem> sourceItems = clientQuoteMapper.selectItemsByQuoteId(clientQuoteId);
if (sourceItems != null && !sourceItems.isEmpty()) {
rfq.setItems(sourceItems.stream().map(src -> {
BizRfqItem item = new BizRfqItem();
item.setMaterialId(src.getMaterialId());
item.setMaterialName(src.getMaterialName());
item.setSpec(src.getSpec());
item.setUnit(src.getUnit());
item.setQuantity(src.getQuantity());
item.setExpectedPrice(src.getUnitPrice());
item.setRemark(src.getRemark());
return item;
}).collect(Collectors.toList()));
}
rfqMapper.insertBizRfq(rfq);
if (rfq.getItems() != null) {
for (BizRfqItem item : rfq.getItems()) {
item.setRfqId(rfq.getRfqId());
itemMapper.insertBizRfqItem(item);
}
}
return rfqMapper.selectBizRfqById(rfq.getRfqId());
}
}

View File

@@ -9,6 +9,9 @@
<result property="rfqTitle" column="rfq_title"/>
<result property="deadline" column="deadline"/>
<result property="deliveryAddr" column="delivery_addr"/>
<result property="clientQuoteId" column="client_quote_id"/>
<result property="clientQuoteNo" column="clientQuoteNo"/>
<result property="clientName" column="clientName"/>
<result property="status" column="status"/>
<result property="remark" column="remark"/>
<result property="createBy" column="create_by"/>
@@ -17,22 +20,40 @@
<result property="updateTime" column="update_time"/>
</resultMap>
<sql id="selectRfqSql">
SELECT r.*,
cq.quote_no AS clientQuoteNo,
cq.client_name AS clientName
FROM biz_rfq r
LEFT JOIN biz_client_quote cq ON r.client_quote_id = cq.quote_id
</sql>
<select id="selectBizRfqList" resultMap="BaseRM">
SELECT * FROM biz_rfq
<include refid="selectRfqSql"/>
<where>
<if test="tenantId != null"> AND tenant_id=#{tenantId}</if>
<if test="rfqNo != null and rfqNo != ''"> AND rfq_no LIKE CONCAT('%',#{rfqNo},'%')</if>
<if test="rfqTitle != null and rfqTitle != ''"> AND rfq_title LIKE CONCAT('%',#{rfqTitle},'%')</if>
<if test="status != null and status != ''"> AND status=#{status}</if>
<if test="tenantId != null"> AND r.tenant_id=#{tenantId}</if>
<if test="rfqNo != null and rfqNo != ''"> AND r.rfq_no LIKE CONCAT('%',#{rfqNo},'%')</if>
<if test="rfqTitle != null and rfqTitle != ''"> AND r.rfq_title LIKE CONCAT('%',#{rfqTitle},'%')</if>
<if test="status != null and status != ''"> AND r.status=#{status}</if>
<if test="clientQuoteId != null"> AND r.client_quote_id=#{clientQuoteId}</if>
</where>
ORDER BY rfq_id DESC
ORDER BY r.rfq_id DESC
</select>
<select id="selectBizRfqById" resultMap="BaseRM">SELECT * FROM biz_rfq WHERE rfq_id=#{id}</select>
<select id="selectBizRfqById" resultMap="BaseRM">
<include refid="selectRfqSql"/>
WHERE r.rfq_id=#{id}
</select>
<select id="selectNextRfqNo" resultType="String">
SELECT CONCAT('RFQ-', DATE_FORMAT(NOW(),'%Y%m'), '-',
LPAD(IFNULL(MAX(CAST(SUBSTRING_INDEX(rfq_no,'-',-1) AS UNSIGNED)),0)+1,3,'0'))
FROM biz_rfq WHERE rfq_no LIKE CONCAT('RFQ-', DATE_FORMAT(NOW(),'%Y%m'), '%')
</select>
<insert id="insertBizRfq" useGeneratedKeys="true" keyProperty="rfqId">
INSERT INTO biz_rfq(tenant_id,rfq_no,rfq_title,deadline,delivery_addr,status,remark,create_by,create_time)
VALUES(#{tenantId},#{rfqNo},#{rfqTitle},#{deadline},#{deliveryAddr},#{status},#{remark},#{createBy},NOW())
INSERT INTO biz_rfq(tenant_id,rfq_no,rfq_title,deadline,delivery_addr,client_quote_id,status,remark,create_by,create_time)
VALUES(#{tenantId},#{rfqNo},#{rfqTitle},#{deadline},#{deliveryAddr},#{clientQuoteId},#{status},#{remark},#{createBy},NOW())
</insert>
<update id="updateBizRfq">
@@ -41,6 +62,7 @@
<if test="rfqTitle != null">rfq_title=#{rfqTitle},</if>
<if test="deadline != null">deadline=#{deadline},</if>
<if test="deliveryAddr != null">delivery_addr=#{deliveryAddr},</if>
<if test="clientQuoteId != null">client_quote_id=#{clientQuoteId},</if>
<if test="status != null">status=#{status},</if>
<if test="remark != null">remark=#{remark},</if>
update_by=#{updateBy}, update_time=NOW()