liuwb 7 месяцев назад
Родитель
Сommit
d529141430
21 измененных файлов с 751 добавлено и 52 удалено
  1. 25 0
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/iqc/IqcTaskController.java
  2. 2 2
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/iqc/vo/IqcApplyDetailRespVO.java
  3. 2 2
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/iqc/vo/IqcApplyDetailSaveReqVO.java
  4. 27 0
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/iqc/vo/IqcInspectBillDetailItemRespVO.java
  5. 28 0
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/iqc/vo/IqcInspectBillMainRespVO.java
  6. 51 0
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/iqc/vo/IqcInspectBillSaveReqVO.java
  7. 2 2
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/iqc/vo/IqcTaskRespVO.java
  8. 45 0
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/dal/mysql/iqc/IqcTaskMapper.java
  9. 27 0
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/service/iqc/IqcTaskService.java
  10. 75 0
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/service/iqc/IqcTaskServiceImpl.java
  11. 3 3
      yudao-module-qms/src/main/resources/mapper/iqc/IqcApplyMapper.xml
  12. 62 1
      yudao-module-qms/src/main/resources/mapper/iqc/IqcTaskMapper.xml
  13. 2 2
      yudao-ui/yudao-ui-admin-vue3/src/api/qms/iqc/apply/index.ts
  14. 49 1
      yudao-ui/yudao-ui-admin-vue3/src/api/qms/iqc/task/index.ts
  15. 3 3
      yudao-ui/yudao-ui-admin-vue3/src/config/qmsModules.ts
  16. 2 0
      yudao-ui/yudao-ui-admin-vue3/src/router/modules/remaining.ts
  17. 103 0
      yudao-ui/yudao-ui-admin-vue3/src/store/modules/qms/inspectBill.ts
  18. 19 0
      yudao-ui/yudao-ui-admin-vue3/src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue
  19. 11 13
      yudao-ui/yudao-ui-admin-vue3/src/views/qms/ApplicationForm.vue
  20. 181 0
      yudao-ui/yudao-ui-admin-vue3/src/views/qms/iqc/task/InspectBillEdit.vue
  21. 32 23
      yudao-ui/yudao-ui-admin-vue3/src/views/qms/iqc/task/ProcessDetailForm.vue

+ 25 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/iqc/IqcTaskController.java

@@ -3,6 +3,9 @@ package cn.iocoder.yudao.module.qms.controller.admin.iqc;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
+import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcInspectBillDetailItemRespVO;
+import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcInspectBillMainRespVO;
+import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcInspectBillSaveReqVO;
 import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskDetailRespVO;
 import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskPageReqVO;
 import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskRespVO;
@@ -46,6 +49,28 @@ public class IqcTaskController {
         return success(iqcTaskService.getIqcTaskDetail(id));
     }
 
+    @GetMapping("/detail-list")
+    @Operation(summary = "获取来料检验单明细列表")
+    @PreAuthorize("@ss.hasPermission('qms:iqc:task:list')")
+    public CommonResult<java.util.List<IqcInspectBillDetailItemRespVO>> getInspectBillDetailList(
+            @RequestParam("taskId") Long taskId) {
+        return success(iqcTaskService.getInspectBillDetailList(taskId));
+    }
+
+    @GetMapping("/inspect-bill/main")
+    @Operation(summary = "获取来料检验单主表信息")
+    @PreAuthorize("@ss.hasPermission('qms:iqc:task:list')")
+    public CommonResult<IqcInspectBillMainRespVO> getInspectBillMain(@RequestParam("taskId") Long taskId) {
+        return success(iqcTaskService.getInspectBillMain(taskId));
+    }
+
+    @PostMapping("/inspect-bill/save")
+    @Operation(summary = "保存来料检验单(主表+明细)")
+    @PreAuthorize("@ss.hasPermission('qms:iqc:task:list')")
+    public CommonResult<Long> saveInspectBill(@Valid @RequestBody IqcInspectBillSaveReqVO reqVO) {
+        return success(iqcTaskService.saveInspectBill(reqVO));
+    }
+
     @PostMapping("/start")
     @Operation(summary = "发起来料检验任务流程")
     @PreAuthorize("@ss.hasPermission('qms:iqc:task:start')")

+ 2 - 2
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/iqc/vo/IqcApplyDetailRespVO.java

@@ -30,6 +30,6 @@ public class IqcApplyDetailRespVO {
     @Schema(description = "单位")
     private String unit;
 
-    @Schema(description = "备注")
-    private String remark;
+    @Schema(description = "供应商编码")
+    private String supplierCode;
 }

+ 2 - 2
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/iqc/vo/IqcApplyDetailSaveReqVO.java

