[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:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user