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.LinkedHashMap;
|
import java.util.Map;
|
|
import com.talentcard.server.business.merchant.auth.MerchantPrincipal;
|
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.provider.ValueSource;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.datasource.DriverManagerDataSource;
|
|
class ManualConsumptionServiceTests {
|
|
private JdbcTemplate jdbc;
|
private ManualConsumptionService service;
|
private MerchantPrincipal principal;
|
|
@BeforeEach
|
void setUp() {
|
DriverManagerDataSource dataSource = new DriverManagerDataSource(
|
"jdbc:h2:mem:manual-consumption-" + System.nanoTime() + ";MODE=MySQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1", "sa", "");
|
jdbc = new JdbcTemplate(dataSource);
|
jdbc.execute("""
|
create table talent_dynamic_code (
|
id bigint primary key,
|
status tinyint not null,
|
merchant_id bigint,
|
used_at timestamp
|
)
|
""");
|
jdbc.execute("""
|
create table merchant (
|
id bigint primary key,
|
merchant_type_code varchar(64),
|
merchant_rights_config varchar(4000),
|
deleted tinyint not null default 0
|
)
|
""");
|
jdbc.execute("""
|
create table merchant_store_consume_rule (
|
id bigint primary key,
|
merchant_id bigint not null,
|
card_level varchar(64) not null,
|
rule_name varchar(128) not null,
|
discount_rate decimal(8, 4) not null,
|
subsidy_enabled tinyint not null,
|
status tinyint not null,
|
valid_start date,
|
valid_end date,
|
sort_no int not null default 0
|
)
|
""");
|
service = new ManualConsumptionService(jdbc);
|
principal = new MerchantPrincipal(9L, 88L, 7L, "operator");
|
}
|
|
@Test
|
void consumesActiveOrExpiredCodeAfterSuccessfulScan() {
|
jdbc.update("insert into talent_dynamic_code(id, status) values (1, 0), (2, 2)");
|
|
assertThat(service.consumeScannedTalentCode(principal, 1L)).isEqualTo(1);
|
assertThat(service.consumeScannedTalentCode(principal, 2L)).isEqualTo(1);
|
assertThat(jdbc.queryForObject("select status from talent_dynamic_code where id = 1", Integer.class)).isEqualTo(1);
|
assertThat(jdbc.queryForObject("select status from talent_dynamic_code where id = 2", Integer.class)).isEqualTo(1);
|
}
|
|
@Test
|
void rejectsAlreadyUsedCode() {
|
jdbc.update("insert into talent_dynamic_code(id, status, merchant_id) values (3, 1, 66)");
|
|
assertThat(service.consumeScannedTalentCode(principal, 3L)).isZero();
|
assertThat(jdbc.queryForObject("select merchant_id from talent_dynamic_code where id = 3", Long.class)).isEqualTo(66L);
|
}
|
|
@Test
|
void treatsLegacyPaidParkingConfigAsStoreConsumption() {
|
Map<String, Object> right = new LinkedHashMap<>();
|
right.put("storeConsume", true);
|
right.put("consumeEnabled", false);
|
right.put("parkingType", "paid");
|
|
assertThat(service.configuredStoreConsumptionEnabled(right)).isTrue();
|
right.put("parkingType", "free");
|
assertThat(service.configuredStoreConsumptionEnabled(right)).isFalse();
|
right.put("consumeEnabled", true);
|
assertThat(service.configuredStoreConsumptionEnabled(right)).isFalse();
|
}
|
|
@Test
|
void paidParkingUsesParkingDiscountInsteadOfUnrelatedConsumeFields() {
|
Map<String, Object> right = configuredRight("卓越人才卡", "6");
|
right.put("parkingType", "paid");
|
right.put("consumeType", "优惠");
|
right.put("consumeCoupon", "8");
|
right.put("consumeDiscount", "9");
|
Map<String, Object> rule = service.configuredRule(right);
|
assertThat(rule).containsEntry("benefitText", "到店消费6折");
|
assertThat(service.quote(rule, null, new BigDecimal("10")))
|
.containsEntry("payAmount", 600L).containsEntry("discountAmount", 400L);
|
right.put("consumeType", "折扣");
|
assertThat(service.configuredRule(right)).containsEntry("benefitText", "到店消费6折");
|
}
|
|
@Test
|
void parkingDedicatedRightsOverrideLegacyAndRespectDisabledAndFreeLevels() {
|
jdbc.execute("""
|
create table transport_parking_card_level_right (
|
merchant_id bigint, card_level varchar(64), enabled tinyint, label varchar(128),
|
benefit_detail varchar(500), charge_type varchar(32), store_consume_enabled tinyint,
|
discount_text varchar(64), government_subsidy tinyint, consume_usage_text varchar(500),
|
status tinyint, deleted tinyint default 0
|
)
|
""");
|
jdbc.update("insert into merchant(id, merchant_type_code, merchant_rights_config) values (88, 'parking', null)");
|
jdbc.update("""
|
insert into transport_parking_card_level_right
|
(merchant_id, card_level, enabled, charge_type, store_consume_enabled, discount_text, status)
|
values (88, '卓越人才卡', 1, 'PAID', 1, '6折', 1)
|
""");
|
assertThat(service.hasApplicableRule(88L, "EXCELLENT", "")).isTrue();
|
assertThat(service.hasApplicableRule(88L, "杰出人才卡", "")).isFalse();
|
assertParkingDetailAndQuote();
|
jdbc.update("update merchant set merchant_rights_config = ? where id = 88",
|
"[{\"level\":\"卓越人才卡\",\"enabled\":true,\"storeConsume\":true,\"consumeEnabled\":true,\"parkingType\":\"paid\",\"discount\":\"9\"}]");
|
jdbc.update("update transport_parking_card_level_right set status = 0");
|
assertThat(service.hasApplicableRule(88L, "卓越人才卡", "")).isFalse();
|
jdbc.update("update transport_parking_card_level_right set status = 1, charge_type = 'FREE'");
|
assertThat(service.hasApplicableRule(88L, "卓越人才卡", "")).isFalse();
|
jdbc.update("update transport_parking_card_level_right set charge_type = 'PAID', store_consume_enabled = 0");
|
assertThat(service.hasApplicableRule(88L, "卓越人才卡", "")).isFalse();
|
}
|
|
private void assertParkingDetailAndQuote() {
|
prepareConsumptionDetail();
|
for (String discount : new String[] {"6折", "7折", "10折"}) {
|
jdbc.update("update transport_parking_card_level_right set discount_text = ?", discount);
|
Map<String, Object> detail = service.detail(principal, "parking-scan");
|
assertThat(detail).containsEntry("cardLevel", "卓越人才卡");
|
java.util.List<?> rules = (java.util.List<?>) detail.get("rules");
|
assertThat(rules).hasSize(1);
|
Map<?, ?> rule = (Map<?, ?>) rules.get(0);
|
assertThat(rule.get("benefitText")).isEqualTo("到店消费" + discount);
|
Map<String, Object> quote = service.quote(principal, new ManualConsumptionCreateRequest(
|
"parking-scan", ((Number) rule.get("id")).longValue(), new BigDecimal("10")));
|
assertThat(quote.get("payAmount")).isEqualTo(Long.parseLong(discount.replace("折", "")) * 100);
|
}
|
}
|
|
@ParameterizedTest
|
@ValueSource(strings = {"carWash", "dental"})
|
void merchantUsesEachScannedCardLevelDiscountInsteadOfItsLabel(String merchantType) {
|
prepareConsumptionDetail();
|
String[] levels = {"卓越人才卡", "杰出人才卡", "菁英人才卡"};
|
String[] discounts = {"7", "8.5", "9"};
|
java.util.List<Map<String, Object>> rights = new java.util.ArrayList<>();
|
for (int index = 0; index < levels.length; index++) {
|
Map<String, Object> right = configuredRight(levels[index], discounts[index]);
|
right.put("tag", levels[index] + "到店消费可用");
|
right.put("enabled", true);
|
right.put("storeConsume", true);
|
right.put("consumeEnabled", true);
|
rights.add(right);
|
}
|
jdbc.update("insert into merchant(id, merchant_type_code, merchant_rights_config) values (88, ?, ?)",
|
merchantType, com.alibaba.fastjson.JSON.toJSONString(rights));
|
for (int index = 0; index < levels.length; index++) {
|
jdbc.update("update talent_card set card_level = ? where id = 1", levels[index]);
|
Map<String, Object> detail = service.detail(principal, "parking-scan");
|
assertThat(detail).containsEntry("cardLevel", levels[index]);
|
java.util.List<?> rules = (java.util.List<?>) detail.get("rules");
|
assertThat(rules).hasSize(1);
|
Map<?, ?> rule = (Map<?, ?>) rules.get(0);
|
assertThat(rule.get("benefitText")).isEqualTo("到店消费" + discounts[index] + "折");
|
Map<String, Object> quote = service.quote(principal, new ManualConsumptionCreateRequest(
|
"parking-scan", ((Number) rule.get("id")).longValue(), new BigDecimal("100")));
|
assertThat(quote.get("payAmount")).isEqualTo(new BigDecimal(discounts[index]).multiply(new BigDecimal("1000")).longValue());
|
}
|
}
|
|
private void prepareConsumptionDetail() {
|
jdbc.execute("alter table talent_dynamic_code add column talent_id bigint");
|
jdbc.execute("alter table talent_dynamic_code add column card_id bigint");
|
jdbc.execute("""
|
create table merchant_verification_attempt (
|
id bigint, dynamic_code_id bigint, status tinyint, expires_at timestamp, manual_order_id bigint,
|
verification_id varchar(64), credential_type varchar(64), merchant_id bigint, store_id bigint, operator_id bigint
|
)
|
""");
|
jdbc.execute("create table talent_card (id bigint, card_level varchar(64), deleted tinyint)");
|
jdbc.execute("create table talent_user (id bigint, name varchar(64), mobile varchar(32), gender tinyint, source_area_type varchar(32), deleted tinyint)");
|
jdbc.execute("create table merchant_card_benefit (id bigint, merchant_id bigint, card_level varchar(64), benefit_text varchar(500), status tinyint, sort_no int)");
|
jdbc.execute("""
|
create table merchant_coupon (
|
id bigint, merchant_id bigint, coupon_name varchar(64), coupon_type varchar(64), coupon_amount decimal,
|
talent_levels varchar(64), condition_type varchar(64), condition_amount decimal,
|
valid_start date, valid_end date, deleted tinyint, status tinyint, store_consume tinyint
|
)
|
""");
|
jdbc.execute("create table benefit_order (coupon_id bigint, talent_id bigint, deleted tinyint, order_status tinyint, expires_at timestamp)");
|
jdbc.update("insert into talent_dynamic_code(id, status, talent_id, card_id) values (1, 0, 1, 1)");
|
jdbc.update("insert into talent_card values (1, '卓越人才卡', 0)");
|
jdbc.update("insert into talent_user values (1, '测试', '13800000000', 1, 'LOCAL', 0)");
|
jdbc.update("insert into merchant_verification_attempt values (1, 1, 0, null, null, 'parking-scan', 'TALENT_CODE', 88, 7, 9)");
|
}
|
|
@Test
|
void cultureHidesLegacyCouponsAndRejectsCouponQuotesAndOrders() {
|
prepareConsumptionDetail();
|
Map<String, Object> right = configuredRight("卓越人才卡", "8");
|
right.put("enabled", true);
|
right.put("storeConsume", true);
|
right.put("consumeEnabled", true);
|
jdbc.update("insert into merchant(id, merchant_type_code, merchant_rights_config) values (88, 'culture', ?)",
|
com.alibaba.fastjson.JSON.toJSONString(java.util.List.of(right)));
|
jdbc.update("""
|
insert into merchant_coupon (id, merchant_id, coupon_name, coupon_amount, talent_levels,
|
condition_type, deleted, status, store_consume)
|
values (1, 88, '旧优惠券', 20, '卓越人才卡', 'NONE', 0, 1, 1)
|
""");
|
Map<String, Object> detail = service.detail(principal, "parking-scan");
|
assertThat((java.util.List<?>) detail.get("coupons")).isEmpty();
|
ManualConsumptionCreateRequest discountOnly = new ManualConsumptionCreateRequest("parking-scan", -1L, new BigDecimal("100"));
|
assertThat(service.quote(principal, discountOnly)).containsEntry("payAmount", 8000L).containsEntry("couponAmount", 0L);
|
ManualConsumptionCreateRequest withCoupon = new ManualConsumptionCreateRequest("parking-scan", -1L, new BigDecimal("100"), 1L);
|
assertThatThrownBy(() -> service.quote(principal, withCoupon))
|
.isInstanceOf(com.talentcard.server.common.BusinessException.class).hasMessageContaining("不支持优惠券");
|
assertThatThrownBy(() -> service.create(principal, withCoupon, "culture-coupon-test"))
|
.isInstanceOf(com.talentcard.server.common.BusinessException.class).hasMessageContaining("不支持优惠券");
|
assertThat(jdbc.queryForObject("select status from talent_dynamic_code where id = 1", Integer.class)).isZero();
|
assertThat(jdbc.queryForObject("select count(*) from benefit_order", Integer.class)).isZero();
|
|
jdbc.update("update merchant set merchant_type_code = 'food' where id = 88");
|
assertThat((java.util.List<?>) service.detail(principal, "parking-scan").get("coupons")).hasSize(1);
|
assertThat(service.quote(principal, withCoupon)).containsEntry("payAmount", 6000L).containsEntry("couponAmount", 2000L);
|
}
|
|
@Test
|
void cultureCurrentBenefitUsesOnlyTheScannedLevelsStoreConsumptionDescription() {
|
prepareConsumptionDetail();
|
String[] levels = {"卓越人才卡", "杰出人才卡", "菁英人才卡"};
|
java.util.List<Map<String, Object>> rights = new java.util.ArrayList<>();
|
for (String level : levels) {
|
Map<String, Object> right = configuredRight(level, "8");
|
right.put("enabled", true);
|
right.put("storeConsume", true);
|
right.put("consumeEnabled", true);
|
right.put("detail", "其他权益详情");
|
right.put("consumeDesc", level + "到店消费权益\n第二行 保留空格");
|
rights.add(right);
|
}
|
jdbc.update("insert into merchant(id, merchant_type_code, merchant_rights_config) values (88, 'culture', ?)",
|
com.alibaba.fastjson.JSON.toJSONString(rights));
|
jdbc.update("insert into merchant_card_benefit values (1, 88, 'EXCELLENT', '旧权益文案', 1, 0)");
|
for (String level : levels) {
|
jdbc.update("update talent_card set card_level = ? where id = 1", level);
|
String expected = level + "到店消费权益\n第二行 保留空格";
|
assertThat(service.detail(principal, "parking-scan"))
|
.containsEntry("cardLevel", level).containsEntry("benefitText", expected);
|
assertThat(service.benefitTextForMerchant(88L, level)).isEqualTo(expected);
|
}
|
rights.get(0).put("consumeDesc", "");
|
rights.get(1).put("enabled", false);
|
jdbc.update("update merchant set merchant_rights_config = ? where id = 88", com.alibaba.fastjson.JSON.toJSONString(rights));
|
assertThat(service.benefitTextForMerchant(88L, levels[0])).isEmpty();
|
assertThat(service.benefitTextForMerchant(88L, levels[1])).isEmpty();
|
assertThat(service.benefitTextForMerchant(88L, "普通游客")).isEmpty();
|
}
|
|
@Test
|
void childcareCurrentBenefitUsesTheScannedLevelsRightsDescription() {
|
prepareConsumptionDetail();
|
String[] levels = {"卓越人才卡", "杰出人才卡", "菁英人才卡"};
|
java.util.List<Map<String, Object>> rights = new java.util.ArrayList<>();
|
for (String level : levels) {
|
Map<String, Object> right = configuredRight(level, "8");
|
right.put("enabled", true);
|
right.put("storeConsume", true);
|
right.put("consumeEnabled", true);
|
right.put("detail", level + "托管权益描述\n第二行内容");
|
right.put("consumeDesc", "不同的到店消费使用说明");
|
rights.add(right);
|
}
|
jdbc.update("insert into merchant(id, merchant_type_code, merchant_rights_config) values (88, 'children', ?)",
|
com.alibaba.fastjson.JSON.toJSONString(rights));
|
jdbc.update("insert into merchant_card_benefit values (1, 88, 'EXCELLENT', '旧权益文案', 1, 0)");
|
for (String level : levels) {
|
jdbc.update("update talent_card set card_level = ? where id = 1", level);
|
assertThat(service.detail(principal, "parking-scan"))
|
.containsEntry("cardLevel", level)
|
.containsEntry("benefitText", level + "托管权益描述\n第二行内容");
|
assertThat(service.benefitTextForMerchant(88L, level)).isEqualTo(level + "托管权益描述\n第二行内容");
|
}
|
|
rights.get(0).put("detail", "");
|
rights.get(1).put("enabled", false);
|
jdbc.update("update merchant set merchant_rights_config = ? where id = 88", com.alibaba.fastjson.JSON.toJSONString(rights));
|
assertThat(service.benefitTextForMerchant(88L, levels[0])).isEmpty();
|
assertThat(service.benefitTextForMerchant(88L, levels[1])).isEmpty();
|
assertThat(service.benefitTextForMerchant(88L, "普通游客")).isEmpty();
|
}
|
|
@Test
|
void calculatesConfiguredPercentageAndFixedReductionPrices() {
|
Map<String, Object> dentalRight = configuredRight("卓越人才卡", "");
|
dentalRight.put("consumeType", "折扣");
|
dentalRight.put("consumeDiscount", "7");
|
dentalRight.put("consumeDiscountSubsidy", true);
|
Map<String, Object> dentalAmounts = service.quote(service.configuredRule(dentalRight), null, new BigDecimal("100.00"));
|
assertThat(dentalAmounts).containsEntry("originalAmount", 10000L).containsEntry("payAmount", 7000L)
|
.containsEntry("discountAmount", 3000L).containsEntry("subsidyAmount", 3000L);
|
|
Map<String, Object> hotelRight = configuredRight("卓越人才卡(外地)", "");
|
hotelRight.put("consumeType", "优惠");
|
hotelRight.put("consumeCoupon", "80");
|
hotelRight.put("consumeCouponSubsidy", true);
|
Map<String, Object> hotelAmounts = service.quote(service.configuredRule(hotelRight), null, new BigDecimal("150.00"));
|
assertThat(hotelAmounts).containsEntry("originalAmount", 15000L).containsEntry("payAmount", 7000L).containsEntry("discountAmount", 8000L);
|
|
Map<String, Object> fullyReducedAmounts = service.quote(service.configuredRule(hotelRight), null, new BigDecimal("50.00"));
|
assertThat(fullyReducedAmounts).containsEntry("payAmount", 0L).containsEntry("discountAmount", 5000L);
|
}
|
|
@Test
|
void structuredRightsDoNotFallBackToLegacySeedRules() {
|
jdbc.update("insert into merchant(id, merchant_rights_config) values (?, ?)", 88L,
|
"[{\"level\":\"卓越人才卡\",\"enabled\":true,\"storeConsume\":false,\"consumeEnabled\":false}]");
|
jdbc.update("""
|
insert into merchant_store_consume_rule(id, merchant_id, card_level, rule_name, discount_rate, subsidy_enabled, status)
|
values (1, 88, 'EXCELLENT', '历史默认9折', 0.9000, 1, 1)
|
""");
|
|
assertThat(service.hasApplicableRule(88L, "卓越人才卡", "OUTSIDE")).isFalse();
|
}
|
|
@Test
|
void matchesExcellentHotelConfigByTalentSourceArea() {
|
assertThat(service.matchesConfiguredLevel("卓越人才卡(外地)", "卓越人才卡", "OUTSIDE")).isTrue();
|
assertThat(service.matchesConfiguredLevel("卓越人才卡(本地)", "卓越人才卡", "OUTSIDE")).isFalse();
|
assertThat(service.matchesConfiguredLevel("卓越人才卡(本地)", "卓越人才卡", "LOCAL")).isTrue();
|
}
|
|
@Test
|
void benefitTextUsesConfiguredConsumeDescForCardLevel() {
|
jdbc.execute("""
|
create table merchant_card_benefit (
|
id bigint auto_increment primary key,
|
merchant_id bigint not null,
|
card_level varchar(64),
|
benefit_text varchar(500),
|
status tinyint not null default 1,
|
sort_no int not null default 0
|
)
|
""");
|
jdbc.update("insert into merchant(id, merchant_rights_config) values (?, ?)", 88L,
|
"[{\"level\":\"卓越人才卡(本地)\",\"enabled\":true,\"storeConsume\":true,\"consumeEnabled\":true,\"consumeDesc\":\"local-excellent-desc\"},"
|
+ "{\"level\":\"卓越人才卡(外地)\",\"enabled\":true,\"storeConsume\":true,\"consumeEnabled\":true,\"consumeDesc\":\"outside-excellent-desc\"},"
|
+ "{\"level\":\"杰出人才卡\",\"enabled\":true,\"storeConsume\":true,\"consumeEnabled\":true,\"consumeDesc\":\"outstanding-desc\"}]");
|
|
assertThat(service.benefitTextForMerchant(88L, "卓越人才卡", "OUTSIDE")).isEqualTo("outside-excellent-desc");
|
assertThat(service.benefitTextForMerchant(88L, "卓越人才卡", "LOCAL")).isEqualTo("local-excellent-desc");
|
assertThat(service.benefitTextForMerchant(88L, "杰出人才卡")).isEqualTo("outstanding-desc");
|
assertThat(service.benefitTextForMerchant(88L, "卓越人才卡")).isEqualTo("local-excellent-desc");
|
}
|
|
private Map<String, Object> configuredRight(String level, String discount) {
|
Map<String, Object> right = new LinkedHashMap<>();
|
right.put("level", level);
|
right.put("tag", "到店消费优惠");
|
right.put("discount", discount);
|
right.put("governmentSubsidy", true);
|
return right;
|
}
|
}
|