Răsfoiți Sursa

Merge branch 'sit' into uat-20260202

dujian 3 săptămâni în urmă
părinte
comite
ae7ecb7364

+ 24 - 0
alien-entity/src/main/java/shop/alien/entity/store/LawyerUser.java

@@ -134,6 +134,30 @@ public class LawyerUser extends Model<LawyerUser> {
     @TableField("alipay_account")
     private String alipayAccount;
 
+    @ApiModelProperty(value = "律师支付宝appid")
+    @TableField("lawyer_ali_id")
+    private String lawyerAliId;
+
+    @ApiModelProperty(value = "律师支付宝名称")
+    @TableField("lawyer_ali_name")
+    private String lawyerAliName;
+
+    @ApiModelProperty(value = "律师微信appid")
+    @TableField("lawyer_wechat_id")
+    private String lawyerWechatId;
+
+    @ApiModelProperty(value = "律师微信名称")
+    @TableField("lawyer_wechat_name")
+    private String lawyerWechatName;
+
+    @ApiModelProperty(value = "银行卡号")
+    @TableField("bank_card_no")
+    private String bankCardNo;
+
+    @ApiModelProperty(value = "开户银行名称")
+    @TableField("bank_name")
+    private String bankName;
+
     // ========== 律师特有字段 ==========
 
     @ApiModelProperty(value = "律师执业证号")

+ 24 - 0
alien-entity/src/main/java/shop/alien/entity/store/StorePaymentConfig.java

@@ -126,6 +126,30 @@ public class StorePaymentConfig {
     @TableField(value = "wechat_pay_public_key_file", typeHandler = BlobByteArrayTypeHandler.class)
     private byte[] wechatPayPublicKeyFile;
 
+    @ApiModelProperty(value = "收款银行卡号")
+    @TableField("bank_card_no")
+    private String bankCardNo;
+
+    @ApiModelProperty(value = "开户银行名称")
+    @TableField("bank_name")
+    private String bankName;
+
+    @ApiModelProperty(value = "商家微信 appid")
+    @TableField("store_wechat_id")
+    private String storeWechatId;
+
+    @ApiModelProperty(value = "商家微信名称")
+    @TableField("store_wechat_name")
+    private String storeWechatName;
+
+    @ApiModelProperty(value = "商家支付宝 appid")
+    @TableField("store_ali_id")
+    private String storeAliId;
+
+    @ApiModelProperty(value = "商家支付宝名称")
+    @TableField("store_ali_name")
+    private String storeAliName;
+
     @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
     @TableField("delete_flag")
     @TableLogic

+ 35 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/LawyerPaymentAccountDto.java

@@ -0,0 +1,35 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 律师收款账号统一入参
+ */
+@Data
+@ApiModel(value = "LawyerPaymentAccountDto对象", description = "律师收款账号统一入参")
+public class LawyerPaymentAccountDto {
+
+    @ApiModelProperty(value = "律师ID", required = true)
+    private Integer lawyerId;
+
+    @ApiModelProperty(value = "微信ID")
+    private String wechatId;
+
+    @ApiModelProperty(value = "微信名称")
+    private String wechatName;
+
+    @ApiModelProperty(value = "支付宝ID")
+    private String aliId;
+
+    @ApiModelProperty(value = "支付宝名称")
+    private String aliName;
+
+    @ApiModelProperty(value = "银行卡号")
+    private String bankCardNo;
+
+    @ApiModelProperty(value = "开户银行名称")
+    private String bankName;
+}
+

+ 1 - 1
alien-entity/src/main/java/shop/alien/entity/store/dto/StoreRenovationRequirementDto.java

@@ -86,7 +86,7 @@ public class StoreRenovationRequirementDto {
     private List<String> attachmentUrls;
 
     @ApiModelProperty(value = "创建时间")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @JsonFormat(pattern = "M月d日 H:mm", timezone = "GMT+8")
     @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date createdTime;
 

+ 2 - 3
alien-entity/src/main/java/shop/alien/mapper/LawyerUserMapper.java

@@ -109,15 +109,14 @@ Integer updateLawyerUser(LawyerUser user);
            "user.time_num, " +
            "user.certificate_image, " +
            "user.business_license_image, " +
-           "firm.firm_name AS firmName, " +
+           "user.law_firm AS lawFirm, " +
+           "user.address AS address, " +
            "firmTwo.payment_account AS paymentNum, " +
-           "firmTwo.address AS address, " +
            "GROUP_CONCAT(area.problem_scenario_id SEPARATOR ',') AS problemScenarioId, " +
            "GROUP_CONCAT(a.name SEPARATOR ',') AS scenarioNames, " +
            "(SELECT GROUP_CONCAT(id SEPARATOR ',') FROM lawyer_legal_problem_scenario WHERE level = 1) AS firstLevelScenarioId, " +
            "(SELECT GROUP_CONCAT(name SEPARATOR ',') FROM lawyer_legal_problem_scenario WHERE level = 1) AS firstLevelScenario " +
            "FROM lawyer_user user " +
-           "LEFT JOIN law_firm firm ON firm.id = user.firm_id AND firm.delete_flag = 0 " +
            "LEFT JOIN law_firm_payment firmTwo ON firmTwo.firm_id = user.firm_id " +
            "LEFT JOIN lawyer_service_area area ON area.lawyer_user_id = user.id AND area.delete_flag = 0 AND area.status = 1 " +
            "LEFT JOIN lawyer_legal_problem_scenario a ON a.id = area.problem_scenario_id AND a.delete_flag = 0 " +

+ 3 - 1
alien-entity/src/main/java/shop/alien/mapper/StoreReservationMapper.java

@@ -24,6 +24,7 @@ public interface StoreReservationMapper extends BaseMapper<UserReservation> {
      * @param dateFrom   预约日期起(可选)
      * @param dateTo     预约日期止(可选)
      * @param orderStatus 订单状态(可选,0:待支付 1:待使用 2:已完成 3:已过期 4:已取消 5:已关闭 6:退款中 7:已退款 8:商家预订)
+     * @param reservationUserName 预约用户姓名(可选,模糊查询)
      * @return 预约信息列表
      */
     List<StoreReservationListVo> getStoreReservationList(
@@ -31,6 +32,7 @@ public interface StoreReservationMapper extends BaseMapper<UserReservation> {
             @Param("status") Integer status,
             @Param("dateFrom") Date dateFrom,
             @Param("dateTo") Date dateTo,
-            @Param("orderStatus") Integer orderStatus
+            @Param("orderStatus") Integer orderStatus,
+            @Param("reservationUserName") String reservationUserName
     );
 }

+ 5 - 2
alien-entity/src/main/resources/mapper/StoreReservationMapper.xml

@@ -93,9 +93,9 @@
             ur.created_time
         FROM
             user_reservation ur
-        LEFT JOIN store_booking_category sbc ON ur.category_id = sbc.id AND sbc.delete_flag = 0
+        LEFT JOIN store_booking_category sbc ON ur.category_id = sbc.id
         LEFT JOIN user_reservation_table urt ON ur.id = urt.reservation_id AND urt.delete_flag = 0
-        LEFT JOIN store_booking_table sbt ON urt.table_id = sbt.id AND sbt.delete_flag = 0
+        LEFT JOIN store_booking_table sbt ON urt.table_id = sbt.id
         LEFT JOIN life_user lu ON ur.user_id = lu.id AND lu.delete_flag = 0
         LEFT JOIN user_reservation_order uro ON ur.id = uro.reservation_id AND uro.delete_flag = 0
         WHERE
@@ -110,6 +110,9 @@
         <if test="dateTo != null">
             AND ur.reservation_date &lt;= #{dateTo}
         </if>
+        <if test="reservationUserName != null and reservationUserName != ''">
+            AND ur.reservation_user_name LIKE CONCAT('%', #{reservationUserName}, '%')
+        </if>
         <choose>
             <when test="orderStatus != null">
                 AND uro.order_status = #{orderStatus}

+ 4 - 6
alien-entity/src/main/resources/mapper/UserReservationMapper.xml

@@ -181,7 +181,7 @@
 
     <!-- 查询分类下是否有符合条件的预订信息
          条件:订单状态为"待使用"(1)或"已完成"(2)
-         对于"已完成"状态的订单,结束时间需要在当前时间3小时内 -->
+         对于"已完成"状态的订单,要求 当前时间 < 结束时间 + 3小时 -->
     <select id="countReservationsByCategoryAndOrderStatus" resultType="java.lang.Long">
         SELECT COUNT(DISTINCT ur.id)
         FROM user_reservation ur
@@ -194,7 +194,7 @@
             -- 订单状态为"待使用"(1),直接符合条件
             uro.order_status = 1
             OR
-            -- 订单状态为"已完成"(2),需要检查结束时间在3小时内
+            -- 订单状态为"已完成"(2),需要检查 当前时间 小于 订单结束时间+ 3小时
             (
               uro.order_status = 2
               AND ur.end_time IS NOT NULL
@@ -204,8 +204,7 @@
                 (
                   LENGTH(TRIM(ur.end_time)) &gt; 5
                   AND STR_TO_DATE(TRIM(ur.end_time), '%Y-%m-%d %H:%i') IS NOT NULL
-                  AND STR_TO_DATE(TRIM(ur.end_time), '%Y-%m-%d %H:%i') &lt; NOW()
-                  AND STR_TO_DATE(TRIM(ur.end_time), '%Y-%m-%d %H:%i') &gt; DATE_SUB(NOW(), INTERVAL 3 HOUR)
+                  AND DATE_ADD(STR_TO_DATE(TRIM(ur.end_time), '%Y-%m-%d %H:%i'), INTERVAL 3 HOUR) &gt; NOW()
                 )
                 OR
                 -- 如果 end_time 是时间格式 (HH:mm),需要结合 reservation_date
@@ -213,8 +212,7 @@
                   LENGTH(TRIM(ur.end_time)) &lt;= 5
                   AND ur.reservation_date IS NOT NULL
                   AND STR_TO_DATE(CONCAT(DATE_FORMAT(ur.reservation_date, '%Y-%m-%d'), ' ', TRIM(ur.end_time)), '%Y-%m-%d %H:%i') IS NOT NULL
-                  AND STR_TO_DATE(CONCAT(DATE_FORMAT(ur.reservation_date, '%Y-%m-%d'), ' ', TRIM(ur.end_time)), '%Y-%m-%d %H:%i') &lt; NOW()
-                  AND STR_TO_DATE(CONCAT(DATE_FORMAT(ur.reservation_date, '%Y-%m-%d'), ' ', TRIM(ur.end_time)), '%Y-%m-%d %H:%i') &gt; DATE_SUB(NOW(), INTERVAL 3 HOUR)
+                  AND DATE_ADD(STR_TO_DATE(CONCAT(DATE_FORMAT(ur.reservation_date, '%Y-%m-%d'), ' ', TRIM(ur.end_time)), '%Y-%m-%d %H:%i'), INTERVAL 3 HOUR) &gt; NOW()
                 )
               )
             )

+ 47 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/controller/LawyerUserController.java

@@ -7,6 +7,8 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.LawyerUser;
+import shop.alien.entity.store.StoreDictionary;
+import shop.alien.entity.store.dto.LawyerPaymentAccountDto;
 import shop.alien.entity.store.dto.LawyerRecommendedDto;
 import shop.alien.entity.store.dto.LawyerUserDto;
 import shop.alien.entity.store.vo.LawyerUserVo;
@@ -346,5 +348,50 @@ public class LawyerUserController {
         return lawyerUserService.updateLawyerUserCharge(lawyerUserDto);
     }
 
+    @ApiOperation("设置收款账号-保存微信")
+    @ApiOperationSupport(order = 20)
+    @PostMapping("/saveWechatAccount")
+    public R<Boolean> saveWechatAccount(@RequestBody LawyerPaymentAccountDto request) {
+        log.info("LawyerUserController.saveWechatAccount?request={}", request);
+        return lawyerUserService.saveLawyerWechatAccount(request.getLawyerId(), request.getWechatId(), request.getWechatName());
+    }
+
+    @ApiOperation("设置收款账号-保存支付宝")
+    @ApiOperationSupport(order = 21)
+    @PostMapping("/saveAlipayAccount")
+    public R<Boolean> saveAlipayAccount(@RequestBody LawyerPaymentAccountDto request) {
+        log.info("LawyerUserController.saveAlipayAccount?request={}", request);
+        return lawyerUserService.saveLawyerAlipayAccount(request.getLawyerId(), request.getAliId(), request.getAliName());
+    }
+
+    @ApiOperation("设置收款账号-保存银行卡")
+    @ApiOperationSupport(order = 22)
+    @PostMapping("/saveBankAccount")
+    public R<Boolean> saveBankAccount(@RequestBody LawyerPaymentAccountDto request) {
+        log.info("LawyerUserController.saveBankAccount?request={}", request);
+        return lawyerUserService.saveLawyerBankAccount(request.getLawyerId(), request.getBankCardNo(), request.getBankName());
+    }
+
+    @ApiOperation("设置收款账号-查询微信/支付宝/银行卡")
+    @ApiOperationSupport(order = 23)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "lawyerId", value = "律师ID", dataType = "int", paramType = "query", required = true)
+    })
+    @GetMapping("/getPaymentAccounts")
+    public R<Map<String, String>> getPaymentAccounts(@RequestParam Integer lawyerId) {
+        log.info("LawyerUserController.getPaymentAccounts?lawyerId={}", lawyerId);
+        return lawyerUserService.getLawyerPaymentAccounts(lawyerId);
+    }
+
+
+    @ApiOperation("获取字典")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({@ApiImplicitParam(name = "dictType", value = "字典类型", dataType = "String", paramType = "query", required = true)})
+    @GetMapping("/getDict")
+    public R<List<StoreDictionary>> getDict(String dictType) {
+        return R.data(lawyerUserService.getDict(dictType));
+    }
+
+
 }
 

