diff --git a/setup/stock_move_value/odoo/addons/stock_move_value b/setup/stock_move_value/odoo/addons/stock_move_value
new file mode 120000
index 00000000..5b7f2d66
--- /dev/null
+++ b/setup/stock_move_value/odoo/addons/stock_move_value
@@ -0,0 +1 @@
+../../../../stock_move_value
\ No newline at end of file
diff --git a/setup/stock_return_value_discrepancy/setup.py b/setup/stock_move_value/setup.py
similarity index 100%
rename from setup/stock_return_value_discrepancy/setup.py
rename to setup/stock_move_value/setup.py
diff --git a/setup/stock_return_value_discrepancy/odoo/addons/stock_return_value_discrepancy b/setup/stock_return_value_discrepancy/odoo/addons/stock_return_value_discrepancy
deleted file mode 120000
index c7253663..00000000
--- a/setup/stock_return_value_discrepancy/odoo/addons/stock_return_value_discrepancy
+++ /dev/null
@@ -1 +0,0 @@
-../../../../stock_return_value_discrepancy
\ No newline at end of file
diff --git a/stock_return_value_discrepancy/README.rst b/stock_move_value/README.rst
similarity index 100%
rename from stock_return_value_discrepancy/README.rst
rename to stock_move_value/README.rst
diff --git a/stock_return_value_discrepancy/__init__.py b/stock_move_value/__init__.py
similarity index 100%
rename from stock_return_value_discrepancy/__init__.py
rename to stock_move_value/__init__.py
diff --git a/stock_return_value_discrepancy/__manifest__.py b/stock_move_value/__manifest__.py
similarity index 90%
rename from stock_return_value_discrepancy/__manifest__.py
rename to stock_move_value/__manifest__.py
index 98c95296..78bb0ef5 100644
--- a/stock_return_value_discrepancy/__manifest__.py
+++ b/stock_move_value/__manifest__.py
@@ -1,7 +1,7 @@
# Copyright 2024 Quartile (https://www.quartile.co)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{
- "name": "Stock Account Value Discrepancy",
+ "name": "Stock Move Value",
"version": "16.0.1.0.0",
"author": "Quartile, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/stock-logistics-workflow",
diff --git a/stock_move_value/i18n/ja.po b/stock_move_value/i18n/ja.po
new file mode 100644
index 00000000..a9e32d21
--- /dev/null
+++ b/stock_move_value/i18n/ja.po
@@ -0,0 +1,92 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * stock_move_value
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 16.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2024-07-13 10:23+0000\n"
+"PO-Revision-Date: 2024-07-13 10:23+0000\n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: stock_move_value
+#: model:ir.model.fields,help:stock_move_value.field_stock_move__move_origin_value
+msgid ""
+"Corresponding value of the origin move as of the the time move was done. "
+"Only updated for vendor returns."
+msgstr ""
+"在庫移動完了時点での元在庫移動の数量見合い評価額です。仕入返品のときのみ更新されます。"
+
+#. module: stock_move_value
+#: model:ir.model.fields,field_description:stock_move_value.field_stock_move__value_currency_id
+msgid "Currency"
+msgstr "通貨"
+
+#. module: stock_move_value
+#: model_terms:ir.ui.view,arch_db:stock_move_value.view_move_search_inherit
+msgid "Discrepancy Reviewed"
+msgstr "差額レビュー済"
+
+#. module: stock_move_value
+#: model:ir.model.fields,field_description:stock_move_value.field_stock_move__move_origin_value
+msgid "Move Origin Value"
+msgstr "元移動評価額"
+
+#. module: stock_move_value
+#: model:ir.model.fields,field_description:stock_move_value.field_stock_move__move_value
+msgid "Move Value"
+msgstr "移動評価額"
+
+#. module: stock_move_value
+#: model:ir.model.fields,help:stock_move_value.field_stock_move__value_discrepancy
+msgid "Move Value + Move Origin Value. Only updated for vendor returns."
+msgstr "移動評価額 + 元移動評価額。仕入返品のときのみ更新されます。"
+
+#. module: stock_move_value
+#: model_terms:ir.ui.view,arch_db:stock_move_value.view_move_search_inherit
+msgid "Return Value Discrepancy"
+msgstr "返品評価額差異"
+
+#. module: stock_move_value
+#: model:ir.model.fields,help:stock_move_value.field_stock_move__to_review_discrepancy
+msgid ""
+"Selected when Value Discrepancy is not zero. Users are expected to unselect "
+"it when review is done."
+msgstr ""
+"返品評価額差異があるときに選択されます。レビューが済みましたら選択を外してください。"
+
+#. module: stock_move_value
+#: model:ir.model,name:stock_move_value.model_stock_move
+msgid "Stock Move"
+msgstr "在庫移動"
+
+#. module: stock_move_value
+#: model_terms:ir.ui.view,arch_db:stock_move_value.view_move_tree
+#: model_terms:ir.ui.view,arch_db:stock_move_value.view_stock_move_form_inherit
+msgid "To Review"
+msgstr "要レビュー"
+
+#. module: stock_move_value
+#: model:ir.model.fields,field_description:stock_move_value.field_stock_move__to_review_discrepancy
+#: model_terms:ir.ui.view,arch_db:stock_move_value.view_move_search_inherit
+msgid "To Review Discrepancy"
+msgstr "要差異レビュー"
+
+#. module: stock_move_value
+#: model:ir.model.fields,field_description:stock_move_value.field_stock_move__value_discrepancy
+msgid "Value Discrepancy"
+msgstr "評価額差異"
+
+#. module: stock_move_value
+#: model:ir.model.fields,help:stock_move_value.field_stock_move__move_value
+msgid ""
+"Value of the move including related SVL values (i.e. price differences and "
+"landed costs)"
+msgstr ""
+"関連在庫評価レイヤー(価格差異や仕入諸掛によるもの)の金額を含む移動の評価額。"
diff --git a/stock_return_value_discrepancy/models/__init__.py b/stock_move_value/models/__init__.py
similarity index 100%
rename from stock_return_value_discrepancy/models/__init__.py
rename to stock_move_value/models/__init__.py
diff --git a/stock_move_value/models/stock_move.py b/stock_move_value/models/stock_move.py
new file mode 100644
index 00000000..e86a770f
--- /dev/null
+++ b/stock_move_value/models/stock_move.py
@@ -0,0 +1,74 @@
+# Copyright 2024 Quartile (https://www.quartile.co)
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from collections import defaultdict
+
+from odoo import api, fields, models
+
+
+class StockMove(models.Model):
+ _inherit = "stock.move"
+
+ value_currency_id = fields.Many2one(
+ "res.currency", related="company_id.currency_id"
+ )
+ move_value = fields.Monetary(
+ compute="_compute_move_value",
+ store=True,
+ currency_field="value_currency_id",
+ help="Value of the move including related SVL values (i.e. price differences "
+ "and landed costs)",
+ )
+ move_origin_value = fields.Monetary(
+ currency_field="value_currency_id",
+ help="Corresponding value of the origin move as of the the time move was done. "
+ "Only updated for vendor returns.",
+ )
+ value_discrepancy = fields.Monetary(
+ currency_field="value_currency_id",
+ help="Move Value + Move Origin Value. Only updated for vendor returns.",
+ )
+ to_review_discrepancy = fields.Boolean(
+ help="Selected when Value Discrepancy is not zero. Users are expected to "
+ "unselect it when review is done.",
+ )
+
+ @api.depends(
+ "stock_valuation_layer_ids",
+ "stock_valuation_layer_ids.stock_valuation_layer_ids",
+ )
+ def _compute_move_value(self):
+ for move in self:
+ # There can be multiple svls per move in case landed costs are entered
+ move.move_value = sum(move.stock_valuation_layer_ids.mapped("value"))
+
+ def _action_done(self, cancel_backorder=False):
+ origin_values = defaultdict(dict)
+ for move in self:
+ if not move._is_out():
+ continue
+ origin_move = move.origin_returned_move_id
+ if not origin_move:
+ continue
+ # There should be only one record
+ origin_svls = origin_move.stock_valuation_layer_ids.filtered(
+ lambda r: r.quantity > 0
+ )
+ origin_values[move.id] = {
+ "remaining_qty": origin_svls.remaining_qty,
+ "remaining_value": origin_svls.remaining_value,
+ }
+ moves = super()._action_done(cancel_backorder)
+ for move in moves:
+ move.move_value = sum(move.stock_valuation_layer_ids.mapped("value"))
+ if not move._is_out() or not move.origin_returned_move_id:
+ continue
+ move.move_origin_value = (
+ origin_values[move.id]["remaining_value"]
+ * move.product_qty
+ / origin_values[move.id]["remaining_qty"]
+ )
+ move.value_discrepancy = move.move_origin_value + move.move_value
+ if move.value_discrepancy != 0.0:
+ move.to_review_discrepancy = True
+ return moves
diff --git a/stock_move_value/readme/DESCRIPTION.md b/stock_move_value/readme/DESCRIPTION.md
new file mode 100644
index 00000000..9aea13ee
--- /dev/null
+++ b/stock_move_value/readme/DESCRIPTION.md
@@ -0,0 +1,11 @@
+This module adds value fields to the stock move model, to add visivility of how the move
+has affected the stock valuation.
+
+- **Move Value**: Value of the move including related SVL values (i.e. price differences
+ and landed costs)
+- **Move Origin Value**: Corresponding value of the origin move as of the the time move
+ was done. Only updated for vendor returns.
+- **Value Discrepancy**: Move Value + Move Origin Value. Only updated for vendor
+ returns.
+- **To Review**: Selected when Value Discrepancy is not zero. Users are expected to
+ unselect it when review is done.
diff --git a/stock_return_value_discrepancy/static/description/index.html b/stock_move_value/static/description/index.html
similarity index 100%
rename from stock_return_value_discrepancy/static/description/index.html
rename to stock_move_value/static/description/index.html
diff --git a/stock_move_value/tests/__init__.py b/stock_move_value/tests/__init__.py
new file mode 100644
index 00000000..7589f1f5
--- /dev/null
+++ b/stock_move_value/tests/__init__.py
@@ -0,0 +1 @@
+from . import test_stock_move_value
diff --git a/stock_move_value/tests/test_stock_move_value.py b/stock_move_value/tests/test_stock_move_value.py
new file mode 100644
index 00000000..921197f4
--- /dev/null
+++ b/stock_move_value/tests/test_stock_move_value.py
@@ -0,0 +1,82 @@
+# Copyright 2024 Quartile Limited
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
+
+from odoo.tests.common import Form, TransactionCase
+
+
+class TestStockReturnValueDiscrepancy(TransactionCase):
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ product_category = cls.env["product.category"].create(
+ {
+ "name": "test category",
+ "property_cost_method": "average",
+ "property_valuation": "manual_periodic",
+ }
+ )
+ cls.product = cls.env["product.product"].create(
+ {"name": "test product", "categ_id": product_category.id}
+ )
+ cls.location_stock = cls.env.ref("stock.stock_location_stock")
+ cls.location_vendor = cls.env.ref("stock.stock_location_suppliers")
+ cls.picking_in = cls.env["stock.picking"].create(
+ {
+ "picking_type_id": cls.env.ref("stock.picking_type_in").id,
+ "location_id": cls.location_vendor.id,
+ "location_dest_id": cls.location_stock.id,
+ }
+ )
+ cls.move = cls.env["stock.move"].create(
+ {
+ "name": cls.product.name,
+ "product_id": cls.product.id,
+ "product_uom": cls.product.uom_id.id,
+ "location_id": cls.location_vendor.id,
+ "location_dest_id": cls.location_stock.id,
+ "picking_id": cls.picking_in.id,
+ "product_uom_qty": 10,
+ "quantity_done": 10,
+ }
+ )
+
+ def create_return_picking(self, origin_pick, returned_qty):
+ stock_return_picking_form = Form(
+ self.env["stock.return.picking"].with_context(
+ active_ids=origin_pick.ids,
+ active_id=origin_pick.id,
+ active_model="stock.picking",
+ )
+ )
+ return_modal = stock_return_picking_form.save()
+ return_modal.product_return_moves.quantity = returned_qty
+ return_modal.location_id = self.location_vendor.id
+ return_action = return_modal.create_returns()
+ picking = self.env["stock.picking"].browse(return_action["res_id"])
+ picking.move_ids[0].quantity_done = returned_qty
+ return picking
+
+ def test_picking_return_to_vendor(self):
+ self.product.standard_price = 10.0
+ self.picking_in._action_done()
+ self.assertEqual(self.move.move_value, 100.0)
+ self.assertEqual(self.move.move_origin_value, 0.0)
+ self.assertEqual(self.move.value_discrepancy, 0.0)
+ self.assertFalse(self.move.to_review_discrepancy)
+ # Return with no value discrepancy
+ return_pick_1 = self.create_return_picking(self.picking_in, 2.0)
+ return_pick_1._action_done()
+ return_move_1 = return_pick_1.move_ids[0]
+ self.assertEqual(return_move_1.move_value, -20.0)
+ self.assertEqual(return_move_1.move_origin_value, 20.0)
+ self.assertEqual(return_move_1.value_discrepancy, 0.0)
+ self.assertFalse(return_move_1.to_review_discrepancy)
+ # Return with value discrepancy
+ self.product.standard_price = 30.0
+ return_pick_2 = self.create_return_picking(self.picking_in, 5.0)
+ return_pick_2._action_done()
+ return_move_2 = return_pick_2.move_ids[0]
+ self.assertEqual(return_move_2.move_value, -150.0)
+ self.assertEqual(return_move_2.move_origin_value, 50.0)
+ self.assertEqual(return_move_2.value_discrepancy, -100.0)
+ self.assertTrue(return_move_2.to_review_discrepancy)
diff --git a/stock_move_value/views/stock_move_views.xml b/stock_move_value/views/stock_move_views.xml
new file mode 100644
index 00000000..553e2640
--- /dev/null
+++ b/stock_move_value/views/stock_move_views.xml
@@ -0,0 +1,85 @@
+
+
+
+ stock.move.form.inherit
+ stock.move
+
+
+
+
+
+
+
+
+
+
+
+
+ stock.move.tree
+ stock.move
+
+
+
+
+
+
+
+
+
+
+
+
+ stock.move.search.inherit
+ stock.move
+
+
+
+
+
+
+
+
+
+
+
diff --git a/stock_return_value_discrepancy/models/stock_move.py b/stock_return_value_discrepancy/models/stock_move.py
deleted file mode 100644
index 1a6be808..00000000
--- a/stock_return_value_discrepancy/models/stock_move.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright 2024 Quartile (https://www.quartile.co)
-# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
-
-from odoo import fields, models
-
-
-class StockMove(models.Model):
- _inherit = "stock.move"
-
- has_return_value_discrepancy = fields.Boolean()
- to_check_discrepancy = fields.Boolean()
-
- def _action_done(self, cancel_backorder=False):
- moves = super()._action_done(cancel_backorder)
- for move in moves:
- if not (move._is_out() and move.origin_returned_move_id):
- continue
- origin_move_svls = (
- move.origin_returned_move_id.stock_valuation_layer_ids.filtered(
- lambda r: r.quantity > 0
- )
- )
- for svl in origin_move_svls:
- origin_move_svls |= svl.stock_valuation_layer_ids
- origin_unit_cost = 0
- return_unit_cost = 0
- if origin_move_svls:
- origin_value = sum(origin_move_svls.mapped("value"))
- origin_quantity = sum(origin_move_svls.mapped("quantity"))
- origin_unit_cost = origin_value / origin_quantity
- return_move_svls = move.stock_valuation_layer_ids
- if return_move_svls:
- return_value = sum(return_move_svls.mapped("value"))
- return_quantity = sum(return_move_svls.mapped("quantity"))
- return_unit_cost = abs(return_value / return_quantity)
- if origin_unit_cost != return_unit_cost:
- move.has_return_value_discrepancy = True
- move.to_check_discrepancy = True
- return moves
diff --git a/stock_return_value_discrepancy/readme/DESCRIPTION.md b/stock_return_value_discrepancy/readme/DESCRIPTION.md
deleted file mode 100644
index 4f9c0401..00000000
--- a/stock_return_value_discrepancy/readme/DESCRIPTION.md
+++ /dev/null
@@ -1,2 +0,0 @@
-This module checks if there is a unit cost difference between the stock
-valuation layers (SVLs) of receipts and their returns.
diff --git a/stock_return_value_discrepancy/views/stock_move_views.xml b/stock_return_value_discrepancy/views/stock_move_views.xml
deleted file mode 100644
index 0a7adf35..00000000
--- a/stock_return_value_discrepancy/views/stock_move_views.xml
+++ /dev/null
@@ -1,65 +0,0 @@
-
-
-
- stock.move.form.inherit
- stock.move
-
-
-
-
-
-
-
-
-
-
-
- stock.move.tree
- stock.move
-
-
-
-
-
-
-
-
-
- stock.move.search.inherit
- stock.move
-
-
-
-
-
-
-
-
-
-