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