rk
3 天以前 33caf2bb79bb3c561916c91ae386ec772411e2e8
代码生成
已修改38个文件
1297 ■■■■ 文件已修改
server/admin/src/main/java/com/doumee/api/business/PricingRuleController.java 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/admin/src/main/resources/application.yml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/db/db_change.sql 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/biz/system/impl/OperationConfigBizImpl.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/config/jwt/JwtTokenUtil.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/config/jwt/WebMvcConfig.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/core/constants/Constants.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/model/DriverInfo.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/model/Member.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/model/Orders.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/model/ShopInfo.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/dto/DriverActiveOrderDTO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/dto/MyOrderDTO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/dto/OperationConfigDTO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/dto/RevenueShareItemDTO.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/dto/RevenueShareSaveDTO.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/dto/ShopApplyDTO.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/dto/ShopLoginDTO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/dto/ShopUpdateDTO.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/vo/PlatformAboutVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/vo/RevenueShareVO.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/vo/ShopDetailVO.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/vo/UserCenterVO.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/MemberService.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/OrdersService.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/DriverInfoServiceImpl.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/MemberServiceImpl.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/OrdersRefundServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/OrdersServiceImpl.java 289 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/PricingRuleServiceImpl.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/ShopInfoServiceImpl.java 556 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/SmsrecordServiceImpl.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/resources/application-dev.yml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/api/web/AccountApi.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/api/web/ConfigApi.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/api/web/MemberApi.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/api/web/PaymentCallback.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/resources/application.yml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/admin/src/main/java/com/doumee/api/business/PricingRuleController.java
@@ -42,64 +42,6 @@
    @Autowired
    private PricingRuleService pricingRuleService;
    /*@PreventRepeat
    @ApiOperation("新建")
    @PostMapping("/create")
    @RequiresPermissions("business:pricingRule:create")
    public ApiResponse create(@RequestBody PricingRule pricingRule) {
        return ApiResponse.success(pricingRuleService.create(pricingRule));
    }
    @ApiOperation("根据ID删除")
    @GetMapping("/delete/{id}")
    @RequiresPermissions("business:pricingRule:delete")
    public ApiResponse deleteById(@PathVariable Integer id) {
        pricingRuleService.deleteById(id);
        return ApiResponse.success(null);
    }
    @ApiOperation("批量删除")
    @GetMapping("/delete/batch")
    @RequiresPermissions("business:pricingRule:delete")
    public ApiResponse deleteByIdInBatch(@RequestParam String ids) {
        String[] idArray = ids.split(",");
        List<Integer> idList = new ArrayList<>();
        for (String id : idArray) {
            idList.add(Integer.valueOf(id));
        }
        pricingRuleService.deleteByIdInBatch(idList);
        return ApiResponse.success(null);
    }
    @ApiOperation("根据ID修改")
    @PostMapping("/updateById")
    @RequiresPermissions("business:pricingRule:update")
    public ApiResponse updateById(@RequestBody PricingRule pricingRule) {
        pricingRuleService.updateById(pricingRule);
        return ApiResponse.success(null);
    }
    @ApiOperation("分页查询")
    @PostMapping("/page")
    @RequiresPermissions("business:pricingRule:query")
    public ApiResponse<PageData<PricingRule>> findPage(@RequestBody PageWrap<PricingRule> pageWrap) {
        return ApiResponse.success(pricingRuleService.findPage(pageWrap));
    }
    @ApiOperation("导出Excel")
    @PostMapping("/exportExcel")
    @RequiresPermissions("business:pricingRule:exportExcel")
    public void exportExcel(@RequestBody PageWrap<PricingRule> pageWrap, HttpServletResponse response) {
        List<PricingRule> pricingRuleList = pricingRuleService.findPage(pageWrap).getRecords();
        ExcelExporter.build(PricingRule.class).export(pricingRuleList, "计价规则配置", response);
    }
    @ApiOperation("根据ID查询")
    @GetMapping("/{id}")
    @RequiresPermissions("business:pricingRule:query")
    public ApiResponse findById(@PathVariable Integer id) {
        return ApiResponse.success(pricingRuleService.findById(id));
    }*/
    @PreventRepeat
    @ApiOperation("批量保存就地存取规则")
server/admin/src/main/resources/application.yml
@@ -12,7 +12,7 @@
spring:
  profiles:
    active: pro
    active: dev
  # JSON返回配置
  jackson:
    # 默认时区
server/services/db/db_change.sql
@@ -5,6 +5,43 @@
-- ============================================================
-- 2026/05/11 司机版本类型字段
-- ============================================================
ALTER TABLE `driver_info` ADD COLUMN `VERSION_TYPE` INT DEFAULT 0 COMMENT '司机版本类型:0=正式版本;1=变更版本';
ALTER TABLE `driver_info` ADD COLUMN `RELATION_DRIVER_ID` INT DEFAULT NULL COMMENT '关联正式版本司机主键(变更版本使用)';
-- ============================================================
-- 2026/05/09 门店收益比例配置字段
-- ============================================================
ALTER TABLE `shop_info` ADD COLUMN `REVENUE_SHARE_CONFIG` TEXT COMMENT '收益比例配置(JSON): remoteCompanyDeposit/remotePersonalDeposit/remoteCompanyTake/remotePersonalTake/localCompanyDeposit/localPersonalDeposit';
ALTER TABLE `member` ADD COLUMN `LOGIN_SHOP_ID` INT COMMENT '已登录的门店主键';
ALTER TABLE `shop_info` ADD COLUMN `VERSION_TYPE` INT DEFAULT 0 COMMENT '门店版本类型:0=正式版本;1=变更版本';
ALTER TABLE `shop_info` ADD COLUMN `RELATION_SHOP_ID` INT COMMENT '关联正式版本门店主键(变更版本使用)';
-- ============================================================
-- 2026/05/07 操作半径校验配置
-- ============================================================
INSERT INTO `system_dict_data` (`DICT_ID`, `CODE`, `LABEL`, `REMARK`, `SORT`, `DISABLED`, `CREATE_USER`, `CREATE_TIME`, `DELETED`) VALUES ((SELECT `id` FROM `system_dict` WHERE `code` = 'OPERATION_CONFIG'), '500', 'OPERATION_RADIUS', '允许操作半径(m)', 0, 0, 1, NOW(), 0);
-- ============================================================
-- 2026/05/07 即将到达取件时间通知相关变更
-- ============================================================
-- 订单表增加取件通知状态字段
ALTER TABLE `orders` ADD COLUMN `PICK_UP_NOTIFY_STATUS` INT DEFAULT 0 COMMENT '取件时间即将到达通知状态:0=未通知;1=已通知';
-- 运营配置:即将到达取件时间提前通知(分钟)
INSERT INTO `system_dict_data` (`DICT_ID`, `CODE`, `LABEL`, `REMARK`, `SORT`, `DISABLED`, `CREATE_USER`, `CREATE_TIME`, `DELETED`) VALUES ((SELECT `id` FROM `system_dict` WHERE `code` = 'OPERATION_CONFIG'), '30', 'ARRIVAL_PICK_UP_TIME', '即将到达取件时间提前通知(分钟)', 0, 0, 1, NOW(), 0);
-- 定时任务:即将到达取件时间通知(每5分钟执行一次)
INSERT INTO `system_job` (`JOB_NAME`, `HANDLER`, `CRON`, `WITH_LOG`, `WITH_ASYNC`, `STATUS`, `REMARK`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) VALUES ('即将到达取件时间通知', 'arrivalPickUpNotifyJob', '0 */5 * * * ?', 1, 0, 1, '查询即将到达取件时间的订单,发送短信通知会员', 1, NOW(), 1, NOW(), 0);
-- ============================================================
-- 2026/04/30 APP版本文件上传字典配置
-- ============================================================
INSERT INTO `system_dict_data` (`DICT_ID`, `CODE`, `LABEL`, `REMARK`, `SORT`, `DISABLED`, `CREATE_USER`, `CREATE_TIME`, `DELETED`) VALUES ((SELECT `id` FROM `system_dict` WHERE `code` = 'OSS'), '', 'APP_FILES', 'APP版本文件存储路径', 0, 0, 1, NOW(), 0);
server/services/src/main/java/com/doumee/biz/system/impl/OperationConfigBizImpl.java
@@ -37,6 +37,8 @@
        dto.setNoGrabNotifyTime(getValue(Constants.OP_NO_GRAB_NOTIFY_TIME));
        dto.setNoGrabNotifyUsers(getValue(Constants.OP_NO_GRAB_NOTIFY_USERS));
        dto.setDefaultDeliveryRange(getValue(Constants.OP_DEFAULT_DELIVERY_RANGE));
        dto.setArrivalPickUpTime(getValue(Constants.OP_ARRIVAL_PICK_UP_TIME));
        dto.setOperationRadius(getValue(Constants.OP_OPERATION_RADIUS));
        return dto;
    }
@@ -55,6 +57,8 @@
        saveOrUpdate(Constants.OP_NO_GRAB_NOTIFY_TIME, "无人抢单通知时间", dto.getNoGrabNotifyTime());
        saveOrUpdate(Constants.OP_NO_GRAB_NOTIFY_USERS, "无人抢单短信通知人员", dto.getNoGrabNotifyUsers());
        saveOrUpdate(Constants.OP_DEFAULT_DELIVERY_RANGE, "默认配送范围", dto.getDefaultDeliveryRange());
        saveOrUpdate(Constants.OP_ARRIVAL_PICK_UP_TIME, "即将到达取件时间通知", dto.getArrivalPickUpTime());
