From 07c0b081ea49da450d397e441608c0add375174d Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期日, 20 九月 2026 20:22:52 +0800
Subject: [PATCH] 新疆-融邦 1.油品出库要绑定销售订单

---
 src/views/inventoryManagement/dispatchLog/Record.vue                |    5 
 src/views/salesManagement/deliveryLedger/index.vue                  |    6 +
 src/api/inventoryManagement/stockOut.js                             |    9 +
 src/views/inventoryManagement/stockManagement/OilStockOutDialog.vue |  288 +++++++++++++++++++++++++++++++++++++++++++----
 4 files changed, 279 insertions(+), 29 deletions(-)

diff --git a/src/api/inventoryManagement/stockOut.js b/src/api/inventoryManagement/stockOut.js
index b70b7bc..7320ec3 100644
--- a/src/api/inventoryManagement/stockOut.js
+++ b/src/api/inventoryManagement/stockOut.js
@@ -45,6 +45,15 @@
     });
 }
 
+// 娌瑰搧鍑哄簱琛ㄥ崟-鍙粦瀹氱殑閿�鍞彴璐︿笅鎷夛紙鍒嗛〉鍙傛暟涓庡嚭搴撳彴璐﹀垪琛ㄤ竴鑷达級
+export const bindableSalesLedger = (params) => {
+    return request({
+        url: "/stockOutRecord/bindableSalesLedger",
+        method: "get",
+        params,
+    });
+}
+
 // 娌瑰搧鍑哄簱鍙敤閲忔煡璇紙涓婃鍓╀綑娌归噺 + 褰撳墠搴撳瓨锛�
 export const getOilOutAvailableQty = (params) => {
     return request({
diff --git a/src/views/inventoryManagement/dispatchLog/Record.vue b/src/views/inventoryManagement/dispatchLog/Record.vue
index 5658104..feb7bf5 100644
--- a/src/views/inventoryManagement/dispatchLog/Record.vue
+++ b/src/views/inventoryManagement/dispatchLog/Record.vue
@@ -165,6 +165,11 @@
                            prop="shippingNo"
                            min-width="140"
                            show-overflow-tooltip />
+          <!-- 缁戝畾浜嗛攢鍞彴璐︾殑鍑哄簱璁板綍鎵嶆湁鍊硷紝鏈粦瀹氫负绌� -->
+          <el-table-column label="閿�鍞悎鍚屽彿"
+                           prop="salesContractNo"
+                           min-width="160"
+                           show-overflow-tooltip />
           <el-table-column label="鏉ユ簮"
                            prop="recordType"
                            min-width="90"
diff --git a/src/views/inventoryManagement/stockManagement/OilStockOutDialog.vue b/src/views/inventoryManagement/stockManagement/OilStockOutDialog.vue
index 0b6244a..a3c01df 100644
--- a/src/views/inventoryManagement/stockManagement/OilStockOutDialog.vue
+++ b/src/views/inventoryManagement/stockManagement/OilStockOutDialog.vue
@@ -10,38 +10,78 @@
                ref="formRef">
         <el-row :gutter="30">
           <el-col :span="24">
-            <el-form-item label="浜у搧鍚嶇О"
-                          prop="productModelId"
-                          :rules="[
+            <el-form-item label="缁戝畾閿�鍞彴璐�">
+              <el-select v-model="formState.salesLedgerId"
+                         placeholder="涓嶇粦瀹氬垯鎸夊師鏈夊嚭搴撴祦绋�"
+                         clearable
+                         filterable
+                         remote
+                         :remote-method="searchSalesLedger"
+                         :loading="salesLedgerLoading"
+                         style="width: 100%"
+                         @change="handleSalesLedgerChange">
+                <el-option v-for="item in salesLedgerOptions"
+                           :key="item.id"
+                           :label="salesLedgerLabel(item)"
+                           :value="item.id" />
+              </el-select>
+              <div v-if="boundLedgerRow"
+                   class="bound-ledger-tip">
+                宸茬粦瀹氾細{{ salesLedgerLabel(boundLedgerRow) }}锛涘嚭搴撶被鍒�佸鎴穥{ isStorageBound ? '銆佹补搴�/鍌ㄧ綈' : '' }}浠ュ彴璐︿负鍑�
+              </div>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <template v-if="!isStorageBound">
+          <el-row :gutter="30">
+            <el-col :span="24">
+              <el-form-item label="浜у搧鍚嶇О"
+                            prop="productModelId"
+                            :rules="[
                 {
                 required: true,
                 message: '璇烽�夋嫨浜у搧',
                 trigger: 'change',
               }
             ]">
-              <el-button type="primary"
-                         @click="showProductSelectDialog = true">
-                {{ formState.productName ? formState.productName : '閫夋嫨浜у搧' }}
-              </el-button>
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row :gutter="30">
-          <el-col :span="12">
-            <el-form-item label="瑙勬牸"
-                          prop="model">
-              <el-input v-model="formState.model"
-                        disabled />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="鍗曚綅"
-                          prop="unit">
-              <el-input v-model="formState.unit"
-                        disabled />
-            </el-form-item>
-          </el-col>
-        </el-row>
+                <!-- 缁戝畾鍗栨补鍙拌处鍚庯紝浜у搧鍙兘浠庤鍙拌处鏄庣粏閲岄�夛紙浠e偍鍙拌处娌℃湁鏄庣粏锛� -->
+                <el-select v-if="hasBoundLedger"
+                           v-model="formState.productModelId"
+                           placeholder="璇烽�夋嫨鍙拌处浜у搧"
+                           filterable
+                           clearable
+                           style="width: 100%"
+                           @change="handleLedgerProductChange">
+                  <el-option v-for="(item, index) in ledgerProductOptions"
+                             :key="`${item.productModelId}-${index}`"
+                             :label="ledgerProductLabel(item)"
+                             :value="item.productModelId" />
+                </el-select>
+                <el-button v-else
+                           type="primary"
+                           @click="showProductSelectDialog = true">
+                  {{ formState.productName ? formState.productName : '閫夋嫨浜у搧' }}
+                </el-button>
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="30">
+            <el-col :span="12">
+              <el-form-item label="瑙勬牸"
+                            prop="model">
+                <el-input v-model="formState.model"
+                          disabled />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="鍗曚綅"
+                            prop="unit">
+                <el-input v-model="formState.unit"
+                          disabled />
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </template>
         <div class="form-section-title">鍑哄簱淇℃伅</div>
         <el-row :gutter="30">
           <el-col :span="8">
@@ -51,6 +91,7 @@
               <el-select v-model="formState.outCategory"
                          placeholder="璇烽�夋嫨"
                          clearable
+                         :disabled="hasBoundLedger"
                          style="width: 100%">
                 <el-option label="瀹㈠瓨"
                            value="瀹㈠瓨" />
@@ -86,6 +127,7 @@
               <el-select v-model="formState.oilDepotId"
                          placeholder="璇烽�夋嫨"
                          clearable
+                         :disabled="isStorageBound"
                          popper-class="oil-depot-select-popper"
                          style="width: 100%"
                          @change="depotChange">
@@ -112,6 +154,7 @@
               <el-select v-model="formState.tankId"
                          placeholder="璇烽�夋嫨"
                          clearable
+                         :disabled="isStorageBound"
                          popper-class="tank-select-popper"
                          style="width: 100%"
                          @change="tankChange">
@@ -148,7 +191,7 @@
           <el-col :span="12">
             <el-form-item label="娌瑰搧"
                           prop="oilProduct"
-                          :rules="[{ required: true, message: '璇烽�夋嫨娌瑰搧', trigger: 'change' }]">
+                          :rules="isStorageBound ? [] : [{ required: true, message: '璇烽�夋嫨娌瑰搧', trigger: 'change' }]">
               <el-input :model-value="formState.oilProduct"
                         readonly
                         placeholder="--" />
@@ -170,6 +213,7 @@
                          placeholder="璇烽�夋嫨"
                          clearable
                          filterable
+                         :disabled="hasBoundLedger"
                          popper-class="customer-select-popper"
                          style="width: 100%"
                          @change="customerChange">
@@ -447,7 +491,12 @@
     addOilOutRecordOnly,
     getStockInventoryBatchNoQty,
   } from "@/api/inventoryManagement/stockInventory.js";
