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 - - - - - - - - - -