diff options
| author | Laurent Smet <las@odoo.com> | 2019-10-29 12:21:27 +0000 |
|---|---|---|
| committer | qdp-odoo <qdp@odoo.com> | 2020-04-10 09:47:24 +0000 |
| commit | caeb782841fc5a7ad71a196e2c9ee67644ef9074 (patch) | |
| tree | 28103bc29ab4f0d98162095f607c4c6ab1433f25 /addons/account_check_printing | |
| parent | b4d1360ed1d71c8fda44ca0c3b1a80d5a112b93f (diff) | |
[IMP] account,*: Improve bank statements/payments workflow
- Create journal entries as soon as bank/cash statement lines are created, temporary booked on a suspense account set on the journal.
- Simplify the management of "blue" lines in the reconciliation widget. A "blue" line is now a journal item using a temporary liquidity account (outstanding payment/receipt accounts, set on the journal).
- Adapt and simplify the bank reconciliation report.
- Remove the bank reconciliation threshold date. The reconciliation report will show the not already reconciled journal entries using a liquidity account and the not already reconciled journal entries using a temporary liquidity account. Without accounting, an account.payment will involve directly the liquidity account and then, will be considered as a statement line directly.
- Remove the post_at bank reconciliation feature. The "paid" state will be set on the invoices only if reconciled with a journal entry involving the journal's liquidity account.
With invoicing, the payment will do that so the "in_payment" state should never be shown up.
With accounting, only the statement lines have the power to move an invoice to the "paid" state.
- Fix various corner cases about the management of multi-currency in bank statement lines.
- Fix the conversion dates in multi-currency: Since the bank/cash is always used on the statement lines, it will use always the real "bank" date instead of the fictive payment one.
- Ensure the 'reconcile' method will raise an error if the involved moves are not posted.
related enterprise PR odoo/enterprise#7019
closes odoo/odoo#41301
--task: 2092096
Related: odoo/upgrade#1018
Signed-off-by: Quentin De Paoli (qdp) <qdp@openerp.com>
Diffstat (limited to 'addons/account_check_printing')
4 files changed, 130 insertions, 125 deletions
diff --git a/addons/account_check_printing/models/account_payment.py b/addons/account_check_printing/models/account_payment.py index eeb098c2f45..1034cf84ada 100644 --- a/addons/account_check_printing/models/account_payment.py +++ b/addons/account_check_printing/models/account_payment.py @@ -7,31 +7,38 @@ from odoo.tools.misc import formatLang, format_date INV_LINES_PER_STUB = 9 -class AccountPaymentRegister(models.TransientModel): - _inherit = "account.payment.register" - - def _prepare_payment_vals(self, invoices): - res = super(AccountPaymentRegister, self)._prepare_payment_vals(invoices) - if self.payment_method_id == self.env.ref('account_check_printing.account_payment_method_check'): - currency_id = self.env['res.currency'].browse(res['currency_id']) - res.update({ - 'check_amount_in_words': currency_id.amount_to_text(res['amount']), - }) - return res - class AccountPayment(models.Model): _inherit = "account.payment" - check_amount_in_words = fields.Char(string="Amount in Words") - check_manual_sequencing = fields.Boolean(related='journal_id.check_manual_sequencing', readonly=1) - check_number = fields.Char(string="Check Number", readonly=True, copy=False, + check_amount_in_words = fields.Char(string="Amount in Words", store=True, readonly=False, + compute='_compute_check_amount_in_words') + check_manual_sequencing = fields.Boolean(related='journal_id.check_manual_sequencing') + check_number = fields.Char(string="Check Number", store=True, readonly=True, + compute='_compute_check_number', help="The selected journal is configured to print check numbers. If your pre-printed check paper already has numbers " "or if the current numbering is wrong, you can change it in the journal configuration page.") - check_number_int = fields.Integer(string="Check Number Integer", store=True, compute='_compute_check_number') + check_number_int = fields.Integer(string="Check Number Integer", store=True, + compute='_compute_check_number_int') + + @api.depends('payment_method_id', 'currency_id', 'amount') + def _compute_check_amount_in_words(self): + for pay in self: + if pay.currency_id and pay.payment_method_id == self.env.ref('account_check_printing.account_payment_method_check'): + pay.check_amount_in_words = pay.currency_id.amount_to_text(pay.amount) + else: + pay.check_amount_in_words = False + + @api.depends('journal_id') + def _compute_check_number(self): + for pay in self: + if pay.journal_id.check_manual_sequencing: + pay.check_number = pay.journal_id.check_sequence_id.number_next_actual + else: + pay.check_number = False @api.depends('check_number') - def _compute_check_number(self): + def _compute_check_number_int(self): # store check number as int to avoid doing a lot of checks and transformations # when calculating next_check_number for record in self: @@ -42,29 +49,8 @@ class AccountPayment(models.Model): number = 0 record.check_number_int = number - @api.onchange('amount', 'currency_id') - def _onchange_amount(self): - res = super(AccountPayment, self)._onchange_amount() - self.check_amount_in_words = self.currency_id.amount_to_text(self.amount) if self.currency_id else '' - return res - - @api.onchange('journal_id') - def _onchange_journal_id(self): - if hasattr(super(AccountPayment, self), '_onchange_journal_id'): - super(AccountPayment, self)._onchange_journal_id() - if self.journal_id.check_manual_sequencing: - self.check_number = self.journal_id.check_sequence_id.number_next_actual - - def _check_communication(self, payment_method_id, communication): - super(AccountPayment, self)._check_communication(payment_method_id, communication) - if payment_method_id == self.env.ref('account_check_printing.account_payment_method_check').id: - if not communication: - return - if len(communication) > 60: - raise ValidationError(_("A check memo cannot exceed 60 characters.")) - - def post(self): - res = super(AccountPayment, self).post() + def action_post(self): + res = super(AccountPayment, self).action_post() payment_method_check = self.env.ref('account_check_printing.account_payment_method_check') for payment in self.filtered(lambda p: p.payment_method_id == payment_method_check and p.check_manual_sequencing): sequence = payment.journal_id.check_sequence_id @@ -105,8 +91,8 @@ class AccountPayment(models.Model): self.filtered(lambda r: r.state == 'draft').post() return self.do_print_checks() - def unmark_sent(self): - self.write({'state': 'posted'}) + def action_unmark_sent(self): + self.write({'is_move_sent': False}) def do_print_checks(self): check_layout = self[0].company_id.account_check_printing_layout @@ -115,13 +101,9 @@ class AccountPayment(models.Model): report_action = self.env.ref(check_layout, False) if not report_action: raise UserError(_("Something went wrong with Check Layout, please select another layout in Invoicing/Accounting Settings and try again.")) - self.write({'state': 'sent'}) + self.write({'is_move_sent': True}) return report_action.report_action(self) - def set_check_amount_in_words(self): - for payment in self: - payment.check_amount_in_words = payment.currency_id.amount_to_text(payment.amount) if payment.currency_id else '' - ####################### #CHECK PRINTING METHODS ####################### @@ -132,15 +114,15 @@ class AccountPayment(models.Model): multi_stub = self.company_id.account_check_printing_multi_stub return { 'sequence_number': self.check_number if (self.journal_id.check_manual_sequencing and self.check_number != 0) else False, - 'payment_date': format_date(self.env, self.payment_date), + 'date': format_date(self.env, self.date), 'partner_id': self.partner_id, 'partner_name': self.partner_id.name, 'currency': self.currency_id, 'state': self.state, 'amount': formatLang(self.env, self.amount, currency_obj=self.currency_id) if i == 0 else 'VOID', 'amount_in_word': self._check_fill_line(self.check_amount_in_words) if i == 0 else 'VOID', - 'memo': self.communication, - 'stub_cropped': not multi_stub and len(self.invoice_ids) > INV_LINES_PER_STUB, + 'memo': self.ref, + 'stub_cropped': not multi_stub and len(self.move_id._get_reconciled_invoices()) > INV_LINES_PER_STUB, # If the payment does not reference an invoice, there is no stub line to display 'stub_lines': p, } @@ -158,12 +140,12 @@ class AccountPayment(models.Model): """ The stub is the summary of paid invoices. It may spill on several pages, in which case only the check on first page is valid. This function returns a list of stub lines per page. """ - if len(self.reconciled_invoice_ids) == 0: + if len(self.move_id._get_reconciled_invoices()) == 0: return None multi_stub = self.company_id.account_check_printing_multi_stub - invoices = self.reconciled_invoice_ids.sorted(key=lambda r: r.invoice_date_due) + invoices = self.move_id._get_reconciled_invoices().sorted(key=lambda r: r.invoice_date_due) debits = invoices.filtered(lambda r: r.move_type == 'in_invoice') credits = invoices.filtered(lambda r: r.move_type == 'in_refund') @@ -201,10 +183,10 @@ class AccountPayment(models.Model): # Find the account.partial.reconcile which are common to the invoice and the payment if invoice.move_type in ['in_invoice', 'out_refund']: invoice_sign = 1 - invoice_payment_reconcile = invoice.line_ids.mapped('matched_debit_ids').filtered(lambda r: r.debit_move_id in self.move_line_ids) + invoice_payment_reconcile = invoice.line_ids.mapped('matched_debit_ids').filtered(lambda r: r.debit_move_id in self.line_ids) else: invoice_sign = -1 - invoice_payment_reconcile = invoice.line_ids.mapped('matched_credit_ids').filtered(lambda r: r.credit_move_id in self.move_line_ids) + invoice_payment_reconcile = invoice.line_ids.mapped('matched_credit_ids').filtered(lambda r: r.credit_move_id in self.line_ids) if self.currency_id != self.journal_id.company_id.currency_id: amount_paid = abs(sum(invoice_payment_reconcile.mapped('amount_currency'))) diff --git a/addons/account_check_printing/tests/test_print_check.py b/addons/account_check_printing/tests/test_print_check.py index 1a79d7c419b..d693bdc3bb4 100644 --- a/addons/account_check_printing/tests/test_print_check.py +++ b/addons/account_check_printing/tests/test_print_check.py @@ -1,81 +1,105 @@ -# Make sure / performs a floating point division even if environment is python 2 -from __future__ import division - -from odoo.addons.account.tests.common import AccountTestCommon +# -*- coding: utf-8 -*- +from odoo.addons.account.tests.common import AccountTestInvoicingCommon from odoo.addons.account_check_printing.models.account_payment import INV_LINES_PER_STUB from odoo.tests import tagged -from odoo.tests.common import Form -import time -import math +import math @tagged('post_install', '-at_install') -class TestPrintCheck(AccountTestCommon): +class TestPrintCheck(AccountTestInvoicingCommon): @classmethod def setUpClass(cls): - super(TestPrintCheck, cls).setUpClass() + super().setUpClass() - cls.invoice_model = cls.env['account.move'] - cls.invoice_line_model = cls.env['account.move.line'] - cls.payment_model = cls.env['account.payment'] - - cls.partner_axelor = cls.env['res.partner'].create({'name': 'A Partner'}) - cls.product = cls.env['product.product'].create({'name': 'A test Product'}) cls.payment_method_check = cls.env.ref("account_check_printing.account_payment_method_check") - cls.account_payable = cls.env['account.account'].search([('user_type_id', '=', cls.env.ref('account.data_account_type_payable').id)], limit=1) - cls.account_expenses = cls.env['account.account'].search([('user_type_id', '=', cls.env.ref('account.data_account_type_expenses').id)], limit=1) - - cls.bank_journal = cls.env['account.journal'].create({'name': 'Bank', 'type': 'bank', 'code': 'BNK67'}) - cls.bank_journal.check_manual_sequencing = True - - def create_invoice(self, amount=100, is_refund=False): - invoice = self.env['account.move'].with_context(default_move_type=is_refund and 'out_refund' or 'in_invoice').create({ - 'partner_id': self.partner_axelor.id, - 'invoice_date': time.strftime('%Y') + '-06-26', - 'date': time.strftime('%Y') + '-06-26', - 'invoice_line_ids': [ - (0, 0, { - 'product_id': self.product.id, - 'quantity': 1, - 'price_unit': is_refund and amount / 4 or amount, - }) - ] + cls.company_data['default_journal_bank'].write({ + 'outbound_payment_method_ids': [(6, 0, ( + cls.env.ref('account.account_payment_method_manual_out').id, + cls.payment_method_check.id, + ))], }) - invoice.post() - return invoice - - def create_payment(self, invoices): - payment_register = Form(self.env['account.payment'].with_context(active_model='account.move', active_ids=invoices.ids)) - payment_register.payment_date = time.strftime('%Y') + '-07-15' - payment_register.journal_id = self.bank_journal - payment_register.payment_method_id = self.payment_method_check - payment = payment_register.save() - payment.post() - return payment - - def test_print_check(self): - # Make a payment for 10 invoices and 5 credit notes - invoices = self.env['account.move'] - for i in range(0, 15): - invoices |= self.create_invoice(is_refund=(i % 3 == 0)) - payment = self.create_payment(invoices) - self.assertEqual(all(payment.mapped('check_amount_in_words')), True, 'The amount in words is not set on all the payments') - self.assertEqual(all(payment.mapped('check_number')), True, 'The check number is not set on all the payments') - - # Check the data generated for the report - self.env.ref('base.main_company').write({'account_check_printing_multi_stub': True}) + + def test_inbound_check_manual_sequencing(self): + ''' Test the check generation for customer invoices. ''' + nb_invoices_to_test = INV_LINES_PER_STUB + 1 + + self.company_data['default_journal_bank'].write({ + 'check_manual_sequencing': True, + 'check_next_number': 42, + }) + + # Create 10 customer invoices. + out_invoices = self.env['account.move'].create([{ + 'move_type': 'out_invoice', + 'partner_id': self.partner_a.id, + 'date': '2017-01-01', + 'invoice_date': '2017-01-01', + 'invoice_line_ids': [(0, 0, {'product_id': self.product_a.id, 'price_unit': 100.0})] + } for i in range(nb_invoices_to_test)]) + out_invoices.post() + + # Create a single payment. + payment = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=out_invoices.ids).create({ + 'group_payment': True, + 'payment_method_id': self.payment_method_check.id, + })._create_payments() + + # Check created payment. + self.assertRecordValues(payment, [{ + 'payment_method_id': self.payment_method_check.id, + 'check_amount_in_words': payment.currency_id.amount_to_text(100.0 * nb_invoices_to_test), + 'check_number': '00042', + }]) + + # Check pages. + self.company_data['company'].account_check_printing_multi_stub = True report_pages = payment._check_get_pages() - self.assertEqual(len(report_pages), int(math.ceil(len(payment.reconciled_invoice_ids) / INV_LINES_PER_STUB))) - self.env.ref('base.main_company').write({'account_check_printing_multi_stub': False}) + self.assertEqual(len(report_pages), int(math.ceil(len(out_invoices) / INV_LINES_PER_STUB))) + + self.company_data['company'].account_check_printing_multi_stub = False report_pages = payment._check_get_pages() self.assertEqual(len(report_pages), 1) - def test_from_register(self): - invoices = self.env['account.move'] - for i in range(0, 3): - invoices |= self.create_invoice(is_refund=(i % 3 == 0)) - payment = self.create_payment(invoices) - self.assertEqual(all(payment.mapped('check_amount_in_words')), True, 'The amount in words is not set on all the payments') + def test_outbound_check_manual_sequencing(self): + ''' Test the check generation for refunds. ''' + nb_invoices_to_test = INV_LINES_PER_STUB + 1 + + self.company_data['default_journal_bank'].write({ + 'check_manual_sequencing': True, + 'check_next_number': 42, + }) + + # Create 10 refunds. + out_refunds = self.env['account.move'].create([{ + 'move_type': 'out_refund', + 'partner_id': self.partner_a.id, + 'date': '2017-01-01', + 'invoice_date': '2017-01-01', + 'invoice_line_ids': [(0, 0, {'product_id': self.product_a.id, 'price_unit': 100.0})] + } for i in range(nb_invoices_to_test)]) + out_refunds.post() + + # Create a single payment. + payment = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=out_refunds.ids).create({ + 'group_payment': True, + 'payment_method_id': self.payment_method_check.id, + })._create_payments() + + # Check created payment. + self.assertRecordValues(payment, [{ + 'payment_method_id': self.payment_method_check.id, + 'check_amount_in_words': payment.currency_id.amount_to_text(100.0 * nb_invoices_to_test), + 'check_number': '00042', + }]) + + # Check pages. + self.company_data['company'].account_check_printing_multi_stub = True + report_pages = payment._check_get_pages() + self.assertEqual(len(report_pages), int(math.ceil(len(out_refunds) / INV_LINES_PER_STUB))) + + self.company_data['company'].account_check_printing_multi_stub = False + report_pages = payment._check_get_pages() + self.assertEqual(len(report_pages), 1) diff --git a/addons/account_check_printing/views/account_payment_views.xml b/addons/account_check_printing/views/account_payment_views.xml index 91f27101fd2..32093a46845 100644 --- a/addons/account_check_printing/views/account_payment_views.xml +++ b/addons/account_check_printing/views/account_payment_views.xml @@ -5,17 +5,16 @@ <field name="model">account.payment</field> <field name="inherit_id" ref="account.view_account_payment_form" /> <field name="arch" type="xml"> - <xpath expr="//button[@name='post']" position="before"> + <xpath expr="//button[@name='action_post']" position="before"> <button name="print_checks" class="oe_highlight" attrs="{'invisible': ['|', ('payment_method_code', '!=', 'check_printing'), ('state', '!=', 'posted')]}" string="Print Check" type="object"/> - <button name="unmark_sent" attrs="{'invisible': ['|', ('payment_method_code', '!=', 'check_printing'), ('state', '!=', 'sent')]}" string="Unmark Sent" type="object"/> - <button name="set_check_amount_in_words" attrs="{'invisible': ['|', ('payment_method_code', '!=', 'check_printing'), ('check_amount_in_words', '!=', False)]}" string="Reset Amount in Words" type="object" groups="base.group_no_one"/> + <button name="action_unmark_sent" attrs="{'invisible': ['|', ('payment_method_code', '!=', 'check_printing'), ('is_move_sent', '=', False)]}" string="Unmark Sent" type="object"/> </xpath> <xpath expr="//div[@name='amount_div']" position="after"> <field name="check_amount_in_words" attrs="{'invisible': [('payment_method_code', '!=', 'check_printing')], 'readonly': [('state', '!=', 'draft')]}" groups="base.group_no_one"/> </xpath> - <xpath expr="//field[@name='communication']" position="after"> - <field name="check_manual_sequencing" invisible="1" readonly="1"/> - <field name="check_number" attrs="{'invisible': ['|', ('payment_method_code', '!=', 'check_printing'), ('check_number', '=', '0')]}"/> + <xpath expr="//field[@name='ref']" position="after"> + <field name="check_manual_sequencing" invisible="1"/> + <field name="check_number" attrs="{'invisible': ['|', ('payment_method_code', '!=', 'check_printing'), ('check_number', '=', False)]}"/> </xpath> </field> </record> diff --git a/addons/account_check_printing/wizard/print_prenumbered_checks.py b/addons/account_check_printing/wizard/print_prenumbered_checks.py index 1af58dd11a5..abbda0c7f6d 100644 --- a/addons/account_check_printing/wizard/print_prenumbered_checks.py +++ b/addons/account_check_printing/wizard/print_prenumbered_checks.py @@ -21,7 +21,7 @@ class PrintPreNumberedChecks(models.TransientModel): def print_checks(self): check_number = int(self.next_check_number) payments = self.env['account.payment'].browse(self.env.context['payment_ids']) - payments.filtered(lambda r: r.state == 'draft').post() + payments.filtered(lambda r: r.state == 'draft').action_post() payments.filtered(lambda r: r.state not in ('sent', 'cancelled')).write({'state': 'sent'}) for payment in payments: payment.check_number = check_number |
