Repository: OCA/account-financial-tools
Pull request #2065: [17.0][FW][FIX] account_move_line_purchase_info: do not consider stock entry lines on revaluations
Author: login: JordiMForgeFlow
Labels: name: approved
name: merged 🎉
Created: 2025-04-09T08:32:14Z | Merged: 2025-04-11T07:36:08Z

FW of https://github.com/OCA/account-financial-tools/pull/2018, related to https://github.com/OCA/account-financial-tools/issues/2017


Diff:
diff --git a/account_move_line_purchase_info/README.rst b/account_move_line_purchase_info/README.rst
index f63c9a78feb..384be07da86 100644
--- a/account_move_line_purchase_info/README.rst
+++ b/account_move_line_purchase_info/README.rst
@@ -33,6 +33,16 @@ This module will add the purchase order line to journal items.
 The ultimate goal is to establish the purchase order line as one of the
 key fields to reconcile the Goods Received Not Invoiced accrual account.
 
+Field oca_purchase_line_id it's necessary. In Odoo >=16 automatic
+revaluation for a product with FIFO costing method only works if invoice
+lines related to a purchase order line do not include stock journal
+items. To avoid that oca_purchase_line_id includes invoice and stock
+journal items, and we keep Odoo field invoice_lines just with bill
+lines.
+
+-  Check issue
+   https://github.com/OCA/account-financial-tools/issues/2017
+
 **Table of contents**
 
 .. contents::
@@ -44,10 +54,10 @@ Usage
 The purchase order line will be automatically copied to the journal
 items.
 
-- When a supplier invoice is created referencing purchase orders, the
-  purchase order line will be copied to the corresponding journal item.
-- When a stock move is validated and generates a journal entry, the
-  purchase order line is copied to the account move line.
+-  When a supplier invoice is created referencing purchase orders, the
+   purchase order line will be copied to the corresponding journal item.
+-  When a stock move is validated and generates a journal entry, the
+   purchase order line is copied to the account move line.
 
 Bug Tracker
 ===========
@@ -70,9 +80,9 @@ Authors
 Contributors
 ------------
 
-- Jordi Ballester Alomar <jordi.ballester@forgeflow.com>
-- Héctor Villarreal <hector.villarreal@forgeflow.com>
-- Pimolnat Suntian <pimolnats@ecosoft.co.th>
+-  Jordi Ballester Alomar <jordi.ballester@forgeflow.com>
+-  Héctor Villarreal <hector.villarreal@forgeflow.com>
+-  Pimolnat Suntian <pimolnats@ecosoft.co.th>
 
 Maintainers
 -----------