//        saveOrUpdate(Constants.OP_OPERATION_RADIUS, "允许操作半径", dto.getOperationRadius());
    }
    private String getValue(String label) {
server/services/src/main/java/com/doumee/config/jwt/JwtTokenUtil.java
@@ -85,6 +85,7 @@
    public static String generateTokenForRedis(Integer userId, Integer userType, String userInfo, RedisTemplate<String,Object> redisTemplate) {
        // 删除该用户之前登录的token
        String userTokenMappingKey = Constants.REDIS_TOKEN_KEY + "user_" + userType + "_" + userId;
        String oldToken = (String) redisTemplate.opsForValue().get(userTokenMappingKey);
        if (StringUtils.isNotBlank(oldToken)) {
            redisTemplate.delete(Constants.REDIS_TOKEN_KEY + oldToken);
@@ -98,6 +99,22 @@
    }
    public static String generateShopTokenForRedis(Integer userId, String userInfo, RedisTemplate<String,Object> redisTemplate) {
        // 删除该用户之前登录的token
        String userTokenMappingKey = Constants.REDIS_TOKEN_KEY + "user_" + Constants.TWO + "_" + userId;
        String oldToken = (String) redisTemplate.opsForValue().get(userTokenMappingKey);
        if (StringUtils.isNotBlank(oldToken)) {
            redisTemplate.delete(Constants.REDIS_TOKEN_KEY + oldToken);
        }
        // 生成新token
        String tokenKey = Constants.TWO +""+ UUID.randomUUID() + "_" + userId;
        redisTemplate.opsForValue().set(Constants.REDIS_TOKEN_KEY + tokenKey, userInfo, redisExpire, TimeUnit.DAYS);
        // 记录用户与token的映射关系
        redisTemplate.opsForValue().set(userTokenMappingKey, tokenKey, redisExpire, TimeUnit.DAYS);
        return tokenKey;
    }
    /**
     * 刷新令牌
     *
server/services/src/main/java/com/doumee/config/jwt/WebMvcConfig.java
@@ -162,16 +162,16 @@
                throw new BusinessException(ResponseStatus.SHOP_BE_OVERDUE);
            }
            String openid = shop.getOpenid();
            Integer shopId = getTokenId(token);
            Integer isDeleted = dao.queryForObject(" select COALESCE(DELETED,0)  from shop_info where id  = ?", Integer.class, shopId);
            Integer shopId = shop.getId();//getTokenId(token);
            Integer isDeleted = dao.queryForObject(" select COALESCE(DELETED,0)  from shop_info where VERSION_TYPE = 0 and  id  = ?", Integer.class, shopId);
            if(isDeleted== Constants.ONE){
                throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"商户已删除,请联系管理员");
            }
            Integer isForbidden = dao.queryForObject(" select COALESCE(STATUS,0)  from shop_info where id  = ?", Integer.class, shopId);
            Integer isForbidden = dao.queryForObject(" select COALESCE(STATUS,0)  from shop_info where VERSION_TYPE = 0 and id  = ?", Integer.class, shopId);
            if(isForbidden == Constants.ONE){
                throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"商户已禁用,请联系管理员");
            }
            Integer count = dao.queryForObject("select count(1) from shop_info where id  = ?", Integer.class, shopId);
            Integer count = dao.queryForObject("select count(1) from shop_info where VERSION_TYPE = 0 and  id  = ?", Integer.class, shopId);
            if (count != null && count > 0) {
                request.setAttribute(JwtTokenUtil.SHOP_ID, shop.getId());
                return true;
server/services/src/main/java/com/doumee/core/constants/Constants.java
@@ -47,6 +47,8 @@
    public static final String FEE_STANDARDS ="FEE_STANDARDS" ;
    public static final String ABOUT_US ="ABOUT_US" ;
    public static final String SERVER_PHONE ="SERVER_PHONE" ;
    public static final String ARRIVAL_PICK_UP_TIME ="ARRIVAL_PICK_UP_TIME" ;//即将到达取件时间配置(分钟)
    public static final String USER_AGREEMENT ="USER_AGREEMENT" ;
    public static final String PRIVACY_AGREEMENT ="PRIVACY_AGREEMENT" ;
@@ -104,6 +106,8 @@
    public static final String OP_NO_GRAB_NOTIFY_TIME = "NO_GRAB_NOTIFY_TIME";
    public static final String OP_NO_GRAB_NOTIFY_USERS = "NO_GRAB_NOTIFY_USERS";
    public static final String OP_DEFAULT_DELIVERY_RANGE = "DEFAULT_DELIVERY_RANGE";
    public static final String OP_ARRIVAL_PICK_UP_TIME = "ARRIVAL_PICK_UP_TIME";
    public static final String OP_OPERATION_RADIUS = "OPERATION_RADIUS";
    // 芯烨云打印机配置
    public static final String XPYUN_CONFIG = "XPYUN_CONFIG";
@@ -393,7 +397,7 @@
        deposited(2, "已寄存"),
        accepted(3, "已接单"),
        delivering(4, "派送中"),
        arrived(5, "已到店/已送达/待取件"),
        arrived(5, "已到店/已送达"),
        overdue(6, "存在逾期"), //弃用
        finished(7, "已完成"),
        cancelled(99, "已取消")
@@ -611,27 +615,29 @@
    @Getter
    @AllArgsConstructor
    public enum SmsNotify {
        PLATFORM_WAIT_GRAB("SMS_505865290", "平台端-待抢单", "您好,订单:{orderNo}已超过{time}分钟无司机抢单,请尽快加急派单,避免客户过久等待。"),
        SHOP_REFUNDING("SMS_505905263", "门店端-退款中", "行李订单:{orderNo}客户已提交退款申请,请尽快处理。"),
        SHOP_WAIT_VERIFY("SMS_505915292", "门店端-待核验", "新行李订单:{orderNo}客户已支付,请尽快核验用户物品信息。"),
        DRIVER_REFUNDING("SMS_505905264", "司机端-退款中", "行李订单:{orderNo}用户已提交退款申请,该订单任务已取消,请勿前往。"),
        DRIVER_WAIT_PICKUP("SMS_505960277", "司机端-待取件", "您已抢单成功,订单{orderNo},请按时到{address}取件。"),
        MEMBER_CANCELLED("SMS_505615328", "会员端-已取消", "您的行李订单:{orderNo}已取消,感谢您的支持,欢迎下次再会。"),
        MEMBER_REFUNDED("SMS_505850299", "会员端-已退款", "您的行李订单:{orderNo}退款已完成,金额{money}元将原路退回,请注意查收。"),
        MEMBER_ARRIVED("SMS_505645328", "会员端-已送达", "您的行李订单:{orderNo}已送到{address},请及时取件,取件码:{code}。"),
        MEMBER_DELIVERING("SMS_505715321", "会员端-配送中", "您的行李订单:{orderNo}已由司机{name}取件,正运往目的地。"),
        VERIFY_CODE("SMS_333770877", "验证码短信", "您的验证码为:{code},请勿泄露于他人!"),
        DRIVER_AUTH_REJECTED("SMS_505790115", "司机端-司机认证被拒绝", "尊敬的{driver},很遗憾,您的司机认证未通过审核。原因:{reason}。您可修改资料后重新提交。"),
        DRIVER_AUTH_APPROVED("SMS_505885083", "司机端-司机认证通过", "尊敬的{driver},恭喜您已通过平台司机认证审核。您可登录司机端APP开始接单,配送过程中请注意安全,祝您接单顺利!"),
        DRIVER_URGENT_DISPATCH("SMS_505940293", "司机端-加急派单", "您好,您有一个新的行李订单(编号:{orderNo})。起点:{address1},终点:{address2},配送费{money1}元(含加急费{money2}元)。请尽快确认订单任务。"),
        SHOP_AUTH_REJECTED("SMS_505925106", "门店端-资料审核被拒绝", "很遗憾,您的门店\"{storeName}\"未通过审核,原因:{reason},您可修改资料后重新提交。"),
        SHOP_AUTH_APPROVED_DEPOSIT("SMS_505705111", "门店端-审核通过需缴纳押金", "恭喜您!您的门店\"{storeName}\"已通过初步审核。请支付押金{money}元以完成入驻,支付后即可登录门店后台正式接单。"),
        SHOP_AUTH_SUCCESS("SMS_505915289", "门店端-成功入驻通知", "恭喜您!您的门店\"{storeName}\"已通过平台审核,正式入驻成功。 您可登录商家后台开始接单,账号:注册手机号,初始密码::{password}(建议首次登录后修改)。"),
        PLATFORM_WAIT_GRAB("SMS_505865290", "平台端-待抢单", "您好,订单:{orderNo}已超过{time}分钟无司机抢单,请尽快加急派单,避免客户过久等待。", true),
        SHOP_REFUNDING("SMS_505905263", "门店端-退款中", "行李订单:{orderNo}客户已提交退款申请,请尽快处理。", false),
        SHOP_WAIT_VERIFY("SMS_505915292", "门店端-待核验", "新行李订单:{orderNo}客户已支付,请尽快核验用户物品信息。", false),
        DRIVER_REFUNDING("SMS_505905264", "司机端-退款中", "行李订单:{orderNo}用户已提交退款申请,该订单任务已取消,请勿前往。", true),
        DRIVER_WAIT_PICKUP("SMS_505960277", "司机端-待取件", "您已抢单成功,订单{orderNo},请按时到{address}取件。", true),
        MEMBER_CANCELLED("SMS_505615328", "会员端-已取消", "您的行李订单:{orderNo}已取消,感谢您的支持,欢迎下次再会。", true),
        MEMBER_REFUNDED("SMS_505850299", "会员端-已退款", "您的行李订单:{orderNo}退款已完成,金额{money}元将原路退回,请注意查收。", true),
        MEMBER_ARRIVED("SMS_505645328", "会员端-已送达", "您的行李订单:{orderNo}已送到{address},请及时取件,取件码:{code}。", true),
        MEMBER_DELIVERING("SMS_505715321", "会员端-配送中", "您的行李订单:{orderNo}已由司机{name}取件,正运往目的地。", true),
        VERIFY_CODE("SMS_333770877", "验证码短信", "您的验证码为:{code},请勿泄露于他人!", true),
        DRIVER_AUTH_REJECTED("SMS_505790115", "司机端-司机认证被拒绝", "尊敬的{driver},很遗憾,您的司机认证未通过审核。原因:{reason}。您可修改资料后重新提交。", true),
        DRIVER_AUTH_APPROVED("SMS_505885083", "司机端-司机认证通过", "尊敬的{driver},恭喜您已通过平台司机认证审核。您可登录司机端APP开始接单,配送过程中请注意安全,祝您接单顺利!", true),
        DRIVER_URGENT_DISPATCH("SMS_505940293", "司机端-加急派单", "您好,您有一个新的行李订单(编号:{orderNo})。起点:{address1},终点:{address2},配送费{money1}元(含加急费{money2}元)。请尽快确认订单任务。", true),
        SHOP_AUTH_REJECTED("SMS_505925106", "门店端-资料审核被拒绝", "很遗憾,您的门店\"{storeName}\"未通过审核,原因:{reason},您可修改资料后重新提交。", true),
        SHOP_AUTH_APPROVED_DEPOSIT("SMS_506135030", "门店端-审核通过需缴纳押金", "恭喜您!您的门店\"{storeName}\"已通过初步审核。请支付押金{money}元以完成入驻,支付后即可登录门店后台正式接单。", true),
        SHOP_AUTH_SUCCESS("SMS_505885083", "门店端-成功入驻通知", "恭喜您!您的门店\"{storeName}\"已通过平台审核,正式入驻成功。 您可登录商家后台开始接单,账号:注册手机号,初始密码::{password}(建议首次登录后修改)。", true),
        MEMBER_TIME_OUT("SMS_506190182", "会员端-即将超时", "您的行李订单:{orderNo}即将到达预计取件时间,请尽快取件,超时将产生逾期费用,请知悉。", true),
        ;
        private final String templateCode;
        private final String title;
        private final String content;
        private final boolean enabled;
        public String format(String... params) {
            String result = this.content;
server/services/src/main/java/com/doumee/dao/business/model/DriverInfo.java
@@ -240,4 +240,10 @@
    @ApiModelProperty(value = "极光推送别名")
    private String jpushAlias;
    @ApiModelProperty(value = "司机版本类型:0=正式版本;1=变更版本", example = "0")
    private Integer versionType;
    @ApiModelProperty(value = "关联正式版本司机主键(变更版本使用)", example = "1")
    private Integer relationDriverId;
}
server/services/src/main/java/com/doumee/dao/business/model/Member.java
@@ -128,6 +128,9 @@
    @ApiModelProperty(value = "用户类型:0=会员用户;1=司机;2=店铺人员;(司机与店铺均和会员表使用同主键值)", example = "1")
    private Integer userType;
    @ApiModelProperty(value = "已登录的门店主键", example = "1")
    private Integer loginShopId;
    @ApiModelProperty(value = "业务状态:0=未认证;1=认证通过;2=认证未通过 ;3=已支付押金", example = "1")
    private Integer businessStatus;
server/services/src/main/java/com/doumee/dao/business/model/Orders.java
@@ -371,12 +371,12 @@
    @TableField(exist = false)
    @ApiModelProperty(value = "创建开始时间(查询用)", example = "2026-01-01 00:00:00")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date createStartTime;
    @TableField(exist = false)
    @ApiModelProperty(value = "创建结束时间(查询用)", example = "2026-12-31 23:59:59")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date createEndTime;
    @TableField(exist = false)
@@ -448,6 +448,12 @@
    private Integer shopId;
    @ApiModelProperty(value = "店铺订单序号")
    private Long autoNum;
    @ApiModelProperty(value = "取件时间即将到达通知状态:0=未通知;1=已通知")
    private Integer pickUpNotifyStatus;
    @ApiModelProperty(value = "序号")
    @TableField(exist = false)
    private Integer sortNum;
server/services/src/main/java/com/doumee/dao/business/model/ShopInfo.java
@@ -189,6 +189,24 @@
    @ApiModelProperty(value = "芯烨云打印机SN编号")
    private String printerSn;
    @ApiModelProperty(value = "收益比例配置(JSON)", notes = "{\n" +
            "  \"localDeposit\": 5,\n" +
            "  \"remoteDeposit\": 5,\n" +
            "  \"remoteTake\": 5\n" +
            "}")
    private String revenueShareConfig;
    @ApiModelProperty(value = "门店版本类型:0=正式版本;1=变更版本", example = "0")
    private Integer versionType;
    @ApiModelProperty(value = "关联正式版本门店主键(变更版本使用)", example = "1")
    private Integer relationShopId;
    @ApiModelProperty(value = "当前登录的会员主键", hidden = true)
    @TableField(exist = false)
    private Integer memberId;
    // 非持久化:附件列表
    @TableField(exist = false)
server/services/src/main/java/com/doumee/dao/dto/DriverActiveOrderDTO.java
@@ -19,4 +19,7 @@
    @ApiModelProperty(value = "订单状态:3=已抢单;4=派送中", example = "3", required = true)
    private Integer status;
    @ApiModelProperty(value = "搜索关键词(收件人/收件人电话模糊/订单号精准)")
    private String keyword;
}
server/services/src/main/java/com/doumee/dao/dto/MyOrderDTO.java
@@ -21,4 +21,7 @@
    @ApiModelProperty(value = "合并状态(可选,不传查全部): 0=待支付 1=待核验 2=待配送 3=待收货 4=已完成 5=退款 6会员首页 7门店待处理订单", example = "0")
    private Integer combinedStatus;
    @ApiModelProperty(value = "搜索关键词(收件人/收件人电话模糊/订单号精准)")
    private String keyword;
}
server/services/src/main/java/com/doumee/dao/dto/OperationConfigDTO.java
@@ -46,4 +46,10 @@
    @ApiModelProperty(value = "默认配送范围(km)", required = true)
    private String defaultDeliveryRange;
    @ApiModelProperty(value = "即将到达取件时间提前通知(分钟)", required = true)
    private String arrivalPickUpTime;
    @ApiModelProperty(value = "允许操作半径(m)", required = true)
    private String operationRadius;
}
server/services/src/main/java/com/doumee/dao/dto/RevenueShareItemDTO.java
@@ -16,7 +16,7 @@
@ApiModel("分成比例规则项")
public class RevenueShareItemDTO implements Serializable {
    @ApiModelProperty(value = "类型(0=企业寄, 1=个人寄, 2=企业取, 3=个人取, 4=配送员)", required = true, example = "0")
    @ApiModelProperty(value = "类型(0=异地企业寄, 1=异地个人寄, 2=异地企业取, 3=异地个人取, 4=配送员, 5=就地企业存, 6=就地个人存)", required = true, example = "0")
    @NotNull(message = "类型不能为空")
    private Integer fieldType;
server/services/src/main/java/com/doumee/dao/dto/RevenueShareSaveDTO.java
@@ -23,7 +23,7 @@
    @NotNull(message = "城市主键不能为空")
    private Integer cityId;
    @ApiModelProperty(value = "规则明细列表(企业寄/个人寄/企业取/个人取/配送员共5条)", required = true)
    @ApiModelProperty(value = "规则明细列表(异地企业寄/异地个人寄/异地企业取/异地个人取/配送员/就地企业存/就地个人存共7条)", required = true)
    @NotEmpty(message = "规则明细不能为空")
    @Valid
    private List<RevenueShareItemDTO> items;
server/services/src/main/java/com/doumee/dao/dto/ShopApplyDTO.java
@@ -111,4 +111,13 @@
    @ApiModelProperty(value = "支付宝实名姓名", required = true)
    @NotBlank(message = "支付宝实名姓名不能为空")
    private String aliName;
    @ApiModelProperty(value = "就地寄存分成占比(%)", required = true, example = "5.5")
    private Double localDeposit;
    @ApiModelProperty(value = "异地存件分成占比(%)", required = true, example = "5.5")
    private Double remoteDeposit;
    @ApiModelProperty(value = "异地取件分成占比(%)", required = true, example = "5.5")
    private Double remoteTake;
}
server/services/src/main/java/com/doumee/dao/dto/ShopLoginDTO.java
@@ -8,8 +8,8 @@
@ApiModel("门店登录请求对象")
public class ShopLoginDTO {
    @ApiModelProperty(value = "openid")
    private String openid;
    @ApiModelProperty(value = "会员主键")
    private Integer memberId;
    @ApiModelProperty(value = "登录手机号")
    private String telephone;
server/services/src/main/java/com/doumee/dao/dto/ShopUpdateDTO.java
@@ -118,4 +118,16 @@
    @NotBlank(message = "支付宝实名姓名不能为空")
    private String aliName;
    @ApiModelProperty(value = "就地寄存分成占比(%)", required = true, example = "5.5")
    @NotNull(message = "就地寄存分成占比不能为空")
    private Double localDeposit;
    @ApiModelProperty(value = "异地存件分成占比(%)", required = true, example = "5.5")
    @NotNull(message = "异地存件分成占比不能为空")
    private Double remoteDeposit;
    @ApiModelProperty(value = "异地取件分成占比(%)", required = true, example = "5.5")
    @NotNull(message = "异地取件分成占比不能为空")
    private Double remoteTake;
}
server/services/src/main/java/com/doumee/dao/vo/PlatformAboutVO.java
@@ -61,4 +61,7 @@
    @ApiModelProperty(value = "禁寄物品")
    private String prohibitedItems;
    @ApiModelProperty(value = "服务电话")
    private String serverPhone;
}
server/services/src/main/java/com/doumee/dao/vo/RevenueShareVO.java
@@ -21,7 +21,7 @@
    @ApiModelProperty(value = "城市主键")
    private Integer cityId;
    @ApiModelProperty(value = "类型(0=企业寄, 1=个人寄, 2=企业取, 3=个人取, 4=配送员)")
    @ApiModelProperty(value = "类型(0=异地企业寄, 1=异地个人寄, 2=异地企业取, 3=异地个人取, 4=配送员, 5=就地企业存, 6=就地个人存)")
    private Integer fieldType;
    @ApiModelProperty(value = "类型名称")
server/services/src/main/java/com/doumee/dao/vo/ShopDetailVO.java
@@ -127,6 +127,9 @@
    @ApiModelProperty(value = "门店评分")
    private BigDecimal score;
    @ApiModelProperty(value = "配送距离")
    private BigDecimal deliveryRange;
    @ApiModelProperty(value = "创建时间")
    private Date createTime;
@@ -172,4 +175,19 @@
    @ApiModelProperty(value = "门店头像(全路径)")
    private String shopAvatar;
    @ApiModelProperty(value = "就地寄存分成占比(%)", example = "5.5")
    private Double localDeposit;
    @ApiModelProperty(value = "异地存件分成占比(%)", example = "5.5")
    private Double remoteDeposit;
    @ApiModelProperty(value = "异地取件分成占比(%)", example = "5.5")
    private Double remoteTake;
    @ApiModelProperty(value = "门店版本类型:0=正式版本;1=变更版本", example = "0")
    private Integer versionType;
    @ApiModelProperty(value = "关联正式版本门店主键")
    private Integer relationShopId;
}
server/services/src/main/java/com/doumee/dao/vo/UserCenterVO.java
@@ -45,7 +45,7 @@
    @ApiModelProperty(value = "我注册的门店主键(门店用户时返回)")
    private Integer shopId;
    @ApiModelProperty(value = "我注册的门店审核状态(门店用户时返回): 0=待审核 1=已通过 2=已驳回 3=已缴纳保证金")
    @ApiModelProperty(value = "我注册的门店审核状态: 0=待审批 1=已通过 2=已驳回 3=已支付押金 4=变更中 5=变更未通过")
    private Integer shopAuditStatus;
    @ApiModelProperty(value = "我当前绑定的门店")
server/services/src/main/java/com/doumee/service/business/MemberService.java
@@ -14,6 +14,7 @@
import com.doumee.dao.dto.UpdMobileRequest;
import com.doumee.dao.dto.WxPhoneRequest;
import com.doumee.dao.vo.AccountResponse;
import com.doumee.dao.vo.MemberContactVO;
import com.doumee.dao.vo.MemberDetailVO;
import com.doumee.dao.vo.MemberListVO;
import com.doumee.dao.vo.PlatformAboutVO;
@@ -153,6 +154,8 @@
    void logOff(String token,Integer memberId);
    MemberContactVO getContactInfo(Integer memberId);
    /**
     * 验证手机号
server/services/src/main/java/com/doumee/service/business/OrdersService.java
@@ -431,4 +431,21 @@
     */
    void printOrderLabel(Integer orderId, Integer shopId);
    /**
     * 即将到达取件时间通知
     * @return 通知数量
     */
    int notifyArrivalPickUp();
    /**
     * 校验操作半径
     * @param orderId     订单主键
     * @param userId      操作用户主键
     * @param userType    用户类型:0=门店;1=司机
     * @param lng         操作人经度
     * @param lat         操作人纬度
     * @return true=在允许操作范围内;false=超出允许操作范围
     */
    Boolean checkOperationRadius(Integer orderId, Integer userId, Integer userType, Double lng, Double lat);
}
server/services/src/main/java/com/doumee/service/business/impl/DriverInfoServiceImpl.java
@@ -300,7 +300,9 @@
        String code = RandomStringUtils.randomNumeric(6);
        // 发送短信
        String templateParam = "{\"code\":\"" + code + "\"}";
        AliSmsService.sendSms(telephone, Constants.SmsNotify.VERIFY_CODE.getTemplateCode(), templateParam);
        if (Constants.SmsNotify.VERIFY_CODE.isEnabled()) {
            AliSmsService.sendSms(telephone, Constants.SmsNotify.VERIFY_CODE.getTemplateCode(), templateParam);
        }
        // 保存短信记录
        Smsrecord smsrecord = new Smsrecord();
        smsrecord.setDeleted(Constants.ZERO);
@@ -406,6 +408,7 @@
            driverInfo.setMemberId(member.getId());
            driverInfo.setStatus(Constants.ZERO);
            driverInfo.setAuditStatus(99);
            driverInfo.setVersionType(Constants.ZERO);
            driverInfo.setJpushAlias(org.springframework.util.DigestUtils.md5DigestAsHex(telephone.getBytes()));
            driverInfoMapper.insert(driverInfo);
        }
