5 小时以前 9bad721754fe8bbe2e5f459d0706e0fefac569f3
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
package cn.iocoder.yudao.module.qcreport.service.instance;
 
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
 
import java.time.LocalDate;
import java.util.Locale;
 
import static org.junit.jupiter.api.Assertions.assertEquals;
 
/**
 * 报告编号生成规则。
 * <p>
 * 这里守的是「编号一定能被自己读懂」:{@link ReportNoGenerator#nextSequence} 是对
 * {@link ReportNoGenerator#format} 的逆运算,两者格式一旦对不上,
 * 表现是流水号悄悄从 1 重来,然后撞唯一索引、重试 5 次后报「编号冲突」——
 * 用户看到的是「出不了件」,很难联想到是编号格式的问题。
 */
class ReportNoGeneratorTest {
 
    private static final LocalDate DAY = LocalDate.of(2026, 9, 18);
 
    @Test
    @DisplayName("前缀与编号格式")
    void prefixAndFormat() {
        assertEquals("QR20260918-", ReportNoGenerator.prefix(DAY));
        assertEquals("QR20260918-0001", ReportNoGenerator.format(DAY, 1));
        assertEquals("QR20260918-0042", ReportNoGenerator.format(DAY, 42));
        assertEquals("QR20260918-9999", ReportNoGenerator.format(DAY, 9999));
    }
 
    @Test
    @DisplayName("序号超过 9999 时位宽自然加宽,不截断也不回绕")
    void formatWidensBeyondFourDigits() {
        assertEquals("QR20260918-10000", ReportNoGenerator.format(DAY, 10_000));
        assertEquals("QR20260918-100000", ReportNoGenerator.format(DAY, 100_000));
    }
 
    @Test
    @DisplayName("编号里的数字与年份不受服务器 locale 影响")
    void independentOfDefaultLocale() {
        Locale original = Locale.getDefault();
        try {
            // 泰历会把 2026 年写成 2569;非拉丁数字字形会写出阿拉伯-印度数字
            Locale.setDefault(Locale.forLanguageTag("th-TH-u-ca-buddhist-nu-thai"));
            assertEquals("QR20260918-0007", ReportNoGenerator.format(DAY, 7));
        } finally {
            Locale.setDefault(original);
        }
    }
 
    @Test
    @DisplayName("同一天有编号时序号递增")
    void nextSequenceIncrements() {
        assertEquals(2, ReportNoGenerator.nextSequence("QR20260918-0001", DAY));
        assertEquals(43, ReportNoGenerator.nextSequence("QR20260918-0042", DAY));
        assertEquals(10_000, ReportNoGenerator.nextSequence("QR20260918-9999", DAY));
        // 位宽加宽之后仍要能接着往下走
        assertEquals(100_001, ReportNoGenerator.nextSequence("QR20260918-100000", DAY));
    }
 
    @Test
    @DisplayName("还没有编号时从 1 开始")
    void nextSequenceStartsAtOneWhenAbsent() {
        assertEquals(1, ReportNoGenerator.nextSequence(null, DAY));
    }
 
    @Test
    @DisplayName("拿到的编号属于别的日期时,按新的一天从 1 开始")
    void nextSequenceResetsOnOtherDay() {
        assertEquals(1, ReportNoGenerator.nextSequence("QR20260917-0042", DAY));
        assertEquals(1, ReportNoGenerator.nextSequence("QR20260919-0042", DAY));
    }
 
    @Test
    @DisplayName("格式不认识的编号一律归 1,而不是抛异常或硬解析")
    void nextSequenceFallsBackOnUnknownFormat() {
        // 不是本生成器的前缀
        assertEquals(1, ReportNoGenerator.nextSequence("QC-2026-0001", DAY));
        // 尾部不是数字
        assertEquals(1, ReportNoGenerator.nextSequence("QR20260918-ABCD", DAY));
        // 尾部混了数字与字母
        assertEquals(1, ReportNoGenerator.nextSequence("QR20260918-00A1", DAY));
        // 位数不足 4 位
        assertEquals(1, ReportNoGenerator.nextSequence("QR20260918-1", DAY));
        // 位数超过上限(本生成器产出不了这么长的尾巴,说明不是它写的)
        assertEquals(1, ReportNoGenerator.nextSequence("QR20260918-1234567890", DAY));
        // 只有前缀、没有流水
        assertEquals(1, ReportNoGenerator.nextSequence("QR20260918-", DAY));
        // 空串
        assertEquals(1, ReportNoGenerator.nextSequence("", DAY));
    }
 
}