소스 검색

chore: auto bump version and changelog [skip ci]

iwt 5 일 전
부모
커밋
b3e18f84ef

+ 13 - 0
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java

@@ -10933,6 +10933,7 @@ log.info("迁移298: 已为无 openid 的 child 账号补齐 family_members 记
         backfillHealthPlanTaskPrerequisite();
         backfillTemplateInstanceFamilyMemberId();
         createReportTransferRequestsTable();
+        addProductMinDiscountLevel();
 
         // 迁移341: BASIC 体验会员 features JSON 补 referral_commission(迁移315 未包含,前端对比表已显示可参与)
         try {
@@ -11646,4 +11647,16 @@ log.info("迁移298: 已为无 openid 的 child 账号补齐 family_members 记
             log.warn("迁移349: 创建 report_transfer_requests 表失败: {}", e.getMessage());
         }
     }
+
+    // 迁移350: products 表添加 min_discount_level 列(商品粒度控制会员价门槛,NULL=所有会员均可享会员价)
+    private void addProductMinDiscountLevel() {
+        try {
+            jdbcTemplate.execute("ALTER TABLE products ADD COLUMN min_discount_level VARCHAR(50) DEFAULT NULL " +
+                    "COMMENT '享受会员价所需的最低会员级别编码(FREE/BASIC/FAMILY/PREMIUM/LIFETIME,NULL=所有会员均可)' AFTER min_visible_level");
+            log.info("迁移350: 已添加 products.min_discount_level 列");
+        } catch (Exception e) {
+            // 列已存在,忽略
+            log.debug("迁移350: products.min_discount_level 列已存在,跳过");
+        }
+    }
 }

+ 2 - 0
cfc-backend/src/main/java/com/etotem/cfc/dto/ProductDTO.java

@@ -34,6 +34,7 @@ public class ProductDTO {
     private Integer deliveryMethod;        // 配送方式: 1=快递 2=自提 3=两者皆可
     private List<String> visibleRoles;     // 可见角色列表,NULL=全部角色可见(已废弃,保留兼容)
     private String minVisibleLevel;        // 最低可见会员级别编码,NULL=全部可见
+    private String minDiscountLevel;       // 享受会员价所需的最低会员级别,NULL=所有会员均可
     private List<String> imageList;
     private Long energyDimensionId;
     private Integer effectivePpoint;     // 有效P点(基点,如1000=10%)
@@ -123,6 +124,7 @@ public class ProductDTO {
             }
         }
         d.minVisibleLevel = p.getMinVisibleLevel();
+        d.minDiscountLevel = p.getMinDiscountLevel();
         d.createdAt = p.getCreatedAt();
         d.updatedAt = p.getUpdatedAt();
         // 会员体系重构:商品对所有用户统一定价(活动侧保留会员价,商品侧停用)

+ 4 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/Product.java