diff --git a/account_move_line_purchase_info/__manifest__.py b/account_move_line_purchase_info/__manifest__.py
index 2f25dbc4527..581dff6d8df 100644
--- a/account_move_line_purchase_info/__manifest__.py
+++ b/account_move_line_purchase_info/__manifest__.py
@@ -5,7 +5,7 @@
 {
     "name": "Account Move Line Purchase Info",
     "summary": "Introduces the purchase order line to the journal items",
-    "version": "17.0.1.0.1",
+    "version": "17.0.2.0.0",
     "author": "ForgeFlow, Odoo Community Association (OCA)",
     "website": "https://github.com/OCA/account-financial-tools",
     "category": "Generic",
diff --git a/account_move_line_purchase_info/migrations/17.0.2.0.0/post-migration.py b/account_move_line_purchase_info/migrations/17.0.2.0.0/post-migration.py
new file mode 100644
index 00000000000..872e40ed284
--- /dev/null
+++ b/account_move_line_purchase_info/migrations/17.0.2.0.0/post-migration.py
@@ -0,0 +1,34 @@
+import logging
+
+from openupgradelib import openupgrade
+
+_logger = logging.getLogger(__name__)
+
+
+@openupgrade.migrate()
+def migrate(env, version):
+    purchase_orders = env["purchase.order"].search([("state", "=", "purchase")])
+    for order in purchase_orders:
+        try:
+            _logger.info(f"Processing Purchase Order: {order.name} (ID: {order.id})")
+            valued_lines = order.order_line.invoice_lines.filtered(
+                lambda line: line.product_id
+                and line.product_id.cost_method != "standard"
+                and (
+                    not line.company_id.tax_lock_date
+                    or line.date > line.company_id.tax_lock_date
+                )
+            )
+            svls, _amls = valued_lines._apply_price_difference()
+
+            if svls:
+                svls._validate_accounting_entries()
+
+            bills = order.invoice_ids.filtered(lambda bill: bill.state == "posted")
+            bills._stock_account_anglo_saxon_reconcile_valuation()
+
+        except Exception as e:
+            _logger.error(
+                f"Error processing Purchase Order {order.name} (ID: {order.id}): {str(e)}",
+                exc_info=True,
+            )
diff --git a/account_move_line_purchase_info/migrations/17.0.2.0.0/pre-migration.py b/account_move_line_purchase_info/migrations/17.0.2.0.0/pre-migration.py
new file mode 100644
index 00000000000..91335613f1e
--- /dev/null
+++ b/account_move_line_purchase_info/migrations/17.0.2.0.0/pre-migration.py
@@ -0,0 +1,31 @@
+from openupgradelib import openupgrade
+
+
+@openupgrade.migrate()
+def migrate(env, version):
+    openupgrade.logged_query(
+        env.cr,
+        """
+        ALTER TABLE account_move_line
+        ADD COLUMN IF NOT EXISTS oca_purchase_line_id INTEGER;
+        """,
+    )
+
+    openupgrade.logged_query(
+        env.cr,
+        """
+        UPDATE account_move_line
+        SET oca_purchase_line_id = purchase_line_id;
+        """,
+    )
+
+    openupgrade.logged_query(
+        env.cr,
+        """
+        UPDATE account_move_line
+        SET purchase_line_id = NULL
+        FROM account_move
+        WHERE account_move_line.move_id = account_move.id
+            AND account_move.move_type = 'entry';
+        """,
+    )
diff --git a/account_move_line_purchase_info/models/account_move.py b/account_move_line_purchase_info/models/account_move.py
index b8a6e1bbebd..d952b913a26 100644
--- a/account_move_line_purchase_info/models/account_move.py
+++ b/account_move_line_purchase_info/models/account_move.py
@@ -2,14 +2,38 @@
 #   (https://www.forgeflow.com)
 # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
 
-from odoo import fields, models
+from odoo import api, fields, models
 
 
 class AccountMoveLine(models.Model):
     _inherit = "account.move.line"
 
+    # Set related None, to make it compute and avoid base related to purchase_line_id
     purchase_order_id = fields.Many2one(
         comodel_name="purchase.order",
+        related=None,
         store=True,
         index=True,
+        compute="_compute_purchase_id",
     )
+
+    oca_purchase_line_id = fields.Many2one(
+        comodel_name="purchase.order.line",
+        string="OCA Purchase Line",
+        store=True,
+        index=True,
+        compute="_compute_oca_purchase_line_id",
+    )
+
+    @api.depends("purchase_line_id")
+    def _compute_oca_purchase_line_id(self):
+        for rec in self:
+            if rec.purchase_line_id:
+                rec.oca_purchase_line_id = rec.purchase_line_id
+
+    @api.depends("purchase_line_id", "oca_purchase_line_id")
+    def _compute_purchase_id(self):
+        for rec in self:
+            rec.purchase_order_id = (
+                rec.purchase_line_id.order_id.id or rec.oca_purchase_line_id.order_id.id
+            )
diff --git a/account_move_line_purchase_info/models/purchase_order.py b/account_move_line_purchase_info/models/purchase_order.py
index fc4934e2ceb..93c8775006a 100644
--- a/account_move_line_purchase_info/models/purchase_order.py
+++ b/account_move_line_purchase_info/models/purchase_order.py
@@ -4,12 +4,12 @@
 class PurchaseOrder(models.Model):
     _inherit = "purchase.order"
 
-    @api.depends("order_line.invoice_lines.move_id")
+    @api.depends("order_line.stock_invoice_lines.move_id")
     def _compute_journal_entries(self):
         for order in self:
-            journal_entries = order.mapped("order_line.invoice_lines.move_id").filtered(
-                lambda r: r.move_type == "entry"
-            )
+            journal_entries = order.mapped(
+                "order_line.stock_invoice_lines.move_id"
+            ).filtered(lambda r: r.move_type == "entry")
             order.journal_entry_ids = journal_entries
             order.journal_entries_count = len(journal_entries)
 
@@ -21,17 +21,6 @@ def _compute_journal_entries(self):
         string="Journal Entries",
     )
 
