From 4853f7c8e01ccbeae80e0243b632efc360067df8 Mon Sep 17 00:00:00 2001
From: gongchunyi <deslre0381@gmail.com>
Date: 星期一, 20 四月 2026 14:49:20 +0800
Subject: [PATCH] Merge branch 'dev_河南_鹤壁天沐玻璃厂' of http://114.132.189.42:9002/r/product-inventory-management into dev_河南_鹤壁天沐玻璃厂

---
 src/views/salesManagement/salesLedger/index.vue |  298 +++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 277 insertions(+), 21 deletions(-)

diff --git a/src/views/salesManagement/salesLedger/index.vue b/src/views/salesManagement/salesLedger/index.vue
index 208c14c..0de79fd 100644
--- a/src/views/salesManagement/salesLedger/index.vue
+++ b/src/views/salesManagement/salesLedger/index.vue
@@ -221,11 +221,13 @@
                                align="center">
                 <template #default="scope">
                   <el-tag v-if="scope.row.productStockStatus == 1"
+                  type="warning">閮ㄥ垎鍏ュ簱</el-tag>
+                  <el-tag v-else-if="scope.row.productStockStatus == 2"
                           type="success">宸插叆搴�</el-tag>
                   <el-tag v-else-if="scope.row.productStockStatus == 0"
-                          type="info">鏈嚭搴�</el-tag>
+                          type="info">鏈叆搴�</el-tag>
                   <el-tag v-else
-                          type="danger">涓嶈冻</el-tag>
+                          type="danger">鏈叆搴�</el-tag>
                 </template>
               </el-table-column>
               <!-- <el-table-column label="鍙戣揣鐘舵��" width="140" align="center">
@@ -375,7 +377,7 @@
                          show-overflow-tooltip />
         <el-table-column fixed="right"
                          label="鎿嶄綔"
-                         width="200"
+                         width="280"
                          align="center">
           <template #default="scope">
             <el-button link
@@ -389,6 +391,9 @@
             <el-button link
                        type="primary"
                        @click="downLoadFile(scope.row)">闄勪欢</el-button>
+            <el-button link
+                       type="primary"
+                       @click="openLedgerQrDialog(scope.row)">浜岀淮鐮�</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -661,12 +666,12 @@
                                v-model="scope.row.settlePieceArea"
                                :min="0"
                                :step="1"
-                               :precision="10"
+                               :precision="4"
                                style="width: 100%"
                                placeholder="璇疯緭鍏�"
                                clearable
                                @change="() => handleInlineSettleAreaChange(scope.row)" />
-              <span v-else>{{ scope.row.settlePieceArea ?? "" }}</span>
+              <span v-else>{{ scope.row.settlePieceArea ? Number(scope.row.settlePieceArea).toFixed(4) : "" }}</span>
             </template>
           </el-table-column>
           <el-table-column label="鏁伴噺"
@@ -696,10 +701,10 @@
                                v-model="scope.row.actualTotalArea"
                                :min="0"
                                :step="1"
-                               :precision="10"
+                               :precision="4"
                                style="width: 100%"
                                placeholder="鑷姩璁$畻" />
-              <span v-else>{{ scope.row.actualTotalArea ?? "" }}</span>
+              <span v-else>{{ scope.row.actualTotalArea ? Number(scope.row.actualTotalArea).toFixed(4) : "" }}</span>
             </template>
           </el-table-column>
           <el-table-column label="鍚◣鍗曚环(鍏�)"
@@ -1336,8 +1341,8 @@
                           prop="actualPieceArea">
               <el-input-number v-model="productForm.actualPieceArea"
                                :min="0"
-                               :step="0.00001"
-                               :precision="5"
+                               :step="0.0001"
+                               :precision="4"
                                style="width: 100%"
                                placeholder="璇疯緭鍏�"
                                clearable
