[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:
Simon Lejeune
2017-11-14 17:36:52 +01:00
parent 20c19560cd
commit 48f4ce4afa
2 changed files with 104 additions and 6 deletions
+14 -6
View File
@@ -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)