@@ -1143,6 +1146,13 @@
                .eq(Objects.nonNull(dto.getStatus()),Orders::getStatus, dto.getStatus())
                .eq(Orders::getDeleted, Constants.ZERO)
                .orderByAsc(Orders::getAcceptTime);
        // 关键词搜索:收件人/收件人电话模糊、订单号精准
        if (StringUtils.isNotBlank(dto.getKeyword())) {
            String kw = dto.getKeyword().trim();
            wrapper.and(w -> w.like(Orders::getTakeUser, kw)
                    .or().like(Orders::getTakePhone, kw)
                    .or().eq(Orders::getCode, kw));
        }
        List<Orders> ordersList = ordersMapper.selectJoinList(Orders.class, wrapper);
@@ -1876,6 +1886,9 @@
        if (StringUtils.isBlank(phone)) {
            return;
        }
        if (!smsNotify.isEnabled()) {
            return;
        }
        String content = smsNotify.format(paramPairs);
        try {
            JSONObject templateParam = new JSONObject();
@@ -1996,7 +2009,7 @@
                .select("s1.name", Orders::getDepositShopName)
                .select("s1.address", Orders::getDepositShopAddress)
                .select("s2.name", Orders::getTakeShopName)
                .select("s2.address", Orders::getDepositShopAddress)
                .select("s2.address", Orders::getTakeShopAddress)
                .select("s1.link_phone as takeShopLinkPhone")
                .select("s2.link_phone as takeShopLinkPhone")
                .select("c2.other_field as c2OtherField")
server/services/src/main/java/com/doumee/service/business/impl/MemberServiceImpl.java
@@ -25,6 +25,7 @@
import com.doumee.dao.dto.UpdMobileRequest;
import com.doumee.dao.dto.WxPhoneRequest;
import com.doumee.dao.vo.AccountResponse;
import com.doumee.dao.vo.MemberContactVO;
import com.doumee.dao.vo.MemberDetailVO;
import com.doumee.dao.vo.MemberListVO;
import com.doumee.dao.vo.PlatformAboutVO;
@@ -353,13 +354,40 @@
     * 门店用户身份时,填充门店审核状态
     */
    private void fillShopInfo(UserCenterVO userCenterVO, Member member) {
        ShopInfo shopInfo = shopInfoMapper.selectOne(new QueryWrapper<ShopInfo>().lambda()
        // 查询正式版本门店
        ShopInfo official = shopInfoMapper.selectOne(new QueryWrapper<ShopInfo>().lambda()
                .eq(ShopInfo::getRegionMemberId, member.getId())
                .eq(ShopInfo::getVersionType, Constants.ZERO)
                .eq(ShopInfo::getDeleted, Constants.ZERO)
                .last("limit 1"));
        if (shopInfo != null) {
            userCenterVO.setShopId(shopInfo.getId());
            userCenterVO.setShopAuditStatus(shopInfo.getAuditStatus());
        if (official != null) {
            userCenterVO.setShopId(official.getId());
            if (Constants.equalsInteger(official.getAuditStatus(), Constants.THREE)) {
                // 正式版本已支付押金,查询最新变更版本状态
                ShopInfo changeVersion = shopInfoMapper.selectOne(new QueryWrapper<ShopInfo>().lambda()
                        .eq(ShopInfo::getRelationShopId, official.getId())
                        .eq(ShopInfo::getVersionType, Constants.ONE)
                        .eq(ShopInfo::getDeleted, Constants.ZERO)
                        .orderByDesc(ShopInfo::getCreateTime)
                        .last("limit 1"));
                if (changeVersion != null) {
                    if (Constants.equalsInteger(changeVersion.getAuditStatus(), Constants.THREE)) {
                        userCenterVO.setShopAuditStatus(Constants.THREE);
                    } else if (Constants.equalsInteger(changeVersion.getAuditStatus(), Constants.ZERO)) {
                        userCenterVO.setShopAuditStatus(4); // 变更中
                    } else if (Constants.equalsInteger(changeVersion.getAuditStatus(), Constants.TWO)) {
                        userCenterVO.setShopAuditStatus(5); // 变更未通过
                    } else {
                        userCenterVO.setShopAuditStatus(Constants.THREE);
                    }
                } else {
                    // 无变更版本,保持正式版本状态
                    userCenterVO.setShopAuditStatus(Constants.THREE);
                }
            } else {
                // 正式版本未支付押金,直接返回当前状态
                userCenterVO.setShopAuditStatus(official.getAuditStatus());
            }
        }
        // 根据openid查询当前绑定的门店
        if (StringUtils.isNotBlank(member.getOpenid())) {
@@ -458,6 +486,7 @@
        vo.setDriverPrivacyPolicy(getDictValue(Constants.DRIVER_PRIVACY_POLICY));
        vo.setPriceDescription(getDictValue(Constants.PRICE_DESCRIPTION));
        vo.setProhibitedItems(getDictValue(Constants.PROHIBITED_ITEMS));
        vo.setServerPhone(StringUtils.trimToNull(systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.SERVER_PHONE).getCode()));
        return vo;
    }
@@ -473,8 +502,14 @@
    public void logOut(String token,Integer userId,Integer userType){
        if(Constants.equalsInteger(userType,Constants.ZERO)){
            memberMapper.update(new UpdateWrapper<Member>().lambda().setSql(" openid = null ").eq(Member::getId,userId));
        }else if(Constants.equalsInteger(userType,Constants.TWO)){
            shopInfoMapper.update(new UpdateWrapper<ShopInfo>().lambda().setSql(" openid = null ").eq(ShopInfo::getId,userId));
        }else if (Constants.equalsInteger(userType,Constants.TWO)){
            try {
                Integer lastIndex = token.lastIndexOf("_")+1;
                Integer tokenId = Integer.valueOf(token.substring(lastIndex));
                memberMapper.update(new UpdateWrapper<Member>().lambda().setSql(" LOGIN_SHOP_ID = null ").eq(Member::getId,tokenId));
            }catch (Exception e){
            }
        }
        jwtTokenUtil.logout(token);
    }
@@ -493,6 +528,31 @@
        redisTemplate.delete(token);
    }
    @Override
    public MemberContactVO getContactInfo(Integer memberId) {
        MemberContactVO vo = new MemberContactVO();
        // 优先从历史订单取收件人信息
        Orders lastOrder = ordersMapper.selectOne(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getMemberId, memberId)
                .eq(Orders::getDeleted, Constants.ZERO)
                .isNotNull(Orders::getTakeUser)
                .ne(Orders::getTakeUser, "")
                .orderByDesc(Orders::getId)
                .last("limit 1"));
        if (lastOrder != null && StringUtils.isNotBlank(lastOrder.getTakeUser())) {
            vo.setName(lastOrder.getTakeUser());
            vo.setPhone(lastOrder.getTakePhone());
            return vo;
        }
        // 取用户信息
        Member member = memberMapper.selectById(memberId);
        if (member != null) {
            vo.setName(member.getName() != null ? member.getName() : "");
            vo.setPhone(member.getTelephone() != null ? member.getTelephone() : "");
        }
        return vo;
    }
    /***************管理端会员列表接口*************/
server/services/src/main/java/com/doumee/service/business/impl/OrdersRefundServiceImpl.java
@@ -173,7 +173,7 @@
                wrapper.ge(OrdersRefund::getCreateTime, model.getCreateStartTime());
            }
            if (model.getCreateEndTime() != null) {
                wrapper.le(OrdersRefund::getCreateTime, model.getCreateEndTime());
                wrapper.le(OrdersRefund::getCreateTime, Utils.Date.getEnd(model.getCreateEndTime()));
            }
        }