+ 19 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/service/LawyerUserService.java

@@ -4,10 +4,12 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.IService;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.LawyerUser;
+import shop.alien.entity.store.StoreDictionary;
 import shop.alien.entity.store.dto.LawyerUserDto;
 import shop.alien.entity.store.vo.LawyerUserVo;
 
 import javax.servlet.http.HttpServletResponse;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -197,5 +199,22 @@ public interface LawyerUserService extends IService<LawyerUser> {
                           Integer pageSize) throws Exception;
 
     R<LawyerUserVo> updateLawyerUserCharge(LawyerUserDto lawyerUserDto);
+
+    R<Boolean> saveLawyerWechatAccount(Integer lawyerId, String wechatId, String wechatName);
+
+    R<Boolean> saveLawyerAlipayAccount(Integer lawyerId, String aliId, String aliName);
+
+    R<Boolean> saveLawyerBankAccount(Integer lawyerId, String bankCardNo, String bankName);
+
+    R<Map<String, String>> getLawyerPaymentAccounts(Integer lawyerId);
+
+    /**
+     * 查询字典值
+     *
+     * @param dictType 字典类型
+     * @return list
+     */
+    List<StoreDictionary> getDict(String dictType);
+
 }
 

+ 111 - 9
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerUserServiceImpl.java

