Browse Source

1.增加定时器生成账单处理
2.APP增加用户商户授信记录、授信账单列表、授信三级账明细接口

FengChaoYu 1 week ago
parent
commit
2a4a8d7d63

+ 63 - 0
mall-miniapp-service/src/main/java/com/gree/mall/miniapp/controller/user/UserCompanyCreditController.java

@@ -0,0 +1,63 @@
+package com.gree.mall.miniapp.controller.user;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.gree.mall.miniapp.bean.user.UserWxBean;
+import com.gree.mall.miniapp.enums.TransactionTypeEnum;
+import com.gree.mall.miniapp.helper.ResponseHelper;
+import com.gree.mall.miniapp.logic.user.UserCompanyCreditLogic;
+import com.gree.mall.miniapp.plus.entity.UserCompanyCredit;
+import com.gree.mall.miniapp.plus.entity.UserCompanyCreditBill;
+import com.gree.mall.miniapp.plus.entity.UserCompanyCreditBillItem;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+@Slf4j
+@RestController
+@Api(value = "用户商户授信API", tags ={"用户商户授信API"} )
+@RequestMapping(value = "/user/company/credit", produces = "application/json; charset=utf-8")
+public class UserCompanyCreditController {
+
+    @Resource
+    UserCompanyCreditLogic userCompanyCreditLogic;
+
+    @PostMapping("/record/list")
+    @ApiOperation(value = "授信记录")
+    public ResponseHelper<List<UserCompanyCredit>> recordList() {
+        List<UserCompanyCredit> list = userCompanyCreditLogic.recordList();
+        return ResponseHelper.success(list);
+    }
+
+    @PostMapping("/bill/list")
+    @ApiOperation(value = "授信账单列表")
+    public ResponseHelper<List<UserCompanyCreditBill>> billList(
+            @ApiParam(required = true, value = "商户id") @RequestParam String companyWechatId,
+            @ApiParam(value = "年度") @RequestParam(required = false) Integer year,
+            @ApiParam(required = true, value = "pageNum") @RequestParam Integer pageNum,
+            @ApiParam(required = true, value = "pageSize") @RequestParam Integer pageSize
+    ) {
+        IPage<UserCompanyCreditBill> list = userCompanyCreditLogic.billList(companyWechatId, year, pageNum, pageSize);
+        return ResponseHelper.success(list);
+    }
+
+    @PostMapping("/acc/list")
+    @ApiOperation(value = "授信三级账明细")
+    public ResponseHelper<List<UserCompanyCreditBillItem>> accList(
+            @ApiParam(required = true, value = "商户id") @RequestParam String companyWechatId,
+            @ApiParam(value = "账单id, 为空查当期") @RequestParam(required = false) String billId,
+            @ApiParam(value = "类型") @RequestParam(required = false) TransactionTypeEnum type,
+            @ApiParam(required = true, value = "pageNum") @RequestParam Integer pageNum,
+            @ApiParam(required = true, value = "pageSize") @RequestParam Integer pageSize
+    ) {
+        IPage<UserCompanyCreditBillItem> list = userCompanyCreditLogic.accList(companyWechatId, billId, type, pageNum, pageSize);
+        return ResponseHelper.success(list);
+    }
+}

+ 52 - 1
mall-miniapp-service/src/main/java/com/gree/mall/miniapp/logic/user/UserCompanyCreditLogic.java

@@ -2,24 +2,31 @@ package com.gree.mall.miniapp.logic.user;
 
 import cn.hutool.core.date.DateTime;
 import cn.hutool.core.date.DateUtil;
+import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.IdWorker;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.gree.mall.miniapp.bean.user.CurrentCompanyWechat;
+import com.gree.mall.miniapp.bean.user.SettleMonthWagesWorkerBean;
 import com.gree.mall.miniapp.commonmapper.LockQueryMapper;
 import com.gree.mall.miniapp.constant.Constant;
 import com.gree.mall.miniapp.enums.TransactionTypeEnum;
 import com.gree.mall.miniapp.exception.RemoteServiceException;
 import com.gree.mall.miniapp.helper.ResponseHelper;