server/services/src/main/java/com/doumee/service/business/impl/OrdersServiceImpl.java
@@ -21,6 +21,7 @@
import com.doumee.core.model.PageData;
import com.doumee.core.model.PageWrap;
import com.doumee.core.utils.DateUtil;
import com.doumee.core.utils.GeoUtils;
import com.doumee.core.utils.ID;
import com.doumee.core.utils.geocode.MapUtil;
import com.doumee.core.utils.Utils;
@@ -237,7 +238,7 @@
        queryWrapper.like(StringUtils.isNotBlank(pageWrap.getModel().getTakeShopName()),  "s2.name",  pageWrap.getModel().getTakeShopName());
        queryWrapper.like(StringUtils.isNotBlank(pageWrap.getModel().getGoodsInfo()), Orders::getGoodsInfo, pageWrap.getModel().getGoodsInfo());
        queryWrapper.ge(pageWrap.getModel().getCreateStartTime() != null, Orders::getCreateTime, pageWrap.getModel().getCreateStartTime());
        queryWrapper.le(pageWrap.getModel().getCreateEndTime() != null, Orders::getCreateTime, pageWrap.getModel().getCreateEndTime());
        queryWrapper.le(pageWrap.getModel().getCreateEndTime() != null, Orders::getCreateTime, Utils.Date.getEnd(pageWrap.getModel().getCreateEndTime()));
        queryWrapper.eq(pageWrap.getModel().getDepositShopId() != null, Orders::getDepositShopId, pageWrap.getModel().getDepositShopId());
        queryWrapper.eq(pageWrap.getModel().getType() != null, Orders::getType, pageWrap.getModel().getType());
        queryWrapper.eq(pageWrap.getModel().getStatus() != null, Orders::getStatus, pageWrap.getModel().getStatus());
@@ -254,6 +255,9 @@
            if (o.getStatus() != null) {
                Constants.OrderStatus os = Constants.OrderStatus.getByKey(o.getStatus());
                o.setStatusDesc(os != null ? os.getValue() : "");
            }
            if(Constants.equalsInteger(o.getIsUrgent(),Constants.ZERO)){
                o.setUrgentAmount(Constants.ZERO.longValue());
            }
        }
        return pageData;
@@ -504,6 +508,7 @@
        // 3. 逐项计算运费:起步价 + 超出部分阶梯价
        List<ItemPriceVO> itemList = new ArrayList<>();
        long itemPriceTotal = 0L;
        long maxExtraFeeTotal = 0L; // 最大超出距离费用(单价×数量)
        for (OrderItemDTO item : dto.getItems()) {
            PricingRule rule = ruleMap.get(String.valueOf(item.getCategoryId()));
@@ -519,13 +524,15 @@
            long extraPricePerUnit = Long.parseLong(rule.getFieldE());
            // 阶梯计价:距离 ≤ 起步距离取起步价,超出按 ceil(超出距离/单位) × 单价累加
            long extraFee = 0L;
            long unitPrice;
            if (distanceKm.compareTo(startDistance) <= 0) {
                unitPrice = startPrice;
            } else {
                BigDecimal extraKm = distanceKm.subtract(startDistance);
                BigDecimal extraCount = extraKm.divide(extraDistanceUnit, 0, RoundingMode.CEILING);
                unitPrice = startPrice + extraCount.longValue() * extraPricePerUnit;
                extraFee = extraCount.longValue() * extraPricePerUnit;
                unitPrice = startPrice + extraFee;
            }
            long subtotal = unitPrice * item.getQuantity();
@@ -548,8 +555,17 @@
            vo.setExtraPrice(extraPricePerUnit);
            itemList.add(vo);
            itemPriceTotal += subtotal;
            // 所有物品的起步价×数量 累加
            itemPriceTotal += startPrice * item.getQuantity();
            // 记录最大的超出距离费用
            long extraFeeTotal = extraFee * item.getQuantity();
            if (extraFeeTotal > maxExtraFeeTotal) {
                maxExtraFeeTotal = extraFeeTotal;
            }
        }
        // 多物品时只加最大的超出距离费用
        itemPriceTotal += maxExtraFeeTotal;
        // 4. 保价费用:报价金额 × 保价费率(字典 INSURANCE_RATE),元→分(保价金额>0时计费)
        long insuranceFeeFen = 0L;
@@ -1432,20 +1448,31 @@
            return;
        }
        Integer cityId = Integer.valueOf(orders.getCityId());
        boolean isRemote = Constants.equalsInteger(orders.getType(), Constants.ONE);
        boolean isCompany = Constants.equalsInteger(depositShop.getCompanyType(), Constants.ONE);
        // 司机占比:fieldA=4(配送员)
        // 司机占比:fieldA=4(配送员),始终从城市配置取
        BigDecimal driverRata = getRevenueShareRata(cityId, Constants.FOUR);
        // 寄件门店占比:fieldA=0(企业寄)/1(个人寄)
        int depositFieldA = Constants.equalsInteger(depositShop.getCompanyType(), Constants.ONE) ? Constants.ZERO : Constants.ONE;
        BigDecimal depositShopRata = getRevenueShareRata(cityId, depositFieldA);
        // 取件门店占比:无取件门店时比例为0
        BigDecimal takeShopRata = BigDecimal.ZERO;
        if (takeShop != null) {
            int takeFieldA = Constants.equalsInteger(takeShop.getCompanyType(), Constants.ONE) ? Constants.TWO : Constants.THREE;
            takeShopRata = getRevenueShareRata(cityId, takeFieldA);
        // 存件门店占比
        BigDecimal depositShopRata;
        if (isRemote) {
            int fallbackFieldA = isCompany ? Constants.ZERO : Constants.ONE;
            depositShopRata = getShopRevenueShare(depositShop, "remoteDeposit", cityId, fallbackFieldA);
        } else {
            int fallbackFieldA = isCompany ? Constants.FIVE : Constants.SIX;
            depositShopRata = getShopRevenueShare(depositShop, "localDeposit", cityId, fallbackFieldA);
        }
        // 计算薪酬(分):totalAmount 为分,rata 为比例值(如 0.15 表示 15%)
        // 取件门店占比
        BigDecimal takeShopRata = BigDecimal.ZERO;
        if (isRemote && takeShop != null) {
            boolean takeIsCompany = Constants.equalsInteger(takeShop.getCompanyType(), Constants.ONE);
            int fallbackFieldA = takeIsCompany ? Constants.TWO : Constants.THREE;
            takeShopRata = getShopRevenueShare(takeShop, "remoteTake", cityId, fallbackFieldA);
        }
        // 计算薪酬(分)
        long driverFee = new BigDecimal(totalAmount).multiply(driverRata).longValue();
        long depositShopFee = new BigDecimal(totalAmount).multiply(depositShopRata).longValue();
        long takeShopFee = new BigDecimal(totalAmount).multiply(takeShopRata).longValue();
@@ -1462,7 +1489,7 @@
     * 从 pricing_rule 表获取分成比例(type=4)
     *
     * @param cityId   城市主键
     * @param fieldA   类型:0=企业寄, 1=个人寄, 2=企业取, 3=个人取, 4=配送员
     * @param fieldA   类型:0=异地企业寄, 1=异地个人寄, 2=异地企业取, 3=异地个人取, 4=配送员, 5=就地企业存, 6=就地个人存
     * @return 分成比例(如 0.15 表示 15%)
     */
    private BigDecimal getRevenueShareRata(Integer cityId, int fieldA) {
@@ -1473,10 +1500,35 @@
                .eq(PricingRule::getFieldA, String.valueOf(fieldA))
                .last("limit 1"));
        if (rule != null && StringUtils.isNotBlank(rule.getFieldB())) {
            // fieldB 存储的是百分比整数(如15表示15%),转换为小数比例(0.15)
            // fieldB 存储的是百分比数字(如15表示15%),转换为小数比例(0.15)
            return new BigDecimal(rule.getFieldB()).divide(new BigDecimal("100"), 4, BigDecimal.ROUND_HALF_UP);
        }
        return BigDecimal.ZERO;
    }
    /**
     * 获取门店收益占比:优先从门店 revenueShareConfig 取,无则兜底城市 pricing_rule
     *
     * @param shop           门店信息
     * @param jsonKey        revenueShareConfig 中的键名
     * @param cityId         城市主键(兜底用)
     * @param fallbackFieldA 兜底 pricing_rule fieldA 值
     * @return 分成比例
     */
    private BigDecimal getShopRevenueShare(ShopInfo shop, String jsonKey, Integer cityId, int fallbackFieldA) {
        if (shop != null && StringUtils.isNotBlank(shop.getRevenueShareConfig())) {
            try {
                JSONObject config = JSONObject.parseObject(shop.getRevenueShareConfig());
                if (config != null && config.containsKey(jsonKey)) {
                    Double value = config.getDouble(jsonKey);
                    if (value != null) {
                        return new BigDecimal(value).divide(new BigDecimal("100"), 4, BigDecimal.ROUND_HALF_UP);
                    }
                }
            } catch (Exception ignored) {
            }
        }
        return getRevenueShareRata(cityId, fallbackFieldA);
    }
    @Override
@@ -1516,6 +1568,13 @@
                .eq(status != null, Orders::getStatus, status)
                .in(statusList != null, Orders::getStatus, statusList)
                .orderByDesc(Orders::getCreateTime);
        // 关键词搜索:收件人/收件人电话模糊、订单号精准
        if (model != null && StringUtils.isNotBlank(model.getKeyword())) {
            String kw = model.getKeyword().trim();
            wrapper.and(w -> w.like(Orders::getTakeUser, kw)
                    .or().like(Orders::getTakePhone, kw)
                    .or().eq(Orders::getCode, kw));
        }
        IPage<Orders> orderPage = ordersMapper.selectJoinPage(p, Orders.class, wrapper);
        List<MyOrderVO> voList = new ArrayList<>();
@@ -1665,8 +1724,20 @@
        }
        wrapper.eq(status != null, Orders::getStatus, status)
                .in(statusList != null && !Constants.equalsInteger(combinedStatus, Constants.SEVEN), Orders::getStatus, statusList)
                .orderByDesc(Orders::getId);
                .in(statusList != null && !Constants.equalsInteger(combinedStatus, Constants.SEVEN), Orders::getStatus, statusList);
        if(Objects.nonNull(model.getCombinedStatus())&&Constants.equalsInteger(model.getCombinedStatus(),Constants.OrderCombinedStatus.finished.getKey())){
            wrapper.orderByDesc(Orders::getFinishTime);
        }else{
            wrapper.orderByDesc(Orders::getId);
        }
        // 关键词搜索:收件人/收件人电话模糊、订单号精准
        MyOrderDTO shopModel = pageWrap.getModel();
        if (shopModel != null && StringUtils.isNotBlank(shopModel.getKeyword())) {
            String kw = shopModel.getKeyword().trim();
            wrapper.and(w -> w.like(Orders::getTakeUser, kw)
                    .or().like(Orders::getTakePhone, kw)
                    .or().eq(Orders::getCode, kw));
        }
        IPage<Orders> orderPage = ordersMapper.selectJoinPage(p, Orders.class, wrapper);
        List<MyOrderVO> voList = new ArrayList<>();
@@ -2210,6 +2281,14 @@
        order.setWxExternalNo(wxTradeNo);
        order.setPayAmount(order.getTotalAmount());
        order.setUpdateTime(now);
        // 计算店铺订单序号:当前存件门店当天已支付订单数 + 1
        Date todayStart = DateUtil.getStartOfDay(now);
        Long currentCount = ordersMapper.selectCount(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getDepositShopId, order.getDepositShopId())
                .eq(Orders::getPayStatus, Constants.ONE)
                .ge(Orders::getPayTime, todayStart)
                .eq(Orders::getDeleted, Constants.ZERO));
        order.setAutoNum(currentCount + 1);
        // 生成会员核销码
        order.setMemberVerifyCode(generateVerifyCode());
        // 异地寄存:计算预计送达时间
