summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--addons/account/__manifest__.py3
-rw-r--r--addons/account/models/account_move.py19
-rw-r--r--addons/account/models/company.py6
-rw-r--r--addons/account/security/ir.model.access.csv3
-rw-r--r--addons/account/tests/test_account_move_in_invoice.py5
-rw-r--r--addons/account/tests/test_account_move_out_invoice.py5
-rw-r--r--addons/account/tests/test_transfer_wizard.py20
-rw-r--r--addons/account/views/account_move_views.xml30
-rw-r--r--addons/account/wizard/__init__.py3
-rw-r--r--addons/account/wizard/account_accrual_accounting.py248
-rw-r--r--addons/account/wizard/account_accrual_accounting_view.xml42
-rw-r--r--addons/account/wizard/account_automatic_entry_wizard.py398
-rw-r--r--addons/account/wizard/account_automatic_entry_wizard_views.xml55
-rw-r--r--addons/account/wizard/account_transfer_wizard.py195
-rw-r--r--addons/account/wizard/account_transfer_wizard.xml41
15 files changed, 496 insertions, 577 deletions
diff --git a/addons/account/__manifest__.py b/addons/account/__manifest__.py
index 1ec5bdde556..0ebd0cac36f 100644
--- a/addons/account/__manifest__.py
+++ b/addons/account/__manifest__.py
@@ -23,7 +23,7 @@ You could use this simplified accounting in case you work with an (external) acc
'data/account_data.xml',
'data/digest_data.xml',
'views/account_payment_view.xml',
- 'wizard/account_accrual_accounting_view.xml',
+ 'wizard/account_automatic_entry_wizard_views.xml',
'wizard/account_unreconcile_view.xml',
'wizard/account_move_reversal_view.xml',
'wizard/account_resequence_views.xml',
@@ -74,7 +74,6 @@ You could use this simplified accounting in case you work with an (external) acc
'wizard/account_invoice_send_views.xml',
'views/account_tax_report_views.xml',
'report/account_hash_integrity_templates.xml',
- 'wizard/account_transfer_wizard.xml',
'views/res_currency.xml',
'views/account_menuitem.xml',
'views/account_analytic_default_view.xml',
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index 621cbb22b23..fccccb49ae6 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -2750,6 +2750,7 @@ class AccountMoveLine(models.Model):
check_company=True,
tracking=True)
account_internal_type = fields.Selection(related='account_id.user_type_id.type', string="Internal Type", readonly=True)
+ account_internal_group = fields.Selection(related='account_id.user_type_id.internal_group', string="Internal Group", readonly=True)
account_root_id = fields.Many2one(related='account_id.root_id', string="Account Root", store=True, readonly=True)
sequence = fields.Integer(default=10)
name = fields.Text(string='Label', tracking=True)
@@ -4693,8 +4694,8 @@ class AccountMoveLine(models.Model):
action['domain'] = [('id', 'in', ids)]
return action
- def action_accrual_entry(self):
- [action] = self.env.ref('account.account_accrual_accounting_wizard_action').read()
+ def action_automatic_entry(self):
+ [action] = self.env.ref('account.account_automatic_entry_wizard_action').read()
action['context'] = self.env.context
return action
@@ -4706,20 +4707,6 @@ class AccountMoveLine(models.Model):
('statement_line_id', '!=', False),
]
- def action_transfer_accounts_wizard(self):
- wizard = self.env['account.transfer.wizard'].create({})
-
- return {
- 'name': _("Transfer Entries"),
- 'type': 'ir.actions.act_window',
- 'view_type': 'form',
- 'view_mode': 'form',
- 'res_model': 'account.transfer.wizard',
- 'res_id': wizard.id,
- 'target': 'new',
- 'context': self.env.context,
- }
-
def _get_attachment_domains(self):
self.ensure_one()
domains = [[('res_model', '=', 'account.move'), ('res_id', '=', self.move_id.id)]]
diff --git a/addons/account/models/company.py b/addons/account/models/company.py
index acebbb209bf..2f3f1e5864c 100644
--- a/addons/account/models/company.py
+++ b/addons/account/models/company.py
@@ -103,11 +103,11 @@ class ResCompany(models.Model):
# Accrual Accounting
expense_accrual_account_id = fields.Many2one('account.account',
help="Account used to move the period of an expense",
- domain="[('internal_group', '=', 'liability'), ('internal_type', 'not in', ('receivable', 'payable')), ('reconcile', '=', True), ('company_id', '=', id)]")
+ domain="[('internal_group', '=', 'liability'), ('internal_type', 'not in', ('receivable', 'payable')), ('company_id', '=', id)]")
revenue_accrual_account_id = fields.Many2one('account.account',
help="Account used to move the period of a revenue",
- domain="[('internal_group', '=', 'asset'), ('internal_type', 'not in', ('receivable', 'payable')), ('reconcile', '=', True), ('company_id', '=', id)]")
- accrual_default_journal_id = fields.Many2one('account.journal', help="Journal used by default for moving the period of an entry", domain="[('type', '=', 'general')]")
+ domain="[('internal_group', '=', 'asset'), ('internal_type', 'not in', ('receivable', 'payable')), ('company_id', '=', id)]")
+ automatic_entry_default_journal_id = fields.Many2one('account.journal', help="Journal used by default for moving the period of an entry", domain="[('type', '=', 'general')]")
# Technical field to hide country specific fields in company form view
country_code = fields.Char(related='country_id.code')
diff --git a/addons/account/security/ir.model.access.csv b/addons/account/security/ir.model.access.csv
index 4725df73507..8020b7d06ec 100644
--- a/addons/account/security/ir.model.access.csv
+++ b/addons/account/security/ir.model.access.csv
@@ -125,7 +125,7 @@ access_account_payment,account.payment,model_account_payment,account.group_accou
access_account_payment_register,access.account.payment.register,model_account_payment_register,account.group_account_invoice,1,1,1,0
access_account_bank_statement_closebalance,access.account.bank.statement.closebalance,model_account_bank_statement_closebalance,account.group_account_user,1,1,1,0
-access_account_accrual_accounting_wizard,access.account.accrual.accounting.wizard,model_account_accrual_accounting_wizard,account.group_account_user,1,1,1,0
+access_account_automatic_entry_wizard,access.account.automatic.entry.wizard,model_account_automatic_entry_wizard,account.group_account_user,1,1,1,0
access_account_unreconcile,access.account.unreconcile,model_account_unreconcile,account.group_account_user,1,1,1,0
access_account_resequence,access.account.resequence.wizard,model_account_resequence_wizard,account.group_account_user,1,1,1,0
access_validate_account_move,access.validate.account.move,model_validate_account_move,account.group_account_invoice,1,1,1,0
@@ -138,7 +138,6 @@ access_account_financial_year_op,access.account.financial.year.op,model_account_
access_account_setup_bank_manual_config,access.account.setup.bank.manual.config,model_account_setup_bank_manual_config,account.group_account_manager,1,1,1,0
access_tax_adjustments_wizard,access.tax.adjustments.wizard,model_tax_adjustments_wizard,account.group_account_user,1,1,1,0
access_account_invoice_send,access.account.invoice.send,model_account_invoice_send,account.group_account_invoice,1,1,1,0
-access_account_transfer_wizard,access.account.transfer.wizard,model_account_transfer_wizard,account.group_account_user,1,1,1,0
access_account_analytic_default,account.analytic.default,model_account_analytic_default,account.group_account_readonly,1,0,0,0
access_account_analytic_default_analytic,account.analytic.default analytic,model_account_analytic_default,analytic.group_analytic_accounting,1,0,0,0
diff --git a/addons/account/tests/test_account_move_in_invoice.py b/addons/account/tests/test_account_move_in_invoice.py
index 69c85523809..bd8e9a09b42 100644
--- a/addons/account/tests/test_account_move_in_invoice.py
+++ b/addons/account/tests/test_account_move_in_invoice.py
@@ -1653,8 +1653,9 @@ class TestAccountMoveInInvoiceOnchanges(AccountTestInvoicingCommon):
})
move.action_post()
- wizard = self.env['account.accrual.accounting.wizard']\
+ wizard = self.env['account.automatic.entry.wizard']\
.with_context(active_model='account.move.line', active_ids=move.invoice_line_ids.ids).create({
+ 'action': 'change_period',
'date': '2018-01-01',
'percentage': 60,
'journal_id': self.company_data['default_journal_misc'].id,
@@ -1671,7 +1672,7 @@ class TestAccountMoveInInvoiceOnchanges(AccountTestInvoicingCommon):
'reconcile': True,
}).id,
})
- wizard_res = wizard.amend_entries()
+ wizard_res = wizard.do_action()
self.assertInvoiceValues(move, [
{
diff --git a/addons/account/tests/test_account_move_out_invoice.py b/addons/account/tests/test_account_move_out_invoice.py
index b18edefe04e..e77eff26027 100644
--- a/addons/account/tests/test_account_move_out_invoice.py
+++ b/addons/account/tests/test_account_move_out_invoice.py
@@ -2330,8 +2330,9 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
})
move.action_post()
- wizard = self.env['account.accrual.accounting.wizard']\
+ wizard = self.env['account.automatic.entry.wizard']\
.with_context(active_model='account.move.line', active_ids=move.invoice_line_ids.ids).create({
+ 'action': 'change_period',
'date': '2018-01-01',
'percentage': 60,
'journal_id': self.company_data['default_journal_misc'].id,
@@ -2348,7 +2349,7 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
'reconcile': True,
}).id,
})
- wizard_res = wizard.amend_entries()
+ wizard_res = wizard.do_action()
self.assertInvoiceValues(move, [
{
diff --git a/addons/account/tests/test_transfer_wizard.py b/addons/account/tests/test_transfer_wizard.py
index 76a393f790d..03b76235398 100644
--- a/addons/account/tests/test_transfer_wizard.py
+++ b/addons/account/tests/test_transfer_wizard.py
@@ -196,12 +196,13 @@ class TestTransferWizard(AccountTestInvoicingCommon):
# We use a form to pass the context properly to the depends_context move_line_ids field
context = {'active_model': 'account.move.line', 'active_ids': active_move_lines.ids}
- with Form(self.env['account.transfer.wizard'].with_context(context)) as wizard_form:
+ with Form(self.env['account.automatic.entry.wizard'].with_context(context)) as wizard_form:
+ wizard_form.action = 'change_account'
wizard_form.destination_account_id = self.receivable_account
wizard_form.journal_id = self.journal
wizard = wizard_form.save()
- transfer_move_id = wizard.button_transfer()['res_id']
+ transfer_move_id = wizard.do_action()['res_id']
transfer_move = self.env['account.move'].browse(transfer_move_id)
payable_transfer = transfer_move.line_ids.filtered(lambda x: x.account_id == self.payable_account)
@@ -221,12 +222,13 @@ class TestTransferWizard(AccountTestInvoicingCommon):
# We use a form to pass the context properly to the depends_context move_line_ids field
context = {'active_model': 'account.move.line', 'active_ids': active_move_lines.ids}
- with Form(self.env['account.transfer.wizard'].with_context(context)) as wizard_form:
+ with Form(self.env['account.automatic.entry.wizard'].with_context(context)) as wizard_form:
+ wizard_form.action = 'change_account'
wizard_form.destination_account_id = self.accounts[4]
wizard_form.journal_id = self.journal
wizard = wizard_form.save()
- transfer_move_id = wizard.button_transfer()['res_id']
+ transfer_move_id = wizard.do_action()['res_id']
transfer_move = self.env['account.move'].browse(transfer_move_id)
groups = {}
@@ -251,12 +253,13 @@ class TestTransferWizard(AccountTestInvoicingCommon):
# We use a form to pass the context properly to the depends_context move_line_ids field
context = {'active_model': 'account.move.line', 'active_ids': active_move_lines.ids}
- with Form(self.env['account.transfer.wizard'].with_context(context)) as wizard_form:
+ with Form(self.env['account.automatic.entry.wizard'].with_context(context)) as wizard_form:
+ wizard_form.action = 'change_account'
wizard_form.destination_account_id = self.test_currency_account
wizard_form.journal_id = self.journal
wizard = wizard_form.save()
- transfer_move_id = wizard.button_transfer()['res_id']
+ transfer_move_id = wizard.do_action()['res_id']
transfer_move = self.env['account.move'].browse(transfer_move_id)
destination_line = transfer_move.line_ids.filtered(lambda x: x.account_id == self.test_currency_account)
@@ -273,12 +276,13 @@ class TestTransferWizard(AccountTestInvoicingCommon):
# We use a form to pass the context properly to the depends_context move_line_ids field
context = {'active_model': 'account.move.line', 'active_ids': active_move_lines.ids}
- with Form(self.env['account.transfer.wizard'].with_context(context)) as wizard_form:
+ with Form(self.env['account.automatic.entry.wizard'].with_context(context)) as wizard_form:
+ wizard_form.action = 'change_account'
wizard_form.destination_account_id = self.receivable_account
wizard_form.journal_id = self.journal
wizard = wizard_form.save()
- transfer_move_id = wizard.button_transfer()['res_id']
+ transfer_move_id = wizard.do_action()['res_id']
transfer_move = self.env['account.move'].browse(transfer_move_id)
destination_lines = transfer_move.line_ids.filtered(lambda x: x.account_id == self.receivable_account)
diff --git a/addons/account/views/account_move_views.xml b/addons/account/views/account_move_views.xml
index 09f700adb78..8738b921ce7 100644
--- a/addons/account/views/account_move_views.xml
+++ b/addons/account/views/account_move_views.xml
@@ -758,6 +758,7 @@
<field name="is_rounding_line" invisible="1"/>
<field name="exclude_from_invoice_tab" invisible="1"/>
<field name="account_internal_type" invisible="1"/>
+ <field name="account_internal_group" invisible="1"/>
<field name="always_set_currency_id" invisible="1"/>
</tree>
<kanban class="o_kanban_mobile">
@@ -837,6 +838,7 @@
<field name="is_rounding_line" invisible="1"/>
<field name="exclude_from_invoice_tab" invisible="1"/>
<field name="account_internal_type" invisible="1"/>
+ <field name="account_internal_group" invisible="1"/>
<field name="always_set_currency_id" invisible="1"/>
</kanban>
<form>
@@ -949,6 +951,16 @@
<field name="country_id" invisible="1"/>
<field name="tax_tag_ids" widget="many2many_tags" string="Tax Grids" optional="show" domain="[('country_id', '=', country_id), ('applicability', '=', 'taxes')]"/>
+ <!-- Buttons -->
+ <button name="action_automatic_entry"
+ type="object"
+ icon="fa-calendar"
+ string="Cut-Off"
+ aria-label="Change Period"
+ class="float-right"
+ attrs="{'invisible': [('account_internal_group', 'not in', ('income', 'expense'))], 'column_invisible': [('parent.move_type', '=', 'entry')]}"
+ context="{'hide_automatic_options': 1, 'default_action': 'change_period'}"/>
+
<!-- Others fields -->
<field name="payment_id" invisible="1"/>
@@ -976,6 +988,7 @@
<field name="is_rounding_line" invisible="1"/>
<field name="exclude_from_invoice_tab" invisible="1"/>
<field name="account_internal_type" invisible="1"/>
+ <field name="account_internal_group" invisible="1"/>
</tree>
<!-- Form view to cover mobile use -->
<form>
@@ -1163,13 +1176,13 @@
<field name="context">{'search_default_account_id': [active_id]}</field>
</record>
- <record id="action_accrual_entry" model="ir.actions.server">
- <field name="name">Create Accrual Entries</field>
+ <record id="action_automatic_entry" model="ir.actions.server">
+ <field name="name">Automatic Entries</field>
<field name="model_id" ref="account.model_account_move_line"/>
<field name="groups_id" eval="[(4, ref('account.group_account_user'))]"/>
<field name="binding_model_id" ref="account.model_account_move_line"/>
<field name="state">code</field>
- <field name="code">action = records.action_accrual_entry()</field>
+ <field name="code">action = records.action_automatic_entry()</field>
</record>
<record id="action_account_moves_all_a" model="ir.actions.act_window">
@@ -1408,16 +1421,5 @@ if records:
</field>
</record>
- <record model="ir.actions.server" id="action_move_transfer_accounts">
- <field name="name">Transfer Entries</field>
- <field name="model_id" ref="account.model_account_move_line"/>
- <field name="binding_model_id" ref="account.model_account_move_line" />
- <field name="state">code</field>
- <field name="code">
-if records:
- action = records.action_transfer_accounts_wizard()
- </field>
- </record>
-
</data>
</odoo>
diff --git a/addons/account/wizard/__init__.py b/addons/account/wizard/__init__.py
index a130d327938..b88c746d3f5 100644
--- a/addons/account/wizard/__init__.py
+++ b/addons/account/wizard/__init__.py
@@ -2,7 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from . import account_accrual_accounting
+from . import account_automatic_entry_wizard
from . import account_unreconcile
from . import account_validate_account_move
from . import pos_box
@@ -15,5 +15,4 @@ from . import setup_wizards
from . import wizard_tax_adjustments
from . import account_invoice_send
from . import base_document_layout
-from . import account_transfer_wizard
from . import account_payment_register
diff --git a/addons/account/wizard/account_accrual_accounting.py b/addons/account/wizard/account_accrual_accounting.py
deleted file mode 100644
index 2f22ac54e11..00000000000
--- a/addons/account/wizard/account_accrual_accounting.py
+++ /dev/null
@@ -1,248 +0,0 @@
-# -*- coding: utf-8 -*-
-from odoo import api, fields, models, _
-from odoo.exceptions import UserError
-from odoo.tools.misc import format_date
-
-import json
-
-class AccrualAccountingWizard(models.TransientModel):
- _name = 'account.accrual.accounting.wizard'
- _description = 'Create accrual entries.'
-
- date = fields.Date(required=True)
- company_id = fields.Many2one('res.company', required=True, readonly=True)
- account_type = fields.Selection([('income', 'Revenue'), ('expense', 'Expense')])
- active_move_line_ids = fields.Many2many('account.move.line')
- journal_id = fields.Many2one('account.journal', required=True, readonly=False,
- domain="[('company_id', '=', company_id), ('type', '=', 'general')]",
- related="company_id.accrual_default_journal_id")
- expense_accrual_account = fields.Many2one('account.account', readonly=False,
- domain="[('company_id', '=', company_id), ('internal_type', 'not in', ('receivable', 'payable')), ('internal_group', '=', 'liability'), ('reconcile', '=', True)]",
- related="company_id.expense_accrual_account_id")
- revenue_accrual_account = fields.Many2one('account.account', readonly=False,
- domain="[('company_id', '=', company_id), ('internal_type', 'not in', ('receivable', 'payable')), ('internal_group', '=', 'asset'), ('reconcile', '=', True)]",
- related="company_id.revenue_accrual_account_id")
- percentage = fields.Float("Percentage", default=100.0)
- total_amount = fields.Monetary(compute="_compute_total_amount", currency_field='company_currency_id')
- company_currency_id = fields.Many2one('res.currency', related='company_id.currency_id')
- data = fields.Text(compute="_compute_data")
- preview_data = fields.Text(compute="_compute_preview_data")
-
- @api.constrains('percentage')
- def _constraint_percentage(self):
- for record in self:
- if not (0.0 < record.percentage <= 100.0):
- raise UserError(_("Percentage must be between 0 and 100"))
-
- @api.depends('percentage', 'active_move_line_ids')
- def _compute_total_amount(self):
- for record in self:
- record.total_amount = sum(record.active_move_line_ids.mapped(lambda l: record.percentage * (l.debit + l.credit) / 100))
-
- @api.model
- def default_get(self, fields):
- if self.env.context.get('active_model') != 'account.move.line' or not self.env.context.get('active_ids'):
- raise UserError(_('This can only be used on journal items'))
- rec = super(AccrualAccountingWizard, self).default_get(fields)
- if not set(fields) & set(['active_move_line_ids', 'company_id', 'account_type']):
- return super().default_get(fields)
- active_move_line_ids = self.env['account.move.line'].browse(self.env.context['active_ids'])
- rec['active_move_line_ids'] = active_move_line_ids.ids
-
- if any(move.state != 'posted' for move in active_move_line_ids.mapped('move_id')):
- raise UserError(_('You can only change the period for posted journal items.'))
- if any(move_line.reconciled for move_line in active_move_line_ids):
- raise UserError(_('You can only change the period for items that are not yet reconciled.'))
- if any(line.account_id.user_type_id != active_move_line_ids[0].account_id.user_type_id for line in active_move_line_ids):
- raise UserError(_('All accounts on the lines must be from the same type.'))
- if any(line.company_id != active_move_line_ids[0].company_id for line in active_move_line_ids):
- raise UserError(_('All lines must be from the same company.'))
- rec['company_id'] = active_move_line_ids[0].company_id.id
- account_types_allowed = self.env.ref('account.data_account_type_expenses') + self.env.ref('account.data_account_type_revenue') + self.env.ref('account.data_account_type_other_income')
- if active_move_line_ids[0].account_id.user_type_id not in account_types_allowed:
- raise UserError(_('You can only change the period for items in these types of accounts: %s') % ", ".join(account_types_allowed.mapped('name')))
- rec['account_type'] = active_move_line_ids[0].account_id.user_type_id.internal_group
- return rec
-
- @api.depends('active_move_line_ids', 'journal_id', 'revenue_accrual_account', 'expense_accrual_account', 'percentage', 'date', 'account_type')
- def _compute_data(self):
- for record in self:
- # set the accrual account on the selected journal items
- accrual_account = record.revenue_accrual_account if record.account_type == 'income' else record.expense_accrual_account
-
- move_data = {}
-
- # compute the account.move.lines and the total amount per move
- for aml in record.active_move_line_ids:
- if aml.move_id not in move_data:
- move_data[aml.move_id] = {
- 'total_amount': 0,
- 'move_vals': {
- 'new_date': {'line_ids': []},
- 'original_date': {'line_ids': []},
- }
- }
- # total amount per move is used to build the account.move references and chatter messages
- move_data[aml.move_id]['total_amount'] += aml.balance
-
- # account.move.line data
- reported_debit = aml.company_id.currency_id.round((record.percentage / 100) * aml.debit)
- reported_credit = aml.company_id.currency_id.round((record.percentage / 100) * aml.credit)
- if aml.currency_id:
- reported_amount_currency = aml.currency_id.round((record.percentage / 100) * aml.amount_currency)
- else:
- reported_amount_currency = 0.0
-
- move_data[aml.move_id]['move_vals']['new_date']['line_ids'] += [
- (0, 0, {
- 'name': aml.name,
- 'debit': reported_debit,
- 'credit': reported_credit,
- 'amount_currency': reported_amount_currency,
- 'currency_id': aml.currency_id.id,
- 'account_id': aml.account_id.id,
- 'partner_id': aml.partner_id.id,
- }),
- (0, 0, {
- 'name': _('Accrual Adjusting Entry'),
- 'debit': reported_credit,
- 'credit': reported_debit,
- 'amount_currency': -reported_amount_currency,
- 'currency_id': aml.currency_id.id,
- 'account_id': accrual_account.id,
- 'partner_id': aml.partner_id.id,
- }),
- ]
- move_data[aml.move_id]['move_vals']['original_date']['line_ids'] += [
- (0, 0, {
- 'name': aml.name,
- 'debit': reported_credit,
- 'credit': reported_debit,
- 'amount_currency': -reported_amount_currency,
- 'currency_id': aml.currency_id.id,
- 'account_id': aml.account_id.id,
- 'partner_id': aml.partner_id.id,
- }),
- (0, 0, {
- 'name': _('Accrual Adjusting Entry'),
- 'debit': reported_debit,
- 'credit': reported_credit,
- 'amount_currency': reported_amount_currency,
- 'currency_id': aml.currency_id.id,
- 'account_id': accrual_account.id,
- 'partner_id': aml.partner_id.id,
- }),
- ]
-
- # complete the account.move data
- for move in record.active_move_line_ids.move_id:
- def format_strings(string):
- return string.format(
- percent=record.percentage,
- name=move.name,
- id=move.id,
- origin_amount=origin_amount,
- date=format_date(self.env, move.date),
- new_date=record.date and format_date(self.env, record.date) or _('[Not set]'),
- )
- origin_amount = abs(move_data[move]['total_amount'])
- ref1 = format_strings(_('Adjusting Entry of {name} ({percent}% of {origin_amount} recognized from {date})'))
- ref2 = format_strings(_('Adjusting Entry of {name} ({percent}% of {origin_amount} recognized on {new_date})'))
- move_data[move]['move_vals']['new_date']['ref'] = ref1
- move_data[move]['move_vals']['new_date']['date'] = fields.Date.to_string(record.date)
- move_data[move]['move_vals']['new_date']['journal_id'] = record.journal_id.id
- move_data[move]['move_vals']['original_date']['ref'] = ref2
- move_data[move]['move_vals']['original_date']['date'] = fields.Date.to_string(move.date)
- move_data[move]['move_vals']['original_date']['journal_id'] = record.journal_id.id
- move_data[move]['log_messages'] = {
- 'new_date': format_strings(
- _('Accrual Adjusting Entry')
- + ' <a href=# data-oe-model=account.move data-oe-id={id}>{name}</a>: '
- + _('{percent}% of {origin_amount} recognized from {date}')
- ),
- 'original_date': format_strings(
- _('Accrual Adjusting Entry for')
- + ' <a href=# data-oe-model=account.move data-oe-id={id}>{name}</a>: '
- + _('{percent}% of {origin_amount} recognized on {date}')
- ),
- 'origin': format_strings(
- _('Accrual Adjusting Entries have been created for this invoice')
- + ':<ul><li><a href=# data-oe-model=account.move data-oe-id=%(second_id)d>%(second_name)s</a> '
- + _('cancelling {percent}%% of {origin_amount} ')
- + '</li><li><a href=# data-oe-model=account.move data-oe-id=%(first_id)d>%(first_name)s</a> '
- + _('postponing it to {date}')
- + '</li></ul>'
- )
- }
-
- move_vals = [m for o in move_data.values() for m in o['move_vals'].values()]
- log_messages = [o['log_messages'] for o in move_data.values()]
-
- record.data = json.dumps({
- 'move_vals': move_vals,
- 'log_messages': log_messages,
- })
-
- @api.depends('data')
- def _compute_preview_data(self):
- for record in self:
- data = json.loads(record.data)
- move_vals, log_messages = (data['move_vals'], data['log_messages'])
-
- preview_vals = []
- for move in move_vals[:4]:
- preview_vals += [self.env['account.move']._move_dict_to_preview_vals(move, record.company_id.currency_id)]
-
- preview_discarded = max(0, len(move_vals) - len(preview_vals))
- preview_columns = [
- {'field': 'account_id', 'label': _('Account')},
- {'field': 'name', 'label': _('Label')},
- {'field': 'debit', 'label': _('Debit'), 'class': 'text-right text-nowrap'},
- {'field': 'credit', 'label': _('Credit'), 'class': 'text-right text-nowrap'},
- ]
-
- record.preview_data = json.dumps({
- 'groups_vals': preview_vals,
- 'options': {
- 'discarded_number': _("%d moves", preview_discarded) if preview_discarded else False,
- 'columns': preview_columns,
- },
- })
-
- def amend_entries(self):
- accrual_account = self.revenue_accrual_account if self.account_type == 'income' else self.expense_accrual_account
- data = json.loads(self.data)
- move_vals, log_messages = (data['move_vals'], data['log_messages'])
-
- created_moves = self.env['account.move'].create(move_vals)
- created_moves.post()
-
- # Reconcile.
- index = 0
- for move in self.active_move_line_ids.mapped('move_id'):
- accrual_moves = created_moves[index:index + 2]
-
- to_reconcile = accrual_moves.mapped('line_ids').filtered(lambda line: line.account_id == accrual_account)
- to_reconcile.reconcile()
- move.message_post(body=log_messages[index//2]['origin'] % {
- 'first_id': accrual_moves[0].id,
- 'first_name': accrual_moves[0].name,
- 'second_id': accrual_moves[1].id,
- 'second_name': accrual_moves[1].name,
- })
- accrual_moves[0].message_post(body=log_messages[index//2]['new_date'])
- accrual_moves[1].message_post(body=log_messages[index//2]['original_date'])
- index += 2
-
- # open the generated entries
- action = {
- 'name': _('Generated Entries'),
- 'domain': [('id', 'in', created_moves.ids)],
- 'res_model': 'account.move',
- 'view_mode': 'tree,form',
- 'type': 'ir.actions.act_window',
- 'views': [(self.env.ref('account.view_move_tree').id, 'tree'), (False, 'form')],
- }
- if len(created_moves) == 1:
- action.update({'view_mode': 'form', 'res_id': created_moves.id})
- return action
diff --git a/addons/account/wizard/account_accrual_accounting_view.xml b/addons/account/wizard/account_accrual_accounting_view.xml
deleted file mode 100644
index 98b88d94530..00000000000
--- a/addons/account/wizard/account_accrual_accounting_view.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<odoo>
- <data>
- <record id="account_accrual_accounting_wizard_form_view" model="ir.ui.view">
- <field name="name">account.accrual.accounting.wizard.form</field>
- <field name="model">account.accrual.accounting.wizard</field>
- <field name="arch" type="xml">
- <form>
- <field name="account_type" invisible="1"/>
- <field name="company_id" invisible="1"/>
- <group>
- <group>
- <field name="date" string="Recognize on"/>
- <field name="expense_accrual_account" string="Accrued Account"
- attrs="{'invisible': [('account_type', '!=', 'expense')], 'required': [('account_type', '=', 'expense')]}"/>
- <field name="revenue_accrual_account" string="Accrued Account"
- attrs="{'invisible': [('account_type', '!=', 'income')], 'required': [('account_type', '=', 'income')]}"/>
- </group>
- <group>
- <field name="percentage" string="Percent (%)"/>
- <field name="total_amount" string="Amount"/>
- <field name="journal_id"/>
- </group>
- </group>
- <label for="preview_data" string="The following Journal Entries will be generated"/>
- <field name="preview_data" widget="grouped_view_widget"/>
- <footer>
- <button string="Create Journal Entries" name="amend_entries" type="object" class="oe_highlight"/>
- <button string="Cancel" class="btn btn-secondary" special="cancel"/>
- </footer>
- </form>
- </field>
- </record>
-
- <record id="account_accrual_accounting_wizard_action" model="ir.actions.act_window">
- <field name="name">Create Accrual Entries for the expense/revenue recognition</field>
- <field name="res_model">account.accrual.accounting.wizard</field>
- <field name="view_mode">form</field>
- <field name="target">new</field>
- </record>
- </data>
-</odoo>
diff --git a/addons/account/wizard/account_automatic_entry_wizard.py b/addons/account/wizard/account_automatic_entry_wizard.py
new file mode 100644
index 00000000000..920e209c5a5
--- /dev/null
+++ b/addons/account/wizard/account_automatic_entry_wizard.py
@@ -0,0 +1,398 @@
+# -*- coding: utf-8 -*-
+from odoo import api, fields, models, _
+from odoo.exceptions import UserError
+from odoo.tools.misc import format_date, formatLang
+
+from collections import defaultdict
+from itertools import groupby
+import json
+
+class AutomaticEntryWizard(models.TransientModel):
+ _name = 'account.automatic.entry.wizard'
+ _description = 'Create Automatic Entries'
+
+ # General
+ action = fields.Selection([('change_period', 'Change Period'), ('change_account', 'Change Account')], required=True)
+ move_data = fields.Text(compute="_compute_move_data", help="JSON value of the moves to be created")
+ preview_move_data = fields.Text(compute="_compute_preview_move_data", help="JSON value of the data to be displayed in the previewer")
+ move_line_ids = fields.Many2many('account.move.line')
+ date = fields.Date(required=True, default=fields.Date.today())
+ company_id = fields.Many2one('res.company', required=True, readonly=True)
+ company_currency_id = fields.Many2one('res.currency', related='company_id.currency_id')
+ percentage = fields.Float("Percentage", compute='_compute_percentage', readonly=False, store=True, help="Percentage of each line to execute the action on.")
+ total_amount = fields.Monetary(compute='_compute_total_amount', store=True, readonly=False, currency_field='company_currency_id', help="Total amount impacted by the automatic entry.")
+ journal_id = fields.Many2one('account.journal', required=True, readonly=False, string="Journal",
+ domain="[('company_id', '=', company_id), ('type', '=', 'general')]",
+ related="company_id.automatic_entry_default_journal_id",
+ help="Journal where to create the entry.")
+
+ # change period
+ account_type = fields.Selection([('income', 'Revenue'), ('expense', 'Expense')], compute='_compute_account_type', store=True)
+ expense_accrual_account = fields.Many2one('account.account', readonly=False,
+ domain="[('company_id', '=', company_id), ('internal_type', 'not in', ('receivable', 'payable'))]",
+ related="company_id.expense_accrual_account_id")
+ revenue_accrual_account = fields.Many2one('account.account', readonly=False,
+ domain="[('company_id', '=', company_id), ('internal_type', 'not in', ('receivable', 'payable'))]",
+ related="company_id.revenue_accrual_account_id")
+
+ # change account
+ destination_account_id = fields.Many2one(string="To", comodel_name='account.account', help="Account to transfer to.")
+ display_currency_helper = fields.Boolean(string="Currency Conversion Helper", compute='_compute_display_currency_helper', help="Technical field used to indicate whether or not to display the tooltip informing a currency conversion will be performed with the transfer")
+
+ @api.constrains('percentage', 'action')
+ def _constraint_percentage(self):
+ for record in self:
+ if not (0.0 < record.percentage <= 100.0):
+ raise UserError(_("Percentage must be between 0 and 100"))
+ if record.percentage != 100 and record.action != 'change_period':
+ print(record.percentage)
+ raise UserError(_("Percentage can only be set for Change Period method"))
+
+ @api.depends('percentage', 'move_line_ids')
+ def _compute_total_amount(self):
+ for record in self:
+ record.total_amount = (record.percentage or 100) * sum(record.move_line_ids.mapped('balance')) / 100
+
+ @api.depends('total_amount', 'move_line_ids')
+ def _compute_percentage(self):
+ for record in self:
+ record.percentage = record.total_amount / (sum(record.move_line_ids.mapped('balance')) or record.total_amount) * 100
+
+ @api.depends('move_line_ids')
+ def _compute_account_type(self):
+ for record in self:
+ record.account_type = 'income' if sum(record.move_line_ids.mapped('balance')) < 0 else 'expense'
+
+ @api.depends('destination_account_id')
+ def _compute_display_currency_helper(self):
+ for record in self:
+ record.display_currency_helper = bool(record.destination_account_id.currency_id)
+
+ @api.model
+ def default_get(self, fields):
+ res = super().default_get(fields)
+ if not set(fields) & set(['move_line_ids', 'company_id']):
+ return res
+
+ if self.env.context.get('active_model') != 'account.move.line' or not self.env.context.get('active_ids'):
+ raise UserError(_('This can only be used on journal items'))
+ move_line_ids = self.env['account.move.line'].browse(self.env.context['active_ids'])
+ res['move_line_ids'] = [(6, 0, move_line_ids.ids)]
+
+ if any(move.state != 'posted' for move in move_line_ids.mapped('move_id')):
+ raise UserError(_('You can only change the period/account for posted journal items.'))
+ if any(move_line.reconciled for move_line in move_line_ids):
+ raise UserError(_('You can only change the period/account for items that are not yet reconciled.'))
+ if any(line.company_id != move_line_ids[0].company_id for line in move_line_ids):
+ raise UserError(_('You cannot use this wizard on journal entries belonging to different companies.'))
+ res['company_id'] = move_line_ids[0].company_id.id
+
+ allowed_actions = set(dict(self._fields['action'].selection))
+ if self.env.context.get('default_action'):
+ allowed_actions = {self.env.context['default_action']}
+ if any(line.account_id.user_type_id != move_line_ids[0].account_id.user_type_id for line in move_line_ids):
+ allowed_actions.discard('change_period')
+ if not allowed_actions:
+ raise UserError(_('No possible action found with the selected lines.'))
+ res['action'] = allowed_actions.pop()
+ return res
+
+ def _get_move_dict_vals_change_account(self):
+ line_vals = []
+
+ # Group data from selected move lines
+ counterpart_balances = defaultdict(lambda: defaultdict(lambda: 0))
+ grouped_source_lines = defaultdict(lambda: self.env['account.move.line'])
+
+ for line in self.move_line_ids.filtered(lambda x: x.account_id != self.destination_account_id):
+ counterpart_currency = line.currency_id
+ counterpart_amount_currency = line.amount_currency
+
+ if self.destination_account_id.currency_id and self.destination_account_id.currency_id != self.company_id.currency_id:
+ counterpart_currency = self.destination_account_id.currency_id
+ counterpart_amount_currency = self.company_id.currency_id._convert(line.balance, self.destination_account_id.currency_id, self.company_id, line.date)
+
+ if counterpart_currency:
+ counterpart_balances[(line.partner_id, counterpart_currency)]['amount_currency'] += counterpart_amount_currency
+
+ counterpart_balances[(line.partner_id, counterpart_currency)]['balance'] += line.balance
+ grouped_source_lines[(line.partner_id, line.currency_id, line.account_id)] += line
+
+ # Generate counterpart lines' vals
+ for (counterpart_partner, counterpart_currency), counterpart_vals in counterpart_balances.items():
+ source_accounts = self.move_line_ids.mapped('account_id')
+ counterpart_label = len(source_accounts) == 1 and _("Transfer from %s", source_accounts.display_name) or _("Transfer counterpart")
+
+ if not self.company_id.currency_id.is_zero(counterpart_vals['balance']) or (counterpart_currency and not counterpart_currency.is_zero(counterpart_vals['amount_currency'])):
+ line_vals.append({
+ 'name': counterpart_label,
+ 'debit': counterpart_vals['balance'] > 0 and self.company_id.currency_id.round(counterpart_vals['balance']) or 0,
+ 'credit': counterpart_vals['balance'] < 0 and self.company_id.currency_id.round(-counterpart_vals['balance']) or 0,
+ 'account_id': self.destination_account_id.id,
+ 'partner_id': counterpart_partner.id or None,
+ 'amount_currency': counterpart_currency and counterpart_currency.round((counterpart_vals['balance'] < 0 and -1 or 1) * abs(counterpart_vals['amount_currency'])) or 0,
+ 'currency_id': counterpart_currency.id or None,
+ })
+
+ # Generate change_account lines' vals
+ for (partner, currency, account), lines in grouped_source_lines.items():
+ account_balance = sum(line.balance for line in lines)
+ if not (currency or self.company_id.currency_id).is_zero(account_balance):
+ account_amount_currency = currency and currency.round(sum(line.amount_currency for line in lines)) or 0
+ line_vals.append({
+ 'name': _('Transfer to %s', self.destination_account_id.display_name or _('[Not set]')),
+ 'debit': account_balance < 0 and self.company_id.currency_id.round(-account_balance) or 0,
+ 'credit': account_balance > 0 and self.company_id.currency_id.round(account_balance) or 0,
+ 'account_id': account.id,
+ 'partner_id': partner.id or None,
+ 'currency_id': currency.id or None,
+ 'amount_currency': (account_balance > 0 and -1 or 1) * abs(account_amount_currency),
+ })
+
+ return [{
+ 'currency_id': self.journal_id.currency_id.id or self.journal_id.company_id.currency_id.id,
+ 'move_type': 'entry',
+ 'journal_id': self.journal_id.id,
+ 'date': fields.Date.to_string(self.date),
+ 'ref': self.destination_account_id.display_name and _("Transfer entry to %s", self.destination_account_id.display_name or ''),
+ 'line_ids': [(0, 0, line) for line in line_vals],
+ }]
+
+ def _get_move_dict_vals_change_period(self):
+ # set the change_period account on the selected journal items
+ accrual_account = self.revenue_accrual_account if self.account_type == 'income' else self.expense_accrual_account
+
+ move_data = {'new_date': {
+ 'currency_id': self.journal_id.currency_id.id or self.journal_id.company_id.currency_id.id,
+ 'move_type': 'entry',
+ 'line_ids': [],
+ 'ref': _('Adjusting Entry'),
+ 'date': fields.Date.to_string(self.date),
+ 'journal_id': self.journal_id.id,
+ }}
+ # complete the account.move data
+ for date, grouped_lines in groupby(self.move_line_ids, lambda m: m.move_id.date):
+ grouped_lines = list(grouped_lines)
+ amount = sum(l.balance for l in grouped_lines)
+ move_data[date] = {
+ 'currency_id': self.journal_id.currency_id.id or self.journal_id.company_id.currency_id.id,
+ 'move_type': 'entry',
+ 'line_ids': [],
+ 'ref': self._format_strings(_('Adjusting Entry of {date} ({percent:f}% recognized on {new_date})'), grouped_lines[0].move_id, amount),
+ 'date': fields.Date.to_string(date),
+ 'journal_id': self.journal_id.id,
+ }
+
+ # compute the account.move.lines and the total amount per move
+ for aml in self.move_line_ids:
+ # account.move.line data
+ reported_debit = aml.company_id.currency_id.round((self.percentage / 100) * aml.debit)
+ reported_credit = aml.company_id.currency_id.round((self.percentage / 100) * aml.credit)
+ if aml.currency_id:
+ reported_amount_currency = aml.currency_id.round((self.percentage / 100) * aml.amount_currency)
+ else:
+ reported_amount_currency = 0.0
+
+ move_data['new_date']['line_ids'] += [
+ (0, 0, {
+ 'name': aml.name,
+ 'debit': reported_debit,
+ 'credit': reported_credit,
+ 'amount_currency': reported_amount_currency,
+ 'currency_id': aml.currency_id.id,
+ 'account_id': aml.account_id.id,
+ 'partner_id': aml.partner_id.id,
+ }),
+ (0, 0, {
+ 'name': _('Adjusting Entry'),
+ 'debit': reported_credit,
+ 'credit': reported_debit,
+ 'amount_currency': -reported_amount_currency,
+ 'currency_id': aml.currency_id.id,
+ 'account_id': accrual_account.id,
+ 'partner_id': aml.partner_id.id,
+ }),
+ ]
+ move_data[aml.move_id.date]['line_ids'] += [
+ (0, 0, {
+ 'name': aml.name,
+ 'debit': reported_credit,
+ 'credit': reported_debit,
+ 'amount_currency': -reported_amount_currency,
+ 'currency_id': aml.currency_id.id,
+ 'account_id': aml.account_id.id,
+ 'partner_id': aml.partner_id.id,
+ }),
+ (0, 0, {
+ 'name': _('Adjusting Entry'),
+ 'debit': reported_debit,
+ 'credit': reported_credit,
+ 'amount_currency': reported_amount_currency,
+ 'currency_id': aml.currency_id.id,
+ 'account_id': accrual_account.id,
+ 'partner_id': aml.partner_id.id,
+ }),
+ ]
+
+ move_vals = [m for m in move_data.values()]
+ return move_vals
+
+ @api.depends('move_line_ids', 'journal_id', 'revenue_accrual_account', 'expense_accrual_account', 'percentage', 'date', 'account_type', 'action', 'destination_account_id')
+ def _compute_move_data(self):
+ for record in self:
+ if record.action == 'change_period':
+ if any(line.account_id.user_type_id != record.move_line_ids[0].account_id.user_type_id for line in record.move_line_ids):
+ raise UserError(_('All accounts on the lines must be of the same type.'))
+ if record.action == 'change_period':
+ record.move_data = json.dumps(record._get_move_dict_vals_change_period())
+ elif record.action == 'change_account':
+ record.move_data = json.dumps(record._get_move_dict_vals_change_account())
+
+ @api.depends('move_data')
+ def _compute_preview_move_data(self):
+ for record in self:
+ preview_columns = [
+ {'field': 'account_id', 'label': _('Account')},
+ {'field': 'name', 'label': _('Label')},
+ {'field': 'debit', 'label': _('Debit'), 'class': 'text-right text-nowrap'},
+ {'field': 'credit', 'label': _('Credit'), 'class': 'text-right text-nowrap'},
+ ]
+ if record.action == 'change_account':
+ preview_columns[2:2] = [{'field': 'partner_id', 'label': _('Partner')}]
+
+ move_vals = json.loads(record.move_data)
+ preview_vals = []
+ for move in move_vals[:4]:
+ preview_vals += [self.env['account.move']._move_dict_to_preview_vals(move, record.company_id.currency_id)]
+ preview_discarded = max(0, len(move_vals) - len(preview_vals))
+
+ record.preview_move_data = json.dumps({
+ 'groups_vals': preview_vals,
+ 'options': {
+ 'discarded_number': _("%d moves", preview_discarded) if preview_discarded else False,
+ 'columns': preview_columns,
+ },
+ })
+
+ def do_action(self):
+ move_vals = json.loads(self.move_data)
+ if self.action == 'change_period':
+ return self._do_action_change_period(move_vals)
+ elif self.action == 'change_account':
+ return self._do_action_change_account(move_vals)
+
+ def _do_action_change_period(self, move_vals):
+ accrual_account = self.revenue_accrual_account if self.account_type == 'income' else self.expense_accrual_account
+
+ created_moves = self.env['account.move'].create(move_vals)
+ created_moves._post()
+
+ destination_move = created_moves[0]
+ destination_messages = []
+ for move in self.move_line_ids.move_id:
+ amount = sum((self.move_line_ids._origin & move.line_ids).mapped('balance'))
+ accrual_move = created_moves[1:].filtered(lambda m: m.date == move.date)
+
+ if accrual_account.reconcile:
+ to_reconcile = (accrual_move + destination_move).mapped('line_ids').filtered(lambda line: line.account_id == accrual_account)
+ to_reconcile.reconcile()
+ move.message_post(body=self._format_strings(_('Adjusting Entries have been created for this invoice:<ul><li>%(link1)s cancelling '
+ '{percent:f}%% of {amount}</li><li>%(link0)s postponing it to {new_date}</li></ul>',
+ link0=self._format_move_link(destination_move),
+ link1=self._format_move_link(accrual_move),
+ ), move, amount))
+ destination_messages += [self._format_strings(_('Adjusting Entry {link}: {percent:f}% of {amount} recognized from {date}'), move, amount)]
+ accrual_move.message_post(body=self._format_strings(_('Adjusting Entry for {link}: {percent:f}% of {amount} recognized on {new_date}'), move, amount))
+ destination_move.message_post(body='<br/>\n'.join(destination_messages))
+
+ # open the generated entries
+ action = {
+ 'name': _('Generated Entries'),
+ 'domain': [('id', 'in', created_moves.ids)],
+ 'res_model': 'account.move',
+ 'view_mode': 'tree,form',
+ 'type': 'ir.actions.act_window',
+ 'views': [(self.env.ref('account.view_move_tree').id, 'tree'), (False, 'form')],
+ }
+ if len(created_moves) == 1:
+ action.update({'view_mode': 'form', 'res_id': created_moves.id})
+ return action
+
+ def _do_action_change_account(self, move_vals):
+ new_move = self.env['account.move'].create(move_vals)
+ new_move._post()
+
+ # Group lines
+ grouped_lines = defaultdict(lambda: self.env['account.move.line'])
+ destination_lines = self.move_line_ids.filtered(lambda x: x.account_id == self.destination_account_id)
+ for line in self.move_line_ids - destination_lines:
+ grouped_lines[(line.partner_id, line.currency_id, line.account_id)] += line
+
+ # Reconcile
+ for (partner, currency, account), lines in grouped_lines.items():
+ if account.reconcile:
+ to_reconcile = lines + new_move.line_ids.filtered(lambda x: x.account_id == account and x.partner_id == partner and x.currency_id == currency)
+ to_reconcile.reconcile()
+
+ if destination_lines and self.destination_account_id.reconcile:
+ to_reconcile = destination_lines + new_move.line_ids.filtered(lambda x: x.account_id == self.destination_account_id and x.partner_id == partner and x.currency_id == currency)
+ to_reconcile.reconcile()
+
+ # Log the operation on source moves
+ acc_transfer_per_move = defaultdict(lambda: defaultdict(lambda: 0)) # dict(move, dict(account, balance))
+ for line in self.move_line_ids:
+ acc_transfer_per_move[line.move_id][line.account_id] += line.balance
+
+ for move, balances_per_account in acc_transfer_per_move.items():
+ message_to_log = self._format_transfer_source_log(balances_per_account, new_move)
+ if message_to_log:
+ move.message_post(body=message_to_log)
+
+ # Log on target move as well
+ new_move.message_post(body=self._format_new_transfer_move_log(acc_transfer_per_move))
+
+ return {
+ 'name': _("Transfer"),
+ 'type': 'ir.actions.act_window',
+ 'view_type': 'form',
+ 'view_mode': 'form',
+ 'res_model': 'account.move',
+ 'res_id': new_move.id,
+ }
+
+ # Transfer utils
+ def _format_new_transfer_move_log(self, acc_transfer_per_move):
+ format = _("<li>{amount} ({debit_credit}) from {link}, <strong>%(account_source_name)s</strong></li>")
+ rslt = _("This entry transfers the following amounts to <strong>%(destination)s</strong> <ul>", destination=self.destination_account_id.display_name)
+ for move, balances_per_account in acc_transfer_per_move.items():
+ for account, balance in balances_per_account.items():
+ if account != self.destination_account_id: # Otherwise, logging it here is confusing for the user
+ rslt += self._format_strings(format, move, balance) % {'account_source_name': account.display_name}
+
+ rslt += '</ul>'
+ return rslt
+
+ def _format_transfer_source_log(self, balances_per_account, transfer_move):
+ transfer_format = _("<li>{amount} ({debit_credit}) from <strong>%s</strong> were transferred to <strong>{account_target_name}</strong> by {link}</li>")
+ content = ''
+ for account, balance in balances_per_account.items():
+ if account != self.destination_account_id:
+ content += self._format_strings(transfer_format, transfer_move, balance) % account.display_name
+ return content and '<ul>' + content + '</ul>' or None
+
+ def _format_move_link(self, move):
+ move_link_format = "<a href=# data-oe-model=account.move data-oe-id={move_id}>{move_name}</a>"
+ return move_link_format.format(move_id=move.id, move_name=move.name)
+
+ def _format_strings(self, string, move, amount):
+ return string.format(
+ percent=self.percentage,
+ name=move.name,
+ id=move.id,
+ amount=formatLang(self.env, abs(amount), currency_obj=self.company_id.currency_id),
+ debit_credit=amount < 0 and _('C') or _('D'),
+ link=self._format_move_link(move),
+ date=format_date(self.env, move.date),
+ new_date=self.date and format_date(self.env, self.date) or _('[Not set]'),
+ account_target_name=self.destination_account_id.display_name,
+ )
diff --git a/addons/account/wizard/account_automatic_entry_wizard_views.xml b/addons/account/wizard/account_automatic_entry_wizard_views.xml
new file mode 100644
index 00000000000..6b22bd3fa61
--- /dev/null
+++ b/addons/account/wizard/account_automatic_entry_wizard_views.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <data>
+ <record id="account_automatic_entry_wizard_form" model="ir.ui.view">
+ <field name="name">account.automatic.entry.wizard.form</field>
+ <field name="model">account.automatic.entry.wizard</field>
+ <field name="arch" type="xml">
+ <form>
+ <field name="account_type" invisible="1"/>
+ <field name="company_id" invisible="1"/>
+ <field name="display_currency_helper" invisible="1"/>
+ <div attrs="{'invisible': [('display_currency_helper', '=', False)]}" class="alert alert-info text-center" role="status">
+ The selected destination account is set to use a specific currency. Every entry transferred to it will be converted into this currency, causing
+ the loss of any pre-existing foreign currency amount.
+ </div>
+ <field name="action" invisible="context.get('hide_automatic_options')" widget="radio" options="{'horizontal': true}"/>
+ <group>
+ <group attrs="{'invisible': [('action', '!=', 'change_period')]}">
+ <field name="date" string="Recognition Date"/>
+ <field name="expense_accrual_account" string="Accrued Account"
+ attrs="{'invisible': [('account_type', '!=', 'expense')], 'required': [('account_type', '=', 'expense'), ('action', '=', 'change_period')]}"/>
+ <field name="revenue_accrual_account" string="Accrued Account"
+ attrs="{'invisible': [('account_type', '!=', 'income')], 'required': [('account_type', '=', 'income'), ('action', '=', 'change_period')]}"/>
+ </group>
+ <group attrs="{'invisible': [('action', '!=', 'change_account')]}">
+ <field name="date" string="Transfer Date"/>
+ <field name="destination_account_id" attrs="{'required': [('action', '=', 'change_account')]}" domain="[('company_id', '=', company_id)]"/>
+ </group>
+ <group>
+ <label for="total_amount" string="Adjusting Amount" attrs="{'invisible': [('action', '!=', 'change_period')]}"/>
+ <div attrs="{'invisible': [('action', '!=', 'change_period')]}">
+ <field name="percentage" style="width:40% !important" class="oe_inline" attrs="{'readonly': [('action', '!=', 'change_period')]}"/>%<span class="px-3"></span>(<field name="total_amount" class="oe_inline"/>)
+ </div>
+ <field name="total_amount" readonly="1" attrs="{'invisible': [('action', '=', 'change_period')]}"/>
+ <field name="journal_id"/>
+ </group>
+ </group>
+ <label for="preview_move_data" string="The following Journal Entries will be generated"/>
+ <field name="preview_move_data" widget="grouped_view_widget"/>
+ <footer>
+ <button string="Create Journal Entries" name="do_action" type="object" class="oe_highlight"/>
+ <button string="Cancel" class="btn btn-secondary" special="cancel"/>
+ </footer>
+ </form>
+ </field>
+ </record>
+
+ <record id="account_automatic_entry_wizard_action" model="ir.actions.act_window">
+ <field name="name">Create Automatic Entries for selected Journal Items</field>
+ <field name="res_model">account.automatic.entry.wizard</field>
+ <field name="view_mode">form</field>
+ <field name="target">new</field>
+ </record>
+ </data>
+</odoo>
diff --git a/addons/account/wizard/account_transfer_wizard.py b/addons/account/wizard/account_transfer_wizard.py
deleted file mode 100644
index 709adc51f74..00000000000
--- a/addons/account/wizard/account_transfer_wizard.py
+++ /dev/null
@@ -1,195 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from odoo import api, fields, models, _
-from odoo.exceptions import UserError
-from odoo.tools.misc import format_date, formatLang
-
-from collections import defaultdict
-
-import json
-
-
-class AccountTransferWizard(models.TransientModel):
- _name = 'account.transfer.wizard'
- _description = "Account Transfer Wizard"
-
- move_line_ids = fields.One2many(string="Journal Items", comodel_name='account.move.line', compute="_retrieve_fields_from_context", help="Journal entries to transfer accounts from.")
- destination_account_id = fields.Many2one(string="To", comodel_name='account.account', help="Account to transfer to.")
- journal_id = fields.Many2one(string="Journal", comodel_name='account.journal', help="Journal where to create the transfer entry.")
- date = fields.Date(string="Date", help="Date to be set on the transfer entry.", default=fields.Date.today)
- company_id = fields.Many2one(string="Company", comodel_name='res.company', compute='_retrieve_fields_from_context')
- display_currency_helper = fields.Boolean(string="Currency Conversion Helper", compute='_compute_display_currency_helper', help="Technical field used to indicate whether or not to display the tooltip informing a currency conversion will be performed with the transfer")
- aml_preview_data = fields.Text(string="AML Preview Data", compute='_compute_aml_preview_data', help="Preview JSON data for aml preview widget")
-
- @api.depends_context('active_model')
- def _retrieve_fields_from_context(self):
- if self._context.get('active_model') == 'account.move.line':
- aml = self.env['account.move.line'].browse(self._context.get('active_ids', []))
-
- company = aml.mapped('company_id')
- if len(company) > 1:
- raise UserError(_("You cannot create a transfer for entries belonging to different companies."))
-
- for record in self:
- record.move_line_ids = aml
- record.company_id = company
- else:
- raise UserError(_("Transfer wizard should only be called on account.move.line objects"))
-
- @api.depends('destination_account_id')
- def _compute_display_currency_helper(self):
- for record in self:
- record.display_currency_helper = bool(record.destination_account_id.currency_id)
-
- @api.depends('destination_account_id')
- def _compute_aml_preview_data(self):
- for record in self:
- record.aml_preview_data = json.dumps({
- 'groups_vals': [self.env['account.move']._move_dict_to_preview_vals(self._get_move_to_create_dict(), record.company_id.currency_id)],
- 'options': {
- 'columns': [
- {'field': 'account_id', 'label': _('Account')},
- {'field': 'name', 'label': _('Label')},
- {'field': 'partner_id', 'label': _('Partner')},
- {'field': 'debit', 'label': _('Debit'), 'class': 'text-right text-nowrap'},
- {'field': 'credit', 'label': _('Credit'), 'class': 'text-right text-nowrap'},
- ],
- },
- })
-
- def _get_move_to_create_dict(self):
- line_vals = []
-
- # Group data from selected move lines
- counterpart_balances = defaultdict(lambda: defaultdict(lambda: 0))
- grouped_source_lines = defaultdict(lambda: self.env['account.move.line'])
-
- for line in self.move_line_ids.filtered(lambda x: x.account_id != self.destination_account_id):
- counterpart_currency = line.currency_id
- counterpart_amount_currency = line.amount_currency
-
- if self.destination_account_id.currency_id and self.destination_account_id.currency_id != self.company_id.currency_id:
- counterpart_currency = self.destination_account_id.currency_id
- counterpart_amount_currency = self.company_id.currency_id._convert(line.balance, self.destination_account_id.currency_id, self.company_id, line.date)
-
- if counterpart_currency:
- counterpart_balances[(line.partner_id, counterpart_currency)]['amount_currency'] += counterpart_amount_currency
-
- counterpart_balances[(line.partner_id, counterpart_currency)]['balance'] += line.balance
- grouped_source_lines[(line.partner_id, line.currency_id, line.account_id)] += line
-
- # Generate counterpart lines' vals
- for (counterpart_partner, counterpart_currency), counterpart_vals in counterpart_balances.items():
- source_accounts = self.move_line_ids.mapped('account_id')
- counterpart_label = len(source_accounts) == 1 and _("Transfer from %s", source_accounts.display_name) or _("Transfer counterpart")
-
- if not self.company_id.currency_id.is_zero(counterpart_vals['balance']) or (counterpart_currency and not counterpart_currency.is_zero(counterpart_vals['amount_currency'])):
- line_vals.append({
- 'name': counterpart_label,
- 'debit': counterpart_vals['balance'] > 0 and self.company_id.currency_id.round(counterpart_vals['balance']) or 0,
- 'credit': counterpart_vals['balance'] < 0 and self.company_id.currency_id.round(-counterpart_vals['balance']) or 0,
- 'account_id': self.destination_account_id.id,
- 'partner_id': counterpart_partner.id or None,
- 'amount_currency': counterpart_currency and counterpart_currency.round((counterpart_vals['balance'] < 0 and -1 or 1) * abs(counterpart_vals['amount_currency'])) or 0,
- 'currency_id': counterpart_currency.id or None,
- })
-
- # Generate transfer lines' vals
- for (partner, currency, account), lines in grouped_source_lines.items():
- account_balance = sum(line.balance for line in lines)
- if not (currency or self.company_id.currency_id).is_zero(account_balance):
- account_amount_currency = currency and currency.round(sum(line.amount_currency for line in lines)) or 0
- line_vals.append({
- 'name': _('Transfer to %s') % (self.destination_account_id.display_name or _('Destination Account')),
- 'debit': account_balance < 0 and self.company_id.currency_id.round(-account_balance) or 0,
- 'credit': account_balance > 0 and self.company_id.currency_id.round(account_balance) or 0,
- 'account_id': account.id,
- 'partner_id': partner.id or None,
- 'currency_id': currency.id or None,
- 'amount_currency': (account_balance > 0 and -1 or 1) * abs(account_amount_currency),
- })
-
- return {
- 'journal_id': self.journal_id.id,
- 'date': self.date,
- 'ref': self.destination_account_id.display_name and _("Transfer entry to %s", self.destination_account_id.display_name) or '',
- 'line_ids': [(0, 0, line) for line in line_vals],
- }
-
- def button_transfer(self):
- new_move = self.env['account.move'].create(self._get_move_to_create_dict())
- new_move.post()
-
- # Group lines
- grouped_lines = defaultdict(lambda: self.env['account.move.line'])
- destination_lines = self.move_line_ids.filtered(lambda x: x.account_id == self.destination_account_id)
- for line in self.move_line_ids - destination_lines:
- grouped_lines[(line.partner_id, line.currency_id, line.account_id)] += line
-
- # Reconcile
- for (partner, currency, account), lines in grouped_lines.items():
- if account.reconcile:
- to_reconcile = lines + new_move.line_ids.filtered(lambda x: x.account_id == account and x.partner_id == partner and x.currency_id == currency)
- to_reconcile.reconcile()
-
- if destination_lines and self.destination_account_id.reconcile:
- to_reconcile = destination_lines + new_move.line_ids.filtered(lambda x: x.account_id == self.destination_account_id and x.partner_id == partner and x.currency_id == currency)
- to_reconcile.reconcile()
-
- # Log the operation on source moves
- acc_transfer_per_move = defaultdict(lambda: defaultdict(lambda: 0)) # dict(move, dict(account, balance))
- for line in self.move_line_ids:
- acc_transfer_per_move[line.move_id][line.account_id] += line.balance
-
- for move, balances_per_account in acc_transfer_per_move.items():
- message_to_log = self._format_source_log(balances_per_account, new_move)
- if message_to_log:
- move.message_post(body=message_to_log)
-
- # Log on target move as well
- new_move.message_post(body=self._format_new_move_log(acc_transfer_per_move))
-
- return {
- 'name': _("Transfer"),
- 'type': 'ir.actions.act_window',
- 'view_type': 'form',
- 'view_mode': 'form',
- 'res_model': 'account.move',
- 'res_id': new_move.id,
- }
-
- def _format_new_move_log(self, acc_transfer_per_move):
- format = _("%(amount)s %(currency_symbol)s (%(debit_credit)s) from %(link)s, <strong>%(account_source_name)s</strong>")
- rslt = _("This entry transfers the following amounts to <strong>%(destination)s</strong> <ul>") % {'destination': self.destination_account_id.display_name}
- for move, balances_per_account in acc_transfer_per_move.items():
- for account, balance in balances_per_account.items():
- if account != self.destination_account_id: # Otherwise, logging it here is confusing for the user
- rslt += ('<li>' + format +'</li>') % {
- 'amount': abs(balance),
- 'debit_credit': balance < 0 and _('C') or _('D'),
- 'currency_symbol': self.company_id.currency_id.symbol,
- 'account_source_name': account.display_name,
- 'link': self._format_move_link(move),
- }
-
- rslt += '</ul>'
- return rslt
-
- def _format_source_log(self, balances_per_account, transfer_move):
- transfer_format = _("%(amount)s %(currency_symbol)s (%(debit_credit)s) from <strong>%(account_source_name)s</strong> were transferred to <strong>%(account_target_name)s</strong> by %(link)s")
- content = ''
- for account, balance in balances_per_account.items():
- if account != self.destination_account_id:
- content += ('<li>' + transfer_format + '</li>') % {
- 'amount': abs(balance),
- 'debit_credit': balance < 0 and _('C') or _('D'),
- 'currency_symbol': self.company_id.currency_id.symbol,
- 'account_source_name': account.display_name,
- 'account_target_name': self.destination_account_id.display_name,
- 'link': self._format_move_link(transfer_move),
- }
- return content and '<ul>' + content + '</ul>' or None
-
- def _format_move_link(self, move):
- move_link_format = "<a href=# data-oe-model=account.move data-oe-id=%(move_id)s>%(move_name)s</a>"
- return move_link_format % {'move_id': move.id, 'move_name': move.name}
diff --git a/addons/account/wizard/account_transfer_wizard.xml b/addons/account/wizard/account_transfer_wizard.xml
deleted file mode 100644
index 052bf8cdc9b..00000000000
--- a/addons/account/wizard/account_transfer_wizard.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<odoo>
- <data>
-
- <record id="account_move_transfer_accounts_wizard" model="ir.ui.view">
- <field name="name">account.transfer.wizard.form</field>
- <field name="model">account.transfer.wizard</field>
- <field name="arch" type="xml">
- <form>
-
- <field name="display_currency_helper" invisible="1"/>
- <div attrs="{'invisible': [('display_currency_helper', '=', False)]}" class="alert alert-info text-center" role="status">
- The selected destination account is set to use a specific currency. Every entry transferred to it will be converted into this currency, causing
- the loss of any pre-existing foreign currency amount.
- </div>
-
- <group>
- <group>
- <field name="company_id" invisible="1"/>
- <field name="destination_account_id" required="1" domain="[('company_id', '=', company_id)]"/>
- <field name="date" required="1"/>
- </group>
- <group>
- <field name="journal_id" required="1" domain="[('type', '=', 'general'), ('company_id', '=', company_id)]"/>
- </group>
- </group>
-
- <p><strong>The following journal entry will be generated</strong></p>
-
- <field name="aml_preview_data" widget="grouped_view_widget"/>
-
- <footer>
- <button string="Transfer Entries" name="button_transfer" type="object" class="oe_highlight"/>
- <button string="Cancel" special="cancel"/>
- </footer>
- </form>
- </field>
- </record>
-
- </data>
-</odoo>