Add an easy way to not post the entries in the future when calling post() on it, but rather set it to be auto-posted at accounting date. This is useful when we are creating a lot of entries in batch and some might be in the future, some in the past, and we don't want to separate that in two batch every time. (asset, accrual, transfer,... )
1322 lines
55 KiB
Python
1322 lines
55 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import time
|
|
from datetime import datetime
|
|
from unittest.mock import patch
|
|
|
|
from odoo import fields
|
|
from odoo.tests import Form
|
|
from odoo.tests.common import TransactionCase, tagged
|
|
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
|
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
|
|
|
|
|
class TestStockValuation(TransactionCase):
|
|
def setUp(self):
|
|
super(TestStockValuation, self).setUp()
|
|
self.supplier_location = self.env.ref('stock.stock_location_suppliers')
|
|
self.stock_location = self.env.ref('stock.stock_location_stock')
|
|
self.partner_id = self.env['res.partner'].create({
|
|
'name': 'Wood Corner Partner',
|
|
'company_id': self.env.user.company_id.id,
|
|
})
|
|
self.product1 = self.env['product.product'].create({
|
|
'name': 'Large Desk',
|
|
'standard_price': 1299.0,
|
|
'list_price': 1799.0,
|
|
'type': 'product',
|
|
})
|
|
Account = self.env['account.account']
|
|
self.stock_input_account = Account.create({
|
|
'name': 'Stock Input',
|
|
'code': 'StockIn',
|
|
'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
|
|
'reconcile': True,
|
|
})
|
|
self.stock_output_account = Account.create({
|
|
'name': 'Stock Output',
|
|
'code': 'StockOut',
|
|
'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
|
|
'reconcile': True,
|
|
})
|
|
self.stock_valuation_account = Account.create({
|
|
'name': 'Stock Valuation',
|
|
'code': 'Stock Valuation',
|
|
'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',
|
|
'type': 'general',
|
|
})
|
|
self.product1.categ_id.write({
|
|
'property_stock_account_input_categ_id': self.stock_input_account.id,
|
|
'property_stock_account_output_categ_id': self.stock_output_account.id,
|
|
'property_stock_valuation_account_id': self.stock_valuation_account.id,
|
|
'property_stock_journal': self.stock_journal.id,
|
|
})
|
|
|
|
def test_change_unit_cost_average_1(self):
|
|
""" Confirm a purchase order and create the associated receipt, change the unit cost of the
|
|
purchase order before validating the receipt, the value of the received goods should be set
|
|
according to the last unit cost.
|
|
"""
|
|
self.product1.product_tmpl_id.categ_id.property_cost_method = 'average'
|
|
po1 = self.env['purchase.order'].create({
|
|
'partner_id': self.partner_id.id,
|
|
'order_line': [
|
|
(0, 0, {
|
|
'name': self.product1.name,
|
|
'product_id': self.product1.id,
|
|
'product_qty': 10.0,
|
|
'product_uom': self.product1.uom_po_id.id,
|
|
'price_unit': 100.0,
|
|
'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
|
}),
|
|
],
|
|
})
|
|
po1.button_confirm()
|
|
|
|
picking1 = po1.picking_ids[0]
|
|
move1 = picking1.move_lines[0]
|
|
|
|
# the unit price of the purchase order line is copied to the in move
|
|
self.assertEqual(move1.price_unit, 100)
|
|
|
|
# update the unit price on the purchase order line
|
|
po1.order_line.price_unit = 200
|
|
|
|
# the unit price on the stock move is not directly updated
|
|
self.assertEqual(move1.price_unit, 100)
|
|
|
|
# validate the receipt
|
|
res_dict = picking1.button_validate()
|
|
wizard = Form(self.env[(res_dict.get('res_model'))].with_context(res_dict['context'])).save()
|
|
wizard.process()
|
|
|
|
# the unit price of the valuationlayer used the latest value
|
|
self.assertEqual(move1.stock_valuation_layer_ids.unit_cost, 200)
|
|
|
|
self.assertEqual(self.product1.value_svl, 2000)
|
|
|
|
def test_standard_price_change_1(self):
|
|
""" Confirm a purchase order and create the associated receipt, change the unit cost of the
|
|
purchase order and the standard price of the product before validating the receipt, the
|
|
value of the received goods should be set according to the last standard price.
|
|
"""
|
|
self.product1.product_tmpl_id.categ_id.property_cost_method = 'standard'
|
|
|
|
# set a standard price
|
|
self.product1.product_tmpl_id.standard_price = 10
|
|
|
|
po1 = self.env['purchase.order'].create({
|
|
'partner_id': self.partner_id.id,
|
|
'order_line': [
|
|
(0, 0, {
|
|
'name': self.product1.name,
|
|
'product_id': self.product1.id,
|
|
'product_qty': 10.0,
|
|
'product_uom': self.product1.uom_po_id.id,
|
|
'price_unit': 11.0,
|
|
'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
|
}),
|
|
],
|
|
})
|
|
po1.button_confirm()
|
|
|
|
picking1 = po1.picking_ids[0]
|
|
move1 = picking1.move_lines[0]
|
|
|
|
# the move's unit price reflects the purchase order line's cost even if it's useless when
|
|
# the product's cost method is standard
|
|
self.assertEqual(move1.price_unit, 11)
|
|
|
|
# set a new standard price
|
|
self.product1.product_tmpl_id.standard_price = 12
|
|
|
|
# the unit price on the stock move is not directly updated
|
|
self.assertEqual(move1.price_unit, 11)
|
|
|
|
# validate the receipt
|
|
res_dict = picking1.button_validate()
|
|
wizard = Form(self.env[(res_dict.get('res_model'))].with_context(res_dict['context'])).save()
|
|
wizard.process()
|
|
|
|
# the unit price of the valuation layer used the latest value
|
|
self.assertEqual(move1.stock_valuation_layer_ids.unit_cost, 12)
|
|
|
|
self.assertEqual(self.product1.value_svl, 120)
|
|
|
|
def test_change_currency_rate_average_1(self):
|
|
""" Confirm a purchase order in another currency and create the associated receipt, change
|
|
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.company.currency_id = usd_currency.id
|
|
|
|
eur_currency = self.env.ref('base.EUR')
|
|
|
|
self.product1.product_tmpl_id.categ_id.property_cost_method = 'average'
|
|
|
|
# default currency is USD, create a purchase order in EUR
|
|
po1 = self.env['purchase.order'].create({
|
|
'partner_id': self.partner_id.id,
|
|
'currency_id': eur_currency.id,
|
|
'order_line': [
|
|
(0, 0, {
|
|
'name': self.product1.name,
|
|
'product_id': self.product1.id,
|
|
'product_qty': 10.0,
|
|
'product_uom': self.product1.uom_po_id.id,
|
|
'price_unit': 100.0,
|
|
'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
|
}),
|
|
],
|
|
})
|
|
po1.button_confirm()
|
|
|
|
picking1 = po1.picking_ids[0]
|
|
move1 = picking1.move_lines[0]
|
|
|
|
# convert the price unit in the company currency
|
|
price_unit_usd = po1.currency_id._convert(
|
|
po1.order_line.price_unit, po1.company_id.currency_id,
|
|
self.env.company, fields.Date.today(), round=False)
|
|
|
|
# the unit price of the move is the unit price of the purchase order line converted in
|
|
# the company's currency
|
|
self.assertAlmostEqual(move1.price_unit, price_unit_usd, places=2)
|
|
|
|
# change the rate of the currency
|
|
self.env['res.currency.rate'].create({
|
|
'name': time.strftime('%Y-%m-%d'),
|
|
'rate': 2.0,
|
|
'currency_id': eur_currency.id,
|
|
'company_id': po1.company_id.id,
|
|
})
|
|
eur_currency._compute_current_rate()
|
|
price_unit_usd_new_rate = po1.currency_id._convert(
|
|
po1.order_line.price_unit, po1.company_id.currency_id,
|
|
self.env.company, fields.Date.today(), round=False)
|
|
|
|
# the new price_unit is lower than th initial because of the rate's change
|
|
self.assertLess(price_unit_usd_new_rate, price_unit_usd)
|
|
|
|
# the unit price on the stock move is not directly updated
|
|
self.assertAlmostEqual(move1.price_unit, price_unit_usd, places=2)
|
|
|
|
# validate the receipt
|
|
res_dict = picking1.button_validate()
|
|
wizard = Form(self.env[(res_dict.get('res_model'))].with_context(res_dict['context'])).save()
|
|
wizard.process()
|
|
|
|
# the unit price of the valuation layer used the latest value
|
|
self.assertAlmostEqual(move1.stock_valuation_layer_ids.unit_cost, price_unit_usd_new_rate)
|
|
|
|
self.assertAlmostEqual(self.product1.value_svl, price_unit_usd_new_rate * 10, delta=0.1)
|
|
|
|
def test_extra_move_fifo_1(self):
|
|
""" Check that the extra move when over processing a receipt is correctly merged back in
|
|
the original move.
|
|
"""
|
|
self.product1.product_tmpl_id.categ_id.property_cost_method = 'fifo'
|
|
po1 = self.env['purchase.order'].create({
|
|
'partner_id': self.partner_id.id,
|
|
'order_line': [
|
|
(0, 0, {
|
|
'name': self.product1.name,
|
|
'product_id': self.product1.id,
|
|
'product_qty': 10.0,
|
|
'product_uom': self.product1.uom_po_id.id,
|
|
'price_unit': 100.0,
|
|
'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
|
}),
|
|
],
|
|
})
|
|
po1.button_confirm()
|
|
|
|
picking1 = po1.picking_ids[0]
|
|
move1 = picking1.move_lines[0]
|
|
move1.quantity_done = 15
|
|
picking1.button_validate()
|
|
|
|
# there should be only one move
|
|
self.assertEqual(len(picking1.move_lines), 1)
|
|
self.assertEqual(move1.price_unit, 100)
|
|
self.assertEqual(move1.stock_valuation_layer_ids.unit_cost, 100)
|
|
self.assertEqual(move1.product_qty, 15)
|
|
self.assertEqual(self.product1.value_svl, 1500)
|
|
|
|
def test_backorder_fifo_1(self):
|
|
""" Check that the backordered move when under processing a receipt correctly keep the
|
|
price unit of the original move.
|
|
"""
|
|
self.product1.product_tmpl_id.categ_id.property_cost_method = 'fifo'
|
|
po1 = self.env['purchase.order'].create({
|
|
'partner_id': self.partner_id.id,
|
|
'order_line': [
|
|
(0, 0, {
|
|
'name': self.product1.name,
|
|
'product_id': self.product1.id,
|
|
'product_qty': 10.0,
|
|
'product_uom': self.product1.uom_po_id.id,
|
|
'price_unit': 100.0,
|
|
'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
|
}),
|
|
],
|
|
})
|
|
po1.button_confirm()
|
|
|
|
picking1 = po1.picking_ids[0]
|
|
move1 = picking1.move_lines[0]
|
|
move1.quantity_done = 5
|
|
res_dict = picking1.button_validate()
|
|
self.assertEqual(res_dict['res_model'], 'stock.backorder.confirmation')
|
|
wizard = self.env[(res_dict.get('res_model'))].browse(res_dict.get('res_id')).with_context(res_dict['context'])
|
|
wizard.process()
|
|
|
|
self.assertEqual(len(picking1.move_lines), 1)
|
|
self.assertEqual(move1.price_unit, 100)
|
|
self.assertEqual(move1.product_qty, 5)
|
|
|
|
picking2 = po1.picking_ids.filtered(lambda p: p.backorder_id)
|
|
move2 = picking2.move_lines[0]
|
|
self.assertEqual(len(picking2.move_lines), 1)
|
|
self.assertEqual(move2.price_unit, 100)
|
|
self.assertEqual(move2.product_qty, 5)
|
|
|
|
|
|
@tagged('post_install', '-at_install')
|
|
class TestStockValuationWithCOA(AccountTestInvoicingCommon):
|
|
|
|
@classmethod
|
|
def setUpClass(cls, chart_template_ref=None):
|
|
super().setUpClass(chart_template_ref=chart_template_ref)
|
|
|
|
cls.supplier_location = cls.env.ref('stock.stock_location_suppliers')
|
|
cls.stock_location = cls.env.ref('stock.stock_location_stock')
|
|
cls.partner_id = cls.env['res.partner'].create({'name': 'Wood Corner Partner'})
|
|
cls.product1 = cls.env['product.product'].create({'name': 'Large Desk'})
|
|
|
|
cls.cat = cls.env['product.category'].create({
|
|
'name': 'cat',
|
|
})
|
|
cls.product1 = cls.env['product.product'].create({
|
|
'name': 'product1',
|
|
'type': 'product',
|
|
'categ_id': cls.cat.id,
|
|
})
|
|
cls.product1_copy = cls.env['product.product'].create({
|
|
'name': 'product1',
|
|
'type': 'product',
|
|
'categ_id': cls.cat.id,
|
|
})
|
|
|
|
Account = cls.env['account.account']
|
|
cls.usd_currency = cls.env.ref('base.USD')
|
|
cls.eur_currency = cls.env.ref('base.EUR')
|
|
|
|
cls.stock_input_account = Account.create({
|
|
'name': 'Stock Input',
|
|
'code': 'StockIn',
|
|
'user_type_id': cls.env.ref('account.data_account_type_current_assets').id,
|
|
'reconcile': True,
|
|
})
|
|
cls.stock_output_account = Account.create({
|
|
'name': 'Stock Output',
|
|
'code': 'StockOut',
|
|
'user_type_id': cls.env.ref('account.data_account_type_current_assets').id,
|
|
'reconcile': True,
|
|
})
|
|
cls.stock_valuation_account = Account.create({
|
|
'name': 'Stock Valuation',
|
|
'code': 'Stock Valuation',
|
|
'user_type_id': cls.env.ref('account.data_account_type_current_assets').id,
|
|
})
|
|
cls.price_diff_account = Account.create({
|
|
'name': 'price diff account',
|
|
'code': 'price diff account',
|
|
'user_type_id': cls.env.ref('account.data_account_type_current_assets').id,
|
|
})
|
|
cls.stock_journal = cls.env['account.journal'].create({
|
|
'name': 'Stock Journal',
|
|
'code': 'STJTEST',
|
|
'type': 'general',
|
|
})
|
|
cls.product1.categ_id.write({
|
|
'property_stock_account_input_categ_id': cls.stock_input_account.id,
|
|
'property_stock_account_output_categ_id': cls.stock_output_account.id,
|
|
'property_stock_valuation_account_id': cls.stock_valuation_account.id,
|
|
'property_stock_journal': cls.stock_journal.id,
|
|
})
|
|
|
|
def test_fifo_anglosaxon_return(self):
|
|
self.env.company.anglo_saxon_accounting = True
|
|
self.product1.product_tmpl_id.categ_id.property_cost_method = 'fifo'
|
|
self.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'
|
|
self.product1.property_account_creditor_price_difference = self.price_diff_account
|
|
|
|
# Receive 10@10 ; create the vendor bill
|
|
po1 = self.env['purchase.order'].create({
|
|
'partner_id': self.partner_id.id,
|
|
'order_line': [
|
|
(0, 0, {
|
|
'name': self.product1.name,
|
|
'product_id': self.product1.id,
|
|
'product_qty': 10.0,
|
|
'product_uom': self.product1.uom_po_id.id,
|
|
'price_unit': 10.0,
|
|
'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
|
}),
|
|
],
|
|
})
|
|
po1.button_confirm()
|
|
receipt_po1 = po1.picking_ids[0]
|
|
receipt_po1.move_lines.quantity_done = 10
|
|
receipt_po1.button_validate()
|
|
|
|
move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
|
|
move_form.partner_id = self.partner_id
|
|
move_form.purchase_id = po1
|
|
invoice_po1 = move_form.save()
|
|
invoice_po1.action_post()
|
|
|
|
# Receive 10@20 ; create the vendor bill
|
|
po2 = self.env['purchase.order'].create({
|
|
'partner_id': self.partner_id.id,
|
|
'order_line': [
|
|
(0, 0, {
|
|
'name': self.product1.name,
|
|
'product_id': self.product1.id,
|
|
'product_qty': 10.0,
|
|
'product_uom': self.product1.uom_po_id.id,
|
|
'price_unit': 20.0,
|
|
'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
|
}),
|
|
],
|
|
})
|
|
po2.button_confirm()
|
|
receipt_po2 = po2.picking_ids[0]
|
|
receipt_po2.move_lines.quantity_done = 10
|
|
receipt_po2.button_validate()
|
|
|
|
move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
|
|
move_form.partner_id = self.partner_id
|
|
move_form.purchase_id = po2
|
|
invoice_po2 = move_form.save()
|
|
invoice_po2.action_post()
|
|
|
|
# valuation of product1 should be 300
|
|
self.assertEqual(self.product1.value_svl, 300)
|
|
|
|
# return the second po
|
|
stock_return_picking_form = Form(self.env['stock.return.picking']
|
|
.with_context(active_ids=receipt_po2.ids, active_id=receipt_po2.ids[0],
|
|
active_model='stock.picking'))
|
|
stock_return_picking = stock_return_picking_form.save()
|
|
stock_return_picking.product_return_moves.quantity = 10
|
|
stock_return_picking_action = stock_return_picking.create_returns()
|
|
return_pick = self.env['stock.picking'].browse(stock_return_picking_action['res_id'])
|
|
return_pick.move_lines[0].move_line_ids[0].qty_done = 10
|
|
return_pick.button_validate()
|
|
|
|
# valuation of product1 should be 200 as the first items will be sent out
|
|
self.assertEqual(self.product1.value_svl, 200)
|
|
|
|
# create a credit note for po2
|
|
move_form = Form(self.env['account.move'].with_context(default_move_type='in_refund'))
|
|
move_form.partner_id = self.partner_id
|
|
move_form.purchase_id = po2
|
|
with move_form.invoice_line_ids.edit(0) as line_form:
|
|
line_form.quantity = 10
|
|
creditnote_po2 = move_form.save()
|
|
creditnote_po2.action_post()
|
|
|
|
# check the anglo saxon entries
|
|
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):
|
|
self.env.company.anglo_saxon_accounting = True
|
|
self.product1.product_tmpl_id.categ_id.property_cost_method = 'fifo'
|
|
self.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'
|
|
self.product1.property_account_creditor_price_difference = self.price_diff_account
|
|
|
|
# Create PO
|
|
po_form = Form(self.env['purchase.order'])
|
|
po_form.partner_id = self.partner_id
|
|
with po_form.order_line.new() as po_line:
|
|
po_line.product_id = self.product1
|
|
po_line.product_qty = 1
|
|
po_line.price_unit = 10.0
|
|
order = po_form.save()
|
|
order.button_confirm()
|
|
|
|
# Receive the goods
|
|
receipt = order.picking_ids[0]
|
|
receipt.move_lines.quantity_done = 1
|
|
receipt.button_validate()
|
|
|
|
# Create an invoice with a different price
|
|
move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
|
|
move_form.partner_id = order.partner_id
|
|
move_form.purchase_id = order
|
|
with move_form.invoice_line_ids.edit(0) as line_form:
|
|
line_form.price_unit = 15.0
|
|
invoice = move_form.save()
|
|
invoice.action_post()
|
|
|
|
# Check what was posted in the price difference account
|
|
price_diff_aml = self.env['account.move.line'].search([('account_id','=', self.price_diff_account.id)])
|
|
self.assertEqual(len(price_diff_aml), 1, "Only one line should have been generated in the price difference account.")
|
|
self.assertAlmostEqual(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)])
|
|
self.assertEqual(len(input_aml), 3, "Only three lines should have been generated in stock input account: one when receiving the product, one when making the invoice.")
|
|
invoice_amls = input_aml.filtered(lambda l: l.move_id == invoice)
|
|
picking_aml = input_aml - invoice_amls
|
|
self.assertAlmostEqual(sum(invoice_amls.mapped('debit')), 15, "Total debit value on stock input account should be equal to the original PO price of the product.")
|
|
self.assertAlmostEqual(sum(invoice_amls.mapped('credit')), 5, "Total debit value on stock input account should be equal to the original PO price of the product.")
|
|
self.assertAlmostEqual(sum(picking_aml.mapped('credit')), 10, "Total credit value on stock input account should be equal to the original PO price of the product.")
|
|
|
|
def test_valuation_from_increasing_tax(self):
|
|
""" Check that a tax without account will increment the stock value.
|
|
"""
|
|
|
|
tax_with_no_account = self.env['account.tax'].create({
|
|
'name': "Tax with no account",
|
|
'amount_type': 'fixed',
|
|
'amount': 5,
|
|
'sequence': 8,
|
|
})
|
|
|
|
self.product1.product_tmpl_id.categ_id.property_cost_method = 'fifo'
|
|
self.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'
|
|
|
|
# Receive 10@10 ; create the vendor bill
|
|
po1 = self.env['purchase.order'].create({
|
|
'partner_id': self.partner_id.id,
|
|
'order_line': [
|
|
(0, 0, {
|
|
'name': self.product1.name,
|
|
'product_id': self.product1.id,
|
|
'taxes_id': [(4, tax_with_no_account.id)],
|
|
'product_qty': 10.0,
|
|
'product_uom': self.product1.uom_po_id.id,
|
|
'price_unit': 10.0,
|
|
'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
|
}),
|
|
],
|
|
})
|
|
po1.button_confirm()
|
|
receipt_po1 = po1.picking_ids[0]
|
|
receipt_po1.move_lines.quantity_done = 10
|
|
receipt_po1.button_validate()
|
|
|
|
# valuation of product1 should be 15 as the tax with no account set
|
|
# has gone to the stock account, and must be reflected in inventory valuation
|
|
self.assertEqual(self.product1.value_svl, 150)
|
|
|
|
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
|
|
company.currency_id = self.usd_currency
|
|
|
|
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.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.move'].with_context(default_move_type='in_invoice').create({
|
|
'move_type': 'in_invoice',
|
|
'invoice_date': date_po,
|
|
'date': date_po,
|
|
'currency_id': self.eur_currency.id,
|
|
'partner_id': self.partner_id.id,
|
|
'invoice_line_ids': [(0, 0, {
|
|
'name': 'Test',
|
|
'price_unit': 100.0,
|
|
'product_id': self.product1.id,
|
|
'purchase_line_id': po.order_line.id,
|
|
'quantity': 1.0,
|
|
'account_id': self.stock_input_account.id,
|
|
})]
|
|
})
|
|
|
|
inv.action_post()
|
|
|
|
move_lines = inv.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
|
|
company.currency_id = self.usd_currency
|
|
self.product1.product_tmpl_id.categ_id.property_cost_method = 'average'
|
|
self.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'
|
|
|
|
date_po = '2019-01-01'
|
|
date_invoice = '2019-01-16'
|
|
|
|
# SetUp product Average
|
|
self.product1.product_tmpl_id.write({
|
|
'purchase_method': 'purchase',
|
|
'property_account_creditor_price_difference': self.price_diff_account.id,
|
|
})
|
|
|
|
# SetUp product Standard
|
|
# should have bought at 60 USD
|
|
# actually invoiced at 70 EUR > 35 USD
|
|
product_categ_standard = self.cat.copy({
|
|
'property_cost_method': 'standard',
|
|
'property_stock_account_input_categ_id': self.stock_input_account.id,
|
|
'property_stock_account_output_categ_id': self.stock_output_account.id,
|
|
'property_stock_valuation_account_id': self.stock_valuation_account.id,
|
|
'property_stock_journal': self.stock_journal.id,
|
|
})
|
|
product_standard = self.product1_copy
|
|
product_standard.write({
|
|
'categ_id': product_categ_standard.id,
|
|
'name': 'Standard Val',
|
|
'standard_price': 60,
|
|
'property_account_creditor_price_difference': self.price_diff_account.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.move'].with_context(default_move_type='in_invoice').create({
|
|
'move_type': 'in_invoice',
|
|
'invoice_date': date_invoice,
|
|
'date': date_invoice,
|
|
'currency_id': self.eur_currency.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_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_line_id': line_product_standard.id,
|
|
'quantity': 1.0,
|
|
'account_id': self.stock_input_account.id,
|
|
})
|
|
]
|
|
})
|
|
|
|
inv.action_post()
|
|
|
|
for p in patchers:
|
|
p.stop()
|
|
|
|
move_lines = inv.line_ids
|
|
self.assertEqual(len(move_lines), 5)
|
|
|
|
# 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_lines = product_lines.filtered(lambda l: l.account_id == self.stock_input_account)
|
|
self.assertAlmostEqual(sum(stock_lines.mapped('amount_currency')), 100.00)
|
|
self.assertAlmostEqual(sum(stock_lines.mapped('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_lines = product_lines.filtered(lambda l: l.account_id == self.stock_input_account)
|
|
self.assertAlmostEqual(sum(stock_lines.mapped('amount_currency')), 120.00)
|
|
self.assertAlmostEqual(sum(stock_lines.mapped('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
|
|
company.currency_id = self.usd_currency
|
|
self.product1.product_tmpl_id.categ_id.property_cost_method = 'average'
|
|
self.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'
|
|
|
|
date_po = '2019-01-01'
|
|
date_delivery = '2019-01-08'
|
|
date_invoice = '2019-01-16'
|
|
|
|
product_avg = self.product1_copy
|
|
product_avg.write({
|
|
'purchase_method': 'purchase',
|
|
'name': 'AVG',
|
|
'standard_price': 60,
|
|
'property_account_creditor_price_difference': self.price_diff_account.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_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 and Delivery
|
|
today = date_po
|
|
def _today(*args, **kwargs):
|
|
return datetime.strptime(today, "%Y-%m-%d").date()
|
|
def _now(*args, **kwargs):
|
|
return datetime.strptime(today + ' 01:00:00', "%Y-%m-%d %H:%M:%S")
|
|
|
|
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)
|
|
|
|
today = date_delivery
|
|
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()
|
|
# 5 Units received at rate 0.7 = 42.86
|
|
self.assertAlmostEqual(product_avg.standard_price, 42.86)
|
|
|
|
today = date_invoice
|
|
inv = self.env['account.move'].with_context(default_move_type='in_invoice').create({
|
|
'move_type': 'in_invoice',
|
|
'invoice_date': date_invoice,
|
|
'date': date_invoice,
|
|
'currency_id': self.eur_currency.id,
|
|
'partner_id': self.partner_id.id,
|
|
'invoice_line_ids': [
|
|
(0, 0, {
|
|
'name': product_avg.name,
|
|
'price_unit': 30.0,
|
|
'product_id': product_avg.id,
|
|
'purchase_line_id': line_product_avg.id,
|
|
'quantity': 1.0,
|
|
'account_id': self.stock_input_account.id,
|
|
})
|
|
]
|
|
})
|
|
|
|
inv.action_post()
|
|
|
|
for p in patchers:
|
|
p.stop()
|
|
|
|
move_lines = inv.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)
|
|
|
|
def test_average_realtime_with_two_delivery_anglo_saxon_valuation_multicurrency_different_dates(self):
|
|
"""
|
|
The PO and invoice are in the same foreign currency.
|
|
The deliveries occur at different times and rates
|
|
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
|
|
company.currency_id = self.usd_currency
|
|
exchange_diff_journal = company.currency_exchange_journal_id.exists()
|
|
|
|
date_po = '2019-01-01'
|
|
date_delivery = '2019-01-08'
|
|
date_delivery1 = '2019-01-10'
|
|
date_invoice = '2019-01-16'
|
|
date_invoice1 = '2019-01-20'
|
|
|
|
self.product1.categ_id.property_valuation = 'real_time'
|
|
self.product1.categ_id.property_cost_method = 'average'
|
|
product_avg = self.product1_copy
|
|
product_avg.write({
|
|
'purchase_method': 'purchase',
|
|
'name': 'AVG',
|
|
'standard_price': 0,
|
|
'property_account_creditor_price_difference': self.price_diff_account.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_delivery,
|
|
'rate': 0.7,
|
|
'currency_id': self.eur_currency.id,
|
|
'company_id': company.id,
|
|
})
|
|
self.env['res.currency.rate'].create({
|
|
'name': date_delivery1,
|
|
'rate': 0.8,
|
|
'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,
|
|
})
|
|
self.env['res.currency.rate'].create({
|
|
'name': date_invoice1,
|
|
'rate': 2.2,
|
|
'currency_id': self.eur_currency.id,
|
|
'company_id': company.id,
|
|
})
|
|
|
|
# To allow testing validation of PO and Delivery
|
|
today = date_po
|
|
def _today(*args, **kwargs):
|
|
return datetime.strptime(today, "%Y-%m-%d").date()
|
|
def _now(*args, **kwargs):
|
|
return datetime.strptime(today + ' 01:00:00', "%Y-%m-%d %H:%M:%S")
|
|
|
|
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,
|
|
'date_order': date_po,
|
|
'order_line': [
|
|
(0, 0, {
|
|
'name': product_avg.name,
|
|
'product_id': product_avg.id,
|
|
'product_qty': 10.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)
|
|
|
|
today = date_delivery
|
|
picking = po.picking_ids
|
|
(picking.move_lines
|
|
.filtered(lambda l: l.purchase_line_id == line_product_avg)
|
|
.write({'quantity_done': 5.0}))
|
|
|
|
picking.button_validate()
|
|
picking._action_done() # Create Backorder
|
|
# 5 Units received at rate 0.7 = 42.86
|
|
self.assertAlmostEqual(product_avg.standard_price, 42.86)
|
|
|
|
today = date_invoice
|
|
inv = self.env['account.move'].with_context(default_move_type='in_invoice').create({
|
|
'move_type': 'in_invoice',
|
|
'invoice_date': date_invoice,
|
|
'date': date_invoice,
|
|
'currency_id': self.eur_currency.id,
|
|
'partner_id': self.partner_id.id,
|
|
'invoice_line_ids': [
|
|
(0, 0, {
|
|
'name': product_avg.name,
|
|
'price_unit': 20.0,
|
|
'product_id': product_avg.id,
|
|
'purchase_line_id': line_product_avg.id,
|
|
'quantity': 5.0,
|
|
'account_id': self.stock_input_account.id,
|
|
})
|
|
]
|
|
})
|
|
|
|
inv.action_post()
|
|
|
|
today = date_delivery1
|
|
backorder_picking = self.env['stock.picking'].search([('backorder_id', '=', picking.id)])
|
|
(backorder_picking.move_lines
|
|
.filtered(lambda l: l.purchase_line_id == line_product_avg)
|
|
.write({'quantity_done': 5.0}))
|
|
backorder_picking.button_validate()
|
|
# 5 Units received at rate 0.7 (42.86) + 5 Units received at rate 0.8 (37.50) = 40.18
|
|
self.assertAlmostEqual(product_avg.standard_price, 40.18)
|
|
|
|
today = date_invoice1
|
|
inv1 = self.env['account.move'].with_context(default_move_type='in_invoice').create({
|
|
'move_type': 'in_invoice',
|
|
'invoice_date': date_invoice1,
|
|
'date': date_invoice1,
|
|
'currency_id': self.eur_currency.id,
|
|
'partner_id': self.partner_id.id,
|
|
'invoice_line_ids': [
|
|
(0, 0, {
|
|
'name': product_avg.name,
|
|
'price_unit': 40.0,
|
|
'product_id': product_avg.id,
|
|
'purchase_line_id': line_product_avg.id,
|
|
'quantity': 5.0,
|
|
'account_id': self.stock_input_account.id,
|
|
})
|
|
]
|
|
})
|
|
|
|
inv1.action_post()
|
|
|
|
for p in patchers:
|
|
p.stop()
|
|
|
|
##########################
|
|
# Invoice 0 #
|
|
##########################
|
|
move_lines = inv.line_ids
|
|
self.assertEqual(len(move_lines), 4)
|
|
|
|
# PAYABLE CHECK
|
|
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, -50.00)
|
|
|
|
# # PRODUCTS CHECKS
|
|
|
|
# DELIVERY DIFFERENCE (AVERAGE)
|
|
stock_lines = move_lines.filtered(lambda l: l.account_id == self.stock_input_account)
|
|
self.assertEqual(len(stock_lines), 2)
|
|
self.assertAlmostEqual(sum(stock_lines.mapped('amount_currency')), 150.00)
|
|
self.assertAlmostEqual(sum(stock_lines.mapped('balance')), 75.00)
|
|
|
|
price_diff_line = move_lines.filtered(lambda l: l.account_id == self.price_diff_account)
|
|
self.assertAlmostEqual(price_diff_line.amount_currency, -50.00)
|
|
self.assertAlmostEqual(price_diff_line.balance, -25.00)
|
|
|
|
full_reconcile = stock_lines.mapped('full_reconcile_id')
|
|
self.assertTrue(full_reconcile.exists())
|
|
|
|
reconciled_lines = full_reconcile.reconciled_line_ids - stock_lines
|
|
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, -150)
|
|
self.assertAlmostEqual(stock_journal_line.balance, -214.29)
|
|
|
|
exchange_stock_line = reconciled_lines.filtered(lambda l: l.journal_id == exchange_diff_journal)
|
|
self.assertEqual(exchange_stock_line.amount_currency, 0.00)
|
|
self.assertAlmostEqual(exchange_stock_line.balance, 139.29)
|
|
|
|
##########################
|
|
# Invoice 1 #
|
|
##########################
|
|
move_lines = inv1.line_ids
|
|
self.assertEqual(len(move_lines), 4)
|
|
|
|
# PAYABLE CHECK
|
|
payable_line = move_lines.filtered(lambda l: l.account_id.internal_type == 'payable')
|
|
self.assertEqual(payable_line.amount_currency, -200.0)
|
|
self.assertAlmostEqual(payable_line.balance, -90.91)
|
|
|
|
# # PRODUCTS CHECKS
|
|
|
|
# DELIVERY DIFFERENCE (AVERAGE)
|
|
stock_lines = move_lines.filtered(lambda l: l.account_id == self.stock_input_account)
|
|
self.assertEqual(stock_lines.mapped('journal_id'), inv.journal_id)
|
|
self.assertAlmostEqual(sum(stock_lines.mapped('amount_currency')), 150.00)
|
|
self.assertAlmostEqual(sum(stock_lines.mapped('balance')), 68.18)
|
|
|
|
price_diff_line = move_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, 22.73)
|
|
|
|
full_reconcile = stock_lines.mapped('full_reconcile_id')
|
|
self.assertTrue(full_reconcile.exists())
|
|
|
|
reconciled_lines = full_reconcile.reconciled_line_ids - stock_lines
|
|
self.assertEqual(len(reconciled_lines), 3)
|
|
|
|
stock_journal_line = reconciled_lines.filtered(lambda l: l.journal_id == self.stock_journal)
|
|
self.assertEqual(stock_journal_line.amount_currency, -150)
|
|
self.assertAlmostEqual(stock_journal_line.balance, -187.5)
|
|
|
|
exchange_stock_lines = reconciled_lines.filtered(lambda l: l.journal_id == exchange_diff_journal)
|
|
self.assertAlmostEqual(sum(exchange_stock_lines.mapped('amount_currency')), 0.00)
|
|
self.assertAlmostEqual(sum(exchange_stock_lines.mapped('balance')), 119.32)
|
|
|
|
def test_anglosaxon_valuation_price_total_diff_discount(self):
|
|
"""
|
|
PO: price unit: 110
|
|
Inv: price unit: 100
|
|
discount: 10
|
|
"""
|
|
self.env.company.anglo_saxon_accounting = True
|
|
self.product1.categ_id.property_cost_method = 'fifo'
|
|
self.product1.categ_id.property_valuation = 'real_time'
|
|
self.product1.property_account_creditor_price_difference = self.price_diff_account
|
|
|
|
# Create PO
|
|
po_form = Form(self.env['purchase.order'])
|
|
po_form.partner_id = self.partner_id
|
|
with po_form.order_line.new() as po_line:
|
|
po_line.product_id = self.product1
|
|
po_line.product_qty = 1
|
|
po_line.price_unit = 110.0
|
|
order = po_form.save()
|
|
order.button_confirm()
|
|
|
|
# Receive the goods
|
|
receipt = order.picking_ids[0]
|
|
receipt.move_lines.quantity_done = 1
|
|
receipt.button_validate()
|
|
|
|
# Create an invoice with a different price and a discount
|
|
invoice_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
|
|
invoice_form.purchase_id = order
|
|
with invoice_form.invoice_line_ids.edit(0) as line_form:
|
|
line_form.price_unit = 100.0
|
|
line_form.discount = 10.0
|
|
invoice = invoice_form.save()
|
|
invoice.action_post()
|
|
|
|
# Check what was posted in the price difference account
|
|
price_diff_aml = self.env['account.move.line'].search([('account_id','=', self.price_diff_account.id)])
|
|
self.assertEqual(len(price_diff_aml), 1, "Only one line should have been generated in the price difference account.")
|
|
self.assertAlmostEqual(price_diff_aml.credit, 20, "Price difference should be equal to 20 (110-90)")
|
|
|
|
# Check what was posted in stock input account
|
|
input_aml = self.env['account.move.line'].search([('account_id','=', self.stock_input_account.id)])
|
|
self.assertEqual(len(input_aml), 3, "Only two lines should have been generated in stock input account: one when receiving the product, two when making the invoice.")
|
|
self.assertAlmostEqual(sum(input_aml.mapped('debit')), 110, "Total debit value on stock input account should be equal to the original PO price of the product.")
|
|
self.assertAlmostEqual(sum(input_aml.mapped('credit')), 110, "Total credit value on stock input account should be equal to the original PO price of the product.")
|
|
|
|
def test_anglosaxon_valuation_discount(self):
|
|
"""
|
|
PO: price unit: 100
|
|
Inv: price unit: 100
|
|
discount: 10
|
|
"""
|
|
self.env.company.anglo_saxon_accounting = True
|
|
self.product1.categ_id.property_cost_method = 'fifo'
|
|
self.product1.categ_id.property_valuation = 'real_time'
|
|
self.product1.property_account_creditor_price_difference = self.price_diff_account
|
|
|
|
# Create PO
|
|
po_form = Form(self.env['purchase.order'])
|
|
po_form.partner_id = self.partner_id
|
|
with po_form.order_line.new() as po_line:
|
|
po_line.product_id = self.product1
|
|
po_line.product_qty = 1
|
|
po_line.price_unit = 100.0
|
|
order = po_form.save()
|
|
order.button_confirm()
|
|
|
|
# Receive the goods
|
|
receipt = order.picking_ids[0]
|
|
receipt.move_lines.quantity_done = 1
|
|
receipt.button_validate()
|
|
|
|
# Create an invoice with a different price and a discount
|
|
invoice_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
|
|
invoice_form.purchase_id = order
|
|
with invoice_form.invoice_line_ids.edit(0) as line_form:
|
|
line_form.tax_ids.clear()
|
|
line_form.discount = 10.0
|
|
invoice = invoice_form.save()
|
|
invoice.action_post()
|
|
|
|
# Check what was posted in the price difference account
|
|
price_diff_aml = self.env['account.move.line'].search([('account_id', '=', self.price_diff_account.id)])
|
|
self.assertEqual(len(price_diff_aml), 1, "Only one line should have been generated in the price difference account.")
|
|
self.assertAlmostEqual(price_diff_aml.credit, 10, "Price difference should be equal to 10 (100-90)")
|
|
|
|
# Check what was posted in stock input account
|
|
input_aml = self.env['account.move.line'].search([('account_id', '=', self.stock_input_account.id)])
|
|
self.assertEqual(len(input_aml), 3, "Three lines generated in stock input account: one when receiving the product, two when making the invoice.")
|
|
self.assertAlmostEqual(sum(input_aml.mapped('debit')), 100, "Total debit value on stock input account should be equal to the original PO price of the product.")
|
|
self.assertAlmostEqual(sum(input_aml.mapped('credit')), 100, "Total credit value on stock input account should be equal to the original PO price of the product.")
|
|
|
|
def test_anglosaxon_valuation_price_unit_diff_discount(self):
|
|
"""
|
|
PO: price unit: 90
|
|
Inv: price unit: 100
|
|
discount: 10
|
|
"""
|
|
self.env.company.anglo_saxon_accounting = True
|
|
self.product1.categ_id.property_cost_method = 'fifo'
|
|
self.product1.categ_id.property_valuation = 'real_time'
|
|
self.product1.property_account_creditor_price_difference = self.price_diff_account
|
|
|
|
# Create PO
|
|
po_form = Form(self.env['purchase.order'])
|
|
po_form.partner_id = self.partner_id
|
|
with po_form.order_line.new() as po_line:
|
|
po_line.product_id = self.product1
|
|
po_line.product_qty = 1
|
|
po_line.price_unit = 90.0
|
|
order = po_form.save()
|
|
order.button_confirm()
|
|
|
|
# Receive the goods
|
|
receipt = order.picking_ids[0]
|
|
receipt.move_lines.quantity_done = 1
|
|
receipt.button_validate()
|
|
|
|
# Create an invoice with a different price and a discount
|
|
invoice_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
|
|
invoice_form.purchase_id = order
|
|
with invoice_form.invoice_line_ids.edit(0) as line_form:
|
|
line_form.price_unit = 100.0
|
|
line_form.discount = 10.0
|
|
invoice = invoice_form.save()
|
|
invoice.action_post()
|
|
|
|
# Check if something was posted in the price difference account
|
|
price_diff_aml = self.env['account.move.line'].search([('account_id','=', self.price_diff_account.id)])
|
|
self.assertEqual(len(price_diff_aml), 0, "No line should have been generated in the price difference account.")
|
|
|
|
# Check what was posted in stock input account
|
|
input_aml = self.env['account.move.line'].search([('account_id','=', self.stock_input_account.id)])
|
|
self.assertEqual(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.assertAlmostEqual(sum(input_aml.mapped('debit')), 90, "Total debit value on stock input account should be equal to the original PO price of the product.")
|
|
self.assertAlmostEqual(sum(input_aml.mapped('credit')), 90, "Total credit value on stock input account should be equal to the original PO price of the product.")
|