@@ -2438,26 +2517,37 @@
        otherOrders.setUpdateTime(now);
        otherOrdersMapper.updateById(otherOrders);
        // 4. 查询门店信息(通过注册会员主键关联)
        // 4. 查询变更版本门店信息(通过注册会员主键关联)
        ShopInfo shopInfo = shopInfoMapper.selectOne(new QueryWrapper<ShopInfo>().lambda()
                .eq(ShopInfo::getRegionMemberId, otherOrders.getMemberId())
                .eq(ShopInfo::getDeleted, Constants.ZERO)
                .eq(ShopInfo::getVersionType, Constants.ONE)
                .last("limit 1"));
        if (shopInfo == null) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "门店不存在");
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "门店变更记录不存在");
        }
        // 5. 更新门店状态:已支付押金
        shopInfo.setAuditStatus(Constants.THREE); // 3=已支付押金
        // 5. 更新变更版本支付状态
        Member member = memberMapper.selectById(otherOrders.getMemberId());
        shopInfo.setAuditStatus(Constants.THREE);
        shopInfo.setPayStatus(Constants.ONE);
        shopInfo.setPayTime(now);
        shopInfo.setWxExternalNo(wxTradeNo);
        shopInfo.setCode(otherOrders.getCode());
        Member member = memberMapper.selectById(otherOrders.getMemberId());
        if (member != null) {
            shopInfo.setPayMemberOpenId(member.getOpenid());
        }
        shopInfo.setUpdateTime(now);
        shopInfoMapper.updateById(shopInfo);
        // 6. 同步更新正式版本状态为已支付押金
        if (shopInfo.getRelationShopId() != null) {
            ShopInfo officialShop = shopInfoMapper.selectById(shopInfo.getRelationShopId());
            if (officialShop != null) {
                officialShop.setAuditStatus(Constants.THREE);
                officialShop.setUpdateTime(now);
                shopInfoMapper.updateById(officialShop);
            }
        }
        // 短信通知门店:成功入驻
        String rawPassword = shopInfo.getTelephone() != null && shopInfo.getTelephone().length() >= 6
@@ -2804,6 +2894,10 @@
            }
            order.setMemberVerifyCode(generateVerifyCode());
            ordersMapper.updateById(order);
            // 寄存时图片必填
            if (images == null || images.isEmpty()) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "请上传寄存图片");
            }
            // 保存寄存图片(obj_type=2 订单寄存图片,最多3张)
            saveVerifyImages(order.getId(), images, Constants.FileType.ORDER_DEPOSIT.getKey(), shopId);
            // 记录订单日志
@@ -2841,6 +2935,12 @@
            order.setStatus(Constants.OrderStatus.finished.getStatus());
            order.setConfirmArriveTime(now);
            ordersMapper.updateById(order);
            // 就地寄存(type=0)取件时图片不必填,其他类型取件必填
            if (!Constants.equalsInteger(order.getType(), Constants.ZERO)) {
                if (images == null || images.isEmpty()) {
                    throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "请上传取件图片");
                }
            }
            // 订单完成,释放核销码
            String verifyCode = order.getMemberVerifyCode();
            if (StringUtils.isNotBlank(verifyCode)) {
@@ -3534,7 +3634,12 @@
            return "司机已取件,正运往目的地";
        }
        if (Constants.equalsInteger(status, Constants.OrderStatus.arrived.getStatus())) {
            return "行李已送达服务点,请及时前往取件";
            if(Constants.equalsInteger(order.getType(),Constants.ZERO)){
                return "行李已寄存,请凭取件码前往指定门店取件~";
            }else{
                return "行李已送达服务点,请及时前往取件";
            }
        }
        if (Constants.equalsInteger(status, Constants.OrderStatus.finished.getStatus())) {
            if (Constants.equalsInteger(order.getCommentStatus(), Constants.ONE)) {
@@ -4021,7 +4126,6 @@
                            "time", String.valueOf(noGrabMinutes));
                }
                // 标记已通知 + 记录通知时间
                order.setPlatformSmsNotified(Constants.ONE);
                order.setPlatformSmsNotifiedTime(now);
                order.setUpdateTime(now);
                ordersMapper.updateById(order);
@@ -4134,6 +4238,9 @@
        if (StringUtils.isBlank(phone)) {
            return;
        }
        if (!smsNotify.isEnabled()) {
            return;
        }
        String content = smsNotify.format(paramPairs);
        try {
            JSONObject templateParam = new JSONObject();
@@ -4200,8 +4307,10 @@
            }
        }
        //序号
        String sort = Constants.formatIntegerNum(shopId)+"-"+orders.getId();
        //序号:商铺ID-日期-自增长序号(如 32-06-001)
        String dateStr = new SimpleDateFormat("dd").format(orders.getPayTime() != null ? orders.getPayTime() : new Date());
        String autoNumStr = String.format("%03d", orders.getAutoNum() != null ? orders.getAutoNum() : 0);
        String sort = shopId + "-" + dateStr + "-" + autoNumStr;
//        String content = printService.getPrintContent(shop.getName(), detailList, userInfo, orders.getCode(), orders.getRemark(),
        String content = printService.getPrintContent(shop.getName(), detailList, userInfo, orders.getCode(), sort,
                orders.getTakeLocationRemark(),
@@ -4271,4 +4380,132 @@
        JPushUtil.sendByAliases(aliases, title, content, extras);
    }
    @Override
    public int notifyArrivalPickUp() {
        String timeStr = operationConfigBiz.getConfig().getArrivalPickUpTime();
        if (StringUtils.isBlank(timeStr)) {
            return 0;
        }
        int minutes;
        try {
            minutes = Integer.parseInt(timeStr);
        } catch (NumberFormatException e) {
            log.warn("即将到达取件时间配置异常: {}", timeStr);
            return 0;
        }
        if (minutes <= 0) {
            return 0;
        }
        // 计算时间窗口:当前时间 + minutes 就是"即将到达"的临界点
        Date now = new Date();
        Date threshold = new Date(now.getTime() + (long) minutes * 60 * 1000);
        // 查询:status=5、未通知、就地寄存 or 异地(有取件门店)、预计取件时间在 now~threshold 之间
        List<Orders> orders = ordersMapper.selectList(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getStatus, Constants.OrderStatus.arrived.getStatus())
                .eq(Orders::getPayStatus, Constants.ONE)
                .eq(Orders::getDeleted, Constants.ZERO)
                .and(w -> w
                        .eq(Orders::getType, Constants.ZERO)
                        .or(w2 -> w2.eq(Orders::getType, Constants.ONE).isNotNull(Orders::getTakeShopId))
                )
                .ne(Orders::getPickUpNotifyStatus, Constants.ONE)
                .isNotNull(Orders::getExpectedTakeTime)
                .ge(Orders::getExpectedTakeTime, now)
                .le(Orders::getExpectedTakeTime, threshold));
        if (orders == null || orders.isEmpty()) {
            return 0;
        }
        int count = 0;
        for (Orders order : orders) {
            Member member = memberMapper.selectById(order.getMemberId());
            if (member != null && StringUtils.isNotBlank(member.getTelephone())) {
                sendSmsNotify(member.getTelephone(), Constants.SmsNotify.MEMBER_TIME_OUT,
                        "orderNo", order.getCode());
            }
            order.setPickUpNotifyStatus(Constants.ONE);
            ordersMapper.updateById(order);
            count++;
        }
        return count;
    }
    @Override
    public Boolean checkOperationRadius(Integer orderId, Integer userId, Integer userType, Double lng, Double lat) {
        String radiusStr = operationConfigBiz.getConfig().getOperationRadius();
        if (StringUtils.isBlank(radiusStr)) {
            return true;
        }
        double radiusM;
        try {
            radiusM = Double.parseDouble(radiusStr);
        } catch (NumberFormatException e) {
            return true;
        }
        if (radiusM <= 0) {
            return true;
        }
        Orders order = ordersMapper.selectById(orderId);
        if (order == null) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "订单不存在");
        }
        BigDecimal targetLat;
        BigDecimal targetLgt;
        if (Constants.equalsInteger(userType, Constants.ZERO)) {
            // 门店操作
            if (Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.waitDeposit.getStatus())) {
                // status=1 门店寄存核验
                if (!Constants.equalsInteger(order.getDepositShopId(), userId)) {
                    throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "无权操作该订单");
                }
                targetLat = order.getDepositLat();
                targetLgt = order.getDepositLgt();
            } else if (Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.arrived.getStatus())) {
                // status=5 门店完成核销
                if (Constants.equalsInteger(order.getType(), Constants.ZERO)) {
                    // 就地存取 → 对比存件门店
                    if (!Constants.equalsInteger(order.getDepositShopId(), userId)) {
                        throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "无权操作该订单");
                    }
                    targetLat = order.getDepositLat();
                    targetLgt = order.getDepositLgt();
                } else {
                    // 异地存取 → 对比取件门店
                    if (!Constants.equalsInteger(order.getTakeShopId(), userId)) {
                        throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "无权操作该订单");
                    }
                    targetLat = order.getTakeLat();
                    targetLgt = order.getTakeLgt();
                }
            } else {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "订单状态不允许此操作");
            }
        } else if (Constants.equalsInteger(userType, Constants.ONE)) {
            // 司机操作
            if (Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.deposited.getStatus())) {
                // status=2 司机取件
                if (!Constants.equalsInteger(order.getAcceptDriver(), userId)) {
                    throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "无权操作该订单");
                }
                targetLat = order.getDepositLat();
                targetLgt = order.getDepositLgt();
            } else if (Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.delivering.getStatus())) {
                // status=4 司机送达
                if (!Constants.equalsInteger(order.getAcceptDriver(), userId)) {
                    throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "无权操作该订单");
                }
                targetLat = order.getTakeLat();
                targetLgt = order.getTakeLgt();
            } else {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "订单状态不允许此操作");
            }
        } else {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "用户类型不合法");
        }
        if (targetLat == null || targetLgt == null) {
            return true;
        }
        double distanceKm = GeoUtils.haversineDistance(lat, lng, targetLat.doubleValue(), targetLgt.doubleValue());
        return distanceKm * 1000 <= radiusM;
    }
}
server/services/src/main/java/com/doumee/service/business/impl/PricingRuleServiceImpl.java
@@ -576,15 +576,15 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchSaveRevenueShare(RevenueShareSaveDTO request) {
        // 校验:必须包含 fieldType 0-4 各一条
        // 校验:必须包含 fieldType 0-6 各一条
        Set<Integer> fieldTypes = request.getItems().stream()
                .map(RevenueShareItemDTO::getFieldType)
                .collect(Collectors.toSet());
        if (fieldTypes.size() != Constants.FIVE) {
        if (fieldTypes.size() != Constants.SEVEN) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(),
                    "必须包含企业寄(0)、个人寄(1)、企业取(2)、个人取(3)、配送员(4)共5条数据");
                    "必须包含异地企业寄(0)、异地个人寄(1)、异地企业取(2)、异地个人取(3)、配送员(4)、就地企业存(5)、就地个人存(6)共7条数据");
        }
        for (int i = 0; i <= Constants.FOUR; i++) {
        for (int i = 0; i <= Constants.SIX; i++) {
            if (!fieldTypes.contains(i)) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(),
                        "缺少类型" + i + "的数据");
@@ -634,10 +634,10 @@
        Map<String, PricingRule> existingMap = rules.stream()
                .collect(Collectors.toMap(PricingRule::getFieldA, r -> r));
        // 固定返回5条:企业寄(0)、个人寄(1)、企业取(2)、个人取(3)、配送员(4)
        // 固定返回7条:异地企业寄(0)、异地个人寄(1)、异地企业取(2)、异地个人取(3)、配送员(4)、就地企业存(5)、就地个人存(6)
        List<RevenueShareVO> result = new ArrayList<>();
        String[] typeNames = {"企业寄", "个人寄", "企业取", "个人取", "配送员"};
        for (int i = 0; i <= Constants.FOUR; i++) {
        String[] typeNames = {"异地企业寄件", "异地个人寄件", "异地企业取件", "异地个人取件", "配送员", "就地企业存件", "就地个人存件"};
        for (int i = 0; i <= Constants.SIX; i++) {
            RevenueShareVO vo = new RevenueShareVO();
            vo.setFieldType(i);
            vo.setFieldTypeName(typeNames[i]);
server/services/src/main/java/com/doumee/service/business/impl/ShopInfoServiceImpl.java
@@ -258,10 +258,105 @@
    public void applyShop(ShopApplyDTO request) {
        Member member = memberMapper.selectById(request.getMemberId());
        // 根据类型校验附件
        validateCompanyTypeFields(request);
        // 2. 根据类型校验附件
        Date now = new Date();
        // 查询该会员最新的变更版本记录
        QueryWrapper<ShopInfo> changeQw = new QueryWrapper<>();
        changeQw.lambda()
                .eq(ShopInfo::getRegionMemberId, member.getId())
                .eq(ShopInfo::getVersionType, Constants.ONE)
                .eq(ShopInfo::getDeleted, Constants.ZERO)
                .orderByDesc(ShopInfo::getCreateTime)
                .last("limit 1");
        ShopInfo changeVersion = shopInfoMapper.selectOne(changeQw);
        if (changeVersion == null) {
            // 首次申请:创建正式版本 + 变更版本
            checkTelephoneUnique(request.getTelephone(), null);
            String rawPassword = generateDefaultPassword(request.getTelephone());
            String salt = RandomStringUtils.randomAlphabetic(6);
            String encryptedPassword = Utils.Secure.encryptPassword(rawPassword, salt);
            // 正式版本
            ShopInfo official = new ShopInfo();
            applyFieldsFromRequest(official, request, member, encryptedPassword, salt, now);
            official.setVersionType(Constants.ZERO);
            official.setAuditStatus(Constants.ZERO);
            official.setStatus(Constants.ZERO);
            official.setDeleted(Constants.ZERO);
            official.setCreateTime(now);
            official.setUpdateTime(now);
            official.setRegionMemberId(member.getId());
            setDepositAmountFromPricingRule(official);
            setDefaultDeliveryRange(official);
            shopInfoMapper.insert(official);
            // 保存正式版本附件
            saveShopAttachments(official.getId(), request, now);
            // 创建变更版本(拷贝正式版本数据)
            createChangeVersion(official, official.getId(), now);
        } else if (Constants.equalsInteger(changeVersion.getAuditStatus(), Constants.ONE)) {
            // 审批通过待支付押金
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "门店已审批通过,请完成押金支付");
        } else if (Constants.equalsInteger(changeVersion.getAuditStatus(), Constants.THREE)) {
            // 最新变更版本已支付押金(status=3):生成新的变更版本
            Integer relationShopId = changeVersion.getRelationShopId();
            // 校验手机号唯一性(排除正式版本和变更版本)
            checkTelephoneUnique(request.getTelephone(), relationShopId);
            String rawPassword = generateDefaultPassword(request.getTelephone());
            String salt = RandomStringUtils.randomAlphabetic(6);
            String encryptedPassword = Utils.Secure.encryptPassword(rawPassword, salt);
            ShopInfo newChange = new ShopInfo();
            applyFieldsFromRequest(newChange, request, member, encryptedPassword, salt, now);
            newChange.setVersionType(Constants.ONE);
            newChange.setRelationShopId(relationShopId);
            newChange.setAuditStatus(Constants.ZERO);
            newChange.setStatus(Constants.ZERO);
            newChange.setDeleted(Constants.ZERO);
            newChange.setCreateTime(now);
            newChange.setUpdateTime(now);
            newChange.setRegionMemberId(member.getId());
            setDepositAmountFromPricingRule(newChange);
            shopInfoMapper.insert(newChange);
            // 保存新变更版本附件
            saveShopAttachments(newChange.getId(), request, now);
        } else {
            // 最新变更版本 status=0(待审批) 或 2(被驳回):直接更新
            Integer relationShopId = changeVersion.getRelationShopId();
            // 校验手机号唯一性
            checkTelephoneUnique(request.getTelephone(), relationShopId);
            String rawPassword = generateDefaultPassword(request.getTelephone());
            String salt = RandomStringUtils.randomAlphabetic(6);
            String encryptedPassword = Utils.Secure.encryptPassword(rawPassword, salt);
            applyFieldsFromRequest(changeVersion, request, member, encryptedPassword, salt, now);
            changeVersion.setAuditStatus(Constants.ZERO);
            changeVersion.setAuditRemark(null);
            changeVersion.setAuditTime(null);
            changeVersion.setAuditUserId(null);
            changeVersion.setUpdateTime(now);
            setDepositAmountFromPricingRule(changeVersion);
            shopInfoMapper.updateById(changeVersion);
            // 删除旧附件 + 保存新附件
            deleteShopAttachments(changeVersion.getId());
            saveShopAttachments(changeVersion.getId(), request, now);
        }
    }
    private void validateCompanyTypeFields(ShopApplyDTO request) {
        if (Constants.equalsInteger(request.getCompanyType(), Constants.ZERO)) {
            // 个人类型:必须上传劳动合同和社保证明
            if (CollectionUtils.isEmpty(request.getLaborContractImgs())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "个人类型必须上传有效劳动合同");
            }
@@ -269,7 +364,6 @@
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "个人类型必须上传社保缴纳证明");
            }
        } else if (Constants.equalsInteger(request.getCompanyType(), Constants.ONE)) {
            // 企业类型:必须填写法人信息和营业执照
            if (StringUtils.isBlank(request.getBusinessImg())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "企业类型必须上传营业执照");
            }
