summaryrefslogtreecommitdiff
path: root/addons/account_check_printing
diff options
context:
space:
mode:
authorLaurent Smet <las@odoo.com>2019-10-29 12:21:27 +0000
committerqdp-odoo <qdp@odoo.com>2020-04-10 09:47:24 +0000
commitcaeb782841fc5a7ad71a196e2c9ee67644ef9074 (patch)
tree28103bc29ab4f0d98162095f607c4c6ab1433f25 /addons/account_check_printing
parentb4d1360ed1d71c8fda44ca0c3b1a80d5a112b93f (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')
-rw-r--r--addons/account_check_printing/models/account_payment.py90
-rw-r--r--addons/account_check_printing/tests/test_print_check.py152
-rw-r--r--addons/account_check_printing/views/account_payment_views.xml11
-rw-r--r--addons/account_check_printing/wizard/print_prenumbered_checks.py2
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