[FIX] account: Fix some issues since account-pocalypse

1) Improve readability / performance of moves checks

The queries checking the move name & the duplicate vendor reference was complex
and unefficient for no reason.

2) Recompute amount_currency when reversing invoice

When reversing an invoice, the amount_currency wasn't recomputed even when dealing with
a refund date having another currency rate.

3) The refund must not keep the payment ref of the reverted invoice.

The payment ref is stored inside the receivable/payable line's name. However, when
duplicating the invoice to create a refund, this must be emptied until a new
payment ref will be set.

4) Reintroduce user_id field on account.move

The field user_id has been renamed to invoice_user_id. However, some mail templates
need to access the generic user_id field for multiple models.

5) Fix 'misc' Journal Entry totals

Misc journal entry must have an amount total always positive and not signed.
Furthermore, we don't know how to compute the amount_untaxed/amount_tax so don't
compute that with such entries.

6) Fix cancelling an exchange difference entry / tax cash basis entry.

When cancelling an exchange difference entry, the reconciliation is undo and then,
the exchange difference entry is reversed. Same for tax cash basis entries.

issue: https://github.com/odoo/odoo/issues/35841

7) Fix creation of cash basis entries for reversed journal entries.

When reconciling entries with one being the reverse of the another one, cash basis entries
for taxes must not be created. However, it must be the case when reconciling the reverse entry
(like refunds) with something.

issue: https://github.com/odoo/odoo/issues/35276

8) Fix matching rules ignoring the structured communication.

Since account-pocalypse, the structured communication is landing on the name of the
journal items. Then, the matching rules must consider it.

closes odoo/odoo#36062