@@ -283,104 +377,43 @@
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "企业类型必须填写法人身份证号码");
            }
        }
    }
        Date now = new Date();
        String rawPassword = generateDefaultPassword(request.getTelephone());
        String salt = RandomStringUtils.randomAlphabetic(6);
        String encryptedPassword = Utils.Secure.encryptPassword(rawPassword, salt);
    private void applyFieldsFromRequest(ShopInfo shop, ShopApplyDTO request, Member member,
                                        String password, String salt, Date now) {
        shop.setCompanyType(request.getCompanyType());
        shop.setName(request.getName());
        shop.setTelephone(request.getTelephone());
        shop.setLinkName(request.getLinkName());
        shop.setLinkPhone(request.getLinkPhone());
        shop.setIdcard(request.getIdcard());
        shop.setAreaId(request.getAreaId());
        shop.setLongitude(request.getLongitude());
        shop.setLatitude(request.getLatitude());
        shop.setAddress(request.getAddress());
        shop.setIdcardImg(request.getIdcardImg());
        shop.setIdcardImgBack(request.getIdcardImgBack());
        shop.setBusinessImg(request.getBusinessImg());
        shop.setLegalPersonName(request.getLegalPersonName());
        shop.setLegalPersonPhone(request.getLegalPersonPhone());
        shop.setLegalPersonCard(request.getLegalPersonCard());
        shop.setPassword(password);
        shop.setSalt(salt);
        shop.setAliAccount(request.getAliAccount());
        shop.setAliName(request.getAliName());
        shop.setRevenueShareConfig(buildRevenueShareConfig(request.getLocalDeposit(), request.getRemoteDeposit(), request.getRemoteTake()));
        shop.setOpenid(member.getOpenid());
    }
        // 3. 查询该会员是否已有门店记录
        QueryWrapper<ShopInfo> existQw = new QueryWrapper<>();
        existQw.lambda()
                .eq(ShopInfo::getRegionMemberId, member.getId())
                .eq(ShopInfo::getDeleted, Constants.ZERO)
                .last("limit 1");
        ShopInfo existing = shopInfoMapper.selectOne(existQw);
    private void saveShopAttachments(Integer shopId, ShopApplyDTO request, Date now) {
        saveMultifileList(shopId, Constants.FileType.STORE_FRONT.getKey(), request.getStoreFrontImgs(), now);
        saveMultifileList(shopId, Constants.FileType.STORE_INTERIOR.getKey(), request.getStoreInteriorImgs(), now);
        saveMultifileList(shopId, Constants.FileType.OTHER_MATERIAL.getKey(), request.getOtherMaterialImgs(), now);
        saveMultifileList(shopId, Constants.FileType.LABOR_CONTRACT.getKey(), request.getLaborContractImgs(), now);
        saveMultifileList(shopId, Constants.FileType.SOCIAL_SECURITY.getKey(), request.getSocialSecurityImgs(), now);
    }
        Integer shopId;
        if (existing != null) {
            // 校验状态:只有待审批(0)和被驳回(2)可修改
            if (!Constants.equalsInteger(existing.getAuditStatus(), Constants.TWO)) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前门店状态不允许修改");
            }
            // 校验openid匹配:当前登录会员的openid必须与门店的openid一致
            if (existing.getOpenid() != null && !existing.getOpenid().equals(member.getOpenid())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "无权限操作当前门店信息");
            }
            // 校验手机号唯一性(排除自身)
            if (!request.getTelephone().equals(existing.getTelephone())) {
                checkTelephoneUnique(request.getTelephone(), existing.getId());
            }
            // 更新
            existing.setCompanyType(request.getCompanyType());
            existing.setName(request.getName());
            existing.setTelephone(request.getTelephone());
            existing.setLinkName(request.getLinkName());
            existing.setLinkPhone(request.getLinkPhone());
            existing.setIdcard(request.getIdcard());
            existing.setAreaId(request.getAreaId());
            existing.setLongitude(request.getLongitude());
            existing.setLatitude(request.getLatitude());
            existing.setAddress(request.getAddress());
            existing.setIdcardImg(request.getIdcardImg());
            existing.setIdcardImgBack(request.getIdcardImgBack());
            existing.setBusinessImg(request.getBusinessImg());
            existing.setLegalPersonName(request.getLegalPersonName());
            existing.setLegalPersonPhone(request.getLegalPersonPhone());
            existing.setLegalPersonCard(request.getLegalPersonCard());
            existing.setPassword(encryptedPassword);
            existing.setSalt(salt);
            existing.setAliAccount(request.getAliAccount());
            existing.setAliName(request.getAliName());
            existing.setUpdateTime(now);
            existing.setAuditRemark(null);
            existing.setAuditTime(null);
            existing.setAuditUserId(null);
            existing.setAuditStatus(Constants.ZERO);
            // 读取押金金额
            setDepositAmountFromPricingRule(existing);
            shopInfoMapper.updateById(existing);
            shopId = existing.getId();
        } else {
            // 1. 校验门店手机号唯一性(shop_info.telephone)
            checkTelephoneUnique(request.getTelephone(), null);
            // 新建
            ShopInfo shopInfo = new ShopInfo();
            shopInfo.setCompanyType(request.getCompanyType());
            shopInfo.setName(request.getName());
            shopInfo.setTelephone(request.getTelephone());
            shopInfo.setLinkName(request.getLinkName());
            shopInfo.setLinkPhone(request.getLinkPhone());
            shopInfo.setIdcard(request.getIdcard());
            shopInfo.setAreaId(request.getAreaId());
            shopInfo.setLongitude(request.getLongitude());
            shopInfo.setLatitude(request.getLatitude());
            shopInfo.setAddress(request.getAddress());
            shopInfo.setIdcardImg(request.getIdcardImg());
            shopInfo.setIdcardImgBack(request.getIdcardImgBack());
            shopInfo.setBusinessImg(request.getBusinessImg());
            shopInfo.setLegalPersonName(request.getLegalPersonName());
            shopInfo.setLegalPersonPhone(request.getLegalPersonPhone());
            shopInfo.setLegalPersonCard(request.getLegalPersonCard());
            shopInfo.setPassword(encryptedPassword);
            shopInfo.setSalt(salt);
            shopInfo.setAliAccount(request.getAliAccount());
            shopInfo.setAliName(request.getAliName());
            shopInfo.setAuditStatus(Constants.ZERO);
            shopInfo.setStatus(Constants.ZERO);
            shopInfo.setDeleted(Constants.ZERO);
            shopInfo.setCreateTime(now);
            shopInfo.setUpdateTime(now);
            shopInfo.setRegionMemberId(member.getId());
            // 读取押金金额
            setDepositAmountFromPricingRule(shopInfo);
            // 设置默认配送范围
            setDefaultDeliveryRange(shopInfo);
            shopInfoMapper.insert(shopInfo);
            shopId = shopInfo.getId();
        }
        // 4. 删除旧附件记录
    private void deleteShopAttachments(Integer shopId) {
        multifileMapper.delete(new QueryWrapper<Multifile>().lambda()
                .eq(Multifile::getObjId, shopId)
                .in(Multifile::getObjType,
@@ -389,13 +422,6 @@
                        Constants.FileType.OTHER_MATERIAL.getKey(),
                        Constants.FileType.LABOR_CONTRACT.getKey(),
                        Constants.FileType.SOCIAL_SECURITY.getKey()));
        // 5. 保存新附件记录
        saveMultifileList(shopId, Constants.FileType.STORE_FRONT.getKey(), request.getStoreFrontImgs(), now);
        saveMultifileList(shopId, Constants.FileType.STORE_INTERIOR.getKey(), request.getStoreInteriorImgs(), now);
        saveMultifileList(shopId, Constants.FileType.OTHER_MATERIAL.getKey(), request.getOtherMaterialImgs(), now);
        saveMultifileList(shopId, Constants.FileType.LABOR_CONTRACT.getKey(), request.getLaborContractImgs(), now);
        saveMultifileList(shopId, Constants.FileType.SOCIAL_SECURITY.getKey(), request.getSocialSecurityImgs(), now);
    }
    @Override
