summaryrefslogtreecommitdiff
path: root/addons/account/models/chart_template.py
diff options
context:
space:
mode:
Diffstat (limited to 'addons/account/models/chart_template.py')
-rw-r--r--addons/account/models/chart_template.py143
1 files changed, 116 insertions, 27 deletions
diff --git a/addons/account/models/chart_template.py b/addons/account/models/chart_template.py
index 6b6841dc868..eff7872f377 100644
--- a/addons/account/models/chart_template.py
+++ b/addons/account/models/chart_template.py
@@ -3,7 +3,7 @@
from odoo.exceptions import AccessError
from odoo import api, fields, models, _
from odoo import SUPERUSER_ID
-from odoo.exceptions import UserError
+from odoo.exceptions import UserError, ValidationError
from odoo.http import request
import logging
@@ -559,15 +559,20 @@ class AccountChartTemplate(models.Model):
account_ref.update(account_template_ref)
# writing account values after creation of accounts
- for key, value in generated_tax_res['account_dict'].items():
- if value['refund_account_id'] or value['account_id'] or value['cash_basis_account_id'] or value['cash_basis_base_account_id']:
+ for key, value in generated_tax_res['account_dict']['account.tax'].items():
+ if value['cash_basis_transition_account_id'] or value['cash_basis_base_account_id']:
AccountTaxObj.browse(key).write({
- 'refund_account_id': account_ref.get(value['refund_account_id'], False),
- 'account_id': account_ref.get(value['account_id'], False),
- 'cash_basis_account_id': account_ref.get(value['cash_basis_account_id'], False),
+ 'cash_basis_transition_account_id': account_ref.get(value['cash_basis_transition_account_id'], False),
'cash_basis_base_account_id': account_ref.get(value['cash_basis_base_account_id'], False),
})
+ AccountTaxRepartitionLineObj = self.env['account.tax.repartition.line']
+ for key, value in generated_tax_res['account_dict']['account.tax.repartition.line'].items():
+ if value['account_id']:
+ AccountTaxRepartitionLineObj.browse(key).write({
+ 'account_id': account_ref.get(value['account_id']),
+ })
+
# Create Journals - Only done for root chart template
if not self.parent_id:
self.generate_journals(account_ref, company)
@@ -808,18 +813,15 @@ class AccountTaxTemplate(models.Model):
children_tax_ids = fields.Many2many('account.tax.template', 'account_tax_template_filiation_rel', 'parent_tax', 'child_tax', string='Children Taxes')
sequence = fields.Integer(required=True, default=1,
help="The sequence field is used to define order in which the tax lines are applied.")
- amount = fields.Float(required=True, digits=(16, 4))
- account_id = fields.Many2one('account.account.template', string='Tax Account', ondelete='restrict',
- help="Account that will be set on invoice tax lines for invoices. Leave empty to use the expense account.", oldname='account_collected_id')
- refund_account_id = fields.Many2one('account.account.template', string='Tax Account on Refunds', ondelete='restrict',
- help="Account that will be set on invoice tax lines for refunds. Leave empty to use the expense account.", oldname='account_paid_id')
+ amount = fields.Float(required=True, digits=(16, 4), default=0)
description = fields.Char(string='Display on Invoices')
price_include = fields.Boolean(string='Included in Price', default=False,
help="Check this if the price you use on the product and invoices includes this tax.")
include_base_amount = fields.Boolean(string='Affect Subsequent Taxes', default=False,
help="If set, taxes which are computed after this one will be computed based on the price tax included.")
analytic = fields.Boolean(string="Analytic Cost", help="If set, the amount computed by this tax will be assigned to the same analytic account as the invoice line (if any)")
- tag_ids = fields.Many2many('account.account.tag', string='Account tag', help="Optional tags you may want to assign for custom reporting")
+ invoice_repartition_line_ids = fields.One2many(string="Repartition for Invoices", comodel_name="account.tax.repartition.line.template", inverse_name="invoice_tax_id", copy=True, help="Repartition when the tax is used on an invoice")
+ refund_repartition_line_ids = fields.One2many(string="Repartition for Refund Invoices", comodel_name="account.tax.repartition.line.template", inverse_name="refund_tax_id", copy=True, help="Repartition when the tax is used on a refund")
tax_group_id = fields.Many2one('account.tax.group', string="Tax Group")
tax_exigibility = fields.Selection(
[('on_invoice', 'Based on Invoice'),
@@ -828,12 +830,11 @@ class AccountTaxTemplate(models.Model):
oldname='use_cash_basis',
help="Based on Invoice: the tax is due as soon as the invoice is validated.\n"
"Based on Payment: the tax is due as soon as the payment of the invoice is received.")
- cash_basis_account_id = fields.Many2one(
- 'account.account.template',
- string='Tax Received Account',
+ cash_basis_transition_account_id = fields.Many2one(
+ comodel_name='account.account.template',
+ string="Cash Basis Transition Account",
domain=[('deprecated', '=', False)],
- oldname='cash_basis_account',
- help='Account used as counterpart for the journal entry, for taxes eligible based on payments.')
+ help="Account used to transition the tax amount for cash basis taxes. It will contain the tax amount as long as the original invoice has not been reconciled ; at reconciliation, this amount cancelled on this account and put on the regular tax account.")
cash_basis_base_account_id = fields.Many2one(
'account.account.template',
domain=[('deprecated', '=', False)],
@@ -874,11 +875,17 @@ class AccountTaxTemplate(models.Model):
'price_include': self.price_include,
'include_base_amount': self.include_base_amount,
'analytic': self.analytic,
- 'tag_ids': [(6, 0, [t.id for t in self.tag_ids])],
'children_tax_ids': [(6, 0, children_ids)],
'tax_exigibility': self.tax_exigibility,
}
+ # We add repartition lines if there are some, so that if there are none,
+ # default_get is called and creates the default ones properly.
+ if self.invoice_repartition_line_ids:
+ val['invoice_repartition_line_ids'] = self.invoice_repartition_line_ids.get_repartition_line_create_vals(company)
+ if self.refund_repartition_line_ids:
+ val['refund_repartition_line_ids'] = self.refund_repartition_line_ids.get_repartition_line_create_vals(company)
+
if self.tax_group_id:
val['tax_group_id'] = self.tax_group_id.id
return val
@@ -894,7 +901,7 @@ class AccountTaxTemplate(models.Model):
}
"""
ChartTemplate = self.env['account.chart.template']
- todo_dict = {}
+ todo_dict = {'account.tax': {}, 'account.tax.repartition.line': {}}
tax_template_to_tax = {}
templates_todo = list(self)
@@ -903,27 +910,36 @@ class AccountTaxTemplate(models.Model):
templates_todo = []
# create taxes in batch
- template_vals = []
+ tax_template_vals = []
for template in templates:
if all(child.id in tax_template_to_tax for child in template.children_tax_ids):
vals = template._get_tax_vals(company, tax_template_to_tax)
- template_vals.append((template, vals))
+ tax_template_vals.append((template, vals))
else:
# defer the creation of this tax to the next batch
templates_todo.append(template)
- taxes = ChartTemplate._create_records_with_xmlid('account.tax', template_vals, company)
+ taxes = ChartTemplate._create_records_with_xmlid('account.tax', tax_template_vals, company)
# fill in tax_template_to_tax and todo_dict
- for tax, (template, vals) in zip(taxes, template_vals):
+ for tax, (template, vals) in zip(taxes, tax_template_vals):
tax_template_to_tax[template.id] = tax.id
# Since the accounts have not been created yet, we have to wait before filling these fields
- todo_dict[tax.id] = {
- 'account_id': template.account_id.id,
- 'refund_account_id': template.refund_account_id.id,
- 'cash_basis_account_id': template.cash_basis_account_id.id,
+ todo_dict['account.tax'][tax.id] = {
+ 'cash_basis_transition_account_id': template.cash_basis_transition_account_id.id,
'cash_basis_base_account_id': template.cash_basis_base_account_id.id,
}
+ # We also have to delay the assignation of accounts to repartition lines
+ all_tax_rep_lines = tax.invoice_repartition_line_ids + tax.refund_repartition_line_ids
+ all_template_rep_lines = template.invoice_repartition_line_ids + template.refund_repartition_line_ids
+ for i in range(0, len(all_tax_rep_lines)):
+ # We assume template and tax repartition lines are in the same order
+ template_account = all_template_rep_lines[i].account_id
+ if template_account:
+ todo_dict['account.tax.repartition.line'][all_tax_rep_lines[i].id] = {
+ 'account_id': template_account.id,
+ }
+
if any(template.tax_exigibility == 'on_payment' for template in self):
# When a CoA is being installed automatically and if it is creating account tax(es) whose field `Use Cash Basis`(tax_exigibility) is set to True by default
# (example of such CoA's are l10n_fr and l10n_mx) then in the `Accounting Settings` the option `Cash Basis` should be checked by default.
@@ -934,6 +950,79 @@ class AccountTaxTemplate(models.Model):
'account_dict': todo_dict
}
+# Tax Repartition Line Template
+
+class AccountTaxRepartitionLineTemplate(models.Model):
+ _name = "account.tax.repartition.line.template"
+ _description = "Tax Repartition Line Template"
+
+ factor_percent = fields.Float(string="%", required=True, help="Factor to apply on the account move lines generated from this repartition line, in percents")
+ repartition_type = fields.Selection(string="Based On", selection=[('base', 'Base'), ('tax', 'of tax')], required=True, default='tax', help="Base on which the factor will be applied.")
+ account_id = fields.Many2one(string="Account", comodel_name='account.account.template', help="Account on which to post the tax amount")
+ invoice_tax_id = fields.Many2one(comodel_name='account.tax.template', help="The tax set to apply this repartition on invoices. Mutually exclusive with refund_tax_id")
+ refund_tax_id = fields.Many2one(comodel_name='account.tax.template', help="The tax set to apply this repartition on refund invoices. Mutually exclusive with invoice_tax_id")
+ tag_ids = fields.Many2many(string="Financial Tags", relation='account_tax_repartition_financial_tags', comodel_name='account.account.tag', copy=True, help="Additional tags that will be assigned by this repartition line for use in financial reports")
+
+ # These last two fields are helpers used to ease the declaration of account.account.tag objects in XML.
+ # They are directly linked to account.tax.report.line objects, which create corresponding + and - tags
+ # at creation. This way, we avoid declaring + and - separately every time.
+ plus_report_line_ids = fields.Many2many(string="Plus Tax Report Lines", relation='account_tax_repartition_plus_report_line', comodel_name='account.tax.report.line', copy=True, help="Tax report lines whose '+' tag will be assigned to move lines by this repartition line")
+ minus_report_line_ids = fields.Many2many(string="Minus Report Lines", relation='account_tax_repartition_minus_report_line', comodel_name='account.tax.report.line', copy=True, help="Tax report lines whose '-' tag will be assigned to move lines by this repartition line")
+
+ @api.model
+ def create(self, vals):
+ if vals.get('plus_report_line_ids'):
+ vals['plus_report_line_ids'] = self._convert_tag_syntax_to_orm(vals['plus_report_line_ids'])
+
+ if vals.get('minus_report_line_ids'):
+ vals['minus_report_line_ids'] = self._convert_tag_syntax_to_orm(vals['minus_report_line_ids'])
+
+ if vals.get('tag_ids'):
+ vals['tag_ids'] = self._convert_tag_syntax_to_orm(vals['tag_ids'])
+
+ return super(AccountTaxRepartitionLineTemplate, self).create(vals)
+
+ @api.model
+ def _convert_tag_syntax_to_orm(self, tags_list):
+ """ Repartition lines give the possibility to directly give
+ a list of ids to create for tags instead of a list of ORM commands.
+
+ This function checks that tags_list uses this syntactic sugar and returns
+ an ORM-compliant version of it if it does.
+ """
+ if tags_list and all(isinstance(elem, int) for elem in tags_list):
+ return [(6, False, tags_list)]
+ return tags_list
+
+ @api.constrains('invoice_tax_id', 'refund_tax_id')
+ def validate_tax_template_link(self):
+ for record in self:
+ if record.invoice_tax_id and record.refund_tax_id:
+ raise ValidationError(_("Tax repartition line templates should apply to either invoices or refunds, not both at the same time. invoice_tax_id and refund_tax_id should not be set together."))
+
+ @api.constrains('plus_report_line_ids', 'minus_report_line_ids')
+ def validate_tags(self):
+ all_tax_rep_lines = self.mapped('plus_report_line_ids') + self.mapped('minus_report_line_ids')
+ lines_without_tag = all_tax_rep_lines.filtered(lambda x: not x.tag_name)
+ if lines_without_tag:
+ raise ValidationError(_("The following tax report lines are used in some tax repartition template though they don't generate any tag: %s . This probably means you forgot to set a tag_name on these lines.") % str(lines_without_tag.mapped('name')))
+
+ def get_repartition_line_create_vals(self, company):
+ rslt = [(5, 0, 0)]
+ for record in self:
+ tags_to_add = self.env['account.account.tag']
+ tags_to_add += record.plus_report_line_ids.mapped('tag_ids').filtered(lambda x: not x.tax_negate)
+ tags_to_add += record.minus_report_line_ids.mapped('tag_ids').filtered(lambda x: x.tax_negate)
+ tags_to_add += record.tag_ids
+
+ rslt.append((0, 0, {
+ 'factor_percent': record.factor_percent,
+ 'repartition_type': record.repartition_type,
+ 'tag_ids': [(6, 0, tags_to_add.ids)],
+ 'company_id': company.id,
+ }))
+ return rslt
+
# Fiscal Position Templates
class AccountFiscalPositionTemplate(models.Model):