Browse Source

对接拼团数据1

master
wangfukang 1 month ago
parent
commit
1241177676
  1. 16
      hiver-admin/test-output/test-report.html
  2. 158
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/AdminDataStatisticsController.java
  3. 14
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/AdminSettlementController.java
  4. 18
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/MallRefundRecordMapper.java
  5. 9
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/WorkerDailyBalanceRecordMapper.java
  6. 46
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/WorkerDailyBalanceRecord.java
  7. 83
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/quartz/MerchantSettlementTask.java
  8. 7
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/WorkerDailyBalanceRecordService.java
  9. 91
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallDeliveryOrderServiceImpl.java
  10. 140
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallRefundRecordServiceImpl.java
  11. 12
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/WorkerDailyBalanceRecordServiceImpl.java
  12. 15
      hiver-modules/hiver-mall/src/main/resources/db/worker_daily_balance_record.sql

16
hiver-admin/test-output/test-report.html

@ -35,7 +35,7 @@
<a href="#"><span class="badge badge-primary">Hiver</span></a>
</li>
<li class="m-r-10">
<a href="#"><span class="badge badge-primary">八月 13, 2026 11:52:38</span></a>
<a href="#"><span class="badge badge-primary">八月 16, 2026 18:06:44</span></a>
</li>
</ul>
</div>
@ -84,7 +84,7 @@
<div class="test-detail">
<span class="meta text-white badge badge-sm"></span>
<p class="name">passTest</p>
<p class="text-sm"><span>11:52:38 上</span> / <span>0.033 secs</span></p>
<p class="text-sm"><span>18:06:45 下</span> / <span>0.022 secs</span></p>
</div>
<div class="test-contents d-none">
<div class="detail-head">
@ -92,9 +92,9 @@
<div class="info">
<div class='float-right'><span class='badge badge-default'>#test-id=1</span></div>
<h5 class="test-status text-pass">passTest</h5>
<span class='badge badge-success'>08.13.2026 11:52:38</span>
<span class='badge badge-danger'>08.13.2026 11:52:39</span>
<span class='badge badge-default'>0.033 secs</span>
<span class='badge badge-success'>08.16.2026 18:06:45</span>
<span class='badge badge-danger'>08.16.2026 18:06:45</span>
<span class='badge badge-default'>0.022 secs</span>
</div>
<div class="m-t-10 m-l-5"></div>
</div>
@ -104,7 +104,7 @@
<tbody>
<tr class="event-row">
<td><span class="badge log pass-bg">Pass</span></td>
<td>11:52:39</td>
<td>18:06:45</td>
<td>
Test passed
</td>
@ -128,13 +128,13 @@
<div class="col-md-3">
<div class="card"><div class="card-body">
<p class="m-b-0">Started</p>
<h3>八月 13, 2026 11:52:38</h3>
<h3>八月 16, 2026 18:06:44</h3>
</div></div>
</div>
<div class="col-md-3">
<div class="card"><div class="card-body">
<p class="m-b-0">Ended</p>
<h3>八月 13, 2026 11:52:39</h3>
<h3>八月 16, 2026 18:06:45</h3>
</div></div>
</div>
<div class="col-md-3">

158
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/AdminDataStatisticsController.java

