|
|
@@ -0,0 +1,640 @@
|
|
|
+package shop.alien.storeplatform.service.impl;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
|
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.springframework.beans.BeanUtils;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+import shop.alien.entity.result.R;
|
|
|
+import shop.alien.entity.store.StoreCashOutRecord;
|
|
|
+import shop.alien.entity.store.StoreIncomeDetailsRecord;
|
|
|
+import shop.alien.entity.store.StoreInfo;
|
|
|
+import shop.alien.entity.store.StoreUser;
|
|
|
+import shop.alien.entity.store.vo.StoreCashOutRecordVo;
|
|
|
+import shop.alien.entity.store.vo.StoreIncomeDetailsRecordVo;
|
|
|
+import shop.alien.mapper.StoreCashOutRecordMapper;
|
|
|
+import shop.alien.mapper.StoreIncomeDetailsRecordMapper;
|
|
|
+import shop.alien.mapper.StoreInfoMapper;
|
|
|
+import shop.alien.mapper.StoreUserMapper;
|
|
|
+import shop.alien.storeplatform.service.IncomeManageService;
|
|
|
+import shop.alien.util.common.ListToPage;
|
|
|
+import shop.alien.util.common.constant.CouponTypeEnum;
|
|
|
+import shop.alien.util.date.DateUtils;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.math.RoundingMode;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.time.LocalDate;
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.time.LocalTime;
|
|
|
+import java.util.*;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+/**
|
|
|
+ * web端商户收入管理服务实现
|
|
|
+ *
|
|
|
+ * @author ssk
|
|
|
+ * @since 2025-11-14
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class IncomeManageServiceImpl extends ServiceImpl<StoreIncomeDetailsRecordMapper, StoreIncomeDetailsRecord> implements IncomeManageService {
|
|
|
+
|
|
|
+ private final StoreIncomeDetailsRecordMapper storeIncomeDetailsRecordMapper;
|
|
|
+ private final StoreInfoMapper storeInfoMapper;
|
|
|
+ private final StoreUserMapper storeUserMapper;
|
|
|
+ private final StoreCashOutRecordMapper storeCashOutRecordMapper;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public com.alibaba.fastjson2.JSONObject getPaymentCycle(Integer storeId, Integer incomeType, Integer paymentType,
|
|
|
+ String startTime, String endTime, int page, int size) {
|
|
|
+ log.info("IncomeManageServiceImpl.getPaymentCycle - 开始查询账期: storeId={}, incomeType={}, paymentType={}, startTime={}, endTime={}, page={}, size={}",
|
|
|
+ storeId, incomeType, paymentType, startTime, endTime, page, size);
|
|
|
+
|
|
|
+ QueryWrapper<StoreIncomeDetailsRecord> wrapper = new QueryWrapper<>();
|
|
|
+ com.alibaba.fastjson2.JSONObject jsonObject = new com.alibaba.fastjson2.JSONObject();
|
|
|
+ Date now = new Date();
|
|
|
+ SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
|
|
|
+
|
|
|
+ // 1. 根据账期类型设置查询条件
|
|
|
+ if (paymentType != null && paymentType == 0) {
|
|
|
+ // 未到账期: 未绑定提现记录 & 当前时间-3天大于创建时间
|
|
|
+ wrapper.isNull("sidr.cash_out_id");
|
|
|
+ wrapper.gt("sidr.created_time", DateUtils.calcDays(new Date(), -3));
|
|
|
+ Date startDate = DateUtils.calcDays(now, -3);
|
|
|
+ jsonObject.put("date", df.format(startDate) + " ~ " + df.format(now));
|
|
|
+ log.debug("IncomeManageServiceImpl.getPaymentCycle - 未到账期: date={}", jsonObject.get("date"));
|
|
|
+ } else if (paymentType != null && paymentType == 1) {
|
|
|
+ // 已到账期: 已绑定提现记录 & 当前时间-4~27天大于创建时间
|
|
|
+ wrapper.isNotNull("sidr.cash_out_id");
|
|
|
+ wrapper.between("sidr.created_time", DateUtils.calcDays(new Date(), -27), DateUtils.calcDays(new Date(), -4));
|
|
|
+ Date startDate = DateUtils.calcDays(now, -27);
|
|
|
+ Date endDate = DateUtils.calcDays(now, -4);
|
|
|
+ jsonObject.put("date", df.format(startDate) + " ~ " + df.format(endDate));
|
|
|
+ log.debug("IncomeManageServiceImpl.getPaymentCycle - 已到账期: date={}", jsonObject.get("date"));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 门店ID条件
|
|
|
+ if (storeId != null) {
|
|
|
+ wrapper.eq("sidr.store_id", storeId);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 时间范围条件(用户选择的时间范围)
|
|
|
+ if (StringUtils.isNotBlank(startTime) && StringUtils.isNotBlank(endTime)) {
|
|
|
+ LocalDate startDate = LocalDate.parse(startTime);
|
|
|
+ LocalDate endDate = LocalDate.parse(endTime);
|
|
|
+ LocalDateTime startOfDay = startDate.atStartOfDay();
|
|
|
+ LocalDateTime endOfDay = endDate.atTime(LocalTime.MAX);
|
|
|
+ wrapper.between("sidr.created_time", startOfDay, endOfDay)
|
|
|
+ .orderByDesc("sidr.created_time");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 收入类型条件
|
|
|
+ if (null != incomeType) {
|
|
|
+ if (0 == incomeType) {
|
|
|
+ // 主页: 包含优惠券和代金券
|
|
|
+ wrapper.in("sidr.income_type", CouponTypeEnum.COUPON.getCode(), CouponTypeEnum.GROUP_BUY.getCode());
|
|
|
+ log.debug("IncomeManageServiceImpl.getPaymentCycle - 收入类型: 主页(优惠券+代金券)");
|
|
|
+ } else {
|
|
|
+ wrapper.eq("sidr.income_type", incomeType);
|
|
|
+ log.debug("IncomeManageServiceImpl.getPaymentCycle - 收入类型: {}", incomeType);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 查询收入明细记录列表
|
|
|
+ List<StoreIncomeDetailsRecordVo> list = storeIncomeDetailsRecordMapper.selectRecordList(wrapper);
|
|
|
+ log.info("IncomeManageServiceImpl.getPaymentCycle - 查询到收入记录数: {}", list.size());
|
|
|
+
|
|
|
+ // 6. 查询门店信息(用于获取抽成比例)
|
|
|
+ StoreInfo storeInfo = storeInfoMapper.selectById(storeId);
|
|
|
+ if (storeInfo == null) {
|
|
|
+ log.warn("IncomeManageServiceImpl.getPaymentCycle - 门店不存在: storeId={}", storeId);
|
|
|
+ throw new RuntimeException("门店不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 7. 处理列表数据:格式化金额、日期
|
|
|
+ for (StoreIncomeDetailsRecordVo storeIncomeDetailsRecord : list) {
|
|
|
+ if (storeIncomeDetailsRecord == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ StoreIncomeDetailsRecordVo vo = new StoreIncomeDetailsRecordVo();
|
|
|
+ BeanUtils.copyProperties(storeIncomeDetailsRecord, vo);
|
|
|
+
|
|
|
+ // 将金额从分转换为元(保留2位小数)
|
|
|
+ storeIncomeDetailsRecord.setMoneyStr(
|
|
|
+ new BigDecimal(vo.getMoney())
|
|
|
+ .divide(new BigDecimal(100), 2, RoundingMode.HALF_UP)
|
|
|
+ .toString()
|
|
|
+ );
|
|
|
+
|
|
|
+ // 格式化日期
|
|
|
+ String format = df.format(storeIncomeDetailsRecord.getCreatedTime());
|
|
|
+ storeIncomeDetailsRecord.setDate(format);
|
|
|
+
|
|
|
+ // 设置抽成比例
|
|
|
+ vo.setCommissionRate(storeInfo.getCommissionRate());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 8. 手动分页
|
|
|
+ jsonObject.put("data", ListToPage.setPage(list, page, size));
|
|
|
+
|
|
|
+ // 9. 计算总金额(元)
|
|
|
+ int totalMoney = list.stream().mapToInt(StoreIncomeDetailsRecord::getMoney).sum();
|
|
|
+ jsonObject.put("money",
|
|
|
+ new BigDecimal(totalMoney)
|
|
|
+ .divide(new BigDecimal(100), 2, RoundingMode.HALF_UP)
|
|
|
+ .toString()
|
|
|
+ );
|
|
|
+
|
|
|
+ log.info("IncomeManageServiceImpl.getPaymentCycle - 查询完成: 总记录数={}, 总金额={}元",
|
|
|
+ list.size(), jsonObject.get("money"));
|
|
|
+
|
|
|
+ return jsonObject;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Map<String, Object> getAccountBalance(Integer storeId) {
|
|
|
+ log.info("IncomeManageServiceImpl.getAccountBalance - 开始查询账户余额: storeId={}", storeId);
|
|
|
+
|
|
|
+ Map<String, Object> map = new HashMap<>();
|
|
|
+
|
|
|
+ // 1. 查询店铺用户信息,获取账户总余额
|
|
|
+ LambdaQueryWrapper<StoreUser> storeUserWrapper = new LambdaQueryWrapper<>();
|
|
|
+ storeUserWrapper.eq(StoreUser::getStoreId, storeId);
|
|
|
+ StoreUser storeUser = storeUserMapper.selectOne(storeUserWrapper);
|
|
|
+
|
|
|
+ if (storeUser == null) {
|
|
|
+ log.warn("IncomeManageServiceImpl.getAccountBalance - 店铺用户不存在: storeId={}", storeId);
|
|
|
+ throw new RuntimeException("店铺用户不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 账户总余额(单位:元,保留2位小数)
|
|
|
+ String balance = new BigDecimal(storeUser.getMoney())
|
|
|
+ .divide(new BigDecimal(100), 2, RoundingMode.HALF_UP)
|
|
|
+ .toString();
|
|
|
+ map.put("balance", balance);
|
|
|
+ log.debug("IncomeManageServiceImpl.getAccountBalance - 账户总余额: {}元", balance);
|
|
|
+
|
|
|
+ // 3. 查询可提现金额(4~27天内的收入,且未绑定提现记录)
|
|
|
+ LambdaQueryWrapper<StoreIncomeDetailsRecord> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.between(StoreIncomeDetailsRecord::getCreatedTime,
|
|
|
+ DateUtils.calcDays(new Date(), -27),
|
|
|
+ DateUtils.calcDays(new Date(), -4))
|
|
|
+ .isNull(StoreIncomeDetailsRecord::getCashOutId) // 未绑定提现记录
|
|
|
+ .eq(StoreIncomeDetailsRecord::getStoreId, storeId);
|
|
|
+
|
|
|
+ List<StoreIncomeDetailsRecord> incomeList = storeIncomeDetailsRecordMapper.selectList(wrapper);
|
|
|
+
|
|
|
+ // 计算可提现收入总额(分)
|
|
|
+ int cashOutMoney = incomeList.stream()
|
|
|
+ .mapToInt(StoreIncomeDetailsRecord::getMoney)
|
|
|
+ .sum();
|
|
|
+
|
|
|
+ log.debug("IncomeManageServiceImpl.getAccountBalance - 4~27天内收入总额: {}分", cashOutMoney);
|
|
|
+
|
|
|
+ // 4. 查询已提现和待审核的金额(需要扣除)
|
|
|
+ // payment_status: 1-待审核, 3-已提现
|
|
|
+ QueryWrapper<StoreCashOutRecord> cashOutWrapper = new QueryWrapper<>();
|
|
|
+ cashOutWrapper.eq("store_id", storeId)
|
|
|
+ .in("payment_status", "1", "3")
|
|
|
+ .eq("delete_flag", "0");
|
|
|
+
|
|
|
+ List<StoreCashOutRecord> storeCashOutRecords = storeCashOutRecordMapper.selectList(cashOutWrapper);
|
|
|
+
|
|
|
+ // 计算已提现和待审核的总金额(分)
|
|
|
+ int totalCashOutAmount = storeCashOutRecords.stream()
|
|
|
+ .collect(Collectors.summingInt(StoreCashOutRecord::getMoney));
|
|
|
+
|
|
|
+ log.debug("IncomeManageServiceImpl.getAccountBalance - 已提现+待审核金额: {}分", totalCashOutAmount);
|
|
|
+
|
|
|
+ // 5. 计算最终可提现金额 = 可提现收入 - (已提现 + 待审核)
|
|
|
+ BigDecimal finalCashOutMoney = new BigDecimal(cashOutMoney)
|
|
|
+ .subtract(BigDecimal.valueOf(totalCashOutAmount))
|
|
|
+ .divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);
|
|
|
+
|
|
|
+ map.put("cashOutMoney", finalCashOutMoney.toString());
|
|
|
+
|
|
|
+ log.info("IncomeManageServiceImpl.getAccountBalance - 查询完成: 总余额={}元, 可提现金额={}元",
|
|
|
+ balance, finalCashOutMoney);
|
|
|
+
|
|
|
+ return map;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 提现申请
|
|
|
+ *
|
|
|
+ * @param storeId 门店ID
|
|
|
+ * @param payPassword 支付密码
|
|
|
+ * @param withdrawalMoney 提现金额(单位:分)
|
|
|
+ * @return 提现结果
|
|
|
+ */
|
|
|
+ @Transactional
|
|
|
+ @Override
|
|
|
+ public R<?> cashOut(Integer storeId, String payPassword, Integer withdrawalMoney) {
|
|
|
+ log.info("IncomeManageServiceImpl.cashOut - 开始处理提现: storeId={}, withdrawalMoney={}分",
|
|
|
+ storeId, withdrawalMoney);
|
|
|
+
|
|
|
+ // 1. 验证支付密码并查询用户信息
|
|
|
+ LambdaQueryWrapper<StoreUser> userWrapper = new LambdaQueryWrapper<>();
|
|
|
+ userWrapper.eq(StoreUser::getStoreId, storeId)
|
|
|
+ .eq(StoreUser::getPayPassword, payPassword);
|
|
|
+ StoreUser storeUser = storeUserMapper.selectOne(userWrapper);
|
|
|
+
|
|
|
+ if (storeUser == null) {
|
|
|
+ log.warn("IncomeManageServiceImpl.cashOut - 支付密码错误: storeId={}", storeId);
|
|
|
+ return R.fail("支付密码错误");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 验证账户余额是否充足
|
|
|
+ if (storeUser.getMoney() < withdrawalMoney) {
|
|
|
+ log.warn("IncomeManageServiceImpl.cashOut - 余额不足: 账户余额={}分, 提现金额={}分",
|
|
|
+ storeUser.getMoney(), withdrawalMoney);
|
|
|
+ return R.fail("余额不足");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 验证提现金额是否满足最低要求(0.1元 = 10分)
|
|
|
+ BigDecimal amountInYuan = new BigDecimal(withdrawalMoney)
|
|
|
+ .divide(new BigDecimal(100), 2, RoundingMode.DOWN);
|
|
|
+ if (amountInYuan.compareTo(new BigDecimal("0.1")) < 0) {
|
|
|
+ log.warn("IncomeManageServiceImpl.cashOut - 提现金额过小: {}元", amountInYuan);
|
|
|
+ return R.fail("金额不能小于0.1元");
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("IncomeManageServiceImpl.cashOut - 验证通过: 账户余额={}分, 提现金额={}元",
|
|
|
+ storeUser.getMoney(), amountInYuan);
|
|
|
+
|
|
|
+ // 4. 调用支付宝转账
|
|
|
+ // 注意:这里需要集成支付宝API,目前先创建提现申请记录
|
|
|
+ // 实际生产环境需要调用 AliApi.pay() 或 AliApi.payAccount()
|
|
|
+
|
|
|
+ try {
|
|
|
+ // TODO: 调用支付宝转账API
|
|
|
+ // StoreAliPayLog pay;
|
|
|
+ // if (StringUtils.isNotBlank(storeUser.getAlipayAccount())) {
|
|
|
+ // pay = aliApi.pay(storeUser.getName(), storeUser.getIdCard(),
|
|
|
+ // storeUser.getAlipayAccount(), amountInYuan.toString());
|
|
|
+ // } else {
|
|
|
+ // pay = aliApi.payAccount(storeUser.getName(), storeUser.getIdCard(),
|
|
|
+ // null, amountInYuan.toString(), storeUser.getPhone());
|
|
|
+ // }
|
|
|
+
|
|
|
+ // 5. 创建提现记录(待审核状态)
|
|
|
+ // Web端采用人工审核模式,不直接调用支付宝接口
|
|
|
+ StoreCashOutRecord storeCashOutRecord = new StoreCashOutRecord();
|
|
|
+ storeCashOutRecord.setStoreId(storeId);
|
|
|
+ storeCashOutRecord.setMoney(withdrawalMoney);
|
|
|
+ storeCashOutRecord.setCashOutType(0);
|
|
|
+ storeCashOutRecord.setPaymentStatus(1); // 1-待审核
|
|
|
+ storeCashOutRecord.setDeleteFlag(0);
|
|
|
+ storeCashOutRecord.setStoreUserId(storeUser.getId());
|
|
|
+ storeCashOutRecord.setPayDate(new Date());
|
|
|
+
|
|
|
+ // 插入提现记录
|
|
|
+ int insertResult = storeCashOutRecordMapper.insert(storeCashOutRecord);
|
|
|
+ if (insertResult <= 0) {
|
|
|
+ log.error("IncomeManageServiceImpl.cashOut - 创建提现记录失败");
|
|
|
+ throw new RuntimeException("创建提现记录失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 扣减账户余额
|
|
|
+ StoreUser updateUser = new StoreUser();
|
|
|
+ updateUser.setId(storeUser.getId());
|
|
|
+ updateUser.setMoney(storeUser.getMoney() - withdrawalMoney);
|
|
|
+ int updateResult = storeUserMapper.updateById(updateUser);
|
|
|
+ if (updateResult <= 0) {
|
|
|
+ log.error("IncomeManageServiceImpl.cashOut - 更新账户余额失败");
|
|
|
+ throw new RuntimeException("更新账户余额失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("IncomeManageServiceImpl.cashOut - 提现申请成功: 提现记录ID={}, 提现金额={}元",
|
|
|
+ storeCashOutRecord.getId(), amountInYuan);
|
|
|
+
|
|
|
+ // 7. 返回结果
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
+ result.put("cashOutRecordId", storeCashOutRecord.getId());
|
|
|
+ result.put("withdrawalMoney", amountInYuan.toString());
|
|
|
+ result.put("status", "pending"); // pending-待审核
|
|
|
+ result.put("message", "提现申请已提交,等待审核");
|
|
|
+
|
|
|
+ return R.data(result);
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("IncomeManageServiceImpl.cashOut - 提现失败: {}", e.getMessage(), e);
|
|
|
+
|
|
|
+ // 如果是已知的运行时异常,直接抛出让事务回滚
|
|
|
+ if (e instanceof RuntimeException) {
|
|
|
+ throw (RuntimeException) e;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 其他异常也需要回滚
|
|
|
+ throw new RuntimeException("提现失败:" + e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 提现记录查询
|
|
|
+ *
|
|
|
+ * @param storeId 门店ID
|
|
|
+ * @param cashOutStartTime 开始时间
|
|
|
+ * @param cashOutEndTime 结束时间
|
|
|
+ * @param paymentStatus 提现状态
|
|
|
+ * @param page 页码
|
|
|
+ * @param size 每页条数
|
|
|
+ * @return 提现记录列表
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public Object getCashOutRecordList(Integer storeId, String cashOutStartTime, String cashOutEndTime,
|
|
|
+ String paymentStatus, Integer page, Integer size) {
|
|
|
+ log.info("IncomeManageServiceImpl.getCashOutRecordList - 开始查询提现记录: storeId={}, cashOutStartTime={}, cashOutEndTime={}, paymentStatus={}, page={}, size={}",
|
|
|
+ storeId, cashOutStartTime, cashOutEndTime, paymentStatus, page, size);
|
|
|
+
|
|
|
+ // 1. 构建查询条件(使用QueryWrapper和表别名scor,以配合selectCashoutRecordList的LEFT JOIN查询)
|
|
|
+ QueryWrapper<StoreCashOutRecord> wrapper = new QueryWrapper<>();
|
|
|
+
|
|
|
+ // 必填条件:门店ID
|
|
|
+ wrapper.eq("scor.store_id", storeId);
|
|
|
+
|
|
|
+ // 可选条件:提现状态
|
|
|
+ if (StringUtils.isNotBlank(paymentStatus)) {
|
|
|
+ wrapper.eq("scor.payment_status", paymentStatus);
|
|
|
+ log.debug("IncomeManageServiceImpl.getCashOutRecordList - 筛选提现状态: {}", paymentStatus);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 可选条件:结束时间
|
|
|
+ if (StringUtils.isNotBlank(cashOutEndTime)) {
|
|
|
+ wrapper.le("scor.created_time", cashOutEndTime + " 23:59:59");
|
|
|
+ log.debug("IncomeManageServiceImpl.getCashOutRecordList - 结束时间: {}", cashOutEndTime);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 可选条件:开始时间
|
|
|
+ if (StringUtils.isNotBlank(cashOutStartTime)) {
|
|
|
+ wrapper.ge("scor.created_time", cashOutStartTime + " 00:00:00");
|
|
|
+ log.debug("IncomeManageServiceImpl.getCashOutRecordList - 开始时间: {}", cashOutStartTime);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 删除标识(软删除)
|
|
|
+ wrapper.eq("scor.delete_flag", 0);
|
|
|
+
|
|
|
+ // 按创建时间倒序排序
|
|
|
+ wrapper.orderByDesc("scor.created_time");
|
|
|
+
|
|
|
+ // 2. 查询所有符合条件的记录(LEFT JOIN store_user,获取结算账户信息)
|
|
|
+ List<StoreCashOutRecord> recordList = storeCashOutRecordMapper.selectCashoutRecordList(wrapper);
|
|
|
+ log.info("IncomeManageServiceImpl.getCashOutRecordList - 查询到提现记录数: {}", recordList.size());
|
|
|
+
|
|
|
+ // 3. 手动分页
|
|
|
+ IPage<StoreCashOutRecord> storeCashOutRecordIPage = ListToPage.setPage(recordList, page, size);
|
|
|
+
|
|
|
+ // 4. 构建返回VO
|
|
|
+ StoreCashOutRecordVo vo = new StoreCashOutRecordVo();
|
|
|
+
|
|
|
+ // 分页后的记录列表
|
|
|
+ vo.setCashOutRecordList(storeCashOutRecordIPage.getRecords());
|
|
|
+
|
|
|
+ // 提现总金额(仅统计状态为1-待审核的记录)
|
|
|
+ int totalMoney = recordList.stream()
|
|
|
+ .filter(item -> "1".equals(item.getPaymentStatus().toString()))
|
|
|
+ .mapToInt(StoreCashOutRecord::getMoney)
|
|
|
+ .sum();
|
|
|
+ BigDecimal cashOutAllMoney = new BigDecimal(totalMoney)
|
|
|
+ .divide(new BigDecimal(100), 2, RoundingMode.DOWN);
|
|
|
+ vo.setCashOutAllMoney(cashOutAllMoney);
|
|
|
+
|
|
|
+ // 提现记录总数
|
|
|
+ vo.setCashOutNum(recordList.size());
|
|
|
+
|
|
|
+ log.info("IncomeManageServiceImpl.getCashOutRecordList - 查询完成: 总记录数={}, 总金额={}元",
|
|
|
+ recordList.size(), cashOutAllMoney);
|
|
|
+
|
|
|
+ return vo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 团购收益查询
|
|
|
+ *
|
|
|
+ * @param storeId 门店ID
|
|
|
+ * @param date 日期
|
|
|
+ * @param incomeType 收入类型
|
|
|
+ * @param page 页码
|
|
|
+ * @param size 每页条数
|
|
|
+ * @return 团购收益信息
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public Object getGroupIncome(Integer storeId, String date, Integer incomeType, Integer page, Integer size) {
|
|
|
+ log.info("IncomeManageServiceImpl.getGroupIncome - 开始查询团购收益: storeId={}, date={}, incomeType={}, page={}, size={}",
|
|
|
+ storeId, date, incomeType, page, size);
|
|
|
+
|
|
|
+ // 1. 构建查询条件:指定日期内、未绑定提现记录
|
|
|
+ LambdaQueryWrapper<StoreIncomeDetailsRecord> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.between(StoreIncomeDetailsRecord::getCreatedTime, date + " 00:00:00", date + " 23:59:59")
|
|
|
+ .isNull(StoreIncomeDetailsRecord::getCashOutId) // 未绑定提现记录
|
|
|
+ .eq(StoreIncomeDetailsRecord::getStoreId, storeId);
|
|
|
+
|
|
|
+ // 2. 收入类型筛选
|
|
|
+ // 2025-11-17 前端展示逻辑将团购券去掉,仅展示优惠券(前端不传0)
|
|
|
+ if (null != incomeType && 0 == incomeType) {
|
|
|
+ wrapper.in(StoreIncomeDetailsRecord::getIncomeType,
|
|
|
+ CouponTypeEnum.COUPON.getCode(), CouponTypeEnum.GROUP_BUY.getCode());
|
|
|
+ log.debug("IncomeManageServiceImpl.getGroupIncome - 收入类型: 主页(优惠券+团购券)");
|
|
|
+ } else if (null != incomeType) {
|
|
|
+ wrapper.eq(StoreIncomeDetailsRecord::getIncomeType, incomeType);
|
|
|
+ log.debug("IncomeManageServiceImpl.getGroupIncome - 收入类型: {}", incomeType);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 查询店铺信息
|
|
|
+ StoreInfo storeInfo = storeInfoMapper.selectById(storeId);
|
|
|
+ if (storeInfo == null) {
|
|
|
+ log.warn("IncomeManageServiceImpl.getGroupIncome - 门店不存在: storeId={}", storeId);
|
|
|
+ throw new RuntimeException("门店不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 查询收入明细记录
|
|
|
+ List<StoreIncomeDetailsRecord> list = this.list(wrapper);
|
|
|
+ log.info("IncomeManageServiceImpl.getGroupIncome - 查询到收入记录数: {}", list.size());
|
|
|
+
|
|
|
+ // 5. 计算总收入和手续费
|
|
|
+ Integer allIncome = 0;
|
|
|
+ Integer commission = 0;
|
|
|
+ for (StoreIncomeDetailsRecord storeIncomeDetailsRecord : list) {
|
|
|
+ allIncome += storeIncomeDetailsRecord.getMoney();
|
|
|
+ commission += storeIncomeDetailsRecord.getCommission();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 构建返回VO
|
|
|
+ StoreIncomeDetailsRecordVo vo = new StoreIncomeDetailsRecordVo();
|
|
|
+ vo.setCommissionRate(storeInfo.getCommissionRate());
|
|
|
+ vo.setDate(date);
|
|
|
+ vo.setStoreName(storeInfo.getStoreName());
|
|
|
+ vo.setStoreId(storeId);
|
|
|
+
|
|
|
+ // 7. 格式化金额信息
|
|
|
+ // 售价(收入+手续费)
|
|
|
+ vo.setIncomeMoney(
|
|
|
+ new BigDecimal(allIncome)
|
|
|
+ .add(new BigDecimal(commission))
|
|
|
+ .divide(new BigDecimal(100), 2, RoundingMode.HALF_UP)
|
|
|
+ .toString()
|
|
|
+ );
|
|
|
+
|
|
|
+ // 手续费
|
|
|
+ vo.setCommissionStr(
|
|
|
+ new BigDecimal(commission)
|
|
|
+ .divide(new BigDecimal(100), 2, RoundingMode.HALF_UP)
|
|
|
+ .toString()
|
|
|
+ );
|
|
|
+
|
|
|
+ // 收益(售价-手续费,存入时已经减完了)
|
|
|
+ vo.setNoYetPaymentMoney(
|
|
|
+ new BigDecimal(allIncome)
|
|
|
+ .divide(new BigDecimal(100), 2, RoundingMode.HALF_UP)
|
|
|
+ .toString()
|
|
|
+ );
|
|
|
+
|
|
|
+ // 8. 查询明细记录列表(含退款标识)
|
|
|
+ QueryWrapper<StoreIncomeDetailsRecordVo> incomeWrapper = new QueryWrapper<>();
|
|
|
+ incomeWrapper.eq("income.delete_flag", 0)
|
|
|
+ .eq("income.store_id", storeId)
|
|
|
+ .between("income.created_time", date + " 00:00:00", date + " 23:59:59")
|
|
|
+ .orderByDesc("income.created_time");
|
|
|
+
|
|
|
+ if (null != incomeType) {
|
|
|
+ if (0 == incomeType) {
|
|
|
+ incomeWrapper.in("income_type",
|
|
|
+ CouponTypeEnum.COUPON.getCode(), CouponTypeEnum.GROUP_BUY.getCode());
|
|
|
+ } else {
|
|
|
+ incomeWrapper.eq("income_type", incomeType);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ List<StoreIncomeDetailsRecordVo> incomeDetailsRecordVoList =
|
|
|
+ storeIncomeDetailsRecordMapper.getIncomeList(incomeWrapper);
|
|
|
+
|
|
|
+ // 9. 按退款类型分组
|
|
|
+ Map<String, List<StoreIncomeDetailsRecordVo>> collect =
|
|
|
+ incomeDetailsRecordVoList.stream()
|
|
|
+ .collect(Collectors.groupingBy(x -> x.getRefundType()));
|
|
|
+
|
|
|
+ // 10. 设置明细列表(不含退款)和统计信息
|
|
|
+ vo.setIncomeDetailsRecordVoList(new ArrayList<>());
|
|
|
+ vo.setRefundMoney(new BigDecimal(0));
|
|
|
+ vo.setCouponCount(0);
|
|
|
+
|
|
|
+ // 正常收入记录(未退款)
|
|
|
+ if (collect.containsKey("false")) {
|
|
|
+ vo.setIncomeDetailsRecordVoList(
|
|
|
+ ListToPage.setPage(incomeDetailsRecordVoList, page, size).getRecords()
|
|
|
+ );
|
|
|
+ // 计算券数量(正常订单 - 退款订单)
|
|
|
+ vo.setCouponCount(
|
|
|
+ collect.get("false").size() -
|
|
|
+ (collect.containsKey("true") ? collect.get("true").size() : 0)
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // 退款金额统计
|
|
|
+ if (collect.containsKey("true")) {
|
|
|
+ vo.setRefundMoney(
|
|
|
+ new BigDecimal(
|
|
|
+ collect.get("true").stream()
|
|
|
+ .mapToInt(StoreIncomeDetailsRecordVo::getMoney)
|
|
|
+ .sum()
|
|
|
+ ).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP)
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("IncomeManageServiceImpl.getGroupIncome - 查询完成: 总收入={}元, 手续费={}元, 券数量={}",
|
|
|
+ vo.getNoYetPaymentMoney(), vo.getCommissionStr(), vo.getCouponCount());
|
|
|
+
|
|
|
+ return vo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 快速提现申请(免审核,直接通过状态)
|
|
|
+ * 与app端applyCashOut逻辑保持一致
|
|
|
+ *
|
|
|
+ * @param storeId 门店ID
|
|
|
+ * @param payPassword 支付密码
|
|
|
+ * @param withdrawalMoney 提现金额(单位:分)
|
|
|
+ * @return 提现结果
|
|
|
+ */
|
|
|
+ @Transactional
|
|
|
+ @Override
|
|
|
+ public R<?> applyFastCashOut(Integer storeId, String payPassword, Integer withdrawalMoney) {
|
|
|
+ log.info("IncomeManageServiceImpl.applyFastCashOut - 开始处理快速提现: storeId={}, withdrawalMoney={}分",
|
|
|
+ storeId, withdrawalMoney);
|
|
|
+
|
|
|
+ // 1. 验证支付密码并查询用户信息
|
|
|
+ LambdaQueryWrapper<StoreUser> userWrapper = new LambdaQueryWrapper<>();
|
|
|
+ userWrapper.eq(StoreUser::getStoreId, storeId)
|
|
|
+ .eq(StoreUser::getPayPassword, payPassword);
|
|
|
+ StoreUser storeUser = storeUserMapper.selectOne(userWrapper);
|
|
|
+
|
|
|
+ if (storeUser == null) {
|
|
|
+ log.warn("IncomeManageServiceImpl.applyFastCashOut - 支付密码错误: storeId={}", storeId);
|
|
|
+ return R.fail("支付密码错误");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 验证账户余额是否充足
|
|
|
+ if (storeUser.getMoney() <= withdrawalMoney) {
|
|
|
+ log.warn("IncomeManageServiceImpl.applyFastCashOut - 余额不足: 账户余额={}分, 提现金额={}分",
|
|
|
+ storeUser.getMoney(), withdrawalMoney);
|
|
|
+ return R.fail("余额不足");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 验证提现金额是否满足最低要求(0.1元 = 10分)
|
|
|
+ BigDecimal amountInYuan = new BigDecimal(withdrawalMoney)
|
|
|
+ .divide(new BigDecimal(100), 2, RoundingMode.DOWN);
|
|
|
+ if (amountInYuan.compareTo(new BigDecimal("0.1")) < 0) {
|
|
|
+ log.warn("IncomeManageServiceImpl.applyFastCashOut - 提现金额过小: {}元", amountInYuan);
|
|
|
+ return R.fail("金额不能小于0.1元");
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("IncomeManageServiceImpl.applyFastCashOut - 验证通过: 账户余额={}分, 提现金额={}元",
|
|
|
+ storeUser.getMoney(), amountInYuan);
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 4. 创建提现记录(已通过状态,与app端保持一致)
|
|
|
+ StoreCashOutRecord storeCashOutRecord = new StoreCashOutRecord();
|
|
|
+ storeCashOutRecord.setStoreId(storeId);
|
|
|
+ storeCashOutRecord.setMoney(withdrawalMoney);
|
|
|
+ storeCashOutRecord.setCashOutType(0); // 0-全部提现
|
|
|
+ storeCashOutRecord.setPaymentStatus(3); // 3-已通过(与app端保持一致)
|
|
|
+ storeCashOutRecord.setDeleteFlag(0);
|
|
|
+ storeCashOutRecord.setIncomeStartTime(new Date());
|
|
|
+ storeCashOutRecord.setIncomeEndTime(new Date());
|
|
|
+ storeCashOutRecord.setStoreUserId(storeUser.getId());
|
|
|
+
|
|
|
+ // 5. 插入提现记录
|
|
|
+ int insertResult = storeCashOutRecordMapper.insert(storeCashOutRecord);
|
|
|
+ if (insertResult <= 0) {
|
|
|
+ log.error("IncomeManageServiceImpl.applyFastCashOut - 创建提现记录失败");
|
|
|
+ throw new RuntimeException("创建提现记录失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("IncomeManageServiceImpl.applyFastCashOut - 快速提现申请成功: 提现记录ID={}, 提现金额={}元",
|
|
|
+ storeCashOutRecord.getId(), amountInYuan);
|
|
|
+
|
|
|
+ // 6. 返回结果(返回StoreCashOutRecord对象,与app端保持一致)
|
|
|
+ return R.data(storeCashOutRecord);
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("IncomeManageServiceImpl.applyFastCashOut - 快速提现失败: {}", e.getMessage(), e);
|
|
|
+
|
|
|
+ // 如果是已知的运行时异常,直接抛出让事务回滚
|
|
|
+ if (e instanceof RuntimeException) {
|
|
|
+ throw (RuntimeException) e;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 其他异常也需要回滚
|
|
|
+ throw new RuntimeException("快速提现失败:" + e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|