gongchunyi
4 天以前 9f281fb21c139b96cd2be8809e1c704782868c95
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
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 发货审批:合同号})。
 * <p>
 * 典型场景:销售订单已发货条数与有效发货审批条数应一致;若批量脚本重复插入,会出现同一事由多条
 * {@code approve_delete=0} 的记录,或存在合同号在「未发货」台账上仍有一条发货审批的孤儿数据。
 * <p>
 * 处理策略(与若依删除习惯一致,<b>软删除</b>):
 * <ol>
 *   <li><b>重复事由</b>:同一 {@code approve_reason} 下保留 {@code id} 最小的一条,其余 {@code approve_delete=1}。</li>
 *   <li><b>孤儿事由</b>:事由解析出的合同号在 {@code sales_ledger} 中不存在 {@code delivery_status=5}(已发货)的订单时,
 *       将该发货审批软删除。(若你希望以其它规则定义「已发货」,需改 SQL。)</li>
 * </ol>
 * 同步将对应 {@code approve_node} 的 {@code delete_flag} 置为 1(删除标记)。
 * <p>
 * <b>执行前必须</b>: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<ApproveRowRef> duplicateExtras = selectDuplicateDeliveryExtras(named, type7);
        int n1 = applySoftRemove(named, duplicateExtras);
        log.warn("[发货审批清理] 重复事由软删除行数={}, id列表={}", n1, formatIds(duplicateExtras));
 
        List<ApproveRowRef> 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<ApproveRowRef> refs) {
        return refs.stream().map(r -> String.valueOf(r.id)).collect(Collectors.joining(","));
    }
 
    private List<ApproveRowRef> 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<ApproveRowRef> 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<ApproveRowRef> refs) {
        if (refs.isEmpty()) {
            return 0;
        }
        Set<Long> idSet = new LinkedHashSet<>();
        Set<String> approveIdSet = new LinkedHashSet<>();
        for (ApproveRowRef r : refs) {
            idSet.add(r.id);
            if (r.approveId != null) {
                approveIdSet.add(r.approveId);
            }
        }
        List<Long> ids = new ArrayList<>(idSet);
        List<String> 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;
        }
    }
}