From 578708f403706d31bafc074f766b64086619899d Mon Sep 17 00:00:00 2001 From: Suherdy Yacob Date: Sat, 24 Jan 2026 18:35:25 +0700 Subject: [PATCH] feat: Remove inventory revaluation logic from vendor bill line price updates and add a confirming test. --- __manifest__.py | 4 +- models/account_move_line.py | 106 +---------------------------------- tests/__init__.py | 1 + tests/test_no_revaluation.py | 72 ++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 106 deletions(-) create mode 100644 tests/test_no_revaluation.py diff --git a/__manifest__.py b/__manifest__.py index 8113caf..605de5e 100755 --- a/__manifest__.py +++ b/__manifest__.py @@ -23,7 +23,9 @@ "author": "Suherdy Yacob", "category": "Accounting", "depends": [ - "account" + "account", + "purchase", + "stock_account", ], "data": [ "views/account_move_views.xml", diff --git a/models/account_move_line.py b/models/account_move_line.py index 745a0e1..d6d7d5f 100755 --- a/models/account_move_line.py +++ b/models/account_move_line.py @@ -3,8 +3,7 @@ from odoo import api, fields, models, _ from odoo.exceptions import UserError -from odoo.tools import float_compare -from datetime import timedelta + class AccountMoveLine(models.Model): @@ -163,110 +162,7 @@ class AccountMoveLine(models.Model): if was_locked: 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 diff --git a/tests/__init__.py b/tests/__init__.py index 14b6aa4..1486d10 100755 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -6,3 +6,4 @@ from . import test_price_total_property from . import test_edge_cases from . import test_view_configuration from . import test_integration +from . import test_no_revaluation diff --git a/tests/test_no_revaluation.py b/tests/test_no_revaluation.py new file mode 100644 index 0000000..cb5c3da --- /dev/null +++ b/tests/test_no_revaluation.py @@ -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.