[FIX] mrp_subcontracting_purchase, purchase_stock: price difference
entry wrong when resupply subcontractor In case product is using standard price and automated inventory valuation. We post account entries in price difference account when the price on purchase order is different from the cost of product. However, when we do subcontracting and resupply our subcontractor, the cost is not just the price on purchase order, but also the cost of the components we sent to the subcontractor. Currently, we didn't take the components cost in to account when post price difference entries. Fix it in this commit. Task-3223451 closes odoo/odoo#132282 X-original-commit: e771535b487e76f4e5a23b767d220d7f640292a6 Signed-off-by: William Henrotin (whe) <whe@odoo.com> Signed-off-by: Yuchen Huang (yhu) <yhu@odoo.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import account_move_line
|
||||
from . import stock_picking
|
||||
from . import purchase_order
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models
|
||||
|
||||
|
||||
class AccountMoveLine(models.Model):
|
||||
_inherit = 'account.move.line'
|
||||
|
||||
def _get_price_unit_val_dif_and_relevant_qty(self):
|
||||
price_unit_val_dif, relevant_qty = super()._get_price_unit_val_dif_and_relevant_qty()
|
||||
if self.product_id.cost_method == 'standard' and self.purchase_line_id:
|
||||
components_cost = 0
|
||||
subcontract_production = self.purchase_line_id.move_ids._get_subcontract_production()
|
||||
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
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
from odoo import Command
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.fields import Date
|
||||
from odoo.tests import Form
|
||||
|
||||
from odoo.addons.mrp_subcontracting.tests.common import TestMrpSubcontractingCommon
|
||||
@@ -234,3 +235,55 @@ class MrpSubcontractingPurchaseTest(TestMrpSubcontractingCommon):
|
||||
|
||||
self.env['stock.warehouse.orderpoint']._get_orderpoint_action()
|
||||
self.assertTrue(self.env['stock.warehouse.orderpoint'].search([('product_id', '=', component.id)]))
|
||||
|
||||
def test_subcontracting_resupply_price_diff(self):
|
||||
"""Test that the price difference is correctly computed when a subcontracted
|
||||
product is resupplied.
|
||||
"""
|
||||
resupply_sub_on_order_route = self.env['stock.route'].search([('name', '=', 'Resupply Subcontractor on Order')])
|
||||
(self.comp1 + self.comp2).write({'route_ids': [(6, None, [resupply_sub_on_order_route.id])]})
|
||||
product_category_all = self.env.ref('product.product_category_all')
|
||||
product_category_all.property_cost_method = 'standard'
|
||||
product_category_all.property_valuation = 'real_time'
|
||||
|
||||
stock_price_diff_acc_id = self.env['account.account'].create({
|
||||
'name': 'default_account_stock_price_diff',
|
||||
'code': 'STOCKDIFF',
|
||||
'reconcile': True,
|
||||
'account_type': 'asset_current',
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
product_category_all.property_account_creditor_price_difference_categ = stock_price_diff_acc_id
|
||||
|
||||
self.comp1.standard_price = 10.0
|
||||
self.comp2.standard_price = 20.0
|
||||
self.finished.standard_price = 100
|
||||
|
||||
# Create a PO for 1 finished product.
|
||||
po_form = Form(self.env['purchase.order'])
|
||||
po_form.partner_id = self.subcontractor_partner1
|
||||
with po_form.order_line.new() as po_line:
|
||||
po_line.product_id = self.finished
|
||||
po_line.product_qty = 1
|
||||
po_line.price_unit = 50 # should be 70
|
||||
po = po_form.save()
|
||||
po.button_confirm()
|
||||
|
||||
action = po.action_view_subcontracting_resupply()
|
||||
resupply_picking = self.env[action['res_model']].browse(action['res_id'])
|
||||
resupply_picking.move_ids.quantity_done = 1
|
||||
resupply_picking.button_validate()
|
||||
|
||||
action = po.action_view_picking()
|
||||
final_picking = self.env[action['res_model']].browse(action['res_id'])
|
||||
final_picking.move_ids.quantity_done = 1
|
||||
final_picking.button_validate()
|
||||
|
||||
action = po.action_create_invoice()
|
||||
invoice = self.env['account.move'].browse(action['res_id'])
|
||||
invoice.invoice_date = Date.today()
|
||||
invoice.action_post()
|
||||
|
||||
# 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)
|
||||
|
||||
@@ -53,43 +53,8 @@ class AccountMove(models.Model):
|
||||
debit_pdiff_account = line.product_id.product_tmpl_id.get_product_accounts(fiscal_pos=move.fiscal_position_id)['expense']
|
||||
if not debit_pdiff_account:
|
||||
continue
|
||||
# Retrieve stock valuation moves.
|
||||
valuation_stock_moves = self.env['stock.move'].search([
|
||||
('purchase_line_id', '=', line.purchase_line_id.id),
|
||||
('state', '=', 'done'),
|
||||
('product_qty', '!=', 0.0),
|
||||
]) if line.purchase_line_id else self.env['stock.move']
|
||||
|
||||
if line.product_id.cost_method != 'standard' and line.purchase_line_id:
|
||||
if move.move_type == 'in_refund':
|
||||
valuation_stock_moves = valuation_stock_moves.filtered(lambda stock_move: stock_move._is_out())
|
||||
else:
|
||||
valuation_stock_moves = valuation_stock_moves.filtered(lambda stock_move: stock_move._is_in())
|
||||
|
||||
if not valuation_stock_moves:
|
||||
continue
|
||||
|
||||
valuation_price_unit_total, valuation_total_qty = valuation_stock_moves._get_valuation_price_and_qty(line, move.currency_id)
|
||||
valuation_price_unit = valuation_price_unit_total / valuation_total_qty
|
||||
valuation_price_unit = line.product_id.uom_id._compute_price(valuation_price_unit, line.product_uom_id)
|
||||
else:
|
||||
# Valuation_price unit is always expressed in invoice currency, so that it can always be computed with the good rate
|
||||
price_unit = line.product_id.uom_id._compute_price(line.product_id.standard_price, line.product_uom_id)
|
||||
price_unit = -price_unit if line.move_id.move_type == 'in_refund' else price_unit
|
||||
valuation_date = valuation_stock_moves and max(valuation_stock_moves.mapped('date')) or move.date
|
||||
valuation_price_unit = line.company_currency_id._convert(
|
||||
price_unit, move.currency_id,
|
||||
move.company_id, valuation_date, round=False
|
||||
)
|
||||
|
||||
price_unit = line._get_gross_unit_price()
|
||||
|
||||
price_unit_val_dif = price_unit - valuation_price_unit
|
||||
# If there are some valued moves, we only consider their quantity already used
|
||||
if line.product_id.cost_method == 'standard':
|
||||
relevant_qty = line.quantity
|
||||
else:
|
||||
relevant_qty = line._get_out_and_not_invoiced_qty(valuation_stock_moves)
|
||||
price_unit_val_dif, relevant_qty = line._get_price_unit_val_dif_and_relevant_qty()
|
||||
price_subtotal = relevant_qty * price_unit_val_dif
|
||||
|
||||
# We consider there is a price difference if the subtotal is not zero. In case a
|
||||
|
||||
@@ -278,3 +278,45 @@ class AccountMoveLine(models.Model):
|
||||
'stock_valuation_layer_id': corrected_layer.id,
|
||||
'price_diff_value': self.currency_id.round(pdiff * quantity),
|
||||
}
|
||||
|
||||
def _get_price_unit_val_dif_and_relevant_qty(self):
|
||||
self.ensure_one()
|
||||
# Retrieve stock valuation moves.
|
||||
valuation_stock_moves = self.env['stock.move'].search([
|
||||
('purchase_line_id', '=', self.purchase_line_id.id),
|
||||
('state', '=', 'done'),
|
||||
('product_qty', '!=', 0.0),
|
||||
]) if self.purchase_line_id else self.env['stock.move']
|
||||
|
||||
if self.product_id.cost_method != 'standard' and self.purchase_line_id:
|
||||
if self.move_type == 'in_refund':
|
||||
valuation_stock_moves = valuation_stock_moves.filtered(lambda stock_move: stock_move._is_out())
|
||||
else:
|
||||
valuation_stock_moves = valuation_stock_moves.filtered(lambda stock_move: stock_move._is_in())
|
||||
|
||||
if not valuation_stock_moves:
|
||||
return 0, 0
|
||||
|
||||
valuation_price_unit_total, valuation_total_qty = valuation_stock_moves._get_valuation_price_and_qty(self, self.move_id.currency_id)
|
||||
valuation_price_unit = valuation_price_unit_total / valuation_total_qty
|
||||
valuation_price_unit = self.product_id.uom_id._compute_price(valuation_price_unit, self.product_uom_id)
|
||||
else:
|
||||
# Valuation_price unit is always expressed in invoice currency, so that it can always be computed with the good rate
|
||||
price_unit = self.product_id.uom_id._compute_price(self.product_id.standard_price, self.product_uom_id)
|
||||
price_unit = -price_unit if self.move_id.move_type == 'in_refund' else price_unit
|
||||
valuation_date = valuation_stock_moves and max(valuation_stock_moves.mapped('date')) or self.date
|
||||
valuation_price_unit = self.company_currency_id._convert(
|
||||
price_unit, self.currency_id,
|
||||
self.company_id, valuation_date, round=False
|
||||
)
|
||||
|
||||
price_unit = self._get_gross_unit_price()
|
||||
|
||||
price_unit_val_dif = price_unit - valuation_price_unit
|
||||
# If there are some valued moves, we only consider their quantity already used
|
||||
if self.product_id.cost_method == 'standard':
|
||||
relevant_qty = self.quantity
|
||||
else:
|
||||
relevant_qty = self._get_out_and_not_invoiced_qty(valuation_stock_moves)
|
||||
|
||||
return price_unit_val_dif, relevant_qty
|
||||
|
||||
Reference in New Issue
Block a user