Parcourir la source

feat(发票管理): 重构发票金额分配逻辑并优化表格展示

liujt il y a 4 mois
Parent
commit
e28794bf20

+ 34 - 5
src/views/financialManage/invoiceManage/components/addOrEditDialogNew.vue

@@ -14,7 +14,7 @@
     <el-form
       ref="form"
       :rules="rules"
-      class="el-form-box"
+      class="el-form-box-add"
       :model="form"
       label-width="160px"
     >
@@ -330,6 +330,7 @@
         :contactData="contactData"
         ref="tableInfoRef"
         :invoiceAmount="form.invoiceAmount"
+        @invoiceAmountChange="invoiceAmountChange"
       ></table-info-new>
     </div>
     <div slot="footer">
@@ -624,9 +625,9 @@
           {
             width: 160,
             prop: 'invoiceAmount',
-            label: '开票金额',
+            label: '本次开票金额',
             align: 'center',
-            slot: 'invoiceAmount',
+            // slot: 'invoiceAmount',
             fixed: 'right'
           }
         ];
@@ -1066,6 +1067,30 @@
         this.$forceUpdate();
       },
 
+      invoiceAmountChange(item, sum) {
+        console.log('item~~~', item, sum);
+        const sameKeyItems = this.form.receiptPayments.filter((d) => d.relatedOrderNo === item.key)
+        console.log('sameKeyItems~~~', sameKeyItems);
+        
+        // 计算 sum 值(使用 item 的 defaultAmountTotalPrice 或其他适当的字段)
+        // const sum = parseFloat(item.defaultAmountTotalPrice) || 0;
+        let remainingTotal = sum;
+        
+        // 从上到下分配 sum 给相同订单号的数据
+        sameKeyItems.forEach(paymentItem => {
+          if (remainingTotal <= 0) {
+            paymentItem.invoiceAmount = 0;
+          } else {
+            const unInvoiceAmount = parseFloat(paymentItem.unInvoiceAmount) || 0;
+            const allocateAmount = Math.min(remainingTotal, unInvoiceAmount);
+            paymentItem.invoiceAmount = allocateAmount.toFixed(2);
+            remainingTotal -= allocateAmount;
+          }
+        });
+        
+        console.log('分配完成后的 sameKeyItems:', sameKeyItems);
+      },
+
       getValidate() {
         console.log(this.form);
         let arr = [
@@ -1191,7 +1216,7 @@
             formCreateUserId: data.createUserId,
             variables: {
               type: data.type,
-              businessCode: data.code,
+              businessCode: data.orderNo,
 
               businessName: data.companyName,
               businessType: data.type == 1 ? '开票' : '收票'
@@ -1229,4 +1254,8 @@
     }
   };
 </script>
-<style scoped lang="scss"></style>
+<style scoped lang="scss">
+.el-form-box .el-table .el-table__row .el-table__cell {
+  padding: 10px 0;
+}
+</style>

+ 207 - 274
src/views/financialManage/invoiceManage/components/tableInfoNew.vue

@@ -1,82 +1,78 @@
 <template>
   <div>
-    <el-tabs type="border-card">
-      <el-tab-pane :label="item.key" v-for="(item, idx) in tableForm.detailList" :key="idx">
-        <el-form :ref="'form' + idx" :model="tableForm">
-          <ele-pro-table
-            ref="table"
-            :needPage="false"
-            :columns="columns"
-            :datasource="item.details"
-            row-key="id"
-            class="time-form"
-          >
-            <template v-slot:toolbar>
-              <span>本次开票合计:{{ item.amountTotalPrice }}</span>
-            </template>
-            
-            <template v-slot:invoiceAmount="scope">
-            <el-form-item
-              style="width: 100%;"
-              :prop="'detailList.' + idx + '.details.' + scope.$index + '.invoiceAmount'"
-              :rules="[
-                {
-                  required: true,
-                  message: '请输入开票金额',
-                  trigger: 'blur'
-                },
-                {
-                  validator: (rule, value, callback) => {
-                    if (value === undefined || value === null || value === '') {
-                      callback('请输入开票金额');
-                    } else if (parseFloat(value) < 0 || parseFloat(value) > item.amountTotalPrice) {
-                      callback('开票金额不能小于0且小于等于本次开票合计');  
-                    } else {
-                      callback();
-                    }
-                  },
-                  trigger: 'blur'
+    <el-form :ref="'form'" :model="tableForm">
+      <ele-pro-table
+        ref="table"
+        :needPage="false"
+        :columns="columns"
+        :datasource="tableForm.detailList"
+        row-key="id"
+        class="time-form"
+      >
+        <template v-slot:toolbar>
+          <span>本次开票合计:{{ totalAmountTotalPrice }}</span>
+        </template>
+        
+        <template v-slot:invoiceAmount="scope">
+        <el-form-item
+          style="width: 100%;"
+          :prop="'detailList.' + scope.$index + '.invoiceAmount'"
+          :rules="[
+            {
+              required: true,
+              message: '请输入开票金额',
+              trigger: 'blur'
+            },
+            {
+              validator: (rule, value, callback) => {
+                if (value === undefined || value === null || value === '') {
+                  callback('请输入开票金额');
+                } else if (parseFloat(value) < 0 || parseFloat(value) > scope.row.defaultAmountTotalPrice) {
+                  callback('开票金额不能小于0且小于等于本次开票合计');  
+                } else {
+                  callback();
                 }
-              ]"
-            > 
-              <span v-if="dialogType === 'view'">{{ scope.row.invoiceAmount }}</span>
-              <el-input v-else v-model="scope.row.invoiceAmount" type="number" @input="(val) => invoiceAmountChange(val, scope.row, idx, scope.$index)"></el-input>
-            </el-form-item>
-          </template>
+              },
+              trigger: 'blur'
+            }
+          ]"
+        > 
+          <span v-if="dialogType === 'view'"> {{ scope.row.invoiceAmount }}</span>
+          <el-input v-else v-model="scope.row.invoiceAmount" type="number" @input="(val) => invoiceAmountChange(val, scope.row, scope.$index)"></el-input>
+        </el-form-item>
+      </template>
 
