完善招投标导入测试与文档

This commit is contained in:
wkc
2026-04-22 16:20:37 +08:00
parent 0c5fa6b2c8
commit 5a9b79d4ee
42 changed files with 1814 additions and 707 deletions

View File

@@ -5,6 +5,7 @@ import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionExcel;
import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionSupplierExcel;
import com.ruoyi.info.collection.domain.vo.CcdiPurchaseTransactionVO;
import com.ruoyi.info.collection.domain.vo.ImportResultVO;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
@@ -33,12 +34,12 @@ import java.util.ArrayList;
import java.util.List;
/**
* 采购交易信息Controller
* 招投标信息维护Controller
*
* @author ruoyi
* @date 2026-02-06
*/
@Tag(name = "采购交易信息管理")
@Tag(name = "招投标信息维护")
@RestController
@RequestMapping("/ccdi/purchaseTransaction")
public class CcdiPurchaseTransactionController extends BaseController {
@@ -50,9 +51,9 @@ public class CcdiPurchaseTransactionController extends BaseController {
private ICcdiPurchaseTransactionImportService transactionImportService;
/**
* 查询采购交易列表
* 查询招投标信息列表
*/
@Operation(summary = "查询采购交易列表")
@Operation(summary = "查询招投标信息列表")
@PreAuthorize("@ss.hasPermi('ccdi:purchaseTransaction:list')")
@GetMapping("/list")
public TableDataInfo list(CcdiPurchaseTransactionQueryDTO queryDTO) {
@@ -64,9 +65,9 @@ public class CcdiPurchaseTransactionController extends BaseController {
}
/**
* 获取采购交易详细信息
* 获取招投标信息详细信息
*/
@Operation(summary = "获取采购交易详细信息")
@Operation(summary = "获取招投标信息详细信息")
@Parameter(name = "purchaseId", description = "采购事项ID", required = true)
@PreAuthorize("@ss.hasPermi('ccdi:purchaseTransaction:query')")
@GetMapping(value = "/{purchaseId}")
@@ -75,66 +76,81 @@ public class CcdiPurchaseTransactionController extends BaseController {
}
/**
* 新增采购交易
* 新增招投标信息
*/
@Operation(summary = "新增采购交易")
@Operation(summary = "新增招投标信息")
@PreAuthorize("@ss.hasPermi('ccdi:purchaseTransaction:add')")
@Log(title = "采购交易信息", businessType = BusinessType.INSERT)
@Log(title = "招投标信息维护", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@Validated @RequestBody CcdiPurchaseTransactionAddDTO addDTO) {
return toAjax(transactionService.insertTransaction(addDTO));
}
/**
* 修改采购交易
* 修改招投标信息
*/
@Operation(summary = "修改采购交易")
@Operation(summary = "修改招投标信息")
@PreAuthorize("@ss.hasPermi('ccdi:purchaseTransaction:edit')")
@Log(title = "采购交易信息", businessType = BusinessType.UPDATE)
@Log(title = "招投标信息维护", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@Validated @RequestBody CcdiPurchaseTransactionEditDTO editDTO) {
return toAjax(transactionService.updateTransaction(editDTO));
}
/**
* 删除采购交易
* 删除招投标信息
*/
@Operation(summary = "删除采购交易")
@Operation(summary = "删除招投标信息")
@Parameter(name = "purchaseIds", description = "采购事项ID数组", required = true)
@PreAuthorize("@ss.hasPermi('ccdi:purchaseTransaction:remove')")
@Log(title = "采购交易信息", businessType = BusinessType.DELETE)
@Log(title = "招投标信息维护", businessType = BusinessType.DELETE)
@DeleteMapping("/{purchaseIds}")
public AjaxResult remove(@PathVariable String[] purchaseIds) {
return toAjax(transactionService.deleteTransactionByIds(purchaseIds));
}
/**
* 下载带字典下拉框的导入模板
* 使用@DictDropdown注解自动添加下拉框
* 下载双Sheet导入模板
*/
@Operation(summary = "下载导入模板")
@PostMapping("/importTemplate")
public void importTemplate(HttpServletResponse response) {
EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiPurchaseTransactionExcel.class, "采购交易信息");
EasyExcelUtil.importTemplateWithDictDropdown(
response,
CcdiPurchaseTransactionExcel.class,
"招投标主信息",
CcdiPurchaseTransactionSupplierExcel.class,
"供应商明细",
"招投标信息维护导入模板"
);
}
/**
* 异步导入采购交易
* 异步导入招投标信息
*/
@Operation(summary = "异步导入采购交易")
@Operation(summary = "异步导入招投标信息")
@Parameter(name = "file", description = "导入文件", required = true)
@PreAuthorize("@ss.hasPermi('ccdi:purchaseTransaction:import')")
@Log(title = "采购交易信息", businessType = BusinessType.IMPORT)
@Log(title = "招投标信息维护", businessType = BusinessType.IMPORT)
@PostMapping("/importData")
public AjaxResult importData(@Parameter(description = "导入文件") MultipartFile file) throws Exception {
List<CcdiPurchaseTransactionExcel> list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiPurchaseTransactionExcel.class);
List<CcdiPurchaseTransactionExcel> mainList = EasyExcelUtil.importExcel(
file.getInputStream(),
CcdiPurchaseTransactionExcel.class,
"招投标主信息"
);
List<CcdiPurchaseTransactionSupplierExcel> supplierList = EasyExcelUtil.importExcel(
file.getInputStream(),
CcdiPurchaseTransactionSupplierExcel.class,
"供应商明细"
);
if (list == null || list.isEmpty()) {
if ((mainList == null || mainList.isEmpty()) && (supplierList == null || supplierList.isEmpty())) {
return error("至少需要一条数据");
}
// 提交异步任务
String taskId = transactionService.importTransaction(list);
String taskId = transactionService.importTransaction(mainList, supplierList);
// 立即返回,不等待后台任务完成
ImportResultVO result = new ImportResultVO();

View File

@@ -0,0 +1,64 @@
package com.ruoyi.info.collection.domain;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 招投标供应商明细对象 ccdi_purchase_transaction_supplier
*/
@Data
public class CcdiPurchaseTransactionSupplier implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
private Long id;
/** 采购事项ID */
private String purchaseId;
/** 供应商名称 */
private String supplierName;
/** 供应商统一信用代码 */
private String supplierUscc;
/** 供应商联系人 */
private String contactPerson;
/** 供应商联系电话 */
private String contactPhone;
/** 供应商银行账户 */
private String supplierBankAccount;
/** 是否中标1-是0-否 */
private Integer isBidWinner;
/** 排序 */
private Integer sortOrder;
/** 创建时间 */
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/** 更新时间 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
/** 创建人 */
@TableField(fill = FieldFill.INSERT)
private String createdBy;
/** 更新人 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updatedBy;
}

View File

@@ -2,6 +2,7 @@ package com.ruoyi.info.collection.domain.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
import lombok.Data;
@@ -9,15 +10,16 @@ import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
* 采购交易信息新增DTO
* 招投标信息新增DTO
*
* @author ruoyi
* @date 2026-02-06
*/
@Data
@Schema(description = "采购交易信息新增")
@Schema(description = "招投标信息新增")
public class CcdiPurchaseTransactionAddDTO implements Serializable {
@Serial
@@ -88,30 +90,10 @@ public class CcdiPurchaseTransactionAddDTO implements Serializable {
@Schema(description = "采购方式")
private String purchaseMethod;
/** 中标供应商名称 */
@Size(max = 200, message = "中标供应商名称长度不能超过200个字符")
@Schema(description = "中标供应商名称")
private String supplierName;
/** 供应商联系人 */
@Size(max = 50, message = "供应商联系人长度不能超过50个字符")
@Schema(description = "供应商联系人")
private String contactPerson;
/** 供应商联系电话 */
@Pattern(regexp = "^1[3-9]\\d{9}$|^0\\d{2,3}-?\\d{7,8}$", message = "供应商联系电话格式不正确")
@Schema(description = "供应商联系电话")
private String contactPhone;
/** 供应商统一信用代码 */
@Pattern(regexp = "^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$", message = "供应商统一信用代码格式不正确")
@Schema(description = "供应商统一信用代码")
private String supplierUscc;
/** 供应商银行账户 */
@Size(max = 50, message = "供应商银行账户长度不能超过50个字符")
@Schema(description = "供应商银行账户")
private String supplierBankAccount;
/** 供应商明细 */
@Valid
@Schema(description = "供应商明细列表")
private List<CcdiPurchaseTransactionSupplierDTO> supplierList;
/** 采购申请日期(或立项日期) */
@NotNull(message = "采购申请日期不能为空")

View File

@@ -2,6 +2,7 @@ package com.ruoyi.info.collection.domain.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
import lombok.Data;
@@ -9,15 +10,16 @@ import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
* 采购交易信息编辑DTO
* 招投标信息编辑DTO
*
* @author ruoyi
* @date 2026-02-06
*/
@Data
@Schema(description = "采购交易信息编辑")
@Schema(description = "招投标信息编辑")
public class CcdiPurchaseTransactionEditDTO implements Serializable {
@Serial
@@ -88,30 +90,10 @@ public class CcdiPurchaseTransactionEditDTO implements Serializable {
@Schema(description = "采购方式")
private String purchaseMethod;
/** 中标供应商名称 */
@Size(max = 200, message = "中标供应商名称长度不能超过200个字符")
@Schema(description = "中标供应商名称")
private String supplierName;
/** 供应商联系人 */
@Size(max = 50, message = "供应商联系人长度不能超过50个字符")
@Schema(description = "供应商联系人")
private String contactPerson;
/** 供应商联系电话 */
@Pattern(regexp = "^1[3-9]\\d{9}$|^0\\d{2,3}-?\\d{7,8}$", message = "供应商联系电话格式不正确")
@Schema(description = "供应商联系电话")
private String contactPhone;
/** 供应商统一信用代码 */
@Pattern(regexp = "^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$", message = "供应商统一信用代码格式不正确")
@Schema(description = "供应商统一信用代码")
private String supplierUscc;
/** 供应商银行账户 */
@Size(max = 50, message = "供应商银行账户长度不能超过50个字符")
@Schema(description = "供应商银行账户")
private String supplierBankAccount;
/** 供应商明细 */
@Valid
@Schema(description = "供应商明细列表")
private List<CcdiPurchaseTransactionSupplierDTO> supplierList;
/** 采购申请日期(或立项日期) */
@NotNull(message = "采购申请日期不能为空")

View File

@@ -9,13 +9,13 @@ import java.io.Serializable;
import java.util.Date;
/**
* 采购交易信息查询DTO
* 招投标信息查询DTO
*
* @author ruoyi
* @date 2026-02-06
*/
@Data
@Schema(description = "采购交易信息查询条件")
@Schema(description = "招投标信息查询条件")
public class CcdiPurchaseTransactionQueryDTO implements Serializable {
@Serial

View File

@@ -0,0 +1,54 @@
package com.ruoyi.info.collection.domain.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 招投标供应商明细DTO
*/
@Data
@Schema(description = "招投标供应商明细")
public class CcdiPurchaseTransactionSupplierDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@NotBlank(message = "供应商名称不能为空")
@Size(max = 200, message = "供应商名称长度不能超过200个字符")
@Schema(description = "供应商名称")
private String supplierName;
@Pattern(
regexp = "^$|^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$",
message = "供应商统一信用代码格式不正确"
)
@Schema(description = "供应商统一信用代码")
private String supplierUscc;
@Size(max = 50, message = "供应商联系人长度不能超过50个字符")
@Schema(description = "供应商联系人")
private String contactPerson;
@Pattern(
regexp = "^$|^1[3-9]\\d{9}$|^0\\d{2,3}-?\\d{7,8}$",
message = "供应商联系电话格式不正确"
)
@Schema(description = "供应商联系电话")
private String contactPhone;
@Size(max = 50, message = "供应商银行账户长度不能超过50个字符")
@Schema(description = "供应商银行账户")
private String supplierBankAccount;
@Schema(description = "是否中标1-是0-否")
private Integer isBidWinner;
@Schema(description = "排序")
private Integer sortOrder;
}

View File

@@ -11,7 +11,7 @@ import java.math.BigDecimal;
import java.util.Date;
/**
* 采购交易信息Excel导入导出对象
* 招投标主信息Excel导入导出对象
*
* @author ruoyi
* @date 2026-02-06
@@ -88,107 +88,82 @@ public class CcdiPurchaseTransactionExcel implements Serializable {
@Required
private String purchaseMethod;
/** 中标供应商名称 */
@ExcelProperty(value = "中标供应商名称", index = 12)
@ColumnWidth(25)
private String supplierName;
/** 供应商联系人 */
@ExcelProperty(value = "供应商联系人", index = 13)
@ColumnWidth(15)
private String contactPerson;
/** 供应商联系电话 */
@ExcelProperty(value = "供应商联系电话", index = 14)
@ColumnWidth(18)
private String contactPhone;
/** 供应商统一信用代码 */
@ExcelProperty(value = "供应商统一信用代码", index = 15)
@ColumnWidth(25)
private String supplierUscc;
/** 供应商银行账户 */
@ExcelProperty(value = "供应商银行账户", index = 16)
@ColumnWidth(20)
private String supplierBankAccount;
/** 采购申请日期(或立项日期) */
@ExcelProperty(value = "采购申请日期", index = 17)
@ExcelProperty(value = "采购申请日期", index = 12)
@ColumnWidth(18)
@Required
private Date applyDate;
/** 采购计划批准日期 */
@ExcelProperty(value = "采购计划批准日期", index = 18)
@ExcelProperty(value = "采购计划批准日期", index = 13)
@ColumnWidth(18)
private Date planApproveDate;
/** 采购公告发布日期 */
@ExcelProperty(value = "采购公告发布日期", index = 19)
@ExcelProperty(value = "采购公告发布日期", index = 14)
@ColumnWidth(18)
private Date announceDate;
/** 开标日期 */
@ExcelProperty(value = "开标日期", index = 20)
@ExcelProperty(value = "开标日期", index = 15)
@ColumnWidth(18)
private Date bidOpenDate;
/** 合同签订日期 */
@ExcelProperty(value = "合同签订日期", index = 21)
@ExcelProperty(value = "合同签订日期", index = 16)
@ColumnWidth(18)
private Date contractSignDate;
/** 预计交货日期 */
@ExcelProperty(value = "预计交货日期", index = 22)
@ExcelProperty(value = "预计交货日期", index = 17)
@ColumnWidth(18)
private Date expectedDeliveryDate;
/** 实际交货日期 */
@ExcelProperty(value = "实际交货日期", index = 23)
@ExcelProperty(value = "实际交货日期", index = 18)
@ColumnWidth(18)
private Date actualDeliveryDate;
/** 验收日期 */
@ExcelProperty(value = "验收日期", index = 24)
@ExcelProperty(value = "验收日期", index = 19)
@ColumnWidth(18)
private Date acceptanceDate;
/** 结算日期 */
@ExcelProperty(value = "结算日期", index = 25)
@ExcelProperty(value = "结算日期", index = 20)
@ColumnWidth(18)
private Date settlementDate;
/** 申请人工号 */
@ExcelProperty(value = "申请人工号", index = 26)
@ExcelProperty(value = "申请人工号", index = 21)
@ColumnWidth(15)
@Required
private String applicantId;
/** 申请人姓名 */
@ExcelProperty(value = "申请人姓名", index = 27)
@ExcelProperty(value = "申请人姓名", index = 22)
@ColumnWidth(15)
@Required
private String applicantName;
/** 申请部门 */
@ExcelProperty(value = "申请部门", index = 28)
@ExcelProperty(value = "申请部门", index = 23)
@ColumnWidth(18)
@Required
private String applyDepartment;
/** 采购负责人工号 */
@ExcelProperty(value = "采购负责人工号", index = 29)
@ExcelProperty(value = "采购负责人工号", index = 24)
@ColumnWidth(15)
private String purchaseLeaderId;
/** 采购负责人姓名 */
@ExcelProperty(value = "采购负责人姓名", index = 30)
@ExcelProperty(value = "采购负责人姓名", index = 25)
@ColumnWidth(15)
private String purchaseLeaderName;
/** 采购部门 */
@ExcelProperty(value = "采购部门", index = 31)
@ExcelProperty(value = "采购部门", index = 26)
@ColumnWidth(18)
private String purchaseDepartment;
}

View File

@@ -0,0 +1,55 @@
package com.ruoyi.info.collection.domain.excel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.ruoyi.common.annotation.DictDropdown;
import com.ruoyi.common.annotation.Required;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 招投标供应商明细Excel对象
*/
@Data
public class CcdiPurchaseTransactionSupplierExcel implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@ExcelProperty(value = "采购事项ID", index = 0)
@ColumnWidth(20)
@Required
private String purchaseId;
@ExcelProperty(value = "供应商名称", index = 1)
@ColumnWidth(25)
@Required
private String supplierName;
@ExcelProperty(value = "供应商统一信用代码", index = 2)
@ColumnWidth(25)
private String supplierUscc;
@ExcelProperty(value = "供应商联系人", index = 3)
@ColumnWidth(15)
private String contactPerson;
@ExcelProperty(value = "供应商联系电话", index = 4)
@ColumnWidth(18)
private String contactPhone;
@ExcelProperty(value = "供应商银行账户", index = 5)
@ColumnWidth(20)
private String supplierBankAccount;
@ExcelProperty(value = "是否中标", index = 6)
@ColumnWidth(12)
@DictDropdown(dictType = "ccdi_yes_no_flag")
private String isBidWinner;
@ExcelProperty(value = "排序", index = 7)
@ColumnWidth(10)
private Integer sortOrder;
}

View File

@@ -0,0 +1,45 @@
package com.ruoyi.info.collection.domain.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 招投标供应商明细VO
*/
@Data
@Schema(description = "招投标供应商明细")
public class CcdiPurchaseTransactionSupplierVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "主键ID")
private Long id;
@Schema(description = "采购事项ID")
private String purchaseId;
@Schema(description = "供应商名称")
private String supplierName;
@Schema(description = "供应商统一信用代码")
private String supplierUscc;
@Schema(description = "供应商联系人")
private String contactPerson;
@Schema(description = "供应商联系电话")
private String contactPhone;
@Schema(description = "供应商银行账户")
private String supplierBankAccount;
@Schema(description = "是否中标1-是0-否")
private Integer isBidWinner;
@Schema(description = "排序")
private Integer sortOrder;
}

View File

@@ -8,15 +8,16 @@ import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
* 采购交易信息VO
* 招投标信息VO
*
* @author ruoyi
* @date 2026-02-06
*/
@Data
@Schema(description = "采购交易信息")
@Schema(description = "招投标信息")
public class CcdiPurchaseTransactionVO implements Serializable {
@Serial
@@ -90,6 +91,14 @@ public class CcdiPurchaseTransactionVO implements Serializable {
@Schema(description = "供应商银行账户")
private String supplierBankAccount;
/** 参与供应商数 */
@Schema(description = "参与供应商数")
private Integer supplierCount;
/** 供应商明细 */
@Schema(description = "供应商明细列表")
private List<CcdiPurchaseTransactionSupplierVO> supplierList;
/** 采购申请日期(或立项日期) */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "采购申请日期")

View File

@@ -6,13 +6,13 @@ import lombok.Data;
import java.math.BigDecimal;
/**
* 采购交易信息导入失败记录VO
* 招投标信息导入失败记录VO
*
* @author ruoyi
* @date 2026-02-06
*/
@Data
@Schema(description = "采购交易信息导入失败记录")
@Schema(description = "招投标信息导入失败记录")
public class PurchaseTransactionImportFailureVO {
/** 采购事项ID */

View File

@@ -35,6 +35,14 @@ public interface CcdiPurchaseTransactionMapper extends BaseMapper<CcdiPurchaseTr
*/
CcdiPurchaseTransactionVO selectTransactionById(@Param("purchaseId") String purchaseId);
/**
* 删除指定采购事项ID对应的供应商明细
*
* @param purchaseIds 采购事项ID列表
* @return 删除行数
*/
int deleteSuppliersByPurchaseIds(@Param("purchaseIds") List<String> purchaseIds);
/**
* 批量插入采购交易数据
*

View File

@@ -0,0 +1,12 @@
package com.ruoyi.info.collection.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.info.collection.domain.CcdiPurchaseTransactionSupplier;
import org.apache.ibatis.annotations.Mapper;
/**
* 招投标供应商明细Mapper
*/
@Mapper
public interface CcdiPurchaseTransactionSupplierMapper extends BaseMapper<CcdiPurchaseTransactionSupplier> {
}

View File

@@ -1,6 +1,7 @@
package com.ruoyi.info.collection.service;
import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionExcel;
import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionSupplierExcel;
import com.ruoyi.info.collection.domain.vo.PurchaseTransactionImportFailureVO;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
@@ -17,11 +18,17 @@ public interface ICcdiPurchaseTransactionImportService {
/**
* 异步导入采购交易数据
*
* @param excelList Excel数据列表
* @param taskId 任务ID
* @param userName 当前用户名
* @param mainExcelList 主信息Excel数据列表
* @param supplierExcelList 供应商明细Excel数据列表
* @param taskId 任务ID
* @param userName 当前用户名
*/
void importTransactionAsync(List<CcdiPurchaseTransactionExcel> excelList, String taskId, String userName);
void importTransactionAsync(
List<CcdiPurchaseTransactionExcel> mainExcelList,
List<CcdiPurchaseTransactionSupplierExcel> supplierExcelList,
String taskId,
String userName
);
/**
* 查询导入状态

View File

@@ -5,6 +5,7 @@ import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionExcel;
import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionSupplierExcel;
import com.ruoyi.info.collection.domain.vo.CcdiPurchaseTransactionVO;
import java.util.List;
@@ -77,8 +78,12 @@ public interface ICcdiPurchaseTransactionService {
/**
* 导入采购交易数据(异步)
*
* @param excelList Excel实体列表
* @param mainExcelList 主信息Excel实体列表
* @param supplierExcelList 供应商明细Excel实体列表
* @return 任务ID
*/
String importTransaction(List<CcdiPurchaseTransactionExcel> excelList);
String importTransaction(
List<CcdiPurchaseTransactionExcel> mainExcelList,
List<CcdiPurchaseTransactionSupplierExcel> supplierExcelList
);
}

View File

@@ -2,12 +2,15 @@ package com.ruoyi.info.collection.service.impl;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.info.collection.domain.CcdiPurchaseTransaction;
import com.ruoyi.info.collection.domain.CcdiPurchaseTransactionSupplier;
import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionAddDTO;
import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionExcel;
import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionSupplierExcel;
import com.ruoyi.info.collection.domain.vo.ImportResult;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import com.ruoyi.info.collection.domain.vo.PurchaseTransactionImportFailureVO;
import com.ruoyi.info.collection.mapper.CcdiPurchaseTransactionMapper;
import com.ruoyi.info.collection.mapper.CcdiPurchaseTransactionSupplierMapper;
import com.ruoyi.info.collection.service.ICcdiPurchaseTransactionImportService;
import com.ruoyi.info.collection.utils.ImportLogUtils;
import com.ruoyi.common.utils.StringUtils;
@@ -41,79 +44,119 @@ public class CcdiPurchaseTransactionImportServiceImpl implements ICcdiPurchaseTr
@Resource
private CcdiPurchaseTransactionMapper transactionMapper;
@Resource
private CcdiPurchaseTransactionSupplierMapper supplierMapper;
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Override
@Async
@Transactional
public void importTransactionAsync(List<CcdiPurchaseTransactionExcel> excelList, String taskId, String userName) {
public void importTransactionAsync(
List<CcdiPurchaseTransactionExcel> mainExcelList,
List<CcdiPurchaseTransactionSupplierExcel> supplierExcelList,
String taskId,
String userName
) {
long startTime = System.currentTimeMillis();
List<CcdiPurchaseTransactionExcel> safeMainList = mainExcelList == null ? List.of() : mainExcelList;
List<CcdiPurchaseTransactionSupplierExcel> safeSupplierList = supplierExcelList == null ? List.of() : supplierExcelList;
int totalCount = countImportUnits(safeMainList, safeSupplierList);
// 记录导入开始
ImportLogUtils.logImportStart(log, taskId, "采购交易信息", excelList.size(), userName);
ImportLogUtils.logImportStart(log, taskId, "招投标信息维护", totalCount, userName);
List<CcdiPurchaseTransaction> newRecords = new ArrayList<>();
List<CcdiPurchaseTransaction> newTransactions = new ArrayList<>();
List<CcdiPurchaseTransactionSupplier> newSuppliers = new ArrayList<>();
List<PurchaseTransactionImportFailureVO> failures = new ArrayList<>();
// 批量查询已存在的采购事项ID
ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的采购事项ID", excelList.size());
Set<String> existingIds = getExistingPurchaseIds(excelList);
ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的采购事项ID", safeMainList.size());
Set<String> existingIds = getExistingPurchaseIds(safeMainList);
ImportLogUtils.logBatchQueryComplete(log, taskId, "采购事项ID", existingIds.size());
// 用于跟踪Excel文件内已处理的采购事项ID
Set<String> processedIds = new HashSet<>();
Map<String, List<CcdiPurchaseTransactionExcel>> mainGroupMap = safeMainList.stream()
.filter(item -> StringUtils.isNotEmpty(item.getPurchaseId()))
.collect(Collectors.groupingBy(
CcdiPurchaseTransactionExcel::getPurchaseId,
LinkedHashMap::new,
Collectors.toList()
));
Map<String, List<CcdiPurchaseTransactionSupplierExcel>> supplierGroupMap = safeSupplierList.stream()
.filter(item -> StringUtils.isNotEmpty(item.getPurchaseId()))
.collect(Collectors.groupingBy(
CcdiPurchaseTransactionSupplierExcel::getPurchaseId,
LinkedHashMap::new,
Collectors.toList()
));
LinkedHashSet<String> purchaseIds = new LinkedHashSet<>();
purchaseIds.addAll(mainGroupMap.keySet());
purchaseIds.addAll(supplierGroupMap.keySet());
// 分类数据
for (int i = 0; i < excelList.size(); i++) {
CcdiPurchaseTransactionExcel excel = excelList.get(i);
for (CcdiPurchaseTransactionSupplierExcel supplierExcel : safeSupplierList) {
if (StringUtils.isEmpty(supplierExcel.getPurchaseId())) {
failures.add(buildFailure(null, null, "供应商明细Sheet中的采购事项ID不能为空"));
}
}
int index = 0;
for (String purchaseId : purchaseIds) {
index++;
List<CcdiPurchaseTransactionExcel> mainRows = mainGroupMap.getOrDefault(purchaseId, List.of());
List<CcdiPurchaseTransactionSupplierExcel> supplierRows = supplierGroupMap.getOrDefault(purchaseId, List.of());
try {
// 转换为AddDTO进行验证
CcdiPurchaseTransactionAddDTO addDTO = new CcdiPurchaseTransactionAddDTO();
BeanUtils.copyProperties(excel, addDTO);
// 验证数据
validateTransactionData(addDTO, existingIds);
CcdiPurchaseTransaction transaction = new CcdiPurchaseTransaction();
BeanUtils.copyProperties(excel, transaction);
if (existingIds.contains(excel.getPurchaseId())) {
// 采购事项ID已存在直接报错
throw new RuntimeException(String.format("采购事项ID[%s]已存在,请勿重复导入", excel.getPurchaseId()));
} else if (processedIds.contains(excel.getPurchaseId())) {
// Excel文件内部重复
throw new RuntimeException(String.format("采购事项ID[%s]在导入文件中重复,已跳过此条记录", excel.getPurchaseId()));
} else {
transaction.setCreatedBy(userName);
transaction.setUpdatedBy(userName);
newRecords.add(transaction);
processedIds.add(excel.getPurchaseId()); // 标记为已处理
if (existingIds.contains(purchaseId)) {
throw new RuntimeException(String.format("采购事项ID[%s]已存在,请勿重复导入", purchaseId));
}
if (mainRows.isEmpty()) {
throw new RuntimeException(String.format("采购事项ID[%s]缺少招投标主信息", purchaseId));
}
if (mainRows.size() > 1) {
throw new RuntimeException(String.format("采购事项ID[%s]在招投标主信息Sheet中重复", purchaseId));
}
CcdiPurchaseTransactionExcel mainExcel = mainRows.getFirst();
CcdiPurchaseTransactionAddDTO addDTO = new CcdiPurchaseTransactionAddDTO();
BeanUtils.copyProperties(mainExcel, addDTO);
validateTransactionData(addDTO);
List<CcdiPurchaseTransactionSupplier> suppliers = buildSupplierEntities(purchaseId, supplierRows, userName);
CcdiPurchaseTransaction transaction = new CcdiPurchaseTransaction();
BeanUtils.copyProperties(mainExcel, transaction);
fillWinnerSummary(transaction, suppliers);
transaction.setCreatedBy(userName);
transaction.setUpdatedBy(userName);
newTransactions.add(transaction);
newSuppliers.addAll(suppliers);
// 记录进度
ImportLogUtils.logProgress(log, taskId, i + 1, excelList.size(),
newRecords.size(), failures.size());
ImportLogUtils.logProgress(log, taskId, index, Math.max(totalCount, purchaseIds.size()),
newTransactions.size(), failures.size());
} catch (Exception e) {
PurchaseTransactionImportFailureVO failure = new PurchaseTransactionImportFailureVO();
BeanUtils.copyProperties(excel, failure);
failure.setErrorMessage(e.getMessage());
failures.add(failure);
CcdiPurchaseTransactionExcel mainExcel = mainRows.isEmpty() ? null : mainRows.getFirst();
failures.add(buildFailure(mainExcel, purchaseId, e.getMessage()));
// 记录验证失败日志
String keyData = String.format("采购事项ID=%s, 采购类别=%s, 标的物=%s",
excel.getPurchaseId(), excel.getPurchaseCategory(), excel.getSubjectName());
ImportLogUtils.logValidationError(log, taskId, i + 1, e.getMessage(), keyData);
purchaseId,
mainExcel == null ? "" : mainExcel.getPurchaseCategory(),
mainExcel == null ? "" : mainExcel.getSubjectName());
ImportLogUtils.logValidationError(log, taskId, index, e.getMessage(), keyData);
}
}
// 批量插入新数据
if (!newRecords.isEmpty()) {
if (!newTransactions.isEmpty()) {
ImportLogUtils.logBatchOperationStart(log, taskId, "插入",
(newRecords.size() + 499) / 500, 500);
saveBatch(newRecords, 500);
(newTransactions.size() + 499) / 500, 500);
saveBatch(newTransactions, 500);
}
if (!newSuppliers.isEmpty()) {
saveSupplierBatch(newSuppliers, 500);
}
// 保存失败记录到Redis
@@ -128,8 +171,8 @@ public class CcdiPurchaseTransactionImportServiceImpl implements ICcdiPurchaseTr
}
ImportResult result = new ImportResult();
result.setTotalCount(excelList.size());
result.setSuccessCount(newRecords.size());
result.setTotalCount(totalCount);
result.setSuccessCount(newTransactions.size());
result.setFailureCount(failures.size());
// 更新最终状态
@@ -138,8 +181,8 @@ public class CcdiPurchaseTransactionImportServiceImpl implements ICcdiPurchaseTr
// 记录导入完成
long duration = System.currentTimeMillis() - startTime;
ImportLogUtils.logImportComplete(log, taskId, "采购交易信息",
excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration);
ImportLogUtils.logImportComplete(log, taskId, "招投标信息维护",
totalCount, result.getSuccessCount(), result.getFailureCount(), duration);
}
/**
@@ -243,13 +286,22 @@ public class CcdiPurchaseTransactionImportServiceImpl implements ICcdiPurchaseTr
}
}
private void saveSupplierBatch(List<CcdiPurchaseTransactionSupplier> list, int batchSize) {
for (int i = 0; i < list.size(); i += batchSize) {
int end = Math.min(i + batchSize, list.size());
List<CcdiPurchaseTransactionSupplier> subList = list.subList(i, end);
for (CcdiPurchaseTransactionSupplier supplier : subList) {
supplierMapper.insert(supplier);
}
}
}
/**
* 验证采购交易数据
*
* @param addDTO 新增DTO
* @param existingIds 已存在的采购事项ID集合
* @param addDTO 新增DTO
*/
private void validateTransactionData(CcdiPurchaseTransactionAddDTO addDTO, Set<String> existingIds) {
private void validateTransactionData(CcdiPurchaseTransactionAddDTO addDTO) {
// 验证必填字段
if (StringUtils.isEmpty(addDTO.getPurchaseId())) {
throw new RuntimeException("采购事项ID不能为空");
@@ -310,4 +362,161 @@ public class CcdiPurchaseTransactionImportServiceImpl implements ICcdiPurchaseTr
throw new RuntimeException("结算金额必须大于0");
}
}
private List<CcdiPurchaseTransactionSupplier> buildSupplierEntities(
String purchaseId,
List<CcdiPurchaseTransactionSupplierExcel> supplierRows,
String userName
) {
List<CcdiPurchaseTransactionSupplierExcel> normalizedRows = supplierRows == null
? List.of()
: supplierRows.stream()
.filter(Objects::nonNull)
.filter(this::hasAnySupplierValue)
.toList();
long winnerCount = normalizedRows.stream()
.filter(item -> parseIsBidWinner(item.getIsBidWinner()) == 1)
.count();
if (winnerCount > 1) {
throw new RuntimeException(String.format("采购事项ID[%s]存在多条中标供应商", purchaseId));
}
Set<String> duplicateSupplierKeys = new LinkedHashSet<>();
List<CcdiPurchaseTransactionSupplier> result = new ArrayList<>();
for (int i = 0; i < normalizedRows.size(); i++) {
CcdiPurchaseTransactionSupplierExcel supplierRow = normalizedRows.get(i);
validateSupplierRow(supplierRow);
String duplicateKey = StringUtils.trimToEmpty(supplierRow.getSupplierName()) + "|"
+ StringUtils.trimToEmpty(supplierRow.getSupplierUscc());
if (!duplicateSupplierKeys.add(duplicateKey)) {
throw new RuntimeException(String.format("采购事项ID[%s]存在重复供应商", purchaseId));
}
CcdiPurchaseTransactionSupplier supplier = new CcdiPurchaseTransactionSupplier();
supplier.setPurchaseId(purchaseId);
supplier.setSupplierName(StringUtils.trim(supplierRow.getSupplierName()));
supplier.setSupplierUscc(StringUtils.trimToNull(supplierRow.getSupplierUscc()));
supplier.setContactPerson(StringUtils.trimToNull(supplierRow.getContactPerson()));
supplier.setContactPhone(StringUtils.trimToNull(supplierRow.getContactPhone()));
supplier.setSupplierBankAccount(StringUtils.trimToNull(supplierRow.getSupplierBankAccount()));
supplier.setIsBidWinner(parseIsBidWinner(supplierRow.getIsBidWinner()));
supplier.setSortOrder(supplierRow.getSortOrder() == null ? i + 1 : supplierRow.getSortOrder());
supplier.setCreatedBy(userName);
supplier.setUpdatedBy(userName);
result.add(supplier);
}
return result;
}
private void validateSupplierRow(CcdiPurchaseTransactionSupplierExcel supplierRow) {
if (StringUtils.isEmpty(supplierRow.getSupplierName())) {
throw new RuntimeException("供应商名称不能为空");
}
if (StringUtils.length(supplierRow.getSupplierName()) > 200) {
throw new RuntimeException("供应商名称长度不能超过200个字符");
}
if (StringUtils.length(supplierRow.getContactPerson()) > 50) {
throw new RuntimeException("供应商联系人长度不能超过50个字符");
}
if (StringUtils.length(supplierRow.getSupplierBankAccount()) > 50) {
throw new RuntimeException("供应商银行账户长度不能超过50个字符");
}
if (StringUtils.isNotEmpty(supplierRow.getContactPhone())
&& !supplierRow.getContactPhone().matches("^1[3-9]\\d{9}$|^0\\d{2,3}-?\\d{7,8}$")) {
throw new RuntimeException("供应商联系电话格式不正确");
}
if (StringUtils.isNotEmpty(supplierRow.getSupplierUscc())
&& !supplierRow.getSupplierUscc().matches("^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$")) {
throw new RuntimeException("供应商统一信用代码格式不正确");
}
parseIsBidWinner(supplierRow.getIsBidWinner());
}
private boolean hasAnySupplierValue(CcdiPurchaseTransactionSupplierExcel supplierRow) {
return StringUtils.isNotEmpty(supplierRow.getPurchaseId())
|| StringUtils.isNotEmpty(supplierRow.getSupplierName())
|| StringUtils.isNotEmpty(supplierRow.getSupplierUscc())
|| StringUtils.isNotEmpty(supplierRow.getContactPerson())
|| StringUtils.isNotEmpty(supplierRow.getContactPhone())
|| StringUtils.isNotEmpty(supplierRow.getSupplierBankAccount())
|| StringUtils.isNotEmpty(supplierRow.getIsBidWinner())
|| supplierRow.getSortOrder() != null;
}
private int parseIsBidWinner(String rawValue) {
if (StringUtils.isEmpty(rawValue)) {
return 0;
}
String normalized = StringUtils.trim(rawValue);
if ("1".equals(normalized) || "".equals(normalized) || "Y".equalsIgnoreCase(normalized)
|| "TRUE".equalsIgnoreCase(normalized)) {
return 1;
}
if ("0".equals(normalized) || "".equals(normalized) || "N".equalsIgnoreCase(normalized)
|| "FALSE".equalsIgnoreCase(normalized)) {
return 0;
}
throw new RuntimeException("是否中标仅支持填写“是/否”或“1/0”");
}
private void fillWinnerSummary(
CcdiPurchaseTransaction transaction,
List<CcdiPurchaseTransactionSupplier> supplierList
) {
CcdiPurchaseTransactionSupplier winner = supplierList.stream()
.filter(item -> Objects.equals(item.getIsBidWinner(), 1))
.findFirst()
.orElse(null);
if (winner == null) {
transaction.setSupplierName(null);
transaction.setSupplierUscc(null);
transaction.setContactPerson(null);
transaction.setContactPhone(null);
transaction.setSupplierBankAccount(null);
return;
}
transaction.setSupplierName(winner.getSupplierName());
transaction.setSupplierUscc(winner.getSupplierUscc());
transaction.setContactPerson(winner.getContactPerson());
transaction.setContactPhone(winner.getContactPhone());
transaction.setSupplierBankAccount(winner.getSupplierBankAccount());
}
private PurchaseTransactionImportFailureVO buildFailure(
CcdiPurchaseTransactionExcel mainExcel,
String purchaseId,
String errorMessage
) {
PurchaseTransactionImportFailureVO failure = new PurchaseTransactionImportFailureVO();
if (mainExcel != null) {
BeanUtils.copyProperties(mainExcel, failure);
}
if (StringUtils.isNotEmpty(purchaseId)) {
failure.setPurchaseId(purchaseId);
}
failure.setErrorMessage(errorMessage);
return failure;
}
private int countImportUnits(
List<CcdiPurchaseTransactionExcel> mainExcelList,
List<CcdiPurchaseTransactionSupplierExcel> supplierExcelList
) {
LinkedHashSet<String> purchaseIds = new LinkedHashSet<>();
purchaseIds.addAll(
mainExcelList.stream()
.map(CcdiPurchaseTransactionExcel::getPurchaseId)
.filter(StringUtils::isNotEmpty)
.collect(Collectors.toCollection(LinkedHashSet::new))
);
purchaseIds.addAll(
supplierExcelList.stream()
.map(CcdiPurchaseTransactionSupplierExcel::getPurchaseId)
.filter(StringUtils::isNotEmpty)
.collect(Collectors.toCollection(LinkedHashSet::new))
);
return purchaseIds.size();
}
}

View File

@@ -2,24 +2,34 @@ package com.ruoyi.info.collection.service.impl;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.CcdiPurchaseTransaction;
import com.ruoyi.info.collection.domain.CcdiPurchaseTransactionSupplier;
import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionQueryDTO;
import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionSupplierDTO;
import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionExcel;
import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionSupplierExcel;
import com.ruoyi.info.collection.domain.vo.CcdiPurchaseTransactionVO;
import com.ruoyi.info.collection.domain.vo.CcdiPurchaseTransactionSupplierVO;
import com.ruoyi.info.collection.mapper.CcdiPurchaseTransactionMapper;
import com.ruoyi.info.collection.mapper.CcdiPurchaseTransactionSupplierMapper;
import com.ruoyi.info.collection.service.ICcdiPurchaseTransactionImportService;
import com.ruoyi.info.collection.service.ICcdiPurchaseTransactionService;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import jakarta.annotation.Resource;
import org.springframework.beans.BeanUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@@ -39,6 +49,9 @@ public class CcdiPurchaseTransactionServiceImpl implements ICcdiPurchaseTransact
@Resource
private ICcdiPurchaseTransactionImportService transactionImportService;
@Resource
private CcdiPurchaseTransactionSupplierMapper supplierMapper;
@Resource
private RedisTemplate<String, Object> redisTemplate;
@@ -93,7 +106,14 @@ public class CcdiPurchaseTransactionServiceImpl implements ICcdiPurchaseTransact
*/
@Override
public CcdiPurchaseTransactionVO selectTransactionById(String purchaseId) {
return transactionMapper.selectTransactionById(purchaseId);
CcdiPurchaseTransactionVO detail = transactionMapper.selectTransactionById(purchaseId);
if (detail == null) {
return null;
}
List<CcdiPurchaseTransactionSupplierVO> supplierList = selectSupplierListByPurchaseId(purchaseId);
detail.setSupplierList(supplierList);
detail.setSupplierCount(supplierList.size());
return detail;
}
/**
@@ -110,9 +130,12 @@ public class CcdiPurchaseTransactionServiceImpl implements ICcdiPurchaseTransact
throw new RuntimeException("该采购事项ID已存在");
}
List<CcdiPurchaseTransactionSupplier> supplierList = buildSupplierEntities(addDTO.getPurchaseId(), addDTO.getSupplierList());
CcdiPurchaseTransaction transaction = new CcdiPurchaseTransaction();
BeanUtils.copyProperties(addDTO, transaction);
fillWinnerSummary(transaction, supplierList);
int result = transactionMapper.insert(transaction);
saveSuppliers(supplierList);
return result;
}
@@ -126,9 +149,13 @@ public class CcdiPurchaseTransactionServiceImpl implements ICcdiPurchaseTransact
@Override
@Transactional
public int updateTransaction(CcdiPurchaseTransactionEditDTO editDTO) {
List<CcdiPurchaseTransactionSupplier> supplierList = buildSupplierEntities(editDTO.getPurchaseId(), editDTO.getSupplierList());
CcdiPurchaseTransaction transaction = new CcdiPurchaseTransaction();
BeanUtils.copyProperties(editDTO, transaction);
fillWinnerSummary(transaction, supplierList);
int result = transactionMapper.updateById(transaction);
transactionMapper.deleteSuppliersByPurchaseIds(List.of(editDTO.getPurchaseId()));
saveSuppliers(supplierList);
return result;
}
@@ -142,19 +169,24 @@ public class CcdiPurchaseTransactionServiceImpl implements ICcdiPurchaseTransact
@Override
@Transactional
public int deleteTransactionByIds(String[] purchaseIds) {
transactionMapper.deleteSuppliersByPurchaseIds(List.of(purchaseIds));
return transactionMapper.deleteBatchIds(java.util.List.of(purchaseIds));
}
/**
* 导入采购交易数据(异步)
*
* @param excelList Excel实体列表
* @param mainExcelList 主信息Excel实体列表
* @param supplierExcelList 供应商明细Excel实体列表
* @return 任务ID
*/
@Override
@Transactional
public String importTransaction(java.util.List<CcdiPurchaseTransactionExcel> excelList) {
if (StringUtils.isNull(excelList) || excelList.isEmpty()) {
public String importTransaction(
List<CcdiPurchaseTransactionExcel> mainExcelList,
List<CcdiPurchaseTransactionSupplierExcel> supplierExcelList
) {
if ((mainExcelList == null || mainExcelList.isEmpty()) && (supplierExcelList == null || supplierExcelList.isEmpty())) {
throw new RuntimeException("至少需要一条数据");
}
@@ -170,7 +202,7 @@ public class CcdiPurchaseTransactionServiceImpl implements ICcdiPurchaseTransact
Map<String, Object> statusData = new HashMap<>();
statusData.put("taskId", taskId);
statusData.put("status", "PROCESSING");
statusData.put("totalCount", excelList.size());
statusData.put("totalCount", countImportUnits(mainExcelList, supplierExcelList));
statusData.put("successCount", 0);
statusData.put("failureCount", 0);
statusData.put("progress", 0);
@@ -181,8 +213,134 @@ public class CcdiPurchaseTransactionServiceImpl implements ICcdiPurchaseTransact
redisTemplate.expire(statusKey, 7, TimeUnit.DAYS);
// 调用异步导入服务
transactionImportService.importTransactionAsync(excelList, taskId, userName);
transactionImportService.importTransactionAsync(mainExcelList, supplierExcelList, taskId, userName);
return taskId;
}
private int countImportUnits(
List<CcdiPurchaseTransactionExcel> mainExcelList,
List<CcdiPurchaseTransactionSupplierExcel> supplierExcelList
) {
LinkedHashSet<String> purchaseIds = new LinkedHashSet<>();
if (mainExcelList != null) {
purchaseIds.addAll(
mainExcelList.stream()
.map(CcdiPurchaseTransactionExcel::getPurchaseId)
.filter(StringUtils::isNotEmpty)
.collect(Collectors.toCollection(LinkedHashSet::new))
);
}
if (supplierExcelList != null) {
purchaseIds.addAll(
supplierExcelList.stream()
.map(CcdiPurchaseTransactionSupplierExcel::getPurchaseId)
.filter(StringUtils::isNotEmpty)
.collect(Collectors.toCollection(LinkedHashSet::new))
);
}
return purchaseIds.isEmpty() ? 0 : purchaseIds.size();
}
private List<CcdiPurchaseTransactionSupplier> buildSupplierEntities(
String purchaseId,
List<CcdiPurchaseTransactionSupplierDTO> supplierDTOList
) {
List<CcdiPurchaseTransactionSupplierDTO> normalizedList = normalizeSupplierList(supplierDTOList);
validateSupplierList(normalizedList);
List<CcdiPurchaseTransactionSupplier> supplierList = new ArrayList<>();
for (int i = 0; i < normalizedList.size(); i++) {
CcdiPurchaseTransactionSupplierDTO dto = normalizedList.get(i);
CcdiPurchaseTransactionSupplier supplier = new CcdiPurchaseTransactionSupplier();
BeanUtils.copyProperties(dto, supplier);
supplier.setPurchaseId(purchaseId);
supplier.setIsBidWinner(Objects.equals(dto.getIsBidWinner(), 1) ? 1 : 0);
supplier.setSortOrder(dto.getSortOrder() == null ? i + 1 : dto.getSortOrder());
supplierList.add(supplier);
}
return supplierList;
}
private List<CcdiPurchaseTransactionSupplierDTO> normalizeSupplierList(
List<CcdiPurchaseTransactionSupplierDTO> supplierDTOList
) {
if (supplierDTOList == null) {
return List.of();
}
return supplierDTOList.stream()
.filter(Objects::nonNull)
.filter(this::hasAnySupplierValue)
.toList();
}
private boolean hasAnySupplierValue(CcdiPurchaseTransactionSupplierDTO supplierDTO) {
return StringUtils.isNotEmpty(supplierDTO.getSupplierName())
|| StringUtils.isNotEmpty(supplierDTO.getSupplierUscc())
|| StringUtils.isNotEmpty(supplierDTO.getContactPerson())
|| StringUtils.isNotEmpty(supplierDTO.getContactPhone())
|| StringUtils.isNotEmpty(supplierDTO.getSupplierBankAccount())
|| supplierDTO.getIsBidWinner() != null
|| supplierDTO.getSortOrder() != null;
}
private void validateSupplierList(List<CcdiPurchaseTransactionSupplierDTO> supplierList) {
long winnerCount = supplierList.stream()
.filter(item -> Objects.equals(item.getIsBidWinner(), 1))
.count();
if (winnerCount > 1) {
throw new RuntimeException("同一招投标事项仅允许维护一条中标供应商");
}
LinkedHashSet<String> duplicateKeys = new LinkedHashSet<>();
for (CcdiPurchaseTransactionSupplierDTO supplier : supplierList) {
String duplicateKey = StringUtils.trimToEmpty(supplier.getSupplierName()) + "|"
+ StringUtils.trimToEmpty(supplier.getSupplierUscc());
if (!duplicateKeys.add(duplicateKey)) {
throw new RuntimeException("同一招投标事项存在重复供应商,请检查供应商名称和统一信用代码");
}
}
}
private void fillWinnerSummary(
CcdiPurchaseTransaction transaction,
List<CcdiPurchaseTransactionSupplier> supplierList
) {
CcdiPurchaseTransactionSupplier winnerSupplier = supplierList.stream()
.filter(item -> Objects.equals(item.getIsBidWinner(), 1))
.findFirst()
.orElse(null);
if (winnerSupplier == null) {
transaction.setSupplierName(null);
transaction.setSupplierUscc(null);
transaction.setContactPerson(null);
transaction.setContactPhone(null);
transaction.setSupplierBankAccount(null);
return;
}
transaction.setSupplierName(winnerSupplier.getSupplierName());
transaction.setSupplierUscc(winnerSupplier.getSupplierUscc());
transaction.setContactPerson(winnerSupplier.getContactPerson());
transaction.setContactPhone(winnerSupplier.getContactPhone());
transaction.setSupplierBankAccount(winnerSupplier.getSupplierBankAccount());
}
private void saveSuppliers(List<CcdiPurchaseTransactionSupplier> supplierList) {
for (CcdiPurchaseTransactionSupplier supplier : supplierList) {
supplierMapper.insert(supplier);
}
}
private List<CcdiPurchaseTransactionSupplierVO> selectSupplierListByPurchaseId(String purchaseId) {
return supplierMapper.selectList(
new LambdaQueryWrapper<CcdiPurchaseTransactionSupplier>()
.eq(CcdiPurchaseTransactionSupplier::getPurchaseId, purchaseId)
.orderByAsc(CcdiPurchaseTransactionSupplier::getSortOrder)
.orderByAsc(CcdiPurchaseTransactionSupplier::getId)
).stream().map(entity -> {
CcdiPurchaseTransactionSupplierVO vo = new CcdiPurchaseTransactionSupplierVO();
BeanUtils.copyProperties(entity, vo);
return vo;
}).collect(Collectors.toList());
}
}

View File

@@ -1,6 +1,8 @@
package com.ruoyi.info.collection.utils;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.handler.WriteHandler;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.ruoyi.info.collection.handler.DictDropdownWriteHandler;
@@ -98,6 +100,23 @@ public class EasyExcelUtil {
}
}
/**
* 导入Excel按指定Sheet名称
*
* @param inputStream 输入流
* @param clazz 实体类
* @param sheetName 工作表名称
* @param <T> 泛型
* @return 数据列表
*/
public static <T> List<T> importExcel(java.io.InputStream inputStream, Class<T> clazz, String sheetName) {
try {
return EasyExcel.read(inputStream).head(clazz).sheet(sheetName).doReadSync();
} catch (Exception e) {
throw new RuntimeException("导入Excel失败", e);
}
}
/**
* 下载导入模板
*
@@ -210,6 +229,45 @@ public class EasyExcelUtil {
}
}
/**
* 下载双Sheet导入模板带字典下拉框
*
* @param response 响应对象
* @param firstClazz 第一张Sheet实体类
* @param firstSheetName 第一张Sheet名称
* @param secondClazz 第二张Sheet实体类
* @param secondSheetName 第二张Sheet名称
* @param fileName 文件名称
* @param <T1> 第一张Sheet泛型
* @param <T2> 第二张Sheet泛型
*/
public static <T1, T2> void importTemplateWithDictDropdown(
HttpServletResponse response,
Class<T1> firstClazz,
String firstSheetName,
Class<T2> secondClazz,
String secondSheetName,
String fileName
) {
setResponseHeader(response, fileName);
try (ExcelWriter writer = EasyExcel.write(response.getOutputStream()).build()) {
writer.write(List.of(), buildTemplateSheet(0, firstClazz, firstSheetName));
writer.write(List.of(), buildTemplateSheet(1, secondClazz, secondSheetName));
} catch (IOException e) {
throw new RuntimeException("下载双Sheet导入模板失败", e);
}
}
private static <T> WriteSheet buildTemplateSheet(int sheetNo, Class<T> clazz, String sheetName) {
return EasyExcel.writerSheet(sheetNo, sheetName)
.head(clazz)
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
.registerWriteHandler(new DictDropdownWriteHandler(clazz))
.registerWriteHandler(new TextFormatWriteHandler(clazz))
.registerWriteHandler(new RequiredFieldWriteHandler(clazz))
.build();
}
/**
* 导出Excel带字典下拉框
* 导出的数据包含实际值,但模板中有下拉框供后续编辑使用

View File

@@ -23,6 +23,7 @@
<result property="contactPhone" column="contact_phone"/>
<result property="supplierUscc" column="supplier_uscc"/>
<result property="supplierBankAccount" column="supplier_bank_account"/>
<result property="supplierCount" column="supplier_count"/>
<result property="applyDate" column="apply_date"/>
<result property="planApproveDate" column="plan_approve_date"/>
<result property="announceDate" column="announce_date"/>
@@ -47,49 +48,61 @@
<!-- 分页查询采购交易列表 -->
<select id="selectTransactionPage" resultMap="CcdiPurchaseTransactionVOResult">
SELECT
purchase_id, purchase_category, project_name, subject_name, subject_desc,
purchase_qty, budget_amount, bid_amount, actual_amount, contract_amount, settlement_amount,
purchase_method, supplier_name, contact_person, contact_phone, supplier_uscc, supplier_bank_account,
apply_date, plan_approve_date, announce_date, bid_open_date, contract_sign_date,
expected_delivery_date, actual_delivery_date, acceptance_date, settlement_date,
applicant_id, applicant_name, apply_department, purchase_leader_id, purchase_leader_name, purchase_department,
created_by, create_time, updated_by, update_time
FROM ccdi_purchase_transaction
t.purchase_id, t.purchase_category, t.project_name, t.subject_name, t.subject_desc,
t.purchase_qty, t.budget_amount, t.bid_amount, t.actual_amount, t.contract_amount, t.settlement_amount,
t.purchase_method, t.supplier_name, t.contact_person, t.contact_phone, t.supplier_uscc, t.supplier_bank_account,
IFNULL(supplier_stats.supplier_count, 0) AS supplier_count,
t.apply_date, t.plan_approve_date, t.announce_date, t.bid_open_date, t.contract_sign_date,
t.expected_delivery_date, t.actual_delivery_date, t.acceptance_date, t.settlement_date,
t.applicant_id, t.applicant_name, t.apply_department, t.purchase_leader_id, t.purchase_leader_name, t.purchase_department,
t.created_by, t.create_time, t.updated_by, t.update_time
FROM ccdi_purchase_transaction t
LEFT JOIN (
SELECT purchase_id, COUNT(*) AS supplier_count
FROM ccdi_purchase_transaction_supplier
GROUP BY purchase_id
) supplier_stats ON supplier_stats.purchase_id = t.purchase_id
<where>
<if test="query.projectName != null and query.projectName != ''">
AND project_name LIKE CONCAT('%', #{query.projectName}, '%')
AND t.project_name LIKE CONCAT('%', #{query.projectName}, '%')
</if>
<if test="query.subjectName != null and query.subjectName != ''">
AND subject_name LIKE CONCAT('%', #{query.subjectName}, '%')
AND t.subject_name LIKE CONCAT('%', #{query.subjectName}, '%')
</if>
<if test="query.applicantName != null and query.applicantName != ''">
AND applicant_name LIKE CONCAT('%', #{query.applicantName}, '%')
AND t.applicant_name LIKE CONCAT('%', #{query.applicantName}, '%')
</if>
<if test="query.applicantId != null and query.applicantId != ''">
AND applicant_id = #{query.applicantId}
AND t.applicant_id = #{query.applicantId}
</if>
<if test="query.applyDateStart != null">
AND apply_date &gt;= #{query.applyDateStart}
AND t.apply_date &gt;= #{query.applyDateStart}
</if>
<if test="query.applyDateEnd != null">
AND apply_date &lt;= #{query.applyDateEnd}
AND t.apply_date &lt;= #{query.applyDateEnd}
</if>
</where>
ORDER BY create_time DESC
ORDER BY t.create_time DESC
</select>
<!-- 查询采购交易详情 -->
<select id="selectTransactionById" resultMap="CcdiPurchaseTransactionVOResult">
SELECT
purchase_id, purchase_category, project_name, subject_name, subject_desc,
purchase_qty, budget_amount, bid_amount, actual_amount, contract_amount, settlement_amount,
purchase_method, supplier_name, contact_person, contact_phone, supplier_uscc, supplier_bank_account,
apply_date, plan_approve_date, announce_date, bid_open_date, contract_sign_date,
expected_delivery_date, actual_delivery_date, acceptance_date, settlement_date,
applicant_id, applicant_name, apply_department, purchase_leader_id, purchase_leader_name, purchase_department,
created_by, create_time, updated_by, update_time
FROM ccdi_purchase_transaction
WHERE purchase_id = #{purchaseId}
t.purchase_id, t.purchase_category, t.project_name, t.subject_name, t.subject_desc,
t.purchase_qty, t.budget_amount, t.bid_amount, t.actual_amount, t.contract_amount, t.settlement_amount,
t.purchase_method, t.supplier_name, t.contact_person, t.contact_phone, t.supplier_uscc, t.supplier_bank_account,
IFNULL(supplier_stats.supplier_count, 0) AS supplier_count,
t.apply_date, t.plan_approve_date, t.announce_date, t.bid_open_date, t.contract_sign_date,
t.expected_delivery_date, t.actual_delivery_date, t.acceptance_date, t.settlement_date,
t.applicant_id, t.applicant_name, t.apply_department, t.purchase_leader_id, t.purchase_leader_name, t.purchase_department,
t.created_by, t.create_time, t.updated_by, t.update_time
FROM ccdi_purchase_transaction t
LEFT JOIN (
SELECT purchase_id, COUNT(*) AS supplier_count
FROM ccdi_purchase_transaction_supplier
GROUP BY purchase_id
) supplier_stats ON supplier_stats.purchase_id = t.purchase_id
WHERE t.purchase_id = #{purchaseId}
</select>
<!-- 批量插入采购交易数据 -->
@@ -137,4 +150,12 @@
</foreach>
</update>
<delete id="deleteSuppliersByPurchaseIds">
DELETE FROM ccdi_purchase_transaction_supplier
WHERE purchase_id IN
<foreach collection="purchaseIds" item="purchaseId" open="(" separator="," close=")">
#{purchaseId}
</foreach>
</delete>
</mapper>