package com.ruoyi.sales.service.impl;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.ruoyi.basic.mapper.CustomerMapper;
|
import com.ruoyi.basic.pojo.Customer;
|
import com.ruoyi.common.exception.base.BaseException;
|
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.sales.dto.SalesLedgerDto;
|
import com.ruoyi.sales.mapper.SalesLedgerMapper;
|
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
|
import com.ruoyi.sales.pojo.SalesLedger;
|
import com.ruoyi.sales.pojo.SalesLedgerProduct;
|
import com.ruoyi.sales.service.ISalesLedgerService;
|
import lombok.AllArgsConstructor;
|
import org.springframework.beans.BeanUtils;
|
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import java.lang.reflect.Field;
|
import java.math.BigDecimal;
|
import java.time.LocalDate;
|
import java.time.format.DateTimeFormatter;
|
import java.util.*;
|
import java.util.concurrent.TimeUnit;
|
import java.util.function.Function;
|
import java.util.stream.Collectors;
|
|
/**
|
* 销售台账Service业务层处理
|
*
|
* @author ruoyi
|
* @date 2025-05-08
|
*/
|
@Service
|
@AllArgsConstructor
|
public class SalesLedgerServiceImpl extends ServiceImpl<SalesLedgerMapper, SalesLedger> implements ISalesLedgerService {
|
|
private SalesLedgerMapper salesLedgerMapper;
|
|
private CustomerMapper customerMapper;
|
|
private SalesLedgerProductMapper salesLedgerProductMapper;
|
|
private static final String LOCK_PREFIX = "contract_no_lock:";
|
private static final long LOCK_WAIT_TIMEOUT = 10; // 锁等待超时时间(秒)
|
private static final long LOCK_EXPIRE_TIME = 30; // 锁自动过期时间(秒)
|
|
private final RedisTemplate<String, String> redisTemplate;
|
|
@Override
|
public List<SalesLedger> selectSalesLedgerList(SalesLedgerDto salesLedgerDto) {
|
LambdaQueryWrapper<SalesLedger> queryWrapper = new LambdaQueryWrapper<>();
|
if (StringUtils.isNotBlank(salesLedgerDto.getCustomerName())) {
|
queryWrapper.eq(SalesLedger::getCustomerName, salesLedgerDto.getCustomerName());
|
}
|
return salesLedgerMapper.selectList(queryWrapper);
|
}
|
|
public SalesLedgerDto getSalesLedgerWithProducts(SalesLedgerDto salesLedgerDto) {
|
// 1. 查询主表
|
SalesLedger salesLedger = salesLedgerMapper.selectById(salesLedgerDto.getId());
|
if (salesLedger == null) {
|
throw new BaseException("台账不存在");
|
}
|
|
// 2. 查询子表
|
LambdaQueryWrapper<SalesLedgerProduct> productWrapper = new LambdaQueryWrapper<>();
|
productWrapper.eq(SalesLedgerProduct::getSalesLedgerId, salesLedger.getId());
|
List<SalesLedgerProduct> products = salesLedgerProductMapper.selectList(productWrapper);
|
|
// 3. 转换 DTO
|
SalesLedgerDto resultDto = new SalesLedgerDto();
|
BeanUtils.copyProperties(salesLedger, resultDto);
|
if (!products.isEmpty()) {
|
resultDto.setHasChildren(true);
|
resultDto.setProductData(products);
|
}
|
return resultDto;
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public int deleteSalesLedgerByIds(Long[] ids) {
|
List<Long> idList = Arrays.stream(ids)
|
.filter(Objects::nonNull)
|
.collect(Collectors.toList());
|
|
if (CollectionUtils.isEmpty(idList)) {
|
return 0;
|
}
|
// 1. 先删除子表数据
|
LambdaQueryWrapper<SalesLedgerProduct> productWrapper = new LambdaQueryWrapper<>();
|
productWrapper.in(SalesLedgerProduct::getSalesLedgerId, idList);
|
salesLedgerProductMapper.delete(productWrapper);
|
|
// 2. 再删除主表数据
|
return salesLedgerMapper.deleteBatchIds(idList);
|
}
|
|
@Transactional(rollbackFor = Exception.class)
|
public int addOrUpdateSalesLedger(SalesLedgerDto salesLedgerDto) {
|
// 1. 校验客户信息
|
Customer customer = customerMapper.selectById(salesLedgerDto.getCustomerId());
|
if (customer == null) {
|
throw new BaseException("客户不存在");
|
}
|
|
// 2. DTO转Entity
|
SalesLedger salesLedger = convertToEntity(salesLedgerDto);
|
salesLedger.setCustomerName(customer.getCustomerName());
|
salesLedger.setTenantId(customer.getTenantId());
|
|
// 3. 新增或更新主表
|
if (salesLedger.getId() == null) {
|
// 生成合同编号
|
String contractNo = generateSalesContractNo();
|
salesLedger.setSalesContractNo(contractNo);
|
salesLedgerMapper.insert(salesLedger);
|
} else {
|
salesLedgerMapper.updateById(salesLedger);
|
}
|
|
// 4. 处理子表数据
|
List<SalesLedgerProduct> productList = salesLedgerDto.getProductData();
|
if (productList != null && !productList.isEmpty()) {
|
handleSalesLedgerProducts(salesLedger.getId(), productList);
|
|
// ✅ 调用通用方法更新主表金额
|
updateMainContractAmount(
|
salesLedger.getId(),
|
productList,
|
SalesLedgerProduct::getTaxInclusiveTotalPrice,
|
salesLedgerMapper,
|
SalesLedger.class
|
);
|
}
|
return 1; // 操作成功返回1
|
}
|
|
private void handleSalesLedgerProducts(Long salesLedgerId, List<SalesLedgerProduct> products) {
|
// 按ID分组,区分新增和更新的记录
|
Map<Boolean, List<SalesLedgerProduct>> partitionedProducts = products.stream()
|
.peek(p -> p.setSalesLedgerId(salesLedgerId))
|
.collect(Collectors.partitioningBy(p -> p.getId() != null));
|
|
List<SalesLedgerProduct> updateList = partitionedProducts.get(true);
|
List<SalesLedgerProduct> insertList = partitionedProducts.get(false);
|
|
// 执行更新操作
|
if (!updateList.isEmpty()) {
|
for (SalesLedgerProduct product : updateList) {
|
salesLedgerProductMapper.updateById(product);
|
}
|
}
|
// 执行插入操作
|
if (!insertList.isEmpty()) {
|
for (SalesLedgerProduct salesLedgerProduct : insertList) {
|
salesLedgerProductMapper.insert(salesLedgerProduct);
|
}
|
}
|
}
|
|
private SalesLedger convertToEntity(SalesLedgerDto dto) {
|
SalesLedger entity = new SalesLedger();
|
BeanUtils.copyProperties(dto, entity);
|
return entity;
|
}
|
|
@Transactional(readOnly = true)
|
public String generateSalesContractNo() {
|
LocalDate currentDate = LocalDate.now();
|
String datePart = currentDate.format(DateTimeFormatter.BASIC_ISO_DATE);
|
String lockKey = LOCK_PREFIX + datePart;
|
String lockValue = Thread.currentThread().getId() + "-" + System.nanoTime(); // 唯一标识锁持有者
|
|
try {
|
// 1. 尝试获取分布式锁(循环直到超时)
|
long startWaitTime = System.currentTimeMillis();
|
while (System.currentTimeMillis() - startWaitTime < LOCK_WAIT_TIMEOUT * 1000) {
|
// SET key value NX PX 30000:仅当锁不存在时获取,设置30秒过期
|
Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, LOCK_EXPIRE_TIME, TimeUnit.SECONDS);
|
if (Boolean.TRUE.equals(locked)) {
|
break; // 成功获取锁
|
}
|
// 短暂休眠避免忙等待
|
try {
|
Thread.sleep(100);
|
} catch (InterruptedException e) {
|
Thread.currentThread().interrupt();
|
throw new RuntimeException("获取锁时被中断", e);
|
}
|
}
|
|
if (!redisTemplate.hasKey(lockKey)) {
|
throw new RuntimeException("获取合同编号生成锁失败:超时");
|
}
|
|
// 2. 查询当天已存在的序列号(与原逻辑一致)
|
List<Integer> existingSequences = salesLedgerMapper.selectSequencesByDate(datePart);
|
int nextSequence = findFirstMissingSequence(existingSequences);
|
|
return datePart + String.format("%02d", nextSequence);
|
} finally {
|
// 3. 释放锁(使用Lua脚本保证原子性,避免误删其他线程的锁)
|
String luaScript = "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end";
|
redisTemplate.execute(
|
new DefaultRedisScript<>(luaScript, Long.class),
|
Collections.singletonList(lockKey),
|
lockValue // 只有持有相同值的线程才能删除锁
|
);
|
}
|
}
|
|
private int findFirstMissingSequence(List<Integer> sequences) {
|
if (sequences.isEmpty()) {
|
return 1;
|
}
|
// 排序后查找第一个缺失的正整数(与原逻辑一致)
|
sequences.sort(Integer::compareTo);
|
int next = 1;
|
for (int seq : sequences) {
|
if (seq == next) {
|
next++;
|
} else if (seq > next) {
|
break;
|
}
|
}
|
return next;
|
}
|
|
public <T, S> void updateMainContractAmount(
|
Long mainId,
|
List<T> subList,
|
Function<T, BigDecimal> amountGetter,
|
BaseMapper<S> mainMapper,
|
Class<S> mainEntityClass) {
|
|
if (mainId == null || subList == null || subList.isEmpty()) {
|
return;
|
}
|
|
// 计算子表金额总和
|
BigDecimal totalAmount = subList.stream()
|
.map(amountGetter)
|
.filter(Objects::nonNull)
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
// 构造主表更新对象(支持任意主表类型)
|
try {
|
S entity = mainEntityClass.getDeclaredConstructor().newInstance();
|
Field idField = mainEntityClass.getDeclaredField("id");
|
idField.setAccessible(true);
|
idField.set(entity, mainId);
|
|
// 设置 contractAmount 字段,注意这里假设字段名为 "contractAmount"
|
Field amountField = mainEntityClass.getDeclaredField("contractAmount");
|
amountField.setAccessible(true);
|
amountField.set(entity, totalAmount);
|
|
mainMapper.updateById(entity);
|
} catch (Exception e) {
|
throw new RuntimeException("动态更新主表金额失败", e);
|
}
|
}
|
}
|