-          <template v-slot:taxRate="scope">
-            <el-form-item
-              style="width: 100%;"
-              :prop="'detailList.' + idx + '.details.' + scope.$index + '.taxRate'"
-              :rules="[
-                {
-                  required: true,
-                  message: '请输入税率',
-                  trigger: 'blur'
-                },
-                {
-                  validator: (rule, value, callback) => {
-                    if (value === undefined || value === null || value === '') {
-                      callback('请输入税率');
-                    } else if (parseFloat(value) < 0) {
-                      callback('税率不能小于0');
-                    } else {
-                      callback();
-                    }
-                  },
-                  trigger: 'blur'
+      <template v-slot:taxRate="scope">
+        <el-form-item
+          style="width: 100%;"
+          :prop="'detailList.' + scope.$index + '.taxRate'"
+          :rules="[
+            {
+              required: true,
+              message: '请输入税率',
+              trigger: 'blur'
+            },
+            {
+              validator: (rule, value, callback) => {
+                if (value === undefined || value === null || value === '') {
+                  callback('请输入税率');
+                } else if (parseFloat(value) < 0) {
+                  callback('税率不能小于0');
+                } else {
+                  callback();
                 }
-              ]"
-            > 
-              <span v-if="dialogType === 'view'">{{ scope.row.taxRate }}</span>
-              <el-input v-else v-model="scope.row.taxRate" type="number" @input="getNotaxSinglePrice(scope.row, idx, scope.$index)" style="width: 80%;"></el-input>%
-            </el-form-item>
-          </template>
+              },
+              trigger: 'blur'
+            }
+          ]"
+        > 
+          <span v-if="dialogType === 'view'"> {{ scope.row.taxRate }}</span>
+          <el-input v-else v-model="scope.row.taxRate" type="number" @input="getNotaxSinglePrice(scope.row, 0, scope.$index)" style="width: 80%;"></el-input>%
+        </el-form-item>
+      </template>
 
-          </ele-pro-table>
-        </el-form>
-      </el-tab-pane>
-    </el-tabs>
+      </ele-pro-table>
+    </el-form>
     
   </div>
 </template>
