package com.ruoyi.purchase.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.common.exception.base.BaseException;
|
import com.ruoyi.common.utils.DateUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.bean.BeanUtils;
|
import com.ruoyi.other.mapper.TempFileMapper;
|
import com.ruoyi.other.pojo.TempFile;
|
import com.ruoyi.purchase.dto.TicketRegistrationDto;
|
import com.ruoyi.purchase.mapper.ProductRecordMapper;
|
import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
|
import com.ruoyi.purchase.mapper.TicketRegistrationMapper;
|
import com.ruoyi.purchase.pojo.ProductRecord;
|
import com.ruoyi.purchase.pojo.PurchaseLedger;
|
import com.ruoyi.purchase.pojo.TicketRegistration;
|
import com.ruoyi.purchase.service.ITicketRegistrationService;
|
import com.ruoyi.sales.mapper.CommonFileMapper;
|
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
|
import com.ruoyi.sales.pojo.CommonFile;
|
import com.ruoyi.sales.pojo.SalesLedgerProduct;
|
import lombok.RequiredArgsConstructor;
|
import lombok.extern.slf4j.Slf4j;
|
import org.apache.commons.io.FilenameUtils;
|
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import java.io.IOException;
|
import java.math.BigDecimal;
|
import java.nio.file.Files;
|
import java.nio.file.Path;
|
import java.nio.file.Paths;
|
import java.nio.file.StandardCopyOption;
|
import java.time.LocalDate;
|
import java.time.LocalDateTime;
|
import java.time.format.DateTimeFormatter;
|
import java.util.Arrays;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.UUID;
|
import java.util.stream.Collectors;
|
|
/**
|
* 来票登记Service业务层处理
|
*
|
* @author ruoyi
|
* @date 2025-05-13
|
*/
|
@Service
|
@RequiredArgsConstructor
|
@Slf4j
|
public class TicketRegistrationServiceImpl extends ServiceImpl<TicketRegistrationMapper, TicketRegistration> implements ITicketRegistrationService {
|
|
private final TicketRegistrationMapper ticketRegistrationMapper;
|
|
private final PurchaseLedgerMapper purchaseLedgerMapper;
|
|
private final SalesLedgerProductMapper salesLedgerProductMapper;
|
|
private final CommonFileMapper commonFileMapper;
|
|
private final TempFileMapper tempFileMapper;
|
|
private final ProductRecordMapper productRecordMapper;
|
|
@Value("${file.upload-dir}")
|
private String uploadDir;
|
|
|
@Override
|
public List<TicketRegistration> selectTicketRegistrationList(TicketRegistration ticketRegistration) {
|
LambdaQueryWrapper<TicketRegistration> queryWrapper = new LambdaQueryWrapper<>();
|
if (StringUtils.isNotBlank(ticketRegistration.getPurchaseContractNumber())) {
|
queryWrapper.like(TicketRegistration::getPurchaseContractNumber, ticketRegistration.getPurchaseContractNumber())
|
.like(TicketRegistration::getSupplierName, ticketRegistration.getSupplierName())
|
.eq(TicketRegistration::getIssueDate, ticketRegistration.getIssueDate());
|
}
|
return ticketRegistrationMapper.selectList(queryWrapper);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public int addOrUpdateRegistration(TicketRegistrationDto ticketRegistrationDto) throws IOException {
|
// 1. 查询采购台账记录
|
PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectById(ticketRegistrationDto.getPurchaseLedgerId());
|
if (purchaseLedger == null) {
|
// 处理采购台账不存在的情况,例如抛出异常或返回错误
|
throw new IllegalArgumentException("采购台账记录不存在,ID: " + ticketRegistrationDto.getPurchaseLedgerId());
|
}
|
|
// 3. 创建或更新票据登记实体
|
TicketRegistration ticketRegistration = new TicketRegistration();
|
BeanUtils.copyProperties(ticketRegistrationDto, ticketRegistration);
|
ticketRegistration.setPurchaseContractNumber(purchaseLedger.getPurchaseContractNumber());
|
ticketRegistration.setTenantId(purchaseLedger.getTenantId());
|
ticketRegistration.setContractAmount(purchaseLedger.getContractAmount());
|
ticketRegistration.setSalesLedgerId(purchaseLedger.getSalesLedgerId());
|
|
// 4. 处理子表数据
|
List<SalesLedgerProduct> productData = ticketRegistrationDto.getProductData();
|
if (CollectionUtils.isNotEmpty(productData)) {
|
handleSalesLedgerProducts(purchaseLedger.getId(), productData, 2);
|
}
|
|
// 5. 执行插入或更新操作
|
int rowsAffected = ticketRegistrationMapper.insert(ticketRegistration);
|
|
// 6. 增加采购台账产品开票记录
|
List<SalesLedgerProduct> salesLedgerProducts = ticketRegistrationDto.getProductData();
|
if (CollectionUtils.isNotEmpty(salesLedgerProducts)){
|
for (SalesLedgerProduct salesLedgerProduct : salesLedgerProducts) {
|
ProductRecord productRecord = new ProductRecord();
|
productRecord.setTicketRegistrationId(ticketRegistration.getId());
|
productRecord.setPurchaseLedgerId(ticketRegistrationDto.getPurchaseLedgerId());
|
productRecord.setCreatedAt(DateUtils.getNowDate());
|
BeanUtils.copyProperties(salesLedgerProduct,productRecord);
|
productRecord.setId(null);
|
productRecord.setType("2");
|
productRecordMapper.insert(productRecord);
|
}
|
}
|
// 迁移临时文件到正式目录
|
if (ticketRegistrationDto.getTempFileIds() != null && !ticketRegistrationDto.getTempFileIds().isEmpty()) {
|
migrateTempFilesToFormal(ticketRegistration.getId(), ticketRegistrationDto.getTempFileIds());
|
}
|
return rowsAffected;
|
}
|
|
|
/**
|
* 将临时文件迁移到正式目录
|
*
|
* @param businessId 业务ID(销售台账ID)
|
* @param tempFileIds 临时文件ID列表
|
* @throws IOException 文件操作异常
|
*/
|
private void migrateTempFilesToFormal(Long businessId, List<String> tempFileIds) throws IOException {
|
if (CollectionUtils.isEmpty(tempFileIds)) {
|
return;
|
}
|
|
// 构建正式目录路径(按业务类型和日期分组)
|
String formalDir = uploadDir + LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE);
|
|
Path formalDirPath = Paths.get(formalDir);
|
|
// 确保正式目录存在(递归创建)
|
if (!Files.exists(formalDirPath)) {
|
Files.createDirectories(formalDirPath);
|
}
|
|
for (String tempFileId : tempFileIds) {
|
// 查询临时文件记录
|
TempFile tempFile = tempFileMapper.selectById(tempFileId);
|
if (tempFile == null) {
|
log.warn("临时文件不存在,跳过处理: {}", tempFileId);
|
continue;
|
}
|
|
// 构建正式文件名(包含业务ID和时间戳,避免冲突)
|
String originalFilename = tempFile.getOriginalName();
|
String fileExtension = FilenameUtils.getExtension(originalFilename);
|
String formalFilename = businessId + "_" +
|
System.currentTimeMillis() + "_" +
|
UUID.randomUUID().toString().substring(0, 8) +
|
(com.ruoyi.common.utils.StringUtils.hasText(fileExtension) ? "." + fileExtension : "");
|
|
Path formalFilePath = formalDirPath.resolve(formalFilename);
|
|
try {
|
// 执行文件迁移(使用原子操作确保安全性)
|
Files.move(
|
Paths.get(tempFile.getTempPath()),
|
formalFilePath,
|
StandardCopyOption.REPLACE_EXISTING,
|
StandardCopyOption.ATOMIC_MOVE
|
);
|
log.info("文件迁移成功: {} -> {}", tempFile.getTempPath(), formalFilePath);
|
|
// 更新文件记录(关联到业务ID)
|
CommonFile fileRecord = new CommonFile();
|
fileRecord.setCommonId(businessId);
|
fileRecord.setName(originalFilename);
|
fileRecord.setUrl(formalFilePath.toString());
|
fileRecord.setCreateTime(LocalDateTime.now());
|
fileRecord.setType(tempFile.getType());
|
commonFileMapper.insert(fileRecord);
|
|
log.info("文件迁移成功: {} -> {}", tempFile.getTempPath(), formalFilePath);
|
} catch (IOException e) {
|
log.error("文件迁移失败: {}", tempFile.getTempPath(), e);
|
// 可选择回滚事务或记录失败文件
|
throw new IOException("文件迁移异常", e);
|
}
|
}
|
}
|
|
|
@Override
|
public int delRegistration(Long[] ids) {
|
return ticketRegistrationMapper.deleteBatchIds(Arrays.asList(ids));
|
}
|
|
@Override
|
public TicketRegistrationDto getRegistrationById(TicketRegistrationDto ticketRegistrationDto) {
|
TicketRegistration ticketRegistration = ticketRegistrationMapper.selectById(ticketRegistrationDto.getId());
|
LambdaQueryWrapper<PurchaseLedger> purchaseQueryWrapper = new LambdaQueryWrapper<>();
|
purchaseQueryWrapper.eq(PurchaseLedger::getId, ticketRegistration.getPurchaseLedgerId());
|
PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectOne(purchaseQueryWrapper);
|
if (ticketRegistration == null) {
|
throw new BaseException("采购台账不存在");
|
}
|
// 创建并填充DTO
|
TicketRegistrationDto resultDto = new TicketRegistrationDto();
|
BeanUtils.copyProperties(ticketRegistration, resultDto);
|
|
// 查询并设置关联产品
|
LambdaQueryWrapper<SalesLedgerProduct> queryWrapper = new LambdaQueryWrapper<>();
|
queryWrapper.eq(SalesLedgerProduct::getSalesLedgerId, purchaseLedger.getId())
|
.eq(SalesLedgerProduct::getType, 2);
|
List<SalesLedgerProduct> productList = salesLedgerProductMapper.selectList(queryWrapper);
|
resultDto.setProductData(productList);
|
return resultDto;
|
}
|
|
@Override
|
public List getTicketNo(TicketRegistrationDto ticketRegistrationDto) {
|
LambdaQueryWrapper<TicketRegistration> queryWrapper = new LambdaQueryWrapper<>();
|
queryWrapper.select(TicketRegistration::getId,TicketRegistration::getInvoiceNumber,TicketRegistration::getInvoiceAmount)
|
.eq(TicketRegistration::getPurchaseLedgerId,ticketRegistrationDto.getId());
|
List<Map<String, Object>> result = ticketRegistrationMapper.selectMaps(queryWrapper);
|
// 将下划线命名转换为驼峰命名
|
return result.stream().map(map -> map.entrySet().stream()
|
.collect(Collectors.toMap(
|
entry -> underlineToCamel(entry.getKey()),
|
Map.Entry::getValue))
|
).collect(Collectors.toList());
|
}
|
|
private void handleSalesLedgerProducts(Long salesLedgerId, List<SalesLedgerProduct> products, Integer type) {
|
if (products == null || products.isEmpty()) {
|
return;
|
}
|
|
// 过滤出有 ID 的记录(即需要更新的记录)
|
List<SalesLedgerProduct> updateList = products.stream()
|
.filter(p -> p.getId() != null)
|
.peek(p -> {
|
p.setSalesLedgerId(salesLedgerId);
|
p.setType(type);
|
})
|
.collect(Collectors.toList());
|
|
// 批量更新(需要 MyBatis 提供批量更新方法)
|
if (!updateList.isEmpty()) {
|
updateList.forEach(product -> {
|
// 非空校验,任一字段为空则抛出异常
|
if (product.getQuantity() == null) {
|
throw new BaseException("数量不能为空");
|
}
|
if (product.getTicketsNum() == null) {
|
throw new BaseException("已开票数量不能为空");
|
}
|
if (product.getTaxInclusiveTotalPrice() == null) {
|
throw new BaseException("含税总价不能为空");
|
}
|
if (product.getTicketsAmount() == null) {
|
throw new BaseException("本次来票金额(元)不能为空");
|
}
|
|
// 计算 futureTickets(直接使用 BigDecimal 计算,避免精度丢失)
|
product.setFutureTickets(
|
product.getQuantity()
|
.subtract(BigDecimal.valueOf(product.getTicketsNum()))
|
.longValueExact() // 使用 exact 方法确保无小数部分
|
);
|
|
// 计算 futureTicketsAmount
|
product.setFutureTicketsAmount(
|
product.getTaxInclusiveTotalPrice()
|
.subtract(product.getTicketsAmount())
|
);
|
product.setType(type);
|
salesLedgerProductMapper.updateById(product);
|
});
|
}
|
}
|
|
/**
|
* 下划线命名转驼峰命名
|
*/
|
private String underlineToCamel(String param) {
|
if (param == null || "".equals(param.trim())) {
|
return "";
|
}
|
int len = param.length();
|
StringBuilder sb = new StringBuilder(len);
|
for (int i = 0; i < len; i++) {
|
char c = param.charAt(i);
|
if (c == '_') {
|
if (++i < len) {
|
sb.append(Character.toUpperCase(param.charAt(i)));
|
}
|
} else {
|
sb.append(Character.toLowerCase(c));
|
}
|
}
|
return sb.toString();
|
}
|
}
|