如何将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
相关产品推荐
相关产品推荐

