diff --git a/account_liquidity_forecast_sale/README.rst b/account_liquidity_forecast_sale/README.rst new file mode 100644 index 00000000000..2cb3648fc3a --- /dev/null +++ b/account_liquidity_forecast_sale/README.rst @@ -0,0 +1,87 @@ +=============================== +Account Liquidity Forecast Sale +=============================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:32303c30dc7d9ce2c82299004c2f99409472e2163d9b65b72098273c9a5c047a + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Faccount--financial--reporting-lightgray.png?logo=github + :target: https://github.com/OCA/account-financial-reporting/tree/16.0/account_liquidity_forecast_sale + :alt: OCA/account-financial-reporting +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/account-financial-reporting-16-0/account-financial-reporting-16-0-account_liquidity_forecast_sale + :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/account-financial-reporting&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module extends the functionality of account_liquidity_forecast by enabling the option +of adding the Requests for Quotation on the report. The report uses Date Order (date_order) +field on the Request for Quotation. + +To launch the report wizard, navigate to Accounting/Invoicing > Reporting > Liquidity Forecast. +A configuration wizard will open, allowing you to define the parameters for the report. + +This module adds a new option: + +- Select "Include draft SO": If checked, the report will include draft Sale Orders. + +**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 to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* ForgeFlow + +Contributors +~~~~~~~~~~~~ + +* `ForgeFlow `__: + + * Mateu Griful + +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/account-financial-reporting `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/account_liquidity_forecast_sale/__init__.py b/account_liquidity_forecast_sale/__init__.py new file mode 100644 index 00000000000..5f9d18631d8 --- /dev/null +++ b/account_liquidity_forecast_sale/__init__.py @@ -0,0 +1,2 @@ +from . import report +from . import wizards diff --git a/account_liquidity_forecast_sale/__manifest__.py b/account_liquidity_forecast_sale/__manifest__.py new file mode 100644 index 00000000000..02ea3a10cb0 --- /dev/null +++ b/account_liquidity_forecast_sale/__manifest__.py @@ -0,0 +1,17 @@ +# Copyright 2025 ForgeFlow S.L. (https://www.forgeflow.com) +{ + "name": "Account Liquidity Forecast Sale", + "version": "16.0.1.0.0", + "category": "Reporting", + "summary": "Account Liquidity Forecast Sale", + "author": "ForgeFlow," "Odoo Community Association (OCA)", + "website": "https://github.com/OCA/account-financial-reporting", + "depends": ["account_liquidity_forecast", "sale"], + "data": [ + "wizards/account_liquidity_forecast_wizard_views.xml", + ], + "installable": True, + "application": True, + "auto_install": False, + "license": "AGPL-3", +} diff --git a/account_liquidity_forecast_sale/readme/CONTRIBUTORS.rst b/account_liquidity_forecast_sale/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000000..65dfe206971 --- /dev/null +++ b/account_liquidity_forecast_sale/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* `ForgeFlow `__: + + * Mateu Griful diff --git a/account_liquidity_forecast_sale/readme/DESCRIPTION.rst b/account_liquidity_forecast_sale/readme/DESCRIPTION.rst new file mode 100644 index 00000000000..9aff54fbede --- /dev/null +++ b/account_liquidity_forecast_sale/readme/DESCRIPTION.rst @@ -0,0 +1,10 @@ +This module extends the functionality of account_liquidity_forecast by enabling the option +of adding the Requests for Quotation on the report. The report uses Date Order (date_order) +field on the Request for Quotation. + +To launch the report wizard, navigate to Accounting/Invoicing > Reporting > Liquidity Forecast. +A configuration wizard will open, allowing you to define the parameters for the report. + +This module adds a new option: + +- Select "Include draft SO": If checked, the report will include draft Sale Orders. diff --git a/account_liquidity_forecast_sale/report/__init__.py b/account_liquidity_forecast_sale/report/__init__.py new file mode 100644 index 00000000000..5fb4d79f934 --- /dev/null +++ b/account_liquidity_forecast_sale/report/__init__.py @@ -0,0 +1 @@ +from . import liquidity_forecast diff --git a/account_liquidity_forecast_sale/report/liquidity_forecast.py b/account_liquidity_forecast_sale/report/liquidity_forecast.py new file mode 100644 index 00000000000..71c73aa5f5a --- /dev/null +++ b/account_liquidity_forecast_sale/report/liquidity_forecast.py @@ -0,0 +1,70 @@ +# Copyright 2025 ForgeFlow S.L. (https://www.forgeflow.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import _, models + + +class LiquidityForecastReport(models.AbstractModel): + _inherit = "report.account_liquidity_forecast.liquidity_forecast" + + def _prepare_liquidity_forecast_lines_period( + self, data, liquidity_forecast_lines, period, periods + ): + res = super()._prepare_liquidity_forecast_lines_period( + data, liquidity_forecast_lines, period, periods + ) + include_so_draft = data.get("include_so_draft", False) + if include_so_draft: + self._prepare_cash_flow_lines_sale_draft( + data, liquidity_forecast_lines, period, periods + ) + return res + + def _prepare_cash_flow_lines_sale_draft( + self, data, liquidity_forecast_lines, period, periods + ): + domain = self.get_so_domain(period) + + draft_sale_orders = self.env["sale.order"].search(domain) + if not draft_sale_orders: + return + + sale_lines = draft_sale_orders.mapped("order_line") + if not sale_lines: + return + + code = "cash_flow_line_out_sale_draft" + existing_lines = [ + line for line in liquidity_forecast_lines if line["code"] == code + ] + + if existing_lines: + flow_line = existing_lines[0] + else: + flow_line = { + "code": code, + "type": "amount", + "level": "detail", + "model": "sale.order.line", + "title": _("Draft Sale Orders"), + "sequence": 3500, + "periods": { + p["sequence"]: {"amount": 0.0, "domain": []} for p in periods + }, + } + liquidity_forecast_lines.append(flow_line) + + total = sum(sale_line.price_subtotal for sale_line in sale_lines) + flow_line["periods"][period["sequence"]]["amount"] += total + flow_line["periods"][period["sequence"]]["domain"] = [ + ("id", "in", sale_lines.ids) + ] + + def get_so_domain(self, period): + domain = [ + ("state", "in", ["draft", "sent"]), + ("date_order", "<=", period["date_to"]), + ] + if period["sequence"] > 0: + domain.append(("date_order", ">=", period["date_from"])) + return domain diff --git a/account_liquidity_forecast_sale/static/description/index.html b/account_liquidity_forecast_sale/static/description/index.html new file mode 100644 index 00000000000..71fbdaa285e --- /dev/null +++ b/account_liquidity_forecast_sale/static/description/index.html @@ -0,0 +1,434 @@ + + + + + +Account Liquidity Forecast Sale + + + +
+