@@ -1349,8 +1354,8 @@
                           prop="actualTotalArea">
               <el-input-number v-model="productForm.actualTotalArea"
                                :min="0"
-                               :step="0.00001"
-                               :precision="5"
+                               :step="0.0001"
+                               :precision="4"
                                style="width: 100%"
                                placeholder="璇疯緭鍏�"
                                clearable />
@@ -1361,8 +1366,8 @@
                           prop="settlePieceArea">
               <el-input-number v-model="productForm.settlePieceArea"
                                :min="0"
-                               :step="0.00001"
-                               :precision="5"
+                               :step="0.0001"
+                               :precision="4"
                                style="width: 100%"
                                placeholder="璇疯緭鍏�"
                                clearable
@@ -1374,8 +1379,8 @@
                           prop="settleTotalArea">
               <el-input-number v-model="productForm.settleTotalArea"
                                :min="0"
-                               :step="0.00001"
-                               :precision="5"
+                               :step="0.0001"
+                               :precision="4"
                                style="width: 100%"
                                placeholder="璇疯緭鍏�"
                                clearable />
@@ -1386,8 +1391,8 @@
                           prop="settleTotalArea">
               <el-input-number v-model="productForm.settleTotalArea"
                                :min="0"
-                               :step="0.00001"
-                               :precision="5"
+                               :step="0.0001"
+                               :precision="4"
                                style="width: 100%"
                                placeholder="璇疯緭鍏�"
                                clearable />
@@ -1662,6 +1667,11 @@
         <el-table-column prop="quantity"
                          label="鏁伴噺"
                          width="100" />
+        <el-table-column prop="stockedQuantity"
+                         label="宸插叆搴撴暟閲�"
+                         width="120"
+                         align="center"
+                         show-overflow-tooltip />
         <el-table-column prop="floorCode"
                          label="妤煎眰缂栧彿"
                          show-overflow-tooltip />
@@ -1674,6 +1684,24 @@
           纭鍏ュ簱
         </el-button>
       </template>
+    </el-dialog>
+    <el-dialog v-model="ledgerQrDialogVisible"
+               title="閿�鍞鍗曚簩缁寸爜"
+               width="360px"
+               draggable
+               :close-on-click-modal="false">
+      <div class="ledger-qr-dialog">
+        <img v-if="ledgerQrCompositeUrl"
+             :src="ledgerQrCompositeUrl"
+             alt="閿�鍞鍗曚簩缁寸爜"
+             class="ledger-qr-composite-img" />
+        <el-button type="primary"
+                   class="ledger-qr-save-btn"
+                   :disabled="!ledgerQrCompositeUrl"
+                   @click="downloadLedgerQrCode">
+          淇濆瓨鍥剧墖
+        </el-button>
+      </div>
     </el-dialog>
   </div>
 </template>
@@ -1722,6 +1750,7 @@
   import { printSalesOrder } from "./components/salesOrderPrint.js";
   import { printSalesDeliveryNote } from "./components/salesDeliveryPrint.js";
   import { printSalesLabel } from "./components/salesLabelPrint.js";
+  import QRCode from "qrcode";
   // import { salesLedgerProductSetProcessFlowConfig } from "@/api/salesManagement/salesProcessFlowConfig.js";
 
   const userStore = useUserStore();
@@ -1756,6 +1785,113 @@
   const selectedStockProductIds = ref([]);
   const stockLoading = ref(false);
   const currentStockLedgerId = ref(null);
