feat(bid): 完成批量业务优化与功能完善

1.  统一所有表格操作列样式,移除固定宽度避免布局溢出
2.  新增报价单自动编号与脏数据清理功能
3.  优化订单状态筛选与展示逻辑,新增closed状态支持
4.  完善操作日志管理,新增统计分析与详情查看功能
5.  优化报价单流程,调整提交审批逻辑与权限控制
6.  修复客户端订单查询SQL,优化关联查询逻辑
7.  新增报价单提交时自动更新提交时间的功能
This commit is contained in:
2026-06-18 20:17:02 +08:00
parent 7b71822a32
commit 41b2e3e772
31 changed files with 1146 additions and 205 deletions

View File

@@ -45,4 +45,44 @@ public interface SysOperLogMapper
* 清空操作日志
*/
public void cleanOperLog();
/**
* 查询操作日志统计信息
*
* @param operLog 操作日志对象(带筛选条件)
* @return 统计结果
*/
public java.util.Map<String, Object> selectOperLogStats(SysOperLog operLog);
/**
* 按模块统计操作日志
*
* @param operLog 操作日志对象(带筛选条件)
* @return 各模块统计列表
*/
public java.util.List<java.util.Map<String, Object>> selectModuleStats(SysOperLog operLog);
/**
* 按操作类型统计
*
* @param operLog 操作日志对象(带筛选条件)
* @return 各操作类型统计列表
*/
public java.util.List<java.util.Map<String, Object>> selectBusinessTypeStats(SysOperLog operLog);
/**
* 查询每日操作趋势
*
* @param operLog 操作日志对象(带筛选条件)
* @return 每日趋势列表
*/
public java.util.List<java.util.Map<String, Object>> selectDailyTrend(SysOperLog operLog);
/**
* 按模块统计异常日志
*
* @param operLog 操作日志对象(带筛选条件)
* @return 各模块异常统计列表
*/
public java.util.List<java.util.Map<String, Object>> selectErrorStats(SysOperLog operLog);
}

View File

@@ -10,4 +10,8 @@ public interface BizQuotationMapper {
int updateBizQuotation(BizQuotation record);
int deleteBizQuotationById(Long id);
int deleteBizQuotationByIds(Long[] ids);
/** 自动生成报价单号Q-YYYYMM-NNN */
String selectNextQuoteNo();
/** 查询所有无编号报价单ID用于清理脏数据 */
List<Long> selectIdsByQuoteNoNull();
}

View File

@@ -45,4 +45,44 @@ public interface ISysOperLogService
* 清空操作日志
*/
public void cleanOperLog();
/**
* 查询操作日志统计信息
*
* @param operLog 操作日志对象(带筛选条件)
* @return 统计结果
*/
public java.util.Map<String, Object> selectOperLogStats(SysOperLog operLog);
/**
* 按模块统计
*
* @param operLog 操作日志对象(带筛选条件)
* @return 各模块统计列表
*/
public java.util.List<java.util.Map<String, Object>> selectModuleStats(SysOperLog operLog);
/**
* 按操作类型统计
*
* @param operLog 操作日志对象(带筛选条件)
* @return 各类型统计列表
*/
public java.util.List<java.util.Map<String, Object>> selectBusinessTypeStats(SysOperLog operLog);
/**
* 查询每日操作趋势
*
* @param operLog 操作日志对象(带筛选条件)
* @return 每日趋势列表
*/
public java.util.List<java.util.Map<String, Object>> selectDailyTrend(SysOperLog operLog);
/**
* 按模块统计异常日志
*
* @param operLog 操作日志对象(带筛选条件)
* @return 各模块异常统计列表
*/
public java.util.List<java.util.Map<String, Object>> selectErrorStats(SysOperLog operLog);
}

View File

