[REF] stock_account: fifo "negative" cost method with valuation layers
Re-implement _run_fifo_vacuum on the product this time with float_is_zero and without remaining_value. Trigger the vacuum at each receipt. Before it was done in the scheduler each night, meaning the valuation on the move was not really understandable nor consistent (the estimated value could be fixed by some other move than the next receipt). task-1875873
This commit is contained in:
@@ -255,6 +255,81 @@ class ProductProduct(models.Model):
|
||||
}
|
||||
return vals
|
||||
|
||||
def _run_fifo_vacuum(self):
|
||||
"""Compensate layer valued at an estimated price with the price of future receipts
|
||||
if any. If the estimated price is equals to the real price, no layer is created but
|
||||
the original layer is marked as compensated.
|
||||
"""
|
||||
self.ensure_one()
|
||||
svls_to_vacuum = self.env['stock.valuation.layer'].search([
|
||||
('product_id', '=', self.id),
|
||||
('remaining_qty', '<', 0),
|
||||
('stock_move_id', '!=', False),
|
||||
])
|
||||
for svl_to_vacuum in svls_to_vacuum:
|
||||
domain = [
|
||||
('company_id', '=', svl_to_vacuum.company_id.id),
|
||||
('product_id', '=', self.id),
|
||||
('remaining_qty', '>', 0),
|
||||
'|',
|
||||
('create_date', '>', svl_to_vacuum.create_date),
|
||||
'&',
|
||||
('create_date', '=', svl_to_vacuum.create_date),
|
||||
('id', '>', svl_to_vacuum.id)
|
||||
]
|
||||
candidates = self.env['stock.valuation.layer'].search(domain)
|
||||
if not candidates:
|
||||
continue
|
||||
qty_to_take_on_candidates = abs(svl_to_vacuum.remaining_qty)
|
||||
qty_taken_on_candidates = 0
|
||||
tmp_value = 0
|
||||
for candidate in candidates:
|
||||
qty_taken_on_candidate = min(candidate.remaining_qty, qty_to_take_on_candidates)
|
||||
qty_taken_on_candidates += qty_taken_on_candidate
|
||||
|
||||
value_taken_on_candidate = qty_taken_on_candidate * candidate.unit_cost
|
||||
candidate_vals = {
|
||||
'remaining_qty': candidate.remaining_qty - qty_taken_on_candidate,
|
||||
}
|
||||
candidate.write(candidate_vals)
|
||||
|
||||
qty_to_take_on_candidates -= qty_taken_on_candidate
|
||||
tmp_value += value_taken_on_candidate
|
||||
if float_is_zero(qty_to_take_on_candidates, precision_rounding=self.uom_id.rounding):
|
||||
break
|
||||
|
||||
# Get the estimated value we will correct.
|
||||
remaining_value_before_vacuum = svl_to_vacuum.unit_cost * qty_taken_on_candidates
|
||||
new_remaining_qty = svl_to_vacuum.remaining_qty + qty_taken_on_candidates
|
||||
corrected_value = remaining_value_before_vacuum - tmp_value
|
||||
svl_to_vacuum.write({
|
||||
'remaining_qty': new_remaining_qty,
|
||||
})
|
||||
|
||||
# Don't create a layer or an accounting entry if the corrected value is zero.
|
||||
if svl_to_vacuum.currency_id.is_zero(corrected_value):
|
||||
continue
|
||||
|
||||
corrected_value = svl_to_vacuum.currency_id.round(corrected_value)
|
||||
move = svl_to_vacuum.stock_move_id
|
||||
vals = {
|
||||
'product_id': self.id,
|
||||
'value': corrected_value,
|
||||
'unit_cost': 0,
|
||||
'quantity': 0,
|
||||
'remaining_qty': 0,
|
||||
'description': 'vacuum',
|
||||
'stock_move_id': move.id,
|
||||
'company_id': move.company_id.id,
|
||||
'description': 'Revaluation of %s (negative inventory)' % move.picking_id.name or move.name,
|
||||
}
|
||||
vacuum_svl = self.env['stock.valuation.layer'].create(vals)
|
||||
|
||||
# Create the account move.
|
||||
if self.valuation != 'real_time':
|
||||
continue
|
||||
vacuum_svl.stock_move_id.with_context(svl_id=vacuum_svl.id, force_valuation_amount=vacuum_svl.value, forced_quantity=vacuum_svl.quantity, forced_ref=vacuum_svl.description)._account_entry_move()
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Miscellaneous
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@@ -402,6 +402,12 @@ class StockMove(models.Model):
|
||||
if not stock_valuation_layer.product_id.valuation == 'real_time':
|
||||
continue
|
||||
stock_valuation_layer.stock_move_id.with_context(svl_id=stock_valuation_layer.id, force_valuation_amount=abs(stock_valuation_layer.value), forced_quantity=stock_valuation_layer.quantity)._account_entry_move()
|
||||
|
||||
# For every in move, run the vacuum for the linked product.
|
||||
products_to_vacuum = valued_moves['in'].mapped('product_id')
|
||||
for product_to_vacuum in products_to_vacuum:
|
||||
product_to_vacuum._run_fifo_vacuum()
|
||||
|
||||
return res
|
||||
|
||||
def _sanity_check_for_valuation(self):
|
||||
@@ -714,6 +720,10 @@ class StockMove(models.Model):
|
||||
|
||||
# Create Journal Entry for products leaving the company
|
||||
if self._is_out():
|
||||
if self.env.context.get('force_valuation_amount'):
|
||||
force_valuation_amount = self.env.context['force_valuation_amount']
|
||||
self.env.context = dict(self.env.context, force_valuation_amount=-1*force_valuation_amount)
|
||||
|
||||
journal_id, acc_src, acc_dest, acc_valuation = self._get_accounting_data_for_valuation()
|
||||
if location_to and location_to.usage == 'supplier': # goods returned to supplier
|
||||
self.with_context(force_company=company_from.id)._create_account_move_line(acc_valuation, acc_src, journal_id)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -312,6 +312,19 @@ class TestStockValuationFIFO(TestStockValuationCommon):
|
||||
self.assertEqual(self.product1.quantity_svl, 5)
|
||||
self.assertEqual(sum(self.product1.stock_valuation_layer_ids.mapped('remaining_qty')), 5)
|
||||
|
||||
def test_negative_1(self):
|
||||
self.product1.product_tmpl_id.categ_id.property_valuation = 'manual_periodic'
|
||||
move1 = self._make_in_move(self.product1, 10, unit_cost=10)
|
||||
move2 = self._make_in_move(self.product1, 10, unit_cost=20)
|
||||
move3 = self._make_out_move(self.product1, 30)
|
||||
self.assertEqual(move3.stock_valuation_layer_ids.remaining_qty, -10)
|
||||
move4 = self._make_in_move(self.product1, 10, unit_cost=30)
|
||||
self.assertEqual(sum(self.product1.stock_valuation_layer_ids.mapped('remaining_qty')), 0)
|
||||
move5 = self._make_in_move(self.product1, 10, unit_cost=40)
|
||||
|
||||
self.assertEqual(self.product1.value_svl, 400)
|
||||
self.assertEqual(self.product1.quantity_svl, 10)
|
||||
|
||||
def test_change_in_past_decrease_in_1(self):
|
||||
self.product1.product_tmpl_id.categ_id.property_valuation = 'manual_periodic'
|
||||
move1 = self._make_in_move(self.product1, 20, unit_cost=10)
|
||||
@@ -321,14 +334,13 @@ class TestStockValuationFIFO(TestStockValuationCommon):
|
||||
self.assertEqual(self.product1.value_svl, 0)
|
||||
self.assertEqual(self.product1.quantity_svl, 0)
|
||||
|
||||
@skip('fifo negative')
|
||||
def test_change_in_past_decrease_in_2(self):
|
||||
self.product1.product_tmpl_id.categ_id.property_valuation = 'manual_periodic'
|
||||
move1 = self._make_in_move(self.product1, 20, unit_cost=10)
|
||||
move2 = self._make_out_move(self.product1, 10)
|
||||
move3 = self._make_out_move(self.product1, 10)
|
||||
move1.move_line_ids.qty_done = 10
|
||||
move1 = self._make_in_move(self.product1, 20, unit_cost=15)
|
||||
move1.move_line_ids.with_context(svl=True).qty_done = 10
|
||||
move4 = self._make_in_move(self.product1, 20, unit_cost=15)
|
||||
|
||||
self.assertEqual(self.product1.value_svl, 150)
|
||||
self.assertEqual(self.product1.quantity_svl, 10)
|
||||
@@ -343,7 +355,6 @@ class TestStockValuationFIFO(TestStockValuationCommon):
|
||||
self.assertEqual(self.product1.value_svl, 100)
|
||||
self.assertEqual(self.product1.quantity_svl, 10)
|
||||
|
||||
@skip('fifo negative')
|
||||
def test_change_in_past_increase_in_2(self):
|
||||
self.product1.product_tmpl_id.categ_id.property_valuation = 'manual_periodic'
|
||||
move1 = self._make_in_move(self.product1, 10, unit_cost=10)
|
||||
@@ -351,7 +362,7 @@ class TestStockValuationFIFO(TestStockValuationCommon):
|
||||
move3 = self._make_out_move(self.product1, 15)
|
||||
move4 = self._make_out_move(self.product1, 20)
|
||||
move5 = self._make_in_move(self.product1, 100, unit_cost=15)
|
||||
move1.move_line_ids.qty_done = 20
|
||||
move1.move_line_ids.with_context(svl=True).qty_done = 20
|
||||
|
||||
self.assertEqual(self.product1.value_svl, 1375)
|
||||
self.assertEqual(self.product1.quantity_svl, 95)
|
||||
|
||||
Reference in New Issue
Block a user