shinnosuke
2026-09-17 5a4da0dd1db1727a039bbefc40551525eff6d0fb
更新....
4个文件已修改
101 ■■■■■ 已修改文件
talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminBusinessService.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-server/src/main/java/com/talentcard/server/business/portal/ParkingMerchantPortalService.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-server/src/test/java/com/talentcard/server/business/admin/AdminBusinessServiceTests.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-server/src/test/java/com/talentcard/server/business/portal/ParkingMerchantPortalServiceTests.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminBusinessService.java
@@ -858,13 +858,13 @@
                select p.id, p.id as product_id, p.merchant_id, m.merchant_name, m.merchant_type, m.merchant_type_code, m.contact_mobile,
                       p.product_no, p.product_type, p.product_name, p.subtitle, p.cover_url,
                       p.original_price, p.sale_price, p.subsidy_amount, p.is_government_subsidy,
                       p.stock, p.sales_count, p.detail_text, p.benefit_desc, p.purchase_notice,
                       p.stock, %s, p.detail_text, p.benefit_desc, p.purchase_notice,
                       p.valid_start_time, p.valid_end_time, p.product_images, p.rights_config,
                       p.audit_status, p.audit_remark, p.status, p.sort_no, p.submit_time, p.created_at, p.updated_at
                from merchant_product p
                left join merchant m on m.id = p.merchant_id
                where p.deleted = 0 and p.merchant_id = ?
                """);
                """.formatted(salesCountSelect("p.id", "p")));
        StringBuilder countSql = new StringBuilder("""
                select count(*)
                from merchant_product p
@@ -913,7 +913,7 @@
                select r.id, r.product_id, r.merchant_id, m.merchant_name, m.merchant_type, m.merchant_type_code, m.contact_mobile,
                       r.product_no, r.product_type, r.product_name,
                       r.subtitle, r.cover_url, r.original_price, r.sale_price, r.subsidy_amount,
                       r.is_government_subsidy, r.stock, r.sales_count, r.detail_text, r.benefit_desc,
                       r.is_government_subsidy, r.stock, %s, r.detail_text, r.benefit_desc,
                       r.purchase_notice, r.valid_start_time, r.valid_end_time, r.product_images, r.rights_config,
                       r.audit_status, r.audit_remark, r.audit_by, r.audit_time, r.submit_time,
                       r.source_created_at as created_at, p.status
@@ -921,7 +921,7 @@
                left join merchant_product p on p.id = r.product_id
                left join merchant m on m.id = r.merchant_id
                where r.deleted = 0
                """);
                """.formatted(salesCountSelect("r.product_id", "p")));
        StringBuilder countSql = new StringBuilder("""
                select count(*)
                from merchant_product_audit_record r
@@ -980,13 +980,13 @@
                select p.id, p.merchant_id, m.merchant_name, m.merchant_type, m.merchant_type_code, m.contact_mobile,
                       p.product_no, p.product_type, p.product_name,
                       p.subtitle, p.cover_url, p.original_price, p.sale_price, p.subsidy_amount,
                       p.is_government_subsidy, p.stock, p.sales_count, p.detail_text, p.benefit_desc,
                       p.is_government_subsidy, p.stock, %s, p.detail_text, p.benefit_desc,
                       p.purchase_notice, p.valid_start_time, p.valid_end_time, p.product_images, p.rights_config,
                       p.audit_status, p.audit_remark, p.status, p.sort_no, p.submit_time, p.created_at, p.updated_at
                from merchant_product p
                left join merchant m on m.id = p.merchant_id
                where p.deleted = 0 and p.id = ?
                """, id);
                """.formatted(salesCountSelect("p.id", "p")), id);
    }
    @Transactional(readOnly = true)
@@ -995,7 +995,7 @@
                select r.id, r.product_id, r.merchant_id, m.merchant_name, m.merchant_type, m.merchant_type_code, m.contact_mobile,
                       r.product_no, r.product_type, r.product_name, r.subtitle, r.cover_url,
                       r.original_price, r.sale_price, r.subsidy_amount, r.is_government_subsidy,
                       r.stock, r.sales_count, r.detail_text, r.benefit_desc, r.purchase_notice,
                       r.stock, %s, r.detail_text, r.benefit_desc, r.purchase_notice,
                       r.valid_start_time, r.valid_end_time, r.product_images, r.rights_config,
                       r.audit_status, r.audit_remark, r.audit_by, r.audit_time, r.submit_time,
                       r.source_created_at as created_at, p.status