@@ -409,12 +435,25 @@
    @Override
    public ShopDetailVO getMyShop(Integer memberId) {
        // 查询最新的变更版本
        QueryWrapper<ShopInfo> qw = new QueryWrapper<>();
        qw.lambda()
                .eq(ShopInfo::getRegionMemberId, memberId)
                .eq(ShopInfo::getVersionType, Constants.ONE)
                .eq(ShopInfo::getDeleted, Constants.ZERO)
                .orderByDesc(ShopInfo::getCreateTime)
                .last("limit 1");
        ShopInfo shopInfo = shopInfoMapper.selectOne(qw);
        if (shopInfo == null) {
            // 无变更版本则查正式版本
            QueryWrapper<ShopInfo> officialQw = new QueryWrapper<>();
            officialQw.lambda()
                    .eq(ShopInfo::getRegionMemberId, memberId)
                    .eq(ShopInfo::getVersionType, Constants.ZERO)
                    .eq(ShopInfo::getDeleted, Constants.ZERO)
                    .last("limit 1");
            shopInfo = shopInfoMapper.selectOne(officialQw);
        }
        if (shopInfo == null) {
            return null;
        }
@@ -431,52 +470,153 @@
                || (auditDTO.getAuditStatus() != 0 && auditDTO.getAuditStatus() != 1)) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "审批状态参数错误");
        }
        ShopInfo shopInfo = shopInfoMapper.selectById(auditDTO.getId());
        if (shopInfo == null || Constants.equalsInteger(shopInfo.getDeleted(), Constants.ONE)) {
        // 审批的是变更版本
        ShopInfo changeVersion = shopInfoMapper.selectById(auditDTO.getId());
        if (changeVersion == null || Constants.equalsInteger(changeVersion.getDeleted(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        if (!Constants.equalsInteger(shopInfo.getAuditStatus(), Constants.ZERO)) {
        if (!Constants.equalsInteger(changeVersion.getAuditStatus(), Constants.ZERO)) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前状态不允许审批");
        }
        Date now = new Date();
        // auditDTO.auditStatus: 0=通过 → auditStatus=1, 1=驳回 → auditStatus=2
        // auditDTO.auditStatus: 0=通过 → 1, 1=驳回 → 2
        Integer newAuditStatus = Constants.equalsInteger(auditDTO.getAuditStatus(), Constants.ZERO) ? Constants.ONE : Constants.TWO;
        shopInfo.setAuditStatus(newAuditStatus);
        shopInfo.setAuditTime(now);
        shopInfo.setAuditRemark(auditDTO.getAuditRemark());
        shopInfo.setAuditUserId(auditDTO.getAuditUser());
        shopInfo.setUpdateTime(now);
        // 审批通过时,校验城市pricing_rule配置,读取押金金额
        if (Constants.equalsInteger(newAuditStatus, Constants.ONE)) {
            // 1. 解析门店所在城市
            Areas area = areasBiz.resolveArea(shopInfo.getAreaId());
            if (area == null || area.getParentId() == null) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "门店区划信息异常,无法确定所在城市");
            }
            Integer cityId = area.getParentId();
            // 2. 校验 pricing_rule 配置(城市开通在押金支付完成后处理)
            Areas cityArea = areasService.findById(cityId, Constants.ONE);
            if (cityArea == null) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "城市信息不存在");
        // 查找正式版本
        Integer officialId = changeVersion.getRelationShopId();
        ShopInfo official = officialId != null ? shopInfoMapper.selectById(officialId) : null;
        boolean hasPaidOfficial = official != null
                && Constants.equalsInteger(official.getAuditStatus(), Constants.THREE);
        if (!hasPaidOfficial) {
            // 场景1:不存在auditStatus=3的正式版本(首次审批)
            changeVersion.setAuditStatus(newAuditStatus);
            changeVersion.setAuditTime(now);
            changeVersion.setAuditRemark(auditDTO.getAuditRemark());
            changeVersion.setAuditUserId(auditDTO.getAuditUser());
            changeVersion.setUpdateTime(now);
            if (Constants.equalsInteger(newAuditStatus, Constants.ONE)) {
                validateCityAndSetDeposit(changeVersion);
            }
            if (!Constants.equalsInteger(cityArea.getStatus(), Constants.ONE)) {
                List<String> errors = validateCityPricingRules(cityId);
                if (!errors.isEmpty()) {
                    throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(),
                            "城市[" + cityArea.getName() + "]尚未开通,定价规则未配置完整:" + String.join(";", errors));
            shopInfoMapper.updateById(changeVersion);
            // 同步更新正式版本审批状态
            if (official != null) {
                official.setAuditStatus(newAuditStatus);
                official.setAuditTime(now);
                official.setAuditRemark(auditDTO.getAuditRemark());
                official.setAuditUserId(auditDTO.getAuditUser());
                official.setUpdateTime(now);
                if (Constants.equalsInteger(newAuditStatus, Constants.ONE)) {
                    official.setDepositAmount(changeVersion.getDepositAmount());
                }
                shopInfoMapper.updateById(official);
            }
            // 3. 从PricingRule读取押金金额(审批时更新)
            setDepositAmountFromPricingRule(shopInfo);
        }
        shopInfoMapper.updateById(shopInfo);
            // 短信通知
            sendAuditSms(changeVersion, newAuditStatus, auditDTO.getAuditRemark());
        } else {
            // 场景2:存在auditStatus=3的正式版本(变更审批)
            if (Constants.equalsInteger(newAuditStatus, Constants.ONE)) {
                // 审批通过:变更版本直接标记auditStatus=3,同步数据到正式版本
                changeVersion.setAuditStatus(Constants.THREE);
                changeVersion.setAuditTime(now);
                changeVersion.setAuditRemark(auditDTO.getAuditRemark());
                changeVersion.setAuditUserId(auditDTO.getAuditUser());
                changeVersion.setUpdateTime(now);
                shopInfoMapper.updateById(changeVersion);
        // 短信通知
                // 同步变更版本数据到正式版本
                syncChangeToOfficial(changeVersion, official, now);
            } else {
                // 审批驳回:仅标记变更版本状态
                changeVersion.setAuditStatus(Constants.TWO);
                changeVersion.setAuditTime(now);
                changeVersion.setAuditRemark(auditDTO.getAuditRemark());
                changeVersion.setAuditUserId(auditDTO.getAuditUser());
                changeVersion.setUpdateTime(now);
                shopInfoMapper.updateById(changeVersion);
            }
        }
    }
    private void validateCityAndSetDeposit(ShopInfo shopInfo) {
        Areas area = areasBiz.resolveArea(shopInfo.getAreaId());
        if (area == null || area.getParentId() == null) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "门店区划信息异常,无法确定所在城市");
        }
        Integer cityId = area.getParentId();
        Areas cityArea = areasService.findById(cityId, Constants.ONE);
        if (cityArea == null) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "城市信息不存在");
        }
        if (!Constants.equalsInteger(cityArea.getStatus(), Constants.ONE)) {
            List<String> errors = validateCityPricingRules(cityId);
            if (!errors.isEmpty()) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(),
                        "城市[" + cityArea.getName() + "]尚未开通,定价规则未配置完整:" + String.join(";", errors));
            }
        }
        setDepositAmountFromPricingRule(shopInfo);
    }
    private void syncChangeToOfficial(ShopInfo changeVersion, ShopInfo official, Date now) {
        official.setCompanyType(changeVersion.getCompanyType());
        official.setName(changeVersion.getName());
        official.setTelephone(changeVersion.getTelephone());
        official.setLinkName(changeVersion.getLinkName());
        official.setLinkPhone(changeVersion.getLinkPhone());
        official.setIdcard(changeVersion.getIdcard());
        official.setAreaId(changeVersion.getAreaId());
        official.setLongitude(changeVersion.getLongitude());
        official.setLatitude(changeVersion.getLatitude());
        official.setAddress(changeVersion.getAddress());
        official.setIdcardImg(changeVersion.getIdcardImg());
        official.setIdcardImgBack(changeVersion.getIdcardImgBack());
        official.setBusinessImg(changeVersion.getBusinessImg());
        official.setLegalPersonName(changeVersion.getLegalPersonName());
        official.setLegalPersonPhone(changeVersion.getLegalPersonPhone());
        official.setLegalPersonCard(changeVersion.getLegalPersonCard());
        official.setPassword(changeVersion.getPassword());
        official.setSalt(changeVersion.getSalt());
        official.setAliAccount(changeVersion.getAliAccount());
        official.setAliName(changeVersion.getAliName());
        official.setRevenueShareConfig(changeVersion.getRevenueShareConfig());
        official.setDepositAmount(changeVersion.getDepositAmount());
        official.setUpdateTime(now);
        shopInfoMapper.updateById(official);
        // 同步附件:先删正式版本旧附件,再从变更版本拷贝
        deleteShopAttachments(official.getId());
        List<Multifile> changeFiles = multifileMapper.selectList(new QueryWrapper<Multifile>().lambda()
                .eq(Multifile::getObjId, changeVersion.getId())
                .eq(Multifile::getIsdeleted, Constants.ZERO)
                .in(Multifile::getObjType,
                        Constants.FileType.STORE_FRONT.getKey(),
                        Constants.FileType.STORE_INTERIOR.getKey(),
                        Constants.FileType.OTHER_MATERIAL.getKey(),
                        Constants.FileType.LABOR_CONTRACT.getKey(),
                        Constants.FileType.SOCIAL_SECURITY.getKey()));
        for (Multifile f : changeFiles) {
            Multifile copy = new Multifile();
            copy.setCreator(f.getCreator());
            copy.setCreateDate(now);
            copy.setIsdeleted(Constants.ZERO);
            copy.setName(f.getName());
            copy.setInfo(f.getInfo());
            copy.setObjId(official.getId());
            copy.setType(f.getType());
            copy.setObjType(f.getObjType());
            copy.setFileurl(f.getFileurl());
            copy.setSortnum(f.getSortnum());
            multifileMapper.insert(copy);
        }
    }
    private void sendAuditSms(ShopInfo shopInfo, Integer newAuditStatus, String auditRemark) {
        if (Constants.equalsInteger(newAuditStatus, Constants.ONE)) {
            // 审核通过 → 通知缴纳押金
            String depositMoney = shopInfo.getDepositAmount() != null
                    ? new java.math.BigDecimal(shopInfo.getDepositAmount())
                        .divide(new java.math.BigDecimal(100), 2, java.math.RoundingMode.HALF_UP).toPlainString() : "0";
@@ -485,11 +625,10 @@
                    "storeName", shopInfo.getName(),
                    "money", depositMoney);
        } else if (Constants.equalsInteger(newAuditStatus, Constants.TWO)) {
            // 审核驳回
            sendSmsNotify(shopInfo.getTelephone(),
                    Constants.SmsNotify.SHOP_AUTH_REJECTED,
                    "storeName", shopInfo.getName(),
                    "reason", auditDTO.getAuditRemark() != null ? auditDTO.getAuditRemark() : "");
                    "reason", auditRemark != null ? auditRemark : "");
        }
    }
@@ -590,6 +729,7 @@
        shopInfo.setLegalPersonCard(request.getLegalPersonCard());
        shopInfo.setAliAccount(request.getAliAccount());
        shopInfo.setAliName(request.getAliName());
        shopInfo.setRevenueShareConfig(buildRevenueShareConfig(request.getLocalDeposit(), request.getRemoteDeposit(), request.getRemoteTake()));
        shopInfo.setUpdateTime(now);
        shopInfoMapper.updateById(shopInfo);
