package com.talentcard.server.business.merchant;
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|
import java.math.BigDecimal;
|
import java.util.List;
|
import java.util.Map;
|
|
import com.talentcard.server.business.PageResult;
|
import com.talentcard.server.business.merchant.auth.MerchantPrincipal;
|
import com.talentcard.server.common.BusinessException;
|
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 MerchantManagementServiceTests {
|
|
@Autowired
|
private JdbcTemplate jdbc;
|
|
@Autowired
|
private MerchantManagementService service;
|
|
private Long merchantId;
|
private Long storeId;
|
private MerchantPrincipal principal;
|
|
@BeforeEach
|
void prepareMerchant() {
|
String suffix = String.valueOf(System.nanoTime());
|
jdbc.update("insert into merchant(merchant_name, address, business_status, status, can_login_merchant_app, deleted) values (?, ?, 'OPEN', 1, 1, 0)",
|
"Management test merchant " + suffix, "测试商户地址");
|
merchantId = jdbc.queryForObject("select max(id) from merchant", Long.class);
|
jdbc.update("insert into merchant_store(merchant_id, store_name, address, status, deleted) values (?, ?, ?, 1, 0)",
|
merchantId, "测试门店", "测试门店地址");
|
storeId = jdbc.queryForObject("select max(id) from merchant_store", Long.class);
|
principal = new MerchantPrincipal(1000L, merchantId, storeId, "operator");
|
insertProduct("ONLINE", 1, 2);
|
insertProduct("OFFLINE", 0, 2);
|
insertProduct("PENDING", 0, 1);
|
}
|
|
@Test
|
void returnsMerchantScopedProfileAndProducts() {
|
Map<String, Object> profile = service.profile(principal);
|
PageResult<Map<String, Object>> online = service.products(principal, "online", 1, 10);
|
PageResult<Map<String, Object>> offline = service.products(principal, "offline", 1, 10);
|
PageResult<Map<String, Object>> review = service.products(principal, "review", 1, 10);
|
|
assertThat(profile).containsEntry("address", "测试门店地址").containsEntry("businessStatus", "open").containsEntry("onlineProductCount", 1L);
|
assertThat(online.total()).isEqualTo(1);
|
assertThat(offline.total()).isEqualTo(1);
|
assertThat(review.total()).isEqualTo(3);
|
}
|
|
@Test
|
void updatesBusinessHoursAndRejectsOverlaps() {
|
Map<String, Object> saved = service.updateBusinessHours(principal, new MerchantBusinessHoursRequest(List.of(
|
new MerchantBusinessHoursRequest.Period("08:00", "12:00"),
|
new MerchantBusinessHoursRequest.Period("14:00", "18:00")
|
)));
|
|
assertThat(saved.get("periods")).asList().hasSize(2);
|
assertThatThrownBy(() -> service.updateBusinessHours(principal, new MerchantBusinessHoursRequest(List.of(
|
new MerchantBusinessHoursRequest.Period("08:00", "12:00"),
|
new MerchantBusinessHoursRequest.Period("11:00", "18:00")
|
)))).isInstanceOf(BusinessException.class);
|
}
|
|
@Test
|
void updatingProductResubmitsForReviewAndPreventsPendingProductFromGoingOnline() {
|
Long productId = jdbc.queryForObject("select id from merchant_product where merchant_id = ? and product_no like 'MANAGEMENT-ONLINE-%'", Long.class, merchantId);
|
Map<String, Object> updated = service.updateProduct(principal, productId, new MerchantProductUpdateRequest(
|
"更新后的商品", new BigDecimal("99.90"), "https://example.com/product.jpg", List.of("https://example.com/product-1.jpg"),
|
"更新后的图文详情", Map.of("validity", "30天", "usage", "到店核销")
|
));
|
|
assertThat(updated).containsEntry("status", "offline").containsEntry("auditStatus", "pending");
|
assertThatThrownBy(() -> service.updateProductStatus(principal, productId, new MerchantProductStatusRequest("online")))
|
.isInstanceOf(BusinessException.class);
|
}
|
|
/**
|
* 体检医院在后台团单里只有「商品名称 / 封面图 / 可预约时间 / 检查项目」,没有商品金额。
|
* 商家端照这套字段保存时:可预约时间要落到 purchase_notice.validPeriod,金额允许为空并按 0(免费)落库。
|
*/
|
@Test
|
void savesHealthCheckProductWithoutPriceAndKeepsReservationWindow() {
|
jdbc.update("update merchant set merchant_type_code = 'medical' where id = ?", merchantId);
|
Long productId = jdbc.queryForObject("select id from merchant_product where merchant_id = ? and product_no like 'MANAGEMENT-ONLINE-%'", Long.class, merchantId);
|
|
Map<String, Object> updated = service.updateProduct(principal, productId, new MerchantProductUpdateRequest(
|
"查血", null, "https://example.com/cover.jpg", List.of(),
|
"[{\"name\":\"查血\",\"meaning\":\"检查血脂\",\"gender\":[\"男\",\"女\"]}]",
|
Map.of("validPeriod", "2026-09-16 至 2026-09-18")
|
));
|
|
assertThat(updated).containsEntry("merchantTypeCode", "medical");
|
assertThat((BigDecimal) updated.get("salePrice")).isEqualByComparingTo(BigDecimal.ZERO);
|
assertThat((Map<String, String>) updated.get("purchaseNotice"))
|
.containsEntry("validPeriod", "2026-09-16 至 2026-09-18");
|
Map<String, Object> detail = service.product(principal, productId);
|
assertThat(String.valueOf(detail.get("detailText"))).contains("检查血脂");
|
}
|
|
/** 非体检医院仍然要有商品金额:商家端少了金额字段时不能静默按免费存下去。 */
|
@Test
|
void rejectsMissingPriceForNonHealthCheckMerchant() {
|
Long productId = jdbc.queryForObject("select id from merchant_product where merchant_id = ? and product_no like 'MANAGEMENT-ONLINE-%'", Long.class, merchantId);
|
|
assertThatThrownBy(() -> service.updateProduct(principal, productId, new MerchantProductUpdateRequest(
|
"普通商品", null, "https://example.com/cover.jpg", List.of("https://example.com/product-1.jpg"),
|
"图文详情", Map.of()
|
))).isInstanceOf(BusinessException.class).hasMessageContaining("商品金额");
|
}
|
|
private void insertProduct(String marker, int status, int auditStatus) {
|
jdbc.update("""
|
insert into merchant_product(merchant_id, product_no, product_type, product_name, cover_url, original_price, sale_price,
|
audit_status, status, submit_time, deleted)
|
values (?, ?, 'food', ?, '/static/test.jpg', 100.00, 80.00, ?, ?, now(), 0)
|
""", merchantId, "MANAGEMENT-" + marker + "-" + System.nanoTime(), marker + " product", auditStatus, status);
|
}
|
}
|