فهرست منبع

Merge branch 'dev' of http://110.41.163.243:9980/kd-aiot/kd-aiot-frontend-wt into dev

tanzx 5 روز پیش
والد
کامیت
d780e14acd

+ 11 - 0
src/api/bpm/components/purchasingManage/purchasePlanManage.js

@@ -63,3 +63,14 @@ export async function cancel(data) {
   }
   return Promise.reject(new Error(res.data.message));
 }
+/**
+ * 获取审批采购部门
+ * 返回部门对象(id / name / parentId / type);无匹配部门时返回空对象
+ */
+export async function getApprovalPurchaseDept() {
+  const res = await request.get(`/eom/purchaseplan/getApprovalPurchaseDept`);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}

+ 22 - 2
src/components/CommomSelect/dept-select.vue

@@ -30,21 +30,41 @@
     },
     data () {
       return {
-        treeData: []
+        treeData: [],
+        /** 组织原始平铺数据:用于按 id 校验节点是否存在 */
+        orgList: [],
+        /** 首屏组织数据加载 promise */
+        dataLoading: null
       };
     },
     created () {
-      this.getData();
+      this.dataLoading = this.getData();
     },
     methods: {
       async getData (params = {}) {
         const data = await listOrganizations(params);
+        this.orgList = data || [];
         this.treeData = this.$util.toTreeData({
           data: data || [],
           idField: 'id',
           parentIdField: 'parentId'
         });
       },
+      /**
+       * 判断部门 id 是否存在于组织架构中(等待首屏数据加载完成)
+       * 供父组件在「程序化默认值」场景下校验,避免传入不存在的部门 id
+       */
+      async hasNode (id) {
+        if (id === undefined || id === null || id === '') return false;
+        try {
+          if (this.dataLoading) await this.dataLoading;
+        } catch (error) {
+          return false;
+        }
+        return this.orgList.some(
+          item => item && String(item.id) === String(id)
+        );
+      },
       /* 更新选中数据 */
       updateValue (value) {
         this.$emit('input', value);

+ 60 - 6
src/views/bpm/handleTask/components/purchasePlanManage/submit.vue

@@ -8,7 +8,7 @@
         style="margin-bottom: 20px"
       >
         <deptSelect
-        
+          ref="deptSelectRef"
           v-model="form.useDeptId"
           @changeGroup="searchDeptNodeClick"
         />
@@ -127,7 +127,8 @@
   import {
     assign,
     cancel,
-    UpdateInformation
+    UpdateInformation,
+    getApprovalPurchaseDept
   } from '@/api/bpm/components/purchasingManage/purchasePlanManage';
   import deptSelect from '@/components/CommomSelect/dept-select.vue';
   import { getUserPage } from '@/api/system/organization';
@@ -170,7 +171,42 @@
     created() {
       this.getAllUsers()
     },
+    mounted() {
+      // 需要部门下拉已挂载(ref 可用)后才能校验默认值是否合法
+      this.loadApprovalPurchaseDept()
+    },
     methods: {
+      /**
+       * 主管指派节点:接口返回审批采购部门时,自动预选该部门并带出其下的采购员。
+       * 接口返回空对象、或返回的部门不在组织架构中 → 不预选,保持手选,
+       * 避免把不存在的部门 id 传给采购员查询接口、导致通过时取不到采购员。
+       */
+      async loadApprovalPurchaseDept() {
+        if (this.taskDefinitionKey !== 'deptLeaderAssign') return;
+        try {
+          const dept = await getApprovalPurchaseDept();
+          if (!dept || !Object.keys(dept).length) return;
+          // 部门主键,与 /main/group/getGroupList 节点 id(dept-select 的 value-key)对应
+          const deptId = dept.id;
+          if (deptId === undefined || deptId === null || deptId === '') return;
+          const deptSelect = this.$refs.deptSelectRef;
+          const exists = deptSelect ? await deptSelect.hasNode(deptId) : false;
+          if (!exists) {
+            this.$message.warning(
+              `审批采购部门「${dept.name || deptId}」不在组织架构中,请手动选择采购部门`
+            );
+            return;
+          }
+          this.form.useDeptId = deptId;
+          // 部门确定后按该部门筛选采购员,与手动选择部门的行为保持一致
+          this.searchDeptNodeClick(deptId);
+        } catch (error) {
+          console.warn(
+            '[采购计划审批] 获取审批采购部门失败:',
+            error.message || error
+          );
+        }
+      },
       getAllUsers() {
         this.userOptions = [];
         listAllUserBind().then((data) => {
@@ -255,6 +291,10 @@
         console.log(this.form)
         //主管指派采购员
         if (this.taskDefinitionKey === 'deptLeaderAssign') {
+          if (!this.form.useDeptId) {
+            this.$message.warning(`请选择采购部门!`);
+            return;
+          }
           if (!this.form.userId) {
             this.$message.warning(`请选择采购员!`);
             return;
@@ -265,7 +305,9 @@
             assignList: this.form.assignList,
             id: this.taskId,
             reason: this.form.reason,
-            businessId: this.businessId
+            businessId: this.businessId,
+            // 采购部门:取界面上当前选中的部门(可能是接口默认带出的,也可能是手选的)
+            useDeptId: this.form.useDeptId
           });
         }
         // //采购员核对
@@ -284,6 +326,10 @@
 
         if(this.taskDefinitionKey === 'deptLeaderAssign') {
           variables.userId = this.selectId.join(',');
+          // 采购部门:与界面显示保持一致(默认带出的或手选的),通过/驳回都要带对
+          if (this.form.useDeptId) {
+            variables.useDeptId = this.form.useDeptId;
+          }
         }
         await API({
           id: this.taskId,
@@ -296,12 +342,20 @@
         });
       },
       async rejectTask(status) {
+        const variables = {
+          pass: !!status
+        };
+        // 主管指派节点驳回:部门同样按界面当前值带出,保证与通过时口径一致
+        if (
+          this.taskDefinitionKey === 'deptLeaderAssign' &&
+          this.form.useDeptId
+        ) {
+          variables.useDeptId = this.form.useDeptId;
+        }
         await rejectTask({
           id: this.taskId,
           reason: this.form.reason,
-          variables: {
-            pass: !!status
-          }
+          variables
         });
         this.$emit('handleAudit', {
           status,

+ 88 - 0
src/views/bpm/handleTask/components/purchaseReceiveMultiApproval/detailDialog.vue

@@ -0,0 +1,88 @@
+<template>
+  <div class="ele-body" v-loading="loading">
+    <headerTitle title="收货单详情"></headerTitle>
+    <receiptInfo
+      v-if="form.id"
+      :form="form"
+      :detailData="form"
+      :taskDefinitionKey="taskDefinitionKey"
+    />
+  </div>
+</template>
+
+<script>
+  import {
+    getReceiveSaleOrderrecordDetail,
+    getReceiveTaskDetail
+  } from '@/api/bpm/components/purchasingManage/purchaseorderreceive';
+  import receiptInfo from '@/views/bpm/handleTask/components/purchaseOrder/invoice/receiptInfo.vue';
+
+  /**
+   * 生产性物资采购收货多人审批 - 详情
+   * 只展示「收货单详情 + 物品清单」,不含入库单(入库单在收货单审批流程里处理)
+   */
+  export default {
+    name: 'PurchaseReceiveMultiApprovalDetail',
+    components: { receiptInfo },
+    props: {
+      // 收货单主键
+      businessId: {
+        default: ''
+      },
+      taskId: {
+        default: ''
+      },
+      taskDefinitionKey: {
+        default: ''
+      },
+      // 流程实例主键
+      id: {
+        default: ''
+      }
+    },
+    data() {
+      return {
+        loading: false,
+        form: {
+          productList: [],
+          receiveFiles: [],
+          receiveNo: ''
+        }
+      };
+    },
+    watch: {
+      businessId: {
+        immediate: true,
+        handler() {
+          this.getInfo();
+        }
+      }
+    },
+    methods: {
+      async getInfo() {
+        if (!this.businessId) return;
+        this.loading = true;
+        try {
+          // 仓管节点用任务维度明细,其余节点用收货单详情
+          const detail =
+            this.taskDefinitionKey === 'storeManagerApprove'
+              ? await getReceiveTaskDetail(this.businessId, this.taskId)
+              : await getReceiveSaleOrderrecordDetail(this.businessId);
+          this.form = Object.assign(
+            { productList: [], receiveFiles: [], receiveNo: '' },
+            detail || {}
+          );
+        } catch (error) {
+          this.form = { productList: [], receiveFiles: [], receiveNo: '' };
+          this.$message.error(error.message || '收货单详情加载失败');
+        } finally {
+          this.loading = false;
+        }
+      },
+      // 详情只读,无编辑项;保留 save 供父级统一调用
+      save() {
+        return this.form;
+      }
+    }
+  };
+</script>

+ 134 - 0
src/views/bpm/handleTask/components/purchaseReceiveMultiApproval/submit.vue

@@ -0,0 +1,134 @@
+<template>
+  <el-col :span="16" :offset="6">
+    <el-form label-width="100px" ref="formRef" :model="form">
+      <el-form-item label="审批意见" style="margin-bottom: 20px">
+        <ApprovalOpinion ref="approvalOpinion" v-model="form.reason" />
+      </el-form-item>
+    </el-form>
+    <div style="margin-left: 10%; margin-bottom: 20px; font-size: 14px">
+      <el-button
+        icon="el-icon-edit-outline"
+        type="success"
+        size="mini"
+        :loading="isLoading"
+        v-click-once
+        @click="$refs.approvalOpinion.validate(1) && handleAudit(1)"
+        >通过
+      </el-button>
+      <el-button
+        icon="el-icon-circle-close"
+        type="danger"
+        size="mini"
+        :loading="isLoading"
+        v-click-once
+        @click="$refs.approvalOpinion.validate(0) && handleAudit(0)"
+        >驳回
+      </el-button>
+      <el-dropdown
+        @command="(command) => handleCommand(command)"
+        style="margin-left: 30px"
+      >
+        <span class="el-dropdown-link">
+          更多<i class="el-icon-arrow-down el-icon--right"></i>
+        </span>
+        <el-dropdown-menu slot="dropdown">
+          <el-dropdown-item command="cancel">作废</el-dropdown-item>
+        </el-dropdown-menu>
+      </el-dropdown>
+    </div>
+  </el-col>
+</template>
+
+<script>
+  import {
+    approveTaskWithVariables,
+    rejectTask,
+    cancelTask
+  } from '@/api/bpm/task';
+
+  /**
+   * 生产性物资采购收货多人审批 - 审批操作
+   * 详情只读,不涉及入库,只做通过 / 驳回 / 作废
+   */
+  export default {
+    name: 'PurchaseReceiveMultiApprovalSubmit',
+    props: {
+      businessId: {
+        default: ''
+      },
+      taskId: {
+        default: ''
+      },
+      id: {
+        default: ''
+      },
+      taskDefinitionKey: {
+        default: ''
+      }
+    },
+    data() {
+      return {
+        isLoading: false,
+        form: {
+          reason: '同意'
+        }
+      };
+    },
+    methods: {
+      async handleAudit(status) {
+        if (!this.form.reason && !!status) {
+          this.$message.warning('请填写审批意见');
+          return;
+        }
+        await this._approveTaskWithVariables(status);
+      },
+      handleCommand(command) {
+        if (command === 'cancel') {
+          this.$confirm('是否确认作废?', {
+            type: 'warning',
+            cancelButtonText: '取消',
+            confirmButtonText: '确定'
+          })
+            .then(() => {
+              cancelTask({
+                id: this.id,
+                taskId: this.taskId,
+                reason: this.form.reason,
+                businessId: this.businessId
+              })
+                .then(() => {
+                  this.$emit('handleClose');
+                })
+                .catch(() => {
+                  this.$message.error('流程作废失败');
+                });
+            })
+            .catch(() => {});
+        }
+      },
+      async _approveTaskWithVariables(status) {
+        const API = !!status ? approveTaskWithVariables : rejectTask;
+        try {
+          this.isLoading = true;
+          const res = await API({
+            id: this.taskId,
+            reason: this.form.reason,
+            variables: {
+              pass: !!status
+            }
+          });
+          if (res.data.code != '-1') {
+            this.$emit('handleAudit', {
+              status,
+              title: status === 0 ? '驳回' : ''
+            });
+          }
+        } finally {
+          this.isLoading = false;
+        }
+      }
+    }
+  };
+</script>
+
+<style lang="scss"></style>

+ 7 - 0
src/views/bpm/handleTask/index.vue

@@ -346,6 +346,13 @@
       pcView: '/bpm/handleTask/components/contact/contactDetailDialog.vue',
       pcHandle: '/bpm/handleTask/components/contact/submit.vue'
     },
+    // 生产性物资采购收货多人审批(只展示收货单详情,不含入库单)
+    purchase_receive_approve3: {
+      pcView:
+        '/bpm/handleTask/components/purchaseReceiveMultiApproval/detailDialog.vue',
+      pcHandle:
+        '/bpm/handleTask/components/purchaseReceiveMultiApproval/submit.vue'
+    },
     hr_recruit_hiring_approval: {
       pcView:
         '/bpm/handleTask/components/hiringApproval/hiringApprovalDetail.vue',

+ 16 - 16
src/views/bpm/outgoingManagement/details.vue

@@ -471,7 +471,7 @@
             fixed: 'left'
           }
         ];
-        
+
         // 出库单号 - 条件显示
         if (this.isIds) {
           columns.push({
@@ -482,7 +482,7 @@
             showOverflowTooltip: true
           });
         }
-        
+
         // 基础列
         columns.push(
           {
@@ -506,7 +506,7 @@
             showOverflowTooltip: true
           }
         );
-        
+
         // 级别 - 仅 clientEnvironmentId == 4 时显示
         if (this.clientEnvironmentId == 4) {
           columns.push({
@@ -515,7 +515,7 @@
             align: 'center'
           });
         }
-        
+
         // 型号、规格、牌号
         columns.push(
           {
@@ -538,7 +538,7 @@
             showOverflowTooltip: true
           }
         );
-        
+
         // 动态列
         this.newColumns.forEach(item => {
           columns.push({
@@ -549,7 +549,7 @@
             showOverflowTooltip: item.showOverflowTooltip
           });
         });
-        
+
         // 批次号
         columns.push({
           label: '批次号',
@@ -558,7 +558,7 @@
           align: 'center',
           showOverflowTooltip: true
         });
-        
+
         // 包装数量 - 根据 clientEnvironmentId 显示不同内容
         if (this.clientEnvironmentId == 3) {
           columns.push({
@@ -574,7 +574,7 @@
             width: 80
           });
         }
-        
+
         // 最小包装单元/单位 - 根据 clientEnvironmentId 显示
         if (this.clientEnvironmentId == 3) {
           columns.push({
@@ -590,7 +590,7 @@
             prop: 'packingUnit'
           });
         }
-        
+
         // 计量数量、计量单位
         columns.push(
           {
@@ -603,7 +603,7 @@
             prop: 'measureUnit'
           }
         );
-        
+
         // 重量、重量单位
         columns.push(
           {
@@ -619,7 +619,7 @@
             showOverflowTooltip: true
           }
         );
-        
+
         // 机型、颜色
         columns.push(
           {
@@ -633,7 +633,7 @@
             width: 150
           }
         );
-        
+
         // 单价 - 条件显示
         if (this.isPrice == 1) {
           columns.push({
@@ -643,7 +643,7 @@
             slot: 'singlePrice'
           });
         }
-        
+
         // 锁定数量、库存
         columns.push(
           {
@@ -658,7 +658,7 @@
             showOverflowTooltip: true
           }
         );
-        
+
         // 金额 - 条件显示
         if (this.isPrice == 1) {
           columns.push({
@@ -669,7 +669,7 @@
             slot: 'totalMoney'
           });
         }
-        
+
         // 采购原因 - 仅 clientEnvironmentId == 4 时显示
         if (this.clientEnvironmentId == 4) {
           columns.push({
@@ -679,7 +679,7 @@
             showOverflowTooltip: true
           });
         }
-        
+
         return columns;
       }
     },

+ 90 - 8
src/views/bpm/outgoingManagement/outbound.vue

@@ -199,8 +199,8 @@
               :needPage="false"
             >
               <!-- 单价列 -->
-              <template v-if="isPrice == 1" v-slot:price="{ row }">
-                {{ row.price ? row.price : ' ' + '元' }}/{{ row.measureUnit }}
+              <template v-if="isPrice == 1" v-slot:unitPrice="{ row }">
+                {{ row.unitPrice ? row.unitPrice + '元' : ' '}}/{{ row.measureUnit }}
               </template>
               <!-- 金额列 -->
               <template v-if="isPrice == 1" v-slot:totalMoney="{ row }">
@@ -419,6 +419,31 @@
   import tableColumnsMixin from '@/mixins/tableColumnsMixin';
   import { uniqById } from '@/utils/util.js';
 
+  // 采购退货报错提示的物品描述,便于仓储人员定位到具体物品、仓库与批次。
+  function describeReturnProduct(product) {
+    const item = product || {};
+    const code = item.categoryCode || item.productCode || item.productId || '';
+    const name = item.categoryName || item.productName || '';
+    const title = [code, name].filter(Boolean).join(' ') || '未指定物品';
+    const warehouse = item.warehouseName || item.warehouseId || '未指定仓库';
+    const batch = item.batchNo || '未指定批次';
+    return title + '(仓库:' + warehouse + ',批次:' + batch + ')';
+  }
+
+  // 出库明细自身的描述:提示里用明细里的物品/仓库/批次,避免拿同单其它退货物品张冠李戴。
+  function describeOutInDetail(detail) {
+    const item = detail || {};
+    const code = item.categoryCode || item.productCode || item.categoryId || '';
+    const name = item.categoryName || item.productName || '';
+    const title = [code, name].filter(Boolean).join(' ') || '未指定物品';
+    const warehouse = item.warehouseName || item.warehouseId || '未指定仓库';
+    const batch = item.batchNo || '未指定批次';
+    const systemBatch = item.systemBatchNo || '未指定系统批次';
+    return (
+      title + '(仓库:' + warehouse + ',批次:' + batch + ',系统批次:' + systemBatch + ')'
+    );
+  }
+
   export default {
     mixins: [tableColumnsMixin],
     components: {
@@ -663,9 +688,9 @@
         if (this.isPrice == 1) {
           columns.push({
             label: '单价',
-            prop: 'price',
+            prop: 'unitPrice',
             align: 'center',
-            slot: 'price'
+            slot: 'unitPrice'
           });
         }
 
@@ -736,7 +761,7 @@
       this.getFieldModel();
       this.getListItems();
       this.initDeptData().then(() => {
-        
+
         if(this.bizType != 10) {
           console.log('created~~~~~~~~~~11111111111111111111111111111111')
           this.initFormData();
@@ -1379,8 +1404,35 @@
                   if (!needed) break;
                 }
                 if (needed > 0) {
+                  const matched = rows.filter((row) =>
+                    this.$refs.assetsDialogRef.matchesReturnProduct(
+                      product,
+                      row
+                    )
+                  );
+                  const desc = describeReturnProduct(product);
+                  if (!matched.length) {
+                    throw new Error(
+                      `采购退货物品 ${desc} 未找到可出库的来源批次:需要 ${needed}。请核对退货单的仓库、业务批次、系统批次是否与该物品现有库存一致。`
+                    );
+                  }
+                  const availableSum = matched.reduce(
+                    (sum, row) =>
+                      sum +
+                      Math.max(
+                        0,
+                        Number(row.availableCountBase || 0) -
+                          Number(row.lockQuantity || 0)
+                      ),
+                    0
+                  );
+                  if (availableSum <= 0) {
+                    throw new Error(
+                      `采购退货物品 ${desc} 的可用库存不足:需要 ${needed},该物品在当前仓库/批次的候选库存已耗尽(可分配 0)。若实物已退回,请先做「归还入库」或「不良品隔离调拨」并关联原采购收货来源,或联系仓管核对该批次库存。`
+                    );
+                  }
                   throw new Error(
-                    `采购退货物品 ${product.productId} 的来源批次库存不足`
+                    `采购退货物品 ${desc} 的可用库存不足:需要 ${needed},当前可分配 0(候选批次合计可用 ${availableSum} 已被本单其它退货物品优先占用)。请调小本行退货数量,或拆分成多张退货单后重试。`
                   );
                 }
               }
@@ -1404,7 +1456,9 @@
                   !matchingProducts.length ||
                   new Set(matchingProducts.map((item) => String(item.receiveProductId))).size !== 1
                 ) {
-                  throw new Error('采购退货来源批次不一致');
+                  throw new Error(
+                    `采购退货来源批次不一致:${describeOutInDetail(detail)};出库明细 ${detail.id},请刷新后重试或联系仓管核对。`
+                  );
                 }
                 // 历史入库明细缺少来源 ID 时,仍按收货单和唯一退货产品回填来源。
                 detail.sourceReceiveProductId = matchingProducts[0].receiveProductId;
@@ -1413,7 +1467,35 @@
                 );
               }
               if (!details?.length || requested.some((item) => item.num !== 0)) {
-                throw new Error('采购退货来源批次可出库数量不足');
+                const pending = requested.filter((item) => item.num !== 0);
+                const pendingProduct = pending.length
+                  ? returnProducts.find((item) =>
+                      this.$refs.assetsDialogRef.matchesReturnProduct(
+                        item,
+                        pending[0]
+                      )
+                    )
+                  : null;
+                const pendingDesc = pending.length
+                  ? `:${
+                      pendingProduct
+                        ? describeReturnProduct(pendingProduct)
+                        : describeOutInDetail(pending[0])
+                    }`
+                  : '';
+                const pendingNum = Number(
+                  pending
+                    .reduce((sum, item) => sum + Number(item.num || 0), 0)
+                    .toFixed(4)
+                );
+                const pendingDetail = pending.length
+                  ? pending
+                      .map((item) => `出库明细 ${item.id} 剩余 ${item.num}`)
+                      .join(';')
+                  : '候选批次未返回可出库明细';
+                throw new Error(
+                  `采购退货来源批次可出库数量不足${pendingDesc};仍有 ${pendingNum} 未分配(${pendingDetail}),请刷新候选批次后重试。`
+                );
               }
               if (!this.productList.length) this.detailData(details, '2');
             } catch (error) {

+ 3 - 2
vue.config.js

@@ -44,11 +44,12 @@ module.exports = {
         // 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:51001/api',
+        // target: 'http://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.116:18086', // 赵沙金
-        // target: 'http://192.168.1.113:18086', // 郭丹
+        target: 'http://192.168.1.113:18086', // 郭丹
+        // target: 'http://192.168.1.102:18086', // 吴志豪
 
         changeOrigin: true, // 只有这个值为true的情况下 s才表示开启跨域
         pathRewrite: {