From 31f4e932c57ad6bfc480057055e8cb67cde8152f Mon Sep 17 00:00:00 2001 From: "Arthur Jochems (ajoc)" Date: Wed, 17 Jan 2024 01:32:09 +0100 Subject: [PATCH] [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) --- .../tests/test_analytic_account.py | 40 +++++++++++++++++++ addons/stock_account/models/stock_move.py | 15 ++++--- .../stock_account/models/stock_move_line.py | 6 +++ 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/addons/mrp_account/tests/test_analytic_account.py b/addons/mrp_account/tests/test_analytic_account.py index 3ab6e3559ce..cd7bd5411cc 100644 --- a/addons/mrp_account/tests/test_analytic_account.py +++ b/addons/mrp_account/tests/test_analytic_account.py @@ -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) diff --git a/addons/stock_account/models/stock_move.py b/addons/stock_account/models/stock_move.py index c906ecbf54e..7d6c5d2167a 100644 --- a/addons/stock_account/models/stock_move.py +++ b/addons/stock_account/models/stock_move.py @@ -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) diff --git a/addons/stock_account/models/stock_move_line.py b/addons/stock_account/models/stock_move_line.py index a9bffa7b97a..9b4c15340d1 100644 --- a/addons/stock_account/models/stock_move_line.py +++ b/addons/stock_account/models/stock_move_line.py @@ -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 # -------------------------------------------------------------------------