diff --git a/rental_check_availability/README.rst b/rental_check_availability/README.rst new file mode 100644 index 00000000..fab23a32 --- /dev/null +++ b/rental_check_availability/README.rst @@ -0,0 +1,46 @@ +Rental Check Availability +==================================================== + +*This file has been generated on 2022-04-20-11-34-50. Changes to it will be overwritten.* + +Summary +------- + +Extends the sale_rental module for checking availability of the rented product. + +Description +----------- + +This module activates availability checks on stockable products related to rental services in +sale orders. In the base functionality only the total amount of products in stock is checked and user is +informed when the amount of products to rent out in a sale order is higher. + +After the installation of this module the availability is checked in consideration of the total amount +of goods in stock and the amount of products used in concurrent sale orders at the certain desired timeframe. +In case of insufficient products in stock the user receives visual notification on respective sale order line +and can access the list of concurrent sale orders directly. + + +Usage +----- + +To use this module, you need to: + +#. Go to Rental Orders and create a new one. + +#. Add a product available for being rented out in sale order line. + +#. If there is not enough stock on hand to fullfil the order and + possible concurrent ones the sale order line will be colorized. + Yellow marks that there are concurrent quotations and red indicates + concurrent orders. + +#. To check the concurrent order for a critical sale order line just click + on the inline button being displayed in the sale order line. + +Changelog +--------- + +- 8d191ff7 2022-04-10 15:41:16 +0200 wagner@elegosoft.com (HEAD -> v14, origin/v14) add missing/lost documentation (issue #4516) +- 4509f78a 2022-02-23 20:48:33 +0100 wagner@elegosoft.com (origin/feature_4516_add_files_ported_from_v12_v14, feature_4516_add_files_ported_from_v12_v14) add files ported to v14 by cpatel and khanhbui (issue #4516) + diff --git a/rental_check_availability/README/CONTRIBUTORS.rst b/rental_check_availability/README/CONTRIBUTORS.rst new file mode 100644 index 00000000..357fa53c --- /dev/null +++ b/rental_check_availability/README/CONTRIBUTORS.rst @@ -0,0 +1,6 @@ + +Contributors +------------ + +elego Software Solutions GmbH, Odoo Community Association (OCA) +Ben Brich (www.humanilog.org), Yu Weng (www.elegosoft.com) diff --git a/rental_check_availability/README/DESCRIPTION.rst b/rental_check_availability/README/DESCRIPTION.rst new file mode 100644 index 00000000..4bb39ded --- /dev/null +++ b/rental_check_availability/README/DESCRIPTION.rst @@ -0,0 +1,21 @@ +Rental Check Availability +==================================================== + +*This file has been generated on 2022-04-20-11-34-50. Changes to it will be overwritten.* + +Summary +------- + +Extends the sale_rental module for checking availability of the rented product. + +Description +----------- + +This module activates availability checks on stockable products related to rental services in +sale orders. In the base functionality, only the total amount of products in stock is checked and the user is +informed when the amount of products to rent out in a sale order is higher. + +After the installation of this module, the availability is checked in consideration of the total amount +of goods in stock and the amount of products used in concurrent sale orders at a certain desired timeframe. +In case of insufficient products in stock, the user receives a visual notification on the respective sale order line +and can access the list of concurrent sale orders directly. diff --git a/rental_check_availability/README/HISTORY.rst b/rental_check_availability/README/HISTORY.rst new file mode 100644 index 00000000..d3b08eb7 --- /dev/null +++ b/rental_check_availability/README/HISTORY.rst @@ -0,0 +1,6 @@ + +Changelog +--------- + +- 8d191ff7 2022-04-10 15:41:16 +0200 wagner@elegosoft.com (HEAD -> v14, origin/v14) add missing/lost documentation (issue #4516) +- 4509f78a 2022-02-23 20:48:33 +0100 wagner@elegosoft.com (origin/feature_4516_add_files_ported_from_v12_v14, feature_4516_add_files_ported_from_v12_v14) add files ported to v14 by cpatel and khanhbui (issue #4516) diff --git a/rental_check_availability/README/USAGE.rst b/rental_check_availability/README/USAGE.rst new file mode 100644 index 00000000..5c58c398 --- /dev/null +++ b/rental_check_availability/README/USAGE.rst @@ -0,0 +1,17 @@ + +Usage +----- + +To use this module, you need to: + +#. Go to Rental Orders and create a new one. + +#. Add a product available for being rented out in sale order line. + +#. If there is not enough stock on hand to fullfil the order and + possible concurrent ones the sale order line will be colorized. + Yellow marks that there are concurrent quotations and red indicates + concurrent orders. + +#. To check the concurrent order for a critical sale order line just click + on the inline button being displayed in the sale order line. diff --git a/rental_check_availability/__init__.py b/rental_check_availability/__init__.py new file mode 100644 index 00000000..77c9fdfc --- /dev/null +++ b/rental_check_availability/__init__.py @@ -0,0 +1,3 @@ +# Part of rental-vertical See LICENSE file for full copyright and licensing details. + +from . import models diff --git a/rental_check_availability/__manifest__.py b/rental_check_availability/__manifest__.py new file mode 100644 index 00000000..ec777a5b --- /dev/null +++ b/rental_check_availability/__manifest__.py @@ -0,0 +1,26 @@ +# Part of rental-vertical See LICENSE file for full copyright and licensing details. + +{ + "name": "Rental Check Availability", + "summary": "Extends the sale_rental module for checking availability" + "of the rented product.", + "version": "15.0.1.0.0", + "category": "Rental", + "author": "elego Software Solutions GmbH, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/vertical-rental", + "contributors": [ + "Ben Brich (www.humanilog.org)", + "Yu Weng (www.elegosoft.com)", + ], + "depends": [ + "rental_pricelist", + ], + "data": [ + "views/sale_view.xml", + ], + "demo": [], + "qweb": [], + "application": False, + "installable": True, + "license": "AGPL-3", +} diff --git a/rental_check_availability/i18n/de.po b/rental_check_availability/i18n/de.po new file mode 100644 index 00000000..46dc2fde --- /dev/null +++ b/rental_check_availability/i18n/de.po @@ -0,0 +1,80 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * rental_check_availability +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2021-10-18 11:56+0000\n" +"PO-Revision-Date: 2021-10-18 14:06+0200\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: \n" +"X-Generator: Poedit 3.0\n" + +#. module: rental_check_availability +#: model_terms:ir.ui.view,arch_db:rental_check_availability.view_order_form_order_line_tree_color +msgid "Check Availability" +msgstr "Verfügbarkeit prüfen" + +#. module: rental_check_availability +#: model:ir.model.fields,field_description:rental_check_availability.field_sale_order_line__concurrent_orders +msgid "Concurrent Orders" +msgstr "Zeitgleiche Aufträge" + +#. module: rental_check_availability +#: code:addons/rental_check_availability/models/sale.py:114 +#, python-format +msgid "No found concurrent Rental Order/Quotation(s)." +msgstr "Es konnten keine zeitgleichen Aufträge gefunden werden." + +#. module: rental_check_availability +#: selection:sale.order.line,concurrent_orders:0 +msgid "None" +msgstr "Keine" + +#. module: rental_check_availability +#: code:addons/rental_check_availability/models/sale.py:48 +#, python-format +msgid "Not enough stock!" +msgstr "Bestand nicht ausreichend!" + +#. module: rental_check_availability +#: selection:sale.order.line,concurrent_orders:0 +msgid "Order" +msgstr "Auftrag" + +#. module: rental_check_availability +#: selection:sale.order.line,concurrent_orders:0 +msgid "Quotation" +msgstr "Angebot" + +#. module: rental_check_availability +#: model:ir.model,name:rental_check_availability.model_sale_order +msgid "Sale Order" +msgstr "Verkaufsauftrag" + +#. module: rental_check_availability +#: model:ir.model,name:rental_check_availability.model_sale_order_line +msgid "Sales Order Line" +msgstr "Auftragsposition" + +#. module: rental_check_availability +#: model_terms:ir.ui.view,arch_db:rental_check_availability.view_order_form +msgid "View concurrent Order/Quotation(s)" +msgstr "Zeitgleiche Aufträge anzeigen." + +#. module: rental_check_availability +#: code:addons/rental_check_availability/models/sale.py:49 +#, python-format +msgid "" +"You want to rent %.2f %s but you only have %.2f %s available in the selected " +"period." +msgstr "" +"Sie versuchen %.2f %s Einheiten zu vermieten, es sind aber nur %.2f %s " +"Einheiten im aktuellen Bestand verfügbar." diff --git a/rental_check_availability/i18n/rental_check_availability.pot b/rental_check_availability/i18n/rental_check_availability.pot new file mode 100644 index 00000000..7de29b8c --- /dev/null +++ b/rental_check_availability/i18n/rental_check_availability.pot @@ -0,0 +1,73 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * rental_check_availability +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \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: rental_check_availability +#: model_terms:ir.ui.view,arch_db:rental_check_availability.view_order_form_order_line_tree_color +msgid "Check Availability" +msgstr "" + +#. module: rental_check_availability +#: model:ir.model.fields,field_description:rental_check_availability.field_sale_order_line__concurrent_orders +msgid "Concurrent Orders" +msgstr "" + +#. module: rental_check_availability +#: code:addons/rental_check_availability/models/sale.py:114 +#, python-format +msgid "No found concurrent Rental Order/Quotation(s)." +msgstr "" + +#. module: rental_check_availability +#: selection:sale.order.line,concurrent_orders:0 +msgid "None" +msgstr "" + +#. module: rental_check_availability +#: code:addons/rental_check_availability/models/sale.py:48 +#, python-format +msgid "Not enough stock!" +msgstr "" + +#. module: rental_check_availability +#: selection:sale.order.line,concurrent_orders:0 +msgid "Order" +msgstr "" + +#. module: rental_check_availability +#: selection:sale.order.line,concurrent_orders:0 +msgid "Quotation" +msgstr "" + +#. module: rental_check_availability +#: model:ir.model,name:rental_check_availability.model_sale_order +msgid "Sale Order" +msgstr "" + +#. module: rental_check_availability +#: model:ir.model,name:rental_check_availability.model_sale_order_line +msgid "Sales Order Line" +msgstr "" + +#. module: rental_check_availability +#: model_terms:ir.ui.view,arch_db:rental_check_availability.view_order_form +msgid "View concurrent Order/Quotation(s)" +msgstr "" + +#. module: rental_check_availability +#: code:addons/rental_check_availability/models/sale.py:49 +#, python-format +msgid "You want to rent %.2f %s but you only have %.2f %s available in the selected period." +msgstr "" + diff --git a/rental_check_availability/models/__init__.py b/rental_check_availability/models/__init__.py new file mode 100644 index 00000000..27d189bd --- /dev/null +++ b/rental_check_availability/models/__init__.py @@ -0,0 +1,2 @@ +# Part of rental-vertical See LICENSE file for full copyright and licensing details. +from . import sale diff --git a/rental_check_availability/models/sale.py b/rental_check_availability/models/sale.py new file mode 100644 index 00000000..f29e094e --- /dev/null +++ b/rental_check_availability/models/sale.py @@ -0,0 +1,135 @@ +# Part of rental-vertical See LICENSE file for full copyright and licensing details. +from odoo import _, api, exceptions, fields, models + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + def action_check_rental_availability(self): + for order in self: + for line in order.order_line: + line._check_rental_availability() + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + concurrent_orders = fields.Selection( + selection=[ + ("none", "None"), + ("quotation", "Quotation"), + ("order", "Order"), + ], + default="none", + ) + + # (override) from module rental_pricelist + def _check_rental_availability(self): + self.ensure_one() + res = {} + if not self.start_date or not self.end_date or not self.rental_qty: + return {} + total_qty = self.product_id.rented_product_id.with_context( + location=self.order_id.warehouse_id.rental_view_location_id.id + ).qty_available + max_ol_qty = self._get_max_overlapping_rental_qty() + avail_qty = total_qty - max_ol_qty + if self.rental_qty > avail_qty: + res = self._get_concurrent_orders() + if total_qty == 0: + self.concurrent_orders = "none" + elif res["quotation"] and not res["order"]: + self.concurrent_orders = "quotation" + else: + self.concurrent_orders = "order" + res["warning"] = { + "title": _("Not enough stock!"), + "message": _( + "You want to rent %(requested_units).2f Unit(s) but you only have " + "%(available_units).2f Unit(s) available in the selected period." + ) + % { + "requested_units": 3.00, # Replace with the actual requested units + "available_units": 2.00, # Replace with the actual available units + }, + } + else: + self.concurrent_orders = "none" + return res + + # (override) from module rental_pricelist + @api.onchange("start_date", "end_date", "product_uom") + def onchange_start_end_date(self): + res = {} + if self.start_date and self.end_date: + number = self._get_number_of_time_unit() + self.number_of_time_unit = number + res = self._check_rental_availability() + return res + + def _get_concurrent_order_lines(self): + self.ensure_one() + domain = [] + if self.id: + domain = [("id", "!=", self.id)] + domain += [ + ("state", "!=", "cancel"), + ("display_product_id", "=", self.display_product_id.id), + "|", + "&", + ("start_date", "<=", self.start_date), + ("end_date", ">=", self.start_date), + "&", + ("start_date", "<=", self.end_date), + ("end_date", ">=", self.end_date), + ] + res = self.search(domain) + return res + + def _get_concurrent_orders(self): + self.ensure_one() + sols = self._get_concurrent_order_lines() + sos = sols.mapped("order_id") + quotations = sos.filtered(lambda o: o.state in ["draft", "sent"]) + orders = sos.filtered(lambda o: o.state in ["sale"]) + return { + "quotation": quotations, + "order": orders, + "sale_order_ids": quotations.ids + orders.ids, + } + + def action_view_concurrent_orders(self): + self.ensure_one() + record_ids = self._get_concurrent_orders()["sale_order_ids"] + if record_ids: + action = self.env.ref("rental_base.action_rental_orders").read([])[0] + action["domain"] = [("id", "in", record_ids)] + return action + raise exceptions.UserError(_("No found concurrent Rental Order/Quotation(s).")) + + def _get_max_overlapping_rental_qty(self): + self.ensure_one() + lines = self._get_concurrent_order_lines() + max_qty = 0 + for line in lines: + ol_lines = self.search( + [ + ("id", "in", lines.ids), + ("start_date", "<=", line.start_date), + ("end_date", ">=", line.start_date), + ] + ) + tmp_qty = sum(line.rental_qty for line in ol_lines) + if tmp_qty > max_qty: + max_qty = tmp_qty + ol_lines = self.search( + [ + ("id", "in", lines.ids), + ("start_date", "<=", line.end_date), + ("end_date", ">=", line.end_date), + ] + ) + tmp_qty = sum(line.rental_qty for line in ol_lines) + if tmp_qty > max_qty: + max_qty = tmp_qty + return max_qty diff --git a/rental_check_availability/static/description/icon.png b/rental_check_availability/static/description/icon.png new file mode 100644 index 00000000..3a0328b5 Binary files /dev/null and b/rental_check_availability/static/description/icon.png differ diff --git a/rental_check_availability/static/description/index.html b/rental_check_availability/static/description/index.html new file mode 100644 index 00000000..f7cc6a69 --- /dev/null +++ b/rental_check_availability/static/description/index.html @@ -0,0 +1,404 @@ + + + + + + +Rental Check Availability + + + +
+