@@ -85,15 +81,6 @@
     name: 'tableInfo',
     components: {},
     props: {
-      form: {
-        type: Object,
-        default: () => {
-          return {
-            productMap: [],
-            receiptPayments: []
-          };
-        }
-      },
       dialogType: {
         type: String,
         default: ''
@@ -123,7 +110,12 @@
             align: 'center',
             fixed: 'left'
           },
-
+          {
+            width: 100,
+            prop: 'key',
+            label: '编码',
+            align: 'center'
+          },
           {
             width: 100,
             prop: 'typeName',
@@ -192,7 +184,7 @@
           {
             minWidth: 150,
             prop: 'invoiceAmount',
-            label: '开票金额',
+            label: '本次开票金额',
             slot: 'invoiceAmount',
             align: 'center'
           },
@@ -202,6 +194,12 @@
             label: '已开票金额',
             align: 'center'
           },
+          {
+            minWidth: 150,
+            prop: 'unInvoiceAmount',
+            label: '未开票金额',
+            align: 'center'
+          },
           {
             minWidth: 150,
             prop: 'taxRate',
@@ -327,127 +325,109 @@
         ],
       };
     },
+    computed: {
+      totalAmountTotalPrice() {
+        return this.tableForm.detailList.reduce((total, item) => {
+          return total + (parseFloat(item.amountTotalPrice) || 0);
+        }, 0);
+      }
+    },
     mounted() {
       // this.tableForm = this.form;
     },
