Browse Source

feat: 开票

liujt 2 months ago
parent
commit
8d66d4c788

+ 288 - 0
src/BIZComponents/collectTableForm/index.vue

@@ -0,0 +1,288 @@
+<!-- 收款明细表单公共组件 -->
+<!-- 职责:收款信息汇总区 + 新增按钮 + 可编辑表格(列/填写/校验/汇总内置),form 由父组件传入 -->
+<template>
+  <div>
+    <!-- 收款信息汇总区 -->
+    <headerTitle title="收款信息" size="16px"></headerTitle>
+    <div style="margin: 10px 0; font-size: 14px; color: #606266;">
+      <span>{{ totalLabel }}:{{ totalPrice || 0 }} 元</span>
+      <span style="margin-left: 20px;">已收金额:{{ receivedAmount }} 元</span>
+      <span style="margin-left: 20px;">未收金额:{{ unreceivedAmount }} 元</span>
+    </div>
+
+    <el-button
+      v-if="!isView"
+      size="small"
+      type="primary"
+      icon="el-icon-plus"
+      class="ele-btn-icon"
+      style="margin-bottom: 10px;"
+      @click="addRow"
+    >
+      新增
+    </el-button>
+
+    <el-form ref="form" :model="form" class="table-form">
+      <ele-pro-table
+        ref="table"
+        :needPage="false"
+        :columns="columns"
+        :datasource="form.tableData"
+        :toolkit="[]"
+      >
+        <!-- 收款时间 -->
+        <template v-slot:receivedDate="{ row: r, $index }">
+          <el-form-item
+            :prop="`tableData.${$index}.receivedDate`"
+            :rules="{ required: true, message: '请选择收款时间', trigger: 'change' }"
+            class="table-form-item"
+          >
+            <el-date-picker
+              v-model="r.receivedDate"
+              type="date"
+              placeholder="年/月/日"
+              value-format="yyyy-MM-dd"
+              style="width: 100%"
+              size="small"
+              :disabled="isView || r?.id"
+            />
+          </el-form-item>
+        </template>
+        <!-- 已收金额 -->
+        <template v-slot:receivedTotalPrice="{ row: r, $index }">
+          <el-form-item
+            :prop="`tableData.${$index}.receivedTotalPrice`"
+            :rules="{ required: true, message: '请输入已收金额', trigger: 'blur' }"
+            class="table-form-item"
+          >
+            <el-input
+              v-model="r.receivedTotalPrice"
+              placeholder="请输入"
+              size="small"
+              :disabled="isView || r?.id"
+              @input="calcAmount"
+            />
+          </el-form-item>
+        </template>
+        <!-- 支付类型 -->
+        <template v-slot:paymentType="{ row: r, $index }">
+          <el-form-item
+            :prop="`tableData.${$index}.paymentType`"
+            :rules="{ required: true, message: '请选择支付类型', trigger: 'change' }"
+            class="table-form-item"
+          >
+            <DictSelection
+              v-model="r.paymentType"
+              dictName="支付类型"
+              placeholder="请选择"
+              class="payment-select"
+              :disabled="isView || r?.id"
+            />
+          </el-form-item>
+        </template>
+        <!-- 附件 -->
+        <template v-slot:files="{ row: r }">
+          <fileMain v-model="r.files" :limit="1" :type="isView || r?.id ? 'view' : 'add'"></fileMain>
+        </template>
+        <!-- 备注 -->
+        <template v-slot:remark="{ row: r }">
+          <el-input
+            v-model="r.remark"
+            placeholder="请输入"
+            size="small"
+            :disabled="isView || r?.id"
+          />
+        </template>
+        <!-- 操作 -->
+        <template v-slot:action="{ row: r }">
+          <el-link
+            v-if="!isView && !r?.id"
+            type="danger"
+            :underline="false"
+            @click="removeRow(r)"
+          >
+            删除
+          </el-link>
+        </template>
+      </ele-pro-table>
+    </el-form>
+  </div>
+</template>
+
+<script>
+  import headerTitle from '@/components/header-title';
+  import fileMain from '@/components/addDoc/index.vue';
+
+  export default {
+    name: 'CollectTableForm',
+    components: {
+      headerTitle,
+      fileMain
+    },
+    props: {
+      // 表单数据(含 tableData),.sync 双向,父组件传入
+      form: {
+        type: Object,
+        default: () => ({ tableData: [] })
+      },
+      // 汇总区总金额(如预收金额/应收金额)
+      totalPrice: {
+        type: [Number, String],
+        default: 0
+      },
+      // 汇总区总金额标签(如"预收金额"/"应收金额")
+      totalLabel: {
+        type: String,
+        default: '预收金额'
+      },
+      // 新增行的空白模板
+      rowTemplate: {
+        type: Object,
+        default: () => ({
+          receivedDate: '',
+          receivedTotalPrice: '',
+          paymentType: '',
+          files: [],
+          remark: ''
+        })
+      },
+      // 弹窗类型:view 查看模式禁用编辑,其它(add/update)可编辑
+      dialogType: {
+        type: String,
+        default: 'update'
+      }
+    },
+    data() {
+      return {};
+    },
+    computed: {
+      // 查看模式:禁用所有编辑
+      isView() {
+        return this.dialogType === 'view';
+      },
+      columns() {
+        return [
+          {
+            type: 'index',
+            label: '序号',
+            width: 55,
+            align: 'center',
+            fixed: 'left'
+          },
+          {
+            prop: 'receivedDate',
+            label: '收款时间',
+            align: 'center',
+            minWidth: 160,
+            slot: 'receivedDate'
+          },
+          {
+            prop: 'receivedTotalPrice',
+            label: '已收金额',
+            align: 'center',
+            minWidth: 140,
+            slot: 'receivedTotalPrice'
+          },
+          {
+            prop: 'paymentType',
+            label: '支付类型',
+            align: 'center',
+            minWidth: 140,
+            slot: 'paymentType'
+          },
+          {
+            prop: 'files',
+            label: '附件',
+            align: 'center',
+            minWidth: 120,
+            slot: 'files'
+          },
+          {
+            prop: 'remark',
+            label: '备注',
+            align: 'center',
+            minWidth: 140,
+            slot: 'remark'
+          },
+          {
+            columnKey: 'action',
+            label: '操作',
+            width: 80,
+            align: 'center',
+            resizable: false,
+            slot: 'action',
+            fixed: 'right',
+            show: !this.isView
+          }
+        ];
+      },
+      receivedAmount() {
+        console.log('this.form.tableData', this.form.tableData)
+        return this.form.tableData?.reduce((sum, item) => {
+          const val = parseFloat(item.receivedTotalPrice);
+          return sum + (isNaN(val) ? 0 : val);
+        }, 0).toFixed(2);
+      },
+      unreceivedAmount() {
+        const total = parseFloat(this.totalPrice) || 0;
+        const received = parseFloat(this.receivedAmount) || 0;
+        return (total - received).toFixed(2);
+      }
+    },
+    methods: {
+      addRow() {
+        console.log('this.form.tableData', this.form.tableData)
+        if (this.isView) return;
+        this.form.tableData.push({ ...this.rowTemplate });
+        this.calcAmount();
+      },
+
+      removeRow(row) {
+        if (this.isView) return;
+        const index = this.form.tableData.indexOf(row);
+        if (index !== -1) {
+          this.form.tableData.splice(index, 1);
+        }
+        this.calcAmount();
+      },
+
+      calcAmount() {
+        this.$forceUpdate();
+      },
+
+      // 校验:查看模式直接通过,否则至少一条 + 各列必填
+      validate() {
+        return new Promise((resolve, reject) => {
+          if (this.isView) {
+            resolve(true);
+            return;
+          }
+          if (this.form.tableData.length === 0) {
+            this.$message.warning('请至少添加一条收款记录');
+            reject(false);
+            return;
+          }
+          this.$refs.form.validate((valid) => {
+            valid ? resolve(true) : reject(false);
+          });
+        });
+      },
+
+      // 获取整个表单数据,供父组件提交
+      getData() {
+        return {
+          form: this.form,
+          tableData: this.form.tableData,
+          receivedAmount: this.receivedAmount,
+          unreceivedAmount: this.unreceivedAmount
+        };
+      }
+    }
+  };
+</script>
+
+<style lang="scss" scoped>
+  .table-form-item {
+    margin-bottom: 0 !important;
+  }
+</style>