-    @api.depends("order_line.invoice_lines.move_id")
-    def _compute_invoice(self):
-        """Overwritten compute to avoid show all Journal Entries with
-        purchase_order_line as invoice_lines One2many would take them into account."""
-        for order in self:
-            invoices = order.order_line.invoice_lines.move_id.filtered(
-                lambda m: m.is_invoice(include_receipts=True)
-            )
-            order.invoice_ids = invoices
-            order.invoice_count = len(invoices)
-
     def action_view_journal_entries(self, invoices=False):
         """This function returns an action that display existing journal entries of
         given purchase order ids. When only one found, show the journal entry
diff --git a/account_move_line_purchase_info/models/purchase_order_line.py b/account_move_line_purchase_info/models/purchase_order_line.py
index 2bd05793570..b219e136835 100644
--- a/account_move_line_purchase_info/models/purchase_order_line.py
+++ b/account_move_line_purchase_info/models/purchase_order_line.py
@@ -1,12 +1,16 @@
 # Copyright 2019-2020 ForgeFlow S.L.
 #   (https://www.forgeflow.com)
 # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
-from odoo import api, models
+from odoo import api, fields, models
 
 
 class PurchaseOrderLine(models.Model):
     _inherit = "purchase.order.line"
 
+    stock_invoice_lines = fields.One2many(
+        "account.move.line", "oca_purchase_line_id", readonly=True, copy=False
+    )
+
     @api.depends("name", "order_id.name", "order_id.state")
     @api.depends_context("po_line_info")
     def _compute_display_name(self):
diff --git a/account_move_line_purchase_info/models/stock_move.py b/account_move_line_purchase_info/models/stock_move.py
index e6db0388eb5..34b9b07920c 100644
--- a/account_move_line_purchase_info/models/stock_move.py
+++ b/account_move_line_purchase_info/models/stock_move.py
@@ -15,5 +15,5 @@ def _prepare_account_move_line(
             qty, cost, credit_account_id, debit_account_id, svl_id, description
         )
         for line in res:
-            line[2]["purchase_line_id"] = self.purchase_line_id.id
+            line[2]["oca_purchase_line_id"] = self.purchase_line_id.id
         return res
diff --git a/account_move_line_purchase_info/readme/DESCRIPTION.md b/account_move_line_purchase_info/readme/DESCRIPTION.md
index 8ea578615b0..ccd288542d4 100644
--- a/account_move_line_purchase_info/readme/DESCRIPTION.md
+++ b/account_move_line_purchase_info/readme/DESCRIPTION.md
@@ -1,4 +1,11 @@
 This module will add the purchase order line to journal items.
 
-The ultimate goal is to establish the purchase order line as one of the
-key fields to reconcile the Goods Received Not Invoiced accrual account.
+The ultimate goal is to establish the purchase order line as one of the key
+fields to reconcile the Goods Received Not Invoiced accrual account.
+
+Field oca_purchase_line_id it's necessary. In Odoo >=16 automatic
+revaluation for a product with FIFO costing method only works if invoice
+lines related to a purchase order line do not include stock journal items.
+To avoid that oca_purchase_line_id includes invoice and stock journal items,
+and we keep Odoo field invoice_lines just with bill lines.
+- Check issue https://github.com/OCA/account-financial-tools/issues/2017
diff --git a/account_move_line_purchase_info/static/description/index.html b/account_move_line_purchase_info/static/description/index.html
index 845e0e73977..e6b60a24749 100644
--- a/account_move_line_purchase_info/static/description/index.html
+++ b/account_move_line_purchase_info/static/description/index.html
@@ -373,6 +373,16 @@ <h1 class="title">Account Move Line Purchase Info</h1>
 <p>This module will add the purchase order line to journal items.</p>
 <p>The ultimate goal is to establish the purchase order line as one of the
 key fields to reconcile the Goods Received Not Invoiced accrual account.</p>
+<p>Field oca_purchase_line_id it’s necessary. In Odoo &gt;=16 automatic
+revaluation for a product with FIFO costing method only works if invoice
+lines related to a purchase order line do not include stock journal
+items. To avoid that oca_purchase_line_id includes invoice and stock
+journal items, and we keep Odoo field invoice_lines just with bill
+lines.</p>
+<ul class="simple">
+<li>Check issue
+<a class="reference external" href="https://github.com/OCA/account-financial-tools/issues/2017">https://github.com/OCA/account-financial-tools/issues/2017</a></li>
+</ul>
 <p><strong>Table of contents</strong></p>
 <div class="contents local topic" id="contents">
 <ul class="simple">
diff --git a/account_move_line_purchase_info/tests/test_account_move_line_purchase_info.py b/account_move_line_purchase_info/tests/test_account_move_line_purchase_info.py
index 3c44e2d8e94..b9482e351f1 100644
--- a/account_move_line_purchase_info/tests/test_account_move_line_purchase_info.py
+++ b/account_move_line_purchase_info/tests/test_account_move_line_purchase_info.py
@@ -153,7 +153,7 @@ def _check_account_balance(
         """
         domain = [("account_id", "=", account_id)]
         if purchase_line:
