[FIX] mrp_subcontracting_purchase: create svl on price edit

When a product is purchased and received, valuation layers are created.
if we created a bill for the PO and change the product price, when the
account move is posted, `_apply_price_difference` creates new SVLs to
correct the product value.

Before this commit, the above scenario did not work with subcontracted
products, mainly because the valuation layer is linked to the finished
product move of the MO and not the incoming move of the PO.
This is fixed in `_get_valued_in_moves` where if the move is_subcontract
the orig move is the one linked to the valuation layer.

Now that we have the valuation layer, another issue arises, in the aml
`_generate_price_difference_vals` when the difference is calculated.
the `unit_valuation_difference` is not properly calculated because the
value in the layer includes the components value, so its incorrect to compare
it to the AML price, so we use `_get_layer_price_unit` to get the layer_price_unit
relevant to the AML, which in this case is the price of subcontracting (price on the PO)

Also for automated inventory valuation, when the inventory correction entry is
created it must credit the stock input account since the value of the
subcontracted product is corrected.

closes odoo/odoo#131565

Taskid: 3449931
Signed-off-by: William Henrotin (whe) <whe@odoo.com>
This commit is contained in:
Ahmed Khalaf
2023-09-01 13:20:48 +00:00
parent 86d1884e4a
commit c687a89b5c
8 changed files with 87 additions and 2 deletions
@@ -51,6 +51,10 @@ class StockMove(models.Model):
'balance': -component_cost,
'account_id': credit_account_id,
}
# if svl passed is not linked to the move in self, the valuation is a correction and should always credit the
# `stock_input` account as it adds directly to the value of the subcontracted product
elif svl_id and self.stock_valuation_layer_ids.ids and svl_id not in self.stock_valuation_layer_ids.ids:
rslt['credit_line_vals']['account_id'] = self.product_id.product_tmpl_id.get_product_accounts()['stock_input'].id
return rslt
def _get_dest_account(self, account_data):
@@ -3,4 +3,5 @@
from . import account_move_line
from . import stock_picking
from . import stock_valuation_layer
from . import purchase_order
@@ -14,3 +14,9 @@ class AccountMoveLine(models.Model):
components_cost -= sum(subcontract_production.move_raw_ids.stock_valuation_layer_ids.mapped('value'))
price_unit_val_dif = price_unit_val_dif + components_cost / relevant_qty
return price_unit_val_dif, relevant_qty
def _get_valued_in_moves(self):
res = super()._get_valued_in_moves()
# subcontracted move valuations are not linked to the PO move but its orig move (the MO finished move)
res |= res.filtered(lambda m: m.is_subcontract).move_orig_ids
return res
@@ -0,0 +1,20 @@
from odoo import models
class StockValuationLayer(models.Model):
_inherit = 'stock.valuation.layer'
def _get_layer_price_unit(self):
""" For a subcontracted product, we want a way to get the subcontracting cost (the price on the PO)
This override deducts the value of subcomponents from the layer price.
"""
components_price = 0
production = self.stock_move_id.production_id
if production.subcontractor_id and production.state == 'done':
# each layer has a quantity and price for each move, to get the correct component price for each move
# we need to get the components used for each quantity
for move in production.move_raw_ids:
components_price += sum(move.sudo().stock_valuation_layer_ids.mapped('value')) / production.product_uom_qty
return super()._get_layer_price_unit() - components_price
@@ -292,3 +292,44 @@ class MrpSubcontractingPurchaseTest(TestMrpSubcontractingCommon):
# price diff line should be 100 - 50 - 10 - 20
price_diff_line = invoice.line_ids.filtered(lambda m: m.account_id == stock_price_diff_acc_id)
self.assertEqual(price_diff_line.credit, 20)
def test_subcontract_product_price_change(self):
""" Create a PO for subcontracted product, receive the product (finish MO),
create vendor bill and edit the product price, confirm the bill.
An extra SVL should be created to correct the valuation of the product
Also check account move data for real time inventory
"""
product_category_all = self.env.ref('product.product_category_all')
product_category_all.property_cost_method = 'fifo'
product_category_all.property_valuation = 'real_time'
stock_in_acc_id = product_category_all.property_stock_account_input_categ_id.id
purchase = self.env['purchase.order'].create({
'partner_id': self.subcontractor_partner1.id,
'order_line': [Command.create({
'name': self.finished.name,
'product_id': self.finished.id,
'product_uom_qty': 10,
'product_uom': self.finished.uom_id.id,
'price_unit': 1,
})],
})
purchase.button_confirm()
# receive product
receipt = purchase.picking_ids
receipt.move_ids.quantity_done = 10
receipt.button_validate()
# create bill
purchase.action_create_invoice()
aml = self.env['account.move.line'].search([('purchase_line_id', '=', purchase.order_line.id)])
# add 0.5 per unit ( 0.5 x 10 ) = 5 extra valuation
aml.price_unit = 1.5
aml.move_id.invoice_date = Date.today()
aml.move_id.action_post()
svl = aml.stock_valuation_layer_ids
self.assertEqual(len(svl), 1)
self.assertEqual(svl.value, 5)
# check for the automated inventory valuation
account_move_credit_line = svl.account_move_id.line_ids.filtered(lambda l: l.credit > 0)
self.assertEqual(account_move_credit_line.account_id.id, stock_in_acc_id)
self.assertEqual(account_move_credit_line.credit, 5)
+1
View File
@@ -11,3 +11,4 @@ from . import res_company
from . import stock
from . import stock_move
from . import stock_rule
from . import stock_valuation_layer
@@ -191,7 +191,7 @@ class AccountMoveLine(models.Model):
out_qty_to_invoice = 0
aml = initial_pdiff_svl.account_move_line_id
parent_layer = initial_pdiff_svl.stock_valuation_layer_id
layer_price_unit = parent_layer.value / parent_layer.quantity
layer_price_unit = parent_layer._get_layer_price_unit()
else:
sign = 1
# get the invoiced qty of the layer without considering `self`
@@ -199,7 +199,7 @@ class AccountMoveLine(models.Model):
remaining_out_qty_to_invoice = max(0, out_layer_qty - invoiced_layer_qty)
out_qty_to_invoice = min(remaining_out_qty_to_invoice, invoicing_layer_qty)
qty_to_correct = invoicing_layer_qty - out_qty_to_invoice
layer_price_unit = layer.value / layer.quantity
layer_price_unit = layer._get_layer_price_unit()
aml = self
aml_gross_price_unit = aml._get_gross_unit_price()
@@ -0,0 +1,12 @@
from odoo import models
class StockValuationLayer(models.Model):
_inherit = 'stock.valuation.layer'
def _get_layer_price_unit(self):
""" This function returns the value of product in a layer per unit, relative to the aml
the function is designed to be overriden to add logic to price unit calculation
:param layer: the layer the price unit is derived from
"""
return self.value / self.quantity