feat: 添加采购交易异步导入Service实现类
This commit is contained in:
@@ -0,0 +1,284 @@
|
|||||||
|
package com.ruoyi.ccdi.service.impl;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.ruoyi.ccdi.domain.CcdiPurchaseTransaction;
|
||||||
|
import com.ruoyi.ccdi.domain.dto.CcdiPurchaseTransactionAddDTO;
|
||||||
|
import com.ruoyi.ccdi.domain.excel.CcdiPurchaseTransactionExcel;
|
||||||
|
import com.ruoyi.ccdi.domain.vo.ImportFailureVO;
|
||||||
|
import com.ruoyi.ccdi.domain.vo.ImportResult;
|
||||||
|
import com.ruoyi.ccdi.domain.vo.ImportStatusVO;
|
||||||
|
import com.ruoyi.ccdi.mapper.CcdiPurchaseTransactionMapper;
|
||||||
|
import com.ruoyi.ccdi.service.ICcdiPurchaseTransactionImportService;
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 采购交易信息异步导入服务层处理
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2026-02-06
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@EnableAsync
|
||||||
|
public class CcdiPurchaseTransactionImportServiceImpl implements ICcdiPurchaseTransactionImportService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CcdiPurchaseTransactionMapper transactionMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Async
|
||||||
|
@Transactional
|
||||||
|
public void importTransactionAsync(List<CcdiPurchaseTransactionExcel> excelList, Boolean isUpdateSupport, String taskId, String userName) {
|
||||||
|
List<CcdiPurchaseTransaction> newRecords = new ArrayList<>();
|
||||||
|
List<CcdiPurchaseTransaction> updateRecords = new ArrayList<>();
|
||||||
|
List<ImportFailureVO> failures = new ArrayList<>();
|
||||||
|
|
||||||
|
// 批量查询已存在的采购事项ID
|
||||||
|
Set<String> existingIds = getExistingPurchaseIds(excelList);
|
||||||
|
|
||||||
|
// 分类数据
|
||||||
|
for (int i = 0; i < excelList.size(); i++) {
|
||||||
|
CcdiPurchaseTransactionExcel excel = excelList.get(i);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 转换为AddDTO进行验证
|
||||||
|
CcdiPurchaseTransactionAddDTO addDTO = new CcdiPurchaseTransactionAddDTO();
|
||||||
|
BeanUtils.copyProperties(excel, addDTO);
|
||||||
|
|
||||||
|
// 验证数据(支持更新模式)
|
||||||
|
validateTransactionData(addDTO, isUpdateSupport, existingIds);
|
||||||
|
|
||||||
|
CcdiPurchaseTransaction transaction = new CcdiPurchaseTransaction();
|
||||||
|
BeanUtils.copyProperties(excel, transaction);
|
||||||
|
|
||||||
|
if (existingIds.contains(excel.getPurchaseId())) {
|
||||||
|
if (isUpdateSupport) {
|
||||||
|
transaction.setUpdatedBy(userName);
|
||||||
|
updateRecords.add(transaction);
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("采购事项ID已存在且未启用更新支持");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
transaction.setCreatedBy(userName);
|
||||||
|
newRecords.add(transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
ImportFailureVO failure = new ImportFailureVO();
|
||||||
|
BeanUtils.copyProperties(excel, failure);
|
||||||
|
failure.setErrorMessage(e.getMessage());
|
||||||
|
failures.add(failure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量插入新数据
|
||||||
|
if (!newRecords.isEmpty()) {
|
||||||
|
saveBatch(newRecords, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量更新已有数据(先删除再插入)
|
||||||
|
if (!updateRecords.isEmpty() && isUpdateSupport) {
|
||||||
|
transactionMapper.insertOrUpdateBatch(updateRecords);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存失败记录到Redis
|
||||||
|
if (!failures.isEmpty()) {
|
||||||
|
String failuresKey = "import:purchaseTransaction:" + taskId + ":failures";
|
||||||
|
redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImportResult result = new ImportResult();
|
||||||
|
result.setTotalCount(excelList.size());
|
||||||
|
result.setSuccessCount(newRecords.size() + updateRecords.size());
|
||||||
|
result.setFailureCount(failures.size());
|
||||||
|
|
||||||
|
// 更新最终状态
|
||||||
|
String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS";
|
||||||
|
updateImportStatus(taskId, finalStatus, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取导入失败记录
|
||||||
|
*
|
||||||
|
* @param taskId 任务ID
|
||||||
|
* @return 失败记录列表
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<ImportFailureVO> getImportFailures(String taskId) {
|
||||||
|
String key = "import:purchaseTransaction:" + taskId + ":failures";
|
||||||
|
Object failuresObj = redisTemplate.opsForValue().get(key);
|
||||||
|
|
||||||
|
if (failuresObj == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.parseArray(JSON.toJSONString(failuresObj), ImportFailureVO.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询导入状态
|
||||||
|
*
|
||||||
|
* @param taskId 任务ID
|
||||||
|
* @return 导入状态信息
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ImportStatusVO getImportStatus(String taskId) {
|
||||||
|
String key = "import:purchaseTransaction:" + taskId;
|
||||||
|
Boolean hasKey = redisTemplate.hasKey(key);
|
||||||
|
|
||||||
|
if (Boolean.FALSE.equals(hasKey)) {
|
||||||
|
throw new RuntimeException("任务不存在或已过期");
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Object, Object> statusMap = redisTemplate.opsForHash().entries(key);
|
||||||
|
|
||||||
|
ImportStatusVO statusVO = new ImportStatusVO();
|
||||||
|
statusVO.setTaskId((String) statusMap.get("taskId"));
|
||||||
|
statusVO.setStatus((String) statusMap.get("status"));
|
||||||
|
statusVO.setTotalCount((Integer) statusMap.get("totalCount"));
|
||||||
|
statusVO.setSuccessCount((Integer) statusMap.get("successCount"));
|
||||||
|
statusVO.setFailureCount((Integer) statusMap.get("failureCount"));
|
||||||
|
statusVO.setProgress((Integer) statusMap.get("progress"));
|
||||||
|
statusVO.setStartTime((Long) statusMap.get("startTime"));
|
||||||
|
statusVO.setEndTime((Long) statusMap.get("endTime"));
|
||||||
|
statusVO.setMessage((String) statusMap.get("message"));
|
||||||
|
|
||||||
|
return statusVO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新导入状态
|
||||||
|
*/
|
||||||
|
private void updateImportStatus(String taskId, String status, ImportResult result) {
|
||||||
|
String key = "import:purchaseTransaction:" + taskId;
|
||||||
|
Map<String, Object> statusData = new HashMap<>();
|
||||||
|
statusData.put("status", status);
|
||||||
|
statusData.put("successCount", result.getSuccessCount());
|
||||||
|
statusData.put("failureCount", result.getFailureCount());
|
||||||
|
statusData.put("progress", 100);
|
||||||
|
statusData.put("endTime", System.currentTimeMillis());
|
||||||
|
|
||||||
|
if ("SUCCESS".equals(status)) {
|
||||||
|
statusData.put("message", "全部成功!共导入" + result.getTotalCount() + "条数据");
|
||||||
|
} else {
|
||||||
|
statusData.put("message", "成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "条");
|
||||||
|
}
|
||||||
|
|
||||||
|
redisTemplate.opsForHash().putAll(key, statusData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量查询已存在的采购事项ID
|
||||||
|
*/
|
||||||
|
private Set<String> getExistingPurchaseIds(List<CcdiPurchaseTransactionExcel> excelList) {
|
||||||
|
List<String> purchaseIds = excelList.stream()
|
||||||
|
.map(CcdiPurchaseTransactionExcel::getPurchaseId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (purchaseIds.isEmpty()) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<CcdiPurchaseTransaction> existingTransactions = transactionMapper.selectBatchIds(purchaseIds);
|
||||||
|
return existingTransactions.stream()
|
||||||
|
.map(CcdiPurchaseTransaction::getPurchaseId)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量保存
|
||||||
|
*/
|
||||||
|
private void saveBatch(List<CcdiPurchaseTransaction> list, int batchSize) {
|
||||||
|
// 使用真正的批量插入,分批次执行以提高性能
|
||||||
|
for (int i = 0; i < list.size(); i += batchSize) {
|
||||||
|
int end = Math.min(i + batchSize, list.size());
|
||||||
|
List<CcdiPurchaseTransaction> subList = list.subList(i, end);
|
||||||
|
transactionMapper.insertBatch(subList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证采购交易数据
|
||||||
|
*
|
||||||
|
* @param addDTO 新增DTO
|
||||||
|
* @param isUpdateSupport 是否支持更新
|
||||||
|
* @param existingIds 已存在的采购事项ID集合
|
||||||
|
*/
|
||||||
|
private void validateTransactionData(CcdiPurchaseTransactionAddDTO addDTO, Boolean isUpdateSupport, Set<String> existingIds) {
|
||||||
|
// 验证必填字段
|
||||||
|
if (StringUtils.isEmpty(addDTO.getPurchaseId())) {
|
||||||
|
throw new RuntimeException("采购事项ID不能为空");
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(addDTO.getPurchaseCategory())) {
|
||||||
|
throw new RuntimeException("采购类别不能为空");
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(addDTO.getSubjectName())) {
|
||||||
|
throw new RuntimeException("标的物名称不能为空");
|
||||||
|
}
|
||||||
|
if (addDTO.getPurchaseQty() == null) {
|
||||||
|
throw new RuntimeException("采购数量不能为空");
|
||||||
|
}
|
||||||
|
if (addDTO.getBudgetAmount() == null) {
|
||||||
|
throw new RuntimeException("预算金额不能为空");
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(addDTO.getPurchaseMethod())) {
|
||||||
|
throw new RuntimeException("采购方式不能为空");
|
||||||
|
}
|
||||||
|
if (addDTO.getApplyDate() == null) {
|
||||||
|
throw new RuntimeException("采购申请日期不能为空");
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(addDTO.getApplicantId())) {
|
||||||
|
throw new RuntimeException("申请人工号不能为空");
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(addDTO.getApplicantName())) {
|
||||||
|
throw new RuntimeException("申请人姓名不能为空");
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(addDTO.getApplyDepartment())) {
|
||||||
|
throw new RuntimeException("申请部门不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证工号格式(7位数字)
|
||||||
|
if (!addDTO.getApplicantId().matches("^\\d{7}$")) {
|
||||||
|
throw new RuntimeException("申请人工号必须为7位数字");
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotEmpty(addDTO.getPurchaseLeaderId()) && !addDTO.getPurchaseLeaderId().matches("^\\d{7}$")) {
|
||||||
|
throw new RuntimeException("采购负责人工号必须为7位数字");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证金额非负
|
||||||
|
if (addDTO.getPurchaseQty().compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
throw new RuntimeException("采购数量必须大于0");
|
||||||
|
}
|
||||||
|
if (addDTO.getBudgetAmount().compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
throw new RuntimeException("预算金额必须大于0");
|
||||||
|
}
|
||||||
|
if (addDTO.getBidAmount() != null && addDTO.getBidAmount().compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
throw new RuntimeException("中标金额必须大于0");
|
||||||
|
}
|
||||||
|
if (addDTO.getActualAmount() != null && addDTO.getActualAmount().compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
throw new RuntimeException("实际采购金额必须大于0");
|
||||||
|
}
|
||||||
|
if (addDTO.getContractAmount() != null && addDTO.getContractAmount().compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
throw new RuntimeException("合同金额必须大于0");
|
||||||
|
}
|
||||||
|
if (addDTO.getSettlementAmount() != null && addDTO.getSettlementAmount().compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
throw new RuntimeException("结算金额必须大于0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user