Signed-off-by: Laurent Smet <smetl@users.noreply.github.com>
This commit is contained in:
Laurent Smet
2019-08-29 13:04:55 +00:00
parent 156142caf2
commit 152d1f4fc7
5 changed files with 373 additions and 54 deletions
+68 -44
View File
@@ -170,6 +170,8 @@ class AccountMove(models.Model):
states={'draft': [('readonly', False)]},
string='Salesperson',
default=lambda self: self.env.user)
user_id = fields.Many2one(string='User', related='invoice_user_id',
help='Technical field used to fit the generic behavior in mail templates.')
invoice_payment_state = fields.Selection(selection=[
('not_paid', 'Not Paid'),
('in_payment', 'In Payment'),
@@ -933,35 +935,43 @@ class AccountMove(models.Model):
total_tax_currency = 0.0
total_residual = 0.0
total_residual_currency = 0.0
total = 0.0
total_currency = 0.0
currencies = set()
for line in move.line_ids:
if line.currency_id:
currencies.add(line.currency_id)
# Untaxed amount.
if (move.is_invoice(include_receipts=True) and not line.exclude_from_invoice_tab)\
or (move.type == 'entry' and line.debit and not line.tax_line_id):
total_untaxed += line.balance
total_untaxed_currency += line.amount_currency
if move.is_invoice(include_receipts=True):
# === Invoices ===
# Tax amount.
if line.tax_line_id:
total_tax += line.balance
total_tax_currency += line.amount_currency
if not line.exclude_from_invoice_tab:
# Untaxed amount.
total_untaxed += line.balance
total_untaxed_currency += line.amount_currency
total += line.balance
total_currency += line.amount_currency
elif line.tax_line_id:
# Tax amount.
total_tax += line.balance
total_tax_currency += line.amount_currency
total += line.balance
total_currency += line.amount_currency
elif line.account_id.user_type_id.type in ('receivable', 'payable'):
# Residual amount.
total_residual += line.amount_residual
total_residual_currency += line.amount_residual_currency
else:
# === Miscellaneous journal entry ===
if line.debit:
total += line.balance
total_currency += line.amount_currency
# Residual amount.
if move.type == 'entry' or line.account_id.user_type_id.type in ('receivable', 'payable'):
total_residual += line.amount_residual
total_residual_currency += line.amount_residual_currency
total = total_untaxed + total_tax
total_currency = total_untaxed_currency + total_tax_currency
if (move.type == 'entry' and total < 0.0) or move.is_inbound():
sign = -1
else:
if move.type == 'entry' or move.is_outbound():
sign = 1
else:
sign = -1
move.amount_untaxed = sign * (total_untaxed_currency if len(currencies) == 1 else total_untaxed)
move.amount_tax = sign * (total_tax_currency if len(currencies) == 1 else total_tax)
@@ -1236,27 +1246,23 @@ class AccountMove(models.Model):
if 'posted' in self.mapped('line_ids.payment_id.state'):
raise ValidationError(_("You cannot modify a journal entry linked to a posted payment."))
@api.constrains('name', 'journal_id')
@api.constrains('name', 'journal_id', 'state')
def _check_unique_sequence_number(self):
if not self:
moves = self.filtered(lambda move: move.state == 'posted')
if not moves:
return
# /!\ Computed stored fields are not yet inside the database.
self._cr.execute('''
WITH current_moves (id, name, state, company_id, journal_id, type) as ( values {} )
SELECT move2.id
FROM ( SELECT id, name, state, company_id, journal_id, type from account_move
UNION ALL SELECT id, name, state, company_id, journal_id, type from current_moves
) move
FROM account_move move
INNER JOIN account_move move2 ON
move2.name = move.name
AND move2.company_id = move.company_id
AND move2.journal_id = move.journal_id
AND move2.type = move.type
AND move2.id != move.id
WHERE move.id IN %s
AND move.state = 'posted'
AND move2.state = 'posted'
'''.format(", ".join(["(%s, %s, %s, %s, %s, %s)"] * len(self))), list(chain(*self.mapped(lambda r: [r.id, r.name, r.state, r.company_id.id, r.journal_id.id, r.type]))) + [tuple(self.ids)])
WHERE move.id IN %s AND move2.state = 'posted'
''', [tuple(moves.ids)])
res = self._cr.fetchone()
if res:
raise ValidationError(_('Posted journal entry must have an unique sequence number per company.'))
@@ -1267,23 +1273,21 @@ class AccountMove(models.Model):
if not moves:
return
# /!\ Computed stored fields are not yet inside the database.
self._cr.execute('''
WITH current_moves (id, ref, company_id, commercial_partner_id, type, invoice_date) as ( values {} )
SELECT move2.id
FROM ( SELECT id, ref, company_id, commercial_partner_id, type, invoice_date from account_move
UNION ALL SELECT id, ref, company_id, commercial_partner_id, type, invoice_date from current_moves
) move
FROM account_move move
JOIN account_journal journal ON journal.id = move.journal_id
JOIN res_partner partner ON partner.id = move.partner_id
INNER JOIN account_move move2 ON
move2.ref = move.ref
AND move2.company_id = move.company_id
AND move2.commercial_partner_id = move.commercial_partner_id
AND move2.company_id = journal.company_id
AND move2.commercial_partner_id = partner.commercial_partner_id
AND move2.type = move.type
AND (move2.invoice_date = move.invoice_date OR move.invoice_date is NULL)
AND (move.invoice_date is NULL OR move2.invoice_date = move.invoice_date)
AND move2.id != move.id
WHERE move.id IN %s
AND move.type in ('in_invoice', 'in_refund')
AND move.ref IS NOT NULL
'''.format(", ".join(["(%s, %s, %s, %s, %s, CAST(%s as DATE))"] * len(self))), list(chain(*self.mapped(lambda r: [r.id, r.ref, r.company_id.id, r.commercial_partner_id.id, r.type, r.invoice_date or None]))) + [tuple(moves.ids)])
''', [tuple(moves.ids)])
if self._cr.fetchone():
raise ValidationError(_('Duplicated vendor reference detected. You probably encoded twice the same vendor bill/credit note.'))
@@ -1860,7 +1864,16 @@ class AccountMove(models.Model):
'reversed_entry_id': move.id,
})
move_vals_list.append(move._reverse_move_vals(default_values, cancel=cancel))
reverse_moves = self.env['account.move'].create(move_vals_list)
for move, reverse_move in zip(self, reverse_moves.with_context(check_move_validity=False)):
# Update amount_currency if the date has changed.
if move.date != reverse_move.date:
for line in reverse_move.line_ids:
if line.currency_id:
line._onchange_currency()
reverse_move._recompute_dynamic_lines(recompute_all_taxes=False)
reverse_moves._check_balanced()
# Reconcile moves together to cancel the previous one.
if cancel:
@@ -1980,6 +1993,10 @@ class AccountMove(models.Model):
excluded_move_ids = AccountMoveLine.search(AccountMoveLine._get_suspense_moves_domain() + [('move_id', 'in', self.ids)]).mapped('move_id').ids
for move in self:
if move in move.line_ids.mapped('full_reconcile_id.exchange_move_id'):
raise UserError(_('You cannot cancel an exchange difference journal entry.'))
if move.tax_cash_basis_rec_id:
raise UserError(_('You cannot cancel a tax cash basis journal entry.'))
if not move.journal_id.update_posted and move.id not in excluded_move_ids:
raise UserError(_('You cannot modify a posted entry of this journal.\nFirst you should set the journal to allow cancelling entries.'))
# We remove all the analytics entries for this journal
@@ -3211,7 +3228,11 @@ class AccountMoveLine(models.Model):
for after_rec_dict in cash_basis_subjected:
new_rec = part_rec.create(after_rec_dict)
# if the pair belongs to move being reverted, do not create CABA entry
if cash_basis and not (new_rec.debit_move_id + new_rec.credit_move_id).mapped('move_id.reversed_entry_id'):
if cash_basis and not (
new_rec.debit_move_id.move_id == new_rec.credit_move_id.move_id.reversed_entry_id
or
new_rec.credit_move_id.move_id == new_rec.debit_move_id.move_id.reversed_entry_id
):
new_rec.create_tax_cash_basis_entry(cash_basis_percentage_before_rec)
self.recompute()
@@ -3386,8 +3407,11 @@ class AccountMoveLine(models.Model):
@api.multi
def copy_data(self, default=None):
res = super(AccountMoveLine, self).copy_data(default=default)
if self._context.get('include_business_fields'):
for line, values in zip(self, res):
for line, values in zip(self, res):
# Don't copy the name of a payment term line.
if line.move_id.is_invoice() and line.account_id.user_type_id.type in ('receivable', 'payable'):
values['name'] = ''
if self._context.get('include_business_fields'):
line._copy_data_extend_business_fields(values)
return res
@@ -433,12 +433,23 @@ class AccountReconcileModel(models.Model):
aml.amount_currency AS aml_amount_currency,
account.internal_type AS account_internal_type,
-- Determine a matching or not with the statement line communication using the move.name or move.ref.
regexp_split_to_array(TRIM(REGEXP_REPLACE(move.name, '[^0-9|^\s]', '', 'g')),'\s+')
&& regexp_split_to_array(TRIM(REGEXP_REPLACE(st_line.name, '[^0-9|^\s]', '', 'g')), '\s+')
-- Determine a matching or not with the statement line communication using the aml.name, move.name or move.ref.
(
aml.name IS NOT NULL
AND
TRIM(REGEXP_REPLACE(aml.name, '[^0-9|^\s]', '', 'g')) != ''
AND
regexp_split_to_array(TRIM(REGEXP_REPLACE(aml.name, '[^0-9|^\s]', '', 'g')),'\s+')
&& regexp_split_to_array(TRIM(REGEXP_REPLACE(st_line.name, '[^0-9|^\s]', '', 'g')), '\s+')
)
OR
regexp_split_to_array(TRIM(REGEXP_REPLACE(move.name, '[^0-9|^\s]', '', 'g')),'\s+')
&& regexp_split_to_array(TRIM(REGEXP_REPLACE(st_line.name, '[^0-9|^\s]', '', 'g')), '\s+')
OR
(
move.ref IS NOT NULL
AND
TRIM(REGEXP_REPLACE(move.ref, '[^0-9|^\s]', '', 'g')) != ''
AND
regexp_split_to_array(TRIM(REGEXP_REPLACE(move.ref, '[^0-9|^\s]', '', 'g')),'\s+')
&& regexp_split_to_array(TRIM(REGEXP_REPLACE(st_line.name, '[^0-9|^\s]', '', 'g')), '\s+')
@@ -482,11 +493,22 @@ class AccountReconcileModel(models.Model):
TRIM(REGEXP_REPLACE(st_line.name, '[^0-9|^\s]', '', 'g')) != ''
AND
(
regexp_split_to_array(TRIM(REGEXP_REPLACE(move.name, '[^0-9|^\s]', '', 'g')),'\s+')
&& regexp_split_to_array(TRIM(REGEXP_REPLACE(st_line.name, '[^0-9|^\s]', '', 'g')), '\s+')
(
aml.name IS NOT NULL
AND
TRIM(REGEXP_REPLACE(aml.name, '[^0-9|^\s]', '', 'g')) != ''
AND
regexp_split_to_array(TRIM(REGEXP_REPLACE(aml.name, '[^0-9|^\s]', '', 'g')),'\s+')
&& regexp_split_to_array(TRIM(REGEXP_REPLACE(st_line.name, '[^0-9|^\s]', '', 'g')), '\s+')
)
OR
regexp_split_to_array(TRIM(REGEXP_REPLACE(move.name, '[^0-9|^\s]', '', 'g')),'\s+')
&& regexp_split_to_array(TRIM(REGEXP_REPLACE(st_line.name, '[^0-9|^\s]', '', 'g')), '\s+')
OR
(
move.ref IS NOT NULL
AND
TRIM(REGEXP_REPLACE(move.ref, '[^0-9|^\s]', '', 'g')) != ''
AND
regexp_split_to_array(TRIM(REGEXP_REPLACE(move.ref, '[^0-9|^\s]', '', 'g')),'\s+')
&& regexp_split_to_array(TRIM(REGEXP_REPLACE(st_line.name, '[^0-9|^\s]', '', 'g')), '\s+')
@@ -110,3 +110,16 @@ class TestAccountMove(InvoiceTestCommon):
with self.assertRaises(UserError):
self.test_move.post()
def test_misc_unique_sequence_number(self):
''' Ensure two journal entries can't share the same name when using the same sequence. '''
self.test_move.post()
# Edit the sequence to force the next move to get the same name.
self.test_move.journal_id\
.sequence_id.date_range_ids\
.filtered(lambda seq: seq.date_from == fields.Date.from_string('2016-01-01')).number_next -= 1
test_move2 = self.test_move.copy()
with self.assertRaises(ValidationError):
test_move2.post()
@@ -3,6 +3,7 @@ from odoo.addons.account.tests.invoice_test_common import InvoiceTestCommon
from odoo.tests.common import Form
from odoo.tests import tagged
from odoo import fields
from odoo.exceptions import ValidationError
@tagged('post_install', '-at_install')
@@ -851,7 +852,7 @@ class TestAccountMoveInInvoiceOnchanges(InvoiceTestCommon):
self.term_line_vals_1,
], self.move_vals)
def test_in_invoice_create_draft_refund(self):
def test_in_invoice_create_refund(self):
self.invoice.post()
move_reversal = self.env['account.move.reversal'].with_context(active_ids=self.invoice.ids).create({
@@ -885,8 +886,10 @@ class TestAccountMoveInInvoiceOnchanges(InvoiceTestCommon):
},
{
**self.term_line_vals_1,
'name': '',
'debit': 1128.0,
'credit': 0.0,
'date_maturity': move_reversal.date,
},
], {
**self.move_vals,
@@ -898,7 +901,7 @@ class TestAccountMoveInInvoiceOnchanges(InvoiceTestCommon):
move_reversal = self.env['account.move.reversal'].with_context(active_ids=self.invoice.ids).create({
'date': fields.Date.from_string('2019-02-01'),
'reason': 'because',
'reason': 'no reason again',
'refund_method': 'cancel',
})
reversal = move_reversal.reverse_moves()
@@ -927,8 +930,10 @@ class TestAccountMoveInInvoiceOnchanges(InvoiceTestCommon):
},
{
**self.term_line_vals_1,
'name': '',
'debit': 1128.0,
'credit': 0.0,
'date_maturity': move_reversal.date,
},
], {
**self.move_vals,
@@ -938,6 +943,128 @@ class TestAccountMoveInInvoiceOnchanges(InvoiceTestCommon):
'invoice_payment_state': 'paid',
})
def test_in_invoice_create_refund_multi_currency(self):
''' Test the account.move.reversal takes care about the currency rates when setting
a custom reversal date.
'''
move_form = Form(self.invoice)
move_form.date = '2016-01-01'
move_form.currency_id = self.currency_data['currency']
move_form.save()
self.invoice.post()
# The currency rate changed from 1/3 to 1/2.
move_reversal = self.env['account.move.reversal'].with_context(active_ids=self.invoice.ids).create({
'date': fields.Date.from_string('2017-01-01'),
'reason': 'no reason',
'refund_method': 'refund',
})
reversal = move_reversal.reverse_moves()
reverse_move = self.env['account.move'].browse(reversal['res_id'])
self.assertInvoiceValues(reverse_move, [
{
**self.product_line_vals_1,
'amount_currency': -800.0,
'currency_id': self.currency_data['currency'].id,
'debit': 0.0,
'credit': 400.0,
},
{
**self.product_line_vals_2,
'amount_currency': -160.0,
'currency_id': self.currency_data['currency'].id,
'debit': 0.0,
'credit': 80.0,
},
{
**self.tax_line_vals_1,
'amount_currency': -144.0,
'currency_id': self.currency_data['currency'].id,
'debit': 0.0,
'credit': 72.0,
},
{
**self.tax_line_vals_2,
'amount_currency': -24.0,
'currency_id': self.currency_data['currency'].id,
'debit': 0.0,
'credit': 12.0,
},
{
**self.term_line_vals_1,
'name': '',
'amount_currency': 1128.0,
'currency_id': self.currency_data['currency'].id,
'debit': 564.0,
'credit': 0.0,
'date_maturity': move_reversal.date,
},
], {
**self.move_vals,
'currency_id': self.currency_data['currency'].id,
'date': move_reversal.date,
'state': 'draft',
'ref': 'Reversal of: %s, %s' % (self.invoice.name, move_reversal.reason),
'invoice_payment_state': 'not_paid',
})
move_reversal = self.env['account.move.reversal'].with_context(active_ids=self.invoice.ids).create({
'date': fields.Date.from_string('2017-01-01'),
'reason': 'no reason again',
'refund_method': 'cancel',
})
reversal = move_reversal.reverse_moves()
reverse_move = self.env['account.move'].browse(reversal['res_id'])
self.assertInvoiceValues(reverse_move, [
{
**self.product_line_vals_1,
'amount_currency': -800.0,
'currency_id': self.currency_data['currency'].id,
'debit': 0.0,
'credit': 400.0,
},
{
**self.product_line_vals_2,
'amount_currency': -160.0,
'currency_id': self.currency_data['currency'].id,
'debit': 0.0,
'credit': 80.0,
},
{
**self.tax_line_vals_1,
'amount_currency': -144.0,
'currency_id': self.currency_data['currency'].id,
'debit': 0.0,
'credit': 72.0,
},
{
**self.tax_line_vals_2,
'amount_currency': -24.0,
'currency_id': self.currency_data['currency'].id,
'debit': 0.0,
'credit': 12.0,
},
{
**self.term_line_vals_1,
'name': '',
'amount_currency': 1128.0,
'currency_id': self.currency_data['currency'].id,
'debit': 564.0,
'credit': 0.0,
'date_maturity': move_reversal.date,
},
], {
**self.move_vals,
'currency_id': self.currency_data['currency'].id,
'date': move_reversal.date,
'state': 'posted',
'ref': 'Reversal of: %s, %s' % (self.invoice.name, move_reversal.reason),
'invoice_payment_state': 'paid',
})
def test_in_invoice_create_1(self):
# Test creating an account_move with the least information.
move = self.env['account.move'].create({
@@ -1041,3 +1168,11 @@ class TestAccountMoveInInvoiceOnchanges(InvoiceTestCommon):
**self.move_vals,
'currency_id': self.currency_data['currency'].id,
})
def test_in_invoice_duplicate_supplier_reference(self):
''' Ensure two vendor bills can't share the same vendor reference. '''
self.invoice.ref = 'a supplier reference'
invoice2 = self.invoice.copy()
with self.assertRaises(ValidationError):
invoice2.ref = 'a supplier reference'
@@ -836,7 +836,7 @@ class TestAccountMoveOutInvoiceOnchanges(InvoiceTestCommon):
self.assertRecordValues(invoice_copy, [{'name': 'INV/2019/0043'}])
def test_out_invoice_create_draft_refund(self):
def test_out_invoice_create_refund(self):
self.invoice.post()
move_reversal = self.env['account.move.reversal'].with_context(active_ids=self.invoice.ids).create({
@@ -870,12 +870,14 @@ class TestAccountMoveOutInvoiceOnchanges(InvoiceTestCommon):
},
{
**self.term_line_vals_1,
'name': self.invoice.name,
'name': '',
'debit': 0.0,
'credit': 1410.0,
'date_maturity': move_reversal.date,
},
], {
**self.move_vals,
'name': '/',
'date': move_reversal.date,
'state': 'draft',
'ref': 'Reversal of: %s, %s' % (self.invoice.name, move_reversal.reason),
@@ -913,9 +915,10 @@ class TestAccountMoveOutInvoiceOnchanges(InvoiceTestCommon):
},
{
**self.term_line_vals_1,
'name': self.invoice.name,
'name': '',
'debit': 0.0,
'credit': 1410.0,
'date_maturity': move_reversal.date,
},
], {
**self.move_vals,
@@ -925,6 +928,128 @@ class TestAccountMoveOutInvoiceOnchanges(InvoiceTestCommon):
'invoice_payment_state': 'paid',
})
def test_out_invoice_create_refund_multi_currency(self):
''' Test the account.move.reversal takes care about the currency rates when setting
a custom reversal date.
'''
move_form = Form(self.invoice)
move_form.date = '2016-01-01'
move_form.currency_id = self.currency_data['currency']
move_form.save()
self.invoice.post()
# The currency rate changed from 1/3 to 1/2.
move_reversal = self.env['account.move.reversal'].with_context(active_ids=self.invoice.ids).create({
'date': fields.Date.from_string('2017-01-01'),
'reason': 'no reason',
'refund_method': 'refund',
})
reversal = move_reversal.reverse_moves()
reverse_move = self.env['account.move'].browse(reversal['res_id'])
self.assertInvoiceValues(reverse_move, [
{
**self.product_line_vals_1,
'amount_currency': 1000.0,
'currency_id': self.currency_data['currency'].id,
'debit': 500.0,
'credit': 0.0,
},
{
**self.product_line_vals_2,
'amount_currency': 200.0,
'currency_id': self.currency_data['currency'].id,
'debit': 100.0,
'credit': 0.0,
},
{
**self.tax_line_vals_1,
'amount_currency': 180.0,
'currency_id': self.currency_data['currency'].id,
'debit': 90.0,
'credit': 0.0,
},
{
**self.tax_line_vals_2,
'amount_currency': 30.0,
'currency_id': self.currency_data['currency'].id,
'debit': 15.0,
'credit': 0.0,
},
{
**self.term_line_vals_1,
'name': '',
'amount_currency': -1410.0,
'currency_id': self.currency_data['currency'].id,
'debit': 0.0,
'credit': 705.0,
'date_maturity': move_reversal.date,
},
], {
**self.move_vals,
'currency_id': self.currency_data['currency'].id,
'date': move_reversal.date,
'state': 'draft',
'ref': 'Reversal of: %s, %s' % (self.invoice.name, move_reversal.reason),
'invoice_payment_state': 'not_paid',
})
move_reversal = self.env['account.move.reversal'].with_context(active_ids=self.invoice.ids).create({
'date': fields.Date.from_string('2017-01-01'),
'reason': 'no reason',
'refund_method': 'cancel',
})
reversal = move_reversal.reverse_moves()
reverse_move = self.env['account.move'].browse(reversal['res_id'])
self.assertInvoiceValues(reverse_move, [
{
**self.product_line_vals_1,
'amount_currency': 1000.0,
'currency_id': self.currency_data['currency'].id,
'debit': 500.0,
'credit': 0.0,
},
{
**self.product_line_vals_2,
'amount_currency': 200.0,
'currency_id': self.currency_data['currency'].id,
'debit': 100.0,
'credit': 0.0,
},
{
**self.tax_line_vals_1,
'amount_currency': 180.0,
'currency_id': self.currency_data['currency'].id,
'debit': 90.0,
'credit': 0.0,
},
{
**self.tax_line_vals_2,
'amount_currency': 30.0,
'currency_id': self.currency_data['currency'].id,
'debit': 15.0,
'credit': 0.0,
},
{
**self.term_line_vals_1,
'name': '',
'amount_currency': -1410.0,
'currency_id': self.currency_data['currency'].id,
'debit': 0.0,
'credit': 705.0,
'date_maturity': move_reversal.date,
},
], {
**self.move_vals,
'currency_id': self.currency_data['currency'].id,
'date': move_reversal.date,
'state': 'posted',
'ref': 'Reversal of: %s, %s' % (self.invoice.name, move_reversal.reason),
'invoice_payment_state': 'paid',
})
def test_out_invoice_create_1(self):
# Test creating an account_move with the least information.
move = self.env['account.move'].create({