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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
package com.talentcard.server.business.merchant;
 
import static org.assertj.core.api.Assertions.assertThat;
 
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.Map;
 
import com.talentcard.server.business.PageResult;
import com.talentcard.server.business.merchant.auth.MerchantPrincipal;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ActiveProfiles;
 
@SpringBootTest
@ActiveProfiles("test")
class MerchantWorkServiceTests {
 
    @Autowired
    private JdbcTemplate jdbc;
 
    @Autowired
    private MerchantWorkService merchantWorkService;
 
    private Long merchantId;
    private Long storeId;
    private Long talentId;
    private MerchantPrincipal principal;
 
    @BeforeEach
    void prepareOrders() {
        String suffix = String.valueOf(System.nanoTime());
        jdbc.update("insert into merchant(merchant_name, status, can_login_merchant_app, deleted) values (?, 1, 1, 0)", "Order test merchant " + suffix);
        merchantId = jdbc.queryForObject("select max(id) from merchant", Long.class);
        jdbc.update("insert into merchant_store(merchant_id, store_name, status, deleted) values (?, ?, 1, 0)", merchantId, "Order test store");
        storeId = jdbc.queryForObject("select max(id) from merchant_store", Long.class);
        jdbc.update("insert into talent_user(openid, name, mobile, status, deleted) values (?, ?, ?, 1, 0)", "order-test-" + suffix, "订单测试用户", "13800000000");
        talentId = jdbc.queryForObject("select max(id) from talent_user", Long.class);
        principal = new MerchantPrincipal(9001L, merchantId, storeId, "operator");
        insertOrder("GROUP_BUY", 1, 0, LocalDateTime.of(2026, 7, 10, 9, 0));
        insertOrder("GROUP_BUY", 1, 1, LocalDateTime.of(2026, 7, 11, 9, 0));
        insertOrder("STORE_CONSUME", 0, 0, LocalDateTime.of(2026, 7, 12, 9, 0));
        insertOrder("FREE_STAY", 1, 1, LocalDateTime.of(2026, 7, 13, 9, 0));
    }
 
    @Test
    void filtersOrdersByCategoryStatusAndDateWithoutExposingFullMobile() {
        PageResult<Map<String, Object>> groupPending = merchantWorkService.orders(merchantId, storeId, 1, 10,
                new MerchantOrderQuery("group", "pending_verification", "2026-07-10", "2026-07-10"));
        PageResult<Map<String, Object>> storePending = merchantWorkService.orders(merchantId, storeId, 1, 10,
                new MerchantOrderQuery("store", "pending_payment", null, null));
        PageResult<Map<String, Object>> stays = merchantWorkService.orders(merchantId, storeId, 1, 10,
                new MerchantOrderQuery("stay", "all", null, null));
 
        assertThat(groupPending.total()).isEqualTo(1);
        assertThat(groupPending.records().get(0)).containsEntry("category", "group").containsEntry("status", "pending_verification");
        assertThat(String.valueOf(groupPending.records().get(0).get("customerMobile"))).isEqualTo("138****0000");
        assertThat(storePending.total()).isEqualTo(1);
        assertThat(storePending.records().get(0)).containsEntry("category", "store").containsEntry("status", "pending_payment");
        assertThat(stays.total()).isEqualTo(1);
        assertThat(stays.records().get(0)).containsEntry("category", "stay").containsEntry("status", "completed");
    }
 
    @Test
    void verifiesAndRevokesGroupBuyIdempotently() {
        String orderNo = insertOrder("GROUP_BUY", 1, 0, LocalDateTime.now());
        Long orderId = jdbc.queryForObject("select id from benefit_order where order_no = ?", Long.class, orderNo);
 
        Map<String, Object> verified = merchantWorkService.verify(orderNo, principal,
                new VerifyRequest("merchant", "test verification"), "verify-" + orderNo);
        Map<String, Object> repeatedVerification = merchantWorkService.verify(orderNo, principal,
                new VerifyRequest("merchant", "test verification"), "verify-" + orderNo);
 
        assertThat(verified).containsEntry("status", "completed");
        assertThat(repeatedVerification).containsEntry("status", "completed");
        assertThat(jdbc.queryForObject("select count(*) from verify_record where order_id = ?", Integer.class, orderId)).isEqualTo(1);
 
        String revokeKey = "revoke-" + orderNo;
        Map<String, Object> revoked = merchantWorkService.revokeVerification(principal,
                new MerchantRevokeVerificationRequest(String.valueOf(orderId), "test reversal"), revokeKey);
        Map<String, Object> repeatedRevoke = merchantWorkService.revokeVerification(principal,
                new MerchantRevokeVerificationRequest(String.valueOf(orderId), "test reversal"), revokeKey);
 
        assertThat(revoked).containsEntry("orderStatus", "pending_verification");
        assertThat(repeatedRevoke).containsEntry("orderStatus", "pending_verification");
        assertThat(jdbc.queryForObject("select verify_status from benefit_order where id = ?", Integer.class, orderId)).isZero();
        assertThat(jdbc.queryForObject("select status from verify_record where order_id = ?", Integer.class, orderId)).isEqualTo(2);
    }
 
