[IMP] account: Prevent inconsistent amounts and currencies on vendor bills.

when a Vendor bill in an inactive currency is created (via an an EDI or the OCR,
or when the user create the invoice in an active foreign currency, then desactivate it)
it should not be possible to validate it.

Task ID: #2289537

closes odoo/odoo#54096

Signed-off-by: Laurent Smet <smetl@users.noreply.github.com>
This commit is contained in:
Nicolas (vin)
2020-11-17 07:50:22 +00:00
parent af017d9557
commit b94b52ff7e
3 changed files with 52 additions and 5 deletions
+15
View File
@@ -316,6 +316,9 @@ class AccountMove(models.Model):
tax_lock_date_message = fields.Char(
compute='_compute_tax_lock_date_message',
help="Technical field used to display a message when the invoice's accounting date is prior of the tax lock date.")
display_inactive_currency_warning = fields.Boolean(
compute="_compute_display_inactive_currency_warning",
help="Technical field used for tracking the status of the currency")
# Technical field to hide Reconciled Entries stat button
has_reconciled_entries = fields.Boolean(compute="_compute_has_reconciled_entries")
show_reset_to_draft_button = fields.Boolean(compute='_compute_show_reset_to_draft_button')
@@ -1355,6 +1358,11 @@ class AccountMove(models.Model):
vendor_display_name = _('#Created by: %s', move.sudo().create_uid.name or self.env.user.name)
move.invoice_partner_display_name = vendor_display_name
@api.depends('currency_id')
def _compute_display_inactive_currency_warning(self):
for move in self.with_context(active_test=False):
move.display_inactive_currency_warning = not move.currency_id.active
def _compute_payments_widget_to_reconcile_info(self):
for move in self:
move.invoice_outstanding_credits_debits_widget = json.dumps(False)
@@ -2404,6 +2412,10 @@ class AccountMove(models.Model):
if move.is_invoice(include_receipts=True) and float_compare(move.amount_total, 0.0, precision_rounding=move.currency_id.rounding) < 0:
raise UserError(_("You cannot validate an invoice with a negative total amount. You should create a credit note instead. Use the action menu to transform it into a credit note or refund."))
if move.display_inactive_currency_warning:
raise UserError(_("You cannot validate an invoice with an inactive currency: %s",
move.currency_id.name))
# Handle case when the invoice_date is not set. In that case, the invoice_date is set at today and then,
# lines are recomputed accordingly.
# /!\ 'check_move_validity' must be there since the dynamic lines will be recomputed outside the 'onchange'
@@ -2769,6 +2781,9 @@ class AccountMove(models.Model):
action['res_id'] = self.copy().id
return action
def action_activate_currency(self):
self.currency_id.filtered(lambda currency: not currency.active).write({'active': True})
@api.model
def _move_dict_to_preview_vals(self, move_vals, currency_id=None):
preview_vals = {
@@ -745,3 +745,25 @@ class TestAccountMove(AccountTestInvoicingCommon):
# You can remove journal items if the related journal entry is draft.
self.test_move.button_draft()
self.test_move.line_ids.unlink()
def test_account_move_inactive_currency_raise_error_on_post(self):
""" Ensure a move cannot be posted when using an inactive currency """
move = self.env['account.move'].create({
'move_type': 'in_invoice',
'partner_id': self.partner_a.id,
'invoice_date': fields.Date.from_string('2019-01-01'),
'currency_id': self.currency_data['currency'].id,
'invoice_payment_term_id': self.pay_terms_a.id,
'invoice_line_ids': [{}]
})
move.currency_id.active = False
with self.assertRaises(UserError), self.cr.savepoint():
move.action_post()
# Make sure that the invoice can still be posted when the currency is active
move.action_activate_currency()
move.action_post()
self.assertEqual(move.state, 'posted')
+15 -5
View File
@@ -523,7 +523,7 @@
attrs="{'invisible': ['|', '|', ('state', '!=', 'draft'), ('auto_post', '=', True), ('move_type', '!=', 'entry')]}"/>
<button name="action_post" string="Confirm" class="oe_highlight"
type="object" groups="account.group_account_invoice"
attrs="{'invisible': ['|', '|', ('state', '!=', 'draft'), ('auto_post', '=', True), ('move_type', '=', 'entry')]}"/>
attrs="{'invisible': ['|', '|', '|', ('state', '!=', 'draft'), ('auto_post', '=', True), ('move_type', '=', 'entry'), ('display_inactive_currency_warning','=',True)]}"/>
<!-- Send (only invoices) -->
<button name="action_invoice_sent"
type="object"
@@ -562,24 +562,33 @@
</header>
<!-- Invoice outstanding credits -->
<div groups="account.group_account_invoice,account.group_account_readonly"
class="alert alert-warning" role="alert" style="margin-bottom:0px;"
class="alert alert-warning mb-0" role="alert"
attrs="{'invisible': ['|', ('state', '!=', 'draft'), ('tax_lock_date_message', '=', False)]}">
<field name="tax_lock_date_message" nolabel="1"/>
</div>
<div groups="account.group_account_invoice,account.group_account_readonly"
class="alert alert-info" role="alert" style="margin-bottom:0px;"
class="alert alert-info mb-0" role="alert"
attrs="{'invisible': ['|', '|', ('move_type', 'not in', ('out_invoice', 'out_refund')), ('invoice_has_outstanding', '=', False), ('payment_state', 'not in', ('not_paid', 'partial'))]}">
You have <bold><a class="alert-link" href="#outstanding" role="button">outstanding payments</a></bold> for this customer. You can allocate them to mark this invoice as paid.
</div>
<div groups="account.group_account_invoice,account.group_account_readonly"
class="alert alert-info" role="alert" style="margin-bottom:0px;"
class="alert alert-info mb-0" role="alert"
attrs="{'invisible': ['|', '|', ('move_type', 'not in', ('in_invoice', 'in_refund')), ('invoice_has_outstanding', '=', False), ('payment_state', 'not in', ('not_paid', 'partial'))]}">
You have <bold><a class="alert-link" href="#outstanding" role="button">outstanding debits</a></bold> for this vendor. You can allocate them to mark this bill as paid.
</div>
<div class="alert alert-info" role="alert" style="margin-bottom:0px;"
<div class="alert alert-info mb-0" role="alert"
attrs="{'invisible': ['|', ('state', '!=', 'draft'), ('auto_post', '=', False)]}">
This move is configured to be posted automatically at the accounting date: <field name="date" readonly="1"/>.
</div>
<!-- Currency consistency -->
<div class="alert alert-warning mb-0" role="alert"
attrs="{'invisible': ['|', ('display_inactive_currency_warning', '=', False), ('move_type', 'not in', ('in_invoice', 'in_refund', 'in_receipt'))]}">
In order to validate this bill, you must <button class="oe_link" type="object" name="action_activate_currency" style="padding: 0; vertical-align: baseline;">activate the currency of the bill</button>. The journal entries need to be computed by Odoo before being posted in your company's currency.
</div>
<div class="alert alert-warning mb-0" role="alert"
attrs="{'invisible': ['|', ('display_inactive_currency_warning', '=', False), ('move_type', 'not in', ('out_invoice', 'out_refund', 'out_receipt'))]}">
In order to validate this invoice, you must <button class="oe_link" type="object" name="action_activate_currency" style="padding: 0; vertical-align: baseline;">activate the currency of the invoice</button>. The journal entries need to be computed by Odoo before being posted in your company's currency.
</div>
<sheet>
<div name="button_box" class="oe_button_box">
<button name="open_reconcile_view"
@@ -626,6 +635,7 @@
<field name="has_reconciled_entries" invisible="1"/>
<field name="restrict_mode_hash_table" invisible="1"/>
<field name="country_code" invisible="1"/>
<field name="display_inactive_currency_warning" invisible="1"/>
<div class="oe_title">
<!-- Invoice draft header -->