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} 的记录,或存在合同号在「未发货」台账上仍有一条发货审批的孤儿数据。 *

* 处理策略(与若依删除习惯一致,软删除): *

    *
  1. 重复事由:同一 {@code approve_reason} 下保留 {@code id} 最小的一条,其余 {@code approve_delete=1}。
  2. *
  3. 孤儿事由:事由解析出的合同号在 {@code sales_ledger} 中不存在 {@code delivery_status=5}(已发货)的订单时, * 将该发货审批软删除。(若你希望以其它规则定义「已发货」,需改 SQL。)
  4. *
* 同步将对应 {@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; } } }