    @Test
    void derivesConsistentDiscountBreakdownForLegacyOrderSnapshots() {
        String orderNo = "LEGACY-" + System.nanoTime();
        jdbc.update("""
                insert into benefit_order(order_no, talent_id, merchant_id, store_id, order_type, order_amount,
                                          subsidy_amount, base_pay_amount, coupon_amount, pay_amount,
                                          order_status, verify_status, deleted)
                values (?, ?, ?, ?, 'GROUP_BUY', 14800, 0, 14800, 0, 1, 1, 0, 0)
                """, orderNo, talentId, merchantId, storeId);
 
        Map<String, Object> detail = merchantWorkService.order(orderNo, merchantId, storeId);
 
        assertThat(detail).containsEntry("talentDiscountAmount", 14799L)
                .containsEntry("couponAmount", 0L)
                .containsEntry("subsidyAmount", 14799L);
    }
 
    @Test
    void aCancelledOrderIsShownAsCancelledInsteadOfWaitingToBeVerified() {
        String orderNo = insertOrder("GROUP_BUY", 2, 0, LocalDateTime.now());
 
        assertThat(recordOf(list("group", "all"), orderNo))
                .containsEntry("status", "cancelled")
                .containsEntry("statusText", "已取消");
        assertThat(orderNos(list("group", "pending_verification")))
                .as("已取消的订单不是待核销:商户照它去核销只会被资金边界拒绝")
                .doesNotContain(orderNo);
        assertThat(orderNos(list("group", "closed"))).contains(orderNo);
    }
 
    @Test
    void aRefundedOrderIsShownAsRefundedAndStopsWaitingToBeVerified() {
        String orderNo = insertOrder("GROUP_BUY", 1, 0, LocalDateTime.now());
        jdbc.update("update benefit_order set refund_status = 2 where order_no = ?", orderNo);
 
        assertThat(recordOf(list("group", "all"), orderNo))
                .containsEntry("status", "refunded")
                .containsEntry("statusText", "已退款");
        assertThat(orderNos(list("group", "pending_verification"))).doesNotContain(orderNo);
        assertThat(orderNos(list("group", "closed"))).contains(orderNo);
    }
 
    @Test
    void aRefundInFlightIsShownAsRefunding() {
        String orderNo = insertOrder("GROUP_BUY", 1, 0, LocalDateTime.now());
        jdbc.update("update benefit_order set refund_status = 1 where order_no = ?", orderNo);
 
        assertThat(recordOf(list("group", "all"), orderNo)).containsEntry("statusText", "退款中");
        assertThat(orderNos(list("group", "pending_verification"))).doesNotContain(orderNo);
    }
 
    @Test
    void theOrderDetailCarriesTheSameClosedState() {
        String orderNo = insertOrder("GROUP_BUY", 1, 0, LocalDateTime.now());
        jdbc.update("update benefit_order set refund_status = 1 where order_no = ?", orderNo);
 
        assertThat(merchantWorkService.order(orderNo, merchantId, storeId))
                .containsEntry("status", "refunded")
                .containsEntry("statusText", "退款中");
        assertThat(merchantWorkService.order(insertOrder("GROUP_BUY", 2, 0, LocalDateTime.now()), merchantId, storeId))
                .containsEntry("status", "cancelled");
    }
 
    private PageResult<Map<String, Object>> list(String category, String status) {
        return merchantWorkService.orders(merchantId, storeId, 1, 50, new MerchantOrderQuery(category, status, null, null));
    }
 
    private Map<String, Object> recordOf(PageResult<Map<String, Object>> page, String orderNo) {
        return page.records().stream()
                .filter(record -> orderNo.equals(String.valueOf(record.get("orderNo"))))
                .findFirst()
                .orElseThrow(() -> new AssertionError("Order " + orderNo + " is missing from the merchant order list"));
    }
 
    private java.util.List<String> orderNos(PageResult<Map<String, Object>> page) {
        return page.records().stream().map(record -> String.valueOf(record.get("orderNo"))).toList();
    }
 
    private String insertOrder(String type, int orderStatus, int verifyStatus, LocalDateTime createdAt) {
        String orderNo = "ORDER-" + System.nanoTime();
        jdbc.update("""
                insert into benefit_order(order_no, talent_id, merchant_id, store_id, order_type, order_amount,
                                          subsidy_amount, pay_amount, order_status, verify_status, created_at, deleted)
                values (?, ?, ?, ?, ?, 10000, 1000, 9000, ?, ?, ?, 0)
                """, orderNo, talentId, merchantId, storeId, type, orderStatus, verifyStatus,
                Timestamp.valueOf(createdAt));
        return orderNo;
    }
}