From 9646ba4b026e4dbee0c0d5ac3d52b428bfef7b29 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期五, 28 八月 2026 10:49:44 +0800
Subject: [PATCH] 新疆-新聚 1.销售台账中的销售合同按框架合同处理 2.设备台账需按车间/区域汇总设备,车间/位置可自行新增,且支持子父级递归 3.计量器具台账支持通过 Excel 文件批量新增数据 4.设备保养定时任务新增“年度保养”任务频率

---
 src/views/salesManagement/salesLedger/index.vue |  284 ++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 262 insertions(+), 22 deletions(-)

diff --git a/src/views/salesManagement/salesLedger/index.vue b/src/views/salesManagement/salesLedger/index.vue
index 610c17c..ccf90b4 100644
--- a/src/views/salesManagement/salesLedger/index.vue
+++ b/src/views/salesManagement/salesLedger/index.vue
@@ -177,7 +177,7 @@
                   <el-button
                     link
                     type="primary"
-                    :disabled="!canShip(scope.row)"
+                    :disabled="!canShip(scope.row) || props.row.contractType === 2"
                     @click="openDeliveryForm(scope.row)"
                   >
                     鍙戣揣
@@ -261,7 +261,7 @@
           width="200"
           show-overflow-tooltip
         />
-        <el-table-column fixed="right" label="鎿嶄綔" width="220" align="center">
+        <el-table-column fixed="right" label="鎿嶄綔" width="320" align="center">
           <template #default="scope">
             <el-button link type="primary" @click="openForm('view', scope.row)"
               >璇︽儏
@@ -276,6 +276,20 @@
                 !canEditLedger(scope.row)
               "
               >缂栬緫
+            </el-button>
+            <el-button
+              v-if="scope.row.contractType === 2"
+              link
+              type="success"
+              @click="openFrameworkOrderDialog(scope.row)"
+              >鍒涘缓妗嗘灦璁㈠崟
+            </el-button>
+            <el-button
+              v-if="scope.row.contractType === 2"
+              link
+              type="primary"
+              @click="openFrameworkOrderList(scope.row)"
+              >妗嗘灦璁㈠崟
             </el-button>
             <el-button link type="primary" @click="openFileDialog(scope.row)"
               >闄勪欢
@@ -320,6 +334,21 @@
             <el-button type="primary" plain @click="openQuotationDialog">
               浠庨攢鍞姤浠峰鍏�
             </el-button>
+          </el-col>
+        </el-row>
+        <el-row :gutter="30">
+          <el-col :span="12">
+            <el-form-item label="鍚堝悓绫诲瀷锛�" prop="contractType">
+              <el-select
+                v-model="form.contractType"
+                placeholder="璇烽�夋嫨"
+                clearable
+                :disabled="operationType === 'view' || operationType === 'edit'"
+              >
+                <el-option label="鏅�氶攢鍞悎鍚�" :value="1" />
+                <el-option label="妗嗘灦鍚堝悓" :value="2" />
+              </el-select>
+            </el-form-item>
           </el-col>
         </el-row>
         <el-row :gutter="30">
@@ -467,7 +496,7 @@
             </el-form-item>
           </el-col>
         </el-row>
-        <el-row :gutter="30">
+        <el-row :gutter="30" v-if="!isFrameworkContract">
           <el-col :span="12">
             <el-form-item label="浜よ揣鏃ユ湡锛�" prop="entryDate">
               <el-date-picker
@@ -750,7 +779,7 @@
               />
             </el-form-item>
           </el-col>
-          <el-col :span="12">
+          <el-col :span="12" v-if="!isFrameworkContract">
             <el-form-item label="绋庣巼(%)锛�" prop="taxRate">
               <el-select
                 v-model="productForm.taxRate"
@@ -783,7 +812,7 @@
               />
             </el-form-item>
           </el-col>
-          <el-col :span="12">
+          <el-col :span="12" v-if="!isFrameworkContract">
             <el-form-item label="鏁伴噺锛�" prop="quantity">
               <el-input-number
                 :step="0.1"
@@ -799,7 +828,7 @@
           </el-col>
         </el-row>
         <el-row :gutter="30">
