[FIX] stock(_account): don't count unpicked moves for analytics

As a result of the Quantitypocalypse (7dda6bb), move lines are being
created for MO components before they're actually consumed. This in turn
automatically creates analytics lines, debiting accounts for these
unconsumed parts. This commit globally prevents any move line that is
not yet picked (i.e. consumed/reserved) from being included in an
analytic entry.

Additionally, an edge case where setting a move's qty to 0 did not
trigger adjustments of dependent models was fixed.

Part-of: odoo#146114

Task-3621105

closes odoo/odoo#146114

Signed-off-by: William Henrotin (whe) <whe@odoo.com>
This commit is contained in:
Arthur Jochems (ajoc)
2024-02-01 07:06:46 +00:00
parent 43f296177e
commit 31f4e932c5
3 changed files with 56 additions and 5 deletions
@@ -392,3 +392,43 @@ class TestAnalyticAccount(TestMrpAnalyticAccount):
self.assertAlmostEqual(line_with_A_accounts.amount, -100/3, delta=0.02)
self.assertAlmostEqual(line_with_B_accounts.amount, -100/3, delta=0.02)
self.assertAlmostEqual(line_with_B_accounts.amount, -100/3, delta=0.02)
def test_mo_qty_analytics(self):
"""
This test tests multiple behaviours and edge cases. First off, when
an analytic distribution with an MO, the analytic entries should only
be generated when the MO's components are consumed/reserved (i.e.
picked). Second, when changing the produced quantity in a confirmed MO,
it should appropriately adjust the amount of picked components. Third,
analytic entries should at all times reflect the current situation, and
thus must be regenerated every time there's a change in components'
picked status.
"""
# refill components
location = self.env.ref('stock.stock_location_stock')
self.env['stock.quant']._update_available_quantity(self.component, location, 10)
# create a mo
mo_form = Form(self.env['mrp.production'])
mo_form.product_id = self.product
mo_form.bom_id = self.bom
mo_form.product_qty = 10.0
mo_form.analytic_distribution = {str(self.analytic_account.id): 100.0}
mo = mo_form.save()
mo.action_confirm()
self.assertEqual(mo.state, 'confirmed')
self.assertEqual(self.analytic_account.balance, 0.0)
# increase qty_producing to 5.0
mo_form = Form(mo)
mo_form.qty_producing = 5.0
mo_form.save()
self.assertEqual(mo.state, 'progress')
self.assertEqual(self.analytic_account.balance, -50.0)
# decrease qty_producing to 0.0
mo_form = Form(mo)
mo_form.qty_producing = 0.0
mo_form.save()
self.assertEqual(mo.state, 'progress')
self.assertEqual(self.analytic_account.balance, 0.0)
+10 -5
View File
@@ -20,6 +20,10 @@ class StockMove(models.Model):
stock_valuation_layer_ids = fields.One2many('stock.valuation.layer', 'stock_move_id')
analytic_account_line_ids = fields.Many2many('account.analytic.line', copy=False)
def _inverse_picked(self):
super()._inverse_picked()
self._account_analytic_entry_move()
def _filter_anglo_saxon_moves(self, product):
return self.filtered(lambda m: m.product_id.id == product.id)
@@ -420,11 +424,12 @@ class StockMove(models.Model):
amount, unit_amount = 0, 0
if self.state != 'done':
unit_amount = self.product_uom._compute_quantity(
self.quantity, self.product_id.uom_id)
# Falsy in FIFO but since it's an estimation we don't require exact correct cost. Otherwise
# we would have to recompute all the analytic estimation at each out.
amount = - unit_amount * self.product_id.standard_price
if self.picked:
unit_amount = self.product_uom._compute_quantity(
self.quantity, self.product_id.uom_id)
# Falsy in FIFO but since it's an estimation we don't require exact correct cost. Otherwise
# we would have to recompute all the analytic estimation at each out.
amount = - unit_amount * self.product_id.standard_price
elif self.product_id.valuation == 'real_time' and not self._ignore_automatic_valuation():
accounts_data = self.product_id.product_tmpl_id.get_product_accounts()
account_valuation = accounts_data.get('stock_valuation', False)
@@ -53,6 +53,12 @@ class StockMoveLine(models.Model):
self.env['stock.move'].browse(analytic_move_to_recompute)._account_analytic_entry_move()
return res
def unlink(self):
analytic_move_to_recompute = self.move_id
res = super().unlink()
analytic_move_to_recompute._account_analytic_entry_move()
return res
# -------------------------------------------------------------------------
# SVL creation helpers
# -------------------------------------------------------------------------