[FIX] stock_account: the quantity to value depends on the sublocations
We can imagine a scenario into which the main stock location (wh/stock) is set as a view location to force people to put things in the sublocations. Since the stock_account refactoring, only the location of the stock move is considered to know if the locations should be valued and, if so, the quantity to value. It is problematic since a view location shouldn't be valued, but if its move lines put or take things to or from a valued location to a non valued one, the valuation mechanism should run. We now introduce these specifications to handle this kind of usecase correctly: - the authority to get the price_unit is on the stock move - the authority to now how much quantity should be valued is delegated to the move lines.
This commit is contained in:
@@ -198,7 +198,10 @@ class StockMove(models.Model):
|
||||
def _run_fifo(self, move, quantity=None):
|
||||
move.ensure_one()
|
||||
# Find back incoming stock moves (called candidates here) to value this move.
|
||||
qty_to_take_on_candidates = quantity or move.product_qty
|
||||
valued_move_lines = move.move_line_ids.filtered(lambda ml: ml.location_id._should_be_valued() and not ml.location_dest_id._should_be_valued())
|
||||
valued_quantity = sum(valued_move_lines.mapped('qty_done'))
|
||||
|
||||
qty_to_take_on_candidates = quantity or valued_quantity
|
||||
candidates = move.product_id._get_fifo_candidates_in_move()
|
||||
new_standard_price = 0
|
||||
tmp_value = 0 # to accumulate the value taken on the candidates
|
||||
@@ -252,32 +255,37 @@ class StockMove(models.Model):
|
||||
def _run_valuation(self, quantity=None):
|
||||
self.ensure_one()
|
||||
if self._is_in():
|
||||
valued_move_lines = self.move_line_ids.filtered(lambda ml: not ml.location_id._should_be_valued() and ml.location_dest_id._should_be_valued())
|
||||
valued_quantity = sum(valued_move_lines.mapped('qty_done'))
|
||||
|
||||
if self.product_id.cost_method in ['fifo', 'average']:
|
||||
price_unit = self._get_price_unit()
|
||||
value = price_unit * (quantity or self.product_qty)
|
||||
value = price_unit * (quantity or valued_quantity)
|
||||
vals = {
|
||||
'price_unit': price_unit,
|
||||
'value': value if quantity is None or not self.value else self.value,
|
||||
'remaining_value': value if quantity is None else self.remaining_value + value,
|
||||
}
|
||||
if self.product_id.cost_method == 'fifo':
|
||||
vals['remaining_qty'] = self.product_qty if quantity is None else self.remaining_qty + quantity
|
||||
vals['remaining_qty'] = valued_quantity if quantity is None else self.remaining_qty + quantity
|
||||
self.write(vals)
|
||||
else: # standard
|
||||
value = self.product_id.standard_price * (quantity or self.product_qty)
|
||||
value = self.product_id.standard_price * (quantity or valued_quantity)
|
||||
self.write({
|
||||
'price_unit': self.product_id.standard_price,
|
||||
'value': value if quantity is None or not self.value else self.value,
|
||||
})
|
||||
elif self._is_out():
|
||||
valued_move_lines = self.move_line_ids.filtered(lambda ml: ml.location_id._should_be_valued() and not ml.location_dest_id._should_be_valued())
|
||||
valued_quantity = sum(valued_move_lines.mapped('qty_done'))
|
||||
if self.product_id.cost_method == 'fifo':
|
||||
self.env['stock.move']._run_fifo(self, quantity=quantity)
|
||||
elif self.product_id.cost_method in ['standard', 'average']:
|
||||
curr_rounding = self.company_id.currency_id.rounding
|
||||
value = -float_round(self.product_id.standard_price * (self.product_qty if quantity is None else quantity), precision_rounding=curr_rounding)
|
||||
value = -float_round(self.product_id.standard_price * (valued_quantity if quantity is None else quantity), precision_rounding=curr_rounding)
|
||||
self.write({
|
||||
'value': value if quantity is None else self.value + value,
|
||||
'price_unit': value / self.product_qty,
|
||||
'price_unit': value / valued_quantity,
|
||||
})
|
||||
|
||||
def _action_done(self):
|
||||
|
||||
@@ -2644,3 +2644,93 @@ class TestStockValuation(TransactionCase):
|
||||
# (9 * 10) + (15 * 10) / 19
|
||||
self.assertEqual(self.product1.standard_price, 12.63)
|
||||
|
||||
def test_fifo_sublocation_valuation_1(self):
|
||||
""" Set the main stock as a view location. Receive 2 units of a
|
||||
product, put 1 unit in an internal sublocation and the second
|
||||
one in a scrap sublocation. Only a single unit, the one in the
|
||||
internal sublocation, should be valued. Then, send these two
|
||||
quants to a customer, only the one in the internal location
|
||||
should be valued.
|
||||
"""
|
||||
self.product1.product_tmpl_id.cost_method = 'fifo'
|
||||
|
||||
view_location = self.env['stock.location'].create({'name': 'view', 'usage': 'view'})
|
||||
subloc1 = self.env['stock.location'].create({
|
||||
'name': 'internal',
|
||||
'usage': 'internal',
|
||||
'location_id': view_location.id,
|
||||
})
|
||||
# sane settings for a scrap location, company_id doesn't matter
|
||||
subloc2 = self.env['stock.location'].create({
|
||||
'name': 'scrap',
|
||||
'usage': 'inventory',
|
||||
'location_id': view_location.id,
|
||||
'scrap_location': True,
|
||||
})
|
||||
|
||||
move1 = self.env['stock.move'].create({
|
||||
'name': '2 units @ 10.00 per unit',
|
||||
'location_id': self.supplier_location.id,
|
||||
'location_dest_id': self.stock_location.id,
|
||||
'product_id': self.product1.id,
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 2.0,
|
||||
'price_unit': 10,
|
||||
})
|
||||
move1._action_confirm()
|
||||
move1._action_assign()
|
||||
|
||||
move1.write({'move_line_ids': [
|
||||
(0, None, {
|
||||
'product_id': self.product1.id,
|
||||
'qty_done': 1,
|
||||
'location_id': self.supplier_location.id,
|
||||
'location_dest_id': subloc1.id,
|
||||
'product_uom_id': self.uom_unit.id
|
||||
}),
|
||||
(0, None, {
|
||||
'product_id': self.product1.id,
|
||||
'qty_done': 1,
|
||||
'location_id': self.supplier_location.id,
|
||||
'location_dest_id': subloc2.id,
|
||||
'product_uom_id': self.uom_unit.id
|
||||
}),
|
||||
]})
|
||||
|
||||
move1._action_done()
|
||||
self.assertEqual(move1.value, 10)
|
||||
self.assertEqual(move1.remaining_value, 10)
|
||||
self.assertEqual(move1.remaining_qty, 1)
|
||||
self.assertEqual(self.product1.stock_value, 10)
|
||||
self.assertTrue(len(move1.account_move_ids), 1)
|
||||
|
||||
move2 = self.env['stock.move'].create({
|
||||
'name': '2 units out',
|
||||
'location_id': self.stock_location.id,
|
||||
'location_dest_id': self.customer_location.id,
|
||||
'product_id': self.product1.id,
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 2.0,
|
||||
})
|
||||
move2._action_confirm()
|
||||
move2._action_assign()
|
||||
|
||||
move2.write({'move_line_ids': [
|
||||
(0, None, {
|
||||
'product_id': self.product1.id,
|
||||
'qty_done': 1,
|
||||
'location_id': subloc1.id,
|
||||
'location_dest_id': self.supplier_location.id,
|
||||
'product_uom_id': self.uom_unit.id
|
||||
}),
|
||||
(0, None, {
|
||||
'product_id': self.product1.id,
|
||||
'qty_done': 1,
|
||||
'location_id': subloc2.id,
|
||||
'location_dest_id': self.supplier_location.id,
|
||||
'product_uom_id': self.uom_unit.id
|
||||
}),
|
||||
]})
|
||||
move2._action_done()
|
||||
self.assertEqual(move2.value, -10)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user