274 lines
11 KiB
Python
274 lines
11 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
|
|
|
import time
|
|
from datetime import datetime
|
|
|
|
from flectra.tests.common import TransactionCase
|
|
from flectra.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.ref('base.res_partner_1')
|
|
self.product1 = self.env.ref('product.product_product_8')
|
|
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,
|
|
})
|
|
self.stock_output_account = Account.create({
|
|
'name': 'Stock Output',
|
|
'code': 'StockOut',
|
|
'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
|
|
})
|
|
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.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.assertEquals(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.assertEquals(move1.price_unit, 100)
|
|
|
|
# validate the receipt
|
|
res_dict = picking1.button_validate()
|
|
wizard = self.env[(res_dict.get('res_model'))].browse(res_dict.get('res_id'))
|
|
wizard.process()
|
|
|
|
# the unit price of the stock move has been updated to the latest value
|
|
self.assertEquals(move1.price_unit, 200)
|
|
|
|
self.assertEquals(self.product1.stock_value, 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.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.assertEquals(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.assertEquals(move1.price_unit, 11)
|
|
|
|
# validate the receipt
|
|
res_dict = picking1.button_validate()
|
|
wizard = self.env[(res_dict.get('res_model'))].browse(res_dict.get('res_id'))
|
|
wizard.process()
|
|
|
|
# the unit price of the stock move has been updated to the latest value
|
|
self.assertEquals(move1.price_unit, 12)
|
|
|
|
self.assertEquals(self.product1.stock_value, 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.
|
|
"""
|
|
usd_currency = self.env.ref('base.USD')
|
|
self.env.user.company_id.currency_id = usd_currency.id
|
|
|
|
eur_currency = self.env.ref('base.EUR')
|
|
|
|
self.product1.product_tmpl_id.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.compute(po1.order_line.price_unit, po1.company_id.currency_id, round=True)
|
|
|
|
# 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)
|
|
|
|
# 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.compute(po1.order_line.price_unit, po1.company_id.currency_id, round=True)
|
|
|
|
# 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)
|
|
|
|
# validate the receipt
|
|
res_dict = picking1.button_validate()
|
|
wizard = self.env[(res_dict.get('res_model'))].browse(res_dict.get('res_id'))
|
|
wizard.process()
|
|
|
|
# the unit price of the stock move has been updated to the latest value
|
|
self.assertEquals(round(move1.price_unit), round(price_unit_usd_new_rate))
|
|
|
|
self.assertAlmostEqual(self.product1.stock_value, 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.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
|
|
res_dict = picking1.button_validate()
|
|
self.assertEqual(res_dict['res_model'], 'stock.overprocessed.transfer')
|
|
wizard = self.env[(res_dict.get('res_model'))].browse(res_dict.get('res_id'))
|
|
wizard.action_confirm()
|
|
|
|
# there should be only one move
|
|
self.assertEqual(len(picking1.move_lines), 1)
|
|
self.assertEqual(move1.price_unit, 100)
|
|
self.assertEqual(move1.product_qty, 15)
|
|
self.assertEqual(self.product1.stock_value, 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.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'))
|
|
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)
|
|
|