@@ -1003,7 +1003,7 @@
                left join merchant_product p on p.id = r.product_id
                left join merchant m on m.id = r.merchant_id
                where r.deleted = 0 and r.id = ?
                """, auditRecordId);
                """.formatted(salesCountSelect("r.product_id", "p")), auditRecordId);
    }
    @Transactional
@@ -1580,6 +1580,18 @@
        return Boolean.TRUE.equals(value == null ? defaultValue : value) ? (byte) 1 : (byte) 0;
    }
    /**
     * 团单销量 = 该商品的有效订单数(已支付,且未取消/未退款)。
     *
     * 商品表上的 sales_count 只在部分流程(如酒店房型预约)里累加,普通团单下单后一直是 0,
     * 后台列表就统计不出销量。这里直接按订单算,同时保留 sales_count 作为下限,兼容历史数据。
     */
    String salesCountSelect(String productIdExpression, String productAlias) {
        return "greatest(coalesce(" + productAlias + ".sales_count, 0), (select count(*) from benefit_order bo"
                + " where bo.deleted = 0 and bo.product_id = " + productIdExpression
                + " and bo.order_status = 1 and (bo.refund_status is null or bo.refund_status = 0))) as sales_count";
    }
    private List<Long> couponProductIds(Long couponId) {
        return jdbcTemplate.queryForList(
                "select product_id from merchant_coupon_product where coupon_id = ? order by id",
@@ -1589,13 +1601,13 @@
    private List<Map<String, Object>> couponProducts(Long couponId) {
        return DatabaseRows.normalize(jdbcTemplate.queryForList("""
                select p.id, p.product_name, p.cover_url, p.sale_price, p.sales_count, p.submit_time, p.created_at,
                select p.id, p.product_name, p.cover_url, p.sale_price, %s, p.submit_time, p.created_at,
                       p.audit_status, p.status
                from merchant_coupon_product cp
                join merchant_product p on p.id = cp.product_id
                where cp.coupon_id = ? and p.deleted = 0
                order by cp.id
                """, couponId));
                """.formatted(salesCountSelect("p.id", "p")), couponId));
    }
    private void syncCouponProducts(Long couponId, List<Long> productIds, String productScope) {
talent-card-server/src/main/java/com/talentcard/server/business/portal/ParkingMerchantPortalService.java
@@ -65,8 +65,11 @@
        String parkingType = text(right == null ? null : right.get("parkingType"), "paid");
        // 付费权益到店出示人才码,免费权益先登记车牌。
        result.put("actionMode", "paid".equalsIgnoreCase(parkingType) ? "talentCode" : "plate");
        result.put("plateFormMode", text(benefit.get("plateFormMode"), "simple"));
        result.put("benefit", benefitView(benefit));
        // 车牌表单模式以商家权益里配的「停车场选择范围」为准:
        // 全部可选(all)只要填车牌;只选其一(selected)才需要挑停车场。
        String plateFormMode = effectivePlateFormMode(right, benefit);
        result.put("plateFormMode", plateFormMode);
        result.put("benefit", benefitView(benefit, plateFormMode));
        result.put("applicableStores", parkingStores(merchant.get("usageInstructions")));
        result.put("merchantRightsConfig", rights);
        result.put("merchantRight", right == null ? Map.of() : right);
@@ -127,7 +130,7 @@
            throw new BusinessException(ErrorCode.CONFLICT, "付费停车权益无需登记车牌");
        }
        Map<String, Object> selectedParkingLot = null;
        if ("withParking".equals(benefit.get("plateFormMode"))) {
        if (requiresParkingLotSelection(right, benefit)) {
            if (request.parkingLotId() == null) throw new BusinessException(ErrorCode.BAD_REQUEST, "请选择停车场");
            selectedParkingLot = parkingStores(merchant.get("usageInstructions")).stream()
                    .filter(item -> String.valueOf(request.parkingLotId()).equals(String.valueOf(item.get("id"))))
@@ -254,15 +257,34 @@
                text(parkingLot.get("storeName"), text(parkingLot.get("name"), "")));
    }
    private Map<String, Object> benefitView(Map<String, Object> benefit) {
    private Map<String, Object> benefitView(Map<String, Object> benefit, String plateFormMode) {
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("benefitId", benefit.get("id"));
        result.put("actionMode", text(benefit.get("actionMode"), "plate"));
        result.put("plateFormMode", text(benefit.get("plateFormMode"), "simple"));
        result.put("plateFormMode", plateFormMode);
        result.put("validEnd", benefit.get("validEnd"));
        return result;
    }
    /**
     * 是否需要小程序里挑一个停车场。
     * 以商家权益(merchant_rights_config)里的 parkingScope 为准:selected/ONE=只选其一,all=全部可选;
     * 权益里没配范围的旧数据,继续沿用停车权益表的 plate_form_mode。
     */
    private boolean requiresParkingLotSelection(Map<String, Object> right, Map<String, Object> benefit) {
        if (right != null) {
            String scope = text(right.get("parkingScope"), "").trim().toUpperCase(Locale.ROOT);
            if (!scope.isBlank()) {
                return "SELECTED".equals(scope) || "ONE".equals(scope) || scope.contains("只选") || scope.contains("其一");
            }
        }
        return "withParking".equalsIgnoreCase(text(benefit.get("plateFormMode"), ""));
    }
    private String effectivePlateFormMode(Map<String, Object> right, Map<String, Object> benefit) {
        return requiresParkingLotSelection(right, benefit) ? "withParking" : "simple";
    }
    private Map<String, Object> registrationView(Map<String, Object> row) {
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("registrationId", row.get("id"));
talent-card-server/src/test/java/com/talentcard/server/business/admin/AdminBusinessServiceTests.java
@@ -42,4 +42,18 @@
    void supportsMerchantAppLoginForHotelMerchants() {
        assertThat(service.supportsMerchantAppLogin("酒店服务")).isTrue();
    }
    @Test
    void productSalesCountComesFromValidOrders() {
        // 商品表上的 sales_count 只有部分流程会累加,销量必须按有效订单算,否则后台列表一直是空的。
        String salesSelect = service.salesCountSelect("r.product_id", "p");
        assertThat(salesSelect)
                .contains("greatest")
                .contains("from benefit_order bo")
                .contains("bo.product_id = r.product_id")
                .contains("bo.order_status = 1")
                .contains("bo.refund_status is null or bo.refund_status = 0")
                .endsWith("as sales_count");
    }
}
talent-card-server/src/test/java/com/talentcard/server/business/portal/ParkingMerchantPortalServiceTests.java
@@ -43,4 +43,27 @@
        assertThat(freeTags).containsExactly("免费");
        assertThat(paidTags).containsExactly("8.5折");
    }
    @Test
    void parkingScopeDecidesWhetherThePlatePageNeedsAParkingLot() throws Exception {
        Method method = ParkingMerchantPortalService.class.getDeclaredMethod(
                "requiresParkingLotSelection", Map.class, Map.class);
        method.setAccessible(true);
        // 后台选“停车场全部可选”时只需要填车牌,不再让小程序挑停车场。
        assertThat(method.invoke(service, Map.of("parkingScope", "all"), Map.of("plateFormMode", "withParking")))
                .isEqualTo(false);
        assertThat(method.invoke(service, Map.of("parkingScope", "ALL"), Map.of()))
                .isEqualTo(false);
        // 后台选“停车场只选其一”时保留停车场搜索。
        assertThat(method.invoke(service, Map.of("parkingScope", "selected"), Map.of("plateFormMode", "simple")))
                .isEqualTo(true);
        assertThat(method.invoke(service, Map.of("parkingScope", "ONE"), Map.of()))
                .isEqualTo(true);
        // 老数据没配范围时沿用停车权益表的 plate_form_mode。
        assertThat(method.invoke(service, Map.of(), Map.of("plateFormMode", "withParking")))
                .isEqualTo(true);
        assertThat(method.invoke(service, Map.of(), Map.of("plateFormMode", "simple")))
                .isEqualTo(false);
    }
}