+
+  const ledgerQrDialogVisible = ref(false);
+  const ledgerQrCompositeUrl = ref("");
+  const ledgerQrDownloadBaseName = ref("");
+
+  const sanitizeLedgerQrFilename = s =>
+    String(s)
+      .replace(/[\\/:*?"<>|]/g, "_")
+      .trim()
+      .slice(0, 80) || "ledger";
+
+  const wrapLedgerQrTextLines = (ctx, text, maxWidth) => {
+    const chars = [...text];
+    const lines = [];
+    let line = "";
+    for (const ch of chars) {
+      const test = line + ch;
+      if (ctx.measureText(test).width > maxWidth && line.length) {
+        lines.push(line);
+        line = ch;
+      } else {
+        line = test;
+      }
+    }
+    if (line) lines.push(line);
+    return lines;
+  };
+
+  const buildLedgerQrCompositeDataUrl = row =>
+    new Promise((resolve, reject) => {
+      const payload = JSON.stringify({
+        id: row.id,
+        salesContractNo: (row.salesContractNo ?? "").trim(),
+        type: "XS",
+      });
+      QRCode.toDataURL(payload, { width: 220, margin: 2 })
+        .then(qrDataUrl => {
+          const contract = (row.salesContractNo ?? "").trim() || "鈥�";
+          const img = new Image();
+          img.onload = () => {
+            const QR_SIZE = 220;
+            const padTop = 16;
+            const gapAfterQr = 14;
+            const bottomPad = 48;
+            const horizontalPad = 20;
+            const lineHeight = 20;
+            const fontSize = 14;
+            const label = `閿�鍞悎鍚屽彿锛�${contract}`;
+
+            const canvas = document.createElement("canvas");
+            const ctx = canvas.getContext("2d");
+            canvas.width = Math.max(QR_SIZE + horizontalPad * 2, 280);
+            ctx.font = `${fontSize}px "Microsoft YaHei", "PingFang SC", sans-serif`;
+            const lines = wrapLedgerQrTextLines(ctx, label, canvas.width - horizontalPad * 2);
+            const textBlockHeight = lines.length * lineHeight;
+            canvas.height = padTop + QR_SIZE + gapAfterQr + textBlockHeight + bottomPad;
+
+            ctx.fillStyle = "#ffffff";
+            ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+            const qrX = (canvas.width - QR_SIZE) / 2;
+            ctx.drawImage(img, qrX, padTop, QR_SIZE, QR_SIZE);
+
+            ctx.fillStyle = "#606266";
+            ctx.font = `${fontSize}px "Microsoft YaHei", "PingFang SC", sans-serif`;
+            ctx.textAlign = "center";
+            ctx.textBaseline = "top";
+            const textY0 = padTop + QR_SIZE + gapAfterQr;
+            lines.forEach((ln, i) => {
+              ctx.fillText(ln, canvas.width / 2, textY0 + i * lineHeight);
+            });
+
+            const baseName = sanitizeLedgerQrFilename(
+              contract !== "鈥�" ? contract : String(row.id)
+            );
+            resolve({ dataUrl: canvas.toDataURL("image/png"), baseName });
+          };
+          img.onerror = () => reject(new Error("浜岀淮鐮佸浘鐗囧姞杞藉け璐�"));
+          img.src = qrDataUrl;
+        })
+        .catch(reject);
+    });
+
+  const openLedgerQrDialog = async row => {
+    if (row?.id === undefined || row?.id === null || row?.id === "") {
+      ElMessage.warning("鏃犳硶鐢熸垚浜岀淮鐮侊細缂哄皯鍙拌处 ID");
+      return;
+    }
+    ledgerQrCompositeUrl.value = "";
+    ledgerQrDownloadBaseName.value = "";
+    try {
+      const { dataUrl, baseName } = await buildLedgerQrCompositeDataUrl(row);
+      ledgerQrCompositeUrl.value = dataUrl;
+      ledgerQrDownloadBaseName.value = baseName;
+      ledgerQrDialogVisible.value = true;
+    } catch {
+      ElMessage.error("浜岀淮鐮佺敓鎴愬け璐�");
+    }
+  };
+
+  const downloadLedgerQrCode = () => {
+    if (!ledgerQrCompositeUrl.value) return;
+    const a = document.createElement("a");
+    a.href = ledgerQrCompositeUrl.value;
+    a.download = `閿�鍞彴璐︿簩缁寸爜-${ledgerQrDownloadBaseName.value}.png`;
+    a.click();
+  };
 
   // 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
   const operationType = ref("");
@@ -2705,7 +2841,7 @@
       const res = await productList({ salesLedgerId: id, type: 1 });
       stockProductList.value = [];
       stockProductList.value =
-        res.data.filter(item => item.productStockStatus == 0) || [];
+        res.data.filter(item => item.productStockStatus == 0 || item.productStockStatus == 1) || [];
     } catch (e) {
       proxy?.$modal?.msgError?.("鑾峰彇浜у搧鍒楄〃澶辫触");
     } finally {
@@ -3644,7 +3780,35 @@
       } else {
         const res = await getProcessCard(selectedId);
         const processCardData = res?.data ?? {};
-        printFinishedProcessCard(processCardData);
+        // 琛ラ綈浜岀淮鐮佹墍闇�鐨勫彴璐︽爣璇嗭紙鍚庣鏁版嵁鏈夋椂涓嶅甫 id锛�
+        if (processCardData && typeof processCardData === "object") {
+          processCardData.salesLedgerId = processCardData.salesLedgerId ?? selectedId;
+          processCardData.salesContractNo =
+            (processCardData.salesContractNo ?? "").trim() ||
+            String(selectedRow?.salesContractNo ?? "").trim();
+        }
+        const routeNodes = processCardData?.routeNodes;
+        const isProcessRouteEmpty =
+          !Array.isArray(routeNodes) || routeNodes.length === 0;
+        if (isProcessRouteEmpty) {
+          proxy.$modal.closeLoading();
+          try {
+            await ElMessageBox.confirm(
+              "褰撳墠璁㈠崟鏈粦瀹氬伐鑹鸿矾绾夸篃娌℃湁璁剧疆榛樿鐨勫伐鑹鸿矾绾匡紝鏄惁浠嶈鎵撳嵃锛�",
+              "鎻愮ず",
+              {
+                confirmButtonText: "鎵撳嵃",
+                cancelButtonText: "鍙栨秷",
+                type: "warning",
+              }
+            );
+          } catch {
+            return;
+          }
+          await printFinishedProcessCard(processCardData);
+        } else {
+          await printFinishedProcessCard(processCardData);
+        }
       }
     } catch (error) {
       console.error(
@@ -4060,6 +4224,40 @@
     return statusStr === "寰呭彂璐�" || statusStr === "瀹℃牳鎷掔粷";
   };
 
+  const getLedgerDisplayName = ledger =>
+    String(ledger?.salesContractNo || "").trim() ||
+    String(ledger?.projectName || "").trim() ||
+    `ID:${ledger?.id ?? "-"}`;
+
+  const validateLedgersStockedBeforeDelivery = async ledgers => {
+    const invalidLedgers = [];
+    for (const ledger of ledgers || []) {
+      const ledgerId = ledger?.id;
+      const ledgerName = getLedgerDisplayName(ledger);
+      if (!ledgerId) {
+        invalidLedgers.push(`${ledgerName}(缂哄皯鍙拌处ID)`);
+        continue;
+      }
+      let products = [];
+      try {
+        const res = await productList({ salesLedgerId: ledgerId, type: 1 });
+        products = Array.isArray(res?.data) ? res.data : [];
+      } catch (e) {
+        invalidLedgers.push(`${ledgerName}(鏄庣粏鍔犺浇澶辫触)`);
+        continue;
+      }
+      const unstockedProducts = products.filter(
+        item => Number(item?.productStockStatus) !== 2
+      );
+      if (unstockedProducts.length > 0) {
+        invalidLedgers.push(
+          `${ledgerName}(鏈叏閮ㄥ叆搴�${unstockedProducts.length}鏉�)`
+        );
+      }
+    }
+    return invalidLedgers;
+  };
+
   const handleBulkDelivery = async () => {
     if (selectedRows.value.length === 0) {
       proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
@@ -4068,7 +4266,7 @@
 
     // 鍙厑璁搞�愭湭鍙戣揣/瀹℃壒澶辫触銆戣繘鍏ュ彂璐ф祦绋�
     const statusItem = selectedRows.value[0].deliveryStatus;
-    const isTrue = true;
+    let isTrue = true;
     selectedRows.value.forEach(row => {
       if (row.deliveryStatus != 1 && row.deliveryStatus != 3) {
         proxy.$modal.msgWarning("浠呮湭鍙戣揣鎴栧鎵瑰け璐ョ殑鍙拌处鍙互鍙戣揣");
@@ -4082,6 +4280,29 @@
       }
     });
     if (!isTrue) {
+      return;
+    }
+
+    proxy.$modal.loading("姝e湪鏍¢獙鏄庣粏鍏ュ簱鐘舵�侊紝璇风◢鍊�...");
+    const invalidLedgers = await validateLedgersStockedBeforeDelivery(
+      selectedRows.value
+    );
+    proxy.$modal.closeLoading();
+    if (invalidLedgers.length > 0) {
+      try {
+        await ElMessageBox.alert(
+          `浠ヤ笅閿�鍞彴璐﹀瓨鍦ㄦ湭鍏ㄩ儴鍏ュ簱鐨勬槑缁嗭紝鏆備笉鍙彂璐э細\n${invalidLedgers.join(
+            "\n"
+          )}`,
+          "鎻愮ず",
+          {
+            type: "warning",
+            confirmButtonText: "鐭ラ亾浜�",
+          }
+        );
+      } catch {
+        /* 鍏抽棴寮圭獥 */
+      }
       return;
     }
 
@@ -4201,11 +4422,30 @@
   };
 
   // 鎵撳紑鍙戣揣寮规锛堝崟鏉★級
-  const openDeliveryForm = row => {
+  const openDeliveryForm = async row => {
     // 鍙厑璁搞�愭湭鍙戣揣/瀹℃壒澶辫触銆戝彂璐э紱宸插彂璐�/瀹℃壒涓笉鍏佽
     const status = Number(row.deliveryStatus);
     if (status !== 1 && status !== 3) {
       proxy.$modal.msgWarning("鍙湁鍙戣揣鐘舵�佷负鏈彂璐ф垨瀹℃壒澶辫触鐨勮褰曟墠鍙互鍙戣揣");
+      return;
+    }
+
+    proxy.$modal.loading("姝e湪鏍¢獙鏄庣粏鍏ュ簱鐘舵�侊紝璇风◢鍊�...");
+    const invalidLedgers = await validateLedgersStockedBeforeDelivery([row]);
+    proxy.$modal.closeLoading();
+    if (invalidLedgers.length > 0) {
+      try {
+        await ElMessageBox.alert(
+          `褰撳墠閿�鍞彴璐﹀瓨鍦ㄦ湭鍏ㄩ儴鍏ュ簱鐨勬槑缁嗭紝鏆備笉鍙彂璐э細\n${invalidLedgers[0]}`,
+          "鎻愮ず",
+          {
+            type: "warning",
+            confirmButtonText: "鐭ラ亾浜�",
+          }
+        );
+      } catch {
+        /* 鍏抽棴寮圭獥 */
+      }
       return;
     }
 
@@ -4356,4 +4596,20 @@
     justify-content: space-between;
     margin-bottom: 10px;
   }
+
+  .ledger-qr-dialog {
+    text-align: center;
+    padding-bottom: 8px;
+  }
+
+  .ledger-qr-composite-img {
+    max-width: 100%;
+    height: auto;
+    display: block;
+    margin: 0 auto 28px;
+  }
+
+  .ledger-qr-save-btn {
+    margin-bottom: 12px;
+  }
 </style>

--
Gitblit v1.9.3