diff --git a/rma_account_unreconciled/README.rst b/rma_account_unreconciled/README.rst new file mode 100644 index 00000000..4d6f0581 --- /dev/null +++ b/rma_account_unreconciled/README.rst @@ -0,0 +1,62 @@ +======================= +RMA Account Unreconcile +======================= + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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-LGPL--3-blue.png + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-ForgeFlow%2Fstock--rma-lightgray.png?logo=github + :target: https://github.com/ForgeFlow/stock-rma/tree/14.0/rma_account_unreconciled + :alt: ForgeFlow/stock-rma + +|badge1| |badge2| |badge3| + +This module adds a new fields "Unreconciled" on RMA Order Lines, that allows +to find Order's with unreconciled journal items related. + +This module allows to reconcile those Orders in a single click. In accounting +settings users will be able to set up a specific account for write-off. + +**Table of contents** + +.. contents:: + :local: + +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 smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* ForgeFlow + +Contributors +~~~~~~~~~~~~ + +* Jordi Ballester Alomar +* Christopher Ormaza + +Maintainers +~~~~~~~~~~~ + +This module is part of the `ForgeFlow/stock-rma `_ project on GitHub. + +You are welcome to contribute. diff --git a/rma_account_unreconciled/__init__.py b/rma_account_unreconciled/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/rma_account_unreconciled/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/rma_account_unreconciled/__manifest__.py b/rma_account_unreconciled/__manifest__.py new file mode 100644 index 00000000..b3202a48 --- /dev/null +++ b/rma_account_unreconciled/__manifest__.py @@ -0,0 +1,17 @@ +# Copyright 2022 ForgeFlow S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) + +{ + "name": "RMA Account Unreconcile", + "version": "14.0.1.0.0", + "license": "LGPL-3", + "category": "RMA", + "summary": "Integrates RMA with Invoice Processing", + "author": "ForgeFlow", + "website": "https://github.com/ForgeFlow/stock-rma", + "depends": ["account_move_line_rma_order_line", "rma"], + "data": [ + "views/rma_line_view.xml", + ], + "installable": True, +} diff --git a/rma_account_unreconciled/models/__init__.py b/rma_account_unreconciled/models/__init__.py new file mode 100644 index 00000000..9c48acd4 --- /dev/null +++ b/rma_account_unreconciled/models/__init__.py @@ -0,0 +1 @@ +from . import rma_line diff --git a/rma_account_unreconciled/models/rma_line.py b/rma_account_unreconciled/models/rma_line.py new file mode 100644 index 00000000..f8865366 --- /dev/null +++ b/rma_account_unreconciled/models/rma_line.py @@ -0,0 +1,91 @@ +# Copyright 2022 ForgeFlow S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) + +from odoo import _, api, fields, models +from odoo.osv import expression + + +class RmaOrderLine(models.Model): + + _inherit = "rma.order.line" + + unreconciled = fields.Boolean( + compute="_compute_unreconciled", + search="_search_unreconciled", + help="Indicates that a Purchase Order has related Journal items not " + "reconciled.\nNote that if it is false it can be either that " + "everything is reconciled or that the related accounts do not " + "allow reconciliation", + ) + + def _compute_unreconciled(self): + acc_item = self.env["account.move.line"] + for rec in self: + domain = rec._get_rma_unreconciled_base_domain() + unreconciled_domain = expression.AND( + [domain, [("rma_line_id", "=", rec.id)]] + ) + unreconciled_items = acc_item.search(unreconciled_domain) + rec.unreconciled = len(unreconciled_items) > 0 + + def _search_unreconciled(self, operator, value): + if operator != "=" or not isinstance(value, bool): + raise ValueError(_("Unsupported search operator")) + acc_item = self.env["account.move.line"] + domain = self._get_rma_unreconciled_base_domain() + unreconciled_domain = expression.AND([domain, [("rma_line_id", "!=", False)]]) + unreconciled_items = acc_item.search(unreconciled_domain) + unreconciled_pos = unreconciled_items.mapped("rma_line_id") + if value: + return [("id", "in", unreconciled_pos.ids)] + else: + return [("id", "not in", unreconciled_pos.ids)] + + @api.model + def _get_rma_unreconciled_base_domain(self): + categories = self.env["product.category"].search( + [("property_valuation", "=", "real_time")] + ) + included_accounts = ( + categories.mapped("property_stock_account_input_categ_id").ids + ) + (categories.mapped("property_stock_account_output_categ_id").ids) + unreconciled_domain = [ + ("account_id.reconcile", "=", True), + ("account_id", "in", included_accounts), + ("move_id.state", "=", "posted"), + # for some reason when amount_residual is zero + # is marked as reconciled, this is better check + ("full_reconcile_id", "=", False), + ("company_id", "in", self.env.companies.ids), + ] + return unreconciled_domain + + def action_view_unreconciled(self): + self.ensure_one() + acc_item = self.env["account.move.line"] + domain = self._get_rma_unreconciled_base_domain() + unreconciled_domain = expression.AND([domain, [("rma_line_id", "=", self.id)]]) + unreconciled_items = acc_item.search(unreconciled_domain) + action = self.env.ref("account.action_account_moves_all") + action_dict = action.read()[0] + action_dict["domain"] = [("id", "in", unreconciled_items.ids)] + return action_dict + + def action_open_reconcile(self): + aml_model = self.env["account.move.line"] + action = self.action_view_unreconciled() + amls = ( + action.get("domain") and aml_model.search(action.get("domain")) or aml_model + ) + accounts = amls.mapped("account_id") + action_context = { + "show_mode_selector": False, + "account_ids": accounts.ids, + "active_model": "account.move.line", + "active_ids": amls.ids, + } + return { + "type": "ir.actions.client", + "tag": "manual_reconciliation_view", + "context": action_context, + } diff --git a/rma_account_unreconciled/readme/CONTRIBUTORS.rst b/rma_account_unreconciled/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..68f3dbd5 --- /dev/null +++ b/rma_account_unreconciled/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Jordi Ballester Alomar +* Christopher Ormaza diff --git a/rma_account_unreconciled/readme/DESCRIPTION.rst b/rma_account_unreconciled/readme/DESCRIPTION.rst new file mode 100644 index 00000000..1378495d --- /dev/null +++ b/rma_account_unreconciled/readme/DESCRIPTION.rst @@ -0,0 +1,5 @@ +This module adds a new fields "Unreconciled" on RMA Order Lines, that allows +to find Order's with unreconciled journal items related. + +This module allows to reconcile those Orders in a single click. In accounting +settings users will be able to set up a specific account for write-off. diff --git a/rma_account_unreconciled/static/description/index.html b/rma_account_unreconciled/static/description/index.html new file mode 100644 index 00000000..5595ade8 --- /dev/null +++ b/rma_account_unreconciled/static/description/index.html @@ -0,0 +1,418 @@ + + + + + + +RMA Account Unreconcile + + + +
+