-  import { getOilOutAvailableQty } from "@/api/inventoryManagement/stockOut.js";
+  import {
+    getOilOutAvailableQty,
+    bindableSalesLedger,
+  } from "@/api/inventoryManagement/stockOut.js";
+  import { productList } from "@/api/salesManagement/salesLedger.js";
+  import { productModelList } from "@/api/basicData/productModel";
   import { allOilDepot } from "@/api/basicData/oilDepot.js";
   import { allTankInfo } from "@/api/basicData/tankInfo.js";
   import { allVehicle } from "@/api/basicData/vehicle.js";
@@ -463,6 +512,8 @@
   const emit = defineEmits(["update:visible", "completed"]);
 
   const buildEmptyForm = () => ({
+    // 缁戝畾鐨勯攢鍞彴璐� id锛涗负绌鸿〃绀轰笉缁戝畾锛岃蛋鍘熸湁鍑哄簱娴佺▼
+    salesLedgerId: undefined,
     productId: undefined,
     productModelId: undefined,
     productName: "",
@@ -507,6 +558,17 @@
   const batchNoOptions = ref([]);
   // 閿�鍞被鍑哄簱鐨勫彲鐢ㄩ噺鎻愮ず锛堜笂娆″墿浣� + 褰撳墠搴撳瓨锛�
   const availableQty = ref(null);
+  // 缁戝畾閿�鍞彴璐︼細涓嬫媺鍊欓�夈�佸綋鍓嶇粦瀹氳銆佸彴璐︽槑缁嗛檺瀹氬嚭鐨勪骇鍝�
+  const salesLedgerOptions = ref([]);
+  const salesLedgerLoading = ref(false);
+  const boundLedgerRow = ref(null);
+  const ledgerProductOptions = ref([]);
+  // 鍙拌处鏄庣粏琛屽彧鏈� productModelId锛屼骇鍝佸悕绉�/鍨嬪彿/鍗曚綅瑕佸洖浜у搧瑙勬牸琛ㄥ彇
+  const productModelCache = ref(null);
+
+  const hasBoundLedger = computed(() => !!formState.value.salesLedgerId);
+  // 缁戜唬鍌ㄥ彴璐︼細涓嶇敤閫変骇鍝侊紝娌瑰簱/鍌ㄧ綈鐢卞彴璐﹀甫鍏�
+  const isStorageBound = computed(() => boundLedgerRow.value?.ledgerType === "浠e偍");
 
   const fetchBatchNoOptions = () => {
     batchNoOptions.value = [];
@@ -607,6 +669,8 @@
   const closeModal = () => {
     proxy.resetForm("formRef");
     formState.value = buildEmptyForm();
+    boundLedgerRow.value = null;
+    ledgerProductOptions.value = [];
     isShow.value = false;
   };
 
@@ -679,10 +743,154 @@
     }
   };
 
+  const salesLedgerLabel = item =>
+    [item.salesContractNo, item.customerName, item.projectName]
+      .filter(Boolean)
+      .join(" / ");
+
+  // 鎼滅储妗嗗彧鏈変竴涓紝鑰屽悗绔笁涓弬鏁版槸 AND 鍏崇郴锛屾墍浠ヤ緷娆℃寜 鍚堝悓鍙� 鈫� 瀹㈡埛鍚� 鈫� 椤圭洰鍚� 璇曪紝鍛戒腑鍗虫
+  const LEDGER_SEARCH_FIELDS = ["salesContractNo", "customerName", "projectName"];
+
+  const queryLedger = params =>
+    bindableSalesLedger({ current: 1, size: 50, ...params }).then(
+      res => res.data?.records || []
+    );
+
+  // 缁戝畾涓嬫媺锛氬崠娌瑰拰浠e偍閮戒細杩斿洖锛屼笉鍋氱被鍨嬭繃婊わ紝鐢辩敤鎴疯嚜宸遍��
+  const fetchSalesLedgerOptions = async keyword => {
+    salesLedgerLoading.value = true;
+    try {
+      let records = [];
+      if (keyword) {
+        for (const field of LEDGER_SEARCH_FIELDS) {
+          records = await queryLedger({ [field]: keyword });
+          if (records.length) {
+            break;
+          }
+        }
+      } else {
+        records = await queryLedger({});
+      }
+      // 鎼滅储鍚庡凡閫変腑鐨勫彴璐﹀彲鑳戒笉鍦ㄧ粨鏋滈噷锛岃ˉ涓婇伩鍏嶄笅鎷夋鍙樉绀轰竴涓� id
+      if (
+        boundLedgerRow.value &&
+        !records.some(item => item.id === boundLedgerRow.value.id)
+      ) {
+        records.unshift(boundLedgerRow.value);
+      }
+      salesLedgerOptions.value = records;
+    } catch (e) {
+      salesLedgerOptions.value = [];
+    } finally {
+      salesLedgerLoading.value = false;
+    }
+  };
+
+  const searchSalesLedger = keyword =>
+    fetchSalesLedgerOptions(String(keyword || "").trim());
+
+  // 浜у搧瑙勬牸琛ㄥ叏閲忓彇涓�娆″苟缂撳瓨锛氬彴璐︽槑缁嗗彧鏈� productModelId锛屽悕绉�/鍨嬪彿/鍗曚綅寰椾粠杩欓噷琛�
+  const getProductModels = () => {
+    if (productModelCache.value) {
+      return Promise.resolve(productModelCache.value);
+    }
+    return productModelList({ current: 1, size: 1000 })
+      .then(res => {
+        const list = res?.records || res?.data?.records || res.data || [];
+        productModelCache.value = Array.isArray(list) ? list : [];
+        return productModelCache.value;
+      })
+      .catch(() => []);
+  };
+
+  // 鍙拌处鏄庣粏 鈫� 浜у搧涓嬫媺鍊欓�夛紱浠e偍鍙拌处娌℃湁鏄庣粏锛屼笉浼氳皟鍒拌繖閲�
+  const fetchLedgerProducts = ledgerId => {
+    ledgerProductOptions.value = [];
+    return Promise.all([
+      productList({ salesLedgerId: ledgerId, type: 1 }),
+      getProductModels(),
+    ]).then(([ledgerRes, models]) => {
+      const rows = Array.isArray(ledgerRes?.data) ? ledgerRes.data : [];
+      const modelMap = new Map((models || []).map(item => [String(item.id), item]));
+      ledgerProductOptions.value = rows.map(row => {
+        const model = modelMap.get(String(row.productModelId)) || {};
+        return {
+          productId: row.productId ?? model.productId,
+          productModelId: row.productModelId ?? model.id,
+          productName:
+            model.productName || row.productCategory || row.specificationModel || "",
+          model: model.model || row.specificationModel || "",
+          unit: model.unit || row.unit || "",
+        };
+      });
+    });
+  };
+
+  const ledgerProductLabel = item =>
+    [item.productName, item.model].filter(Boolean).join(" / ");
+
+  const clearProduct = () => {
+    formState.value.productId = undefined;
+    formState.value.productModelId = undefined;
+    formState.value.productName = "";
+    formState.value.model = "";
+    formState.value.unit = "";
+    formState.value.oilProduct = undefined;
+    formState.value.batchNo = undefined;
+    batchNoOptions.value = [];
+  };
+
+  const handleLedgerProductChange = productModelId => {
+    const item = ledgerProductOptions.value.find(
+      row => row.productModelId === productModelId
+    );
+    if (!item) {
+      clearProduct();
+      return;
+    }
+    formState.value.productId = item.productId;
+    formState.value.productName = item.productName;
+    formState.value.model = item.model;
+    formState.value.unit = item.unit;
+    formState.value.oilProduct = item.productName || item.model || undefined;
+    formState.value.batchNo = undefined;
+    fetchBatchNoOptions();
+    proxy.$refs["formRef"]?.validateField("productModelId");
+  };
+
+  const handleSalesLedgerChange = id => {
+    const row = salesLedgerOptions.value.find(item => item.id === id);
+    // 娓呯┖閫夋嫨鏃� el-select 浼氱粰绌轰覆锛屽綊涓�鍖栨垚 undefined锛岄伩鍏嶆妸绌轰覆褰撳彴璐� id 浼犲悗绔�
+    if (!row) {
+      formState.value.salesLedgerId = undefined;
+    }
+    boundLedgerRow.value = row || null;
+    ledgerProductOptions.value = [];
+    clearProduct();
+    if (!row) {
+      // 鍙栨秷缁戝畾锛氬嚭搴撶被鍒�佸鎴锋仮澶嶄负鎵嬮��
+      return;
+    }
+    // 鍑哄簱绫诲埆涓庡鎴蜂互鍙拌处涓哄噯锛堝悗绔篃浼氬己鍒惰鐩栵級锛屽墠绔悓姝ュ睍绀哄苟缃伆
+    formState.value.outCategory = row.ledgerType === "浠e偍" ? "浠e偍" : "閿�鍞�";
+    customerChange(row.customerId);
+    formState.value.customerName =
+      row.customerName || formState.value.customerName || "";
+    if (row.ledgerType === "浠e偍") {
+      formState.value.oilDepotId = row.oilDepotId;
+      formState.value.oilDepotName = row.oilDepotName || "";
+      formState.value.tankId = row.tankId;
+      formState.value.tankNo = row.tankNo || "";
+      return;
+    }
+    fetchLedgerProducts(row.id);
+  };
+
   const handleSubmit = () => {
     proxy.$refs["formRef"].validate(valid => {
       if (valid) {
-        if (!formState.value.productModelId) {
+        // 缁戜唬鍌ㄥ彴璐︿笉鐢ㄩ�変骇鍝侊紙浠e偍鍙拌处娌℃湁浜у搧鏄庣粏锛�
+        if (!formState.value.productModelId && !isStorageBound.value) {
           proxy.$modal.msgError("璇烽�夋嫨浜у搧");
           return;
         }
@@ -704,7 +912,18 @@
     });
   };
 
+  // 姣忔鎵撳紑閲嶆柊鎷夊彴璐﹀�欓�夛紝閬垮厤鍦ㄥ埆澶勬柊寤哄彴璐﹀悗鏈〉鐪嬩笉鍒�
+  watch(
+    () => props.visible,
+    visible => {
+      if (visible) {
+        fetchSalesLedgerOptions("");
+      }
+    }
+  );
+
   onMounted(() => {
+    fetchSalesLedgerOptions("");
     allOilDepot().then(res => {
       oilDepotList.value = res?.data || [];
     });
@@ -733,6 +952,17 @@
     border-radius: 3px;
   }
 
+  // 缁戝畾閿�鍞彴璐﹀悗鐨勬彁绀烘潯
+  .bound-ledger-tip {
+    margin-top: 6px;
+    padding: 6px 14px;
+    font-size: 13px;
+    color: #303133;
+    background: rgba(64, 158, 255, 0.08);
+    border-left: 3px solid #409eff;
+    border-radius: 3px;
+  }
+
   // 鍌ㄥ簱鍚嶇О涓嬫媺閫夐」锛氬皬鍗$墖鏍峰紡
   .oil-depot-option {
     padding: 8px 10px;
diff --git a/src/views/salesManagement/deliveryLedger/index.vue b/src/views/salesManagement/deliveryLedger/index.vue
index e8eaeae..4a480ed 100644
--- a/src/views/salesManagement/deliveryLedger/index.vue
+++ b/src/views/salesManagement/deliveryLedger/index.vue
@@ -76,6 +76,12 @@
           prop="shippingNo"
           show-overflow-tooltip
         />
+        <!-- 娌瑰搧鍑哄簱琛屽彇缁戝畾鐨勯攢鍞彴璐﹀悎鍚屽彿锛屾湭缁戝畾涓虹┖ -->
+        <el-table-column
+          label="閿�鍞悎鍚屽彿"
+          prop="salesContractNo"
+          show-overflow-tooltip
+        />
         <el-table-column
           label="瀹㈡埛鍚嶇О"
           prop="customerName"

--
Gitblit v1.9.3