@@ -57,6 +57,7 @@ public class LawyerUserServiceImpl extends ServiceImpl<LawyerUserMapper, LawyerU
     private final OrderReviewMapper orderReviewMapper;
     private final LifeNoticeMapper lifeNoticeMapper;
     private final LawyerLicenseVerifyUtil lawyerLicenseVerifyUtil;
+    private final StoreDictionaryMapper storeDictionaryMapper;
 
     @Override
     public R<IPage<LawyerUser>> getLawyerUserList(int pageNum, int pageSize, String name, String phone, Integer status) {
@@ -736,15 +737,15 @@ public class LawyerUserServiceImpl extends ServiceImpl<LawyerUserMapper, LawyerU
         }
 
         // 修改执业证、姓名或头像时,先走 AI 执业证核验(与库中数据合并后校验)
-        if (lawyerUserVo.getCertificateImage() != null || lawyerUserVo.getName() != null || lawyerUserVo.getHeadImg() != null) {
-            String certUrl = lawyerUserVo.getCertificateImage() != null ? lawyerUserVo.getCertificateImage() : existing.getCertificateImage();
-            String name = lawyerUserVo.getName() != null ? lawyerUserVo.getName() : existing.getName();
-            String headUrl = lawyerUserVo.getHeadImg() != null ? lawyerUserVo.getHeadImg() : existing.getHeadImg();
-            LawyerLicenseVerifyUtil.VerifyResult licenseResult = lawyerLicenseVerifyUtil.verify(certUrl, name, headUrl);
-            if (!licenseResult.isPassed()) {
-                return R.fail("执业证核验失败");
-            }
-        }
+//        if (lawyerUserVo.getCertificateImage() != null || lawyerUserVo.getName() != null || lawyerUserVo.getHeadImg() != null) {
+//            String certUrl = lawyerUserVo.getCertificateImage() != null ? lawyerUserVo.getCertificateImage() : existing.getCertificateImage();
+//            String name = lawyerUserVo.getName() != null ? lawyerUserVo.getName() : existing.getName();
+//            String headUrl = lawyerUserVo.getHeadImg() != null ? lawyerUserVo.getHeadImg() : existing.getHeadImg();
+//            LawyerLicenseVerifyUtil.VerifyResult licenseResult = lawyerLicenseVerifyUtil.verify(certUrl, name, headUrl);
+//            if (!licenseResult.isPassed()) {
+//                return R.fail("执业证核验失败");
+//            }
+//        }
 
 // 只有当有字段需要更新时才执行更新操作
         if (hasUpdate) {
@@ -898,6 +899,107 @@ public class LawyerUserServiceImpl extends ServiceImpl<LawyerUserMapper, LawyerU
         return R.success("修改成功");
     }
 