-          <el-col :span="12">
+          <el-col :span="12" v-if="!isFrameworkContract">
             <el-form-item label="鍚◣鎬讳环(鍏�)锛�" prop="taxInclusiveTotalPrice">
               <el-input
                 v-model="productForm.taxInclusiveTotalPrice"
@@ -809,7 +838,7 @@
               />
             </el-form-item>
           </el-col>
-          <el-col :span="12">
+          <el-col :span="12" v-if="!isFrameworkContract">
             <el-form-item
               label="涓嶅惈绋庢�讳环(鍏�)锛�"
               prop="taxExclusiveTotalPrice"
@@ -824,7 +853,7 @@
           </el-col>
         </el-row>
         <el-row :gutter="30">
-          <el-col :span="12">
+          <el-col :span="12" v-if="!isFrameworkContract">
             <el-form-item label="鍙戠エ绫诲瀷锛�" prop="invoiceType">
               <el-select
                 v-model="productForm.invoiceType"
@@ -836,7 +865,7 @@
               </el-select>
             </el-form-item>
           </el-col>
-          <el-col :span="12">
+          <el-col :span="12" v-if="!isFrameworkContract">
             <el-form-item label="鏄惁鐢熶骇锛�" prop="isProduction">
               <el-radio-group v-model="productForm.isProduction">
                 <el-radio label="鏄�" :value="true" />
@@ -1177,6 +1206,104 @@
         </div>
       </template>
     </el-dialog>
+    <!-- 鍒涘缓妗嗘灦璁㈠崟寮圭獥 -->
+    <el-dialog
+      v-model="frameworkOrderDialogVisible"
+      title="鍒涘缓妗嗘灦璁㈠崟"
+      width="70%"
+      :close-on-click-modal="false"
+    >
+      <el-form label-width="120px" label-position="top">
+        <el-form-item label="鐖舵鏋跺悎鍚岋細">
+          <el-input
+            :model-value="currentFrameworkContract?.salesContractNo"
+            disabled
+          />
+        </el-form-item>
+        <el-form-item label="澶囨敞锛�">
+          <el-input
+            v-model="frameworkOrderForm.remarks"
+            type="textarea"
+            :rows="2"
+            placeholder="璇疯緭鍏ュ娉�"
+          />
+        </el-form-item>
+        <el-form-item label="浜у搧锛堝~鍐欎氦璐ф棩鏈燂級锛�">
+          <el-table :data="frameworkOrderProducts" border>
+            <el-table-column label="浜у搧澶х被" prop="productCategory" />
+            <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" />
+            <el-table-column label="鍗曚綅" prop="unit" />
+            <el-table-column label="浜よ揣鏃ユ湡" align="center">
+              <template #default="scope">
+                <el-date-picker
+                  v-model="scope.row.deliveryDate"
+                  value-format="YYYY-MM-DD"
+                  format="YYYY-MM-DD"
+                  type="date"
+                  placeholder="璇烽�夋嫨"
+                  clearable
+                />
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button type="primary" @click="submitFrameworkOrder"
+          >纭鍒涘缓
+        </el-button>
+        <el-button @click="frameworkOrderDialogVisible = false">鍙栨秷</el-button>
+      </template>
+    </el-dialog>
+    <!-- 妗嗘灦璁㈠崟鍒楄〃寮圭獥 -->
+    <el-dialog
+      v-model="frameworkOrderListVisible"
+      title="妗嗘灦璁㈠崟鍒楄〃"
+      width="80%"
+      :close-on-click-modal="false"
+    >
+      <el-alert
+        v-if="frameworkContractShippedQty !== null"
+        type="info"
+        :closable="false"
+        style="margin-bottom: 12px"
+        :title="'鐖舵鏋跺悎鍚岀疮璁″凡鍙戣揣鏁伴噺锛�' + frameworkContractShippedQty"
+      />
+      <el-table :data="frameworkOrderList" border :row-key="(row) => row.id">
+        <el-table-column type="expand">
+          <template #default="props">
+            <el-table :data="props.row.products" border size="small">
+              <el-table-column label="浜у搧澶х被" prop="productCategory" />
+              <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" />
+              <el-table-column label="鍗曚綅" prop="unit" />
+              <el-table-column label="鎿嶄綔" width="80" align="center">
+                <template #default="scope">
+                  <el-button
+                    link
+                    type="primary"
+                    @click="shipFrameworkOrderProduct(scope.row, props.row)"
+                    >鍙戣揣
+                  </el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+          </template>
+        </el-table-column>
+        <el-table-column
+          label="璁㈠崟缂栧彿"
+          prop="salesContractNo"
+          min-width="180"
+        />
+        <el-table-column label="椤圭洰鍚嶇О" prop="projectName" min-width="160" />
+        <el-table-column label="澶囨敞" prop="remarks" min-width="160" />
+        <el-table-column
+          label="绱鍙戣揣鏁伴噺"
+          prop="shippedQuantity"
+          width="140"
+          align="center"
+        />
+      </el-table>
+    </el-dialog>
   </div>
 </template>
 
