MrShi
3 天以前 e9a7cddce776382916e975402986144a88899ac5
Merge branch 'master' of http://139.186.142.91:10010/r/productDev/gtzxinglijicun
已添加26个文件
已修改42个文件
3746 ■■■■ 文件已修改
server/admin/src/main/java/com/doumee/api/business/CouponController.java 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/admin/src/main/java/com/doumee/api/business/DataBoardController.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/admin/src/main/java/com/doumee/api/business/DriverInfoController.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/admin/src/main/java/com/doumee/api/business/MemberCouponController.java 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/admin/src/main/java/com/doumee/api/business/PricingRuleController.java 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/admin/src/main/java/com/doumee/job/ArrivalPickUpNotifyJob.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/admin/src/main/java/com/doumee/job/RegisterCouponGiftJob.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/admin/src/main/resources/application.yml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/db/db_change.sql 133 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/biz/system/impl/OperationConfigBizImpl.java 18 ●●●●● 补丁 | 查看 | 原始文档 | 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 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/config/wx/WxPayV3Service.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/core/constants/Constants.java 70 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/CouponMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/MemberCouponMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/model/Coupon.java 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/model/DriverInfo.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/model/Member.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/model/MemberCoupon.java 134 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/model/Orders.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/model/ShopInfo.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/dto/DataBoardQueryDTO.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/dto/DriverActiveOrderDTO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/dto/DriverCheckRadiusDTO.java 30 ●●●●● 补丁 | 查看 | 原始文档 | 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 27 ●●●●● 补丁 | 查看 | 原始文档 | 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/ShopCheckRadiusDTO.java 30 ●●●●● 补丁 | 查看 | 原始文档 | 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/DataBoardVO.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/vo/LuggageTypeItem.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/vo/MemberContactVO.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/vo/MemberTrendVO.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/vo/OrderTrendVO.java 21 ●●●●● 补丁 | 查看 | 原始文档 | 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/RevenueTrendVO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/vo/ShopDetailVO.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/vo/ShopPerformanceVO.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/vo/ShopRevenueShareVO.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/vo/UserCenterVO.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/CouponService.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/DataBoardService.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/DriverInfoService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/MemberCouponService.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/MemberService.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/OrdersService.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/ShopInfoService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/CouponServiceImpl.java 177 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/DataBoardServiceImpl.java 338 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/DriverInfoServiceImpl.java 445 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/MemberCouponServiceImpl.java 149 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/MemberServiceImpl.java 173 ●●●●● 补丁 | 查看 | 原始文档 | 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 381 ●●●● 补丁 | 查看 | 原始文档 | 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 596 ●●●● 补丁 | 查看 | 原始文档 | 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 37 ●●●●● 补丁 | 查看 | 原始文档 | 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/CouponController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,91 @@
package com.doumee.api.business;
import com.doumee.api.BaseController;
import com.doumee.core.annotation.pr.PreventRepeat;
import com.doumee.core.model.ApiResponse;
import com.doumee.core.model.PageData;
import com.doumee.core.model.PageWrap;
import com.doumee.dao.business.model.Coupon;
import com.doumee.service.business.CouponService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
@Api(tags = "优惠券管理")
@RestController
@RequestMapping("/business/coupon")
public class CouponController extends BaseController {
    @Autowired
    private CouponService couponService;
    @PreventRepeat
    @ApiOperation("新建")
    @PostMapping("/create")
    @RequiresPermissions("business:coupon:create")
    public ApiResponse create(@RequestBody Coupon coupon) {
        return ApiResponse.success(couponService.create(coupon));
    }
    @ApiOperation("根据ID删除")
    @GetMapping("/delete/{id}")
    @RequiresPermissions("business:coupon:delete")
    public ApiResponse deleteById(@PathVariable Integer id) {
        couponService.deleteById(id);
        return ApiResponse.success(null);
    }
    @ApiOperation("批量删除")
    @GetMapping("/delete/batch")
    @RequiresPermissions("business:coupon: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));
        }
        couponService.deleteByIdInBatch(idList);
        return ApiResponse.success(null);
    }
    @ApiOperation("根据ID修改")
    @PostMapping("/updateById")
    @RequiresPermissions("business:coupon:update")
    public ApiResponse updateById(@RequestBody Coupon coupon) {
        couponService.updateById(coupon);
        return ApiResponse.success(null);
    }
    @ApiOperation("修改状态")
    @PostMapping("/updateStatus")
    @RequiresPermissions("business:coupon:update")
    public ApiResponse updateStatus(@RequestBody Coupon coupon) {
        couponService.updateStatus(coupon);
        return ApiResponse.success(null);
    }
    @ApiOperation("分页查询")
    @PostMapping("/page")
    @RequiresPermissions("business:coupon:query")
    public ApiResponse<PageData<Coupon>> findPage(@RequestBody PageWrap<Coupon> pageWrap) {
        return ApiResponse.success(couponService.findPage(pageWrap));
    }
    @ApiOperation("根据ID查询")
    @GetMapping("/{id}")
    @RequiresPermissions("business:coupon:query")
    public ApiResponse findById(@PathVariable Integer id) {
        return ApiResponse.success(couponService.findById(id));
    }
    @ApiOperation("有效优惠券列表")
    @GetMapping("/validList")
    public ApiResponse<List<Coupon>> validList() {
        return ApiResponse.success(couponService.findValidList());
    }
}
server/admin/src/main/java/com/doumee/api/business/DataBoardController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,55 @@
package com.doumee.api.business;
import com.doumee.api.BaseController;
import com.doumee.core.model.ApiResponse;
import com.doumee.dao.dto.DataBoardQueryDTO;
import com.doumee.dao.vo.*;
import com.doumee.service.business.DataBoardService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@Api(tags = "数据看板")
@RestController
@RequestMapping("/business/dataBoard")
public class DataBoardController extends BaseController {
    @Autowired
    private DataBoardService dataBoardService;
    @ApiOperation("经营看板")
    @PostMapping("/overview")
    public ApiResponse<DataBoardVO> overview(@RequestBody DataBoardQueryDTO query) {
        return ApiResponse.success(dataBoardService.overview(query));
    }
    @ApiOperation("近30天会员注册趋势")
    @PostMapping("/memberTrend")
    public ApiResponse<List<MemberTrendVO>> memberTrend() {
        return ApiResponse.success(dataBoardService.memberTrend());
    }
    @ApiOperation("近30天订单趋势")
    @PostMapping("/orderTrend")
    public ApiResponse<List<OrderTrendVO>> orderTrend() {
        return ApiResponse.success(dataBoardService.orderTrend());
    }
    @ApiOperation("近30天营收趋势")
    @PostMapping("/revenueTrend")
    public ApiResponse<List<RevenueTrendVO>> revenueTrend() {
        return ApiResponse.success(dataBoardService.revenueTrend());
    }
    @ApiOperation("门店业绩统计")
    @PostMapping("/shopPerformance")
    public ApiResponse<ShopPerformanceVO> shopPerformance(@RequestBody DataBoardQueryDTO query) {
        return ApiResponse.success(dataBoardService.shopPerformance(query));
    }
}
server/admin/src/main/java/com/doumee/api/business/DriverInfoController.java
@@ -131,6 +131,7 @@
        DriverInfo query = new DriverInfo();
        query.setAuditStatus(3);
        query.setDeleted(0);
        query.setVersionType(0);
        return ApiResponse.success(driverInfoService.findList(query));
    }
server/admin/src/main/java/com/doumee/api/business/MemberCouponController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,77 @@
package com.doumee.api.business;
import com.doumee.api.BaseController;
import com.doumee.core.annotation.pr.PreventRepeat;
import com.doumee.core.model.ApiResponse;
import com.doumee.core.model.PageData;
import com.doumee.core.model.PageWrap;
import com.doumee.dao.business.model.MemberCoupon;
import com.doumee.service.business.MemberCouponService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
@Api(tags = "用户优惠券管理")
@RestController
@RequestMapping("/business/memberCoupon")
public class MemberCouponController extends BaseController {
    @Autowired
    private MemberCouponService memberCouponService;
    @PreventRepeat
    @ApiOperation("新建")
    @PostMapping("/create")
    @RequiresPermissions("business:memberCoupon:create")
    public ApiResponse create(@RequestBody MemberCoupon memberCoupon) {
        return ApiResponse.success(memberCouponService.create(memberCoupon));
    }
    @ApiOperation("根据ID删除")
    @GetMapping("/delete/{id}")
    @RequiresPermissions("business:memberCoupon:delete")
    public ApiResponse deleteById(@PathVariable Integer id) {
        memberCouponService.deleteById(id);
        return ApiResponse.success(null);
    }
    @ApiOperation("批量删除")
    @GetMapping("/delete/batch")
    @RequiresPermissions("business:memberCoupon: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));
        }
        memberCouponService.deleteByIdInBatch(idList);
        return ApiResponse.success(null);
    }
    @ApiOperation("根据ID修改")
    @PostMapping("/updateById")
    @RequiresPermissions("business:memberCoupon:update")
    public ApiResponse updateById(@RequestBody MemberCoupon memberCoupon) {
        memberCouponService.updateById(memberCoupon);
        return ApiResponse.success(null);
    }
    @ApiOperation("分页查询")
    @PostMapping("/page")
    @RequiresPermissions("business:memberCoupon:query")
    public ApiResponse<PageData<MemberCoupon>> findPage(@RequestBody PageWrap<MemberCoupon> pageWrap) {
        return ApiResponse.success(memberCouponService.findPage(pageWrap));
    }
    @ApiOperation("根据ID查询")
    @GetMapping("/{id}")
    @RequiresPermissions("business:memberCoupon:query")
    public ApiResponse findById(@PathVariable Integer id) {
        return ApiResponse.success(memberCouponService.findById(id));
    }
}
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/java/com/doumee/job/ArrivalPickUpNotifyJob.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
package com.doumee.job;
import com.doumee.core.job.BaseJob;
import com.doumee.core.job.JobContext;
import com.doumee.core.job.JobParam;
import com.doumee.service.business.OrdersService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
 * å³å°†åˆ°è¾¾å–件时间短信通知
 * @author rk
 * @date 2026/05/07
 */