+ 9 - 5
src/views/financialManage/advanceReceipt/components/addDialog.vue

@@ -536,8 +536,10 @@
               this.$message.success(this.isUpdate ? '修改成功' : '新增成功');
               if(type === 1) {
                 this.sub(res);
+              } else {
+                this.updateVisible(false);
+                this.$emit('done');
               }
-              this.$emit('done');
             })
             .catch(() => {
               this.loading = false;
@@ -547,11 +549,13 @@
 
       async sub(res) {
         console.log('sub', res, this.businessId)
-        const data = await getAdvanceReceiptInfo({
-          id: this.businessId || res
-        });
-        submit(data.id).then(() => {
+        const data = await getAdvanceReceiptInfo(this.businessId || res);
+        submit({
+          id: data.id
+        }).then(() => {
           this.$message.success('提交成功');
+          this.updateVisible(false);
+          this.$emit('done');
         })
       },
 

+ 38 - 232
src/views/financialManage/advanceReceipt/components/collectDialog.vue

@@ -9,106 +9,12 @@
     title="收款"
     @update:visible="updateVisible"
   >
-    <div>
-      <!-- 收款信息 -->
-      <headerTitle title="收款信息" size="16px"></headerTitle>
-      <div style="margin: 10px 0; font-size: 14px; color: #606266;">
-        <span>预收金额:{{ row.advanceTotalPrice || 0 }} 元</span>
-        <span style="margin-left: 20px;">已收金额:{{ receivedAmount }} 元</span>
-        <span style="margin-left: 20px;">未收金额:{{ unreceivedAmount }} 元</span>
-      </div>
-
-      <el-button
-        size="small"
-        type="primary"
-        icon="el-icon-plus"
-        class="ele-btn-icon"
-        style="margin-bottom: 10px;"
-        @click="addRow"
-      >
-        新增
-      </el-button>
-
-      <el-form ref="form" :model="form" class="table-form">
-      <ele-pro-table
-        ref="table"
-        :needPage="false"
-        :columns="columns"
-        :datasource="form.tableData"
-        :toolkit="[]"
-      >
-        <!-- 收款时间 -->
-        <template v-slot:receivedDate="{ row: r, $index }">
-          <el-form-item
-            :prop="`tableData.${$index}.receivedDate`"
-            :rules="{ required: true, message: '请选择收款时间', trigger: 'change' }"
-            class="table-form-item"
-          >
-            <el-date-picker
-              v-model="r.receivedDate"
-              type="date"
-              placeholder="年/月/日"
-              value-format="yyyy-MM-dd"
-              style="width: 100%"
-              size="small"
-            />
-          </el-form-item>
-        </template>
-        <!-- 已收金额 -->
-        <template v-slot:receivedTotalPrice="{ row: r, $index }">
-          <el-form-item
-            :prop="`tableData.${$index}.receivedTotalPrice`"
-            :rules="{ required: true, message: '请输入已收金额', trigger: 'blur' }"
-            class="table-form-item"
-          >
-            <el-input
-              v-model="r.receivedTotalPrice"
-              placeholder="请输入"
-              size="small"
-              @input="calcAmount"
-            />
-          </el-form-item>
-        </template>
-        <!-- 支付类型 -->
-        <template v-slot:paymentType="{ row: r, $index }">
-          <el-form-item
-            :prop="`tableData.${$index}.paymentType`"
-            :rules="{ required: true, message: '请选择支付类型', trigger: 'change' }"
-            class="table-form-item"
-          >
-            <DictSelection
-              v-model="r.paymentType"
-              dictName="支付类型"
-              placeholder="请选择"
-              class="payment-select"
-            />
-          </el-form-item>
-        </template>
-        <!-- 附件 -->
-        <template v-slot:files="{ row: r }">
-          <fileMain v-model="r.files" :limit="1"></fileMain>
-        </template>
-        <!-- 备注 -->
-        <template v-slot:remark="{ row: r }">
-          <el-input
-            v-model="r.remark"
-            placeholder="请输入"
-            size="small"
-          />
-        </template>
-        <!-- 操作 -->
-        <template v-slot:action="{ row: r }">
-          <el-link
-            type="danger"
-            :underline="false"
-            @click="removeRow(r)"
-          >
-            删除
-          </el-link>
-        </template>
-      </ele-pro-table>
-      </el-form>
-    </div>
+    <collect-table-form
+      ref="collectTableFormRef"
+      :form.sync="form"
+      :total-price="row.advanceTotalPrice"
+      total-label="预收金额"
+    ></collect-table-form>
 
     <template v-slot:footer>
       <el-button type="primary" :loading="loading" @click="save">
@@ -120,19 +26,15 @@
 </template>
 
 <script>
-  import headerTitle from '@/components/header-title';
-  import fileMain from '@/components/addDoc/index.vue';
+  import collectTableForm from '@/BIZComponents/collectTableForm/index.vue';
   import { saveDetailDetail } from '@/api/financialManage/advanceReceipt';
   import {
-    addAdvanceReceipt,
-    updateAdvanceReceipt,
     getAdvanceReceiptInfo
   } from '@/api/financialManage/advanceReceipt';
 
   export default {
     components: {
-      headerTitle,
-      fileMain
+      collectTableForm
     },
     data() {
       return {
@@ -142,142 +44,46 @@
         row: {},
         form: {
           tableData: []
-        },
-        columns: [
-          {
-            type: 'index',
-            label: '序号',
-            width: 55,
-            align: 'center',
-            fixed: 'left'
-          },
-          {
-            prop: 'receivedDate',
-            label: '收款时间',
-            align: 'center',
-            minWidth: 160,
-            slot: 'receivedDate'
-          },
-          {
-            prop: 'receivedTotalPrice',
-            label: '已收金额',
-            align: 'center',
-            minWidth: 140,
-            slot: 'receivedTotalPrice'
-          },
-          {
-            prop: 'paymentType',
-            label: '支付类型',
-            align: 'center',
-            minWidth: 140,
-            slot: 'paymentType'
-          },
-          {
-            prop: 'files',
-            label: '附件',
-            align: 'center',
-            minWidth: 120,
-            slot: 'files'
-          },
-          {
-            prop: 'remark',
-            label: '备注',
-            align: 'center',
-            minWidth: 140,
-            slot: 'remark'
-          },
-          {
-            columnKey: 'action',
-            label: '操作',
-            width: 80,
-            align: 'center',
-            resizable: false,
-            slot: 'action',
-            fixed: 'right'
-          }
-        ]
+        }
       };
     },
-    computed: {
-      receivedAmount() {
-        return this.form.tableData.reduce((sum, item) => {
-          const val = parseFloat(item.receivedTotalPrice);
-          return sum + (isNaN(val) ? 0 : val);
-        }, 0).toFixed(2);
-      },
-      unreceivedAmount() {
-        const total = parseFloat(this.row.advanceTotalPrice) || 0;
-        const received = parseFloat(this.receivedAmount) || 0;
-        return (total - received).toFixed(2);
-      }
-    },
     methods: {
       async open(row) {
         this.visible = true;
         this.row = row || {};
         this.info = await getAdvanceReceiptInfo(row.id);
-        this.form.tableData = this.info.detailList || [];
-      },
-
-      addRow() {
-        this.form.tableData.push({
-          receivedDate: '',
-          receivedTotalPrice: '',
-          paymentType: '',
-          files: [],
-          remark: ''
-        });
-      },
-
-      removeRow(row) {
-        const index = this.form.tableData.indexOf(row);
-        if (index !== -1) {
-          this.form.tableData.splice(index, 1);
-        }
-        this.calcAmount();
-      },
-
-      calcAmount() {
-        // 触发 computed 重新计算
-        this.$forceUpdate();
-      },
-
-      save() {
-        // 简单校验
-        if (this.form.tableData.length === 0) {
-          this.$message.warning('请至少添加一条收款记录');
-          return;
-        }
-        for (let i = 0; i < this.form.tableData.length; i++) {
-          const item = this.form.tableData[i];
-          if (!item.receivedDate) {
-            this.$message.warning(`第${i + 1}行收款时间不能为空`);
-            return;
-          }
-          if (!item.receivedTotalPrice) {
-            this.$message.warning(`第${i + 1}行已收金额不能为空`);
-            return;
-          }
-          if (!item.paymentType) {
-            this.$message.warning(`第${i + 1}行支付类型不能为空`);
-            return;
-          }
-        }
-
-        this.loading = true;
-        const params = this.form.tableData.map(item => {
+        this.form.tableData = this.info.detailList.map(item => {
           return {
             ...item,
-            advanceId: this.row.id,
-            advanceCode: this.row.code,
+            paymentType: item.paymentType.toString()
           }
-        })
-        saveDetailDetail(params).then(() => {
-          this.loading = false;
-          this.$message.success('收款成功');
-          this.updateVisible(false);
-          this.$emit('done');
-        });
+        }) || [];
+      },
+
+      save() {
+        // 先校验公共组件内的表单
+        this.$refs.collectTableFormRef.validate().then(() => {
+          // 取整个表单数据
+          const data = this.$refs.collectTableFormRef.getData();
+          this.loading = true;
+          const params = data.tableData.map((item) => {
+            return {
+              ...item,
+              advanceId: this.row.id,
+              advanceCode: this.row.code
+            };
+          });
+          saveDetailDetail(params)
+            .then(() => {
+              this.loading = false;
+              this.$message.success('收款成功');
+              this.updateVisible(false);
+              this.$emit('done');
+            })
+            .catch(() => {
+              this.loading = false;
+            });
+        }).catch(() => {});
       },
 
       updateVisible(value) {

+ 22 - 22
src/views/financialManage/advanceReceipt/index.vue

@@ -86,7 +86,7 @@
             </template>
             <template v-else-if="row.status == 0">
               <el-link
-                v-if="$hasPermission('eom:finadvance:collect')"
+                v-if="$hasPermission('eom:finadvance:complete')"
                 type="primary"
                 :underline="false"
                 @click="handleCollect(row)"
@@ -198,20 +198,20 @@
             showOverflowTooltip: true,
             minWidth: 140
           },
-          {
-            prop: 'reconciledAmount',
-            label: '已对账确收金额',
-            align: 'center',
-            showOverflowTooltip: true,
-            minWidth: 140
-          },
-          {
-            prop: 'unreconciledAmount',
-            label: '未对账确收金额',
-            align: 'center',
-            showOverflowTooltip: true,
-            minWidth: 140
-          },
+          // {
+          //   prop: 'reconciledAmount',
+          //   label: '已对账确收金额',
+          //   align: 'center',
+          //   showOverflowTooltip: true,
+          //   minWidth: 140
+          // },
+          // {
+          //   prop: 'unreconciledAmount',
+          //   label: '未对账确收金额',
+          //   align: 'center',
+          //   showOverflowTooltip: true,
+          //   minWidth: 140
+          // },
           {
             prop: 'uncollectedAdvanceAmount',
             label: '预收款未收金额',
@@ -219,13 +219,13 @@
             showOverflowTooltip: true,
             minWidth: 140
           },
-          {
-            prop: 'uncollectedToReceivableAmount',
-            label: '未收转应收金额',
-            align: 'center',
-            showOverflowTooltip: true,
-            minWidth: 140
-          },
+          // {
+          //   prop: 'uncollectedToReceivableAmount',
+          //   label: '未收转应收金额',
+          //   align: 'center',
+          //   showOverflowTooltip: true,
+          //   minWidth: 140
+          // },
           {
             prop: 'collectionStatus',
             label: '收款状态',

+ 78 - 1
src/views/financialManage/invoiceManage/components/addOrEditDialog.vue

@@ -272,6 +272,23 @@
         </el-col>
       </el-row>
     </el-form>
+    <div class="payinfo" style="margin-top: 20px;">
+        <headerTitle title="来源单据"></headerTitle>
+        <!-- <el-form ref="form" :model="tableForm"> -->
+          <ele-pro-table
+            ref="table"
+            :needPage="false"
+            :columns="columns"
+            :datasource="form.receiptPayments"
+            row-key="id"
+            class="time-form"
+          >
+            <template v-slot:toolbar>
+              <!-- <span>已开票金额:{{ totalInvoiceAmount }}</span> -->
+            </template>
+          </ele-pro-table>
+        <!-- </el-form> -->
+      </div>
     <div style="margin-top: 20px">
       <headerTitle title="关联物品清单"></headerTitle>
       <table-info
@@ -283,6 +300,7 @@
         @setPrice="setPrice"
         :invoiceAmount="form.invoiceAmount"
       ></table-info>
+      
     </div>
     <div slot="footer">
       <el-button type="primary" @click="handleSave(0)">保存</el-button>
@@ -440,7 +458,66 @@
       };
     },
     computed: {
-      ...mapGetters(['user'])
+      ...mapGetters(['user']),
+      columns() {
+        return [
+          {
+            width: 45,
+            type: 'index',
+            columnKey: 'index',
+            align: 'center',
+            fixed: 'left'
+          },
+          {
+            width: 150,
+            prop: 'sourceOrderDetailNo',
+            label: '来源编码',
+            align: 'center'
+          },
+          {
+            width: 150,
+            prop: 'relatedOrderNo',
+            label: '来源编号',
+            align: 'center'
+          },
+          {
+            width: 120,
+            prop: 'relatedOrderType',
+            label: '来源类型',
+            align: 'center',
+            formatter: (row) => {
+              return row.relatedOrderType == 1 ? '销售订单' : '采购订单'
+            }
+          },
+          // {
+          //   width: 170,
+          //   prop: 'planPaymentAmount',
+          //   label: this.form.type == 1 ? '计划收款金额' : '计划付款金额',
+          //   align: 'center',
+          //   slot: 'planPaymentAmount'
+          // },
+          {
+            width: 150,
+            prop: 'unInvoiceAmount',
+            label: '未开票金额',
+            align: 'center'
+          },
+          {
+            width: 150,
+            prop: 'invoicedAmount',
+            label: '已开票金额',
+            align: 'center'
+          },
+          {
+            width: 160,
+            prop: 'invoiceAmount',
+            label: '本次开票金额',
+            align: 'center',
+            // slot: 'invoiceAmount',
+            fixed: 'right'
+          }
+        ];
+      },
     },
     data() {
       return {

+ 10 - 9
src/views/financialManage/invoiceManage/components/addOrEditDialogNew.vue

@@ -44,20 +44,21 @@
               style="width: 100%"
             >
               <el-option v-for="item in sourceTypeOp" :label="item.label" :value="item.value" :key="item.value"></el-option>
-              <!-- <el-option label="对账单" :value="1"></el-option>
+              <el-option label="对账单" :value="1"></el-option>
               <el-option label="合同" :value="2"></el-option>
               <el-option label="订单" :value="3"></el-option>
-              <el-option label="收款计划" :value="1"></el-option>
+              <!-- <el-option label="收款计划" :value="1"></el-option> -->
               <el-option
-                label="应收"
-                :value="5"
+                label="已收"
+                :value="8"
                 v-if="form.type == 1"
               ></el-option>
               <el-option
-                label="应付"
-                :value="4"
+                label="已付"
+                :value="9"
                 v-if="form.type == 2"
-              ></el-option> -->
+              ></el-option>
+              <el-option label="其他无订单无合同" :value="10"></el-option>
             </el-select>
           </el-form-item>
         </el-col>
@@ -369,7 +370,7 @@
       @reload="reload"
     ></process-submit-dialog>
     <!--对账单-->
-    <!-- <account-statement-dialog
+    <account-statement-dialog
       ref="accountStatementDialogRef"
       :form="form"
       v-if="accountStatementDialogFlag"
@@ -378,7 +379,7 @@
       :contactData="contactData"
       :type="form.type"
     >
-    </account-statement-dialog> -->
+    </account-statement-dialog>
     <!--采购订单-->
     <purchasinOrderListDialog
       ref="orderListDialogRef"

+ 107 - 57
src/views/financialManage/invoiceManage/components/tableInfo.vue

@@ -10,7 +10,11 @@
         class="time-form"
       >
         <template v-slot:toolbar>
-          <span>本次开票合计:{{ invoiceAmount }}</span>
+          <el-button type="text" @click="addRow">新增</el-button>
+          <el-button type="text" @click="removeRow(scope.row)">选择商品</el-button>
+          <el-button type="text" @click="removeRow(scope.row)">批量设置税率</el-button>
+          <el-button type="text" @click="removeRow(scope.row)">批量设置单价</el-button>
+          <el-button type="text" @click="removeRow(scope.row)">批量设置折让比例</el-button>
         </template>
         
         <template v-slot:totalCount="scope">
@@ -89,13 +93,13 @@
             fixed: 'left'
           },
 
-          {
-            width: 100,
-            prop: 'typeName',
-            label: '类型',
-            slot: 'typeName',
-            align: 'center'
-          },
+          // {
+          //   width: 100,
+          //   prop: 'typeName',
+          //   label: '类型',
+          //   slot: 'typeName',
+          //   align: 'center'
+          // },
           // {
           //   minWidth: 100,
           //   prop: 'sourceCode',
@@ -110,43 +114,42 @@
           //   slot: 'productCategoryName',
           //   align: "center"
           // },
-          // {
-          //   width: 100,
-          //   prop: 'productCode',
-          //   label: '编码',
-          //   slot: 'productCode',
-          //   align: "center"
-          // },
+          {
+            width: 100,
+            prop: 'productCode',
+            label: '产品编码',
+            align: "center"
+          },
           {
             minWidth: 100,
             prop: 'productName',
-            label: '名称',
-            slot: 'productName',
+            label: '产品名称',
             align: 'center'
           },
           {
             minWidth: 100,
-            prop: 'modelType',
-            label: '型号',
-            slot: 'modelType',
+            prop: 'batchNo',
+            label: '批次号',
             align: 'center'
           },
-
           {
             minWidth: 100,
             prop: 'specification',
             label: '规格',
-            slot: 'specification',
             align: 'center'
           },
-          // {
-          //   width: 100,
-          //   prop: 'productBrand',
-          //   label: '牌号',
-          //   slot: 'productBrand',
-          //   align: "center"
-          // },
-
+          {
+            minWidth: 100,
+            prop: 'modelType',
+            label: '型号',
+            align: 'center'
+          },
+          {
+            minWidth: 100,
+            prop: 'colorKey',
+            label: '颜色',
+            align: 'center'
+          },
           {
             width: 100,
             prop: 'totalCount',
@@ -158,45 +161,50 @@
           {
             width: 100,
             prop: 'measuringUnit',
-            label: '单位',
+            label: '计量单位',
             slot: 'measuringUnit',
             align: 'center'
           },
-          // {
-          //   width: 150,
-          //   prop: 'pricingWay',
-          //   label: '计价方式',
-          //   formatter: (row, column) => {
-          //     return row.pricingWay == 1
-          //       ? '按数量计价'
-          //       : row.pricingWay == 2
-          //       ? '按重量计价'
-          //       : '';
-          //   },
-          //   align: 'center'
-
-          // },
+          {
+            width: 80,
+            prop: 'singleWeight',
+            label: '重量',
+            align: 'center'
+          },
+          {
+            width: 80,
+            prop: 'singleWeight',
+            label: '重量单位',
+            align: 'center'
+          },
+          {
+            width: 150,
+            prop: 'pricingWay',
+            label: '计价方式',
+            formatter: (row, column) => {
+              return row.pricingWay == 1
+                ? '按数量计价'
+                : row.pricingWay == 2
+                ? '按重量计价'
+                : '';
+            },
+            align: 'center'
+          },
           {
             minWidth: 80,
             prop: 'singlePrice',
-            label: '单价(¥)',
+            label: '工艺路线',
             slot: 'singlePrice',
             align: 'center'
           },
-          // {
-          //   width: 80,
-          //   prop: 'singleWeight',
-          //   label: '单量',
-          //   align: 'center'
-          // },
-
           {
             minWidth: 80,
-            prop: 'totalPrice',
-            label: '金额(¥)',
-            slot: 'totalPrice',
+            prop: 'singlePrice',
+            label: '单价',
+            slot: 'singlePrice',
             align: 'center'
           },
+          
           {
             minWidth: 80,
             prop: 'taxRate',
@@ -208,7 +216,49 @@
                 ? _row.taxRate+'%'
                 : '';
             },
-          }
+          },
+          {
+            minWidth: 80,
+            prop: 'singlePrice',
+            label: '不含税单价',
+            slot: 'singlePrice',
+            align: 'center'
+          },
+          {
+            minWidth: 80,
+            prop: 'totalPrice',
+            label: '合计',
+            slot: 'totalPrice',
+            align: 'center'
+          },
+          {
+            minWidth: 80,
+            prop: 'totalPrice',
+            label: '折让比例',
+            slot: 'totalPrice',
+            align: 'center'
+          },
+          {
+            minWidth: 80,
+            prop: 'totalPrice',
+            label: '折让单价',
+            slot: 'totalPrice',
+            align: 'center'
+          },
+          {
+            minWidth: 80,
+            prop: 'totalPrice',
+            label: '折让合计',
+            slot: 'totalPrice',
+            align: 'center'
+          },
+          {
+            minWidth: 80,
+            prop: 'action',
+            label: '操作',
+            slot: 'action',
+            align: 'center'
+          },
           // {
           //   width: 100,
           //   prop: 'sourceType',

+ 62 - 12
src/views/financialManage/receivableManage/components/collectionDialog.vue

@@ -11,9 +11,15 @@
     :maxable="true"
     :resizable="true"
   >
-    <info-table ref="feeRelatedInfoTable" v-if="form.id" :form.sync="form"></info-table>
+    <collect-table-form
+      ref="collectTableFormRef"
+      :form.sync="form"
+      :total-price="form.receivableTotalPrice"
+      :dialog-type="dialogType"
+      total-label="应收金额"
+    ></collect-table-form>
     <div slot="footer">
-      <el-button type="primary" @click="handleSave(0)">保存</el-button>
+      <el-button type="primary" @click="save(0)">保存</el-button>
       <el-button @click="cancel">返回</el-button>
     </div>
 
@@ -21,12 +27,14 @@
 </template>
 <script>
 import infoTable from "./infoTable.vue";
+import collectTableForm from '@/BIZComponents/collectTableForm/index.vue';
 import {finReceivableSaveAPI, getFinReceivableInfoAPI, finReceivableUpdateAmountAPI} from "@/api/financialManage/receivableManage";
 
 export default {
   name: "collectionDialog",
   components: {
-    infoTable
+    infoTable,
+    collectTableForm
   },
   props: {
     collectionDialogFlag: {
@@ -37,10 +45,14 @@ export default {
   computed: {},
   data() {
     return {
+      row: {},
       dialogType: '',
       title: '',
+      loading: false,
+      info: {},
       form: {
         id: '',
+        tableData: []
       },
       rules: {}
     }
@@ -50,26 +62,64 @@ export default {
   },
   methods: {
     //初始化
-    async init(row = {}, type) {
-      this.title = '收款'
-      this.dialogType = type
-
-      await this.getInfo(row.id)
-
+    async open(row, type) {
+      this.row = row || {};
+      this.dialogType = type || 'update';
+      this.title = this.dialogType === 'view' ? '收款详情' : '收款';
+      this.info = await getFinReceivableInfoAPI(row.id);
+      // 直接替换整个 form,保证 tableData 等字段响应式更新
+      this.form = {
+        ...this.info,
+        tableData: this.info.detailList || []
+      };
     },
     //获取详情
     async getInfo(id) {
       this.form = await getFinReceivableInfoAPI(id)
     },
+
+    save() {
+        // 先校验公共组件内的表单
+        this.$refs.collectTableFormRef.validate().then(() => {
+          // 取整个表单数据
+          const data = this.$refs.collectTableFormRef.getData();
+          this.loading = true;
+          const params = data.tableData.map((item) => {
+            return {
+              ...item,
+              receivableId: this.row.id,
+              receivableCode: this.row.code
+            };
+          });
+          finReceivableSaveAPI(params)
+            .then(() => {
+              this.loading = false;
+              this.$message.success('收款成功');
+              this.updateVisible(false);
+              this.$emit('done');
+            })
+            .catch(() => {
+              this.loading = false;
+            });
+        }).catch(() => {});
+      },
+
+      updateVisible(value) {
+        this.visible = value;
+        if (!value) {
+          this.row = {};
+          this.form.tableData = [];
+        }
+      },
     //
     async handleSave() {
       let data = await this.$refs.feeRelatedInfoTable.getTableValidate()
       console.log('data~~~', data)
       let detailList = data[0]
       let receivableTotalPrice = data[1]
-      await finReceivableUpdateAmountAPI(this.form.id, {
-        receivableTotalPrice: receivableTotalPrice
-      })
+      // await finReceivableUpdateAmountAPI(this.form.id, {
+      //   receivableTotalPrice: receivableTotalPrice
+      // })
      await finReceivableSaveAPI(detailList)
       this.$message.success('操作成功')
       this.done()

+ 1 - 1
src/views/financialManage/receivableManage/index.vue

@@ -503,7 +503,7 @@
       handleCollection(row = {}, type) {
         this.collectionDialogFlag = true;
         this.$nextTick(() => {
-          this.$refs.collectionDialogRef.init(row, type);
+          this.$refs.collectionDialogRef.open(row, type);
         });
       },
       // 应收

+ 16 - 4
src/views/purchasingManage/purchaseOrder/accountstatement/components/payablesInfo.vue

@@ -105,6 +105,12 @@
         </template>
       </ele-pro-table>
     </el-form>
+    <!-- 付款条件 -->
+    <receiveTermsDialog
+      ref="receiveTermsDialogRef"
+      conditionType="rules"
+      @changeParent="changeReceiveTerms"
+    ></receiveTermsDialog>
   </div>
 </template>
 
@@ -114,10 +120,11 @@
     transactionMethodsOp,
     paymentStatus
   } from '@/enum/dict';
+  import receiveTermsDialog from '@/views/financialManage/advanceReceipt/components/receiveTermsDialog.vue';
 
   export default {
     name: 'payablesInfo',
-    components: {},
+    components: { receiveTermsDialog },
     props: {
       dataForm: {
         type: Object,
@@ -198,7 +205,7 @@
             prop: 'statementAdvanceConfirmAmount',
             label: '本次对账预付款确收金额',
             align: 'center',
-            slot: 'statementAdvanceConfirmAmount'
+            // slot: 'statementAdvanceConfirmAmount'
           },
           {
             minWidth: 120,
@@ -258,9 +265,14 @@
     },
     methods: {
       openPaymentTerms(index) {
-        // 暂存当前行索引,便于后续接入付款条件选择器写回对应行
         this.currentRowIndex = index;
-        this.$message.info('付款条件选择器待接入');
+        this.$refs.receiveTermsDialogRef.open();
+      },
+      changeReceiveTerms(row, index) {
+        const targetIndex = index != null ? index : this.currentRowIndex;
+        if (targetIndex == null) return;
+        this.updateRowField(targetIndex, 'collectionConditionId', row.id);
+        this.updateRowField(targetIndex, 'collectionConditionName', row.name);
       },
       // 行内字段修改:仅更新本地副本
       updateRowField(index, key, val) {

+ 69 - 17
src/views/saleManage/saleOrder/accountstatement/components/accountDetailTable.vue

@@ -9,11 +9,15 @@
         max-height="500px"
         :datasource="flatList"
         :cache-key="cacheKeyUrl"
+        :selection.sync="selection"
         class="time-form"
       >
         <template v-slot:toolbar>
           <div class="detail-toolbar">
-            <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" />
+            <!-- <el-button
               v-if="!isView"
               type="primary"
               size="small"
@@ -33,7 +37,7 @@
               size="small"
               @click="batchSetDiscount"
               >批量设置折让比例</el-button
-            >
+            > -->
             <!-- <div class="summary-info">
               <span
                 >本次总对账金额:<b>{{ totalStatementAmount }}</b> 元</span
@@ -166,7 +170,11 @@
       </el-form>
       <div slot="footer">
         <el-button @click="batchDialogVisible = false">取消</el-button>
-        <el-button type="primary" @click="confirmBatchSet">确定</el-button>
+        <el-button
+          type="primary"
+          @click="confirmBatchSet({ key: batchType, value: batchValue })"
+          >确定</el-button
+        >
       </div>
     </el-dialog>
 
@@ -182,11 +190,13 @@
   import { parameterGetByCode } from '@/api/main/index.js';
   import { pricingWayList } from '@/enum/dict.js';
   import ProductionVersion from '@/components/ProductionVersion2/index.vue';
+  import setAllValue from '@/BIZComponents/setAllValue.vue'; //批量修改
 
   export default {
     name: 'accountDetailTable',
     components: {
-      ProductionVersion
+      ProductionVersion,
+      setAllValue
     },
     props: {
       datasource: {
@@ -207,6 +217,7 @@
         cacheKeyUrl: 'eom-202608171423-account-detail-table',
         isTaxRate: 0,
         flatList: [],
+        selection: [],
         totalStatementAmount: 0,
         totalStatementCount: 0,
         batchDialogVisible: false,
@@ -223,6 +234,13 @@
       columns() {
         let columnsVersion = this.columnsVersion;
         return [
+          {
+            width: 45,
+            type: 'selection',
+            columnKey: 'selection',
+            align: 'center',
+            fixed: 'left'
+          },
           {
             width: 60,
             label: '序号',
@@ -231,24 +249,25 @@
             align: 'center',
             fixed: 'left'
           },
+          
           {
             minWidth: 140,
             prop: 'orderNo',
-            label: '销售订单编码',
+            label: this.queryDimension == 3 ? '采购订单编码' : '销售订单编码',
             align: 'center',
             showOverflowTooltip: true
           },
           {
             minWidth: 140,
             prop: 'statementSubOrderNo',
-            label: this.queryDimension == 1 ? '发货单编码' : '调拨单编码',
+            label: this.queryDimension == 3 ? '收货单编码' : this.queryDimension == 1 ? '发货单编码' : '调拨单编码',
             align: 'center',
             showOverflowTooltip: true
           },
           {
             minWidth: 120,
             prop: 'productOperateTime',
-            label: this.queryDimension == 1 ? '发货日期' : '调拨单日期',
+            label: this.queryDimension == 3 ? '收货日期' : this.queryDimension == 1 ? '发货日期' : '调拨单日期',
             align: 'center',
             showOverflowTooltip: true
           },
@@ -299,7 +318,7 @@
           {
             minWidth: 120,
             prop: 'totalCount',
-            label: this.queryDimension == 1 ? '发货数量' : '调拨数量',
+            label: this.queryDimension == 3 ? '收货数量' : this.queryDimension == 1 ? '发货数量' : '调拨数量',
             align: 'center',
           },
           
@@ -634,9 +653,11 @@
         const max = Math.abs(row.outboundCount || 0);
         if (Math.abs(val) > max) {
           callback(new Error('不能超出库数量'));
-        } else if (this.queryDimension == 1 && val != row.outboundCount) {
-          callback(new Error('按发货单查询时不可修改'));
-        } else {
+        } 
+        // else if (this.queryDimension == 1 && val != row.outboundCount) {
+        //   callback(new Error('按发货单查询时不可修改'));
+        // } 
+        else {
           callback();
         }
       },
@@ -659,10 +680,15 @@
         this.batchValue = '';
         this.batchDialogVisible = true;
       },
-      confirmBatchSet() {
-        // flatList 为表格实际渲染的扁平行数据,直接逐行设置并重新计算
-        this.flatList.forEach((item) => {
-          this.$set(item, this.batchType, +this.batchValue);
+      // 批量设置:只作用于选中的行(selection)
+      confirmBatchSet({ key, value }) {
+        if (!this.selection.length) {
+          this.$message.warning('请先勾选需要批量设置的行');
+          this.batchDialogVisible = false;
+          return;
+        }
+        this.selection.forEach((item) => {
+          this.$set(item, key, +value);
           this.calcRow(item);
         });
         this.calcTotals();
@@ -680,15 +706,41 @@
       },
       getValidForm() {
         return new Promise((resolve, reject) => {
-          this.$refs.detailForm.validate((valid) => {
+          this.$refs.detailForm.validate((valid, invalidFields) => {
             if (valid) resolve(true);
             else {
-              this.$message.error('对账明细未填写完整');
+              this.$message.warning(this.buildValidateTip(invalidFields));
               reject(false);
             }
           });
         });
       },
+      // 根据校验失败字段生成具体提示
+      buildValidateTip(invalidFields) {
+        const fieldLabelMap = {
+          statementCount: '本次对账数量',
+          singlePrice: '单价',
+          taxRate: '税率',
+          discountRatio: '折让比例'
+        };
+        const tips = [];
+        Object.keys(invalidFields || {}).forEach((path) => {
+          // path 形如 flatList.0.singlePrice
+          const match = path.match(/flatList\.(\d+)\.(.+)/);
+          if (match) {
+            const rowIndex = +match[1] + 1;
+            const field = match[2];
+            const label = fieldLabelMap[field] || field;
+            const msg =
+              (invalidFields[path] && invalidFields[path][0] && invalidFields[path][0].message) ||
+              '';
+            tips.push(`第${rowIndex}行「${label}」${msg || '未填写'}`);
+          } else {
+            tips.push(path);
+          }
+        });
+        return tips.length ? tips.join(';') : '对账明细未填写完整';
+      },
       // 打开工艺路线选择弹窗(参考 inventoryTable.openVersion)
       openVersion(index) {
         this.$refs.versionRefs.open(index);

+ 98 - 38
src/views/saleManage/saleOrder/accountstatement/components/receivableInfo.vue

@@ -31,39 +31,59 @@
         class="time-form"
       >
         <template v-slot:fundType="scope">
-          <el-select
-            v-model="scope.row.fundType"
-            placeholder="请选择"
-            class="ele-block"
-            :disabled="isView"
-            @change="(val) => updateRowField(scope.$index, 'fundType', val)"
+          <el-form-item
+            :prop="'receivableList.' + scope.$index + '.fundType'"
+            :rules="{
+              required: true,
+              message: '请选择款项类型',
+              trigger: 'change'
+            }"
+            label-width="0"
           >
-            <el-option
-              v-for="item in paymentTypeOp"
-              :key="item.value"
-              :label="item.label"
-              :value="item.value"
-            />
-          </el-select>
+            <el-select
+              v-model="scope.row.fundType"
+              placeholder="请选择"
+              class="ele-block"
+              :disabled="isView"
+              @change="(val) => updateRowField(scope.$index, 'fundType', val)"
+            >
+              <el-option
+                v-for="item in paymentTypeOp"
+                :key="item.value"
+                :label="item.label"
+                :value="item.value"
+              />
+            </el-select>
+          </el-form-item>
         </template>
 
         <template v-slot:transactionMode="scope">
-          <el-select
-            v-model="scope.row.transactionMode"
-            placeholder="请选择"
-            class="ele-block"
-            :disabled="isView"
-            @change="
-              (val) => updateRowField(scope.$index, 'transactionMode', val)
-            "
+          <el-form-item
+            :prop="'receivableList.' + scope.$index + '.transactionMode'"
+            :rules="{
+              required: true,
+              message: '请选择交易方式',
+              trigger: 'change'
+            }"
+            label-width="0"
           >
-            <el-option
-              v-for="item in transactionMethodsOp"
-              :key="item.value"
-              :label="item.label"
-              :value="item.value"
-            />
-          </el-select>
+            <el-select
+              v-model="scope.row.transactionMode"
+              placeholder="请选择"
+              class="ele-block"
+              :disabled="isView"
+              @change="
+                (val) => updateRowField(scope.$index, 'transactionMode', val)
+              "
+            >
+              <el-option
+                v-for="item in transactionMethodsOp"
+                :key="item.value"
+                :label="item.label"
+                :value="item.value"
+              />
+            </el-select>
+          </el-form-item>
         </template>
 
         <template v-slot:collectionConditionName="scope">
@@ -105,6 +125,10 @@
             >删除</el-link
           >
         </template>
+
+        <template v-slot:columnRequired="{ column }">
+          <span class="is-required">{{ column.label }}</span>
+        </template>
       </ele-pro-table>
     </el-form>
     <!-- 收款条件 -->
@@ -171,7 +195,7 @@
           },
           {
             minWidth: 140,
-            prop: 'advanceCode',
+            prop: 'advanceCodes',
             label: '预收编码',
             align: 'center',
             showOverflowTooltip: true
@@ -181,14 +205,16 @@
             minWidth: 140,
             label: '款项类型',
             align: 'center',
-            slot: 'fundType'
+            slot: 'fundType',
+            headerSlot: 'columnRequired'
           },
           {
             prop: 'transactionMode',
             minWidth: 140,
             label: '交易方式',
             align: 'center',
-            slot: 'transactionMode'
+            slot: 'transactionMode',
+            headerSlot: 'columnRequired'
           },
           {
             prop: 'collectionConditionName',
@@ -199,15 +225,33 @@
           },
           {
             minWidth: 140,
-            prop: 'unreconciledAdvanceConfirmAmount',
-            label: '未对账预收款确收金额',
+            prop: 'advanceTotalAmt',
+            label: '预收金额',
             align: 'center',
             showOverflowTooltip: true
           },
           {
             minWidth: 160,
-            prop: 'statementAdvanceConfirmAmount',
-            label: '本次对账预收款确收金额',
+            prop: 'confirmedTotalAmt',
+            label: '预收款确收金额',
+            align: 'center'
+          },
+          {
+            minWidth: 160,
+            prop: 'uncollectedAdvanceAmt',
+            label: '预收款未收金额',
+            align: 'center'
+          },
+          {
+            minWidth: 160,
+            prop: 'lastReceivedAmt',
+            label: '上期对账金额',
+            align: 'center'
+          },
+          {
+            minWidth: 160,
+            prop: 'statementAmt',
+            label: '本次对账应收总金额',
             align: 'center'
           },
           {
@@ -219,8 +263,8 @@
           },
           {
             minWidth: 120,
-            prop: 'receivedPrice',
-            label: '本次应收金额',
+            prop: 'receivedAmt',
+            label: '累计应收金额',
             align: 'center',
             showOverflowTooltip: true
           }
@@ -329,18 +373,30 @@
         }
         this.recalcRow(row);
       },
-      // 根据本次对账金额重算单行:确收金额 = min(未对账预收款确收金额, 本次对账金额);应收金额 = 本次对账金额 - 确收金额
+      // 根据本次对账金额重算单行:
+      // 确收金额 = min(未对账预收款确收金额, 本次对账金额)
+      // 累计应收金额 = 本次对账应收总金额 - 预收款确收金额 + 上期对账金额
       recalcRow(row) {
         const statementAmount = +row.statementAmount || 0;
         let val = +row.statementAdvanceConfirmAmount || 0;
         const max = this.getMaxConfirmAmount(row);
         if (val > max) val = max;
         this.$set(row, 'statementAdvanceConfirmAmount', val);
+        // 本次应收金额 = 本次对账金额 - 确收金额(保留旧字段)
         this.$set(
           row,
           'receivedPrice',
           Math.round((statementAmount - val) * 100) / 100
         );
+        // 累计应收金额 = 本次对账应收总金额 - 预收款确收金额 + 上期对账金额
+        // 本次对账应收总金额优先取 statementAmt,为空时回退到 statementAmount
+        const statementAmt = +row.statementAmt || +row.statementAmount || 0;
+        const lastReceivedAmt = +row.lastReceivedAmt || 0;
+        this.$set(
+          row,
+          'receivedAmt',
+          Math.round((statementAmt - val + lastReceivedAmt) * 100) / 100
+        );
       },
       // 明细表按销售单号汇总本次对账金额后,同步到对应销售单号的应收行
       syncStatementAmount(summaryMap) {
@@ -349,6 +405,7 @@
           const amount = summaryMap[row.salesCode];
           if (amount !== undefined) {
             this.$set(row, 'statementAmount', amount);
+            this.$set(row, 'statementAmt', amount);
             this.recalcRow(row);
           }
         });
@@ -402,5 +459,8 @@
       justify-content: flex-end;
       margin-bottom: 10px;
     }
+    .el-form-item {
+      margin-bottom: 0!important;
+    }
   }
 </style>