@@ -33,8 +33,8 @@ public class IqcApplyDetailSaveReqVO {
     @Schema(description = "单位")
     private String unit;
 
-    @Schema(description = "备注")
-    private String remark;
+    @Schema(description = "供应商编码")
+    private String supplierCode;
 
     @Schema(description = "物料编号", hidden = true)
     private Long materialId;

+ 27 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/iqc/vo/IqcInspectBillDetailItemRespVO.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.qms.controller.admin.iqc.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - IQC 检验单明细 Response VO")
+@Data
+public class IqcInspectBillDetailItemRespVO {
+
+    @Schema(description = "明细编号", example = "1")
+    private Long id;
+
+    @Schema(description = "检验单编号", example = "1001")
+    private Long billId;
+
+    @Schema(description = "字段A")
+    private String a;
+
+    @Schema(description = "字段B")
+    private String b;
+
+    @Schema(description = "字段C")
+    private String c;
+
+    @Schema(description = "字段D")
+    private String d;
+}

+ 28 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/iqc/vo/IqcInspectBillMainRespVO.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.qms.controller.admin.iqc.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+@Schema(description = "管理后台 - IQC 检验单主表信息 Response VO")
+@Data
+public class IqcInspectBillMainRespVO {
+
+    @Schema(description = "检验单编号", example = "1001")
+    private Long billId;
+
+    @Schema(description = "字段A")
+    private String mainA;
+
+    @Schema(description = "字段B")
+    private String mainB;
+
+    @Schema(description = "字段C")
+    private String mainC;
+
+    @Schema(description = "字段D")
+    private String mainD;
+
+    @JsonIgnore
+    private String comment;
+}

+ 51 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/iqc/vo/IqcInspectBillSaveReqVO.java

@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.qms.controller.admin.iqc.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import jakarta.validation.constraints.NotNull;
+import java.util.List;
+
+@Schema(description = "管理后台 - IQC 检验单保存 Request VO")
+@Data
+public class IqcInspectBillSaveReqVO {
+
+    @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotNull(message = "任务编号不能为空")
+    private Long taskId;
+
+    @Schema(description = "主表字段A")
+    private String mainA;
+
+    @Schema(description = "主表字段B")
+    private String mainB;
+
+    @Schema(description = "主表字段C")
+    private String mainC;
+
+    @Schema(description = "主表字段D")
+    private String mainD;
+
+    @Schema(description = "明细列表")
+    private List<IqcInspectBillDetailSaveReqVO> details;
+
+    @Schema(description = "检验单明细保存 Request VO")
+    @Data
+    public static class IqcInspectBillDetailSaveReqVO {
+
+        @Schema(description = "明细编号")
+        private Long id;
+
+        @Schema(description = "字段A")
+        private String a;
+
+        @Schema(description = "字段B")
+        private String b;
+
+        @Schema(description = "字段C")
+        private String c;
+
+        @Schema(description = "字段D")
+        private String d;
+    }
+}

+ 2 - 2
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/iqc/vo/IqcTaskRespVO.java

@@ -46,6 +46,6 @@ public class IqcTaskRespVO {
     @Schema(description = "流程实例编号")
     private String processInstanceId;
 
-    @Schema(description = "备注")
-    private String remark;
+    @Schema(description = "供应商编码")
+    private String supplierCode;
 }

+ 45 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/dal/mysql/iqc/IqcTaskMapper.java

@@ -3,6 +3,9 @@ package cn.iocoder.yudao.module.qms.dal.mysql.iqc;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
+import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcInspectBillDetailItemRespVO;
+import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcInspectBillMainRespVO;
+import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcInspectBillSaveReqVO;
 import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskDetailRespVO;
 import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskProcessInfoVO;
 import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskPageReqVO;
@@ -36,6 +39,48 @@ public interface IqcTaskMapper {
     @TenantIgnore
     IqcTaskDetailRespVO selectTaskDetail(@Param("id") Long id);
 
+    /**
+     * 查询检验单明细(根据任务ID)
+     */
+    @TenantIgnore
+    java.util.List<IqcInspectBillDetailItemRespVO> selectInspectBillDetailList(@Param("taskId") Long taskId);
+
+    /**
+     * 查询检验单主表信息
+     */
+    @TenantIgnore
+    IqcInspectBillMainRespVO selectInspectBillMain(@Param("taskId") Long taskId);
+
+    /**
+     * 插入检验单主表
+     */
+    @TenantIgnore
+    void insertInspectBillMain(@Param("id") Long id,
+                               @Param("taskId") Long taskId,
+                               @Param("comment") String comment,
+                               @Param("creatorId") Long creatorId);
+
+    /**
+     * 更新检验单主表
+     */
+    @TenantIgnore
+    void updateInspectBillMain(@Param("id") Long id,
+                               @Param("comment") String comment,
+                               @Param("modifierId") Long modifierId);
+
+    /**
+     * 删除检验单明细
+     */
+    @TenantIgnore
+    void deleteInspectBillDetailByBillId(@Param("billId") Long billId);
+
+    /**
+     * 批量插入检验单明细
+     */
+    @TenantIgnore
+    void insertInspectBillDetailBatch(@Param("billId") Long billId,
+                                      @Param("list") java.util.List<IqcInspectBillSaveReqVO.IqcInspectBillDetailSaveReqVO> list);
+
     /**
      * 更新流程相关字段
      */

+ 27 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/service/iqc/IqcTaskService.java

@@ -1,6 +1,9 @@
 package cn.iocoder.yudao.module.qms.service.iqc;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcInspectBillDetailItemRespVO;
+import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcInspectBillMainRespVO;
+import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcInspectBillSaveReqVO;
 import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskDetailRespVO;
 import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskPageReqVO;
 import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskRespVO;
@@ -26,6 +29,30 @@ public interface IqcTaskService {
      */
     IqcTaskDetailRespVO getIqcTaskDetail(Long taskId);
 
+    /**
+     * 获取检验单明细
+     *
+     * @param taskId 任务编号
+     * @return 明细列表
+     */
+    java.util.List<IqcInspectBillDetailItemRespVO> getInspectBillDetailList(Long taskId);
+
+    /**
+     * 获取检验单主表信息
+     *
+     * @param taskId 任务编号
+     * @return 主表信息
+     */
+    IqcInspectBillMainRespVO getInspectBillMain(Long taskId);
+
+    /**
+     * 保存检验单(主表+明细)
+     *
+     * @param reqVO 保存数据
+     * @return 主表编号
+     */
+    Long saveInspectBill(IqcInspectBillSaveReqVO reqVO);
+
     /**
      * 发起来料检验任务流程
      *

+ 75 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/service/iqc/IqcTaskServiceImpl.java

@@ -5,6 +5,12 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
 import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
 import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcInspectBillDetailItemRespVO;
+import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcInspectBillMainRespVO;
+import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcInspectBillSaveReqVO;
 import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskDetailRespVO;
 import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskPageReqVO;
 import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskProcessInfoVO;
@@ -55,6 +61,75 @@ public class IqcTaskServiceImpl implements IqcTaskService {
         return iqcTaskMapper.selectTaskDetail(taskId);
     }
 
+    @Override
+    public java.util.List<IqcInspectBillDetailItemRespVO> getInspectBillDetailList(Long taskId) {
+        if (taskId == null) {
+            return java.util.Collections.emptyList();
+        }
+        return iqcTaskMapper.selectInspectBillDetailList(taskId);
+    }
+
+    @Override
+    public IqcInspectBillMainRespVO getInspectBillMain(Long taskId) {
+        if (taskId == null) {
+            return null;
+        }
+        IqcInspectBillMainRespVO main = iqcTaskMapper.selectInspectBillMain(taskId);
+        if (main == null || StrUtil.isBlank(main.getComment())) {
+            return main;
+        }
+        try {
+            java.util.Map<String, Object> data = JsonUtils.parseObject(main.getComment(), java.util.Map.class);
+            if (data != null) {
+                main.setMainA(data.get("A") == null ? null : String.valueOf(data.get("A")));
+                main.setMainB(data.get("B") == null ? null : String.valueOf(data.get("B")));
+                main.setMainC(data.get("C") == null ? null : String.valueOf(data.get("C")));
+                main.setMainD(data.get("D") == null ? null : String.valueOf(data.get("D")));
+            }
+        } catch (Exception ignore) {
+            // 不是 JSON 时,兼容把 comment 当作 A
+            main.setMainA(main.getComment());
+        }
+        return main;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long saveInspectBill(IqcInspectBillSaveReqVO reqVO) {
+        IqcInspectBillMainRespVO main = iqcTaskMapper.selectInspectBillMain(reqVO.getTaskId());
+        Long userId = getLoginUserId();
+        Long billId;
+
+        java.util.Map<String, Object> commentPayload = new java.util.HashMap<>();
+        commentPayload.put("A", reqVO.getMainA());
+        commentPayload.put("B", reqVO.getMainB());
+        commentPayload.put("C", reqVO.getMainC());
+        commentPayload.put("D", reqVO.getMainD());
+        String comment = JsonUtils.toJsonString(commentPayload);
+
+        if (main == null || main.getBillId() == null) {
+            billId = IdUtil.getSnowflakeNextId();
+            iqcTaskMapper.insertInspectBillMain(billId, reqVO.getTaskId(), comment, userId);
+        } else {
+            billId = main.getBillId();
+            iqcTaskMapper.updateInspectBillMain(billId, comment, userId);
+        }
+
+        // 先清空再插入,简化批量编辑
+        iqcTaskMapper.deleteInspectBillDetailByBillId(billId);
+        java.util.List<IqcInspectBillSaveReqVO.IqcInspectBillDetailSaveReqVO> list =
+                reqVO.getDetails() == null ? java.util.Collections.emptyList() : reqVO.getDetails();
+        if (!list.isEmpty()) {
+            for (IqcInspectBillSaveReqVO.IqcInspectBillDetailSaveReqVO item : list) {
+                if (item.getId() == null) {
+                    item.setId(IdUtil.getSnowflakeNextId());
+                }
+            }
+            iqcTaskMapper.insertInspectBillDetailBatch(billId, list);
+        }
+        return billId;
+    }
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public String startIqcTaskProcess(Long taskId) {

+ 3 - 3
yudao-module-qms/src/main/resources/mapper/iqc/IqcApplyMapper.xml

@@ -89,7 +89,7 @@
             COALESCE(FINSPECTSTATUS, '待检验') AS inspectStatus,
             FAPPLYQTY AS quantity,
             FUNIT AS unit,
-            NULL AS remark
+            FSUPPLIER AS supplierCode
         FROM qms_qcp_insappnentry
         WHERE glid = #{applyId}
         ORDER BY FSEQ ASC
@@ -126,12 +126,12 @@
 
     <insert id="insertApplyDetails">
         INSERT INTO qms_qcp_insappnentry
-            (id, glid, FSEQ, FMATERIELID, FSRCORDERNUM, FSRCORDERTYPE, FLOTNUMBER, FUNIT, FAPPLYQTY, FINSPECTSTATUS)
+            (id, glid, FSEQ, FMATERIELID, FSRCORDERNUM, FSRCORDERTYPE, FLOTNUMBER, FUNIT, FAPPLYQTY, FINSPECTSTATUS, FSUPPLIER)
         VALUES
         <foreach collection="details" item="detail" separator=",">
             (#{detail.id}, #{applyId}, #{detail.seq}, #{detail.materialId},
              #{detail.materialCode}, #{detail.materialName}, #{detail.batch},
-             #{detail.unit}, #{detail.quantity}, #{detail.inspectStatus})
+             #{detail.unit}, #{detail.quantity}, #{detail.inspectStatus}, #{detail.supplierCode})
         </foreach>
     </insert>
 

+ 62 - 1
yudao-module-qms/src/main/resources/mapper/iqc/IqcTaskMapper.xml

@@ -9,7 +9,7 @@
             a.FAPPLYTIME AS applyTime,
             a.FAPPLYUSER AS applicantId,
             u.username AS applicantName,
-            a.FCOMMENT AS remark,
+            b.FSUPPLIER AS supplierCode,
             b.process_instance_id AS processInstanceId,
             b.FSRCORDERNUM AS sourceOrderNum,
             b.FSRCORDERTYPE AS sourceOrderType,
@@ -74,6 +74,67 @@
         WHERE b.id = #{id}
     </select>
 
+    <select id="selectInspectBillDetailList" resultType="cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcInspectBillDetailItemRespVO">
+        SELECT
+            l.id AS id,
+            l.billid AS billId,
+            l.jyxm AS a,
+            l.bz AS b,
+            l.sx AS c,
+            l.xx AS d
+        FROM qms_qcp_inspbill b
+        LEFT JOIN qms_qcp_inspbilllist l ON l.billid = b.id
+        WHERE b.hid = #{taskId}
+        ORDER BY l.id
+    </select>
+
+    <select id="selectInspectBillMain" resultType="cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcInspectBillMainRespVO">
+        SELECT
+            b.id AS billId,
+            b.FCOMMENT AS comment
+        FROM qms_qcp_inspbill b
+        WHERE b.hid = #{taskId}
+        ORDER BY b.id DESC
+        LIMIT 1
+    </select>
+
+    <insert id="insertInspectBillMain">
+        INSERT INTO qms_qcp_inspbill (
+            id,
+            hid,
+            FCOMMENT,
+            FCREATORID,
+            FCREATETIME
+        ) VALUES (
+            #{id},
+            #{taskId},
+            #{comment},
+            #{creatorId},
+            NOW()
+        )
+    </insert>
+
+    <update id="updateInspectBillMain">
+        UPDATE qms_qcp_inspbill
+        SET
+            FCOMMENT = #{comment},
+            FMODIFIERID = #{modifierId},
+            FMODIFYTIME = NOW()
+        WHERE id = #{id}
+    </update>
+
+    <delete id="deleteInspectBillDetailByBillId">
+        DELETE FROM qms_qcp_inspbilllist WHERE billid = #{billId}
+    </delete>
+
+    <insert id="insertInspectBillDetailBatch">
+        INSERT INTO qms_qcp_inspbilllist (id, billid, jyxm, bz, sx, xx)
+        VALUES
+        <foreach collection="list" item="item" separator=",">
+            (#{item.id}, #{billId}, #{item.a}, #{item.b}, #{item.c}, #{item.d})
+        </foreach>
+    </insert>
+
     <update id="updateTaskProcessInfo">
         UPDATE qms_qcp_insappnentry
         <set>

+ 2 - 2
yudao-ui/yudao-ui-admin-vue3/src/api/qms/iqc/apply/index.ts

@@ -33,7 +33,7 @@ export interface IqcApplyDetailSaveReqVO {
   inspectStatus?: string
   quantity?: number
   unit?: string
-  remark?: string
+  supplierCode?: string
 }
 
 export interface IqcApplySaveReqVO {
@@ -53,7 +53,7 @@ export interface IqcApplyDetailRespVO {
   inspectStatus?: string
   quantity?: number
   unit?: string
-  remark?: string
+  supplierCode?: string
 }
 
 export const getIqcApply = (id: string) => {

+ 49 - 1
yudao-ui/yudao-ui-admin-vue3/src/api/qms/iqc/task/index.ts

@@ -17,7 +17,7 @@ export interface IqcTaskRespVO {
   applicantId?: number | string
   applicantName?: string
   processInstanceId?: string
-  remark?: string
+  supplierCode?: string
 }
 
 export interface IqcTaskDetailRespVO {
@@ -37,6 +37,38 @@ export interface IqcTaskDetailRespVO {
   remark?: string
 }
 
+export interface IqcInspectBillDetailItemRespVO {
+  id?: number | string
+  billId?: number | string
+  a?: string
+  b?: string
+  c?: string
+  d?: string
+}
+
+export interface IqcInspectBillMainRespVO {
+  billId?: number | string
+  mainA?: string
+  mainB?: string
+  mainC?: string
+  mainD?: string
+}
+
+export interface IqcInspectBillSaveReqVO {
+  taskId: number | string
+  mainA?: string
+  mainB?: string
+  mainC?: string
+  mainD?: string
+  details?: Array<{
+    id?: number | string
+    a?: string
+    b?: string
+    c?: string
+    d?: string
+  }>
+}
+
 export const getIqcTaskPage = (params: IqcTaskPageReqVO) => {
   return request.get<PageResult<IqcTaskRespVO[]>>({ url: '/qms/iqc-task/page', params })
 }
@@ -45,6 +77,22 @@ export const getIqcTaskDetail = (id: number | string) => {
   return request.get<IqcTaskDetailRespVO>({ url: `/qms/iqc-task/get?id=${id}` })
 }
 
+export const getIqcInspectBillDetailList = (taskId: number | string) => {
+  return request.get<IqcInspectBillDetailItemRespVO[]>({
+    url: `/qms/iqc-task/detail-list?taskId=${taskId}`
+  })
+}
+
+export const getIqcInspectBillMain = (taskId: number | string) => {
+  return request.get<IqcInspectBillMainRespVO>({
+    url: `/qms/iqc-task/inspect-bill/main?taskId=${taskId}`
+  })
+}
+
+export const saveIqcInspectBill = (data: IqcInspectBillSaveReqVO) => {
+  return request.post<number>({ url: '/qms/iqc-task/inspect-bill/save', data })
+}
+
 export const startIqcTaskProcess = (id: number | string) => {
   return request.post<string>({ url: '/qms/iqc-task/start', data: { id } })
 }

+ 3 - 3
yudao-ui/yudao-ui-admin-vue3/src/config/qmsModules.ts

@@ -55,7 +55,7 @@ export const QMS_MODULES: Record<string, any> = {
           { key: 'inspectStatus', label: '状态', type: 'select', width: 120, options: ['待检验', '检验中', '检验完成'] },
           { key: 'quantity', label: '数量', type: 'number', width: 120, min: 0 },
           { key: 'unit', label: '单位', type: 'input', width: 100 },
-          { key: 'remark', label: '备注', type: 'input', minWidth: 200 }
+          { key: 'supplierCode', label: '供应商编码', type: 'input', minWidth: 200 }
         ],
         defaultRow: {
           materialCode: '',
@@ -65,7 +65,7 @@ export const QMS_MODULES: Record<string, any> = {
           inspectStatus: '待检验',
           quantity: 1,
           unit: 'PCS',
-          remark: ''
+          supplierCode: ''
         }
       },
       extraSections: [],
@@ -89,7 +89,7 @@ export const QMS_MODULES: Record<string, any> = {
         { key: 'quantity', label: '数量', width: 100 },
         { key: 'unit', label: '单位', width: 80 },
         { key: 'applyTime', label: '申请时间', width: 170, type: 'datetime' },
-        { key: 'remark', label: '备注', minWidth: 200, type: 'tooltip' }
+        { key: 'supplierCode', label: '供应商编码', minWidth: 200, type: 'tooltip' }
       ],
       advancedFilters: []
     },

+ 2 - 0
yudao-ui/yudao-ui-admin-vue3/src/router/modules/remaining.ts

@@ -730,6 +730,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
       { path: 'iqc/apply/edit/:id', name: 'IqcApplicationEdit', component: () => import('@/views/qms/ApplicationForm.vue'), meta: { module: 'iqc', title: '编辑申请', noCache: true, hidden: true, canTo: true, activeMenu: '/qms', mode: 'edit' } },
       { path: 'iqc/apply/view/:id', name: 'IqcApplicationView', component: () => import('@/views/qms/ApplicationForm.vue'), meta: { module: 'iqc', title: '查看申请', noCache: true, hidden: true, canTo: true, activeMenu: '/qms', mode: 'view' } },
       { path: 'iqc/task/list', name: 'IqcTaskList', component: () => import('@/views/qms/TaskList.vue'), meta: { module: 'iqc', title: '来料检验任务', noCache: true, hidden: true, canTo: true, activeMenu: '/qms' } },
+      { path: 'iqc/task/edit/:taskId', name: 'IqcInspectBillEdit', component: () => import('@/views/qms/iqc/task/InspectBillEdit.vue'), meta: { module: 'iqc', title: '来料检验单编辑', noCache: true, hidden: true, canTo: true, activeMenu: '/qms' } },
       { path: 'iqc/favorites', name: 'IqcFavorites', component: () => import('@/views/qms/Favorites.vue'), meta: { module: 'iqc', title: '我的关注', noCache: true, hidden: true, canTo: true, activeMenu: '/qms' } },
       { path: 'iqc/help', name: 'IqcHelp', component: () => import('@/views/qms/Help.vue'), meta: { module: 'iqc', title: '帮助', noCache: true, hidden: true, canTo: true, activeMenu: '/qms' } },
       // IPQC 过程检验
@@ -765,6 +766,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
       { path: 'iqc/iqc/apply/list', name: 'S5IqcApplicationList', component: () => import('@/views/qms/ApplicationList.vue'), meta: { module: 'iqc', title: '来料检验申请', noCache: true, hidden: true, canTo: true, activeMenu: '/s5' } },
       { path: 'iqc/qms/iqc/apply/list', name: 'S5IqcApplicationListCompat', component: () => import('@/views/qms/ApplicationList.vue'), meta: { module: 'iqc', title: '来料检验申请', noCache: true, hidden: true, canTo: true, activeMenu: '/s5' } },
       { path: 'iqc/iqc/task/list', name: 'S5IqcTaskList', component: () => import('@/views/qms/TaskList.vue'), meta: { module: 'iqc', title: '来料检验任务', noCache: true, hidden: true, canTo: true, activeMenu: '/s5' } },
+      { path: 'iqc/iqc/task/edit/:taskId', name: 'S5IqcInspectBillEdit', component: () => import('@/views/qms/iqc/task/InspectBillEdit.vue'), meta: { module: 'iqc', title: '来料检验单编辑', noCache: true, hidden: true, canTo: true, activeMenu: '/s5' } },
       { path: 'iqc/qms/iqc/task/list', name: 'S5IqcTaskListCompat', component: () => import('@/views/qms/TaskList.vue'), meta: { module: 'iqc', title: '来料检验任务', noCache: true, hidden: true, canTo: true, activeMenu: '/s5' } }
     ]
   },

+ 103 - 0
yudao-ui/yudao-ui-admin-vue3/src/store/modules/qms/inspectBill.ts

@@ -0,0 +1,103 @@
+import { defineStore } from 'pinia'
+import {
+  getIqcInspectBillDetailList,
+  getIqcInspectBillMain,
+  IqcInspectBillDetailItemRespVO,
+  IqcInspectBillSaveReqVO,
+  saveIqcInspectBill
+} from '@/api/qms/iqc/task'
+
+type InspectBillMain = {
+  mainA: string
+  mainB: string
+  mainC: string
+  mainD: string
+  billId?: number | string
+}
+
+const createEmptyMain = (): InspectBillMain => ({
+  mainA: '',
+  mainB: '',
+  mainC: '',
+  mainD: ''
+})
+
+const createEmptyRow = (): IqcInspectBillDetailItemRespVO => ({
+  a: '',
+  b: '',
+  c: '',
+  d: ''
+})
+
+export const useIqcInspectBillStore = defineStore('iqcInspectBill', {
+  state: () => ({
+    drafts: {} as Record<string, { main: InspectBillMain; details: IqcInspectBillDetailItemRespVO[] }>
+  }),
+  actions: {
+    ensureDraft(taskId: string) {
+      if (!this.drafts[taskId]) {
+        this.drafts[taskId] = {
+          main: createEmptyMain(),
+          details: [createEmptyRow()]
+        }
+      }
+      return this.drafts[taskId]
+    },
+    setMain(taskId: string, main: Partial<InspectBillMain>) {
+      const draft = this.ensureDraft(taskId)
+      draft.main = { ...draft.main, ...main }
+    },
+    setDetails(taskId: string, details: IqcInspectBillDetailItemRespVO[]) {
+      const draft = this.ensureDraft(taskId)
+      draft.details = details && details.length ? details : [createEmptyRow()]
+    },
+    addDetailRow(taskId: string) {
+      const draft = this.ensureDraft(taskId)
+      draft.details.push(createEmptyRow())
+    },
+    deleteDetailRow(taskId: string, index: number) {
+      const draft = this.ensureDraft(taskId)
+      draft.details.splice(index, 1)
+      if (!draft.details.length) {
+        draft.details.push(createEmptyRow())
+      }
+    },
+    async load(taskId: string) {
+      const draft = this.ensureDraft(taskId)
+      const [mainResult, detailResult] = await Promise.all([
+        getIqcInspectBillMain(taskId),
+        getIqcInspectBillDetailList(taskId)
+      ])
+      const main = (mainResult as any)?.data ?? mainResult
+      const details = (detailResult as any)?.data ?? detailResult
+      draft.main = {
+        mainA: main?.mainA || '',
+        mainB: main?.mainB || '',
+        mainC: main?.mainC || '',
+        mainD: main?.mainD || '',
+        billId: main?.billId
+      }
+      draft.details = Array.isArray(details) && details.length ? details : [createEmptyRow()]
+      return draft
+    },
+    async save(taskId: string) {
+      const draft = this.ensureDraft(taskId)
+      const payload: IqcInspectBillSaveReqVO = {
+        taskId,
+        mainA: draft.main.mainA,
+        mainB: draft.main.mainB,
+        mainC: draft.main.mainC,
+        mainD: draft.main.mainD,
+        details: draft.details.map((item) => ({
+          id: item.id,
+          a: item.a,
+          b: item.b,
+          c: item.c,
+          d: item.d
+        }))
+      }
+      const result = await saveIqcInspectBill(payload)
+      return (result as any)?.data ?? result
+    }
+  }
+})

+ 19 - 0
yudao-ui/yudao-ui-admin-vue3/src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue

@@ -529,11 +529,13 @@ import type { FormInstance, FormRules } from 'element-plus'
 import SignDialog from './SignDialog.vue'
 import ProcessInstanceTimeline from '../detail/ProcessInstanceTimeline.vue'
 import { isEmpty } from '@/utils/is'
+import { useIqcInspectBillStore } from '@/store/modules/qms/inspectBill'
 
 defineOptions({ name: 'ProcessInstanceBtnContainer' })
 
 const router = useRouter() // 路由
 const message = useMessage() // 消息弹窗
+const inspectBillStore = useIqcInspectBillStore()
 
 const userId = useUserStoreWithOut().getUser.id // 当前登录的编号
 const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
@@ -799,6 +801,22 @@ const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) =>
     if (pass) {
       const nextAssigneesValid = validateNextAssignees()
       if (!nextAssigneesValid) return
+      if (isIqcProcess.value) {
+        const taskId = props.processInstance?.businessKey
+        if (!taskId) {
+          message.warning('任务编号缺失,无法保存检验单')
+          return
+        }
+        try {
+          if (!inspectBillStore.drafts?.[String(taskId)]) {
+            await inspectBillStore.load(String(taskId))
+          }
+          await inspectBillStore.save(String(taskId))
+        } catch {
+          message.error('保存检验单失败')
+          return
+        }
+      }
       const variables = getUpdatedProcessInstanceVariables()
       // 审批通过数据
       const data = {
@@ -1073,6 +1091,7 @@ const getButtonDisplayName = (btnType: OperationButtonType) => {
 
 const PROCESS_SKIP_NORMAL_FORM_KEYS = new Set(['qms_iqc_task'])
 const shouldSkipNormalForm = computed(() => PROCESS_SKIP_NORMAL_FORM_KEYS.has(props.processDefinition?.key))
+const isIqcProcess = computed(() => props.processDefinition?.key === 'qms_iqc_task')
 
 const loadTodoTask = (task: any) => {
   approveForm.value = {}

+ 11 - 13
yudao-ui/yudao-ui-admin-vue3/src/views/qms/ApplicationForm.vue

@@ -2,7 +2,6 @@
   <div class="page-container" v-loading="loading">
     <div class="page-header">
       <h1>{{ pageTitle }}</h1>
-      <IqcStatusBadge v-if="applicationData && !isCreate" :status="applicationData.status" />
     </div>
 
     <div class="form-container">
@@ -69,7 +68,6 @@ defineOptions({ name: 'IqcApplicationForm' })
 
 import { reactive, ref, computed, onMounted } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
-import IqcStatusBadge from '@/components/Qms/StatusBadge.vue'
 import { getQmsModuleConfig, DEFAULT_QMS_MODULE } from '@/config/qmsModules'
 import { createIqcApply, getIqcApply, updateIqcApply } from '@/api/qms/iqc/apply'
 
@@ -181,16 +179,16 @@ const buildPayload = () => ({
   businessType: formData.businessType || undefined,
   department: formData.department || undefined,
   remark: formData.remark || undefined,
-  details: (formData.details || []).map((detail: any) => ({
-    materialCode: detail.materialCode || '',
-    materialName: detail.materialName || '',
-    specification: detail.specification || '',
-    batch: detail.batch || '',
-    inspectStatus: detail.inspectStatus || '待检验',
-    quantity: detail.quantity ?? 0,
-    unit: detail.unit || '',
-    remark: detail.remark || ''
-  }))
+    details: (formData.details || []).map((detail: any) => ({
+      materialCode: detail.materialCode || '',
+      materialName: detail.materialName || '',
+      specification: detail.specification || '',
+      batch: detail.batch || '',
+      inspectStatus: detail.inspectStatus || '待检验',
+      quantity: detail.quantity ?? 0,
+      unit: detail.unit || '',
+      supplierCode: detail.supplierCode || ''
+    }))
 })
 
 const applyResponseToForm = (data: any) => {
@@ -209,7 +207,7 @@ const applyResponseToForm = (data: any) => {
       inspectStatus: detail.inspectStatus || '待检验',
       quantity: detail.quantity ?? 0,
       unit: detail.unit || '',
-      remark: detail.remark || ''
+      supplierCode: detail.supplierCode || ''
     }))
     : [createDetailRow()]
 }

+ 181 - 0
yudao-ui/yudao-ui-admin-vue3/src/views/qms/iqc/task/InspectBillEdit.vue

@@ -0,0 +1,181 @@
+<template>
+  <div class="iqc-edit-page" v-loading="loading">
+    <div class="page-header">
+      <div>
+        <h2>来料检验单编辑</h2>
+        <div class="sub-title">任务编号:{{ taskId }}</div>
+      </div>
+      <div class="header-actions">
+        <el-button @click="handleBack">返回</el-button>
+        <el-button type="primary" @click="handleSave" :loading="saving">保存</el-button>
+      </div>
+    </div>
+
+    <el-card class="section-card" shadow="never">
+      <template #header>
+        <div class="section-title">主表信息(A/B/C/D 占位)</div>
+      </template>
+      <el-form label-width="100px">
+        <el-row :gutter="16">
+          <el-col :span="6">
+            <el-form-item label="A">
+              <el-input v-model="mainForm.mainA" placeholder="请输入 A" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="6">
+            <el-form-item label="B">
+              <el-input v-model="mainForm.mainB" placeholder="请输入 B" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="6">
+            <el-form-item label="C">
+              <el-input v-model="mainForm.mainC" placeholder="请输入 C" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="6">
+            <el-form-item label="D">
+              <el-input v-model="mainForm.mainD" placeholder="请输入 D" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+    </el-card>
+
+    <el-card class="section-card" shadow="never">
+      <template #header>
+        <div class="section-title">
+          检验明细(A/B/C/D 占位)
+          <div class="section-actions">
+            <el-button type="primary" size="small" @click="handleAddRow">新增行</el-button>
+          </div>
+        </div>
+      </template>
+      <el-table :data="detailRows" border>
+        <el-table-column label="A" width="220">
+          <template #default="{ row }">
+            <el-input v-model="row.a" placeholder="请输入 A" />
+          </template>
+        </el-table-column>
+        <el-table-column label="B" width="220">
+          <template #default="{ row }">
+            <el-input v-model="row.b" placeholder="请输入 B" />
+          </template>
+        </el-table-column>
+        <el-table-column label="C" width="180">
+          <template #default="{ row }">
+            <el-input v-model="row.c" placeholder="请输入 C" />
+          </template>
+        </el-table-column>
+        <el-table-column label="D" width="180">
+          <template #default="{ row }">
+            <el-input v-model="row.d" placeholder="请输入 D" />
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="100">
+          <template #default="{ $index }">
+            <el-button link type="danger" size="small" @click="handleDeleteRow($index)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+  </div>
+</template>
+
+<script setup lang="ts">
+defineOptions({ name: 'IqcInspectBillEdit' })
+
+import { computed, ref, watch } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { useIqcInspectBillStore } from '@/store/modules/qms/inspectBill'
+
+const route = useRoute()
+const router = useRouter()
+const inspectBillStore = useIqcInspectBillStore()
+
+const loading = ref(false)
+const saving = ref(false)
+
+const taskId = computed(() => String(route.params.taskId || ''))
+
+const mainForm = computed(() => {
+  if (!taskId.value) return inspectBillStore.ensureDraft('temp').main
+  return inspectBillStore.ensureDraft(taskId.value).main
+})
+
+const detailRows = computed(() => {
+  if (!taskId.value) return []
+  return inspectBillStore.ensureDraft(taskId.value).details
+})
+
+const fetchData = async () => {
+  if (!taskId.value) return
+  loading.value = true
+  try {
+    await inspectBillStore.load(taskId.value)
+  } finally {
+    loading.value = false
+  }
+}
+
+const handleAddRow = () => {
+  if (!taskId.value) return
+  inspectBillStore.addDetailRow(taskId.value)
+}
+
+const handleDeleteRow = (index: number) => {
+  if (!taskId.value) return
+  inspectBillStore.deleteDetailRow(taskId.value, index)
+}
+
+const handleSave = async () => {
+  if (!taskId.value) return
+  saving.value = true
+  try {
+    await inspectBillStore.save(taskId.value)
+    ElMessage.success('保存成功')
+  } catch {
+    ElMessage.error('保存失败')
+  } finally {
+    saving.value = false
+  }
+}
+
+const handleBack = () => {
+  router.back()
+}
+
+watch(taskId, () => { fetchData() }, { immediate: true })
+</script>
+
+<style scoped lang="scss">
+.iqc-edit-page {
+  padding: 20px;
+}
+.page-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 16px;
+  h2 {
+    margin: 0;
+    font-size: 20px;
+  }
+  .sub-title {
+    font-size: 12px;
+    color: var(--el-text-color-secondary);
+    margin-top: 4px;
+  }
+}
+.section-card {
+  margin-bottom: 16px;
+}
+.section-title {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-weight: 600;
+  .section-actions {
+    margin-left: auto;
+  }
+}
+</style>

+ 32 - 23
yudao-ui/yudao-ui-admin-vue3/src/views/qms/iqc/task/ProcessDetailForm.vue

@@ -40,17 +40,9 @@
             {{ detail.sourceOrderType || '-' }}
           </el-descriptions-item>
         </el-descriptions>
-      </div>
-
-      <div class="form-section">
-        <div class="section-title">检验明细</div>
-        <el-table :data="detailRows" border>
-          <el-table-column prop="batch" label="批次" width="160" />
-          <el-table-column prop="quantity" label="数量" width="120" />
-          <el-table-column prop="unit" label="单位" width="100" />
-          <el-table-column prop="sourceOrderNum" label="来源单号" min-width="200" />
-          <el-table-column prop="sourceOrderType" label="来源类型" width="140" />
-        </el-table>
+        <div class="section-actions-inline">
+          <el-button plain size="small" @click="handleOpenEditor">打开编辑页面</el-button>
+        </div>
       </div>
 
       <div class="form-section">
@@ -72,24 +64,25 @@
 defineOptions({ name: 'IqcTaskProcessDetailForm' })
 
 import { computed, ref, watch } from 'vue'
-import { getIqcTaskDetail, IqcTaskDetailRespVO } from '@/api/qms/iqc/task'
+import { useRouter } from 'vue-router'
+import {
+  getIqcTaskDetail,
+  IqcTaskDetailRespVO
+} from '@/api/qms/iqc/task'
 
 const props = defineProps<{ id: string }>()
-
+const router = useRouter()
 const loading = ref(false)
 const detail = ref<IqcTaskDetailRespVO | null>(null)
 
 const remarkValue = computed(() => detail.value?.remark || '')
-const detailRows = computed(() => {
-  if (!detail.value) return []
-  return [{
-    batch: detail.value.batch || '-',
-    quantity: detail.value.quantity ?? '-',
-    unit: detail.value.unit || '-',
-    sourceOrderNum: detail.value.sourceOrderNum || '-',
-    sourceOrderType: detail.value.sourceOrderType || '-'
-  }]
-})
+
+const handleOpenEditor = () => {
+  if (!props.id) return
+  const currentPath = router.currentRoute.value.path || ''
+  const basePath = currentPath.startsWith('/s5') ? '/s5/iqc/iqc/task/edit' : '/qms/iqc/task/edit'
+  router.push(`${basePath}/${props.id}`)
+}
 
 const formatDateTime = (value: any) => {
   if (!value) return '-'
@@ -133,10 +126,26 @@ watch(() => props.id, () => { fetchDetail() }, { immediate: true })
 .form-section {
   margin-bottom: 16px;
   .section-title {
+    display: flex;
+    align-items: center;
     font-size: 14px;
     font-weight: 600;
     color: var(--el-text-color-primary);
     margin-bottom: 10px;
+    gap: 8px;
+  }
+  .section-hint {
+    font-size: 12px;
+    color: var(--el-text-color-secondary);
+    font-weight: 400;
+  }
+  .section-actions {
+    margin-left: auto;
+  }
+  .section-actions-inline {
+    display: flex;
+    justify-content: flex-end;
+    margin-top: 8px;
   }
 }
 </style>