@@ -55,6 +55,9 @@ public class Product implements Serializable {
     /** 最低可见会员级别编码(NULL=全部可见): FREE/FAMILY/PROVIDER/PREMIUM/LIFETIME */
     private String minVisibleLevel;
 
+    /** 享受会员价所需的最低会员级别编码(NULL=所有会员均可;BASIC/FAMILY/PREMIUM等) */
+    private String minDiscountLevel;
+
     // 佣金系统 - 平台利润率(千分比)
     private Integer profitRate;
 
@@ -99,6 +102,7 @@ public class Product implements Serializable {
     public Integer getSortOrder() { return sortOrder; }
     public String getVisibleRoles() { return visibleRoles; }
     public String getMinVisibleLevel() { return minVisibleLevel; }
+    public String getMinDiscountLevel() { return minDiscountLevel; }
     public Date getCreatedAt() { return createdAt; }
     public Date getUpdatedAt() { return updatedAt; }
 }

+ 39 - 4
cfc-backend/src/main/java/com/etotem/cfc/service/ProductOrderService.java

@@ -240,8 +240,8 @@ public class ProductOrderService {
         order.setBuyerId(buyerId);
         order.setFamilyId(buyer != null ? buyer.getFamilyId() : null);
         order.setQuantity(dto.getQuantity());
-        order.setUnitPrice(product.getPrice());
-        order.setTotalAmount(product.getPrice() * dto.getQuantity());
+        order.setUnitPrice(getEligibleMemberPrice(product, buyerId));
+        order.setTotalAmount(getEligibleMemberPrice(product, buyerId) * dto.getQuantity());
         order.setPointsUsed(dto.getPointsUsed() != null ? dto.getPointsUsed() : 0);
         int pointsCost = dto.getPointsUsed() != null ? dto.getPointsUsed() : 0;
         order.setPointsCost(pointsCost);
@@ -496,7 +496,7 @@ public class ProductOrderService {
                     return Result.error(bundleCheck.getMessage());
                 }
             }
-            int unitPrice = product.getPrice();
+            int unitPrice = getEligibleMemberPrice(product, buyerId);
             if (item.getSkuId() != null) {
                 ProductSku sku = productSkuService.getById(item.getSkuId());
                 if (sku != null) {
@@ -1559,9 +1559,44 @@ public class ProductOrderService {
             order.setRemark("供应商审核修改: " + changeLog.toString());
         }
 
-        // ===== 6. 更新订单时间 =====
+        // ===== 7. 组装返回 =====
         order.setUpdatedAt(new Date());
         orderMapper.updateById(order);
+    }
+
+    /**
+     * 获取用户有效的会员价。
+     * 规则:如果产品 min_discount_level 为 NULL,所有会员均可享受 memberPrice;
+     * 如果产品 min_discount_level 设置(如 BASIC/FAMILY/PREMIUM),
+     * 则仅当用户会员等级 >= min_discount_level 时才返回 memberPrice,
+     * 否则返回原价 product.getPrice()。
+     */
+    private Integer getEligibleMemberPrice(Product product, Long buyerId) {
+        if (product == null || product.getMemberPrice() == null || product.getMemberPrice() <= 0) {
+            return product != null ? product.getPrice() : 0;
+        }
+        // 产品未设置最低级别,所有会员均可享会员价
+        if (product.getMinDiscountLevel() == null) {
+            return product.getMemberPrice();
+        }
+        // 已设置最低级别,检查用户会员等级
+        String userLevel = "FREE";
+        if (buyerId != null) {
+            User user = userMapper.selectById(buyerId);
+            if (user != null) {
+                userLevel = membershipService.getMemberLevel(userId);
+            }
+        }
+        // 比较级别数字:级别数字越小越低
+        int userLevelOrder = membershipService.getLevelOrder(userLevel);
+        int minLevelOrder = membershipService.getLevelOrder(product.getMinDiscountLevel());
+        // 用户级别 >= 最低要求级别,则可享会员价
+        if (userLevelOrder >= minLevelOrder) {
+            return product.getMemberPrice();
+        }
+        return product.getPrice();
+    }
+}
 
         // ===== 7. 组装返回 =====
         ProductOrderDTO dto = ProductOrderDTO.from(order);

+ 1 - 0
cfc-backend/src/main/resources/schema.sql

@@ -1189,6 +1189,7 @@ CREATE TABLE IF NOT EXISTS products (
     family_scope TINYINT DEFAULT 1 COMMENT '是否面向家庭: 1=家庭成员消费过即排除, 0=仅按个人消费排除',
     visible_roles JSON DEFAULT NULL COMMENT '可见角色: ["parent","child","teacher","admin"],NULL=全部可见',
     min_visible_level VARCHAR(50) DEFAULT NULL COMMENT '最低可见会员级别编码(FREE/FAMILY/PROVIDER/PREMIUM/LIFETIME,NULL=全部可见)',
+    min_discount_level VARCHAR(50) DEFAULT NULL COMMENT '享受会员价所需的最低会员级别编码(FREE/BASIC/FAMILY/PREMIUM/LIFETIME,NULL=所有会员均可)',
     created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
     updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-f0293b4b112a3298668129791685a23f79282cb4
+d82bb8d59974ac6e3dc963b0f835d72be7accc85

+ 2 - 2
cfc-web/package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "cfc-web",
-  "version": "1.0.1529",
+  "version": "1.0.1530",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "cfc-web",
-      "version": "1.0.1529",
+      "version": "1.0.1530",
       "dependencies": {
         "@wangeditor/editor": "^5.1.23",
         "@wangeditor/editor-for-vue": "^1.0.2",

+ 1 - 1
cfc-web/package.json

@@ -1,6 +1,6 @@
 {
   "name": "cfc-web",
-  "version": "1.0.1530",
+  "version": "1.0.1531",
   "private": true,
   "scripts": {
     "dev": "vue-cli-service serve",

+ 16 - 0
cfc-web/public/CHANGELOG-v1.0.md

@@ -4,6 +4,22 @@
 
 ---
 
+## v1.0.1531 (2026-10-07)
+
+### Bug 修复
+- 会员判定取个人与家庭身份中更高级别,防止家庭同步覆盖更高个人会员
+
+### 其他
+-   max 比较取更高者,覆盖全部用户侧调用方(折扣/可见等级/权限/赠礼前置校验等)
+- - resolveEffectiveLevel: level_order 比较,平级时优先非 FREE(当前 FREE=BASIC=1 防同级覆盖)
+- - syncMembershipToFamilyMembers/syncMembershipForNewMember: 家庭同步只升不降,
+-   成员已有更高级别个人身份时保留,避免覆盖
+- - downgradeFamilyMembers: 按家庭历史最高 level_order 守卫,仅降级不高于该级别的成员,
+-   保留 LIFETIME/PREMIUM 等纯个人身份
+- - 新增 MembershipServiceTest 17 用例(纯 Mockito)
+- 
+
+
 ## v1.0.1530 (2026-10-07)
 
 ### Bug 修复

+ 17 - 1
cfc-web/public/CHANGELOG.md

@@ -1,6 +1,6 @@
 # 更新日志
 
-> 当前版本: v1.0.1530
+> 当前版本: v1.0.1531
 
 ## 历史版本
 
@@ -8,6 +8,22 @@
 
 ---
 
+## v1.0.1531 (2026-10-07)
+
+### Bug 修复
+- 会员判定取个人与家庭身份中更高级别,防止家庭同步覆盖更高个人会员
+
+### 其他
+-   max 比较取更高者,覆盖全部用户侧调用方(折扣/可见等级/权限/赠礼前置校验等)
+- - resolveEffectiveLevel: level_order 比较,平级时优先非 FREE(当前 FREE=BASIC=1 防同级覆盖)
+- - syncMembershipToFamilyMembers/syncMembershipForNewMember: 家庭同步只升不降,
+-   成员已有更高级别个人身份时保留,避免覆盖
+- - downgradeFamilyMembers: 按家庭历史最高 level_order 守卫,仅降级不高于该级别的成员,
+-   保留 LIFETIME/PREMIUM 等纯个人身份
+- - 新增 MembershipServiceTest 17 用例(纯 Mockito)
+- 
+
+
 ## v1.0.1530 (2026-10-07)
 
 ### Bug 修复