Kaynağa Gözat

feat:对账单优化

liujt 4 gün önce
ebeveyn
işleme
491d4b90be

+ 735 - 0
src/views/bpm/handleTask/components/saleOrder/accountstatement/accountDetailTable.vue

@@ -0,0 +1,735 @@
+<template>
+  <div class="account-detail-table">
+    <el-form ref="detailForm" :model="{ flatList }">
+      <ele-pro-table
+        ref="detailTable"
+        row-key="id"
+        :needPage="false"
+        :columns="columns"
+        max-height="500px"
+        :datasource="flatList"
+        :cache-key="cacheKeyUrl"
+        class="time-form"
+      >
+        <template v-slot:toolbar>
+          <div class="detail-toolbar">
+            <el-button
+              v-if="!isView"
+              type="primary"
+              size="small"
+              @click="batchSetTaxRate"
+              >批量设置税率</el-button
+            >
+            <el-button
+              v-if="!isView"
+              type="primary"
+              size="small"
+              @click="batchSetPrice"
+              >批量设置单价</el-button
+            >
+            <el-button
+              v-if="!isView"
+              type="primary"
+              size="small"
+              @click="batchSetDiscount"
+              >批量设置折让比例</el-button
+            >
+            <!-- <div class="summary-info">
+              <span
+                >本次总对账金额:<b>{{ totalStatementAmount }}</b> 元</span
+              >
+              <span
+                >本次总对账数量:<b>{{ totalStatementCount }}</b></span
+              >
+            </div> -->
+          </div>
+        </template>
+        <template v-slot:statementCount="scope">
+          <el-form-item
+            :prop="'flatList.' + scope.$index + '.statementCount'"
+            :rules="{
+              validator: (rule, value, cb) =>
+                validateStatementCount(rule, value, cb, scope.row),
+              trigger: 'change'
+            }"
+          >
+            <el-input
+              v-model="scope.row.statementCount"
+              type="number"
+              :disabled="isView || queryDimension == 1"
+              @input="handleCountChange(scope.row, scope.$index)"
+            />
+          </el-form-item>
+        </template>
+
+        <template v-slot:technologyRouteName="scope">
+          <el-input
+            v-model="scope.row.technologyRouteName"
+            placeholder="请选择"
+            :disabled="isView"
+            @click.native="openVersion(scope.$index)"
+          />
+        </template>
+
+        <template v-slot:singlePrice="scope">
+          <el-form-item
+            :prop="'flatList.' + scope.$index + '.singlePrice'"
+            :rules="{
+              required: true,
+              message: '请输入单价',
+              trigger: 'change'
+            }"
+          >
+            <el-input
+              v-model="scope.row.singlePrice"
+              type="number"
+              :disabled="isView"
+              @input="handlePriceChange(scope.row, scope.$index)"
+            />
+            <!-- <div v-if="isPriceInvalid(scope.row)" class="price-warning">
+              单价无效,请核对商品价格
+            </div> -->
+          </el-form-item>
+        </template>
+
+        <template v-slot:taxRate="scope">
+          <el-form-item
+            :prop="'flatList.' + scope.$index + '.taxRate'"
+            :rules="{
+              required: isTaxRate == 1,
+              message: '请输入税率',
+              trigger: 'change'
+            }"
+          >
+            <el-input
+              v-model="scope.row.taxRate"
+              type="number"
+              :disabled="isView"
+              @input="handleTaxChange(scope.row, scope.$index)"
+            />
+          </el-form-item>
+        </template>
+
+        <template v-slot:discountRatio="scope">
+          <el-form-item
+            :prop="'flatList.' + scope.$index + '.discountRatio'"
+            :rules="{
+              required: true,
+              message: '请输入折让比例',
+              trigger: 'change'
+            }"
+          >
+            <el-input
+              v-model="scope.row.discountRatio"
+              type="number"
+              :min="0"
+              :max="100"
+              :disabled="isView"
+              @input="handleDiscountChange(scope.row, scope.$index)"
+            />
+          </el-form-item>
+        </template>
+
+        <template v-slot:statementAmount="scope">
+          <span>{{ scope.row.statementAmount }}</span>
+        </template>
+
+        <template v-slot:headerRequired="{ column }">
+              <span class="is-required">{{ column.label }}</span>
+            </template>
+
+        <template v-slot:action="scope">
+          <el-link
+            v-if="!isView && queryDimension == 2"
+            type="danger"
+            :underline="false"
+            icon="el-icon-delete"
+            @click="removeRow(scope.$index)"
+            >删除</el-link
+          >
+          <span v-else>--</span>
+        </template>
+      </ele-pro-table>
+    </el-form>
+
+    <!-- 批量设置弹窗 -->
+    <el-dialog
+      :visible.sync="batchDialogVisible"
+      title="批量设置"
+      width="400px"
+      append-to-body
+    >
+      <el-form label-width="100px">
+        <el-form-item :label="batchLabel">
+          <el-input v-model="batchValue" type="number" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer">
+        <el-button @click="batchDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="confirmBatchSet">确定</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 工艺路线选择弹窗 -->
+    <ProductionVersion
+      ref="versionRefs"
+      @changeProduct="changeProduct"
+    ></ProductionVersion>
+  </div>
+</template>
+
+<script>
+  import { parameterGetByCode } from '@/api/main/index.js';
+  import { pricingWayList } from '@/enum/dict.js';
+  import ProductionVersion from '@/components/ProductionVersion2/index.vue';
+
+  export default {
+    name: 'accountDetailTable',
+    components: {
+      ProductionVersion
+    },
+    props: {
+      datasource: {
+        type: Array,
+        default: () => []
+      },
+      dialogType: {
+        type: String,
+        default: 'add'
+      },
+      queryDimension: {
+        type: Number,
+        default: 1
+      }
+    },
+    data() {
+      return {
+        cacheKeyUrl: 'eom-202608171423-account-detail-table',
+        isTaxRate: 0,
+        flatList: [],
+        totalStatementAmount: 0,
+        totalStatementCount: 0,
+        batchDialogVisible: false,
+        batchType: '',
+        batchValue: '',
+        batchLabel: '',
+        columnsVersion: 1,
+      };
+    },
+    computed: {
+      isView() {
+        return this.dialogType === 'view';
+      },
+      columns() {
+        let columnsVersion = this.columnsVersion;
+        return [
+          {
+            width: 60,
+            label: '序号',
+            type: 'index',
+            columnKey: 'index',
+            align: 'center',
+            fixed: 'left'
+          },
+          {
+            minWidth: 140,
+            prop: 'orderNo',
+            label: '销售订单编码',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 140,
+            prop: 'statementSubOrderNo',
+            label: this.queryDimension == 1 ? '发货单编码' : '调拨单编码',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 120,
+            prop: 'productOperateTime',
+            label: this.queryDimension == 1 ? '发货日期' : '调拨单日期',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 120,
+            prop: 'productCode',
+            label: '产品编码',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 140,
+            prop: 'productName',
+            label: '产品名称',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 120,
+            prop: 'batchNo',
+            label: '批次号',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          
+          {
+            minWidth: 110,
+            prop: 'historicalStatementCount',
+            label: '已对账数量',
+            align: 'center'
+          },
+          {
+            minWidth: 140,
+            headerSlot: 'headerRequired',
+            prop: 'statementCount',
+            label: '本次对账数量',
+            align: 'center',
+            slot: 'statementCount'
+          },
+          {
+            minWidth: 120,
+            headerSlot: 'headerRequired',
+            prop: 'statementAmount',
+            label: '本次对账金额',
+            align: 'center',
+            slot: 'statementAmount'
+          },
+          {
+            minWidth: 120,
+            prop: 'totalCount',
+            label: this.queryDimension == 1 ? '发货数量' : '调拨数量',
+            align: 'center',
+          },
+          
+          {
+            minWidth: 140,
+            prop: 'technologyRouteName',
+            label: '工艺路线',
+            align: 'center',
+            slot: 'technologyRouteName',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 120,
+            prop: 'singlePrice',
+            label: '单价',
+            align: 'center',
+            slot: 'singlePrice'
+          },
+          {
+            minWidth: 100,
+            prop: 'taxRate',
+            label: '税率',
+            align: 'center',
+            slot: 'taxRate'
+          },
+          {
+            minWidth: 100,
+            prop: 'noTaxSinglePrice',
+            label: '不含税单价',
+            align: 'center',
+          },
+          {
+            minWidth: 100,
+            prop: 'totalPrice',
+            label: '合计',
+            align: 'center',
+          },
+          {
+            minWidth: 120,
+            headerSlot: 'headerRequired',
+            prop: 'discountRatio',
+            label: '折让比例',
+            align: 'center',
+            slot: 'discountRatio'
+          },
+          {
+            minWidth: 120,
+            prop: 'discountSinglePrice',
+            label: '折让单价',
+            align: 'center',
+          },
+          {
+            minWidth: 120,
+            prop: 'discountTotalPrice',
+            label: '折让合计',
+            align: 'center',
+          },
+          {
+            minWidth: 100,
+            prop: 'specification',
+            label: '规格',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 100,
+            prop: 'modelType',
+            label: '型号',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 80,
+            prop: 'color',
+            label: '颜色',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 120,
+            prop: 'outInCount',
+            label: '出库数量',
+            align: 'center',
+          },
+          {
+            minWidth: 120,
+            prop: 'measuringUnit',
+            label: '计量单位',
+            align: 'center',
+          },
+          {
+            minWidth: 120,
+            prop: 'sendTotalWeight',
+            label: '出库重量',
+            align: 'center',
+          },
+          {
+            minWidth: 120,
+            prop: 'weightUnit',
+            label: '重量单位',
+            align: 'center',
+          },
+          {
+            minWidth: 120,
+            prop: 'pricingWay',
+            label: '计价方式',
+            align: 'center',
+            formatter: (row, column) => {
+              return pricingWayList.find((item) => item.id == row.pricingWay)
+                ?.name;
+            }
+          },
+        ]
+      }
+    },
+    watch: {
+      datasource: {
+        handler(newVal, oldVal) {
+          this.$nextTick(() => {
+            // 直接基于原始 productList 扁平行初始化派生字段(单价/税率/折让比例默认值及本次对账金额)
+            const list = (newVal || []).map((item) => this.buildRow(item, item.orderNo || '', false));
+            this.flatList = list;
+            console.log(this.flatList)
+            this.calcTotals();
+          });
+        },
+        immediate: true
+      },
+      queryDimension() {
+        // this.buildFlatList();
+        this.calcTotals();
+      }
+    },
+    created() {
+      parameterGetByCode({ code: 'eom_saleOrder_order-taxRate' }).then(
+        (res) => {
+          this.isTaxRate = res.value;
+        }
+      );
+    },
+    methods: {
+      buildRow(item, orderNo, isReturn) {
+        const outboundCount = +item.totalCount || 0;
+        // 退货单数量为负
+        const count = isReturn ? -Math.abs(outboundCount) : outboundCount;
+        const statemented = +item.statementedCount || 0;
+        // 发货单:本次对账数量=出库数量(不可改);调拨单:默认=出库数量(可改)
+        let statementCount = item.statementCount;
+        if (
+          statementCount === undefined ||
+          statementCount === null ||
+          statementCount === ''
+        ) {
+          statementCount = count;
+        }
+        const singlePrice = +item.singlePrice || 0;
+        const discountRatio =
+          item.discountRatio === undefined ||
+          item.discountRatio === null ||
+          item.discountRatio === ''
+            ? 100
+            : +item.discountRatio;
+        const taxRate =
+          item.taxRate === undefined || item.taxRate === null
+            ? 0
+            : +item.taxRate;
+        const amount =
+          Math.round(
+            statementCount * singlePrice * (discountRatio / 100) * 100
+          ) / 100;
+        const noTaxSinglePrice =
+          singlePrice && taxRate
+            ? +((singlePrice / (1 + taxRate / 100)).toFixed(4))
+            : '';
+        const totalPrice = Math.round(statementCount * singlePrice * 100) / 100;
+        const discountSinglePrice =
+          Math.round(singlePrice * (discountRatio / 100) * 100) / 100;
+        const discountTotalPrice =
+          Math.round(totalPrice * (discountRatio / 100) * 100) / 100;
+        // 直接修改原 item,保证编辑同步回 datasource
+        // this.$set(item, 'id', item.id || Math.random().toString(36).slice(2));
+        this.$set(item, 'saleOrderNo', orderNo);
+        this.$set(
+          item,
+          'statementSubOrderNo',
+          item.statementSubOrderNo || item.subOrderNo || ''
+        );
+        this.$set(
+          item,
+          'productOperateTime',
+          item.productOperateTime || item.deliveryDate || ''
+        );
+        this.$set(item, 'outboundCount', count);
+        this.$set(item, 'statementedCount', statemented);
+        this.$set(item, 'statementCount', statementCount);
+        this.$set(item, 'singlePrice', singlePrice);
+        this.$set(item, 'taxRate', taxRate);
+        this.$set(item, 'discountRatio', discountRatio);
+        this.$set(item, 'statementAmount', amount);
+        this.$set(item, 'noTaxSinglePrice', noTaxSinglePrice);
+        this.$set(item, 'totalPrice', totalPrice);
+        this.$set(item, 'discountSinglePrice', discountSinglePrice);
+        this.$set(item, 'discountTotalPrice', discountTotalPrice);
+        this.$set(item, 'isReturn', isReturn);
+        this.$set(item, 'processRouteName', item.processRouteName || '');
+        return item;
+      },
+      initDefaultValues() {
+        this.datasource.forEach((item) => {
+            if (item.singlePrice === undefined)
+              this.$set(item, 'singlePrice', 0);
+            if (item.taxRate === undefined) this.$set(item, 'taxRate', 0);
+            if (item.discountRatio === undefined)
+              this.$set(item, 'discountRatio', 100);
+        });
+      },
+      buildFlatList() {
+        const list = [];
+        // this.datasource.forEach((order) => {
+        //   const orderNo = order.orderNo || '';
+        //   (order.deliveryProducts || []).forEach((item) => {
+        //     list.push(this.buildRow(item, orderNo, false));
+        //   });
+        //   (order.returnProducts || []).forEach((item) => {
+        //     list.push(this.buildRow(item, orderNo, true));
+        //   });
+        // });
+        // this.flatList = list;
+      },
+      calcTotals() {
+        const amount =
+          this.flatList.reduce(
+            (pre, cur) => pre + Math.round((+cur.statementAmount || 0) * 100),
+            0
+          ) / 100;
+        const count = this.flatList.reduce(
+          (pre, cur) => pre + (+cur.statementCount || 0),
+          0
+        );
+        this.totalStatementAmount = amount;
+        this.totalStatementCount = count;
+        this.$emit('totalChange', amount);
+        this.$emit('countChange', count);
+        // 按销售订单编码(saleOrderNo)汇总本次对账金额,供应收信息同步
+        this.emitStatementAmountChange();
+      },
+      // 按销售订单编码汇总本次对账金额并抛出
+      emitStatementAmountChange() {
+        const summaryMap = {};
+        this.flatList.forEach((row) => {
+          const key = row.saleOrderNo;
+          if (!key) return;
+          summaryMap[key] =
+            Math.round(
+              ((summaryMap[key] || 0) + (+row.statementAmount || 0)) * 100
+            ) / 100;
+        });
+        this.$emit('statementAmountChange', summaryMap);
+      },
+      // 重算某一行金额(参考 inventoryTable.vue 价格计算链)
+      calcRow(row) {
+        const count = +row.statementCount || 0;
+        const price = +row.singlePrice || 0;
+        // 折让比例未填写时按 100(不打折)计,避免金额被算成 0
+        const ratio = row.discountRatio === '' ||
+          row.discountRatio === null ||
+          row.discountRatio === undefined ||
+          isNaN(+row.discountRatio)
+          ? 100
+          : +row.discountRatio;
+        const taxRate = +row.taxRate || 0;
+        // 本次对账金额
+        const amount = Math.round(count * price * (ratio / 100) * 100) / 100;
+        this.$set(row, 'statementAmount', amount);
+        // 不含税单价 = 单价 / (1 + 税率/100)  (与 inventoryTable.getNotaxSinglePrice 一致)
+        if (price && taxRate) {
+          this.$set(
+            row,
+            'noTaxSinglePrice',
+            +((price / (1 + taxRate / 100)).toFixed(4))
+          );
+        } else {
+          this.$set(row, 'noTaxSinglePrice', '');
+        }
+        // 合计 = 对账数量 * 单价
+        this.$set(row, 'totalPrice', Math.round(count * price * 100) / 100);
+        // 折让单价 = 单价 * 折让比例/100
+        this.$set(
+          row,
+          'discountSinglePrice',
+          Math.round(price * (ratio / 100) * 100) / 100
+        );
+        // 折让合计 = 合计 * 折让比例/100
+        this.$set(
+          row,
+          'discountTotalPrice',
+          Math.round(row.totalPrice * (ratio / 100) * 100) / 100
+        );
+      },
+      handleCountChange(row, index) {
+        this.calcRow(row);
+        this.calcTotals();
+        this.emitChange();
+      },
+      handlePriceChange(row, index) {
+        this.calcRow(row);
+        this.calcTotals();
+        this.emitChange();
+      },
+      handleTaxChange(row, index) {
+        // 税率变化需重算不含税单价、合计及折让相关字段
+        this.calcRow(row);
+        this.calcTotals();
+        this.emitChange();
+      },
+      handleDiscountChange(row, index) {
+        this.calcRow(row);
+        this.calcTotals();
+        this.emitChange();
+      },
+      emitChange() {
+        // 表格实际编辑的是 flatList,直接以其为新数据源回写父组件
+        this.$emit('update:datasource', [...this.flatList]);
+      },
+      // 单价校验:单价缺失或为 0 视为无效(需结合商品价目表有效期进一步校验)
+      isPriceInvalid(row) {
+        const price = +row.singlePrice || 0;
+        return price <= 0;
+      },
+      validateStatementCount(rule, value, callback, row) {
+        const val = +value || 0;
+        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 {
+          callback();
+        }
+      },
+      // 批量设置
+      batchSetTaxRate() {
+        this.batchType = 'taxRate';
+        this.batchLabel = '税率';
+        this.batchValue = '';
+        this.batchDialogVisible = true;
+      },
+      batchSetPrice() {
+        this.batchType = 'singlePrice';
+        this.batchLabel = '单价';
+        this.batchValue = '';
+        this.batchDialogVisible = true;
+      },
+      batchSetDiscount() {
+        this.batchType = 'discountRatio';
+        this.batchLabel = '折让比例';
+        this.batchValue = '';
+        this.batchDialogVisible = true;
+      },
+      confirmBatchSet() {
+        // flatList 为表格实际渲染的扁平行数据,直接逐行设置并重新计算
+        this.flatList.forEach((item) => {
+          this.$set(item, this.batchType, +this.batchValue);
+          this.calcRow(item);
+        });
+        this.calcTotals();
+        this.batchDialogVisible = false;
+        this.emitChange();
+      },
+      removeRow(index) {
+        // 调拨单模式下允许删除行,基于扁平行数组操作,保持数据格式一致
+        if (index < 0 || index >= this.flatList.length) return;
+        const list = [...this.flatList];
+        list.splice(index, 1);
+        this.flatList = list;
+        this.calcTotals();
+        this.emitChange();
+      },
+      getValidForm() {
+        return new Promise((resolve, reject) => {
+          this.$refs.detailForm.validate((valid) => {
+            if (valid) resolve(true);
+            else {
+              this.$message.error('对账明细未填写完整');
+              reject(false);
+            }
+          });
+        });
+      },
+      // 打开工艺路线选择弹窗(参考 inventoryTable.openVersion)
+      openVersion(index) {
+        this.$refs.versionRefs.open(index);
+      },
+      // 工艺路线选择回调(参考 inventoryTable.changeProduct)
+      changeProduct(data, index) {
+        if (this.flatList[index]) {
+          this.$set(this.flatList[index], 'technologyRouteName', data.name);
+          this.$set(this.flatList[index], 'technologyRouteId', data.id);
+          this.$set(this.flatList[index], 'technologyRouteVersion', data.version);
+          this.emitChange();
+        }
+      }
+    }
+  };
+</script>
+
+<style scoped lang="scss">
+  .account-detail-table {
+    .detail-toolbar {
+      display: flex;
+      justify-content: flex-start;
+      align-items: center;
+      margin-bottom: 10px;
+      .summary-info {
+        span {
+          margin-left: 20px;
+          b {
+            color: #f56c6c;
+          }
+        }
+      }
+    }
+    .time-form .el-form-item {
+      margin-bottom: 0 !important;
+    }
+    .price-warning {
+      color: #f56c6c;
+      font-size: 12px;
+      line-height: 1.4;
+      margin-top: 2px;
+    }
+  }
+</style>

+ 53 - 2
src/views/bpm/handleTask/components/saleOrder/accountstatement/page.vue

@@ -32,6 +32,25 @@
     <inventoryTable ref="inventoryTableref" :priceObj="priceObj" :dataForm="dataForm" :datasource.sync="datasource"
                     :dialogType="permissionType"
                     :taskDefinitionKey="taskDefinitionKey"></inventoryTable>
+    <!-- <headerTitle title="应收信息" style="margin-top: 30px"></headerTitle>
+      <receivable-info
+        :dataForm.sync="dataForm"
+        :receivableList="receivableList"
+        :dialogType="permissionType"
+        @addAdvanceReceipt="addAdvanceReceipt"
+        ref="receivableInfoRef"
+      ></receivable-info>
+
+      <headerTitle title="对账明细" style="margin-top: 30px"></headerTitle>
+      <account-detail-table
+        ref="accountDetailTableRef"
+        :datasource.sync="datasource"
+        :dialogType="permissionType"
+        :queryDimension="dataForm.queryDimension"
+        @totalChange="handleTotalChange"
+        @countChange="handleCountChange"
+        @statementAmountChange="handleStatementAmountChange"
+      ></account-detail-table> -->
   </div>
 </template>
 
@@ -42,6 +61,8 @@ import InventoryTable from "./inventoryTable.vue";
 import saleForm from "./saleForm.vue";
 import {getSearchMergeListByTypeAPI, infoAccountStatementAPI, accountstatementInfoAPI} from "@/api/bpm/components/saleManage/saleorder";
 import recorpayTableList from './recorpayTableList.vue';
+import AccountDetailTable from './accountDetailTable.vue';
+import ReceivableInfo from './receivableInfo.vue';
 
 export default {
   name: 'page',
@@ -49,11 +70,15 @@ export default {
   components: {
     InventoryTable,
     saleForm,
-    recorpayTableList
+    recorpayTableList,
+    AccountDetailTable,
+    ReceivableInfo
   },
   data() {
     return {
       datasource: [],
+      receivableList: [],
+      statReceipt: null,
       dataForm: {
         sourceType: '',
         dateType: '',
@@ -121,18 +146,44 @@ export default {
     this.getInfo(this.businessId)
   },
   methods: {
+    handleTotalChange(total) {
+        this.dataForm.amountTotalPrice = total;
+      },
+      handleCountChange(count) {
+        this.dataForm.totalStatementCount = count;
+      },
+      // 明细表本次对账金额变化时,按销售单号同步到应收信息对应行
+      handleStatementAmountChange(summaryMap) {
+        if (this.$refs.receivableInfoRef) {
+          this.$refs.receivableInfoRef.syncStatementAmount(summaryMap);
+        }
+      },
+      // 新增预收款:打开预收款选择弹窗
+      addAdvanceReceipt() {
+        this.$refs.advanceReceiptSelectRef.open();
+      },
 
     //获取对账单详情
     async getInfo(id) {
       let data = await accountstatementInfoAPI(id)
       this.datasource = data?.orderList || []
-      // this.recorpayList=data?.recorpayList||[]
+      this.recorpayList=data?.recorpayList||[]
       data.orderTotalAmount = data.orderList.reduce((pre, cur) => pre + Math.round(+cur.orderAmount * 100), 0) / 100;
       data.amountTotalPrice = data.orderList.reduce((pre, cur) => pre + Math.round(+cur.statementAmount * 100), 0) / 100;
       data.amountCompletePrice = data.orderList.reduce((pre, cur) => pre + Math.round(+cur.statementedAmount * 100), 0) / 100;
       data.amountUnCompletePrice = data.orderList.reduce((pre, cur) => pre + Math.round(+cur.unStatementAmount * 100), 0) / 100;
       this.dataForm = data
       //this.priceObj.previousPeriodEndDebt = data.previousPeriodEndDebt
+
+      // this.datasource = data.productList || [];
+      //   this.receivableList =
+      //     (data.statReceipt && data.statReceipt.statReceiptDetailList) || [];
+      //   // 保存查询到的完整 statReceipt 对象,作为提交入参
+      //   this.statReceipt = data.statReceipt || null;
+      //   this.dataForm = {
+      //     ...this.dataForm,
+      //     ...data,
+      //   };
       switch (this.dataForm.dateType) {
         case 1:
           this.dataForm.year = this.dataForm.dateValue

+ 406 - 0
src/views/bpm/handleTask/components/saleOrder/accountstatement/receivableInfo.vue

@@ -0,0 +1,406 @@
+<template>
+  <div class="receivable-info">
+    <el-form ref="form" :model="dataForm" :rules="rules" label-width="160px">
+      <el-row>
+        <el-col :span="8">
+          <el-form-item label="总对账预收款确收金额">
+            <el-input disabled v-model="statementAdvanceTotalPrice">
+              <template slot="append">元</template>
+            </el-input>
+          </el-form-item>
+        </el-col>
+        <el-col :span="8">
+          <el-form-item label="总应收金额">
+            <el-input disabled v-model="receivedTotalPrice">
+              <template slot="append">元</template>
+            </el-input>
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+
+    <el-form :model="{ receivableList: localList }" ref="tableForm">
+      <ele-pro-table
+        ref="receivableTable"
+        row-key="id"
+        :needPage="false"
+        :columns="columns"
+        max-height="400px"
+        :datasource="localList"
+        :cache-key="cacheKeyUrl"
+        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-option
+              v-for="item in paymentTypeOp"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </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-option
+              v-for="item in transactionMethodsOp"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </template>
+
+        <template v-slot:collectionConditionName="scope">
+          <el-input
+            clearable
+            :value="scope.row.collectionConditionName"
+            @click.native="openReceiveTerms(scope.$index)"
+            :placeholder="isView ? '' : '请选择'"
+            :disabled="isView"
+            readonly
+          />
+        </template>
+
+        <template v-slot:statementAdvanceConfirmAmount="scope">
+          <el-form-item
+            :prop="'receivableList.' + scope.$index + '.statementAdvanceConfirmAmount'"
+            :rules="{
+              validator: (rule, value, cb) =>
+                validateConfirmAmount(rule, value, cb, scope.row),
+              trigger: 'change'
+            }"
+          >
+            <el-input
+              v-model="scope.row.statementAdvanceConfirmAmount"
+              type="number"
+              :disabled="isView"
+              @input="handleConfirmAmountChange(scope.row, scope.$index)"
+            />
+          </el-form-item>
+        </template>
+
+        <template v-slot:action="scope">
+          <el-link
+            v-if="!isView"
+            type="danger"
+            :underline="false"
+            icon="el-icon-delete"
+            @click="removeRow(scope.$index)"
+            >删除</el-link
+          >
+        </template>
+      </ele-pro-table>
+    </el-form>
+    <!-- 收款条件 -->
+    <!-- <receiveTermsDialog
+      ref="receiveTermsDialogRef"
+      conditionType="rules"
+      @changeParent="changeReceiveTerms"
+    ></receiveTermsDialog> -->
+  </div>
+</template>
+
+<script>
+  import {
+    paymentTypeOp,
+    transactionMethodsOp,
+    paymentStatus
+  } from '@/enum/dict';
+  // import receiveTermsDialog from '@/views/financialManage/advanceReceipt/components/receiveTermsDialog.vue';
+
+  export default {
+    name: 'receivableInfo',
+    components: {
+      // receiveTermsDialog
+    },
+    props: {
+      dataForm: {
+        type: Object,
+        default: () => ({})
+      },
+      // 父组件传入的初始应收明细,仅作为初始化值,修改不再回写父
+      receivableList: {
+        type: Array,
+        default: () => []
+      },
+      dialogType: {
+        type: String,
+        default: 'add'
+      }
+    },
+    data() {
+      return {
+        cacheKeyUrl: 'eom-202608171425-account-receivable-table',
+        paymentTypeOp,
+        transactionMethodsOp,
+        paymentStatusOp: paymentStatus,
+        rules: {},
+        // 本地维护的应收明细,避免每次编辑都同步回父组件
+        localList: [],
+        columns: [
+          {
+            width: 60,
+            label: '序号',
+            type: 'index',
+            columnKey: 'index',
+            align: 'center',
+            fixed: 'left'
+          },
+          {
+            minWidth: 140,
+            prop: 'salesCode',
+            label: '销售订单编码',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 140,
+            prop: 'advanceCode',
+            label: '预收编码',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'fundType',
+            minWidth: 140,
+            label: '款项类型',
+            align: 'center',
+            slot: 'fundType'
+          },
+          {
+            prop: 'transactionMode',
+            minWidth: 140,
+            label: '交易方式',
+            align: 'center',
+            slot: 'transactionMode'
+          },
+          {
+            prop: 'collectionConditionName',
+            minWidth: 160,
+            label: '收款条件',
+            align: 'center',
+            slot: 'collectionConditionName'
+          },
+          {
+            minWidth: 140,
+            prop: 'unreconciledAdvanceConfirmAmount',
+            label: '未对账预收款确收金额',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 160,
+            prop: 'statementAdvanceConfirmAmount',
+            label: '本次对账预收款确收金额',
+            align: 'center'
+          },
+          {
+            minWidth: 120,
+            prop: 'statementAmount',
+            label: '本次对账金额',
+            align: 'center',
+            showOverflowTooltip: true
+          },
+          {
+            minWidth: 120,
+            prop: 'receivedPrice',
+            label: '本次应收金额',
+            align: 'center',
+            showOverflowTooltip: true
+          }
+        ]
+      };
+    },
+    computed: {
+      isView() {
+        return this.dialogType === 'view';
+      },
+      statementAdvanceTotalPrice() {
+        return (
+          this.localList.reduce(
+            (pre, cur) =>
+              pre + Math.round((+cur.statementAdvanceConfirmAmount || 0) * 100),
+            0
+          ) / 100
+        );
+      },
+      receivedTotalPrice() {
+        return (
+          Math.round(
+            ((+this.dataForm.amountTotalPrice || 0) -
+              this.statementAdvanceTotalPrice) *
+              100
+          ) / 100
+        );
+      }
+    },
+    watch: {
+      // 父传入初始值(查询/编辑回填)时同步到本地副本
+      receivableList: {
+        handler(val) {
+          this.localList = (val || []).map((item) => ({ ...item }));
+          this.recalcAllRows();
+        },
+        immediate: true
+      },
+      'dataForm.amountTotalPrice': {
+        handler() {
+          this.recalcAllRows();
+        },
+        immediate: true
+      }
+    },
+    methods: {
+      openReceiveTerms(index) {
+        this.currentRowIndex = index;
+        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) {
+        const item = this.localList[index];
+        if (!item) return;
+        this.localList[index] = { ...item, [key]: val };
+        this.recalcRow(item);
+      },
+      // 新增一行预收款(空白行,供手动填写)
+      addRow() {
+        this.localList.push({
+          id: 'local_' + Date.now() + '_' + this.localList.length,
+          salesCode: '',
+          advanceCode: '',
+          fundType: '',
+          transactionMode: '',
+          collectionConditionId: '',
+          collectionConditionName: '',
+          unreconciledAdvanceConfirmAmount: 0,
+          statementAdvanceConfirmAmount: 0,
+          statementAmount: 0,
+          receivedPrice: 0
+        });
+      },
+      // 由父组件预收款选择器回调调用:追加一行已映射好的应收明细(本地维护)
+      addReceivableRow(row) {
+        this.localList.push({ ...row });
+      },
+      removeRow(index) {
+        if (index < 0 || index >= this.localList.length) return;
+        this.localList.splice(index, 1);
+      },
+      // 本次对账预收款确收金额上限 = min(未对账预收款确收金额, 本次对账金额)
+      getMaxConfirmAmount(row) {
+        return Math.min(
+          +row.unreconciledAdvanceConfirmAmount || 0,
+          +row.statementAmount || 0
+        );
+      },
+      handleConfirmAmountChange(row, index) {
+        // 限制本次确收金额不能超过未对账金额和本次对账金额
+        let val = +row.statementAdvanceConfirmAmount || 0;
+        const max = this.getMaxConfirmAmount(row);
+        if (val > max) {
+          val = max;
+          this.$set(row, 'statementAdvanceConfirmAmount', val);
+          this.$message.warning(
+            '本次对账预收款确收金额不能超过未对账金额和本次对账金额'
+          );
+        }
+        this.recalcRow(row);
+      },
+      // 根据本次对账金额重算单行:确收金额 = 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
+        );
+      },
+      // 明细表按销售单号汇总本次对账金额后,同步到对应销售单号的应收行
+      syncStatementAmount(summaryMap) {
+        if (!summaryMap || !Object.keys(summaryMap).length) return;
+        this.localList.forEach((row) => {
+          const amount = summaryMap[row.salesCode];
+          if (amount !== undefined) {
+            this.$set(row, 'statementAmount', amount);
+            this.recalcRow(row);
+          }
+        });
+      },
+      recalcAllRows() {
+        this.localList.forEach((row) => {
+          this.recalcRow(row);
+        });
+      },
+      validateConfirmAmount(rule, value, callback, row) {
+        const val = +value || 0;
+        const max = Math.min(
+          +row.unreconciledAdvanceConfirmAmount || 0,
+          +row.statementAmount || 0
+        );
+        if (val < 0) {
+          callback(new Error('不能小于0'));
+        } else if (val > max) {
+          callback(new Error(`不能超过${max}`));
+        } else {
+          callback();
+        }
+      },
+      // 提交时由父组件调用,一次性取回整个应收表单(本地明细 + 汇总)
+      getSubmitData() {
+        return {
+          receivableList: this.localList.map((item) => ({ ...item })),
+          statementAdvanceTotalPrice: this.statementAdvanceTotalPrice,
+          receivedTotalPrice: this.receivedTotalPrice
+        };
+      },
+      validate() {
+        return new Promise((resolve, reject) => {
+          const promises = [this.$refs.form.validate()];
+          if (this.$refs.tableForm) {
+            promises.push(this.$refs.tableForm.validate());
+          }
+          Promise.all(promises)
+            .then(() => resolve(true))
+            .catch(() => reject(false));
+        });
+      }
+    }
+  };
+</script>
+
+<style scoped lang="scss">
+  .receivable-info {
+    .receivable-table-header {
+      display: flex;
+      justify-content: flex-end;
+      margin-bottom: 10px;
+    }
+  }
+</style>

+ 5 - 2
src/views/bpm/handleTask/components/saleOrder/accountstatement/saleForm.vue

@@ -330,11 +330,14 @@
       },
       //计算未对账金额
       accountStatementPrice() {
-        return this.datasource.reduce((pre, cur) => pre + +cur.amountTotalPrice, 0);
+        return this.datasource.reduce(
+          (pre, cur) => pre + +cur.statementAmount,
+          0
+        );
       },
       //计算相关订单号
       relatedOrderNumber() {
-        return this.datasource.map((item) => item.orderNo).join(',');
+        return [...new Set(this.datasource.map((item) => item.orderNo))].join(',');
       }
     },
     methods: {

+ 10 - 2
src/views/bpm/handleTask/components/saleOrder/detailDialog.vue

@@ -15,7 +15,7 @@
       </div>
     </div>
     <div v-show="activeComp == 'main'">
-      <el-form ref="form" :model="form" :rules="rules" label-width="120px">
+      <el-form ref="form" :model="form" :rules="rules" label-width="140px">
         <headerTitle title="订单信息"> </headerTitle>
         <el-row>
           <el-col :span="8">
@@ -87,6 +87,9 @@
             <el-form-item label="业务员:" prop="salesmanName">
               {{ form.salesmanName }}
             </el-form-item>
+            <el-form-item prop="collectionConditionName" label="收款条件:">
+              {{ form.collectionConditionName }}
+            </el-form-item>
             <el-form-item label="订单总金额:" prop="totalAmount">
               {{ form.totalAmount }}元
             </el-form-item>
@@ -114,6 +117,9 @@
               {{ form.totalPlanNum }}
               {{ form.planNumUnit }}
             </el-form-item>
+            <el-form-item prop="isGenerateReceiptPlan" label="生成收款计划表:">
+              {{ form.isGenerateReceiptPlan == 1 ? '是' : '否' }}
+            </el-form-item>
             <el-form-item label="交易方式:" prop="transactionMode">
               {{ transactionMethodsOp.find(item => item.value == form.transactionMode)?.label }}
             </el-form-item>
@@ -134,11 +140,13 @@
               每月{{ form.receiptDate }}日
             </el-form-item>
             <el-form-item
+              v-if="form.isGenerateReceiptPlan != 0"
               label="期数:"
               prop="issueNumber"
             >
               {{ form.issueNumber }}期
             </el-form-item>
+
             <el-form-item label="订单备注:" prop="remark">
               {{ form.remark }}
             </el-form-item>
@@ -249,7 +257,7 @@
         type="view"
       ></typeList>
 
-      <div v-if="form.needProduce != 2 && form.needProduce != 4" style="margin-top: 30px">
+      <div v-if="form.needProduce != 2 && form.needProduce != 4 && form.isGenerateReceiptPlan != 0" style="margin-top: 30px">
         <headerTitle
           title="收款计划"
         ></headerTitle>

+ 2 - 2
vue.config.js

@@ -38,11 +38,11 @@ module.exports = {
         // target: 'http://124.71.68.31:50001', // 测试环境
         // target: 'http://124.71.68.31:50001',
         // target: 'http://192.168.1.105:18086',
-        target: 'http://localhost:18086',
+        // target: 'http://localhost:18086',
         // target: 'http://192.168.1.116:18086',
         // target: 'http://192.168.1.3:18086',
         // target: 'http://192.168.1.251:18186',
-        // target: 'http://192.168.1.251:18086',
+        target: 'http://192.168.1.113:18086',
         // target: 'http://192.168.1.3:18086',
         // target: 'http://aiot.zoomwin.com.cn:51001/api',
         // target: 'http://192.168.1.3:18086',