@@ -1201,6 +1328,9 @@
   delProduct,
   delLedgerFile,
   getProductInventory,
+  createFrameworkOrder,
+  listFrameworkOrders,
+  frameworkContractShippedQuantity,
 } from "@/api/salesManagement/salesLedger.js";
 import { getStockInventoryByModelId } from "@/api/inventoryManagement/stockInventory.js";
 import { modelList, productTreeList } from "@/api/basicData/product.js";
@@ -1250,6 +1380,7 @@
   },
   form: {
     salesContractNo: "",
+    contractType: 2,
     autoGenerateContractNo: true,
     salesman: "",
     customerId: "",
@@ -1267,11 +1398,31 @@
     customerId: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
     entryPerson: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
     entryDate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
-    deliveryDate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+    deliveryDate: [
+      {
+        validator: (rule, value, callback) => {
+          if (form.value.contractType === 2) return callback();
+          if (!value) return callback(new Error("璇烽�夋嫨"));
+          callback();
+        },
+        trigger: "change",
+      },
+    ],
     executionDate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
   },
 });
 const { form, rules } = toRefs(data);
+const isFrameworkContract = computed(() => form.value.contractType === 2);
+const frameworkOptional = (message, trigger = "blur") => ({
+  validator: (_rule, value, callback) => {
+    if (isFrameworkContract.value) return callback();
+    if (value === undefined || value === null || value === "") {
+      return callback(new Error(message));
+    }
+    callback();
+  },
+  trigger,
+});
 const { form: searchForm } = useFormData(data.searchForm);
 // 浜у搧琛ㄥ崟寮规鏁版嵁
 const productFormVisible = ref(false);
@@ -1297,18 +1448,12 @@
       { required: true, message: "璇烽�夋嫨", trigger: "change" },
     ],
     unit: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
-    quantity: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
-    taxInclusiveUnitPrice: [
-      { required: true, message: "璇疯緭鍏�", trigger: "blur" },
-    ],
-    taxRate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
-    taxInclusiveTotalPrice: [
-      { required: true, message: "璇疯緭鍏�", trigger: "blur" },
-    ],
-    taxExclusiveTotalPrice: [
-      { required: true, message: "璇疯緭鍏�", trigger: "blur" },
-    ],
-    invoiceType: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+    quantity: [frameworkOptional("璇疯緭鍏�")],
+    taxInclusiveUnitPrice: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+    taxRate: [frameworkOptional("璇烽�夋嫨", "change")],
+    taxInclusiveTotalPrice: [frameworkOptional("璇疯緭鍏�")],
+    taxExclusiveTotalPrice: [frameworkOptional("璇疯緭鍏�")],
+    invoiceType: [frameworkOptional("璇烽�夋嫨", "change")],
     isProduction: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
   },
 });
@@ -1740,6 +1885,7 @@
   form.value.entryPerson = userStore.id;
   if (type === "add") {
     // 鏂板鏃惰缃綍鍏ユ棩鏈熶负褰撳ぉ
+    form.value.contractType = 2;
     form.value.entryDate = getCurrentDate();
     // 绛捐鏃ユ湡榛樿涓哄綋澶�
     form.value.executionDate = getCurrentDate();
@@ -1892,6 +2038,14 @@
       }
       form.value.storageBlobDTOs = fileList;
       form.value.type = 1;
