[IMP] account: Allow spreading amount over several dates from Cut-Off wizard.
closes odoo/odoo#59246 Task: 2347881 Related: odoo/upgrade#1989 Signed-off-by: Quentin De Paoli (qdp) <qdp@openerp.com>
This commit is contained in:
@@ -126,6 +126,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_automatic_entry_wizard,access.account.automatic.entry.wizard,model_account_automatic_entry_wizard,account.group_account_user,1,1,1,0
|
||||
access_account_automatic_entry_wizard_line,access.account.automatic.entry.wizard.line,model_account_automatic_entry_wizard_line,account.group_account_user,1,1,1,1
|
||||
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_manager,1,1,1,0
|
||||
access_validate_account_move,access.validate.account.move,model_validate_account_move,account.group_account_invoice,1,1,1,0
|
||||
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
from . import test_account_move_reconcile
|
||||
from . import test_account_move_payments_widget
|
||||
from . import test_account_automatic_entry_wizard_period
|
||||
from . import test_account_move_out_invoice
|
||||
from . import test_account_move_out_refund
|
||||
from . import test_account_move_in_invoice
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
from odoo import fields
|
||||
from odoo.tests import tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestAccountAutomaticEntryWizardPeriod(AccountTestInvoicingCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
|
||||
cls.invoice = cls.env['account.move'].create({
|
||||
'move_type': 'out_invoice',
|
||||
'date': '2017-01-01',
|
||||
'partner_id': cls.partner_a.id,
|
||||
'invoice_date': '2017-01-01',
|
||||
'invoice_line_ids': [
|
||||
(0, 0, {
|
||||
'name': "line1",
|
||||
'quantity': 1,
|
||||
'price_unit': 1000.0,
|
||||
'account_id': cls.company_data['default_account_revenue'].id,
|
||||
}),
|
||||
(0, 0, {
|
||||
'name': "line2",
|
||||
'quantity': 1,
|
||||
'price_unit': 200.0,
|
||||
'account_id': cls.company_data['default_account_revenue'].id,
|
||||
}),
|
||||
]
|
||||
})
|
||||
cls.invoice.action_post()
|
||||
|
||||
cls.accrual_account = cls.env['account.account'].create({
|
||||
'name': "test_accrual_account",
|
||||
'code': "TEST",
|
||||
'user_type_id': cls.env.ref('account.data_account_type_revenue').id,
|
||||
'reconcile': True,
|
||||
'company_id': cls.company_data['company'].id,
|
||||
})
|
||||
cls.income_account = cls.company_data['default_account_revenue']
|
||||
|
||||
cls.Wizard = cls.env['account.automatic.entry.wizard']\
|
||||
.with_context(active_model='account.move.line', active_ids=cls.invoice.invoice_line_ids.ids)
|
||||
|
||||
def test_chg_period_100_percent_no_time_division(self):
|
||||
''' Test smoothing the invoice in the period without the time division. '''
|
||||
to_date = lambda string: fields.Date.from_string(string)
|
||||
|
||||
wizard = self.Wizard.create({
|
||||
'action': 'change_period',
|
||||
'journal_id': self.company_data['default_journal_misc'].id,
|
||||
'revenue_accrual_account': self.accrual_account.id,
|
||||
'chg_period_date_from': '2017-06-01',
|
||||
'chg_period_date_to': '2017-12-31', # Large period to check the rounding issues.
|
||||
'chg_period_percentage': 100.0,
|
||||
'chg_period_time_division': False,
|
||||
})
|
||||
wizard_res = wizard.do_action()
|
||||
|
||||
lines = self.env['account.move'].browse(wizard_res['domain'][0][2]).line_ids.sorted(lambda line: (line.date, -abs(line.balance), -line.balance))
|
||||
dest_lines = lines.filtered(lambda line: line.date == self.invoice.date)
|
||||
accrual_lines = lines.filtered(lambda line: line.date != self.invoice.date)
|
||||
self.assertRecordValues(dest_lines, [
|
||||
{'debit': 1000.0, 'credit': 0.0, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-01-01')},
|
||||
{'debit': 0.0, 'credit': 1000.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-01-01')},
|
||||
{'debit': 200.0, 'credit': 0.0, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-01-01')},
|
||||
{'debit': 0.0, 'credit': 200.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-01-01')},
|
||||
])
|
||||
self.assertRecordValues(accrual_lines, [
|
||||
{'debit': 142.86, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-06-30')},
|
||||
{'debit': 0.0, 'credit': 142.86, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-06-30')},
|
||||
{'debit': 28.57, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-06-30')},
|
||||
{'debit': 0.0, 'credit': 28.57, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-06-30')},
|
||||
{'debit': 142.86, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-07-31')},
|
||||
{'debit': 0.0, 'credit': 142.86, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-07-31')},
|
||||
{'debit': 28.57, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-07-31')},
|
||||
{'debit': 0.0, 'credit': 28.57, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-07-31')},
|
||||
{'debit': 142.86, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-08-31')},
|
||||
{'debit': 0.0, 'credit': 142.86, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-08-31')},
|
||||
{'debit': 28.57, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-08-31')},
|
||||
{'debit': 0.0, 'credit': 28.57, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-08-31')},
|
||||
{'debit': 142.86, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-09-30')},
|
||||
{'debit': 0.0, 'credit': 142.86, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-09-30')},
|
||||
{'debit': 28.57, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-09-30')},
|
||||
{'debit': 0.0, 'credit': 28.57, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-09-30')},
|
||||
{'debit': 142.86, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-10-31')},
|
||||
{'debit': 0.0, 'credit': 142.86, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-10-31')},
|
||||
{'debit': 28.57, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-10-31')},
|
||||
{'debit': 0.0, 'credit': 28.57, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-10-31')},
|
||||
{'debit': 142.86, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-11-30')},
|
||||
{'debit': 0.0, 'credit': 142.86, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-11-30')},
|
||||
{'debit': 28.57, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-11-30')},
|
||||
{'debit': 0.0, 'credit': 28.57, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-11-30')},
|
||||
{'debit': 142.84, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-12-31')},
|
||||
{'debit': 0.0, 'credit': 142.84, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-12-31')},
|
||||
{'debit': 28.58, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-12-31')},
|
||||
{'debit': 0.0, 'credit': 28.58, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-12-31')},
|
||||
])
|
||||
|
||||
def test_chg_period_100_percent_time_division(self):
|
||||
''' Same as test_chg_period_100_percent_no_time_division using the time division meaning the smoothing is made
|
||||
to different months considering their number of days.
|
||||
'''
|
||||
to_date = lambda string: fields.Date.from_string(string)
|
||||
|
||||
wizard = self.Wizard.create({
|
||||
'action': 'change_period',
|
||||
'journal_id': self.company_data['default_journal_misc'].id,
|
||||
'revenue_accrual_account': self.accrual_account.id,
|
||||
'chg_period_date_from': '2017-06-01',
|
||||
'chg_period_date_to': '2017-12-31', # Large period to check the rounding issues.
|
||||
'chg_period_percentage': 100.0,
|
||||
'chg_period_time_division': True,
|
||||
})
|
||||
wizard_res = wizard.do_action()
|
||||
|
||||
lines = self.env['account.move'].browse(wizard_res['domain'][0][2]).line_ids.sorted(lambda line: (line.date, -abs(line.balance), -line.balance))
|
||||
dest_lines = lines.filtered(lambda line: line.date == self.invoice.date)
|
||||
accrual_lines = lines.filtered(lambda line: line.date != self.invoice.date)
|
||||
self.assertRecordValues(dest_lines, [
|
||||
{'debit': 1000.0, 'credit': 0.0, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-01-01')},
|
||||
{'debit': 0.0, 'credit': 1000.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-01-01')},
|
||||
{'debit': 200.0, 'credit': 0.0, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-01-01')},
|
||||
{'debit': 0.0, 'credit': 200.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-01-01')},
|
||||
])
|
||||
self.assertRecordValues(accrual_lines, [
|
||||
{'debit': 140.1, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-06-30')},
|
||||
{'debit': 0.0, 'credit': 140.1, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-06-30')},
|
||||
{'debit': 28.02, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-06-30')},
|
||||
{'debit': 0.0, 'credit': 28.02, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-06-30')},
|
||||
{'debit': 144.93, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-07-31')},
|
||||
{'debit': 0.0, 'credit': 144.93, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-07-31')},
|
||||
{'debit': 28.99, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-07-31')},
|
||||
{'debit': 0.0, 'credit': 28.99, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-07-31')},
|
||||
{'debit': 144.93, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-08-31')},
|
||||
{'debit': 0.0, 'credit': 144.93, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-08-31')},
|
||||
{'debit': 28.99, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-08-31')},
|
||||
{'debit': 0.0, 'credit': 28.99, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-08-31')},
|
||||
{'debit': 140.1, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-09-30')},
|
||||
{'debit': 0.0, 'credit': 140.1, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-09-30')},
|
||||
{'debit': 28.02, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-09-30')},
|
||||
{'debit': 0.0, 'credit': 28.02, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-09-30')},
|
||||
{'debit': 144.93, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-10-31')},
|
||||
{'debit': 0.0, 'credit': 144.93, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-10-31')},
|
||||
{'debit': 28.99, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-10-31')},
|
||||
{'debit': 0.0, 'credit': 28.99, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-10-31')},
|
||||
{'debit': 140.1, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-11-30')},
|
||||
{'debit': 0.0, 'credit': 140.1, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-11-30')},
|
||||
{'debit': 28.02, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-11-30')},
|
||||
{'debit': 0.0, 'credit': 28.02, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-11-30')},
|
||||
{'debit': 144.91, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-12-31')},
|
||||
{'debit': 0.0, 'credit': 144.91, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-12-31')},
|
||||
{'debit': 28.97, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-12-31')},
|
||||
{'debit': 0.0, 'credit': 28.97, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-12-31')},
|
||||
])
|
||||
|
||||
def test_chg_period_manual_periods(self):
|
||||
''' Test the smoothing by manual lines. '''
|
||||
to_date = lambda string: fields.Date.from_string(string)
|
||||
|
||||
wizard = self.Wizard.create({
|
||||
'action': 'change_period',
|
||||
'journal_id': self.company_data['default_journal_misc'].id,
|
||||
'revenue_accrual_account': self.accrual_account.id,
|
||||
'chg_period_line_ids': [
|
||||
(0, 0, {'date': '2017-06-01', 'balance': 120.0}),
|
||||
(0, 0, {'date': '2017-07-01', 'balance': 300.0}),
|
||||
(0, 0, {'date': '2017-08-01', 'balance': 700.0}),
|
||||
(0, 0, {'date': '2017-09-01', 'balance': 80.0}),
|
||||
],
|
||||
})
|
||||
wizard_res = wizard.do_action()
|
||||
|
||||
|
||||
lines = self.env['account.move'].browse(wizard_res['domain'][0][2]).line_ids.sorted(lambda line: (line.date, -abs(line.balance), -line.balance))
|
||||
dest_lines = lines.filtered(lambda line: line.date == self.invoice.date)
|
||||
accrual_lines = lines.filtered(lambda line: line.date != self.invoice.date)
|
||||
self.assertRecordValues(dest_lines, [
|
||||
{'debit': 1000.0, 'credit': 0.0, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-01-01')},
|
||||
{'debit': 0.0, 'credit': 1000.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-01-01')},
|
||||
{'debit': 200.0, 'credit': 0.0, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-01-01')},
|
||||
{'debit': 0.0, 'credit': 200.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-01-01')},
|
||||
])
|
||||
self.assertRecordValues(accrual_lines, [
|
||||
{'debit': 100.0, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-06-01')},
|
||||
{'debit': 0.0, 'credit': 100.0, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-06-01')},
|
||||
{'debit': 20.0, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-06-01')},
|
||||
{'debit': 0.0, 'credit': 20.0, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-06-01')},
|
||||
{'debit': 250.0, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-07-01')},
|
||||
{'debit': 0.0, 'credit': 250.0, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-07-01')},
|
||||
{'debit': 50.0, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-07-01')},
|
||||
{'debit': 0.0, 'credit': 50.0, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-07-01')},
|
||||
{'debit': 583.33, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-08-01')},
|
||||
{'debit': 0.0, 'credit': 583.33, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-08-01')},
|
||||
{'debit': 116.67, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-08-01')},
|
||||
{'debit': 0.0, 'credit': 116.67, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-08-01')},
|
||||
{'debit': 66.67, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-09-01')},
|
||||
{'debit': 0.0, 'credit': 66.67, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-09-01')},
|
||||
{'debit': 13.33, 'credit': 0.0, 'account_id': self.accrual_account.id, 'reconciled': True, 'date': to_date('2017-09-01')},
|
||||
{'debit': 0.0, 'credit': 13.33, 'account_id': self.income_account.id, 'reconciled': False, 'date': to_date('2017-09-01')},
|
||||
])
|
||||
@@ -1656,108 +1656,3 @@ class TestAccountMoveInInvoiceOnchanges(AccountTestInvoicingCommon):
|
||||
'amount_total' : self.move_vals['amount_total'],
|
||||
'amount_untaxed' : self.move_vals['amount_untaxed'],
|
||||
})
|
||||
|
||||
def test_in_invoice_change_period_accrual_1(self):
|
||||
move = self.env['account.move'].create({
|
||||
'move_type': 'in_invoice',
|
||||
'date': '2017-01-01',
|
||||
'partner_id': self.partner_a.id,
|
||||
'invoice_date': fields.Date.from_string('2017-01-01'),
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'invoice_payment_term_id': self.pay_terms_a.id,
|
||||
'invoice_line_ids': [
|
||||
(0, None, {
|
||||
'name': self.product_line_vals_1['name'],
|
||||
'product_id': self.product_line_vals_1['product_id'],
|
||||
'product_uom_id': self.product_line_vals_1['product_uom_id'],
|
||||
'quantity': self.product_line_vals_1['quantity'],
|
||||
'price_unit': self.product_line_vals_1['price_unit'],
|
||||
'tax_ids': self.product_line_vals_1['tax_ids'],
|
||||
}),
|
||||
(0, None, {
|
||||
'name': self.product_line_vals_2['name'],
|
||||
'product_id': self.product_line_vals_2['product_id'],
|
||||
'product_uom_id': self.product_line_vals_2['product_uom_id'],
|
||||
'quantity': self.product_line_vals_2['quantity'],
|
||||
'price_unit': self.product_line_vals_2['price_unit'],
|
||||
'tax_ids': self.product_line_vals_2['tax_ids'],
|
||||
}),
|
||||
]
|
||||
})
|
||||
move.action_post()
|
||||
|
||||
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,
|
||||
'expense_accrual_account': self.env['account.account'].create({
|
||||
'name': 'Accrual Expense Account',
|
||||
'code': '234567',
|
||||
'user_type_id': self.env.ref('account.data_account_type_expenses').id,
|
||||
'reconcile': True,
|
||||
}).id,
|
||||
'revenue_accrual_account': self.env['account.account'].create({
|
||||
'name': 'Accrual Revenue Account',
|
||||
'code': '765432',
|
||||
'user_type_id': self.env.ref('account.data_account_type_expenses').id,
|
||||
'reconcile': True,
|
||||
}).id,
|
||||
})
|
||||
wizard_res = wizard.do_action()
|
||||
|
||||
self.assertInvoiceValues(move, [
|
||||
{
|
||||
**self.product_line_vals_1,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': 800.0,
|
||||
'debit': 400.0,
|
||||
'credit': 0.0,
|
||||
},
|
||||
{
|
||||
**self.product_line_vals_2,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': 160.0,
|
||||
'debit': 80.0,
|
||||
'credit': 0.0,
|
||||
},
|
||||
{
|
||||
**self.tax_line_vals_1,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': 144.0,
|
||||
'debit': 72.0,
|
||||
'credit': 0.0,
|
||||
},
|
||||
{
|
||||
**self.tax_line_vals_2,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': 24.0,
|
||||
'debit': 12.0,
|
||||
'credit': 0.0,
|
||||
},
|
||||
{
|
||||
**self.term_line_vals_1,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': -1128.0,
|
||||
'debit': 0.0,
|
||||
'credit': 564.0,
|
||||
'date_maturity': fields.Date.from_string('2017-01-01'),
|
||||
},
|
||||
], {
|
||||
**self.move_vals,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'date': fields.Date.from_string('2017-01-01'),
|
||||
})
|
||||
|
||||
accrual_lines = self.env['account.move'].browse(wizard_res['domain'][0][2]).line_ids.sorted('date')
|
||||
self.assertRecordValues(accrual_lines, [
|
||||
{'amount_currency': -480.0, 'debit': 0.0, 'credit': 240.0, 'account_id': self.product_line_vals_1['account_id'], 'reconciled': False},
|
||||
{'amount_currency': 480.0, 'debit': 240.0, 'credit': 0.0, 'account_id': wizard.expense_accrual_account.id, 'reconciled': True},
|
||||
{'amount_currency': -96.0, 'debit': 0.0, 'credit': 48.0, 'account_id': self.product_line_vals_2['account_id'], 'reconciled': False},
|
||||
{'amount_currency': 96.0, 'debit': 48.0, 'credit': 0.0, 'account_id': wizard.expense_accrual_account.id, 'reconciled': True},
|
||||
{'amount_currency': 480.0, 'debit': 240.0, 'credit': 0.0, 'account_id': self.product_line_vals_1['account_id'], 'reconciled': False},
|
||||
{'amount_currency': -480.0, 'debit': 0.0, 'credit': 240.0, 'account_id': wizard.expense_accrual_account.id, 'reconciled': True},
|
||||
{'amount_currency': 96.0, 'debit': 48.0, 'credit': 0.0, 'account_id': self.product_line_vals_2['account_id'], 'reconciled': False},
|
||||
{'amount_currency': -96.0, 'debit': 0.0, 'credit': 48.0, 'account_id': wizard.expense_accrual_account.id, 'reconciled': True},
|
||||
])
|
||||
|
||||
@@ -2424,113 +2424,6 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
|
||||
'amount_untaxed' : self.move_vals['amount_untaxed'],
|
||||
})
|
||||
|
||||
def test_out_invoice_change_period_accrual_1(self):
|
||||
move = self.env['account.move'].create({
|
||||
'move_type': 'out_invoice',
|
||||
'date': '2017-01-01',
|
||||
'partner_id': self.partner_a.id,
|
||||
'invoice_date': fields.Date.from_string('2017-01-01'),
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'invoice_payment_term_id': self.pay_terms_a.id,
|
||||
'invoice_line_ids': [
|
||||
(0, None, {
|
||||
'name': self.product_line_vals_1['name'],
|
||||
'product_id': self.product_line_vals_1['product_id'],
|
||||
'product_uom_id': self.product_line_vals_1['product_uom_id'],
|
||||
'quantity': self.product_line_vals_1['quantity'],
|
||||
'price_unit': self.product_line_vals_1['price_unit'],
|
||||
'tax_ids': self.product_line_vals_1['tax_ids'],
|
||||
}),
|
||||
(0, None, {
|
||||
'name': self.product_line_vals_2['name'],
|
||||
'product_id': self.product_line_vals_2['product_id'],
|
||||
'product_uom_id': self.product_line_vals_2['product_uom_id'],
|
||||
'quantity': self.product_line_vals_2['quantity'],
|
||||
'price_unit': self.product_line_vals_2['price_unit'],
|
||||
'tax_ids': self.product_line_vals_2['tax_ids'],
|
||||
}),
|
||||
]
|
||||
})
|
||||
move.action_post()
|
||||
|
||||
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,
|
||||
'expense_accrual_account': self.env['account.account'].create({
|
||||
'name': 'Accrual Expense Account',
|
||||
'code': '234567',
|
||||
'user_type_id': self.env.ref('account.data_account_type_expenses').id,
|
||||
'reconcile': True,
|
||||
}).id,
|
||||
'revenue_accrual_account': self.env['account.account'].create({
|
||||
'name': 'Accrual Revenue Account',
|
||||
'code': '765432',
|
||||
'user_type_id': self.env.ref('account.data_account_type_expenses').id,
|
||||
'reconcile': True,
|
||||
}).id,
|
||||
})
|
||||
wizard_res = wizard.do_action()
|
||||
|
||||
self.assertInvoiceValues(move, [
|
||||
{
|
||||
**self.product_line_vals_1,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': -1000.0,
|
||||
'debit': 0.0,
|
||||
'credit': 500.0,
|
||||
},
|
||||
{
|
||||
**self.product_line_vals_2,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': -200.0,
|
||||
'debit': 0.0,
|
||||
'credit': 100.0,
|
||||
},
|
||||
{
|
||||
**self.tax_line_vals_1,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': -180.0,
|
||||
'debit': 0.0,
|
||||
'credit': 90.0,
|
||||
},
|
||||
{
|
||||
**self.tax_line_vals_2,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': -30.0,
|
||||
'debit': 0.0,
|
||||
'credit': 15.0,
|
||||
},
|
||||
{
|
||||
**self.term_line_vals_1,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'name': 'INV/2017/01/0001',
|
||||
'amount_currency': 1410.0,
|
||||
'debit': 705.0,
|
||||
'credit': 0.0,
|
||||
'date_maturity': fields.Date.from_string('2017-01-01'),
|
||||
},
|
||||
], {
|
||||
**self.move_vals,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'date': fields.Date.from_string('2017-01-01'),
|
||||
'payment_reference': 'INV/2017/01/0001',
|
||||
})
|
||||
|
||||
accrual_lines = self.env['account.move'].browse(wizard_res['domain'][0][2]).line_ids.sorted('date')
|
||||
self.assertRecordValues(accrual_lines, [
|
||||
{'amount_currency': 600.0, 'debit': 300.0, 'credit': 0.0, 'account_id': self.product_line_vals_1['account_id'], 'reconciled': False},
|
||||
{'amount_currency': -600.0, 'debit': 0.0, 'credit': 300.0, 'account_id': wizard.revenue_accrual_account.id, 'reconciled': True},
|
||||
{'amount_currency': 120.0, 'debit': 60.0, 'credit': 0.0, 'account_id': self.product_line_vals_2['account_id'], 'reconciled': False},
|
||||
{'amount_currency': -120.0, 'debit': 0.0, 'credit': 60.0, 'account_id': wizard.revenue_accrual_account.id, 'reconciled': True},
|
||||
{'amount_currency': -600.0, 'debit': 0.0, 'credit': 300.0, 'account_id': self.product_line_vals_1['account_id'], 'reconciled': False},
|
||||
{'amount_currency': 600.0, 'debit': 300.0, 'credit': 0.0, 'account_id': wizard.revenue_accrual_account.id, 'reconciled': True},
|
||||
{'amount_currency': -120.0, 'debit': 0.0, 'credit': 60.0, 'account_id': self.product_line_vals_2['account_id'], 'reconciled': False},
|
||||
{'amount_currency': 120.0, 'debit': 60.0, 'credit': 0.0, 'account_id': wizard.revenue_accrual_account.id, 'reconciled': True},
|
||||
])
|
||||
|
||||
def test_out_invoice_filter_zero_balance_lines(self):
|
||||
zero_balance_payment_term = self.env['account.payment.term'].create({
|
||||
'name': 'zero_balance_payment_term',
|
||||
|
||||
@@ -1006,7 +1006,7 @@
|
||||
<button name="action_automatic_entry"
|
||||
type="object"
|
||||
icon="fa-calendar"
|
||||
string="Cut-Off"
|
||||
string="Re-allocate"
|
||||
aria-label="Change Period"
|
||||
class="float-right"
|
||||
attrs="{'invisible': [('account_internal_group', 'not in', ('income', 'expense'))], 'column_invisible': [('parent.move_type', '=', 'entry')]}"
|
||||
|
||||
@@ -5,8 +5,10 @@ from odoo.tools.misc import format_date, formatLang
|
||||
|
||||
from collections import defaultdict
|
||||
from itertools import groupby
|
||||
from dateutil.relativedelta import relativedelta
|
||||
import json
|
||||
|
||||
|
||||
class AutomaticEntryWizard(models.TransientModel):
|
||||
_name = 'account.automatic.entry.wizard'
|
||||
_description = 'Create Automatic Entries'
|
||||
@@ -16,18 +18,21 @@ class AutomaticEntryWizard(models.TransientModel):
|
||||
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=lambda self: fields.Date.context_today(self))
|
||||
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.")
|
||||
total_amount = fields.Monetary(
|
||||
compute='_compute_from_move_line_ids',
|
||||
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)
|
||||
account_type = fields.Selection(
|
||||
selection=[('income', 'Revenue'), ('expense', 'Expense')],
|
||||
compute='_compute_from_move_line_ids')
|
||||
expense_accrual_account = fields.Many2one('account.account', readonly=False,
|
||||
domain="[('company_id', '=', company_id),"
|
||||
"('internal_type', 'not in', ('receivable', 'payable')),"
|
||||
@@ -38,38 +43,105 @@ class AutomaticEntryWizard(models.TransientModel):
|
||||
"('internal_type', 'not in', ('receivable', 'payable')),"
|
||||
"('is_off_balance', '=', False)]",
|
||||
related="company_id.revenue_accrual_account_id")
|
||||
chg_period_date_from = fields.Date(
|
||||
string="Date from",
|
||||
default=lambda self: fields.Date.context_today(self),
|
||||
help="Date at which the smoothing of the balance will start.")
|
||||
chg_period_date_to = fields.Date(
|
||||
string="Date to",
|
||||
default=lambda self: fields.Date.context_today(self),
|
||||
help="Date at which the smoothing of the balance will end.")
|
||||
chg_period_time_division = fields.Boolean(
|
||||
string="Time Division",
|
||||
help="Allocate the balance to different periods considering their number of days.")
|
||||
chg_period_percentage = fields.Float(string="Percentage", default=100.0)
|
||||
chg_period_line_ids = fields.One2many(
|
||||
comodel_name='account.automatic.entry.wizard.line',
|
||||
inverse_name='wizard_id',
|
||||
string='Lines',
|
||||
store=True, readonly=False,
|
||||
compute='_compute_chg_period_line_ids')
|
||||
chg_period_warning_message = fields.Text(
|
||||
string="Warning",
|
||||
compute='_compute_chg_period_warning_message')
|
||||
|
||||
# change account
|
||||
date = fields.Date(string="Transfer Date", default=lambda self: fields.Date.context_today(self))
|
||||
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 currency conversion tooltip. The tooltip informs 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':
|
||||
raise UserError(_("Percentage can only be set for Change Period method"))
|
||||
def _get_total_balance(self):
|
||||
self.ensure_one()
|
||||
return sum(line.amount_residual if line.account_id.reconcile else line.balance for line in self.move_line_ids)
|
||||
|
||||
@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('chg_period_date_from', 'chg_period_date_to', 'chg_period_time_division', 'chg_period_percentage')
|
||||
def _compute_chg_period_line_ids(self):
|
||||
''' Create the lines representing the way the balance will be smoothed per period. '''
|
||||
for wizard in self:
|
||||
one2many_commands = [(5, 0)]
|
||||
|
||||
@api.depends('total_amount', 'move_line_ids')
|
||||
def _compute_percentage(self):
|
||||
for record in self:
|
||||
total = (sum(record.move_line_ids.mapped('balance')) or record.total_amount)
|
||||
if total != 0:
|
||||
record.percentage = (record.total_amount / total) * 100
|
||||
date_from = wizard.chg_period_date_from
|
||||
date_to = wizard.chg_period_date_to
|
||||
percentage = (wizard.chg_period_percentage or 0.0) / 100.0
|
||||
|
||||
if date_from and date_to and percentage:
|
||||
total_balance = wizard.company_currency_id.round(wizard._get_total_balance() * percentage)
|
||||
total_days = 0.0
|
||||
date_distribution = []
|
||||
while date_from <= date_to:
|
||||
next_month_date = (date_from + relativedelta(months=1)).replace(day=1)
|
||||
period_date = next_month_date - relativedelta(days=1)
|
||||
period_days = (period_date - date_from).days
|
||||
date_distribution.append((period_date, period_days))
|
||||
date_from = next_month_date
|
||||
total_days += period_days
|
||||
|
||||
total_reported_balance = 0.0
|
||||
for period_date, nb_days in date_distribution:
|
||||
if wizard.chg_period_time_division:
|
||||
period_balance = wizard.company_currency_id.round(total_balance * nb_days / total_days)
|
||||
else:
|
||||
period_balance = wizard.company_currency_id.round(total_balance / len(date_distribution))
|
||||
one2many_commands.append((0, 0, {
|
||||
'wizard_id': wizard.id,
|
||||
'date': period_date,
|
||||
'balance': period_balance,
|
||||
}))
|
||||
total_reported_balance += period_balance
|
||||
|
||||
# When the percentage is 100%, ensure the full balance is dispatched into the periods.
|
||||
# To avoid a huge gap in the last line, we ensure the rounding error is also smoothed.
|
||||
# For example: smoothing 100 into 12 months:
|
||||
# 12 lines of 8.33 are generated at this point. Since 8.33 * 12 = 99.96, the error of 0.04 will be
|
||||
# smoothed by adding 0.01 to the last 4 periods.
|
||||
if wizard.chg_period_percentage == 100.0:
|
||||
total_rounding_error = wizard.company_currency_id.round(total_balance - total_reported_balance)
|
||||
nber_rounding_steps = int(abs(total_rounding_error / wizard.company_currency_id.rounding))
|
||||
rounding_error = round(
|
||||
nber_rounding_steps and total_rounding_error / nber_rounding_steps or 0.0,
|
||||
wizard.company_currency_id.decimal_places,
|
||||
)
|
||||
nber_periods = len(one2many_commands) - 1
|
||||
if len(one2many_commands) > nber_rounding_steps:
|
||||
for i in range(nber_rounding_steps):
|
||||
one2many_commands[nber_periods - i][2]['balance'] += rounding_error
|
||||
|
||||
wizard.chg_period_line_ids = one2many_commands
|
||||
|
||||
@api.depends('chg_period_date_from', 'chg_period_date_to')
|
||||
def _compute_chg_period_warning_message(self):
|
||||
for wizard in self:
|
||||
if wizard.chg_period_date_from and wizard.chg_period_date_to and wizard.chg_period_date_from > wizard.chg_period_date_to:
|
||||
wizard.chg_period_warning_message = _("Check the dates. The period cannot start later than its own end.")
|
||||
else:
|
||||
record.percentage = 100
|
||||
wizard.chg_period_warning_message = False
|
||||
|
||||
@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('move_line_ids.balance')
|
||||
def _compute_from_move_line_ids(self):
|
||||
for wizard in self:
|
||||
wizard.total_amount = wizard._get_total_balance()
|
||||
wizard.account_type = 'income' if wizard.total_amount < 0.0 else 'expense'
|
||||
|
||||
@api.depends('destination_account_id')
|
||||
def _compute_display_currency_helper(self):
|
||||
@@ -167,80 +239,113 @@ class AutomaticEntryWizard(models.TransientModel):
|
||||
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
|
||||
total_percentage = sum(self.chg_period_line_ids.mapped('percentage'))
|
||||
|
||||
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):
|
||||
move_data = {
|
||||
'original_dates': {},
|
||||
'new_dates': {},
|
||||
}
|
||||
|
||||
# ==== Prepare journal entries (account.move) ====
|
||||
|
||||
for date, grouped_lines in groupby(self.move_line_ids, lambda line: line.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_data['original_dates'][date] = {
|
||||
'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,
|
||||
'line_ids': [],
|
||||
}
|
||||
|
||||
# compute the account.move.lines and the total amount per move
|
||||
for line in self.chg_period_line_ids:
|
||||
ref = _("Adjusting Entry made on {date} ({percent:f}%)").format(
|
||||
date=format_date(self.env, line.date),
|
||||
percent=line.percentage,
|
||||
)
|
||||
move_data['new_dates'][line.date] = {
|
||||
'move_type': 'entry',
|
||||
'ref': ref,
|
||||
'date': fields.Date.to_string(line.date),
|
||||
'journal_id': self.journal_id.id,
|
||||
'line_ids': [],
|
||||
}
|
||||
|
||||
# ==== Prepare journal items (account.move.line) ====
|
||||
|
||||
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)
|
||||
reported_amount_currency = aml.currency_id.round((self.percentage / 100) * aml.amount_currency)
|
||||
|
||||
move_data['new_date']['line_ids'] += [
|
||||
total_reported_balance = 0.0
|
||||
total_reported_amount_currency = 0.0
|
||||
|
||||
# Avoid rounding issues with 100%.
|
||||
if round(total_percentage, 2) == 100.0:
|
||||
total_balance_to_report = aml.balance
|
||||
total_amount_currency_to_report = aml.amount_currency
|
||||
else:
|
||||
total_balance_to_report = aml.company_currency_id.round((total_percentage / 100) * aml.balance)
|
||||
total_amount_currency_to_report = aml.currency_id.round((total_percentage / 100) * aml.amount_currency)
|
||||
|
||||
for i, line in enumerate(self.chg_period_line_ids):
|
||||
# Avoid rounding issue on the last line.
|
||||
if i == len(self.chg_period_line_ids) - 1:
|
||||
reported_balance = total_balance_to_report - total_reported_balance
|
||||
reported_amount_currency = total_amount_currency_to_report - total_reported_amount_currency
|
||||
else:
|
||||
reported_balance = aml.company_currency_id.round((line.percentage / 100) * aml.balance)
|
||||
reported_amount_currency = aml.currency_id.round((line.percentage / 100) * aml.amount_currency)
|
||||
|
||||
total_reported_balance += reported_balance
|
||||
total_reported_amount_currency += reported_amount_currency
|
||||
|
||||
move_data['new_dates'][line.date]['line_ids'] += [
|
||||
(0, 0, {
|
||||
'name': aml.name or '',
|
||||
'currency_id': aml.currency_id.id,
|
||||
'account_id': aml.account_id.id,
|
||||
'partner_id': aml.partner_id.id,
|
||||
'debit': reported_balance if reported_balance > 0.0 else 0.0,
|
||||
'credit': -reported_balance if reported_balance < 0.0 else 0.0,
|
||||
'amount_currency': reported_amount_currency,
|
||||
}),
|
||||
(0, 0, {
|
||||
'name': _('Adjusting Entry'),
|
||||
'currency_id': aml.currency_id.id,
|
||||
'account_id': accrual_account.id,
|
||||
'partner_id': aml.partner_id.id,
|
||||
'debit': -reported_balance if reported_balance < 0.0 else 0.0,
|
||||
'credit': reported_balance if reported_balance > 0.0 else 0.0,
|
||||
'amount_currency': -reported_amount_currency,
|
||||
}),
|
||||
]
|
||||
|
||||
move_data['original_dates'][aml.date]['line_ids'] += [
|
||||
(0, 0, {
|
||||
'name': aml.name or '',
|
||||
'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,
|
||||
'debit': -total_reported_balance if total_reported_balance < 0.0 else 0.0,
|
||||
'credit': total_reported_balance if total_reported_balance > 0.0 else 0.0,
|
||||
'amount_currency': -total_reported_amount_currency,
|
||||
}),
|
||||
(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 or '',
|
||||
'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,
|
||||
'debit': total_reported_balance if total_reported_balance > 0.0 else 0.0,
|
||||
'credit': -total_reported_balance if total_reported_balance < 0.0 else 0.0,
|
||||
'amount_currency': total_reported_amount_currency,
|
||||
}),
|
||||
]
|
||||
|
||||
move_vals = [m for m in move_data.values()]
|
||||
return move_vals
|
||||
return list(move_data['original_dates'].values()) + list(move_data['new_dates'].values())
|
||||
|
||||
@api.depends('move_line_ids', 'journal_id', 'revenue_accrual_account', 'expense_accrual_account', 'percentage', 'date', 'account_type', 'action', 'destination_account_id')
|
||||
@api.depends('move_line_ids', 'journal_id', 'revenue_accrual_account', 'expense_accrual_account',
|
||||
'date', 'account_type', 'action', 'destination_account_id',
|
||||
'chg_period_line_ids')
|
||||
def _compute_move_data(self):
|
||||
for record in self:
|
||||
if record.action == 'change_period':
|
||||
@@ -287,28 +392,44 @@ class AutomaticEntryWizard(models.TransientModel):
|
||||
def _do_action_change_period(self, move_vals):
|
||||
accrual_account = self.revenue_accrual_account if self.account_type == 'income' else self.expense_accrual_account
|
||||
|
||||
# ==== Create new journal entries ====
|
||||
|
||||
created_moves = self.env['account.move'].create(move_vals)
|
||||
created_moves._post()
|
||||
|
||||
# ==== Manage chatters ====
|
||||
|
||||
destination_move = created_moves[0]
|
||||
destination_messages = []
|
||||
accrual_moves = created_moves[1:]
|
||||
|
||||
messages_from = [_("<li>%(link)s</li>", link=self._format_move_link(move)) for move in self.move_line_ids.move_id]
|
||||
messages_from = ''.join([_("Adjusting Entry generated for these journal entries:<ul>")] + messages_from + ["</ul>"])
|
||||
|
||||
messages_to = [_("Adjusting Entries created:<ul>")]
|
||||
messages_to.append(self._format_strings(
|
||||
_("<li>%(link)s cancelling {percent:f}%% of {amount}</li>", link=self._format_move_link(destination_move)),
|
||||
destination_move, self.total_amount,
|
||||
))
|
||||
for accrual_move in accrual_moves:
|
||||
messages_to.append(self._format_strings(
|
||||
_("<li>%(link)s postponing it to {new_date}</li>", link=self._format_move_link(accrual_move)),
|
||||
accrual_move, 0.0,
|
||||
))
|
||||
messages_to = ''.join(messages_to + ["</ul>"])
|
||||
|
||||
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)
|
||||
move.message_post(body=messages_to)
|
||||
destination_move.message_post(body=messages_from)
|
||||
for accrual_move in accrual_moves:
|
||||
accrual_move.message_post(body=messages_from)
|
||||
|
||||
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))
|
||||
# ==== Reconcile ====
|
||||
|
||||
if accrual_account.reconcile and all(move.state == 'posted' for move in created_moves):
|
||||
created_moves.line_ids.filtered(lambda line: line.account_id == accrual_account and not line.reconciled).reconcile()
|
||||
|
||||
# ==== Redirect ====
|
||||
|
||||
# open the generated entries
|
||||
action = {
|
||||
'name': _('Generated Entries'),
|
||||
'domain': [('id', 'in', created_moves.ids)],
|
||||
@@ -389,7 +510,7 @@ class AutomaticEntryWizard(models.TransientModel):
|
||||
|
||||
def _format_strings(self, string, move, amount):
|
||||
return string.format(
|
||||
percent=self.percentage,
|
||||
percent=sum(self.chg_period_line_ids.mapped('percentage')),
|
||||
name=move.name,
|
||||
id=move.id,
|
||||
amount=formatLang(self.env, abs(amount), currency_obj=self.company_id.currency_id),
|
||||
@@ -399,3 +520,43 @@ class AutomaticEntryWizard(models.TransientModel):
|
||||
new_date=self.date and format_date(self.env, self.date) or _('[Not set]'),
|
||||
account_target_name=self.destination_account_id.display_name,
|
||||
)
|
||||
|
||||
|
||||
class AutomaticEntryWizardLine(models.TransientModel):
|
||||
_name = "account.automatic.entry.wizard.line"
|
||||
_description = "Create Automatic Entries Line"
|
||||
|
||||
wizard_id = fields.Many2one(
|
||||
comodel_name='account.automatic.entry.wizard',
|
||||
required=True, readonly=True, ondelete='cascade')
|
||||
date = fields.Date(
|
||||
string="Date",
|
||||
required=True,
|
||||
default=lambda self: fields.Date.context_today(self))
|
||||
percentage = fields.Float(
|
||||
string="Percentage",
|
||||
store=True, readonly=False,
|
||||
compute='_compute_percentage',
|
||||
help="Percentage of each line to execute the action on.")
|
||||
balance = fields.Monetary(
|
||||
string="Amount",
|
||||
store=True, readonly=False,
|
||||
compute='_compute_balance',
|
||||
currency_field='company_currency_id',
|
||||
help="Total amount impacted by the automatic entry.")
|
||||
|
||||
# ==== Display purpose fields ====
|
||||
company_currency_id = fields.Many2one(
|
||||
comodel_name='res.currency',
|
||||
related='wizard_id.company_id.currency_id')
|
||||
|
||||
@api.depends('balance')
|
||||
def _compute_percentage(self):
|
||||
for line in self:
|
||||
total = abs(line.wizard_id._get_total_balance())
|
||||
line.percentage = total and (abs(line.balance) / total) * 100.0 or 0.0
|
||||
|
||||
@api.depends('percentage')
|
||||
def _compute_balance(self):
|
||||
for line in self:
|
||||
line.balance = line.company_currency_id.round((line.percentage / 100.0) * line.wizard_id._get_total_balance())
|
||||
|
||||
@@ -9,34 +9,85 @@
|
||||
<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>
|
||||
|
||||
<div class="alert alert-warning"
|
||||
role="alert"
|
||||
style="margin-bottom:0px;"
|
||||
attrs="{'invisible': ['|', ('action', '!=', 'change_period'), ('chg_period_warning_message', '=', False)]}">
|
||||
<field name="chg_period_warning_message" nolabel="1"/>
|
||||
</div>
|
||||
|
||||
<field name="action" invisible="context.get('hide_automatic_options')" widget="radio" options="{'horizontal': true}"/>
|
||||
|
||||
<group>
|
||||
<group>
|
||||
<field name="total_amount"/>
|
||||
<field name="journal_id"/>
|
||||
</group>
|
||||
<group attrs="{'invisible': [('action', '!=', 'change_period')]}">
|
||||
<field name="date" string="Recognition Date"/>
|
||||
<field name="expense_accrual_account" string="Accrued Account"
|
||||
<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"
|
||||
<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"/>
|
||||
<field name="date"/>
|
||||
<field name="destination_account_id"
|
||||
attrs="{'required': [('action', '=', 'change_account')]}"
|
||||
domain="[('company_id', '=', company_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"/>
|
||||
|
||||
<notebook>
|
||||
<page string="Automatic re-allocation"
|
||||
attrs="{'invisible': [('action', '!=', 'change_period')]}">
|
||||
<span>Create one journal entry per month automatically</span>
|
||||
|
||||
<group>
|
||||
<group>
|
||||
<label for="chg_period_date_from" string="From"/>
|
||||
<div class="d-flex">
|
||||
<field name="chg_period_date_from"/>
|
||||
<span class="oe_inline o_form_label mx-3"> to </span>
|
||||
<field name="chg_period_date_to"/>
|
||||
</div>
|
||||
<field name="chg_period_time_division"/>
|
||||
</group>
|
||||
<group>
|
||||
<label for="chg_period_percentage" string="Re-allocation percentage"/>
|
||||
<div class="d-flex">
|
||||
<field name="chg_period_percentage"/>
|
||||
<span class="oe_inline o_form_label mx-3">%</span>
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
<page string="Manual re-allocation" attrs="{'invisible': [('action', '!=', 'change_period')]}">
|
||||
<field name="chg_period_line_ids">
|
||||
<tree editable="bottom" default_order="date desc">
|
||||
<field name="company_currency_id" invisible="1"/>
|
||||
|
||||
<field name="date"/>
|
||||
<field name="percentage" sum="Total Percentage"/>
|
||||
<field name="balance" sum="Total Balance"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
<page string="Preview">
|
||||
<label for="preview_move_data" string="The following Journal Entries will be generated"/>
|
||||
<field name="preview_move_data" widget="grouped_view_widget"/>
|
||||
</page>
|
||||
</notebook>
|
||||
|
||||
<footer>
|
||||
<button string="Create Journal Entries" name="do_action" type="object" class="oe_highlight"/>
|
||||
<button string="Cancel" class="btn btn-secondary" special="cancel"/>
|
||||
|
||||
Reference in New Issue
Block a user