fix: align merchant consumption benefit matching
| | |
| | | let cancelDetailRequest: (() => void) | null = null |
| | | let cancelQuoteRequest: (() => void) | null = null |
| | | let cancelCreateRequest: (() => void) | null = null |
| | | let quoteTimer = 0 |
| | | |
| | | const formatMoney = (amount: number): string => (amount / 100).toFixed(2) |
| | | const amountValue = (): number => Number(amountText.value) |
| | | const validAmount = (): boolean => /^\d+(\.\d{1,2})?$/.test(amountText.value) && amountValue() > 0 |
| | | const canSubmit = computed((): boolean => validAmount() && selectedRuleId.value.length > 0 && quote.value.payAmount >= 0 && quote.value.originalAmount > 0 && !submitting.value) |
| | | const quoteMatchesAmount = (): boolean => quote.value.originalAmount == Math.round(amountValue() * 100) |
| | | const canSubmit = computed((): boolean => validAmount() && selectedRuleId.value.length > 0 && quote.value.payAmount >= 0 && quoteMatchesAmount() && !submitting.value) |
| | | const selectedRuleName = (): string => { |
| | | for (let index = 0; index < rules.value.length; index++) { |
| | | if (rules.value[index].id == selectedRuleId.value) return rules.value[index].name |
| | |
| | | } |
| | | |
| | | const quoteCurrentAmount = () => { |
| | | if (quoteTimer != 0) { clearTimeout(quoteTimer); quoteTimer = 0 } |
| | | if (cancelQuoteRequest != null) { cancelQuoteRequest(); cancelQuoteRequest = null } |
| | | if (!validAmount()) { if (amountText.value.length > 0) amountError.value = '请输入正确的消费金额'; resetQuote(); return } |
| | | if (selectedRuleId.value.length == 0) { resetQuote(); return } |
| | |
| | | } |
| | | |
| | | const onAmountInput = (event: any) => { |
| | | if (quoteTimer != 0) { clearTimeout(quoteTimer); quoteTimer = 0 } |
| | | if (cancelQuoteRequest != null) { cancelQuoteRequest(); cancelQuoteRequest = null } |
| | | const input = event != null && event.detail != null && event.detail.value != null ? '' + event.detail.value : '' |
| | | amountText.value = input.replace(/[^0-9.]/g, '').replace(/(\..*)\./g, '$1') |
| | | amountError.value = '' |
| | | resetQuote() |
| | | if (validAmount() && selectedRuleId.value.length > 0) { |
| | | quoteTimer = setTimeout(() => { quoteTimer = 0; quoteCurrentAmount() }, 350) |
| | | } |
| | | } |
| | | |
| | | const selectRule = (ruleId: string) => { |
| | |
| | | loadDetail() |
| | | }) |
| | | |
| | | onUnload(() => cancelRequests()) |
| | | onUnload(() => { if (quoteTimer != 0) clearTimeout(quoteTimer); cancelRequests() }) |
| | | </script> |
| | | |
| | | <style> |
| | |
| | | } |
| | | rightsConfigs.splice(0, rightsConfigs.length, ...defaultMerchantRights().map((item) => { |
| | | const saved = Array.isArray(savedRights) ? savedRights.find((entry) => entry?.level === item.level) : undefined |
| | | return { |
| | | const merged = { |
| | | ...item, |
| | | ...(saved || {}), |
| | | consumeEnabled: item.consumeEnabled |
| | | } |
| | | if (isParkingMerchant.value && merged.parkingType === 'paid') merged.consumeEnabled = true |
| | | return merged |
| | | })) |
| | | syncCarWashJumpMeta() |
| | | } |
| | |
| | | const payload = { ...item } |
| | | const supportsItemStoreConsumption = supportsStoreConsumption.value |
| | | || (isParkingMerchant.value && item.parkingType === 'paid') |
| | | if (isParkingMerchant.value && item.parkingType === 'paid') { |
| | | payload.consumeEnabled = true |
| | | } |
| | | if (!supportsItemStoreConsumption) { |
| | | payload.storeConsume = false |
| | | payload.discount = '' |
| | |
| | | result.put("customerName", displayName(string(source.get("talentName")), number(source.get("gender")))); |
| | | result.put("customerMobile", maskMobile(string(source.get("talentMobile")))); |
| | | result.put("cardLevel", string(source.get("cardLevel"))); |
| | | result.put("benefitText", benefitText(principal.merchantId(), string(source.get("cardLevel")))); |
| | | result.put("rules", rules(principal.merchantId(), string(source.get("cardLevel")))); |
| | | List<Map<String, Object>> matchedRules = rules(principal.merchantId(), string(source.get("cardLevel")), string(source.get("sourceAreaType"))); |
| | | if (matchedRules.isEmpty()) throw new BusinessException(ErrorCode.CONFLICT, "当前商户未配置适用于该人才卡的到店消费权益"); |
| | | result.put("benefitText", benefitText(principal.merchantId(), string(source.get("cardLevel")), string(source.get("sourceAreaType")))); |
| | | result.put("rules", matchedRules); |
| | | result.put("coupons", coupons(principal.merchantId(), number(source.get("talentId")), string(source.get("cardLevel")))); |
| | | result.put("expiresAt", source.get("expiresAt")); |
| | | return result; |
| | |
| | | public Map<String, Object> quote(MerchantPrincipal principal, ManualConsumptionCreateRequest request) { |
| | | Map<String, Object> source = source(principal, request.verificationId(), false); |
| | | ensurePending(source); |
| | | Map<String, Object> rule = rule(principal.merchantId(), string(source.get("cardLevel")), request.ruleId()); |
| | | Map<String, Object> rule = rule(principal.merchantId(), string(source.get("cardLevel")), string(source.get("sourceAreaType")), request.ruleId()); |
| | | Map<String, Object> coupon = usableCoupon(principal.merchantId(), number(source.get("talentId")), string(source.get("cardLevel")), request.couponId(), discountedAmount(rule, request.amount())); |
| | | return quote(rule, coupon, request.amount()); |
| | | } |
| | |
| | | return createdOrder(existingOrderId); |
| | | } |
| | | ensurePending(source); |
| | | Map<String, Object> rule = rule(principal.merchantId(), string(source.get("cardLevel")), request.ruleId()); |
| | | Map<String, Object> rule = rule(principal.merchantId(), string(source.get("cardLevel")), string(source.get("sourceAreaType")), request.ruleId()); |
| | | Map<String, Object> coupon = usableCoupon(principal.merchantId(), number(source.get("talentId")), string(source.get("cardLevel")), request.couponId(), discountedAmount(rule, request.amount())); |
| | | Map<String, Object> amounts = quote(rule, coupon, request.amount()); |
| | | Long dynamicCodeId = number(source.get("dynamicCodeId")); |
| | |
| | | List<Map<String, Object>> sources = rows(""" |
| | | select a.id attempt_id, a.dynamic_code_id, a.status attempt_status, a.expires_at, a.manual_order_id, |
| | | d.talent_id, d.card_id, c.card_level, |
| | | t.name talent_name, t.mobile talent_mobile, t.gender |
| | | t.name talent_name, t.mobile talent_mobile, t.gender, t.source_area_type |
| | | from merchant_verification_attempt a |
| | | join talent_dynamic_code d on d.id = a.dynamic_code_id |
| | | join talent_card c on c.id = d.card_id and c.deleted = 0 |
| | |
| | | } |
| | | } |
| | | |
| | | private List<Map<String, Object>> rules(Long merchantId, String cardLevel) { |
| | | List<Map<String, Object>> configured = configuredRules(merchantId, cardLevel); |
| | | if (!configured.isEmpty()) return configured; |
| | | private List<Map<String, Object>> rules(Long merchantId, String cardLevel, String sourceAreaType) { |
| | | List<Map<String, Object>> configured = configuredRules(merchantId, cardLevel, sourceAreaType); |
| | | if (!configured.isEmpty() || hasStructuredRightsConfig(merchantId)) return configured; |
| | | return rows(""" |
| | | select id, card_level, rule_name, discount_rate, subsidy_enabled |
| | | from merchant_store_consume_rule |
| | |
| | | }).toList(); |
| | | } |
| | | |
| | | private List<Map<String, Object>> configuredRules(Long merchantId, String cardLevel) { |
| | | boolean hasApplicableRule(Long merchantId, String cardLevel, String sourceAreaType) { |
| | | return !rules(merchantId, cardLevel, sourceAreaType).isEmpty(); |
| | | } |
| | | |
| | | private boolean hasStructuredRightsConfig(Long merchantId) { |
| | | List<Map<String, Object>> merchants = rows("select merchant_rights_config from merchant where id = ? and deleted = 0 limit 1", merchantId); |
| | | if (merchants.isEmpty()) return false; |
| | | Object raw = merchants.get(0).get("merchantRightsConfig"); |
| | | return raw != null && !string(raw).isBlank(); |
| | | } |
| | | |
| | | private List<Map<String, Object>> configuredRules(Long merchantId, String cardLevel, String sourceAreaType) { |
| | | List<Map<String, Object>> merchants = rows("select merchant_rights_config from merchant where id = ? and deleted = 0 limit 1", merchantId); |
| | | if (merchants.isEmpty()) return List.of(); |
| | | Object raw = merchants.get(0).get("merchantRightsConfig"); |
| | |
| | | } catch (Exception ignored) { |
| | | return List.of(); |
| | | } |
| | | return configs.stream().filter(right -> booleanValue(right.get("enabled")) && booleanValue(right.get("consumeEnabled")) && booleanValue(right.get("storeConsume")) && matchesCardLevel(string(right.get("level")), cardLevel)).map(this::configuredRule).filter(rule -> decimal(rule.get("discountRate")).signum() > 0).toList(); |
| | | return configs.stream() |
| | | .filter(right -> booleanValue(right.get("enabled")) |
| | | && configuredStoreConsumptionEnabled(right) |
| | | && matchesConfiguredLevel(string(right.get("level")), cardLevel, sourceAreaType)) |
| | | .map(this::configuredRule) |
| | | .filter(rule -> decimal(rule.get("discountRate")).signum() > 0 || decimal(rule.get("fixedReduction")).signum() > 0) |
| | | .toList(); |
| | | } |
| | | |
| | | private Map<String, Object> configuredRule(Map<String, Object> right) { |
| | | boolean configuredStoreConsumptionEnabled(Map<String, Object> right) { |
| | | if (!booleanValue(right.get("storeConsume"))) return false; |
| | | return booleanValue(right.get("consumeEnabled")) || "paid".equalsIgnoreCase(string(right.get("parkingType"))); |
| | | } |
| | | |
| | | Map<String, Object> configuredRule(Map<String, Object> right) { |
| | | String level = normalizedCardLevel(string(right.get("level"))); |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("id", configRuleId(level)); |
| | | result.put("cardLevel", level); |
| | | result.put("ruleName", string(right.get("tag")).isBlank() ? "到店消费优惠" : right.get("tag")); |
| | | result.put("name", result.get("ruleName")); |
| | | result.put("discountRate", configuredDiscountRate(right.get("discount"))); |
| | | result.put("subsidyEnabled", booleanValue(right.get("governmentSubsidy")) ? 1 : 0); |
| | | result.put("discountRate", configuredDiscountRate(right)); |
| | | result.put("fixedReduction", configuredFixedReduction(right)); |
| | | result.put("subsidyEnabled", configuredSubsidyEnabled(right) ? 1 : 0); |
| | | result.put("merchantDetail", right.get("detail")); |
| | | result.put("consumeDesc", right.get("consumeDesc")); |
| | | result.put("benefitText", configuredBenefitText(right)); |
| | | return result; |
| | | } |
| | | |
| | | private Map<String, Object> rule(Long merchantId, String cardLevel, Long ruleId) { |
| | | List<Map<String, Object>> configured = configuredRules(merchantId, cardLevel); |
| | | private Map<String, Object> rule(Long merchantId, String cardLevel, String sourceAreaType, Long ruleId) { |
| | | List<Map<String, Object>> configured = configuredRules(merchantId, cardLevel, sourceAreaType); |
| | | for (Map<String, Object> candidate : configured) if (String.valueOf(candidate.get("id")).equals(String.valueOf(ruleId))) return candidate; |
| | | List<Map<String, Object>> found = rows(""" |
| | | select id, card_level, rule_name, discount_rate, subsidy_enabled |
| | |
| | | return switch (level) { case "EXCELLENT" -> -1L; case "OUTSTANDING" -> -2L; case "ELITE" -> -3L; default -> -9L; }; |
| | | } |
| | | |
| | | private BigDecimal configuredDiscountRate(Object value) { |
| | | private BigDecimal configuredDiscountRate(Map<String, Object> right) { |
| | | try { |
| | | Object value = "折扣".equals(string(right.get("consumeType"))) ? right.get("consumeDiscount") : right.get("discount"); |
| | | BigDecimal discount = decimal(string(value)); |
| | | return discount.signum() > 0 && discount.compareTo(BigDecimal.TEN) <= 0 ? discount.divide(BigDecimal.TEN, 4, RoundingMode.HALF_UP) : BigDecimal.ZERO; |
| | | } catch (Exception ignored) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | } |
| | | |
| | | private BigDecimal configuredFixedReduction(Map<String, Object> right) { |
| | | if (!"优惠".equals(string(right.get("consumeType")))) return BigDecimal.ZERO; |
| | | try { |
| | | BigDecimal reduction = decimal(string(right.get("consumeCoupon"))); |
| | | return reduction.signum() > 0 ? reduction : BigDecimal.ZERO; |
| | | } catch (Exception ignored) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | } |
| | | |
| | | private boolean configuredSubsidyEnabled(Map<String, Object> right) { |
| | | String consumeType = string(right.get("consumeType")); |
| | | if ("折扣".equals(consumeType)) return booleanValue(right.get("consumeDiscountSubsidy")); |
| | | if ("优惠".equals(consumeType)) return booleanValue(right.get("consumeCouponSubsidy")); |
| | | return booleanValue(right.get("governmentSubsidy")); |
| | | } |
| | | |
| | | private boolean booleanValue(Object value) { |
| | |
| | | return "true".equals(text) || "1".equals(text) || "yes".equals(text); |
| | | } |
| | | |
| | | private Map<String, Object> quote(Map<String, Object> rule, Map<String,Object> coupon, BigDecimal inputAmount) { |
| | | Map<String, Object> quote(Map<String, Object> rule, Map<String,Object> coupon, BigDecimal inputAmount) { |
| | | long originalAmount = inputAmount.movePointRight(2).setScale(0, RoundingMode.HALF_UP).longValueExact(); |
| | | long payAmount = discountedAmountCents(rule, originalAmount); |
| | | long basePayAmount = payAmount; |
| | | long couponAmount = coupon == null ? 0 : Math.min(decimal(coupon.get("couponAmount")).movePointRight(2).setScale(0, RoundingMode.HALF_UP).longValue(), basePayAmount); |
| | | payAmount = basePayAmount - couponAmount; |
| | | long discountAmount = originalAmount - payAmount; |
| | | long subsidyAmount = originalAmount - payAmount; |
| | | long subsidyAmount = booleanValue(rule.get("subsidyEnabled")) ? discountAmount : 0; |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("originalAmount", originalAmount); |
| | | result.put("discountAmount", discountAmount); |
| | |
| | | } |
| | | |
| | | private long discountedAmountCents(Map<String, Object> rule, long originalAmount) { |
| | | BigDecimal fixedReduction = rule.get("fixedReduction") == null ? BigDecimal.ZERO : decimal(rule.get("fixedReduction")); |
| | | if (fixedReduction.signum() > 0) { |
| | | long reductionCents = fixedReduction.movePointRight(2).setScale(0, RoundingMode.HALF_UP).longValueExact(); |
| | | return Math.max(0, originalAmount - reductionCents); |
| | | } |
| | | BigDecimal rate = decimal(rule.get("discountRate")); |
| | | if (rate.signum() <= 0 || rate.compareTo(BigDecimal.ONE) > 0) throw new BusinessException(ErrorCode.CONFLICT, "商户优惠规则配置无效"); |
| | | return BigDecimal.valueOf(originalAmount).multiply(rate).setScale(0, RoundingMode.HALF_UP).longValueExact(); |
| | |
| | | return result; |
| | | } |
| | | |
| | | private String benefitText(Long merchantId, String cardLevel) { |
| | | private String benefitText(Long merchantId, String cardLevel, String sourceAreaType) { |
| | | List<Map<String, Object>> benefits = rows(""" |
| | | select card_level, benefit_text from merchant_card_benefit |
| | | where merchant_id = ? and status = 1 order by sort_no desc, id desc |
| | | """, merchantId); |
| | | String configuredDescription = configuredRules(merchantId, cardLevel).stream() |
| | | String configuredDescription = configuredRules(merchantId, cardLevel, sourceAreaType).stream() |
| | | .map(rule -> string(rule.get("consumeDesc"))) |
| | | .filter(value -> !value.isBlank()) |
| | | .findFirst() |
| | | .orElse(""); |
| | | if (configuredDescription.isBlank()) { |
| | | configuredDescription = configuredRules(merchantId, cardLevel).stream() |
| | | configuredDescription = configuredRules(merchantId, cardLevel, sourceAreaType).stream() |
| | | .map(rule -> string(rule.get("merchantDetail"))) |
| | | .filter(value -> !value.isBlank()) |
| | | .findFirst() |
| | | .orElse(""); |
| | | } |
| | | if (configuredDescription.isBlank()) { |
| | | configuredDescription = configuredRules(merchantId, cardLevel).stream() |
| | | configuredDescription = configuredRules(merchantId, cardLevel, sourceAreaType).stream() |
| | | .map(rule -> string(rule.get("benefitText"))) |
| | | .filter(value -> !value.isBlank()) |
| | | .findFirst() |
| | |
| | | } |
| | | |
| | | String benefitTextForMerchant(Long merchantId, String cardLevel) { |
| | | return benefitText(merchantId, cardLevel); |
| | | return benefitText(merchantId, cardLevel, ""); |
| | | } |
| | | |
| | | boolean matchesConfiguredLevel(String configured, String actual, String sourceAreaType) { |
| | | if (!"EXCELLENT".equals(normalizedCardLevel(actual))) return matchesCardLevel(configured, actual); |
| | | String configuredUpper = configured.trim().toUpperCase(); |
| | | String areaUpper = sourceAreaType.trim().toUpperCase(); |
| | | if (configuredUpper.contains("本地") || configuredUpper.contains("LOCAL")) { |
| | | return areaUpper.contains("LOCAL") || areaUpper.contains("本地"); |
| | | } |
| | | if (configuredUpper.contains("外地") || configuredUpper.contains("OUTSIDE")) { |
| | | return areaUpper.contains("OUTSIDE") || areaUpper.contains("外地"); |
| | | } |
| | | return matchesCardLevel(configured, actual); |
| | | } |
| | | |
| | | private boolean matchesCardLevel(String configured, String actual) { |
| | |
| | | private static final String FREE_STAY_CODE = "FREE_STAY_CODE"; |
| | | |
| | | private final JdbcTemplate jdbc; |
| | | private final ManualConsumptionService manualConsumptionService; |
| | | |
| | | public MerchantVerificationService(JdbcTemplate jdbc) { |
| | | public MerchantVerificationService(JdbcTemplate jdbc, ManualConsumptionService manualConsumptionService) { |
| | | this.jdbc = jdbc; |
| | | this.manualConsumptionService = manualConsumptionService; |
| | | } |
| | | |
| | | /** |
| | |
| | | Long expectedMerchantId = number(code.get("expectedMerchantId")); |
| | | if (expectedMerchantId != null && !expectedMerchantId.equals(principal.merchantId())) { |
| | | throw new BusinessException(ErrorCode.CONFLICT, "该人才码不可在当前商户核销"); |
| | | } |
| | | if (!manualConsumptionService.hasApplicableRule(principal.merchantId(), value(code.get("cardLevel"), ""), value(code.get("sourceAreaType"), ""))) { |
| | | throw new BusinessException(ErrorCode.CONFLICT, "当前商户未配置适用于该人才卡的到店消费权益"); |
| | | } |
| | | String verificationId = createAttempt(principal, TALENT_CODE, number(code.get("dynamicCodeId")), null, null, code.get("expiresAt")); |
| | | return scanResult(verificationId, TALENT_CODE, "人才码有效,请录入消费金额并发送待支付订单", code.get("talentName"), |
| | |
| | | List<Map<String, Object>> codes = rows(""" |
| | | select d.id dynamic_code_id, d.expected_merchant_id, d.expires_at, |
| | | c.id card_id, c.card_level, c.status card_status, c.valid_end, |
| | | t.id talent_id, t.name talent_name, t.mobile talent_mobile, t.status talent_status |
| | | t.id talent_id, t.name talent_name, t.mobile talent_mobile, t.status talent_status, t.source_area_type |
| | | from talent_dynamic_code d |
| | | join talent_card c on c.id = d.card_id and c.deleted = 0 |
| | | join talent_user t on t.id = d.talent_id and t.deleted = 0 |
| | |
| | | |
| | | import static org.assertj.core.api.Assertions.assertThat; |
| | | |
| | | 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; |
| | |
| | | used_at timestamp |
| | | ) |
| | | """); |
| | | jdbc.execute(""" |
| | | create table merchant ( |
| | | id bigint primary key, |
| | | 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"); |
| | | } |
| | |
| | | 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(); |
| | | } |
| | | |
| | | @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(); |
| | | } |
| | | |
| | | 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; |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | @Test |
| | | void talentCodeRejectsMerchantWithoutApplicableStoreConsumptionRule() { |
| | | jdbc.update("delete from merchant_store_consume_rule where merchant_id = ?", merchantId); |
| | | |
| | | assertThatThrownBy(() -> verificationService.scan(principal, new VerificationScanRequest(token))) |
| | | .isInstanceOf(com.talentcard.server.common.BusinessException.class) |
| | | .hasMessageContaining("未配置适用于该人才卡的到店消费权益"); |
| | | } |
| | | |
| | | @Test |
| | | void talentCodeStoreConsumptionUsesConfiguredCouponAfterTalentDiscount() { |
| | | Map<String, Object> scanned = verificationService.scan(principal, new VerificationScanRequest(token)); |
| | | String verificationId = String.valueOf(scanned.get("verificationId")); |