+import com.gree.mall.miniapp.logic.common.CommonLogic;
 import com.gree.mall.miniapp.plus.entity.*;
+import com.gree.mall.miniapp.plus.service.UserCompanyCreditBillItemService;
 import com.gree.mall.miniapp.plus.service.UserCompanyCreditBillService;
 import com.gree.mall.miniapp.plus.service.UserCompanyCreditService;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.integration.redis.util.RedisLockRegistry;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.support.TransactionSynchronization;
 import org.springframework.transaction.support.TransactionSynchronizationManager;
 
 import java.math.BigDecimal;
-import java.util.Arrays;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.Lock;
@@ -32,7 +39,9 @@ public class UserCompanyCreditLogic {
     private final RedisLockRegistry redisLockRegistry;
     private final UserCompanyCreditService userCompanyCreditService;
     private final UserCompanyCreditBillService userCompanyCreditBillService;
+    private final UserCompanyCreditBillItemService userCompanyCreditBillItemService;
     private final LockQueryMapper lockQueryMapper;
+    private final CommonLogic commonLogic;
 
     public void createCreditRecord(OrderInfo orderInfo, User user) throws Exception {
         if(!TransactionSynchronizationManager.isSynchronizationActive()) {
@@ -116,4 +125,46 @@ public class UserCompanyCreditLogic {
             obtain.unlock();
         }
     }
+
+    public List<UserCompanyCredit> recordList() {
+        CurrentCompanyWechat currentCompanyWechat = commonLogic.getCurrentCompanyWechat();
+        return userCompanyCreditService.lambdaQuery()
+                .select(UserCompanyCredit::getCompanyWechatId, UserCompanyCredit::getCompanyWechatName,
+                        UserCompanyCredit::getCreditLimit, UserCompanyCredit::getAvailableCredit,
+                        UserCompanyCredit::getIsCreditEnabled, UserCompanyCredit::getBillingDay,
+                        UserCompanyCredit::getPaymentGracePeriod, UserCompanyCredit::getCreateTime)
+                .eq(UserCompanyCredit::getUserId, currentCompanyWechat.getUserId())
+                .list();
+    }
+
+
+    public IPage<UserCompanyCreditBill> billList(String companyWechatId, Integer year, Integer pageNum, Integer pageSize) {
+        CurrentCompanyWechat currentCompanyWechat = commonLogic.getCurrentCompanyWechat();
+
+        DateTime start = DateUtil.beginOfYear(DateUtil.parse(year + "-01-01"));
+        DateTime end = DateUtil.endOfYear(DateUtil.parse(year + "-01-01"));
+        IPage<UserCompanyCreditBill> page = userCompanyCreditBillService.lambdaQuery()
+                .eq(UserCompanyCreditBill::getUserId, currentCompanyWechat.getUserId())
+                .eq(UserCompanyCreditBill::getCompanyWechatId, companyWechatId)
+                .between(Objects.nonNull(year), UserCompanyCreditBill::getCreateTime, start, end)
+                .orderByDesc(UserCompanyCreditBill::getBillId)
+                .page(new Page<>(pageNum, pageSize));
+
+        return page;
+    }
+
+    public IPage<UserCompanyCreditBillItem> accList(String companyWechatId, String billId, TransactionTypeEnum type, Integer pageNum, Integer pageSize) {
+        CurrentCompanyWechat currentCompanyWechat = commonLogic.getCurrentCompanyWechat();
+
+        IPage<UserCompanyCreditBillItem> page = userCompanyCreditBillItemService.lambdaQuery()
+                .eq(UserCompanyCreditBillItem::getUserId, currentCompanyWechat.getUserId())
+                .eq(UserCompanyCreditBillItem::getCompanyWechatId, companyWechatId)
+                .eq(StringUtils.isNotBlank(billId), UserCompanyCreditBillItem::getUserCompanyCreditBillId, billId)
+                .isNull(StringUtils.isBlank(billId), UserCompanyCreditBillItem::getUserCompanyCreditBillId)
+                .eq(Objects.nonNull(type), UserCompanyCreditBillItem::getTransactionType, type.getKey())
+                .orderByDesc(UserCompanyCreditBillItem::getCreateTime)
+                .page(new Page<>(pageNum, pageSize));
+
+        return page;
+    }
 }

+ 4 - 1
mall-server-api/src/main/java/com/gree/mall/manager/commonmapper/LockQueryMapper.java

@@ -30,5 +30,8 @@ public interface LockQueryMapper {
     List<GoodsMaterialStorage> queryExistGoodsMaterialStockList(@Param("dto") GoodsMaterialStockDTO dto,
                                                                 @Param("stockList") List<GoodsMaterialStockDTO> stockList);
 
-    UserCompanyCredit queryExistUserCompanyCredit(@Param("companyWechatId") String companyWechatId, @Param("userId") String userId);
+    UserCompanyCredit queryExistUserCompanyCredit(@Param("companyWechatId") String companyWechatId,
+                                                  @Param("userId") String userId);
+
+    List<UserCompanyCredit> findByBillingDay(@Param("billingDay") Integer billingDay);
 }

+ 3 - 2
mall-server-api/src/main/java/com/gree/mall/manager/controller/member/UserCompanyCreditController.java

@@ -87,14 +87,15 @@ public class UserCompanyCreditController {
     @PostMapping("/repay")
     @ApiOperation(value = "恢复商户授信额度")
     public ResponseHelper repay(
-            @ApiParam(value = "账单", required = true) @RequestParam String billId,
+            @ApiParam(value = "账单id(不传就记当期还款)") @RequestParam(required = false) String billId,
+            @ApiParam(value = "授信记录id(账单id不传,此必传)") @RequestParam(required = false) String userCompanyCreditId,
             @ApiParam(value = "还款金额", required = true) @RequestParam BigDecimal amount) throws Exception {
         Lock obtain = redisLockRegistry.obtain(Constant.RedisPrefix.LOCK_COMMON + billId);
         if(!obtain.tryLock(10, TimeUnit.SECONDS)){
             return ResponseHelper.error("系统繁忙,请稍后再试");
         }
         try {
-            userCompanyCreditLogic.billRepay(billId, amount);
+            userCompanyCreditLogic.billRepay(billId, userCompanyCreditId, amount);
             return ResponseHelper.success();
         }finally {
             obtain.unlock();

+ 46 - 22
mall-server-api/src/main/java/com/gree/mall/manager/logic/user/UserCompanyCreditLogic.java

@@ -72,32 +72,55 @@ public class UserCompanyCreditLogic {
      * @param amount
      */
     @Transactional
-    public void billRepay(String billId, BigDecimal amount) throws Exception {
+    public void billRepay(String billId, String userCompanyCreditId, BigDecimal amount) throws Exception {
         AdminUserCom adminUser = commonLogic.getAdminUser();
-        UserCompanyCreditBill creditBill = userCompanyCreditBillService.getById(billId);
-        if (!creditBill.getCompanyWechatId().equals(adminUser.getCompanyWechatId())) {
+        if (!StringUtils.isBlank(adminUser.getCompanyWechatId())) {
             throw new RemoteServiceException("请使用商户账号");
         }
-        if (creditBill.getIsPaid()) {
-            throw new RemoteServiceException(billId + "账单已还款");
-        }
 
-        // 判断还款金额是否大于剩余未还
-        if (amount.compareTo(creditBill.getRemainingAmount()) > 0) {
-            throw new RemoteServiceException("还款金额不能大于剩余未还金额");
+        if (StringUtils.isBlank(billId) && StringUtils.isBlank(userCompanyCreditId)) {
+            throw new RemoteServiceException("传参异常");
         }
 
-        // 剩余未还金额 - 还款金额 = (新)剩余未还金额
-        final BigDecimal newRemainingAmount = creditBill.getRemainingAmount().subtract(amount);
+        String userId;
+        String companyWechatId;
 
-        // 恢复使用额度
-        this.repay(null, creditBill.getUserId(), creditBill.getCompanyWechatId(), amount);
+        if (StringUtils.isNotBlank(billId)) {
+            UserCompanyCreditBill creditBill = userCompanyCreditBillService.getById(billId);
+            if (!creditBill.getCompanyWechatId().equals(adminUser.getCompanyWechatId())) {
+                throw new RemoteServiceException("请使用商户账号");
+            }
+            if (creditBill.getIsPaid()) {
+                throw new RemoteServiceException(billId + "账单已还款");
+            }
+
+            // 判断还款金额是否大于剩余未还
+            if (amount.compareTo(creditBill.getRemainingAmount()) > 0) {
+                throw new RemoteServiceException("还款金额不能大于剩余未还金额");
+            }
+
+            userId = creditBill.getUserId();
+            companyWechatId = creditBill.getCompanyWechatId();
 
-        // 更新账单
-        creditBill.setIsPaid(newRemainingAmount.compareTo(BigDecimal.ZERO) == 0)
-                .setRemainingAmount(newRemainingAmount)
-                .updateById();
+            // 剩余未还金额 - 还款金额 = (新)剩余未还金额
+            final BigDecimal newRemainingAmount = creditBill.getRemainingAmount().subtract(amount);
 
+            // 更新账单
+            creditBill.setIsPaid(newRemainingAmount.compareTo(BigDecimal.ZERO) == 0)
+                    .setRemainingAmount(newRemainingAmount)
+                    .updateById();
+        } else {
+            final UserCompanyCredit userCompanyCredit = userCompanyCreditService.getById(userCompanyCreditId);
+            if (!userCompanyCredit.getCompanyWechatId().equals(adminUser.getCompanyWechatId())) {
+                throw new RemoteServiceException("请使用商户账号");
+            }
+
+            userId = userCompanyCredit.getUserId();
+            companyWechatId = userCompanyCredit.getCompanyWechatId();
+        }
+
+        // 恢复使用额度
+        this.repay(null, userId, companyWechatId, amount, billId);
     }
 
     /**
@@ -109,15 +132,15 @@ public class UserCompanyCreditLogic {
             return;
         }
 
-        this.repay(orderInfo.getOrderId(), orderInfo.getUserId(), orderInfo.getCompanyWechatId(), orderInfo.getPayAmount());
+        this.repay(orderInfo.getOrderId(), orderInfo.getUserId(), orderInfo.getCompanyWechatId(), orderInfo.getPayAmount(), null);
     }
 
-    private void repay(String orderId, String userId, String companyWechatId, BigDecimal amount) throws Exception {
-        if(!TransactionSynchronizationManager.isSynchronizationActive()) {
+    private void repay(String orderId, String userId, String companyWechatId, BigDecimal amount, String billId) throws Exception {
+        if (!TransactionSynchronizationManager.isSynchronizationActive()) {
             throw new RemoteServiceException("请先开启事务");
         }
         Lock obtain = redisLockRegistry.obtain(Constant.RedisPrefix.LOCK_USER_COMPANY_CREDIT + userId + ":" + companyWechatId);
-        if(!obtain.tryLock(10, TimeUnit.SECONDS)){
+        if (!obtain.tryLock(10, TimeUnit.SECONDS)) {
             throw new RemoteServiceException("操作过于频繁, 请稍后再试");
         }
         // 处理授信
@@ -151,11 +174,12 @@ public class UserCompanyCreditLogic {
             item.setId(IdWorker.getIdStr())
                     .setOrderId(orderId)
                     .setTransactionType(StringUtils.isNotBlank(orderId) ? TransactionTypeEnum.REFUND.getKey() : TransactionTypeEnum.REPAYMENT.getKey())
-                    .setDescription(StringUtils.isNotBlank(orderId) ? TransactionTypeEnum.CONSUMPTION.getRemark() : TransactionTypeEnum.REPAYMENT.getRemark())
+                    .setDescription(StringUtils.isNotBlank(orderId) ? TransactionTypeEnum.REFUND.getRemark() : TransactionTypeEnum.REPAYMENT.getRemark())
                     .setCompanyWechatId(userCompanyCredit.getCompanyWechatId())
                     .setCompanyWechatName(userCompanyCredit.getCompanyWechatName())
                     .setUserId(userCompanyCredit.getUserId())
                     .setAmount(amount)
+                    .setRepaymentBillId(StringUtils.isNotBlank(billId) ? billId : null)
                     .setCreateTime(curDate)
                     .insert();
         } finally {

+ 110 - 0
mall-server-api/src/main/java/com/gree/mall/manager/schedule/UserCompanyCreditBillSchedule.java

@@ -1,18 +1,32 @@
 package com.gree.mall.manager.schedule;
 
+import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.date.DateTime;
 import cn.hutool.core.date.DateUtil;
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
+import com.gree.mall.manager.commonmapper.LockQueryMapper;
+import com.gree.mall.manager.enums.TransactionTypeEnum;
 import com.gree.mall.manager.enums.workorder.OrderBaseStatusEnum;
 import com.gree.mall.manager.plus.entity.PgOrderBase;
+import com.gree.mall.manager.plus.entity.UserCompanyCredit;
+import com.gree.mall.manager.plus.entity.UserCompanyCreditBill;
+import com.gree.mall.manager.plus.entity.UserCompanyCreditBillItem;
+import com.gree.mall.manager.plus.service.UserCompanyCreditBillItemService;
+import com.gree.mall.manager.plus.service.UserCompanyCreditBillService;
+import com.gree.mall.manager.plus.service.UserCompanyCreditService;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
 
+import javax.annotation.Resource;
+import java.math.BigDecimal;
 import java.util.Date;
 import java.util.List;
+import java.util.Objects;
 import java.util.stream.Collectors;
 
 /**
@@ -26,6 +40,14 @@ import java.util.stream.Collectors;
 @Slf4j
 public class UserCompanyCreditBillSchedule {
 
+    @Resource
+    UserCompanyCreditBillService userCompanyCreditBillService;
+    @Resource
+    UserCompanyCreditBillItemService userCompanyCreditBillItemService;
+    @Resource
+    LockQueryMapper lockQueryMapper;
+
+
     /**
      * 每日凌晨1点执行,检查是否有用户需要在当天生成账单
      */
@@ -41,6 +63,94 @@ public class UserCompanyCreditBillSchedule {
         }
 
         // 获取账单日为今天的所有用户商户授信关系
+        List<UserCompanyCredit> userCompanyCreditList = lockQueryMapper.findByBillingDay(currentDay);
+        
+        if (CollectionUtil.isNotEmpty(userCompanyCreditList)) {
+            for (UserCompanyCredit credit : userCompanyCreditList) {
+                generateBillForUserCompany(credit);
+            }
+        }
+    }
+
+    private void generateBillForUserCompany(UserCompanyCredit credit) {
+        final String userId = credit.getUserId();
+        final String companyWechatId = credit.getCompanyWechatId();
+
+        DateTime today = DateUtil.date();
+        // 获取授信记录明细开始时间
+        Date startTime;
+        // 获取授信记录明细结束时间
+        Date endTime = DateUtil.endOfDay(DateUtil.offsetDay(today, -1));
+
+        if (Objects.isNull(credit.getLastBillingDate())) {
+            // 第一次一成账单开始时间取授信创建时间
+            startTime = credit.getCreateTime();
+        } else {
+            // 取上次的结束时间(已加1秒,比如上次获取授信记录明细结束时间为2025-10-22 23:59:59, 再加1秒等于下次执行的开始时间2025-10-23 00:00:00)
+            startTime = credit.getLastBillingDate();
+        }
+
+        // 处理月末情况
+        if (credit.getBillingDay() == 29) {
+            endTime = DateUtil.endOfDay(DateUtil.offsetDay(DateUtil.endOfMonth(today), -1));
+        }
+
+        // 查询该周期内示对账的消费明细
+        List<UserCompanyCreditBillItem> billItemList = userCompanyCreditBillItemService.lambdaQuery()
+                .select(UserCompanyCreditBillItem::getId, UserCompanyCreditBillItem::getTransactionType, UserCompanyCreditBillItem::getAmount)
+                .eq(UserCompanyCreditBillItem::getUserId, userId)
+                .eq(UserCompanyCreditBillItem::getCompanyWechatId, companyWechatId)
+                .between(UserCompanyCreditBillItem::getCreateTime, startTime, endTime)
+                .isNull(UserCompanyCreditBillItem::getUserCompanyCreditBillId)
+                .list();
+
+        if (billItemList.isEmpty()) {
+            // 没有需要对账的明细
+            return;
+        }
+
+        // 计算总金额
+        BigDecimal totalAmount = billItemList.stream()
+                .map(item -> {
+                    if (item.getTransactionType().equals(TransactionTypeEnum.CONSUMPTION.getKey())) {
+                        return item.getAmount();
+                    } else if (item.getTransactionType().equals(TransactionTypeEnum.REFUND.getKey())) {
+                        return item.getAmount().negate();
+                    } else if (item.getTransactionType().equals(TransactionTypeEnum.REPAYMENT.getKey())) {
+                        return (StringUtils.isBlank(item.getRepaymentBillId())) ? item.getAmount().negate() : BigDecimal.ZERO;
+                    }
+                    return BigDecimal.ZERO;
+                })
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+        // 检查总还款金额少于0的就等于0否则就是总金额
+        totalAmount = totalAmount.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : totalAmount;
+
+        // 计算还款日(账单日+账期天数)
+        Date dueDate = DateUtil.offsetDay(today, credit.getPaymentGracePeriod());
+
+        // 创建账单
+        UserCompanyCreditBill bill = new UserCompanyCreditBill();
+        bill.setBillId(IdWorker.getIdStr())
+                .setUserId(credit.getUserId())
+                .setCompanyWechatId(credit.getCompanyWechatId())
+                .setCompanyWechatName(credit.getCompanyWechatName())
+                .setAmount(totalAmount)
+                .setRemainingAmount(totalAmount)
+                .setDueDate(dueDate)
+                .setIsPaid(totalAmount.compareTo(BigDecimal.ZERO) <= 0)
+                .setRemark(DateUtil.formatDateTime(startTime) + " - " + DateUtil.formatDateTime(endTime))
+                .insert();
+
+        userCompanyCreditBillItemService.lambdaUpdate()
+                .set(UserCompanyCreditBillItem::getUserCompanyCreditBillId, bill.getBillId())
+                .in(UserCompanyCreditBillItem::getId, billItemList.stream()
+                        .map(UserCompanyCreditBillItem::getId)
+                        .collect(Collectors.toList()))
+                .update();
 
+        // 更新用户商户授信记录
+        credit.setLastBillingDate(DateUtil.offsetSecond(endTime, 1))
+                .updateById();
     }
 }

+ 11 - 3
mall-server-api/src/main/resources/mapper/LockQueryMapper.xml

@@ -37,11 +37,19 @@
         FROM
             user_company_credit
         WHERE
-            company_wechat_id = #{companyWechatId}
-            AND
             user_id = #{userId}
+            AND
+            company_wechat_id = #{companyWechatId}
+        FOR UPDATE
+    </select>
+    <select id="findByBillingDay" resultType="com.gree.mall.manager.plus.entity.UserCompanyCredit">
+        SELECT
+        *
+        FROM
+        user_company_credit
+        WHERE
+        billing_day = #{billingDay}
         FOR UPDATE
     </select>
-
 
 </mapper>