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> groupPending = merchantWorkService.orders(merchantId, storeId, 1, 10, new MerchantOrderQuery("group", "pending_verification", "2026-07-10", "2026-07-10")); PageResult> storePending = merchantWorkService.orders(merchantId, storeId, 1, 10, new MerchantOrderQuery("store", "pending_payment", null, null)); PageResult> 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 verified = merchantWorkService.verify(orderNo, principal, new VerifyRequest("merchant", "test verification"), "verify-" + orderNo); Map 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 revoked = merchantWorkService.revokeVerification(principal, new MerchantRevokeVerificationRequest(String.valueOf(orderId), "test reversal"), revokeKey); Map 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 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> list(String category, String status) { return merchantWorkService.orders(merchantId, storeId, 1, 50, new MerchantOrderQuery(category, status, null, null)); } private Map recordOf(PageResult> 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 orderNos(PageResult> 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; } }