[FIX] purchase: real time average multicurrency valuation
Do a PO in a foreign currency Invoice it in the same foreign currency Validate the invoice Before this commit, The account move of the invoice contained a price difference line which is wrong, since the price has not changed in any ways After this commit, the move has only two lines one in the stock input, one in the payable Also, the case where the price does change due to a change in currency rate is handled OPW 1955315 closes odoo/odoo#32616 Signed-off-by: Lucas Perais (lpe) <lpe@odoo.com>
This commit is contained in:
@@ -194,9 +194,11 @@ class AccountInvoice(models.Model):
|
||||
for val_stock_move in valuation_stock_move:
|
||||
# In case val_stock_move is a return move, its valuation entries have been made with the
|
||||
# currency rate corresponding to the original stock move
|
||||
valuation_date = val_stock_move.origin_returned_move_id.date or val_stock_move.date_expected
|
||||
valuation_date = val_stock_move.origin_returned_move_id.date or val_stock_move.date
|
||||
valuation_price_unit_total += company_currency.with_context(date=valuation_date).compute(abs(val_stock_move.price_unit) * val_stock_move.product_qty, inv.currency_id, round=False)
|
||||
valuation_total_qty += val_stock_move.product_qty
|
||||
|
||||
# in Stock Move, price unit is in company_currency
|
||||
valuation_price_unit = valuation_price_unit_total / valuation_total_qty
|
||||
valuation_price_unit = i_line.product_id.uom_id._compute_price(valuation_price_unit, i_line.uom_id)
|
||||
line_quantity = valuation_total_qty
|
||||
@@ -209,7 +211,6 @@ class AccountInvoice(models.Model):
|
||||
invoice_cur_prec = inv.currency_id.decimal_places
|
||||
|
||||
if float_compare(valuation_price_unit, i_line.price_unit, precision_digits=invoice_cur_prec) != 0 and float_compare(line['price_unit'], i_line.price_unit, precision_digits=invoice_cur_prec) == 0:
|
||||
|
||||
# price with discount and without tax included
|
||||
price_unit = i_line.price_unit * (1 - (i_line.discount or 0.0) / 100.0)
|
||||
tax_ids = []
|
||||
|
||||
@@ -749,7 +749,7 @@ class PurchaseOrderLine(models.Model):
|
||||
if line.product_uom.id != line.product_id.uom_id.id:
|
||||
price_unit *= line.product_uom.factor / line.product_id.uom_id.factor
|
||||
if order.currency_id != order.company_id.currency_id:
|
||||
price_unit = order.currency_id.compute(price_unit, order.company_id.currency_id, round=False)
|
||||
price_unit = order.currency_id.with_context(date=order.date_approve).compute(price_unit, order.company_id.currency_id, round=False)
|
||||
return price_unit
|
||||
|
||||
@api.multi
|
||||
@@ -769,7 +769,7 @@ class PurchaseOrderLine(models.Model):
|
||||
'name': self.name or '',
|
||||
'product_id': self.product_id.id,
|
||||
'product_uom': self.product_uom.id,
|
||||
'date': self.order_id.date_order,
|
||||
'date': self.order_id.date_approve,
|
||||
'date_expected': self.date_planned,
|
||||
'location_id': self.order_id.partner_id.property_stock_supplier.id,
|
||||
'location_dest_id': self.order_id._get_destination_location(),
|
||||
|
||||
@@ -46,7 +46,7 @@ class StockMove(models.Model):
|
||||
if line.product_uom.id != line.product_id.uom_id.id:
|
||||
price_unit *= line.product_uom.factor / line.product_id.uom_id.factor
|
||||
if order.currency_id != order.company_id.currency_id:
|
||||
price_unit = order.currency_id.compute(price_unit, order.company_id.currency_id, round=False)
|
||||
price_unit = order.currency_id.with_context(date=self.date or order.date_approve).compute(price_unit, order.company_id.currency_id, round=False)
|
||||
return price_unit
|
||||
return super(StockMove, self)._get_price_unit()
|
||||
|
||||
|
||||
@@ -63,8 +63,10 @@ class TestValuationReconciliation(ValuationReconciliationTestCase):
|
||||
""" Tests the case into which we receive the goods first, and then make the invoice.
|
||||
"""
|
||||
test_product = self.test_product_delivery
|
||||
purchase_order = self._create_purchase(test_product, '2018-01-01')
|
||||
self._process_pickings(purchase_order.picking_ids)
|
||||
date_po_and_delivery = '2018-01-01'
|
||||
|
||||
purchase_order = self._create_purchase(test_product, date_po_and_delivery)
|
||||
self._process_pickings(purchase_order.picking_ids, date=date_po_and_delivery)
|
||||
|
||||
invoice = self._create_invoice_for_po(purchase_order, '2018-02-02')
|
||||
self.env['res.currency.rate'].create({
|
||||
@@ -150,8 +152,9 @@ class TestValuationReconciliation(ValuationReconciliationTestCase):
|
||||
""" Tests the case into which we receive part of the goods first, then 2 invoices at different rates, and finally the remaining quantities
|
||||
"""
|
||||
test_product = self.test_product_delivery
|
||||
purchase_order = self._create_purchase(test_product, '2017-01-01', quantity=5.0)
|
||||
self._process_pickings(purchase_order.picking_ids, quantity=2.0)
|
||||
date_po_and_delivery0 = '2017-01-01'
|
||||
purchase_order = self._create_purchase(test_product, date_po_and_delivery0, quantity=5.0)
|
||||
self._process_pickings(purchase_order.picking_ids, quantity=2.0, date=date_po_and_delivery0)
|
||||
picking = self.env['stock.picking'].search([('purchase_id', '=', purchase_order.id)], order="id asc", limit=1)
|
||||
|
||||
invoice = self._create_invoice_for_po(purchase_order, '2017-01-15')
|
||||
@@ -184,6 +187,8 @@ class TestValuationReconciliation(ValuationReconciliationTestCase):
|
||||
'rate': 12.195747002,
|
||||
'name': '2017-04-01',
|
||||
})
|
||||
# We don't need to make the date of processing explicit since the very last rate
|
||||
# will be taken
|
||||
self._process_pickings(purchase_order.picking_ids.filtered(lambda x: x.state != 'done'), quantity=3.0)
|
||||
picking = self.env['stock.picking'].search([('purchase_id', '=', purchase_order.id)], order='id desc', limit=1)
|
||||
self.check_reconciliation(invoice2, picking)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import time
|
||||
from datetime import datetime
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.tests import Form
|
||||
from odoo.tests.common import TransactionCase, tagged
|
||||
@@ -143,6 +144,7 @@ class TestStockValuation(TransactionCase):
|
||||
the currency rate, validate the receipt and then check that the value of the received goods
|
||||
is set according to the last currency rate.
|
||||
"""
|
||||
self.env['res.currency.rate'].search([]).unlink()
|
||||
usd_currency = self.env.ref('base.USD')
|
||||
self.env.user.company_id.currency_id = usd_currency.id
|
||||
|
||||
@@ -285,6 +287,9 @@ class TestStockValuationWithCOA(AccountingTestCase):
|
||||
self.partner_id = self.env.ref('base.res_partner_1')
|
||||
self.product1 = self.env.ref('product.product_product_8')
|
||||
Account = self.env['account.account']
|
||||
self.usd_currency = self.env.ref('base.USD')
|
||||
self.eur_currency = self.env.ref('base.EUR')
|
||||
|
||||
self.stock_input_account = Account.create({
|
||||
'name': 'Stock Input',
|
||||
'code': 'StockIn',
|
||||
@@ -302,6 +307,11 @@ class TestStockValuationWithCOA(AccountingTestCase):
|
||||
'code': 'Stock Valuation',
|
||||
'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
|
||||
})
|
||||
self.price_diff_account = Account.create({
|
||||
'name': 'price diff account',
|
||||
'code': 'price diff account',
|
||||
'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
|
||||
})
|
||||
self.stock_journal = self.env['account.journal'].create({
|
||||
'name': 'Stock Journal',
|
||||
'code': 'STJTEST',
|
||||
@@ -319,12 +329,7 @@ class TestStockValuationWithCOA(AccountingTestCase):
|
||||
self.product1.product_tmpl_id.cost_method = 'fifo'
|
||||
self.product1.product_tmpl_id.valuation = 'real_time'
|
||||
self.product1.product_tmpl_id.invoice_policy = 'delivery'
|
||||
price_diff_account = self.env['account.account'].create({
|
||||
'name': 'price diff account',
|
||||
'code': 'price diff account',
|
||||
'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
|
||||
})
|
||||
self.product1.property_account_creditor_price_difference = price_diff_account
|
||||
self.product1.property_account_creditor_price_difference = self.price_diff_account
|
||||
|
||||
# Receive 10@10 ; create the vendor bill
|
||||
po1 = self.env['purchase.order'].create({
|
||||
@@ -411,7 +416,7 @@ class TestStockValuationWithCOA(AccountingTestCase):
|
||||
creditnote_po2.action_invoice_open()
|
||||
|
||||
# check the anglo saxon entries
|
||||
price_diff_entry = self.env['account.move.line'].search([('account_id', '=', price_diff_account.id)])
|
||||
price_diff_entry = self.env['account.move.line'].search([('account_id', '=', self.price_diff_account.id)])
|
||||
self.assertEqual(price_diff_entry.credit, 100)
|
||||
|
||||
def test_anglosaxon_valuation(self):
|
||||
@@ -419,12 +424,7 @@ class TestStockValuationWithCOA(AccountingTestCase):
|
||||
self.product1.product_tmpl_id.cost_method = 'fifo'
|
||||
self.product1.product_tmpl_id.valuation = 'real_time'
|
||||
self.product1.product_tmpl_id.invoice_policy = 'delivery'
|
||||
price_diff_account = self.env['account.account'].create({
|
||||
'name': 'price diff account',
|
||||
'code': 'price diff account',
|
||||
'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
|
||||
})
|
||||
self.product1.property_account_creditor_price_difference = price_diff_account
|
||||
self.product1.property_account_creditor_price_difference = self.price_diff_account
|
||||
|
||||
# Create PO
|
||||
po_form = Form(self.env['purchase.order'])
|
||||
@@ -453,12 +453,422 @@ class TestStockValuationWithCOA(AccountingTestCase):
|
||||
invoice.action_invoice_open()
|
||||
|
||||
# Check what was posted in the price difference account
|
||||
price_diff_aml = self.env['account.move.line'].search([('account_id','=',price_diff_account.id)])
|
||||
price_diff_aml = self.env['account.move.line'].search([('account_id','=', self.price_diff_account.id)])
|
||||
self.assertEquals(len(price_diff_aml), 1, "Only one line should have been generated in the price difference account.")
|
||||
self.assertAlmostEquals(price_diff_aml.debit, 5, "Price difference should be equal to 5 (15-10)")
|
||||
|
||||
# Check what was posted in stock input account
|
||||
input_aml = self.env['account.move.line'].search([('account_id','=',self.stock_input_account.id)])
|
||||
input_aml = self.env['account.move.line'].search([('account_id','=', self.stock_input_account.id)])
|
||||
self.assertEquals(len(input_aml), 2, "Only two lines should have been generated in stock input account: one when receiving the product, one when making the invoice.")
|
||||
self.assertAlmostEquals(sum(input_aml.mapped('debit')), 10, "Total debit value on stock input account should be equal to the original PO price of the product.")
|
||||
self.assertAlmostEquals(sum(input_aml.mapped('credit')), 10, "Total credit value on stock input account should be equal to the original PO price of the product.")
|
||||
|
||||
def test_average_realtime_anglo_saxon_valuation_multicurrency_same_date(self):
|
||||
"""
|
||||
The PO and invoice are in the same foreign currency.
|
||||
The PO is invoiced on the same date as its creation.
|
||||
This shouldn't create a price difference entry.
|
||||
"""
|
||||
company = self.env.user.company_id
|
||||
company.anglo_saxon_accounting = True
|
||||
|
||||
date_po = '2019-01-01'
|
||||
|
||||
# SetUp product
|
||||
self.product1.product_tmpl_id.cost_method = 'average'
|
||||
self.product1.product_tmpl_id.valuation = 'real_time'
|
||||
self.product1.product_tmpl_id.invoice_policy = 'order'
|
||||
self.product1.product_tmpl_id.purchase_method = 'purchase'
|
||||
|
||||
self.product1.property_account_creditor_price_difference = self.price_diff_account
|
||||
|
||||
# SetUp currency and rates
|
||||
self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", (self.usd_currency.id, company.id))
|
||||
self.env['res.currency.rate'].search([]).unlink()
|
||||
self.env['res.currency.rate'].create({
|
||||
'name': date_po,
|
||||
'rate': 1.0,
|
||||
'currency_id': self.usd_currency.id,
|
||||
'company_id': company.id,
|
||||
})
|
||||
|
||||
self.env['res.currency.rate'].create({
|
||||
'name': date_po,
|
||||
'rate': 1.5,
|
||||
'currency_id': self.eur_currency.id,
|
||||
'company_id': company.id,
|
||||
})
|
||||
|
||||
# Proceed
|
||||
po = self.env['purchase.order'].create({
|
||||
'currency_id': self.eur_currency.id,
|
||||
'partner_id': self.partner_id.id,
|
||||
'order_line': [
|
||||
(0, 0, {
|
||||
'name': self.product1.name,
|
||||
'product_id': self.product1.id,
|
||||
'product_qty': 1.0,
|
||||
'product_uom': self.product1.uom_po_id.id,
|
||||
'price_unit': 100.0,
|
||||
'date_planned': date_po,
|
||||
}),
|
||||
],
|
||||
})
|
||||
po.button_confirm()
|
||||
|
||||
inv = self.env['account.invoice'].create({
|
||||
'type': 'in_invoice',
|
||||
'date_invoice': date_po,
|
||||
'currency_id': self.eur_currency.id,
|
||||
'purchase_id': po.id,
|
||||
'partner_id': self.partner_id.id,
|
||||
'invoice_line_ids': [(0, 0, {
|
||||
'name': 'Test',
|
||||
'price_subtotal': 100.0,
|
||||
'price_unit': 100.0,
|
||||
'product_id': self.product1.id,
|
||||
'purchase_id': po.id,
|
||||
'purchase_line_id': po.order_line.id,
|
||||
'quantity': 1.0,
|
||||
'account_id': self.stock_input_account.id,
|
||||
})]
|
||||
})
|
||||
|
||||
inv.action_invoice_open()
|
||||
|
||||
move_lines = inv.move_id.line_ids
|
||||
self.assertEqual(len(move_lines), 2)
|
||||
|
||||
payable_line = move_lines.filtered(lambda l: l.account_id.internal_type == 'payable')
|
||||
self.assertEqual(payable_line.amount_currency, -100.0)
|
||||
self.assertAlmostEqual(payable_line.balance, -66.67)
|
||||
|
||||
stock_line = move_lines.filtered(lambda l: l.account_id == self.stock_input_account)
|
||||
self.assertEqual(stock_line.amount_currency, 100.0)
|
||||
self.assertAlmostEqual(stock_line.balance, 66.67)
|
||||
|
||||
def test_realtime_anglo_saxon_valuation_multicurrency_different_dates(self):
|
||||
"""
|
||||
The PO and invoice are in the same foreign currency.
|
||||
The PO is invoiced at a later date than its creation.
|
||||
This should create a price difference entry for standard cost method
|
||||
Not for average cost method though, since the PO and invoice have the same currency
|
||||
"""
|
||||
company = self.env.user.company_id
|
||||
company.anglo_saxon_accounting = True
|
||||
|
||||
date_po = '2019-01-01'
|
||||
date_invoice = '2019-01-16'
|
||||
|
||||
# SetUp product Average
|
||||
self.product1.product_tmpl_id.write({
|
||||
'cost_method': 'average',
|
||||
'valuation': 'real_time',
|
||||
'purchase_method': 'purchase',
|
||||
'property_account_creditor_price_difference': self.price_diff_account.id,
|
||||
})
|
||||
self.product1.invoice_policy = 'order'
|
||||
|
||||
# SetUp product Standard
|
||||
# should have bought at 60 USD
|
||||
# actually invoiced at 70 EUR > 35 USD
|
||||
product_standard = self.product1.product_tmpl_id.copy({
|
||||
'cost_method': 'standard',
|
||||
'name': 'Standard Val',
|
||||
'standard_price': 60,
|
||||
'property_account_creditor_price_difference': self.price_diff_account.id
|
||||
}).product_variant_id
|
||||
|
||||
# SetUp currency and rates
|
||||
self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", (self.usd_currency.id, company.id))
|
||||
self.env['res.currency.rate'].search([]).unlink()
|
||||
self.env['res.currency.rate'].create({
|
||||
'name': date_po,
|
||||
'rate': 1.0,
|
||||
'currency_id': self.usd_currency.id,
|
||||
'company_id': company.id,
|
||||
})
|
||||
|
||||
self.env['res.currency.rate'].create({
|
||||
'name': date_po,
|
||||
'rate': 1.5,
|
||||
'currency_id': self.eur_currency.id,
|
||||
'company_id': company.id,
|
||||
})
|
||||
|
||||
self.env['res.currency.rate'].create({
|
||||
'name': date_invoice,
|
||||
'rate': 2,
|
||||
'currency_id': self.eur_currency.id,
|
||||
'company_id': company.id,
|
||||
})
|
||||
|
||||
# To allow testing validation of PO
|
||||
def _today(*args, **kwargs):
|
||||
return date_po
|
||||
patchers = [
|
||||
patch('odoo.fields.Date.context_today', _today),
|
||||
]
|
||||
|
||||
for p in patchers:
|
||||
p.start()
|
||||
|
||||
# Proceed
|
||||
po = self.env['purchase.order'].create({
|
||||
'currency_id': self.eur_currency.id,
|
||||
'partner_id': self.partner_id.id,
|
||||
'order_line': [
|
||||
(0, 0, {
|
||||
'name': self.product1.name,
|
||||
'product_id': self.product1.id,
|
||||
'product_qty': 1.0,
|
||||
'product_uom': self.product1.uom_po_id.id,
|
||||
'price_unit': 100.0,
|
||||
'date_planned': date_po,
|
||||
}),
|
||||
(0, 0, {
|
||||
'name': product_standard.name,
|
||||
'product_id': product_standard.id,
|
||||
'product_qty': 1.0,
|
||||
'product_uom': product_standard.uom_po_id.id,
|
||||
'price_unit': 40.0,
|
||||
'date_planned': date_po,
|
||||
}),
|
||||
],
|
||||
})
|
||||
po.button_confirm()
|
||||
|
||||
line_product_average = po.order_line.filtered(lambda l: l.product_id == self.product1)
|
||||
line_product_standard = po.order_line.filtered(lambda l: l.product_id == product_standard)
|
||||
|
||||
inv = self.env['account.invoice'].create({
|
||||
'type': 'in_invoice',
|
||||
'date_invoice': date_invoice,
|
||||
'currency_id': self.eur_currency.id,
|
||||
'purchase_id': po.id,
|
||||
'partner_id': self.partner_id.id,
|
||||
'invoice_line_ids': [
|
||||
(0, 0, {
|
||||
'name': self.product1.name,
|
||||
'price_subtotal': 100.0,
|
||||
'price_unit': 100.0,
|
||||
'product_id': self.product1.id,
|
||||
'purchase_id': po.id,
|
||||
'purchase_line_id': line_product_average.id,
|
||||
'quantity': 1.0,
|
||||
'account_id': self.stock_input_account.id,
|
||||
}),
|
||||
(0, 0, {
|
||||
'name': product_standard.name,
|
||||
'price_subtotal': 70.0,
|
||||
'price_unit': 70.0,
|
||||
'product_id': product_standard.id,
|
||||
'purchase_id': po.id,
|
||||
'purchase_line_id': line_product_standard.id,
|
||||
'quantity': 1.0,
|
||||
'account_id': self.stock_input_account.id,
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
inv.action_invoice_open()
|
||||
|
||||
for p in patchers:
|
||||
p.stop()
|
||||
|
||||
move_lines = inv.move_id.line_ids
|
||||
self.assertEqual(len(move_lines), 4)
|
||||
|
||||
# Ensure no exchange difference move has been created
|
||||
self.assertTrue(all([not l.reconciled for l in move_lines]))
|
||||
|
||||
# PAYABLE CHECK
|
||||
payable_line = move_lines.filtered(lambda l: l.account_id.internal_type == 'payable')
|
||||
self.assertEqual(payable_line.amount_currency, -170.0)
|
||||
self.assertAlmostEqual(payable_line.balance, -85.00)
|
||||
|
||||
# PRODUCTS CHECKS
|
||||
|
||||
# NO EXCHANGE DIFFERENCE (average)
|
||||
# We ordered for a value of 100 EUR
|
||||
# But by the time we are invoiced for it
|
||||
# the foreign currency appreciated from 1.5 to 2.0
|
||||
# We still have to pay 100 EUR, which now values at 50 USD
|
||||
product_lines = move_lines.filtered(lambda l: l.product_id == self.product1)
|
||||
|
||||
# Stock-wise, we have been invoiced 100 EUR, and we ordered 100 EUR
|
||||
# there is no price difference
|
||||
# However, 100 EUR should be converted at the time of the invoice
|
||||
stock_line = product_lines.filtered(lambda l: l.account_id == self.stock_input_account)
|
||||
self.assertEqual(stock_line.amount_currency, 100.00)
|
||||
self.assertAlmostEqual(stock_line.balance, 50.00)
|
||||
|
||||
# PRICE DIFFERENCE (STANDARD)
|
||||
# We ordered a product that should have cost 60 USD (120 EUR)
|
||||
# However, we effectively got invoiced 70 EUR (35 USD)
|
||||
product_lines = move_lines.filtered(lambda l: l.product_id == product_standard)
|
||||
|
||||
stock_line = product_lines.filtered(lambda l: l.account_id == self.stock_input_account)
|
||||
self.assertEqual(stock_line.amount_currency, 120.00)
|
||||
self.assertAlmostEqual(stock_line.balance, 60.00)
|
||||
|
||||
price_diff_line = product_lines.filtered(lambda l: l.account_id == self.price_diff_account)
|
||||
self.assertEqual(price_diff_line.amount_currency, -50.00)
|
||||
self.assertAlmostEqual(price_diff_line.balance, -25.00)
|
||||
|
||||
def test_average_realtime_with_delivery_anglo_saxon_valuation_multicurrency_different_dates(self):
|
||||
"""
|
||||
The PO and invoice are in the same foreign currency.
|
||||
The delivery occurs in between PO validation and invoicing
|
||||
The invoice is created at an even different date
|
||||
This should create a price difference entry.
|
||||
"""
|
||||
company = self.env.user.company_id
|
||||
company.anglo_saxon_accounting = True
|
||||
|
||||
date_po = '2019-01-01'
|
||||
date_delivery = '2019-01-08'
|
||||
date_invoice = '2019-01-16'
|
||||
|
||||
product_avg = self.product1.product_tmpl_id.copy({
|
||||
'valuation': 'real_time',
|
||||
'purchase_method': 'purchase',
|
||||
'cost_method': 'average',
|
||||
'name': 'AVG',
|
||||
'standard_price': 60,
|
||||
'property_account_creditor_price_difference': self.price_diff_account.id
|
||||
}).product_variant_id
|
||||
product_avg.invoice_policy = 'order'
|
||||
|
||||
# SetUp currency and rates
|
||||
self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", (self.usd_currency.id, company.id))
|
||||
self.env['res.currency.rate'].search([]).unlink()
|
||||
self.env['res.currency.rate'].create({
|
||||
'name': date_po,
|
||||
'rate': 1.0,
|
||||
'currency_id': self.usd_currency.id,
|
||||
'company_id': company.id,
|
||||
})
|
||||
|
||||
self.env['res.currency.rate'].create({
|
||||
'name': date_po,
|
||||
'rate': 1.5,
|
||||
'currency_id': self.eur_currency.id,
|
||||
'company_id': company.id,
|
||||
})
|
||||
|
||||
self.env['res.currency.rate'].create({
|
||||
'name': date_delivery,
|
||||
'rate': 0.7,
|
||||
'currency_id': self.eur_currency.id,
|
||||
'company_id': company.id,
|
||||
})
|
||||
|
||||
self.env['res.currency.rate'].create({
|
||||
'name': date_invoice,
|
||||
'rate': 2,
|
||||
'currency_id': self.eur_currency.id,
|
||||
'company_id': company.id,
|
||||
})
|
||||
|
||||
# To allow testing validation of PO
|
||||
def _today(*args, **kwargs):
|
||||
return date_po
|
||||
# To allow testing validation of Delivery
|
||||
def _now(*args, **kwargs):
|
||||
return date_delivery + ' 01:00:00'
|
||||
|
||||
patchers = [
|
||||
patch('odoo.fields.Date.context_today', _today),
|
||||
patch('odoo.fields.Datetime.now', _now),
|
||||
]
|
||||
|
||||
for p in patchers:
|
||||
p.start()
|
||||
|
||||
# Proceed
|
||||
po = self.env['purchase.order'].create({
|
||||
'currency_id': self.eur_currency.id,
|
||||
'partner_id': self.partner_id.id,
|
||||
'order_line': [
|
||||
(0, 0, {
|
||||
'name': product_avg.name,
|
||||
'product_id': product_avg.id,
|
||||
'product_qty': 1.0,
|
||||
'product_uom': product_avg.uom_po_id.id,
|
||||
'price_unit': 30.0,
|
||||
'date_planned': date_po,
|
||||
})
|
||||
],
|
||||
})
|
||||
po.button_confirm()
|
||||
|
||||
line_product_avg = po.order_line.filtered(lambda l: l.product_id == product_avg)
|
||||
|
||||
picking = po.picking_ids
|
||||
(picking.move_lines
|
||||
.filtered(lambda l: l.purchase_line_id == line_product_avg)
|
||||
.write({'quantity_done': 1.0}))
|
||||
|
||||
picking.button_validate()
|
||||
|
||||
inv = self.env['account.invoice'].create({
|
||||
'type': 'in_invoice',
|
||||
'date_invoice': date_invoice,
|
||||
'currency_id': self.eur_currency.id,
|
||||
'purchase_id': po.id,
|
||||
'partner_id': self.partner_id.id,
|
||||
'invoice_line_ids': [
|
||||
(0, 0, {
|
||||
'name': product_avg.name,
|
||||
'price_subtotal': 30.0,
|
||||
'price_unit': 30.0,
|
||||
'product_id': product_avg.id,
|
||||
'purchase_id': po.id,
|
||||
'purchase_line_id': line_product_avg.id,
|
||||
'quantity': 1.0,
|
||||
'account_id': self.stock_input_account.id,
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
inv.action_invoice_open()
|
||||
|
||||
for p in patchers:
|
||||
p.stop()
|
||||
|
||||
move_lines = inv.move_id.line_ids
|
||||
self.assertEqual(len(move_lines), 2)
|
||||
|
||||
# PAYABLE CHECK
|
||||
payable_line = move_lines.filtered(lambda l: l.account_id.internal_type == 'payable')
|
||||
self.assertEqual(payable_line.amount_currency, -30.0)
|
||||
self.assertAlmostEqual(payable_line.balance, -15.00)
|
||||
|
||||
# PRODUCTS CHECKS
|
||||
|
||||
# DELIVERY DIFFERENCE (AVERAGE)
|
||||
# We ordered a product at 30 EUR valued at 20 USD
|
||||
# We received it when the exchange rate has appreciated
|
||||
# So, the actualized 20 USD are now 20*1.5/0.7 = 42.86 USD
|
||||
product_lines = move_lines.filtered(lambda l: l.product_id == product_avg)
|
||||
|
||||
# Although those 42.86 USD are just due to the exchange difference
|
||||
stock_line = product_lines.filtered(lambda l: l.account_id == self.stock_input_account)
|
||||
self.assertEqual(stock_line.journal_id, inv.journal_id)
|
||||
self.assertEqual(stock_line.amount_currency, 30.00)
|
||||
self.assertAlmostEqual(stock_line.balance, 15.00)
|
||||
full_reconcile = stock_line.full_reconcile_id
|
||||
self.assertTrue(full_reconcile.exists())
|
||||
|
||||
reconciled_lines = full_reconcile.reconciled_line_ids - stock_line
|
||||
self.assertEqual(len(reconciled_lines), 2)
|
||||
|
||||
stock_journal_line = reconciled_lines.filtered(lambda l: l.journal_id == self.stock_journal)
|
||||
self.assertEqual(stock_journal_line.amount_currency, -30.00)
|
||||
self.assertAlmostEqual(stock_journal_line.balance, -42.86)
|
||||
|
||||
exhange_diff_journal = company.currency_exchange_journal_id.exists()
|
||||
exchange_stock_line = reconciled_lines.filtered(lambda l: l.journal_id == exhange_diff_journal)
|
||||
self.assertEqual(exchange_stock_line.amount_currency, 0.00)
|
||||
self.assertAlmostEqual(exchange_stock_line.balance, 27.86)
|
||||
|
||||
Reference in New Issue
Block a user