RMA Account Unreconcile

+ + +

Beta License: LGPL-3 ForgeFlow/stock-rma

+

This module adds a new fields “Unreconciled” on RMA Order Lines, that allows +to find Order’s with unreconciled journal items related.

+

This module allows to reconcile those Orders in a single click. In accounting +settings users will be able to set up a specific account for write-off.

+

Table of contents

+ +
+

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 smashing it by providing a detailed and welcomed +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • ForgeFlow
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is part of the ForgeFlow/stock-rma project on GitHub.

+

You are welcome to contribute.

+
+
+
+ + diff --git a/rma_account_unreconciled/tests/__init__.py b/rma_account_unreconciled/tests/__init__.py new file mode 100644 index 00000000..ba3f6aeb --- /dev/null +++ b/rma_account_unreconciled/tests/__init__.py @@ -0,0 +1 @@ +from . import test_rma_account_unreconciled diff --git a/rma_account_unreconciled/tests/test_rma_account_unreconciled.py b/rma_account_unreconciled/tests/test_rma_account_unreconciled.py new file mode 100644 index 00000000..82427a76 --- /dev/null +++ b/rma_account_unreconciled/tests/test_rma_account_unreconciled.py @@ -0,0 +1,108 @@ +# Copyright 2022 ForgeFlow S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) + + +from odoo.addons.rma.tests.test_rma import TestRma + + +class TestRmaAccountUnreconciled(TestRma): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.rma_refund_wiz = cls.env["rma.refund"] + for categ in cls.rma_customer_id.mapped("rma_line_ids.product_id.categ_id"): + categ.write( + { + "property_valuation": "real_time", + "property_cost_method": "fifo", + } + ) + categ.property_stock_account_input_categ_id.write( + { + "reconcile": True, + } + ) + categ.property_stock_account_output_categ_id.write( + { + "reconcile": True, + } + ) + for product in cls.rma_customer_id.mapped("rma_line_ids.product_id"): + product.write( + { + "standard_price": 10.0, + } + ) + + def test_unreconciled_moves(self): + for rma_line in self.rma_customer_id.rma_line_ids: + rma_line.write( + { + "refund_policy": "received", + } + ) + rma_line.action_rma_approve() + self.assertFalse(rma_line.unreconciled) + self.rma_customer_id.rma_line_ids.action_rma_to_approve() + wizard = self.rma_make_picking.with_context( + { + "active_ids": self.rma_customer_id.rma_line_ids.ids, + "active_model": "rma.order.line", + "picking_type": "incoming", + "active_id": 1, + } + ).create({}) + wizard._create_picking() + res = self.rma_customer_id.rma_line_ids.action_view_in_shipments() + picking = self.env["stock.picking"].browse(res["res_id"]) + picking.action_assign() + for mv in picking.move_lines: + mv.quantity_done = mv.product_uom_qty + picking.button_validate() + for rma_line in self.rma_customer_id.rma_line_ids: + rma_line._compute_unreconciled() + self.assertTrue(rma_line.unreconciled) + make_refund = self.rma_refund_wiz.with_context( + { + "customer": True, + "active_ids": self.rma_customer_id.rma_line_ids.ids, + "active_model": "rma.order.line", + } + ).create( + { + "description": "Test refund", + } + ) + for item in make_refund.item_ids: + item.write( + { + "qty_to_refund": item.product_qty, + } + ) + make_refund.invoice_refund() + self.rma_customer_id.rma_line_ids.refund_line_ids.move_id.filtered( + lambda x: x.state != "posted" + ).action_post() + for rma_line in self.rma_customer_id.rma_line_ids: + rma_line._compute_unreconciled() + self.assertTrue(rma_line.unreconciled) + + self.assertEqual( + self.env["rma.order.line"].search_count( + [ + ("type", "=", "customer"), + ("unreconciled", "=", True), + ("rma_id", "=", self.rma_customer_id.id), + ] + ), + 3, + ) + for rma_line in self.rma_customer_id.rma_line_ids: + aml_domain = rma_line.action_view_unreconciled().get("domain") + aml_lines = ( + aml_domain and self.env["account.move.line"].search(aml_domain) or False + ) + if aml_lines: + aml_lines.reconcile() + rma_line._compute_unreconciled() + self.assertFalse(rma_line.unreconciled) diff --git a/rma_account_unreconciled/views/rma_line_view.xml b/rma_account_unreconciled/views/rma_line_view.xml new file mode 100644 index 00000000..3ccc9fbd --- /dev/null +++ b/rma_account_unreconciled/views/rma_line_view.xml @@ -0,0 +1,54 @@ + + + + + + rma.order.line.view.form + rma.order.line + + +
+ + + +
+
+
+ + + rma.order.line.search.view + rma.order.line + + + + + + + + +
+