@@ -14,5 +14,7 @@ public interface IBizQuotationService {
int rejectQuotation(Long quotationId);
int deleteBizQuotationById(Long id);
int deleteBizQuotationByIds(Long[] ids);
int deleteBizQuotationByNoQuoteNo();
void updateSubmitTime(Long quotationId);
List<BizQuotationItem> selectItemsByQuotationId(Long quotationId);
}

View File

@@ -30,6 +30,12 @@ public class BizQuotationServiceImpl implements IBizQuotationService {
@Override
@Transactional
public int insertBizQuotation(BizQuotation q) {
if (q.getQuoteNo() == null || q.getQuoteNo().isBlank()) {
q.setQuoteNo(mapper.selectNextQuoteNo());
}
if (q.getStatus() == null) {
q.setStatus("draft");
}
int rows = mapper.insertBizQuotation(q);
saveItems(q);
return rows;
@@ -38,6 +44,13 @@ public class BizQuotationServiceImpl implements IBizQuotationService {
@Override
@Transactional
public int updateBizQuotation(BizQuotation q) {
// 对历史遗留的空编号记录,编辑时自动补号
if (q.getQuoteNo() == null || q.getQuoteNo().isBlank()) {
BizQuotation existing = mapper.selectBizQuotationById(q.getQuotationId());
if (existing != null && (existing.getQuoteNo() == null || existing.getQuoteNo().isBlank())) {
q.setQuoteNo(mapper.selectNextQuoteNo());
}
}
itemMapper.deleteByQuotationId(q.getQuotationId());
saveItems(q);
return mapper.updateBizQuotation(q);
@@ -48,6 +61,7 @@ public class BizQuotationServiceImpl implements IBizQuotationService {
BigDecimal total = BigDecimal.ZERO;
for (BizQuotationItem item : q.getItems()) {
item.setQuotationId(q.getQuotationId());
if (item.getRfqItemId() == null) item.setRfqItemId(0L);
if (item.getUnitPrice() != null && item.getQuantity() != null) {
item.setTotalPrice(item.getUnitPrice().multiply(item.getQuantity()));
total = total.add(item.getTotalPrice());
@@ -85,12 +99,31 @@ public class BizQuotationServiceImpl implements IBizQuotationService {
return mapper.updateBizQuotation(q);
}
@Override
public void updateSubmitTime(Long quotationId) {
BizQuotation q = new BizQuotation();
q.setQuotationId(quotationId);
q.setSubmitTime(new Date());
mapper.updateBizQuotation(q);
}
@Override
public int deleteBizQuotationById(Long id) { return mapper.deleteBizQuotationById(id); }
@Override
public int deleteBizQuotationByIds(Long[] ids) { return mapper.deleteBizQuotationByIds(ids); }
@Override
@Transactional
public int deleteBizQuotationByNoQuoteNo() {
List<Long> ids = mapper.selectIdsByQuoteNoNull();
if (ids.isEmpty()) return 0;
for (Long id : ids) {
itemMapper.deleteByQuotationId(id);
}
return mapper.deleteBizQuotationByIds(ids.toArray(new Long[0]));
}
@Override
public List<BizQuotationItem> selectItemsByQuotationId(Long quotationId) {
return itemMapper.selectItemsByQuotationId(quotationId);

View File

@@ -73,4 +73,34 @@ public class SysOperLogServiceImpl implements ISysOperLogService
{
operLogMapper.cleanOperLog();
}
@Override
public java.util.Map<String, Object> selectOperLogStats(SysOperLog operLog)
{
return operLogMapper.selectOperLogStats(operLog);
}
@Override
public java.util.List<java.util.Map<String, Object>> selectModuleStats(SysOperLog operLog)
{
return operLogMapper.selectModuleStats(operLog);
}
@Override
public java.util.List<java.util.Map<String, Object>> selectBusinessTypeStats(SysOperLog operLog)
{
return operLogMapper.selectBusinessTypeStats(operLog);
}
@Override
public java.util.List<java.util.Map<String, Object>> selectDailyTrend(SysOperLog operLog)
{
return operLogMapper.selectDailyTrend(operLog);
}
@Override
public java.util.List<java.util.Map<String, Object>> selectErrorStats(SysOperLog operLog)
{
return operLogMapper.selectErrorStats(operLog);
}
}

View File

@@ -76,19 +76,21 @@
</delete>
<select id="selectClientDeliveryOrders" resultType="java.util.Map">
SELECT d.do_no,
d.delivery_date,
d.delay_date,
d.actual_close_date,
d.delivery_status,
d.total_amount,
s.supplier_name,
(SELECT COUNT(*) FROM biz_delivery_order_item WHERE do_id = d.do_id) AS item_count
FROM biz_client_quote cq
JOIN biz_rfq r ON r.client_quote_id = cq.quote_id
JOIN biz_delivery_order d ON d.rfq_id = r.rfq_id
SELECT d.do_id AS doId,
d.do_no AS doNo,
d.delivery_date AS deliveryDate,
d.delay_date AS delayDate,
d.actual_close_date AS actualCloseDate,
d.delivery_status AS deliveryStatus,
d.total_amount AS totalAmount,
d.remark,
s.supplier_name AS supplierName,
(SELECT COUNT(*) FROM biz_delivery_order_item WHERE do_id = d.do_id) AS itemCount
FROM biz_delivery_order d
LEFT JOIN biz_supplier s ON d.supplier_id = s.supplier_id
WHERE cq.client_id = #{clientId}
WHERE d.client_quote_id IN (
SELECT cq.quote_id FROM biz_client_quote cq WHERE cq.client_id = #{clientId}
)
ORDER BY d.create_time DESC
</select>
</mapper>

View File

@@ -41,7 +41,7 @@
<if test="query.type != null and query.type != ''"> AND d.type=#{query.type}</if>
<if test="query.doNo != null and query.doNo != ''"> AND d.do_no LIKE CONCAT('%',#{query.doNo},'%')</if>
<if test="query.supplierId != null"> AND d.supplier_id=#{query.supplierId}</if>
<if test="query.deliveryStatus != null and query.deliveryStatus != ''"> AND d.delivery_status=#{query.deliveryStatus}</if>
<if test="query.deliveryStatus != null and query.deliveryStatus != ''"> AND FIND_IN_SET(d.delivery_status, #{query.deliveryStatus})</if>
<if test="query.supplierName != null and query.supplierName != ''"> AND s.supplier_name LIKE CONCAT('%',#{query.supplierName},'%')</if>
<if test="query.clientName != null and query.clientName != ''"> AND cl.client_name LIKE CONCAT('%',#{query.clientName},'%')</if>
</where>

View File

@@ -66,11 +66,21 @@
<if test="status != null">status=#{status},</if>
<if test="note != null">note=#{note},</if>
<if test="submitTime != null">submit_time=#{submitTime},</if>
<if test="quoteNo != null and quoteNo != ''">quote_no=#{quoteNo},</if>
update_time=NOW()
</set>
WHERE quotation_id=#{quotationId}
</update>
<select id="selectNextQuoteNo" resultType="String">
SELECT CONCAT('Q-', DATE_FORMAT(NOW(),'%Y%m'), '-', LPAD(IFNULL(MAX(CAST(SUBSTRING_INDEX(quote_no,'-',-1) AS UNSIGNED)),0)+1,3,'0'))
FROM biz_quotation WHERE quote_no LIKE CONCAT('Q-', DATE_FORMAT(NOW(),'%Y%m'), '%')
</select>
<select id="selectIdsByQuoteNoNull" resultType="Long">
SELECT quotation_id FROM biz_quotation WHERE quote_no IS NULL OR quote_no = ''
</select>
<delete id="deleteBizQuotationById">DELETE FROM biz_quotation WHERE quotation_id=#{id}</delete>
<delete id="deleteBizQuotationByIds">
DELETE FROM biz_quotation WHERE quotation_id IN

View File

@@ -82,6 +82,110 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<update id="cleanOperLog">
truncate table sys_oper_log
</update>
</update>
<select id="selectOperLogStats" resultType="java.util.Map">
SELECT
COUNT(*) AS total_count,
SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) AS error_count,
SUM(CASE WHEN DATE(oper_time) = CURDATE() THEN 1 ELSE 0 END) AS today_count,
COUNT(DISTINCT oper_name) AS user_count,
ROUND(AVG(cost_time), 0) AS avg_cost_time,
COUNT(DISTINCT title) AS module_count
FROM sys_oper_log
<where>
<if test="operName != null and operName != ''">
AND (title LIKE CONCAT('%', #{title}, '%') OR oper_name LIKE CONCAT('%', #{operName}, '%'))
</if>
<if test="title != null and title != ''">
AND title LIKE CONCAT('%', #{title}, '%')
</if>
</where>
</select>
<select id="selectModuleStats" resultType="java.util.Map">
SELECT title AS module_name, COUNT(*) AS count
FROM sys_oper_log
<where>
<if test="operName != null and operName != ''">
AND oper_name LIKE CONCAT('%', #{operName}, '%')
</if>
<if test="title != null and title != ''">
AND title LIKE CONCAT('%', #{title}, '%')
</if>
<if test="params.beginTime != null and params.beginTime != ''">
AND oper_time >= #{params.beginTime}
</if>
<if test="params.endTime != null and params.endTime != ''">
AND oper_time &lt;= #{params.endTime}
</if>
</where>
GROUP BY title
ORDER BY count DESC
LIMIT 20
</select>
<select id="selectBusinessTypeStats" resultType="java.util.Map">
SELECT business_type AS type, COUNT(*) AS count
FROM sys_oper_log
<where>
<if test="operName != null and operName != ''">
AND oper_name LIKE CONCAT('%', #{operName}, '%')
</if>
<if test="title != null and title != ''">
AND title LIKE CONCAT('%', #{title}, '%')
</if>
<if test="params.beginTime != null and params.beginTime != ''">
AND oper_time >= #{params.beginTime}
</if>
<if test="params.endTime != null and params.endTime != ''">
AND oper_time &lt;= #{params.endTime}
</if>
</where>
GROUP BY business_type
ORDER BY count DESC
</select>
<select id="selectDailyTrend" resultType="java.util.Map">
SELECT DATE_FORMAT(oper_time, '%Y-%m-%d') AS day, COUNT(*) AS count
FROM sys_oper_log
<where>
<if test="operName != null and operName != ''">
AND oper_name LIKE CONCAT('%', #{operName}, '%')
</if>
<if test="title != null and title != ''">
AND title LIKE CONCAT('%', #{title}, '%')
</if>
<if test="params.beginTime != null and params.beginTime != ''">
AND oper_time >= #{params.beginTime}
</if>
<if test="params.endTime != null and params.endTime != ''">
AND oper_time &lt;= #{params.endTime}
</if>
</where>
GROUP BY DATE_FORMAT(oper_time, '%Y-%m-%d')
ORDER BY day DESC
LIMIT 14
</select>
<select id="selectErrorStats" resultType="java.util.Map">
SELECT title AS module_name, COUNT(*) AS error_count
FROM sys_oper_log
WHERE status = 1
<if test="operName != null and operName != ''">
AND oper_name LIKE CONCAT('%', #{operName}, '%')
</if>
<if test="title != null and title != ''">
AND title LIKE CONCAT('%', #{title}, '%')
</if>
<if test="params.beginTime != null and params.beginTime != ''">
AND oper_time >= #{params.beginTime}
</if>
<if test="params.endTime != null and params.endTime != ''">
AND oper_time &lt;= #{params.endTime}
</if>
GROUP BY title
ORDER BY error_count DESC
LIMIT 10
</select>
</mapper>