diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/AdminDataStatisticsController.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/AdminDataStatisticsController.java
index 90a9998e..b511dcd4 100644
--- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/AdminDataStatisticsController.java
+++ b/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
summary(@RequestBody DataStatisticsQuery query) {
@@ -143,12 +161,16 @@ public class AdminDataStatisticsController {
.ge(BillingUsageRecord::getBillingDate, range.startDate)
.le(BillingUsageRecord::getBillingDate, range.endDate));
+ List workerBalanceRecords = workerDailyBalanceRecordService.list(new LambdaQueryWrapper()
+ .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 refundedOrderIds = new HashSet<>();
- Set 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 shopIds = new HashSet<>();
Set 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 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 settlementDeliveries = mallDeliveryOrderMapper.selectList(new LambdaQueryWrapper()
+ * .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().setData(vo);
}
+ @PostMapping("/workerDebtPage")
+ @ApiOperation(value = "åˆ†é¡µæŸ¥è¯¢æ¬ æ¬¾é…é€å‘˜æ¯æ—¥ä½™é¢è®°å½•")
+ public Result> workerDebtPage(@RequestBody WorkerDebtQuery query) {
+ DateRange range = resolveDateRange(query);
+ String regionId = query == null ? null : query.getRegionId();
+ Page page = workerDailyBalanceRecordService.page(
+ new Page<>(pageNumber(query), pageSize(query)),
+ new LambdaQueryWrapper()
+ .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>().setData(page);
+ }
+
+ private BigDecimal sumPositiveWorkerBalance(List 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 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 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 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 settlementRecords = mallSettlementRecordMapper.selectList(new LambdaQueryWrapper()
.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 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;
}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/AdminSettlementController.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/AdminSettlementController.java
index d3f3bd18..eb02a8f3 100644
--- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/AdminSettlementController.java
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/AdminSettlementController.java
@@ -155,7 +155,7 @@ public class AdminSettlementController {
QueryWrapper 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>().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列表")
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/MallRefundRecordMapper.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/MallRefundRecordMapper.java
index f2c01a38..b7fad913 100644
--- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/MallRefundRecordMapper.java
+++ b/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 {
IPage 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 selectWorkerBalanceForDebug(@Param("workerId") String workerId);
}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/WorkerDailyBalanceRecordMapper.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/WorkerDailyBalanceRecordMapper.java
new file mode 100644
index 00000000..f3f65f49
--- /dev/null
+++ b/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 {
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/WorkerDailyBalanceRecord.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/WorkerDailyBalanceRecord.java
new file mode 100644
index 00000000..e68da868
--- /dev/null
+++ b/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;
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/quartz/MerchantSettlementTask.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/quartz/MerchantSettlementTask.java
index 8df553d5..3d1d4ec9 100644
--- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/quartz/MerchantSettlementTask.java
+++ b/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 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 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 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;
+ }
}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/WorkerDailyBalanceRecordService.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/WorkerDailyBalanceRecordService.java
new file mode 100644
index 00000000..f17f2f65
--- /dev/null
+++ b/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 {
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallDeliveryOrderServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallDeliveryOrderServiceImpl.java
index 684dc6be..827294df 100644
--- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallDeliveryOrderServiceImpl.java
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallDeliveryOrderServiceImpl.java
@@ -69,6 +69,8 @@ public class MallDeliveryOrderServiceImpl extends ServiceImpl 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 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 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 completedOrders, int freeOrderCount) {
if (delivery == null || worker == null) {
return;
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallRefundRecordServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallRefundRecordServiceImpl.java
index cd361f69..6bef38a5 100644
--- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallRefundRecordServiceImpl.java
+++ b/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
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 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= 0) {
- worker.setDepoBal(currentDepoBal.subtract(amount));
- worker.setDepoBalRel(currentDepoBalRel);
- } else {
- worker.setDepoBal(BigDecimal.ZERO);
- worker.setDepoBalRel(currentDepoBalRel.subtract(amount.subtract(currentDepoBal)));
+ Map 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 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 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
+ implements WorkerDailyBalanceRecordService {
+}
diff --git a/hiver-modules/hiver-mall/src/main/resources/db/worker_daily_balance_record.sql b/hiver-modules/hiver-mall/src/main/resources/db/worker_daily_balance_record.sql
new file mode 100644
index 00000000..c906bb7b
--- /dev/null
+++ b/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='é…é€å‘˜æ¯æ—¥ä½™é¢è®°å½•表';