You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将Odoo会计模块中税费计算方式从行级改为单据级?

解决方案:Odoo Account Move 统一汇总计算税费

首先得说,Sale模块搞定后转Account Move确实容易卡壳——毕竟后者牵扯到凭证分录的双向联动,逻辑绕得多。结合你已经强制所有行使用相同税费配置的前提,下面是你需要修改的核心方法和实现思路:

1. 重写核心税务计算方法 _recompute_tax_lines

这是你已经入手的核心点,但需要完全替换原有逐行累加的逻辑,改成基于所有应税行总金额统一计算税费:

from odoo import models, fields, api
import json

class AccountMove(models.Model):
    _inherit = 'account.move'

    @api.depends('line_ids.price_subtotal', 'line_ids.tax_ids', 'line_ids.tax_tag_ids')
    def _recompute_tax_lines(self):
        # 跳过官方逐行计算逻辑,自定义汇总计算
        for move in self:
            # 清空原有税务分录
            move.tax_line_ids.unlink()
            if not move.line_ids or move.state != 'draft':
                continue
            
            # 筛选出所有应税行(排除税务分录、标题、备注行)
            taxable_lines = move.line_ids.filtered(
                lambda l: l.display_type not in ('line_section', 'line_note', 'tax')
            )
            if not taxable_lines:
                continue
            
            # 计算应税总金额
            total_taxable_amount = sum(taxable_lines.mapped('price_subtotal'))
            # 取统一的税费配置(你已强制所有行相同,直接取第一行的税即可)
            tax_ids = taxable_lines[0].tax_ids
            
            # 调用税的统一计算方法
            tax_compute_result = tax_ids.compute_all(
                total_taxable_amount,
                currency=move.currency_id,
                quantity=1,
                product=taxable_lines[0].product_id,
                partner=move.partner_id,
            )
            
            # 生成新的税务分录
            for tax_vals in tax_compute_result['taxes']:
                tax = self.env['account.tax'].browse(tax_vals['id'])
                self.env['account.move.line'].create({
                    'name': tax.name,
                    'move_id': move.id,
                    'account_id': tax_vals['account_id'] or taxable_lines[0].account_id.id,
                    'tax_base_amount': total_taxable_amount,
                    'tax_line_id': tax.id,
                    'amount_currency': tax_vals['amount'],
                    'debit': tax_vals['amount'] > 0 and tax_vals['amount'] or 0,
                    'credit': tax_vals['amount'] < 0 and -tax_vals['amount'] or 0,
                    'currency_id': move.currency_id.id,
                    'partner_id': move.partner_id.id,
                })

2. 禁用行级税费计算,避免前端混乱

默认情况下,Odoo会在每个行项目上计算tax_amount,这会导致前端显示重复税费,所以需要重写行的税费计算方法,强制行级税费为0:

class AccountMoveLine(models.Model):
    _inherit = 'account.move.line'

    @api.depends('price_subtotal', 'tax_ids', 'tax_tag_ids')
    def _compute_tax_amount(self):
        # 强制行级税费为0,所有税费统一在末尾汇总展示
        for line in self:
            line.tax_amount = 0.0

3. 调整前端税务汇总展示方法 _compute_tax_totals_json

这个方法负责生成前端发票/账单顶部的税务汇总数据,需要确保它读取我们统一计算的结果,而非逐行累加:

class AccountMove(models.Model):
    _inherit = 'account.move'

    @api.depends('line_ids.price_subtotal', 'tax_line_ids.amount_currency')
    def _compute_tax_totals_json(self):
        for move in self:
            # 计算不含税总额和含税总额
            untaxed_total = sum(move.line_ids.filtered(
                lambda l: l.display_type not in ('tax', 'line_section', 'line_note')
            ).mapped('price_subtotal'))
            tax_total = sum(move.tax_line_ids.mapped('amount_currency'))
            total_amount = untaxed_total + tax_total

            # 按税组整理汇总数据
            tax_groups = []
            grouped_taxes = move.tax_line_ids.grouped('tax_id.tax_group_id')
            for tax_group, tax_lines in grouped_taxes.items():
                tax_groups.append({
                    'name': tax_group.name,
                    'amount': sum(tax_lines.mapped('amount_currency')),
                    'base': sum(tax_lines.mapped('tax_base_amount')),
                })

            # 组装成前端需要的JSON格式
            tax_totals = {
                'amount_untaxed': untaxed_total,
                'amount_total': total_amount,
                'tax_groups': tax_groups,
            }
            move.tax_totals_json = json.dumps(tax_totals)

4. 可选:锁定行项目税费配置,防止意外修改

为了确保所有行始终使用统一税费,可以重写行的产品变更钩子,强制继承已有行的税费配置:

class AccountMoveLine(models.Model):
    _inherit = 'account.move.line'

    @api.onchange('product_id')
    def _onchange_product_id(self):
        # 先执行官方逻辑,再强制替换为统一税费
        res = super()._onchange_product_id()
        if self.move_id:
            existing_tax_lines = self.move_id.line_ids.filtered(
                lambda l: l.tax_ids and l.display_type not in ('tax', 'line_section', 'line_note')
            )
            if existing_tax_lines:
                self.tax_ids = existing_tax_lines[0].tax_ids
        return res

关键测试场景

修改完成后,一定要覆盖这些场景验证:

  • 新建发票/账单,添加多行项目,确认税费只在末尾汇总
  • 修改行项目金额,确认汇总税费实时更新
  • 切换合作伙伴(可能影响税的属地规则),确认税费计算正确
  • 草稿转已验证状态,确认凭证分录和税费金额一致

内容的提问来源于stack exchange,提问作者holydragon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 20:22:30