@@ -621,6 +761,81 @@
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "手机号格式异常,无法生成默认密码");
        }
        return telephone.substring(telephone.length() - 6) + "@123456";
    }
    private String buildRevenueShareConfig(Double localDeposit, Double remoteDeposit, Double remoteTake) {
        if (localDeposit == null && remoteDeposit == null && remoteTake == null) {
            return null;
        }
        JSONObject config = new JSONObject();
        if (localDeposit != null) {
            config.put("localDeposit", localDeposit);
        }
        if (remoteDeposit != null) {
            config.put("remoteDeposit", remoteDeposit);
        }
        if (remoteTake != null) {
            config.put("remoteTake", remoteTake);
        }
        return config.toJSONString();
    }
    private void createChangeVersion(ShopInfo origin, Integer originShopId, Date now) {
        ShopInfo changeShop = new ShopInfo();
        changeShop.setCompanyType(origin.getCompanyType());
        changeShop.setName(origin.getName());
        changeShop.setTelephone(origin.getTelephone());
        changeShop.setLinkName(origin.getLinkName());
        changeShop.setLinkPhone(origin.getLinkPhone());
        changeShop.setIdcard(origin.getIdcard());
        changeShop.setAreaId(origin.getAreaId());
        changeShop.setLongitude(origin.getLongitude());
        changeShop.setLatitude(origin.getLatitude());
        changeShop.setAddress(origin.getAddress());
        changeShop.setIdcardImg(origin.getIdcardImg());
        changeShop.setIdcardImgBack(origin.getIdcardImgBack());
        changeShop.setBusinessImg(origin.getBusinessImg());
        changeShop.setLegalPersonName(origin.getLegalPersonName());
        changeShop.setLegalPersonPhone(origin.getLegalPersonPhone());
        changeShop.setLegalPersonCard(origin.getLegalPersonCard());
        changeShop.setAliAccount(origin.getAliAccount());
        changeShop.setAliName(origin.getAliName());
        changeShop.setDepositAmount(origin.getDepositAmount());
        changeShop.setRevenueShareConfig(origin.getRevenueShareConfig());
        changeShop.setVersionType(Constants.ONE);
        changeShop.setRelationShopId(originShopId);
        changeShop.setRegionMemberId(origin.getRegionMemberId());
        changeShop.setOpenid(origin.getOpenid());
        changeShop.setStatus(Constants.ZERO);
        changeShop.setDeleted(Constants.ZERO);
        changeShop.setCreateTime(now);
        changeShop.setUpdateTime(now);
        shopInfoMapper.insert(changeShop);
        // 拷贝附件到变更版本
        List<Multifile> originFiles = multifileMapper.selectList(new QueryWrapper<Multifile>().lambda()
                .eq(Multifile::getObjId, originShopId)
                .eq(Multifile::getIsdeleted, Constants.ZERO)
                .in(Multifile::getObjType,
                        Constants.FileType.STORE_FRONT.getKey(),
                        Constants.FileType.STORE_INTERIOR.getKey(),
                        Constants.FileType.OTHER_MATERIAL.getKey(),
                        Constants.FileType.LABOR_CONTRACT.getKey(),
                        Constants.FileType.SOCIAL_SECURITY.getKey()));
        for (Multifile f : originFiles) {
            Multifile copy = new Multifile();
            copy.setCreator(f.getCreator());
            copy.setCreateDate(now);
            copy.setIsdeleted(Constants.ZERO);
            copy.setName(f.getName());
            copy.setInfo(f.getInfo());
            copy.setObjId(changeShop.getId());
            copy.setType(f.getType());
            copy.setObjType(f.getObjType());
            copy.setFileurl(f.getFileurl());
            copy.setSortnum(f.getSortnum());
            multifileMapper.insert(copy);
        }
    }
    private void checkTelephoneUnique(String telephone, Integer excludeId) {
@@ -696,6 +911,21 @@
        vo.setAliAccount(shopInfo.getAliAccount());
        vo.setAliName(shopInfo.getAliName());
        vo.setDepositAmount(shopInfo.getDepositAmount());
        vo.setDeliveryRange(shopInfo.getDeliveryArea());
        vo.setVersionType(shopInfo.getVersionType());
        vo.setRelationShopId(shopInfo.getRelationShopId());
        // 解析收益比例配置
        if (StringUtils.isNotBlank(shopInfo.getRevenueShareConfig())) {
            try {
                JSONObject config = JSONObject.parseObject(shopInfo.getRevenueShareConfig());
                if (config != null) {
                    vo.setLocalDeposit(config.getDouble("localDeposit"));
                    vo.setRemoteDeposit(config.getDouble("remoteDeposit"));
                    vo.setRemoteTake(config.getDouble("remoteTake"));
                }
            } catch (Exception ignored) {
            }
        }
        // 拼接图片前缀
        String imgPrefix = "";
@@ -795,6 +1025,7 @@
        qw.lambda()
                .eq(ShopInfo::getDeleted, Constants.ZERO)
                .eq(ShopInfo::getStatus, Constants.ZERO)
                .eq(ShopInfo::getVersionType, Constants.ZERO)
                .eq(ShopInfo::getAuditStatus, Constants.THREE);
        // 门店营业类型筛选
@@ -869,7 +1100,7 @@
    @Override
    public ShopWebDetailVO getShopWebDetail(ShopDetailQueryDTO dto) {
        ShopInfo shop = shopInfoMapper.selectById(dto.getId());
        if (Objects.isNull(shop) || Constants.equalsInteger(shop.getDeleted(), Constants.ONE)) {
        if (Objects.isNull(shop) || Constants.equalsInteger(shop.getDeleted(), Constants.ONE) || Constants.equalsInteger(shop.getVersionType(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
@@ -1019,7 +1250,7 @@
    @Override
    public ShopCenterVO getShopCenterInfo(Integer shopId) {
        ShopInfo shop = shopInfoMapper.selectById(shopId);
        if (shop == null || Constants.equalsInteger(shop.getDeleted(), Constants.ONE)) {
        if (shop == null || Constants.equalsInteger(shop.getDeleted(), Constants.ONE) || Constants.equalsInteger(shop.getVersionType(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        ShopCenterVO vo = new ShopCenterVO();
@@ -1173,6 +1404,7 @@
        }
        ShopInfo shop = shopInfoMapper.selectOne(new QueryWrapper<ShopInfo>().lambda().eq(ShopInfo::getTelephone, dto.getTelephone())
                .eq(ShopInfo::getDeleted,Constants.ZERO)
                .eq(ShopInfo::getVersionType,Constants.ZERO)
                .last("limit 1")
        );
        if(shop==null){
@@ -1192,31 +1424,26 @@
        if(!pwd.equals(shop.getPassword())){
            throw new BusinessException(ResponseStatus.ACCOUNT_INCORRECT);
        }
        // 更新当前登录会员的openid到门店
        if(StringUtils.isNotBlank(dto.getOpenid())){
            shopInfoMapper.update(null,new UpdateWrapper<ShopInfo>().lambda()
                    .set(ShopInfo::getOpenid,dto.getOpenid())
                    .eq(ShopInfo::getId,shop.getId())
            );
            // 清空其他门店的同一openid,保证唯一
            shopInfoMapper.update(null,new UpdateWrapper<ShopInfo>().lambda()
                    .set(ShopInfo::getOpenid,null)
                    .eq(ShopInfo::getOpenid,dto.getOpenid())
                    .ne(ShopInfo::getId,shop.getId())
            );
            shop.setOpenid(dto.getOpenid());
        if(Objects.nonNull(dto.getMemberId())){
            Member member = memberMapper.selectById(dto.getMemberId());
            if(Objects.nonNull(member)){
                memberMapper.update(new UpdateWrapper<Member>().lambda()
                        .set(Member::getLoginShopId,shop.getId())
                        .eq(Member::getId,member.getId())
                );
                shop.setMemberId(member.getId());
            }else{
                throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"会员信息异常,请联系管理员");
            }
        }
        // 创建token(generateTokenForRedis 已自动清除该用户旧token,保证唯一有效)
        String token = JwtTokenUtil.generateTokenForRedis(shop.getId(), Constants.TWO, JSONObject.toJSONString(shop), redisTemplate);
        String token = JwtTokenUtil.generateShopTokenForRedis(shop.getMemberId(), JSONObject.toJSONString(shop), redisTemplate);
        // 构建响应
        ShopLoginVO vo = new ShopLoginVO();
        vo.setToken(token);
        vo.setShopId(shop.getId());
        vo.setShopName(shop.getName());
        vo.setCompanyType(shop.getCompanyType());
        // 所属城市名称
        Areas area = areasBiz.resolveArea(shop.getAreaId());
        if (area != null) {
@@ -1228,11 +1455,15 @@
    @Override
    public ShopLoginVO shopSilentLogin(Integer memberId) {
        Member member = memberMapper.selectById(memberId);
        if(Objects.isNull(member)||StringUtils.isBlank(member.getOpenid())){
        if(Objects.isNull(member)){
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "当前登录会员身份异常,请联系管理员!");
        }
        if (Objects.isNull(member.getLoginShopId())) {
            return null;
        }
        ShopInfo shop = shopInfoMapper.selectOne(new QueryWrapper<ShopInfo>().lambda()
                .eq(ShopInfo::getOpenid, member.getOpenid())
                .eq(ShopInfo::getId, member.getLoginShopId())
                .eq(ShopInfo::getVersionType,Constants.ZERO)
                .eq(ShopInfo::getDeleted, Constants.ZERO)
                .last("limit 1"));
        if (shop == null) {
@@ -1242,7 +1473,7 @@
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"门店已禁用,请联系管理员");
        }
        // 创建token(generateTokenForRedis 已自动清除该用户旧token,保证唯一有效)
        String token = JwtTokenUtil.generateTokenForRedis(shop.getId(), Constants.TWO, JSONObject.toJSONString(shop), redisTemplate);
        String token = JwtTokenUtil.generateShopTokenForRedis(member.getId(), JSONObject.toJSONString(shop), redisTemplate);
        ShopLoginVO vo = new ShopLoginVO();
        vo.setToken(token);
@@ -1380,6 +1611,9 @@
        if (StringUtils.isBlank(phone)) {
            return;
        }
        if (!smsNotify.isEnabled()) {
            return;
        }
        String content = smsNotify.format(paramPairs);
        try {
            JSONObject templateParam = new JSONObject();
server/services/src/main/java/com/doumee/service/business/impl/SmsrecordServiceImpl.java
@@ -178,7 +178,10 @@
        String digits = Strings.randomNumeric(4);
        //发送验证码
        String templateParam = "{\"code\":\"" + digits + "\"}";
        String error = AliSmsService.sendSms(phone, Constants.SmsNotify.VERIFY_CODE.getTemplateCode(), templateParam);
        String error = null;
        if (Constants.SmsNotify.VERIFY_CODE.isEnabled()) {
            error = AliSmsService.sendSms(phone, Constants.SmsNotify.VERIFY_CODE.getTemplateCode(), templateParam);
        }
        if (error != null) {
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "短信发送失败,请稍后重试");
        }
server/services/src/main/resources/application-dev.yml
@@ -95,8 +95,8 @@
    apiV3Key: NJTLJSTZYXZRGScaiwubuzichanbu666 #apiV3Key
    serialNumer: 25D19D18217F4588841E5CD1AA0D1533DE8AF84A #商户证书序列号
    privateKeyPath: pay/pro/wx/apiclient_key.pem
    v3NotifyUrl: http://xiaopiqiu2.natapp1.cc/web/api/wxPayV3Notify
    v3RefundNotifyUrl: http://xiaopiqiu2.natapp1.cc/web/api/wxRefundV3Notify
    v3NotifyUrl: https://test.doumee.cn/gtzxljc_web/web/api/wxPayV3Notify
    v3RefundNotifyUrl: https://test.doumee.cn/gtzxljc_web/web/api/wxRefundV3Notify
alipay:
server/web/src/main/java/com/doumee/api/web/AccountApi.java
@@ -65,9 +65,14 @@
    }
    @LoginRequired
    @ApiOperation(value = "门店账号密码登录", notes = "小程序端,门店用户通过手机号+密码登录")
    @PostMapping("/shopLogin")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true),
    })
    public ApiResponse<ShopLoginVO> shopLogin(@RequestBody @Validated ShopLoginDTO dto) {
        dto.setMemberId(getMemberId());
        return ApiResponse.success("操作成功", shopInfoService.shopPasswordLogin(dto));
    }
server/web/src/main/java/com/doumee/api/web/ConfigApi.java
@@ -19,6 +19,8 @@
import com.doumee.dao.dto.AreasDto;
import com.doumee.dao.dto.CalculateLocalPriceDTO;
import com.doumee.dao.dto.CalculateRemotePriceDTO;
import com.doumee.dao.dto.DriverCheckRadiusDTO;
import com.doumee.dao.dto.ShopCheckRadiusDTO;
import com.doumee.dao.dto.DirectionInfoDTO;
import com.doumee.dao.dto.SameCityCheckDTO;
import com.doumee.dao.vo.PriceCalculateVO;
@@ -299,5 +301,26 @@
        return ApiResponse.success("查询成功", appVersion);
    }
    @LoginShopRequired
    @ApiOperation(value = "门店校验操作半径", notes = "校验门店操作人当前位置是否在目标点允许操作半径内")
    @PostMapping("/checkShopOperationRadius")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true)
    })
    public ApiResponse<Boolean> checkShopOperationRadius(@RequestBody @Valid ShopCheckRadiusDTO dto) {
        Boolean result = ordersService.checkOperationRadius(dto.getOrderId(), this.getShopId(), Constants.ZERO, dto.getLng(), dto.getLat());
        return ApiResponse.success("操作成功", result);
    }
    @LoginDriverRequired
    @ApiOperation(value = "司机校验操作半径", notes = "校验司机当前位置是否在目标点允许操作半径内")
    @PostMapping("/checkDriverOperationRadius")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true)
    })
    public ApiResponse<Boolean> checkDriverOperationRadius(@RequestBody @Valid DriverCheckRadiusDTO dto) {
        Boolean result = ordersService.checkOperationRadius(dto.getOrderId(), this.getDriverId(), Constants.ONE, dto.getLng(), dto.getLat());
        return ApiResponse.success("操作成功", result);
    }
}
server/web/src/main/java/com/doumee/api/web/MemberApi.java
@@ -5,6 +5,7 @@
import com.doumee.core.model.ApiResponse;
import com.doumee.dao.business.model.Member;
import com.doumee.dao.dto.UpdMobileRequest;
import com.doumee.dao.vo.MemberContactVO;
import com.doumee.dao.vo.UserCenterVO;
import com.doumee.service.business.MemberService;
import com.doumee.service.business.SmsrecordService;
@@ -111,6 +112,15 @@
        return ApiResponse.success("操作成功");
    }
    @LoginRequired
    @ApiOperation("获取用户收件信息")
    @GetMapping("/getContactInfo")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true)
    })
    public ApiResponse<MemberContactVO> getContactInfo() {
        return ApiResponse.success("查询成功", memberService.getContactInfo(getMemberId()));
    }
}
server/web/src/main/java/com/doumee/api/web/PaymentCallback.java
@@ -231,9 +231,12 @@
                                    templateParam.put("orderNo", refundOrder.getCode());
                                    templateParam.put("money", String.valueOf(Constants.getFormatMoney(
                                            refundOrder.getRefundAmount() != null ? refundOrder.getRefundAmount() : 0L)));
                                    String smsError = AliSmsService.sendSms(refundMember.getTelephone(),
                                            Constants.SmsNotify.MEMBER_REFUNDED.getTemplateCode(),
                                            templateParam.toJSONString());
                                    String smsError = null;
                                    if (Constants.SmsNotify.MEMBER_REFUNDED.isEnabled()) {
                                        smsError = AliSmsService.sendSms(refundMember.getTelephone(),
                                                Constants.SmsNotify.MEMBER_REFUNDED.getTemplateCode(),
                                                templateParam.toJSONString());
                                    }
                                    if (smsError == null) {
                                        log.info("退款短信发送成功: phone={}", refundMember.getTelephone());
                                    } else {
server/web/src/main/resources/application.yml
@@ -12,7 +12,7 @@
spring:
  profiles:
    active: pro
    active: dev
  # JSON返回配置
  jackson:
    # 默认时区