diff --git a/.DS_Store b/.DS_Store index 8eda584e..b6a1e2df 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/AGENTS.md b/AGENTS.md index 6d097d8c..b491f64e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -80,7 +80,7 @@ mvn clean compile # 启动主应用(Jar) -cd ruoyi-admin/target && java -jar ruoyi-admin.jar +sh bin/restart_java_backend.sh # 打包全部模块 mvn clean package diff --git a/bin/restart_java_backend.sh b/bin/restart_java_backend.sh index bc8f1ae3..f92dcc79 100755 --- a/bin/restart_java_backend.sh +++ b/bin/restart_java_backend.sh @@ -215,7 +215,7 @@ follow_logs() { start_action() { running_pids=$(collect_pids) if [ -n "${running_pids:-}" ]; then - log_error "检测到已有后端进程在运行: $running_pids,请先执行 stop 或 restart" + log_error "检测到已有后端进程在运行: ${running_pids},请先执行 stop 或 restart" exit 1 fi diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiPurchaseTransactionController.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiPurchaseTransactionController.java index 192d29fc..3bb210d4 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiPurchaseTransactionController.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiPurchaseTransactionController.java @@ -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 list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiPurchaseTransactionExcel.class); + List mainList = EasyExcelUtil.importExcel( + file.getInputStream(), + CcdiPurchaseTransactionExcel.class, + "招投标主信息" + ); + List 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(); diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiPurchaseTransactionSupplier.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiPurchaseTransactionSupplier.java new file mode 100644 index 00000000..a074008d --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiPurchaseTransactionSupplier.java @@ -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; +} diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiPurchaseTransactionAddDTO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiPurchaseTransactionAddDTO.java index f6977913..a33a2878 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiPurchaseTransactionAddDTO.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiPurchaseTransactionAddDTO.java @@ -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 supplierList; /** 采购申请日期(或立项日期) */ @NotNull(message = "采购申请日期不能为空") diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiPurchaseTransactionEditDTO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiPurchaseTransactionEditDTO.java index ac97d4c4..e4f10257 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiPurchaseTransactionEditDTO.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiPurchaseTransactionEditDTO.java @@ -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 supplierList; /** 采购申请日期(或立项日期) */ @NotNull(message = "采购申请日期不能为空") diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiPurchaseTransactionQueryDTO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiPurchaseTransactionQueryDTO.java index 68f8c482..16e89290 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiPurchaseTransactionQueryDTO.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiPurchaseTransactionQueryDTO.java @@ -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 diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiPurchaseTransactionSupplierDTO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiPurchaseTransactionSupplierDTO.java new file mode 100644 index 00000000..5b037908 --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiPurchaseTransactionSupplierDTO.java @@ -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; +} diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiPurchaseTransactionExcel.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiPurchaseTransactionExcel.java index 7c996cfe..fa8e1ebe 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiPurchaseTransactionExcel.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiPurchaseTransactionExcel.java @@ -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; } diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiPurchaseTransactionSupplierExcel.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiPurchaseTransactionSupplierExcel.java new file mode 100644 index 00000000..0655f64a --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiPurchaseTransactionSupplierExcel.java @@ -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; +} diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiPurchaseTransactionSupplierVO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiPurchaseTransactionSupplierVO.java new file mode 100644 index 00000000..4a56508c --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiPurchaseTransactionSupplierVO.java @@ -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; +} diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiPurchaseTransactionVO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiPurchaseTransactionVO.java index 698ea3ef..3bd4664f 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiPurchaseTransactionVO.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiPurchaseTransactionVO.java @@ -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 supplierList; + /** 采购申请日期(或立项日期) */ @JsonFormat(pattern = "yyyy-MM-dd") @Schema(description = "采购申请日期") diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/PurchaseTransactionImportFailureVO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/PurchaseTransactionImportFailureVO.java index 3f8fddd7..04deab5d 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/PurchaseTransactionImportFailureVO.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/PurchaseTransactionImportFailureVO.java @@ -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 */ diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiPurchaseTransactionMapper.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiPurchaseTransactionMapper.java index bc83d918..f5009e3e 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiPurchaseTransactionMapper.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiPurchaseTransactionMapper.java @@ -35,6 +35,14 @@ public interface CcdiPurchaseTransactionMapper extends BaseMapper purchaseIds); + /** * 批量插入采购交易数据 * diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiPurchaseTransactionSupplierMapper.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiPurchaseTransactionSupplierMapper.java new file mode 100644 index 00000000..3599e6d3 --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiPurchaseTransactionSupplierMapper.java @@ -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 { +} diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiPurchaseTransactionImportService.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiPurchaseTransactionImportService.java index 4bcbec5d..5913e2ec 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiPurchaseTransactionImportService.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiPurchaseTransactionImportService.java @@ -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 excelList, String taskId, String userName); + void importTransactionAsync( + List mainExcelList, + List supplierExcelList, + String taskId, + String userName + ); /** * 查询导入状态 diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiPurchaseTransactionService.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiPurchaseTransactionService.java index ec8f1c50..c8cecc2c 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiPurchaseTransactionService.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiPurchaseTransactionService.java @@ -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 excelList); + String importTransaction( + List mainExcelList, + List supplierExcelList + ); } diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiPurchaseTransactionImportServiceImpl.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiPurchaseTransactionImportServiceImpl.java index 5aa58483..b922f062 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiPurchaseTransactionImportServiceImpl.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiPurchaseTransactionImportServiceImpl.java @@ -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 redisTemplate; @Override @Async @Transactional - public void importTransactionAsync(List excelList, String taskId, String userName) { + public void importTransactionAsync( + List mainExcelList, + List supplierExcelList, + String taskId, + String userName + ) { long startTime = System.currentTimeMillis(); + List safeMainList = mainExcelList == null ? List.of() : mainExcelList; + List 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 newRecords = new ArrayList<>(); + List newTransactions = new ArrayList<>(); + List newSuppliers = new ArrayList<>(); List failures = new ArrayList<>(); // 批量查询已存在的采购事项ID - ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的采购事项ID", excelList.size()); - Set existingIds = getExistingPurchaseIds(excelList); + ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的采购事项ID", safeMainList.size()); + Set existingIds = getExistingPurchaseIds(safeMainList); ImportLogUtils.logBatchQueryComplete(log, taskId, "采购事项ID", existingIds.size()); - // 用于跟踪Excel文件内已处理的采购事项ID - Set processedIds = new HashSet<>(); + Map> mainGroupMap = safeMainList.stream() + .filter(item -> StringUtils.isNotEmpty(item.getPurchaseId())) + .collect(Collectors.groupingBy( + CcdiPurchaseTransactionExcel::getPurchaseId, + LinkedHashMap::new, + Collectors.toList() + )); + Map> supplierGroupMap = safeSupplierList.stream() + .filter(item -> StringUtils.isNotEmpty(item.getPurchaseId())) + .collect(Collectors.groupingBy( + CcdiPurchaseTransactionSupplierExcel::getPurchaseId, + LinkedHashMap::new, + Collectors.toList() + )); + LinkedHashSet 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 mainRows = mainGroupMap.getOrDefault(purchaseId, List.of()); + List 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 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 list, int batchSize) { + for (int i = 0; i < list.size(); i += batchSize) { + int end = Math.min(i + batchSize, list.size()); + List 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 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 buildSupplierEntities( + String purchaseId, + List supplierRows, + String userName + ) { + List 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 duplicateSupplierKeys = new LinkedHashSet<>(); + List 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 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 mainExcelList, + List supplierExcelList + ) { + LinkedHashSet 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(); + } } diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiPurchaseTransactionServiceImpl.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiPurchaseTransactionServiceImpl.java index bbe61d79..1b6cd039 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiPurchaseTransactionServiceImpl.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiPurchaseTransactionServiceImpl.java @@ -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 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 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 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 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 excelList) { - if (StringUtils.isNull(excelList) || excelList.isEmpty()) { + public String importTransaction( + List mainExcelList, + List supplierExcelList + ) { + if ((mainExcelList == null || mainExcelList.isEmpty()) && (supplierExcelList == null || supplierExcelList.isEmpty())) { throw new RuntimeException("至少需要一条数据"); } @@ -170,7 +202,7 @@ public class CcdiPurchaseTransactionServiceImpl implements ICcdiPurchaseTransact Map 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 mainExcelList, + List supplierExcelList + ) { + LinkedHashSet 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 buildSupplierEntities( + String purchaseId, + List supplierDTOList + ) { + List normalizedList = normalizeSupplierList(supplierDTOList); + validateSupplierList(normalizedList); + + List 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 normalizeSupplierList( + List 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 supplierList) { + long winnerCount = supplierList.stream() + .filter(item -> Objects.equals(item.getIsBidWinner(), 1)) + .count(); + if (winnerCount > 1) { + throw new RuntimeException("同一招投标事项仅允许维护一条中标供应商"); + } + + LinkedHashSet 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 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 supplierList) { + for (CcdiPurchaseTransactionSupplier supplier : supplierList) { + supplierMapper.insert(supplier); + } + } + + private List selectSupplierListByPurchaseId(String purchaseId) { + return supplierMapper.selectList( + new LambdaQueryWrapper() + .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()); + } } diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/utils/EasyExcelUtil.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/utils/EasyExcelUtil.java index 463f749b..d1df8570 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/utils/EasyExcelUtil.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/utils/EasyExcelUtil.java @@ -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 泛型 + * @return 数据列表 + */ + public static List importExcel(java.io.InputStream inputStream, Class 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 第一张Sheet泛型 + * @param 第二张Sheet泛型 + */ + public static void importTemplateWithDictDropdown( + HttpServletResponse response, + Class firstClazz, + String firstSheetName, + Class 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 WriteSheet buildTemplateSheet(int sheetNo, Class 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(带字典下拉框) * 导出的数据包含实际值,但模板中有下拉框供后续编辑使用 diff --git a/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiPurchaseTransactionMapper.xml b/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiPurchaseTransactionMapper.xml index 0d1a80da..c2273071 100644 --- a/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiPurchaseTransactionMapper.xml +++ b/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiPurchaseTransactionMapper.xml @@ -23,6 +23,7 @@ + @@ -47,49 +48,61 @@ @@ -137,4 +150,12 @@ + + DELETE FROM ccdi_purchase_transaction_supplier + WHERE purchase_id IN + + #{purchaseId} + + + diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectExtendedPurchaseDetailVO.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectExtendedPurchaseDetailVO.java index 7e8dd911..ae378f1a 100644 --- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectExtendedPurchaseDetailVO.java +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectExtendedPurchaseDetailVO.java @@ -1,6 +1,7 @@ package com.ruoyi.ccdi.project.domain.vo; import java.math.BigDecimal; +import java.util.List; import lombok.Data; /** @@ -80,4 +81,6 @@ public class CcdiProjectExtendedPurchaseDetailVO { private String updatedBy; private String updateTime; + + private List supplierList; } diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectExtendedPurchaseSupplierVO.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectExtendedPurchaseSupplierVO.java new file mode 100644 index 00000000..f694af1b --- /dev/null +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectExtendedPurchaseSupplierVO.java @@ -0,0 +1,28 @@ +package com.ruoyi.ccdi.project.domain.vo; + +import lombok.Data; + +/** + * 专项核查采购供应商明细 + */ +@Data +public class CcdiProjectExtendedPurchaseSupplierVO { + + private Long id; + + private String purchaseId; + + private String supplierName; + + private String supplierUscc; + + private String contactPerson; + + private String contactPhone; + + private String supplierBankAccount; + + private Integer isBidWinner; + + private Integer sortOrder; +} diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiProjectSpecialCheckMapper.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiProjectSpecialCheckMapper.java index 9bdc28b6..6dc3e8ab 100644 --- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiProjectSpecialCheckMapper.java +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiProjectSpecialCheckMapper.java @@ -6,6 +6,7 @@ import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedRecruitmentQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferQueryDTO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseDetailVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseListItemVO; +import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseSupplierVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentDetailVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentListItemVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferDetailVO; @@ -96,6 +97,18 @@ public interface CcdiProjectSpecialCheckMapper { @Param("purchaseId") String purchaseId ); + /** + * 查询专项核查采购供应商明细 + * + * @param projectId 项目ID + * @param purchaseId 采购事项ID + * @return 供应商明细 + */ + List selectExtendedPurchaseSuppliers( + @Param("projectId") Long projectId, + @Param("purchaseId") String purchaseId + ); + /** * 查询专项核查招聘拓展列表 * diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectSpecialCheckServiceImpl.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectSpecialCheckServiceImpl.java index e8ea1b28..17d2844e 100644 --- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectSpecialCheckServiceImpl.java +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectSpecialCheckServiceImpl.java @@ -103,6 +103,9 @@ public class CcdiProjectSpecialCheckServiceImpl implements ICcdiProjectSpecialCh if (detail == null) { throw new ServiceException("当前记录不属于该项目专项核查范围"); } + detail.setSupplierList(defaultList( + specialCheckMapper.selectExtendedPurchaseSuppliers(queryDTO.getProjectId(), queryDTO.getPurchaseId()) + )); return detail; } diff --git a/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectSpecialCheckMapper.xml b/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectSpecialCheckMapper.xml index a84ce68b..9b286cb1 100644 --- a/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectSpecialCheckMapper.xml +++ b/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectSpecialCheckMapper.xml @@ -574,6 +574,33 @@ where p.purchase_id = #{purchaseId} + +