-            domain.extend([("purchase_line_id", "=", purchase_line.id)])
+            domain.extend([("oca_purchase_line_id", "=", purchase_line.id)])
 
         balance = self._get_balance(domain)
         if purchase_line:
@@ -207,7 +207,8 @@ def test_purchase_invoice(self):
     def test_display_name(self):
         purchase = self._create_purchase([(self.product, 1)])
         po_line = purchase.order_line[0]
-        name_get = po_line.with_context(**{"po_line_info": True}).name_get()
+        name_get = po_line.with_context(**{"po_line_info": True}).read(["display_name"])
+        name_get = [(po_line.id, name_get[0]["display_name"])]
         self.assertEqual(
             name_get,
             [
@@ -219,7 +220,8 @@ def test_display_name(self):
                 )
             ],
         )
-        name_get_no_ctx = po_line.name_get()
+        name_get_no_ctx = po_line.read(["display_name"])
+        name_get_no_ctx = [(po_line.id, name_get_no_ctx[0]["display_name"])]
         self.assertEqual(name_get_no_ctx, [(po_line.id, po_line.name)])
 
     def test_purchase_order_with_journal_entries_and_vendor_bills(self):
diff --git a/purchase_unreconciled/__manifest__.py b/purchase_unreconciled/__manifest__.py
index 9b6e2109702..3484626b30d 100644
--- a/purchase_unreconciled/__manifest__.py
+++ b/purchase_unreconciled/__manifest__.py
@@ -3,7 +3,7 @@
 
 {
     "name": "Purchase Unreconciled",
-    "version": "17.0.1.0.0",
+    "version": "17.0.2.0.0",
     "author": "ForgeFlow S.L., Odoo Community Association (OCA)",
     "website": "https://github.com/OCA/account-financial-tools",
     "category": "Purchases",
diff --git a/purchase_unreconciled/models/purchase_order.py b/purchase_unreconciled/models/purchase_order.py
index 4e3fb7e1ef7..f50e0cacaa5 100644
--- a/purchase_unreconciled/models/purchase_order.py
+++ b/purchase_unreconciled/models/purchase_order.py
@@ -118,8 +118,8 @@ def action_reconcile(self):
         all_writeoffs = self.env["account.move.line"]
         reconciling_groups = self.env["account.move.line"].read_group(
             domain=unreconciled_domain,
-            fields=["account_id", "product_id", "purchase_line_id"],
-            groupby=["account_id", "product_id", "purchase_line_id"],
+            fields=["account_id", "product_id", "oca_purchase_line_id"],
+            groupby=["account_id", "product_id", "oca_purchase_line_id"],
             lazy=False,
         )
         unreconciled_items = self.env["account.move.line"].search(unreconciled_domain)
@@ -127,7 +127,9 @@ def action_reconcile(self):
             account_id = group["account_id"][0]
             product_id = group["product_id"][0] if group["product_id"] else False
             purchase_line_id = (
-                group["purchase_line_id"][0] if group["purchase_line_id"] else False
+                group["oca_purchase_line_id"][0]
+                if group["oca_purchase_line_id"]
+                else False
             )
             unreconciled_items_group = unreconciled_items.filtered(
                 lambda line, account_id=account_id, product_id=product_id: (


Reviews:
user: login: LoisRForgeFlow
body: :+1: 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T06:54:27Z
user: login: AaronHForgeFlow
body: Code review LGTM! Thank you updating the purchase_unreconciled module
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T11:35:56Z
user: login: MiquelRForgeFlow
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T11:47:47Z
user: login: JordiMForgeFlow
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T07:03:57Z
user: login: LoisRForgeFlow
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T07:15:41Z
user: login: AaronHForgeFlow
body: code LGTM! Thank you
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T07:24:08Z

Review comments:
diff_hunk: @@ -5,7 +5,7 @@
 {
     "name": "Account Move Line Purchase Info",
     "summary": "Introduces the purchase order line to the journal items",
-    "version": "17.0.1.0.1",
+    "version": "17.0.2.0.0",
     "author": "ForgeFlow, Odoo Community Association (OCA)",
     "website": "https://github.com/OCA/account-financial-tools",
     "category": "Generic",
path: account_move_line_purchase_info/__manifest__.py
user: login: MiquelRForgeFlow
body: BTW, why this module doesn't depend on `account_move_line_stock_info`?
created_at: 2025-04-10T11:47:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5,7 +5,7 @@
 {
     "name": "Account Move Line Purchase Info",
     "summary": "Introduces the purchase order line to the journal items",
-    "version": "17.0.1.0.1",
+    "version": "17.0.2.0.0",
     "author": "ForgeFlow, Odoo Community Association (OCA)",
     "website": "https://github.com/OCA/account-financial-tools",
     "category": "Generic",
path: account_move_line_purchase_info/__manifest__.py
user: login: JordiMForgeFlow
body: @MiquelRForgeFlow I don't see that it uses anything specific from account_move_line_stock_info
created_at: 2025-04-11T07:03:57Z
author_association: CONTRIBUTOR

PR comments:
user: login: AaronHForgeFlow
created_at: 2025-04-11T07:25:37Z
body: /ocabot merge nobump
author_association: CONTRIBUTOR
user: login: OCA-git-bot
created_at: 2025-04-11T07:25:46Z
body: What a great day to merge this nice PR. Let's do it!
Prepared branch [17.0-ocabot-merge-pr-2065-by-AaronHForgeFlow-bump-nobump](https://github.com/OCA/account-financial-tools/commits/17.0-ocabot-merge-pr-2065-by-AaronHForgeFlow-bump-nobump), awaiting test results.
author_association: CONTRIBUTOR
user: login: OCA-git-bot
created_at: 2025-04-11T07:36:09Z
body: Congratulations, your PR was merged at e22c646088be1c69896fa94657eacf4e2460099f. Thanks a lot for contributing to OCA. ❤️
author_association: CONTRIBUTOR