Files
odoo_source/addons/account/tests/test_reconciliation.py
T

2302 lines
108 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import time
import unittest
from datetime import timedelta
from odoo import api, fields
from odoo.addons.account.tests.common import TestAccountReconciliationCommon
from odoo.exceptions import UserError
from odoo.tests import Form, tagged
@tagged('post_install', '-at_install')
class TestReconciliationExec(TestAccountReconciliationCommon):
def test_statement_usd_invoice_eur_transaction_eur(self):
customer_move_lines, supplier_move_lines = self.make_customer_and_supplier_flows(self.currency_euro_id, 30, self.bank_journal_usd, 42, 30, self.currency_euro_id)
self.assertRecordValues(customer_move_lines, [
{'debit': 0.0, 'credit': 30.0, 'amount_currency': -42, 'currency_id': self.currency_usd_id},
{'debit': 30.0, 'credit': 0.0, 'amount_currency': 42, 'currency_id': self.currency_usd_id},
])
self.assertRecordValues(supplier_move_lines, [
{'debit': 30.0, 'credit': 0.0, 'amount_currency': 42, 'currency_id': self.currency_usd_id},
{'debit': 0.0, 'credit': 30.0, 'amount_currency': -42, 'currency_id': self.currency_usd_id},
])
def test_statement_usd_invoice_usd_transaction_usd(self):
customer_move_lines, supplier_move_lines = self.make_customer_and_supplier_flows(self.currency_usd_id, 50, self.bank_journal_usd, 50, 0, False)
self.assertRecordValues(customer_move_lines, [
{'debit': 0.0, 'credit': 32.70, 'amount_currency': -50, 'currency_id': self.currency_usd_id},
{'debit': 32.70, 'credit': 0.0, 'amount_currency': 50, 'currency_id': self.currency_usd_id},
])
self.assertRecordValues(supplier_move_lines, [
{'debit': 32.70, 'credit': 0.0, 'amount_currency': 50, 'currency_id': self.currency_usd_id},
{'debit': 0.0, 'credit': 32.70, 'amount_currency': -50, 'currency_id': self.currency_usd_id},
])
def test_statement_usd_invoice_usd_transaction_eur(self):
customer_move_lines, supplier_move_lines = self.make_customer_and_supplier_flows(self.currency_usd_id, 50, self.bank_journal_usd, 50, 40, self.currency_euro_id)
self.assertRecordValues(customer_move_lines, [
{'debit': 0.0, 'credit': 40.0, 'amount_currency': -50, 'currency_id': self.currency_usd_id},
{'debit': 40.0, 'credit': 0.0, 'amount_currency': 50, 'currency_id': self.currency_usd_id},
])
exchange_lines = customer_move_lines.mapped('full_reconcile_id.exchange_move_id.line_ids')
self.assertRecordValues(exchange_lines, [
{'debit': 7.30, 'credit': 0.0, 'account_id': self.account_rcv.id},
{'debit': 0.0, 'credit': 7.30, 'account_id': self.diff_income_account.id},
])
self.assertRecordValues(supplier_move_lines, [
{'debit': 40.0, 'credit': 0.0, 'amount_currency': 50, 'currency_id': self.currency_usd_id},
{'debit': 0.0, 'credit': 40.0, 'amount_currency': -50, 'currency_id': self.currency_usd_id},
])
exchange_lines = supplier_move_lines.mapped('full_reconcile_id.exchange_move_id.line_ids')
self.assertRecordValues(exchange_lines, [
{'debit': 0.0, 'credit': 7.30, 'account_id': self.account_rsa.id},
{'debit': 7.30, 'credit': 0.0, 'account_id': self.diff_expense_account.id},
])
def test_statement_usd_invoice_chf_transaction_chf(self):
self.env['res.currency.rate'].create({
'rate': 1.3086,
'currency_id': self.env.ref('base.CHF').id,
'name': '2010-01-02',
})
customer_move_lines, supplier_move_lines = self.make_customer_and_supplier_flows(self.currency_swiss_id, 50, self.bank_journal_usd, 42, 50, self.currency_swiss_id)
self.assertRecordValues(customer_move_lines, [
{'debit': 0.0, 'credit': 27.47, 'amount_currency': -50, 'currency_id': self.currency_swiss_id},
{'debit': 27.47, 'credit': 0.0, 'amount_currency': 42, 'currency_id': self.currency_usd_id},
])
exchange_lines = customer_move_lines.mapped('full_reconcile_id.exchange_move_id.line_ids')
self.assertRecordValues(exchange_lines, [
{'debit': 0.0, 'credit': 10.74, 'account_id': self.account_rcv.id},
{'debit': 10.74, 'credit': 0.0, 'account_id': self.diff_expense_account.id},
])
self.assertRecordValues(supplier_move_lines, [
{'debit': 27.47, 'credit': 0.0, 'amount_currency': 50, 'currency_id': self.currency_swiss_id},
{'debit': 0.0, 'credit': 27.47, 'amount_currency': -42, 'currency_id': self.currency_usd_id},
])
exchange_lines = supplier_move_lines.mapped('full_reconcile_id.exchange_move_id.line_ids')
self.assertRecordValues(exchange_lines, [
{'debit': 10.74, 'credit': 0.0, 'account_id': self.account_rsa.id},
{'debit': 0.0, 'credit': 10.74, 'account_id': self.diff_income_account.id},
])
def test_statement_eur_invoice_usd_transaction_usd(self):
customer_move_lines, supplier_move_lines = self.make_customer_and_supplier_flows(self.currency_usd_id, 50, self.bank_journal_euro, 40, 50, self.currency_usd_id)
self.assertRecordValues(customer_move_lines, [
{'debit': 0.0, 'credit': 40.0, 'amount_currency': -50, 'currency_id': self.currency_usd_id},
{'debit': 40.0, 'credit': 0.0, 'amount_currency': 50, 'currency_id': self.currency_usd_id},
])
exchange_lines = customer_move_lines.mapped('full_reconcile_id.exchange_move_id.line_ids')
self.assertRecordValues(exchange_lines, [
{'debit': 7.30, 'credit': 0.0, 'account_id': self.account_rcv.id},
{'debit': 0.0, 'credit': 7.30, 'account_id': self.diff_income_account.id},
])
self.assertRecordValues(supplier_move_lines, [
{'debit': 40.0, 'credit': 0.0, 'amount_currency': 50, 'currency_id': self.currency_usd_id},
{'debit': 0.0, 'credit': 40.0, 'amount_currency': -50, 'currency_id': self.currency_usd_id},
])
exchange_lines = supplier_move_lines.mapped('full_reconcile_id.exchange_move_id.line_ids')
self.assertRecordValues(exchange_lines, [
{'debit': 0.0, 'credit': 7.30, 'account_id': self.account_rsa.id},
{'debit': 7.30, 'credit': 0.0, 'account_id': self.diff_expense_account.id},
])
def test_statement_eur_invoice_usd_transaction_eur(self):
customer_move_lines, supplier_move_lines = self.make_customer_and_supplier_flows(self.currency_usd_id, 50, self.bank_journal_euro, 40, 0.0, False)
self.assertRecordValues(customer_move_lines, [
{'debit': 0.0, 'credit': 40.0, 'amount_currency': 0.0, 'currency_id': False},
{'debit': 40.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': False},
])
self.assertRecordValues(supplier_move_lines, [
{'debit': 40.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': False},
{'debit': 0.0, 'credit': 40.0, 'amount_currency': 0.0, 'currency_id': False},
])
def test_statement_euro_invoice_usd_transaction_chf(self):
customer_move_lines, supplier_move_lines = self.make_customer_and_supplier_flows(self.currency_usd_id, 50, self.bank_journal_euro, 42, 50, self.currency_swiss_id)
self.assertRecordValues(customer_move_lines, [
{'debit': 0.0, 'credit': 42.0, 'amount_currency': -50, 'currency_id': self.currency_swiss_id},
{'debit': 42.0, 'credit': 0.0, 'amount_currency': 50, 'currency_id': self.currency_swiss_id},
])
self.assertRecordValues(supplier_move_lines, [
{'debit': 42.0, 'credit': 0.0, 'amount_currency': 50, 'currency_id': self.currency_swiss_id},
{'debit': 0.0, 'credit': 42.0, 'amount_currency': -50, 'currency_id': self.currency_swiss_id},
])
def test_statement_euro_invoice_usd_transaction_euro_full(self):
# Create a customer invoice of 50 USD.
partner = self.env['res.partner'].create({'name': 'test'})
move = self.env['account.move'].with_context(default_type='out_invoice').create({
'type': 'out_invoice',
'partner_id': partner.id,
'invoice_date': '%s-07-01' % time.strftime('%Y'),
'date': '%s-07-01' % time.strftime('%Y'),
'currency_id': self.currency_usd_id,
'invoice_line_ids': [
(0, 0, {'quantity': 1, 'price_unit': 50.0, 'name': 'test'})
],
})
move.post()
# Create a bank statement of 40 EURO.
bank_stmt = self.env['account.bank.statement'].create({
'journal_id': self.bank_journal_euro.id,
'date': '%s-01-01' % time.strftime('%Y'),
'line_ids': [
(0, 0, {
'name': 'test',
'partner_id': partner.id,
'amount': 40.0,
'date': '%s-01-01' % time.strftime('%Y')
})
],
})
# Reconcile the bank statement with the invoice.
receivable_line = move.line_ids.filtered(lambda line: line.account_id.user_type_id.type in ('receivable', 'payable'))
bank_stmt.line_ids[0].process_reconciliation(counterpart_aml_dicts=[{
'move_line': receivable_line,
'debit': 0.0,
'credit': 32.7,
'name': 'test_statement_euro_invoice_usd_transaction_euro_full',
}], new_aml_dicts=[{
'debit': 0.0,
'credit': 7.3,
'name': 'exchange difference',
'account_id': self.diff_income_account.id
}])
self.assertRecordValues(bank_stmt.move_line_ids, [
{'debit': 0.0, 'credit': 7.3, 'amount_currency': 0.0, 'currency_id': False},
{'debit': 0.0, 'credit': 32.7, 'amount_currency': 0.0, 'currency_id': False},
{'debit': 40.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': False},
])
# The invoice should be paid, as the payments totally cover its total
self.assertEqual(move.invoice_payment_state, 'paid', 'The invoice should be paid by now')
self.assertTrue(receivable_line.reconciled, 'The invoice should be totally reconciled')
self.assertTrue(receivable_line.full_reconcile_id, 'The invoice should have a full reconcile number')
self.assertEqual(receivable_line.amount_residual, 0, 'The invoice should be totally reconciled')
self.assertEqual(receivable_line.amount_residual_currency, 0, 'The invoice should be totally reconciled')
@unittest.skip('adapt to new accounting')
def test_balanced_exchanges_gain_loss(self):
# The point of this test is to show that we handle correctly the gain/loss exchanges during reconciliations in foreign currencies.
# For instance, with a company set in EUR, and a USD rate set to 0.033,
# the reconciliation of an invoice of 2.00 USD (60.61 EUR) and a bank statement of two lines of 1.00 USD (30.30 EUR)
# will lead to an exchange loss, that should be handled correctly within the journal items.
env = api.Environment(self.cr, self.uid, {})
# We update the currency rate of the currency USD in order to force the gain/loss exchanges in next steps
rateUSDbis = env.ref("base.rateUSDbis")
rateUSDbis.write({
'name': time.strftime('%Y-%m-%d') + ' 00:00:00',
'rate': 0.033,
})
# We create a customer invoice of 2.00 USD
invoice = self.account_invoice_model.create({
'partner_id': self.partner_agrolait_id,
'currency_id': self.currency_usd_id,
'name': 'Foreign invoice with exchange gain',
'account_id': self.account_rcv_id,
'type': 'out_invoice',
'invoice_date': time.strftime('%Y-%m-%d'),
'date': time.strftime('%Y-%m-%d'),
'journal_id': self.bank_journal_usd_id,
'invoice_line': [
(0, 0, {
'name': 'line that will lead to an exchange gain',
'quantity': 1,
'price_unit': 2,
})
]
})
invoice.post()
# We create a bank statement with two lines of 1.00 USD each.
statement = self.acc_bank_stmt_model.create({
'journal_id': self.bank_journal_usd_id,
'date': time.strftime('%Y-%m-%d'),
'line_ids': [
(0, 0, {
'name': 'half payment',
'partner_id': self.partner_agrolait_id,
'amount': 1.0,
'date': time.strftime('%Y-%m-%d')
}),
(0, 0, {
'name': 'second half payment',
'partner_id': self.partner_agrolait_id,
'amount': 1.0,
'date': time.strftime('%Y-%m-%d')
})
]
})
# We process the reconciliation of the invoice line with the two bank statement lines
line_id = None
for l in invoice.line_id:
if l.account_id.id == self.account_rcv_id:
line_id = l
break
for statement_line in statement.line_ids:
statement_line.process_reconciliation([
{'counterpart_move_line_id': line_id.id, 'credit': 1.0, 'debit': 0.0, 'name': line_id.name}
])
# The invoice should be paid, as the payments totally cover its total
self.assertEqual(invoice.state, 'paid', 'The invoice should be paid by now')
reconcile = None
for payment in invoice.payment_ids:
reconcile = payment.reconcile_model_id
break
# The invoice should be reconciled (entirely, not a partial reconciliation)
self.assertTrue(reconcile, 'The invoice should be totally reconciled')
result = {}
exchange_loss_line = None
for line in reconcile.line_id:
res_account = result.setdefault(line.account_id, {'debit': 0.0, 'credit': 0.0, 'count': 0})
res_account['debit'] = res_account['debit'] + line.debit
res_account['credit'] = res_account['credit'] + line.credit
res_account['count'] += 1
if line.credit == 0.01:
exchange_loss_line = line
# We should be able to find a move line of 0.01 EUR on the Debtors account, being the cent we lost during the currency exchange
self.assertTrue(exchange_loss_line, 'There should be one move line of 0.01 EUR in credit')
# The journal items of the reconciliation should have their debit and credit total equal
# Besides, the total debit and total credit should be 60.61 EUR (2.00 USD)
self.assertEqual(sum(res['debit'] for res in result.values()), 60.61)
self.assertEqual(sum(res['credit'] for res in result.items()), 60.61)
counterpart_exchange_loss_line = None
for line in exchange_loss_line.move_id.line_id:
if line.account_id.id == self.account_fx_expense_id:
counterpart_exchange_loss_line = line
# We should be able to find a move line of 0.01 EUR on the Foreign Exchange Loss account
self.assertTrue(counterpart_exchange_loss_line, 'There should be one move line of 0.01 EUR on account "Foreign Exchange Loss"')
def test_manual_reconcile_wizard_opw678153(self):
def create_move(name, amount, amount_currency, currency_id):
debit_line_vals = {
'name': name,
'debit': amount > 0 and amount or 0.0,
'credit': amount < 0 and -amount or 0.0,
'account_id': self.account_rcv.id,
'amount_currency': amount_currency,
'currency_id': currency_id,
}
credit_line_vals = debit_line_vals.copy()
credit_line_vals['debit'] = debit_line_vals['credit']
credit_line_vals['credit'] = debit_line_vals['debit']
credit_line_vals['account_id'] = self.account_rsa.id
credit_line_vals['amount_currency'] = -debit_line_vals['amount_currency']
vals = {
'journal_id': self.bank_journal_euro.id,
'line_ids': [(0,0, debit_line_vals), (0, 0, credit_line_vals)]
}
return self.env['account.move'].create(vals).id
move_list_vals = [
('1', -1.83, 0, self.currency_swiss_id),
('2', 728.35, 795.05, self.currency_swiss_id),
('3', -4.46, 0, self.currency_swiss_id),
('4', 0.32, 0, self.currency_swiss_id),
('5', 14.72, 16.20, self.currency_swiss_id),
('6', -737.10, -811.25, self.currency_swiss_id),
]
move_ids = []
for name, amount, amount_currency, currency_id in move_list_vals:
move_ids.append(create_move(name, amount, amount_currency, currency_id))
aml_recs = self.env['account.move.line'].search([('move_id', 'in', move_ids), ('account_id', '=', self.account_rcv.id), ('reconciled', '=', False)])
aml_recs.reconcile()
for aml in aml_recs:
self.assertTrue(aml.reconciled, 'The journal item should be totally reconciled')
self.assertEqual(aml.amount_residual, 0, 'The journal item should be totally reconciled')
self.assertEqual(aml.amount_residual_currency, 0, 'The journal item should be totally reconciled')
move_list_vals = [
('2', 728.35, 795.05, self.currency_swiss_id),
('3', -4.46, 0, False),
('4', 0.32, 0, False),
('5', 14.72, 16.20, self.currency_swiss_id),
('6', -737.10, -811.25, self.currency_swiss_id),
]
move_ids = []
for name, amount, amount_currency, currency_id in move_list_vals:
move_ids.append(create_move(name, amount, amount_currency, currency_id))
aml_recs = self.env['account.move.line'].search([('move_id', 'in', move_ids), ('account_id', '=', self.account_rcv.id), ('reconciled', '=', False)])
aml_recs.reconcile(self.account_rsa, self.bank_journal_usd)
for aml in aml_recs:
self.assertTrue(aml.reconciled, 'The journal item should be totally reconciled')
self.assertEqual(aml.amount_residual, 0, 'The journal item should be totally reconciled')
self.assertEqual(aml.amount_residual_currency, 0, 'The journal item should be totally reconciled')
def test_manual_reconcile_wizard_same_account(self):
move_ids = self.env['account.move']
debit_line_vals = {
'name': '1',
'debit': 728.35,
'credit': 0.0,
'account_id': self.account_rcv.id,
'amount_currency': 795.05,
'currency_id': self.currency_swiss_id,
}
credit_line_vals = {
'name': '1',
'debit': 0.0,
'credit': 728.35,
'account_id': self.account_rsa.id,
'amount_currency': -795.05,
'currency_id': self.currency_swiss_id,
}
vals = {
'journal_id': self.bank_journal_euro.id,
'date': time.strftime('%Y') + '-02-15',
'line_ids': [(0,0, debit_line_vals), (0, 0, credit_line_vals)]
}
move_ids += self.env['account.move'].create(vals)
debit_line_vals = {
'name': '2',
'debit': 0.0,
'credit': 737.10,
'account_id': self.account_rcv.id,
'amount_currency': -811.25,
'currency_id': self.currency_swiss_id,
}
credit_line_vals = {
'name': '2',
'debit': 737.10,
'credit': 0.0,
'account_id': self.account_rsa.id,
'amount_currency': 811.25,
'currency_id': self.currency_swiss_id,
}
vals = {
'journal_id': self.bank_journal_euro.id,
'date': time.strftime('%Y') + '-07-15',
'line_ids': [(0,0, debit_line_vals), (0, 0, credit_line_vals)]
}
move_ids += self.env['account.move'].create(vals)
account_move_line = move_ids.mapped('line_ids').filtered(lambda l: l.account_id == self.account_rcv)
writeoff_vals = [{
'account_id': self.account_rcv.id,
'journal_id': self.bank_journal_euro.id,
'date': time.strftime('%Y') + '-04-15',
'debit': 8.75,
'credit': 0.0
}]
writeoff_line = account_move_line._create_writeoff(writeoff_vals)
(account_move_line + writeoff_line).reconcile()
self.assertEqual(len(writeoff_line), 1, "The writeoff_line (balance_line) should have only one moves line")
self.assertTrue(all(l.reconciled for l in writeoff_line), 'The balance lines should be totally reconciled')
self.assertTrue(all(l.reconciled for l in account_move_line), 'The move lines should be totally reconciled')
def test_reconcile_bank_statement_with_payment_and_writeoff(self):
# Use case:
# Company is in EUR, create a bill for 80 USD and register payment of 80 USD.
# create a bank statement in USD bank journal with a bank statement line of 85 USD
# Reconcile bank statement with payment and put the remaining 5 USD in bank fees or another account.
invoice = self.create_invoice(type='out_invoice', invoice_amount=80, currency_id=self.currency_usd_id)
# register payment on invoice
payment = self.env['account.payment'].create({'payment_type': 'inbound',
'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
'partner_type': 'customer',
'partner_id': self.partner_agrolait_id,
'amount': 80,
'currency_id': self.currency_usd_id,
'payment_date': time.strftime('%Y') + '-07-15',
'journal_id': self.bank_journal_usd.id,
'invoice_ids': [(6, 0, invoice.ids)],
})
payment.post()
bank_move_line = payment.move_line_ids.filtered(lambda line: line.account_id.internal_type == 'liquidity')
# create bank statement
bank_stmt = self.acc_bank_stmt_model.create({
'journal_id': self.bank_journal_usd.id,
'date': time.strftime('%Y') + '-07-15',
})
bank_stmt_line = self.acc_bank_stmt_line_model.create({'name': 'payment',
'statement_id': bank_stmt.id,
'partner_id': self.partner_agrolait_id,
'amount': 85,
'date': time.strftime('%Y') + '-07-15',})
#reconcile the statement with invoice and put remaining in another account
bank_stmt_line.process_reconciliation(payment_aml_rec= bank_move_line, new_aml_dicts=[{
'account_id': self.diff_income_account.id,
'debit': 0,
'credit': 5,
'name': 'bank fees',
}])
# Check that move lines associated to bank_statement are correct
bank_stmt_aml = self.env['account.move.line'].search([('statement_id', '=', bank_stmt.id)])
bank_stmt_aml |= bank_stmt_aml.mapped('move_id').mapped('line_ids')
self.assertEqual(len(bank_stmt_aml), 4, "The bank statement should have 4 moves lines")
lines = {
self.account_usd.id: [
{'debit': 3.27, 'credit': 0.0, 'amount_currency': 5, 'currency_id': self.currency_usd_id},
{'debit': 52.33, 'credit': 0, 'amount_currency': 80, 'currency_id': self.currency_usd_id}
],
self.diff_income_account.id: {'debit': 0.0, 'credit': 3.27, 'amount_currency': -5, 'currency_id': self.currency_usd_id},
self.account_rcv.id: {'debit': 0.0, 'credit': 52.33, 'amount_currency': -80, 'currency_id': self.currency_usd_id},
}
payments = bank_stmt_aml.mapped('payment_id')
# creation and reconciliation of the over-amount statement
# has created an another payment
self.assertEqual(len(payments), 2)
# Check amount of second, automatically created payment
self.assertEqual((payments - payment).amount, 5)
for aml in bank_stmt_aml:
line = lines[aml.account_id.id]
if type(line) == list:
# find correct line inside the list
if line[0]['debit'] == round(aml.debit, 2):
line = line[0]
else:
line = line[1]
self.assertEqual(round(aml.debit, 2), line['debit'])
self.assertEqual(round(aml.credit, 2), line['credit'])
self.assertEqual(round(aml.amount_currency, 2), line['amount_currency'])
self.assertEqual(aml.currency_id.id, line['currency_id'])
def test_partial_reconcile_currencies_01(self):
# client Account (payable, rsa)
# Debit Credit
# --------------------------------------------------------
# Pay a : 25/0.5 = 50 | Inv a : 50/0.5 = 100
# Pay b: 50/0.75 = 66.66 | Inv b : 50/0.75 = 66.66
# Pay c: 25/0.8 = 31.25 |
#
# Debit_currency = 100 | Credit currency = 100
# Debit = 147.91 | Credit = 166.66
# Balance Debit = 18.75
# Counterpart Credit goes in Exchange diff
dest_journal_id = self.env['account.journal'].search([('type', '=', 'purchase'), ('company_id', '=', self.company.id)], limit=1)
self.bank_journal_euro.write({'default_debit_account_id': self.account_rsa.id,
'default_credit_account_id': self.account_rsa.id})
dest_journal_id.write({'default_debit_account_id': self.account_rsa.id,
'default_credit_account_id': self.account_rsa.id})
# Setting up rates for USD (main_company is in EUR)
self.env['res.currency.rate'].create({'name': time.strftime('%Y') + '-' + '07' + '-01',
'rate': 0.5,
'currency_id': self.currency_usd_id,
'company_id': self.company.id})
self.env['res.currency.rate'].create({'name': time.strftime('%Y') + '-' + '08' + '-01',
'rate': 0.75,
'currency_id': self.currency_usd_id,
'company_id': self.company.id})
self.env['res.currency.rate'].create({'name': time.strftime('%Y') + '-' + '09' + '-01',
'rate': 0.80,
'currency_id': self.currency_usd_id,
'company_id': self.company.id})
# Preparing Invoices (from vendor)
invoice_a = self.env['account.move'].with_context(default_type='in_invoice').create({
'type': 'in_invoice',
'partner_id': self.partner_agrolait_id,
'currency_id': self.currency_usd_id,
'invoice_date': '%s-07-01' % time.strftime('%Y'),
'date': '%s-07-01' % time.strftime('%Y'),
'invoice_line_ids': [
(0, 0, {'product_id': self.product.id, 'quantity': 1, 'price_unit': 50.0})
],
})
invoice_b = self.env['account.move'].with_context(default_type='in_invoice').create({
'type': 'in_invoice',
'partner_id': self.partner_agrolait_id,
'currency_id': self.currency_usd_id,
'invoice_date': '%s-08-01' % time.strftime('%Y'),
'date': '%s-08-01' % time.strftime('%Y'),
'invoice_line_ids': [
(0, 0, {'product_id': self.product.id, 'quantity': 1, 'price_unit': 50.0})
],
})
(invoice_a + invoice_b).post()
# Preparing Payments
# One partial for invoice_a (fully assigned to it)
payment_a = self.env['account.payment'].create({'payment_type': 'outbound',
'amount': 25,
'currency_id': self.currency_usd_id,
'journal_id': self.bank_journal_euro.id,
'company_id': self.company.id,
'payment_date': time.strftime('%Y') + '-' + '07' + '-01',
'partner_id': self.partner_agrolait_id,
'payment_method_id': self.env.ref('account.account_payment_method_manual_out').id,
'destination_journal_id': dest_journal_id.id,
'partner_type': 'supplier'})
# One that will complete the payment of a, the rest goes to b
payment_b = self.env['account.payment'].create({'payment_type': 'outbound',
'amount': 50,
'currency_id': self.currency_usd_id,
'journal_id': self.bank_journal_euro.id,
'company_id': self.company.id,
'payment_date': time.strftime('%Y') + '-' + '08' + '-01',
'partner_id': self.partner_agrolait_id,
'payment_method_id': self.env.ref('account.account_payment_method_manual_out').id,
'destination_journal_id': dest_journal_id.id,
'partner_type': 'supplier'})
# The last one will complete the payment of b
payment_c = self.env['account.payment'].create({'payment_type': 'outbound',
'amount': 25,
'currency_id': self.currency_usd_id,
'journal_id': self.bank_journal_euro.id,
'company_id': self.company.id,
'payment_date': time.strftime('%Y') + '-' + '09' + '-01',
'partner_id': self.partner_agrolait_id,
'payment_method_id': self.env.ref('account.account_payment_method_manual_out').id,
'destination_journal_id': dest_journal_id.id,
'partner_type': 'supplier'})
payment_a.post()
payment_b.post()
payment_c.post()
# Assigning payments to invoices
debit_line_a = payment_a.move_line_ids.filtered(lambda l: l.debit and l.account_id == dest_journal_id.default_debit_account_id)
debit_line_b = payment_b.move_line_ids.filtered(lambda l: l.debit and l.account_id == dest_journal_id.default_debit_account_id)
debit_line_c = payment_c.move_line_ids.filtered(lambda l: l.debit and l.account_id == dest_journal_id.default_debit_account_id)
invoice_a.js_assign_outstanding_line(debit_line_a.id)
invoice_a.js_assign_outstanding_line(debit_line_b.id)
invoice_b.js_assign_outstanding_line(debit_line_b.id)
invoice_b.js_assign_outstanding_line(debit_line_c.id)
# Asserting correctness (only in the payable account)
full_reconcile = False
reconciled_amls = (debit_line_a + debit_line_b + debit_line_c + (invoice_a + invoice_b).mapped('line_ids'))\
.filtered(lambda l: l.account_id == self.account_rsa)
for aml in reconciled_amls:
self.assertEqual(aml.amount_residual, 0.0)
self.assertEqual(aml.amount_residual_currency, 0.0)
self.assertTrue(aml.reconciled)
if not full_reconcile:
full_reconcile = aml.full_reconcile_id
else:
self.assertTrue(aml.full_reconcile_id == full_reconcile)
full_rec_move = full_reconcile.exchange_move_id
# Globally check whether the amount is correct
self.assertEqual(sum(full_rec_move.mapped('line_ids.debit')), 18.75)
# Checking if the direction of the move is correct
full_rec_payable = full_rec_move.line_ids.filtered(lambda l: l.account_id == self.account_rsa)
self.assertEqual(full_rec_payable.balance, 18.75)
def test_unreconcile(self):
# Use case:
# 2 invoices paid with a single payment. Unreconcile the payment with one invoice, the
# other invoice should remain reconciled.
inv1 = self.create_invoice(invoice_amount=10, currency_id=self.currency_usd_id)
inv2 = self.create_invoice(invoice_amount=20, currency_id=self.currency_usd_id)
payment = self.env['account.payment'].create({
'payment_type': 'inbound',
'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
'partner_type': 'customer',
'partner_id': self.partner_agrolait_id,
'amount': 100,
'currency_id': self.currency_usd_id,
'journal_id': self.bank_journal_usd.id,
})
payment.post()
credit_aml = payment.move_line_ids.filtered('credit')
# Check residual before assignation
self.assertAlmostEqual(inv1.amount_residual, 10)
self.assertAlmostEqual(inv2.amount_residual, 20)
# Assign credit and residual
inv1.js_assign_outstanding_line(credit_aml.id)
inv2.js_assign_outstanding_line(credit_aml.id)
self.assertAlmostEqual(inv1.amount_residual, 0)
self.assertAlmostEqual(inv2.amount_residual, 0)
# Unreconcile one invoice at a time and check residual
credit_aml.remove_move_reconcile()
self.assertAlmostEqual(inv1.amount_residual, 10)
self.assertAlmostEqual(inv2.amount_residual, 20)
def test_unreconcile_exchange(self):
# Use case:
# - Company currency in EUR
# - Create 2 rates for USD:
# 1.0 on 2018-01-01
# 0.5 on 2018-02-01
# - Create an invoice on 2018-01-02 of 111 USD
# - Register a payment on 2018-02-02 of 111 USD
# - Unreconcile the payment
self.env['res.currency.rate'].create({
'name': time.strftime('%Y') + '-07-01',
'rate': 1.0,
'currency_id': self.currency_usd_id,
'company_id': self.company.id
})
self.env['res.currency.rate'].create({
'name': time.strftime('%Y') + '-08-01',
'rate': 0.5,
'currency_id': self.currency_usd_id,
'company_id': self.company.id
})
inv = self.create_invoice(invoice_amount=111, currency_id=self.currency_usd_id)
payment = self.env['account.payment'].create({
'payment_type': 'inbound',
'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
'partner_type': 'customer',
'partner_id': self.partner_agrolait_id,
'amount': 111,
'currency_id': self.currency_usd_id,
'journal_id': self.bank_journal_usd.id,
'payment_date': time.strftime('%Y') + '-08-01',
})
payment.post()
credit_aml = payment.move_line_ids.filtered('credit')
# Check residual before assignation
self.assertAlmostEqual(inv.amount_residual, 111)
# Assign credit, check exchange move and residual
inv.js_assign_outstanding_line(credit_aml.id)
self.assertEqual(len(payment.move_line_ids.mapped('full_reconcile_id').exchange_move_id), 1)
self.assertAlmostEqual(inv.amount_residual, 0)
# Unreconcile invoice and check residual
credit_aml.with_context(invoice_id=inv.id).remove_move_reconcile()
self.assertAlmostEqual(inv.amount_residual, 111)
def test_revert_payment_and_reconcile(self):
payment = self.env['account.payment'].create({
'payment_method_id': self.inbound_payment_method.id,
'payment_type': 'inbound',
'partner_type': 'customer',
'partner_id': self.partner_agrolait_id,
'journal_id': self.bank_journal_usd.id,
'payment_date': '2018-06-04',
'amount': 666,
})
payment.post()
self.assertEqual(len(payment.move_line_ids), 2)
bank_line = payment.move_line_ids.filtered(lambda l: l.account_id.id == self.bank_journal_usd.default_debit_account_id.id)
customer_line = payment.move_line_ids - bank_line
self.assertEqual(len(bank_line), 1)
self.assertEqual(len(customer_line), 1)
self.assertNotEqual(bank_line.id, customer_line.id)
self.assertEqual(bank_line.move_id.id, customer_line.move_id.id)
move = bank_line.move_id
# Reversing the payment's move
reversed_move = move._reverse_moves([{'date': '2018-06-04'}])
self.assertEqual(len(reversed_move), 1)
self.assertEqual(len(reversed_move.line_ids), 2)
# Testing the reconciliation matching between the move lines and their reversed counterparts
reversed_bank_line = reversed_move.line_ids.filtered(lambda l: l.account_id.id == self.bank_journal_usd.default_debit_account_id.id)
reversed_customer_line = reversed_move.line_ids - reversed_bank_line
self.assertEqual(len(reversed_bank_line), 1)
self.assertEqual(len(reversed_customer_line), 1)
self.assertNotEqual(reversed_bank_line.id, reversed_customer_line.id)
self.assertEqual(reversed_bank_line.move_id.id, reversed_customer_line.move_id.id)
self.assertEqual(reversed_bank_line.full_reconcile_id.id, bank_line.full_reconcile_id.id)
self.assertEqual(reversed_customer_line.full_reconcile_id.id, customer_line.full_reconcile_id.id)
def test_revert_payment_and_reconcile_exchange(self):
# A reversal of a reconciled payment which created a currency exchange entry, should create reversal moves
# which move lines should be reconciled two by two with the original move's lines
def _determine_debit_credit_line(move):
line_ids_reconciliable = move.line_ids.filtered(lambda l: l.account_id.reconcile or l.account_id.internal_type == 'liquidity')
return line_ids_reconciliable.filtered(lambda l: l.debit), line_ids_reconciliable.filtered(lambda l: l.credit)
def _move_revert_test_pair(move, revert):
self.assertTrue(move.line_ids)
self.assertTrue(revert.line_ids)
move_lines = _determine_debit_credit_line(move)
revert_lines = _determine_debit_credit_line(revert)
# in the case of the exchange entry, only one pair of lines will be found
if move_lines[0] and revert_lines[1]:
self.assertTrue(move_lines[0].full_reconcile_id.exists())
self.assertEqual(move_lines[0].full_reconcile_id.id, revert_lines[1].full_reconcile_id.id)
if move_lines[1] and revert_lines[0]:
self.assertTrue(move_lines[1].full_reconcile_id.exists())
self.assertEqual(move_lines[1].full_reconcile_id.id, revert_lines[0].full_reconcile_id.id)
self.env['res.currency.rate'].create({
'name': time.strftime('%Y') + '-07-01',
'rate': 1.0,
'currency_id': self.currency_usd_id,
'company_id': self.company.id
})
self.env['res.currency.rate'].create({
'name': time.strftime('%Y') + '-08-01',
'rate': 0.5,
'currency_id': self.currency_usd_id,
'company_id': self.company.id
})
inv = self.create_invoice(invoice_amount=111, currency_id=self.currency_usd_id)
payment = self.env['account.payment'].create({
'payment_type': 'inbound',
'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
'partner_type': 'customer',
'partner_id': self.partner_agrolait_id,
'amount': 111,
'currency_id': self.currency_usd_id,
'journal_id': self.bank_journal_usd.id,
'payment_date': time.strftime('%Y') + '-08-01',
})
payment.post()
credit_aml = payment.move_line_ids.filtered('credit')
inv.js_assign_outstanding_line(credit_aml.id)
self.assertTrue(inv.invoice_payment_state == 'paid', 'The invoice should be paid')
exchange_reconcile = payment.move_line_ids.mapped('full_reconcile_id')
exchange_move = exchange_reconcile.exchange_move_id
payment_move = payment.move_line_ids[0].move_id
reverted_payment_move = payment_move._reverse_moves([{'date': time.strftime('%Y') + '-08-01'}], cancel=True)
# After reversal of payment, the invoice should be open
self.assertTrue(inv.state == 'posted', 'The invoice should be open again')
self.assertFalse(exchange_reconcile.exists())
reverted_exchange_move = self.env['account.move'].search([('journal_id', '=', exchange_move.journal_id.id), ('ref', 'ilike', exchange_move.name)], limit=1)
_move_revert_test_pair(payment_move, reverted_payment_move)
_move_revert_test_pair(exchange_move, reverted_exchange_move)
def test_partial_reconcile_currencies_02(self):
####
# Day 1: Invoice Cust/001 to customer (expressed in USD)
# Market value of USD (day 1): 1 USD = 0.5 EUR
# * Dr. 100 USD / 50 EUR - Accounts receivable
# * Cr. 100 USD / 50 EUR - Revenue
####
dest_journal_id = self.env['account.journal'].search(
[('type', '=', 'purchase'),
('company_id', '=', self.company.id)],
limit=1)
# Delete any old rate - to make sure that we use the ones we need.
old_rates = self.env['res.currency.rate'].search(
[('currency_id', '=', self.currency_usd_id)])
old_rates.unlink()
self.env['res.currency.rate'].create({
'currency_id': self.currency_usd_id,
'name': time.strftime('%Y') + '-01-01',
'rate': 2,
})
invoice_cust_1 = self.env['account.move'].with_context(default_type='out_invoice').create({
'type': 'out_invoice',
'partner_id': self.partner_agrolait_id,
'invoice_date': '%s-01-01' % time.strftime('%Y'),
'date': '%s-01-01' % time.strftime('%Y'),
'currency_id': self.currency_usd_id,
'invoice_line_ids': [
(0, 0, {'quantity': 1, 'price_unit': 100.0, 'name': 'product that cost 100'})
],
})
invoice_cust_1.post()
aml = invoice_cust_1.invoice_line_ids[0]
self.assertEqual(aml.credit, 50.0)
#####
# Day 2: Receive payment for half invoice Cust/1 (in USD)
# -------------------------------------------------------
# Market value of USD (day 2): 1 USD = 1 EUR
# Payment transaction:
# * Dr. 50 USD / 50 EUR - EUR Bank (valued at market price
# at the time of receiving the money)
# * Cr. 50 USD / 50 EUR - Accounts Receivable
#####
self.env['res.currency.rate'].create({
'currency_id': self.currency_usd_id,
'name': time.strftime('%Y') + '-01-02',
'rate': 1,
})
# register payment on invoice
payment = self.env['account.payment'].create(
{'payment_type': 'inbound',
'payment_method_id': self.env.ref(
'account.account_payment_method_manual_in').id,
'partner_type': 'customer',
'partner_id': self.partner_agrolait_id,
'amount': 50,
'currency_id': self.currency_usd_id,
'payment_date': time.strftime('%Y') + '-01-02',
'journal_id': dest_journal_id.id,
'invoice_ids': [(6, 0, invoice_cust_1.ids)],
})
payment.post()
# We expect at this point that the invoice should still be open,
# because they owe us still 50 CC.
self.assertEqual(invoice_cust_1.invoice_payment_state, 'not_paid', 'Invoice is in status %s' % invoice_cust_1.state)
def test_multiple_term_reconciliation_opw_1906665(self):
'''Test that when registering a payment to an invoice with multiple
payment term lines the reconciliation happens against the line
with the earliest date_maturity
'''
payment_term = self.env['account.payment.term'].create({
'name': 'Pay in 2 installments',
'line_ids': [
# Pay 50% immediately
(0, 0, {
'value': 'percent',
'value_amount': 50,
}),
# Pay the rest after 14 days
(0, 0, {
'value': 'balance',
'days': 14,
})
],
})
# can't use self.create_invoice because it validates and we need to set payment_term_id
invoice = self.create_invoice_partner(
partner_id=self.partner_agrolait_id,
payment_term_id=payment_term.id,
currency_id=self.currency_usd_id,
)
payment = self.env['account.payment'].create({
'payment_type': 'inbound',
'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
'partner_type': 'customer',
'partner_id': self.partner_agrolait_id,
'amount': 25,
'currency_id': self.currency_usd_id,
'journal_id': self.bank_journal_usd.id,
})
payment.post()
receivable_line = payment.move_line_ids.filtered('credit')
invoice.js_assign_outstanding_line(receivable_line.id)
self.assertTrue(receivable_line.matched_debit_ids)
def test_reconciliation_cash_basis01(self):
# Simulates an expense made up by 2 lines
# one is subject to a cash basis tax
# the other is not subject to tax
company = self.company
company.tax_cash_basis_journal_id = self.cash_basis_journal
purchase_move = self.env['account.move'].create({
'journal_id': self.purchase_journal.id,
'line_ids': [
(0, 0, {'account_id': self.account_rsa.id, 'debit': 0.0, 'credit': 100.0}),
(0, 0, {'account_id': self.account_rsa.id, 'debit': 0.0, 'credit': 50.0}),
(0, 0, {'account_id': self.expense_account.id, 'debit': 50.0, 'credit': 0.0}),
(0, 0, {'account_id': self.expense_account.id, 'debit': 83.33, 'credit': 0.0, 'tax_ids': [(4, self.tax_cash_basis.id)], 'tax_exigible': False}),
(0, 0, {
'account_id': self.tax_waiting_account.id,
'debit': 16.67,
'credit': 0.0,
'tax_repartition_line_id': self.tax_cash_basis.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id,
'tax_base_amount': 83.33,
'tax_exigible': False
}),
],
})
purchase_move.post()
payment_move = self.env['account.move'].create({
'journal_id': self.bank_journal_euro.id,
'line_ids': [
(0, 0, {'account_id': self.account_rsa.id, 'debit': 150.0, 'credit': 0.0}),
(0, 0, {'account_id': self.account_euro.id, 'debit': 0.0, 'credit': 150.0}),
],
})
payment_move.post()
(purchase_move + payment_move).invalidate_cache(['line_ids'])
to_reconcile = (purchase_move + payment_move).mapped('line_ids').filtered(lambda l: l.account_id.internal_type == 'payable')
to_reconcile.reconcile()
cash_basis_moves = self.env['account.move'].search([('journal_id', '=', self.cash_basis_journal.id)])
self.assertEqual(len(cash_basis_moves), 2)
self.assertTrue(cash_basis_moves.exists())
# check reconciliation in Payable account
purchase_move_line_ids = purchase_move.line_ids.sorted()
payment_move_line_ids = payment_move.line_ids.sorted()
self.assertTrue(purchase_move_line_ids[0].full_reconcile_id.exists())
self.assertEqual(purchase_move_line_ids[0].full_reconcile_id.reconciled_line_ids,
purchase_move_line_ids[0] + purchase_move_line_ids[1] + payment_move_line_ids[0])
cash_basis_aml_ids = cash_basis_moves.mapped('line_ids')
# check reconciliation in the tax waiting account
self.assertTrue(purchase_move_line_ids[4].full_reconcile_id.exists())
self.assertEqual(purchase_move_line_ids[4].full_reconcile_id.reconciled_line_ids,
cash_basis_aml_ids.filtered(lambda l: l.account_id == self.tax_waiting_account) + purchase_move_line_ids[4])
self.assertEqual(len(cash_basis_aml_ids), 8)
# check amounts
cash_basis_move1 = cash_basis_moves.filtered(lambda m: m.amount_total == 33.34)
cash_basis_move2 = cash_basis_moves.filtered(lambda m: m.amount_total == 66.66)
self.assertTrue(cash_basis_move1.exists())
self.assertTrue(cash_basis_move2.exists())
# For first move
move_lines = cash_basis_move1.line_ids
base_amount_tax_lines = move_lines.filtered(lambda l: l.account_id == self.tax_base_amount_account)
self.assertEqual(len(base_amount_tax_lines), 2)
self.assertAlmostEqual(sum(base_amount_tax_lines.mapped('credit')), 27.78)
self.assertAlmostEqual(sum(base_amount_tax_lines.mapped('debit')), 27.78)
self.assertAlmostEqual((move_lines - base_amount_tax_lines).filtered(lambda l: l.account_id == self.tax_waiting_account).credit,
5.56)
self.assertAlmostEqual((move_lines - base_amount_tax_lines).filtered(lambda l: l.account_id == self.tax_final_account).debit,
5.56)
# For second move
move_lines = cash_basis_move2.line_ids
base_amount_tax_lines = move_lines.filtered(lambda l: l.account_id == self.tax_base_amount_account)
self.assertEqual(len(base_amount_tax_lines), 2)
self.assertAlmostEqual(sum(base_amount_tax_lines.mapped('credit')), 55.55)
self.assertAlmostEqual(sum(base_amount_tax_lines.mapped('debit')), 55.55)
self.assertAlmostEqual((move_lines - base_amount_tax_lines).filtered(lambda l: l.account_id == self.tax_waiting_account).credit,
11.11)
self.assertAlmostEqual((move_lines - base_amount_tax_lines).filtered(lambda l: l.account_id == self.tax_final_account).debit,
11.11)
def test_reconciliation_cash_basis02(self):
# Simulates an invoice made up by 2 lines
# both subjected to cash basis taxes
# with 2 payment terms
# And partial payment not martching any payment term
company = self.company
company.tax_cash_basis_journal_id = self.cash_basis_journal
tax_cash_basis10percent = self.tax_cash_basis.copy({'amount': 10})
tax_waiting_account10 = self.tax_waiting_account.copy({
'name': 'TAX WAIT 10',
'code': 'TWAIT1',
})
purchase_move = self.env['account.move'].create({
'journal_id': self.purchase_journal.id,
'line_ids': [
(0, 0, {'account_id': self.account_rsa.id, 'debit': 0.0, 'credit': 50.0}),
(0, 0, {'account_id': self.account_rsa.id, 'debit': 0.0, 'credit': 105.0}),
(0, 0, {'name': 'expenseTaxed 10%', 'account_id': self.expense_account.id, 'debit': 50.0, 'credit': 0.0, 'tax_ids': [(4, tax_cash_basis10percent.id)], 'tax_exigible': False}),
(0, 0, {
'name': 'TaxLine0',
'account_id': tax_waiting_account10.id,
'debit': 5.0,
'credit': 0.0,
'tax_repartition_line_id': tax_cash_basis10percent.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id,
'tax_exigible': False,
'tax_base_amount': 50.00,
}),
(0, 0, {'name': 'expenseTaxed 20%', 'account_id': self.expense_account.id, 'debit': 83.33, 'credit': 0.0, 'tax_ids': [(4, self.tax_cash_basis.id)], 'tax_exigible': False}),
(0, 0, {
'name': 'TaxLine1',
'account_id': self.tax_waiting_account.id,
'debit': 16.67,
'credit': 0.0,
'tax_repartition_line_id': self.tax_cash_basis.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id,
'tax_exigible': False,
'tax_base_amount': 83.33,
}),
],
})
purchase_move.post()
payment_move0 = self.env['account.move'].create({
'journal_id': self.bank_journal_euro.id,
'line_ids': [
(0, 0, {'account_id': self.account_rsa.id, 'debit': 40.0, 'credit': 0.0}),
(0, 0, {'account_id': self.account_euro.id, 'debit': 0.0, 'credit': 40.0}),
],
})
payment_move0.post()
payment_move1 = self.env['account.move'].create({
'journal_id': self.bank_journal_euro.id,
'line_ids': [
(0, 0, {'account_id': self.account_rsa.id, 'debit': 115.0, 'credit': 0.0}),
(0, 0, {'account_id': self.account_euro.id, 'debit': 0.0, 'credit': 115.0}),
],
})
payment_move1.post()
(purchase_move + payment_move0).mapped('line_ids').sorted().filtered(lambda l: l.account_id.internal_type == 'payable').reconcile()
(purchase_move + payment_move1).mapped('line_ids').sorted().filtered(lambda l: l.account_id.internal_type == 'payable').reconcile()
cash_basis_moves = self.env['account.move'].search([('journal_id', '=', self.cash_basis_journal.id)])
self.assertEqual(len(cash_basis_moves), 3)
self.assertTrue(cash_basis_moves.exists())
# check reconciliation in Payable account
purchase_move_line_ids = purchase_move.line_ids.sorted()
self.assertTrue(purchase_move_line_ids[0].full_reconcile_id.exists())
self.assertEqual(purchase_move_line_ids[0].full_reconcile_id.reconciled_line_ids,
(purchase_move + payment_move0 + payment_move1).mapped('line_ids').filtered(lambda l: l.account_id.internal_type == 'payable'))
cash_basis_aml_ids = cash_basis_moves.mapped('line_ids')
# check reconciliation in the tax waiting account
self.assertTrue(purchase_move_line_ids[3].full_reconcile_id.exists())
self.assertEqual(purchase_move_line_ids[3].full_reconcile_id.reconciled_line_ids,
cash_basis_aml_ids.filtered(lambda l: l.account_id == tax_waiting_account10) + purchase_move_line_ids[3])
self.assertTrue(purchase_move_line_ids[5].full_reconcile_id.exists())
self.assertEqual(purchase_move_line_ids[5].full_reconcile_id.reconciled_line_ids,
cash_basis_aml_ids.filtered(lambda l: l.account_id == self.tax_waiting_account) + purchase_move_line_ids[5])
self.assertEqual(len(cash_basis_aml_ids), 24)
# check amounts
expected_move_amounts = [
{'base_20': 56.45, 'tax_20': 11.29, 'base_10': 33.87, 'tax_10': 3.39},
{'base_20': 5.38, 'tax_20': 1.08, 'base_10': 3.23, 'tax_10': 0.32},
{'base_20': 21.50, 'tax_20': 4.30, 'base_10': 12.90, 'tax_10': 1.29},
]
index = 0
for cb_move in cash_basis_moves:
expected = expected_move_amounts[index]
move_lines = cb_move.line_ids
base_amount_tax_lines20per = move_lines.filtered(lambda l: l.account_id == self.tax_base_amount_account and '20%' in l.name)
base_amount_tax_lines10per = move_lines.filtered(lambda l: l.account_id == self.tax_base_amount_account and '10%' in l.name)
self.assertEqual(len(base_amount_tax_lines20per), 2)
self.assertAlmostEqual(sum(base_amount_tax_lines20per.mapped('credit')), expected['base_20'])
self.assertAlmostEqual(sum(base_amount_tax_lines20per.mapped('debit')), expected['base_20'])
self.assertEqual(len(base_amount_tax_lines10per), 2)
self.assertAlmostEqual(sum(base_amount_tax_lines10per.mapped('credit')), expected['base_10'])
self.assertAlmostEqual(sum(base_amount_tax_lines10per.mapped('debit')), expected['base_10'])
self.assertAlmostEqual(
(move_lines - base_amount_tax_lines20per - base_amount_tax_lines10per)
.filtered(lambda l: l.account_id == self.tax_waiting_account).credit,
expected['tax_20']
)
self.assertAlmostEqual(
(move_lines - base_amount_tax_lines20per - base_amount_tax_lines10per)
.filtered(lambda l: 'TaxLine1' in l.name).debit,
expected['tax_20']
)
self.assertAlmostEqual(
(move_lines - base_amount_tax_lines20per - base_amount_tax_lines10per)
.filtered(lambda l: l.account_id == tax_waiting_account10).credit,
expected['tax_10']
)
self.assertAlmostEqual(
(move_lines - base_amount_tax_lines20per - base_amount_tax_lines10per)
.filtered(lambda l: 'TaxLine0' in l.name).debit,
expected['tax_10']
)
index += 1
def test_reconciliation_to_check(self):
partner = self.env['res.partner'].create({'name': 'UncertainPartner'})
currency = self.env.company.currency_id
invoice = self.create_invoice_partner(currency_id=currency.id, partner_id=partner.id)
journal = self.env['account.journal'].create({'name': 'Bank', 'type': 'bank', 'code': 'THE', 'restrict_mode_hash_table':False})
statement = self.make_payment(invoice, journal, 50)
st_line = statement.line_ids
previous_move_lines = st_line.journal_entry_ids.ids
previous_name = st_line.move_name
with self.assertRaises(UserError): #you need edition mode to be able to change it
st_line.with_context(suspense_moves_mode=False).process_reconciliation(
counterpart_aml_dicts=[],
new_aml_dicts=[{
'debit': 0,
'credit': 50,
'name': 'exchange difference',
'account_id': self.diff_income_account.id
}],
)
st_line.with_context(suspense_moves_mode=True).process_reconciliation(
counterpart_aml_dicts=[],
new_aml_dicts=[{
'debit': 0,
'credit': 50,
'name': 'exchange difference',
'account_id': self.diff_income_account.id
}],
)
self.assertEqual(previous_name, st_line.move_name) # the name of the move hasnt changed
self.assertNotEqual(previous_move_lines, st_line.journal_entry_ids.ids) # the lines are new
def test_reconciliation_cash_basis_fx_01(self):
"""
Company's Currency EUR
Having issued an invoice at date Nov-21-2018 as:
Accounts Amount Currency Debit(EUR) Credit(EUR)
---------------------------------------------------------------------
Expenses 5,301.00 USD 106,841.65 0.00
Taxes 848.16 USD 17,094.66 0.00
Payables -6,149.16 USD 0.00 123,936.31
On Dec-20-2018 user issues an FX Journal Entry as:
Accounts Amount Currency Debit(EUR) Credit(EUR)
---------------------------------------------------------------------
Payables 0.00 USD 167.86 0.00
FX Gains 0.00 USD 0.00 167.86
On Same day user records a payment for:
Accounts Amount Currency Debit(EUR) Credit(EUR)
---------------------------------------------------------------------
Payables 6,149.16 USD 123,768.45 0.00
Bank -6,149.16 USD 0.00 123,768.45
And then reconciles the Payables Items which shall render only one Tax
Cash Basis Journal Entry because of the actual payment, i.e.
amount_currency != 0:
Accounts Amount Currency Debit(EUR) Credit(EUR)
---------------------------------------------------------------------
Tax Base Acc. 0.00 USD 106,841.65 0.00
Tax Base Acc. 0.00 USD 0.00 106,841.65
Creditable Taxes 848.16 USD 17,094.66 0.00
Taxes -848.16 USD 0.00 17,094.66
"""
company = self.company
company.country_id = self.ref('base.us')
company.tax_cash_basis_journal_id = self.cash_basis_journal
# Purchase
purchase_move = self.env['account.move'].create({
'journal_id': self.purchase_journal.id,
'line_ids': [
(0, 0, {
'name': 'expenseTaxed',
'account_id': self.expense_account.id,
'currency_id': self.currency_usd_id,
'tax_ids': [(4, self.tax_cash_basis.id)],
'tax_exigible': False,
'debit': 106841.65, 'credit': 0.0, 'amount_currency': 5301.00,
}),
(0, 0, {
'name': 'TaxLine',
'account_id': self.tax_waiting_account.id,
'currency_id': self.currency_usd_id,
'tax_repartition_line_id': self.tax_cash_basis.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id,
'tax_exigible': False,
'tax_base_amount': 106841.65,
'debit': 17094.66, 'credit': 0.0, 'amount_currency': 848.16,
}),
(0, 0, {
'name': 'Payable',
'account_id': self.account_rsa.id,
'currency_id': self.currency_usd_id,
'debit': 0.0, 'credit': 123936.31, 'amount_currency': -6149.16,
}),
],
})
purchase_move.post()
# FX 01 Move
fx_move_01 = self.env['account.move'].create({
'journal_id': self.fx_journal.id,
'line_ids': [
(0, 0, {
'account_id': self.account_rsa.id,
'currency_id': self.currency_usd_id,
'debit': 167.86, 'credit': 0.0, 'amount_currency': 0.00,
}),
(0, 0, {
'account_id': self.diff_income_account.id,
'currency_id': self.currency_usd_id,
'debit': 0.0, 'credit': 167.86, 'amount_currency': 0.0,
}),
],
})
fx_move_01.post()
# Payment Move
payment_move = self.env['account.move'].create({
'journal_id': self.bank_journal_usd.id,
'line_ids': [
(0, 0, {
'account_id': self.account_rsa.id,
'currency_id': self.currency_usd_id,
'debit': 123768.45, 'credit': 0.0, 'amount_currency': 6149.16,
}),
(0, 0, {
'account_id': self.account_usd.id,
'currency_id': self.currency_usd_id,
'debit': 0.0, 'credit': 123768.45, 'amount_currency': -6149.16,
}),
],
})
payment_move.post()
to_reconcile = (
(purchase_move + payment_move + fx_move_01).mapped('line_ids')
.filtered(lambda l: l.account_id.internal_type == 'payable'))
to_reconcile.reconcile()
# check reconciliation in Payable account
purchase_line_ids = purchase_move.line_ids.sorted()
fx_move_01_line_ids = fx_move_01.line_ids.sorted()
payment_move_line_ids = payment_move.line_ids.sorted()
self.assertTrue(purchase_line_ids[2].full_reconcile_id.exists())
self.assertEqual(
purchase_line_ids[2].full_reconcile_id.reconciled_line_ids,
purchase_line_ids[2] + fx_move_01_line_ids[0] + payment_move_line_ids[0])
# check cash basis
cash_basis_moves = self.env['account.move'].search(
[('journal_id', '=', self.cash_basis_journal.id)])
self.assertEqual(len(cash_basis_moves), 1)
cash_basis_aml_ids = cash_basis_moves.mapped('line_ids')
self.assertEqual(len(cash_basis_aml_ids), 4)
# check amounts
cash_basis_move1 = cash_basis_moves.filtered(lambda m: m.currency_id.is_zero(sum(line.credit for line in m.line_ids) - 123936.31))
self.assertTrue(cash_basis_move1.exists())
# For first move
move_lines = cash_basis_moves.line_ids
base_amount_tax_lines = move_lines.filtered(
lambda l: l.account_id == self.tax_base_amount_account)
self.assertEqual(len(base_amount_tax_lines), 2)
self.assertAlmostEqual(
sum(base_amount_tax_lines.mapped('credit')), 106841.65)
self.assertAlmostEqual(
sum(base_amount_tax_lines.mapped('debit')), 106841.65)
self.assertAlmostEqual(
(move_lines - base_amount_tax_lines)
.filtered(lambda l: l.account_id == self.tax_waiting_account)
.credit, 17094.66)
self.assertAlmostEqual(
(move_lines - base_amount_tax_lines)
.filtered(lambda l: l.account_id == self.tax_final_account)
.debit, 17094.66)
def test_reconciliation_cash_basis_fx_02(self):
"""
Company's Currency EUR
Having issued an invoice at date Nov-21-2018 as:
Accounts Amount Currency Debit(EUR) Credit(EUR)
---------------------------------------------------------------------
Expenses 5,301.00 USD 106,841.65 0.00
Taxes 848.16 USD 17,094.66 0.00
Payables -6,149.16 USD 0.00 123,936.31
On Nov-30-2018 user issues an FX Journal Entry as:
Accounts Amount Currency Debit(EUR) Credit(EUR)
---------------------------------------------------------------------
FX Losses 0.00 USD 1.572.96 0.00
Payables 0.00 USD 0.00 1.572.96
On Dec-20-2018 user issues an FX Journal Entry as:
Accounts Amount Currency Debit(EUR) Credit(EUR)
---------------------------------------------------------------------
Payables 0.00 USD 1.740.82 0.00
FX Gains 0.00 USD 0.00 1.740.82
On Same day user records a payment for:
Accounts Amount Currency Debit(EUR) Credit(EUR)
---------------------------------------------------------------------
Payables 6,149.16 USD 123,768.45 0.00
Bank -6,149.16 USD 0.00 123,768.45
And then reconciles the Payables Items which shall render only one Tax
Cash Basis Journal Entry because of the actual payment, i.e.
amount_currency != 0:
Accounts Amount Currency Debit(EUR) Credit(EUR)
---------------------------------------------------------------------
Tax Base Acc. 0.00 USD 106,841.65 0.00
Tax Base Acc. 0.00 USD 0.00 106,841.65
Creditable Taxes 848.16 USD 17,094.66 0.00
Taxes -848.16 USD 0.00 17,094.66
"""
company = self.company
company.country_id = self.ref('base.us')
company.tax_cash_basis_journal_id = self.cash_basis_journal
# Purchase
purchase_move = self.env['account.move'].create({
'journal_id': self.purchase_journal.id,
'line_ids': [
(0, 0, {
'name': 'expenseTaxed',
'account_id': self.expense_account.id,
'currency_id': self.currency_usd_id,
'tax_ids': [(4, self.tax_cash_basis.id)],
'tax_exigible': False,
'debit': 106841.65, 'credit': 0.0, 'amount_currency': 5301.00,
}),
(0, 0, {
'name': 'TaxLine',
'account_id': self.tax_waiting_account.id,
'currency_id': self.currency_usd_id,
'tax_repartition_line_id': self.tax_cash_basis.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id,
'tax_exigible': False,
'tax_base_amount': 106841.65,
'debit': 17094.66, 'credit': 0.0, 'amount_currency': 848.16,
}),
(0, 0, {
'name': 'Payable',
'account_id': self.account_rsa.id,
'currency_id': self.currency_usd_id,
'debit': 0.0, 'credit': 123936.31, 'amount_currency': -6149.16,
}),
],
})
purchase_move.post()
# FX 01 Move
fx_move_01 = self.env['account.move'].create({
'journal_id': self.fx_journal.id,
'line_ids': [
(0, 0, {
'account_id': self.account_rsa.id,
'currency_id': self.currency_usd_id,
'debit': 0.0, 'credit': 1572.96, 'amount_currency': 0.00,
}),
(0, 0, {
'account_id': self.diff_expense_account.id,
'currency_id': self.currency_usd_id,
'debit': 1572.96, 'credit': 0.0, 'amount_currency': 0.0,
}),
],
})
fx_move_01.post()
# FX 02 Move
fx_move_02 = self.env['account.move'].create({
'journal_id': self.fx_journal.id,
'line_ids': [
(0, 0, {
'account_id': self.account_rsa.id,
'currency_id': self.currency_usd_id,
'debit': 1740.82, 'credit': 0.0, 'amount_currency': 0.00,
}),
(0, 0, {
'account_id': self.diff_income_account.id,
'currency_id': self.currency_usd_id,
'debit': 0.0, 'credit': 1740.82, 'amount_currency': 0.0,
}),
],
})
fx_move_02.post()
# Payment Move
payment_move = self.env['account.move'].create({
'journal_id': self.bank_journal_usd.id,
'line_ids': [
(0, 0, {
'account_id': self.account_rsa.id,
'currency_id': self.currency_usd_id,
'debit': 123768.45, 'credit': 0.0, 'amount_currency': 6149.16,
}),
(0, 0, {
'account_id': self.account_usd.id,
'currency_id': self.currency_usd_id,
'debit': 0.0, 'credit': 123768.45, 'amount_currency': -6149.16,
}),
],
})
payment_move.post()
to_reconcile = (
(purchase_move + payment_move + fx_move_01 + fx_move_02)
.mapped('line_ids')
.filtered(lambda l: l.account_id.internal_type == 'payable'))
to_reconcile.reconcile()
# check reconciliation in Payable account
purchase_move_line_ids = purchase_move.line_ids.sorted()
fx_move_01_line_ids = fx_move_01.line_ids.sorted()
fx_move_02_line_ids = fx_move_02.line_ids.sorted()
payment_move_line_ids = payment_move.line_ids.sorted()
self.assertTrue(purchase_move_line_ids[2].full_reconcile_id.exists())
self.assertEqual(
purchase_move_line_ids[2].full_reconcile_id.reconciled_line_ids,
purchase_move_line_ids[2] + fx_move_01_line_ids[0] + fx_move_02_line_ids[0] +
payment_move_line_ids[0])
# check cash basis
cash_basis_moves = self.env['account.move'].search(
[('journal_id', '=', self.cash_basis_journal.id)])
self.assertEqual(len(cash_basis_moves), 1)
cash_basis_aml_ids = cash_basis_moves.mapped('line_ids')
self.assertEqual(len(cash_basis_aml_ids), 4)
# check amounts
cash_basis_move1 = cash_basis_moves.filtered(lambda m: m.currency_id.is_zero(sum(line.credit for line in m.line_ids) - 123936.31))
self.assertTrue(cash_basis_move1.exists())
# For first move
move_lines = cash_basis_move1.line_ids
base_amount_tax_lines = move_lines.filtered(
lambda l: l.account_id == self.tax_base_amount_account)
self.assertEqual(len(base_amount_tax_lines), 2)
self.assertAlmostEqual(
sum(base_amount_tax_lines.mapped('credit')), 106841.65)
self.assertAlmostEqual(
sum(base_amount_tax_lines.mapped('debit')), 106841.65)
self.assertAlmostEqual(
(move_lines - base_amount_tax_lines)
.filtered(lambda l: l.account_id == self.tax_waiting_account)
.credit, 17094.66)
self.assertAlmostEqual(
(move_lines - base_amount_tax_lines)
.filtered(lambda l: l.account_id == self.tax_final_account)
.debit, 17094.66)
def test_reconciliation_cash_basis_revert(self):
company = self.company
company.tax_cash_basis_journal_id = self.cash_basis_journal
tax_cash_basis10percent = self.tax_cash_basis.copy({'amount': 10})
self.tax_waiting_account.reconcile = True
tax_waiting_account10 = self.tax_waiting_account.copy({
'name': 'TAX WAIT 10',
'code': 'TWAIT1',
})
AccountMoveLine = self.env['account.move.line'].with_context(check_move_validity=False)
# Purchase
purchase_move = self.env['account.move'].create({
'name': 'invoice',
'journal_id': self.purchase_journal.id,
})
purchase_payable_line0 = AccountMoveLine.create({
'account_id': self.account_rsa.id,
'credit': 175,
'move_id': purchase_move.id,
})
AccountMoveLine.create({
'name': 'expenseTaxed 10%',
'account_id': self.expense_account.id,
'debit': 50,
'move_id': purchase_move.id,
'tax_ids': [(4, tax_cash_basis10percent.id, False)],
})
tax_line0 = AccountMoveLine.create({
'name': 'TaxLine0',
'account_id': tax_waiting_account10.id,
'debit': 5,
'move_id': purchase_move.id,
'tax_repartition_line_id': tax_cash_basis10percent.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id,
'tax_base_amount': 50,
})
AccountMoveLine.create({
'name': 'expenseTaxed 20%',
'account_id': self.expense_account.id,
'debit': 100,
'move_id': purchase_move.id,
'tax_ids': [(4, self.tax_cash_basis.id, False)],
})
tax_line1 = AccountMoveLine.create({
'name': 'TaxLine1',
'account_id': self.tax_waiting_account.id,
'debit': 20,
'move_id': purchase_move.id,
'tax_repartition_line_id': self.tax_cash_basis.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id,
'tax_base_amount': 100,
})
purchase_move.post()
reverted = purchase_move._reverse_moves(cancel=True)
self.assertTrue(reverted.exists())
for inv_line in [purchase_payable_line0, tax_line0, tax_line1]:
self.assertTrue(inv_line.full_reconcile_id.exists())
reverted_expected = reverted.line_ids.filtered(lambda l: l.account_id == inv_line.account_id)
self.assertEqual(len(reverted_expected), 1)
self.assertEqual(reverted_expected.full_reconcile_id, inv_line.full_reconcile_id)
def test_reconciliation_revert_future(self):
company = self.company
company.tax_cash_basis_journal_id = self.cash_basis_journal
tax_cash_basis10percent = self.tax_cash_basis.copy({'amount': 10})
self.tax_waiting_account.reconcile = True
tax_waiting_account10 = self.tax_waiting_account.copy({
'name': 'TAX WAIT 10',
'code': 'TWAIT1',
})
AccountMoveLine = self.env['account.move.line'].with_context(check_move_validity=False)
# Purchase
purchase_move = self.env['account.move'].create({
'name': 'invoice',
'journal_id': self.purchase_journal.id,
})
purchase_payable_line0 = AccountMoveLine.create({
'account_id': self.account_rsa.id,
'credit': 175,
'move_id': purchase_move.id,
})
AccountMoveLine.create({
'name': 'expenseTaxed 10%',
'account_id': self.expense_account.id,
'debit': 50,
'move_id': purchase_move.id,
'tax_ids': [(4, tax_cash_basis10percent.id, False)],
})
tax_line0 = AccountMoveLine.create({
'name': 'TaxLine0',
'account_id': tax_waiting_account10.id,
'debit': 5,
'move_id': purchase_move.id,
'tax_repartition_line_id': tax_cash_basis10percent.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id,
'tax_base_amount': 50,
})
AccountMoveLine.create({
'name': 'expenseTaxed 20%',
'account_id': self.expense_account.id,
'debit': 100,
'move_id': purchase_move.id,
'tax_ids': [(4, self.tax_cash_basis.id, False)],
})
tax_line1 = AccountMoveLine.create({
'name': 'TaxLine1',
'account_id': self.tax_waiting_account.id,
'debit': 20,
'move_id': purchase_move.id,
'tax_repartition_line_id': self.tax_cash_basis.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id,
'tax_base_amount': 100,
})
purchase_move.post()
reverse_date = fields.Date.today() + timedelta(days=+7)
revertWidget = self.env['account.move.reversal'].create({
'move_id': purchase_move.id,
'date': reverse_date,
'reason': ['cancel'],
})
action = revertWidget.reverse_moves();
reverse_move = self.env['account.move'].search([('reversed_entry_id', '=', purchase_move.id)])
self.assertEqual(len(reverse_move), 1)
self.assertEqual(reverse_move.state, 'draft')
self.assertEqual(reverse_move.auto_post, True)
self.assertEqual(reverse_move.date, reverse_date)
def test_reconciliation_cash_basis_foreign_currency_low_values(self):
self.company.tax_cash_basis_journal_id = self.cash_basis_journal
journal = self.env['account.journal'].create({
'name': 'Bank', 'type': 'bank', 'code': 'THE',
'currency_id': self.currency_usd_id,
})
usd = self.env['res.currency'].browse(self.currency_usd_id)
usd.rate_ids.unlink()
self.env['res.currency.rate'].create({
'name': time.strftime('%Y-01-01'),
'rate': 1/17.0,
'currency_id': self.currency_usd_id,
'company_id': self.company.id,
})
move_form = Form(self.env['account.move'].with_context(default_type='out_invoice'))
move_form.partner_id = self.partner_agrolait
move_form.currency_id = self.env.ref('base.USD')
move_form.invoice_date = time.strftime('%Y') + '-07-01'
with move_form.invoice_line_ids.new() as line_form:
line_form.name = 'test line'
line_form.price_unit = 50
line_form.tax_ids.clear()
invoice = move_form.save()
move_form = Form(invoice)
with move_form.invoice_line_ids.edit(0) as line_form:
line_form.tax_ids.add(self.tax_cash_basis)
invoice = move_form.save()
invoice.post()
self.assertTrue(invoice.currency_id != self.env.user.company_id.currency_id)
# First Payment
payment0 = self.make_payment(invoice, journal, amount=59.99)
self.assertEqual(invoice.amount_residual, 0.01)
tax_waiting_line = invoice.line_ids.filtered(lambda l: l.account_id == self.tax_waiting_account)
self.assertTrue(tax_waiting_line.exists())
self.assertFalse(tax_waiting_line.reconciled)
move_caba0 = tax_waiting_line.matched_debit_ids.debit_move_id.move_id
self.assertTrue(move_caba0.exists())
self.assertEqual(move_caba0.journal_id, self.env.user.company_id.tax_cash_basis_journal_id)
pay_receivable_line0 = payment0.move_line_ids.filtered(lambda l: l.account_id == self.account_rcv)
self.assertTrue(pay_receivable_line0.reconciled)
self.assertEqual(pay_receivable_line0.matched_debit_ids, move_caba0.tax_cash_basis_rec_id)
# Second Payment
payment1 = self.make_payment(invoice, journal, 0.01)
self.assertEqual(invoice.amount_residual, 0)
self.assertEqual(invoice.invoice_payment_state, 'paid')
self.assertTrue(tax_waiting_line.reconciled)
move_caba1 = tax_waiting_line.matched_debit_ids.mapped('debit_move_id').mapped('move_id').filtered(lambda m: m != move_caba0)
self.assertEqual(len(move_caba1.exists()), 1)
self.assertEqual(move_caba1.journal_id, self.env.user.company_id.tax_cash_basis_journal_id)
pay_receivable_line1 = payment1.move_line_ids.filtered(lambda l: l.account_id == self.account_rcv)
self.assertTrue(pay_receivable_line1.reconciled)
self.assertEqual(pay_receivable_line1.matched_debit_ids, move_caba1.tax_cash_basis_rec_id)
def test_reconciliation_with_currency(self):
#reconciliation on an account having a foreign currency being
#the same as the company one
account_rcv = self.account_rcv
account_rcv.currency_id = self.currency_euro_id
aml_obj = self.env['account.move.line'].with_context(
check_move_validity=False)
general_move1 = self.env['account.move'].create({
'name': 'general1',
'journal_id': self.general_journal.id,
})
aml_obj.create({
'name': 'debit1',
'account_id': account_rcv.id,
'debit': 11,
'move_id': general_move1.id,
})
aml_obj.create({
'name': 'credit1',
'account_id': self.account_rsa.id,
'credit': 11,
'move_id': general_move1.id,
})
general_move1.post()
general_move2 = self.env['account.move'].create({
'name': 'general2',
'journal_id': self.general_journal.id,
})
aml_obj.create({
'name': 'credit2',
'account_id': account_rcv.id,
'credit': 10,
'move_id': general_move2.id,
})
aml_obj.create({
'name': 'debit2',
'account_id': self.account_rsa.id,
'debit': 10,
'move_id': general_move2.id,
})
general_move2.post()
general_move3 = self.env['account.move'].create({
'name': 'general3',
'journal_id': self.general_journal.id,
})
aml_obj.create({
'name': 'credit3',
'account_id': account_rcv.id,
'credit': 1,
'move_id': general_move3.id,
})
aml_obj.create({
'name': 'debit3',
'account_id': self.account_rsa.id,
'debit': 1,
'move_id': general_move3.id,
})
general_move3.post()
to_reconcile = ((general_move1 + general_move2 + general_move3)
.mapped('line_ids')
.filtered(lambda l: l.account_id.id == account_rcv.id))
to_reconcile.reconcile()
for aml in to_reconcile:
self.assertEqual(aml.amount_residual, 0.0)
def test_inv_refund_foreign_payment_writeoff_domestic2(self):
company = self.company
self.env['res.currency.rate'].search([]).unlink()
self.env['res.currency.rate'].create({
'name': time.strftime('%Y') + '-07-01',
'rate': 1.0,
'currency_id': self.currency_euro_id,
'company_id': company.id
})
self.env['res.currency.rate'].create({
'name': time.strftime('%Y') + '-07-01',
'rate': 1.110600, # Don't change this !
'currency_id': self.currency_usd_id,
'company_id': self.company.id
})
inv1 = self.create_invoice(invoice_amount=800, currency_id=self.currency_usd_id)
inv2 = self.create_invoice(type="out_refund", invoice_amount=400, currency_id=self.currency_usd_id)
payment = self.env['account.payment'].create({
'payment_method_id': self.inbound_payment_method.id,
'payment_type': 'inbound',
'partner_type': 'customer',
'partner_id': inv1.partner_id.id,
'amount': 200.00,
'journal_id': self.bank_journal_euro.id,
'company_id': company.id,
})
payment.post()
inv1_receivable = inv1.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
inv2_receivable = inv2.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
pay_receivable = payment.move_line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
move_balance = self.env['account.move'].create({
'partner_id': inv1.partner_id.id,
'date': time.strftime('%Y') + '-07-01',
'journal_id': self.bank_journal_euro.id,
'line_ids': [
(0, False, {'credit': 160.16, 'account_id': inv1_receivable.account_id.id, 'name': 'Balance WriteOff'}),
(0, False, {'debit': 160.16, 'account_id': self.diff_expense_account.id, 'name': 'Balance WriteOff'}),
]
})
move_balance.post()
move_balance_receiv = move_balance.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
(inv1_receivable + inv2_receivable + pay_receivable + move_balance_receiv).reconcile()
self.assertTrue(inv1_receivable.full_reconcile_id.exists())
self.assertEqual(inv1_receivable.full_reconcile_id, inv2_receivable.full_reconcile_id)
self.assertEqual(inv1_receivable.full_reconcile_id, pay_receivable.full_reconcile_id)
self.assertEqual(inv1_receivable.full_reconcile_id, move_balance_receiv.full_reconcile_id)
self.assertEqual(inv1.invoice_payment_state, 'paid')
self.assertEqual(inv2.invoice_payment_state, 'paid')
def test_inv_refund_foreign_payment_writeoff_domestic3(self):
"""
Receivable
Domestic (Foreign)
592.47 (658.00) | INV 1 > Done in foreign
| 202.59 (225.00) INV 2 > Done in foreign
| 372.10 (413.25) PAYMENT > Done in domestic (the 413.25 is virtual, non stored)
| 17.78 (19.75) WriteOff > Done in domestic (the 19.75 is virtual, non stored)
Reconciliation should be full
Invoices should be marked as paid
"""
company = self.company
self.env['res.currency.rate'].search([]).unlink()
self.env['res.currency.rate'].create({
'name': time.strftime('%Y') + '-07-01',
'rate': 1.0,
'currency_id': self.currency_euro_id,
'company_id': company.id
})
self.env['res.currency.rate'].create({
'name': time.strftime('%Y') + '-07-01',
'rate': 1.110600, # Don't change this !
'currency_id': self.currency_usd_id,
'company_id': company.id
})
inv1 = self.create_invoice(invoice_amount=658, currency_id=self.currency_usd_id)
inv2 = self.create_invoice(type="out_refund", invoice_amount=225, currency_id=self.currency_usd_id)
payment = self.env['account.payment'].create({
'payment_method_id': self.inbound_payment_method.id,
'payment_type': 'inbound',
'partner_type': 'customer',
'partner_id': inv1.partner_id.id,
'amount': 372.10,
'journal_id': self.bank_journal_euro.id,
'company_id': company.id,
})
payment.post()
inv1_receivable = inv1.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
inv2_receivable = inv2.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
pay_receivable = payment.move_line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
move_balance = self.env['account.move'].create({
'partner_id': inv1.partner_id.id,
'date': time.strftime('%Y') + '-07-01',
'journal_id': self.bank_journal_euro.id,
'line_ids': [
(0, False, {'credit': 17.78, 'account_id': inv1_receivable.account_id.id, 'name': 'Balance WriteOff'}),
(0, False, {'debit': 17.78, 'account_id': self.diff_expense_account.id, 'name': 'Balance WriteOff'}),
]
})
move_balance.post()
move_balance_receiv = move_balance.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
(inv1_receivable + inv2_receivable + pay_receivable + move_balance_receiv).reconcile()
self.assertTrue(inv1_receivable.full_reconcile_id.exists())
self.assertEqual(inv1_receivable.full_reconcile_id, inv2_receivable.full_reconcile_id)
self.assertEqual(inv1_receivable.full_reconcile_id, pay_receivable.full_reconcile_id)
self.assertEqual(inv1_receivable.full_reconcile_id, move_balance_receiv.full_reconcile_id)
# This one may be due rounding errors
exchange_rcv = inv1_receivable.full_reconcile_id.exchange_move_id.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
self.assertEqual(exchange_rcv.amount_currency, 0.01)
self.assertEqual(inv1.invoice_payment_state, 'paid')
self.assertEqual(inv2.invoice_payment_state, 'paid')
def test_inv_refund_foreign_payment_writeoff_domestic4(self):
"""
Receivable
Domestic (Foreign)
658.00 (658.00) | INV 1 > Done in foreign
| 202.59 (225.00) INV 2 > Done in foreign
| 372.10 (413.25) PAYMENT > Done in domestic (the 413.25 is virtual, non stored)
| 83.31 (92.52) WriteOff > Done in domestic (the 92.52 is virtual, non stored)
Reconciliation should be full
Invoices should be marked as paid
"""
company = self.company
self.env['res.currency.rate'].search([]).unlink()
self.env['res.currency.rate'].create({
'name': time.strftime('%Y') + '-07-01',
'rate': 1.0,
'currency_id': self.currency_euro_id,
'company_id': company.id
})
self.env['res.currency.rate'].create({
'name': time.strftime('%Y') + '-07-01',
'rate': 1.0, # Don't change this !
'currency_id': self.currency_usd_id,
'company_id': company.id
})
self.env['res.currency.rate'].create({
'name': time.strftime('%Y') + '-07-15',
'rate': 1.110600, # Don't change this !
'currency_id': self.currency_usd_id,
'company_id': company.id
})
inv1 = self._create_invoice(invoice_amount=658, currency_id=self.currency_usd_id, date_invoice=time.strftime('%Y') + '-07-01', auto_validate=True)
inv2 = self._create_invoice(type="out_refund", invoice_amount=225, currency_id=self.currency_usd_id, date_invoice=time.strftime('%Y') + '-07-15', auto_validate=True)
payment = self.env['account.payment'].create({
'payment_date': time.strftime('%Y') + '-07-15',
'payment_method_id': self.inbound_payment_method.id,
'payment_type': 'inbound',
'partner_type': 'customer',
'partner_id': inv1.partner_id.id,
'amount': 372.10,
'journal_id': self.bank_journal_euro.id,
'company_id': company.id,
'currency_id': self.currency_euro_id,
})
payment.post()
inv1_receivable = inv1.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
inv2_receivable = inv2.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
pay_receivable = payment.move_line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
self.assertEqual(inv1_receivable.balance, 658)
self.assertEqual(inv2_receivable.balance, -202.59)
self.assertEqual(pay_receivable.balance, -372.1)
move_balance = self.env['account.move'].create({
'partner_id': inv1.partner_id.id,
'date': time.strftime('%Y') + '-07-15',
'journal_id': self.bank_journal_usd.id,
'line_ids': [
(0, False, {'credit': 83.31, 'account_id': inv1_receivable.account_id.id, 'name': 'Balance WriteOff'}),
(0, False, {'debit': 83.31, 'account_id': self.diff_expense_account.id, 'name': 'Balance WriteOff'}),
]
})
move_balance.post()
move_balance_receiv = move_balance.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
(inv1_receivable + inv2_receivable + pay_receivable + move_balance_receiv).reconcile()
self.assertTrue(inv1_receivable.full_reconcile_id.exists())
self.assertEqual(inv1_receivable.full_reconcile_id, inv2_receivable.full_reconcile_id)
self.assertEqual(inv1_receivable.full_reconcile_id, pay_receivable.full_reconcile_id)
self.assertEqual(inv1_receivable.full_reconcile_id, move_balance_receiv.full_reconcile_id)
self.assertFalse(inv1_receivable.full_reconcile_id.exchange_move_id)
self.assertEqual(inv1.invoice_payment_state, 'paid')
self.assertEqual(inv2.invoice_payment_state, 'paid')
def test_inv_refund_foreign_payment_writeoff_domestic5(self):
"""
Receivable
Domestic (Foreign)
600.00 (600.00) | INV 1 > Done in foreign
| 250.00 (250.00) INV 2 > Done in foreign
| 314.07 (314.07) PAYMENT > Done in domestic (foreign non stored)
| 35.93 (60.93) WriteOff > Done in domestic (foreign non stored). WriteOff is included in payment
Reconciliation should be full, without exchange difference
Invoices should be marked as paid
"""
company = self.company
self.env['res.currency.rate'].search([]).unlink()
self.env['res.currency.rate'].create({
'name': time.strftime('%Y') + '-07-01',
'rate': 1.0,
'currency_id': self.currency_euro_id,
'company_id': company.id
})
self.env['res.currency.rate'].create({
'name': time.strftime('%Y') + '-07-01',
'rate': 1.0, # Don't change this !
'currency_id': self.currency_usd_id,
'company_id': company.id
})
inv1 = self._create_invoice(invoice_amount=600, currency_id=self.currency_usd_id, date_invoice=time.strftime('%Y') + '-07-15', auto_validate=True)
inv2 = self._create_invoice(type="out_refund", invoice_amount=250, currency_id=self.currency_usd_id, date_invoice=time.strftime('%Y') + '-07-15', auto_validate=True)
inv1_receivable = inv1.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
inv2_receivable = inv2.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
self.assertEqual(inv1_receivable.balance, 600.00)
self.assertEqual(inv2_receivable.balance, -250)
# partially pay the invoice with the refund
inv1.js_assign_outstanding_line(inv2_receivable.id)
self.assertEqual(inv1.amount_residual, 350)
Payment = self.env['account.payment'].with_context(default_invoice_ids=[(4, inv1.id, False)])
payment = Payment.create({
'payment_date': time.strftime('%Y') + '-07-15',
'payment_method_id': self.inbound_payment_method.id,
'payment_type': 'inbound',
'partner_type': 'customer',
'partner_id': inv1.partner_id.id,
'amount': 314.07,
'journal_id': self.bank_journal_euro.id,
'company_id': company.id,
'currency_id': self.currency_euro_id,
'payment_difference_handling': 'reconcile',
'writeoff_account_id': self.diff_income_account.id,
})
payment.post()
payment_receivable = payment.move_line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
self.assertEqual(payment_receivable.balance, -350)
self.assertTrue(inv1_receivable.full_reconcile_id.exists())
self.assertEqual(inv1_receivable.full_reconcile_id, inv2_receivable.full_reconcile_id)
self.assertEqual(inv1_receivable.full_reconcile_id, payment_receivable.full_reconcile_id)
self.assertFalse(inv1_receivable.full_reconcile_id.exchange_move_id)
self.assertEqual(inv1.invoice_payment_state, 'paid')
self.assertEqual(inv2.invoice_payment_state, 'paid')
def test_inv_refund_foreign_payment_writeoff_domestic6(self):
"""
Receivable
Domestic (Foreign)
540.25 (600.00) | INV 1 > Done in foreign
| 225.10 (250.00) INV 2 > Done in foreign
| 315.15 (350.00) PAYMENT > Done in domestic (the 350.00 is virtual, non stored)
"""
company = self.company
self.env['res.currency.rate'].search([]).unlink()
self.env['res.currency.rate'].create({
'name': time.strftime('%Y') + '-07-01',
'rate': 1.0,
'currency_id': self.currency_euro_id,
'company_id': company.id
})
self.env['res.currency.rate'].create({
'name': time.strftime('%Y') + '-07-01',
'rate': 1.1106, # Don't change this !
'currency_id': self.currency_usd_id,
'company_id': company.id
})
inv1 = self._create_invoice(invoice_amount=600, currency_id=self.currency_usd_id, date_invoice=time.strftime('%Y') + '-07-15', auto_validate=True)
inv2 = self._create_invoice(type="out_refund", invoice_amount=250, currency_id=self.currency_usd_id, date_invoice=time.strftime('%Y') + '-07-15', auto_validate=True)
inv1_receivable = inv1.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
inv2_receivable = inv2.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
self.assertEqual(inv1_receivable.balance, 540.25)
self.assertEqual(inv2_receivable.balance, -225.10)
# partially pay the invoice with the refund
inv1.js_assign_outstanding_line(inv2_receivable.id)
self.assertAlmostEqual(inv1.amount_residual, 350)
self.assertAlmostEqual(inv1_receivable.amount_residual, 315.15)
Payment = self.env['account.payment']
payment = Payment.create({
'payment_date': time.strftime('%Y') + '-07-15',
'payment_method_id': self.inbound_payment_method.id,
'payment_type': 'inbound',
'partner_type': 'customer',
'partner_id': inv1.partner_id.id,
'amount': 314.07,
'journal_id': self.bank_journal_euro.id,
'company_id': company.id,
'currency_id': self.currency_euro_id,
'payment_difference_handling': 'reconcile',
'writeoff_account_id': self.diff_income_account.id,
'invoice_ids': [(4, inv1.id, False)],
})
payment.post()
payment_receivable = payment.move_line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
self.assertTrue(inv1_receivable.full_reconcile_id.exists())
self.assertEqual(inv1_receivable.full_reconcile_id, inv2_receivable.full_reconcile_id)
self.assertEqual(inv1_receivable.full_reconcile_id, payment_receivable.full_reconcile_id)
self.assertFalse(inv1_receivable.full_reconcile_id.exchange_move_id)
self.assertEqual(inv1.invoice_payment_state, 'paid')
self.assertEqual(inv2.invoice_payment_state, 'paid')
def test_inv_refund_foreign_payment_writeoff_domestic6bis(self):
"""
Same as domestic6, but only in foreign currencies
Obviously, it should lead to the same kind of results
Here there is no exchange difference entry though
"""
foreign_0 = self.env['res.currency'].create({
'name': 'foreign0',
'symbol': 'F0'
})
foreign_1 = self.env['res.currency'].browse(self.currency_usd_id)
company = self.company
self.env['res.currency.rate'].search([]).unlink()
self.env['res.currency.rate'].create({
'name': time.strftime('%Y') + '-07-01',
'rate': 1.0,
'currency_id': self.currency_euro_id,
'company_id': company.id
})
self.env['res.currency.rate'].create({
'name': time.strftime('%Y') + '-07-01',
'rate': 1.0,
'currency_id': foreign_0.id,
'company_id': company.id
})
self.env['res.currency.rate'].create({
'name': time.strftime('%Y') + '-07-01',
'rate': 1.1106, # Don't change this !
'currency_id': foreign_1.id,
'company_id': company.id
})
inv1 = self._create_invoice(invoice_amount=600, currency_id=foreign_1.id, date_invoice=time.strftime('%Y') + '-07-15', auto_validate=True)
inv2 = self._create_invoice(type="out_refund", invoice_amount=250, currency_id=foreign_1.id, date_invoice=time.strftime('%Y') + '-07-15', auto_validate=True)
inv1_receivable = inv1.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
inv2_receivable = inv2.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
self.assertEqual(inv1_receivable.balance, 540.25)
self.assertEqual(inv2_receivable.balance, -225.10)
# partially pay the invoice with the refund
inv1.js_assign_outstanding_line(inv2_receivable.id)
self.assertAlmostEqual(inv1.amount_residual, 350)
self.assertAlmostEqual(inv1_receivable.amount_residual, 315.15)
Payment = self.env['account.payment'].with_context(default_invoice_ids=[(4, inv1.id, False)])
payment = Payment.create({
'payment_date': time.strftime('%Y') + '-07-15',
'payment_method_id': self.inbound_payment_method.id,
'payment_type': 'inbound',
'partner_type': 'customer',
'partner_id': inv1.partner_id.id,
'amount': 314.07,
'journal_id': self.bank_journal_euro.id,
'company_id': company.id,
'currency_id': foreign_0.id,
'payment_difference_handling': 'reconcile',
'writeoff_account_id': self.diff_income_account.id,
'invoice_ids': [(4, inv1.id, False)],
})
payment.post()
payment_receivable = payment.move_line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
self.assertTrue(inv1_receivable.full_reconcile_id.exists())
self.assertEqual(inv1_receivable.full_reconcile_id, inv2_receivable.full_reconcile_id)
self.assertEqual(inv1_receivable.full_reconcile_id, payment_receivable.full_reconcile_id)
self.assertFalse(inv1_receivable.full_reconcile_id.exchange_move_id)
self.assertEqual(inv1.invoice_payment_state, 'paid')
self.assertEqual(inv2.invoice_payment_state, 'paid')
def test_inv_refund_foreign_payment_writeoff_domestic7(self):
"""
Receivable
Domestic (Foreign)
5384.48 (5980.00) | INV 1 > Done in foreign
| 5384.43 (5979.95) PAYMENT > Done in domestic (foreign non stored)
| 0.05 (0.00) WriteOff > Done in domestic (foreign non stored). WriteOff is included in payment,
so, the amount in currency is irrelevant
Reconciliation should be full, without exchange difference
Invoices should be marked as paid
"""
company = self.company
self.env['res.currency.rate'].search([]).unlink()
self.env['res.currency.rate'].create({
'name': time.strftime('%Y') + '-07-01',
'rate': 1.0,
'currency_id': self.currency_euro_id,
'company_id': company.id
})
self.env['res.currency.rate'].create({
'name': time.strftime('%Y') + '-07-01',
'rate': 1.1106, # Don't change this !
'currency_id': self.currency_usd_id,
'company_id': company.id
})
inv1 = self._create_invoice(invoice_amount=5980, currency_id=self.currency_usd_id, date_invoice=time.strftime('%Y') + '-07-15', auto_validate=True)
inv1_receivable = inv1.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
self.assertAlmostEqual(inv1_receivable.balance, 5384.48)
Payment = self.env['account.payment']
payment = Payment.create({
'payment_date': time.strftime('%Y') + '-07-15',
'payment_method_id': self.inbound_payment_method.id,
'payment_type': 'inbound',
'partner_type': 'customer',
'partner_id': inv1.partner_id.id,
'amount': 5384.43,
'journal_id': self.bank_journal_euro.id,
'company_id': company.id,
'currency_id': self.currency_euro_id,
'payment_difference_handling': 'reconcile',
'writeoff_account_id': self.diff_income_account.id,
'invoice_ids': [(4, inv1.id, False)],
})
payment.post()
payment_receivable = payment.move_line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
self.assertTrue(inv1_receivable.full_reconcile_id.exists())
self.assertEqual(inv1_receivable.full_reconcile_id, payment_receivable.full_reconcile_id)
self.assertFalse(inv1_receivable.full_reconcile_id.exchange_move_id)
self.assertEqual(inv1.invoice_payment_state, 'paid')
def test_inv_refund_foreign_payment_writeoff_domestic8(self):
"""
Roughly the same as *_domestic7
Though it simulates going through the reconciliation widget
Because the WriteOff is on a different line than the payment
"""
company = self.company
self.env['res.currency.rate'].search([]).unlink()
self.env['res.currency.rate'].create({
'name': time.strftime('%Y') + '-07-01',
'rate': 1.0,
'currency_id': self.currency_euro_id,
'company_id': company.id
})
self.env['res.currency.rate'].create({
'name': time.strftime('%Y') + '-07-01',
'rate': 1.1106, # Don't change this !
'currency_id': self.currency_usd_id,
'company_id': company.id
})
inv1 = self._create_invoice(invoice_amount=5980, currency_id=self.currency_usd_id, date_invoice=time.strftime('%Y') + '-07-15', auto_validate=True)
inv1_receivable = inv1.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
self.assertAlmostEqual(inv1_receivable.balance, 5384.48)
Payment = self.env['account.payment']
payment = Payment.create({
'payment_date': time.strftime('%Y') + '-07-15',
'payment_method_id': self.inbound_payment_method.id,
'payment_type': 'inbound',
'partner_type': 'customer',
'partner_id': inv1.partner_id.id,
'amount': 5384.43,
'journal_id': self.bank_journal_euro.id,
'company_id': company.id,
'currency_id': self.currency_euro_id,
})
payment.post()
payment_receivable = payment.move_line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
move_balance = self.env['account.move'].create({
'partner_id': inv1.partner_id.id,
'date': time.strftime('%Y') + '-07-15',
'journal_id': self.bank_journal_usd.id,
'line_ids': [
(0, False, {'credit': 0.05, 'account_id': inv1_receivable.account_id.id, 'name': 'Balance WriteOff'}),
(0, False, {'debit': 0.05, 'account_id': self.diff_expense_account.id, 'name': 'Balance WriteOff'}),
]
})
move_balance.post()
move_balance_receiv = move_balance.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
(inv1_receivable + payment_receivable + move_balance_receiv).reconcile()
self.assertTrue(inv1_receivable.full_reconcile_id.exists())
self.assertEqual(inv1_receivable.full_reconcile_id, payment_receivable.full_reconcile_id)
self.assertEqual(move_balance_receiv.full_reconcile_id, inv1_receivable.full_reconcile_id)
self.assertFalse(inv1_receivable.full_reconcile_id.exchange_move_id)
self.assertEqual(inv1.invoice_payment_state, 'paid')