Przeglądaj źródła

feat: 添加无来源应收功能,支持手工新增和修改应收信息

xieyong 1 tydzień temu
rodzic
commit
cb72f71127

+ 3 - 0
src/enum/dict.js

@@ -744,6 +744,9 @@ export const payAndReceiveEnum = [
   }
 ];
 
+// 应收来源类型:手工新增的应收(无来源单据、无对账明细)
+export const manualReceivableSourceType = 99;
+
 export const allocationType = [
   {
     code: 1,

+ 53 - 5
src/views/financialManage/invoiceManage/components/tableInfo.vue

@@ -12,11 +12,13 @@
       
         @header-dragend="handleHeaderDragend">
         <template v-slot:toolbar>
-          <el-button type="primary" @click="handParent('', -1)">新增</el-button>
-          <el-button type="primary" @click="handGoods(-1)">选择商品</el-button>
-          <setAllValue :disabled="!selection.length" inputType="number" title="税率" valueKey="taxRate" @success="confirmBatchSet" />
-          <setAllValue :disabled="!selection.length" inputType="number" title="单价" valueKey="singlePrice" @success="confirmBatchSet" />
-          <setAllValue :disabled="!selection.length" inputType="number" title="折让比例" valueKey="discountRatio" @success="confirmBatchSet" />
+          <template v-if="dialogType !== 'view'">
+            <el-button type="primary" @click="handParent('', -1)">新增</el-button>
+            <el-button type="primary" @click="handGoods(-1)">选择商品</el-button>
+            <setAllValue :disabled="!selection.length" inputType="number" title="税率" valueKey="taxRate" @success="confirmBatchSet" />
+            <setAllValue :disabled="!selection.length" inputType="number" title="单价" valueKey="singlePrice" @success="confirmBatchSet" />
+            <setAllValue :disabled="!selection.length" inputType="number" title="折让比例" valueKey="discountRatio" @success="confirmBatchSet" />
+          </template>
         </template>
         
         <template v-slot:totalCount="scope">
@@ -465,6 +467,8 @@ import {
         allQuantity: 0,
         allDiscountPrice: 0,
         selection: [],
+        // 回填明细时置位:本次汇总不派发 setPrice
+        silentNext: false,
         pricingWayList,
         typeList: [
           {
@@ -556,6 +560,11 @@ import {
         this.allQuantity = getAllQuantity(this.tableForm.detailList, this.countObj) || 0;
         this.allDiscountPrice =
           getAllDiscountPrice(this.tableForm.detailList) || 0;
+        // 回填明细时不通知金额变化(silentNext 由 setDetailList 置位)
+        if (this.silentNext) {
+          this.silentNext = false;
+          return;
+        }
         // 通知父组件金额变化
         this.$emit('setPrice', this.allPrice);
       },
@@ -742,6 +751,45 @@ import {
         // 将 productMap(对象,key 为订单号,值为明细数组)转换为扁平行数组
         this.tableForm.detailList = this.convertProductMapToArray(data);
       },
+      // 回填扁平行明细(应收「无来源」用:接口返回的是 productList,不是 productMap)
+      // silent=true 时只渲染,不派发 setPrice(避免打开修改把已保存的金额改掉)
+      setDetailList(list = [], silent = false) {
+        this.silentNext = silent;
+        this.tableForm.detailList = (list || []).map((item) => this.normalizeRow(item));
+        this.$nextTick(() => {
+          this.getNotaxSinglePrice();
+          this.calcAllTotal();
+        });
+      },
+      // 取扁平行明细(提交给应收的 productList)
+      getDetailList() {
+        return (this.tableForm.detailList || []).map((item) => this.normalizeRow(item));
+      },
+      // 明细行兜底:数量/单价/税率/折让比例为 0 时合计算出来是 NaN
+      normalizeRow(row = {}) {
+        const singlePrice = +row.singlePrice || 0;
+        const totalCount = row.totalCount ?? row.statementCount ?? '';
+        const discountRatio =
+          row.discountRatio === undefined || row.discountRatio === null || row.discountRatio === ''
+            ? 100
+            : row.discountRatio;
+        const totalPrice =
+          +row.totalPrice ||
+          (totalCount === '' ? 0 : Math.round((+totalCount || 0) * singlePrice * 100) / 100);
+        const discountTotalPrice =
+          +row.discountTotalPrice ||
+          Math.round((totalPrice * discountRatio) / 100 * 100) / 100;
+        return {
+          ...row,
+          totalCount,
+          singlePrice,
+          taxRate: +row.taxRate || 0,
+          discountRatio,
+          totalPrice,
+          discountTotalPrice,
+          discountAmount: +row.discountAmount || discountTotalPrice
+        };
+      },
       // productMap -> 扁平行数组(表格展示用),key 固定为 99
       convertProductMapToArray(data) {
         const productMap = data?.productMap || {};

+ 286 - 67
src/views/financialManage/receivableManage/components/addReceivableDialog.vue

@@ -14,7 +14,7 @@
     <div v-loading="loading">
       <!--基本信息-->
       <headerTitle title="基本信息" style="margin-top: 30px"></headerTitle>
-      <el-form ref="form" :model="dataForm" :rules="rules" label-width="120px">
+      <el-form ref="form" :model="dataForm" :rules="formRules" label-width="120px">
         <el-row>
           <el-col :span="8">
             <el-form-item label="会计科目:" prop="accountingSubjectId">
@@ -50,13 +50,18 @@
           </el-col>
           <el-col :span="8">
             <el-form-item prop="receivableTotalPrice" label="应收金额">
-              <el-input :disabled="dialogType == 'view'" v-model="dataForm.receivableTotalPrice"></el-input>
+              <!-- 无来源应收没有对账明细,应收金额 = 产品明细合计 -->
+              <el-input
+                :disabled="dialogType == 'view' || isManual"
+                v-model="dataForm.receivableTotalPrice"
+              ></el-input>
             </el-form-item>
           </el-col>
         </el-row>
         <el-row>
           <el-col :span="8">
             <el-form-item prop="invoiceCode" label="发票编码">
+              <!-- 发票编码/发票号/发票种类、来源编码一律带出,新增和修改都不能改 -->
               <el-input disabled v-model="dataForm.invoiceCode"></el-input>
             </el-form-item>
           </el-col>
@@ -68,7 +73,7 @@
           <el-col :span="8">
             <el-form-item prop="invoiceCategory" label="发票种类">
               <el-select
-                :disabled="true"
+                disabled
                 clearable
                 class="ele-block"
                 v-model="dataForm.invoiceCategory"
@@ -83,11 +88,20 @@
         <el-row>
           <el-col :span="8">
             <el-form-item prop="contactName" label="公司名称">
-              <el-input disabled v-model="dataForm.contactName"></el-input>
+              <!-- 无来源应收没有源单带出客户,需要手工选择 -->
+              <el-input
+                v-if="isManual"
+                v-model="dataForm.contactName"
+                placeholder="请选择"
+                readonly
+                @click.native="$refs.parentRef.open()"
+              ></el-input>
+              <el-input v-else disabled v-model="dataForm.contactName"></el-input>
             </el-form-item>
           </el-col>
           <el-col :span="8">
             <el-form-item label="来源类型" prop="sourceType">
+              <!-- 无来源应收的来源类型固定为「无来源」,不可改 -->
               <el-select
                 :disabled="true"
                 clearable
@@ -96,7 +110,7 @@
                 placeholder="请选择"
               >
                 <el-option
-                  v-for="item in payAndReceiveEnum"
+                  v-for="item in sourceTypeOptions"
                   :key="item.value"
                   :label="item.label"
                   :value="item.value"
@@ -143,30 +157,49 @@
         </el-row>
       </el-form>
 
-      <headerTitle title="应收信息" style="margin-top: 30px"></headerTitle>
-      <receivable-info
-        :dataForm.sync="dataForm"
-        :receivableList.sync="receivableList"
-        dialogType="view"
-        @addAdvanceReceipt="addAdvanceReceipt"
-        ref="receivableInfoRef"
-      ></receivable-info>
+      <!-- 产品列表:无来源应收没有对账单,明细手工录入(与发票「其他无订单无合同」同一张表) -->
+      <template v-if="isManual">
+        <headerTitle title="关联物品清单信息" style="margin-top: 30px"></headerTitle>
+        <table-info
+          ref="productTableRef"
+          :dialogType="dialogType"
+          :contactData="contactData"
+          @setPrice="setReceivableTotalPrice"
+        ></table-info>
+      </template>
+
+      <!-- 应收信息:无来源应收没有对账单,不需要 -->
+      <template v-if="!isManual">
+        <headerTitle title="应收信息" style="margin-top: 30px"></headerTitle>
+        <receivable-info
+          :dataForm.sync="dataForm"
+          :receivableList.sync="receivableList"
+          dialogType="view"
+          @addAdvanceReceipt="addAdvanceReceipt"
+          ref="receivableInfoRef"
+        ></receivable-info>
+      </template>
 
-      <headerTitle title="对账明细" style="margin-top: 30px"></headerTitle>
-      <account-detail-table
-        ref="accountDetailTableRef"
-        :datasource.sync="datasource"
-        dialogType="view"
-        :queryDimension="dataForm.queryDimension"
-        @totalChange="handleTotalChange"
-        @countChange="handleCountChange"
-      ></account-detail-table>
+      <!-- 对账明细:无来源应收没有对账单,不需要 -->
+      <template v-if="!isManual">
+        <headerTitle title="对账明细" style="margin-top: 30px"></headerTitle>
+        <account-detail-table
+          ref="accountDetailTableRef"
+          :datasource.sync="datasource"
+          dialogType="view"
+          :queryDimension="dataForm.queryDimension"
+          @totalChange="handleTotalChange"
+          @countChange="handleCountChange"
+        ></account-detail-table>
+      </template>
     </div>
     <advance-receipt-select-dialog
       ref="advanceReceiptSelectRef"
       :contactId="dataForm.contactId"
       @changeParent="onAdvanceReceiptSelected"
     ></advance-receipt-select-dialog>
+    <!-- 客户选择(无来源应收) -->
+    <parentList v-if="isManual" ref="parentRef" @changeParent="changeParent"></parentList>
     <div slot="footer" class="footer">
       <el-button
         v-if="dialogType !== 'view'"
@@ -199,6 +232,7 @@
   import AdvanceReceiptSelectDialog from '@/views/saleManage/saleOrder/accountstatement/components//advanceReceiptSelectDialog.vue';
 
   import saleForm from '@/views/saleManage/saleOrder/accountstatement/components/saleForm.vue';
+  import tableInfo from '@/views/financialManage/invoiceManage/components/tableInfo.vue';
   import {
     getStatementRecordListAPI,
     infoAccountStatementAPI,
@@ -210,8 +244,54 @@
   } from '@/api/saleManage/accountstatement';
   import { getFinReceivableInitInfoAPI, getFinReceivableInfoAPI, saveReceivableAPI, updateReceivableAPI } from '@/api/financialManage/receivableManage';
   import processSubmitDialog from '@/BIZComponents/processSubmitDialog/processSubmitDialog.vue';
-  import { paymentTypeOp, payAndReceiveEnum } from '@/enum/dict';
+  import { paymentTypeOp, payAndReceiveEnum, manualReceivableSourceType } from '@/enum/dict';
   import { getTreeByPid } from '@/api/classifyManage';
+  import parentList from '@/views/saleManage/contact/components/parentList.vue';
+
+  // 应收表单初始值
+  const getDefaultDataForm = () => ({
+    accountingSubjectId: '',
+    accountingSubjectCode: '',
+    accountingSubjectName: '',
+    sourceType: 1,
+    dateType: 1,
+    queryDimension: 1,
+    dateValue: '',
+    contactId: '',
+    contactName: '',
+    customerNo: '',
+    sourceName: '',
+    sourceId: '',
+    sourceCode: '',
+    type: '',
+    year: '',
+    quarter: '',
+    month: '',
+    dateTimeRange: [],
+    orderNo: '',
+    orderId: '',
+    id: '',
+    code: '',
+    startDate: '',
+    endDate: '',
+    repliedFiles: [],
+    // 发票信息
+    invoiceCode: '',
+    invoiceNos: '',
+    invoiceCategory: '',
+    // 应收信息
+    receivableDate: '',
+    receivableTotalPrice: '',
+    paymentType: '',
+    remark: '',
+    files: [],
+    fundType: 2,
+    transactionMethod: 1,
+    collectionTerms: '',
+    receiptStatus: 0,
+    totalAdvanceConfirmAmount: 0,
+    totalReceivableAmount: 0
+  });
 
   export default {
     name: 'addReceivableDialog',
@@ -220,7 +300,9 @@
       AccountDetailTable,
       ReceivableInfo,
       AdvanceReceiptSelectDialog,
-      saleForm
+      saleForm,
+      parentList,
+      tableInfo
     },
     //客户管理数据
     props: {
@@ -254,37 +336,7 @@
         accountingSubjectList: [],
         isDetail: false,
         statReceipt: null,
-        dataForm: {
-          accountingSubjectId: '',
-          accountingSubjectCode: '',
-          accountingSubjectName: '',
-          sourceType: 1,
-          dateType: 1,
-          queryDimension: 1,
-          dateValue: '',
-          contactId: '',
-          contactName: '',
-          sourceName: '',
-          sourceId: '',
-          type: '',
-          year: '',
-          quarter: '',
-          month: '',
-          dateTimeRange: [],
-          orderNo: '',
-          orderId: '',
-          id: '',
-          startDate: '',
-          endDate: '',
-          repliedFiles: [],
-          // 应收信息字段
-          fundType: 2,
-          transactionMethod: 1,
-          collectionTerms: '',
-          receiptStatus: 0,
-          totalAdvanceConfirmAmount: 0,
-          totalReceivableAmount: 0
-        },
+        dataForm: getDefaultDataForm(),
         rules: {
           accountingSubjectId: { required: true, message: '请选择', trigger: 'change' },
           receivableDate: { required: true, message: '请选择', trigger: 'change' },
@@ -313,19 +365,55 @@
         title: '',
         processSubmitDialogFlag: false,
         dialogType: '',
-        businessId: ''
+        businessId: '',
+        // 无来源应收:没有源单、没有对账明细
+        isManual: false,
+        // 无来源应收已收/已开票金额(新增时为 0,修改时取接口返回值)
+        savedAmount: {
+          receivedTotalPrice: 0,
+          invoiceAmount: 0
+        }
       };
     },
     computed: {
+      // 来源类型:手工新增的应收只能是「无来源」;其它场景是源单带出来的类型
+      sourceTypeOptions() {
+        const options = payAndReceiveEnum.map((item) =>
+          item.value === manualReceivableSourceType
+            ? { ...item, label: '无来源' }
+            : item
+        );
+        if (!this.isManual) return options;
+        return options.filter((item) => item.value === manualReceivableSourceType);
+      },
+      // 无来源应收没有源单带出客户,客户、款项类型必须手工填
+      formRules() {
+        if (!this.isManual) return this.rules;
+        return {
+          ...this.rules,
+          contactName: {
+            required: true,
+            message: '请选择公司名称',
+            trigger: 'change'
+          },
+          paymentType: {
+            required: true,
+            message: '请选择款项类型',
+            trigger: 'change'
+          }
+        };
+      },
       // isNonDirectionalSale() {
       //   return this.datasource.some((item) => item.orderCategory == 4);
       // }
     },
     methods: {
       changeSubjectInfo(val) {
-        const { code, name } = this.$refs.treeSelect.getNodeByValue(val);
-        this.dataForm.accountingSubjectCode = code;
-        this.dataForm.accountingSubjectName = name;
+        // ele-tree-select 不会派发 el.form.change,表单不会自动重新校验,这里手动触发一次
+        const node = this.$refs.treeSelect?.getNodeByValue?.(val) || {};
+        this.dataForm.accountingSubjectCode = node.code || '';
+        this.dataForm.accountingSubjectName = node.name || '';
+        this.$refs.form && this.$refs.form.validateField('accountingSubjectId');
       },
       //获取分类管理中的数据
       async getClassifyList(id, listName) {
@@ -359,8 +447,62 @@
         const item = (this.paymentTypeOp || []).find((i) => i.value === val);
         return item ? item.label : val != null && val !== '' ? String(val) : '';
       },
-      async open(dialogType, row, type = '') {
+      // 选择客户(无来源应收)
+      changeParent(obj = {}) {
+        this.dataForm.contactName = obj.name || '';
+        this.dataForm.contactId = obj.id || '';
+        this.dataForm.customerNo = obj.code || '';
+      },
+      // 产品明细合计 -> 应收金额(无来源应收的应收金额由明细算出来)
+      setReceivableTotalPrice(val) {
+        this.dataForm.receivableTotalPrice = val;
+      },
+      // 无来源应收的产品明细(提交用)
+      getProductList() {
+        return this.$refs.productTableRef?.getDetailList?.() || [];
+      },
+      // 按名称在会计科目树里找节点
+      findSubjectNodes(list, name, result = []) {
+        for (let item of list || []) {
+          if (item.name === name) result.push(item);
+          this.findSubjectNodes(item.children, name, result);
+        }
+        return result;
+      },
+      // 按科目编码在会计科目树里找节点
+      findSubjectByCode(list, code) {
+        for (let item of list || []) {
+          if (item.code == code) return item;
+          const child = this.findSubjectByCode(item.children, code);
+          if (child) return child;
+        }
+        return null;
+      },
+      // 默认会计科目:应收账款
+      setDefaultSubject() {
+        const nodes = this.findSubjectNodes(
+          this.accountingSubjectList,
+          '应收账款'
+        );
+        // 同名优先取编码为 1122 的节点
+        let subject = nodes.find((item) => item.code == '1122') || nodes[0];
+        // 名称对不上时退而按科目编码 1122(应收账款)找
+        if (!subject) {
+          subject = this.findSubjectByCode(this.accountingSubjectList, '1122');
+        }
+        if (!subject) {
+          console.warn('未找到会计科目「应收账款」,请手工选择');
+          return;
+        }
+        this.dataForm.accountingSubjectId = subject.id;
+        this.dataForm.accountingSubjectCode = subject.code;
+        this.dataForm.accountingSubjectName = subject.name;
+      },
+      // open(dialogType, row, manual)
+      // manual=true 时为「无来源应收」:没有源单、没有对账明细,不需要应收信息
+      async open(dialogType, row = {}, type = '', manual = false) {
         this.dialogType = dialogType;
+        this.isManual = manual;
         this.title =
           dialogType == 'add'
             ? '新增'
@@ -377,9 +519,40 @@
         //     this.$refs.saleFormRef.getOrderInfo(this.saleOrderData);
         //   }
         // });
-        if (this.dialogType !== 'add') {
+        if (this.isManual) {
+          // 无来源应收:没有对账单初始化信息
+          this.statReceipt = null;
+          this.receivableList = [];
+          this.datasource = [];
+          this.savedAmount = { receivedTotalPrice: 0, invoiceAmount: 0 };
+          this.dataForm = getDefaultDataForm();
+          if (dialogType == 'update' && row.id) {
+            this.businessId = row.id;
+            await this.getInfo(row);
+            // 回填产品明细(静默:不改已保存的应收金额,用户改明细时才重算)
+            this.$nextTick(() => {
+              this.$refs.productTableRef?.setDetailList(this.datasource || [], true);
+            });
+          } else {
+            this.businessId = '';
+            this.dataForm.sourceType = manualReceivableSourceType;
+            // 会计科目默认应收账款
+            this.setDefaultSubject();
+          }
+          // 打开时清掉上一次的校验提示
+          this.$nextTick(() => {
+            this.$refs.form && this.$refs.form.clearValidate();
+          });
+        } else if (this.dialogType !== 'add') {
           this.businessId = row.id;
-          this.getInfo(row);
+          await this.getInfo(row);
+          // 没有对账单的就是手工新增的(来源类型可能被改成了别的,按有没有对账单判断)
+          if (!this.statReceipt && !this.receivableList.length) {
+            this.isManual = true;
+            this.$nextTick(() => {
+              this.$refs.form && this.$refs.form.clearValidate();
+            });
+          }
         } else {
           let data = await getFinReceivableInitInfoAPI({
             statementId: row.id
@@ -417,7 +590,14 @@
         this.dataForm = {
           ...this.dataForm,
           ...data,
-          invoiceCategory: Number(data?.invoiceCategory) || ""
+          invoiceCategory: Number(data?.invoiceCategory) || "",
+          // 无来源应收不带源单(来源类型/来源编码是手工填的,保留原值)
+          ...(this.isManual ? { sourceId: '' } : {})
+        };
+
+        this.savedAmount = {
+          receivedTotalPrice: +data?.receivedTotalPrice || 0,
+          invoiceAmount: +data?.invoiceAmount || 0
         };
 
         this.statReceipt = data.statReceipt || null;
@@ -572,13 +752,30 @@
       },
       //保存
       async save(is) {
-        if (!this.datasource.length)
+        if (!this.isManual && !this.datasource.length)
           return this.$message.warning('暂无对账信息');
 
+        if (this.isManual) {
+          // 无来源应收:基本信息必填项校验(原来的校验只覆盖明细表和应收信息)
+          const valid = await this.$refs.form.validate().catch(() => false);
+          if (!valid) return;
+          // 产品明细:至少一行,且数量/折让比例等行内校验要过
+          const tableRef = this.$refs.productTableRef;
+          if (tableRef) {
+            const detailValid = await new Promise((resolve) => tableRef.validateForm(resolve));
+            if (!detailValid) return;
+            if (!this.getProductList().length) {
+              return this.$message.warning('请添加产品明细');
+            }
+          }
+        }
+
         try {
-          // 校验明细表与应收信息表单
-          await this.$refs.accountDetailTableRef.getValidForm();
-          await this.$refs.receivableInfoRef.validate();
+          if (!this.isManual) {
+            // 校验明细表与应收信息表单(无来源应收没有这两块)
+            await this.$refs.accountDetailTableRef.getValidForm();
+            await this.$refs.receivableInfoRef.validate();
+          }
           console.log(this.dialogType, 'this.dialogType~~~');
           let api =
             this.dialogType == 'add'
@@ -604,6 +801,28 @@
             statReceipt: this.statReceipt
             // recorpayList: this.recorpayList
           };
+          if (this.isManual) {
+            // 无来源应收:不挂源单、不带对账单,明细是手工录入的产品列表
+            // 来源类型固定「无来源」,来源编码由接口带出、不可填
+            const receivableTotalPrice = +this.dataForm.receivableTotalPrice || 0;
+            // 已收/已开票:新增时是 0,修改时沿用接口返回值
+            const receivedTotalPrice = this.savedAmount.receivedTotalPrice;
+            const invoiceAmount = this.savedAmount.invoiceAmount;
+            params = {
+              ...params,
+              sourceType: manualReceivableSourceType,
+              sourceId: '',
+              productList: this.getProductList(),
+              statReceipt: null,
+              receivableTotalPrice,
+              receivedTotalPrice,
+              invoiceAmount,
+              // 未收金额 = 应收金额 - 已收金额
+              unreceiveTotalPrice: receivableTotalPrice - receivedTotalPrice,
+              // 未开票金额 = 应收金额 - 已开票金额
+              unInvoiceAmount: receivableTotalPrice - invoiceAmount
+            };
+          }
           this.saveLoading = true;
           let data = await api(params);
           if (is == 'sub') {

+ 6 - 0
src/views/financialManage/receivableManage/components/collectionDialog.vue

@@ -395,6 +395,12 @@ export default {
       const data = await getProductListForReceiptAPI(id);
       // 新接口只返回产品列表
       this.datasource = Array.isArray(data) ? data : (data.productList || []);
+      // 明细为空(没有对账明细)时无法勾选收款行,直接拦掉
+      if (!this.datasource.length) {
+        this.$message.warning('该应收没有对账明细,不支持收款');
+        this.cancel();
+        return;
+      }
       // 把接口返回的已收/未收金额回填到 row,供顶部汇总和表单展示
       this.row = {
         ...this.row,

+ 55 - 23
src/views/financialManage/receivableManage/components/detailDialog.vue

@@ -107,7 +107,7 @@
                 placeholder="请选择"
               >
                 <el-option
-                  v-for="item in payAndReceiveEnum"
+                  v-for="item in sourceTypeOptions"
                   :key="item.value"
                   :label="item.label"
                   :value="item.value"
@@ -141,24 +141,33 @@
         </el-row>
       </el-form>
 
-      <headerTitle title="应收信息" style="margin-top: 30px"></headerTitle>
-      <receivable-info
-        :dataForm.sync="dataForm"
-        :receivableList.sync="receivableList"
-        dialogType="view"
-        ref="receivableInfoRef"
-        @addAdvanceReceipt="addAdvanceReceipt"
-      ></receivable-info>
+      <!-- 产品列表:无来源应收的明细就是手工录入的产品列表 -->
+      <template v-if="isManual">
+        <headerTitle title="产品列表" style="margin-top: 30px"></headerTitle>
+        <table-info ref="productTableRef" dialogType="view"></table-info>
+      </template>
 
-      <headerTitle title="对账明细" style="margin-top: 30px"></headerTitle>
-      <account-detail-table
-        ref="accountDetailTableRef"
-        :datasource.sync="datasource"
-        dialogType="view"
-        :queryDimension="dataForm.queryDimension"
-        @totalChange="handleTotalChange"
-        @countChange="handleCountChange"
-      ></account-detail-table>
+      <!-- 应收信息、对账明细:无来源应收没有对账单,不需要 -->
+      <template v-if="!isManual">
+        <headerTitle title="应收信息" style="margin-top: 30px"></headerTitle>
+        <receivable-info
+          :dataForm.sync="dataForm"
+          :receivableList.sync="receivableList"
+          dialogType="view"
+          ref="receivableInfoRef"
+          @addAdvanceReceipt="addAdvanceReceipt"
+        ></receivable-info>
+
+        <headerTitle title="对账明细" style="margin-top: 30px"></headerTitle>
+        <account-detail-table
+          ref="accountDetailTableRef"
+          :datasource.sync="datasource"
+          dialogType="view"
+          :queryDimension="dataForm.queryDimension"
+          @totalChange="handleTotalChange"
+          @countChange="handleCountChange"
+        ></account-detail-table>
+      </template>
     </div>
     <bpmDetail
       v-if="activeComp === 'bpm' && dataForm.processInstanceId"
@@ -200,8 +209,9 @@
   import { getFinReceivableInfoAPI, getDetailList } from '@/api/financialManage/receivableManage';
   import { getTreeByPid } from '@/api/classifyManage';
   import infoTable from '@/views/financialManage/receivableManage/components/infoTable.vue';
+  import tableInfo from '@/views/financialManage/invoiceManage/components/tableInfo.vue';
   import bpmDetail from '@/views/bpm/processInstance/detail.vue';
-  import { payAndReceiveEnum } from "@/enum/dict";
+  import { payAndReceiveEnum, manualReceivableSourceType } from "@/enum/dict";
 
   import AccountDetailTable from '@/views/saleManage/saleOrder/accountstatement/components/accountDetailTable.vue';
   import ReceivableInfo from '@/views/saleManage/saleOrder/accountstatement/components//receivableInfo.vue';
@@ -215,10 +225,29 @@ import tabMixins from '@/mixins/tableColumnsMixin';
       infoTable,
       AccountDetailTable,
       ReceivableInfo,
-      collectTableForm
+      collectTableForm,
+      tableInfo
       // fileMain
     },
     computed: {
+      // 无来源应收:没有对账单,不显示应收信息、对账明细
+      isManual() {
+        if (Number(this.dataForm.sourceType) === manualReceivableSourceType) {
+          return true;
+        }
+        // 没有对账单也没有应收信息的,是手工新增的(来源类型可能被改成了别的)
+        return !this.statReceipt && !this.receivableList.length;
+      },
+      // 手工新增的应收(无来源)来源类型只有「无来源」
+      sourceTypeOptions() {
+        const options = payAndReceiveEnum.map((item) =>
+          item.value === manualReceivableSourceType
+            ? { ...item, label: '无来源' }
+            : item
+        );
+        if (!this.isManual) return options;
+        return options.filter((item) => item.value === manualReceivableSourceType);
+      },
       columns() {
         this.columnsVersion;  // mixin 触发重算
         return this.applyColumnWidth([
@@ -355,6 +384,10 @@ import tabMixins from '@/mixins/tableColumnsMixin';
         (data.statReceipt && data.statReceipt.statReceiptDetailList) || [];
 
         this.datasource = data.productList || [];
+        // 无来源应收:产品明细直接展示(只读)
+        this.$nextTick(() => {
+          this.$refs.productTableRef?.setDetailList(this.datasource || [], true);
+        });
 
         this.detailList = await getDetailList(id);
 
@@ -408,9 +441,8 @@ import tabMixins from '@/mixins/tableColumnsMixin';
       },
       //获取会计科目选中数据
       changeSubjectInfo(val) {
-        if (!val) return (this.form.accountingSubjectName = '');
-        let data = this.$refs.treeSelect?.$refs?.tree?.getCurrentNode() || {};
-        this.form.accountingSubjectName = data.name;
+        const node = this.$refs.treeSelect?.getNodeByValue?.(val) || {};
+        this.dataForm.accountingSubjectName = node.name || '';
       },
       cancel() {
         this.$emit('update:detailDialogFlag', false);

+ 51 - 4
src/views/financialManage/receivableManage/index.vue

@@ -21,6 +21,16 @@
         > 
           <!-- 表头工具栏 -->
           <template v-slot:toolbar>
+            <el-button
+              v-if="$hasPermission('eom:finreceivable:save')"
+              size="small"
+              type="primary"
+              icon="el-icon-plus"
+              class="ele-btn-icon"
+              @click="handleAddManualReceivable"
+            >
+              新增
+            </el-button>
             <el-button
               v-if="$hasPermission('eom:finreceivable:push')"
               size="small"
@@ -48,7 +58,7 @@
               type="primary"
               :underline="false"
               icon="el-icon-plus"
-              @click="handleAddReceivable(row, 'update')"
+              @click="handleModify(row)"
               v-if="[0, 3].includes(row.approvalStatus)"
             >
               修改
@@ -149,7 +159,7 @@
   import invoiceDetailDialog from '../invoiceManage/components/detailDialog.vue';
   import processSubmitDialog from "@/BIZComponents/processSubmitDialog/processSubmitDialog.vue";
   import tabMixins from '@/mixins/tableColumnsMixin';
-  import { shippingModeOp, transactionMethodsOp, paymentTypeOp, paymentStatus, invoiceStatusOp } from '@/enum/dict.js';
+  import { shippingModeOp, transactionMethodsOp, paymentTypeOp, paymentStatus, invoiceStatusOp, manualReceivableSourceType } from '@/enum/dict.js';
   import addOrEditDialogNew from '@/views/financialManage/invoiceManage/components/addOrEditDialogNew.vue'
   import addReceivableDialog from '@/views/financialManage/receivableManage/components/addReceivableDialog.vue'
 
@@ -426,8 +436,10 @@
             slot: 'status',
             showOverflowTooltip: true,
             formatter: (_row, _column, cellValue) => {
-              return this.paymentStatus.find((item) => item.value == cellValue)
-                .label;
+              return (
+                this.paymentStatus.find((item) => item.value == cellValue)?.label ||
+                ''
+              );
             }
           },
           {
@@ -516,6 +528,41 @@
           this.$refs.addReceivableDialogRef.open(type, row);
         });
       },
+      // 新增无来源应收(没有源单、没有对账明细)
+      handleAddManualReceivable() {
+        this.addReceivableDialogFlag = true;
+        this.$nextTick(() => {
+          this.$refs.addReceivableDialogRef.open('add', {}, '', true);
+        });
+      },
+      // 修改:有来源单据的走原应收弹窗,无来源的按无来源模式打开
+      handleModify(row) {
+        if (this.isManualReceivable(row)) {
+          this.addReceivableDialogFlag = true;
+          this.$nextTick(() => {
+            this.$refs.addReceivableDialogRef.open('update', row, '', true);
+          });
+          return;
+        }
+        this.handleAddReceivable(row, 'update');
+      },
+      // 是否为手工新增的无来源应收(没有对账单、没有对账明细)
+      isManualReceivable(row) {
+        if (!row) return false;
+        // 来源类型是「无来源」的
+        if (row.sourceType !== undefined && row.sourceType !== null && row.sourceType !== '') {
+          if (Number(row.sourceType) === manualReceivableSourceType) return true;
+        }
+        // 列表返回了对账单/源单 id 的,一定是正常应收
+        if (row.statementId !== undefined && row.statementId !== null && row.statementId !== '') {
+          return false;
+        }
+        if (row.sourceId !== undefined && row.sourceId !== null && row.sourceId !== '') {
+          return false;
+        }
+        // 都没有源单标识的,用「没有来源编码」兜底判断
+        return !row.sourceCode;
+      },
       handleDetail(row = {}, type) {
         this.detailDialogFlag = true;
         this.$nextTick(() => {