diff --git a/addons/mrp_subcontracting_account/models/stock_move.py b/addons/mrp_subcontracting_account/models/stock_move.py index 744f3f993ca..2b92ecc7fce 100644 --- a/addons/mrp_subcontracting_account/models/stock_move.py +++ b/addons/mrp_subcontracting_account/models/stock_move.py @@ -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): diff --git a/addons/mrp_subcontracting_purchase/models/__init__.py b/addons/mrp_subcontracting_purchase/models/__init__.py index 7952bc4194f..d72772bf68b 100644 --- a/addons/mrp_subcontracting_purchase/models/__init__.py +++ b/addons/mrp_subcontracting_purchase/models/__init__.py @@ -3,4 +3,5 @@ from . import account_move_line from . import stock_picking +from . import stock_valuation_layer from . import purchase_order diff --git a/addons/mrp_subcontracting_purchase/models/account_move_line.py b/addons/mrp_subcontracting_purchase/models/account_move_line.py index de7bf8a86c3..51d3eeede80 100644 --- a/addons/mrp_subcontracting_purchase/models/account_move_line.py +++ b/addons/mrp_subcontracting_purchase/models/account_move_line.py @@ -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 diff --git a/addons/mrp_subcontracting_purchase/models/stock_valuation_layer.py b/addons/mrp_subcontracting_purchase/models/stock_valuation_layer.py new file mode 100644 index 00000000000..2e305cb27d2 --- /dev/null +++ b/addons/mrp_subcontracting_purchase/models/stock_valuation_layer.py @@ -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 diff --git a/addons/mrp_subcontracting_purchase/tests/test_mrp_subcontracting_purchase.py b/addons/mrp_subcontracting_purchase/tests/test_mrp_subcontracting_purchase.py index 97b4cf41694..e42fb5d8989 100644 --- a/addons/mrp_subcontracting_purchase/tests/test_mrp_subcontracting_purchase.py +++ b/addons/mrp_subcontracting_purchase/tests/test_mrp_subcontracting_purchase.py @@ -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) diff --git a/addons/purchase_stock/models/__init__.py b/addons/purchase_stock/models/__init__.py index deda82d5df6..fc159a2d26e 100644 --- a/addons/purchase_stock/models/__init__.py +++ b/addons/purchase_stock/models/__init__.py @@ -11,3 +11,4 @@ from . import res_company from . import stock from . import stock_move from . import stock_rule +from . import stock_valuation_layer diff --git a/addons/purchase_stock/models/account_move_line.py b/addons/purchase_stock/models/account_move_line.py index 708aa77939d..99888bcded6 100644 --- a/addons/purchase_stock/models/account_move_line.py +++ b/addons/purchase_stock/models/account_move_line.py @@ -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() diff --git a/addons/purchase_stock/models/stock_valuation_layer.py b/addons/purchase_stock/models/stock_valuation_layer.py new file mode 100644 index 00000000000..3959806cb0f --- /dev/null +++ b/addons/purchase_stock/models/stock_valuation_layer.py @@ -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