ソースを参照

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

xieyong 4 週間 前
親
コミット
3b7aed0180

+ 50 - 1
src/BIZComponents/inventoryTable.vue

@@ -2040,6 +2040,19 @@
         let comitDatasource = copyObj(this.form.datasource);
         if (comitDatasource.length === 0) return [];
         comitDatasource.forEach((v) => {
+          // 计量数量在后端是非空字段,库存台账多选等入口只带回销售数量,
+          // 未触发数量换算时会留空,提交前统一用销售数量兜底,避免提交报 SQL 错误
+          const saleCount = v[this.countObj.countKey];
+          if (
+            (v.totalCount === undefined ||
+              v.totalCount === null ||
+              v.totalCount === '') &&
+            saleCount !== undefined &&
+            saleCount !== null &&
+            saleCount !== ''
+          ) {
+            v.totalCount = saleCount;
+          }
           if (v.guaranteePeriodUnitCode) {
             v.guaranteePeriodUnitName = this.getDictValue(
               '保质期单位',
@@ -2589,6 +2602,8 @@
       },
       async replaceTable(obj, idx) {
         console.log('replaceTable', obj, idx);
+        // 先按物品ID去重并并行查询商品信息,避免逐行串行请求,物品较多时明显加快回显
+        const goodsMap = await this.loadGoodsByCategoryIds(obj);
         for (let index = 0; index < obj.length; index++) {
           const item = obj[index];
           let i = idx == -1 ? index : idx;
@@ -2639,7 +2654,7 @@
               ? this.defTaxRate
               : undefined
           );
-          let goodsData = await getGoodsByCategoryId(item.productId);
+          let goodsData = goodsMap.get(item.productId) || [];
           goodsData = goodsData.filter((item) => item.approvalStatus == 2);
           if (goodsData?.length) {
             let goodsPriceList = goodsData[0]?.goodsPriceList[0];
@@ -2716,10 +2731,44 @@
           }
         }
         // console.log(this.form.datasource);
+        // 计量数量为后端非空字段:只补齐留空的行,避免覆盖已按计量单位换算好的数据
+        this.form.datasource.forEach((item) => {
+          const count = item[this.countObj.countKey];
+          if (
+            (item.totalCount === undefined ||
+              item.totalCount === null ||
+              item.totalCount === '') &&
+            count !== undefined &&
+            count !== null &&
+            count !== ''
+          ) {
+            this.$set(item, 'totalCount', count);
+          }
+        });
         if (this.showSaleCount) {
           this.changeCount();
         }
       },
+      /**
+       * 批量查询物品对应的商品信息。
+       * 按物品ID去重后并行请求,替代逐行 await 的串行查询,
+       * 同一物品选中多行(多批次)时只请求一次。
+       */
+      async loadGoodsByCategoryIds(list) {
+        const goodsMap = new Map();
+        const productIds = [
+          ...new Set(
+            (list || []).map((item) => item.productId).filter((id) => id)
+          )
+        ];
+        await Promise.all(
+          productIds.map(async (productId) => {
+            const goodsList = await getGoodsByCategoryId(productId);
+            goodsMap.set(productId, goodsList || []);
+          })
+        );
+        return goodsMap;
+      },
       remove(index) {
         this.form.datasource.splice(index, 1);
         this.setSort();

+ 76 - 6
src/BIZComponents/selectStockLedger/selectStockLedgerDialog.vue

@@ -50,11 +50,28 @@
             class="dict-table"
             @cell-click="cellClick"
             :selection.sync="selection"
+            :current.sync="currentRow"
+            highlight-current-row
             :cache-key="cacheKeyUrl"
             @columns-change="handleColumnChange"
+            @done="handleTableDone"
             :page-size="20"
             @header-dragend="handleHeaderDragend"
           >
+            <template v-slot:toolbar>
+              <el-button
+                v-if="dimension == '2'"
+                type="primary"
+                size="small"
+                @click="openUnlock"
+                >库存解锁</el-button
+              >
+              <span
+                v-if="dimension == '2' && unlockSourceLabel"
+                class="unlock-tip"
+                >{{ unlockSourceLabel }}</span
+              >
+            </template>
             <template v-slot:saleCount="{ row }">
               <el-input
                 v-model="row.saleCount"
@@ -75,6 +92,8 @@
       </ele-split-layout>
     </el-card>
 
+    <unlock-modal ref="unlockModalRef"></unlock-modal>
+
     <div slot="footer">
       <el-button v-if="!isView" type="primary" size="small" @click="selected"
         >选择</el-button
@@ -92,12 +111,13 @@
     getCategoryPackageDisposition
   } from '@/api/wms';
   import ItemSearch from './item-search.vue';
+  import UnlockModal from './unlockModal.vue';
   import { contactQueryByCategoryIdsAPI } from '@/api/saleManage/contact';
   import tabMixins from '@/mixins/tableColumnsMixin';
 
   export default {
     mixins: [tabMixins],
-    components: { ItemSearch },
+    components: { ItemSearch, UnlockModal },
     props: {
       /*已选择的产品*/
       data: {
@@ -137,6 +157,8 @@
         dictList: {},
         curNodeData: {},
         selection: [],
+        // 表格当前高亮行(点击行即选中),用于库存解锁定位库存数据
+        currentRow: null,
         treeData: [],
         dimension: '2',
         defaultProps: {
@@ -182,7 +204,9 @@
             type: 'selection',
             columnKey: 'selection',
             align: 'center',
-            reserveSelection: true,
+            // 不保留勾选:搜索、切换分页/排序后必须清空历史勾选的库存,
+            // reserveSelection 会让 el-table 在数据变化后按 rowKey 恢复勾选,这里必须关闭
+            reserveSelection: false,
             selectable: (row) => {
               return this.getAvailableStockCount(row) != 0;
             }
@@ -354,6 +378,23 @@
         return this.applyColumnWidth(tempData.filter((item) => !item.hide)).map(
           applyWidth
         );
+      },
+      // 库存解锁操作的来源行:优先取唯一勾选行,否则取当前点击高亮行
+      unlockSourceRow() {
+        if (this.selection.length === 1) {
+          return this.selection[0];
+        }
+        return this.currentRow || null;
+      },
+      // 解锁按钮旁的选中数据提示,避免表格级操作定位不明
+      unlockSourceLabel() {
+        const row = this.unlockSourceRow;
+        if (!row) {
+          return '';
+        }
+        const code = row.categoryCode || '-';
+        const batch = row.systemBatchNo || row.batchNo || '-';
+        return `已选:${code} / ${batch} / 锁库 ${row.lockQuantity || 0}`;
       }
     },
     methods: {
@@ -426,11 +467,29 @@
         return data;
       },
 
+      /* 清空表格勾选与当前行:搜索、切换分页/排序、关闭弹窗时都必须重置 */
+      clearTableSelection() {
+        this.selection = [];
+        this.currentRow = null;
+        this.current = null;
+        this.radio = '';
+        if (this.$refs.table && this.$refs.table.clearSelection) {
+          this.$refs.table.clearSelection();
+        }
+      },
+
       /* 刷新表格 */
       reload(where) {
+        // 查询条件变化后原勾选行已不在结果集中,先清空避免带着旧数据继续操作
+        this.clearTableSelection();
         this.$refs.table.reload({ pageNum: 1, where: where });
       },
 
+      /* 表格数据加载完成:分页、排序等表格内部触发的加载同样清空历史勾选 */
+      handleTableDone() {
+        this.clearTableSelection();
+      },
+
       handleNodeClick(data, node) {
         this.curNodeData = data;
         if (data.id == this.currentId) {
@@ -463,6 +522,13 @@
         this.current = row;
         this.radio = row.id;
       },
+      // 打开库存解锁申请弹窗(校验与数据规格化由子弹窗负责)
+      openUnlock() {
+        if (!this.unlockSourceRow) {
+          return this.$message.warning('请先选择一条库存数据进行解锁');
+        }
+        this.$refs.unlockModalRef.open(this.unlockSourceRow);
+      },
       // 列头拖拽结束 - 保存列宽到本地缓存
       handleHeaderDragend(newWidth, oldWidth, column) {
         if (!column.property) return;
@@ -494,11 +560,8 @@
         }
       },
       handleClose() {
+        this.clearTableSelection();
         this.visible = false;
-        this.current = null;
-        this.selection = [];
-        this.$refs.table.clearSelection();
-        this.radio = '';
       },
 
       isSameStockRecord(source, target) {
@@ -634,4 +697,11 @@
   .topsearch {
     margin-bottom: 15px;
   }
+
+  /* 库存解锁按钮旁展示当前选中的库存数据,避免表格级操作定位不明 */
+  .unlock-tip {
+    margin-left: 8px;
+    font-size: 12px;
+    color: #909399;
+  }
 </style>

+ 349 - 0
src/BIZComponents/selectStockLedger/unlockModal.vue

@@ -0,0 +1,349 @@
+<template>
+  <ele-modal
+    title="库存解锁申请"
+    :visible.sync="visible"
+    :before-close="handleClose"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    append-to-body
+    width="75%"
+    :maxable="true"
+    destroy-on-close
+    @closed="handleClosed"
+    @opened="handleOpened"
+  >
+    <el-card shadow="never">
+      <el-descriptions :column="3" border size="small">
+        <el-descriptions-item label="创建时间">{{
+          formData.createTime || '-'
+        }}</el-descriptions-item>
+        <el-descriptions-item label="物品编码">{{
+          formData.productCode || '-'
+        }}</el-descriptions-item>
+        <el-descriptions-item label="名称">{{
+          formData.productName || '-'
+        }}</el-descriptions-item>
+        <el-descriptions-item label="批次号">{{
+          formData.batchNo || '-'
+        }}</el-descriptions-item>
+        <el-descriptions-item label="系统内部批次号">{{
+          formData.systemBatchNo || '-'
+        }}</el-descriptions-item>
+        <el-descriptions-item label="牌号">{{
+          formData.brandNum || '-'
+        }}</el-descriptions-item>
+        <el-descriptions-item label="型号">{{
+          formData.categoryModel || '-'
+        }}</el-descriptions-item>
+        <el-descriptions-item label="规格">{{
+          formData.specification || '-'
+        }}</el-descriptions-item>
+        <el-descriptions-item label="机型">{{
+          formData.modelKey || '-'
+        }}</el-descriptions-item>
+        <el-descriptions-item label="颜色">{{
+          formData.colorKey || '-'
+        }}</el-descriptions-item>
+        <el-descriptions-item label="计量单位">{{
+          formData.measureUnit || '-'
+        }}</el-descriptions-item>
+        <el-descriptions-item label="锁定数量">{{
+          formData.lockQuantity != null ? formData.lockQuantity : '-'
+        }}</el-descriptions-item>
+        <el-descriptions-item label="仓库">{{
+          formData.warehouseName || '-'
+        }}</el-descriptions-item>
+      </el-descriptions>
+      <div style="margin-top: 12px">
+        <ele-pro-table
+          ref="eleTable"
+          :columns="columns"
+          :datasource="tableData"
+          height="calc(100vh - 405px)"
+          full-height="calc(100vh - 116px)"
+          tool-class="ele-toolbar-form"
+          :pageSize="20"
+          :initLoad="false"
+          :needPage="false"
+          @selection-change="handleSelectionChange"
+        >
+          <template v-slot:unionQuantity="{ row }">
+            <el-input
+              v-model="row.unionQuantity"
+              placeholder="请输入"
+              @input="(value) => inputNum(value, row)"
+            ></el-input>
+          </template>
+        </ele-pro-table>
+      </div>
+    </el-card>
+    <div slot="footer">
+      <el-button
+        :loading="loading"
+        type="primary"
+        size="small"
+        @click="handleSave(0)"
+        >保存</el-button
+      >
+      <el-button
+        :loading="loading"
+        type="primary"
+        size="small"
+        @click="handleSave(1)"
+        >提交</el-button
+      >
+      <el-button size="small" :disabled="loading" @click="handleClose"
+        >取消</el-button
+      >
+    </div>
+  </ele-modal>
+</template>
+<script>
+  import { inboundLockPage, saveUnlock, submitUnlock } from '@/api/wms';
+
+  export default {
+    data() {
+      return {
+        visible: false,
+        loading: false,
+        // 主表数据:展示字段 + 提交时使用白名单字段
+        formData: {},
+        // 该库存维度下的有效锁定明细
+        tableData: [],
+        // 已勾选的解锁明细
+        selectionList: [],
+        columns: [
+          {
+            columnKey: 'selection',
+            type: 'selection',
+            width: 45,
+            align: 'center'
+          },
+          {
+            columnKey: 'index',
+            type: 'index',
+            width: 50,
+            align: 'center',
+            label: '序号',
+            fixed: 'left'
+          },
+          {
+            prop: 'quantity',
+            label: '锁定数量',
+            showOverflowTooltip: true,
+            width: 130,
+            align: 'center'
+          },
+          {
+            prop: 'unionQuantity',
+            label: '解锁数量',
+            slot: 'unionQuantity',
+            showOverflowTooltip: true,
+            width: 130,
+            align: 'center'
+          },
+          {
+            prop: 'sourceOrderNo',
+            label: '关联单据编码',
+            showOverflowTooltip: true,
+            align: 'center'
+          },
+          {
+            prop: 'sourceType',
+            label: '关联单据类型',
+            align: 'center',
+            formatter: (row) =>
+              this.LockOrderSourceTypeEnum[row.sourceType] || '-'
+          },
+          {
+            prop: 'orderDate',
+            label: '锁定时间',
+            align: 'center'
+          }
+        ],
+        LockOrderSourceTypeEnum: {
+          0: '无定义',
+          1: '销售发货单',
+          2: '领料出库单',
+          3: '调拨出库单',
+          4: '销售订单'
+        }
+      };
+    },
+    methods: {
+      /**
+       * 打开解锁弹窗:row 为库存台账行(批次维度)
+       */
+      async open(row) {
+        const productId = row?.categoryId || row?.productId;
+        const productCode = row?.categoryCode || row?.productCode;
+        if (!row?.systemBatchNo && !row?.batchNo) {
+          return this.$message.warning('缺少批次信息,无法发起库存解锁申请');
+        }
+        if (!productId || !productCode || !row?.warehouseId) {
+          return this.$message.warning(
+            '缺少物品或仓库信息,无法发起库存解锁申请'
+          );
+        }
+        if (!(Number(row.lockQuantity) > 0)) {
+          return this.$message.warning('该库存批次没有锁库数量,无需解锁');
+        }
+        this.loading = true;
+        try {
+          // 系统内部批次号为空时,后端要求同时携带批次号、物品ID、物品编码和仓库ID
+          const res = await inboundLockPage({
+            pageNum: 1,
+            size: 500,
+            systemBatchNo: row.systemBatchNo || '',
+            batchNo: row.batchNo || '',
+            productId,
+            productCode,
+            warehouseId: row.warehouseId
+          });
+          const list = res?.list || [];
+          if (!list.length) {
+            return this.$message.warning(
+              '未查询到可解锁的锁库明细,请刷新库存台账后重试'
+            );
+          }
+          // 仅保留后端需要的字段,避免把台账行主键等无关数据回传
+          this.formData = {
+            systemBatchNo: row.systemBatchNo || '',
+            batchNo: row.batchNo || '',
+            productId,
+            productCode,
+            productName: row.categoryName || row.productName || '',
+            warehouseId: row.warehouseId,
+            warehouseName: row.warehouseName || '',
+            createTime: row.createTime,
+            brandNum: row.brandNum,
+            categoryModel: row.categoryModel,
+            specification: row.specification,
+            modelKey: row.modelKey,
+            colorKey: row.colorKey,
+            measureUnit: row.measureUnit,
+            lockQuantity: row.lockQuantity
+          };
+          this.selectionList = [];
+          // 先显示弹窗,再灌入锁定明细:
+          // el-table 在 display:none 的祖先容器内渲染时列宽会算成 0,会出现“有数据但表格看不到行”,
+          // 必须等弹窗显示后再赋值,并在渲染后主动重新布局一次。
+          this.visible = true;
+          this.$nextTick(() => {
+            this.tableData = list.map((item) => ({
+              ...item,
+              unionQuantity: ''
+            }));
+            this.$nextTick(() => {
+              const table = this.$refs.eleTable;
+              if (table && table.doLayout) {
+                table.doLayout();
+              }
+            });
+          });
+        } catch (e) {
+          // 请求异常拦截器已提示 500/-1,这里兜底业务异常并避免未处理的 Promise 拒绝
+          this.$message.error(e?.message || '查询锁库明细失败');
+        } finally {
+          this.loading = false;
+        }
+      },
+      // 解锁数量仅允许数字,且不能超过锁定数量
+      inputNum(value, row) {
+        let numStr = String(value ?? '').replace(/[^\d.]/g, '');
+        // 仅保留一个小数点,避免出现 1.2.3 这类后端无法解析的非法数字
+        const firstDot = numStr.indexOf('.');
+        if (firstDot > -1) {
+          numStr =
+            numStr.slice(0, firstDot + 1) +
+            numStr.slice(firstDot + 1).replace(/\./g, '');
+        }
+        const num = parseFloat(numStr);
+        const max = Number(row.quantity) || 0;
+        if (!isNaN(num) && num > max) {
+          this.$message.warning(`解锁数量不能超过锁定数量(${max})`);
+          this.$set(row, 'unionQuantity', max);
+          return;
+        }
+        this.$set(row, 'unionQuantity', numStr);
+      },
+      handleSelectionChange(selection) {
+        this.selectionList = selection;
+      },
+      handleClose() {
+        this.visible = false;
+      },
+      handleClosed() {
+        this.formData = {};
+        this.tableData = [];
+        this.selectionList = [];
+        this.loading = false;
+      },
+      // 弹窗动画结束后兜底重算列宽,避免表格在隐藏容器内完成布局导致有数据却不显示
+      handleOpened() {
+        const table = this.$refs.eleTable;
+        if (table && table.doLayout) {
+          table.doLayout();
+        }
+      },
+      async handleSave(status) {
+        if (this.loading) {
+          return;
+        }
+        if (!this.selectionList.length) {
+          return this.$message.warning('请选择解锁数据');
+        }
+        const details = [];
+        for (const item of this.selectionList) {
+          const unionQuantity = Number(item.unionQuantity);
+          if (!unionQuantity || unionQuantity <= 0) {
+            return this.$message.error('请填写解锁数量');
+          }
+          if (unionQuantity > Number(item.quantity || 0)) {
+            return this.$message.error('解锁数量不能超过锁定数量');
+          }
+          details.push({
+            lockOrderId: item.lockOrderId,
+            lockOrderDetailId: item.lockOrderDetailId,
+            unionQuantity
+          });
+        }
+        const postData = {
+          systemBatchNo: this.formData.systemBatchNo,
+          batchNo: this.formData.batchNo,
+          productId: this.formData.productId,
+          productCode: this.formData.productCode,
+          productName: this.formData.productName,
+          warehouseId: this.formData.warehouseId,
+          warehouseName: this.formData.warehouseName,
+          details
+        };
+        this.loading = true;
+        let savedId = null;
+        try {
+          savedId = await saveUnlock(postData);
+          if (status === 1) {
+            await submitUnlock({ businessId: savedId });
+            this.$message.success('提交成功');
+          } else {
+            this.$message.success('保存成功');
+          }
+          this.visible = false;
+          this.$emit('success');
+        } catch (e) {
+          if (savedId) {
+            // 草稿已落库,重复保存会产生多条申请,改为提示用户到解锁申请列表重新提交
+            this.visible = false;
+            this.$message.error(
+              `解锁申请已保存为草稿,提交失败:${e?.message || '请稍后重试'}`
+            );
+          } else {
+            this.$message.error(e?.message || '保存失败');
+          }
+        } finally {
+          this.loading = false;
+        }
+      }
+    }
+  };
+</script>

+ 29 - 0
src/api/wms/index.js

@@ -338,3 +338,32 @@ export async function getCategoryPackageDisposition (data){
     }
     return Promise.reject(new Error(res.data.message));
   }
+
+  // 库存台账-按库存维度查询可解锁的锁定明细(系统内部批次号优先,缺失时需带批次号+物品ID+物品编码+仓库ID)
+  export async function inboundLockPage(params) {
+    const res = await request.get(`/wms/lockOrder/v1/inbound-lock-page`, {
+      params
+    });
+    if (res.data.code == 0) {
+      return res.data.data;
+    }
+    return Promise.reject(new Error(res.data.message));
+  }
+
+  // 保存库存解锁申请(草稿),返回申请ID
+  export async function saveUnlock(data) {
+    const res = await request.post(`/wms/inventoryUnlockApprove/save`, data);
+    if (res.data.code == 0) {
+      return res.data.data;
+    }
+    return Promise.reject(new Error(res.data.message));
+  }
+
+  // 提交库存解锁申请审批
+  export async function submitUnlock(data) {
+    const res = await request.post(`/bpm/inventoryUnlockApprove/submit`, data);
+    if (res.data.code == 0) {
+      return res.data.data;
+    }
+    return Promise.reject(new Error(res.data.message));
+  }

+ 10 - 4
src/views/saleManage/saleOrder/components/addDialogNew.vue

@@ -460,9 +460,10 @@
           <el-form-item label="客户电话" prop="partaTel">
             <el-input
               clearable
-              :maxlength="20"
+              maxlength="11"
               v-model="form.partaTel"
               placeholder="请输入"
+              @input="handlePartaTelInput"
             />
           </el-form-item>
           <el-form-item label="客户传真" prop="partaFax">
@@ -1071,9 +1072,9 @@
           partaTel: [
             {
               required: isRequired,
-              pattern: telReg,
-              message: '请输入客户联系电话',
-              trigger: 'blur'
+              pattern: /^[0-9]{0,11}$/,
+              message: '客户联系电话只能为数字且不超过11位',
+              trigger: ['blur', 'change']
             }
           ],
 
@@ -1183,6 +1184,11 @@
       });
     },
     methods: {
+      handlePartaTelInput(value) {
+        this.form.partaTel = String(value || '')
+          .replace(/[^0-9]/g, '')
+          .slice(0, 11);
+      },
       changeReceiveTerms(row, index) {
         console.log('changeReceiveTerms~~~', row)
         this.form.collectionConditionId = row.id;

+ 20 - 1
src/views/saleManage/saleOrder/customerReturnOrder/add-sale-order-dialog.vue

@@ -79,7 +79,13 @@
             prop="linkPhone"
             style="margin-bottom: 22px"
           >
-            <el-input clearable v-model="form.linkPhone" placeholder="请输入"/>
+            <el-input
+              clearable
+              v-model="form.linkPhone"
+              maxlength="11"
+              placeholder="请输入"
+              @input="handleLinkPhoneInput"
+            />
           </el-form-item>
           <el-form-item
             label="收货地址"
@@ -173,6 +179,14 @@ export default {
         linkName: [
           {required: true, message: '请输入', trigger: ['blur', 'change']}
         ],
+        linkPhone: [
+          {
+            required: false,
+            pattern: /^[0-9]{0,11}$/,
+            message: '客户联系电话只能为数字且不超过11位',
+            trigger: ['blur', 'change']
+          }
+        ],
         orderNo: [
           {required: true, message: '请选择', trigger: 'change'}
         ],
@@ -188,6 +202,11 @@ export default {
     };
   },
   methods: {
+    handleLinkPhoneInput(value) {
+      this.form.linkPhone = String(value || '')
+        .replace(/[^0-9]/g, '')
+        .slice(0, 11);
+    },
     //初始化数据
     init(row = {}) {
       this.title = '新增发货单';

+ 18 - 2
src/views/saleManage/saleOrder/entrustedReceive/components/addOrEditDialog.vue

@@ -51,7 +51,13 @@
         </el-col>
         <el-col :span="12">
           <el-form-item label="客户电话" prop="linkPhone">
-            <el-input clearable v-model="form.linkPhone" placeholder="请输入" />
+            <el-input
+              clearable
+              v-model="form.linkPhone"
+              maxlength="11"
+              placeholder="请输入"
+              @input="handleLinkPhoneInput"
+            />
           </el-form-item>
         </el-col>
 
@@ -202,7 +208,12 @@ export default {
           { required: true, message: '请输入', trigger: ['blur', 'change'] }
         ],
         linkPhone: [
-          { required: true, message: '请输入', trigger: ['blur', 'change'] }
+          {
+            required: true,
+            pattern: /^[0-9]{0,11}$/,
+            message: '客户联系电话只能为数字且不超过11位',
+            trigger: ['blur', 'change']
+          }
         ],
         orderNo: [{ required: true, message: '请选择', trigger: 'change' }],
         receiveAddress: [
@@ -221,6 +232,11 @@ export default {
   },
 
   methods: {
+    handleLinkPhoneInput(value) {
+      this.form.linkPhone = String(value || '')
+        .replace(/[^0-9]/g, '')
+        .slice(0, 11);
+    },
     //打开新增编辑弹框
     async open(type, row, orderId) {
       this.batchNo = await getCode('lot_number_code');

+ 12 - 2
src/views/saleManage/saleOrder/invoice/components/addInvoiceDialog.vue

@@ -81,7 +81,13 @@
       <el-row :gutter="12">
         <el-col :span="12">
           <el-form-item label="客户电话" prop="linkPhone">
-            <el-input clearable v-model="form.linkPhone" placeholder="请输入" />
+            <el-input
+              clearable
+              v-model="form.linkPhone"
+              maxlength="11"
+              placeholder="请输入"
+              @input="handleLinkPhoneInput"
+            />
           </el-form-item>
         </el-col>
         <el-col :span="12">
@@ -462,7 +468,8 @@
           linkPhone: [
             {
               required: this.form.needProduce != 2 ? true : false,
-              message: '请输入',
+              message: '请输入11位以内的数字',
+              pattern: /^[0-9]{0,11}$/,
               trigger: 'change'
             }
           ],
@@ -482,6 +489,9 @@
     },
 
     methods: {
+      handleLinkPhoneInput(value) {
+        this.form.linkPhone = String(value || '').replace(/[^0-9]/g, '').slice(0, 11);
+      },
       stockShow() {
         this.$refs.selectStockLedgerDialogRef.open('', -1);
       },

+ 22 - 7
src/views/saleManage/saleOrder/invoice/components/inventoryTable.vue

@@ -107,8 +107,8 @@
               @change="batchNoChange(scope.$index, scope.row)"
             >
               <el-option
-                v-for="(item, optIndex) in scope.row.batchNoList || []"
-                :key="`${item.id}_${optIndex}`"
+                v-for="item in scope.row.batchNoList || []"
+                :key="item.id"
                 :label="item.batchNo"
                 :value="item.id"
               >
@@ -1093,6 +1093,16 @@
     },
     methods: {
       formatPrice,
+      dedupeBatchNoList(list) {
+        const seen = new Set();
+        return (list || []).filter((item) => {
+          if (seen.has(item.id)) {
+            return false;
+          }
+          seen.add(item.id);
+          return true;
+        });
+      },
       getSummaries(param) {
 
 
@@ -1205,7 +1215,7 @@
         this.$set(
           this.form.datasource[index],
           'batchNoList',
-          tempBatchNoList?.list || []
+          this.dedupeBatchNoList(tempBatchNoList?.list)
         );
 
         if (isReset) {
@@ -1500,7 +1510,11 @@
             size: -1
           });
 
-          this.$set(parasm, 'batchNoList', templist?.list);
+          this.$set(
+            parasm,
+            'batchNoList',
+            this.dedupeBatchNoList(templist?.list)
+          );
 
           console.log('parasm~~~~333', parasm);
 
@@ -1694,7 +1708,7 @@
               this.$set(
                 this.form.datasource[index],
                 'batchNoList',
-                templist?.list || []
+                this.dedupeBatchNoList(templist?.list)
               );
               // console.log(this.form.datasource[index]);
             } else {
@@ -1736,7 +1750,7 @@
             size: -1
           });
 
-          let tempBatchNoList = templist1?.list || [];
+          let tempBatchNoList = this.dedupeBatchNoList(templist1?.list);
           console.log('888~~~~', index, item.batchNo);
 
           // 优先按批次明细ID匹配,无ID时按系统批次和业务批次兼容匹配
@@ -1771,6 +1785,7 @@
               id: 999,
               measureQuantity: 0
             });
+            tempBatchNoList = this.dedupeBatchNoList(tempBatchNoList);
             // 设置为不存在的批次ID
             this.$set(this.form.datasource[index], 'batchStockId', 999);
             this.$set(this.form.datasource[index], 'warehouseNum', '');
@@ -1783,7 +1798,7 @@
           this.$set(
             this.form.datasource[index],
             'batchNoList',
-            tempBatchNoList
+            this.dedupeBatchNoList(tempBatchNoList)
           );
         } else {
           if (item.batchNo) {

+ 8 - 0
src/views/saleManage/saleOrder/invoiceConfirm/components/addInvoiceDialog.vue

@@ -703,6 +703,14 @@
           sendNo: [
             { required: true, message: '请选择发货单', trigger: 'change' }
           ],
+          linkPhone: [
+            {
+              required: false,
+              pattern: /^[0-9]{0,11}$/,
+              message: '客户联系电话只能为数字且不超过11位',
+              trigger: ['blur', 'change']
+            }
+          ],
           repliedFiles: [
             { required: true, message: '请上传回执附件', trigger: 'change' }
           ]