+    @Override
+    public R<Boolean> saveLawyerWechatAccount(Integer lawyerId, String wechatId, String wechatName) {
+        if (lawyerId == null) {
+            return R.fail("律师ID不能为空");
+        }
+        if (!StringUtils.hasText(wechatId)) {
+            return R.fail("微信ID不能为空");
+        }
+        if (!StringUtils.hasText(wechatName)) {
+            return R.fail("微信名称不能为空");
+        }
+        LawyerUser lawyerUser = this.getById(lawyerId);
+        if (lawyerUser == null || (lawyerUser.getDeleteFlag() != null && lawyerUser.getDeleteFlag() == 1)) {
+            return R.fail("律师不存在");
+        }
+        LawyerUser update = new LawyerUser();
+        update.setId(lawyerId);
+        update.setLawyerWechatId(wechatId.trim());
+        update.setLawyerWechatName(wechatName.trim());
+        return this.updateById(update) ? R.success("保存成功") : R.fail("保存失败");
+    }
+
+    @Override
+    public R<Boolean> saveLawyerAlipayAccount(Integer lawyerId, String aliId, String aliName) {
+        if (lawyerId == null) {
+            return R.fail("律师ID不能为空");
+        }
+        if (!StringUtils.hasText(aliId)) {
+            return R.fail("支付宝ID不能为空");
+        }
+        if (!StringUtils.hasText(aliName)) {
+            return R.fail("支付宝名称不能为空");
+        }
+        LawyerUser lawyerUser = this.getById(lawyerId);
+        if (lawyerUser == null || (lawyerUser.getDeleteFlag() != null && lawyerUser.getDeleteFlag() == 1)) {
+            return R.fail("律师不存在");
+        }
+        LawyerUser update = new LawyerUser();
+        update.setId(lawyerId);
+        update.setLawyerAliId(aliId.trim());
+        update.setLawyerAliName(aliName.trim());
+        return this.updateById(update) ? R.success("保存成功") : R.fail("保存失败");
+    }
+
+    @Override
+    public R<Boolean> saveLawyerBankAccount(Integer lawyerId, String bankCardNo, String bankName) {
+        if (lawyerId == null) {
+            return R.fail("律师ID不能为空");
+        }
+        if (!StringUtils.hasText(bankCardNo)) {
+            return R.fail("银行卡号不能为空");
+        }
+        if (!StringUtils.hasText(bankName)) {
+            return R.fail("开户银行名称不能为空");
+        }
+        LawyerUser lawyerUser = this.getById(lawyerId);
+        if (lawyerUser == null || (lawyerUser.getDeleteFlag() != null && lawyerUser.getDeleteFlag() == 1)) {
+            return R.fail("律师不存在");
+        }
+        LawyerUser update = new LawyerUser();
+        update.setId(lawyerId);
+        update.setBankCardNo(bankCardNo.trim());
+        update.setBankName(bankName.trim());
+        return this.updateById(update) ? R.success("保存成功") : R.fail("保存失败");
+    }
+
+    @Override
+    public R<Map<String, String>> getLawyerPaymentAccounts(Integer lawyerId) {
+        if (lawyerId == null) {
+            return R.fail("律师ID不能为空");
+        }
+        LawyerUser lawyerUser = this.getById(lawyerId);
+        if (lawyerUser == null || (lawyerUser.getDeleteFlag() != null && lawyerUser.getDeleteFlag() == 1)) {
+            return R.fail("律师不存在");
+        }
+        Map<String, String> result = new LinkedHashMap<>();
+        result.put("lawyerWechatId", lawyerUser.getLawyerWechatId());
+        result.put("lawyerWechatName", lawyerUser.getLawyerWechatName());
+        result.put("lawyerAliId", lawyerUser.getLawyerAliId());
+        result.put("lawyerAliName", lawyerUser.getLawyerAliName());
+        result.put("bankCardNo", lawyerUser.getBankCardNo());
+        result.put("bankName", lawyerUser.getBankName());
+        return R.data(result);
+    }
+
+
+    /**
+     * 查询字典值
+     *
+     * @param dictType 字典类型
+     * @return list
+     */
+    @Override
+    public List<StoreDictionary> getDict(String dictType) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StoreDictionary::getTypeName, dictType);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        return storeDictionaryMapper.selectList(queryWrapper);
+    }
+
+
     /**
      * 将LawyerUserVo转换为LawyerUserExcelVo
      *

+ 151 - 1
alien-store/src/main/java/shop/alien/store/controller/StorePaymentConfigController.java

@@ -471,7 +471,157 @@ public class StorePaymentConfigController {
         }
     }
 
+    @ApiOperationSupport(order = 11)
+    @ApiOperation(value = "设置收款账号-保存银行卡", notes = "按 store_user_id 新增或更新银行卡号、开户银行名称(与微信/支付宝配置同一条 store_payment_config 记录)")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeUserId", value = "店铺用户ID(store_user.id)", dataType = "int", paramType = "form", required = true),
+            @ApiImplicitParam(name = "bankCardNo", value = "银行卡号", dataType = "string", paramType = "form", required = true),
+            @ApiImplicitParam(name = "bankName", value = "开户银行名称", dataType = "string", paramType = "form", required = true)
+    })
+    @PostMapping("/saveBankByStoreUserId")
+    public R<Integer> saveBankByStoreUserId(
+            @RequestParam Integer storeUserId,
+            @RequestParam String bankCardNo,
+            @RequestParam String bankName) {
+        log.info("StorePaymentConfigController.saveBankByStoreUserId storeUserId={}", storeUserId);
+        if (storeUserId == null) {
+            return R.fail("店铺用户ID不能为空");
+        }
+        if (bankCardNo == null || bankCardNo.trim().isEmpty()) {
+            return R.fail("银行卡号不能为空");
+        }
+        if (bankName == null || bankName.trim().isEmpty()) {
+            return R.fail("银行名称不能为空");
+        }
+        try {
+            StoreUser storeUser = storeUserService.getById(storeUserId);
+            if (storeUser == null) {
+                return R.fail("店铺用户不存在");
+            }
+            StorePaymentConfig config = storePaymentConfigService.getByStoreUserId(storeUserId);
+            if (config == null) {
+                config = new StorePaymentConfig();
+                config.setStoreId(storeUser.getStoreId());
+                config.setStoreUserId(storeUserId);
+                config.setAppId("");
+                fillCreatedUser(config);
+            } else {
+                fillUpdatedUser(config);
+            }
+            config.setBankCardNo(bankCardNo.trim());
+            config.setBankName(bankName.trim());
+            boolean ok = config.getId() == null ? storePaymentConfigService.save(config) : storePaymentConfigService.updateById(config);
+            if (!ok) {
+                return R.fail("保存失败");
+            }
+            return R.data(config.getId());
+        } catch (Exception e) {
+            log.error("保存银行卡收款信息失败 storeUserId={}", storeUserId, e);
+            return R.fail("保存失败:" + e.getMessage());
+        }
+    }
+
     @ApiOperationSupport(order = 12)
+    @ApiOperation(value = "设置收款账号-保存商家微信(展示)", notes = "按 store_user_id 新增或更新 store_wechat_id、store_wechat_name(与支付商户微信配置字段不同)")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeUserId", value = "店铺用户ID(store_user.id)", dataType = "int", paramType = "form", required = true),
+            @ApiImplicitParam(name = "storeWechatId", value = "商家微信 appid", dataType = "string", paramType = "form", required = true),
+            @ApiImplicitParam(name = "storeWechatName", value = "商家微信名称", dataType = "string", paramType = "form", required = true)
+    })
+    @PostMapping("/saveMerchantWechatByStoreUserId")
+    public R<Integer> saveMerchantWechatByStoreUserId(
+            @RequestParam Integer storeUserId,
+            @RequestParam String storeWechatId,
+            @RequestParam String storeWechatName) {
+        log.info("StorePaymentConfigController.saveMerchantWechatByStoreUserId storeUserId={}", storeUserId);
+        if (storeUserId == null) {
+            return R.fail("店铺用户ID不能为空");
+        }
+        if (storeWechatId == null || storeWechatId.trim().isEmpty()) {
+            return R.fail("商家微信 appid 不能为空");
+        }
+        if (storeWechatName == null || storeWechatName.trim().isEmpty()) {
+            return R.fail("商家微信名称不能为空");
+        }
+        try {
+            StoreUser storeUser = storeUserService.getById(storeUserId);
+            if (storeUser == null) {
+                return R.fail("店铺用户不存在");
+            }
+            StorePaymentConfig config = storePaymentConfigService.getByStoreUserId(storeUserId);
+            if (config == null) {
+                config = new StorePaymentConfig();
+                config.setStoreId(storeUser.getStoreId());
+                config.setStoreUserId(storeUserId);
+                config.setAppId("");
+                fillCreatedUser(config);
+            } else {
+                fillUpdatedUser(config);
+            }
+            config.setStoreWechatId(storeWechatId.trim());
+            config.setStoreWechatName(storeWechatName.trim());
+            boolean ok = config.getId() == null ? storePaymentConfigService.save(config) : storePaymentConfigService.updateById(config);
+            if (!ok) {
+                return R.fail("保存失败");
+            }
+            return R.data(config.getId());
+        } catch (Exception e) {
+            log.error("保存商家微信信息失败 storeUserId={}", storeUserId, e);
+            return R.fail("保存失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperationSupport(order = 13)
+    @ApiOperation(value = "设置收款账号-保存商家支付宝(展示)", notes = "按 store_user_id 新增或更新 store_ali_id、store_ali_name(与支付宝应用 appId 等支付配置字段不同)")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeUserId", value = "店铺用户ID(store_user.id)", dataType = "int", paramType = "form", required = true),
+            @ApiImplicitParam(name = "storeAliId", value = "商家支付宝 appid", dataType = "string", paramType = "form", required = true),
+            @ApiImplicitParam(name = "storeAliName", value = "商家支付宝名称", dataType = "string", paramType = "form", required = true)
+    })
+    @PostMapping("/saveMerchantAlipayByStoreUserId")
+    public R<Integer> saveMerchantAlipayByStoreUserId(
+            @RequestParam Integer storeUserId,
+            @RequestParam String storeAliId,
+            @RequestParam String storeAliName) {
+        log.info("StorePaymentConfigController.saveMerchantAlipayByStoreUserId storeUserId={}", storeUserId);
+        if (storeUserId == null) {
+            return R.fail("店铺用户ID不能为空");
+        }
+        if (storeAliId == null || storeAliId.trim().isEmpty()) {
+            return R.fail("商家支付宝 appid 不能为空");
+        }
+        if (storeAliName == null || storeAliName.trim().isEmpty()) {
+            return R.fail("商家支付宝名称不能为空");
+        }
+        try {
+            StoreUser storeUser = storeUserService.getById(storeUserId);
+            if (storeUser == null) {
+                return R.fail("店铺用户不存在");
+            }
+            StorePaymentConfig config = storePaymentConfigService.getByStoreUserId(storeUserId);
+            if (config == null) {
+                config = new StorePaymentConfig();
+                config.setStoreId(storeUser.getStoreId());
+                config.setStoreUserId(storeUserId);
+                config.setAppId("");
+                fillCreatedUser(config);
+            } else {
+                fillUpdatedUser(config);
+            }
+            config.setStoreAliId(storeAliId.trim());
+            config.setStoreAliName(storeAliName.trim());
+            boolean ok = config.getId() == null ? storePaymentConfigService.save(config) : storePaymentConfigService.updateById(config);
+            if (!ok) {
+                return R.fail("保存失败");
+            }
+            return R.data(config.getId());
+        } catch (Exception e) {
+            log.error("保存商家支付宝信息失败 storeUserId={}", storeUserId, e);
+            return R.fail("保存失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperationSupport(order = 14)
     @ApiOperation("分页查询支付配置列表")
     @ApiImplicitParams({
             @ApiImplicitParam(name = "page", value = "页码", dataType = "Integer", paramType = "query", defaultValue = "1"),
@@ -496,7 +646,7 @@ public class StorePaymentConfigController {
         }
     }
 
-    @ApiOperationSupport(order = 13)
+    @ApiOperationSupport(order = 15)
     @ApiOperation(value = "新增/更新微信支付配置信息", notes = "按 storeId 查找配置,存在则更新微信字段;不存在则新建一条仅含 storeId 与微信信息的配置。可传表单+两个证书文件(内容存入表)。")
     @ApiImplicitParams({
             @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "int", paramType = "form", required = true),

+ 8 - 5
alien-store/src/main/java/shop/alien/store/controller/StoreReservationController.java

@@ -39,7 +39,8 @@ public class StoreReservationController {
             @ApiImplicitParam(name = "status", value = "预约状态(可选,0:待确认 1:已确认 2:已到店 3:已取消 4:未到店超时)", dataType = "Integer", paramType = "query", required = false),
             @ApiImplicitParam(name = "dateFrom", value = "预约日期起 yyyy-MM-dd", dataType = "String", paramType = "query", required = false),
             @ApiImplicitParam(name = "dateTo", value = "预约日期止 yyyy-MM-dd", dataType = "String", paramType = "query", required = false),
-            @ApiImplicitParam(name = "orderStatus", value = "订单状态(可选,0:待支付 1:待使用 2:已完成 3:已过期 4:已取消 5:已关闭 6:退款中 7:已退款 8:商家预订;不传则默认查询:待使用、已完成、已退款三种状态)", dataType = "Integer", paramType = "query", required = false)
+            @ApiImplicitParam(name = "orderStatus", value = "订单状态(可选,0:待支付 1:待使用 2:已完成 3:已过期 4:已取消 5:已关闭 6:退款中 7:已退款 8:商家预订;不传则默认查询:待使用、已完成、已退款三种状态)", dataType = "Integer", paramType = "query", required = false),
+            @ApiImplicitParam(name = "reservationUserName", value = "预约用户姓名(可选,模糊查询)", dataType = "String", paramType = "query", required = false)
     })
     @GetMapping("/list")
     public R<List<StoreReservationListVo>> getReservationList(
@@ -47,16 +48,18 @@ public class StoreReservationController {
             @RequestParam(required = false) Integer status,
             @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date dateFrom,
             @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date dateTo,
-            @RequestParam(required = false) Integer orderStatus) {
-        log.info("StoreReservationController.getReservationList?storeId={}, status={}, dateFrom={}, dateTo={}, orderStatus={}",
-                storeId, status, dateFrom, dateTo, orderStatus);
+            @RequestParam(required = false) Integer orderStatus,
+            @RequestParam(required = false) String reservationUserName) {
+        log.info("StoreReservationController.getReservationList?storeId={}, status={}, dateFrom={}, dateTo={}, orderStatus={}, reservationUserName={}",
+                storeId, status, dateFrom, dateTo, orderStatus, reservationUserName);
 
         if (storeId == null) {
             return R.fail("门店ID不能为空");
         }
 
         try {
-            List<StoreReservationListVo> list = storeReservationService.getStoreReservationList(storeId, status, dateFrom, dateTo, orderStatus);
+            List<StoreReservationListVo> list = storeReservationService.getStoreReservationList(
+                    storeId, status, dateFrom, dateTo, orderStatus, reservationUserName);
             return R.data(list);
         } catch (Exception e) {
             log.error("查询商家端预约信息列表失败", e);

+ 2 - 1
alien-store/src/main/java/shop/alien/store/service/StoreReservationService.java

@@ -21,10 +21,11 @@ public interface StoreReservationService {
      * @param dateFrom   预约日期起(可选)
      * @param dateTo     预约日期止(可选)
      * @param orderStatus 订单状态(可选,0:待支付 1:待使用 2:已完成 3:已过期 4:已取消 5:已关闭 6:退款中 7:已退款 8:商家预订)
+     * @param reservationUserName 预约用户姓名(可选,模糊查询)
      * @return 预约信息列表
      */
     List<StoreReservationListVo> getStoreReservationList(
-            Integer storeId, Integer status, Date dateFrom, Date dateTo, Integer orderStatus);
+            Integer storeId, Integer status, Date dateFrom, Date dateTo, Integer orderStatus, String reservationUserName);
 
     /**
      * 商家端取消预约

+ 81 - 8
alien-store/src/main/java/shop/alien/store/service/impl/StoreReservationServiceImpl.java

@@ -95,15 +95,16 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
     }
 
     @Override
-    public List<StoreReservationListVo> getStoreReservationList(Integer storeId, Integer status, Date dateFrom, Date dateTo, Integer orderStatus) {
-        log.info("StoreReservationServiceImpl.getStoreReservationList?storeId={}, status={}, dateFrom={}, dateTo={}, orderStatus={}",
-                storeId, status, dateFrom, dateTo, orderStatus);
+    public List<StoreReservationListVo> getStoreReservationList(Integer storeId, Integer status, Date dateFrom, Date dateTo,
+                                                                 Integer orderStatus, String reservationUserName) {
+        log.info("StoreReservationServiceImpl.getStoreReservationList?storeId={}, status={}, dateFrom={}, dateTo={}, orderStatus={}, reservationUserName={}",
+                storeId, status, dateFrom, dateTo, orderStatus, reservationUserName);
 
         if (storeId == null) {
             throw new RuntimeException("门店ID不能为空");
         }
 
-        return baseMapper.getStoreReservationList(storeId, status, dateFrom, dateTo, orderStatus);
+        return baseMapper.getStoreReservationList(storeId, status, dateFrom, dateTo, orderStatus, reservationUserName);
     }
 
     @Override
@@ -540,20 +541,23 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
             return true;
         }
 
-        // 判断订单状态:只有已取消(4)、已退款(7)、已完成(2)状态才能删除
+        // 判断订单状态:只有已取消(4)、已退款(7)、已完成(2)、已过期(3)状态才能删除
         Integer orderStatus = order.getOrderStatus();
         if (orderStatus == null) {
             throw new RuntimeException("订单状态异常,无法删除");
         }
 
-        // 定义可删除的订单状态:2:已完成, 4:已取消, 7:已退款
-        boolean canDelete = orderStatus == 2 || orderStatus == 4 || orderStatus == 7;
+        // 定义可删除的订单状态:2:已完成, 3:已过期, 4:已取消, 7:已退款
+        boolean canDelete = orderStatus == 2 || orderStatus == 3 || orderStatus == 4 || orderStatus == 7;
 
         if (!canDelete) {
             String statusText = getOrderStatusText(orderStatus);
-            throw new RuntimeException("订单状态为" + statusText + ",不允许删除。只有已取消、已退款、已完成状态的订单可以删除");
+            throw new RuntimeException("订单状态为" + statusText + ",不允许删除。只有已取消、已退款、已完成、已过期状态的订单可以删除");
         }
 
+        // 已过期订单:仅当当前时间 > 预约结束时间 + 3小时才允许删除
+        validateExpiredReservationDeleteTimeWindow(reservation, orderStatus);
+
         // 删除订单记录(逻辑删除)
         boolean orderDeleteResult = userReservationOrderService.removeById(order.getId());
         if (!orderDeleteResult) {
@@ -571,6 +575,75 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
         return true;
     }
 
+    /**
+     * 删除校验:已过期状态的预订,需满足“当前时间 > 结束时间 + 3小时”。
+     */
+    private void validateExpiredReservationDeleteTimeWindow(UserReservation reservation, Integer orderStatus) {
+        // 仅校验已过期状态
+        if (orderStatus == null || orderStatus != 3) {
+            return;
+        }
+
+        Date reservationEndTime = parseReservationEndDateTime(reservation);
+        if (reservationEndTime == null) {
+            throw new RuntimeException("预约结束时间异常,暂不可删除");
+        }
+
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(reservationEndTime);
+        calendar.add(Calendar.HOUR_OF_DAY, 3);
+        Date endPlusThreeHours = calendar.getTime();
+
+        if (!new Date().after(endPlusThreeHours)) {
+            throw new RuntimeException("已过期订单需在结束3小时后才可删除");
+        }
+    }
+
+    /**
+     * 解析预约结束时间,兼容两种场景:
+     * 1) endTime 已是完整时间(yyyy-MM-dd HH:mm[:ss])
+     * 2) endTime 仅为时分秒(HH:mm[:ss]),需与 reservationDate 拼接
+     */
+    private Date parseReservationEndDateTime(UserReservation reservation) {
+        if (reservation == null || !StringUtils.hasText(reservation.getEndTime())) {
+            return null;
+        }
+        String endTime = reservation.getEndTime().trim();
+
+        // 场景1:endTime 已是完整日期时间
+        String normalizedFullDateTime = normalizeDateTime(endTime);
+        if (normalizedFullDateTime != null) {
+            try {
+                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+                sdf.setLenient(false);
+                return sdf.parse(normalizedFullDateTime);
+            } catch (ParseException e) {
+                log.warn("解析完整结束时间失败,reservationId={}, endTime={}", reservation.getId(), endTime, e);
+            }
+        }
+
+        // 场景2:endTime 仅时间,拼接 reservationDate 再解析
+        if (reservation.getReservationDate() == null) {
+            return null;
+        }
+        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+        String datePart = dateFormat.format(reservation.getReservationDate());
+        String mergedDateTime = datePart + " " + endTime;
+        String normalizedMergedDateTime = normalizeDateTime(mergedDateTime);
+        if (normalizedMergedDateTime == null) {
+            return null;
+        }
+        try {
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            sdf.setLenient(false);
+            return sdf.parse(normalizedMergedDateTime);
+        } catch (ParseException e) {
+            log.warn("解析拼接结束时间失败,reservationId={}, reservationDate={}, endTime={}",
+                    reservation.getId(), datePart, endTime, e);
+            return null;
+        }
+    }
+
     @Override
     public boolean addTimeByStore(Integer reservationId, String addTimeStart, Integer addTimeMinutes) {
         log.info("StoreReservationServiceImpl.addTimeByStore?reservationId={}, addTimeStart={}, addTimeMinutes={}",

+ 43 - 5
alien-store/src/main/java/shop/alien/store/service/impl/UserReservationServiceImpl.java

@@ -366,7 +366,7 @@ public class UserReservationServiceImpl extends ServiceImpl<UserReservationMappe
                 continue;
             }
 
-            if (reservation.getStatus() != null && reservation.getStatus() == 2) {
+            if (reservation.getStatus() != null && reservation.getStatus() == 4) {
                 continue;
             }
 
@@ -377,6 +377,10 @@ public class UserReservationServiceImpl extends ServiceImpl<UserReservationMappe
                     continue;
                 }
             }
