浏览代码

IPQC 业务表单相关

liuwb 2 月之前
父节点
当前提交
06b7c9e2a6

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

@@ -3,6 +3,9 @@ package cn.iocoder.yudao.module.qms.controller.admin.ipqc;
 import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcInspectRecordDetailRespVO;
+import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcInspectRecordMainRespVO;
+import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcInspectRecordSaveReqVO;
 import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcTaskPageReqVO;
 import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcTaskRespVO;
 import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcTaskStartReqVO;
@@ -17,6 +20,7 @@ import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -47,4 +51,25 @@ public class IpqcTaskController {
         }
         return success(processInstanceId);
     }
+
+    @GetMapping("/record/main")
+    @Operation(summary = "获取过程检验记录主表")
+    @PreAuthorize("@ss.hasPermission('qms:ipqc:task:list')")
+    public CommonResult<IpqcInspectRecordMainRespVO> getInspectRecordMain(@RequestParam("taskId") Long taskId) {
+        return success(ipqcTaskService.getInspectRecordMain(taskId));
+    }
+
+    @GetMapping("/record/detail-list")
+    @Operation(summary = "获取过程检验记录明细列表")
+    @PreAuthorize("@ss.hasPermission('qms:ipqc:task:list')")
+    public CommonResult<java.util.List<IpqcInspectRecordDetailRespVO>> getInspectRecordDetailList(@RequestParam("taskId") Long taskId) {
+        return success(ipqcTaskService.getInspectRecordDetailList(taskId));
+    }
+
+    @PostMapping("/record/save")
+    @Operation(summary = "保存过程检验记录(主表+明细)")
+    @PreAuthorize("@ss.hasPermission('qms:ipqc:task:list')")
+    public CommonResult<Long> saveInspectRecord(@Valid @RequestBody IpqcInspectRecordSaveReqVO reqVO) {
+        return success(ipqcTaskService.saveInspectRecord(reqVO));
+    }
 }

+ 44 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/ipqc/vo/IpqcInspectRecordDetailRespVO.java

@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "管理后台 - IPQC 过程检验记录明细 Response VO")
+@Data
+public class IpqcInspectRecordDetailRespVO {
+
+    @Schema(description = "明细主键")
+    private Long id;
+
+    @Schema(description = "主表 ID")
+    private Long glid;
+
+    @Schema(description = "检验项目")
+    private String jyxm;
+
+    @Schema(description = "检验标准")
+    private String jybz;
+
+    @Schema(description = "样本1")
+    private String j1;
+
+    @Schema(description = "样本2")
+    private String j2;
+
+    @Schema(description = "样本3")
+    private String j3;
+
+    @Schema(description = "判定")
+    private Long pd;
+
+    @Schema(description = "检验数量")
+    private BigDecimal jysl;
+
+    @Schema(description = "不合格数量")
+    private BigDecimal bhgsl;
+
+    @Schema(description = "备注")
+    private String bz;
+}

+ 77 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/ipqc/vo/IpqcInspectRecordMainRespVO.java

@@ -0,0 +1,77 @@
+package cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "管理后台 - IPQC 过程检验记录主表 Response VO")
+@Data
+public class IpqcInspectRecordMainRespVO {
+
+    @Schema(description = "主表主键")
+    private Long id;
+
+    @Schema(description = "表单编号")
+    private String bdbh;
+
+    @Schema(description = "版本号")
+    private String bbh;
+
+    @Schema(description = "生效日期")
+    private String sxrq;
+
+    @Schema(description = "单据编号")
+    private String djbh;
+
+    @Schema(description = "产品型号")
+    private String cplx;
+
+    @Schema(description = "生产批号")
+    private String scph;
+
+    @Schema(description = "物料编码")
+    private String wlbm;
+
+    @Schema(description = "物料名称")
+    private String wlmc;
+
+    @Schema(description = "工序编码")
+    private String gxbm;
+
+    @Schema(description = "工序名称")
+    private String gxmc;
+
+    @Schema(description = "生产作业人员")
+    private String sczyry;
+
+    @Schema(description = "来源单据编号")
+    private String lydjbh;
+
+    @Schema(description = "检验日期")
+    private String jyrq;
+
+    @Schema(description = "检验人")
+    private String jyr;
+
+    @Schema(description = "最大样本量")
+    private BigDecimal ybl;
+
+    @Schema(description = "结果判定")
+    private Long jgpd;
+
+    @Schema(description = "附件")
+    private String fj;
+
+    @Schema(description = "检规编号")
+    private String jgbh;
+
+    @Schema(description = "检规版本")
+    private String jgbb;
+
+    @Schema(description = "备注")
+    private String bz;
+
+    @Schema(description = "检验状态")
+    private String status;
+}

+ 121 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/ipqc/vo/IpqcInspectRecordSaveReqVO.java

