package cn.iocoder.yudao.module.mes.controller.admin.qc.attachment; import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; import cn.iocoder.yudao.module.mes.service.qc.iqc.MesQcIqcService; import cn.iocoder.yudao.module.mes.service.qc.ipqc.MesQcIpqcService; import cn.iocoder.yudao.module.mes.service.qc.oqc.MesQcOqcService; import cn.iocoder.yudao.module.mes.service.qc.rqc.MesQcRqcService; import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi; import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; import lombok.Data; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.util.List; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; /** 质检附件独立于检验结论,上传附件不自动将产品判为合格。 */ @RestController @RequestMapping("/mes/qc/attachment") @Validated public class MesQcAttachmentController { @Resource private MesQcIqcService iqcService; @Resource private MesQcIpqcService ipqcService; @Resource private MesQcOqcService oqcService; @Resource private MesQcRqcService rqcService; @Resource private StorageAttachmentApi storageApi; @Resource private org.springframework.jdbc.core.JdbcTemplate jdbcTemplate; @GetMapping("/list") @PreAuthorize("@ss.hasPermission('mes:qc-' + #type.toLowerCase() + ':query')") public CommonResult> list( @RequestParam @Pattern(regexp = "iqc|ipqc|oqc|rqc|patrol") String type, @RequestParam @NotNull Long id) { validateDocument(type, id); var files = storageApi.listAttachments(recordType(type), id); return success(files == null ? List.of() : files); } @PutMapping("/save") @PreAuthorize("@ss.hasPermission('mes:qc-' + #req.type.toLowerCase() + ':update')") @Transactional(rollbackFor = Exception.class) public CommonResult save(@Valid @RequestBody SaveReq req) { lockDocument(req.getType(), req.getId()); validateDocument(req.getType(), req.getId()); // bindAttachments 仅替换关联,不删除共享文件本体。 storageApi.bindAttachments("file", recordType(req.getType()), req.getId(), req.getBlobIds().stream().distinct().toList()); return success(true); } @PostMapping("/append") @PreAuthorize("@ss.hasPermission('mes:qc-' + #req.type.toLowerCase() + ':update')") @Transactional(rollbackFor = Exception.class) public CommonResult append(@Valid @RequestBody AppendReq req) { lockDocument(req.getType(), req.getId()); validateDocument(req.getType(), req.getId()); var existing = storageApi.listAttachments(recordType(req.getType()), req.getId()); var ids = new java.util.LinkedHashSet(); if (existing != null) existing.forEach(blob -> ids.add(blob.getId())); ids.add(req.getBlobId()); if (ids.size() > 50) throw new ServiceException(400, "每张质检单最多上传50份附件"); storageApi.bindAttachments("file", recordType(req.getType()), req.getId(), List.copyOf(ids)); return success(true); } @DeleteMapping("/remove") @PreAuthorize("@ss.hasPermission('mes:qc-' + #type.toLowerCase() + ':update')") @Transactional(rollbackFor = Exception.class) public CommonResult remove( @RequestParam @Pattern(regexp = "iqc|ipqc|oqc|rqc|patrol") String type, @RequestParam @NotNull Long id, @RequestParam @NotNull Long blobId) { lockDocument(type, id); validateDocument(type, id); storageApi.removeReference("file", recordType(type), id, blobId); return success(true); } // ==================== 过程检验「检验行」附件 ==================== /** 过程检验行附件:recordId 为检验结果明细 ID,每行独立上传 */ @GetMapping("/ipqc-detail/list") @PreAuthorize("@ss.hasPermission('mes:qc-ipqc:query')") public CommonResult> listIpqcDetail(@RequestParam @NotNull Long detailId) { validateIpqcDetail(detailId); var files = storageApi.listAttachments(IPQC_DETAIL_RECORD_TYPE, detailId); return success(files == null ? List.of() : files); } @PostMapping("/ipqc-detail/save") @PreAuthorize("@ss.hasPermission('mes:qc-ipqc:update')") @Transactional(rollbackFor = Exception.class) public CommonResult saveIpqcDetail(@Valid @RequestBody IpqcDetailSaveReq req) { validateIpqcDetail(req.getDetailId()); storageApi.bindAttachments("file", IPQC_DETAIL_RECORD_TYPE, req.getDetailId(), req.getBlobIds().stream().distinct().toList()); return success(true); } @DeleteMapping("/ipqc-detail/remove") @PreAuthorize("@ss.hasPermission('mes:qc-ipqc:update')") @Transactional(rollbackFor = Exception.class) public CommonResult removeIpqcDetail(@RequestParam @NotNull Long detailId, @RequestParam @NotNull Long blobId) { validateIpqcDetail(detailId); storageApi.removeReference("file", IPQC_DETAIL_RECORD_TYPE, detailId, blobId); return success(true); } private static final String IPQC_DETAIL_RECORD_TYPE = "mes_qc_ipqc_detail_attachment"; /** 校验明细存在且属于过程检验结果 */ private void validateIpqcDetail(Long detailId) { var ids = jdbcTemplate.queryForList( "SELECT d.id FROM mes_qc_indicator_result_detail d " + "JOIN mes_qc_indicator_result r ON r.id = d.result_id " + "WHERE d.id = ? AND r.qc_type = ? AND d.deleted = 0 AND r.deleted = 0", Long.class, detailId, cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum.IPQC.getType()); if (ids.isEmpty()) { throw new ServiceException(400, "过程检验行不存在或不属于过程检验单,无法上传附件"); } } @Data public static class IpqcDetailSaveReq { @NotNull private Long detailId; /** 允许空列表:表示清空该行附件 */ @NotNull @Size(max = 50) private List<@NotNull Long> blobIds; } private String recordType(String type) { return switch (type) { case "iqc", "ipqc", "oqc", "rqc", "patrol" -> "mes_qc_" + type + "_attachment"; default -> throw new ServiceException(400, "质检类型无效"); }; } private void lockDocument(String type, Long id) { String table = switch (type) { case "iqc" -> "mes_qc_iqc"; case "ipqc" -> "mes_qc_ipqc"; case "oqc" -> "mes_qc_oqc"; case "rqc" -> "mes_qc_rqc"; case "patrol" -> "mes_qc_patrol"; default -> throw new ServiceException(400, "质检类型无效"); }; var ids = jdbcTemplate.queryForList("SELECT id FROM " + table + " WHERE id = ? AND deleted = 0 FOR UPDATE", Long.class, id); if (ids.isEmpty()) throw new ServiceException(400, "质检单不存在"); } private Integer validateDocument(String type, Long id) { return switch (type) { case "iqc" -> iqcService.validateIqcExists(id).getStatus(); case "ipqc" -> ipqcService.validateIpqcExists(id).getStatus(); case "oqc" -> oqcService.validateOqcExists(id).getStatus(); case "rqc" -> rqcService.validateRqcExists(id).getStatus(); case "patrol" -> { var ids = jdbcTemplate.queryForList( "SELECT id FROM mes_qc_patrol WHERE id = ? AND tenant_id = ? AND deleted = 0", Long.class, id, TenantContextHolder.getRequiredTenantId()); if (ids.isEmpty()) throw new ServiceException(400, "巡检记录不存在"); yield 0; } default -> throw new ServiceException(400, "质检类型无效"); }; } @Data public static class SaveReq { @NotNull @Pattern(regexp = "iqc|ipqc|oqc|rqc|patrol") private String type; @NotNull private Long id; @NotEmpty @Size(max = 50) private List<@NotNull Long> blobIds; } @Data public static class AppendReq { @NotNull @Pattern(regexp = "iqc|ipqc|oqc|rqc|patrol") private String type; @NotNull private Long id; @NotNull private Long blobId; } }