diff options
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): |