@@ -0,0 +1,121 @@
+package cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+@Schema(description = "管理后台 - IPQC 过程检验记录保存 Request VO")
+@Data
+public class IpqcInspectRecordSaveReqVO {
+
+    @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "任务编号不能为空")
+    private Long taskId;
+
+    @Schema(description = "主表主键")
+    private Long id;
+
+    @Schema(description = "表单编号")
+    private String bdbh;
+
+    @Schema(description = "版本号")
+    private String bbh;
+
+    @Schema(description = "生效日期")
+    private String sxrq;
+
+    @Schema(description = "单据编号")
+    private String djbh;
+
+    @Schema(description = "产品型号")
+    private String cplx;
+
+    @Schema(description = "生产批号")
+    private String scph;
+
+    @Schema(description = "物料编码")
+    private String wlbm;
+
+    @Schema(description = "物料名称")
+    private String wlmc;
+
+    @Schema(description = "工序编码")
+    private String gxbm;
+
+    @Schema(description = "工序名称")
+    private String gxmc;
+
+    @Schema(description = "生产作业人员")
+    private String sczyry;
+
+    @Schema(description = "来源单据编号")
+    private String lydjbh;
+
+    @Schema(description = "检验日期")
+    private String jyrq;
+
+    @Schema(description = "检验人")
+    private String jyr;
+
+    @Schema(description = "最大样本量")
+    private BigDecimal ybl;
+
+    @Schema(description = "结果判定")
+    private Long jgpd;
+
+    @Schema(description = "附件")
+    private String fj;
+
+    @Schema(description = "检规编号")
+    private String jgbh;
+
+    @Schema(description = "检规版本")
+    private String jgbb;
+
+    @Schema(description = "备注")
+    private String bz;
+
+    @Schema(description = "检验状态")
+    private String status;
+
+    @Schema(description = "明细列表")
+    private List<IpqcInspectRecordDetailSaveReqVO> details;
+
+    @Schema(description = "IPQC 过程检验记录明细保存 VO")
+    @Data
+    public static class IpqcInspectRecordDetailSaveReqVO {
+
+        @Schema(description = "明细主键")
+        private Long id;
+
+        @Schema(description = "检验项目")
+        private String jyxm;
+
+        @Schema(description = "检验标准")
+        private String jybz;
+
+        @Schema(description = "样本1")
+        private String j1;
+
+        @Schema(description = "样本2")
+        private String j2;
+
+        @Schema(description = "样本3")
+        private String j3;
+
+        @Schema(description = "判定")
+        private Long pd;
+
+        @Schema(description = "检验数量")
+        private BigDecimal jysl;
+
+        @Schema(description = "不合格数量")
+        private BigDecimal bhgsl;
+
+        @Schema(description = "备注")
+        private String bz;
+    }
+}

+ 53 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/dal/mysql/ipqc/IpqcTaskMapper.java

@@ -3,6 +3,9 @@ package cn.iocoder.yudao.module.qms.dal.mysql.ipqc;
 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.ipqc.vo.IpqcInspectRecordDetailRespVO;
+import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcInspectRecordMainRespVO;
+import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcInspectRecordSaveReqVO;
 import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcTaskPageReqVO;
 import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcTaskProcessInfoVO;
 import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcTaskRespVO;
