Просмотр исходного кода

Merge branch 'feat-pending' into dev

liujt 8 месяцев назад
Родитель
Сommit
5c8f8b24de

+ 12 - 0
src/views/purchasingManage/purchaseOrder/returnGoods/components/addReturnGoodsDialog.vue

@@ -499,6 +499,18 @@ export default {
         if (productList.length === 0) {
           return this.$message.error('至少选择一个退货产品');
         }
+        let productListData = [];
+        productList.forEach((item) => {
+          if (!item.totalCount) {
+            productListData.push(item.productName);
+          }
+        });
+        console.log(productListData);
+        if (productListData.length) {
+          return this.$message.error(
+            productListData.toString() + ' 退货数量不能为空!'
+          );
+        }
         let commitData = Object.assign({}, this.form, {
           totalAmount: Number(this.totalAmount),
           productList

+ 1 - 1
src/views/purchasingManage/purchaseOrder/returnGoods/components/inventoryTable.vue

@@ -105,9 +105,9 @@
           :prop="'datasource.' + scope.$index + '.totalCount'"
           :rules="[
             { required: true, message: '请输入数字', trigger: 'blur' },
-            { validator: validateTotalCount(scope.row), trigger: 'blur' }
           ]"
         >
+          <!-- { validator: validateTotalCount(scope.row), trigger: 'blur' } -->
           <el-input
             v-model="scope.row.totalCount"
             @input="(val) => changeNum(val, scope.$index, scope.row)"

+ 7 - 1
src/views/saleManage/saleOrder/invoice/components/addInvoiceDialog.vue

@@ -930,7 +930,7 @@ import {
             });
           }),
           new Promise((resolve, reject) => {
-            this.$refs.inventoryTableref.validateForm((valid) => {
+            this.$refs.inventoryTableref.validateFormOptimized((valid) => {
               if (!valid) {
                 reject(false);
               } else {
@@ -982,6 +982,12 @@ import {
             ) {
               item['isReplace'] = 1;
             }
+
+            if(item.isSuspend == 1) {
+              item['reverseQuantity'] = item.saleCount;
+            } else {
+              item['reverseQuantity'] = 0;
+            }
           });
 
           let commitData = Object.assign({}, this.form, {

+ 602 - 44
src/views/saleManage/saleOrder/invoice/components/inventoryTable.vue

@@ -12,6 +12,7 @@
       full-height="calc(100vh - 76px)"
       :show-summary="clientEnvironmentId == 4"
       :summary-method="getSummaries"
+      :row-class-name="tableRowClassName"
     >
       <!-- 表头工具栏 -->
       <template v-slot:toolbar>
@@ -180,7 +181,7 @@
           <el-select
             v-model="scope.row.warehouseId"
             placeholder="请选择"
-            @change="warehouseChange(scope.$index, scope.row)"
+            @change="warehouseChange(scope.$index, scope.row, true)"
           >
             <el-option
               v-for="item in scope.row.warehouseList"
@@ -289,6 +290,21 @@
           </el-input>
         </el-form-item>
       </template>
+      <template v-slot:warehouseNum="scope">
+        <el-form-item
+          :prop="'datasource.' + scope.$index + '.warehouseNum'"
+        > 
+          <el-link
+            v-if="scope.row.isSuspend === 1"
+            :underline="false"
+            type="primary"
+            @click="pendingReplaceTable(scope.row, scope.$index)"
+          >
+            {{ scope.row.warehouseNum }}
+          </el-link>
+          <span v-else>{{ scope.row.warehouseNum }}</span>
+        </el-form-item>
+      </template>
       <!-- <template v-slot:saleUnit="scope">
         <el-form-item
           style="margin-bottom: 20px"
@@ -361,6 +377,27 @@
         >
           替代料
         </el-link>
+        
+        <template v-if="scope.row.batchNo">
+          <el-popconfirm
+            v-if="scope.row.isSuspend === 1"
+            class="ele-action"
+            title="确定要挂起吗?"
+            @confirm="pending(scope.row, 0)"
+          >
+            <template v-slot:reference>
+              <el-link
+                type="primary"
+                :underline="false"
+              >
+            取消挂起
+          </el-link>
+            </template>
+          </el-popconfirm>
+          <el-link v-else type="primary" :underline="false" @click="pendingReplaceTable(scope.row, scope.$index)">
+                挂起
+          </el-link>
+        </template>
       </template>
     </ele-pro-table>
     <productListNew
@@ -385,6 +422,10 @@
       ref="selectStockLedgerDialogRef"
       @changeParent="replaceTable"
     ></selectStockLedgerDialog>
+    <selectStockLedgerDialogPending
+      ref="selectStockLedgerDialogPendingRef"
+      @changeParent="pendingReplaceTableConfirm"
+    ></selectStockLedgerDialogPending>
     <BIZproductList
       ref="BIZproductListRef"
       classType="1"
@@ -420,6 +461,7 @@
   import { parameterGetByCode } from '@/api/main/index.js';
   import { getSummaries } from '@/utils/util.js';
   import { getBatchList } from '@/api/wms';
+  import selectStockLedgerDialogPending from './selectStockLedger/selectStockLedgerDialog.vue';
 
   export default {
     mixins: [dictMixins, getDynamicsColumns, tabMixins],
@@ -455,7 +497,8 @@
       productListNew,
       stockLedgerDialog,
       selectStockLedgerDialog,
-      BIZproductList
+      BIZproductList,
+      selectStockLedgerDialogPending
     },
     data() {
       const defaultForm = {
@@ -931,7 +974,7 @@
           {
             columnKey: 'action',
             label: '操作',
-            width: 180,
+            width: 200,
             align: 'center',
             resizable: false,
             slot: 'action',
@@ -945,6 +988,52 @@
       // this.clientEnvironmentId =
     },
     methods: {
+       // 挂起/取消挂起功能
+      async pending(row) {
+        console.log('pending~~~', row)
+          // 取消挂起不需要二次确认
+          this.$set(row, 'isSuspend', 0);
+          this.$forceUpdate();
+          this.current['offsetDetail'] = [];
+          this.current['isSuspend'] = 0;
+          this.$set(this.current, 'warehouseList', await getIdWarehouseList({
+              categoryId: this.current.id
+            }))
+          this.$set(this.current, 'warehouseId', '')
+      },
+      // 通过挂起状态设置行样式
+      tableRowClassName({row}) {
+        // console.log('tableRowClassName', row)
+        if (row.isSuspend == 1) {
+          return 'warning-row';
+        }
+        return '';
+      },
+      // 挂起选择库存
+      pendingReplaceTable(row, index) {
+        // console.log('handleWarehouseClick', row, index)
+        this.current = row;
+        this.$refs.selectStockLedgerDialogPendingRef.open(row, index)
+      },
+      pendingReplaceTableConfirm(list) {
+        console.log('confirm', list)
+        list.forEach((item) => {
+          item.warehouseList =[{
+            warehouseName: item.warehouseName,
+            warehouseId: item.warehouseId,
+          }]
+        });
+        
+        this.$set(this.current, 'warehouseList', list[0].warehouseList)
+        this.$set(this.current, 'warehouseId', list[0].warehouseId)
+        this.$set(this.current, 'warehouseNum', list[0].measureQuantity)
+        this.current['offsetDetail'] = list
+        this.$set(this.current, 'isSuspend', 1)
+        console.log('current', this.current)
+        // this.$emit('pendingReplaceTableConfirm', list);
+        // this.$emit('replaceTableChange', list);
+        // console.log('pendingReplaceTableConfirm', list)
+      },
       getSummaries(param) {
         if (this.clientEnvironmentId != 4) {
           return;
@@ -1029,7 +1118,7 @@
         this.selection = [];
       },
 
-      async warehouseChange(index, row) {
+      async warehouseChange(index, row, isReset = false) {
         const data = row.warehouseList.find(
           (item) => item.warehouseId == row.warehouseId
         );
@@ -1054,13 +1143,32 @@
           warehouseOutStock || 0
         );
 
-        // this.$set(this.form.datasource[index], 'batchStockId', '');
-
-        // this.$set(this.form.datasource[index], 'batchNo', '');
+        if(isReset) {
+          this.$set(this.form.datasource[index], 'batchStockId', '');
+          this.$set(this.form.datasource[index], 'batchNo', '');
+        }
+        
       },
 
       //批次号选择
       async batchNoChange(index, row) {
+        if(!row.batchStockId) {
+          this.$set(this.form.datasource[index], 'batchNo', '');
+          this.$set(this.form.datasource[index], 'warehouseNum', '');
+          const tempWarehouse = await getIdWarehouseList({
+            categoryId: this.form.datasource[index].productId,
+          })
+          // 有仓库列表
+          if (tempWarehouse?.length) {
+            console.log('666~~~~!!', index, tempWarehouse);
+            this.$set(
+              this.form.datasource[index],
+              'warehouseList',
+              tempWarehouse
+            );
+          }
+          return;
+        }
         const data = row.batchNoList.find(
           (item) => item.id == row.batchStockId
         );
@@ -1403,72 +1511,369 @@
         });
         this.form.datasource.forEach(async (item, index) => {
           if (indexS.includes(index)) {
+            if(!item.batchNo) {
+              console.log('111~~~~', index);
+              this.$set(
+                this.form.datasource[index],
+                'warehouseList',
+                await getIdWarehouseList({
+                  categoryId: item.productId
+                })
+              );
+              if (item.warehouseId) {
+                this.$set(
+                  this.form.datasource[index],
+                  'warehouseNum',
+                  item.stockNum ? item.stockNum : await getWarehouseOutStock({
+                    warehouseId: item.warehouseId,
+                    code: item.productCode
+                  })
+                );
+              } else if (this.form.datasource[index]?.warehouseList?.length) {
+                this.$set(
+                  this.form.datasource[index],
+                  'warehouseId',
+                  this.form.datasource[index]?.warehouseList[0].warehouseId
+                );
+                await this.warehouseChange(index, this.form.datasource[index]);
+              }
+
+              const templist = await getBatchList({
+                categoryCode: item.productCode,
+                warehouseId: item.warehouseId,
+                size: -1
+              });
+
+              console.log('templist~~~~', index, templist.list);
+
+              // 批次号
+              this.$set(
+                this.form.datasource[index],
+                'batchNoList',
+                templist?.list || []
+              );
+              // console.log(this.form.datasource[index]);
+            } else {
+              this.setBatchAndWarehouse(item, index);
+            }
+            
+          }
+        });
+      },
+      async setBatchAndWarehouse(item, index) {
+          console.log('333~~~~', index);
+          // 通过产品编码查仓库
+          const tempWarehouse = await getIdWarehouseList({
+            categoryId: item.productId,
+          })
+          // 有仓库列表
+          if (tempWarehouse?.length) {
+            console.log('666~~~~', index, tempWarehouse);
             this.$set(
               this.form.datasource[index],
               'warehouseList',
-              await getIdWarehouseList({
-                categoryId: item.productId
-              })
+              tempWarehouse
             );
             if (item.warehouseId) {
               this.$set(
                 this.form.datasource[index],
                 'warehouseNum',
-                await getWarehouseOutStock({
+                item.stockNum ? item.stockNum : await getWarehouseOutStock({
                   warehouseId: item.warehouseId,
                   code: item.productCode
                 })
               );
-            } else if (this.form.datasource[index]?.warehouseList?.length) {
-              this.$set(
-                this.form.datasource[index],
-                'warehouseId',
-                this.form.datasource[index]?.warehouseList[0].warehouseId
-              );
-              await this.warehouseChange(index, this.form.datasource[index]);
+            } else {
+              if (this.form.datasource[index]?.warehouseList?.length) {
+                this.$set(
+                  this.form.datasource[index],
+                  'warehouseId',
+                  this.form.datasource[index]?.warehouseList[0].warehouseId
+                );
+                await this.warehouseChange(index, this.form.datasource[index]);
+              }
+              
             }
 
-            const templist = await getBatchList({
+            const templist1 = await getBatchList({
               categoryCode: item.productCode,
               warehouseId: item.warehouseId,
               size: -1
             });
 
-            console.log('templist~~~~', templist.list);
+            let tempBatchNoList = templist1?.list || [];
+            console.log('888~~~~', index, item.batchNo);
 
-            // 批次号
+            const batchNoObj = tempBatchNoList.find(i => i.batchNo == item.batchNo);
+            
+            if(batchNoObj) {
+              this.$set(
+                this.form.datasource[index],
+                'batchStockId',
+                batchNoObj.id
+              );
+              this.$set(
+                this.form.datasource[index],
+                'warehouseNum',
+                item.stockNum ? item.stockNum : batchNoObj.measureQuantity || 0
+              );
+            } else { //不存在
+              // 批次列表添加不存在的批次号对象
+              tempBatchNoList.push({
+                batchNo: item.batchNo,
+                id: 999,
+                measureQuantity: 0
+              })
+              // 设置为不存在的批次ID
+              this.$set(
+                this.form.datasource[index],
+                'batchStockId',
+                999
+              );
+              this.$set(
+                this.form.datasource[index],
+                'warehouseNum',
+                item.stockNum ? item.stockNum : ''
+              );
+            }
+
+            // // 批次号
             this.$set(
               this.form.datasource[index],
               'batchNoList',
-              templist?.list || []
+              tempBatchNoList
             );
-            console.log(this.form.datasource[index]);
+          } else {
             if(item.batchNo) {
-              const batchStockId = templist?.list?.find((n) => n.batchNo == item.batchNo)?.id
-              if(batchStockId) {
+              const tempBatchNoList = [{
+                batchNo: item.batchNo,
+                id: 999,
+                measureQuantity: 0
+              }]
+              this.$set(
+                this.form.datasource[index],
+                'batchNoList',
+                tempBatchNoList
+              );
+              this.$set(
+                this.form.datasource[index],
+                'batchStockId',
+                999
+              );
+              this.$set(
+                this.form.datasource[index],
+                'warehouseNum',
+                item.stockNum ? item.stockNum : ''
+              );
+            }
+          }
+      },
+      async setBatchAndWarehouse1(item, index) {
+        // 通过产品编号和批次号获取仓库列表
+        const warehouse = await getIdWarehouseList({
+          categoryId: item.productId,
+          // batchNo: item.batchNo
+        })
+        // 如果有仓库列表
+        if (warehouse?.length) {
+          console.log('222~~~~', index, item.batchNo);
+          // 设置仓库列表
+          this.$set(
+            this.form.datasource[index],
+            'warehouseList',
+            warehouse
+          );
+          // 如果有仓库ID
+          if (item.warehouseId) {
+            // console.log('444~~~~');
+            // const templist = await getBatchList({
+            //   categoryCode: item.productCode,
+            //   warehouseId: item.warehouseId,
+            //   size: -1
+            // });
+            // const batchNoList = templist?.list || [];
+            // // 批次号
+            // this.$set(
+            //   this.form.datasource[index],
+            //   'batchNoList',
+            //   batchNoList
+            // );
+            // const batchNo = batchNoList.find(i => i.batchNo === item.batchNo);
+            // if (batchNo) {
+            //   this.$set(
+            //     this.form.datasource[index],
+            //     'batchStockId',
+            //     batchNo.id
+            //   );
+            //   this.$set(
+            //     this.form.datasource[index],
+            //     'warehouseNum',
+            //     data.measureQuantity || 0
+            //   );
+            // }
+
+          } 
+          // 没有仓库ID,设置第一个仓库ID
+          else {
+             if (this.form.datasource[index]?.warehouseList?.length){
+              console.log('555~~~~', index);
+              this.$set(
+                this.form.datasource[index],
+                'warehouseId',
+                this.form.datasource[index]?.warehouseList[0].warehouseId
+              );
+
+              // await this.warehouseChange(index, this.form.datasource[index]);
+             }
+          }
+
+          // const wdata = warehouse.find(
+          //   (i) => i.warehouseId == row.warehouseId
+          // );
+
+          // this.$set(
+          //   this.form.datasource[index],
+          //   'warehouseName',
+          //   wdata.warehouseName
+          // );
+          // this.$set(
+          //   this.form.datasource[index],
+          //   'warehouseCode',
+          //   wdata.warehouseCode
+          // );
+          // const warehouseOutStock = await getWarehouseOutStock({
+          //   warehouseId: wdata.warehouseId,
+          //   code: row.productCode
+          // });
+          // this.$set(
+          //   this.form.datasource[index],
+          //   'warehouseNum',
+          //   warehouseOutStock || 0
+          // );
+          
+          console.log('444~~~~');
+          // 通过仓库ID获取批次号列表
+          const templist = await getBatchList({
+            categoryCode: item.productCode,
+            warehouseId: item.warehouseId,
+            size: -1
+          });
+          let batchNoList = templist?.list || [];
+          console.log('batchNoList~~~~', index, batchNoList);
+          // 设置批次列表
+          this.$set(
+            this.form.datasource[index],
+            'batchNoList',
+            batchNoList
+          );
+          // 查找批次号是否存在批次列表
+          const batchNo = batchNoList.find(i => i.batchNo == item.batchNo);
+          console.log('batchNo~~~~', index, batchNo);
+          // 存在
+          if (batchNo) {
+            // 设置批次ID
+            this.$set(
+              this.form.datasource[index],
+              'batchStockId',
+              batchNo.id
+            );
+            // 设置批次库存
+            this.$set(
+              this.form.datasource[index],
+              'warehouseNum',
+              batchNo.measureQuantity || 0
+            );
+            
+          } else { //不存在
+            // 批次列表添加不存在的批次号对象
+            batchNoList = batchNoList.push({
+              batchNo: item.batchNo,
+              id: 999,
+              measureQuantity: 0
+            })
+            // 设置为不存在的批次ID
+            this.$set(
+              this.form.datasource[index],
+              'batchStockId',
+              999
+            );
+            this.$set(
+              this.form.datasource[index],
+              'warehouseNum',
+              ''
+            );
+          }
+        } 
+        // 如果没有仓库列表
+        else {
+          console.log('333~~~~', index);
+          // 通过产品编码查仓库
+          const tempWarehouse = await getIdWarehouseList({
+            categoryId: item.productId,
+          })
+          // 有仓库列表
+          if (tempWarehouse?.length) {
+            console.log('666~~~~', index, tempWarehouse);
+            this.$set(
+              this.form.datasource[index],
+              'warehouseList',
+              tempWarehouse
+            );
+            if (item.warehouseId) {
+              this.$set(
+                this.form.datasource[index],
+                'warehouseNum',
+                await getWarehouseOutStock({
+                  warehouseId: item.warehouseId,
+                  code: item.productCode
+                })
+              );
+            } else {
+              if (this.form.datasource[index]?.warehouseList?.length) {
                 this.$set(
                   this.form.datasource[index],
-                  'batchStockId',
-                  batchStockId
+                  'warehouseId',
+                  this.form.datasource[index]?.warehouseList[0].warehouseId
                 );
+                await this.warehouseChange(index, this.form.datasource[index]);
               }
+              
             }
-            // if (item.batchStockId) {
-            //   // this.$set(
-            //   //   this.form.datasource[index],
-            //   //   'warehouseNum',
-            //   //   await getWarehouseOutStock({
-            //   //     warehouseId: item.warehouseId,
-            //   //     code: item.productCode
-            //   //   })
-            //   // );
-
-            //   await this.batchNoChange(index, this.form.datasource[index]);
-            // }
+
+            const templist1 = await getBatchList({
+              categoryCode: item.productCode,
+              warehouseId: item.warehouseId,
+              size: -1
+            });
+
+            let tempBatchNoList = templist1?.list || [];
+            console.log('888~~~~', index, item.batchNo);
+            tempBatchNoList.push({
+              batchNo: item.batchNo,
+              id: 999,
+              measureQuantity: 0
+            })
+            console.log('tempBatchNoList~~~~', index, tempBatchNoList);
+            // 批次号
+            this.$set(
+              this.form.datasource[index],
+              'batchNoList',
+              tempBatchNoList
+            );
+            this.$set(
+              this.form.datasource[index],
+              'batchStockId',
+              999
+            );
+            this.$set(
+              this.form.datasource[index],
+              'warehouseNum',
+              ''
+            );
           }
-        });
+        }
+        
       },
-
       remove(i) {
         this.form.datasource.splice(i, 1);
         this.setSort();
@@ -1501,10 +1906,18 @@
         //开始表单校验
         this.$refs.form.validate((valid, obj) => {
           let is = false;
+          let isBatchNo = false;
+          let isSuspend = false;
           this.form.datasource.forEach((item) => {
             if (Number(item.totalCount) > item.orderTotalCount) {
               is = true;
             }
+            if (item.batchStockId == 999) {
+              isBatchNo = true;
+            }
+            if(item.isSuspend == 1 && (Number(item.totalCount) > item.warehouseNum)) {
+              isSuspend = true;
+            }
           });
           // 退货单退货不校验数量,实物赔偿无法计算
           if (is && this.sourceType != 3) {
@@ -1513,6 +1926,15 @@
               callback(false);
             }
           }
+          if (isBatchNo) {
+            this.$message.warning('你填的批次号与库存批次号不相符,请重新选择批次号出库或删除批次号');
+            callback(false);
+          }
+          if (isSuspend) {
+            this.$message.warning('挂起产品发货数量不能大于库存');
+            callback(false);
+          }
+
           if (obj) {
             let messages = Object.keys(obj).map((key) => obj[key][0]);
             if (messages.length > 0) {
@@ -1522,11 +1944,147 @@
 
           callback(valid);
         });
+      },
+
+      /**
+       * 优化后的表单校验方法 - 重构版本
+       * 添加了详细的注释、错误消息常量和更清晰的结构
+       */
+      validateFormOptimized(callback) {
+        // 使用常量存储错误消息
+        const ERROR_MESSAGES = {
+          QUANTITY_EXCEEDS_ORDER: '发货数量大于订单总数量',
+          BATCH_NUMBER_INVALID: '你填的批次号与库存批次号不相符,请重新选择批次号出库或删除批次号',
+          SUSPENDED_PRODUCT_OVERSTOCK: '挂起产品发货数量不能大于库存'
+        };
+
+        // 开始表单校验
+        this.$refs.form.validate((valid, obj) => {
+          // 收集所有校验错误
+          const errors = [];
+
+          // 遍历数据源进行业务规则校验
+          this.form.datasource.forEach((item, index) => {
+            const productInfo = this.getProductDisplayInfo(item);
+            
+            // 检查数量是否超过订单数量(退货单除外)
+            if (this.isQuantityExceedsOrder(item)) {
+              if (this.sourceType != 3) { // 非退货单
+                errors.push({ 
+                  type: 'quantity', 
+                  message: `产品[${productInfo.name}]的发货数量${item.totalCount}大于订单总数量${item.orderTotalCount}`, 
+                  stopValidation: this.isTotalCount == 1,
+                  item: item
+                });
+              }
+            }
+
+            // 检查批次号是否正确
+            // if (this.isBatchNumberInvalid(item)) {
+            //   errors.push({ 
+            //     type: 'batch', 
+            //     message: `产品[${productInfo.name}]填写的批次号与库存批次号不相符,请重新选择批次号出库或删除批次号`, 
+            //     stopValidation: true,
+            //     item: item
+            //   });
+            // }
+
+            if (this.isBatchNumberInvalid(item) && item.isSuspend != 1) {
+              // 获取当前批次号和正确的库存批次号信息
+              const currentBatchNumber = item.batchNo || '未填写';
+              // const availableBatches = item.availableBatchNumbers || item.validBatchNumbers || [];
+              // const batchInfo = availableBatches.length > 0 
+              //   ? `当前可选择批次号:${availableBatches.join('、')}` 
+              //   : '当前无有效批次';
+              
+              errors.push({ 
+                type: 'batch', 
+                message: `第${index + 1}条产品[${productInfo.name}]填写的批次号"${currentBatchNumber}"与库存批次号不相符,请重新选择批次号出库或删除批次号`, 
+                stopValidation: true,
+                item: item
+              });
+            }
+
+            // 检查挂起产品库存
+            if (this.isSuspendedProductOverstock(item)) {
+              errors.push({ 
+                type: 'suspend', 
+                message: `挂起产品[${productInfo.name}]的发货数量${item.totalCount}不能大于库存${item.warehouseNum}`, 
+                stopValidation: true,
+                item: item
+              });
+            }
+          });
+
+          // 如果存在错误,显示第一个错误并根据情况终止校验
+          if (errors.length > 0) {
+            this.$message.warning(errors[0].message);
+            if (errors[0].stopValidation) {
+              callback(false);
+              return;
+            }
+          }
+
+          // 显示表单字段错误(如果存在)
+          if (obj) {
+            let messages = Object.keys(obj).map((key) => obj[key][0]);
+            if (messages.length > 0) {
+              this.$message.warning(messages[0].message);
+            }
+          }
+
+          callback(valid);
+        });
+      },
+
+      /**
+       * 检查数量是否超过订单数量
+       * @param {Object} item 数据项
+       * @returns {boolean} 是否超过
+       */
+      isQuantityExceedsOrder(item) {
+        return Number(item.totalCount) > item.orderTotalCount;
+      },
+
+      /**
+       * 检查批次号是否无效
+       * @param {Object} item 数据项
+       * @returns {boolean} 是否无效
+       */
+      isBatchNumberInvalid(item) {
+        return item.batchStockId == 999;
+      },
+
+      /**
+       * 检查挂起产品是否超过库存
+       * @param {Object} item 数据项
+       * @returns {boolean} 是否超过
+       */
+      isSuspendedProductOverstock(item) {
+        return item.isSuspend == 1 && Number(item.totalCount) > item.warehouseNum;
+      },
+
+      /**
+       * 获取产品显示信息
+       * @param {Object} item 数据项
+       * @returns {Object} 包含产品名称和编码的对象
+       */
+      getProductDisplayInfo(item) {
+        return {
+          name: item.productName || item.name || '未知产品',
+          code: item.productCode || item.code || '',
+          info: item.productName && item.productCode 
+            ? `${item.productName}(${item.productCode})` 
+            : (item.productName || item.name || '未知产品')
+        };
       }
     }
   };
 </script>
 <style lang="scss" scoped>
+  ::v-deep .el-table .warning-row {
+    background: #d9ecff;
+  }
   .headbox {
     display: flex;
     justify-content: flex-start;
@@ -1565,9 +2123,9 @@
     background-color: transparent !important;
   }
 
-  ::v-deep .time-form .el-table tr {
-    background-color: #ffffff;
-  }
+  // ::v-deep .time-form .el-table tr {
+  //   background-color: #ffffff;
+  // }
 
   .pricebox {
     display: flex;

+ 28 - 3
src/views/saleManage/saleOrder/invoice/components/inventoryTableDetails.vue

@@ -6,11 +6,13 @@
     :datasource="form.datasource"
     row-key="id"
     @columns-change="handleColumnChange"
+    @row-click="handleRowClick"
     :cache-key="cacheKeyUrl"
     height="350"
     full-height="calc(100vh - 76px)"
     show-summary
     :summary-method="getSummaries"
+    :row-class-name="tableRowClassName"
   >
     <template v-slot:technicalDrawings="{ row }">
       <fileMain v-model="row.technicalDrawings" type="view"></fileMain>
@@ -452,6 +454,26 @@
       }
     },
     methods: {
+      // 通过挂起状态设置行样式
+      tableRowClassName({row}) {
+        console.log('tableRowClassName', row)
+        if (row.isSuspend === 1) {
+          return 'warning-row';
+        }
+        return '';
+      },
+      handleRowClick(row) {
+        // this.current = row;
+        // console.log('handleRowClick', row)
+        if (row.isSuspend === 1) {
+          this.$alert(`替代批次号:${row.offsetDetail[0].batchNo}`, {
+            confirmButtonText: '知道了',
+            type: 'none'
+          }).catch(() => {
+            // 捕获用户取消操作时的Promise rejection
+          });
+        }
+      },
       getSummaries(param) {
         return getSummaries(
           param,
@@ -470,6 +492,9 @@
   };
 </script>
 <style lang="scss" scoped>
+  ::v-deep .el-table .warning-row {
+    background: #d9ecff;
+  }
   .headbox {
     display: flex;
     justify-content: flex-start;
@@ -508,9 +533,9 @@
     background-color: transparent !important;
   }
 
-  ::v-deep .time-form .el-table tr {
-    background-color: #ffffff;
-  }
+  // ::v-deep .time-form .el-table tr {
+  //   background-color: #ffffff;
+  // }
 
   .pricebox {
     display: flex;

+ 249 - 0
src/views/saleManage/saleOrder/invoice/components/selectStockLedger/item-search.vue

@@ -0,0 +1,249 @@
+<!-- 搜索表单 -->
+<template>
+  <div class="itemSearch">
+    <el-form
+      label-width="80px"
+      label-position="left"
+      class="ele-form-search"
+      @keyup.enter.native="search"
+      @submit.native.prevent
+    >
+      <el-row :gutter="15">
+        <el-col :span="6">
+          <el-form-item label="仓库:" prop="warehouseId">
+            <template>
+              <el-select
+                clearable
+                style="width: 100%"
+                v-model="params.warehouseId"
+                placeholder="请选择"
+              >
+                <el-option
+                  v-for="item in warehouseList"
+                  :label="item.name"
+                  :value="item.id"
+                  :key="item.id"
+                >
+                </el-option>
+              </el-select>
+            </template>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6" style="height: 43px">
+          <el-form-item label="列表维度:" prop="dimension">
+            <template>
+              <el-select
+                style="width: 100%"
+                @change="search"
+                v-model="params.dimension"
+                placeholder="请选择"
+              >
+                <el-option label="批次维度" value="2"></el-option>
+                <el-option label="物品维度" value="1"></el-option>
+              </el-select>
+            </template>
+          </el-form-item>
+        </el-col>
+        <!-- <el-col :span="6">
+          <el-form-item label="物品编码:" prop="categoryCode">
+            <el-input
+              clearable
+              placeholder="请输入"
+              v-model.trim="params.categoryCode"
+              disabled
+            ></el-input>
+          </el-form-item>
+        </el-col> -->
+
+        <el-col :span="6">
+          <el-form-item label="物品名称:" prop="categoryName">
+            <el-input
+              clearable
+              v-model="params.categoryName"
+              placeholder="请输入"
+            ></el-input>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="型号:" prop="categoryModel">
+            <el-input
+              clearable
+              v-model="params.categoryModel"
+              placeholder="请输入"
+            ></el-input>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="规格:" prop="specification">
+            <el-input
+              clearable
+              v-model="params.specification"
+              placeholder="请输入"
+            ></el-input>
+          </el-form-item>
+        </el-col>
+        <!--        <el-col :span="6">-->
+        <!--          <el-form-item label="组织机构:">-->
+        <!--            <auth-selection-->
+        <!--              v-model="params.deptIds"-->
+        <!--              style="width: 100%"-->
+        <!--            ></auth-selection>-->
+        <!--          </el-form-item>-->
+        <!--        </el-col>-->
+        <el-col :span="6">
+          <el-form-item label="所属工厂" prop="factoryId">
+            <el-select
+              filterable
+              placeholder="请选择"
+              v-model="params.factoryId"
+              clearable
+              class="w100"
+            >
+              <el-option
+                v-for="item in factoryList"
+                :key="item.id"
+                :label="item.name"
+                :value="item.id"
+              ></el-option>
+            </el-select> </el-form-item
+        ></el-col>
+        <el-col :span="6">
+          <div class="ele-form-actions">
+            <el-button
+              type="primary"
+              icon="el-icon-search"
+              class="ele-btn-icon"
+              @click="search"
+              size="small"
+            >
+              查询
+            </el-button>
+            <el-button
+              @click="reset"
+              icon="el-icon-refresh-left"
+              size="small"
+              type="primary"
+              >重置</el-button
+            >
+          </div>
+        </el-col>
+      </el-row>
+    </el-form>
+  </div>
+</template>
+<script>
+  import { getFactoryarea } from '@/api/main';
+  import { getWarehouseList } from '@/api/wms';
+  export default {
+    data() {
+      // 默认表单数据
+      const defaultParams = {
+        batchNo: '',
+        informationName: '',
+        warehouseId: '',
+        manualBatchNo: '',
+        barcodes: '',
+        engrave: '',
+        clientCode: '',
+        modelType: '',
+        specification: '',
+        code: '',
+        categoryCode: '',
+        assetName: '',
+        qualityResult: '',
+        factoryId: '',
+        dimension: '2'
+      };
+      return {
+        // 表单数据
+        params: { ...defaultParams },
+
+        qualityResultOption: [
+          {
+            value: 0,
+            label: '合格'
+          },
+          {
+            value: 1,
+            label: '不合格'
+          }
+        ],
+        qualityStatusOption: [
+          {
+            value: 1,
+            label: '已质检'
+          },
+          {
+            value: 0,
+            label: '未质检'
+          }
+        ],
+        warehouseList: [],
+        dimension: '1',
+        factoryList: []
+      };
+    },
+    props: {
+      type: {
+        type: String,
+        default: ''
+      }
+    },
+    watch: {
+      type(val) {
+        if (val === 'warehouse') {
+          this.dimension = '3';
+        } else {
+          this.dimension = '1';
+        }
+      }
+    },
+    computed: {
+      // 是否开启响应式布局
+      styleResponsive() {
+        return this.$store.state.theme.styleResponsive;
+      }
+    },
+    created() {
+      this.getArguInfo();
+      this.getFactoryList();
+    },
+    methods: {
+      //获取工厂列表
+      async getFactoryList() {
+        const res = await getFactoryarea({
+          pageNum: 1,
+          size: 9999,
+          type: 1
+        });
+        this.factoryList = res.list;
+      },
+      //搜索数据源
+      async getArguInfo() {
+        const { data } = await getWarehouseList();
+        this.warehouseList = data;
+      },
+      /* 搜索 */
+      search() {
+        console.log(this.current);
+        this.$emit('search', {
+          ...this.params
+        });
+      },
+      /*  重置 */
+      reset() {
+        this.params = { ...this.defaultParams };
+        this.search();
+      }
+    }
+  };
+</script>
+<style lang="scss" scoped>
+  ::v-deep .el-row {
+    display: flex;
+    flex-wrap: wrap;
+  }
+  .ele-form-actions {
+    float: right;
+  }
+</style>

+ 412 - 0
src/views/saleManage/saleOrder/invoice/components/selectStockLedger/selectStockLedgerDialog.vue

@@ -0,0 +1,412 @@
+<template>
+  <el-dialog
+    title="库存台账"
+    :visible.sync="visible"
+    :before-close="handleClose"
+    :close-on-click-modal="false"
+    top="5vh"
+    :close-on-press-escape="false"
+    append-to-body
+    width="70%"
+    :maxable="true"
+    :resizable="true"
+  
+  >
+    <el-card shadow="never">
+      <item-search @search="reload" ref="refSeavch"></item-search>
+      <ele-split-layout
+        width="244px"
+        allow-collapse
+        :right-style="{ overflow: 'hidden' }"
+      >
+        <div class="ele-border-lighter split-layout-right-content">
+          <el-tree
+            :data="treeList"
+            :props="defaultProps"
+            ref="treeRef"
+            :default-expanded-keys="
+              curNodeData && curNodeData.id ? [curNodeData.id] : []
+            "
+            :highlight-current="true"
+            node-key="id"
+            :expand-on-click-node="false"
+            @node-click="handleNodeClick"
+            ><span class="custom-tree-node" slot-scope="{ node, data }">
+              <span
+                >{{ data.factoryName ? data.factoryName + '-' : ''
+                }}{{ data.name }}</span
+              >
+            </span></el-tree
+          >
+        </div>
+        <!-- 表格 -->
+        <template v-slot:content>
+          <ele-pro-table
+            ref="table"
+            :columns="columns"
+            :datasource="datasource"
+            row-key="id"
+            height="calc(100vh - 480px)"
+            class="dict-table"
+            @cell-click="cellClick"
+            :selection.sync="selection"
+          >
+            <template v-slot:action="{ row }">
+              <el-radio class="radio" v-model="radio" :label="row.id"
+                ><i></i
+              ></el-radio>
+            </template>
+          </ele-pro-table>
+        </template>
+      </ele-split-layout>
+    </el-card>
+
+    <div slot="footer">
+      <el-button type="primary" size="small" @click="selected">选择</el-button>
+      <el-button size="small" @click="handleClose">关闭</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+  import {
+    getOutindetailtwoList,
+    getTreeByGroup,
+    getBatchList,
+    getCategoryPackageDisposition
+  } from '@/api/wms';
+  import ItemSearch from './item-search.vue';
+  import { contactQueryByCategoryIdsAPI } from '@/api/saleManage/contact';
+
+  export default {
+    components: { ItemSearch },
+    props: {
+      /*已选择的产品*/
+      data: {
+        type: Array,
+        default: () => []
+      },
+      /*是否需要供应商*/
+      isSupplier: {
+        type: Boolean,
+        default: false
+      }
+    },
+
+    data() {
+      return {
+        visible: false,
+        currentIndex: null,
+        radio: null,
+        dictList: {},
+        curNodeData: {},
+        selection: [],
+        treeData: [],
+        dimension:'2',
+        defaultProps: {
+          children: 'children',
+          label: 'name'
+        },
+        treeList: [],
+        treeLoading: false,
+        currentItem: {}
+      };
+    },
+    watch: {},
+    computed: {
+      columns() {
+        let data = null;
+        if (this.currentIndex != -1) {
+          data = {
+            action: 'action',
+            slot: 'action',
+            align: 'center',
+            label: '选择',
+            reserveSelection: true
+          };
+        }
+        if (this.currentIndex == -1) {
+          data = {
+            label: '选择',
+            width: 45,
+            type: 'selection',
+            columnKey: 'selection',
+            align: 'center',
+            reserveSelection: true
+          };
+        }
+        return [
+          data,
+
+          {
+            columnKey: 'index',
+            type: 'index',
+            width: 50,
+            align: 'center',
+            showOverflowTooltip: true,
+            label: '序号'
+          },
+          {
+            prop: 'categoryCode',
+            label: '编码',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 110
+          },
+          {
+            prop: 'categoryName',
+            label: '名称',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 110
+          },
+          {
+            prop: 'brandNum',
+            align: 'center',
+            label: '牌号',
+            showOverflowTooltip: true
+          },
+          this.dimension=="2"?
+          {
+            prop: 'batchNo',
+            label: '批次号',
+            align: 'center',
+            showOverflowTooltip: true
+          }:{
+              width:1
+          },
+          {
+            prop: 'categoryModel',
+            label: '型号',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'specification',
+            label: '规格',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'measureQuantity',
+            label: '库存',
+            showOverflowTooltip: true,
+            align: 'center',
+            minWidth: 90
+          },
+          {
+            prop: 'measureUnit',
+            label: '单位',
+            showOverflowTooltip: true,
+            align: 'center',
+            minWidth: 90
+          },
+          {
+            prop: 'weight',
+            label: '重量',
+            showOverflowTooltip: true,
+            align: 'center',
+            minWidth: 90
+          },
+          {
+            prop: 'weightUnit',
+            label: '重量单位',
+            showOverflowTooltip: true,
+            align: 'center',
+            minWidth: 90
+          },
+          {
+            prop: 'packingUnit',
+            align: 'center',
+            label: '包装单位',
+            showOverflowTooltip: true
+          }
+        ];
+      }
+    },
+    methods: {
+      getDictV(code, val) {
+        if (!this.dictList[code]) return '';
+        return this.dictList[code].find((item) => item.value == val)?.label;
+      },
+      open(item, currentIndex) {
+        console.log('item', item);
+        this.currentItem = item;
+        this.currentIndex = currentIndex;
+        this.visible = true;
+        this.getTreeData();
+      },
+      async getTreeData() {
+        try {
+          this.treeLoading = true;
+          let res = null;
+          res = await getTreeByGroup({ type: 2 });
+          this.reload();
+          this.treeLoading = false;
+          if (res?.code === '0') {
+            this.treeList = res.data;
+            return this.treeList;
+          }
+        } catch (error) {
+          console.log(error);
+        }
+        this.treeLoading = false;
+      },
+
+      /* 表格数据源 */
+      async datasource({ page, limit, where, order }) {
+        this.dimension = where.dimension||'2';
+        console.log('where', where);
+        let api = where.dimension == '1' ? getOutindetailtwoList : getBatchList;
+        return await api({
+          ...where,
+          categoryCode: this.currentItem.productCode,
+          ...order,
+          pageNum: page,
+          size: limit
+        });
+      },
+
+      /* 刷新表格 */
+      reload(where) {
+        this.$refs.table.reload({ pageNum: 1, where: where, categoryCode: this.currentItem.productCode });
+      },
+
+      handleNodeClick(data, node) {
+        this.curNodeData = data;
+        if (data.id == this.currentId) {
+          this.$refs.treeRef.setCurrentKey(null);
+          this.reload();
+        } else {
+          this.reload({ categoryLevelId: data.id });
+        }
+      },
+      async getSupplierObj(productList, queryName) {
+        try {
+          let categoryIds = productList
+            .filter((item) => item.id)
+            .map((item) => item.id);
+          if (categoryIds.length > 0) {
+            return await contactQueryByCategoryIdsAPI({
+              categoryIds,
+              isQueryEE: 1
+            });
+          } else {
+            return Promise.resolve({});
+          }
+        } catch (e) {
+          return Promise.resolve({});
+        }
+      },
+      // 单击获取id
+      cellClick(row) {
+        if (this.currentIndex == -1) return;
+        this.current = row;
+        this.radio = row.id;
+      },
+      handleClose() {
+        this.visible = false;
+        this.current = null;
+        this.selection = [];
+        this.$refs.table.clearSelection();
+        this.radio = '';
+      },
+
+      async selected() {
+        if (!this.current && !this.selection.length) {
+          return this.$message.warning('请至少选择一条数据');
+        }
+        if (this.currentIndex != -1) {
+          if (
+            this.data
+              .map((item) => item.productCode)
+              .includes(this.current.code)
+          ) {
+            return this.$message.error('选择的物品已经存在列表了');
+          }
+        } else {
+          if (
+            this.selection.some((item) =>
+              this.data.some((i) => i.productCode == item.code)
+            )
+          ) {
+            return this.$message.error('选择的物品已经存在列表了');
+          }
+        }
+        let list = this.currentIndex == -1 ? this.selection : [this.current];
+        //获取供应商
+        if (this.isSupplier) {
+          let supplierList = await this.getSupplierObj(list);
+          list.forEach((item) => {
+            item['entrustedEnterpriseIdList'] = supplierList[item.id];
+            if (supplierList[item.id]?.length === 1) {
+              item['entrustedEnterpriseId'] = supplierList[item.id][0].id;
+            }
+          });
+        }
+        let idList = list.map((item) => item.categoryId);
+
+        // 获取包装规格
+        let packingSpecification = await getCategoryPackageDisposition({
+          categoryIds: idList
+        });
+
+        list.forEach((item, index) => {
+          item.productName = item.categoryName;
+          item.productCode = item.categoryCode;
+          item.productCategoryName = '';
+          item.productBrand = item.brandNum;
+          item.provenance = item.provenance || [];
+          item['packageDispositionList'] = packingSpecification
+            .filter(
+              (ite) => item.categoryId == ite.categoryId && ite.conversionUnit
+            )
+            .sort((a, b) => a.sort - b.sort);
+          this.$set(list[index], 'totalCount', 1);
+          // item.totalCount = 1;
+          item.measuringUnit = item.measureUnit;
+          //item.weightUnit = item.weightUnit;
+          // item.warehouseNum = item.measureQuantity;
+          item.productId = item.categoryId;
+          item.id = '';
+
+          // item.specification = item.specification;
+        });
+        this.$emit('changeParent', list, this.currentIndex);
+        this.handleClose();
+      }
+    }
+  };
+</script>
+
+<style lang="scss" scoped>
+  .tree_col {
+    border: 1px solid #eee;
+    padding: 10px 0;
+    box-sizing: border-box;
+    height: 500px;
+    overflow: auto;
+  }
+
+  .table_col {
+    padding-left: 10px;
+
+    ::v-deep .el-table th.el-table__cell {
+      background: #f2f2f2;
+    }
+  }
+
+  .pagination {
+    text-align: right;
+    padding: 10px 0;
+  }
+
+  .btns {
+    text-align: center;
+    padding: 10px 0;
+  }
+
+  .topsearch {
+    margin-bottom: 15px;
+  }
+</style>