feat(bid): 完成甲方报价模块全量功能开发
1. 新增甲方报价业务实体,继承基础实体类 2. 新增供应商报价明细查询接口,支持按供应商ID展开物料明细 3. 重构甲方报价关联逻辑,通过material_id精确关联物料表 4. 新增甲方报价历史统计、月度趋势、快速新建等服务功能 5. 完善菜单配置,修正甲方报价菜单结构,添加完整权限控制 6. 新增物料搜索自动补全功能,优化报价单详情页面 7. 在供应商详情页新增报价历史Tab页签,展示该供应商的所有报价物料明细 8. 在物料详情页新增甲方报价记录Tab页签,展示该物料的所有甲方报价历史 9. 新增数据库优化脚本,添加索引并修复历史数据关联
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
package com.ruoyi.system.domain.bid;
|
||||
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class BizClientQuote {
|
||||
public class BizClientQuote extends BaseEntity {
|
||||
private Long quoteId;
|
||||
private Long tenantId;
|
||||
private String quoteNo;
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.math.BigDecimal;
|
||||
public class BizClientQuoteItem {
|
||||
private Long itemId;
|
||||
private Long quoteId;
|
||||
private Long materialId;
|
||||
private String materialName;
|
||||
private String spec;
|
||||
private String modelNo;
|
||||
@@ -20,6 +21,8 @@ public class BizClientQuoteItem {
|
||||
public void setItemId(Long v){itemId=v;}
|
||||
public Long getQuoteId(){return quoteId;}
|
||||
public void setQuoteId(Long v){quoteId=v;}
|
||||
public Long getMaterialId(){return materialId;}
|
||||
public void setMaterialId(Long v){materialId=v;}
|
||||
public String getMaterialName(){return materialName;}
|
||||
public void setMaterialName(String v){materialName=v;}
|
||||
public String getSpec(){return spec;}
|
||||
|
||||
@@ -4,8 +4,10 @@ import com.ruoyi.system.domain.bid.BizClientQuote;
|
||||
import com.ruoyi.system.domain.bid.BizClientQuoteItem;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface BizClientQuoteMapper {
|
||||
// ========== CRUD ==========
|
||||
List<BizClientQuote> selectClientQuoteList(@Param("q") BizClientQuote query);
|
||||
BizClientQuote selectClientQuoteById(@Param("quoteId") Long quoteId);
|
||||
List<BizClientQuoteItem> selectItemsByQuoteId(@Param("quoteId") Long quoteId);
|
||||
@@ -15,4 +17,14 @@ public interface BizClientQuoteMapper {
|
||||
int deleteClientQuoteById(@Param("quoteId") Long quoteId);
|
||||
int deleteItemsByQuoteId(@Param("quoteId") Long quoteId);
|
||||
String selectNextQuoteNo();
|
||||
|
||||
// ========== 甲方报价历史 - 统计 ==========
|
||||
Map<String, Object> selectClientQuoteStatistics(@Param("q") BizClientQuote query);
|
||||
List<Map<String, Object>> selectMonthlyTrend();
|
||||
|
||||
// ========== 按物料ID关联查询 ==========
|
||||
List<Map<String, Object>> selectClientQuotesByMaterialId(@Param("materialId") Long materialId);
|
||||
|
||||
// ========== 快速复制 ==========
|
||||
List<BizClientQuoteItem> selectItemsByQuoteIdForCopy(@Param("quoteId") Long quoteId);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package com.ruoyi.system.mapper.bid;
|
||||
|
||||
import com.ruoyi.system.domain.bid.BizQuotationItem;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface BizQuotationItemMapper {
|
||||
List<BizQuotationItem> selectItemsByQuotationId(Long quotationId);
|
||||
List<Map<String, Object>> selectItemsBySupplierId(@Param("supplierId") Long supplierId);
|
||||
int insertBizQuotationItem(BizQuotationItem item);
|
||||
int deleteByQuotationId(Long quotationId);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.ruoyi.system.service.bid;
|
||||
|
||||
import com.ruoyi.system.domain.bid.BizClientQuote;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface IBizClientQuoteService {
|
||||
List<BizClientQuote> selectClientQuoteList(BizClientQuote query);
|
||||
@@ -9,4 +10,14 @@ public interface IBizClientQuoteService {
|
||||
int insertClientQuote(BizClientQuote quote);
|
||||
int updateClientQuote(BizClientQuote quote);
|
||||
int deleteClientQuoteById(Long quoteId);
|
||||
|
||||
// 甲方报价历史 - 统计
|
||||
Map<String, Object> selectClientQuoteStatistics(BizClientQuote query);
|
||||
List<Map<String, Object>> selectMonthlyTrend();
|
||||
|
||||
// 按物料ID查询报价历史
|
||||
List<Map<String, Object>> selectClientQuotesByMaterialId(Long materialId);
|
||||
|
||||
// 基于历史报价快速新建
|
||||
BizClientQuote copyFromExisting(Long quoteId);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class BizClientQuoteServiceImpl implements IBizClientQuoteService {
|
||||
@@ -85,4 +86,70 @@ public class BizClientQuoteServiceImpl implements IBizClientQuoteService {
|
||||
mapper.deleteItemsByQuoteId(quoteId);
|
||||
return mapper.deleteClientQuoteById(quoteId);
|
||||
}
|
||||
|
||||
// ========== 甲方报价历史 - 统计 ==========
|
||||
|
||||
@Override
|
||||
public Map<String, Object> selectClientQuoteStatistics(BizClientQuote query) {
|
||||
return mapper.selectClientQuoteStatistics(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> selectMonthlyTrend() {
|
||||
return mapper.selectMonthlyTrend();
|
||||
}
|
||||
|
||||
// ========== 按物料ID查询 ==========
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> selectClientQuotesByMaterialId(Long materialId) {
|
||||
return mapper.selectClientQuotesByMaterialId(materialId);
|
||||
}
|
||||
|
||||
// ========== 基于历史报价快速新建 ==========
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public BizClientQuote copyFromExisting(Long quoteId) {
|
||||
BizClientQuote existing = mapper.selectClientQuoteById(quoteId);
|
||||
if (existing == null) {
|
||||
throw new RuntimeException("报价单不存在: " + quoteId);
|
||||
}
|
||||
// 创建新的报价单(复制主表信息,重置状态和金额)
|
||||
BizClientQuote newQuote = new BizClientQuote();
|
||||
newQuote.setQuoteNo(mapper.selectNextQuoteNo());
|
||||
newQuote.setClientName(existing.getClientName());
|
||||
newQuote.setRfqId(existing.getRfqId());
|
||||
newQuote.setRfqNo(existing.getRfqNo());
|
||||
newQuote.setRfqTitle(existing.getRfqTitle());
|
||||
newQuote.setStatus("draft");
|
||||
newQuote.setValidityDate(existing.getValidityDate());
|
||||
newQuote.setCurrency(existing.getCurrency());
|
||||
newQuote.setRemark(existing.getRemark());
|
||||
newQuote.setTotalAmount(BigDecimal.ZERO);
|
||||
|
||||
int rows = mapper.insertClientQuote(newQuote);
|
||||
|
||||
// 复制明细
|
||||
List<BizClientQuoteItem> items = mapper.selectItemsByQuoteId(quoteId);
|
||||
if (items != null && !items.isEmpty()) {
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
for (BizClientQuoteItem item : items) {
|
||||
// 重置为新行
|
||||
item.setItemId(null);
|
||||
item.setQuoteId(newQuote.getQuoteId());
|
||||
// 保留原物料信息和价格,方便用户修改
|
||||
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);
|
||||
}
|
||||
mapper.insertClientQuoteItem(item);
|
||||
}
|
||||
newQuote.setTotalAmount(total);
|
||||
mapper.updateClientQuote(newQuote);
|
||||
}
|
||||
newQuote.setItems(items);
|
||||
return newQuote;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,31 +3,48 @@
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.system.mapper.bid.BizClientQuoteMapper">
|
||||
|
||||
<!-- ========== 甲方报价主表操作 ========== -->
|
||||
|
||||
<sql id="quoteColumns">
|
||||
quote_id AS quoteId, tenant_id AS tenantId, quote_no AS quoteNo,
|
||||
client_name AS clientName, rfq_id AS rfqId, rfq_no AS rfqNo, rfq_title AS rfqTitle,
|
||||
status, validity_date AS validityDate, total_amount AS totalAmount,
|
||||
currency, remark, create_by AS createBy, create_time AS createTime
|
||||
</sql>
|
||||
|
||||
<select id="selectClientQuoteList" resultType="com.ruoyi.system.domain.bid.BizClientQuote">
|
||||
SELECT quote_id AS quoteId, tenant_id AS tenantId, quote_no AS quoteNo,
|
||||
client_name AS clientName, rfq_id AS rfqId, rfq_no AS rfqNo, rfq_title AS rfqTitle,
|
||||
status, validity_date AS validityDate, total_amount AS totalAmount,
|
||||
currency, remark, create_by AS createBy, create_time AS createTime
|
||||
SELECT <include refid="quoteColumns"/>
|
||||
FROM biz_client_quote
|
||||
<where>
|
||||
<if test="q.clientName != null and q.clientName != ''">AND client_name LIKE CONCAT('%',#{q.clientName},'%')</if>
|
||||
<if test="q.status != null and q.status != ''">AND status = #{q.status}</if>
|
||||
<if test="q.quoteNo != null and q.quoteNo != ''">AND quote_no LIKE CONCAT('%',#{q.quoteNo},'%')</if>
|
||||
<if test="q.params.beginTime != null and q.params.beginTime != ''">
|
||||
AND create_time >= #{q.params.beginTime}
|
||||
</if>
|
||||
<if test="q.params.endTime != null and q.params.endTime != ''">
|
||||
AND create_time <= #{q.params.endTime}
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY create_time DESC
|
||||
</select>
|
||||
|
||||
<select id="selectClientQuoteById" resultType="com.ruoyi.system.domain.bid.BizClientQuote">
|
||||
SELECT quote_id AS quoteId, tenant_id AS tenantId, quote_no AS quoteNo,
|
||||
client_name AS clientName, rfq_id AS rfqId, rfq_no AS rfqNo, rfq_title AS rfqTitle,
|
||||
status, validity_date AS validityDate, total_amount AS totalAmount,
|
||||
currency, remark, create_by AS createBy, create_time AS createTime
|
||||
SELECT <include refid="quoteColumns"/>
|
||||
FROM biz_client_quote WHERE quote_id = #{quoteId}
|
||||
</select>
|
||||
|
||||
<!-- ========== 甲方报价明细操作 ========== -->
|
||||
|
||||
<sql id="itemColumns">
|
||||
item_id AS itemId, quote_id AS quoteId, material_id AS materialId,
|
||||
material_name AS materialName, spec, model_no AS modelNo, unit, quantity,
|
||||
cost_price AS costPrice, unit_price AS unitPrice, total_price AS totalPrice,
|
||||
delivery_days AS deliveryDays, remark
|
||||
</sql>
|
||||
|
||||
<select id="selectItemsByQuoteId" resultType="com.ruoyi.system.domain.bid.BizClientQuoteItem">
|
||||
SELECT item_id AS itemId, quote_id AS quoteId, material_name AS materialName,
|
||||
spec, model_no AS modelNo, unit, quantity, cost_price AS costPrice,
|
||||
unit_price AS unitPrice, total_price AS totalPrice, delivery_days AS deliveryDays, remark
|
||||
SELECT <include refid="itemColumns"/>
|
||||
FROM biz_client_quote_item WHERE quote_id = #{quoteId} ORDER BY item_id
|
||||
</select>
|
||||
|
||||
@@ -44,9 +61,9 @@
|
||||
</insert>
|
||||
|
||||
<insert id="insertClientQuoteItem" useGeneratedKeys="true" keyProperty="itemId">
|
||||
INSERT INTO biz_client_quote_item (quote_id,material_name,spec,model_no,unit,quantity,
|
||||
INSERT INTO biz_client_quote_item (quote_id,material_id,material_name,spec,model_no,unit,quantity,
|
||||
cost_price,unit_price,total_price,delivery_days,remark)
|
||||
VALUES (#{quoteId},#{materialName},#{spec},#{modelNo},#{unit},#{quantity},
|
||||
VALUES (#{quoteId},#{materialId},#{materialName},#{spec},#{modelNo},#{unit},#{quantity},
|
||||
#{costPrice},#{unitPrice},#{totalPrice},#{deliveryDays},#{remark})
|
||||
</insert>
|
||||
|
||||
@@ -59,4 +76,63 @@
|
||||
|
||||
<delete id="deleteClientQuoteById">DELETE FROM biz_client_quote WHERE quote_id=#{quoteId}</delete>
|
||||
<delete id="deleteItemsByQuoteId">DELETE FROM biz_client_quote_item WHERE quote_id=#{quoteId}</delete>
|
||||
|
||||
<!-- ========== 甲方报价历史 - 统计查询 ========== -->
|
||||
|
||||
<select id="selectClientQuoteStatistics" resultType="java.util.Map">
|
||||
SELECT
|
||||
COUNT(*) AS total_count,
|
||||
COUNT(DISTINCT client_name) AS client_count,
|
||||
COALESCE(SUM(total_amount), 0) AS total_amount_sum,
|
||||
MAX(total_amount) AS max_amount,
|
||||
AVG(total_amount) AS avg_amount
|
||||
FROM biz_client_quote
|
||||
<where>
|
||||
<if test="q.clientName != null and q.clientName != ''">AND client_name LIKE CONCAT('%',#{q.clientName},'%')</if>
|
||||
<if test="q.status != null and q.status != ''">AND status = #{q.status}</if>
|
||||
<if test="q.params.beginTime != null and q.params.beginTime != ''">
|
||||
AND create_time >= #{q.params.beginTime}
|
||||
</if>
|
||||
<if test="q.params.endTime != null and q.params.endTime != ''">
|
||||
AND create_time <= #{q.params.endTime}
|
||||
</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
<!-- ========== 按物料ID查询甲方报价历史(使用material_id精确关联) ========== -->
|
||||
|
||||
<select id="selectClientQuotesByMaterialId" resultType="java.util.Map">
|
||||
SELECT cqi.item_id AS itemId, cqi.spec, cqi.model_no AS modelNo, cqi.unit, cqi.quantity,
|
||||
cqi.cost_price AS costPrice, cqi.unit_price AS unitPrice, cqi.total_price AS totalPrice,
|
||||
cqi.delivery_days AS deliveryDays, cqi.material_id AS materialId,
|
||||
cqi.material_name AS materialName,
|
||||
cq.quote_id AS quoteId, cq.quote_no AS quoteNo, cq.client_name AS clientName,
|
||||
cq.status AS quoteStatus, cq.create_time AS createTime
|
||||
FROM biz_client_quote_item cqi
|
||||
JOIN biz_client_quote cq ON cqi.quote_id = cq.quote_id
|
||||
WHERE cqi.material_id = #{materialId}
|
||||
ORDER BY cq.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- ========== 快速创建 - 复制历史报价单的明细(按报价单ID) ========== -->
|
||||
|
||||
<select id="selectItemsByQuoteIdForCopy" resultType="com.ruoyi.system.domain.bid.BizClientQuoteItem">
|
||||
SELECT <include refid="itemColumns"/>
|
||||
FROM biz_client_quote_item WHERE quote_id = #{quoteId}
|
||||
ORDER BY item_id
|
||||
</select>
|
||||
|
||||
<!-- ========== 甲方报价历史 - 按日期统计趋势 ========== -->
|
||||
|
||||
<select id="selectMonthlyTrend" resultType="java.util.Map">
|
||||
SELECT
|
||||
DATE_FORMAT(create_time, '%Y-%m') AS month,
|
||||
COUNT(*) AS quote_count,
|
||||
COALESCE(SUM(total_amount), 0) AS amount_sum
|
||||
FROM biz_client_quote
|
||||
WHERE create_time >= DATE_SUB(NOW(), INTERVAL 12 MONTH)
|
||||
GROUP BY DATE_FORMAT(create_time, '%Y-%m')
|
||||
ORDER BY month
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -92,14 +92,14 @@
|
||||
ORDER BY q.submit_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 甲方报价历史:通过物料名称关联 -->
|
||||
<!-- 甲方报价历史:通过material_id精确关联 -->
|
||||
<select id="selectClientQuotesByMaterialId" resultType="java.util.Map">
|
||||
SELECT cqi.item_id, cqi.spec, cqi.model_no, cqi.unit, cqi.quantity,
|
||||
cqi.cost_price, cqi.unit_price, cqi.total_price, cqi.delivery_days,
|
||||
cq.quote_id, cq.quote_no, cq.client_name, cq.status AS quote_status, cq.create_time
|
||||
FROM biz_client_quote_item cqi
|
||||
JOIN biz_client_quote cq ON cqi.quote_id = cq.quote_id
|
||||
WHERE cqi.material_name = (SELECT material_name FROM biz_material WHERE material_id = #{materialId})
|
||||
WHERE cqi.material_id = #{materialId}
|
||||
ORDER BY cq.create_time DESC
|
||||
</select>
|
||||
|
||||
|
||||
@@ -19,6 +19,23 @@
|
||||
SELECT * FROM biz_quotation_item WHERE quotation_id=#{quotationId}
|
||||
</select>
|
||||
|
||||
<!-- 按供应商ID查询报价明细(展开为每行一条物料) -->
|
||||
<select id="selectItemsBySupplierId" resultType="java.util.Map">
|
||||
SELECT qi.item_id AS itemId, qi.quotation_id AS quotationId,
|
||||
qi.material_name AS materialName, qi.spec, qi.unit,
|
||||
qi.quantity, qi.unit_price AS unitPrice, qi.total_price AS totalPrice,
|
||||
qi.delivery_days AS deliveryDays,
|
||||
ri.material_id AS materialId,
|
||||
q.quote_no AS quoteNo, q.submit_time AS submitTime,
|
||||
q.status AS quoteStatus, q.total_amount AS totalAmount,
|
||||
q.valid_days AS validDays
|
||||
FROM biz_quotation_item qi
|
||||
JOIN biz_quotation q ON qi.quotation_id = q.quotation_id
|
||||
LEFT JOIN biz_rfq_item ri ON qi.rfq_item_id = ri.item_id
|
||||
WHERE q.supplier_id = #{supplierId}
|
||||
ORDER BY q.submit_time DESC, qi.item_id
|
||||
</select>
|
||||
|
||||
<insert id="insertBizQuotationItem" useGeneratedKeys="true" keyProperty="itemId">
|
||||
INSERT INTO biz_quotation_item(quotation_id,rfq_item_id,material_name,spec,unit,quantity,unit_price,total_price,delivery_days,remark)
|
||||
VALUES(#{quotationId},#{rfqItemId},#{materialName},#{spec},#{unit},#{quantity},#{unitPrice},#{totalPrice},#{deliveryDays},#{remark})
|
||||
|
||||
Reference in New Issue
Block a user