@@ -11,6 +14,8 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 
+import java.util.List;
+
 @Mapper
 public interface IpqcTaskMapper {
 
@@ -20,6 +25,54 @@ public interface IpqcTaskMapper {
     @TenantIgnore
     IpqcTaskProcessInfoVO selectTaskProcessInfo(@Param("id") Long id);
 
+    /**
+     * 查询检验记录主表
+     */
+    @TenantIgnore
+    IpqcInspectRecordMainRespVO selectInspectRecordMain(@Param("workOrder") String workOrder,
+                                                        @Param("batch") String batch);
+
+    /**
+     * 查询检验记录明细
+     */
+    @TenantIgnore
+    List<IpqcInspectRecordDetailRespVO> selectInspectRecordDetailList(@Param("recordId") Long recordId);
+
+    /**
+     * 插入检验记录主表
+     */
+    @TenantIgnore
+    void insertInspectRecordMain(@Param("id") Long id,
+                                 @Param("req") IpqcInspectRecordSaveReqVO req,
+                                 @Param("djbh") String djbh,
+                                 @Param("workOrder") String workOrder,
+                                 @Param("batch") String batch,
+                                 @Param("status") String status);
+
+    /**
+     * 更新检验记录主表
+     */
+    @TenantIgnore
+    void updateInspectRecordMain(@Param("id") Long id,
+                                 @Param("req") IpqcInspectRecordSaveReqVO req,
+                                 @Param("djbh") String djbh,
+                                 @Param("workOrder") String workOrder,
+                                 @Param("batch") String batch,
+                                 @Param("status") String status);
+
+    /**
+     * 删除检验记录明细
+     */
+    @TenantIgnore
+    void deleteInspectRecordDetailByRecordId(@Param("recordId") Long recordId);
+
+    /**
+     * 批量插入检验记录明细
+     */
+    @TenantIgnore
+    void insertInspectRecordDetailBatch(@Param("recordId") Long recordId,
+                                        @Param("list") List<IpqcInspectRecordSaveReqVO.IpqcInspectRecordDetailSaveReqVO> list);
+
     default PageResult<IpqcTaskRespVO> selectTaskPage(IpqcTaskPageReqVO reqVO) {
         Page<IpqcTaskRespVO> page = MyBatisUtils.buildPage(reqVO);
         IPage<IpqcTaskRespVO> result = selectTaskPage(page, reqVO);

+ 11 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/service/ipqc/IpqcTaskService.java

@@ -1,12 +1,23 @@
 package cn.iocoder.yudao.module.qms.service.ipqc;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcInspectRecordDetailRespVO;
+import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcInspectRecordMainRespVO;
+import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcInspectRecordSaveReqVO;
 import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcTaskPageReqVO;
 import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcTaskRespVO;
 
+import java.util.List;
+
 public interface IpqcTaskService {
 
     PageResult<IpqcTaskRespVO> getIpqcTaskPage(IpqcTaskPageReqVO pageReqVO);
 
     String startIpqcTaskProcess(Long taskId);
+
+    IpqcInspectRecordMainRespVO getInspectRecordMain(Long taskId);
+
+    List<IpqcInspectRecordDetailRespVO> getInspectRecordDetailList(Long taskId);
+
+    Long saveInspectRecord(IpqcInspectRecordSaveReqVO reqVO);
 }

+ 93 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/service/ipqc/IpqcTaskServiceImpl.java

@@ -1,16 +1,24 @@
 package cn.iocoder.yudao.module.qms.service.ipqc;
 
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.StrUtil;
 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.qms.controller.admin.ipqc.vo.IpqcInspectRecordDetailRespVO;
+import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcInspectRecordMainRespVO;
+import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcInspectRecordSaveReqVO;
 import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcTaskPageReqVO;
 import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcTaskProcessInfoVO;
 import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcTaskRespVO;
 import cn.iocoder.yudao.module.qms.dal.mysql.ipqc.IpqcTaskMapper;
 import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@@ -55,4 +63,89 @@ public class IpqcTaskServiceImpl implements IpqcTaskService {
                         .setBusinessKey(String.valueOf(taskInfo.getId()))
                         .setVariables(variables));
     }
+
+    @Override
+    public IpqcInspectRecordMainRespVO getInspectRecordMain(Long taskId) {
+        if (taskId == null) {
+            return null;
+        }
+        IpqcTaskProcessInfoVO taskInfo = ipqcTaskMapper.selectTaskProcessInfo(taskId);
+        if (taskInfo == null) {
+            return null;
+        }
+        String workOrder = taskInfo.getWorkOrder();
+        String batch = taskInfo.getBatch();
+        IpqcInspectRecordMainRespVO main = ipqcTaskMapper.selectInspectRecordMain(workOrder, batch);
+        if (main == null) {
+            main = new IpqcInspectRecordMainRespVO();
+            main.setLydjbh(workOrder);
+            main.setScph(batch);
+            main.setWlbm(taskInfo.getMaterialCode());
+        }
+        return main;
+    }
+
+    @Override
+    public List<IpqcInspectRecordDetailRespVO> getInspectRecordDetailList(Long taskId) {
+        if (taskId == null) {
+            return Collections.emptyList();
+        }
+        IpqcTaskProcessInfoVO taskInfo = ipqcTaskMapper.selectTaskProcessInfo(taskId);
+        if (taskInfo == null) {
+            return Collections.emptyList();
+        }
+        IpqcInspectRecordMainRespVO main = ipqcTaskMapper.selectInspectRecordMain(taskInfo.getWorkOrder(), taskInfo.getBatch());
+        if (main == null || main.getId() == null) {
+            return Collections.emptyList();
+        }
+        return ipqcTaskMapper.selectInspectRecordDetailList(main.getId());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long saveInspectRecord(IpqcInspectRecordSaveReqVO reqVO) {
+        if (reqVO == null || reqVO.getTaskId() == null) {
+            return null;
+        }
+        IpqcTaskProcessInfoVO taskInfo = ipqcTaskMapper.selectTaskProcessInfo(reqVO.getTaskId());
+        if (taskInfo == null) {
+            return null;
+        }
+        String workOrder = StrUtil.blankToDefault(reqVO.getLydjbh(), taskInfo.getWorkOrder());
+        String batch = StrUtil.blankToDefault(reqVO.getScph(), taskInfo.getBatch());
+        String materialCode = StrUtil.blankToDefault(reqVO.getWlbm(), taskInfo.getMaterialCode());
+        reqVO.setLydjbh(workOrder);
+        reqVO.setScph(batch);
+        reqVO.setWlbm(materialCode);
+        IpqcInspectRecordMainRespVO existing = ipqcTaskMapper.selectInspectRecordMain(workOrder, batch);
+
+        Long recordId = reqVO.getId();
+        if (recordId == null && existing != null) {
+            recordId = existing.getId();
+        }
+        if (recordId == null) {
+            recordId = IdUtil.getSnowflakeNextId();
+        }
+
+        String djbh = StrUtil.blankToDefault(reqVO.getDjbh(), String.valueOf(recordId));
+        String status = StrUtil.blankToDefault(reqVO.getStatus(), "检验中");
+
+        if (existing == null || existing.getId() == null) {
+            ipqcTaskMapper.insertInspectRecordMain(recordId, reqVO, djbh, workOrder, batch, status);
+        } else {
+            ipqcTaskMapper.updateInspectRecordMain(recordId, reqVO, djbh, workOrder, batch, status);
+        }
+
+        ipqcTaskMapper.deleteInspectRecordDetailByRecordId(recordId);
+        List<IpqcInspectRecordSaveReqVO.IpqcInspectRecordDetailSaveReqVO> details = reqVO.getDetails();
+        if (details != null && !details.isEmpty()) {
+            for (IpqcInspectRecordSaveReqVO.IpqcInspectRecordDetailSaveReqVO item : details) {
+                if (item.getId() == null) {
+                    item.setId(IdUtil.getSnowflakeNextId());
+                }
+            }
+            ipqcTaskMapper.insertInspectRecordDetailBatch(recordId, details);
+        }
+        return recordId;
+    }
 }

+ 119 - 1
yudao-module-qms/src/main/resources/mapper/ipqc/IpqcTaskMapper.xml

@@ -25,7 +25,7 @@
             fr.OrdDate AS startTime,
             fr.DueDate AS completeTime,
             fr.Remark AS remark,
-            NULL AS processInstanceId,
+            bpi.process_instance_id AS processInstanceId,
             CASE d.status_rank
                 WHEN 3 THEN 'processing'
                 WHEN 2 THEN 'completed'
@@ -71,6 +71,27 @@
         ) d
             ON d.scph = fr.LotSerial
            AND d.lydjbh = fr.WorkOrd
+        LEFT JOIN (
+            SELECT business_key, MAX(id) AS process_instance_id
+            FROM bpm_process_instance
+            WHERE process_definition_key = 'qms_ipqc_task'
+            GROUP BY business_key
+        ) bpi
+            ON bpi.business_key = CAST(fr.recid AS CHAR)
+        <where>
+            <if test="req.factoryId != null and req.factoryId != ''">
+                AND fr.`Domain` = #{req.factoryId}
+            </if>
+            <if test="req.status != null and req.status != ''">
+                AND (
+                    CASE d.status_rank
+                        WHEN 3 THEN 'processing'
+                        WHEN 2 THEN 'completed'
+                        ELSE 'pending'
+                    END
+                ) = #{req.status}
+            </if>
+        </where>
         ORDER BY fr.OrdDate DESC, fr.recid DESC
     </select>
 
@@ -85,4 +106,101 @@
         WHERE a.recid = #{id}
     </select>
 
+    <select id="selectInspectRecordMain" resultType="cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcInspectRecordMainRespVO">
+        SELECT
+            id,
+            bdbh,
+            bbh,
+            sxrq,
+            djbh,
+            cplx,
+            scph,
+            wlbm,
+            wlmc,
+            gxbm,
+            gxmc,
+            sczyry,
+            lydjbh,
+            jyrq,
+            jyr,
+            ybl,
+            jgpd,
+            fj,
+            jgbh,
+            jgbb,
+            bz,
+            status
+        FROM qms_gcjyd
+        WHERE lydjbh = #{workOrder}
+          AND scph = #{batch}
+        ORDER BY sxrq DESC, id DESC
+        LIMIT 1
+    </select>
+
+    <select id="selectInspectRecordDetailList" resultType="cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcInspectRecordDetailRespVO">
+        SELECT
+            id,
+            glid,
+            jyxm,
+            jybz,
+            j1,
+            j2,
+            j3,
+            pd,
+            jysl,
+            bhgsl,
+            bz
+        FROM qms_gcjydzb
+        WHERE glid = #{recordId}
+        ORDER BY id ASC
+    </select>
+
+    <insert id="insertInspectRecordMain">
+        INSERT INTO qms_gcjyd
+        (id, bdbh, bbh, sxrq, djbh, cplx, scph, wlbm, wlmc, gxbm, gxmc, sczyry, lydjbh, jyrq, jyr, ybl, jgpd, fj, jgbh, jgbb, bz, status)
+        VALUES
+        (#{id}, #{req.bdbh}, #{req.bbh}, #{req.sxrq}, #{djbh}, #{req.cplx}, #{batch}, #{req.wlbm}, #{req.wlmc}, #{req.gxbm}, #{req.gxmc}, #{req.sczyry}, #{workOrder}, #{req.jyrq}, #{req.jyr}, #{req.ybl}, #{req.jgpd}, #{req.fj}, #{req.jgbh}, #{req.jgbb}, #{req.bz}, #{status})
+    </insert>
+
+    <update id="updateInspectRecordMain">
+        UPDATE qms_gcjyd
+        SET
+            bdbh = #{req.bdbh},
+            bbh = #{req.bbh},
+            sxrq = #{req.sxrq},
+            djbh = #{djbh},
+            cplx = #{req.cplx},
+            scph = #{batch},
+            wlbm = #{req.wlbm},
+            wlmc = #{req.wlmc},
+            gxbm = #{req.gxbm},
+            gxmc = #{req.gxmc},
+            sczyry = #{req.sczyry},
+            lydjbh = #{workOrder},
+            jyrq = #{req.jyrq},
+            jyr = #{req.jyr},
+            ybl = #{req.ybl},
+            jgpd = #{req.jgpd},
+            fj = #{req.fj},
+            jgbh = #{req.jgbh},
+            jgbb = #{req.jgbb},
+            bz = #{req.bz},
+            status = #{status}
+        WHERE id = #{id}
+    </update>
+
+    <delete id="deleteInspectRecordDetailByRecordId">
+        DELETE FROM qms_gcjydzb
+        WHERE glid = #{recordId}
+    </delete>
+
+    <insert id="insertInspectRecordDetailBatch">
+        INSERT INTO qms_gcjydzb
+        (id, glid, jyxm, jybz, j1, j2, j3, pd, jysl, bhgsl, bz)
+        VALUES
+        <foreach collection="list" item="item" separator=",">
+            (#{item.id}, #{recordId}, #{item.jyxm}, #{item.jybz}, #{item.j1}, #{item.j2}, #{item.j3}, #{item.pd}, #{item.jysl}, #{item.bhgsl}, #{item.bz})
+        </foreach>
+    </insert>
+
 </mapper>

+ 1 - 0
yudao-server/src/main/resources/application.yaml

@@ -324,6 +324,7 @@ yudao:
       - ItemMaster
       - mes_morder
       - qms_gcjyd
+      - qms_gcjydzb
     ignore-caches:
       - user_role_ids
       - permission_menu_ids

+ 56 - 0
yudao-ui/yudao-ui-admin-vue3/src/api/qms/ipqc/task/index.ts

@@ -26,6 +26,50 @@ export interface IpqcTaskRespVO {
   processInstanceId?: string
 }
 
+export interface IpqcInspectRecordMainRespVO {
+  id?: number | string
+  bdbh?: string
+  bbh?: string
+  sxrq?: string | number
+  djbh?: string
+  cplx?: string
+  scph?: string
+  wlbm?: string
+  wlmc?: string
+  gxbm?: string
+  gxmc?: string
+  sczyry?: string
+  lydjbh?: string
+  jyrq?: string
+  jyr?: string
+  ybl?: number | string
+  jgpd?: number | string
+  fj?: string | string[]
+  jgbh?: string
+  jgbb?: string
+  bz?: string
+  status?: string
+}
+
+export interface IpqcInspectRecordDetailRespVO {
+  id?: number | string
+  glid?: number | string
+  jyxm?: string
+  jybz?: string
+  j1?: string
+  j2?: string
+  j3?: string
+  pd?: number | string
+  jysl?: number | string
+  bhgsl?: number | string
+  bz?: string
+}
+
+export interface IpqcInspectRecordSaveReqVO extends IpqcInspectRecordMainRespVO {
+  taskId: number | string
+  details?: IpqcInspectRecordDetailRespVO[]
+}
+
 export const getIpqcTaskPage = (params: IpqcTaskPageReqVO) => {
   return request.get<PageResult<IpqcTaskRespVO[]>>({ url: '/qms/ipqc-task/page', params })
 }
@@ -33,3 +77,15 @@ export const getIpqcTaskPage = (params: IpqcTaskPageReqVO) => {
 export const startIpqcTaskProcess = (id: number | string) => {
   return request.post<string>({ url: '/qms/ipqc-task/start', data: { id } })
 }
+
+export const getIpqcInspectRecordMain = (taskId: number | string) => {
+  return request.get<IpqcInspectRecordMainRespVO>({ url: '/qms/ipqc-task/record/main', params: { taskId } })
+}
+
+export const getIpqcInspectRecordDetailList = (taskId: number | string) => {
+  return request.get<IpqcInspectRecordDetailRespVO[]>({ url: '/qms/ipqc-task/record/detail-list', params: { taskId } })
+}
+
+export const saveIpqcInspectRecord = (data: IpqcInspectRecordSaveReqVO) => {
+  return request.post<number>({ url: '/qms/ipqc-task/record/save', data })
+}

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

@@ -735,6 +735,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
       { path: 'iqc/help', name: 'IqcHelp', component: () => import('@/views/qms/Help.vue'), meta: { module: 'iqc', title: '帮助', noCache: true, hidden: true, canTo: true, activeMenu: '/qms' } },
       // IPQC 过程检验
       { path: 'ipqc/task/list', name: 'IpqcTaskList', component: () => import('@/views/qms/TaskList.vue'), meta: { module: 'ipqc', title: '过程检验任务', noCache: true, hidden: true, canTo: true, activeMenu: '/qms' } },
+      { path: 'ipqc/task/edit/:taskId', name: 'IpqcInspectRecordEdit', component: () => import('@/views/qms/ipqc/task/ProcessDetailForm.vue'), meta: { module: 'ipqc', title: '过程检验记录编辑', noCache: true, hidden: true, canTo: true, activeMenu: '/qms' } },
       { path: 'ipqc/favorites', name: 'IpqcFavorites', component: () => import('@/views/qms/Favorites.vue'), meta: { module: 'ipqc', title: '我的关注', noCache: true, hidden: true, canTo: true, activeMenu: '/qms' } },
       { path: 'ipqc/help', name: 'IpqcHelp', component: () => import('@/views/qms/Help.vue'), meta: { module: 'ipqc', title: '帮助', noCache: true, hidden: true, canTo: true, activeMenu: '/qms' } },
       // FQC 成品检验
@@ -774,6 +775,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
     meta: { hidden: true },
     children: [
       { path: 'ipqc/task/list', name: 'S6IpqcTaskList', component: () => import('@/views/qms/TaskList.vue'), meta: { module: 'ipqc', title: '过程检验任务', noCache: true, hidden: true, canTo: true, activeMenu: '/s6' } },
+      { path: 'ipqc/task/edit/:taskId', name: 'S6IpqcInspectRecordEdit', component: () => import('@/views/qms/ipqc/task/ProcessDetailForm.vue'), meta: { module: 'ipqc', title: '过程检验记录编辑', noCache: true, hidden: true, canTo: true, activeMenu: '/s6' } },
       { path: 'ipqc/qms/ipqc/task/list', name: 'S6IpqcTaskListCompat', component: () => import('@/views/qms/TaskList.vue'), meta: { module: 'ipqc', title: '过程检验任务', noCache: true, hidden: true, canTo: true, activeMenu: '/s6' } },
       { path: 'ipqc/favorites', name: 'S6IpqcFavorites', component: () => import('@/views/qms/Favorites.vue'), meta: { module: 'ipqc', title: '我的关注', noCache: true, hidden: true, canTo: true, activeMenu: '/s6' } },
       { path: 'ipqc/help', name: 'S6IpqcHelp', component: () => import('@/views/qms/Help.vue'), meta: { module: 'ipqc', title: '帮助', noCache: true, hidden: true, canTo: true, activeMenu: '/s6' } }

+ 142 - 0
yudao-ui/yudao-ui-admin-vue3/src/store/modules/qms/ipqcInspectRecord.ts

@@ -0,0 +1,142 @@
+import { defineStore } from 'pinia'
+import {
+  getIpqcInspectRecordDetailList,
+  getIpqcInspectRecordMain,
+  IpqcInspectRecordDetailRespVO,
+  IpqcInspectRecordMainRespVO,
+  IpqcInspectRecordSaveReqVO,
+  saveIpqcInspectRecord
+} from '@/api/qms/ipqc/task'
+
+type InspectRecordMain = IpqcInspectRecordMainRespVO
+
+const createEmptyMain = (): InspectRecordMain => ({
+  bdbh: '',
+  bbh: '',
+  sxrq: '',
+  djbh: '',
+  cplx: '',
+  scph: '',
+  wlbm: '',
+  wlmc: '',
+  gxbm: '',
+  gxmc: '',
+  sczyry: '',
+  lydjbh: '',
+  jyrq: '',
+  jyr: '',
+  ybl: undefined,
+  jgpd: undefined,
+  fj: '',
+  jgbh: '',
+  jgbb: '',
+  bz: '',
+  status: ''
+})
+
+const createEmptyRow = (): IpqcInspectRecordDetailRespVO => ({
+  jyxm: '',
+  jybz: '',
+  j1: '',
+  j2: '',
+  j3: '',
+  pd: undefined,
+  jysl: undefined,
+  bhgsl: undefined,
+  bz: ''
+})
+
+const unwrapResult = <T>(result: any): T => (result && result.data !== undefined ? result.data : result)
+
+export const useIpqcInspectRecordStore = defineStore('ipqcInspectRecord', {
+  state: () => ({
+    drafts: {} as Record<string, { main: InspectRecordMain; details: IpqcInspectRecordDetailRespVO[] }>
+  }),
+  actions: {
+    ensureDraft(taskId: string) {
+      if (!this.drafts[taskId]) {
+        this.drafts[taskId] = {
+          main: createEmptyMain(),
+          details: [createEmptyRow()]
+        }
+      }
+      return this.drafts[taskId]
+    },
+    setMain(taskId: string, main: Partial<InspectRecordMain>) {
+      const draft = this.ensureDraft(taskId)
+      draft.main = { ...draft.main, ...main }
+    },
+    setDetails(taskId: string, details: IpqcInspectRecordDetailRespVO[]) {
+      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([
+        getIpqcInspectRecordMain(taskId),
+        getIpqcInspectRecordDetailList(taskId)
+      ])
+      const main = unwrapResult<IpqcInspectRecordMainRespVO>(mainResult) || {}
+      const details = unwrapResult<IpqcInspectRecordDetailRespVO[]>(detailResult) || []
+      draft.main = {
+        ...createEmptyMain(),
+        ...main
+      }
+      draft.details = Array.isArray(details) && details.length ? details : [createEmptyRow()]
+      return draft
+    },
+    async save(taskId: string) {
+      const draft = this.ensureDraft(taskId)
+      const payload: IpqcInspectRecordSaveReqVO = {
+        taskId,
+        id: draft.main.id,
+        bdbh: draft.main.bdbh,
+        bbh: draft.main.bbh,
+        sxrq: draft.main.sxrq,
+        djbh: draft.main.djbh,
+        cplx: draft.main.cplx,
+        scph: draft.main.scph,
+        wlbm: draft.main.wlbm,
+        wlmc: draft.main.wlmc,
+        gxbm: draft.main.gxbm,
+        gxmc: draft.main.gxmc,
+        sczyry: draft.main.sczyry,
+        lydjbh: draft.main.lydjbh,
+        jyrq: draft.main.jyrq,
+        jyr: draft.main.jyr,
+        ybl: draft.main.ybl,
+        jgpd: draft.main.jgpd,
+        fj: Array.isArray(draft.main.fj) ? draft.main.fj.join(',') : draft.main.fj,
+        jgbh: draft.main.jgbh,
+        jgbb: draft.main.jgbb,
+        bz: draft.main.bz,
+        status: draft.main.status,
+        details: draft.details.map((item) => ({
+          id: item.id,
+          jyxm: item.jyxm,
+          jybz: item.jybz,
+          j1: item.j1,
+          j2: item.j2,
+          j3: item.j3,
+          pd: item.pd,
+          jysl: item.jysl,
+          bhgsl: item.bhgsl,
+          bz: item.bz
+        }))
+      }
+      const result = await saveIpqcInspectRecord(payload)
+      return unwrapResult<number>(result)
+    }
+  }
+})

+ 20 - 1
yudao-ui/yudao-ui-admin-vue3/src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue

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

+ 540 - 0
yudao-ui/yudao-ui-admin-vue3/src/views/qms/ipqc/task/ProcessDetailForm.vue

@@ -0,0 +1,540 @@
+<template>
+  <div class="ipqc-record-form" v-loading="loading">
+    <div v-if="isStandalone" 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 v-if="!isStandalone" shadow="never" class="form-card">
+      <template #header>
+        <div class="card-header">
+          <span>过程统计记录表</span>
+          <div class="header-actions">
+            <el-button plain size="small" @click="handleOpenEditor">打开编辑页面</el-button>
+            <el-button type="primary" size="small" @click="handleSave" :loading="saving">保存</el-button>
+          </div>
+        </div>
+      </template>
+      <div class="form-body">
+        <el-form label-width="110px">
+          <div class="form-section">
+            <div class="section-title">基本信息</div>
+            <el-row :gutter="20">
+              <el-col :span="8">
+                <el-form-item label="表单编号">
+                  <el-input v-model="mainForm.bdbh" placeholder="请输入表单编号" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item label="版本号">
+                  <el-input v-model="mainForm.bbh" placeholder="请输入版本号" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item label="生效日期">
+                  <el-date-picker
+                    v-model="mainForm.sxrq"
+                    type="date"
+                    placeholder="请选择日期"
+                    format="YYYY-MM-DD"
+                    value-format="YYYY-MM-DD"
+                    style="width: 100%"
+                  />
+                </el-form-item>
+              </el-col>
+
+              <el-col :span="8">
+                <el-form-item label="单据编号">
+                  <el-input v-model="mainForm.djbh" placeholder="自动生成" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item label="产品型号">
+                  <el-input v-model="mainForm.cplx" placeholder="请输入产品型号" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item label="生产批号">
+                  <el-input v-model="mainForm.scph" placeholder="请输入生产批号" />
+                </el-form-item>
+              </el-col>
+
+              <el-col :span="8">
+                <el-form-item label="物料编码">
+                  <el-input v-model="mainForm.wlbm" placeholder="请输入物料编码" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item label="物料名称">
+                  <el-input v-model="mainForm.wlmc" placeholder="请输入物料名称" />
+                </el-form-item>
+              </el-col>
+
+              <el-col :span="8">
+                <el-form-item label="工序编码">
+                  <el-input v-model="mainForm.gxbm" placeholder="请输入工序编码" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item label="工序名称">
+                  <el-input v-model="mainForm.gxmc" placeholder="请输入工序名称" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item label="生产作业人员">
+                  <el-input v-model="mainForm.sczyry" placeholder="请输入生产作业人员" />
+                </el-form-item>
+              </el-col>
+
+              <el-col :span="8">
+                <el-form-item label="来源单据编号">
+                  <el-input v-model="mainForm.lydjbh" placeholder="请输入来源单据编号" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item label="检验日期">
+                  <el-date-picker
+                    v-model="mainForm.jyrq"
+                    type="datetime"
+                    placeholder="请选择时间"
+                    format="YYYY-MM-DD HH:mm:ss"
+                    value-format="YYYY-MM-DD HH:mm:ss"
+                    style="width: 100%"
+                  />
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item label="检验人">
+                  <el-input v-model="mainForm.jyr" placeholder="请输入检验人" />
+                </el-form-item>
+              </el-col>
+
+              <el-col :span="8">
+                <el-form-item label="最大样本量">
+                  <el-input-number v-model="mainForm.ybl" :min="0" controls-position="right" style="width: 100%" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item label="结果判定">
+                  <el-select v-model="mainForm.jgpd" placeholder="请选择结果判定" style="width: 100%">
+                    <el-option v-for="option in resultOptions" :key="option.value" :label="option.label" :value="option.value" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+
+              <el-col :span="8">
+                <el-form-item label="附件">
+                  <UploadFile v-model="mainForm.fj" :limit="1" :file-type="['pdf','doc','docx','xls','xlsx','png','jpg']" :is-show-tip="false" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item label="检规编号">
+                  <el-input v-model="mainForm.jgbh" placeholder="请输入检规编号" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item label="检规版本">
+                  <el-input v-model="mainForm.jgbb" placeholder="请输入检规版本" />
+                </el-form-item>
+              </el-col>
+
+              <el-col :span="24">
+                <el-form-item label="备注">
+                  <el-input v-model="mainForm.bz" type="textarea" :rows="3" placeholder="请输入备注" />
+                </el-form-item>
+              </el-col>
+            </el-row>
+          </div>
+
+          <div class="form-section">
+            <div class="section-title">
+              明细
+              <div class="section-actions">
+                <el-button type="primary" size="small" @click="handleAddRow">新增行</el-button>
+              </div>
+            </div>
+            <el-table :data="detailRows" border>
+              <el-table-column label="检验项目" width="200">
+                <template #default="{ row }">
+                  <el-input v-model="row.jyxm" placeholder="请输入检验项目" />
+                </template>
+              </el-table-column>
+              <el-table-column label="检验标准" width="220">
+                <template #default="{ row }">
+                  <el-input v-model="row.jybz" placeholder="请输入检验标准" />
+                </template>
+              </el-table-column>
+              <el-table-column label="样本1" width="140">
+                <template #default="{ row }">
+                  <el-input v-model="row.j1" placeholder="请输入样本1" />
+                </template>
+              </el-table-column>
+              <el-table-column label="样本2" width="140">
+                <template #default="{ row }">
+                  <el-input v-model="row.j2" placeholder="请输入样本2" />
+                </template>
+              </el-table-column>
+              <el-table-column label="样本3" width="140">
+                <template #default="{ row }">
+                  <el-input v-model="row.j3" placeholder="请输入样本3" />
+                </template>
+              </el-table-column>
+              <el-table-column label="判定" width="120">
+                <template #default="{ row }">
+                  <el-select v-model="row.pd" placeholder="选择" style="width: 100%">
+                    <el-option v-for="option in resultOptions" :key="option.value" :label="option.label" :value="option.value" />
+                  </el-select>
+                </template>
+              </el-table-column>
+              <el-table-column label="检验数量" width="140">
+                <template #default="{ row }">
+                  <el-input-number v-model="row.jysl" :min="0" controls-position="right" style="width: 100%" />
+                </template>
+              </el-table-column>
+              <el-table-column label="不合格数" width="140">
+                <template #default="{ row }">
+                  <el-input-number v-model="row.bhgsl" :min="0" controls-position="right" style="width: 100%" />
+                </template>
+              </el-table-column>
+              <el-table-column label="备注" min-width="200">
+                <template #default="{ row }">
+                  <el-input v-model="row.bz" placeholder="请输入备注" />
+                </template>
+              </el-table-column>
+              <el-table-column label="操作" width="90">
+                <template #default="{ $index }">
+                  <el-button link type="danger" size="small" @click="handleDeleteRow($index)">删除</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+          </div>
+        </el-form>
+      </div>
+    </el-card>
+
+    <div v-else class="form-body">
+      <el-form label-width="110px">
+        <div class="form-section">
+          <div class="section-title">基本信息</div>
+          <el-row :gutter="20">
+            <el-col :span="8">
+              <el-form-item label="表单编号">
+                <el-input v-model="mainForm.bdbh" placeholder="请输入表单编号" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="版本号">
+                <el-input v-model="mainForm.bbh" placeholder="请输入版本号" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="生效日期">
+                <el-date-picker
+                  v-model="mainForm.sxrq"
+                  type="date"
+                  placeholder="请选择日期"
+                  format="YYYY-MM-DD"
+                  value-format="YYYY-MM-DD"
+                  style="width: 100%"
+                />
+              </el-form-item>
+            </el-col>
+
+            <el-col :span="8">
+              <el-form-item label="单据编号">
+                <el-input v-model="mainForm.djbh" placeholder="自动生成" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="产品型号">
+                <el-input v-model="mainForm.cplx" placeholder="请输入产品型号" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="生产批号">
+                <el-input v-model="mainForm.scph" placeholder="请输入生产批号" />
+              </el-form-item>
+            </el-col>
+
+            <el-col :span="8">
+              <el-form-item label="物料编码">
+                <el-input v-model="mainForm.wlbm" placeholder="请输入物料编码" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="物料名称">
+                <el-input v-model="mainForm.wlmc" placeholder="请输入物料名称" />
+              </el-form-item>
+            </el-col>
+
+            <el-col :span="8">
+              <el-form-item label="工序编码">
+                <el-input v-model="mainForm.gxbm" placeholder="请输入工序编码" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="工序名称">
+                <el-input v-model="mainForm.gxmc" placeholder="请输入工序名称" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="生产作业人员">
+                <el-input v-model="mainForm.sczyry" placeholder="请输入生产作业人员" />
+              </el-form-item>
+            </el-col>
+
+            <el-col :span="8">
+              <el-form-item label="来源单据编号">
+                <el-input v-model="mainForm.lydjbh" placeholder="请输入来源单据编号" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="检验日期">
+                <el-date-picker
+                  v-model="mainForm.jyrq"
+                  type="datetime"
+                  placeholder="请选择时间"
+                  format="YYYY-MM-DD HH:mm:ss"
+                  value-format="YYYY-MM-DD HH:mm:ss"
+                  style="width: 100%"
+                />
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="检验人">
+                <el-input v-model="mainForm.jyr" placeholder="请输入检验人" />
+              </el-form-item>
+            </el-col>
+
+            <el-col :span="8">
+              <el-form-item label="最大样本量">
+                <el-input-number v-model="mainForm.ybl" :min="0" controls-position="right" style="width: 100%" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="结果判定">
+                <el-select v-model="mainForm.jgpd" placeholder="请选择结果判定" style="width: 100%">
+                  <el-option v-for="option in resultOptions" :key="option.value" :label="option.label" :value="option.value" />
+                </el-select>
+              </el-form-item>
+            </el-col>
+
+            <el-col :span="8">
+              <el-form-item label="附件">
+                <UploadFile v-model="mainForm.fj" :limit="1" :file-type="['pdf','doc','docx','xls','xlsx','png','jpg']" :is-show-tip="false" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="检规编号">
+                <el-input v-model="mainForm.jgbh" placeholder="请输入检规编号" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="检规版本">
+                <el-input v-model="mainForm.jgbb" placeholder="请输入检规版本" />
+              </el-form-item>
+            </el-col>
+
+            <el-col :span="24">
+              <el-form-item label="备注">
+                <el-input v-model="mainForm.bz" type="textarea" :rows="3" placeholder="请输入备注" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </div>
+
+        <div class="form-section">
+          <div class="section-title">
+            明细
+            <div class="section-actions">
+              <el-button type="primary" size="small" @click="handleAddRow">新增行</el-button>
+            </div>
+          </div>
+          <el-table :data="detailRows" border>
+            <el-table-column label="检验项目" width="200">
+              <template #default="{ row }">
+                <el-input v-model="row.jyxm" placeholder="请输入检验项目" />
+              </template>
+            </el-table-column>
+            <el-table-column label="检验标准" width="220">
+              <template #default="{ row }">
+                <el-input v-model="row.jybz" placeholder="请输入检验标准" />
+              </template>
+            </el-table-column>
+            <el-table-column label="样本1" width="140">
+              <template #default="{ row }">
+                <el-input v-model="row.j1" placeholder="请输入样本1" />
+              </template>
+            </el-table-column>
+            <el-table-column label="样本2" width="140">
+              <template #default="{ row }">
+                <el-input v-model="row.j2" placeholder="请输入样本2" />
+              </template>
+            </el-table-column>
+            <el-table-column label="样本3" width="140">
+              <template #default="{ row }">
+                <el-input v-model="row.j3" placeholder="请输入样本3" />
+              </template>
+            </el-table-column>
+            <el-table-column label="判定" width="120">
+              <template #default="{ row }">
+                <el-select v-model="row.pd" placeholder="选择" style="width: 100%">
+                  <el-option v-for="option in resultOptions" :key="option.value" :label="option.label" :value="option.value" />
+                </el-select>
+              </template>
+            </el-table-column>
+            <el-table-column label="检验数量" width="140">
+              <template #default="{ row }">
+                <el-input-number v-model="row.jysl" :min="0" controls-position="right" style="width: 100%" />
+              </template>
+            </el-table-column>
+            <el-table-column label="不合格数" width="140">
+              <template #default="{ row }">
+                <el-input-number v-model="row.bhgsl" :min="0" controls-position="right" style="width: 100%" />
+              </template>
+            </el-table-column>
+            <el-table-column label="备注" min-width="200">
+              <template #default="{ row }">
+                <el-input v-model="row.bz" placeholder="请输入备注" />
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" width="90">
+              <template #default="{ $index }">
+                <el-button link type="danger" size="small" @click="handleDeleteRow($index)">删除</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+      </el-form>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+defineOptions({ name: 'IpqcProcessDetailForm' })
+
+import { computed, ref, watch } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { useIpqcInspectRecordStore } from '@/store/modules/qms/ipqcInspectRecord'
+import { UploadFile } from '@/components/UploadFile'
+
+const props = defineProps<{ id?: string }>()
+const route = useRoute()
+const router = useRouter()
+const recordStore = useIpqcInspectRecordStore()
+
+const loading = ref(false)
+const saving = ref(false)
+
+const taskId = computed(() => props.id || String(route.params.taskId || ''))
+const isStandalone = computed(() => !props.id)
+
+const mainForm = computed(() => {
+  if (!taskId.value) return recordStore.ensureDraft('temp').main
+  return recordStore.ensureDraft(taskId.value).main
+})
+
+const detailRows = computed(() => {
+  if (!taskId.value) return []
+  return recordStore.ensureDraft(taskId.value).details
+})
+
+const fetchData = async () => {
+  if (!taskId.value) return
+  loading.value = true
+  try {
+    await recordStore.load(taskId.value)
+  } finally {
+    loading.value = false
+  }
+}
+
+const handleAddRow = () => {
+  if (!taskId.value) return
+  recordStore.addDetailRow(taskId.value)
+}
+
+const handleDeleteRow = (index: number) => {
+  if (!taskId.value) return
+  recordStore.deleteDetailRow(taskId.value, index)
+}
+
+const handleSave = async () => {
+  if (!taskId.value) return
+  saving.value = true
+  try {
+    await recordStore.save(taskId.value)
+    ElMessage.success('保存成功')
+  } catch {
+    ElMessage.error('保存失败')
+  } finally {
+    saving.value = false
+  }
+}
+
+const handleBack = () => { router.back() }
+
+const handleOpenEditor = () => {
+  if (!taskId.value) return
+  const currentPath = router.currentRoute.value.path || ''
+  const basePath = currentPath.startsWith('/s6') ? '/s6/ipqc/task/edit' : '/qms/ipqc/task/edit'
+  router.push(`${basePath}/${taskId.value}`)
+}
+
+const resultOptions = [
+  { label: '合格', value: 1 },
+  { label: '不合格', value: 0 },
+  { label: '待判定', value: 2 }
+]
+
+watch(taskId, () => { fetchData() }, { immediate: true })
+</script>
+
+<style scoped lang="scss">
+.ipqc-record-form {
+  padding: 4px 0;
+}
+.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;
+  }
+}
+.form-card {
+  margin-bottom: 16px;
+}
+.card-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  font-weight: 600;
+}
+.form-body {
+  padding: 8px 4px 4px;
+}
+.section-title {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-weight: 600;
+  margin-bottom: 12px;
+  .section-actions {
+    margin-left: auto;
+  }
+}
+</style>