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 00000000000..5b7f2d666b4 --- /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_move_value/setup.py b/setup/stock_move_value/setup.py new file mode 100644 index 00000000000..28c57bb6403 --- /dev/null +++ b/setup/stock_move_value/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/stock_move_value/README.rst b/stock_move_value/README.rst new file mode 100644 index 00000000000..6967b5700f1 --- /dev/null +++ b/stock_move_value/README.rst @@ -0,0 +1,103 @@ +================ +Stock Move Value +================ + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:60a07245e54e8d796953c1da2564b4f6f8cb7d78eea5fb1a37b08cc3b48b7e83 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fstock--logistics--workflow-lightgray.png?logo=github + :target: https://github.com/OCA/stock-logistics-workflow/tree/16.0/stock_move_value + :alt: OCA/stock-logistics-workflow +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/stock-logistics-workflow-16-0/stock-logistics-workflow-16-0-stock_move_value + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/stock-logistics-workflow&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module adds value fields to the stock move model, to add visibility +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. + +**Table of contents** + +.. contents:: + :local: + +Use Cases / Context +=================== + +Odoo calculates the cost of outgoing stock moves based on the product's +costing method, no matter what the cause of the operation is. This can +be problematic especially when a user just reverts the purchase receipt +operation which was done by mistake (e.g., processing the wrong receipt +picking) for FIFO and AVCO products, as the returned value is not the +same as the value of the original receipt. Some auditors might see such +inconsistencies as the result of intended manipulation on the financial +performance. + +For FIFO products, you can use the OCA module +stock_account_fifo_return_origin (yet to be added with +https://github.com/OCA/stock-logistics-workflow/pull/1649) to return the +same value as the receipt. However, there is no complete solution for +AVCO products. + +This module aims to provide users with a means to identify discrepancies +resulting from their purchase return operations, allowing them to take +necessary actions (e.g., inventory revaluation) as appropriate. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Quartile + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/stock-logistics-workflow `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_move_value/__init__.py b/stock_move_value/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/stock_move_value/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/stock_move_value/__manifest__.py b/stock_move_value/__manifest__.py new file mode 100644 index 00000000000..78bb0ef5bdd --- /dev/null +++ b/stock_move_value/__manifest__.py @@ -0,0 +1,15 @@ +# Copyright 2024 Quartile (https://www.quartile.co) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "Stock Move Value", + "version": "16.0.1.0.0", + "author": "Quartile, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/stock-logistics-workflow", + "category": "stock", + "license": "AGPL-3", + "depends": ["stock_account"], + "data": [ + "views/stock_move_views.xml", + ], + "installable": True, +} diff --git a/stock_move_value/i18n/ja.po b/stock_move_value/i18n/ja.po new file mode 100644 index 00000000000..a9e32d2105b --- /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_move_value/models/__init__.py b/stock_move_value/models/__init__.py new file mode 100644 index 00000000000..6bda2d2428e --- /dev/null +++ b/stock_move_value/models/__init__.py @@ -0,0 +1 @@ +from . import stock_move diff --git a/stock_move_value/models/stock_move.py b/stock_move_value/models/stock_move.py new file mode 100644 index 00000000000..e86a770f673 --- /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/CONTEXT.md b/stock_move_value/readme/CONTEXT.md new file mode 100644 index 00000000000..5e003623894 --- /dev/null +++ b/stock_move_value/readme/CONTEXT.md @@ -0,0 +1,11 @@ +Odoo calculates the cost of outgoing stock moves based on the product's costing method, no matter what the +cause of the operation is. This can be problematic especially when a user just reverts the purchase receipt +operation which was done by mistake (e.g., processing the wrong receipt picking) for FIFO and AVCO products, +as the returned value is not the same as the value of the original receipt. Some auditors might see such +inconsistencies as the result of intended manipulation on the financial performance. + +For FIFO products, you can use the OCA module stock_account_fifo_return_origin (yet to be added with https://github.com/OCA/stock-logistics-workflow/pull/1649) +to return the same value as the receipt. However, there is no complete solution for AVCO products. + +This module aims to provide users with a means to identify discrepancies resulting from their purchase +return operations, allowing them to take necessary actions (e.g., inventory revaluation) as appropriate. diff --git a/stock_move_value/readme/DESCRIPTION.md b/stock_move_value/readme/DESCRIPTION.md new file mode 100644 index 00000000000..9e1bdda9cd1 --- /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 visibility 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_move_value/static/description/index.html b/stock_move_value/static/description/index.html new file mode 100644 index 00000000000..528b9e281ca --- /dev/null +++ b/stock_move_value/static/description/index.html @@ -0,0 +1,445 @@ + + + + + + +Stock Move Value + + + +
+

Stock Move Value

+ + +

Beta License: AGPL-3 OCA/stock-logistics-workflow Translate me on Weblate Try me on Runboat

+

This module adds value fields to the stock move model, to add visibility +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.
  • +
+

Table of contents

+ +
+

Use Cases / Context

+

Odoo calculates the cost of outgoing stock moves based on the product’s +costing method, no matter what the cause of the operation is. This can +be problematic especially when a user just reverts the purchase receipt +operation which was done by mistake (e.g., processing the wrong receipt +picking) for FIFO and AVCO products, as the returned value is not the +same as the value of the original receipt. Some auditors might see such +inconsistencies as the result of intended manipulation on the financial +performance.

+

For FIFO products, you can use the OCA module +stock_account_fifo_return_origin (yet to be added with +https://github.com/OCA/stock-logistics-workflow/pull/1649) to return the +same value as the receipt. However, there is no complete solution for +AVCO products.

+

This module aims to provide users with a means to identify discrepancies +resulting from their purchase return operations, allowing them to take +necessary actions (e.g., inventory revaluation) as appropriate.

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Quartile
  • +
+
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/stock-logistics-workflow project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/stock_move_value/tests/__init__.py b/stock_move_value/tests/__init__.py new file mode 100644 index 00000000000..7589f1f5f87 --- /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 00000000000..921197f4a0d --- /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 00000000000..e36f7a56138 --- /dev/null +++ b/stock_move_value/views/stock_move_views.xml @@ -0,0 +1,88 @@ + + + + stock.move.form.inherit + stock.move + + + + + + + + + + + + + stock.move.tree + stock.move + + + + + + + + + + + + + stock.move.search.inherit + stock.move + + + + + + + + + + +