diff options
| author | Olivier Colson <oco@odoo.com> | 2019-04-19 10:06:07 +0000 |
|---|---|---|
| committer | Olivier Colson <oco@odoo.com> | 2019-05-10 11:52:58 +0000 |
| commit | 3936d655c4bfd845b6ead724cecdc8e7c62816eb (patch) | |
| tree | 402e137e0fa09b06c1b237ab6a346670dea480c2 /addons/account/models/chart_template.py | |
| parent | 574295df57d8da78ca6109f277a5a2332bf38101 (diff) | |
[IMP] account, l10n_*: v13 taxes
- Add repartition lines on taxes
- Link account tags directly to account.move.line; remove the tag_ids field from account.tag
- Add a new report engine dedicated to tax reports, directly generating account tags. It is called as an alternate mode of generic tax report, with a dedicated "Use tax grids" toggle.
>> The biggest change lies in the way the new tax report computes its values.
Everything is now aggregated directly using the tags set on the account move lines. Thanks to that,
modifying the configuration of a tax today will not impact the report for the previous periods anymore.
This is a big improvement, as it means the report will keep on reflecting the values that were submitted
to the state before, whatever the configuration change.
- Add an audit char field to account.move.line telling with tax grids are impacted by the line, with the corresponding amount
- Modify the behavior of cash basis taxes: the cash basis account is now used as the transition account, while the regular account given in tax declaration is used to store the final entry (it was the opposite before)
- Modify every l10n_* module in order to keep them consistent with these changes
closes odoo/odoo#32833
Signed-off-by: Quentin De Paoli (qdp) <qdp@openerp.com>
Diffstat (limited to 'addons/account/models/chart_template.py')
| -rw-r--r-- | addons/account/models/chart_template.py | 143 |
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): |