@Slf4j
@Component("arrivalPickUpNotifyJob")
public class ArrivalPickUpNotifyJob extends BaseJob {
    @Autowired
    private OrdersService ordersService;
    @Override
    public JobContext execute(JobParam param) {
        JobContext jobContext = new JobContext();
        try {
            int count = ordersService.notifyArrivalPickUp();
            jobContext.setHandleSuccessSize(count);
            jobContext.setHandleTotalSize(count);
            jobContext.setContext("即将到达取件时间通知完成,共通知" + count + "单");
        } catch (Exception e) {
            log.error("即将到达取件时间通知任务异常", e);
        }
        return jobContext;
    }
}
server/admin/src/main/java/com/doumee/job/RegisterCouponGiftJob.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package com.doumee.job;
import com.doumee.core.job.BaseJob;
import com.doumee.core.job.JobContext;
import com.doumee.core.job.JobParam;
import com.doumee.service.business.MemberService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Slf4j
@Component("registerCouponGiftJob")
public class RegisterCouponGiftJob extends BaseJob {
    @Autowired
    private MemberService memberService;
    @Override
    public JobContext execute(JobParam param) {
        JobContext jobContext = new JobContext();
        try {
            memberService.giftRegisterCoupon();
            jobContext.setContext("注册满年赠送优惠券任务执行完成");
        } catch (Exception e) {
            log.error("注册满年赠送优惠券任务异常", e);
            jobContext.setContext("注册满年赠送优惠券任务异常:" + e.getMessage());
        }
        return jobContext;
    }
}
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,139 @@
-- ============================================================
-- 2026/05/13 æ³¨å†Œæ»¡å¹´èµ é€ä¼˜æƒ åˆ¸å®šæ—¶ä»»åŠ¡
-- ============================================================
INSERT INTO `system_job` (`JOB_NAME`, `HANDLER`, `CRON`, `WITH_LOG`, `WITH_ASYNC`, `STATUS`, `REMARK`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`)
VALUES ('注册满年赠送优惠券', 'registerCouponGiftJob', '0 0 3 * * ?', 1, 0, 1, '根据运营配置,查询注册满X年的会员赠送优惠券', 1, NOW(), 1, NOW(), 0);
-- ============================================================
-- 2026/05/13 è¿è¥é…ç½®å¢žåŠ ä¼˜æƒ åˆ¸èµ é€è§„åˆ™
-- ============================================================
INSERT INTO `SYSTEM_DICT_DATA` (`DICT_ID`, `CODE`, `LABEL`, `REMARK`, `SORT`, `DISABLED`, `CREATE_USER`, `CREATE_TIME`, `DELETED`) VALUES (105, '', 'ORDER_COUPON_ORDER_COUNT', '下单赠送-订单次数', 0, 0, 1, NOW(), 0);
INSERT INTO `SYSTEM_DICT_DATA` (`DICT_ID`, `CODE`, `LABEL`, `REMARK`, `SORT`, `DISABLED`, `CREATE_USER`, `CREATE_TIME`, `DELETED`) VALUES (105, '', 'ORDER_COUPON_GIFT_COUNT', '下单赠送-至多赠送次数', 1, 0, 1, NOW(), 0);
INSERT INTO `SYSTEM_DICT_DATA` (`DICT_ID`, `CODE`, `LABEL`, `REMARK`, `SORT`, `DISABLED`, `CREATE_USER`, `CREATE_TIME`, `DELETED`) VALUES (105, '', 'ORDER_COUPON_ID', '下单赠送-优惠券ID', 2, 0, 1, NOW(), 0);
INSERT INTO `SYSTEM_DICT_DATA` (`DICT_ID`, `CODE`, `LABEL`, `REMARK`, `SORT`, `DISABLED`, `CREATE_USER`, `CREATE_TIME`, `DELETED`) VALUES (105, '', 'REGISTER_COUPON_YEARS', '注册赠送-注册年数', 3, 0, 1, NOW(), 0);
INSERT INTO `SYSTEM_DICT_DATA` (`DICT_ID`, `CODE`, `LABEL`, `REMARK`, `SORT`, `DISABLED`, `CREATE_USER`, `CREATE_TIME`, `DELETED`) VALUES (105, '', 'REGISTER_COUPON_GIFT_COUNT', '注册赠送-至多赠送次数', 4, 0, 1, NOW(), 0);
INSERT INTO `SYSTEM_DICT_DATA` (`DICT_ID`, `CODE`, `LABEL`, `REMARK`, `SORT`, `DISABLED`, `CREATE_USER`, `CREATE_TIME`, `DELETED`) VALUES (105, '', 'REGISTER_COUPON_ID', '注册赠送-优惠券ID', 5, 0, 1, NOW(), 0);
INSERT INTO `SYSTEM_DICT_DATA` (`DICT_ID`, `CODE`, `LABEL`, `REMARK`, `SORT`, `DISABLED`, `CREATE_USER`, `CREATE_TIME`, `DELETED`) VALUES (105, '', 'REGISTER_GIFT_COUPON_IDS', '新注册赠送-优惠券IDs(多选,分割)', 6, 0, 1, NOW(), 0);
-- ============================================================
-- 2026/05/13 ä¼šå‘˜å¢žåŠ ä¼˜æƒ åˆ¸èµ é€æ¬¡æ•°å­—æ®µ
-- ============================================================
ALTER TABLE `member` ADD COLUMN `ORDER_COUPON_GIFT_COUNT` INT DEFAULT 0 COMMENT '下单赠送优惠券已赠送次数' AFTER `TYPE`;
ALTER TABLE `member` ADD COLUMN `REGISTER_COUPON_GIFT_COUNT` INT DEFAULT 0 COMMENT '注册满年赠送优惠券已赠送次数' AFTER `ORDER_COUPON_GIFT_COUNT`;
-- ============================================================
-- 2026/05/13 ä¼˜æƒ åˆ¸ä¿¡æ¯è¡¨
-- ============================================================
CREATE TABLE `coupon` (
  `ID` int NOT NULL AUTO_INCREMENT COMMENT '主键',
  `CREATOR` int DEFAULT NULL COMMENT '创建人编码',
  `CREATE_DATE` datetime DEFAULT NULL COMMENT '创建时间',
  `EDITOR` int DEFAULT NULL COMMENT '更新人编码',
  `EDIT_DATE` datetime DEFAULT NULL COMMENT '更新时间',
  `ISDELETED` int DEFAULT NULL COMMENT '是否删除0否 1是',
  `STATUS` int DEFAULT NULL COMMENT '状态 0启用 1禁用',
  `REMARK` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注',
  `NAME` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '名称',
  `INFO` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '说明',
  `TYPE` int DEFAULT NULL COMMENT '类型0平台优惠券 ',
  `LIMIT_PRICE` BIGINT DEFAULT NULL COMMENT '满额(分)',
  `PRICE` BIGINT DEFAULT NULL COMMENT '优惠金额(分)',
  `PUSH_DAYS` int DEFAULT NULL COMMENT '推送后领取有效天数',
  `VALID_DAYS` int DEFAULT NULL COMMENT '领取后有效天数',
  `GET_METHOD` int DEFAULT NULL COMMENT '领取方式 0领取',
  `USE_TYPE` int DEFAULT NULL COMMENT '使用类型:0=固定时长;',
  `COUPON_TYPE` int DEFAULT NULL COMMENT '优惠券类型:0=满减券;',
  PRIMARY KEY (`ID`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='优惠券信息表';
INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `MODULE`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) VALUES ('business:coupon:create', '新建优惠券', '优惠券管理', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0);
INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `MODULE`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) VALUES ('business:coupon:delete', '删除优惠券', '优惠券管理', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0);
INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `MODULE`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) VALUES ('business:coupon:update', '修改优惠券', '优惠券管理', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0);
INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `MODULE`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) VALUES ('business:coupon:query', '查询优惠券', '优惠券管理', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0);
-- ============================================================
-- 2026/05/13 ç”¨æˆ·ä¼˜æƒ åˆ¸è¡¨
-- ============================================================
CREATE TABLE `member_coupon` (
  `ID` int NOT NULL AUTO_INCREMENT COMMENT '主键',
  `CREATOR` int DEFAULT NULL COMMENT '创建人编码',
  `CREATE_DATE` datetime DEFAULT NULL COMMENT '创建时间',
  `EDITOR` int DEFAULT NULL COMMENT '更新人编码',
  `EDIT_DATE` datetime DEFAULT NULL COMMENT '更新时间',
  `ISDELETED` int DEFAULT NULL COMMENT '是否删除0否 1是',
  `REMARK` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注',
  `COUPON_ID` int DEFAULT NULL COMMENT '优惠券主键关联表',
  `MEMBER_ID` int DEFAULT NULL COMMENT '用户编码(关联member表)',
  `STATUS` int DEFAULT NULL COMMENT '状态:0=待领取;1=已领取;2=已使用;98=未领取已过期;99=已过期;',
  `VALID_DATE` datetime DEFAULT NULL COMMENT '领取有效期时间',
  `START_DATE` datetime DEFAULT NULL COMMENT '有效期开始时间',
  `END_DATE` datetime DEFAULT NULL COMMENT '有效期结束时间',
  `USE_DATE` datetime DEFAULT NULL COMMENT '使用时间',
  `ORDER_ID` int DEFAULT NULL COMMENT '关联订单主键',
  `NAME` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '名称',
  `INFO` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '说明',
  `TYPE` int DEFAULT NULL COMMENT '类型 0平台优惠券 ',
  `LIMIT_PRICE` BIGINT DEFAULT NULL COMMENT '满额(分)',
  `PRICE` BIGINT DEFAULT NULL COMMENT '优惠金额(分)',
  `GET_METHOD` int DEFAULT NULL COMMENT '领取方式 0领取',
  `COUPON_TYPE` int DEFAULT NULL COMMENT '优惠券类型:0=满减券;',
  `PUSH_DAYS` int DEFAULT NULL COMMENT '推送后领取有效天数',
  `VALID_DAYS` int DEFAULT NULL COMMENT '领取后有效天数',
  PRIMARY KEY (`ID`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户优惠券';
INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `MODULE`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) VALUES ('business:memberCoupon:create', '新建用户优惠券', '用户优惠券', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0);
INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `MODULE`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) VALUES ('business:memberCoupon:delete', '删除用户优惠券', '用户优惠券', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0);
INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `MODULE`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) VALUES ('business:memberCoupon:update', '修改用户优惠券', '用户优惠券', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0);
INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `MODULE`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) VALUES ('business:memberCoupon:query', '查询用户优惠券', '用户优惠券', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0);
-- ============================================================
-- 2026/05/12 è®¢å•增加是否转换订单字段
-- ============================================================
ALTER TABLE `orders` ADD COLUMN `IS_CONVERTED` INT DEFAULT 0 COMMENT '是否转换订单:0=否;1=是(异地转就地)' AFTER `TYPE`;
-- ============================================================
-- 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,15 @@
        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));
        dto.setOrderCouponOrderCount(getValue(Constants.OP_ORDER_COUPON_ORDER_COUNT));
        dto.setOrderCouponGiftCount(getValue(Constants.OP_ORDER_COUPON_GIFT_COUNT));
        dto.setOrderCouponId(getValue(Constants.OP_ORDER_COUPON_ID));
        dto.setRegisterCouponYears(getValue(Constants.OP_REGISTER_COUPON_YEARS));
        dto.setRegisterCouponGiftCount(getValue(Constants.OP_REGISTER_COUPON_GIFT_COUNT));
        dto.setRegisterCouponId(getValue(Constants.OP_REGISTER_COUPON_ID));
        dto.setRegisterGiftCouponIds(getValue(Constants.OP_REGISTER_GIFT_COUPON_IDS));
        return dto;
    }
@@ -55,6 +64,15 @@
        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());
        saveOrUpdate(Constants.OP_ORDER_COUPON_ORDER_COUNT, "下单赠送-订单次数", dto.getOrderCouponOrderCount());
        saveOrUpdate(Constants.OP_ORDER_COUPON_GIFT_COUNT, "下单赠送-至多赠送次数", dto.getOrderCouponGiftCount());
        saveOrUpdate(Constants.OP_ORDER_COUPON_ID, "下单赠送-优惠券ID", dto.getOrderCouponId());
        saveOrUpdate(Constants.OP_REGISTER_COUPON_YEARS, "注册赠送-注册年数", dto.getRegisterCouponYears());
        saveOrUpdate(Constants.OP_REGISTER_COUPON_GIFT_COUNT, "注册赠送-至多赠送次数", dto.getRegisterCouponGiftCount());
        saveOrUpdate(Constants.OP_REGISTER_COUPON_ID, "注册赠送-优惠券ID", dto.getRegisterCouponId());
        saveOrUpdate(Constants.OP_REGISTER_GIFT_COUPON_IDS, "新注册赠送-优惠券IDs", dto.getRegisterGiftCouponIds());
    }
    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
@@ -7,6 +7,7 @@
import com.doumee.core.constants.Constants;
import com.doumee.core.constants.ResponseStatus;
import com.doumee.core.exception.BusinessException;
import com.doumee.dao.business.model.DriverInfo;
import com.doumee.dao.business.model.Member;
import com.doumee.dao.business.model.ShopInfo;
import io.jsonwebtoken.JwtException;
@@ -162,16 +163,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;
@@ -194,9 +195,14 @@
            if(StringUtils.isBlank(tokenRedis)){
                throw new BusinessException(ResponseStatus.BE_OVERDUE);
            }
            Integer memberId = getTokenId(token);
            Integer driverId = getTokenId(token);
            //查询司机信息
            Integer memberId = dao.queryForObject(" select COALESCE(member_id,0)  from driver_info where VERSION_TYPE = 0 and  id  = ?", Integer.class, driverId);
            if(Objects.isNull(memberId)||Constants.equalsInteger(memberId,Constants.ZERO)){
                throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"司机信息信息出错");
            }
            Integer isDeleted = dao.queryForObject(" select COALESCE(DELETED,1)  from member where user_type = 1 and   id  = ?", Integer.class, memberId);
            if(isDeleted== Constants.ONE){
            if(isDeleted == Constants.ONE){
                throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"司机信息已删除,请联系管理员");
            }
            Integer isForbidden = dao.queryForObject(" select COALESCE(STATUS,0)  from member where user_type = 1 and  id  = ?", Integer.class, memberId);
@@ -205,7 +211,7 @@
            }
            Integer count = dao.queryForObject("select count(1) from member where  user_type = 1  and id  = ?", Integer.class, memberId);
            if (count != null && count > 0) {
                request.setAttribute(JwtTokenUtil.DRIVER_ID, memberId);
                request.setAttribute(JwtTokenUtil.DRIVER_ID, driverId);
                return true;
            }else{
                throw new BusinessException(ResponseStatus.BE_OVERDUE.getCode(),"司机信息出错");
server/services/src/main/java/com/doumee/config/wx/WxPayV3Service.java
@@ -59,7 +59,7 @@
            com.wechat.pay.java.service.payments.jsapi.model.Amount amount =
                    new com.wechat.pay.java.service.payments.jsapi.model.Amount();
            amount.setTotal(totalCents.intValue());
            amount.setTotal(1);//totalCents.intValue());
            amount.setCurrency("CNY");
            request.setAmount(amount);
@@ -109,8 +109,8 @@
            request.setNotifyUrl(notifyUrl);
            AmountReq amount = new AmountReq();
            amount.setRefund(refundCents);
            amount.setTotal(totalCents);
            amount.setRefund(1L);//refundCents);
            amount.setTotal(1L);//totalCents);
            amount.setCurrency("CNY");
            request.setAmount(amount);
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,15 @@
    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 OP_ORDER_COUPON_ORDER_COUNT = "ORDER_COUPON_ORDER_COUNT";
    public static final String OP_ORDER_COUPON_GIFT_COUNT = "ORDER_COUPON_GIFT_COUNT";
    public static final String OP_ORDER_COUPON_ID = "ORDER_COUPON_ID";
    public static final String OP_REGISTER_COUPON_YEARS = "REGISTER_COUPON_YEARS";
    public static final String OP_REGISTER_COUPON_GIFT_COUNT = "REGISTER_COUPON_GIFT_COUNT";
    public static final String OP_REGISTER_COUPON_ID = "REGISTER_COUPON_ID";
    public static final String OP_REGISTER_GIFT_COUPON_IDS = "REGISTER_GIFT_COUPON_IDS";
    // èŠ¯çƒ¨äº‘æ‰“å°æœºé…ç½®
    public static final String XPYUN_CONFIG = "XPYUN_CONFIG";
@@ -331,8 +342,8 @@
    public enum OrderLogType {
        createOrder(1, "创建订单", "会员创建寄存订单,订单编号【{param}】"),
        payOrder(1, "订单支付", "会员支付成功,支付金额【{param}】元"),
        urgent(2, "平台加急", "平台加急,奖励金【{param}】元"),
        assignDriver(3, "平台指派", "平台指派司机【{param}】接单,奖励金【{param1}】元"),
        urgent(2, "平台加急", "平台加急,加急费【{param}】元"),
        assignDriver(3, "平台指派", "平台指派司机【{param}】接单,加急费【{param1}】元"),
        memberCancel(4, "会员取消订单", "{param}"),
        driverCancel(4, "司机取消订单", "{param}"),
        systemCancel(4, "系统自动取消", "{param}"),
@@ -381,6 +392,25 @@
    /**
     * ä¼˜æƒ åˆ¸çŠ¶æ€
     */
    @Getter
    @AllArgsConstructor
    public enum CouponStatus {
        waitClaim(0, "待领取"),
        claimed(1, "已领取"),
        used(2, "已使用"),
        expiredUnclaimed(98, "未领取已过期"),
        expired(99, "已过期")
        ;
        private final int status;
        private final String desc;
        public int getKey() { return status; }
        public String getValue() { return desc; }
    }
    /**
     * è®¢å•状态(就地/异地统一)
     * å°±åœ°å¯„å­˜: 0→1→2→3→(6)→7 / 96~99
     * å¼‚地寄存: 0→1→2→3→4→5→(6)→7 / 96~99
@@ -393,7 +423,7 @@
        deposited(2, "已寄存"),
        accepted(3, "已接单"),
        delivering(4, "派送中"),
        arrived(5, "已到店/已送达/待取件"),
        arrived(5, "已到店/已送达"),
        overdue(6, "存在逾期"), //弃用
        finished(7, "已完成"),
        cancelled(99, "已取消")
@@ -611,27 +641,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/CouponMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,7 @@
package com.doumee.dao.business;
import com.doumee.dao.business.model.Coupon;
import com.github.yulichang.base.MPJBaseMapper;
public interface CouponMapper extends MPJBaseMapper<Coupon> {
}
server/services/src/main/java/com/doumee/dao/business/MemberCouponMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,7 @@
package com.doumee.dao.business;
import com.doumee.dao.business.model.MemberCoupon;
import com.github.yulichang.base.MPJBaseMapper;
public interface MemberCouponMapper extends MPJBaseMapper<MemberCoupon> {
}
server/services/src/main/java/com/doumee/dao/business/model/Coupon.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,106 @@
package com.doumee.dao.business.model;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.doumee.core.annotation.excel.ExcelColumn;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@Data
@ApiModel("优惠券信息表")
@TableName("`coupon`")
public class Coupon {
    @TableId(type = IdType.AUTO)
    @ApiModelProperty(value = "主键", example = "1")
    @ExcelColumn(name = "主键")
    private Integer id;
    @ApiModelProperty(value = "创建人编码", example = "1")
    @ExcelColumn(name = "创建人编码")
    private Integer creator;
    @ApiModelProperty(value = "创建时间")
    @ExcelColumn(name = "创建时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createDate;
    @ApiModelProperty(value = "更新人编码", example = "1")
    @ExcelColumn(name = "更新人编码")
    private Integer editor;
    @ApiModelProperty(value = "更新时间")
    @ExcelColumn(name = "更新时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date editDate;
    @ApiModelProperty(value = "是否删除 0否 1是", example = "0")
    @ExcelColumn(name = "是否删除")
    private Integer isdeleted;
    @ApiModelProperty(value = "状态 0启用 1禁用", example = "0")
    @ExcelColumn(name = "状态")
    private Integer status;
    @ApiModelProperty(value = "备注")
    @ExcelColumn(name = "备注")
    private String remark;
    @ApiModelProperty(value = "名称")
    @ExcelColumn(name = "名称")
    private String name;
    @ApiModelProperty(value = "说明")
    @ExcelColumn(name = "说明")
    private String info;
    @ApiModelProperty(value = "类型 0平台优惠券", example = "0")
    @ExcelColumn(name = "类型")
    private Integer type;
    @ApiModelProperty(value = "满额(分)")
    @ExcelColumn(name = "满额")
    private Long limitPrice;
    @ApiModelProperty(value = "优惠金额(分)")
    @ExcelColumn(name = "优惠金额")
    private Long price;
    @ApiModelProperty(value = "推送后领取有效天数", example = "7")
    @ExcelColumn(name = "推送后领取有效天数")
    private Integer pushDays;
    @ApiModelProperty(value = "领取后有效天数", example = "30")
    @ExcelColumn(name = "领取后有效天数")
    private Integer validDays;
    @ApiModelProperty(value = "领取方式 0领取", example = "0")
    @ExcelColumn(name = "领取方式")
    private Integer getMethod;
    @ApiModelProperty(value = "使用类型:0=固定时长", example = "0")
    @ExcelColumn(name = "使用类型")
    private Integer useType;
    @ApiModelProperty(value = "优惠券类型:0=满减券", example = "0")
    @ExcelColumn(name = "优惠券类型")
    private Integer couponType;
    @TableField(exist = false)
    @ApiModelProperty(value = "修改人名称")
    private String editorName;
    @TableField(exist = false)
    @ApiModelProperty(value = "领取数量")
    private Long claimCount;
    @TableField(exist = false)
    @ApiModelProperty(value = "使用数量")
    private Long usedCount;
}
server/services/src/main/java/com/doumee/dao/business/model/DriverInfo.java
@@ -240,4 +240,13 @@
    @ApiModelProperty(value = "极光推送别名")
    private String jpushAlias;
    @ApiModelProperty(value = "司机版本类型:0=正式版本;1=变更版本", example = "0")
    private Integer versionType;
    @ApiModelProperty(value = "关联正式版本司机主键(变更版本使用)", example = "1")
    private Integer relationDriverId;
    @TableField(exist = false)
    @ApiModelProperty(value = "是否存在审批通过的正式版本:true=有;false=无")
    private Boolean hasApprovedOfficial;
}
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;
@@ -151,17 +154,15 @@
    @TableField(exist = false)
    private Integer type;
    @ApiModelProperty(value = "下单赠送优惠券已赠送次数", example = "0")
    private Integer orderCouponGiftCount;
    @ApiModelProperty(value = "注册满年赠送优惠券已赠送次数", example = "0")
    private Integer registerCouponGiftCount;
    @ApiModelProperty(value = "头像全路径")
    @TableField(exist = false)
    private String fullCoverImage;
    @ApiModelProperty(value = "接单权重", example = "1")
    @TableField(exist = false)
    private Integer level;
    @ApiModelProperty(value = "距离", example = "1")
    @TableField(exist = false)
    private BigDecimal distance;
    @ApiModelProperty(value = "身份信息", example = "1")
    @TableField(exist = false)
server/services/src/main/java/com/doumee/dao/business/model/MemberCoupon.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,134 @@
package com.doumee.dao.business.model;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.doumee.core.annotation.excel.ExcelColumn;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@Data
@ApiModel("用户优惠券")
@TableName("`member_coupon`")
public class MemberCoupon {
    @TableId(type = IdType.AUTO)
    @ApiModelProperty(value = "主键", example = "1")
    @ExcelColumn(name = "主键")
    private Integer id;
    @ApiModelProperty(value = "创建人编码", example = "1")
    @ExcelColumn(name = "创建人编码")
    private Integer creator;
    @ApiModelProperty(value = "创建时间")
    @ExcelColumn(name = "创建时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createDate;
    @ApiModelProperty(value = "更新人编码", example = "1")
    @ExcelColumn(name = "更新人编码")
    private Integer editor;
    @ApiModelProperty(value = "更新时间")
    @ExcelColumn(name = "更新时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date editDate;
    @ApiModelProperty(value = "是否删除 0否 1是", example = "0")
    @ExcelColumn(name = "是否删除")
    private Integer isdeleted;
    @ApiModelProperty(value = "备注")
    @ExcelColumn(name = "备注")
    private String remark;
    @ApiModelProperty(value = "优惠券主键关联表", example = "1")
    @ExcelColumn(name = "优惠券主键")
    private Integer couponId;
    @ApiModelProperty(value = "用户编码(关联member表)", example = "1")
    @ExcelColumn(name = "用户编码")
    private Integer memberId;
    @ApiModelProperty(value = "状态:0=待领取;1=已领取;2=已使用;98=未领取已过期;99=已过期", example = "0")
    @ExcelColumn(name = "状态")
    private Integer status;
    @ApiModelProperty(value = "领取有效期时间")
    @ExcelColumn(name = "领取有效期时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date validDate;
    @ApiModelProperty(value = "有效期开始时间")
    @ExcelColumn(name = "有效期开始时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date startDate;
    @ApiModelProperty(value = "有效期结束时间")
    @ExcelColumn(name = "有效期结束时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date endDate;
    @ApiModelProperty(value = "使用时间")
    @ExcelColumn(name = "使用时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date useDate;
    @ApiModelProperty(value = "关联订单主键", example = "1")
    @ExcelColumn(name = "关联订单主键")
    private Integer orderId;
    @ApiModelProperty(value = "名称")
    @ExcelColumn(name = "名称")
    private String name;
    @ApiModelProperty(value = "说明")
    @ExcelColumn(name = "说明")
    private String info;
    @ApiModelProperty(value = "类型 0平台优惠券", example = "0")
    @ExcelColumn(name = "类型")
    private Integer type;
    @ApiModelProperty(value = "满额(分)")
    @ExcelColumn(name = "满额")
    private Long limitPrice;
    @ApiModelProperty(value = "优惠金额(分)")
    @ExcelColumn(name = "优惠金额")
    private Long price;
    @ApiModelProperty(value = "领取方式 0领取", example = "0")
    @ExcelColumn(name = "领取方式")
    private Integer getMethod;
    @ApiModelProperty(value = "优惠券类型:0=满减券", example = "0")
    @ExcelColumn(name = "优惠券类型")
    private Integer couponType;
    @ApiModelProperty(value = "推送后领取有效天数", example = "7")
    @ExcelColumn(name = "推送后领取有效天数")
    private Integer pushDays;
    @ApiModelProperty(value = "领取后有效天数", example = "30")
    @ExcelColumn(name = "领取后有效天数")
    private Integer validDays;
    @TableField(exist = false)
    @ApiModelProperty(value = "会员姓名")
    private String memberName;
    @TableField(exist = false)
    @ApiModelProperty(value = "会员电话")
    private String memberTelephone;
    @TableField(exist = false)
    @ApiModelProperty(value = "会员昵称")
    private String memberNickName;
}
server/services/src/main/java/com/doumee/dao/business/model/Orders.java
@@ -61,6 +61,9 @@
    @ExcelColumn(name = "寄存方式", valueMapping = "0=就地存取;1=异地存取;")
    private Integer type;
    @ApiModelProperty(value = "是否转换订单:0=否;1=是(异地转就地)", example = "0")
    private Integer isConverted;
    @ApiModelProperty(value = "是否必须本人签收:0=否;1=是", example = "0")
    @ExcelColumn(name = "本人签收", valueMapping = "0=否;1=是;")
    private Integer selfTake;
@@ -230,11 +233,10 @@
    private Long declaredFee;
    @ApiModelProperty(value = "加急费用(分)")
    @ExcelColumn(name = "加急费用")
    private Long urgentAmount;
    @ApiModelProperty(value = "平台奖励司机费用(分)")
    @ExcelColumn(name = "平台奖励司机费用")
    @ExcelColumn(name = "加急费用")
    private Long platformRewardAmount;
    @ApiModelProperty(value = "指派司机用户主键(member:id)")
@@ -371,12 +373,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 +450,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/DataBoardQueryDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
package com.doumee.dao.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
@ApiModel("数据看板查询条件")
public class DataBoardQueryDTO implements Serializable {
    @ApiModelProperty(value = "开始时间")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date startDate;
    @ApiModelProperty(value = "结束时间")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date endDate;
    @ApiModelProperty(value = "门店主键(可选)")
    private Integer shopId;
}
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/DriverCheckRadiusDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package com.doumee.dao.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
 * å¸æœºæ ¡éªŒæ“ä½œåŠå¾„请求
 * @author rk
 * @date 2026/05/08
 */
@Data
@ApiModel("司机校验操作半径请求")
public class DriverCheckRadiusDTO {
    @NotNull(message = "订单主键不能为空")
    @ApiModelProperty(value = "订单主键", example = "1", required = true)
    private Integer orderId;
    @NotNull(message = "当前经度不能为空")
    @ApiModelProperty(value = "当前经度", example = "116.404", required = true)
    private Double lng;
    @NotNull(message = "当前纬度不能为空")
    @ApiModelProperty(value = "当前纬度", example = "39.915", required = true)
    private Double lat;
}
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,31 @@
    @ApiModelProperty(value = "默认配送范围(km)", required = true)
    private String defaultDeliveryRange;
    @ApiModelProperty(value = "即将到达取件时间提前通知(分钟)", required = true)
    private String arrivalPickUpTime;
    @ApiModelProperty(value = "允许操作半径(m)", required = true)
    private String operationRadius;
    @ApiModelProperty(value = "下单满X次赠送优惠券-订单次数")
    private String orderCouponOrderCount;
    @ApiModelProperty(value = "下单满X次赠送优惠券-至多赠送次数")
    private String orderCouponGiftCount;
    @ApiModelProperty(value = "下单满X次赠送优惠券-赠送优惠券ID(多个以,分割)")
    private String orderCouponId;
    @ApiModelProperty(value = "注册满X年赠送优惠券-注册年数")
    private String registerCouponYears;
    @ApiModelProperty(value = "注册满X年赠送优惠券-至多赠送次数")
    private String registerCouponGiftCount;
    @ApiModelProperty(value = "注册满X年赠送优惠券-赠送优惠券ID(多个以,分割)")
    private String registerCouponId;
    @ApiModelProperty(value = "新注册用户赠送优惠券ID(多个以,分割)")
    private String registerGiftCouponIds;
}
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/ShopCheckRadiusDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package com.doumee.dao.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
 * é—¨åº—校验操作半径请求
 * @author rk
 * @date 2026/05/08
 */
@Data
@ApiModel("门店校验操作半径请求")
public class ShopCheckRadiusDTO {
    @NotNull(message = "订单主键不能为空")
    @ApiModelProperty(value = "订单主键", example = "1", required = true)
    private Integer orderId;
    @NotNull(message = "当前经度不能为空")
    @ApiModelProperty(value = "当前经度", example = "116.404", required = true)
    private Double lng;
    @NotNull(message = "当前纬度不能为空")
    @ApiModelProperty(value = "当前纬度", example = "39.915", required = true)
    private Double lat;
}
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/DataBoardVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package com.doumee.dao.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;
@Data
@ApiModel("经营看板数据")
public class DataBoardVO implements Serializable {
    @ApiModelProperty(value = "会员总数")
    private Long memberCount;
    @ApiModelProperty(value = "门店总数")
    private Long shopCount;
    @ApiModelProperty(value = "司机总数")
    private Long driverCount;
    @ApiModelProperty(value = "周期总订单数")
    private Long orderCount;
    @ApiModelProperty(value = "周期营收总金额(元)")
    private BigDecimal totalRevenue;
    @ApiModelProperty(value = "行李类型占比")
    private List<LuggageTypeItem> luggageTypeList;
}
server/services/src/main/java/com/doumee/dao/vo/LuggageTypeItem.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
package com.doumee.dao.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@Data
@ApiModel("行李类型占比项")
public class LuggageTypeItem implements Serializable {
    @ApiModelProperty(value = "类型名称")
    private String luggageName;
    @ApiModelProperty(value = "订单数")
    private Long orderCount;
    @ApiModelProperty(value = "行李数")
    private Long luggageCount;
}
server/services/src/main/java/com/doumee/dao/vo/MemberContactVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.doumee.dao.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel("用户收件信息")
public class MemberContactVO {
    @ApiModelProperty(value = "姓名")
    private String name;
    @ApiModelProperty(value = "手机号")
    private String phone;
}
server/services/src/main/java/com/doumee/dao/vo/MemberTrendVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.doumee.dao.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@Data
@ApiModel("会员注册趋势项")
public class MemberTrendVO implements Serializable {
    @ApiModelProperty(value = "日期")
    private String date;
    @ApiModelProperty(value = "注册数量")
    private Long count;
}
server/services/src/main/java/com/doumee/dao/vo/OrderTrendVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
package com.doumee.dao.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@Data
@ApiModel("订单趋势项")
public class OrderTrendVO implements Serializable {
    @ApiModelProperty(value = "日期")
    private String date;
    @ApiModelProperty(value = "就地寄存数量")
    private Long localCount;
    @ApiModelProperty(value = "异地寄存数量")
    private Long remoteCount;
}
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/RevenueTrendVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
package com.doumee.dao.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
@Data
@ApiModel("营收趋势项")
public class RevenueTrendVO implements Serializable {
    @ApiModelProperty(value = "日期")
    private String date;
    @ApiModelProperty(value = "就地寄存营收金额(元)")
    private BigDecimal localRevenue;
    @ApiModelProperty(value = "异地寄存营收金额(元)")
    private BigDecimal remoteRevenue;
}
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/ShopPerformanceVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
package com.doumee.dao.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
@Data
@ApiModel("门店业绩报表")
public class ShopPerformanceVO implements Serializable {
    @ApiModelProperty(value = "总订单数(状态1-6)")
    private Long totalOrderCount;
    @ApiModelProperty(value = "营收总金额(元)")
    private BigDecimal totalRevenue;
    @ApiModelProperty(value = "已完成订单数(状态=7)")
    private Long completedCount;
    @ApiModelProperty(value = "已退款订单数")
    private Long refundedCount;
    @ApiModelProperty(value = "订单完成率(如44.32表示44.32%)")
    private BigDecimal completionRate;
    @ApiModelProperty(value = "订单退款率(如44.32表示44.32%)")
    private BigDecimal refundRate;
}
server/services/src/main/java/com/doumee/dao/vo/ShopRevenueShareVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package com.doumee.dao.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
 * é—¨åº—收益比例配置返回
 * @author rk
 * @date 2026/05/09
 */
@Data
@ApiModel("门店收益比例配置")
public class ShopRevenueShareVO implements Serializable {
    @ApiModelProperty(value = "就地寄存分成占比(%)", example = "5.5")
    private Double localDeposit;
    @ApiModelProperty(value = "异地存件分成占比(%)", example = "5.5")
    private Double remoteDeposit;
    @ApiModelProperty(value = "异地取件分成占比(%)", example = "5.5")
    private Double remoteTake;
}
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/CouponService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package com.doumee.service.business;
import com.doumee.core.model.PageData;
import com.doumee.core.model.PageWrap;
import com.doumee.dao.business.model.Coupon;
import java.util.List;
public interface CouponService {
    Integer create(Coupon coupon);
    void deleteById(Integer id);
    void deleteByIdInBatch(List<Integer> ids);
    void updateById(Coupon coupon);
    void updateStatus(Coupon coupon);
    Coupon findById(Integer id);
    List<Coupon> findList(Coupon coupon);
    PageData<Coupon> findPage(PageWrap<Coupon> pageWrap);
    long count(Coupon coupon);
    List<Coupon> findValidList();
}
server/services/src/main/java/com/doumee/service/business/DataBoardService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package com.doumee.service.business;
import com.doumee.dao.dto.DataBoardQueryDTO;
import com.doumee.dao.vo.*;
import java.util.List;
public interface DataBoardService {
    DataBoardVO overview(DataBoardQueryDTO query);
    List<MemberTrendVO> memberTrend();
    List<OrderTrendVO> orderTrend();
    List<RevenueTrendVO> revenueTrend();
    ShopPerformanceVO shopPerformance(DataBoardQueryDTO query);
}
server/services/src/main/java/com/doumee/service/business/DriverInfoService.java
@@ -314,4 +314,10 @@
     */
    void registerJpushAlias(Integer driverId, String jpushAlias);
    /**
     * åˆå§‹åŒ–历史司机变更版本数据
     * @return å¤„理数量
     */
    int initChangeVersions();
}
server/services/src/main/java/com/doumee/service/business/MemberCouponService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
package com.doumee.service.business;
import com.doumee.core.model.PageData;
import com.doumee.core.model.PageWrap;
import com.doumee.dao.business.model.MemberCoupon;
import java.util.List;
public interface MemberCouponService {
    Integer create(MemberCoupon memberCoupon);
    void deleteById(Integer id);
    void deleteByIdInBatch(List<Integer> ids);
    void updateById(MemberCoupon memberCoupon);
    MemberCoupon findById(Integer id);
    List<MemberCoupon> findList(MemberCoupon memberCoupon);
    PageData<MemberCoupon> findPage(PageWrap<MemberCoupon> pageWrap);
    long count(MemberCoupon memberCoupon);
}
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);
    /**
     * éªŒè¯æ‰‹æœºå·
@@ -184,4 +187,6 @@
     * @return MemberDetailVO
     */
    MemberDetailVO findMemberDetail(Integer id);
}
    void giftRegisterCoupon();
}
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/ShopInfoService.java
@@ -225,4 +225,10 @@
     */
    void maintainPrinterSn(ShopPrinterDTO dto);
    /**
     * åˆå§‹åŒ–历史门店变更版本数据
     * @return å¤„理数量
     */
    int initChangeVersions();
}
server/services/src/main/java/com/doumee/service/business/impl/CouponServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,177 @@
package com.doumee.service.business.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.doumee.core.constants.Constants;
import com.doumee.core.constants.ResponseStatus;
import com.doumee.core.exception.BusinessException;
import com.doumee.core.model.LoginUserInfo;
import com.doumee.core.model.PageData;
import com.doumee.core.model.PageWrap;
import com.doumee.core.utils.Utils;
import com.doumee.dao.business.CouponMapper;
import com.doumee.dao.business.model.Coupon;
import com.doumee.service.business.CouponService;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.util.Date;
import java.util.List;
import java.util.Objects;
@Service
public class CouponServiceImpl implements CouponService {
    @Autowired
    private CouponMapper couponMapper;
    @Override
    @Transactional(rollbackFor = {Exception.class, BusinessException.class})
    public Integer create(Coupon coupon) {
        validateCoupon(coupon);
        LoginUserInfo loginUserInfo = (LoginUserInfo) SecurityUtils.getSubject().getPrincipal();
        coupon.setIsdeleted(Constants.ZERO);
        coupon.setCreateDate(new Date());
        coupon.setCreator(loginUserInfo.getId());
        coupon.setEditDate(new Date());
        coupon.setEditor(loginUserInfo.getId());
        // é»˜è®¤é¡¹
        coupon.setType(Constants.ZERO);
        coupon.setCouponType(Objects.isNull(coupon.getCouponType()) ? Constants.ZERO : coupon.getCouponType());
        coupon.setGetMethod(Objects.isNull(coupon.getGetMethod()) ? Constants.ZERO : coupon.getGetMethod());
        couponMapper.insert(coupon);
        return coupon.getId();
    }
    @Override
    public void deleteById(Integer id) {
        couponMapper.update(new UpdateWrapper<Coupon>().lambda()
                .set(Coupon::getIsdeleted, Constants.ONE)
                .eq(Coupon::getId, id));
    }
    @Override
    public void deleteByIdInBatch(List<Integer> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            return;
        }
        for (Integer id : ids) {
            this.deleteById(id);
        }
    }
    @Override
    @Transactional(rollbackFor = {Exception.class, BusinessException.class})
    public void updateById(Coupon coupon) {
        if (Objects.isNull(coupon) || Objects.isNull(coupon.getId())) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST);
        }
        validateCoupon(coupon);
        LoginUserInfo loginUserInfo = (LoginUserInfo) SecurityUtils.getSubject().getPrincipal();
        coupon.setEditDate(new Date());
        coupon.setEditor(loginUserInfo.getId());
        couponMapper.updateById(coupon);
    }
    @Override
    public void updateStatus(Coupon coupon) {
        if (Objects.isNull(coupon) || Objects.isNull(coupon.getId()) || Objects.isNull(coupon.getStatus())) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST);
        }
        LoginUserInfo loginUserInfo = (LoginUserInfo) SecurityUtils.getSubject().getPrincipal();
        Coupon update = new Coupon();
        update.setId(coupon.getId());
        update.setStatus(coupon.getStatus());
        update.setEditDate(new Date());
        update.setEditor(loginUserInfo.getId());
        couponMapper.updateById(update);
    }
    @Override
    public Coupon findById(Integer id) {
        MPJLambdaWrapper<Coupon> wrapper = new MPJLambdaWrapper<>();
        wrapper.selectAll(Coupon.class)
                .select("(SELECT COUNT(*) FROM member_coupon mc WHERE mc.COUPON_ID = t.ID AND mc.ISDELETED = 0 AND mc.STATUS IN (1,2,99)) AS CLAIM_COUNT")
                .select("(SELECT COUNT(*) FROM member_coupon mc WHERE mc.COUPON_ID = t.ID AND mc.ISDELETED = 0 AND mc.STATUS = 2) AS USED_COUNT")
                .eq(Coupon::getId, id)
                .eq(Coupon::getIsdeleted, Constants.ZERO);
        Coupon coupon = couponMapper.selectJoinOne(Coupon.class, wrapper);
        if (Objects.isNull(coupon)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        return coupon;
    }
    @Override
    public List<Coupon> findList(Coupon coupon) {
        QueryWrapper<Coupon> wrapper = new QueryWrapper<>(coupon);
        return couponMapper.selectList(wrapper);
    }
    @Override
    public PageData<Coupon> findPage(PageWrap<Coupon> pageWrap) {
        IPage<Coupon> page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
        MPJLambdaWrapper<Coupon> wrapper = new MPJLambdaWrapper<>();
        Utils.MP.blankToNull(pageWrap.getModel());
        pageWrap.getModel().setIsdeleted(Constants.ZERO);
        wrapper.selectAll(Coupon.class)
                .select("u.USERNAME AS EDITOR_NAME")
                .select("(SELECT COUNT(*) FROM member_coupon mc WHERE mc.COUPON_ID = t.ID AND mc.ISDELETED = 0 AND mc.STATUS IN (1,2,99)) AS CLAIM_COUNT")
                .select("(SELECT COUNT(*) FROM member_coupon mc WHERE mc.COUPON_ID = t.ID AND mc.ISDELETED = 0 AND mc.STATUS = 2) AS USED_COUNT")
                .innerJoin("system_user u ON u.ID = t.EDITOR")
                .eq(Coupon::getIsdeleted, Constants.ZERO);
        if (pageWrap.getModel().getStatus() != null) {
            wrapper.eq(Coupon::getStatus, pageWrap.getModel().getStatus());
        }
        if (pageWrap.getModel().getName() != null) {
            wrapper.like(Coupon::getName, pageWrap.getModel().getName());
        }
        wrapper.orderByDesc(Coupon::getId);
        return PageData.from(couponMapper.selectJoinPage(page, Coupon.class, wrapper));
    }
    @Override
    public long count(Coupon coupon) {
        QueryWrapper<Coupon> wrapper = new QueryWrapper<>(coupon);
        return couponMapper.selectCount(wrapper);
    }
    @Override
    public List<Coupon> findValidList() {
        return couponMapper.selectList(new QueryWrapper<Coupon>().lambda()
                .eq(Coupon::getIsdeleted, Constants.ZERO)
                .eq(Coupon::getStatus, Constants.ZERO)
                .orderByDesc(Coupon::getId));
    }
    private void validateCoupon(Coupon coupon) {
        if (Objects.isNull(coupon) || StringUtils.isBlank(coupon.getName())) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "优惠券名称不能为空");
        }
        if (Objects.isNull(coupon.getLimitPrice()) || coupon.getLimitPrice() <= 0) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "满额必须大于0");
        }
        if (Objects.isNull(coupon.getPrice()) || coupon.getPrice() <= 0) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "优惠金额必须大于0");
        }
        if (coupon.getPrice() >= coupon.getLimitPrice()) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "优惠金额必须小于满额");
        }
        if (Objects.isNull(coupon.getPushDays()) || coupon.getPushDays() < 1) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "推送后领取有效天数必须大于等于1天");
        }
        if (Objects.isNull(coupon.getValidDays()) || coupon.getValidDays() < 1) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "领取后有效天数必须大于等于1天");
        }
        if (Objects.isNull(coupon.getStatus())) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "状态不能为空");
        }
    }
}
server/services/src/main/java/com/doumee/service/business/impl/DataBoardServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,338 @@
package com.doumee.service.business.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.doumee.core.constants.Constants;
import com.doumee.core.utils.Utils;
import com.doumee.dao.business.*;
import com.doumee.dao.business.model.*;
import com.doumee.dao.dto.DataBoardQueryDTO;
import com.doumee.dao.vo.*;
import com.doumee.service.business.DataBoardService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@Service
public class DataBoardServiceImpl implements DataBoardService {
    @Autowired
    private MemberMapper memberMapper;
    @Autowired
    private ShopInfoMapper shopInfoMapper;
    @Autowired
    private DriverInfoMapper driverInfoMapper;
    @Autowired
    private OrdersMapper ordersMapper;
    @Autowired
    private OrdersDetailMapper ordersDetailMapper;
    @Autowired
    private OtherOrdersMapper otherOrdersMapper;
    @Autowired
    private OrdersRefundMapper ordersRefundMapper;
    @Override
    public DataBoardVO overview(DataBoardQueryDTO query) {
        DataBoardVO vo = new DataBoardVO();
        // ä¼šå‘˜æ€»æ•°ï¼ˆä¸å—æ—¶é—´/门店影响)
        vo.setMemberCount(memberMapper.selectCount(new QueryWrapper<Member>().lambda()
                .eq(Member::getDeleted, Constants.ZERO)
                .eq(Member::getStatus, Constants.ZERO)));
        // é—¨åº—总数(auditStatus=3 æ­£å¼ç‰ˆæœ¬ï¼‰
        vo.setShopCount(shopInfoMapper.selectCount(new QueryWrapper<ShopInfo>().lambda()
                .eq(ShopInfo::getAuditStatus, Constants.THREE)
                .eq(ShopInfo::getVersionType, Constants.ZERO)
                .eq(ShopInfo::getDeleted, Constants.ZERO)));
        // å¸æœºæ€»æ•°ï¼ˆauditStatus=3 æ­£å¼ç‰ˆæœ¬ï¼‰
        vo.setDriverCount(driverInfoMapper.selectCount(new QueryWrapper<DriverInfo>().lambda()
                .eq(DriverInfo::getAuditStatus, Constants.THREE)
                .eq(DriverInfo::getVersionType, Constants.ZERO)
                .eq(DriverInfo::getDeleted, Constants.ZERO)));
        // å‘¨æœŸæ€»è®¢å•æ•°
        vo.setOrderCount(ordersMapper.selectCount(buildOrderQueryWrapper(query)));
        // å‘¨æœŸè¥æ”¶
        List<Orders> orders = ordersMapper.selectList(buildOrderQueryWrapper(query));
        long orderRevenue = 0L;
        for (Orders o : orders) {
            long pay = o.getPayAmount() != null ? o.getPayAmount() : 0L;
            long refund = o.getRefundAmount() != null ? o.getRefundAmount() : 0L;
            long overdue = (o.getOverdueStatus() != null && o.getOverdueStatus() == 2 && o.getOverdueAmount() != null)
                    ? o.getOverdueAmount() : 0L;
            orderRevenue += pay - refund + overdue;
        }
        // åŠ ä¸Šé€¾æœŸè´¹ç”¨è®¢å•ï¼ˆOtherOrders type=2)
        QueryWrapper<OtherOrders> otherQw = new QueryWrapper<>();
        otherQw.lambda()
                .eq(OtherOrders::getType, 2)
                .eq(OtherOrders::getPayStatus, Constants.ONE)
                .eq(OtherOrders::getDeleted, Constants.ZERO);
        if (query.getStartDate() != null) {
            otherQw.lambda().ge(OtherOrders::getPayTime, query.getStartDate());
        }
        if (query.getEndDate() != null) {
            otherQw.lambda().le(OtherOrders::getPayTime, Utils.Date.getEnd(query.getEndDate()));
        }
        List<OtherOrders> otherOrders = otherOrdersMapper.selectList(otherQw);
        long otherRevenue = otherOrders.stream()
                .mapToLong(o -> o.getPayAccount() != null ? o.getPayAccount() : 0L).sum();
        vo.setTotalRevenue(BigDecimal.valueOf(orderRevenue + otherRevenue)
                .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP));
        // è¡ŒæŽç±»åž‹å æ¯”
        List<Integer> orderIds = orders.stream().map(Orders::getId).collect(Collectors.toList());
        List<LuggageTypeItem> luggageTypeList = new ArrayList<>();
        if (!orderIds.isEmpty()) {
            List<OrdersDetail> details = ordersDetailMapper.selectList(new QueryWrapper<OrdersDetail>().lambda()
                    .in(OrdersDetail::getOrderId, orderIds)
                    .eq(OrdersDetail::getDeleted, Constants.ZERO));
            Map<String, List<OrdersDetail>> grouped = details.stream()
                    .filter(d -> StringUtils.isNotBlank(d.getLuggageName()))
                    .collect(Collectors.groupingBy(OrdersDetail::getLuggageName));
            for (Map.Entry<String, List<OrdersDetail>> entry : grouped.entrySet()) {
                LuggageTypeItem item = new LuggageTypeItem();
                item.setLuggageName(entry.getKey());
                item.setOrderCount((long) entry.getValue().stream().map(OrdersDetail::getOrderId).distinct().count());
                item.setLuggageCount((long) entry.getValue().stream()
                        .mapToInt(d -> d.getNum() != null ? d.getNum() : 0).sum());
                luggageTypeList.add(item);
            }
        }
        vo.setLuggageTypeList(luggageTypeList);
        return vo;
    }
    @Override
    public List<MemberTrendVO> memberTrend() {
        Date startDate = get30DaysAgoStart();
        List<Member> members = memberMapper.selectList(new QueryWrapper<Member>().lambda()
                .eq(Member::getDeleted, Constants.ZERO)
                .eq(Member::getStatus, Constants.ZERO)
                .ge(Member::getCreateTime, startDate));
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Map<String, Long> map = members.stream()
                .collect(Collectors.groupingBy(m -> sdf.format(m.getCreateTime()), Collectors.counting()));
        return buildDailyList(startDate, (date) -> {
            MemberTrendVO vo = new MemberTrendVO();
            vo.setDate(date);
            vo.setCount(map.getOrDefault(date, 0L));
            return vo;
        });
    }
    @Override
    public List<OrderTrendVO> orderTrend() {
        Date startDate = get30DaysAgoStart();
        List<Orders> orders = ordersMapper.selectList(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getDeleted, Constants.ZERO)
                .in(Orders::getStatus,
                                Constants.OrderStatus.waitDeposit.getKey(),
                                Constants.OrderStatus.deposited.getKey(),
                                Constants.OrderStatus.accepted.getKey(),
                                Constants.OrderStatus.delivering.getKey(),
                                Constants.OrderStatus.arrived.getKey(),
                                Constants.OrderStatus.overdue.getKey())
                .ge(Orders::getCreateTime, startDate));
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Map<String, List<Orders>> grouped = orders.stream()
                .collect(Collectors.groupingBy(o -> sdf.format(o.getCreateTime())));
        return buildDailyList(startDate, (date) -> {
            List<Orders> dayOrders = grouped.getOrDefault(date, Collections.emptyList());
            OrderTrendVO vo = new OrderTrendVO();
            vo.setDate(date);
            vo.setLocalCount(dayOrders.stream().filter(o -> Constants.equalsInteger(o.getType(), Constants.ZERO)).count());
            vo.setRemoteCount(dayOrders.stream().filter(o -> Constants.equalsInteger(o.getType(), Constants.ONE)).count());
            return vo;
        });
    }
    @Override
    public List<RevenueTrendVO> revenueTrend() {
        Date startDate = get30DaysAgoStart();
        List<Orders> orders = ordersMapper.selectList(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getDeleted, Constants.ZERO)
                .in(Orders::getStatus,
                                Constants.OrderStatus.waitDeposit.getKey(),
                                Constants.OrderStatus.deposited.getKey(),
                                Constants.OrderStatus.accepted.getKey(),
                                Constants.OrderStatus.delivering.getKey(),
                                Constants.OrderStatus.arrived.getKey(),
                                Constants.OrderStatus.overdue.getKey())
                .ge(Orders::getCreateTime, startDate));
        List<OtherOrders> otherOrders = otherOrdersMapper.selectList(new QueryWrapper<OtherOrders>().lambda()
                .eq(OtherOrders::getType, 2)
                .eq(OtherOrders::getPayStatus, Constants.ONE)
                .eq(OtherOrders::getDeleted, Constants.ZERO)
                .ge(OtherOrders::getPayTime, startDate));
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Map<String, Long> localOrderRevenue = new HashMap<>();
        Map<String, Long> remoteOrderRevenue = new HashMap<>();
        for (Orders o : orders) {
            String date = sdf.format(o.getCreateTime());
            long pay = o.getPayAmount() != null ? o.getPayAmount() : 0L;
            long refund = o.getRefundAmount() != null ? o.getRefundAmount() : 0L;
            long overdue = (o.getOverdueStatus() != null && o.getOverdueStatus() == 2 && o.getOverdueAmount() != null)
                    ? o.getOverdueAmount() : 0L;
            long rev = pay - refund + overdue;
            if (Constants.equalsInteger(o.getType(), Constants.ZERO)) {
                localOrderRevenue.merge(date, rev, Long::sum);
            } else {
                remoteOrderRevenue.merge(date, rev, Long::sum);
            }
        }
        Map<String, Long> otherRevenueMap = new HashMap<>();
        for (OtherOrders oo : otherOrders) {
            String date = sdf.format(oo.getPayTime());
            long amt = oo.getPayAccount() != null ? oo.getPayAccount() : 0L;
            otherRevenueMap.merge(date, amt, Long::sum);
        }
        return buildDailyList(startDate, (date) -> {
            RevenueTrendVO vo = new RevenueTrendVO();
            vo.setDate(date);
            long local = localOrderRevenue.getOrDefault(date, 0L) + otherRevenueMap.getOrDefault(date, 0L);
            long remote = remoteOrderRevenue.getOrDefault(date, 0L);
            vo.setLocalRevenue(BigDecimal.valueOf(local).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP));
            vo.setRemoteRevenue(BigDecimal.valueOf(remote).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP));
            return vo;
        });
    }
    @Override
    public ShopPerformanceVO shopPerformance(DataBoardQueryDTO query) {
        ShopPerformanceVO vo = new ShopPerformanceVO();
        // 1. æ€»è®¢å•æ•° + è¥æ”¶ï¼ˆstatus 1-6)
        QueryWrapper<Orders> qw = buildOrderQueryWrapper(query);
        List<Orders> orders = ordersMapper.selectList(qw);
        vo.setTotalOrderCount((long) orders.size());
        long revenue = 0L;
        for (Orders o : orders) {
            long pay = o.getPayAmount() != null ? o.getPayAmount() : 0L;
            long overdue = o.getOverdueAmount() != null ? o.getOverdueAmount() : 0L;
            long refund = o.getRefundAmount() != null ? o.getRefundAmount() : 0L;
            revenue += pay + overdue - refund;
        }
        vo.setTotalRevenue(BigDecimal.valueOf(Math.max(revenue, 0L))
                .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP));
        // 2. å·²å®Œæˆè®¢å•数(status=7,同等筛选条件)
        QueryWrapper<Orders> completedQw = new QueryWrapper<>();
        completedQw.lambda()
                .eq(Orders::getDeleted, Constants.ZERO)
                .eq(Orders::getStatus, Constants.OrderStatus.finished.getKey());
        if (query.getStartDate() != null) {
            completedQw.lambda().ge(Orders::getCreateTime, query.getStartDate());
        }
        if (query.getEndDate() != null) {
            completedQw.lambda().le(Orders::getCreateTime, Utils.Date.getEnd(query.getEndDate()));
        }
        if (query.getShopId() != null) {
            completedQw.lambda().and(w -> w.eq(Orders::getDepositShopId, query.getShopId())
                    .or().eq(Orders::getTakeShopId, query.getShopId()));
        }
        vo.setCompletedCount(ordersMapper.selectCount(completedQw));
        // 3. å·²é€€æ¬¾è®¢å•数(orders_refund status=1 çš„去重订单数)
        List<Integer> orderIds = orders.stream().map(Orders::getId).collect(Collectors.toList());
        if (!orderIds.isEmpty()) {
            QueryWrapper<OrdersRefund> refundQw = new QueryWrapper<>();
            refundQw.lambda()
                    .in(OrdersRefund::getOrderId, orderIds)
                    .eq(OrdersRefund::getStatus, Constants.ONE)
                    .eq(OrdersRefund::getDeleted, Constants.ZERO);
            List<OrdersRefund> refundRecords = ordersRefundMapper.selectList(refundQw);
            long refundedOrderCount = refundRecords.stream()
                    .map(OrdersRefund::getOrderId).distinct().count();
            vo.setRefundedCount(refundedOrderCount);
        } else {
            vo.setRefundedCount(0L);
        }
        // 4. è®¢å•完成率 & é€€æ¬¾çŽ‡ï¼ˆåˆ†æ¯ = status 1-6 + status 7)
        long totalCount = vo.getTotalOrderCount() + vo.getCompletedCount();
        if (totalCount > 0) {
            BigDecimal hundred = BigDecimal.valueOf(100);
            vo.setCompletionRate(BigDecimal.valueOf(vo.getCompletedCount())
                    .multiply(hundred).divide(BigDecimal.valueOf(totalCount), 2, RoundingMode.HALF_UP));
            vo.setRefundRate(BigDecimal.valueOf(vo.getRefundedCount())
                    .multiply(hundred).divide(BigDecimal.valueOf(totalCount), 2, RoundingMode.HALF_UP));
        } else {
            vo.setCompletionRate(BigDecimal.ZERO);
            vo.setRefundRate(BigDecimal.ZERO);
        }
        return vo;
    }
    // ========== ç§æœ‰æ–¹æ³• ==========
    private QueryWrapper<Orders> buildOrderQueryWrapper(DataBoardQueryDTO query) {
        QueryWrapper<Orders> qw = new QueryWrapper<>();
        qw.lambda()
                .eq(Orders::getDeleted, Constants.ZERO)
                .in(Orders::getStatus,
                        Constants.OrderStatus.waitDeposit.getKey(),
                        Constants.OrderStatus.deposited.getKey(),
                        Constants.OrderStatus.accepted.getKey(),
                        Constants.OrderStatus.delivering.getKey(),
                        Constants.OrderStatus.arrived.getKey(),
                        Constants.OrderStatus.overdue.getKey());
        if (query.getStartDate() != null) {
            qw.lambda().ge(Orders::getCreateTime, query.getStartDate());
        }
        if (query.getEndDate() != null) {
            qw.lambda().le(Orders::getCreateTime, Utils.Date.getEnd(query.getEndDate()));
        }
        if (query.getShopId() != null) {
            qw.lambda().and(w -> w.eq(Orders::getDepositShopId, query.getShopId())
                    .or().eq(Orders::getTakeShopId, query.getShopId()));
        }
        return qw;
    }
    private Date get30DaysAgoStart() {
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        cal.add(Calendar.DAY_OF_MONTH, -29);
        return cal.getTime();
    }
    @FunctionalInterface
    private interface DailyVOBuilder<T> {
        T build(String date);
    }
    private <T> List<T> buildDailyList(Date startDate, DailyVOBuilder<T> builder) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        List<T> result = new ArrayList<>();
        Calendar loop = Calendar.getInstance();
        loop.setTime(startDate);
        Calendar end = Calendar.getInstance();
        while (!loop.after(end)) {
            result.add(builder.build(sdf.format(loop.getTime())));
            loop.add(Calendar.DAY_OF_MONTH, 1);
        }
        return result;
    }
}
server/services/src/main/java/com/doumee/service/business/impl/DriverInfoServiceImpl.java
@@ -259,6 +259,10 @@
        if (pageWrap.getModel().getStatus() != null) {
            queryWrapper.eq(DriverInfo::getStatus, pageWrap.getModel().getStatus());
        }
        // ç‰ˆæœ¬
        if (pageWrap.getModel().getVersionType() != null) {
            queryWrapper.eq(DriverInfo::getVersionType, pageWrap.getModel().getVersionType());
        }
        // å®¡æ‰¹çŠ¶æ€
        if (pageWrap.getModel().getAuditStatus() != null) {
            queryWrapper.eq(DriverInfo::getAuditStatus, pageWrap.getModel().getAuditStatus());
@@ -300,7 +304,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);
@@ -354,7 +360,7 @@
        Date now = new Date();
        Member member;
        DriverInfo driverInfo = new DriverInfo();
        if (Objects.nonNull(existMember)) {
            // å·²æœ‰å¸æœºè´¦å·ï¼Œç›´æŽ¥ç™»å½•
            if (!Constants.equalsInteger(existMember.getStatus(), Constants.ZERO)) {
@@ -396,8 +402,6 @@
            memberMapper.insert(member);
            // åˆ›å»ºå¸æœºåŸºç¡€ä¿¡æ¯
            DriverInfo driverInfo = new DriverInfo();
            driverInfo.setId(member.getId());
            driverInfo.setDeleted(Constants.ZERO);
            driverInfo.setCreateTime(now);
            driverInfo.setUpdateTime(now);
@@ -406,13 +410,14 @@
            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);
        }
        // 3. ç”Ÿæˆtoken返回
        AccountResponse accountResponse = new AccountResponse();
        accountResponse.setToken(JwtTokenUtil.generateTokenForRedis(member.getId(), Constants.ONE, JSONObject.toJSONString(member), redisTemplate));
        accountResponse.setToken(JwtTokenUtil.generateTokenForRedis(driverInfo.getId(), Constants.ONE, JSONObject.toJSONString(member), redisTemplate));
        accountResponse.setMember(member);
        return accountResponse;
    }
@@ -436,6 +441,13 @@
        if (!Constants.equalsInteger(member.getStatus(), Constants.ZERO)) {
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "账号已停用,请联系管理员");
        }
        // èŽ·å–å¸æœºè¡¨ä¿¡æ¯
        DriverInfo driverInfo = driverInfoMapper.selectOne(new QueryWrapper<DriverInfo>().lambda().eq(DriverInfo::getMemberId, member.getId())
                .eq(DriverInfo::getDeleted, Constants.ZERO).eq(DriverInfo::getVersionType,Constants.ZERO).last("limit 1")
        );
        if(Objects.isNull(driverInfo)){
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "司机账号不存在");
        }
        // æ ¡éªŒå¯†ç 
        String encryptPwd = secure.encryptPassword(password, member.getSalt());
        if (!encryptPwd.equals(member.getPassword())) {
@@ -446,9 +458,8 @@
                .set(Member::getLoginTime, new Date())
                .setSql("login_times = login_times + 1")
                .eq(Member::getId, member.getId()));
        AccountResponse accountResponse = new AccountResponse();
        accountResponse.setToken(JwtTokenUtil.generateTokenForRedis(member.getId(), Constants.ONE, JSONObject.toJSONString(member), redisTemplate));
        accountResponse.setToken(JwtTokenUtil.generateTokenForRedis(driverInfo.getId(), Constants.ONE, JSONObject.toJSONString(member), redisTemplate));
        accountResponse.setMember(member);
        return accountResponse;
    }
@@ -457,7 +468,7 @@
    @Override
    @Transactional
    public void submitVerify(Integer memberId, DriverVerifyRequest request) {
    public void submitVerify(Integer driverId, DriverVerifyRequest request) {
        // å‚数基础校验
        if (StringUtils.isAnyBlank(request.getName(), request.getIdcard(), request.getLivePlace(),
                request.getCarCode(), request.getIdcardImg(), request.getIdcardImgBack())) {
@@ -469,19 +480,6 @@
        // èº«ä»½è¯å·æ ¼å¼æ ¡éªŒï¼ˆ18位,最后一位可为X)
        if (!request.getIdcard().matches("^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[\\dXx]$")) {
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "身份证号格式不正确");
        }
        // æŸ¥è¯¢å¸æœºä¿¡æ¯
        DriverInfo driverInfo = driverInfoMapper.selectOne(new QueryWrapper<DriverInfo>().lambda()
                .eq(DriverInfo::getMemberId, memberId)
                .eq(DriverInfo::getDeleted, Constants.ZERO)
                .last("limit 1"));
        if (Objects.isNull(driverInfo)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        // çŠ¶æ€æ ¡éªŒï¼šauditStatus=null(未提交)或auditStatus=2(审批驳回)可提交认证
        if (driverInfo.getAuditStatus() != null
                && !(Constants.equalsInteger(driverInfo.getAuditStatus(), Constants.TWO)||Constants.equalsInteger(driverInfo.getAuditStatus(), 99))) {
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "当前状态不允许提交认证");
        }
        // æ ¹æ®è½¦è¾†ç±»åž‹åˆ¤æ–­æ˜¯å¦éœ€è¦é©¾é©¶è¯
        Category category = categoryMapper.selectById(request.getCarType());
@@ -512,8 +510,66 @@
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "其他资料照片最多上传3å¼ ");
        }
        // æ›´æ–°å¸æœºä¿¡æ¯
        // æŸ¥è¯¢æ­£å¼ç‰ˆæœ¬å¸æœº
        DriverInfo official = driverInfoMapper.selectById(driverId);
        if (Objects.isNull(official) || Constants.equalsInteger(official.getDeleted(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        Integer memberId = official.getMemberId();
        Date now = new Date();
        // æŸ¥è¯¢è¯¥ä¼šå‘˜æœ€æ–°çš„变更版本
        QueryWrapper<DriverInfo> changeQw = new QueryWrapper<>();
        changeQw.lambda()
                .eq(DriverInfo::getMemberId, memberId)
                .eq(DriverInfo::getVersionType, Constants.ONE)
                .eq(DriverInfo::getDeleted, Constants.ZERO)
                .orderByDesc(DriverInfo::getCreateTime)
                .last("limit 1");
        DriverInfo changeVersion = driverInfoMapper.selectOne(changeQw);
        if (changeVersion == null) {
            // é¦–次认证:更新正式版本 + åˆ›å»ºå˜æ›´ç‰ˆæœ¬
            applyDriverFieldsFromUpdate(official.getId(), request, now);
            deleteDriverAttachments(official.getId());
            saveDriverAttachments(official.getId(), request, now);
            // é‡æ–°è¯»å–更新后的正式版本,再创建变更版本
            DriverInfo updatedOfficial = driverInfoMapper.selectById(official.getId());
            createDriverChangeVersion(updatedOfficial, official.getId(), now);
        } else if (Constants.equalsInteger(changeVersion.getAuditStatus(), Constants.THREE)) {
            // auditStatus=3:生成新的变更版本
            Integer relationId = changeVersion.getRelationDriverId() != null
                    ? changeVersion.getRelationDriverId() : official.getId();
            DriverInfo newChange = new DriverInfo();
            applyDriverFieldsFromNew(newChange, request, official, now);
            newChange.setVersionType(Constants.ONE);
            newChange.setRelationDriverId(relationId);
            newChange.setAuditStatus(Constants.ZERO);
            newChange.setStatus(Constants.ZERO);
            newChange.setDeleted(Constants.ZERO);
            newChange.setCreateTime(now);
            newChange.setUpdateTime(now);
            newChange.setMemberId(memberId);
            newChange.setTelephone(official.getTelephone());
            newChange.setJpushAlias(official.getJpushAlias());
            driverInfoMapper.insert(newChange);
            saveDriverAttachments(newChange.getId(), request, now);
        } else {
            // auditStatus=0/2:直接更新变更版本
            applyDriverFieldsFromUpdate(changeVersion.getId(), request, now);
            deleteDriverAttachments(changeVersion.getId());
            saveDriverAttachments(changeVersion.getId(), request, now);
        }
        // æ›´æ–°ä¼šå‘˜å¸æœºè®¤è¯çŠ¶æ€ä¸ºè®¤è¯ä¸­
        memberMapper.update(new UpdateWrapper<Member>().lambda()
                .set(Member::getBusinessStatus, Constants.ONE)
                .set(Member::getUpdateTime, now)
                .eq(Member::getId, memberId));
    }
    private void applyDriverFieldsFromUpdate(Integer driverId, DriverVerifyRequest request, Date now) {
        driverInfoMapper.update(new UpdateWrapper<DriverInfo>().lambda()
                .set(DriverInfo::getName, request.getName())
                .set(DriverInfo::getIdcard, request.getIdcard())
@@ -533,39 +589,129 @@
                .set(DriverInfo::getAuditStatus, Constants.ZERO)
                .set(DriverInfo::getAuditRemark, null)
                .set(DriverInfo::getAuditTime, null)
                .eq(DriverInfo::getId, driverInfo.getId()));
                .eq(DriverInfo::getId, driverId));
    }
        // åˆ é™¤æ—§çš„照片记录
        multifileMapper.delete(new QueryWrapper<Multifile>().lambda()
                .eq(Multifile::getObjId, driverInfo.getId())
                .in(Multifile::getObjType, 6, 7, 8));
    private void applyDriverFieldsFromNew(DriverInfo driver, DriverVerifyRequest request, DriverInfo official, Date now) {
        driver.setName(request.getName());
        driver.setIdcard(request.getIdcard());
        driver.setMaritalStatus(request.getMaritalStatus());
        driver.setAreaId(request.getAreaId());
        driver.setLivePlace(request.getLivePlace());
        driver.setCarCode(request.getCarCode());
        driver.setCarType(request.getCarType());
        driver.setCarColor(request.getCarColor());
        driver.setCardStartDate(request.getCardStartDate());
        driver.setCardEndDate(request.getCardEndDate());
        driver.setIdcardImg(request.getIdcardImg());
        driver.setIdcardImgBack(request.getIdcardImgBack());
        driver.setAliAccount(request.getAliAccount());
        driver.setAliName(request.getAliName());
    }
        // ä¿å­˜è½¦è¾†ç…§ç‰‡ objType=6
        saveMultifileList(driverInfo.getId(), 6, request.getCarImgUrls(), now);
        // ä¿å­˜é©¾é©¶è¯ç…§ç‰‡ objType=7
    private void saveDriverAttachments(Integer driverId, DriverVerifyRequest request, Date now) {
        saveMultifileList(driverId, 6, request.getCarImgUrls(), now);
        if (!CollectionUtils.isEmpty(request.getLicenseImgUrls())) {
            saveMultifileList(driverInfo.getId(), 7, request.getLicenseImgUrls(), now);
            saveMultifileList(driverId, 7, request.getLicenseImgUrls(), now);
        }
        // ä¿å­˜å…¶ä»–资料照片 objType=8
        if (!CollectionUtils.isEmpty(request.getOtherImgUrls())) {
            saveMultifileList(driverInfo.getId(), 8, request.getOtherImgUrls(), now);
            saveMultifileList(driverId, 8, request.getOtherImgUrls(), now);
        }
    }
        // æ›´æ–°ä¼šå‘˜å¸æœºè®¤è¯çŠ¶æ€ä¸ºè®¤è¯ä¸­
        memberMapper.update(new UpdateWrapper<Member>().lambda()
                .set(Member::getBusinessStatus, Constants.ONE)
                .set(Member::getUpdateTime, now)
                .eq(Member::getId, memberId));
    private void deleteDriverAttachments(Integer driverId) {
        multifileMapper.delete(new QueryWrapper<Multifile>().lambda()
                .eq(Multifile::getObjId, driverId)
                .in(Multifile::getObjType, 6, 7, 8));
    }
    private void createDriverChangeVersion(DriverInfo official, Integer officialId, Date now) {
        DriverInfo change = new DriverInfo();
        change.setName(official.getName());
        change.setIdcard(official.getIdcard());
        change.setMaritalStatus(official.getMaritalStatus());
        change.setAreaId(official.getAreaId());
        change.setLivePlace(official.getLivePlace());
        change.setImgurl(official.getImgurl());
        change.setCarType(official.getCarType());
        change.setCarCode(official.getCarCode());
        change.setCarColor(official.getCarColor());
        change.setCardStartDate(official.getCardStartDate());
        change.setCardEndDate(official.getCardEndDate());
        change.setIdcardImg(official.getIdcardImg());
        change.setIdcardImgBack(official.getIdcardImgBack());
        change.setAliAccount(official.getAliAccount());
        change.setAliName(official.getAliName());
        change.setVersionType(Constants.ONE);
        change.setRelationDriverId(officialId);
        change.setMemberId(official.getMemberId());
        change.setTelephone(official.getTelephone());
        change.setOpenid(official.getOpenid());
        change.setUnionid(official.getUnionid());
        change.setJpushAlias(official.getJpushAlias());
        change.setScore(official.getScore());
        change.setDriverLevel(official.getDriverLevel());
        change.setBalance(official.getBalance());
        change.setTotalBalance(official.getTotalBalance());
        change.setLongitude(official.getLongitude());
        change.setLatitude(official.getLatitude());
        change.setAuditStatus(official.getAuditStatus());
        change.setStatus(official.getStatus());
        change.setDeleted(official.getDeleted());
        change.setCreateTime(now);
        change.setUpdateTime(now);
        driverInfoMapper.insert(change);
        // æ‹·è´é™„ä»¶
        List<Multifile> originFiles = multifileMapper.selectList(new QueryWrapper<Multifile>().lambda()
                .eq(Multifile::getObjId, officialId)
                .eq(Multifile::getIsdeleted, Constants.ZERO)
                .in(Multifile::getObjType, 6, 7, 8));
        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(change.getId());
            copy.setType(f.getType());
            copy.setObjType(f.getObjType());
            copy.setFileurl(f.getFileurl());
            copy.setSortnum(f.getSortnum());
            multifileMapper.insert(copy);
        }
    }
    @Override
    public DriverInfo getVerifyDetail(Integer memberId) {
        // ä¼˜å…ˆæŸ¥è¯¢æœ€æ–°çš„变更版本
        DriverInfo driverInfo = driverInfoMapper.selectOne(new QueryWrapper<DriverInfo>().lambda()
                .eq(DriverInfo::getId, memberId)
                .eq(DriverInfo::getRelationDriverId, memberId)
                .eq(DriverInfo::getVersionType, Constants.ONE)
                .eq(DriverInfo::getDeleted, Constants.ZERO)
                .orderByDesc(DriverInfo::getCreateTime)
                .last("limit 1"));
        if (Objects.isNull(driverInfo)) {
            // æ— å˜æ›´ç‰ˆæœ¬åˆ™æŸ¥æ­£å¼ç‰ˆæœ¬
            driverInfo = driverInfoMapper.selectOne(new QueryWrapper<DriverInfo>().lambda()
                    .eq(DriverInfo::getId, memberId)
                    .eq(DriverInfo::getVersionType, Constants.ZERO)
                    .eq(DriverInfo::getDeleted, Constants.ZERO)
                    .last("limit 1"));
        }
        if (Objects.isNull(driverInfo)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        // æ ‡è®°æ˜¯å¦å­˜åœ¨å®¡æ‰¹é€šè¿‡çš„æ­£å¼ç‰ˆæœ¬
        if (driverInfo.getRelationDriverId() != null) {
            DriverInfo official = driverInfoMapper.selectById(driverInfo.getRelationDriverId());
            driverInfo.setHasApprovedOfficial(official != null
                    && Constants.equalsInteger(official.getAuditStatus(), Constants.THREE));
        } else if (Constants.equalsInteger(driverInfo.getVersionType(), Constants.ZERO)) {
            driverInfo.setHasApprovedOfficial(Constants.equalsInteger(driverInfo.getAuditStatus(), Constants.THREE));
        } else {
            driverInfo.setHasApprovedOfficial(false);
        }
        // æ‹¼æŽ¥å›¾ç‰‡å‰ç¼€
        String imgPrefix = "";
@@ -622,57 +768,142 @@
        if (Objects.isNull(auditDTO.getId()) || Objects.isNull(auditDTO.getAuditStatus())) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST);
        }
        DriverInfo driverInfo = driverInfoMapper.selectById(auditDTO.getId());
        if (Objects.isNull(driverInfo)) {
        // å®¡æ‰¹çš„æ˜¯å˜æ›´ç‰ˆæœ¬
        DriverInfo changeVersion = driverInfoMapper.selectById(auditDTO.getId());
        if (Objects.isNull(changeVersion) || Constants.equalsInteger(changeVersion.getDeleted(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        // åªæœ‰å®¡æ‰¹çŠ¶æ€ä¸º0(待审批)且已填写认证信息才能审批
        if (!Constants.equalsInteger(driverInfo.getAuditStatus(), Constants.ZERO)
                || StringUtils.isBlank(driverInfo.getIdcard())) {
        if (!Constants.equalsInteger(changeVersion.getAuditStatus(), Constants.ZERO)
                || StringUtils.isBlank(changeVersion.getIdcard())) {
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "当前状态不允许审批");
        }
        Date now = new Date();
        // å®¡æ‰¹ç»“果:auditDTO.auditStatus 0=通过→auditStatus=1,1=拒绝→auditStatus=2
        // auditDTO.auditStatus: 0=通过→3, 1=驳回→2
        Integer newAuditStatus;
        if (Constants.equalsInteger(auditDTO.getAuditStatus(), Constants.ZERO)) {
            newAuditStatus = Constants.THREE;  // å®¡æ‰¹é€šè¿‡
            // å®¡æ‰¹é€šè¿‡æ—¶å¸æœºå®šçº§ä¸ºå¿…å¡«
            newAuditStatus = Constants.THREE;
            if (auditDTO.getDriverLevel() == null || auditDTO.getDriverLevel() < 1 || auditDTO.getDriverLevel() > 5) {
                throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "审批通过时必须填写司机定级");
            }
        } else if (Constants.equalsInteger(auditDTO.getAuditStatus(), Constants.ONE)) {
            newAuditStatus = Constants.TWO;  // å®¡æ‰¹é©³å›ž
            newAuditStatus = Constants.TWO;
        } else {
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "审批状态参数错误");
        }
        // æ›´æ–°å¸æœºå®¡æ‰¹çŠ¶æ€
        driverInfoMapper.update(new UpdateWrapper<DriverInfo>().lambda()
                .set(DriverInfo::getAuditStatus, newAuditStatus)
                .set(DriverInfo::getAuditTime, now)
                .set(DriverInfo::getAuditRemark, auditDTO.getAuditRemark())
                .set(DriverInfo::getAuditUser, auditDTO.getAuditUser())
                .set(auditDTO.getDriverLevel() != null, DriverInfo::getDriverLevel, auditDTO.getDriverLevel())
                .set(DriverInfo::getUpdateTime, now)
                .eq(DriverInfo::getId, auditDTO.getId()));
        // æ›´æ–°ä¼šå‘˜å¸æœºè®¤è¯çŠ¶æ€ï¼šé€šè¿‡=2,驳回=3
        Integer driverStatus = Constants.equalsInteger(newAuditStatus, Constants.ONE) ? Constants.TWO : Constants.THREE;
        memberMapper.update(new UpdateWrapper<Member>().lambda()
                .set(Member::getBusinessStatus, driverStatus)
                .set(Member::getUpdateTime, now)
                .eq(Member::getId, driverInfo.getMemberId()));
        // çŸ­ä¿¡é€šçŸ¥
        if (Constants.equalsInteger(newAuditStatus, Constants.ONE)) {
            // å®¡æ‰¹é€šè¿‡
        // æŸ¥æ‰¾æ­£å¼ç‰ˆæœ¬
        Integer officialId = changeVersion.getRelationDriverId();
        DriverInfo official = officialId != null ? driverInfoMapper.selectById(officialId) : null;
        boolean hasApprovedOfficial = official != null
                && Constants.equalsInteger(official.getAuditStatus(), Constants.THREE);
        if (!hasApprovedOfficial) {
            // åœºæ™¯1:正式版本未审批通过(首次审批)
            driverInfoMapper.update(new UpdateWrapper<DriverInfo>().lambda()
                    .set(DriverInfo::getAuditStatus, newAuditStatus)
                    .set(DriverInfo::getAuditTime, now)
                    .set(DriverInfo::getAuditRemark, auditDTO.getAuditRemark())
                    .set(DriverInfo::getAuditUser, auditDTO.getAuditUser())
                    .set(auditDTO.getDriverLevel() != null, DriverInfo::getDriverLevel, auditDTO.getDriverLevel())
                    .set(DriverInfo::getUpdateTime, now)
                    .eq(DriverInfo::getId, changeVersion.getId()));
            // åŒæ­¥æ›´æ–°æ­£å¼ç‰ˆæœ¬ï¼ˆå®¡æ‰¹çŠ¶æ€ + æ•°æ®å­—段)
            if (official != null) {
                syncDriverChangeToOfficial(changeVersion, official, auditDTO.getDriverLevel(), now);
            }
            // æ›´æ–°ä¼šå‘˜çŠ¶æ€
            Integer driverStatus = Constants.equalsInteger(newAuditStatus, Constants.THREE) ? Constants.TWO : Constants.THREE;
            memberMapper.update(new UpdateWrapper<Member>().lambda()
                    .set(Member::getBusinessStatus, driverStatus)
                    .set(Member::getUpdateTime, now)
                    .eq(Member::getId, changeVersion.getMemberId()));
            // çŸ­ä¿¡é€šçŸ¥
            sendDriverAuditSms(changeVersion, newAuditStatus, auditDTO.getAuditRemark());
        } else {
            // åœºæ™¯2:正式版本已审批通过(变更审批)
            if (Constants.equalsInteger(newAuditStatus, Constants.THREE)) {
                // å®¡æ‰¹é€šè¿‡ï¼šå˜æ›´ç‰ˆæœ¬æ ‡è®°auditStatus=3,同步数据到正式版本
                driverInfoMapper.update(new UpdateWrapper<DriverInfo>().lambda()
                        .set(DriverInfo::getAuditStatus, Constants.THREE)
                        .set(DriverInfo::getAuditTime, now)
                        .set(DriverInfo::getAuditRemark, auditDTO.getAuditRemark())
                        .set(DriverInfo::getAuditUser, auditDTO.getAuditUser())
                        .set(DriverInfo::getDriverLevel, auditDTO.getDriverLevel())
                        .set(DriverInfo::getUpdateTime, now)
                        .eq(DriverInfo::getId, changeVersion.getId()));
                // åŒæ­¥å˜æ›´ç‰ˆæœ¬æ•°æ®åˆ°æ­£å¼ç‰ˆæœ¬
                syncDriverChangeToOfficial(changeVersion, official, auditDTO.getDriverLevel(), now);
            } else {
                // å®¡æ‰¹é©³å›žï¼šä»…标记变更版本
                driverInfoMapper.update(new UpdateWrapper<DriverInfo>().lambda()
                        .set(DriverInfo::getAuditStatus, Constants.TWO)
                        .set(DriverInfo::getAuditTime, now)
                        .set(DriverInfo::getAuditRemark, auditDTO.getAuditRemark())
                        .set(DriverInfo::getAuditUser, auditDTO.getAuditUser())
                        .set(DriverInfo::getUpdateTime, now)
                        .eq(DriverInfo::getId, changeVersion.getId()));
            }
        }
    }
    private void syncDriverChangeToOfficial(DriverInfo changeVersion, DriverInfo official, Integer driverLevel, Date now) {
        official.setName(changeVersion.getName());
        official.setIdcard(changeVersion.getIdcard());
        official.setMaritalStatus(changeVersion.getMaritalStatus());
        official.setAreaId(changeVersion.getAreaId());
        official.setLivePlace(changeVersion.getLivePlace());
        official.setCarType(changeVersion.getCarType());
        official.setCarCode(changeVersion.getCarCode());
        official.setCarColor(changeVersion.getCarColor());
        official.setCardStartDate(changeVersion.getCardStartDate());
        official.setCardEndDate(changeVersion.getCardEndDate());
        official.setIdcardImg(changeVersion.getIdcardImg());
        official.setIdcardImgBack(changeVersion.getIdcardImgBack());
        official.setAliAccount(changeVersion.getAliAccount());
        official.setAliName(changeVersion.getAliName());
        if (driverLevel != null) {
            official.setDriverLevel(driverLevel);
        }
        official.setUpdateTime(now);
        driverInfoMapper.updateById(official);
        // åŒæ­¥é™„件:先删正式版本旧附件,再从变更版本拷贝
        deleteDriverAttachments(official.getId());
        List<Multifile> changeFiles = multifileMapper.selectList(new QueryWrapper<Multifile>().lambda()
                .eq(Multifile::getObjId, changeVersion.getId())
                .eq(Multifile::getIsdeleted, Constants.ZERO)
                .in(Multifile::getObjType, 6, 7, 8));
        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 sendDriverAuditSms(DriverInfo driverInfo, Integer newAuditStatus, String auditRemark) {
        if (Constants.equalsInteger(newAuditStatus, Constants.THREE)) {
            sendSmsNotify(driverInfo.getTelephone(),
                    Constants.SmsNotify.DRIVER_AUTH_APPROVED,
                    "driver", driverInfo.getName());
        } else if (Constants.equalsInteger(newAuditStatus, Constants.TWO)) {
            // å®¡æ‰¹é©³å›ž
            sendSmsNotify(driverInfo.getTelephone(),
                    Constants.SmsNotify.DRIVER_AUTH_REJECTED,
                    "driver", driverInfo.getName(),
                    "reason", auditDTO.getAuditRemark() != null ? auditDTO.getAuditRemark() : "");
                    "reason", auditRemark != null ? auditRemark : "");
        }
    }
@@ -826,7 +1057,8 @@
            throw new BusinessException(ResponseStatus.BAD_REQUEST);
        }
        DriverInfo driver = driverInfoMapper.selectOne(new QueryWrapper<DriverInfo>().lambda()
                .eq(DriverInfo::getMemberId, memberId)
                .eq(DriverInfo::getId, memberId)
                .eq(DriverInfo::getVersionType, Constants.ZERO)
                .eq(DriverInfo::getDeleted, Constants.ZERO)
                .last("limit 1"));
        if (driver == null) {
@@ -841,12 +1073,13 @@
    }
    @Override
    public void updateLocation(Integer memberId, Double longitude, Double latitude) {
    public void updateLocation(Integer driverId, Double longitude, Double latitude) {
        if (longitude == null || latitude == null) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "经纬度不能为空");
        }
        DriverInfo driver = driverInfoMapper.selectOne(new QueryWrapper<DriverInfo>().lambda()
                .eq(DriverInfo::getMemberId, memberId)
                .eq(DriverInfo::getId, driverId)
                .eq(DriverInfo::getVersionType, Constants.ZERO)
                .eq(DriverInfo::getDeleted, Constants.ZERO)
                .last("limit 1"));
        if (driver == null) {
@@ -859,9 +1092,10 @@
    }
    @Override
    public DriverCenterVO getDriverCenterInfo(Integer memberId) {
    public DriverCenterVO getDriverCenterInfo(Integer driveId) {
        DriverInfo driver = driverInfoMapper.selectOne(new QueryWrapper<DriverInfo>().lambda()
                .eq(DriverInfo::getMemberId, memberId)
                .eq(DriverInfo::getId, driveId)
                .eq(DriverInfo::getVersionType, Constants.ZERO)
                .eq(DriverInfo::getDeleted, Constants.ZERO)
                .last("limit 1"));
        if (driver == null) {
@@ -937,6 +1171,7 @@
    public com.doumee.dao.vo.DriverStatsVO getDriverStats(Integer memberId) {
        DriverInfo driver = driverInfoMapper.selectOne(new QueryWrapper<DriverInfo>().lambda()
                .eq(DriverInfo::getId, memberId)
                .eq(DriverInfo::getVersionType, Constants.ZERO)
                .eq(DriverInfo::getDeleted, Constants.ZERO)
                .last("limit 1"));
        if (driver == null) {
@@ -987,7 +1222,8 @@
        // 1. èŽ·å–å¸æœºå®šä½
        DriverInfo driver = driverInfoMapper.selectOne(new QueryWrapper<DriverInfo>().lambda()
                .eq(DriverInfo::getMemberId, memberId)
                .eq(DriverInfo::getId, memberId)
                .eq(DriverInfo::getVersionType, Constants.ZERO)
                .eq(DriverInfo::getDeleted, Constants.ZERO)
                .last("limit 1"));
        if (driver == null || driver.getLatitude() == null || driver.getLongitude() == null) {
@@ -1111,7 +1347,8 @@
        // èŽ·å–å¸æœºä¿¡æ¯
        DriverInfo driver = driverInfoMapper.selectOne(new QueryWrapper<DriverInfo>().lambda()
                .eq(DriverInfo::getMemberId, memberId)
                .eq(DriverInfo::getId, memberId)
                .eq(DriverInfo::getVersionType, Constants.ZERO)
                .eq(DriverInfo::getDeleted, Constants.ZERO)
                .last("limit 1"));
        if (driver == null) {
@@ -1143,6 +1380,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 +2120,9 @@
        if (StringUtils.isBlank(phone)) {
            return;
        }
        if (!smsNotify.isEnabled()) {
            return;
        }
        String content = smsNotify.format(paramPairs);
        try {
            JSONObject templateParam = new JSONObject();
@@ -1996,7 +2243,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")
@@ -2094,8 +2341,12 @@
        }
        try {
            int lastIndex = token.lastIndexOf("_") + 1;
            Integer memberId = Integer.valueOf(token.substring(lastIndex));
            Member member = memberMapper.selectById(memberId);
            Integer driverId = Integer.valueOf(token.substring(lastIndex));
            DriverInfo driverInfo = driverInfoMapper.selectById(driverId);
            if(Objects.isNull(driverInfo)){
                return false;
            }
            Member member = memberMapper.selectById(driverInfo.getMemberId());
            return member != null
                    && !Constants.ONE.equals(member.getDeleted())
                    && !Constants.ONE.equals(member.getStatus())
@@ -2113,4 +2364,42 @@
        driverInfoMapper.updateById(update);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int initChangeVersions() {
        Date now = new Date();
        // 1. è¡¥å…¨ version_type ä¸º NULL çš„记录
        driverInfoMapper.update(new UpdateWrapper<DriverInfo>().lambda()
                .set(DriverInfo::getVersionType, Constants.ZERO)
                .isNull(DriverInfo::getVersionType));
        // 2. æŸ¥è¯¢æ‰€æœ‰å·²æœ‰å˜æ›´ç‰ˆæœ¬çš„ relationDriverId
        List<DriverInfo> changeVersions = driverInfoMapper.selectList(new QueryWrapper<DriverInfo>().lambda()
                .eq(DriverInfo::getVersionType, Constants.ONE)
                .eq(DriverInfo::getDeleted, Constants.ZERO)
                .ne(DriverInfo::getAuditStatus,99)
                .select(DriverInfo::getRelationDriverId)
                .isNotNull(DriverInfo::getRelationDriverId));
        Set<Integer> existingRelationIds = changeVersions.stream()
                .map(DriverInfo::getRelationDriverId)
                .collect(Collectors.toSet());
        // 3. æŸ¥è¯¢æ‰€æœ‰æ²¡æœ‰å˜æ›´ç‰ˆæœ¬çš„æ­£å¼ç‰ˆæœ¬å¸æœº
        QueryWrapper<DriverInfo> qw = new QueryWrapper<>();
        qw.lambda()
                .eq(DriverInfo::getVersionType, Constants.ZERO)
                .eq(DriverInfo::getDeleted, Constants.ZERO);
        List<DriverInfo> officialList = driverInfoMapper.selectList(qw);
        int count = 0;
        for (DriverInfo official : officialList) {
            if (existingRelationIds.contains(official.getId())) {
                continue;
            }
            createDriverChangeVersion(official, official.getId(), now);
            count++;
        }
        return count;
    }
}
server/services/src/main/java/com/doumee/service/business/impl/MemberCouponServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,149 @@
package com.doumee.service.business.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.doumee.core.constants.Constants;
import com.doumee.core.constants.ResponseStatus;
import com.doumee.core.exception.BusinessException;
import com.doumee.core.model.LoginUserInfo;
import com.doumee.core.model.PageData;
import com.doumee.core.model.PageWrap;
import com.doumee.core.utils.Utils;
import com.doumee.dao.business.MemberCouponMapper;
import com.doumee.dao.business.model.MemberCoupon;
import com.doumee.dao.system.model.SystemUser;
import com.doumee.service.business.MemberCouponService;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.util.Date;
import java.util.List;
import java.util.Objects;
@Service
public class MemberCouponServiceImpl implements MemberCouponService {
    @Autowired
    private MemberCouponMapper memberCouponMapper;
    @Override
    @Transactional(rollbackFor = {Exception.class, BusinessException.class})
    public Integer create(MemberCoupon memberCoupon) {
        if (Objects.isNull(memberCoupon) || Objects.isNull(memberCoupon.getCouponId()) || Objects.isNull(memberCoupon.getMemberId())) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST);
        }
        LoginUserInfo loginUserInfo = (LoginUserInfo) SecurityUtils.getSubject().getPrincipal();
        memberCoupon.setIsdeleted(Constants.ZERO);
        memberCoupon.setCreateDate(new Date());
        memberCoupon.setCreator(loginUserInfo.getId());
        memberCoupon.setEditDate(new Date());
        memberCoupon.setEditor(loginUserInfo.getId());
        memberCouponMapper.insert(memberCoupon);
        return memberCoupon.getId();
    }
    @Override
    public void deleteById(Integer id) {
        memberCouponMapper.update(new UpdateWrapper<MemberCoupon>().lambda()
                .set(MemberCoupon::getIsdeleted, Constants.ONE)
                .eq(MemberCoupon::getId, id));
    }
    @Override
    public void deleteByIdInBatch(List<Integer> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            return;
        }
        for (Integer id : ids) {
            this.deleteById(id);
        }
    }
    @Override
    @Transactional(rollbackFor = {Exception.class, BusinessException.class})
    public void updateById(MemberCoupon memberCoupon) {
        if (Objects.isNull(memberCoupon) || Objects.isNull(memberCoupon.getId())) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST);
        }
        LoginUserInfo loginUserInfo = (LoginUserInfo) SecurityUtils.getSubject().getPrincipal();
        memberCoupon.setEditDate(new Date());
        memberCoupon.setEditor(loginUserInfo.getId());
        memberCouponMapper.updateById(memberCoupon);
    }
    @Override
    public MemberCoupon findById(Integer id) {
        MemberCoupon memberCoupon = memberCouponMapper.selectById(id);
        if (Objects.isNull(memberCoupon)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        return memberCoupon;
    }
    @Override
    public List<MemberCoupon> findList(MemberCoupon memberCoupon) {
        QueryWrapper<MemberCoupon> wrapper = new QueryWrapper<>(memberCoupon);
        return memberCouponMapper.selectList(wrapper);
    }
    @Override
    public PageData<MemberCoupon> findPage(PageWrap<MemberCoupon> pageWrap) {
        IPage<MemberCoupon> page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
        MPJLambdaWrapper<MemberCoupon> queryWrapper = new MPJLambdaWrapper<>();
        Utils.MP.blankToNull(pageWrap.getModel());
        pageWrap.getModel().setIsdeleted(Constants.ZERO);
        queryWrapper.selectAll(MemberCoupon.class)
                .select("m.NAME AS MEMBER_NAME")
                .select("m.TELEPHONE AS MEMBER_TELEPHONE")
                .select("m.NICK_NAME AS MEMBER_NICK_NAME")
                .leftJoin("member m ON m.ID = t.MEMBER_ID");
        if (pageWrap.getModel().getId() != null) {
            queryWrapper.eq(MemberCoupon::getId, pageWrap.getModel().getId());
        }
        if (pageWrap.getModel().getIsdeleted() != null) {
            queryWrapper.eq(MemberCoupon::getIsdeleted, pageWrap.getModel().getIsdeleted());
        }
        if (pageWrap.getModel().getStatus() != null) {
            queryWrapper.eq(MemberCoupon::getStatus, pageWrap.getModel().getStatus());
        }
        if (pageWrap.getModel().getCouponId() != null) {
            queryWrapper.eq(MemberCoupon::getCouponId, pageWrap.getModel().getCouponId());
        }
        if (pageWrap.getModel().getMemberId() != null) {
            queryWrapper.eq(MemberCoupon::getMemberId, pageWrap.getModel().getMemberId());
        }
        if (pageWrap.getModel().getOrderId() != null) {
            queryWrapper.eq(MemberCoupon::getOrderId, pageWrap.getModel().getOrderId());
        }
        if (pageWrap.getModel().getType() != null) {
            queryWrapper.eq(MemberCoupon::getType, pageWrap.getModel().getType());
        }
        if (pageWrap.getModel().getCouponType() != null) {
            queryWrapper.eq(MemberCoupon::getCouponType, pageWrap.getModel().getCouponType());
        }
        if (pageWrap.getModel().getGetMethod() != null) {
            queryWrapper.eq(MemberCoupon::getGetMethod, pageWrap.getModel().getGetMethod());
        }
        if (pageWrap.getModel().getName() != null) {
            queryWrapper.like(MemberCoupon::getName, pageWrap.getModel().getName());
        }
        if (pageWrap.getModel().getCreateDate() != null) {
            queryWrapper.ge(MemberCoupon::getCreateDate, Utils.Date.getStart(pageWrap.getModel().getCreateDate()));
            queryWrapper.le(MemberCoupon::getCreateDate, Utils.Date.getEnd(pageWrap.getModel().getCreateDate()));
        }
        queryWrapper.orderByDesc(MemberCoupon::getId);
        return PageData.from(memberCouponMapper.selectJoinPage(page, MemberCoupon.class, queryWrapper));
    }
    @Override
    public long count(MemberCoupon memberCoupon) {
        QueryWrapper<MemberCoupon> wrapper = new QueryWrapper<>(memberCoupon);
        return memberCouponMapper.selectCount(wrapper);
    }
}
server/services/src/main/java/com/doumee/service/business/impl/MemberServiceImpl.java
@@ -3,6 +3,7 @@
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import com.alibaba.fastjson.JSONObject;
import com.doumee.biz.system.OperationConfigBiz;
import com.doumee.biz.system.SystemDictDataBiz;
import com.doumee.config.jwt.JwtTokenUtil;
import com.doumee.config.wx.WxMiniConfig;
@@ -13,18 +14,24 @@
import com.doumee.core.model.PageWrap;
import com.doumee.core.utils.Utils;
import com.doumee.dao.business.MemberMapper;
import com.doumee.dao.business.CouponMapper;
import com.doumee.dao.business.MemberCouponMapper;
import com.doumee.dao.business.OrdersMapper;
import com.doumee.dao.business.ShopInfoMapper;
import com.doumee.dao.business.SmsrecordMapper;
import com.doumee.dao.business.model.Member;
import com.doumee.dao.business.model.Coupon;
import com.doumee.dao.business.model.MemberCoupon;
import com.doumee.dao.business.model.Orders;
import com.doumee.dao.business.model.ShopInfo;
import com.doumee.dao.business.model.MemberRevenue;
import com.doumee.dao.business.model.Smsrecord;
import com.doumee.dao.dto.MemberListQueryDTO;
import com.doumee.dao.dto.OperationConfigDTO;
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;
@@ -39,6 +46,7 @@
import io.swagger.annotations.ApiModelProperty;
import me.chanjar.weixin.common.error.WxErrorException;
import nonapi.io.github.classgraph.json.Id;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
@@ -48,6 +56,7 @@
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
@@ -62,6 +71,7 @@
 * @author æ±Ÿè¹„蹄
 * @date 2025/07/09 12:00
 */
@Slf4j
@Service
public class MemberServiceImpl implements MemberService {
@@ -85,6 +95,15 @@
    @Autowired
    private OrdersMapper ordersMapper;
    @Autowired
    private OperationConfigBiz operationConfigBiz;
    @Autowired
    private CouponMapper couponMapper;
    @Autowired
    private MemberCouponMapper memberCouponMapper;
    @Override
@@ -353,13 +372,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 +504,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 +520,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);
    }
@@ -491,6 +544,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;
    }
@@ -642,4 +720,87 @@
        );
    }
    @Override
    public void giftRegisterCoupon() {
        // 1. è¯»å–配置
        OperationConfigDTO config = operationConfigBiz.getConfig();
        String yearsStr = config.getRegisterCouponYears();
        String maxGiftStr = config.getRegisterCouponGiftCount();
        String couponIdsStr = config.getRegisterCouponId();
        if (StringUtils.isBlank(yearsStr) || StringUtils.isBlank(maxGiftStr) || StringUtils.isBlank(couponIdsStr)) {
            return;
        }
        int configYears = Integer.parseInt(yearsStr);
        int maxGiftCount = Integer.parseInt(maxGiftStr);
        if (configYears <= 0 || maxGiftCount <= 0) {
            return;
        }
        // 2. è§£æžä¼˜æƒ åˆ¸ID列表,查询有效优惠券
        List<Integer> couponIdList = Arrays.stream(couponIdsStr.split(","))
                .map(String::trim).filter(StringUtils::isNotBlank)
                .map(Integer::parseInt).collect(Collectors.toList());
        List<Coupon> validCoupons = couponMapper.selectList(new QueryWrapper<Coupon>().lambda()
                .in(Coupon::getId, couponIdList)
                .eq(Coupon::getIsdeleted, Constants.ZERO)
                .eq(Coupon::getStatus, Constants.ZERO));
        if (CollectionUtils.isEmpty(validCoupons)) {
            return;
        }
        // 3. æŸ¥è¯¢æ‰€æœ‰æ™®é€šä¼šå‘˜
        List<Member> members = memberMapper.selectList(new QueryWrapper<Member>().lambda()
                .eq(Member::getDeleted, Constants.ZERO)
                .eq(Member::getStatus, Constants.ZERO)
                .eq(Member::getUserType, Constants.ZERO)
                .isNotNull(Member::getCreateTime));
        Date now = new Date();
        long msPerYear = 365L * 24 * 60 * 60 * 1000;
        int giftedMemberCount = 0;
        for (Member member : members) {
            int alreadyGifted = member.getRegisterCouponGiftCount() != null ? member.getRegisterCouponGiftCount() : 0;
            int registeredYears = (int) ((now.getTime() - member.getCreateTime().getTime()) / msPerYear);
            int shouldGiftTotal = registeredYears / configYears;
            int remainGift = Math.min(shouldGiftTotal, maxGiftCount) - alreadyGifted;
            if (remainGift <= 0) {
                continue;
            }
            // 4. æ¯æ¬¡èµ é€æ‰€æœ‰æœ‰æ•ˆä¼˜æƒ åˆ¸
            for (int i = 0; i < remainGift; i++) {
                for (Coupon coupon : validCoupons) {
                    MemberCoupon mc = new MemberCoupon();
                    mc.setCouponId(coupon.getId());
                    mc.setMemberId(member.getId());
                    mc.setStatus(Constants.CouponStatus.waitClaim.getKey());
                    Calendar validCal = Calendar.getInstance();
                    validCal.add(Calendar.DAY_OF_MONTH, coupon.getPushDays() != null ? coupon.getPushDays() : 7);
                    mc.setValidDate(validCal.getTime());
                    mc.setName(coupon.getName());
                    mc.setInfo(coupon.getInfo());
                    mc.setType(coupon.getType());
                    mc.setLimitPrice(coupon.getLimitPrice());
                    mc.setPrice(coupon.getPrice());
                    mc.setGetMethod(coupon.getGetMethod());
                    mc.setCouponType(coupon.getCouponType());
                    mc.setPushDays(coupon.getPushDays());
                    mc.setValidDays(coupon.getValidDays());
                    mc.setIsdeleted(Constants.ZERO);
                    mc.setCreateDate(now);
                    mc.setEditDate(now);
                    memberCouponMapper.insert(mc);
                }
            }
            // 5. æ›´æ–°ä¼šå‘˜å·²èµ é€æ¬¡æ•°
            memberMapper.update(new UpdateWrapper<Member>().lambda()
                    .set(Member::getRegisterCouponGiftCount, alreadyGifted + remainGift)
                    .eq(Member::getId, member.getId()));
            giftedMemberCount++;
        }
        log.info("注册满年赠送优惠券完成,共处理{}名会员", giftedMemberCount);
    }
}
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;
@@ -786,6 +802,7 @@
        orders.setDeleted(Constants.ZERO);
        orders.setCreateTime(now);
        orders.setUpdateTime(now);
        orders.setIsConverted(Constants.ZERO);
        // å¯„件信息
        orders.setDepositShopId(dto.getDepositShopId());
@@ -1200,7 +1217,6 @@
        com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<Orders> updateWrapper =
                new UpdateWrapper<Orders>().lambda()
                .eq(Orders::getId, order.getId())
                .set(Orders::getIsUrgent, Constants.ONE)
                .set(Orders::getPlatformRewardAmount, urgentFeeFen)
                .set(Orders::getPlatformSmsNotified, Constants.ZERO) // é‡ç½®é€šçŸ¥çŠ¶æ€ä¸ºæœªé€šçŸ¥
                .set(Orders::getPlatformSmsNotifiedTime, new Date()) // é‡ç½®é€šçŸ¥åŸºå‡†æ—¶é—´ä¸ºå½“前
@@ -1221,7 +1237,8 @@
        if (dto.getDriverId() != null) {
            // æ ¡éªŒå¸æœºä¿¡æ¯
            DriverInfo driverInfo = driverInfoMapper.selectOne(new QueryWrapper<DriverInfo>().lambda()
                    .eq(DriverInfo::getMemberId, dto.getDriverId())
                    .eq(DriverInfo::getId, dto.getDriverId())
                    .eq(DriverInfo::getVersionType, Constants.ZERO)
                    .eq(DriverInfo::getDeleted, Constants.ZERO)
                    .last("limit 1"));
            if (driverInfo == null) {
@@ -1242,13 +1259,10 @@
            updateWrapper.set(Orders::getAcceptType, 1); // 1=系统派单
            updateWrapper.set(Orders::getStatus, Constants.OrderStatus.accepted.getStatus());
            Member driver = memberMapper.selectById(dto.getDriverId());
            String driverName = driver != null ? driver.getName() : String.valueOf(dto.getDriverId());
            OrderLog driverLog = new OrderLog();
            driverLog.setOrderId(order.getId());
            driverLog.setTitle(Constants.OrderLogType.assignDriver.getTitle());
            driverLog.setLogInfo(Constants.OrderLogType.assignDriver.format(driverName, dto.getUrgentFee().toPlainString()));
            driverLog.setLogInfo(Constants.OrderLogType.assignDriver.format(driverInfo.getName(), dto.getUrgentFee().toPlainString()));
            driverLog.setObjType(Constants.OrderLogType.assignDriver.getStatus());
            driverLog.setOrderStatus(Constants.OrderStatus.accepted.getStatus());
            driverLog.setOptUserType(3);
@@ -1432,20 +1446,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 +1487,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 +1498,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 +1566,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 +1722,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<>();
@@ -2052,20 +2121,37 @@
            DriverInfo originalDriver = originalDriverId != null ? driverInfoMapper.selectById(originalDriverId) : null;
            ShopInfo depositShop = shopInfoMapper.selectById(order.getDepositShopId());
            // å–件点信息更新为存件门店,使用 UpdateWrapper ç¡®ä¿ null å­—段也能生效
            // è®¡ç®—就地寄存存件门店分成比例
            Integer cityId = Integer.valueOf(order.getCityId());
            boolean isCompany = depositShop != null && Constants.equalsInteger(depositShop.getCompanyType(), Constants.ONE);
            int fallbackFieldA = isCompany ? Constants.FIVE : Constants.SIX;
            BigDecimal localDepositRata = getShopRevenueShare(depositShop, "localDeposit", cityId, fallbackFieldA);
            Long totalAmount = order.getTotalAmount() != null ? order.getTotalAmount() : 0L;
            Long localDepositFee = new BigDecimal(totalAmount).multiply(localDepositRata)
                    .setScale(0, RoundingMode.HALF_UP).longValue();
            // å–件点信息更新为存件门店,重算分成
            UpdateWrapper<Orders> updateWrapper = new UpdateWrapper<>();
            updateWrapper.lambda()
                    .eq(Orders::getId, order.getId())
                    .set(Orders::getType, Constants.ZERO)
                    .set(Orders::getStatus, Constants.OrderStatus.arrived.getKey())
                    .set(Orders::getIsConverted, Constants.ONE)
                    .set(Orders::getTakeShopId, order.getDepositShopId())
                    .set(Orders::getTakeShopName, order.getDepositShopName())
                    .set(Orders::getTakeShopAddress, order.getDepositShopAddress())
                    .set(Orders::getTakeShopLinkPhone, order.getDepositShopLinkPhone())
                    .set(Orders::getTakeLocation, order.getDepositLocation())
                    .set(Orders::getTakeLocationRemark, order.getDepositLocationRemark())
                    .set(Orders::getTakeLat, order.getDepositLat())
                    .set(Orders::getTakeLgt, order.getDepositLgt())
                    .set(Orders::getExpectedTakeTime, new Date());
                    .set(Orders::getExpectedTakeTime, new Date())
                    // å­˜ä»¶é—¨åº—分成按就地寄存重算
                    .set(Orders::getDepositShopFeeRata, localDepositRata)
                    .set(Orders::getDepositShopFee, localDepositFee)
                    // å¸æœºã€å–件门店分成归零
                    .set(Orders::getDriverFeeRata, BigDecimal.ZERO)
                    .set(Orders::getDriverFee, 0L)
                    .set(Orders::getTakeShopFeeRata, BigDecimal.ZERO)
                    .set(Orders::getTakeShopFee, 0L);
            if (Constants.equalsInteger(status, Constants.OrderStatus.accepted.getStatus())) {
                updateWrapper.lambda()
                        .set(Orders::getAcceptDriver, null)
@@ -2210,6 +2296,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 +2532,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 +2909,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 +2950,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)) {
@@ -3442,6 +3557,7 @@
                new QueryWrapper<OrdersDetail>().lambda()
                        .eq(OrdersDetail::getOrderId, orderId)
                        .eq(OrdersDetail::getDeleted, Constants.ZERO));
        OverdueFeeVO overdueInfo = calculateOverdueFeeInternal(order, details);
        Date now = new Date();
@@ -3470,17 +3586,26 @@
                int actualDays = calcActualDepositDays(now, order.getDepositTime());
                order.setDepositDays(actualDays);
                int estimatedDays = order.getEstimatedDepositDays() != null ? order.getEstimatedDepositDays() : 1;
                int refundDays = estimatedDays - actualDays;
                if (refundDays > 0) {
                    // é€€æ¬¾é‡‘额 = é€€æ¬¾å¤©æ•° Ã— Î£(物品单价 Ã— æ•°é‡)
                    long dailyBaseFee = 0L;
                    for (OrdersDetail d : details) {
                        dailyBaseFee += (d.getUnitPrice() != null ? d.getUnitPrice() : 0L)
                                * (d.getNum() != null ? d.getNum() : 0);
                long dailyBaseFee = 0L;
                for (OrdersDetail d : details) {
                    dailyBaseFee += (d.getLocallyPrice() != null ? d.getLocallyPrice() : 0L)
                            * (d.getNum() != null ? d.getNum() : 0);
                }
                if (Constants.equalsInteger(order.getIsConverted(), Constants.ONE)) {
                    // è½¬æ¢è®¢å•:实际费用与已付金额对比
                    long actualFee = (long) actualDays * dailyBaseFee;
                    long payAmount = order.getPayAmount() != null ? order.getPayAmount() : 0L;
                    if (actualFee < payAmount) {
                        order.setRefundAmount(payAmount - actualFee);
                    }
                    long refundAmount = (long) refundDays * dailyBaseFee;
                    order.setRefundAmount(refundAmount);
                } else {
                    // æ™®é€šè®¢å•:按预计天数与实际天数差计算退款
                    int estimatedDays = order.getEstimatedDepositDays() != null ? order.getEstimatedDepositDays() : 1;
                    int refundDays = estimatedDays - actualDays;
                    if (refundDays > 0) {
                        order.setRefundAmount((long) refundDays * dailyBaseFee);
                    }
                }
            }
@@ -3534,7 +3659,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)) {
@@ -3664,6 +3794,31 @@
        if (Constants.equalsInteger(order.getType(), Constants.ZERO)) {
            // ========== å°±åœ°å¯„å­˜ ==========
            // è½¬æ¢è®¢å•(异地转就地):按寄存时间到当前时间计算实际费用
            if (Constants.equalsInteger(order.getIsConverted(), Constants.ONE)) {
                int actualDays = calcActualDepositDays(now, order.getDepositTime());
                long actualFee = (long) actualDays * dailyBaseFee;
                long payAmount = order.getPayAmount() != null ? order.getPayAmount() : 0L;
                OverdueFeeVO vo = new OverdueFeeVO();
                vo.setDailyBaseFee(dailyBaseFee);
                if (actualFee <= payAmount) {
                    // å®žé™…费用 <= å·²ä»˜é‡‘额:未逾期
                    vo.setOverdue(false);
                    vo.setOverdueDays(0);
                    vo.setOverdueFee(0L);
                } else {
                    // å®žé™…费用 > å·²ä»˜é‡‘额:逾期,差价为逾期费用
                    int overDays = actualDays - (order.getEstimatedDepositDays() != null ? order.getEstimatedDepositDays() : 1);
                    vo.setOverdue(true);
                    vo.setOverdueDays(Math.max(overDays, 1));
                    vo.setOverdueFee(actualFee - payAmount);
                }
                return vo;
            }
            // æ™®é€šå°±åœ°å¯„å­˜
            overdueDays = calcLocalOverdueDays(now, order.getExpectedTakeTime());
            overdueFee = (long) overdueDays * dailyBaseFee;
@@ -4021,7 +4176,6 @@
                            "time", String.valueOf(noGrabMinutes));
                }
                // æ ‡è®°å·²é€šçŸ¥ + è®°å½•通知时间
                order.setPlatformSmsNotified(Constants.ONE);
                order.setPlatformSmsNotifiedTime(now);
                order.setUpdateTime(now);
                ordersMapper.updateById(order);
@@ -4134,6 +4288,9 @@
        if (StringUtils.isBlank(phone)) {
            return;
        }
        if (!smsNotify.isEnabled()) {
            return;
        }
        String content = smsNotify.format(paramPairs);
        try {
            JSONObject templateParam = new JSONObject();
@@ -4200,8 +4357,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 +4430,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
@@ -194,6 +194,9 @@
        if (pageWrap.getModel().getUpdateUser() != null) {
            queryWrapper.lambda().eq(ShopInfo::getUpdateUser, pageWrap.getModel().getUpdateUser());
        }
        if (pageWrap.getModel().getVersionType() != null) {
            queryWrapper.lambda().eq(ShopInfo::getVersionType, pageWrap.getModel().getVersionType());
        }
        if (pageWrap.getModel().getUpdateTime() != null) {
            queryWrapper.lambda().ge(ShopInfo::getUpdateTime, Utils.Date.getStart(pageWrap.getModel().getUpdateTime()));
            queryWrapper.lambda().le(ShopInfo::getUpdateTime, Utils.Date.getEnd(pageWrap.getModel().getUpdateTime()));
@@ -258,10 +261,97 @@
    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();
            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(被驳回):直接更新
            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 +359,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 +372,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 +417,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 +430,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 +465,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 +620,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 +724,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 +756,87 @@
            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.setDeliveryArea(origin.getDeliveryArea());
        changeShop.setScore(origin.getScore());
        changeShop.setBalance(origin.getBalance());
        changeShop.setTotalBalance(origin.getTotalBalance());
        changeShop.setPayStatus(origin.getPayStatus());
        changeShop.setVersionType(Constants.ONE);
        changeShop.setRelationShopId(originShopId);
        changeShop.setAuditStatus(origin.getAuditStatus());
        changeShop.setRegionMemberId(origin.getRegionMemberId());
        changeShop.setOpenid(origin.getOpenid());
        changeShop.setStatus(origin.getStatus());
        changeShop.setDeleted(origin.getDeleted());
        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 +912,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 +1026,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 +1101,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 +1251,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 +1405,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 +1425,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 +1456,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 +1474,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);
@@ -1351,7 +1583,7 @@
        if (shopInfo.getCompanyType() == null) {
            return;
        }
        Areas areas = areasService.findById(shopInfo.getAreaId());
        Areas areas = areasService.getById(shopInfo.getAreaId());
        PricingRule pricingRule = pricingRuleMapper.selectOne(new QueryWrapper<PricingRule>().lambda()
                .eq(PricingRule::getDeleted, Constants.ZERO)
                .eq(PricingRule::getType, Constants.THREE)
@@ -1378,6 +1610,9 @@
     */
    private void sendSmsNotify(String phone, Constants.SmsNotify smsNotify, String... paramPairs) {
        if (StringUtils.isBlank(phone)) {
            return;
        }
        if (!smsNotify.isEnabled()) {
            return;
        }
        String content = smsNotify.format(paramPairs);
@@ -1492,4 +1727,41 @@
        log.info("解绑成功:{}", sn);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int initChangeVersions() {
        Date now = new Date();
        // 1. è¡¥å…¨ version_type ä¸º NULL çš„记录
        shopInfoMapper.update(new UpdateWrapper<ShopInfo>().lambda()
                .set(ShopInfo::getVersionType, Constants.ZERO)
                .isNull(ShopInfo::getVersionType));
        // 2. æŸ¥è¯¢æ‰€æœ‰å·²æœ‰å˜æ›´ç‰ˆæœ¬çš„ relationShopId
        List<ShopInfo> changeVersions = shopInfoMapper.selectList(new QueryWrapper<ShopInfo>().lambda()
                .eq(ShopInfo::getVersionType, Constants.ONE)
                .eq(ShopInfo::getDeleted, Constants.ZERO)
                .select(ShopInfo::getRelationShopId)
                .isNotNull(ShopInfo::getRelationShopId));
        Set<Integer> existingRelationIds = changeVersions.stream()
                .map(ShopInfo::getRelationShopId)
                .collect(Collectors.toSet());
        // 3. æŸ¥è¯¢æ‰€æœ‰æ²¡æœ‰å˜æ›´ç‰ˆæœ¬çš„æ­£å¼ç‰ˆæœ¬é—¨åº—
        QueryWrapper<ShopInfo> qw = new QueryWrapper<>();
        qw.lambda()
                .eq(ShopInfo::getVersionType, Constants.ZERO)
                .eq(ShopInfo::getDeleted, Constants.ZERO);
        List<ShopInfo> officialList = shopInfoMapper.selectList(qw);
        int count = 0;
        for (ShopInfo official : officialList) {
            if (existingRelationIds.contains(official.getId())) {
                continue;
            }
            createChangeVersion(official, official.getId(), now);
            count++;
        }
        return count;
    }
}
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;
@@ -74,6 +76,20 @@
    @Autowired
    private SystemDictDataBiz systemDictDataBiz;
    @Autowired
    private ShopInfoService shopInfoService;
    @Autowired
    private DriverInfoService driverInfoService;
    @ApiOperation("初始化历史门店/司机变更版本数据")
    @PostMapping("/initChangeVersions")
    public ApiResponse<String> initChangeVersions() {
        int shopCount = shopInfoService.initChangeVersions();
        int driverCount = driverInfoService.initChangeVersions();
        return ApiResponse.success("门店初始化 " + shopCount + " æ¡ï¼Œå¸æœºåˆå§‹åŒ– " + driverCount + " æ¡");
    }
    @ApiOperation("全部区划树形查询")
    @PostMapping("/treeList")
@@ -299,5 +315,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:
    # é»˜è®¤æ—¶åŒº