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();