+            // 当前时间已超过预约结束时间则不回显(endTime 为 String,需与 reservationDate 组合判断)
+            if (isReservationEndBeforeNow(reservation.getReservationDate(), reservation.getEndTime())) {
+                continue;
+            }
             UserReservationVo vo = getDetail(reservation.getId());
             if (vo != null) {
                 list.add(vo);
@@ -396,6 +400,44 @@ public class UserReservationServiceImpl extends ServiceImpl<UserReservationMappe
         return c;
     }
 
+    /**
+     * 判断预约是否已结束(当前时刻晚于结束时刻)。endTime 支持 "HH:mm"(需 reservationDate)或 "yyyy-MM-dd HH:mm(:ss)"。
+     */
+    private static boolean isReservationEndBeforeNow(Date reservationDate, String endTimeStr) {
+        if (endTimeStr == null || endTimeStr.trim().isEmpty()) {
+            return false;
+        }
+        String trimmed = endTimeStr.trim();
+        Date now = new Date();
+        if (trimmed.contains(" ")) {
+            for (String pattern : new String[]{"yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm"}) {
+                try {
+                    SimpleDateFormat sdf = new SimpleDateFormat(pattern);
+                    sdf.setLenient(false);
+                    Date end = sdf.parse(trimmed);
+                    return end.before(now);
+                } catch (ParseException ignored) {
+                    // try next pattern
+                }
+            }
+            return false;
+        }
+        if (reservationDate == null) {
+            return false;
+        }
+        int minutes = timeToMinutes(trimmed);
+        if (minutes < 0) {
+            return false;
+        }
+        Calendar endCal = Calendar.getInstance();
+        endCal.setTime(reservationDate);
+        endCal.set(Calendar.HOUR_OF_DAY, minutes / 60);
+        endCal.set(Calendar.MINUTE, minutes % 60);
+        endCal.set(Calendar.SECOND, 0);
+        endCal.set(Calendar.MILLISECOND, 0);
+        return endCal.getTime().before(now);
+    }
+
     @Override
     public IPage<UserReservationVo> pageList(Integer userId, Integer storeId, Integer status,
                                              Date dateFrom, Date dateTo,
@@ -1161,10 +1203,6 @@ public class UserReservationServiceImpl extends ServiceImpl<UserReservationMappe
             if (r != null) {
                 String reservationReason = r.getReason();
                 vo.setMerchantCancelReason(reservationReason);
-                // 与 merchantCancelReason 同源:订单表未落库退款原因时,用预约单原因展示(如商家取消)
-                if (StringUtils.isBlank(order.getRefundReason()) && StringUtils.isNotBlank(reservationReason)) {
-                    order.setRefundReason(reservationReason);
-                }
             }
         }
         return vo;