Account Liquidity Forecast Sale

+ + +

Beta License: AGPL-3 OCA/account-financial-reporting Translate me on Weblate Try me on Runboat

+

This module extends the functionality of account_liquidity_forecast by enabling the option +of adding the Requests for Quotation on the report. The report uses Date Order (date_order) +field on the Request for Quotation.

+

To launch the report wizard, navigate to Accounting/Invoicing > Reporting > Liquidity Forecast. +A configuration wizard will open, allowing you to define the parameters for the report.

+

This module adds a new option:

+
    +
  • Select “Include draft SO”: If checked, the report will include draft Sale Orders.
  • +
+

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 to smash 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 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/account-financial-reporting project on GitHub.

+

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

+
+
+
+ + diff --git a/account_liquidity_forecast_sale/tests/__init__.py b/account_liquidity_forecast_sale/tests/__init__.py new file mode 100644 index 00000000000..e2560e6702f --- /dev/null +++ b/account_liquidity_forecast_sale/tests/__init__.py @@ -0,0 +1 @@ +from . import test_liquidity_forecast diff --git a/account_liquidity_forecast_sale/tests/test_liquidity_forecast.py b/account_liquidity_forecast_sale/tests/test_liquidity_forecast.py new file mode 100644 index 00000000000..d58287ae597 --- /dev/null +++ b/account_liquidity_forecast_sale/tests/test_liquidity_forecast.py @@ -0,0 +1,209 @@ +from datetime import date, timedelta + +from odoo.tests.common import TransactionCase + + +class TestLiquidityForecastSale(TransactionCase): + def setUp(self): + super().setUp() + self.report = self.env["report.account_liquidity_forecast.liquidity_forecast"] + self.company = self.env.user.company_id + self.partner_1 = self.env["res.partner"].create({"name": "Test Partner 1"}) + + self.product_1 = self.env["product.product"].create( + { + "name": "Test Product 1", + "standard_price": 10.0, + "list_price": 15.0, + } + ) + + self.product_2 = self.env["product.product"].create( + { + "name": "Test Product 2", + "standard_price": 20.0, + "list_price": 25.0, + } + ) + + self.today = date.today() + self.date_to = self.today + timedelta(days=30) + + self.wizard_data = { + "company_id": self.company.id, + "date_from": self.today.strftime("%Y-%m-%d"), + "date_to": self.date_to.strftime("%Y-%m-%d"), + "period_length": "days", + "only_posted_moves": True, + "include_so_draft": True, + } + + # Create draft sale order + self.so_draft_1 = self.env["sale.order"].create( + { + "partner_id": self.partner_1.id, + "date_order": self.today, + "order_line": [ + ( + 0, + 0, + { + "product_id": self.product_1.id, + "name": "Draft SO Line", + "product_uom_qty": 5.0, + "price_unit": 20.0, + }, + ) + ], + "company_id": self.company.id, + } + ) + + # Create confirmed sale order + self.so_confirm = self.env["sale.order"].create( + { + "partner_id": self.partner_1.id, + "date_order": self.today, + "order_line": [ + ( + 0, + 0, + { + "product_id": self.product_2.id, + "name": "Confirmed SO Line", + "product_uom_qty": 3.0, + "price_unit": 30.0, + }, + ) + ], + "company_id": self.company.id, + } + ) + self.so_confirm.action_confirm() + + def test_draft_sale_order_included_when_flag_set(self): + """Test that draft SOs are included when include_so_draft=True.""" + data = dict(self.wizard_data) + + lines = [] + periods = self.report._generate_periods(data) + self.report._prepare_cash_flow_lines_sale_draft( + data, lines, periods[0], periods + ) + + draft_lines = [ + line + for line in lines + if line.get("code") == "cash_flow_line_out_sale_draft" + ] + self.assertTrue( + draft_lines, + "Expected draft sale order lines to be included in forecast lines", + ) + + self.assertEqual( + draft_lines[0]["periods"][0]["amount"], 100.0, "Amount does not match" + ) + + def test_draft_sale_order_not_included_when_flag_false(self): + """Test that draft SOs are not included when include_so_draft=False.""" + data = dict(self.wizard_data) + data["include_so_draft"] = False + + lines = [] + periods = self.report._generate_periods(data) + self.report._prepare_liquidity_forecast_lines_period( + data, lines, periods[0], periods + ) + + draft_lines = [ + line + for line in lines + if line.get("code") == "cash_flow_line_out_sale_draft" + ] + self.assertFalse( + draft_lines, + "Draft sale order lines should not be included when flag is False", + ) + + def test_confirmed_sale_order_not_included_in_draft_lines(self): + """Test that confirmed SOs are not included in draft lines.""" + data = dict(self.wizard_data) + + lines = [] + periods = self.report._generate_periods(data) + self.report._prepare_cash_flow_lines_sale_draft( + data, lines, periods[0], periods + ) + + draft_lines = [ + line + for line in lines + if line.get("code") == "cash_flow_line_out_sale_draft" + ] + + self.assertTrue(draft_lines, "Should have draft lines from draft SO") + + domain_ids = draft_lines[0]["periods"][0]["domain"][0][2] + self.assertIn( + self.so_draft_1.id, + domain_ids, + "Domain should contain the draft SO ID", + ) + self.assertNotIn( + self.so_confirm.id, + domain_ids, + "Domain should NOT contain the confirmed SO ID", + ) + + def test_multiple_draft_sale_orders_aggregated(self): + """Test that multiple draft SOs are properly aggregated.""" + # Create additional draft SO + so_draft_2 = self.env["sale.order"].create( + { + "partner_id": self.partner_1.id, + "date_order": self.today, + "order_line": [ + ( + 0, + 0, + { + "product_id": self.product_2.id, + "name": "Additional Draft Line", + "product_uom_qty": 2.0, + "price_unit": 15.0, + }, + ) + ], + "company_id": self.company.id, + } + ) + + data = dict(self.wizard_data) + lines = [] + periods = self.report._generate_periods(data) + self.report._prepare_cash_flow_lines_sale_draft( + data, lines, periods[0], periods + ) + + draft_lines = [ + line + for line in lines + if line.get("code") == "cash_flow_line_out_sale_draft" + ] + self.assertEqual( + len(draft_lines), + 1, + "Should have only one draft line aggregating all draft SOs", + ) + + expected_total = 100.0 + 30.0 + self.assertEqual( + draft_lines[0]["periods"][0]["amount"], + expected_total, + f"Total amount should be {expected_total}", + ) + + domain_ids = draft_lines[0]["periods"][0]["domain"][0][2] + self.assertIn(self.so_draft_1.id, domain_ids) + self.assertIn(so_draft_2.id, domain_ids) diff --git a/account_liquidity_forecast_sale/wizards/__init__.py b/account_liquidity_forecast_sale/wizards/__init__.py new file mode 100644 index 00000000000..78edaf860e3 --- /dev/null +++ b/account_liquidity_forecast_sale/wizards/__init__.py @@ -0,0 +1 @@ +from . import account_liquidity_forecast_wizard diff --git a/account_liquidity_forecast_sale/wizards/account_liquidity_forecast_wizard.py b/account_liquidity_forecast_sale/wizards/account_liquidity_forecast_wizard.py new file mode 100644 index 00000000000..11ce3be4efb --- /dev/null +++ b/account_liquidity_forecast_sale/wizards/account_liquidity_forecast_wizard.py @@ -0,0 +1,17 @@ +# Copyright 2025 ForgeFlow, S.L. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class LiquidityForecastReportWizard(models.TransientModel): + + _inherit = "account.liquidity.forecast.report.wizard" + + include_so_draft = fields.Boolean(default=True, string="Include Draft SO") + + def _prepare_report_liquidity_forecast(self): + self.ensure_one() + res = super()._prepare_report_liquidity_forecast() + res.update({"include_so_draft": self.include_so_draft}) + return res diff --git a/account_liquidity_forecast_sale/wizards/account_liquidity_forecast_wizard_views.xml b/account_liquidity_forecast_sale/wizards/account_liquidity_forecast_wizard_views.xml new file mode 100644 index 00000000000..2e72d996ade --- /dev/null +++ b/account_liquidity_forecast_sale/wizards/account_liquidity_forecast_wizard_views.xml @@ -0,0 +1,17 @@ + + + + Liquidity Forecast Wizard Inherit + account.liquidity.forecast.report.wizard + + + + + + + + + diff --git a/setup/account_liquidity_forecast_sale/odoo/addons/account_liquidity_forecast_sale b/setup/account_liquidity_forecast_sale/odoo/addons/account_liquidity_forecast_sale new file mode 120000 index 00000000000..98aeb5576f1 --- /dev/null +++ b/setup/account_liquidity_forecast_sale/odoo/addons/account_liquidity_forecast_sale @@ -0,0 +1 @@ +../../../../account_liquidity_forecast_sale \ No newline at end of file diff --git a/setup/account_liquidity_forecast_sale/setup.py b/setup/account_liquidity_forecast_sale/setup.py new file mode 100644 index 00000000000..28c57bb6403 --- /dev/null +++ b/setup/account_liquidity_forecast_sale/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)