-    watch: {
-    form: {
-      handler(val) {
-        console.log('tableInfoNew form', val);
-        const data = JSON.parse(JSON.stringify(val));
-        data.detailList = data.productMap ? this.convertToArrayFormat(data) : [
-          {
-            key: '',
-            amountTotalPrice: 0,
-            details: []
-          }
-        ];
-        console.log('tableInfoNew detailList', data.detailList);
-        this.tableForm = data;
-      },
-      deep: true
-    }
-  },
     methods: {
       convertToArrayFormat(data) {
-          return Object.keys(data.productMap).map(key => {
+          const result = [];
+          Object.keys(data.productMap).forEach(key => {
             console.log('key!!!', key);
-              const paymentItem = data.receiptPayments?.filter(item => item?.relatedOrderNo == key)|| [];
-              console.log('paymentItem', paymentItem);
-              return {
-                  key: key,
-                  amountTotalPrice: paymentItem.length ? paymentItem.reduce((acc, cur) => acc + cur.invoiceAmount, 0) : 0,
-                  defaultAmountTotalPrice: paymentItem.length ? paymentItem.reduce((acc, cur) => acc + cur.invoiceAmount, 0) : 0,
-                  details: data.productMap[key] || []
-              };
+            const paymentItem = data.receiptPayments?.filter(item => item?.relatedOrderNo == key) || [];
+            console.log('paymentItem', paymentItem);
+            const amountTotalPrice = paymentItem.length ? paymentItem.reduce((acc, cur) => acc + cur.invoiceAmount, 0) : 0;
+            const defaultAmountTotalPrice = paymentItem.length ? paymentItem.reduce((acc, cur) => acc + cur.unInvoiceAmount, 0) : 0;
+            const details = data.productMap[key] || [];
+            
+            details.forEach(detail => {
+              result.push({
+                key: key,
+                amountTotalPrice: amountTotalPrice,
+                defaultAmountTotalPrice: defaultAmountTotalPrice,
+                ...detail,
+                invoiceAmount: '',
+              });
+            });
+          });
+          
+          // Allocate defaultAmountTotalPrice to invoiceAmount for each key group
+          const uniqueKeys = [...new Set(result.map(item => item.key))];
+          uniqueKeys.forEach(key => {
+            const sameKeyItems = result.filter(item => item.key === key);
+            if (sameKeyItems.length > 0) {
+              const defaultAmountTotalPrice = parseFloat(sameKeyItems[0].defaultAmountTotalPrice) || 0;
+              let remainingTotal = defaultAmountTotalPrice;
+              
+              sameKeyItems.forEach(item => {
+                if (remainingTotal <= 0) {
+                  item.invoiceAmount = 0;
+                } else {
+                  const unInvoiceAmount = parseFloat(item.unInvoiceAmount) || 0;
+                  const allocateAmount = Math.min(remainingTotal, unInvoiceAmount);
+                  item.invoiceAmount = allocateAmount.toFixed(2);
+                  remainingTotal -= allocateAmount;
+                }
+              });
+            }
           });
+          
+          return result;
       },
       // 校验发票金额
-      invoiceAmountChange(val, item, idx, index) {
-        console.log('invoiceAmountChange', val, item, idx, index);
-        console.log('this.tableForm.detailList', this.tableForm.detailList)
-        if(index !=null) {
-          let newData = this.tableForm.detailList[idx].details;
-          console.log('newData', newData);
-          let sum =0;
-          newData.forEach((r) => {
+      invoiceAmountChange(val, item, index) {
+        console.log('invoiceAmountChange', val, item, index);
+        console.log('this.tableForm.detailList', this.tableForm.detailList);
+        if (index != null) {
+          const key = item.key;
+          const sameKeyItems = this.tableForm.detailList.filter(d => d.key === key);
+          console.log('sameKeyItems', sameKeyItems);
+          let sum = 0;
+          sameKeyItems.forEach((r) => {
             if (r.invoiceAmount) {
               sum += Number(r.invoiceAmount);
             }
           });
-          if(val && +val > +this.tableForm.detailList[idx].details[index].unInvoiceAmount) {
+          if (val && +val > +item.unInvoiceAmount) {
             this.$message.error('物品开票金额不能大于未开票金额');
-            this.$set(this.tableForm.detailList[idx].details[index], 'invoiceAmount', '');
+            this.$set(item, 'invoiceAmount', '');
             return;
           }
-          if(sum > this.tableForm.detailList[idx].defaultAmountTotalPrice) {
+          if (sum > item.defaultAmountTotalPrice) {
             this.$message.error('物品开票金额不能大于本次开票合计');
-            this.$set(this.tableForm.detailList[idx].details[index], 'invoiceAmount', '');
+            this.$set(item, 'invoiceAmount', '');
             return;
           }
-          this.$set(this.tableForm.detailList[idx].details[index], 'invoiceAmount', val);
-          this.$set(this.tableForm.detailList[idx], 'amountTotalPrice', sum);
-          console.log('sum', sum, this.tableForm.detailList[idx].details[index].invoiceAmount);
+          this.$set(item, 'invoiceAmount', val);
+          const amountTotalPrice = this.fromPrecision(sum);
+          const targetItems = this.tableForm.detailList.filter(d => d.key === key);
+          targetItems.forEach(targetItem => {
+            this.$set(targetItem, 'amountTotalPrice', amountTotalPrice);
+          });
+          console.log('sum', sum, item.invoiceAmount);
+          this.$emit('invoiceAmountChange', item, sum);
         }
+        
       },
       //计算不含税单价
       getNotaxSinglePrice(item, idx, index) {
         if (item.singlePrice && item.taxRate) {
           this.$set(
-            this.tableForm.detailList[idx].details[index],
+            item,
             'noTaxSinglePrice',
             parseFloat(
               (item.singlePrice / (1 + item.taxRate / 100)).toFixed(2)
             )
           );
         } else {
-          this.$set(this.tableForm.detailList[idx].details[index], 'noTaxSinglePrice', '');
-        }
-      },
-      //获取选择的对账单数据
-      async getAccountData(params) {
-        if (params.children.orderType == 6) {
-          this.tableForm.productMap = params.children.productMap;
-          this.tableForm.productMap.forEach((item, index) => {
-            item.sourceCode = params.children.orderNo;
-            item.sourceId = params.children.id;
-            item.sourceType = params.type == 1 ? 2 : 3;
-            item.type = 12;
-            item.singlePrice = item.discountSinglePrice;
-            item.totalPrice = item.discountTotalPrice;
-            item.typeName = '销售赔付';
-          });
-        } else {
-          this.tableForm.productMap = [];
-          params.children.subList.forEach((item, index) => {
-            item.productMap.forEach((i, n) => {
-              i.sourceCode = item.statementSubOrderCode;
-              i.sourceId = params.children.id;
-              i.sourceType = params.type == 1 ? 2 : 3;
-              i.type = item.subType;
-              console.log(item.subType);
-              i.typeName = this.typeList.find(
-                (i) => i.value == item.subType
-              ).label;
-              // i.singlePrice = item.discountSinglePrice
-              i.totalPrice = i.discountTotalPrice;
-            });
-            this.tableForm.productMap.push(...item.productMap);
-          });
-          this.$refs.table.reload();
+          this.$set(item, 'noTaxSinglePrice', '');
         }
-        this.$emit(
-          'setPrice',
-          params.children.amountTotalPrice - this.invoiceAmount
-        );
-        let row = {
-          id: params.id,
-          name: params.statementNo,
-          code: params.statementNo,
-          linkType: params.type == 1 ? 190 : 290,
-          linkTypeName: params.type == 1 ? '销售对账单' : '采购对账单'
-        };
-        this.setSelectData(row);
       },
       putValue(data) {
         let tempData = JSON.parse(JSON.stringify(data));
@@ -460,33 +440,8 @@
       setValue(data) {
         let tempData = JSON.parse(JSON.stringify(data));
         tempData.detailList = this.convertToArrayFormat(tempData);
-        this.allocateInvoiceAmount(tempData.detailList);
-        // for(let item of tempData.detailList) {
-        //   const total = item.amountTotalPrice;
-        //   const details = item.details;
-        //   const count = details.length;
-          
-        //   if (count === 0) continue;
-          
-        //   // 计算基础金额(向下取整)
-        //   const baseAmount = Math.floor(total / count);
-        //   // 计算余数
-        //   const remainder = total - (baseAmount * count);
-          
-        //   // 为所有项设置基础金额
-        //   details.forEach((detail) => {
-        //     detail.invoiceAmount = baseAmount;
-        //   });
-          
-        //   // 将余数加到最后一项
-        //   if (remainder > 0 && details.length > 0) {
-        //     details[details.length - 1].invoiceAmount += remainder;
-        //   }
-        // }
-        // this.$set(this, 'tableForm', data);
+        console.log('tempData.detailList', tempData.detailList);
         this.tableForm = tempData;
-        // console.log('tableInfoNew detailList~~~', tempData.detailList);
-        // this.$refs.table.reload();
       },
       /**
        * 分配开票金额到明细项(根据未开票金额unInvoiceAmount限制)
@@ -508,10 +463,14 @@
         return data;
       },
       allocateSingleInvoiceAmount(item) {
-        let remainingTotal = this.toPrecision(item.amountTotalPrice);
-        const details = item.details || [];
+        console.log('allocateSingleInvoiceAmount~~~', item);
+        const key = item.key;
+        const defaultAmountTotalPrice = this.toPrecision(item.defaultAmountTotalPrice);
+        const sameKeyItems = this.tableForm.detailList.filter(d => d.key === key);
+        
+        let remainingTotal = defaultAmountTotalPrice;
         
-        details.forEach((detail) => {
+        sameKeyItems.forEach((detail) => {
           if (remainingTotal <= 0) {
             detail.invoiceAmount = 0;
           } else {
@@ -570,82 +529,52 @@
       },
       getTableValidate() {
         return new Promise(async (resolve, reject) => {
-          // 2. 验证每个表格表单
-          const validationPromises = [];
-          this.tableForm.detailList.forEach((item, index) => {
-            validationPromises.push(
-              new Promise((formResolve, formReject) => {
-                // 正确获取表单实例
-                const formInstance = this.$refs['form' + index];
-                
-                if (formInstance) {
-                  // 确保formInstance有validate方法
-                  if (typeof formInstance.validate === 'function') {
-                    formInstance.validate((valid, errors) => {
-                      if (!valid && errors) {
-                        // 显示第一个错误信息
-                        const firstError = Object.values(errors)[0][0];
-                        if (firstError) {
-                          this.$message.warning(firstError.message);
-                        }
-                        formReject(false);
-                      } else {
-                        formResolve(true);
-                      }
-                    });
-                  } else {
-                    // 如果没有validate方法,检查是否是数组(多个表单实例)
-                    if (Array.isArray(formInstance) && formInstance.length > 0) {
-                      const firstForm = formInstance[0];
-                      if (typeof firstForm.validate === 'function') {
-                        firstForm.validate((valid, errors) => {
-                          if (!valid && errors) {
-                            const firstError = Object.values(errors)[0][0];
-                            if (firstError) {
-                              this.$message.warning(firstError.message);
-                            }
-                            formReject(false);
-                          } else {
-                            formResolve(true);
-                          }
-                        });
-                      } else {
-                        formResolve(true); // 没有validate方法时默认通过
-                      }
-                    } else {
-                      formResolve(true); // 不是预期的表单实例时默认通过
-                    }
-                  }
-                } else {
-                  formResolve(true); // 表单不存在时默认通过
-                }
-              })
-            );
-          });
-
-          console.log('validationPromises~~~',validationPromises);
+          // 2. 验证单个表格表单
+          const formInstance = this.$refs['form'];
           
-          // 3. 处理验证结果
-          try {
-            await Promise.all(validationPromises);
-            
-            // 4. 校验 amountTotalPrice 和 defaultAmountTotalPrice 是否相等
-            for (let i = 0; i < this.tableForm.detailList.length; i++) {
-              const item = this.tableForm.detailList[i];
-              const amountTotalPrice = parseFloat(item.amountTotalPrice) || 0;
-              const defaultAmountTotalPrice = parseFloat(item.defaultAmountTotalPrice) || 0;
-              
-              if (amountTotalPrice !== defaultAmountTotalPrice) {
-                this.$message.warning(`第${i + 1}条本次开票合计(${amountTotalPrice})必须等于本次开票金额合计(${defaultAmountTotalPrice})`);
+          if (formInstance && typeof formInstance.validate === 'function') {
+            formInstance.validate(async (valid, errors) => {
+              if (!valid && errors) {
+                // 显示第一个错误信息
+                const firstError = Object.values(errors)[0][0];
+                if (firstError) {
+                  this.$message.warning(firstError.message);
+                }
                 reject(false);
-                return;
+              } else {
+                // 3. 校验 amountTotalPrice 和 defaultAmountTotalPrice 是否相等
+                // for (let i = 0; i < this.tableForm.detailList.length; i++) {
+                //   const item = this.tableForm.detailList[i];
+                //   const amountTotalPrice = parseFloat(item.amountTotalPrice) || 0;
+                //   const defaultAmountTotalPrice = parseFloat(item.defaultAmountTotalPrice) || 0;
+                  
+                //   if (amountTotalPrice !== defaultAmountTotalPrice) {
+                //     this.$message.warning(`第${i + 1}条本次开票合计(${amountTotalPrice})必须等于本次开票金额合计(${defaultAmountTotalPrice})`);
+                //     reject(false);
+                //     return;
+                //   }
+                // }
+                
+                resolve(this.tableForm);
+                console.log('this.tableForm~~~',this.tableForm);
               }
-            }
+            });
+          } else {
+            // 4. 校验 amountTotalPrice 和 defaultAmountTotalPrice 是否相等
+            // for (let i = 0; i < this.tableForm.detailList.length; i++) {
+            //   const item = this.tableForm.detailList[i];
+            //   const amountTotalPrice = parseFloat(item.amountTotalPrice) || 0;
+            //   const defaultAmountTotalPrice = parseFloat(item.defaultAmountTotalPrice) || 0;
+              
+            //   // if (amountTotalPrice !== defaultAmountTotalPrice) {
+            //   //   this.$message.warning(`第${i + 1}条本次开票合计(${amountTotalPrice})必须等于本次开票金额合计(${defaultAmountTotalPrice})`);
+            //   //   reject(false);
+            //   //   return;
+            //   // }
+            // }
             
             resolve(this.tableForm);
             console.log('this.tableForm~~~',this.tableForm);
-          } catch (error) {
-            reject(false);
           }
         });
       },
@@ -670,9 +599,13 @@
         this.$set(this.tableForm.link[0], 'linkTypeName', val.linkTypeName);
       },
       getTableData() {
-        const  data = JSON.parse(JSON.stringify(this.tableForm.detailList));
+        const data = JSON.parse(JSON.stringify(this.tableForm.detailList));
         const transformedData = data.reduce((result, item) => {
-          result[item.key] = item.details;
+          if (!result[item.key]) {
+            result[item.key] = [];
+          }
+          const { key, amountTotalPrice, defaultAmountTotalPrice, ...detail } = item;
+          result[item.key].push(detail);
           return result;
         }, {});
         return transformedData;