package com.ruoyi.sales.service.impl;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
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.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.time.LocalDate;
|
import java.time.format.DateTimeFormatter;
|
import java.util.*;
|
import java.util.concurrent.TimeUnit;
|
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(SalesLedger salesLedger) {
|
return salesLedgerMapper.selectList(new LambdaQueryWrapper<>());
|
}
|
|
public List<SalesLedgerDto> getSalesLedgerWithProducts() {
|
List<SalesLedger> ledgers = salesLedgerMapper.selectList(new LambdaQueryWrapper<>());
|
List<SalesLedgerProduct> products = salesLedgerProductMapper.selectList(new LambdaQueryWrapper<>());
|
|
Map<Long, List<SalesLedgerProduct>> productMap = products.stream()
|
.collect(Collectors.groupingBy(SalesLedgerProduct::getSalesLedgerId));
|
|
return ledgers.stream().map(ledger -> {
|
SalesLedgerDto dto = new SalesLedgerDto();
|
org.springframework.beans.BeanUtils.copyProperties(ledger, dto);
|
|
List<SalesLedgerProduct> ledgerProducts = productMap.getOrDefault(ledger.getId(), Collections.emptyList());
|
if (!ledgerProducts.isEmpty()) {
|
dto.setHasChildren(true);
|
dto.setProductData(ledgerProducts);
|
}
|
return dto;
|
}).collect(Collectors.toList());
|
}
|
|
@Override
|
public SalesLedger selectSalesLedgerById(Long id) {
|
return salesLedgerMapper.selectById(id);
|
}
|
|
@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. 处理子表数据
|
if (salesLedgerDto.getProductData() != null && !salesLedgerDto.getProductData().isEmpty()) {
|
handleSalesLedgerProducts(salesLedger.getId(), salesLedgerDto.getProductData());
|
}
|
|
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()) {
|
salesLedgerProductMapper.updateBatchSomeColumn(updateList);
|
}
|
// 执行插入操作
|
if (!insertList.isEmpty()) {
|
salesLedgerProductMapper.insertBatchSomeColumn(insertList);
|
}
|
}
|
|
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;
|
}
|
}
|