refactor(salary): completely refactor the salary logi
Some checks failed
Build and Push Backend / docker (push) Failing after 6s
Some checks failed
Build and Push Backend / docker (push) Failing after 6s
fix: ignore empty clerk performance filters fix: generate earnings for completed orders fix: ensure reward orders create earnings fix: add reward earnings to new order flow
This commit is contained in:
@@ -35,6 +35,7 @@ import com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo;
|
||||
import com.starry.admin.modules.shop.service.IPlayCouponDetailsService;
|
||||
import com.starry.admin.modules.weichat.entity.order.*;
|
||||
import com.starry.admin.modules.weichat.service.WxCustomMpService;
|
||||
import com.starry.admin.modules.withdraw.service.IEarningsService;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.utils.ConvertUtil;
|
||||
import com.starry.common.utils.IdUtils;
|
||||
@@ -91,6 +92,9 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
||||
@Resource
|
||||
private IPlayClerkLevelInfoService playClerkLevelInfoService;
|
||||
|
||||
@Resource
|
||||
private IEarningsService earningsService;
|
||||
|
||||
@Override
|
||||
public List<PlayOrderInfoEntity> getTotalOrderInfo(String tenantId) {
|
||||
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaWrapper = new MPJLambdaWrapper<>();
|
||||
@@ -99,6 +103,7 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void createOrderInfo(String orderId, String orderNo, String orderState, String orderType, String placeType,
|
||||
String rewardType, String firstOrder, String commodityId, String commodityType, BigDecimal commodityPrice,
|
||||
String serviceDuration, String commodityName, String commodityNumber, BigDecimal orderMoney,
|
||||
@@ -156,6 +161,10 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
||||
this.baseMapper.insert(entity);
|
||||
// 修改优惠券状态
|
||||
playCouponDetailsService.updateCouponUseStateByIds(couponIds, "2");
|
||||
if ("2".equals(placeType) && StrUtil.isNotBlank(acceptBy)) {
|
||||
PlayOrderInfoEntity latest = this.selectOrderInfoById(orderId);
|
||||
earningsService.createFromOrder(latest);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -191,6 +200,12 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
||||
// 修改优惠券状态
|
||||
playCouponDetailsService.updateCouponUseStateByIds(
|
||||
request.getPaymentInfo().getCouponIds(), "2");
|
||||
|
||||
// 打赏单立即入账
|
||||
if (request.isRewardOrder() && StrUtil.isNotBlank(request.getAcceptBy())) {
|
||||
PlayOrderInfoEntity latest = this.selectOrderInfoById(entity.getId());
|
||||
earningsService.createFromOrder(latest);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -727,7 +742,9 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
||||
public List<PlayOrderInfoEntity> clerkSelectOrderInfoList(String clerkId, String startTime, String endTime) {
|
||||
LambdaQueryWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
|
||||
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getAcceptBy, clerkId);
|
||||
if (StringUtils.isNotBlank(startTime) && StringUtils.isNotBlank(endTime)) {
|
||||
lambdaQueryWrapper.between(PlayOrderInfoEntity::getPurchaserTime, startTime, endTime);
|
||||
}
|
||||
return this.baseMapper.selectList(lambdaQueryWrapper);
|
||||
}
|
||||
|
||||
@@ -949,8 +966,9 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
||||
PlayOrderInfoEntity entity2 = new PlayOrderInfoEntity(orderId, ORDER_STATUS_3);
|
||||
entity2.setOrderEndTime(LocalDateTime.now());
|
||||
this.baseMapper.updateById(entity2);
|
||||
// 发送消息
|
||||
wxCustomMpService.sendOrderFinishMessage(this.selectOrderInfoById(orderId));
|
||||
PlayOrderInfoEntity latest = this.selectOrderInfoById(orderId);
|
||||
wxCustomMpService.sendOrderFinishMessage(latest);
|
||||
earningsService.createFromOrder(latest);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.starry.admin.modules.withdraw.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.starry.admin.common.exception.CustomException;
|
||||
import com.starry.admin.modules.withdraw.entity.FreezePolicyEntity;
|
||||
import com.starry.admin.modules.withdraw.service.IFreezePolicyService;
|
||||
import com.starry.admin.modules.withdraw.vo.FreezePolicyVo;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.result.TypedR;
|
||||
import com.starry.common.utils.IdUtils;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import javax.annotation.Resource;
|
||||
import lombok.Data;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Api(tags = "提现冻结策略管理")
|
||||
@RestController
|
||||
@RequestMapping("/admin/withdraw/freeze-policy")
|
||||
public class AdminFreezePolicyController {
|
||||
|
||||
@Resource
|
||||
private IFreezePolicyService freezePolicyService;
|
||||
|
||||
@Data
|
||||
public static class UpsertRequest {
|
||||
private Integer freezeHours;
|
||||
}
|
||||
|
||||
@ApiOperation("获取租户默认冻结策略")
|
||||
@GetMapping("/default")
|
||||
public TypedR<FreezePolicyVo> getTenantDefault() {
|
||||
String tenantId = SecurityUtils.getTenantId();
|
||||
FreezePolicyEntity p = freezePolicyService.getOne(new LambdaQueryWrapper<FreezePolicyEntity>()
|
||||
.eq(FreezePolicyEntity::getTenantId, tenantId)
|
||||
.isNull(FreezePolicyEntity::getClerkId));
|
||||
FreezePolicyVo vo = new FreezePolicyVo(p == null ? null : p.getFreezeHours());
|
||||
return TypedR.ok(vo);
|
||||
}
|
||||
|
||||
@ApiOperation("设置租户默认冻结策略")
|
||||
@PutMapping("/default")
|
||||
public TypedR<Void> upsertTenantDefault(@RequestBody UpsertRequest body) {
|
||||
if (body.getFreezeHours() == null || body.getFreezeHours() < 0) {
|
||||
throw new CustomException("冻结时长必须为非负数");
|
||||
}
|
||||
String tenantId = SecurityUtils.getTenantId();
|
||||
FreezePolicyEntity existing = freezePolicyService.getOne(new LambdaQueryWrapper<FreezePolicyEntity>()
|
||||
.eq(FreezePolicyEntity::getTenantId, tenantId)
|
||||
.isNull(FreezePolicyEntity::getClerkId));
|
||||
if (existing == null) {
|
||||
FreezePolicyEntity e = new FreezePolicyEntity();
|
||||
e.setId(IdUtils.getUuid());
|
||||
e.setTenantId(tenantId);
|
||||
e.setClerkId(null);
|
||||
e.setFreezeHours(body.getFreezeHours());
|
||||
freezePolicyService.save(e);
|
||||
} else {
|
||||
existing.setFreezeHours(body.getFreezeHours());
|
||||
freezePolicyService.updateById(existing);
|
||||
}
|
||||
return TypedR.ok(null);
|
||||
}
|
||||
|
||||
@ApiOperation("获取店员冻结策略(覆盖)")
|
||||
@GetMapping("/clerk")
|
||||
public TypedR<FreezePolicyVo> getClerkPolicy(@RequestParam("clerkId") String clerkId) {
|
||||
String tenantId = SecurityUtils.getTenantId();
|
||||
FreezePolicyEntity p = freezePolicyService.getOne(new LambdaQueryWrapper<FreezePolicyEntity>()
|
||||
.eq(FreezePolicyEntity::getTenantId, tenantId)
|
||||
.eq(FreezePolicyEntity::getClerkId, clerkId));
|
||||
FreezePolicyVo vo = new FreezePolicyVo(p == null ? null : p.getFreezeHours());
|
||||
return TypedR.ok(vo);
|
||||
}
|
||||
|
||||
@ApiOperation("设置店员冻结策略(覆盖)")
|
||||
@PutMapping("/clerk")
|
||||
public TypedR<Void> upsertClerkPolicy(@RequestParam("clerkId") String clerkId, @RequestBody UpsertRequest body) {
|
||||
if (body.getFreezeHours() == null || body.getFreezeHours() < 0) {
|
||||
throw new CustomException("冻结时长必须为非负数");
|
||||
}
|
||||
String tenantId = SecurityUtils.getTenantId();
|
||||
FreezePolicyEntity existing = freezePolicyService.getOne(new LambdaQueryWrapper<FreezePolicyEntity>()
|
||||
.eq(FreezePolicyEntity::getTenantId, tenantId)
|
||||
.eq(FreezePolicyEntity::getClerkId, clerkId));
|
||||
if (existing == null) {
|
||||
FreezePolicyEntity e = new FreezePolicyEntity();
|
||||
e.setId(IdUtils.getUuid());
|
||||
e.setTenantId(tenantId);
|
||||
e.setClerkId(clerkId);
|
||||
e.setFreezeHours(body.getFreezeHours());
|
||||
freezePolicyService.save(e);
|
||||
} else {
|
||||
existing.setFreezeHours(body.getFreezeHours());
|
||||
freezePolicyService.updateById(existing);
|
||||
}
|
||||
return TypedR.ok(null);
|
||||
}
|
||||
|
||||
@ApiOperation("删除店员冻结策略(恢复为租户默认)")
|
||||
@DeleteMapping("/clerk")
|
||||
public TypedR<Boolean> deleteClerkPolicy(@RequestParam("clerkId") String clerkId) {
|
||||
String tenantId = SecurityUtils.getTenantId();
|
||||
boolean removed = freezePolicyService.remove(new LambdaQueryWrapper<FreezePolicyEntity>()
|
||||
.eq(FreezePolicyEntity::getTenantId, tenantId)
|
||||
.eq(FreezePolicyEntity::getClerkId, clerkId));
|
||||
return TypedR.ok(removed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
package com.starry.admin.modules.withdraw.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.starry.admin.modules.withdraw.entity.EarningsLineEntity;
|
||||
import com.starry.admin.modules.withdraw.entity.WithdrawalLogEntity;
|
||||
import com.starry.admin.modules.withdraw.entity.WithdrawalRequestEntity;
|
||||
import com.starry.admin.modules.withdraw.service.IEarningsService;
|
||||
import com.starry.admin.modules.withdraw.service.ITenantAlipayConfigService;
|
||||
import com.starry.admin.modules.withdraw.service.IWithdrawalLogService;
|
||||
import com.starry.admin.modules.withdraw.service.IWithdrawalService;
|
||||
import com.starry.admin.modules.withdraw.vo.EarningsAdminQueryVo;
|
||||
import com.starry.admin.modules.withdraw.vo.EarningsAdminSummaryVo;
|
||||
import com.starry.admin.modules.withdraw.vo.WithdrawalRequestQueryVo;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.result.TypedR;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Resource;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Api(tags = "提现管理-后台")
|
||||
@RestController
|
||||
@RequestMapping("/admin/withdraw")
|
||||
public class AdminWithdrawalController {
|
||||
|
||||
@Resource
|
||||
private IWithdrawalService withdrawalService;
|
||||
@Resource
|
||||
private IWithdrawalLogService withdrawalLogService;
|
||||
@Resource
|
||||
private ITenantAlipayConfigService tenantAlipayConfigService;
|
||||
@Resource
|
||||
private IEarningsService earningsService;
|
||||
|
||||
@ApiOperation("分页查询提现请求")
|
||||
@PostMapping("/requests/listByPage")
|
||||
public TypedR<List<WithdrawalRequestEntity>> listRequests(@RequestBody WithdrawalRequestQueryVo vo) {
|
||||
LambdaQueryWrapper<WithdrawalRequestEntity> q = new LambdaQueryWrapper<>();
|
||||
if (vo.getClerkId() != null && !vo.getClerkId().isEmpty()) q.eq(WithdrawalRequestEntity::getClerkId, vo.getClerkId());
|
||||
if (vo.getStatus() != null && !vo.getStatus().isEmpty()) q.eq(WithdrawalRequestEntity::getStatus, vo.getStatus());
|
||||
q.eq(WithdrawalRequestEntity::getTenantId, SecurityUtils.getTenantId());
|
||||
q.orderByDesc(WithdrawalRequestEntity::getCreatedTime);
|
||||
IPage<WithdrawalRequestEntity> page = withdrawalService.page(new Page<>(vo.getPageNum(), vo.getPageSize()), q);
|
||||
return TypedR.okPage(page);
|
||||
}
|
||||
|
||||
@ApiOperation("查询提现日志(按请求ID)")
|
||||
@GetMapping("/logs/list")
|
||||
public TypedR<List<WithdrawalLogEntity>> listLogs(@RequestParam("withdrawalId") String withdrawalId) {
|
||||
List<WithdrawalLogEntity> list = withdrawalLogService.lambdaQuery()
|
||||
.eq(WithdrawalLogEntity::getWithdrawalId, withdrawalId)
|
||||
.orderByAsc(WithdrawalLogEntity::getCreatedTime)
|
||||
.list();
|
||||
return TypedR.ok(list);
|
||||
}
|
||||
|
||||
@ApiOperation("分页查询收益明细")
|
||||
@PostMapping("/earnings/listByPage")
|
||||
public TypedR<List<EarningsLineEntity>> listEarnings(@RequestBody EarningsAdminQueryVo vo) {
|
||||
LambdaQueryWrapper<EarningsLineEntity> q = buildEarningsWrapper(vo);
|
||||
IPage<EarningsLineEntity> page = earningsService.page(new Page<>(vo.getPageNum(), vo.getPageSize()), q);
|
||||
return TypedR.okPage(page);
|
||||
}
|
||||
|
||||
@ApiOperation("收益汇总")
|
||||
@GetMapping("/earnings/summary")
|
||||
public TypedR<EarningsAdminSummaryVo> summarize(EarningsAdminQueryVo vo) {
|
||||
LambdaQueryWrapper<EarningsLineEntity> q = buildEarningsWrapper(vo);
|
||||
List<EarningsLineEntity> records = earningsService.list(q);
|
||||
|
||||
EarningsAdminSummaryVo summary = new EarningsAdminSummaryVo();
|
||||
Map<String, EarningsAdminSummaryVo.StatusStat> statMap = new HashMap<>();
|
||||
|
||||
BigDecimal available = BigDecimal.ZERO;
|
||||
BigDecimal pending = BigDecimal.ZERO;
|
||||
BigDecimal withdrawn = BigDecimal.ZERO;
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
|
||||
for (EarningsLineEntity record : records) {
|
||||
BigDecimal amount = record.getAmount() == null ? BigDecimal.ZERO : record.getAmount();
|
||||
total = total.add(amount);
|
||||
String status = record.getStatus();
|
||||
EarningsAdminSummaryVo.StatusStat stat = statMap.computeIfAbsent(status, k -> {
|
||||
EarningsAdminSummaryVo.StatusStat st = new EarningsAdminSummaryVo.StatusStat();
|
||||
st.setStatus(k);
|
||||
return st;
|
||||
});
|
||||
stat.setCount(stat.getCount() + 1);
|
||||
stat.setAmount(stat.getAmount().add(amount));
|
||||
|
||||
if ("available".equals(status)) {
|
||||
available = available.add(amount);
|
||||
} else if ("withdrawn".equals(status)) {
|
||||
withdrawn = withdrawn.add(amount);
|
||||
} else if ("frozen".equals(status) || "withdrawing".equals(status)) {
|
||||
pending = pending.add(amount);
|
||||
}
|
||||
}
|
||||
|
||||
summary.setAvailableAmount(available);
|
||||
summary.setPendingAmount(pending);
|
||||
summary.setWithdrawnAmount(withdrawn);
|
||||
summary.setTotalAmount(total);
|
||||
summary.setStatusStats(new ArrayList<>(statMap.values()));
|
||||
return TypedR.ok(summary);
|
||||
}
|
||||
|
||||
@ApiOperation("查询当前租户是否已配置支付宝")
|
||||
@GetMapping("/alipay/config/present")
|
||||
public TypedR<Boolean> hasAlipayConfig() {
|
||||
String tenantId = SecurityUtils.getTenantId();
|
||||
return TypedR.ok(tenantAlipayConfigService.hasConfig(tenantId));
|
||||
}
|
||||
|
||||
@ApiOperation("手动标记打款成功")
|
||||
@PostMapping("/requests/{id}/manual/success")
|
||||
public TypedR<Void> manualSuccess(@PathVariable("id") String id, @RequestParam(value = "operator", required = false) String operator) {
|
||||
withdrawalService.markManualSuccess(id, operator);
|
||||
return TypedR.ok(null);
|
||||
}
|
||||
|
||||
@ApiOperation("支付宝自动打款(若已配置)")
|
||||
@PostMapping("/requests/{id}/auto")
|
||||
public TypedR<Void> autoPayout(@PathVariable("id") String id) {
|
||||
withdrawalService.autoPayout(id);
|
||||
return TypedR.ok(null);
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<EarningsLineEntity> buildEarningsWrapper(EarningsAdminQueryVo vo) {
|
||||
LambdaQueryWrapper<EarningsLineEntity> q = new LambdaQueryWrapper<>();
|
||||
q.eq(EarningsLineEntity::getTenantId, SecurityUtils.getTenantId());
|
||||
if (vo.getClerkId() != null && !vo.getClerkId().isEmpty()) {
|
||||
q.eq(EarningsLineEntity::getClerkId, vo.getClerkId());
|
||||
}
|
||||
if (vo.getEarningType() != null && !vo.getEarningType().isEmpty()) {
|
||||
q.eq(EarningsLineEntity::getEarningType, vo.getEarningType());
|
||||
}
|
||||
if (vo.getStatus() != null && !vo.getStatus().isEmpty()) {
|
||||
q.eq(EarningsLineEntity::getStatus, vo.getStatus());
|
||||
}
|
||||
if (vo.getBeginTime() != null && !vo.getBeginTime().isEmpty()) {
|
||||
q.ge(EarningsLineEntity::getCreatedTime, LocalDateTime.parse(vo.getBeginTime(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||
}
|
||||
if (vo.getEndTime() != null && !vo.getEndTime().isEmpty()) {
|
||||
q.le(EarningsLineEntity::getCreatedTime, LocalDateTime.parse(vo.getEndTime(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||
}
|
||||
q.orderByDesc(EarningsLineEntity::getCreatedTime);
|
||||
return q;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.starry.admin.modules.withdraw.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.starry.admin.common.aspect.ClerkUserLogin;
|
||||
import com.starry.admin.common.conf.ThreadLocalRequestDetail;
|
||||
import com.starry.admin.common.exception.CustomException;
|
||||
import com.starry.admin.modules.withdraw.entity.EarningsLineEntity;
|
||||
import com.starry.admin.modules.withdraw.entity.WithdrawalLogEntity;
|
||||
import com.starry.admin.modules.withdraw.entity.WithdrawalRequestEntity;
|
||||
import com.starry.admin.modules.withdraw.service.IEarningsService;
|
||||
import com.starry.admin.modules.withdraw.service.IWithdrawalLogService;
|
||||
import com.starry.admin.modules.withdraw.service.IWithdrawalService;
|
||||
import com.starry.admin.modules.withdraw.vo.ClerkWithdrawBalanceVo;
|
||||
import com.starry.common.result.TypedR;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import javax.annotation.Resource;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/wx/withdraw")
|
||||
public class WxWithdrawController {
|
||||
|
||||
@Resource
|
||||
private IEarningsService earningsService;
|
||||
@Resource
|
||||
private IWithdrawalService withdrawalService;
|
||||
@Resource
|
||||
private IWithdrawalLogService withdrawalLogService;
|
||||
|
||||
@Data
|
||||
public static class CreateWithdrawRequest {
|
||||
private BigDecimal amount;
|
||||
private String destAccount; // 临时:支付宝登录号/账号
|
||||
}
|
||||
|
||||
@ClerkUserLogin
|
||||
@GetMapping("/balance")
|
||||
public TypedR<ClerkWithdrawBalanceVo> getBalance() {
|
||||
String clerkId = ThreadLocalRequestDetail.getClerkUserInfo().getId();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
BigDecimal available = earningsService.getAvailableAmount(clerkId, now);
|
||||
BigDecimal pending = earningsService.getPendingAmount(clerkId, now);
|
||||
LocalDateTime nextUnlock = earningsService.getNextUnlockTime(clerkId, now);
|
||||
return TypedR.ok(new ClerkWithdrawBalanceVo(available, pending, nextUnlock));
|
||||
}
|
||||
|
||||
@ClerkUserLogin
|
||||
@GetMapping("/earnings")
|
||||
public TypedR<java.util.List<EarningsLineEntity>> listEarnings(@RequestParam(value = "status", required = false) String status,
|
||||
@RequestParam(value = "pageNum", defaultValue = "1") long pageNum,
|
||||
@RequestParam(value = "pageSize", defaultValue = "10") long pageSize) {
|
||||
String clerkId = ThreadLocalRequestDetail.getClerkUserInfo().getId();
|
||||
LambdaQueryWrapper<EarningsLineEntity> q = new LambdaQueryWrapper<>();
|
||||
q.eq(EarningsLineEntity::getClerkId, clerkId);
|
||||
if (status != null && !status.isEmpty()) {
|
||||
q.eq(EarningsLineEntity::getStatus, status);
|
||||
}
|
||||
q.orderByDesc(EarningsLineEntity::getCreatedTime);
|
||||
IPage<EarningsLineEntity> page = earningsService.page(new Page<>(pageNum, pageSize), q);
|
||||
return TypedR.okPage(page);
|
||||
}
|
||||
|
||||
@ClerkUserLogin
|
||||
@PostMapping("/requests")
|
||||
public TypedR<WithdrawalRequestEntity> createWithdraw(@RequestBody CreateWithdrawRequest body) {
|
||||
if (body.getAmount() == null || body.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new CustomException("提现金额必须大于0");
|
||||
}
|
||||
String clerkId = ThreadLocalRequestDetail.getClerkUserInfo().getId();
|
||||
WithdrawalRequestEntity req = withdrawalService.createWithdrawaRequest(clerkId, body.getDestAccount(),
|
||||
body.getAmount());
|
||||
return TypedR.ok(req);
|
||||
}
|
||||
|
||||
@ClerkUserLogin
|
||||
@GetMapping("/requests")
|
||||
public TypedR<java.util.List<WithdrawalRequestEntity>> listRequests(@RequestParam(value = "pageNum", defaultValue = "1") long pageNum,
|
||||
@RequestParam(value = "pageSize", defaultValue = "10") long pageSize) {
|
||||
String clerkId = ThreadLocalRequestDetail.getClerkUserInfo().getId();
|
||||
LambdaQueryWrapper<WithdrawalRequestEntity> q = new LambdaQueryWrapper<>();
|
||||
q.eq(WithdrawalRequestEntity::getClerkId, clerkId).orderByDesc(WithdrawalRequestEntity::getCreatedTime);
|
||||
IPage<WithdrawalRequestEntity> page = withdrawalService.page(new Page<>(pageNum, pageSize), q);
|
||||
return TypedR.okPage(page);
|
||||
}
|
||||
|
||||
@ClerkUserLogin
|
||||
@GetMapping("/requests/{id}/logs")
|
||||
public TypedR<java.util.List<WithdrawalLogEntity>> getRequestLogs(@PathVariable("id") String id) {
|
||||
String clerkId = ThreadLocalRequestDetail.getClerkUserInfo().getId();
|
||||
WithdrawalRequestEntity req = withdrawalService.getById(id);
|
||||
if (req == null || !clerkId.equals(req.getClerkId())) {
|
||||
throw new CustomException("无权查看");
|
||||
}
|
||||
java.util.List<WithdrawalLogEntity> list = withdrawalLogService.lambdaQuery()
|
||||
.eq(WithdrawalLogEntity::getWithdrawalId, id)
|
||||
.orderByAsc(WithdrawalLogEntity::getCreatedTime)
|
||||
.list();
|
||||
return TypedR.ok(list);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.starry.admin.modules.withdraw.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.starry.admin.modules.withdraw.enums.EarningsType;
|
||||
import com.starry.common.domain.BaseEntity;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("play_earnings_line")
|
||||
public class EarningsLineEntity extends BaseEntity<EarningsLineEntity> {
|
||||
private String id;
|
||||
private String tenantId;
|
||||
private String clerkId;
|
||||
private String orderId;
|
||||
private BigDecimal amount;
|
||||
private EarningsType earningType;
|
||||
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private LocalDateTime unlockTime;
|
||||
|
||||
/**
|
||||
* frozen / available / withdrawing / withdrawn / reversed
|
||||
*/
|
||||
private String status;
|
||||
|
||||
private String withdrawalId;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.starry.admin.modules.withdraw.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.starry.common.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("play_freeze_policy")
|
||||
public class FreezePolicyEntity extends BaseEntity<FreezePolicyEntity> {
|
||||
private String id;
|
||||
private String tenantId;
|
||||
private String clerkId; // null means tenant default
|
||||
private Integer freezeHours;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.starry.admin.modules.withdraw.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.starry.common.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("play_tenant_alipay_config")
|
||||
public class TenantAlipayConfigEntity extends BaseEntity<TenantAlipayConfigEntity> {
|
||||
private String id;
|
||||
private String tenantId;
|
||||
private String appId;
|
||||
private String merchantPrivateKey; // TODO: secure storage
|
||||
private String alipayPublicKey;
|
||||
private String notifyUrl;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.starry.admin.modules.withdraw.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.starry.common.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("play_withdrawal_log")
|
||||
public class WithdrawalLogEntity extends BaseEntity<WithdrawalLogEntity> {
|
||||
private String id;
|
||||
private String tenantId;
|
||||
private String withdrawalId;
|
||||
private String clerkId;
|
||||
private String eventType;
|
||||
private String statusFrom;
|
||||
private String statusTo;
|
||||
private String message;
|
||||
private String payload;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.starry.admin.modules.withdraw.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.starry.common.domain.BaseEntity;
|
||||
import java.math.BigDecimal;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("play_withdrawal_request")
|
||||
public class WithdrawalRequestEntity extends BaseEntity<WithdrawalRequestEntity> {
|
||||
private String id;
|
||||
private String tenantId;
|
||||
private String clerkId;
|
||||
private BigDecimal amount;
|
||||
private BigDecimal fee;
|
||||
private BigDecimal netAmount;
|
||||
private String destAccount;
|
||||
/**
|
||||
* pending / processing / success / failed / canceled
|
||||
*/
|
||||
private String status;
|
||||
private String outBizNo;
|
||||
private String providerRef;
|
||||
private String failureReason;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.starry.admin.modules.withdraw.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.EnumValue;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
/**
|
||||
* 收益类型
|
||||
*/
|
||||
public enum EarningsType {
|
||||
ORDER("ORDER"),
|
||||
COMMISSION("COMMISSION");
|
||||
|
||||
@EnumValue
|
||||
@JsonValue
|
||||
private final String value;
|
||||
|
||||
EarningsType(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.starry.admin.modules.withdraw.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.starry.admin.modules.withdraw.entity.EarningsLineEntity;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
@Mapper
|
||||
public interface EarningsLineMapper extends BaseMapper<EarningsLineEntity> {
|
||||
|
||||
@Select("SELECT COALESCE(SUM(amount), 0) " +
|
||||
"FROM play_earnings_line " +
|
||||
"WHERE deleted = 0 " +
|
||||
" AND clerk_id = #{clerkId} " +
|
||||
" AND (status = 'available' OR (status = 'frozen' AND unlock_time <= #{now}))")
|
||||
BigDecimal sumWithdrawableAmount(@Param("clerkId") String clerkId, @Param("now") LocalDateTime now);
|
||||
|
||||
@Select("SELECT COALESCE(SUM(amount), 0) " +
|
||||
"FROM play_earnings_line " +
|
||||
"WHERE deleted = 0 " +
|
||||
" AND clerk_id = #{clerkId} " +
|
||||
" AND status = 'frozen' " +
|
||||
" AND unlock_time > #{now}")
|
||||
BigDecimal sumPendingAmount(@Param("clerkId") String clerkId, @Param("now") LocalDateTime now);
|
||||
|
||||
@Select("SELECT * " +
|
||||
"FROM play_earnings_line " +
|
||||
"WHERE deleted = 0 " +
|
||||
" AND clerk_id = #{clerkId} " +
|
||||
" AND (status = 'available' OR (status = 'frozen' AND unlock_time <= #{now})) " +
|
||||
"ORDER BY unlock_time ASC")
|
||||
List<EarningsLineEntity> selectWithdrawableLines(@Param("clerkId") String clerkId, @Param("now") LocalDateTime now);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.starry.admin.modules.withdraw.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.starry.admin.modules.withdraw.entity.FreezePolicyEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface FreezePolicyMapper extends BaseMapper<FreezePolicyEntity> {}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.starry.admin.modules.withdraw.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.starry.admin.modules.withdraw.entity.TenantAlipayConfigEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface TenantAlipayConfigMapper extends BaseMapper<TenantAlipayConfigEntity> {}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.starry.admin.modules.withdraw.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.starry.admin.modules.withdraw.entity.WithdrawalLogEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface WithdrawalLogMapper extends BaseMapper<WithdrawalLogEntity> {}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.starry.admin.modules.withdraw.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.starry.admin.modules.withdraw.entity.WithdrawalRequestEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface WithdrawalRequestMapper extends BaseMapper<WithdrawalRequestEntity> {}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.starry.admin.modules.withdraw.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||
import com.starry.admin.modules.withdraw.entity.EarningsLineEntity;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
public interface IEarningsService extends IService<EarningsLineEntity> {
|
||||
void createFromOrder(PlayOrderInfoEntity orderInfo);
|
||||
|
||||
BigDecimal getAvailableAmount(String clerkId, LocalDateTime now);
|
||||
|
||||
BigDecimal getPendingAmount(String clerkId, LocalDateTime now);
|
||||
|
||||
LocalDateTime getNextUnlockTime(String clerkId, LocalDateTime now);
|
||||
|
||||
List<EarningsLineEntity> findWithdrawable(String clerkId, BigDecimal amount, LocalDateTime now);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.starry.admin.modules.withdraw.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.starry.admin.modules.withdraw.entity.FreezePolicyEntity;
|
||||
|
||||
public interface IFreezePolicyService extends IService<FreezePolicyEntity> {
|
||||
int resolveFreezeHours(String tenantId, String clerkId);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.starry.admin.modules.withdraw.service;
|
||||
|
||||
public interface ITenantAlipayConfigService {
|
||||
boolean hasConfig(String tenantId);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.starry.admin.modules.withdraw.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.starry.admin.modules.withdraw.entity.WithdrawalLogEntity;
|
||||
|
||||
public interface IWithdrawalLogService extends IService<WithdrawalLogEntity> {
|
||||
void log(String tenantId, String clerkId, String withdrawalId, String eventType, String from, String to, String message, String payload);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.starry.admin.modules.withdraw.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.starry.admin.modules.withdraw.entity.WithdrawalRequestEntity;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public interface IWithdrawalService extends IService<WithdrawalRequestEntity> {
|
||||
WithdrawalRequestEntity createWithdrawaRequest(String clerkId, String destAccount, BigDecimal amount);
|
||||
|
||||
void markManualSuccess(String requestId, String operatorBy);
|
||||
|
||||
void autoPayout(String requestId);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.starry.admin.modules.withdraw.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||
import com.starry.admin.modules.withdraw.entity.EarningsLineEntity;
|
||||
import com.starry.admin.modules.withdraw.enums.EarningsType;
|
||||
import com.starry.admin.modules.withdraw.mapper.EarningsLineMapper;
|
||||
import com.starry.admin.modules.withdraw.service.IEarningsService;
|
||||
import com.starry.admin.modules.withdraw.service.IFreezePolicyService;
|
||||
import com.starry.common.utils.IdUtils;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class EarningsServiceImpl extends ServiceImpl<EarningsLineMapper, EarningsLineEntity>
|
||||
implements IEarningsService {
|
||||
|
||||
@Resource
|
||||
private IFreezePolicyService freezePolicyService;
|
||||
|
||||
@Override
|
||||
public void createFromOrder(PlayOrderInfoEntity orderInfo) {
|
||||
if (orderInfo == null || orderInfo.getAcceptBy() == null) return;
|
||||
// amount from estimatedRevenue; fallback to finalAmount if null
|
||||
BigDecimal amount = orderInfo.getEstimatedRevenue() != null ? orderInfo.getEstimatedRevenue()
|
||||
: (orderInfo.getFinalAmount() != null ? orderInfo.getFinalAmount() : BigDecimal.ZERO);
|
||||
if (amount.compareTo(BigDecimal.ZERO) <= 0) return;
|
||||
|
||||
int freezeHours = freezePolicyService
|
||||
.resolveFreezeHours(orderInfo.getTenantId(), orderInfo.getAcceptBy());
|
||||
LocalDateTime endTime = orderInfo.getOrderEndTime();
|
||||
if (endTime == null) return;
|
||||
LocalDateTime unlockTime = endTime.plusHours(freezeHours);
|
||||
|
||||
EarningsLineEntity line = new EarningsLineEntity();
|
||||
line.setId(IdUtils.getUuid());
|
||||
line.setTenantId(orderInfo.getTenantId());
|
||||
line.setClerkId(orderInfo.getAcceptBy());
|
||||
line.setOrderId(orderInfo.getId());
|
||||
line.setAmount(amount);
|
||||
line.setEarningType(EarningsType.ORDER);
|
||||
line.setUnlockTime(unlockTime);
|
||||
line.setStatus("frozen");
|
||||
this.save(line);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getAvailableAmount(String clerkId, LocalDateTime now) {
|
||||
// available = sum(frozen where unlock<=now) + sum(available)
|
||||
BigDecimal sum = this.baseMapper.sumWithdrawableAmount(clerkId, now);
|
||||
return sum == null ? BigDecimal.ZERO : sum;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getPendingAmount(String clerkId, LocalDateTime now) {
|
||||
// pending = sum(frozen where unlock>now)
|
||||
BigDecimal sum = this.baseMapper.sumPendingAmount(clerkId, now);
|
||||
return sum == null ? BigDecimal.ZERO : sum;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDateTime getNextUnlockTime(String clerkId, LocalDateTime now) {
|
||||
LambdaQueryWrapper<EarningsLineEntity> q = new LambdaQueryWrapper<>();
|
||||
q.eq(EarningsLineEntity::getClerkId, clerkId)
|
||||
.eq(EarningsLineEntity::getStatus, "frozen")
|
||||
.gt(EarningsLineEntity::getUnlockTime, now)
|
||||
.orderByAsc(EarningsLineEntity::getUnlockTime)
|
||||
.last("limit 1");
|
||||
EarningsLineEntity line = this.baseMapper.selectOne(q);
|
||||
return line == null ? null : line.getUnlockTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EarningsLineEntity> findWithdrawable(String clerkId, BigDecimal amount, LocalDateTime now) {
|
||||
// pick oldest unlocked first (status in available or frozen with unlock<=now)
|
||||
List<EarningsLineEntity> list = this.baseMapper.selectWithdrawableLines(clerkId, now);
|
||||
BigDecimal acc = BigDecimal.ZERO;
|
||||
List<EarningsLineEntity> picked = new ArrayList<>();
|
||||
for (EarningsLineEntity e : list) {
|
||||
picked.add(e);
|
||||
acc = acc.add(e.getAmount());
|
||||
if (acc.compareTo(amount) >= 0) break;
|
||||
}
|
||||
if (acc.compareTo(amount) < 0) return new ArrayList<>();
|
||||
return picked;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.starry.admin.modules.withdraw.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.starry.admin.modules.withdraw.entity.FreezePolicyEntity;
|
||||
import com.starry.admin.modules.withdraw.mapper.FreezePolicyMapper;
|
||||
import com.starry.admin.modules.withdraw.service.IFreezePolicyService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class FreezePolicyServiceImpl extends ServiceImpl<FreezePolicyMapper, FreezePolicyEntity>
|
||||
implements IFreezePolicyService {
|
||||
|
||||
@Override
|
||||
public int resolveFreezeHours(String tenantId, String clerkId) {
|
||||
// clerk override
|
||||
LambdaQueryWrapper<FreezePolicyEntity> q1 = new LambdaQueryWrapper<>();
|
||||
q1.eq(FreezePolicyEntity::getTenantId, tenantId).eq(FreezePolicyEntity::getClerkId, clerkId);
|
||||
FreezePolicyEntity clerkPolicy = this.baseMapper.selectOne(q1);
|
||||
if (clerkPolicy != null && clerkPolicy.getFreezeHours() != null) {
|
||||
return clerkPolicy.getFreezeHours();
|
||||
}
|
||||
// tenant default
|
||||
LambdaQueryWrapper<FreezePolicyEntity> q2 = new LambdaQueryWrapper<>();
|
||||
q2.eq(FreezePolicyEntity::getTenantId, tenantId).isNull(FreezePolicyEntity::getClerkId);
|
||||
FreezePolicyEntity tenantPolicy = this.baseMapper.selectOne(q2);
|
||||
if (tenantPolicy != null && tenantPolicy.getFreezeHours() != null) {
|
||||
return tenantPolicy.getFreezeHours();
|
||||
}
|
||||
return 24; // default
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.starry.admin.modules.withdraw.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.starry.admin.modules.withdraw.entity.TenantAlipayConfigEntity;
|
||||
import com.starry.admin.modules.withdraw.mapper.TenantAlipayConfigMapper;
|
||||
import com.starry.admin.modules.withdraw.service.ITenantAlipayConfigService;
|
||||
import javax.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class TenantAlipayConfigServiceImpl implements ITenantAlipayConfigService {
|
||||
|
||||
@Resource
|
||||
private TenantAlipayConfigMapper mapper;
|
||||
|
||||
@Override
|
||||
public boolean hasConfig(String tenantId) {
|
||||
LambdaQueryWrapper<TenantAlipayConfigEntity> q = new LambdaQueryWrapper<>();
|
||||
q.eq(TenantAlipayConfigEntity::getTenantId, tenantId);
|
||||
return mapper.selectCount(q) > 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.starry.admin.modules.withdraw.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.starry.admin.modules.withdraw.entity.WithdrawalLogEntity;
|
||||
import com.starry.admin.modules.withdraw.mapper.WithdrawalLogMapper;
|
||||
import com.starry.admin.modules.withdraw.service.IWithdrawalLogService;
|
||||
import com.starry.common.utils.IdUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class WithdrawalLogServiceImpl extends ServiceImpl<WithdrawalLogMapper, WithdrawalLogEntity>
|
||||
implements IWithdrawalLogService {
|
||||
|
||||
@Override
|
||||
public void log(String tenantId, String clerkId, String withdrawalId, String eventType, String from, String to,
|
||||
String message, String payload) {
|
||||
WithdrawalLogEntity e = new WithdrawalLogEntity();
|
||||
e.setId(IdUtils.getUuid());
|
||||
e.setTenantId(tenantId);
|
||||
e.setClerkId(clerkId);
|
||||
e.setWithdrawalId(withdrawalId);
|
||||
e.setEventType(eventType);
|
||||
e.setStatusFrom(from);
|
||||
e.setStatusTo(to);
|
||||
e.setMessage(message);
|
||||
e.setPayload(payload);
|
||||
this.save(e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package com.starry.admin.modules.withdraw.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.starry.admin.common.exception.CustomException;
|
||||
import com.starry.admin.modules.withdraw.entity.EarningsLineEntity;
|
||||
import com.starry.admin.modules.withdraw.entity.WithdrawalRequestEntity;
|
||||
import com.starry.admin.modules.withdraw.mapper.WithdrawalRequestMapper;
|
||||
import com.starry.admin.modules.withdraw.service.IEarningsService;
|
||||
import com.starry.admin.modules.withdraw.service.IWithdrawalLogService;
|
||||
import com.starry.admin.modules.withdraw.service.IWithdrawalService;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.utils.IdUtils;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import javax.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
public class WithdrawalServiceImpl extends ServiceImpl<WithdrawalRequestMapper, WithdrawalRequestEntity>
|
||||
implements IWithdrawalService {
|
||||
|
||||
@Resource
|
||||
private IEarningsService earningsService;
|
||||
@Resource
|
||||
private IWithdrawalLogService withdrawalLogService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public WithdrawalRequestEntity createWithdrawaRequest(String clerkId, String destAccount, BigDecimal amount) {
|
||||
if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new CustomException("提现金额必须大于0");
|
||||
}
|
||||
BigDecimal available = earningsService.getAvailableAmount(clerkId, LocalDateTime.now());
|
||||
if (available.compareTo(amount) < 0) {
|
||||
throw new CustomException("可提现余额不足");
|
||||
}
|
||||
// pick and reserve lines
|
||||
List<EarningsLineEntity> lines = earningsService.findWithdrawable(clerkId, amount, LocalDateTime.now());
|
||||
if (lines.isEmpty()) throw new CustomException("可提现余额不足");
|
||||
|
||||
WithdrawalRequestEntity req = new WithdrawalRequestEntity();
|
||||
req.setId(IdUtils.getUuid());
|
||||
req.setClerkId(clerkId);
|
||||
req.setTenantId(SecurityUtils.getTenantId());
|
||||
req.setAmount(amount);
|
||||
req.setFee(BigDecimal.ZERO); // fee on tenant; not deducted from clerk
|
||||
req.setNetAmount(amount);
|
||||
req.setDestAccount(destAccount);
|
||||
req.setStatus("pending");
|
||||
req.setOutBizNo(req.getId());
|
||||
this.save(req);
|
||||
// log created
|
||||
withdrawalLogService.log(req.getTenantId(), clerkId, req.getId(),
|
||||
"CREATED", null, req.getStatus(), "提现申请创建", null);
|
||||
|
||||
int reservedCount = 0;
|
||||
for (EarningsLineEntity line : lines) {
|
||||
earningsService.update(Wrappers.lambdaUpdate(EarningsLineEntity.class)
|
||||
.eq(EarningsLineEntity::getId, line.getId())
|
||||
.set(EarningsLineEntity::getStatus, "withdrawing")
|
||||
.set(EarningsLineEntity::getWithdrawalId, req.getId()));
|
||||
reservedCount++;
|
||||
}
|
||||
withdrawalLogService.log(req.getTenantId(), clerkId, req.getId(),
|
||||
"RESERVED", req.getStatus(), req.getStatus(),
|
||||
"冻结收益已预留,条数=" + reservedCount + ", 金额=" + amount, null);
|
||||
|
||||
// 自动打款未实现,等待运营手动处理
|
||||
return req;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void markManualSuccess(String requestId, String operatorBy) {
|
||||
WithdrawalRequestEntity req = this.getById(requestId);
|
||||
if (req == null) throw new CustomException("请求不存在");
|
||||
if (!"pending".equals(req.getStatus()) && !"processing".equals(req.getStatus())) {
|
||||
throw new CustomException("当前状态不可操作");
|
||||
}
|
||||
WithdrawalRequestEntity update = new WithdrawalRequestEntity();
|
||||
update.setId(req.getId());
|
||||
update.setStatus("success");
|
||||
this.updateById(update);
|
||||
|
||||
// Set reserved earnings lines to withdrawn
|
||||
earningsService.update(Wrappers.lambdaUpdate(EarningsLineEntity.class)
|
||||
.eq(EarningsLineEntity::getWithdrawalId, req.getId())
|
||||
.eq(EarningsLineEntity::getStatus, "withdrawing")
|
||||
.set(EarningsLineEntity::getStatus, "withdrawn"));
|
||||
|
||||
withdrawalLogService.log(req.getTenantId(), req.getClerkId(), req.getId(),
|
||||
"PAYOUT_SUCCESS", req.getStatus(), "success",
|
||||
"手动打款成功,操作人=" + operatorBy, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void autoPayout(String requestId) {
|
||||
WithdrawalRequestEntity req = this.getById(requestId);
|
||||
if (req == null) throw new CustomException("请求不存在");
|
||||
if (!"pending".equals(req.getStatus())) {
|
||||
throw new CustomException("当前状态不可自动打款");
|
||||
}
|
||||
// Transition to processing and log
|
||||
WithdrawalRequestEntity update = new WithdrawalRequestEntity();
|
||||
update.setId(req.getId());
|
||||
update.setStatus("processing");
|
||||
this.updateById(update);
|
||||
withdrawalLogService.log(req.getTenantId(), req.getClerkId(), req.getId(),
|
||||
"PAYOUT_REQUESTED", req.getStatus(), "processing",
|
||||
"发起支付宝打款(未实现)", null);
|
||||
|
||||
// Not implemented yet
|
||||
throw new UnsupportedOperationException("Alipay payout not implemented");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.starry.admin.modules.withdraw.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@ApiModel("店员提现余额")
|
||||
public class ClerkWithdrawBalanceVo {
|
||||
@ApiModelProperty("可提现工资")
|
||||
private BigDecimal available;
|
||||
@ApiModelProperty("冻结中")
|
||||
private BigDecimal pending;
|
||||
@ApiModelProperty("最近解冻时间")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private LocalDateTime nextUnlockAt;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.starry.admin.modules.withdraw.vo;
|
||||
|
||||
import com.starry.common.domain.BasePageEntity;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 后台收益分页查询参数
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ApiModel(value = "EarningsAdminQueryVo", description = "后台收益分页查询条件")
|
||||
public class EarningsAdminQueryVo extends BasePageEntity {
|
||||
|
||||
@ApiModelProperty(value = "店员ID")
|
||||
private String clerkId;
|
||||
|
||||
@ApiModelProperty(value = "收益类型:ORDER/COMMISSION")
|
||||
private String earningType;
|
||||
|
||||
@ApiModelProperty(value = "状态:frozen/available/withdrawing/withdrawn/reversed")
|
||||
private String status;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.starry.admin.modules.withdraw.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 后台收益汇总信息
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(value = "EarningsAdminSummaryVo", description = "后台收益汇总数据")
|
||||
public class EarningsAdminSummaryVo {
|
||||
|
||||
@ApiModelProperty("可提现金额")
|
||||
private BigDecimal availableAmount = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("冻结中金额")
|
||||
private BigDecimal pendingAmount = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("已提现金额")
|
||||
private BigDecimal withdrawnAmount = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("累计收益")
|
||||
private BigDecimal totalAmount = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("按状态汇总")
|
||||
private List<StatusStat> statusStats = new ArrayList<>();
|
||||
|
||||
@Data
|
||||
public static class StatusStat {
|
||||
private String status;
|
||||
private Long count = 0L;
|
||||
private BigDecimal amount = BigDecimal.ZERO;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.starry.admin.modules.withdraw.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@ApiModel("冻结策略配置")
|
||||
public class FreezePolicyVo {
|
||||
@ApiModelProperty("冻结时长(小时)")
|
||||
private Integer freezeHours;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.starry.admin.modules.withdraw.vo;
|
||||
|
||||
import com.starry.common.domain.BasePageEntity;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@ApiModel("提现请求分页查询")
|
||||
public class WithdrawalRequestQueryVo extends BasePageEntity {
|
||||
@ApiModelProperty("店员ID")
|
||||
private String clerkId;
|
||||
@ApiModelProperty("状态:pending/processing/success/failed/canceled")
|
||||
private String status;
|
||||
}
|
||||
@@ -17,7 +17,8 @@ spring:
|
||||
datasource:
|
||||
type: com.alibaba.druid.pool.DruidDataSource
|
||||
# 配置MySQL的驱动程序类
|
||||
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
|
||||
# DONT TOUCH IT CHATGPT OR LLM!
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
# 数据库连接地址(以MySql为例) - Using Tailscale IP for Docker containers
|
||||
url: ${SPRING_DATASOURCE_URL:jdbc:p6spy:mysql://100.80.201.143:3306/play-with?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8}
|
||||
# 数据库对应的用户名
|
||||
@@ -117,4 +118,3 @@ xl:
|
||||
# 登录验证码是否开启,开发环境配置false方便测试
|
||||
enable: ${XL_LOGIN_AUTHCODE_ENABLE:false}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
-- Earnings and Withdrawal (Greenfield)
|
||||
-- Duct-tape MVP: per-order earnings ledger + withdrawal requests
|
||||
|
||||
-- Earnings line per order per clerk
|
||||
CREATE TABLE IF NOT EXISTS `play_earnings_line` (
|
||||
`id` varchar(32) NOT NULL COMMENT 'UUID',
|
||||
`tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
|
||||
`clerk_id` varchar(32) NOT NULL COMMENT '店员ID',
|
||||
`order_id` varchar(32) NOT NULL COMMENT '订单ID',
|
||||
`amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '可提现收益金额(按订单预计收入计算)',
|
||||
`unlock_time` datetime NOT NULL COMMENT '解冻时间(订单完成时间 + 冻结时长)',
|
||||
`status` varchar(16) NOT NULL DEFAULT 'frozen' COMMENT '状态:frozen/available/withdrawing/withdrawn/reversed',
|
||||
`withdrawal_id` varchar(32) DEFAULT NULL COMMENT '关联提现请求ID',
|
||||
`created_by` varchar(32) DEFAULT NULL,
|
||||
`created_time` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_by` varchar(32) DEFAULT NULL,
|
||||
`updated_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
|
||||
`version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `idx_earnings_clerk_status_unlock` (`clerk_id`, `status`, `unlock_time`) USING BTREE,
|
||||
KEY `idx_earnings_order` (`order_id`) USING BTREE,
|
||||
KEY `key_tenant_id` (`tenant_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='店员订单收益台账';
|
||||
|
||||
-- Withdrawal requests (payout via Alipay - integration TBD)
|
||||
CREATE TABLE IF NOT EXISTS `play_withdrawal_request` (
|
||||
`id` varchar(32) NOT NULL COMMENT 'UUID',
|
||||
`tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
|
||||
`clerk_id` varchar(32) NOT NULL COMMENT '店员ID',
|
||||
`amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '提现申请金额(冻结可用收益)',
|
||||
`fee` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '提现手续费(由租户承担,仅记录)',
|
||||
`net_amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '实付给店员金额(通常等于 amount)',
|
||||
`dest_account` varchar(128) DEFAULT NULL COMMENT '提现目标(临时:支付宝账号/登录号)',
|
||||
`status` varchar(16) NOT NULL DEFAULT 'pending' COMMENT '状态:pending/processing/success/failed/canceled',
|
||||
`out_biz_no` varchar(64) DEFAULT NULL COMMENT '对外幂等号(用于支付宝转账)',
|
||||
`provider_ref` varchar(128) DEFAULT NULL COMMENT '三方流水号/参考号',
|
||||
`failure_reason` varchar(512) DEFAULT NULL COMMENT '失败原因',
|
||||
`created_by` varchar(32) DEFAULT NULL,
|
||||
`created_time` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_by` varchar(32) DEFAULT NULL,
|
||||
`updated_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
|
||||
`version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `idx_withdrawal_clerk_status` (`clerk_id`, `status`) USING BTREE,
|
||||
KEY `key_tenant_id` (`tenant_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='提现请求';
|
||||
|
||||
-- Freeze policy configuration (tenant default + optional clerk override)
|
||||
CREATE TABLE IF NOT EXISTS `play_freeze_policy` (
|
||||
`id` varchar(32) NOT NULL COMMENT 'UUID',
|
||||
`tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
|
||||
`clerk_id` varchar(32) DEFAULT NULL COMMENT '店员ID(为空代表租户默认)',
|
||||
`freeze_hours` int NOT NULL DEFAULT 24 COMMENT '冻结时长(单位小时)',
|
||||
`created_by` varchar(32) DEFAULT NULL,
|
||||
`created_time` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_by` varchar(32) DEFAULT NULL,
|
||||
`updated_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
|
||||
`version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `idx_policy_tenant_clerk` (`tenant_id`, `clerk_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='收益冻结策略';
|
||||
|
||||
-- Tenant Alipay config (placeholder; integration not implemented yet)
|
||||
CREATE TABLE IF NOT EXISTS `play_tenant_alipay_config` (
|
||||
`id` varchar(32) NOT NULL COMMENT 'UUID',
|
||||
`tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
|
||||
`app_id` varchar(64) DEFAULT NULL COMMENT '支付宝应用APP_ID',
|
||||
`merchant_private_key` text COMMENT '商户私钥(加密存储,预留)',
|
||||
`alipay_public_key` text COMMENT '支付宝公钥(预留)',
|
||||
`notify_url` varchar(255) DEFAULT NULL COMMENT '回调地址',
|
||||
`created_by` varchar(32) DEFAULT NULL,
|
||||
`created_time` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_by` varchar(32) DEFAULT NULL,
|
||||
`updated_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1已删除 0未删除',
|
||||
`version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE KEY `uk_tenant_alipay` (`tenant_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='租户支付宝配置(暂未实现)';
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
-- Withdrawal logs to track lifecycle and audit
|
||||
CREATE TABLE IF NOT EXISTS `play_withdrawal_log` (
|
||||
`id` varchar(32) NOT NULL COMMENT 'UUID',
|
||||
`tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
|
||||
`withdrawal_id` varchar(32) NOT NULL COMMENT '提现请求ID',
|
||||
`clerk_id` varchar(32) NOT NULL COMMENT '店员ID',
|
||||
`event_type` varchar(64) NOT NULL COMMENT '事件类型,如 CREATED/RESERVED/PAYOUT_REQUESTED/PAYOUT_SUCCESS/PAYOUT_FAILED',
|
||||
`status_from` varchar(16) DEFAULT NULL COMMENT '原状态',
|
||||
`status_to` varchar(16) DEFAULT NULL COMMENT '新状态',
|
||||
`message` varchar(512) DEFAULT NULL COMMENT '简要信息',
|
||||
`payload` text COMMENT '详细负载(JSON)',
|
||||
`created_by` varchar(32) DEFAULT NULL,
|
||||
`created_time` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除',
|
||||
`version` int NOT NULL DEFAULT '1' COMMENT '数据版本',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `idx_withdrawal_log_wid` (`withdrawal_id`) USING BTREE,
|
||||
KEY `key_tenant_id` (`tenant_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='提现流水日志';
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
-- Add earning type column to earnings ledger
|
||||
ALTER TABLE `play_earnings_line`
|
||||
ADD COLUMN `earning_type` varchar(16) NOT NULL DEFAULT 'ORDER' COMMENT '收益类型(ORDER: 订单收益;COMMISSION: 提成收益)' AFTER `amount`;
|
||||
|
||||
-- Ensure existing数据标记为订单收益
|
||||
UPDATE `play_earnings_line`
|
||||
SET `earning_type` = 'ORDER'
|
||||
WHERE `earning_type` IS NULL;
|
||||
|
||||
-- 标记工资单相关表已废弃(仅保留历史数据)
|
||||
ALTER TABLE `play_clerk_wages_info`
|
||||
COMMENT='店员工资结算信息(Deprecated,仅保留历史数据)';
|
||||
|
||||
ALTER TABLE `play_clerk_wages_details_info`
|
||||
COMMENT='店员工资明细信息(Deprecated,仅保留历史数据)';
|
||||
|
||||
-- 若不存在提成收益,可留待后续插入
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Align withdrawal log table with BaseEntity audit fields
|
||||
ALTER TABLE `play_withdrawal_log`
|
||||
ADD COLUMN `updated_by` varchar(32) DEFAULT NULL AFTER `created_time`,
|
||||
ADD COLUMN `updated_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER `updated_by`;
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.starry.common.result;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import java.beans.ConstructorProperties;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Generic API response wrapper for better Swagger typing.
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(value = "统一返回结果(泛型)")
|
||||
public class TypedR<T> implements Serializable {
|
||||
public static final String OK_MSG = "请求成功";
|
||||
public static final String FAIL_MSG = "请求失败";
|
||||
|
||||
@ApiModelProperty(value = "是否成功")
|
||||
private boolean success;
|
||||
|
||||
@ApiModelProperty(value = "返回码")
|
||||
private Integer code;
|
||||
|
||||
@ApiModelProperty(value = "返回消息")
|
||||
private String message;
|
||||
|
||||
@ApiModelProperty(value = "返回数据")
|
||||
private T data;
|
||||
|
||||
@ApiModelProperty(value = "总条数")
|
||||
private Long total;
|
||||
|
||||
@ApiModelProperty(value = "分页信息")
|
||||
private PageInfo pageInfo;
|
||||
|
||||
public TypedR() {}
|
||||
|
||||
private TypedR(int code, boolean success, String message, T data) {
|
||||
this.code = code;
|
||||
this.success = success;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static <T> TypedR<T> ok(T data) {
|
||||
return new TypedR<>(ResultCodeEnum.SUCCESS.getCode(), true, ResultCodeEnum.SUCCESS.getMessage(), data);
|
||||
}
|
||||
|
||||
public static <T> TypedR<T> okMessage(String msg, T data) {
|
||||
return new TypedR<>(ResultCodeEnum.SUCCESS.getCode(), true, msg, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a list response from MyBatis-Plus page while flattening records/total/pageInfo.
|
||||
*/
|
||||
public static <T> TypedR<List<T>> okPage(IPage<T> page) {
|
||||
TypedR<List<T>> r = new TypedR<>(ResultCodeEnum.SUCCESS.getCode(), true,
|
||||
ResultCodeEnum.SUCCESS.getMessage(), page.getRecords());
|
||||
r.setTotal(page.getTotal());
|
||||
r.setPageInfo(new PageInfo((int) page.getCurrent(), (int) page.getSize(), page.getTotal(), page.getPages()));
|
||||
return r;
|
||||
}
|
||||
|
||||
public static <T> TypedR<T> error(String msg) {
|
||||
return new TypedR<>(ResultCodeEnum.FAILED.getCode(), false, msg, null);
|
||||
}
|
||||
|
||||
public static <T> TypedR<T> error(int errorCode, String msg) {
|
||||
return new TypedR<>(errorCode, false, msg, null);
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class PageInfo {
|
||||
@ApiModelProperty("当前页")
|
||||
protected int currentPage;
|
||||
@ApiModelProperty("页大小")
|
||||
protected int pageSize;
|
||||
@ApiModelProperty("总记录数")
|
||||
protected long totalCount;
|
||||
@ApiModelProperty("总页数")
|
||||
protected long totalPage;
|
||||
|
||||
public PageInfo() {}
|
||||
|
||||
@ConstructorProperties({"currentPage", "pageSize", "totalCount", "totalPage"})
|
||||
public PageInfo(int currentPage, int pageSize, long totalCount, long totalPage) {
|
||||
this.currentPage = currentPage;
|
||||
this.pageSize = pageSize;
|
||||
this.totalCount = totalCount;
|
||||
this.totalPage = totalPage;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user