feat: Remove inventory revaluation logic from vendor bill line price updates and add a confirming test.
This commit is contained in:
parent
865695577c
commit
578708f403
@ -23,7 +23,9 @@
|
|||||||
"author": "Suherdy Yacob",
|
"author": "Suherdy Yacob",
|
||||||
"category": "Accounting",
|
"category": "Accounting",
|
||||||
"depends": [
|
"depends": [
|
||||||
"account"
|
"account",
|
||||||
|
"purchase",
|
||||||
|
"stock_account",
|
||||||
],
|
],
|
||||||
"data": [
|
"data": [
|
||||||
"views/account_move_views.xml",
|
"views/account_move_views.xml",
|
||||||
|
|||||||
@ -3,8 +3,7 @@
|
|||||||
|
|
||||||
from odoo import api, fields, models, _
|
from odoo import api, fields, models, _
|
||||||
from odoo.exceptions import UserError
|
from odoo.exceptions import UserError
|
||||||
from odoo.tools import float_compare
|
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
|
|
||||||
class AccountMoveLine(models.Model):
|
class AccountMoveLine(models.Model):
|
||||||
@ -163,110 +162,7 @@ class AccountMoveLine(models.Model):
|
|||||||
if was_locked:
|
if was_locked:
|
||||||
po.button_done()
|
po.button_done()
|
||||||
|
|
||||||
# ---------------------------------------------------------
|
|
||||||
# INVENTORY VALUATION UPDATE (AVCO/FIFO FIX)
|
|
||||||
# ---------------------------------------------------------
|
|
||||||
# If PO is received, updating price should update Stock Value
|
|
||||||
if po_line.state in ['purchase', 'done'] and po_line.product_id.type == 'product':
|
|
||||||
for stock_move in po_line.move_ids.filtered(lambda m: m.state == 'done'):
|
|
||||||
# Calculate Diff based on NEW Price (updated above)
|
|
||||||
new_val = price_unit * stock_move.quantity
|
|
||||||
# Current Value from SVLs
|
|
||||||
current_val = sum(stock_move.stock_valuation_layer_ids.mapped('value'))
|
|
||||||
diff = new_val - current_val
|
|
||||||
|
|
||||||
currency = stock_move.company_id.currency_id
|
|
||||||
if not currency.is_zero(diff):
|
|
||||||
# 1. Create Correction SVL
|
|
||||||
svl_vals = {
|
|
||||||
'company_id': stock_move.company_id.id,
|
|
||||||
'product_id': stock_move.product_id.id,
|
|
||||||
'description': _("Valuation correction from Vendor Bill %s") % line.move_id.name,
|
|
||||||
'value': diff,
|
|
||||||
'quantity': 0,
|
|
||||||
'stock_move_id': stock_move.id,
|
|
||||||
}
|
|
||||||
svl = self.env['stock.valuation.layer'].create(svl_vals)
|
|
||||||
|
|
||||||
# Backdate SVL
|
|
||||||
if stock_move.date:
|
|
||||||
new_date = stock_move.date + timedelta(seconds=1)
|
|
||||||
self.env.cr.execute("UPDATE stock_valuation_layer SET create_date = %s WHERE id = %s", (new_date, svl.id))
|
|
||||||
|
|
||||||
# 2. AVCO/FIFO Logic: Update Standard Price and Distribute Value
|
|
||||||
product = stock_move.product_id
|
|
||||||
if product.categ_id.property_cost_method in ['average', 'fifo'] and product.quantity_svl > 0:
|
|
||||||
new_std_price = product.standard_price + (diff / product.quantity_svl)
|
|
||||||
product.with_context(disable_auto_svl=True).sudo().write({'standard_price': new_std_price})
|
|
||||||
|
|
||||||
remaining_svls = self.env['stock.valuation.layer'].search([
|
|
||||||
('product_id', '=', product.id),
|
|
||||||
('remaining_qty', '>', 0),
|
|
||||||
('company_id', '=', stock_move.company_id.id),
|
|
||||||
])
|
|
||||||
|
|
||||||
if remaining_svls:
|
|
||||||
remaining_qty_total = sum(remaining_svls.mapped('remaining_qty'))
|
|
||||||
if remaining_qty_total > 0:
|
|
||||||
remaining_value_to_distribute = diff
|
|
||||||
remaining_value_unit_cost = remaining_value_to_distribute / remaining_qty_total
|
|
||||||
|
|
||||||
for layer in remaining_svls:
|
|
||||||
if float_compare(layer.remaining_qty, remaining_qty_total, precision_rounding=product.uom_id.rounding) >= 0:
|
|
||||||
taken_remaining_value = remaining_value_to_distribute
|
|
||||||
else:
|
|
||||||
taken_remaining_value = remaining_value_unit_cost * layer.remaining_qty
|
|
||||||
|
|
||||||
taken_remaining_value = stock_move.company_id.currency_id.round(taken_remaining_value)
|
|
||||||
layer.sudo().write({'remaining_value': layer.remaining_value + taken_remaining_value})
|
|
||||||
|
|
||||||
remaining_value_to_distribute -= taken_remaining_value
|
|
||||||
remaining_qty_total -= layer.remaining_qty
|
|
||||||
|
|
||||||
# 3. Create Accounting Entry
|
|
||||||
if stock_move.product_id.categ_id.property_valuation == 'real_time':
|
|
||||||
accounts = stock_move.product_id.product_tmpl_id.get_product_accounts()
|
|
||||||
acc_expense = accounts.get('expense')
|
|
||||||
acc_valuation = accounts.get('stock_valuation')
|
|
||||||
|
|
||||||
if acc_expense and acc_valuation:
|
|
||||||
if diff > 0:
|
|
||||||
debit_acc = acc_valuation.id
|
|
||||||
credit_acc = acc_expense.id
|
|
||||||
amount = diff
|
|
||||||
else:
|
|
||||||
debit_acc = acc_expense.id
|
|
||||||
credit_acc = acc_valuation.id
|
|
||||||
amount = abs(diff)
|
|
||||||
|
|
||||||
acc_date = stock_move.date.date() if stock_move.date else fields.Date.today()
|
|
||||||
|
|
||||||
move_vals = {
|
|
||||||
'journal_id': accounts['stock_journal'].id,
|
|
||||||
'company_id': stock_move.company_id.id,
|
|
||||||
'ref': _("Revaluation for %s from Bill Edit") % stock_move.product_id.name,
|
|
||||||
'date': acc_date,
|
|
||||||
'move_type': 'entry',
|
|
||||||
'stock_valuation_layer_ids': [(6, 0, [svl.id])],
|
|
||||||
'line_ids': [
|
|
||||||
(0, 0, {
|
|
||||||
'name': _("Valuation Correction - %s") % stock_move.product_id.name,
|
|
||||||
'account_id': debit_acc,
|
|
||||||
'debit': amount,
|
|
||||||
'credit': 0,
|
|
||||||
'product_id': stock_move.product_id.id,
|
|
||||||
}),
|
|
||||||
(0, 0, {
|
|
||||||
'name': _("Valuation Correction - %s") % stock_move.product_id.name,
|
|
||||||
'account_id': credit_acc,
|
|
||||||
'debit': 0,
|
|
||||||
'credit': amount,
|
|
||||||
'product_id': stock_move.product_id.id,
|
|
||||||
})
|
|
||||||
]
|
|
||||||
}
|
|
||||||
am = self.env['account.move'].create(move_vals)
|
|
||||||
am._post()
|
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|||||||
@ -6,3 +6,4 @@ from . import test_price_total_property
|
|||||||
from . import test_edge_cases
|
from . import test_edge_cases
|
||||||
from . import test_view_configuration
|
from . import test_view_configuration
|
||||||
from . import test_integration
|
from . import test_integration
|
||||||
|
from . import test_no_revaluation
|
||||||
|
|||||||
72
tests/test_no_revaluation.py
Normal file
72
tests/test_no_revaluation.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo.tests import TransactionCase, tagged
|
||||||
|
from odoo.fields import Date
|
||||||
|
|
||||||
|
@tagged('post_install', '-at_install', 'repro_bug')
|
||||||
|
class TestNoRevaluation(TransactionCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.env.company.currency_id = self.env.ref('base.USD')
|
||||||
|
|
||||||
|
# Create a product with automated valuation (AVCO)
|
||||||
|
self.product_category = self.env['product.category'].create({
|
||||||
|
'name': 'Test Auto Valuation',
|
||||||
|
'property_cost_method': 'average',
|
||||||
|
'property_valuation': 'real_time',
|
||||||
|
})
|
||||||
|
self.product = self.env['product.product'].create({
|
||||||
|
'name': 'Test Product',
|
||||||
|
'type': 'consu',
|
||||||
|
'is_storable': True,
|
||||||
|
'categ_id': self.product_category.id,
|
||||||
|
'standard_price': 100.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Create a vendor
|
||||||
|
self.vendor = self.env['res.partner'].create({'name': 'Test Vendor'})
|
||||||
|
|
||||||
|
def test_no_revaluation_on_bill_edit(self):
|
||||||
|
"""
|
||||||
|
Verify that editing a vendor bill line price does NOT trigger
|
||||||
|
inventory revaluation (SVL creation) or accounting entries.
|
||||||
|
"""
|
||||||
|
# 1. Create and Confirm PO
|
||||||
|
po = self.env['purchase.order'].create({
|
||||||
|
'partner_id': self.vendor.id,
|
||||||
|
'order_line': [(0, 0, {
|
||||||
|
'product_id': self.product.id,
|
||||||
|
'product_qty': 10.0,
|
||||||
|
'price_unit': 100.0,
|
||||||
|
})],
|
||||||
|
})
|
||||||
|
po.button_confirm()
|
||||||
|
|
||||||
|
# 2. Receive Products
|
||||||
|
picking = po.picking_ids[0]
|
||||||
|
picking.button_validate()
|
||||||
|
|
||||||
|
# Verify initial SVL
|
||||||
|
svls = self.env['stock.valuation.layer'].search([('product_id', '=', self.product.id)])
|
||||||
|
self.assertEqual(len(svls), 1, "Should be 1 SVL for reception")
|
||||||
|
initial_svl_count = len(svls)
|
||||||
|
|
||||||
|
# 3. Create Vendor Bill
|
||||||
|
action = po.action_create_invoice()
|
||||||
|
bill = self.env['account.move'].browse(action['res_id'])
|
||||||
|
bill.invoice_date = Date.today()
|
||||||
|
|
||||||
|
# 4. Edit Vendor Bill Line Price (Simulate User Edit)
|
||||||
|
# Change price from 100 to 120
|
||||||
|
# This triggers the write method on account.move.line
|
||||||
|
line = bill.invoice_line_ids[0]
|
||||||
|
line.price_unit = 120.0 # This triggers the write method
|
||||||
|
|
||||||
|
# 5. Verify NO new SVL created
|
||||||
|
new_svls = self.env['stock.valuation.layer'].search([('product_id', '=', self.product.id)])
|
||||||
|
self.assertEqual(len(new_svls), initial_svl_count, "No new SVL should be created after bill edit")
|
||||||
|
|
||||||
|
# Verify standard price did NOT change (since no revaluation)
|
||||||
|
# Note: If revaluation logic was present, it would update standard_price
|
||||||
|
# However, since we expect NO revaluation, standard price might stay same or move based on other logic?
|
||||||
|
# The key is checking SVL count.
|
||||||
Loading…
Reference in New Issue
Block a user