Эх сурвалжийг харах

fix(不良品处置): 合格数与不合格数以工单接口为准,补充回流工序与已处置行禁选

启用 refluxTask 按工单编码取回流工序;已处置行不再允许勾选批量处置/删除;详情弹窗复用实例时重置 tab 与状态,新增 ledgerQuantity 送检总数,结论按合格/不合格数判断且不再用 poList 反推覆盖工单返回值;同时切换开发代理地址
lihaiting 3 өдөр өмнө
parent
commit
75bbeb9b13

+ 12 - 0
src/api/bpm/components/qualityReportApproval/qualityReportApproval.js

@@ -75,6 +75,18 @@ export async function getById(id) {
   }
   return Promise.reject(new Error(res.data.message));
 }
+
+/**
+ * 获取回流工序(处置方式选「返工 / 返修」时选择工序)
+ * 与不合格品台账列表页同一接口:入参工单编码
+ */
+export async function refluxTask(data) {
+  const res = await request.post('/mes/workorder/refluxTask', data);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
 /**
  * 不合格品台账列表
  */

+ 172 - 29
src/views/bpm/handleTask/components/qualityReportApproval/detailDialog.vue

@@ -40,7 +40,7 @@
         ref="unqualifiedProductRef"
         v-show="activeComp == 'unqualifiedList'"
         @setQualifiedNumber="setQualifiedNumber"
-        :total="basicInfoData.total || form.total"
+        :total="ledgerQuantity || basicInfoData.total || form.total || 0"
         @setLossNumber="setLossNumber"
         :businessType="businessType"
       ></unqualifiedProduct>
@@ -228,7 +228,12 @@
         template: '',
         fileUrl: '',
         // 不良品处置台账未关联质检工单(手工新增等):此时不加载检测报告,只渲染不合格品处置单
-        isUnqualifiedDisposalNoOrder: false
+        isUnqualifiedDisposalNoOrder: false,
+        // 不良品处置台账已关联质检工单:结论/合格数/不合格数全部以工单详情接口为准,
+        // 明细的处置/删除/加行不得再用 poList 反推覆盖(前端不做兜底、不自己算数)
+        disposalHasWorkOrder: false,
+        // 不良品台账的送检总数:明细合格/不合格计算以它为准,不能用质检工单的 total
+        ledgerQuantity: 0
       };
     },
     mounted() {
@@ -236,6 +241,16 @@
     },
     methods: {
       async open() {
+        // 已办详情复用同一组件实例(不像待办每次新建),open 前重置 tab,
+        // 避免上一条记录的「不合格品处置单」tab 残留或多次 unshift 重复
+        this.tabOptions = [
+          { key: 'main', name: '报告' },
+          { key: 'unqualifiedList', name: '不良品台账' }
+        ];
+        this.activeComp = 'main';
+        this.ledgerQuantity = 0;
+        this.disposalHasWorkOrder = false;
+        this.isUnqualifiedDisposalNoOrder = false;
         let api = getDetailById;
 
         let detail = null;
@@ -252,12 +267,32 @@
           api = getCertifiById;
         }
         if (this.businessType?.includes('不良品处置')) {
-          const data = await getById(this.businessId);
-          const enterprisePageData = await enterprisePage({
-            pageNum: 1,
-            size: 200
-          });
+          let data = null;
+          try {
+            data = await getById(this.businessId);
+          } catch (error) {
+            // 台账详情拿不到时明确提示,不留一个全空白页面
+            console.error('不良品台账详情获取失败', this.businessId, error);
+            this.$message.error(
+              `不良品处置单数据获取失败:${error?.message || error}`
+            );
+            return;
+          }
+          let enterprisePageData = null;
+          try {
+            enterprisePageData = await enterprisePage({
+              pageNum: 1,
+              size: 200
+            });
+          } catch (error) {
+            // 只影响单位名称回填,不中断处置单/台账明细渲染
+            console.warn('企业信息获取失败,单位名称留空', error);
+          }
+          // 台账数量 = 本次送检总数,处置明细的合格/不合格计算以它为准
+          this.ledgerQuantity = data.quantity || 0;
           if (data.qualityWorkOrderId) {
+            // 已关联质检工单:这几个数字的唯一来源就是工单详情接口
+            this.disposalHasWorkOrder = true;
             detail = await api(data.qualityWorkOrderId);
             detail.data.templateList?.forEach((item) => {
               if (item.inspectImage) {
@@ -298,13 +333,37 @@
           });
 
           this.activeComp = 'unqualifiedHtml';
-          const depData = await getGroupById(this.deptId);
-          console.log(depData, 'depData');
+          // 部门ID来源:待办/审批页由流程上下文传入(this.deptId);
+          // 已办查看页父级只传 businessId,此时只能用台账接口返回的 deptId。
+          // 两者都没有时不要发请求——/main/group/getGroupById/undefined 会 404 抛错,
+          // 一旦抛出整个 open() 中断,处置单与台账明细都渲染不出来(表现为整页空白)
+          const deptId = this.deptId || data.deptId || '';
+          let deptHeadPerson = '';
+          if (deptId) {
+            try {
+              const depData = await getGroupById(deptId);
+              deptHeadPerson = depData?.data?.managerFirst?.[0]?.name || '';
+            } catch (error) {
+              // 部门信息取不到不影响处置单回填:部门负责人字段留空并提示
+              console.warn('部门信息获取失败,部门负责人留空', deptId, error);
+            }
+          }
           this.$nextTick(() => {
-            if (data.contentText) {
-              data.contentText = JSON.parse(data.contentText);
+            // contentText 是 JSON 字符串,空值/脏数据直接 JSON.parse 会抛错导致整页不回填
+            if (typeof data.contentText === 'string' && data.contentText) {
+              try {
+                data.contentText = JSON.parse(data.contentText);
+              } catch (error) {
+                console.error('台账 contentText 解析失败,处置单改用台账字段回填', error);
+                data.contentText = null;
+              }
+            }
+            const htmlRef = this.$refs.unqualifiedHtmlRef;
+            if (!htmlRef) {
+              console.error('不合格品处置单组件未渲染,数据无法回填');
+              return;
             }
-            this.$refs.unqualifiedHtmlRef.init(
+            htmlRef.init(
               data.contentText?.basicInfoData
                 ? data.contentText.basicInfoData
                 : {
@@ -320,7 +379,7 @@
                     feedbackPerson: this.fromUser,
                     feedbackDate: this.createTime,
                     deptHeadDate: this.createTime,
-                    deptHeadPerson: depData?.data?.managerFirst[0]?.name,
+                    deptHeadPerson,
                     sampleMeasureUnit: data.measureUnit,
                     vehiclePhotoUrl: vehiclePhotoUrl?.map(
                       (item) => item.storagePath[0]?.storePath
@@ -344,9 +403,13 @@
 
         if (detail.data.reportTemplateJson?.template) {
           this.template = detail.data.reportTemplateJson.template;
-          console.log(detail.data, 'detail.data');
-          this.inspectionItems = detail.data.reportTemplateJson.inspectionItems;
-          this.basicInfoData = detail.data.reportTemplateJson?.basicInfoData;
+          this.inspectionItems =
+            detail.data.reportTemplateJson.inspectionItems || [];
+          // 报告模板里的 basicInfoData 会整体替换页面绑定的对象,
+          // 因此结论/合格数在下面(模板赋值之后)再统一回填
+          if (detail.data.reportTemplateJson.basicInfoData) {
+            this.basicInfoData = detail.data.reportTemplateJson.basicInfoData;
+          }
           this.$nextTick(() => {
             this.$refs.templateRef.init(
               this.basicInfoData,
@@ -363,8 +426,51 @@
 
           // 台账未关联质检工单时没有检测报告可打印,跳过,避免用台账ID请求报告页
           if (!this.isUnqualifiedDisposalNoOrder) {
-            let url = await getJmPrintViewUrl('qmsqualityinspectionprint');
-            this.fileUrl = `${url}?token=${getToken()}&id=${this.businessId}`;
+            try {
+              const url = await getJmPrintViewUrl('qmsqualityinspectionprint');
+              this.fileUrl = `${url}?token=${getToken()}&id=${this.businessId}`;
+            } catch (error) {
+              // 只影响报告页预览,不影响处置单与台账明细
+              console.error('检测报告打印地址获取失败,报告预览留空', error);
+            }
+          }
+        }
+
+        // 不良品处置:结论 / 合格数 / 不合格数唯一来源是「质检工单详情」,
+        // 台账 getById 从不返回这几个字段。必须放在报告模板覆盖 basicInfoData 之后,
+        // 否则会被模板里的 undefined 冲掉(表现就是全部显示 0)。
+        // 台账没有关联质检工单(手工新增)时没有数据来源,保持空即可,不拿明细反推。
+        if (this.businessType?.includes('不良品处置')) {
+          const src = detail.data || {};
+          const hasQualified =
+            src.qualifiedNumber !== undefined && src.qualifiedNumber !== null;
+          const hasNoQualified =
+            src.noQualifiedNumber !== undefined &&
+            src.noQualifiedNumber !== null;
+          this.$set(
+            this.basicInfoData,
+            'qualityResults',
+            src.qualityResults ?? ''
+          );
+          this.$set(
+            this.basicInfoData,
+            'qualifiedNumber',
+            hasQualified ? src.qualifiedNumber : ''
+          );
+          this.$set(
+            this.basicInfoData,
+            'noQualifiedNumber',
+            hasNoQualified ? src.noQualifiedNumber : ''
+          );
+          if (!hasQualified && !hasNoQualified) {
+            // 只在控制台留痕,页面不打扰用户
+            console.warn(
+              '[不良品处置] 合格数/不合格数无数据来源:台账未关联质检工单,或工单未返回 qualifiedNumber / noQualifiedNumber',
+              {
+                businessId: this.businessId,
+                qualityWorkOrderId: src.qualityWorkOrderId
+              }
+            );
           }
         }
       },
@@ -425,20 +531,57 @@
       },
 
       setQualifiedNumber(data) {
-        this.basicInfoData.qualifiedNumber = data.qualifiedNumber;
-        this.basicInfoData.noQualifiedNumber = data.noQualifiedNumber;
-        if (!data.noQualifiedNumber) {
-          if (data.isQualifiedNumber) {
-            this.basicInfoData.qualityResults = 1;
-          } else {
-            this.basicInfoData.qualityResults = 4;
-          }
+        // 不良品处置且台账已关联质检工单:结论 / 合格数 / 不合格数 唯一来源是工单详情接口,
+        // 明细的处置 / 删除 / 加行只影响明细本身,绝不能拿 poList 反推去覆盖工单返回值。
+        // 例:台账只有 1 件待处置、工单却是 90 件检验,按台账总数反推会把 89 冲成 0。
+        if (this.businessType?.includes('不良品处置') && this.disposalHasWorkOrder) {
+          return;
         }
-        if (!data.qualifiedNumber) {
-          this.basicInfoData.qualityResults = 2;
+        // 合格数 / 不合格数一律以台账接口的 qualifiedNumber / noQualifiedNumber 为准;
+        // 只有这两类数在页面上被用户改动(处置/删除/切换结论)时才会重新计算后回传
+        if (data.qualifiedNumber !== undefined && data.qualifiedNumber !== null) {
+          this.$set(
+            this.basicInfoData,
+            'qualifiedNumber',
+            data.qualifiedNumber
+          );
+        }
+        if (
+          data.noQualifiedNumber !== undefined &&
+          data.noQualifiedNumber !== null
+        ) {
+          this.$set(
+            this.basicInfoData,
+            'noQualifiedNumber',
+            data.noQualifiedNumber
+          );
+        }
+        // 初次加载只回显数量,不自动改写台账已保存的「结论」
+        if (data.fromApi) {
+          return;
         }
-        if (data.qualifiedNumber && data.noQualifiedNumber) {
+        // 用户手动选结论:结论以用户选择为准,只回填数量
+        if (data.fromSelect) {
+          return;
+        }
+        // 数量还没算出来(台账总数为 0 等)时不要改写结论,否则会把用户的选择冲掉
+        const qualified = Number(data.qualifiedNumber);
+        const noQualified = Number(data.noQualifiedNumber);
+        if (
+          !Number.isFinite(qualified) ||
+          !Number.isFinite(noQualified) ||
+          qualified + noQualified <= 0
+        ) {
+          return;
+        }
+        if (qualified > 0 && noQualified > 0) {
           this.basicInfoData.qualityResults = 3;
+        } else if (qualified > 0) {
+          // 明细全部按「让步接收」处置时结论应为整单让步接收
+          this.basicInfoData.qualityResults =
+            data.isQualifiedNumber === false ? 4 : 1;
+        } else {
+          this.basicInfoData.qualityResults = 2;
         }
       },
       async getTableValue() {

+ 89 - 25
src/views/bpm/handleTask/components/qualityReportApproval/unqualifiedProduct.vue

@@ -395,6 +395,7 @@
     queryQualitySamplContent,
     queryQualityInventory,
     getById,
+    refluxTask,
     getBadnameList,
     getBadtypeList,
     getReasontypeList
@@ -584,7 +585,9 @@
             type: 'selection',
             columnKey: 'selection',
             reserveSelection: true,
-            align: 'center'
+            align: 'center',
+            // 已处置的行只做展示,不允许再被勾选去「批量处置 / 批量删除」
+            selectable: (row) => row.disposalStatus != 2
           },
           {
             columnKey: 'index',
@@ -731,7 +734,8 @@
             showOverflowTooltip: true,
             filters: [
               { value: 0, text: '待处置' },
-              { value: 1, text: '处置中' }
+              { value: 1, text: '处置中' },
+              { value: 2, text: '处置完成' }
             ],
             filterMethod: this.disposalStatusFilter,
             formatter: (row, column, cellValue) => {
@@ -769,16 +773,16 @@
         return { 0: '待处置', 1: '处置中', 2: '处置完成' };
       },
       // 兜底去重,防止 uniqueKey 极端情况下仍重复
+      // 注意:不再过滤掉「已处置」的行(disposalStatus == 2),
+      // 台账明细要展示全部样品(含已处置),否则审批人看到的条数和实际不合格数对不上
       displayPoList() {
         const seen = new Set();
-        return this.poList
-          .filter((item) => item.disposalStatus != 2)
-          .filter((item) => {
-            const key = item.uniqueKey || item.id;
-            if (!key || seen.has(key)) return false;
-            seen.add(key);
-            return true;
-          });
+        return this.poList.filter((item) => {
+          const key = item.uniqueKey || item.id;
+          if (!key || seen.has(key)) return false;
+          seen.add(key);
+          return true;
+        });
       }
     },
     created() {
@@ -840,7 +844,9 @@
           await this.datasource();
         }
       },
-      setQualifiedNumber() {
+      // fromSelect:结论是用户手动选的,父组件只回填数量、不得按数量反推结论,
+      // 否则「整单合格 / 整单让步接收 / 部分合格」会被下面的推导逻辑立刻改回「整单不合格」
+      setQualifiedNumber(fromSelect = false) {
         let isQualifiedNumber = true;
         let noQualifiedNumber = this.poList
           .filter((item) => ![5, 6, 7].includes(item.disposeType))
@@ -857,9 +863,19 @@
         this.$emit('setQualifiedNumber', {
           noQualifiedNumber,
           qualifiedNumber: this.availableTotal - noQualifiedNumber,
-          isQualifiedNumber
+          isQualifiedNumber,
+          fromSelect
         });
       },
+      // 切换结论时重建明细的来源:质检工单场景用「待处置库存」;
+      // 手工新增台账没有工单库存(qualityWorkerId 为空),用台账自身未处置的行,
+      // 否则选「整单不合格 / 整单让步接收」会把明细清空
+      baseDisposeList() {
+        if (this.qualityWorkerId) {
+          return this.inventoryList;
+        }
+        return this.oldList.filter((item) => item.disposalStatus != 2);
+      },
       selectResult(qualityResults) {
         if (qualityResults == 1) {
           this.poList = JSON.parse(JSON.stringify(this.oldList));
@@ -874,7 +890,7 @@
         }
         if (qualityResults == 4) {
           this.poList = [
-            ...JSON.parse(JSON.stringify(this.inventoryList)),
+            ...JSON.parse(JSON.stringify(this.baseDisposeList())),
             ...JSON.parse(
               JSON.stringify(
                 this.oldList.filter((item) => item.disposalStatus == 2)
@@ -892,7 +908,7 @@
         }
         if (qualityResults == 2) {
           this.poList = [
-            ...JSON.parse(JSON.stringify(this.inventoryList)),
+            ...JSON.parse(JSON.stringify(this.baseDisposeList())),
             ...JSON.parse(
               JSON.stringify(
                 this.oldList.filter((item) => item.disposalStatus == 2)
@@ -911,7 +927,8 @@
           });
         }
         this.qualityResults = qualityResults;
-        this.setQualifiedNumber();
+        // 用户手动选结论:只让父组件刷新数量,结论保持用户选的值
+        this.setQualifiedNumber(true);
       },
       disposeTypeChange() {
         if (this.disposeForm.disposeType == 6) {
@@ -980,21 +997,68 @@
       // /* 表格数据源 */
       async datasource() {
         const arr = await getById(this.id);
-        this.oldList = JSON.parse(JSON.stringify(arr.poList));
-        this.poList = arr.poList.map((item) => {
+        this.oldList = JSON.parse(JSON.stringify(arr.poList || []));
+        this.poList = (arr.poList || []).map((item) => {
           this.ensureUniqueKey(item);
           return item;
         });
-        this.setQualifiedNumber();
+
+        // 回流工序:处置方式选「返工 / 返修」时要选工序,和台账列表页取的是同一份数据。
+        // 台账记录里的工单编码就是 sourceCode,拿它调 /mes/workorder/refluxTask。
+        if (arr.sourceCode) {
+          this.workOrderCode = arr.sourceCode;
+          await this.getRefluxTask();
+        } else {
+          console.warn(
+            '[不良品台账] 台账记录未返回工单编码(sourceCode),处置方式选「返工 / 返修」时无法选择回流工序',
+            arr
+          );
+        }
+        // 合格数 / 不合格数的数据源是「质检工单详情」接口(父组件 detailDialog 用 getDetailById 回填);
+        // 台账 getById 从不返回这两个字段(内外网实测一致),
+        // 用 poList 反推会把「让步接收(5)/留样(6)/消耗(7)」这些处置行排除掉,数也对不上。
+        // 其他业务类型(检测报告单等)保持原有计算逻辑不变。
+        const hasApiNumber =
+          arr.qualifiedNumber !== undefined &&
+          arr.qualifiedNumber !== null &&
+          arr.noQualifiedNumber !== undefined &&
+          arr.noQualifiedNumber !== null;
+        if (this.businessType?.includes('不良品处置')) {
+          if (hasApiNumber) {
+            this.$emit('setQualifiedNumber', {
+              qualifiedNumber: arr.qualifiedNumber,
+              noQualifiedNumber: arr.noQualifiedNumber,
+              // 标记来自接口:父组件只更新数量,不改写台账已保存的「结论」
+              fromApi: true
+            });
+          } else {
+            // 不做兜底:台账接口没给就保持父组件用工单数据回填的值,别拿 poList 反推
+            console.warn(
+              '[不良品台账] 台账 getById 未返回 qualifiedNumber / noQualifiedNumber,合格数/不合格数以质检工单详情回填为准',
+              arr.unqualifiedProductsCode
+            );
+          }
+        } else {
+          this.setQualifiedNumber();
+        }
       },
 
-      //   async getRefluxTask() {
-      //     await refluxTask({
-      //       workOrderCode: this.workOrderCode
-      //     }).then((res) => {
-      //       this.refluxTaskList = res;
-      //     });
-      //   },
+      /**
+       * 回流工序:处置方式选「返工 / 返修」时,工序下拉的数据源
+       * 与不合格品台账列表页一致,用工单编码查询
+       */
+      async getRefluxTask() {
+        if (!this.workOrderCode) return;
+        try {
+          const res = await refluxTask({
+            workOrderCode: this.workOrderCode
+          });
+          this.refluxTaskList = res || [];
+        } catch (error) {
+          this.refluxTaskList = [];
+          this.$message.error(error.message || '回流工序加载失败');
+        }
+      },
 
       remove() {
         let ids = this.selection.map((item) => item.id);

+ 3 - 3
vue.config.js

@@ -38,15 +38,15 @@ module.exports = {
         // target: 'http://124.71.68.31:50001', // 测试环境
         // target: 'http://124.71.68.31:50001',
         // target: 'http://192.168.1.105:18086',
-        target: 'http://localhost:18086',
+        // target: 'http://localhost:18086',
         // target: 'http://192.168.1.116:18086',
         // target: 'http://192.168.1.3:18086',
         // target: 'http://192.168.1.251:18186',
         // target: 'http://192.168.1.251:18086',
         // target: 'http://192.168.1.3:18086',
-        // target: 'http://aiot.zoomwin.com.cn:51007/api',
+        target: 'https://aiot.zoomwin.com.cn:51001/api',
         // target: 'http://192.168.1.3:18086',
-        // target: 'http://192.168.1.147:18086', // 陈兵
+        // target: 'http://192.168.1.9:18086', // 陈兵
         // target: 'http://192.168.1.116:18086', // 赵沙金
         // target: 'http://192.168.1.113:18086', // 郭丹
         // target: 'http://192.168.1.102:18086', // 吴志豪