@ -6,11 +6,14 @@ import cc.hiver.mall.billing.entity.BillingUsageRecord;
import cc.hiver.mall.billing.mapper.BillingUsageRecordMapper;
import cc.hiver.mall.dao.mapper.*;
import cc.hiver.mall.entity.*;
import cc.hiver.mall.service.mybatis.WorkerDailyBalanceRecordService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
@ -29,7 +32,9 @@ import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@Slf4j
@RestController
@Api(tags = "后台管理系统数据统计接口")
@RequestMapping("/hiver/mall/admin/dataStatistics")
@ -59,6 +64,8 @@ public class AdminDataStatisticsController {
private BillingUsageRecordMapper billingUsageRecordMapper;
@Autowired
private MallSettlementRecordMapper mallSettlementRecordMapper;
@Autowired
private WorkerDailyBalanceRecordService workerDailyBalanceRecordService;
@Data
public static class DataStatisticsQuery {
@ -87,7 +94,10 @@ public class AdminDataStatisticsController {
private Boolean actualIncomeAvailable;
private BigDecimal settledAmount;
private BigDecimal headquartersDeductionAmount;
private BigDecimal wechatReceiptAmount;
private BigDecimal wechatFeeAmount;
private BigDecimal deliveryFeeAmount;
private Long workerDebtCount;
private Long smsCount;
private Long contentAuditCount;
private Long realNameAuthCount;
@ -101,6 +111,14 @@ public class AdminDataStatisticsController {
private Long orderUserCount;
}
@Data
public static class WorkerDebtQuery extends DataStatisticsQuery {
@ApiModelProperty(value = "页码")
private int pageNumber = 1;
@ApiModelProperty(value = "每页条数")
private int pageSize = 10;
}
@PostMapping("/summary")
@ApiOperation(value = "按校区和日期范围获取数据统计汇总")
public Result<DataStatisticsSummaryVo> summary(@RequestBody DataStatisticsQuery query) {
@ -143,12 +161,16 @@ public class AdminDataStatisticsController {
.ge(BillingUsageRecord::getBillingDate, range.startDate)
.le(BillingUsageRecord::getBillingDate, range.endDate));
List<WorkerDailyBalanceRecord> workerBalanceRecords = workerDailyBalanceRecordService.list(new LambdaQueryWrapper<WorkerDailyBalanceRecord>()
.eq(StringUtils.isNotBlank(regionId), WorkerDailyBalanceRecord::getRegionId, regionId)
.ge(WorkerDailyBalanceRecord::getBalanceDate, range.startDate)
.le(WorkerDailyBalanceRecord::getBalanceDate, range.endDate));
DataStatisticsSummaryVo vo = new DataStatisticsSummaryVo();
vo.setStartDate(range.startDate.toString());
vo.setEndDate(range.endDate.toString());
Set<String> refundedOrderIds = new HashSet<>();
Set<String> deliveryRefundOrderIds = new HashSet<>();
for (MallRefundRecord refund : refunds) {
if (StringUtils.isBlank(refund.getOrderId())) {
continue;
@ -156,24 +178,12 @@ public class AdminDataStatisticsController {
if (refund.getStatus() != null && refund.getStatus() == 1) {
refundedOrderIds.add(refund.getOrderId());
}
if (refund.getRefundTypeStatus() != null && (refund.getRefundTypeStatus() == 2 || refund.getRefundTypeStatus() == 3 || refund.getRefundTypeStatus() == 4)) {
deliveryRefundOrderIds.add(refund.getOrderId());
}
}
BigDecimal goodsAndPackage = BigDecimal.ZERO;
BigDecimal grossDeliveryFee = BigDecimal.ZERO;
BigDecimal settlementDeliveryFee = BigDecimal.ZERO;
BigDecimal koiDiscount = BigDecimal.ZERO;
Set<String> shopIds = new HashSet<>();
Set<String> userIds = new HashSet<>();
for (MallOrder order : orders) {
goodsAndPackage = goodsAndPackage.add(amount(order.getGoodsAmount())).add(amount(order.getPackageFee()));
BigDecimal orderDeliveryFee = amount(order.getDeliveryFee());
grossDeliveryFee = grossDeliveryFee.add(orderDeliveryFee);
if (!deliveryRefundOrderIds.contains(order.getId())) {
settlementDeliveryFee = settlementDeliveryFee.add(orderDeliveryFee);
}
if (!isRefundedOrder(order, refundedOrderIds)) {
koiDiscount = koiDiscount.add(amount(order.getFreeAmount()));
}
@ -184,13 +194,50 @@ public class AdminDataStatisticsController {
userIds.add(order.getUserId());
}
}
BigDecimal deliveryFeeAmount = subtractWechatFee(settlementDeliveryFee);
BigDecimal grossFlow = subtractWechatFee(goodsAndPackage.add(grossDeliveryFee));
/*
* 原配送费总额逻辑从订单配送费反推并按退款/售后平台追加配送费未取货全退等规则修正
* 现改为查询 worker_daily_balance_record累加所选日期内 daily_balance > 0 的余额快照
*
* BigDecimal settlementDeliveryFee = BigDecimal.ZERO;
* Set<String> settlementDeliveryOrderIds = new HashSet<>();
* for (MallOrder order : orders) {
* BigDecimal orderDeliveryFee = amount(order.getDeliveryFee());
* if (!deliveryRefundOrderIds.contains(order.getId())) {
* settlementDeliveryFee = settlementDeliveryFee.add(orderDeliveryFee);
* if (StringUtils.isNotBlank(order.getId())) {
* settlementDeliveryOrderIds.add(order.getId());
* }
* }
* }
* if (!settlementDeliveryOrderIds.isEmpty()) {
* List<MallDeliveryOrder> settlementDeliveries = mallDeliveryOrderMapper.selectList(new LambdaQueryWrapper<MallDeliveryOrder>()
* .eq(StringUtils.isNotBlank(regionId), MallDeliveryOrder::getRegionId, regionId)
* .in(MallDeliveryOrder::getOrderId, settlementDeliveryOrderIds));
* BigDecimal marketplaceDeliveryFee = settlementDeliveries.stream()
* .map(MallDeliveryOrder::getDeliveryFeeMarketplace)
* .map(this::amount)
* .reduce(BigDecimal.ZERO, BigDecimal::add);
* BigDecimal unpickedMerchantFullRefundDeliveryFee = settlementDeliveries.stream()
* .filter(item -> merchantFullRefundOrderIds.contains(item.getOrderId()))
* .filter(item -> item.getGetTime() == null)
* .map(this::deliverySettlementAmount)
* .reduce(BigDecimal.ZERO, BigDecimal::add);
* settlementDeliveryFee = settlementDeliveryFee.add(marketplaceDeliveryFee).subtract(unpickedMerchantFullRefundDeliveryFee);
* }
* BigDecimal deliveryFeeAmount = settlementDeliveryFee;
*/
BigDecimal deliveryFeeAmount = sumPositiveWorkerBalance(workerBalanceRecords);
long workerDebtCount = workerBalanceRecords.stream()
.filter(item -> amount(item.getDailyBalance()).compareTo(BigDecimal.ZERO) < 0)
.count();
BigDecimal grossFlow = orders.stream()
.map(MallOrder::getTotalAmount)
.map(this::amount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal refundAmount = sumRefundAmount(refunds, 1);
BigDecimal afterSaleAmount = sumRefundAmount(refunds, 4);
BigDecimal effectiveFlow = subtractWechatFee(goodsAndPackage);
BigDecimal headquartersDeduction = effectiveFlow.multiply(ONE_PERCENT);
BigDecimal effectiveFlow = grossFlow.subtract(refundAmount).subtract(afterSaleAmount);
BigDecimal headquartersDeduction = effectiveFlow.subtract(deliveryFeeAmount).multiply(ONE_PERCENT);
BigDecimal collegeRankReward = sumDealingsAmount(rewardRecords, REWARD_COLLEGE_RANK);
BigDecimal deliveryRankReward = sumDealingsAmount(rewardRecords, REWARD_DELIVERY_RANK);
@ -199,6 +246,8 @@ public class AdminDataStatisticsController {
.map(this::amount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal drawPoolAmount = sumDealingsAmount(rewardRecords, REWARD_DRAW_POOL);
BigDecimal wechatReceiptAmount = effectiveFlow;
BigDecimal wechatFee = wechatReceiptAmount.multiply(WECHAT_FEE_RATE);
long smsCount = billingRecords.stream().filter(item -> "sms".equals(item.getRecordType())).count();
long contentAuditCount = billingRecords.stream().filter(item -> "content_audit".equals(item.getRecordType())).count();
@ -209,13 +258,10 @@ public class AdminDataStatisticsController {
BigDecimal rewardTotalAmount = collegeRankReward.add(drawPoolAmount).add(deliveryRankReward);
boolean actualIncomeAvailable = isActualIncomeAvailable(range);
BigDecimal settledAmount = actualIncomeAvailable ? sumSettledAmount(regionId, range) : BigDecimal.ZERO;
BigDecimal actualIncome = grossFlow
BigDecimal actualIncome = wechatReceiptAmount
.subtract(deliveryFeeAmount)
.subtract(headquartersDeduction)
.subtract(koiDiscount)
.subtract(refundAmount)
.subtract(afterSaleAmount)
.subtract(couponDiscount)
.subtract(wechatFee)
.subtract(smsCost)
.subtract(contentAuditCost)
.subtract(realNameCost)
@ -225,14 +271,17 @@ public class AdminDataStatisticsController {
vo.setOrderCount((long) orders.size());
vo.setGrossFlowAmount(money(grossFlow));
vo.setDeliveryFeeAmount(money(deliveryFeeAmount));
vo.setWorkerDebtCount(workerDebtCount);
vo.setRefundAmount(money(refundAmount));
vo.setAfterSaleAmount(money(afterSaleAmount));
vo.setEffectiveFlowAmount(money(effectiveFlow));
vo.setKoiDiscountAmount(money(koiDiscount));
vo.setCouponDiscountAmount(money(couponDiscount));
vo.setOnTimeRate(onTimeRate(deliveries));
vo.setCustomerAverageAmount(orders.isEmpty() ? BigDecimal.ZERO : money(goodsAndPackage.divide(BigDecimal.valueOf(orders.size()), 4, RoundingMode.HALF_UP)));
vo.setCustomerAverageAmount(orders.isEmpty() ? BigDecimal.ZERO : money(grossFlow.divide(BigDecimal.valueOf(orders.size()), 4, RoundingMode.HALF_UP)));
vo.setHeadquartersDeductionAmount(money(headquartersDeduction));
vo.setWechatReceiptAmount(money(wechatReceiptAmount));
vo.setWechatFeeAmount(money(wechatFee));
vo.setActualIncomeAmount(actualIncomeAvailable ? money(actualIncome) : BigDecimal.ZERO);
vo.setActualIncomeAvailable(actualIncomeAvailable);
vo.setSettledAmount(money(settledAmount));
@ -250,6 +299,31 @@ public class AdminDataStatisticsController {
return new ResultUtil<DataStatisticsSummaryVo>().setData(vo);
}
@PostMapping("/workerDebtPage")
@ApiOperation(value = "分页查询欠款配送员每日余额记录")
public Result<Page<WorkerDailyBalanceRecord>> workerDebtPage(@RequestBody WorkerDebtQuery query) {
DateRange range = resolveDateRange(query);
String regionId = query == null ? null : query.getRegionId();
Page<WorkerDailyBalanceRecord> page = workerDailyBalanceRecordService.page(
new Page<>(pageNumber(query), pageSize(query)),
new LambdaQueryWrapper<WorkerDailyBalanceRecord>()
.eq(StringUtils.isNotBlank(regionId), WorkerDailyBalanceRecord::getRegionId, regionId)
.ge(WorkerDailyBalanceRecord::getBalanceDate, range.startDate)
.le(WorkerDailyBalanceRecord::getBalanceDate, range.endDate)
.lt(WorkerDailyBalanceRecord::getDailyBalance, BigDecimal.ZERO)
.orderByAsc(WorkerDailyBalanceRecord::getBalanceDate)
.orderByAsc(WorkerDailyBalanceRecord::getWorkerName));
return new ResultUtil<Page<WorkerDailyBalanceRecord>>().setData(page);
}
private BigDecimal sumPositiveWorkerBalance(List<WorkerDailyBalanceRecord> records) {
return records.stream()
.map(WorkerDailyBalanceRecord::getDailyBalance)
.map(this::amount)
.filter(amount -> amount.compareTo(BigDecimal.ZERO) > 0)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
private BigDecimal sumRefundAmount(List<MallRefundRecord> refunds, int status) {
return refunds.stream()
.filter(item -> item.getStatus() != null && item.getStatus() == status)
@ -258,6 +332,13 @@ public class AdminDataStatisticsController {
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
private BigDecimal deliverySettlementAmount(MallDeliveryOrder delivery) {
if (delivery == null) {
return BigDecimal.ZERO;
}
return amount(delivery.getDeliveryFee()).add(amount(delivery.getDeliveryFeeMarketplace()));
}
private BigDecimal sumDealingsAmount(List<DealingsRecord> records, String dealingsWay) {
return records.stream()
.filter(item -> dealingsWay.equals(item.getDealingsWay()))
@ -266,18 +347,39 @@ public class AdminDataStatisticsController {
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
private void printSettlementDeliveryOrderIds(Set<String> orderIds) {
if (orderIds == null || orderIds.isEmpty()) {
log.info("数据统计结算配送订单ID为空");
return;
}
String orderIdSqlIn = orderIds.stream()
.filter(StringUtils::isNotBlank)
.collect(Collectors.joining("','", "'", "'"));
log.info("数据统计结算配送订单ID, count={}, sql=select * from mall_delivery_order where order_id in ({});", orderIds.size(), orderIdSqlIn);
}
private BigDecimal sumSettledAmount(String regionId, DateRange range) {
Date settlementStart = Date.from(range.start.toInstant().plus(1, ChronoUnit.DAYS));
Date settlementEnd = Date.from(range.end.toInstant().plus(1, ChronoUnit.DAYS));
List<MallSettlementRecord> settlementRecords = mallSettlementRecordMapper.selectList(new LambdaQueryWrapper<MallSettlementRecord>()
.eq(StringUtils.isNotBlank(regionId), MallSettlementRecord::getRegionId, regionId)
.eq(MallSettlementRecord::getStatus, 1)
.ge(MallSettlementRecord::getCreateTime, range.start)
.le(MallSettlementRecord::getCreateTime, range.end));
.ge(MallSettlementRecord::getCreateTime, settlementStart)
.le(MallSettlementRecord::getCreateTime, settlementEnd));
return settlementRecords.stream()
.map(MallSettlementRecord::getSettlementAmount)
.map(this::amount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
private int pageNumber(WorkerDebtQuery query) {
return query == null ? 1 : Math.max(query.getPageNumber(), 1);
}
private int pageSize(WorkerDebtQuery query) {
return query == null ? 10 : Math.max(query.getPageSize(), 10);
}
private boolean isRefundedOrder(MallOrder order, Set<String> refundedOrderIds) {
return (order.getStatus() != null && order.getStatus() == 8)
|| refundedOrderIds.contains(order.getId());
@ -301,10 +403,6 @@ public class AdminDataStatisticsController {
.divide(BigDecimal.valueOf(deliveries.size()), 2, RoundingMode.HALF_UP);
}
private BigDecimal subtractWechatFee(BigDecimal value) {
return amount(value).multiply(BigDecimal.ONE.subtract(WECHAT_FEE_RATE));
}
private BigDecimal amount(BigDecimal value) {
return value == null ? BigDecimal.ZERO : value;
}

14
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/AdminSettlementController.java

@ -155,7 +155,7 @@ public class AdminSettlementController {
QueryWrapper<MallSettlementRecord> qw = new QueryWrapper<>();
qw.select("shop_id",
"COALESCE(SUM(base_amount), 0) AS total_base_amount",
"COALESCE(SUM(commission_amount), 0) AS total_commission_amount",
"COALESCE(SUM(CASE WHEN type = 2 THEN -ABS(commission_amount) ELSE commission_amount END), 0) AS total_commission_amount",
"COALESCE(SUM(settlement_amount), 0) AS total_settlement_amount",
"COUNT(1) AS record_count",
"SUM(CASE WHEN type = 2 THEN 0 ELSE 1 END) AS positive_count",
@ -246,7 +246,7 @@ public class AdminSettlementController {
"COUNT(DISTINCT shop_id) AS shop_count",
"COUNT(1) AS record_count",
"COALESCE(SUM(base_amount), 0) AS total_base_amount",
"COALESCE(SUM(commission_amount), 0) AS total_commission_amount",
"COALESCE(SUM(CASE WHEN type = 2 THEN -ABS(commission_amount) ELSE commission_amount END), 0) AS total_commission_amount",
"COALESCE(SUM(settlement_amount), 0) AS total_settlement_amount",
"COALESCE(SUM(CASE WHEN type = 2 THEN 0 ELSE settlement_amount END), 0) AS positive_settlement_amount",
"COALESCE(SUM(CASE WHEN type = 2 THEN settlement_amount ELSE 0 END), 0) AS negative_settlement_amount");
@ -342,6 +342,7 @@ public class AdminSettlementController {
record.setOrderNumber(order.getNumberCode());
record.setPayTime(order.getPayTime());
}
applyDisplayCommissionAmount(record);
}
return new ResultUtil<Page<MallSettlementRecord>>().setData(page);
}
@ -375,6 +376,15 @@ public class AdminSettlementController {
return value == null ? 0 : Integer.parseInt(String.valueOf(value));
}
private void applyDisplayCommissionAmount(MallSettlementRecord record) {
if (record == null
|| !Integer.valueOf(2).equals(record.getType())
|| record.getCommissionAmount() == null) {
return;
}
record.setCommissionAmount(record.getCommissionAmount().abs().negate());
}
@Data
public static class ConfirmShopReq {
@ApiModelProperty(value = "选中的商家ID列表")

18
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/MallRefundRecordMapper.java

@ -5,12 +5,30 @@ import cc.hiver.mall.pojo.query.MallRefundRecordPageQuery;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.springframework.stereotype.Repository;
import java.math.BigDecimal;
import java.util.Map;
/**
* 退款记录 Mapper 接口
*/
@Repository
public interface MallRefundRecordMapper extends BaseMapper<MallRefundRecord> {
IPage<MallRefundRecord> selectPageVO(IPage<?> page, @Param("q") MallRefundRecordPageQuery q);
@Update("UPDATE t_worker "
+ "SET depo_bal_rel = CASE "
+ "WHEN COALESCE(depo_bal, 0) >= #{amount} THEN COALESCE(depo_bal_rel, 0) "
+ "ELSE COALESCE(depo_bal_rel, 0) - (#{amount} - COALESCE(depo_bal, 0)) END, "
+ "depo_bal = CASE "
+ "WHEN COALESCE(depo_bal, 0) >= #{amount} THEN COALESCE(depo_bal, 0) - #{amount} "
+ "ELSE 0 END "
+ "WHERE worker_id = #{workerId}")
int deductWorkerBalanceAtomic(@Param("workerId") String workerId, @Param("amount") BigDecimal amount);
@Select("SELECT depo_bal AS depoBal, depo_bal_rel AS depoBalRel FROM t_worker WHERE worker_id = #{workerId}")
Map<String, Object> selectWorkerBalanceForDebug(@Param("workerId") String workerId);
}

9
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/WorkerDailyBalanceRecordMapper.java

@ -0,0 +1,9 @@
package cc.hiver.mall.dao.mapper;
import cc.hiver.mall.entity.WorkerDailyBalanceRecord;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
@Repository
public interface WorkerDailyBalanceRecordMapper extends BaseMapper<WorkerDailyBalanceRecord> {
}

46
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/WorkerDailyBalanceRecord.java

@ -0,0 +1,46 @@
package cc.hiver.mall.entity;
import cc.hiver.core.common.utils.SnowFlakeUtil;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Date;
@Data
@TableName("worker_daily_balance_record")
@ApiModel(value = "配送员每日余额记录")
public class WorkerDailyBalanceRecord {
@TableId
@ApiModelProperty(value = "主键")
private String id = SnowFlakeUtil.nextId().toString();
@ApiModelProperty(value = "校区/学校ID")
private String regionId;
@ApiModelProperty(value = "余额日期")
private LocalDate balanceDate;
@ApiModelProperty(value = "配送员ID")
private String workerId;
@ApiModelProperty(value = "配送员姓名")
private String workerName;
@ApiModelProperty(value = "手机号")
private String mobile;
@ApiModelProperty(value = "昨日佣金余额,保留正负数")
private BigDecimal dailyBalance;
@ApiModelProperty(value = "变更后可提现余额")
private BigDecimal availableBalanceAfter;
@ApiModelProperty(value = "创建时间")
private Date createTime;
}

83
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/quartz/MerchantSettlementTask.java

@ -3,15 +3,13 @@ package cc.hiver.mall.quartz;
import cc.hiver.core.entity.Worker;
import cc.hiver.core.serviceimpl.WorkerServiceImpl;
import cc.hiver.mall.dao.mapper.MallReturnOrderGoodsMapper;
import cc.hiver.mall.entity.MallOrder;
import cc.hiver.mall.entity.MallRefundRecord;
import cc.hiver.mall.entity.MallSettlementRecord;
import cc.hiver.mall.entity.ShopTakeaway;
import cc.hiver.mall.entity.*;
import cc.hiver.mall.service.ShopService;
import cc.hiver.mall.service.ShopTakeawayService;
import cc.hiver.mall.service.mybatis.MallOrderService;
import cc.hiver.mall.service.mybatis.MallRefundRecordService;
import cc.hiver.mall.service.mybatis.MallSettlementRecordService;
import cc.hiver.mall.service.mybatis.WorkerDailyBalanceRecordService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@ -19,6 +17,7 @@ import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Date;
import java.util.List;
@ -39,6 +38,8 @@ public class MerchantSettlementTask {
private WorkerServiceImpl workerServiceImpl;
@Autowired
private ShopService shopService;
@Autowired
private WorkerDailyBalanceRecordService workerDailyBalanceRecordService;
@Scheduled(cron = "0 0 2 * * ?")
public void executeSettlement() {
@ -83,11 +84,17 @@ public class MerchantSettlementTask {
// 配送员余额
try {
List<Worker> workers = workerServiceImpl.getAll();
LocalDate balanceDate = LocalDate.now().minusDays(1);
for (Worker worker : workers) {
try {
if(worker.getWorkerStatus() != null && worker.getWorkerStatus() != 3 && worker.getDepoBal().signum() > 0){
worker.setDepoBalRel(worker.getDepoBal().add(worker.getDepoBalRel()));
try {
saveWorkerDailyBalanceRecord(worker, balanceDate);
} catch (Exception e) {
log.error("[MerchantSettlementTask] Failed to save worker daily balance record {}", worker.getWorkerId(), e);
}
if(worker.getWorkerStatus() != null && worker.getWorkerStatus() != 3 && worker.getDepoBal() != null && worker.getDepoBal().signum() != 0){
worker.setDepoBalRel(worker.getDepoBal().add(safeAmount(worker.getDepoBalRel())));
worker.setDepoBal(BigDecimal.ZERO);
workerServiceImpl.update(worker);
}
@ -102,6 +109,34 @@ public class MerchantSettlementTask {
log.info("[MerchantSettlementTask] Daily settlement calculation finished.");
}
private void saveWorkerDailyBalanceRecord(Worker worker, LocalDate balanceDate) {
if (worker == null
|| worker.getWorkerStatus() == null
|| worker.getWorkerStatus() == 3
|| worker.getDepoBal() == null
|| worker.getDepoBal().signum() == 0) {
return;
}
BigDecimal availableBalanceAfter = safeAmount(worker.getDepoBalRel()).add(worker.getDepoBal());
LambdaQueryWrapper<WorkerDailyBalanceRecord> qw = new LambdaQueryWrapper<>();
qw.eq(WorkerDailyBalanceRecord::getBalanceDate, balanceDate);
qw.eq(WorkerDailyBalanceRecord::getWorkerId, worker.getWorkerId());
WorkerDailyBalanceRecord record = workerDailyBalanceRecordService.getOne(qw, false);
if (record == null) {
record = new WorkerDailyBalanceRecord();
record.setCreateTime(new Date());
}
record.setRegionId(worker.getRegion());
record.setBalanceDate(balanceDate);
record.setWorkerId(worker.getWorkerId());
record.setWorkerName(worker.getWorkerName());
record.setMobile(worker.getMobile());
record.setDailyBalance(worker.getDepoBal());
record.setAvailableBalanceAfter(availableBalanceAfter);
workerDailyBalanceRecordService.saveOrUpdate(record);
}
private void processUnsettledOrder(MallOrder order) {
if (!isMerchantSettlementOrder(order)) {
return;
@ -113,7 +148,8 @@ public class MerchantSettlementTask {
long count = mallSettlementRecordService.count(existsQw);
if (count > 0) return; // Skip if already pending confirmation
saveMerchantSettlement(order, null, 1, goodsPackageAmount(order), false);
saveMerchantSettlement(order, null, 1, goodsPackageAmount(order), false,
hasApprovedWorkerFullRefundOrAfterSale(order));
}
private void processApprovedRefund(MallRefundRecord refund) {
@ -153,7 +189,8 @@ public class MerchantSettlementTask {
return;
}
if (Integer.valueOf(2).equals(refund.getRefundTypeStatus()) && isWorkerRecord(refund)) {
saveMerchantSettlement(order, refund.getId(), 1, goodsPackageAmount(order), false);
saveMerchantSettlement(order, refund.getId(), 1, goodsPackageAmount(order), false,
isWorkerFullRefundOrAfterSale(refund));
} else if (Integer.valueOf(1).equals(refund.getRefundTypeStatus()) && isMerchantDeliveryOrder(order) && !isWorkerRecord(refund)) {
BigDecimal deliveryResponsibilityAmount = merchantDeliveryResponsibilityAmount(order, refund);
saveMerchantSettlement(order, refund.getId(), 2, deliveryResponsibilityAmount, true);
@ -161,6 +198,11 @@ public class MerchantSettlementTask {
}
private void saveMerchantSettlement(MallOrder order, String refundId, Integer type, BigDecimal baseAmount, boolean deliveryFeeIncluded) {
saveMerchantSettlement(order, refundId, type, baseAmount, deliveryFeeIncluded, false);
}
private void saveMerchantSettlement(MallOrder order, String refundId, Integer type, BigDecimal baseAmount,
boolean deliveryFeeIncluded, boolean commissionWaived) {
if (baseAmount == null || baseAmount.compareTo(BigDecimal.ZERO) <= 0 || existsSettlement(refundId, type, order.getId())) {
return;
}
@ -169,8 +211,9 @@ public class MerchantSettlementTask {
if (commissionBase.compareTo(BigDecimal.ZERO) < 0) {
commissionBase = BigDecimal.ZERO;
}
BigDecimal actualCommission = commissionBase.multiply(rateDecimal(commissionRate)).setScale(2, BigDecimal.ROUND_HALF_UP);
BigDecimal settlementAmount = baseAmount.subtract(actualCommission);
BigDecimal calculatedCommission = commissionBase.multiply(rateDecimal(commissionRate)).setScale(2, BigDecimal.ROUND_HALF_UP);
BigDecimal actualCommission = commissionWaived ? BigDecimal.ZERO : calculatedCommission;
BigDecimal settlementAmount = baseAmount.subtract(calculatedCommission);
if (type != null && type == 2) {
settlementAmount = settlementAmount.negate();
}
@ -286,4 +329,24 @@ public class MerchantSettlementTask {
private boolean isWorkerRecord(MallRefundRecord refund) {
return refund != null && refund.getLinkId() != null && refund.getLinkId().toUpperCase().startsWith("W");
}
private boolean isWorkerFullRefundOrAfterSale(MallRefundRecord refund) {
return refund != null
&& Integer.valueOf(3).equals(refund.getRefundType())
&& Integer.valueOf(2).equals(refund.getRefundTypeStatus())
&& isWorkerRecord(refund);
}
private boolean hasApprovedWorkerFullRefundOrAfterSale(MallOrder order) {
if (order == null || order.getId() == null) {
return false;
}
LambdaQueryWrapper<MallRefundRecord> refundQw = new LambdaQueryWrapper<>();
refundQw.eq(MallRefundRecord::getOrderId, order.getId());
refundQw.in(MallRefundRecord::getStatus, 1, 4);
refundQw.eq(MallRefundRecord::getRefundType, 3);
refundQw.eq(MallRefundRecord::getRefundTypeStatus, 2);
refundQw.apply("UPPER(link_id) LIKE {0}", "W%");
return mallRefundRecordService.count(refundQw) > 0;
}
}

7
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/WorkerDailyBalanceRecordService.java

@ -0,0 +1,7 @@
package cc.hiver.mall.service.mybatis;
import cc.hiver.mall.entity.WorkerDailyBalanceRecord;
import com.baomidou.mybatisplus.extension.service.IService;
public interface WorkerDailyBalanceRecordService extends IService<WorkerDailyBalanceRecord> {
}

91
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallDeliveryOrderServiceImpl.java

@ -69,6 +69,8 @@ public class MallDeliveryOrderServiceImpl extends ServiceImpl<MallDeliveryOrderM
private static final int PAGE_SIZE = 100;
private static final int PAGE_SIZE_TWENTY = 20;
private static final long COMPLETE_LOCK_SECONDS = 60L;
private static final long WORKER_BALANCE_LOCK_SECONDS = 10L;
private static final long WORKER_BALANCE_LOCK_WAIT_MILLIS = 5000L;
@Autowired
@Lazy
@ -273,9 +275,10 @@ public class MallDeliveryOrderServiceImpl extends ServiceImpl<MallDeliveryOrderM
MallDeliveryOrder delivery = this.getById(deliveryId);
if (delivery == null) return 0;
if (delivery.getStatus() != STATUS_WAIT_ACCEPT) return 1;
String originalWorkerId = delivery.getWorkerId();
// 指派单校验
if (StringUtils.isNotBlank(delivery.getWorkerId()) && !delivery.getWorkerId().equals(workerId)) {
if (StringUtils.isNotBlank(originalWorkerId) && !originalWorkerId.equals(workerId)) {
return 2;
}
@ -328,19 +331,28 @@ public class MallDeliveryOrderServiceImpl extends ServiceImpl<MallDeliveryOrderM
}
delivery.setTransferPickupCode(transferPickupCode);
LambdaUpdateWrapper<MallDeliveryOrder> uw = new LambdaUpdateWrapper<>();
uw.eq(MallDeliveryOrder::getId, deliveryId)
.set(MallDeliveryOrder::getStatus, STATUS_WAIT_PICKUP)
.set(MallDeliveryOrder::getWorkerId, workerId)
.set(MallDeliveryOrder::getWorkerName, workerName)
.set(MallDeliveryOrder::getWorkerPhone, workerPhone)
.set(MallDeliveryOrder::getAcceptTime, acceptTime)
.set(MallDeliveryOrder::getTransferPickupCode, transferPickupCode);
LambdaUpdateWrapper<MallDeliveryOrder> acceptUw = new LambdaUpdateWrapper<>();
acceptUw.eq(MallDeliveryOrder::getId, deliveryId)
.eq(MallDeliveryOrder::getStatus, STATUS_WAIT_ACCEPT)
.set(MallDeliveryOrder::getStatus, STATUS_WAIT_PICKUP)
.set(MallDeliveryOrder::getWorkerId, workerId)
.set(MallDeliveryOrder::getWorkerName, workerName)
.set(MallDeliveryOrder::getWorkerPhone, workerPhone)
.set(MallDeliveryOrder::getAcceptTime, acceptTime)
.set(MallDeliveryOrder::getTransferPickupCode, transferPickupCode);
if (StringUtils.isBlank(originalWorkerId)) {
acceptUw.and(w -> w.isNull(MallDeliveryOrder::getWorkerId).or().eq(MallDeliveryOrder::getWorkerId, ""));
} else {
acceptUw.eq(MallDeliveryOrder::getWorkerId, workerId);
}
if (!appointmentDelivery) {
uw.set(MallDeliveryOrder::getMustFinishTime, mustFinishTime);
acceptUw.set(MallDeliveryOrder::getMustFinishTime, mustFinishTime);
}
if (transferDelivery) {
uw.set(MallDeliveryOrder::getShopAddress, delivery.getShopAddress());
acceptUw.set(MallDeliveryOrder::getShopAddress, delivery.getShopAddress());
}
if (!this.update(acceptUw)) {
return 1;
}
String numberCode = "";
@ -464,6 +476,8 @@ public class MallDeliveryOrderServiceImpl extends ServiceImpl<MallDeliveryOrderM
}
// 更新配送单(含订单编号)
LambdaUpdateWrapper<MallDeliveryOrder> uw = new LambdaUpdateWrapper<>();
uw.eq(MallDeliveryOrder::getId, deliveryId);
uw.set(MallDeliveryOrder::getNumberCode, numberCode);
this.update(uw);
@ -801,6 +815,10 @@ public class MallDeliveryOrderServiceImpl extends ServiceImpl<MallDeliveryOrderM
freeOrderCount = 1;
}
//增加配送员余额
String workerBalanceLockKey = buildWorkerBalanceLockKey(delivery.getWorkerId());
if (lockWorkerBalance(workerBalanceLockKey)) {
releaseWorkerBalanceLockAfterCompletion(workerBalanceLockKey);
}
Worker worker = workerServiceImpl.findByWorkerId(delivery.getWorkerId());
//更新余额
worker.setDepoBal(worker.getDepoBal().add(delivery.getDeliveryFee().add(delivery.getDeliveryFeeMarketplace() == null ? BigDecimal.ZERO : delivery.getDeliveryFeeMarketplace())).setScale(2, RoundingMode.UP));
@ -858,6 +876,57 @@ public class MallDeliveryOrderServiceImpl extends ServiceImpl<MallDeliveryOrderM
}
}
private String buildWorkerBalanceLockKey(String workerId) {
return "worker_balance:" + workerId;
}
private boolean lockWorkerBalance(String lockKey) {
if (TransactionSynchronizationManager.isSynchronizationActive()
&& TransactionSynchronizationManager.hasResource(lockKey)) {
return false;
}
long deadline = System.currentTimeMillis() + WORKER_BALANCE_LOCK_WAIT_MILLIS;
while (System.currentTimeMillis() <= deadline) {
boolean locked = Boolean.TRUE.equals(stringRedisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", WORKER_BALANCE_LOCK_SECONDS, TimeUnit.SECONDS));
if (locked) {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.bindResource(lockKey, Boolean.TRUE);
}
return true;
}
try {
Thread.sleep(50L);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("配送员余额处理中,请稍后再试");
}
}
throw new RuntimeException("配送员余额处理中,请稍后再试");
}
private void releaseWorkerBalanceLockAfterCompletion(String lockKey) {
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
releaseWorkerBalanceLock(lockKey);
return;
}
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCompletion(int status) {
if (TransactionSynchronizationManager.hasResource(lockKey)) {
TransactionSynchronizationManager.unbindResource(lockKey);
}
releaseWorkerBalanceLock(lockKey);
}
});
}
private void releaseWorkerBalanceLock(String lockKey) {
if (StringUtils.isNotBlank(lockKey)) {
stringRedisTemplate.delete(lockKey);
}
}
private void sendWorkerCompleteAsyncAfterCommit(MallDeliveryOrder delivery, Worker worker, List<MallOrder> completedOrders, int freeOrderCount) {
if (delivery == null || worker == null) {
return;

140
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallRefundRecordServiceImpl.java

@ -23,16 +23,20 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@ -43,6 +47,8 @@ import java.util.stream.Collectors;
public class MallRefundRecordServiceImpl extends ServiceImpl<MallRefundRecordMapper, MallRefundRecord>
implements MallRefundRecordService {
private static final Logger log = LoggerFactory.getLogger(MallRefundRecordServiceImpl.class);
// 订单状态常量数据库存储为 "01",用数字10代替
private static final int STATUS_WAIT_SHOP = 1;
private static final int STATUS_WAIT_PICKUP = 3;
@ -60,6 +66,12 @@ public class MallRefundRecordServiceImpl extends ServiceImpl<MallRefundRecordMap
private static final int PAGESIZE = 10;
private static final int PAGENUM = 1;
private static final long AFTER_SALE_REFUND_LOCK_SECONDS = 30L;
private static final long WORKER_BALANCE_LOCK_SECONDS = 60L;
private static final long WORKER_BALANCE_LOCK_WAIT_MILLIS = 5000L;
private static final DefaultRedisScript<Long> RELEASE_WORKER_BALANCE_LOCK_SCRIPT = new DefaultRedisScript<>(
"if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end",
Long.class
);
@Autowired
private MallRefundRecordMapper mallRefundRecordMapper;
@ -275,26 +287,45 @@ public class MallRefundRecordServiceImpl extends ServiceImpl<MallRefundRecordMap
private void deductWorkerBalance(MallRefundRecord refundRecord, Integer dealingsType) {
if (refundRecord == null || refundRecord.getLinkId() == null || !refundRecord.getLinkId().toUpperCase().startsWith("W")) {
log.info("[worker-refund-debug] skip deduct, invalid worker refund record id={}, orderId={}, linkId={}",
refundRecord == null ? null : refundRecord.getId(),
refundRecord == null ? null : refundRecord.getOrderId(),
refundRecord == null ? null : refundRecord.getLinkId());
return;
}
Worker worker = workerServiceImpl.findByWorkerId(refundRecord.getLinkId());
if (worker == null) {
return;
log.info("[worker-refund-debug] deduct enter refundId={}, orderId={}, linkId={}, status={}, refundType={}, refundTypeStatus={}, refundAmount={}, settlementAmount={}, dealingsType={}, thread={}",
refundRecord.getId(), refundRecord.getOrderId(), refundRecord.getLinkId(), refundRecord.getStatus(),
refundRecord.getRefundType(), refundRecord.getRefundTypeStatus(), refundRecord.getRefundAmount(),
refundRecord.getSettlementAmount(), dealingsType, Thread.currentThread().getName());
String lockKey = buildWorkerBalanceLockKey(refundRecord.getLinkId());
String lockToken = lockWorkerBalance(lockKey);
if (lockToken != null) {
releaseWorkerBalanceLockAfterCompletion(lockKey, lockToken);
}
BigDecimal amount = settlementAmountOrRefundAmount(refundRecord);
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
log.info("[worker-refund-debug] skip deduct, amount <= 0 refundId={}, workerId={}, amount={}, refundAmount={}, settlementAmount={}",
refundRecord.getId(), refundRecord.getLinkId(), amount, refundRecord.getRefundAmount(), refundRecord.getSettlementAmount());
return;
}
BigDecimal currentDepoBal = safeAmount(worker.getDepoBal());
BigDecimal currentDepoBalRel = safeAmount(worker.getDepoBalRel());
if (currentDepoBal.compareTo(amount) >= 0) {
worker.setDepoBal(currentDepoBal.subtract(amount));
worker.setDepoBalRel(currentDepoBalRel);
} else {
worker.setDepoBal(BigDecimal.ZERO);
worker.setDepoBalRel(currentDepoBalRel.subtract(amount.subtract(currentDepoBal)));
Map<String, Object> beforeBalance = mallRefundRecordMapper.selectWorkerBalanceForDebug(refundRecord.getLinkId());
log.info("[worker-refund-debug] deduct before atomic refundId={}, workerId={}, amount={}, dbBalance={}",
refundRecord.getId(), refundRecord.getLinkId(), amount, beforeBalance);
int updated = mallRefundRecordMapper.deductWorkerBalanceAtomic(refundRecord.getLinkId(), amount);
Map<String, Object> afterBalance = mallRefundRecordMapper.selectWorkerBalanceForDebug(refundRecord.getLinkId());
log.info("[worker-refund-debug] deduct after atomic refundId={}, workerId={}, amount={}, updated={}, dbBalance={}",
refundRecord.getId(), refundRecord.getLinkId(), amount, updated, afterBalance);
if (updated <= 0) {
log.info("[worker-refund-debug] skip deduct dealings, worker balance row not updated refundId={}, workerId={}",
refundRecord.getId(), refundRecord.getLinkId());
return;
}
Worker worker = workerServiceImpl.findByWorkerId(refundRecord.getLinkId());
if (worker == null) {
log.info("[worker-refund-debug] skip deduct dealings, worker not found after atomic update refundId={}, workerId={}",
refundRecord.getId(), refundRecord.getLinkId());
return;
}
workerServiceImpl.update(worker);
saveWorkerDealingsRecord(
worker,
refundRecord.getId(),
@ -304,6 +335,8 @@ public class MallRefundRecordServiceImpl extends ServiceImpl<MallRefundRecordMap
: DealingsRecordConstant.TYPE_REMARK[10],
dealingsType
);
log.info("[worker-refund-debug] deduct dealings saved refundId={}, workerId={}, amount={}, dealingsType={}",
refundRecord.getId(), worker.getWorkerId(), amount, dealingsType);
}
private void addWorkerCommissionForMerchantRefund(MallOrder order, MallRefundRecord refundRecord) {
@ -321,6 +354,11 @@ public class MallRefundRecordServiceImpl extends ServiceImpl<MallRefundRecordMap
if (dealingsRecordService.count(existsQw) > 0) {
return;
}
String lockKey = buildWorkerBalanceLockKey(delivery.getWorkerId());
String lockToken = lockWorkerBalance(lockKey);
if (lockToken != null) {
releaseWorkerBalanceLockAfterCompletion(lockKey, lockToken);
}
Worker worker = workerServiceImpl.findByWorkerId(delivery.getWorkerId());
if (worker == null) {
return;
@ -334,6 +372,68 @@ public class MallRefundRecordServiceImpl extends ServiceImpl<MallRefundRecordMap
saveWorkerDealingsRecord(worker, refundRecord.getId(), commission, DealingsRecordConstant.TYPE_REMARK[11], DealingsRecordConstant.DEALINGS_TYPE[0]);
}
private String buildWorkerBalanceLockKey(String workerId) {
return "worker_balance:" + workerId;
}
private String lockWorkerBalance(String lockKey) {
if (TransactionSynchronizationManager.isSynchronizationActive()
&& TransactionSynchronizationManager.hasResource(lockKey)) {
log.info("[worker-refund-debug] lock reentrant key={}, thread={}", lockKey, Thread.currentThread().getName());
return null;
}
String lockToken = UUID.randomUUID().toString();
long deadline = System.currentTimeMillis() + WORKER_BALANCE_LOCK_WAIT_MILLIS;
while (System.currentTimeMillis() <= deadline) {
boolean locked = Boolean.TRUE.equals(stringRedisTemplate.opsForValue()
.setIfAbsent(lockKey, lockToken, WORKER_BALANCE_LOCK_SECONDS, TimeUnit.SECONDS));
if (locked) {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.bindResource(lockKey, lockToken);
}
log.info("[worker-refund-debug] lock acquired key={}, token={}, thread={}", lockKey, lockToken, Thread.currentThread().getName());
return lockToken;
}
try {
Thread.sleep(50L);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("配送员余额处理中,请稍后再试");
}
}
log.info("[worker-refund-debug] lock timeout key={}, thread={}", lockKey, Thread.currentThread().getName());
throw new RuntimeException("配送员余额处理中,请稍后再试");
}
private void releaseWorkerBalanceLockAfterCompletion(String lockKey, String lockToken) {
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
releaseWorkerBalanceLock(lockKey, lockToken);
return;
}
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCompletion(int status) {
String token = lockToken;
if (TransactionSynchronizationManager.hasResource(lockKey)) {
Object resource = TransactionSynchronizationManager.unbindResource(lockKey);
if (resource != null) {
token = resource.toString();
}
}
log.info("[worker-refund-debug] lock release afterCompletion key={}, token={}, txStatus={}, thread={}",
lockKey, token, status, Thread.currentThread().getName());
releaseWorkerBalanceLock(lockKey, token);
}
});
}
private void releaseWorkerBalanceLock(String lockKey, String lockToken) {
if (lockKey != null && lockToken != null) {
log.info("[worker-refund-debug] lock release execute key={}, token={}, thread={}", lockKey, lockToken, Thread.currentThread().getName());
stringRedisTemplate.execute(RELEASE_WORKER_BALANCE_LOCK_SCRIPT, Collections.singletonList(lockKey), lockToken);
}
}
private void saveWorkerDealingsRecord(Worker worker, String linkId, BigDecimal amount, String dealingsWay, Integer dealingsType) {
DealingsRecord dealingsRecord = new DealingsRecord();
dealingsRecord.setDealingsWay(dealingsWay);
@ -549,6 +649,11 @@ public class MallRefundRecordServiceImpl extends ServiceImpl<MallRefundRecordMap
public void updateStatus(MallRefundRecord mallRefundRecord) {
MallRefundRecord dbRecord = validateRefundRecordPending(mallRefundRecord);
fillRefundRecordForProcess(mallRefundRecord, dbRecord);
log.info("[worker-refund-debug] updateStatus enter refundId={}, orderId={}, linkId={}, status={}, refundType={}, refundTypeStatus={}, refundAmount={}, settlementAmount={}, deliveryType={}, orderType={}, thread={}",
mallRefundRecord.getId(), mallRefundRecord.getOrderId(), mallRefundRecord.getLinkId(),
mallRefundRecord.getStatus(), mallRefundRecord.getRefundType(), mallRefundRecord.getRefundTypeStatus(),
mallRefundRecord.getRefundAmount(), mallRefundRecord.getSettlementAmount(),
mallRefundRecord.getDeliveryType(), mallRefundRecord.getOrderType(), Thread.currentThread().getName());
boolean rejectStatus = mallRefundRecord.getStatus() != null
&& (mallRefundRecord.getStatus() == 2 || mallRefundRecord.getStatus() == 5);
String rejectReason = mallRefundRecord.getRejectReason() == null ? null : mallRefundRecord.getRejectReason().trim();
@ -848,8 +953,15 @@ public class MallRefundRecordServiceImpl extends ServiceImpl<MallRefundRecordMap
}
refundWechatForRefundRecord(order, mallRefundRecord);
if(mallRefundRecord.getLinkId().toUpperCase().startsWith("W")){
log.info("[worker-refund-debug] worker refund deduct condition refundId={}, orderId={}, linkId={}, otherOrder={}",
mallRefundRecord.getId(), mallRefundRecord.getOrderId(), mallRefundRecord.getLinkId(), order.getOtherOrder());
if(order.getOtherOrder() == null){
log.info("[worker-refund-debug] worker refund call deduct refundId={}, orderId={}, linkId={}",
mallRefundRecord.getId(), mallRefundRecord.getOrderId(), mallRefundRecord.getLinkId());
deductWorkerBalance(mallRefundRecord, DealingsRecordConstant.DEALINGS_TYPE[2]);
} else {
log.info("[worker-refund-debug] worker refund skip deduct by otherOrder refundId={}, orderId={}, linkId={}, otherOrder={}",
mallRefundRecord.getId(), mallRefundRecord.getOrderId(), mallRefundRecord.getLinkId(), order.getOtherOrder());
}
} else if (Integer.valueOf(1).equals(mallRefundRecord.getRefundTypeStatus())) {
addWorkerCommissionForMerchantRefund(order, mallRefundRecord);

12
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/WorkerDailyBalanceRecordServiceImpl.java

@ -0,0 +1,12 @@
package cc.hiver.mall.serviceimpl.mybatis;
import cc.hiver.mall.dao.mapper.WorkerDailyBalanceRecordMapper;
import cc.hiver.mall.entity.WorkerDailyBalanceRecord;
import cc.hiver.mall.service.mybatis.WorkerDailyBalanceRecordService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
@Service
public class WorkerDailyBalanceRecordServiceImpl extends ServiceImpl<WorkerDailyBalanceRecordMapper, WorkerDailyBalanceRecord>
implements WorkerDailyBalanceRecordService {
}

15
hiver-modules/hiver-mall/src/main/resources/db/worker_daily_balance_record.sql

@ -0,0 +1,15 @@
CREATE TABLE IF NOT EXISTS `worker_daily_balance_record` (
`id` varchar(64) NOT NULL COMMENT '主键ID',
`region_id` varchar(64) DEFAULT NULL COMMENT '校区/学校ID',
`balance_date` date NOT NULL COMMENT '余额归属日期,凌晨任务按昨日保存',
`worker_id` varchar(64) NOT NULL COMMENT '配送员ID',
`worker_name` varchar(128) DEFAULT NULL COMMENT '配送员姓名',
`mobile` varchar(32) DEFAULT NULL COMMENT '手机号',
`daily_balance` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '昨日佣金余额,保留正负数',
`available_balance_after` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '变更后可提现余额',
`create_time` datetime NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_worker_balance_date` (`balance_date`, `worker_id`),
KEY `idx_worker_balance_region_date` (`region_id`, `balance_date`),
KEY `idx_worker_balance_amount` (`daily_balance`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配送员每日余额记录表';
Loading…
Cancel
Save