Rental Check Availability

+ +

This file has been generated on 2022-04-20-11-34-50. Changes to it will be overwritten.

+
+

Summary

+

Extends the sale_rental module for checking availability of the rented product.

+
+
+

Description

+

This module activates availability checks on stockable products related to rental services in +sale orders. In the base functionality only the total amount of products in stock is checked and user is +informed when the amount of products to rent out in a sale order is higher.

+

After the installation of this module the availability is checked in consideration of the total amount +of goods in stock and the amount of products used in concurrent sale orders at the certain desired timeframe. +In case of insufficient products in stock the user receives visual notification on respective sale order line +and can access the list of concurrent sale orders directly.

+
+
+

Usage

+

To use this module, you need to:

+
    +
  1. Go to Rental Orders and create a new one.
  2. +
  3. Add a product available for being rented out in sale order line.
  4. +
  5. If there is not enough stock on hand to fullfil the order and +possible concurrent ones the sale order line will be colorized. +Yellow marks that there are concurrent quotations and red indicates +concurrent orders.
  6. +
  7. To check the concurrent order for a critical sale order line just click +on the inline button being displayed in the sale order line.
  8. +
+
+
+

Changelog

+
    +
  • 8d191ff7 2022-04-10 15:41:16 +0200 wagner@elegosoft.com (HEAD -> v14, origin/v14) add missing/lost documentation (issue #4516)
  • +
  • 4509f78a 2022-02-23 20:48:33 +0100 wagner@elegosoft.com (origin/feature_4516_add_files_ported_from_v12_v14, feature_4516_add_files_ported_from_v12_v14) add files ported to v14 by cpatel and khanhbui (issue #4516)
  • +
+
+
+ + diff --git a/rental_check_availability/tests/__init__.py b/rental_check_availability/tests/__init__.py new file mode 100644 index 00000000..67a577ee --- /dev/null +++ b/rental_check_availability/tests/__init__.py @@ -0,0 +1,3 @@ +# Part of rental-vertical See LICENSE file for full copyright and licensing details. + +from . import test_check_availability diff --git a/rental_check_availability/tests/test_check_availability.py b/rental_check_availability/tests/test_check_availability.py new file mode 100644 index 00000000..3fabc111 --- /dev/null +++ b/rental_check_availability/tests/test_check_availability.py @@ -0,0 +1,132 @@ +# Part of rental-vertical See LICENSE file for full copyright and licensing details. + +from dateutil.relativedelta import relativedelta + +from odoo import fields + +from odoo.addons.rental_base.tests.stock_common import RentalStockCommon +from odoo.addons.rental_pricelist.tests.test_rental_pricelist import ( + _run_sol_onchange_date, + _run_sol_onchange_display_product_id, +) + + +class TestRentalCheckAvailability(RentalStockCommon): + def setUp(self): + super().setUp() + + # Product Created A + ProductObj = self.env["product.product"] + self.productA = ProductObj.create( + { + "name": "Product A", + "type": "product", + "rental": True, + "rental_of_day": True, + "rental_price_day": 100, + } + ) + self.today = fields.Date.from_string(fields.Date.today()) + self.date_2_day_later = self.today + relativedelta(days=2) + self.date_5_day_later = self.today + relativedelta(days=5) + self.date_10_day_later = self.today + relativedelta(days=10) + self.date_15_day_later = self.today + relativedelta(days=15) + self.date_20_day_later = self.today + relativedelta(days=20) + self.date_25_day_later = self.today + relativedelta(days=25) + self.date_27_day_later = self.today + relativedelta(days=27) + self.date_30_day_later = self.today + relativedelta(days=30) + + def create_rental_order(self, start_date, end_date, qty): + rental_order = ( + self.env["sale.order"] + .with_context(default_type_id=self.rental_sale_type.id) + .create( + { + "warehouse_id": self.warehouse0.id, + "partner_id": self.partnerA.id, + "pricelist_id": self.env.ref("product.list0").id, + } + ) + ) + line = ( + self.env["sale.order.line"] + .with_context(type_id=self.rental_sale_type.id) + .new( + { + "order_id": rental_order.id, + "display_product_id": self.productA.id, + "start_date": start_date, + "end_date": end_date, + } + ) + ) + _run_sol_onchange_display_product_id(line) + line.rental_qty = qty + _run_sol_onchange_date(line) + values = line._convert_to_write(line._cache) + self.env["sale.order.line"].create(values) + return rental_order + + def test_00_check_availability(self): + # Rental Orders Info + # RO 1 (qty: 2) today -------- 10 (none) + # RO 2 (qty: 2) 20 ----------- 30 (none) + # RO 3 (qty: 3) 27 - 30 (quotation) + # RO 4 (qty: 1) 5 ----------------------- 25 (none) + # RO 5 (qty: 3) today - 2 (order) + # RO 6 (qty: 1) 5 --------- 15 (none) + expected_warning = { + "title": "Not enough stock!", + "message": ( + "You want to rent 3.00 Unit(s) but you only have " + "2.00 Unit(s) available in the selected period." + ), + } + # create some quantity of productA (qty: 4) + self.env["stock.quant"]._update_available_quantity( + self.productA, self.warehouse0.rental_in_location_id, 4 + ) + # RO 1 + rental_order_1 = self.create_rental_order(self.today, self.date_10_day_later, 2) + rental_order_1.action_confirm() + self.assertEqual(rental_order_1.order_line.concurrent_orders, "none") + + # RO 2 + rental_order_2 = self.create_rental_order( + self.date_20_day_later, self.date_30_day_later, 2 + ) + self.assertEqual(rental_order_2.order_line.concurrent_orders, "none") + + # RO 3 + rental_order_3 = self.create_rental_order( + self.date_27_day_later, self.date_30_day_later, 3 + ) + res = rental_order_3.order_line.onchange_start_end_date() + self.assertEqual(res.get("warning", False), expected_warning) + self.assertEqual(rental_order_3.order_line.concurrent_orders, "quotation") + action = rental_order_3.order_line.action_view_concurrent_orders() + self.assertEqual( + action.get("domain", False), [("id", "in", [rental_order_2.id])] + ) + + # RO 4 + rental_order_4 = self.create_rental_order( + self.date_5_day_later, self.date_25_day_later, 1 + ) + self.assertEqual(rental_order_4.order_line.concurrent_orders, "none") + + # RO 5 + rental_order_5 = self.create_rental_order(self.today, self.date_2_day_later, 3) + res = rental_order_5.order_line.onchange_start_end_date() + self.assertEqual(res.get("warning", False), expected_warning) + self.assertEqual(rental_order_5.order_line.concurrent_orders, "order") + action = rental_order_5.order_line.action_view_concurrent_orders() + self.assertEqual( + action.get("domain", False), [("id", "in", [rental_order_1.id])] + ) + + # RO 6 + rental_order_6 = self.create_rental_order( + self.date_5_day_later, self.date_15_day_later, 1 + ) + self.assertEqual(rental_order_6.order_line.concurrent_orders, "none") diff --git a/rental_check_availability/views/sale_view.xml b/rental_check_availability/views/sale_view.xml new file mode 100644 index 00000000..8035464d --- /dev/null +++ b/rental_check_availability/views/sale_view.xml @@ -0,0 +1,64 @@ + + + + sale_rental.view_order_form + sale.order + + + + + + + + + diff --git a/setup/rental_check_availability/odoo/addons/rental_check_availability b/setup/rental_check_availability/odoo/addons/rental_check_availability new file mode 120000 index 00000000..ea97753f --- /dev/null +++ b/setup/rental_check_availability/odoo/addons/rental_check_availability @@ -0,0 +1 @@ +../../../../rental_check_availability \ No newline at end of file diff --git a/setup/rental_check_availability/setup.py b/setup/rental_check_availability/setup.py new file mode 100644 index 00000000..28c57bb6 --- /dev/null +++ b/setup/rental_check_availability/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)