package com.ruoyi.sales;
import com.ruoyi.RuoYiApplication;
import com.ruoyi.common.enums.ApproveTypeEnum;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 清理多出来的「发货审批」{@code approve_process}({@code approve_type=7},事由 {@code 发货审批:合同号})。
*
* 典型场景:销售订单已发货条数与有效发货审批条数应一致;若批量脚本重复插入,会出现同一事由多条
* {@code approve_delete=0} 的记录,或存在合同号在「未发货」台账上仍有一条发货审批的孤儿数据。
*
* 处理策略(与若依删除习惯一致,软删除):
*
* - 重复事由:同一 {@code approve_reason} 下保留 {@code id} 最小的一条,其余 {@code approve_delete=1}。
* - 孤儿事由:事由解析出的合同号在 {@code sales_ledger} 中不存在 {@code delivery_status=5}(已发货)的订单时,
* 将该发货审批软删除。(若你希望以其它规则定义「已发货」,需改 SQL。)
*
* 同步将对应 {@code approve_node} 的 {@code delete_flag} 置为 1(删除标记)。
*
* 执行前必须:VM options {@code -Druoyi.deliveryApprove.cleanup.commit=true} 或环境变量
* {@code RUOYI_DELIVERY_APPROVE_CLEANUP_COMMIT=true},且连业务库。未开启时本用例跳过。
*/
@SpringBootTest(classes = RuoYiApplication.class)
class DeliveryApproveExtraCleanupTest {
private static final Logger log = LoggerFactory.getLogger(DeliveryApproveExtraCleanupTest.class);
private static final String REASON_PREFIX = "发货审批:";
@Autowired
private DataSource dataSource;
@Test
@Transactional
@Rollback(false)
void softRemoveExtraDeliveryApprovals() {
NamedParameterJdbcTemplate named = new NamedParameterJdbcTemplate(dataSource);
int type7 = ApproveTypeEnum.DELIVERY.getCode();
List duplicateExtras = selectDuplicateDeliveryExtras(named, type7);
int n1 = applySoftRemove(named, duplicateExtras);
log.warn("[发货审批清理] 重复事由软删除行数={}, id列表={}", n1, formatIds(duplicateExtras));
List orphans = selectOrphanDeliveryApprovals(named, type7);
int n2 = applySoftRemove(named, orphans);
log.warn("[发货审批清理] 孤儿事由软删除行数={}, id列表={}", n2, formatIds(orphans));
log.warn("[发货审批清理] 完成。重复={}, 孤儿={}, 合计软删除={}", n1, n2, n1 + n2);
}
private static String formatIds(List refs) {
return refs.stream().map(r -> String.valueOf(r.id)).collect(Collectors.joining(","));
}
private List selectDuplicateDeliveryExtras(NamedParameterJdbcTemplate named, int approveType) {
MapSqlParameterSource p = new MapSqlParameterSource();
p.addValue("atype", approveType);
p.addValue("pfx", REASON_PREFIX + "%");
return named.query(
"SELECT ap.id, ap.approve_id AS approveId FROM approve_process ap "
+ "INNER JOIN ( "
+ " SELECT approve_reason, MIN(id) AS keep_id "
+ " FROM approve_process "
+ " WHERE approve_delete = 0 AND approve_type = :atype AND approve_reason LIKE :pfx "
+ " GROUP BY approve_reason "
+ " HAVING COUNT(*) > 1 "
+ ") d ON ap.approve_reason = d.approve_reason AND ap.id <> d.keep_id "
+ "WHERE ap.approve_delete = 0 AND ap.approve_type = :atype",
p,
(rs, i) -> new ApproveRowRef(rs.getLong("id"), rs.getString("approveId")));
}
/**
* 事由中合同号在「已发货」销售台账中不存在则视为孤儿
*/
private List selectOrphanDeliveryApprovals(NamedParameterJdbcTemplate named, int approveType) {
MapSqlParameterSource p = new MapSqlParameterSource();
p.addValue("atype", approveType);
p.addValue("pfx", REASON_PREFIX + "%");
return named.query(
"SELECT ap.id, ap.approve_id AS approveId FROM approve_process ap "
+ "WHERE ap.approve_delete = 0 AND ap.approve_type = :atype AND ap.approve_reason LIKE :pfx "
+ "AND NOT EXISTS ( "
+ " SELECT 1 FROM sales_ledger sl "
+ " WHERE sl.sales_contract_no = TRIM(SUBSTRING(ap.approve_reason, CHAR_LENGTH('发货审批:') + 1)) "
+ " AND sl.delivery_status = 5 "
+ ")",
p,
(rs, i) -> new ApproveRowRef(rs.getLong("id"), rs.getString("approveId")));
}
private int applySoftRemove(NamedParameterJdbcTemplate named, List refs) {
if (refs.isEmpty()) {
return 0;
}
Set idSet = new LinkedHashSet<>();
Set approveIdSet = new LinkedHashSet<>();
for (ApproveRowRef r : refs) {
idSet.add(r.id);
if (r.approveId != null) {
approveIdSet.add(r.approveId);
}
}
List ids = new ArrayList<>(idSet);
List approveIds = new ArrayList<>(approveIdSet);
MapSqlParameterSource p1 = new MapSqlParameterSource();
p1.addValue("ids", ids);
int u1 = named.update("UPDATE approve_process SET approve_delete = 1 WHERE id IN (:ids)", p1);
MapSqlParameterSource p2 = new MapSqlParameterSource();
p2.addValue("aids", approveIds);
int u2 = named.update("UPDATE approve_node SET delete_flag = 1 WHERE delete_flag = 0 AND approve_process_id IN (:aids)", p2);
log.debug("approve_process 更新行数={}, approve_node 更新行数={}", u1, u2);
return u1;
}
private static final class ApproveRowRef {
private final long id;
private final String approveId;
private ApproveRowRef(long id, String approveId) {
this.id = id;
this.approveId = approveId;
}
}
}