Commit c05eb941aa9d153aa82dd1231085147a1f4e49f3
1 parent
796589c3
新增骑手提现审核功能,补充提现申请/审核/打款接口及冻结余额支持
Showing
17 changed files
with
916 additions
and
2 deletions
src/main/java/com/diligrp/rider/controller/AdminRiderWithdrawController.java
0 → 100644
| 1 | +package com.diligrp.rider.controller; | ||
| 2 | + | ||
| 3 | +import com.diligrp.rider.common.result.Result; | ||
| 4 | +import com.diligrp.rider.dto.WithdrawAuditDTO; | ||
| 5 | +import com.diligrp.rider.dto.WithdrawMarkPaidDTO; | ||
| 6 | +import com.diligrp.rider.service.RiderWithdrawService; | ||
| 7 | +import com.diligrp.rider.vo.PageResultVO; | ||
| 8 | +import com.diligrp.rider.vo.RiderWithdrawApplyVO; | ||
| 9 | +import jakarta.servlet.http.HttpServletRequest; | ||
| 10 | +import jakarta.validation.Valid; | ||
| 11 | +import lombok.RequiredArgsConstructor; | ||
| 12 | +import org.springframework.web.bind.annotation.GetMapping; | ||
| 13 | +import org.springframework.web.bind.annotation.PathVariable; | ||
| 14 | +import org.springframework.web.bind.annotation.PostMapping; | ||
| 15 | +import org.springframework.web.bind.annotation.RequestBody; | ||
| 16 | +import org.springframework.web.bind.annotation.RequestMapping; | ||
| 17 | +import org.springframework.web.bind.annotation.RequestParam; | ||
| 18 | +import org.springframework.web.bind.annotation.RestController; | ||
| 19 | + | ||
| 20 | +@RestController | ||
| 21 | +@RequestMapping("/api/admin/rider/withdraw") | ||
| 22 | +@RequiredArgsConstructor | ||
| 23 | +public class AdminRiderWithdrawController { | ||
| 24 | + | ||
| 25 | + private final RiderWithdrawService withdrawService; | ||
| 26 | + | ||
| 27 | + @GetMapping("/list") | ||
| 28 | + public Result<PageResultVO<RiderWithdrawApplyVO>> list(@RequestParam(required = false) Long cityId, | ||
| 29 | + @RequestParam(required = false) Integer status, | ||
| 30 | + @RequestParam(required = false) String keyword, | ||
| 31 | + @RequestParam(required = false) String startDate, | ||
| 32 | + @RequestParam(required = false) String endDate, | ||
| 33 | + @RequestParam(defaultValue = "1") int page, | ||
| 34 | + HttpServletRequest request) { | ||
| 35 | + return Result.success(withdrawService.adminList(resolveQueryCityId(request, cityId), status, keyword, startDate, endDate, page)); | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + @GetMapping("/{id}") | ||
| 39 | + public Result<RiderWithdrawApplyVO> detail(@PathVariable Long id, HttpServletRequest request) { | ||
| 40 | + return Result.success(withdrawService.adminDetail(id, resolveScopedCityId(request))); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + @PostMapping("/{id}/approve") | ||
| 44 | + public Result<Void> approve(@PathVariable Long id, | ||
| 45 | + @RequestBody(required = false) WithdrawAuditDTO dto, | ||
| 46 | + HttpServletRequest request) { | ||
| 47 | + withdrawService.approve(id, dto, resolveScopedCityId(request), currentAdminId(request), currentRole(request)); | ||
| 48 | + return Result.success(); | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + @PostMapping("/{id}/reject") | ||
| 52 | + public Result<Void> reject(@PathVariable Long id, | ||
| 53 | + @RequestBody(required = false) WithdrawAuditDTO dto, | ||
| 54 | + HttpServletRequest request) { | ||
| 55 | + withdrawService.reject(id, dto, resolveScopedCityId(request), currentAdminId(request), currentRole(request)); | ||
| 56 | + return Result.success(); | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + @PostMapping("/{id}/mark-paid") | ||
| 60 | + public Result<Void> markPaid(@PathVariable Long id, | ||
| 61 | + @Valid @RequestBody WithdrawMarkPaidDTO dto, | ||
| 62 | + HttpServletRequest request) { | ||
| 63 | + withdrawService.markPaid(id, dto, resolveScopedCityId(request), currentAdminId(request), currentRole(request)); | ||
| 64 | + return Result.success(); | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + private Long resolveScopedCityId(HttpServletRequest request) { | ||
| 68 | + return "substation".equals(request.getAttribute("role")) ? (Long) request.getAttribute("cityId") : null; | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + private Long resolveQueryCityId(HttpServletRequest request, Long queryCityId) { | ||
| 72 | + Long scopedCityId = resolveScopedCityId(request); | ||
| 73 | + return scopedCityId != null ? scopedCityId : queryCityId; | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | + private Long currentAdminId(HttpServletRequest request) { | ||
| 77 | + return (Long) request.getAttribute("adminId"); | ||
| 78 | + } | ||
| 79 | + | ||
| 80 | + private String currentRole(HttpServletRequest request) { | ||
| 81 | + Object role = request.getAttribute("role"); | ||
| 82 | + return role == null ? "" : role.toString(); | ||
| 83 | + } | ||
| 84 | +} |
src/main/java/com/diligrp/rider/controller/RiderWithdrawController.java
0 → 100644
| 1 | +package com.diligrp.rider.controller; | ||
| 2 | + | ||
| 3 | +import com.diligrp.rider.common.result.Result; | ||
| 4 | +import com.diligrp.rider.dto.RiderWithdrawApplyDTO; | ||
| 5 | +import com.diligrp.rider.service.RiderWithdrawService; | ||
| 6 | +import com.diligrp.rider.vo.PageResultVO; | ||
| 7 | +import com.diligrp.rider.vo.RiderWithdrawApplyVO; | ||
| 8 | +import jakarta.servlet.http.HttpServletRequest; | ||
| 9 | +import jakarta.validation.Valid; | ||
| 10 | +import lombok.RequiredArgsConstructor; | ||
| 11 | +import org.springframework.web.bind.annotation.GetMapping; | ||
| 12 | +import org.springframework.web.bind.annotation.PostMapping; | ||
| 13 | +import org.springframework.web.bind.annotation.RequestBody; | ||
| 14 | +import org.springframework.web.bind.annotation.RequestMapping; | ||
| 15 | +import org.springframework.web.bind.annotation.RequestParam; | ||
| 16 | +import org.springframework.web.bind.annotation.RestController; | ||
| 17 | + | ||
| 18 | +@RestController | ||
| 19 | +@RequestMapping("/api/rider/withdraw") | ||
| 20 | +@RequiredArgsConstructor | ||
| 21 | +public class RiderWithdrawController { | ||
| 22 | + | ||
| 23 | + private final RiderWithdrawService withdrawService; | ||
| 24 | + | ||
| 25 | + @PostMapping("/apply") | ||
| 26 | + public Result<Void> apply(@Valid @RequestBody RiderWithdrawApplyDTO dto, HttpServletRequest request) { | ||
| 27 | + Long riderId = (Long) request.getAttribute("riderId"); | ||
| 28 | + withdrawService.apply(riderId, dto); | ||
| 29 | + return Result.success(); | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + @GetMapping("/list") | ||
| 33 | + public Result<PageResultVO<RiderWithdrawApplyVO>> list(@RequestParam(required = false) Integer status, | ||
| 34 | + @RequestParam(defaultValue = "1") int page, | ||
| 35 | + HttpServletRequest request) { | ||
| 36 | + Long riderId = (Long) request.getAttribute("riderId"); | ||
| 37 | + return Result.success(withdrawService.riderList(riderId, status, page)); | ||
| 38 | + } | ||
| 39 | +} |
src/main/java/com/diligrp/rider/dto/RiderWithdrawApplyDTO.java
0 → 100644
| 1 | +package com.diligrp.rider.dto; | ||
| 2 | + | ||
| 3 | +import jakarta.validation.constraints.DecimalMin; | ||
| 4 | +import jakarta.validation.constraints.NotBlank; | ||
| 5 | +import jakarta.validation.constraints.NotNull; | ||
| 6 | +import lombok.Data; | ||
| 7 | + | ||
| 8 | +import java.math.BigDecimal; | ||
| 9 | + | ||
| 10 | +@Data | ||
| 11 | +public class RiderWithdrawApplyDTO { | ||
| 12 | + | ||
| 13 | + @NotNull(message = "提现金额不能为空") | ||
| 14 | + @DecimalMin(value = "0.01", message = "提现金额必须大于0") | ||
| 15 | + private BigDecimal amount; | ||
| 16 | + | ||
| 17 | + /** 收款账户类型:1=银行卡 2=支付宝 3=微信 */ | ||
| 18 | + @NotNull(message = "收款账户类型不能为空") | ||
| 19 | + private Integer accountType; | ||
| 20 | + | ||
| 21 | + @NotBlank(message = "收款人不能为空") | ||
| 22 | + private String accountName; | ||
| 23 | + | ||
| 24 | + private String bankName; | ||
| 25 | + | ||
| 26 | + private String bankBranch; | ||
| 27 | + | ||
| 28 | + @NotBlank(message = "收款账号不能为空") | ||
| 29 | + private String accountNo; | ||
| 30 | + | ||
| 31 | + private String applyRemark; | ||
| 32 | +} |
src/main/java/com/diligrp/rider/dto/WithdrawAuditDTO.java
0 → 100644
src/main/java/com/diligrp/rider/dto/WithdrawMarkPaidDTO.java
0 → 100644
src/main/java/com/diligrp/rider/entity/Rider.java
| @@ -56,6 +56,9 @@ public class Rider { | @@ -56,6 +56,9 @@ public class Rider { | ||
| 56 | /** 余额(兼职骑手用) */ | 56 | /** 余额(兼职骑手用) */ |
| 57 | private BigDecimal balance; | 57 | private BigDecimal balance; |
| 58 | 58 | ||
| 59 | + /** 冻结余额(提现审核中) */ | ||
| 60 | + private BigDecimal frozenBalance; | ||
| 61 | + | ||
| 59 | /** 是否休息:0=否 1=是 */ | 62 | /** 是否休息:0=否 1=是 */ |
| 60 | private Integer isRest; | 63 | private Integer isRest; |
| 61 | 64 |
src/main/java/com/diligrp/rider/entity/RiderWithdrawApply.java
0 → 100644
| 1 | +package com.diligrp.rider.entity; | ||
| 2 | + | ||
| 3 | +import com.baomidou.mybatisplus.annotation.IdType; | ||
| 4 | +import com.baomidou.mybatisplus.annotation.TableId; | ||
| 5 | +import com.baomidou.mybatisplus.annotation.TableName; | ||
| 6 | +import lombok.Data; | ||
| 7 | + | ||
| 8 | +import java.math.BigDecimal; | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * 骑手提现申请表 | ||
| 12 | + */ | ||
| 13 | +@Data | ||
| 14 | +@TableName("rider_withdraw_apply") | ||
| 15 | +public class RiderWithdrawApply { | ||
| 16 | + | ||
| 17 | + @TableId(type = IdType.AUTO) | ||
| 18 | + private Long id; | ||
| 19 | + | ||
| 20 | + /** 提现单号 */ | ||
| 21 | + private String withdrawNo; | ||
| 22 | + | ||
| 23 | + /** 骑手ID */ | ||
| 24 | + private Long riderId; | ||
| 25 | + | ||
| 26 | + /** 城市ID */ | ||
| 27 | + private Long cityId; | ||
| 28 | + | ||
| 29 | + /** 提现金额 */ | ||
| 30 | + private BigDecimal amount; | ||
| 31 | + | ||
| 32 | + /** 状态:0=待审核 1=审核通过待打款 2=审核拒绝 3=已打款 4=打款失败 */ | ||
| 33 | + private Integer status; | ||
| 34 | + | ||
| 35 | + /** 收款账户类型:1=银行卡 2=支付宝 3=微信 */ | ||
| 36 | + private Integer accountType; | ||
| 37 | + | ||
| 38 | + /** 收款人 */ | ||
| 39 | + private String accountName; | ||
| 40 | + | ||
| 41 | + /** 开户行 */ | ||
| 42 | + private String bankName; | ||
| 43 | + | ||
| 44 | + /** 开户支行 */ | ||
| 45 | + private String bankBranch; | ||
| 46 | + | ||
| 47 | + /** 收款账号 */ | ||
| 48 | + private String accountNo; | ||
| 49 | + | ||
| 50 | + /** 申请备注 */ | ||
| 51 | + private String applyRemark; | ||
| 52 | + | ||
| 53 | + /** 审核/打款备注 */ | ||
| 54 | + private String auditRemark; | ||
| 55 | + | ||
| 56 | + /** 审核人ID */ | ||
| 57 | + private Long auditorId; | ||
| 58 | + | ||
| 59 | + /** 审核人名称 */ | ||
| 60 | + private String auditorName; | ||
| 61 | + | ||
| 62 | + /** 申请时间 */ | ||
| 63 | + private Long applyTime; | ||
| 64 | + | ||
| 65 | + /** 审核时间 */ | ||
| 66 | + private Long auditTime; | ||
| 67 | + | ||
| 68 | + /** 打款时间 */ | ||
| 69 | + private Long payTime; | ||
| 70 | + | ||
| 71 | + /** 打款流水号 */ | ||
| 72 | + private String transferNo; | ||
| 73 | + | ||
| 74 | + private Long createTime; | ||
| 75 | + | ||
| 76 | + private Long updateTime; | ||
| 77 | +} |
src/main/java/com/diligrp/rider/mapper/RiderWithdrawApplyMapper.java
0 → 100644
| 1 | +package com.diligrp.rider.mapper; | ||
| 2 | + | ||
| 3 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | ||
| 4 | +import com.diligrp.rider.entity.Rider; | ||
| 5 | +import com.diligrp.rider.entity.RiderWithdrawApply; | ||
| 6 | +import com.diligrp.rider.vo.RiderWithdrawApplyVO; | ||
| 7 | +import org.apache.ibatis.annotations.Mapper; | ||
| 8 | +import org.apache.ibatis.annotations.Param; | ||
| 9 | + | ||
| 10 | +import java.util.List; | ||
| 11 | + | ||
| 12 | +@Mapper | ||
| 13 | +public interface RiderWithdrawApplyMapper extends BaseMapper<RiderWithdrawApply> { | ||
| 14 | + Rider selectRiderForUpdate(@Param("riderId") Long riderId); | ||
| 15 | + | ||
| 16 | + RiderWithdrawApply selectApplyForUpdate(@Param("id") Long id); | ||
| 17 | + | ||
| 18 | + long countRiderList(@Param("riderId") Long riderId, | ||
| 19 | + @Param("status") Integer status); | ||
| 20 | + | ||
| 21 | + List<RiderWithdrawApplyVO> selectRiderList(@Param("riderId") Long riderId, | ||
| 22 | + @Param("status") Integer status, | ||
| 23 | + @Param("offset") int offset, | ||
| 24 | + @Param("pageSize") int pageSize); | ||
| 25 | + | ||
| 26 | + long countAdminList(@Param("cityId") Long cityId, | ||
| 27 | + @Param("status") Integer status, | ||
| 28 | + @Param("keyword") String keyword, | ||
| 29 | + @Param("startTime") Long startTime, | ||
| 30 | + @Param("endTime") Long endTime); | ||
| 31 | + | ||
| 32 | + List<RiderWithdrawApplyVO> selectAdminList(@Param("cityId") Long cityId, | ||
| 33 | + @Param("status") Integer status, | ||
| 34 | + @Param("keyword") String keyword, | ||
| 35 | + @Param("startTime") Long startTime, | ||
| 36 | + @Param("endTime") Long endTime, | ||
| 37 | + @Param("offset") int offset, | ||
| 38 | + @Param("pageSize") int pageSize); | ||
| 39 | + | ||
| 40 | + RiderWithdrawApplyVO selectAdminDetail(@Param("id") Long id, | ||
| 41 | + @Param("cityId") Long cityId); | ||
| 42 | +} |
src/main/java/com/diligrp/rider/service/RiderWithdrawService.java
0 → 100644
| 1 | +package com.diligrp.rider.service; | ||
| 2 | + | ||
| 3 | +import com.diligrp.rider.dto.RiderWithdrawApplyDTO; | ||
| 4 | +import com.diligrp.rider.dto.WithdrawAuditDTO; | ||
| 5 | +import com.diligrp.rider.dto.WithdrawMarkPaidDTO; | ||
| 6 | +import com.diligrp.rider.vo.PageResultVO; | ||
| 7 | +import com.diligrp.rider.vo.RiderWithdrawApplyVO; | ||
| 8 | + | ||
| 9 | +public interface RiderWithdrawService { | ||
| 10 | + void apply(Long riderId, RiderWithdrawApplyDTO dto); | ||
| 11 | + | ||
| 12 | + PageResultVO<RiderWithdrawApplyVO> riderList(Long riderId, Integer status, int page); | ||
| 13 | + | ||
| 14 | + PageResultVO<RiderWithdrawApplyVO> adminList(Long cityId, Integer status, String keyword, String startDate, String endDate, int page); | ||
| 15 | + | ||
| 16 | + RiderWithdrawApplyVO adminDetail(Long id, Long cityId); | ||
| 17 | + | ||
| 18 | + void approve(Long id, WithdrawAuditDTO dto, Long cityId, Long adminId, String adminRole); | ||
| 19 | + | ||
| 20 | + void reject(Long id, WithdrawAuditDTO dto, Long cityId, Long adminId, String adminRole); | ||
| 21 | + | ||
| 22 | + void markPaid(Long id, WithdrawMarkPaidDTO dto, Long cityId, Long adminId, String adminRole); | ||
| 23 | +} |
src/main/java/com/diligrp/rider/service/impl/AdminRiderServiceImpl.java
| @@ -65,6 +65,7 @@ public class AdminRiderServiceImpl implements AdminRiderService { | @@ -65,6 +65,7 @@ public class AdminRiderServiceImpl implements AdminRiderService { | ||
| 65 | rider.setType(1); | 65 | rider.setType(1); |
| 66 | rider.setIsRest(0); | 66 | rider.setIsRest(0); |
| 67 | rider.setBalance(BigDecimal.ZERO); | 67 | rider.setBalance(BigDecimal.ZERO); |
| 68 | + rider.setFrozenBalance(BigDecimal.ZERO); | ||
| 68 | rider.setUserLogin("phone_" + System.currentTimeMillis()); | 69 | rider.setUserLogin("phone_" + System.currentTimeMillis()); |
| 69 | rider.setCreateTime(System.currentTimeMillis() / 1000); | 70 | rider.setCreateTime(System.currentTimeMillis() / 1000); |
| 70 | riderMapper.insert(rider); | 71 | riderMapper.insert(rider); |
| @@ -163,6 +164,9 @@ public class AdminRiderServiceImpl implements AdminRiderService { | @@ -163,6 +164,9 @@ public class AdminRiderServiceImpl implements AdminRiderService { | ||
| 163 | if (rider.getBalance() != null && rider.getBalance().compareTo(BigDecimal.ZERO) > 0) { | 164 | if (rider.getBalance() != null && rider.getBalance().compareTo(BigDecimal.ZERO) > 0) { |
| 164 | throw new BizException("变更为全职前要保证余额为0"); | 165 | throw new BizException("变更为全职前要保证余额为0"); |
| 165 | } | 166 | } |
| 167 | + if (rider.getFrozenBalance() != null && rider.getFrozenBalance().compareTo(BigDecimal.ZERO) > 0) { | ||
| 168 | + throw new BizException("变更为全职前要保证无提现冻结余额"); | ||
| 169 | + } | ||
| 166 | } | 170 | } |
| 167 | LambdaUpdateWrapper<Rider> wrapper = new LambdaUpdateWrapper<Rider>() | 171 | LambdaUpdateWrapper<Rider> wrapper = new LambdaUpdateWrapper<Rider>() |
| 168 | .eq(Rider::getId, riderId) | 172 | .eq(Rider::getId, riderId) |
src/main/java/com/diligrp/rider/service/impl/MenuBootstrapServiceImpl.java
| @@ -67,6 +67,7 @@ public class MenuBootstrapServiceImpl implements MenuBootstrapService { | @@ -67,6 +67,7 @@ public class MenuBootstrapServiceImpl implements MenuBootstrapService { | ||
| 67 | defaults.add(menu("merchant.fund", "资金对账", "MENU", "/merchant/fund", "", 0L, MenuScopeEnum.BOTH, 43)); | 67 | defaults.add(menu("merchant.fund", "资金对账", "MENU", "/merchant/fund", "", 0L, MenuScopeEnum.BOTH, 43)); |
| 68 | defaults.add(menu("rider.list", "骑手管理", "MENU", "/rider", "UserOutlined", 0L, MenuScopeEnum.BOTH, 50)); | 68 | defaults.add(menu("rider.list", "骑手管理", "MENU", "/rider", "UserOutlined", 0L, MenuScopeEnum.BOTH, 50)); |
| 69 | defaults.add(menu("rider.level", "骑手等级", "MENU", "/rider/level", "TrophyOutlined", 0L, MenuScopeEnum.BOTH, 55)); | 69 | defaults.add(menu("rider.level", "骑手等级", "MENU", "/rider/level", "TrophyOutlined", 0L, MenuScopeEnum.BOTH, 55)); |
| 70 | + defaults.add(menu("rider.withdraw", "骑手提现审核", "MENU", "/rider/withdraw", "", 0L, MenuScopeEnum.BOTH, 56)); | ||
| 70 | defaults.add(menu("rider.evaluate", "骑手评价", "MENU", "/rider/evaluate", "StarOutlined", 0L, MenuScopeEnum.BOTH, 60)); | 71 | defaults.add(menu("rider.evaluate", "骑手评价", "MENU", "/rider/evaluate", "StarOutlined", 0L, MenuScopeEnum.BOTH, 60)); |
| 71 | defaults.add(menu("order.root", "订单管理", "DIR", "", "UnorderedListOutlined", 0L, MenuScopeEnum.BOTH, 70)); | 72 | defaults.add(menu("order.root", "订单管理", "DIR", "", "UnorderedListOutlined", 0L, MenuScopeEnum.BOTH, 70)); |
| 72 | defaults.add(menu("order.list", "订单列表", "MENU", "/order", "", 0L, MenuScopeEnum.BOTH, 71)); | 73 | defaults.add(menu("order.list", "订单列表", "MENU", "/order", "", 0L, MenuScopeEnum.BOTH, 71)); |
src/main/java/com/diligrp/rider/service/impl/RiderWithdrawServiceImpl.java
0 → 100644
| 1 | +package com.diligrp.rider.service.impl; | ||
| 2 | + | ||
| 3 | +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | ||
| 4 | +import com.diligrp.rider.common.exception.BizException; | ||
| 5 | +import com.diligrp.rider.dto.RiderWithdrawApplyDTO; | ||
| 6 | +import com.diligrp.rider.dto.WithdrawAuditDTO; | ||
| 7 | +import com.diligrp.rider.dto.WithdrawMarkPaidDTO; | ||
| 8 | +import com.diligrp.rider.entity.Rider; | ||
| 9 | +import com.diligrp.rider.entity.RiderBalance; | ||
| 10 | +import com.diligrp.rider.entity.RiderWithdrawApply; | ||
| 11 | +import com.diligrp.rider.mapper.RiderBalanceMapper; | ||
| 12 | +import com.diligrp.rider.mapper.RiderMapper; | ||
| 13 | +import com.diligrp.rider.mapper.RiderWithdrawApplyMapper; | ||
| 14 | +import com.diligrp.rider.service.RiderWithdrawService; | ||
| 15 | +import com.diligrp.rider.vo.PageResultVO; | ||
| 16 | +import com.diligrp.rider.vo.RiderWithdrawApplyVO; | ||
| 17 | +import lombok.RequiredArgsConstructor; | ||
| 18 | +import org.springframework.stereotype.Service; | ||
| 19 | +import org.springframework.transaction.annotation.Transactional; | ||
| 20 | + | ||
| 21 | +import java.math.BigDecimal; | ||
| 22 | +import java.time.LocalDate; | ||
| 23 | +import java.time.ZoneId; | ||
| 24 | +import java.time.format.DateTimeParseException; | ||
| 25 | +import java.util.List; | ||
| 26 | + | ||
| 27 | +@Service | ||
| 28 | +@RequiredArgsConstructor | ||
| 29 | +public class RiderWithdrawServiceImpl implements RiderWithdrawService { | ||
| 30 | + | ||
| 31 | + private static final int PAGE_SIZE = 20; | ||
| 32 | + private static final int STATUS_PENDING = 0; | ||
| 33 | + private static final int STATUS_APPROVED = 1; | ||
| 34 | + private static final int STATUS_REJECTED = 2; | ||
| 35 | + private static final int STATUS_PAID = 3; | ||
| 36 | + private static final ZoneId ZONE_ID = ZoneId.of("Asia/Shanghai"); | ||
| 37 | + | ||
| 38 | + private final RiderWithdrawApplyMapper withdrawMapper; | ||
| 39 | + private final RiderMapper riderMapper; | ||
| 40 | + private final RiderBalanceMapper balanceMapper; | ||
| 41 | + | ||
| 42 | + @Override | ||
| 43 | + @Transactional | ||
| 44 | + public void apply(Long riderId, RiderWithdrawApplyDTO dto) { | ||
| 45 | + if (riderId == null || riderId < 1) { | ||
| 46 | + throw new BizException("骑手身份无效"); | ||
| 47 | + } | ||
| 48 | + validateApply(dto); | ||
| 49 | + | ||
| 50 | + Rider rider = withdrawMapper.selectRiderForUpdate(riderId); | ||
| 51 | + if (rider == null) { | ||
| 52 | + throw new BizException("骑手信息不存在"); | ||
| 53 | + } | ||
| 54 | + BigDecimal amount = dto.getAmount().setScale(2, java.math.RoundingMode.HALF_UP); | ||
| 55 | + BigDecimal balance = money(rider.getBalance()); | ||
| 56 | + BigDecimal frozenBalance = money(rider.getFrozenBalance()); | ||
| 57 | + if (balance.compareTo(amount) < 0) { | ||
| 58 | + throw new BizException("可提现余额不足"); | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + long now = now(); | ||
| 62 | + RiderWithdrawApply apply = new RiderWithdrawApply(); | ||
| 63 | + apply.setWithdrawNo(buildWithdrawNo(riderId)); | ||
| 64 | + apply.setRiderId(riderId); | ||
| 65 | + apply.setCityId(rider.getCityId()); | ||
| 66 | + apply.setAmount(amount); | ||
| 67 | + apply.setStatus(STATUS_PENDING); | ||
| 68 | + apply.setAccountType(dto.getAccountType()); | ||
| 69 | + apply.setAccountName(trim(dto.getAccountName())); | ||
| 70 | + apply.setBankName(trim(dto.getBankName())); | ||
| 71 | + apply.setBankBranch(trim(dto.getBankBranch())); | ||
| 72 | + apply.setAccountNo(trim(dto.getAccountNo())); | ||
| 73 | + apply.setApplyRemark(trim(dto.getApplyRemark())); | ||
| 74 | + apply.setAuditRemark(""); | ||
| 75 | + apply.setAuditorId(0L); | ||
| 76 | + apply.setAuditorName(""); | ||
| 77 | + apply.setApplyTime(now); | ||
| 78 | + apply.setAuditTime(0L); | ||
| 79 | + apply.setPayTime(0L); | ||
| 80 | + apply.setTransferNo(""); | ||
| 81 | + apply.setCreateTime(now); | ||
| 82 | + apply.setUpdateTime(now); | ||
| 83 | + withdrawMapper.insert(apply); | ||
| 84 | + | ||
| 85 | + updateRiderMoney(riderId, balance.subtract(amount), frozenBalance.add(amount)); | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + @Override | ||
| 89 | + public PageResultVO<RiderWithdrawApplyVO> riderList(Long riderId, Integer status, int page) { | ||
| 90 | + int currentPage = normalizePage(page); | ||
| 91 | + int offset = (currentPage - 1) * PAGE_SIZE; | ||
| 92 | + PageResultVO<RiderWithdrawApplyVO> result = new PageResultVO<>(); | ||
| 93 | + result.setList(withdrawMapper.selectRiderList(riderId, normalizeStatus(status), offset, PAGE_SIZE)); | ||
| 94 | + result.setPage(currentPage); | ||
| 95 | + result.setPageSize(PAGE_SIZE); | ||
| 96 | + result.setTotal(withdrawMapper.countRiderList(riderId, normalizeStatus(status))); | ||
| 97 | + return result; | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + @Override | ||
| 101 | + public PageResultVO<RiderWithdrawApplyVO> adminList(Long cityId, Integer status, String keyword, String startDate, String endDate, int page) { | ||
| 102 | + int currentPage = normalizePage(page); | ||
| 103 | + int offset = (currentPage - 1) * PAGE_SIZE; | ||
| 104 | + Long startTime = parseStartTime(startDate); | ||
| 105 | + Long endTime = parseEndTime(endDate); | ||
| 106 | + PageResultVO<RiderWithdrawApplyVO> result = new PageResultVO<>(); | ||
| 107 | + result.setList(withdrawMapper.selectAdminList(cityId, normalizeStatus(status), normalizeKeyword(keyword), startTime, endTime, offset, PAGE_SIZE)); | ||
| 108 | + result.setPage(currentPage); | ||
| 109 | + result.setPageSize(PAGE_SIZE); | ||
| 110 | + result.setTotal(withdrawMapper.countAdminList(cityId, normalizeStatus(status), normalizeKeyword(keyword), startTime, endTime)); | ||
| 111 | + return result; | ||
| 112 | + } | ||
| 113 | + | ||
| 114 | + @Override | ||
| 115 | + public RiderWithdrawApplyVO adminDetail(Long id, Long cityId) { | ||
| 116 | + if (id == null || id < 1) { | ||
| 117 | + throw new BizException("提现申请ID不能为空"); | ||
| 118 | + } | ||
| 119 | + RiderWithdrawApplyVO detail = withdrawMapper.selectAdminDetail(id, cityId); | ||
| 120 | + if (detail == null) { | ||
| 121 | + throw new BizException("提现申请不存在"); | ||
| 122 | + } | ||
| 123 | + return detail; | ||
| 124 | + } | ||
| 125 | + | ||
| 126 | + @Override | ||
| 127 | + @Transactional | ||
| 128 | + public void approve(Long id, WithdrawAuditDTO dto, Long cityId, Long adminId, String adminRole) { | ||
| 129 | + RiderWithdrawApply apply = lockApply(id, cityId); | ||
| 130 | + if (apply.getStatus() == null || apply.getStatus() != STATUS_PENDING) { | ||
| 131 | + throw new BizException("只有待审核申请可以审核通过"); | ||
| 132 | + } | ||
| 133 | + Rider rider = lockRider(apply.getRiderId()); | ||
| 134 | + ensureFrozenEnough(rider, apply.getAmount()); | ||
| 135 | + long now = now(); | ||
| 136 | + apply.setStatus(STATUS_APPROVED); | ||
| 137 | + apply.setAuditRemark(trim(dto == null ? null : dto.getRemark())); | ||
| 138 | + apply.setAuditorId(adminId == null ? 0L : adminId); | ||
| 139 | + apply.setAuditorName(buildAdminName(adminId, adminRole)); | ||
| 140 | + apply.setAuditTime(now); | ||
| 141 | + apply.setUpdateTime(now); | ||
| 142 | + withdrawMapper.updateById(apply); | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + @Override | ||
| 146 | + @Transactional | ||
| 147 | + public void reject(Long id, WithdrawAuditDTO dto, Long cityId, Long adminId, String adminRole) { | ||
| 148 | + RiderWithdrawApply apply = lockApply(id, cityId); | ||
| 149 | + if (apply.getStatus() == null || apply.getStatus() != STATUS_PENDING) { | ||
| 150 | + throw new BizException("只有待审核申请可以拒绝"); | ||
| 151 | + } | ||
| 152 | + String remark = trim(dto == null ? null : dto.getRemark()); | ||
| 153 | + if (remark.isBlank()) { | ||
| 154 | + throw new BizException("拒绝原因不能为空"); | ||
| 155 | + } | ||
| 156 | + Rider rider = lockRider(apply.getRiderId()); | ||
| 157 | + BigDecimal amount = money(apply.getAmount()); | ||
| 158 | + ensureFrozenEnough(rider, amount); | ||
| 159 | + updateRiderMoney(rider.getId(), money(rider.getBalance()).add(amount), money(rider.getFrozenBalance()).subtract(amount)); | ||
| 160 | + | ||
| 161 | + long now = now(); | ||
| 162 | + apply.setStatus(STATUS_REJECTED); | ||
| 163 | + apply.setAuditRemark(remark); | ||
| 164 | + apply.setAuditorId(adminId == null ? 0L : adminId); | ||
| 165 | + apply.setAuditorName(buildAdminName(adminId, adminRole)); | ||
| 166 | + apply.setAuditTime(now); | ||
| 167 | + apply.setUpdateTime(now); | ||
| 168 | + withdrawMapper.updateById(apply); | ||
| 169 | + } | ||
| 170 | + | ||
| 171 | + @Override | ||
| 172 | + @Transactional | ||
| 173 | + public void markPaid(Long id, WithdrawMarkPaidDTO dto, Long cityId, Long adminId, String adminRole) { | ||
| 174 | + if (dto == null || trim(dto.getTransferNo()).isBlank()) { | ||
| 175 | + throw new BizException("打款流水号不能为空"); | ||
| 176 | + } | ||
| 177 | + RiderWithdrawApply apply = lockApply(id, cityId); | ||
| 178 | + if (apply.getStatus() == null || apply.getStatus() != STATUS_APPROVED) { | ||
| 179 | + throw new BizException("只有审核通过待打款申请可以标记已打款"); | ||
| 180 | + } | ||
| 181 | + Rider rider = lockRider(apply.getRiderId()); | ||
| 182 | + BigDecimal amount = money(apply.getAmount()); | ||
| 183 | + ensureFrozenEnough(rider, amount); | ||
| 184 | + BigDecimal nextFrozenBalance = money(rider.getFrozenBalance()).subtract(amount); | ||
| 185 | + BigDecimal currentBalance = money(rider.getBalance()); | ||
| 186 | + updateRiderMoney(rider.getId(), currentBalance, nextFrozenBalance); | ||
| 187 | + | ||
| 188 | + long now = now(); | ||
| 189 | + RiderBalance record = new RiderBalance(); | ||
| 190 | + record.setUid(rider.getId()); | ||
| 191 | + record.setType(2); | ||
| 192 | + record.setAction("withdraw_paid"); | ||
| 193 | + record.setActionId(apply.getId()); | ||
| 194 | + record.setOrderNo(apply.getWithdrawNo()); | ||
| 195 | + record.setNums(amount.negate()); | ||
| 196 | + record.setTotal(currentBalance); | ||
| 197 | + record.setAddTime(now); | ||
| 198 | + balanceMapper.insert(record); | ||
| 199 | + | ||
| 200 | + String remark = trim(dto.getRemark()); | ||
| 201 | + apply.setStatus(STATUS_PAID); | ||
| 202 | + apply.setAuditRemark(remark.isBlank() ? apply.getAuditRemark() : remark); | ||
| 203 | + apply.setAuditorId(adminId == null ? apply.getAuditorId() : adminId); | ||
| 204 | + apply.setAuditorName(buildAdminName(adminId, adminRole)); | ||
| 205 | + apply.setPayTime(now); | ||
| 206 | + apply.setTransferNo(trim(dto.getTransferNo())); | ||
| 207 | + apply.setUpdateTime(now); | ||
| 208 | + withdrawMapper.updateById(apply); | ||
| 209 | + } | ||
| 210 | + | ||
| 211 | + private void validateApply(RiderWithdrawApplyDTO dto) { | ||
| 212 | + if (dto == null) { | ||
| 213 | + throw new BizException("提现申请不能为空"); | ||
| 214 | + } | ||
| 215 | + if (dto.getAccountType() == null || dto.getAccountType() < 1 || dto.getAccountType() > 3) { | ||
| 216 | + throw new BizException("收款账户类型不正确"); | ||
| 217 | + } | ||
| 218 | + if (dto.getAmount() == null || dto.getAmount().compareTo(BigDecimal.ZERO) <= 0) { | ||
| 219 | + throw new BizException("提现金额必须大于0"); | ||
| 220 | + } | ||
| 221 | + if (trim(dto.getAccountName()).isBlank()) { | ||
| 222 | + throw new BizException("收款人不能为空"); | ||
| 223 | + } | ||
| 224 | + if (trim(dto.getAccountNo()).isBlank()) { | ||
| 225 | + throw new BizException("收款账号不能为空"); | ||
| 226 | + } | ||
| 227 | + if (dto.getAccountType() == 1 && trim(dto.getBankName()).isBlank()) { | ||
| 228 | + throw new BizException("银行卡提现需填写开户行"); | ||
| 229 | + } | ||
| 230 | + } | ||
| 231 | + | ||
| 232 | + private RiderWithdrawApply lockApply(Long id, Long cityId) { | ||
| 233 | + if (id == null || id < 1) { | ||
| 234 | + throw new BizException("提现申请ID不能为空"); | ||
| 235 | + } | ||
| 236 | + RiderWithdrawApply apply = withdrawMapper.selectApplyForUpdate(id); | ||
| 237 | + if (apply == null) { | ||
| 238 | + throw new BizException("提现申请不存在"); | ||
| 239 | + } | ||
| 240 | + if (cityId != null && !cityId.equals(apply.getCityId())) { | ||
| 241 | + throw new BizException("只能操作当前租户提现申请"); | ||
| 242 | + } | ||
| 243 | + return apply; | ||
| 244 | + } | ||
| 245 | + | ||
| 246 | + private Rider lockRider(Long riderId) { | ||
| 247 | + Rider rider = withdrawMapper.selectRiderForUpdate(riderId); | ||
| 248 | + if (rider == null) { | ||
| 249 | + throw new BizException("骑手信息不存在"); | ||
| 250 | + } | ||
| 251 | + return rider; | ||
| 252 | + } | ||
| 253 | + | ||
| 254 | + private void ensureFrozenEnough(Rider rider, BigDecimal amount) { | ||
| 255 | + if (money(rider.getFrozenBalance()).compareTo(money(amount)) < 0) { | ||
| 256 | + throw new BizException("冻结余额不足,请核对提现申请状态"); | ||
| 257 | + } | ||
| 258 | + } | ||
| 259 | + | ||
| 260 | + private void updateRiderMoney(Long riderId, BigDecimal balance, BigDecimal frozenBalance) { | ||
| 261 | + riderMapper.update(null, new LambdaUpdateWrapper<Rider>() | ||
| 262 | + .eq(Rider::getId, riderId) | ||
| 263 | + .set(Rider::getBalance, money(balance)) | ||
| 264 | + .set(Rider::getFrozenBalance, money(frozenBalance))); | ||
| 265 | + } | ||
| 266 | + | ||
| 267 | + private BigDecimal money(BigDecimal value) { | ||
| 268 | + return value == null ? BigDecimal.ZERO : value.setScale(2, java.math.RoundingMode.HALF_UP); | ||
| 269 | + } | ||
| 270 | + | ||
| 271 | + private long now() { | ||
| 272 | + return System.currentTimeMillis() / 1000; | ||
| 273 | + } | ||
| 274 | + | ||
| 275 | + private String trim(String value) { | ||
| 276 | + return value == null ? "" : value.trim(); | ||
| 277 | + } | ||
| 278 | + | ||
| 279 | + private String buildWithdrawNo(Long riderId) { | ||
| 280 | + return "WD" + System.currentTimeMillis() + String.format("%04d", riderId % 10000); | ||
| 281 | + } | ||
| 282 | + | ||
| 283 | + private String buildAdminName(Long adminId, String adminRole) { | ||
| 284 | + if (adminId == null) { | ||
| 285 | + return "系统"; | ||
| 286 | + } | ||
| 287 | + return ("substation".equals(adminRole) ? "分站管理员" : "平台管理员") + "#" + adminId; | ||
| 288 | + } | ||
| 289 | + | ||
| 290 | + private int normalizePage(int page) { | ||
| 291 | + return Math.max(page, 1); | ||
| 292 | + } | ||
| 293 | + | ||
| 294 | + private Integer normalizeStatus(Integer status) { | ||
| 295 | + return status == null || status < 0 ? null : status; | ||
| 296 | + } | ||
| 297 | + | ||
| 298 | + private String normalizeKeyword(String keyword) { | ||
| 299 | + return keyword == null || keyword.isBlank() ? null : keyword.trim(); | ||
| 300 | + } | ||
| 301 | + | ||
| 302 | + private Long parseStartTime(String startDate) { | ||
| 303 | + if (startDate == null || startDate.isBlank()) { | ||
| 304 | + return null; | ||
| 305 | + } | ||
| 306 | + return parseDate(startDate, "开始日期格式不正确").atStartOfDay(ZONE_ID).toEpochSecond(); | ||
| 307 | + } | ||
| 308 | + | ||
| 309 | + private Long parseEndTime(String endDate) { | ||
| 310 | + if (endDate == null || endDate.isBlank()) { | ||
| 311 | + return null; | ||
| 312 | + } | ||
| 313 | + return parseDate(endDate, "结束日期格式不正确").plusDays(1).atStartOfDay(ZONE_ID).toEpochSecond(); | ||
| 314 | + } | ||
| 315 | + | ||
| 316 | + private LocalDate parseDate(String value, String errorMessage) { | ||
| 317 | + try { | ||
| 318 | + return LocalDate.parse(value); | ||
| 319 | + } catch (DateTimeParseException ex) { | ||
| 320 | + throw new BizException(errorMessage); | ||
| 321 | + } | ||
| 322 | + } | ||
| 323 | +} |
src/main/java/com/diligrp/rider/vo/RiderWithdrawApplyVO.java
0 → 100644
| 1 | +package com.diligrp.rider.vo; | ||
| 2 | + | ||
| 3 | +import lombok.Data; | ||
| 4 | + | ||
| 5 | +import java.math.BigDecimal; | ||
| 6 | + | ||
| 7 | +@Data | ||
| 8 | +public class RiderWithdrawApplyVO { | ||
| 9 | + private Long id; | ||
| 10 | + private String withdrawNo; | ||
| 11 | + private Long riderId; | ||
| 12 | + private String riderName; | ||
| 13 | + private String mobile; | ||
| 14 | + private Long cityId; | ||
| 15 | + private String cityName; | ||
| 16 | + private BigDecimal riderBalance; | ||
| 17 | + private BigDecimal riderFrozenBalance; | ||
| 18 | + private BigDecimal amount; | ||
| 19 | + private Integer status; | ||
| 20 | + private Integer accountType; | ||
| 21 | + private String accountName; | ||
| 22 | + private String bankName; | ||
| 23 | + private String bankBranch; | ||
| 24 | + private String accountNo; | ||
| 25 | + private String applyRemark; | ||
| 26 | + private String auditRemark; | ||
| 27 | + private Long auditorId; | ||
| 28 | + private String auditorName; | ||
| 29 | + private Long applyTime; | ||
| 30 | + private Long auditTime; | ||
| 31 | + private Long payTime; | ||
| 32 | + private String transferNo; | ||
| 33 | + private Long createTime; | ||
| 34 | + private Long updateTime; | ||
| 35 | +} |
src/main/resources/20260509-rider-withdraw.sql
0 → 100644
| 1 | +-- 骑手提现审核增量脚本 | ||
| 2 | +-- 在已有环境执行;全新环境可直接使用 schema.sql。 | ||
| 3 | + | ||
| 4 | +ALTER TABLE `rider` | ||
| 5 | + ADD COLUMN `frozen_balance` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '冻结余额(提现审核中)' AFTER `balance`; | ||
| 6 | + | ||
| 7 | +CREATE TABLE `rider_withdraw_apply` ( | ||
| 8 | + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, | ||
| 9 | + `withdraw_no` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '提现单号', | ||
| 10 | + `rider_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '骑手ID', | ||
| 11 | + `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '城市ID', | ||
| 12 | + `amount` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '提现金额', | ||
| 13 | + `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0=待审核 1=审核通过待打款 2=审核拒绝 3=已打款 4=打款失败', | ||
| 14 | + `account_type` TINYINT NOT NULL DEFAULT 1 COMMENT '收款账户类型:1=银行卡 2=支付宝 3=微信', | ||
| 15 | + `account_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '收款人', | ||
| 16 | + `bank_name` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '开户行', | ||
| 17 | + `bank_branch` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '开户支行', | ||
| 18 | + `account_no` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '收款账号', | ||
| 19 | + `apply_remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '申请备注', | ||
| 20 | + `audit_remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '审核/打款备注', | ||
| 21 | + `auditor_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '审核人ID', | ||
| 22 | + `auditor_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '审核人名称', | ||
| 23 | + `apply_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '申请时间', | ||
| 24 | + `audit_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '审核时间', | ||
| 25 | + `pay_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '打款时间', | ||
| 26 | + `transfer_no` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '打款流水号', | ||
| 27 | + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, | ||
| 28 | + `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, | ||
| 29 | + PRIMARY KEY (`id`), | ||
| 30 | + UNIQUE KEY `uk_withdraw_no` (`withdraw_no`), | ||
| 31 | + KEY `idx_rider_status` (`rider_id`, `status`), | ||
| 32 | + KEY `idx_city_status_time` (`city_id`, `status`, `apply_time`) | ||
| 33 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手提现申请表'; | ||
| 34 | + | ||
| 35 | +INSERT INTO `sys_menu` (`code`, `name`, `type`, `path`, `icon`, `parent_id`, `menu_scope`, `list_order`, `visible`, `status`, `create_time`) | ||
| 36 | +SELECT 'rider.withdraw', '骑手提现审核', 'MENU', '/rider/withdraw', '', 0, 'BOTH', 56, 1, 1, UNIX_TIMESTAMP() | ||
| 37 | +WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `code` = 'rider.withdraw'); | ||
| 38 | + | ||
| 39 | +INSERT INTO `sys_role_menu` (`role_id`, `menu_id`, `create_time`) | ||
| 40 | +SELECT r.id, m.id, UNIX_TIMESTAMP() | ||
| 41 | +FROM `sys_role` r | ||
| 42 | +INNER JOIN `sys_menu` m ON m.code = 'rider.withdraw' | ||
| 43 | +WHERE r.code IN ('platform_admin', 'substation_admin') | ||
| 44 | + AND NOT EXISTS ( | ||
| 45 | + SELECT 1 FROM `sys_role_menu` rm WHERE rm.role_id = r.id AND rm.menu_id = m.id | ||
| 46 | + ); |
src/main/resources/data-init.sql
| @@ -90,7 +90,8 @@ INSERT INTO `sys_menu` (`code`, `name`, `type`, `path`, `icon`, `parent_id`, `me | @@ -90,7 +90,8 @@ INSERT INTO `sys_menu` (`code`, `name`, `type`, `path`, `icon`, `parent_id`, `me | ||
| 90 | ('system.root', '系统管理', 'DIR', '', 'ControlOutlined', 0, 'PLATFORM', 100, 1, 1, UNIX_TIMESTAMP()), | 90 | ('system.root', '系统管理', 'DIR', '', 'ControlOutlined', 0, 'PLATFORM', 100, 1, 1, UNIX_TIMESTAMP()), |
| 91 | ('system.menu', '菜单管理', 'MENU', '/system/menu', '', 19, 'PLATFORM', 101, 1, 1, UNIX_TIMESTAMP()), | 91 | ('system.menu', '菜单管理', 'MENU', '/system/menu', '', 19, 'PLATFORM', 101, 1, 1, UNIX_TIMESTAMP()), |
| 92 | ('system.role_menu', '角色菜单', 'MENU', '/system/role-menu', '', 19, 'PLATFORM', 102, 1, 1, UNIX_TIMESTAMP()), | 92 | ('system.role_menu', '角色菜单', 'MENU', '/system/role-menu', '', 19, 'PLATFORM', 102, 1, 1, UNIX_TIMESTAMP()), |
| 93 | -('admin.user', '平台账号', 'MENU', '/admin-user', '', 19, 'PLATFORM', 103, 1, 1, UNIX_TIMESTAMP()); | 93 | +('admin.user', '平台账号', 'MENU', '/admin-user', '', 19, 'PLATFORM', 103, 1, 1, UNIX_TIMESTAMP()), |
| 94 | +('rider.withdraw', '骑手提现审核', 'MENU', '/rider/withdraw', '', 0, 'BOTH', 56, 1, 1, UNIX_TIMESTAMP()); | ||
| 94 | 95 | ||
| 95 | INSERT INTO `sys_role_menu` (`role_id`, `menu_id`, `create_time`) | 96 | INSERT INTO `sys_role_menu` (`role_id`, `menu_id`, `create_time`) |
| 96 | SELECT 1, id, UNIX_TIMESTAMP() FROM `sys_menu` WHERE `menu_scope` IN ('PLATFORM', 'BOTH'); | 97 | SELECT 1, id, UNIX_TIMESTAMP() FROM `sys_menu` WHERE `menu_scope` IN ('PLATFORM', 'BOTH'); |
src/main/resources/mapper/RiderWithdrawApplyMapper.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | ||
| 3 | +<mapper namespace="com.diligrp.rider.mapper.RiderWithdrawApplyMapper"> | ||
| 4 | + | ||
| 5 | + <sql id="adminListFilter"> | ||
| 6 | + FROM rider_withdraw_apply w | ||
| 7 | + INNER JOIN rider r ON r.id = w.rider_id AND r.is_del = 0 | ||
| 8 | + LEFT JOIN city c ON c.id = w.city_id | ||
| 9 | + WHERE 1 = 1 | ||
| 10 | + <if test="cityId != null"> | ||
| 11 | + AND w.city_id = #{cityId} | ||
| 12 | + </if> | ||
| 13 | + <if test="status != null"> | ||
| 14 | + AND w.status = #{status} | ||
| 15 | + </if> | ||
| 16 | + <if test="keyword != null and keyword != ''"> | ||
| 17 | + AND (w.withdraw_no LIKE CONCAT('%', #{keyword}, '%') | ||
| 18 | + OR r.user_nickname LIKE CONCAT('%', #{keyword}, '%') | ||
| 19 | + OR r.mobile LIKE CONCAT('%', #{keyword}, '%')) | ||
| 20 | + </if> | ||
| 21 | + <if test="startTime != null"> | ||
| 22 | + AND w.apply_time >= #{startTime} | ||
| 23 | + </if> | ||
| 24 | + <if test="endTime != null"> | ||
| 25 | + AND w.apply_time < #{endTime} | ||
| 26 | + </if> | ||
| 27 | + </sql> | ||
| 28 | + | ||
| 29 | + <select id="selectRiderForUpdate" resultType="com.diligrp.rider.entity.Rider"> | ||
| 30 | + SELECT * FROM rider WHERE id = #{riderId} AND is_del = 0 FOR UPDATE | ||
| 31 | + </select> | ||
| 32 | + | ||
| 33 | + <select id="selectApplyForUpdate" resultType="com.diligrp.rider.entity.RiderWithdrawApply"> | ||
| 34 | + SELECT * FROM rider_withdraw_apply WHERE id = #{id} FOR UPDATE | ||
| 35 | + </select> | ||
| 36 | + | ||
| 37 | + <select id="countRiderList" resultType="long"> | ||
| 38 | + SELECT COUNT(1) | ||
| 39 | + FROM rider_withdraw_apply | ||
| 40 | + WHERE rider_id = #{riderId} | ||
| 41 | + <if test="status != null"> | ||
| 42 | + AND status = #{status} | ||
| 43 | + </if> | ||
| 44 | + </select> | ||
| 45 | + | ||
| 46 | + <select id="selectRiderList" resultType="com.diligrp.rider.vo.RiderWithdrawApplyVO"> | ||
| 47 | + SELECT | ||
| 48 | + w.id, | ||
| 49 | + w.withdraw_no AS withdrawNo, | ||
| 50 | + w.rider_id AS riderId, | ||
| 51 | + w.city_id AS cityId, | ||
| 52 | + w.amount, | ||
| 53 | + w.status, | ||
| 54 | + w.account_type AS accountType, | ||
| 55 | + w.account_name AS accountName, | ||
| 56 | + w.bank_name AS bankName, | ||
| 57 | + w.bank_branch AS bankBranch, | ||
| 58 | + w.account_no AS accountNo, | ||
| 59 | + w.apply_remark AS applyRemark, | ||
| 60 | + w.audit_remark AS auditRemark, | ||
| 61 | + w.auditor_id AS auditorId, | ||
| 62 | + w.auditor_name AS auditorName, | ||
| 63 | + w.apply_time AS applyTime, | ||
| 64 | + w.audit_time AS auditTime, | ||
| 65 | + w.pay_time AS payTime, | ||
| 66 | + w.transfer_no AS transferNo, | ||
| 67 | + w.create_time AS createTime, | ||
| 68 | + w.update_time AS updateTime | ||
| 69 | + FROM rider_withdraw_apply w | ||
| 70 | + WHERE w.rider_id = #{riderId} | ||
| 71 | + <if test="status != null"> | ||
| 72 | + AND w.status = #{status} | ||
| 73 | + </if> | ||
| 74 | + ORDER BY w.id DESC | ||
| 75 | + LIMIT #{offset}, #{pageSize} | ||
| 76 | + </select> | ||
| 77 | + | ||
| 78 | + <select id="countAdminList" resultType="long"> | ||
| 79 | + SELECT COUNT(1) | ||
| 80 | + <include refid="adminListFilter"/> | ||
| 81 | + </select> | ||
| 82 | + | ||
| 83 | + <select id="selectAdminList" resultType="com.diligrp.rider.vo.RiderWithdrawApplyVO"> | ||
| 84 | + SELECT | ||
| 85 | + w.id, | ||
| 86 | + w.withdraw_no AS withdrawNo, | ||
| 87 | + w.rider_id AS riderId, | ||
| 88 | + r.user_nickname AS riderName, | ||
| 89 | + r.mobile, | ||
| 90 | + w.city_id AS cityId, | ||
| 91 | + c.name AS cityName, | ||
| 92 | + r.balance AS riderBalance, | ||
| 93 | + r.frozen_balance AS riderFrozenBalance, | ||
| 94 | + w.amount, | ||
| 95 | + w.status, | ||
| 96 | + w.account_type AS accountType, | ||
| 97 | + w.account_name AS accountName, | ||
| 98 | + w.bank_name AS bankName, | ||
| 99 | + w.bank_branch AS bankBranch, | ||
| 100 | + w.account_no AS accountNo, | ||
| 101 | + w.apply_remark AS applyRemark, | ||
| 102 | + w.audit_remark AS auditRemark, | ||
| 103 | + w.auditor_id AS auditorId, | ||
| 104 | + w.auditor_name AS auditorName, | ||
| 105 | + w.apply_time AS applyTime, | ||
| 106 | + w.audit_time AS auditTime, | ||
| 107 | + w.pay_time AS payTime, | ||
| 108 | + w.transfer_no AS transferNo, | ||
| 109 | + w.create_time AS createTime, | ||
| 110 | + w.update_time AS updateTime | ||
| 111 | + <include refid="adminListFilter"/> | ||
| 112 | + ORDER BY w.id DESC | ||
| 113 | + LIMIT #{offset}, #{pageSize} | ||
| 114 | + </select> | ||
| 115 | + | ||
| 116 | + <select id="selectAdminDetail" resultType="com.diligrp.rider.vo.RiderWithdrawApplyVO"> | ||
| 117 | + SELECT | ||
| 118 | + w.id, | ||
| 119 | + w.withdraw_no AS withdrawNo, | ||
| 120 | + w.rider_id AS riderId, | ||
| 121 | + r.user_nickname AS riderName, | ||
| 122 | + r.mobile, | ||
| 123 | + w.city_id AS cityId, | ||
| 124 | + c.name AS cityName, | ||
| 125 | + r.balance AS riderBalance, | ||
| 126 | + r.frozen_balance AS riderFrozenBalance, | ||
| 127 | + w.amount, | ||
| 128 | + w.status, | ||
| 129 | + w.account_type AS accountType, | ||
| 130 | + w.account_name AS accountName, | ||
| 131 | + w.bank_name AS bankName, | ||
| 132 | + w.bank_branch AS bankBranch, | ||
| 133 | + w.account_no AS accountNo, | ||
| 134 | + w.apply_remark AS applyRemark, | ||
| 135 | + w.audit_remark AS auditRemark, | ||
| 136 | + w.auditor_id AS auditorId, | ||
| 137 | + w.auditor_name AS auditorName, | ||
| 138 | + w.apply_time AS applyTime, | ||
| 139 | + w.audit_time AS auditTime, | ||
| 140 | + w.pay_time AS payTime, | ||
| 141 | + w.transfer_no AS transferNo, | ||
| 142 | + w.create_time AS createTime, | ||
| 143 | + w.update_time AS updateTime | ||
| 144 | + FROM rider_withdraw_apply w | ||
| 145 | + INNER JOIN rider r ON r.id = w.rider_id AND r.is_del = 0 | ||
| 146 | + LEFT JOIN city c ON c.id = w.city_id | ||
| 147 | + WHERE w.id = #{id} | ||
| 148 | + <if test="cityId != null"> | ||
| 149 | + AND w.city_id = #{cityId} | ||
| 150 | + </if> | ||
| 151 | + LIMIT 1 | ||
| 152 | + </select> | ||
| 153 | + | ||
| 154 | +</mapper> |
src/main/resources/schema.sql
| @@ -19,6 +19,7 @@ CREATE TABLE `rider` ( | @@ -19,6 +19,7 @@ CREATE TABLE `rider` ( | ||
| 19 | `user_status` TINYINT NOT NULL DEFAULT 2 COMMENT '审核状态:0=拒绝 1=通过 2=待审核', | 19 | `user_status` TINYINT NOT NULL DEFAULT 2 COMMENT '审核状态:0=拒绝 1=通过 2=待审核', |
| 20 | `status` TINYINT NOT NULL DEFAULT 1 COMMENT '账号状态:0=禁用 1=正常', | 20 | `status` TINYINT NOT NULL DEFAULT 1 COMMENT '账号状态:0=禁用 1=正常', |
| 21 | `balance` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '余额(兼职用)', | 21 | `balance` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '余额(兼职用)', |
| 22 | + `frozen_balance` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '冻结余额(提现审核中)', | ||
| 22 | `is_rest` TINYINT NOT NULL DEFAULT 0 COMMENT '是否休息:0=否 1=是', | 23 | `is_rest` TINYINT NOT NULL DEFAULT 0 COMMENT '是否休息:0=否 1=是', |
| 23 | `hold_order_limit` INT NOT NULL DEFAULT 0 COMMENT '个人持单上限,0=不限制', | 24 | `hold_order_limit` INT NOT NULL DEFAULT 0 COMMENT '个人持单上限,0=不限制', |
| 24 | `id_no` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '身份证号', | 25 | `id_no` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '身份证号', |
| @@ -80,6 +81,35 @@ CREATE TABLE `rider_balance` ( | @@ -80,6 +81,35 @@ CREATE TABLE `rider_balance` ( | ||
| 80 | KEY `idx_action_id` (`action_id`) | 81 | KEY `idx_action_id` (`action_id`) |
| 81 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手余额流水表'; | 82 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手余额流水表'; |
| 82 | 83 | ||
| 84 | +-- 骑手提现申请表 | ||
| 85 | +CREATE TABLE `rider_withdraw_apply` ( | ||
| 86 | + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, | ||
| 87 | + `withdraw_no` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '提现单号', | ||
| 88 | + `rider_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '骑手ID', | ||
| 89 | + `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '城市ID', | ||
| 90 | + `amount` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '提现金额', | ||
| 91 | + `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0=待审核 1=审核通过待打款 2=审核拒绝 3=已打款 4=打款失败', | ||
| 92 | + `account_type` TINYINT NOT NULL DEFAULT 1 COMMENT '收款账户类型:1=银行卡 2=支付宝 3=微信', | ||
| 93 | + `account_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '收款人', | ||
| 94 | + `bank_name` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '开户行', | ||
| 95 | + `bank_branch` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '开户支行', | ||
| 96 | + `account_no` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '收款账号', | ||
| 97 | + `apply_remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '申请备注', | ||
| 98 | + `audit_remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '审核/打款备注', | ||
| 99 | + `auditor_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '审核人ID', | ||
| 100 | + `auditor_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '审核人名称', | ||
| 101 | + `apply_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '申请时间', | ||
| 102 | + `audit_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '审核时间', | ||
| 103 | + `pay_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '打款时间', | ||
| 104 | + `transfer_no` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '打款流水号', | ||
| 105 | + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, | ||
| 106 | + `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, | ||
| 107 | + PRIMARY KEY (`id`), | ||
| 108 | + UNIQUE KEY `uk_withdraw_no` (`withdraw_no`), | ||
| 109 | + KEY `idx_rider_status` (`rider_id`, `status`), | ||
| 110 | + KEY `idx_city_status_time` (`city_id`, `status`, `apply_time`) | ||
| 111 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手提现申请表'; | ||
| 112 | + | ||
| 83 | -- 骑手订单统计表 | 113 | -- 骑手订单统计表 |
| 84 | CREATE TABLE `rider_order_count` ( | 114 | CREATE TABLE `rider_order_count` ( |
| 85 | `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, | 115 | `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, |
| @@ -598,4 +628,4 @@ CREATE TABLE `rider_device` ( | @@ -598,4 +628,4 @@ CREATE TABLE `rider_device` ( | ||
| 598 | PRIMARY KEY (`id`), | 628 | PRIMARY KEY (`id`), |
| 599 | UNIQUE KEY `uk_registration_id` (`registration_id`), | 629 | UNIQUE KEY `uk_registration_id` (`registration_id`), |
| 600 | KEY `idx_rider_status` (`rider_id`, `status`) | 630 | KEY `idx_rider_status` (`rider_id`, `status`) |
| 601 | -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手设备推送绑定表'; | ||
| 602 | \ No newline at end of file | 631 | \ No newline at end of file |
| 632 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手设备推送绑定表'; |