+      if (isFrameworkContract.value) {
+        form.value.productData = form.value.productData.map((item) => ({
+          ...item,
+          quantity: null,
+          deliveryDate: null,
+          isProduction: false,
+        }));
+      }
       if (form.value.autoGenerateContractNo) {
         form.value.salesContractNo = "";
       }
@@ -2933,6 +3087,92 @@
   deliveryFormVisible.value = false;
   currentDeliveryRow.value = null;
 };
+
+// 妗嗘灦鍚堝悓 / 妗嗘灦璁㈠崟鐩稿叧
+const frameworkOrderDialogVisible = ref(false);
+const frameworkOrderListVisible = ref(false);
+const currentFrameworkContract = ref(null);
+const frameworkOrderForm = reactive({
+  remarks: "",
+});
+const frameworkOrderProducts = ref([]);
+const frameworkOrderList = ref([]);
+const frameworkContractShippedQty = ref(null);
+
+const openFrameworkOrderDialog = async (row) => {
+  currentFrameworkContract.value = row;
+  frameworkOrderForm.remarks = "";
+  frameworkOrderProducts.value = [];
+  try {
+    const res = await productList({ salesLedgerId: row.id, type: 1 });
+    frameworkOrderProducts.value = (res.data || []).map((p) => ({
+      parentProductId: p.id,
+      productCategory: p.productCategory,
+      specificationModel: p.specificationModel,
+      unit: p.unit,
+      deliveryDate: "",
+    }));
+  } catch (e) {
+    frameworkOrderProducts.value = [];
+  }
+  frameworkOrderDialogVisible.value = true;
+};
+
+const submitFrameworkOrder = async () => {
+  const selected = frameworkOrderProducts.value.filter((p) => p.deliveryDate);
+  if (selected.length === 0) {
+    proxy.$modal.msgWarning("璇疯嚦灏戜负涓�涓骇鍝佸~鍐欎氦璐ф棩鏈�");
+    return;
+  }
+  try {
+    await createFrameworkOrder({
+      id: currentFrameworkContract.value.id,
+      remarks: frameworkOrderForm.remarks,
+      productData: selected.map((p) => ({
+        parentProductId: p.parentProductId,
+        deliveryDate: p.deliveryDate,
+      })),
+    });
+    proxy.$modal.msgSuccess("妗嗘灦璁㈠崟鍒涘缓鎴愬姛");
+    frameworkOrderDialogVisible.value = false;
+  } catch (e) {
+    // 閿欒宸茬敱璇锋眰鎷︽埅鍣ㄧ粺涓�鎻愮ず
+  }
+};
+
+const openFrameworkOrderList = async (row) => {
+  currentFrameworkContract.value = row;
+  frameworkContractShippedQty.value = null;
+  frameworkOrderList.value = [];
+  frameworkOrderListVisible.value = true;
+  listFrameworkOrders({ parentContractId: row.id }).then(async (res) => {
+    const orders = res.data || [];
+    const withProducts = [];
+    for (const order of orders) {
+      let products = [];
+      try {
+        const productRes = await productList({ salesLedgerId: order.id, type: 1 });
+        products = productRes.data || [];
+      } catch (e) {
+        products = [];
+      }
+      withProducts.push({ ...order, products });
+    }
+    frameworkOrderList.value = withProducts;
+  });
+  frameworkContractShippedQuantity({ parentContractId: row.id }).then((res) => {
+    frameworkContractShippedQty.value = res.data ?? 0;
+  });
+};
+
+const shipFrameworkOrderProduct = (productRow, orderRow) => {
+  if (!orderRow || !orderRow.id) return;
+  frameworkOrderListVisible.value = false;
+  openDeliveryForm({
+    ...productRow,
+    salesLedgerId: orderRow.id,
+  });
+};
 const currentFactoryName = ref("");
 const getCurrentFactoryName = async () => {
   let res = await userStore.getInfo();

--
Gitblit v1.9.3