From eac95067acba398ddb839893c166b6f536616b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A0n=20Todorovich?= Date: Mon, 23 Aug 2021 13:00:41 -0300 Subject: [PATCH 01/16] [ADD] website_sale_cart_expire Module that allows to automatically cancel carts without activity after a configurable time. --- website_sale_cart_expire/README.rst | 88 ++++ website_sale_cart_expire/__init__.py | 1 + website_sale_cart_expire/__manifest__.py | 16 + website_sale_cart_expire/data/ir_cron.xml | 19 + .../i18n/website_sale_cart_expire.pot | 93 ++++ website_sale_cart_expire/models/__init__.py | 3 + .../models/res_config_settings.py | 13 + website_sale_cart_expire/models/sale_order.py | 27 ++ website_sale_cart_expire/models/website.py | 45 ++ website_sale_cart_expire/readme/CONFIGURE.rst | 1 + .../readme/CONTRIBUTORS.rst | 3 + .../readme/DESCRIPTION.rst | 1 + .../static/description/icon.png | Bin 0 -> 9455 bytes .../static/description/index.html | 432 ++++++++++++++++++ website_sale_cart_expire/tests/__init__.py | 1 + .../tests/test_website_sale_cart_expire.py | 59 +++ .../views/res_config_settings.xml | 54 +++ 17 files changed, 856 insertions(+) create mode 100644 website_sale_cart_expire/README.rst create mode 100644 website_sale_cart_expire/__init__.py create mode 100644 website_sale_cart_expire/__manifest__.py create mode 100644 website_sale_cart_expire/data/ir_cron.xml create mode 100644 website_sale_cart_expire/i18n/website_sale_cart_expire.pot create mode 100644 website_sale_cart_expire/models/__init__.py create mode 100644 website_sale_cart_expire/models/res_config_settings.py create mode 100644 website_sale_cart_expire/models/sale_order.py create mode 100644 website_sale_cart_expire/models/website.py create mode 100644 website_sale_cart_expire/readme/CONFIGURE.rst create mode 100644 website_sale_cart_expire/readme/CONTRIBUTORS.rst create mode 100644 website_sale_cart_expire/readme/DESCRIPTION.rst create mode 100644 website_sale_cart_expire/static/description/icon.png create mode 100644 website_sale_cart_expire/static/description/index.html create mode 100644 website_sale_cart_expire/tests/__init__.py create mode 100644 website_sale_cart_expire/tests/test_website_sale_cart_expire.py create mode 100644 website_sale_cart_expire/views/res_config_settings.xml diff --git a/website_sale_cart_expire/README.rst b/website_sale_cart_expire/README.rst new file mode 100644 index 0000000000..fff544d6be --- /dev/null +++ b/website_sale_cart_expire/README.rst @@ -0,0 +1,88 @@ +======================== +Website Sale Cart Expire +======================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! 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-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%2Fe--commerce-lightgray.png?logo=github + :target: https://github.com/OCA/e-commerce/tree/13.0/website_sale_cart_expire + :alt: OCA/e-commerce +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/e-commerce-13-0/e-commerce-13-0-website_sale_cart_expire + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/113/13.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Allows to automatically cancel carts without activity after a configurable time. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +Go to Website > Settings and set a delay for Expire Carts settings. + +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 +~~~~~~~ + +* Camptocamp + +Contributors +~~~~~~~~~~~~ + +* `Camptocamp `_ + + * Iván Todorovich + +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. + +.. |maintainer-ivantodorovich| image:: https://github.com/ivantodorovich.png?size=40px + :target: https://github.com/ivantodorovich + :alt: ivantodorovich + +Current `maintainer `__: + +|maintainer-ivantodorovich| + +This module is part of the `OCA/e-commerce `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/website_sale_cart_expire/__init__.py b/website_sale_cart_expire/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/website_sale_cart_expire/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/website_sale_cart_expire/__manifest__.py b/website_sale_cart_expire/__manifest__.py new file mode 100644 index 0000000000..d5cf809318 --- /dev/null +++ b/website_sale_cart_expire/__manifest__.py @@ -0,0 +1,16 @@ +# Copyright 2021 Camptocamp (http://www.camptocamp.com). +# @author Iván Todorovich +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Website Sale Cart Expire", + "summary": "Expire abandoned carts", + "version": "13.0.1.0.0", + "author": "Camptocamp, Odoo Community Association (OCA)", + "maintainers": ["ivantodorovich"], + "website": "https://github.com/OCA/e-commerce", + "license": "AGPL-3", + "category": "Website", + "depends": ["website_sale"], + "data": ["data/ir_cron.xml", "views/res_config_settings.xml"], +} diff --git a/website_sale_cart_expire/data/ir_cron.xml b/website_sale_cart_expire/data/ir_cron.xml new file mode 100644 index 0000000000..9cf889b7b5 --- /dev/null +++ b/website_sale_cart_expire/data/ir_cron.xml @@ -0,0 +1,19 @@ + + + + + + Website: Expire Carts + + code + model._scheduler_website_expire_cart() + 5 + minutes + -1 + + + diff --git a/website_sale_cart_expire/i18n/website_sale_cart_expire.pot b/website_sale_cart_expire/i18n/website_sale_cart_expire.pot new file mode 100644 index 0000000000..f2fa8d6ef8 --- /dev/null +++ b/website_sale_cart_expire/i18n/website_sale_cart_expire.pot @@ -0,0 +1,93 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * website_sale_cart_expire +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.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: website_sale_cart_expire +#: model_terms:ir.ui.view,arch_db:website_sale_cart_expire.res_config_settings_view_form +msgid "" +"Expire Carts\n" +" " +msgstr "" + +#. module: website_sale_cart_expire +#: model_terms:ir.ui.view,arch_db:website_sale_cart_expire.res_config_settings_view_form +msgid "hours" +msgstr "" + +#. module: website_sale_cart_expire +#: model_terms:ir.ui.view,arch_db:website_sale_cart_expire.res_config_settings_view_form +msgid "Automatically cancel carts without activity after a period of time" +msgstr "" + +#. module: website_sale_cart_expire +#: model:ir.model.fields,help:website_sale_cart_expire.field_res_config_settings__cart_expire_delay +#: model:ir.model.fields,help:website_sale_cart_expire.field_website__cart_expire_delay +msgid "" +"Automatically cancel website orders after the given time.\n" +"Set to 0 to disable this feature." +msgstr "" + +#. module: website_sale_cart_expire +#: model:ir.model.fields,field_description:website_sale_cart_expire.field_sale_order__cart_expire_date +msgid "Cart Expire Date" +msgstr "" + +#. module: website_sale_cart_expire +#: code:addons/website_sale_cart_expire/models/website.py:0 +#, python-format +msgid "Cart expired" +msgstr "" + +#. module: website_sale_cart_expire +#: model_terms:ir.ui.view,arch_db:website_sale_cart_expire.res_config_settings_view_form +msgid "Cart is cancelled after" +msgstr "" + +#. module: website_sale_cart_expire +#: model_terms:ir.ui.view,arch_db:website_sale_cart_expire.res_config_settings_view_form +msgid "Carts are cancelled after this delay." +msgstr "" + +#. module: website_sale_cart_expire +#: model:ir.model,name:website_sale_cart_expire.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: website_sale_cart_expire +#: model:ir.model.fields,field_description:website_sale_cart_expire.field_res_config_settings__cart_expire_delay +#: model:ir.model.fields,field_description:website_sale_cart_expire.field_website__cart_expire_delay +msgid "Expire Delay" +msgstr "" + +#. module: website_sale_cart_expire +#: model:ir.model,name:website_sale_cart_expire.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: website_sale_cart_expire +#: model:ir.model.fields,help:website_sale_cart_expire.field_sale_order__cart_expire_date +msgid "Technical field: The date this cart will automatically expire" +msgstr "" + +#. module: website_sale_cart_expire +#: model:ir.model,name:website_sale_cart_expire.model_website +msgid "Website" +msgstr "" + +#. module: website_sale_cart_expire +#: model:ir.actions.server,name:website_sale_cart_expire.ir_cron_cart_expire_ir_actions_server +#: model:ir.cron,cron_name:website_sale_cart_expire.ir_cron_cart_expire +#: model:ir.cron,name:website_sale_cart_expire.ir_cron_cart_expire +msgid "Website: Expire Carts" +msgstr "" diff --git a/website_sale_cart_expire/models/__init__.py b/website_sale_cart_expire/models/__init__.py new file mode 100644 index 0000000000..5adc056c27 --- /dev/null +++ b/website_sale_cart_expire/models/__init__.py @@ -0,0 +1,3 @@ +from . import sale_order +from . import website +from . import res_config_settings diff --git a/website_sale_cart_expire/models/res_config_settings.py b/website_sale_cart_expire/models/res_config_settings.py new file mode 100644 index 0000000000..72190d947a --- /dev/null +++ b/website_sale_cart_expire/models/res_config_settings.py @@ -0,0 +1,13 @@ +# Copyright 2021 Camptocamp (http://www.camptocamp.com). +# @author Iván Todorovich +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + cart_expire_delay = fields.Float( + related="website_id.cart_expire_delay", readonly=False + ) diff --git a/website_sale_cart_expire/models/sale_order.py b/website_sale_cart_expire/models/sale_order.py new file mode 100644 index 0000000000..674cf9ce96 --- /dev/null +++ b/website_sale_cart_expire/models/sale_order.py @@ -0,0 +1,27 @@ +# Copyright 2021 Camptocamp (http://www.camptocamp.com). +# @author Iván Todorovich +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from datetime import timedelta + +from odoo import api, fields, models + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + cart_expire_date = fields.Datetime( + compute="_compute_cart_expire_date", + help="Technical field: The date this cart will automatically expire", + ) + + @api.depends("write_date", "website_id.cart_expire_delay") + def _compute_cart_expire_date(self): + for rec in self: + if rec.state in ["draft", "sent"] and rec.website_id.cart_expire_delay > 0: + # In case of draft records, use current date + from_date = rec.write_date or fields.Datetime.now() + expire_delta = timedelta(hours=rec.website_id.cart_expire_delay) + rec.cart_expire_date = from_date + expire_delta + elif rec.cart_expire_date: + rec.cart_expire_date = False diff --git a/website_sale_cart_expire/models/website.py b/website_sale_cart_expire/models/website.py new file mode 100644 index 0000000000..63b0764da2 --- /dev/null +++ b/website_sale_cart_expire/models/website.py @@ -0,0 +1,45 @@ +# Copyright 2021 Camptocamp (http://www.camptocamp.com). +# @author Iván Todorovich +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from datetime import timedelta + +from odoo import _, api, fields, models +from odoo.osv import expression + + +class Website(models.Model): + _inherit = "website" + + cart_expire_delay = fields.Float( + string="Expire Delay", + default=0.0, + help="Automatically cancel website orders after the given time.\n" + "Set to 0 to disable this feature.", + ) + + def _get_cart_expire_delay_domain(self): + self.ensure_one() + expire_date = fields.Datetime.now() - timedelta(hours=self.cart_expire_delay) + return [ + ("website_id", "=", self.id), + ("state", "in", ["draft", "sent"]), + ("write_date", "<=", expire_date), + ] + + @api.model + def _scheduler_website_expire_cart(self): + websites = self.search([("cart_expire_delay", ">", 0)]) + if not websites: + return True + # Get all carts to expire + carts_to_expire_domains = [ + website._get_cart_expire_delay_domain() for website in websites + ] + carts_to_expire = self.env["sale.order"].search( + expression.OR(carts_to_expire_domains) + ) + # Expire carts + for cart in carts_to_expire: + cart.message_post(body=_("Cart expired")) + carts_to_expire.action_cancel() diff --git a/website_sale_cart_expire/readme/CONFIGURE.rst b/website_sale_cart_expire/readme/CONFIGURE.rst new file mode 100644 index 0000000000..6780aaa700 --- /dev/null +++ b/website_sale_cart_expire/readme/CONFIGURE.rst @@ -0,0 +1 @@ +Go to Website > Settings and set a delay for Expire Carts settings. diff --git a/website_sale_cart_expire/readme/CONTRIBUTORS.rst b/website_sale_cart_expire/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..df7472d000 --- /dev/null +++ b/website_sale_cart_expire/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* `Camptocamp `_ + + * Iván Todorovich diff --git a/website_sale_cart_expire/readme/DESCRIPTION.rst b/website_sale_cart_expire/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..dff26ee3d9 --- /dev/null +++ b/website_sale_cart_expire/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +Allows to automatically cancel carts without activity after a configurable time. diff --git a/website_sale_cart_expire/static/description/icon.png b/website_sale_cart_expire/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/website_sale_cart_expire/static/description/index.html b/website_sale_cart_expire/static/description/index.html new file mode 100644 index 0000000000..4d0d63c6e4 --- /dev/null +++ b/website_sale_cart_expire/static/description/index.html @@ -0,0 +1,432 @@ + + + + + + +Website Sale Cart Expire + + + +
+

Website Sale Cart Expire

+ + +

Beta License: AGPL-3 OCA/e-commerce Translate me on Weblate Try me on Runbot

+

Allows to automatically cancel carts without activity after a configurable time.

+

Table of contents

+ +
+

Configuration

+

Go to Website > Settings and set a delay for Expire Carts settings.

+
+
+

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

+
    +
  • Camptocamp
  • +
+
+
+

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.

+

Current maintainer:

+

ivantodorovich

+

This module is part of the OCA/e-commerce project on GitHub.

+

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

+
+
+
+ + diff --git a/website_sale_cart_expire/tests/__init__.py b/website_sale_cart_expire/tests/__init__.py new file mode 100644 index 0000000000..7043f0665e --- /dev/null +++ b/website_sale_cart_expire/tests/__init__.py @@ -0,0 +1 @@ +from . import test_website_sale_cart_expire diff --git a/website_sale_cart_expire/tests/test_website_sale_cart_expire.py b/website_sale_cart_expire/tests/test_website_sale_cart_expire.py new file mode 100644 index 0000000000..7e385b2030 --- /dev/null +++ b/website_sale_cart_expire/tests/test_website_sale_cart_expire.py @@ -0,0 +1,59 @@ +# Copyright 2021 Camptocamp (http://www.camptocamp.com). +# @author Iván Todorovich +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from datetime import datetime, timedelta + +import mock + +from odoo.tests import common + + +class TestWebsiteSaleCartExpire(common.SavepointCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + # Websites + cls.website_1 = cls.env.ref("website.default_website") + cls.website_2 = cls.env.ref("website.website2") + cls.website_1.cart_expire_delay = 0.00 # hours (= disabled) + cls.website_2.cart_expire_delay = 2.00 # hours + # Orders + cls.order_1 = cls.env.ref("website_sale.website_sale_order_1") + cls.order_2 = cls.env.ref("website_sale.website_sale_order_2") + cls.order_3 = cls.env.ref("website_sale.website_sale_order_3") + cls.order_4 = cls.env.ref("website_sale.website_sale_order_4") + cls.orders = cls.order_1 + cls.order_2 + cls.order_3 + cls.order_4 + # Set to draft and assign all to website_2 + # (this also updates write_date to now()) + cls.orders.write({"state": "draft", "website_id": cls.website_2.id}) + + def test_expire_dates(self): + # Expire Date is set in the future + self.assertTrue(self.order_1.cart_expire_date) + # Changing to a website without expire delay should remove it + self.order_1.website_id = self.website_1 + self.assertFalse(self.order_1.cart_expire_date) + + def test_expire_scheduler(self): + # Case 1: We haven't reached the expire date yet + self.env["website"]._scheduler_website_expire_cart() + for order in self.orders: + self.assertEqual(order.state, "draft") + # Case 2: We have reached website 2 expire date + with mock.patch("odoo.fields.Datetime.now") as mock_now: + mock_now.return_value = datetime.now() + timedelta(hours=3) + self.env["website"]._scheduler_website_expire_cart() + for order in self.orders: + self.assertEqual(order.state, "cancel") + + def test_expire_scheduler_multi_website(self): + # For this test, we split the orders among the 2 websites + (self.order_1 + self.order_2).write({"website_id": self.website_1.id}) + with mock.patch("odoo.fields.Datetime.now") as mock_now: + mock_now.return_value = datetime.now() + timedelta(hours=3) + self.env["website"]._scheduler_website_expire_cart() + self.assertEqual(self.order_1.state, "draft", "No expire delay on website 1") + self.assertEqual(self.order_2.state, "draft", "No expire delay on website 1") + self.assertEqual(self.order_3.state, "cancel", "Should've been cancelled") + self.assertEqual(self.order_4.state, "cancel", "Should've been cancelled") diff --git a/website_sale_cart_expire/views/res_config_settings.xml b/website_sale_cart_expire/views/res_config_settings.xml new file mode 100644 index 0000000000..5919514e7b --- /dev/null +++ b/website_sale_cart_expire/views/res_config_settings.xml @@ -0,0 +1,54 @@ + + + + + res.config.settings + + +
+
+
+
+ Expire Carts + +
+ Automatically cancel carts without activity after a period of time +
+
+
+
+
+
+
+
+
+
+ + + From 7bb6bdcb4c9f0c02666f7d5bbc37cfb8c29a3c60 Mon Sep 17 00:00:00 2001 From: Ioan Galan Date: Mon, 15 Nov 2021 15:25:07 +0100 Subject: [PATCH 02/16] [MIG] website_sale_cart_expire: Migration to 14.0 --- website_sale_cart_expire/README.rst | 10 ++++---- website_sale_cart_expire/__manifest__.py | 2 +- .../i18n/website_sale_cart_expire.pot | 23 ++++++++++++++++++- .../static/description/index.html | 6 ++--- 4 files changed, 31 insertions(+), 10 deletions(-) diff --git a/website_sale_cart_expire/README.rst b/website_sale_cart_expire/README.rst index fff544d6be..cc4f618264 100644 --- a/website_sale_cart_expire/README.rst +++ b/website_sale_cart_expire/README.rst @@ -14,13 +14,13 @@ Website Sale Cart Expire :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fe--commerce-lightgray.png?logo=github - :target: https://github.com/OCA/e-commerce/tree/13.0/website_sale_cart_expire + :target: https://github.com/OCA/e-commerce/tree/14.0/website_sale_cart_expire :alt: OCA/e-commerce .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/e-commerce-13-0/e-commerce-13-0-website_sale_cart_expire + :target: https://translation.odoo-community.org/projects/e-commerce-14-0/e-commerce-14-0-website_sale_cart_expire :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/113/13.0 + :target: https://runbot.odoo-community.org/runbot/113/14.0 :alt: Try me on Runbot |badge1| |badge2| |badge3| |badge4| |badge5| @@ -43,7 +43,7 @@ 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 `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -83,6 +83,6 @@ Current `maintainer `__: |maintainer-ivantodorovich| -This module is part of the `OCA/e-commerce `_ project on GitHub. +This module is part of the `OCA/e-commerce `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/website_sale_cart_expire/__manifest__.py b/website_sale_cart_expire/__manifest__.py index d5cf809318..fa3bd4e1fc 100644 --- a/website_sale_cart_expire/__manifest__.py +++ b/website_sale_cart_expire/__manifest__.py @@ -5,7 +5,7 @@ { "name": "Website Sale Cart Expire", "summary": "Expire abandoned carts", - "version": "13.0.1.0.0", + "version": "14.0.1.0.0", "author": "Camptocamp, Odoo Community Association (OCA)", "maintainers": ["ivantodorovich"], "website": "https://github.com/OCA/e-commerce", diff --git a/website_sale_cart_expire/i18n/website_sale_cart_expire.pot b/website_sale_cart_expire/i18n/website_sale_cart_expire.pot index f2fa8d6ef8..024839b5de 100644 --- a/website_sale_cart_expire/i18n/website_sale_cart_expire.pot +++ b/website_sale_cart_expire/i18n/website_sale_cart_expire.pot @@ -4,7 +4,7 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 13.0\n" +"Project-Id-Version: Odoo Server 14.0\n" "Report-Msgid-Bugs-To: \n" "Last-Translator: \n" "Language-Team: \n" @@ -64,12 +64,33 @@ msgstr "" msgid "Config Settings" msgstr "" +#. module: website_sale_cart_expire +#: model:ir.model.fields,field_description:website_sale_cart_expire.field_res_config_settings__display_name +#: model:ir.model.fields,field_description:website_sale_cart_expire.field_sale_order__display_name +#: model:ir.model.fields,field_description:website_sale_cart_expire.field_website__display_name +msgid "Display Name" +msgstr "" + #. module: website_sale_cart_expire #: model:ir.model.fields,field_description:website_sale_cart_expire.field_res_config_settings__cart_expire_delay #: model:ir.model.fields,field_description:website_sale_cart_expire.field_website__cart_expire_delay msgid "Expire Delay" msgstr "" +#. module: website_sale_cart_expire +#: model:ir.model.fields,field_description:website_sale_cart_expire.field_res_config_settings__id +#: model:ir.model.fields,field_description:website_sale_cart_expire.field_sale_order__id +#: model:ir.model.fields,field_description:website_sale_cart_expire.field_website__id +msgid "ID" +msgstr "" + +#. module: website_sale_cart_expire +#: model:ir.model.fields,field_description:website_sale_cart_expire.field_res_config_settings____last_update +#: model:ir.model.fields,field_description:website_sale_cart_expire.field_sale_order____last_update +#: model:ir.model.fields,field_description:website_sale_cart_expire.field_website____last_update +msgid "Last Modified on" +msgstr "" + #. module: website_sale_cart_expire #: model:ir.model,name:website_sale_cart_expire.model_sale_order msgid "Sales Order" diff --git a/website_sale_cart_expire/static/description/index.html b/website_sale_cart_expire/static/description/index.html index 4d0d63c6e4..d1bea70974 100644 --- a/website_sale_cart_expire/static/description/index.html +++ b/website_sale_cart_expire/static/description/index.html @@ -367,7 +367,7 @@

Website Sale Cart Expire

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: AGPL-3 OCA/e-commerce Translate me on Weblate Try me on Runbot

+

Beta License: AGPL-3 OCA/e-commerce Translate me on Weblate Try me on Runbot

Allows to automatically cancel carts without activity after a configurable time.

Table of contents

@@ -391,7 +391,7 @@

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.

+feedback.

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

@@ -423,7 +423,7 @@

Maintainers

promote its widespread use.

Current maintainer:

ivantodorovich

-

This module is part of the OCA/e-commerce project on GitHub.

+

This module is part of the OCA/e-commerce project on GitHub.

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

From f971516b9bae8151e56501021560f9b9c8972405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A0n=20Todorovich?= Date: Fri, 10 Jun 2022 13:29:47 -0300 Subject: [PATCH 03/16] [MIG] website_sale_cart_expire: Migration to 15.0 --- website_sale_cart_expire/__manifest__.py | 4 ++-- website_sale_cart_expire/data/ir_cron.xml | 22 +++++++++---------- website_sale_cart_expire/models/__init__.py | 2 +- website_sale_cart_expire/models/website.py | 9 +++----- .../tests/test_website_sale_cart_expire.py | 12 +++++----- .../views/res_config_settings.xml | 2 +- 6 files changed, 22 insertions(+), 29 deletions(-) diff --git a/website_sale_cart_expire/__manifest__.py b/website_sale_cart_expire/__manifest__.py index fa3bd4e1fc..072e84ae4f 100644 --- a/website_sale_cart_expire/__manifest__.py +++ b/website_sale_cart_expire/__manifest__.py @@ -4,8 +4,8 @@ { "name": "Website Sale Cart Expire", - "summary": "Expire abandoned carts", - "version": "14.0.1.0.0", + "summary": "Cancel carts without activity after a configurable time", + "version": "15.0.1.0.0", "author": "Camptocamp, Odoo Community Association (OCA)", "maintainers": ["ivantodorovich"], "website": "https://github.com/OCA/e-commerce", diff --git a/website_sale_cart_expire/data/ir_cron.xml b/website_sale_cart_expire/data/ir_cron.xml index 9cf889b7b5..f78506926c 100644 --- a/website_sale_cart_expire/data/ir_cron.xml +++ b/website_sale_cart_expire/data/ir_cron.xml @@ -4,16 +4,14 @@ @author Iván Todorovich License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> - - - - Website: Expire Carts - - code - model._scheduler_website_expire_cart() - 5 - minutes - -1 - - + + + Website: Expire Carts + + code + model._scheduler_website_expire_cart() + 5 + minutes + -1 + diff --git a/website_sale_cart_expire/models/__init__.py b/website_sale_cart_expire/models/__init__.py index 5adc056c27..0cf62fc1fa 100644 --- a/website_sale_cart_expire/models/__init__.py +++ b/website_sale_cart_expire/models/__init__.py @@ -1,3 +1,3 @@ +from . import res_config_settings from . import sale_order from . import website -from . import res_config_settings diff --git a/website_sale_cart_expire/models/website.py b/website_sale_cart_expire/models/website.py index 63b0764da2..a5a54a9039 100644 --- a/website_sale_cart_expire/models/website.py +++ b/website_sale_cart_expire/models/website.py @@ -32,14 +32,11 @@ def _scheduler_website_expire_cart(self): websites = self.search([("cart_expire_delay", ">", 0)]) if not websites: return True - # Get all carts to expire - carts_to_expire_domains = [ - website._get_cart_expire_delay_domain() for website in websites - ] carts_to_expire = self.env["sale.order"].search( - expression.OR(carts_to_expire_domains) + expression.OR( + [website._get_cart_expire_delay_domain() for website in websites] + ) ) - # Expire carts for cart in carts_to_expire: cart.message_post(body=_("Cart expired")) carts_to_expire.action_cancel() diff --git a/website_sale_cart_expire/tests/test_website_sale_cart_expire.py b/website_sale_cart_expire/tests/test_website_sale_cart_expire.py index 7e385b2030..4d127b4025 100644 --- a/website_sale_cart_expire/tests/test_website_sale_cart_expire.py +++ b/website_sale_cart_expire/tests/test_website_sale_cart_expire.py @@ -4,12 +4,12 @@ from datetime import datetime, timedelta -import mock +from freezegun import freeze_time -from odoo.tests import common +from odoo.tests import TransactionCase -class TestWebsiteSaleCartExpire(common.SavepointCase): +class TestWebsiteSaleCartExpire(TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() @@ -41,8 +41,7 @@ def test_expire_scheduler(self): for order in self.orders: self.assertEqual(order.state, "draft") # Case 2: We have reached website 2 expire date - with mock.patch("odoo.fields.Datetime.now") as mock_now: - mock_now.return_value = datetime.now() + timedelta(hours=3) + with freeze_time(datetime.now() + timedelta(hours=3)): self.env["website"]._scheduler_website_expire_cart() for order in self.orders: self.assertEqual(order.state, "cancel") @@ -50,8 +49,7 @@ def test_expire_scheduler(self): def test_expire_scheduler_multi_website(self): # For this test, we split the orders among the 2 websites (self.order_1 + self.order_2).write({"website_id": self.website_1.id}) - with mock.patch("odoo.fields.Datetime.now") as mock_now: - mock_now.return_value = datetime.now() + timedelta(hours=3) + with freeze_time(datetime.now() + timedelta(hours=3)): self.env["website"]._scheduler_website_expire_cart() self.assertEqual(self.order_1.state, "draft", "No expire delay on website 1") self.assertEqual(self.order_2.state, "draft", "No expire delay on website 1") diff --git a/website_sale_cart_expire/views/res_config_settings.xml b/website_sale_cart_expire/views/res_config_settings.xml index 5919514e7b..8570634253 100644 --- a/website_sale_cart_expire/views/res_config_settings.xml +++ b/website_sale_cart_expire/views/res_config_settings.xml @@ -42,7 +42,7 @@ name="cart_expire_delay" widget="float_time" /> - hours + hours. From 07470569ba4c34e91ec7cd2ba655c6f601d62df7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A0n=20Todorovich?= Date: Fri, 15 Jul 2022 12:18:55 -0300 Subject: [PATCH 04/16] [FIX] website_sale_cart_expire: Don't cancel sent quotations In a typical website flow, quotations are moved to sent state when a payment by Wire transfer is chosen. In this scenario, the quotation shouldn't be automatically cancelled. It's up to the salesman to determine when a sent quotation is expired, as technically speaking this isn't just a cart anymore. --- website_sale_cart_expire/models/website.py | 2 +- .../tests/test_website_sale_cart_expire.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/website_sale_cart_expire/models/website.py b/website_sale_cart_expire/models/website.py index a5a54a9039..8db9211c49 100644 --- a/website_sale_cart_expire/models/website.py +++ b/website_sale_cart_expire/models/website.py @@ -23,7 +23,7 @@ def _get_cart_expire_delay_domain(self): expire_date = fields.Datetime.now() - timedelta(hours=self.cart_expire_delay) return [ ("website_id", "=", self.id), - ("state", "in", ["draft", "sent"]), + ("state", "=", "draft"), ("write_date", "<=", expire_date), ] diff --git a/website_sale_cart_expire/tests/test_website_sale_cart_expire.py b/website_sale_cart_expire/tests/test_website_sale_cart_expire.py index 4d127b4025..2e0af62afc 100644 --- a/website_sale_cart_expire/tests/test_website_sale_cart_expire.py +++ b/website_sale_cart_expire/tests/test_website_sale_cart_expire.py @@ -55,3 +55,14 @@ def test_expire_scheduler_multi_website(self): self.assertEqual(self.order_2.state, "draft", "No expire delay on website 1") self.assertEqual(self.order_3.state, "cancel", "Should've been cancelled") self.assertEqual(self.order_4.state, "cancel", "Should've been cancelled") + + @freeze_time(datetime.now() + timedelta(hours=3)) + def test_expire_scheduler_ignore_sent_quotation(self): + """Test that sent quotations aren't cancelled + + Quotations can be sent manually or automatically when using + wire transfer as payment. They shouldn't be cancelled. + """ + self.order_1.action_quotation_sent() + self.env["website"]._scheduler_website_expire_cart() + self.assertNotEqual(self.order_1.state, "cancel") From b457a68b8edddd2dd0f15097983200b45f139f39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A0n=20Todorovich?= Date: Fri, 15 Jul 2022 12:59:46 -0300 Subject: [PATCH 05/16] [FIX] website_sale_cart_expire: Don't cancel carts in payment --- website_sale_cart_expire/models/sale_order.py | 24 ++++++++++- website_sale_cart_expire/models/website.py | 14 ++++-- .../tests/test_website_sale_cart_expire.py | 43 +++++++++++++++++++ 3 files changed, 76 insertions(+), 5 deletions(-) diff --git a/website_sale_cart_expire/models/sale_order.py b/website_sale_cart_expire/models/sale_order.py index 674cf9ce96..3cdf4b53e6 100644 --- a/website_sale_cart_expire/models/sale_order.py +++ b/website_sale_cart_expire/models/sale_order.py @@ -15,12 +15,32 @@ class SaleOrder(models.Model): help="Technical field: The date this cart will automatically expire", ) - @api.depends("write_date", "website_id.cart_expire_delay") + def _should_bypass_cart_expiration(self): + """Hook method to prevent a cart from expiring""" + self.ensure_one() + # We don't want to cancel carts that are already in payment. + return any( + tx.state in ["pending", "authorized", "done"] for tx in self.transaction_ids + ) + + @api.depends( + "write_date", + "website_id.cart_expire_delay", + "transaction_ids.last_state_change", + ) def _compute_cart_expire_date(self): for rec in self: - if rec.state in ["draft", "sent"] and rec.website_id.cart_expire_delay > 0: + if ( + rec.state == "draft" + and rec.website_id.cart_expire_delay + and not rec._should_bypass_cart_expiration() + ): # In case of draft records, use current date from_date = rec.write_date or fields.Datetime.now() + # In case or records with transactions, consider last tx date + if rec.transaction_ids: + last_tx_date = max(rec.transaction_ids.mapped("last_state_change")) + from_date = max(from_date, last_tx_date) expire_delta = timedelta(hours=rec.website_id.cart_expire_delay) rec.cart_expire_date = from_date + expire_delta elif rec.cart_expire_date: diff --git a/website_sale_cart_expire/models/website.py b/website_sale_cart_expire/models/website.py index 8db9211c49..411eef2890 100644 --- a/website_sale_cart_expire/models/website.py +++ b/website_sale_cart_expire/models/website.py @@ -25,6 +25,11 @@ def _get_cart_expire_delay_domain(self): ("website_id", "=", self.id), ("state", "=", "draft"), ("write_date", "<=", expire_date), + # We don't want to cancel carts that are already in payment. + "|", + ("transaction_ids", "=", False), + "!", + ("transaction_ids.state", "in", ["pending", "authorized", "done"]), ] @api.model @@ -32,11 +37,14 @@ def _scheduler_website_expire_cart(self): websites = self.search([("cart_expire_delay", ">", 0)]) if not websites: return True - carts_to_expire = self.env["sale.order"].search( + carts = self.env["sale.order"].search( expression.OR( [website._get_cart_expire_delay_domain() for website in websites] ) ) - for cart in carts_to_expire: + now = fields.Datetime.now() + for cart in carts: + if not cart.cart_expire_date or cart.cart_expire_date > now: + continue cart.message_post(body=_("Cart expired")) - carts_to_expire.action_cancel() + cart.action_cancel() diff --git a/website_sale_cart_expire/tests/test_website_sale_cart_expire.py b/website_sale_cart_expire/tests/test_website_sale_cart_expire.py index 2e0af62afc..fb31891e34 100644 --- a/website_sale_cart_expire/tests/test_website_sale_cart_expire.py +++ b/website_sale_cart_expire/tests/test_website_sale_cart_expire.py @@ -6,6 +6,7 @@ from freezegun import freeze_time +from odoo import fields from odoo.tests import TransactionCase @@ -13,6 +14,7 @@ class TestWebsiteSaleCartExpire(TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() + cls.tx_counter = 0 # Websites cls.website_1 = cls.env.ref("website.default_website") cls.website_2 = cls.env.ref("website.website2") @@ -28,6 +30,21 @@ def setUpClass(cls): # (this also updates write_date to now()) cls.orders.write({"state": "draft", "website_id": cls.website_2.id}) + def _create_payment_transaction(self, order): + self.tx_counter += 1 + acquirer = self.env.ref("payment.payment_acquirer_test") + return self.env["payment.transaction"].create( + { + "acquirer_id": acquirer.id, + "reference": f"{order.name}-{self.tx_counter}", + "amount": order.amount_total, + "currency_id": order.currency_id.id, + "partner_id": order.partner_id.id, + "operation": "online_direct", + "sale_order_ids": [fields.Command.set([order.id])], + } + ) + def test_expire_dates(self): # Expire Date is set in the future self.assertTrue(self.order_1.cart_expire_date) @@ -66,3 +83,29 @@ def test_expire_scheduler_ignore_sent_quotation(self): self.order_1.action_quotation_sent() self.env["website"]._scheduler_website_expire_cart() self.assertNotEqual(self.order_1.state, "cancel") + + @freeze_time(datetime.now() + timedelta(hours=3)) + def test_expire_scheduler_ignore_in_payment(self): + """Carts with a payment transaction in progress shouldn't expire""" + self._create_payment_transaction(self.order_1) + tx_2 = self._create_payment_transaction(self.order_2) + tx_2._set_pending() + tx_3 = self._create_payment_transaction(self.order_3) + tx_3._set_canceled() + tx_4 = self._create_payment_transaction(self.order_4) + tx_4._set_error("Something went wrong") + # Carts with transactions in progress are not canceled + # Even those with 'draft' or 'error' transactions, because + # the timer is reseted whenever a tx state changes. + self.env["website"]._scheduler_website_expire_cart() + self.assertNotEqual(self.order_1.state, "cancel") + self.assertNotEqual(self.order_2.state, "cancel") + self.assertNotEqual(self.order_3.state, "cancel") + self.assertNotEqual(self.order_4.state, "cancel") + # In case of error, another transaction can be initialized + # However for order_1, no more activity was detected, so it's canceled + with freeze_time(datetime.now() + timedelta(hours=3)): + self._create_payment_transaction(self.order_4) + self.env["website"]._scheduler_website_expire_cart() + self.assertEqual(self.order_1.state, "cancel") + self.assertNotEqual(self.order_4.state, "cancel") From a04c0ea7c99aa84f784ff770af8dd4f9ead5748d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A0n=20Todorovich?= Date: Fri, 15 Jul 2022 14:40:57 -0300 Subject: [PATCH 06/16] [FIX] Use savepoint and autocommit to avoid possible cron crash --- website_sale_cart_expire/__manifest__.py | 2 +- website_sale_cart_expire/data/ir_cron.xml | 2 +- .../migrations/15.0.1.1.0/post-migrate.py | 17 +++++++++++++++++ website_sale_cart_expire/models/website.py | 17 ++++++++++++++--- 4 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 website_sale_cart_expire/migrations/15.0.1.1.0/post-migrate.py diff --git a/website_sale_cart_expire/__manifest__.py b/website_sale_cart_expire/__manifest__.py index 072e84ae4f..8d53475bce 100644 --- a/website_sale_cart_expire/__manifest__.py +++ b/website_sale_cart_expire/__manifest__.py @@ -5,7 +5,7 @@ { "name": "Website Sale Cart Expire", "summary": "Cancel carts without activity after a configurable time", - "version": "15.0.1.0.0", + "version": "15.0.1.1.0", "author": "Camptocamp, Odoo Community Association (OCA)", "maintainers": ["ivantodorovich"], "website": "https://github.com/OCA/e-commerce", diff --git a/website_sale_cart_expire/data/ir_cron.xml b/website_sale_cart_expire/data/ir_cron.xml index f78506926c..582b8f6338 100644 --- a/website_sale_cart_expire/data/ir_cron.xml +++ b/website_sale_cart_expire/data/ir_cron.xml @@ -9,7 +9,7 @@ Website: Expire Carts code - model._scheduler_website_expire_cart() + model._scheduler_website_expire_cart(autocommit=True) 5 minutes -1 diff --git a/website_sale_cart_expire/migrations/15.0.1.1.0/post-migrate.py b/website_sale_cart_expire/migrations/15.0.1.1.0/post-migrate.py new file mode 100644 index 0000000000..cb04ec548c --- /dev/null +++ b/website_sale_cart_expire/migrations/15.0.1.1.0/post-migrate.py @@ -0,0 +1,17 @@ +# Copyright 2022 Camptocamp SA (https://www.camptocamp.com). +# @author Iván Todorovich +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import SUPERUSER_ID, api + + +def migrate(cr, version): + if not version: + return + env = api.Environment(cr, SUPERUSER_ID, {}) + cron = env.ref( + "website_sale_cart_expire.ir_cron_cart_expire", + raise_if_not_found=False, + ) + if cron: + cron.code = "model._scheduler_website_expire_cart(autocommit=True)" diff --git a/website_sale_cart_expire/models/website.py b/website_sale_cart_expire/models/website.py index 411eef2890..14e2fc1bf7 100644 --- a/website_sale_cart_expire/models/website.py +++ b/website_sale_cart_expire/models/website.py @@ -2,11 +2,14 @@ # @author Iván Todorovich # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging from datetime import timedelta from odoo import _, api, fields, models from odoo.osv import expression +_logger = logging.getLogger(__name__) + class Website(models.Model): _inherit = "website" @@ -33,7 +36,7 @@ def _get_cart_expire_delay_domain(self): ] @api.model - def _scheduler_website_expire_cart(self): + def _scheduler_website_expire_cart(self, autocommit=False): websites = self.search([("cart_expire_delay", ">", 0)]) if not websites: return True @@ -46,5 +49,13 @@ def _scheduler_website_expire_cart(self): for cart in carts: if not cart.cart_expire_date or cart.cart_expire_date > now: continue - cart.message_post(body=_("Cart expired")) - cart.action_cancel() + try: + with self.env.cr.savepoint(): + cart.message_post(body=_("Cart expired")) + cart.action_cancel() + except Exception as e: + _logger.exception("Unable to cancel expired cart %s: %s", cart, e) + else: + if autocommit: + self.env.cr.commit() # pylint: disable=invalid-commit + return True From f3b53fd5f66787e5e0b9b052647d5bada8ee8942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A0n=20Todorovich?= Date: Fri, 15 Jul 2022 17:16:06 -0300 Subject: [PATCH 07/16] [IMP] website_sale_cart_expire: Display the cart expiration timer on website --- website_sale_cart_expire/README.rst | 13 +- website_sale_cart_expire/__init__.py | 1 + website_sale_cart_expire/__manifest__.py | 11 +- .../controllers/__init__.py | 1 + website_sale_cart_expire/controllers/main.py | 22 ++++ .../i18n/website_sale_cart_expire.pot | 37 ++---- website_sale_cart_expire/readme/CONFIGURE.rst | 3 + .../static/description/index.html | 8 +- .../static/src/js/website_sale_cart_expire.js | 119 ++++++++++++++++++ website_sale_cart_expire/views/templates.xml | 25 ++++ 10 files changed, 202 insertions(+), 38 deletions(-) create mode 100644 website_sale_cart_expire/controllers/__init__.py create mode 100644 website_sale_cart_expire/controllers/main.py create mode 100644 website_sale_cart_expire/static/src/js/website_sale_cart_expire.js create mode 100644 website_sale_cart_expire/views/templates.xml diff --git a/website_sale_cart_expire/README.rst b/website_sale_cart_expire/README.rst index cc4f618264..8a84ecd16f 100644 --- a/website_sale_cart_expire/README.rst +++ b/website_sale_cart_expire/README.rst @@ -14,13 +14,13 @@ Website Sale Cart Expire :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fe--commerce-lightgray.png?logo=github - :target: https://github.com/OCA/e-commerce/tree/14.0/website_sale_cart_expire + :target: https://github.com/OCA/e-commerce/tree/15.0/website_sale_cart_expire :alt: OCA/e-commerce .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/e-commerce-14-0/e-commerce-14-0-website_sale_cart_expire + :target: https://translation.odoo-community.org/projects/e-commerce-15-0/e-commerce-15-0-website_sale_cart_expire :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/113/14.0 + :target: https://runbot.odoo-community.org/runbot/113/15.0 :alt: Try me on Runbot |badge1| |badge2| |badge3| |badge4| |badge5| @@ -37,13 +37,16 @@ Configuration Go to Website > Settings and set a delay for Expire Carts settings. +A cart expiration timer can be displayed on the website by enabling the +Cart Expiration Timer setting in the Customize menu. + 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 `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -83,6 +86,6 @@ Current `maintainer `__: |maintainer-ivantodorovich| -This module is part of the `OCA/e-commerce `_ project on GitHub. +This module is part of the `OCA/e-commerce `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/website_sale_cart_expire/__init__.py b/website_sale_cart_expire/__init__.py index 0650744f6b..91c5580fed 100644 --- a/website_sale_cart_expire/__init__.py +++ b/website_sale_cart_expire/__init__.py @@ -1 +1,2 @@ +from . import controllers from . import models diff --git a/website_sale_cart_expire/__manifest__.py b/website_sale_cart_expire/__manifest__.py index 8d53475bce..e870716265 100644 --- a/website_sale_cart_expire/__manifest__.py +++ b/website_sale_cart_expire/__manifest__.py @@ -12,5 +12,14 @@ "license": "AGPL-3", "category": "Website", "depends": ["website_sale"], - "data": ["data/ir_cron.xml", "views/res_config_settings.xml"], + "data": [ + "data/ir_cron.xml", + "views/res_config_settings.xml", + "views/templates.xml", + ], + "assets": { + "web.assets_frontend": [ + "website_sale_cart_expire/static/src/js/website_sale_cart_expire.js", + ], + }, } diff --git a/website_sale_cart_expire/controllers/__init__.py b/website_sale_cart_expire/controllers/__init__.py new file mode 100644 index 0000000000..12a7e529b6 --- /dev/null +++ b/website_sale_cart_expire/controllers/__init__.py @@ -0,0 +1 @@ +from . import main diff --git a/website_sale_cart_expire/controllers/main.py b/website_sale_cart_expire/controllers/main.py new file mode 100644 index 0000000000..48c4d594d4 --- /dev/null +++ b/website_sale_cart_expire/controllers/main.py @@ -0,0 +1,22 @@ +# Copyright 2022 Camptocamp SA (https://www.camptocamp.com). +# @author Iván Todorovich +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import http +from odoo.http import request + +from odoo.addons.website_sale.controllers.main import WebsiteSale + + +class WebsiteSaleCartExpire(WebsiteSale): + @http.route( + ["/shop/cart/get_expire_date"], + type="json", + auth="public", + methods=["POST"], + website=True, + csrf=False, + ) + def get_expire_date(self, **kw): + order = request.website.sale_get_order() + return order.cart_expire_date diff --git a/website_sale_cart_expire/i18n/website_sale_cart_expire.pot b/website_sale_cart_expire/i18n/website_sale_cart_expire.pot index 024839b5de..e2f7c43c60 100644 --- a/website_sale_cart_expire/i18n/website_sale_cart_expire.pot +++ b/website_sale_cart_expire/i18n/website_sale_cart_expire.pot @@ -4,7 +4,7 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 14.0\n" +"Project-Id-Version: Odoo Server 15.0\n" "Report-Msgid-Bugs-To: \n" "Last-Translator: \n" "Language-Team: \n" @@ -21,8 +21,8 @@ msgid "" msgstr "" #. module: website_sale_cart_expire -#: model_terms:ir.ui.view,arch_db:website_sale_cart_expire.res_config_settings_view_form -msgid "hours" +#: model:ir.model,name:website_sale_cart_expire.model_sale_order +msgid "Allowing sale_order_type to work with website_sale." msgstr "" #. module: website_sale_cart_expire @@ -64,38 +64,12 @@ msgstr "" msgid "Config Settings" msgstr "" -#. module: website_sale_cart_expire -#: model:ir.model.fields,field_description:website_sale_cart_expire.field_res_config_settings__display_name -#: model:ir.model.fields,field_description:website_sale_cart_expire.field_sale_order__display_name -#: model:ir.model.fields,field_description:website_sale_cart_expire.field_website__display_name -msgid "Display Name" -msgstr "" - #. module: website_sale_cart_expire #: model:ir.model.fields,field_description:website_sale_cart_expire.field_res_config_settings__cart_expire_delay #: model:ir.model.fields,field_description:website_sale_cart_expire.field_website__cart_expire_delay msgid "Expire Delay" msgstr "" -#. module: website_sale_cart_expire -#: model:ir.model.fields,field_description:website_sale_cart_expire.field_res_config_settings__id -#: model:ir.model.fields,field_description:website_sale_cart_expire.field_sale_order__id -#: model:ir.model.fields,field_description:website_sale_cart_expire.field_website__id -msgid "ID" -msgstr "" - -#. module: website_sale_cart_expire -#: model:ir.model.fields,field_description:website_sale_cart_expire.field_res_config_settings____last_update -#: model:ir.model.fields,field_description:website_sale_cart_expire.field_sale_order____last_update -#: model:ir.model.fields,field_description:website_sale_cart_expire.field_website____last_update -msgid "Last Modified on" -msgstr "" - -#. module: website_sale_cart_expire -#: model:ir.model,name:website_sale_cart_expire.model_sale_order -msgid "Sales Order" -msgstr "" - #. module: website_sale_cart_expire #: model:ir.model.fields,help:website_sale_cart_expire.field_sale_order__cart_expire_date msgid "Technical field: The date this cart will automatically expire" @@ -112,3 +86,8 @@ msgstr "" #: model:ir.cron,name:website_sale_cart_expire.ir_cron_cart_expire msgid "Website: Expire Carts" msgstr "" + +#. module: website_sale_cart_expire +#: model_terms:ir.ui.view,arch_db:website_sale_cart_expire.res_config_settings_view_form +msgid "hours." +msgstr "" diff --git a/website_sale_cart_expire/readme/CONFIGURE.rst b/website_sale_cart_expire/readme/CONFIGURE.rst index 6780aaa700..8a18e47bc3 100644 --- a/website_sale_cart_expire/readme/CONFIGURE.rst +++ b/website_sale_cart_expire/readme/CONFIGURE.rst @@ -1 +1,4 @@ Go to Website > Settings and set a delay for Expire Carts settings. + +A cart expiration timer can be displayed on the website by enabling the +Cart Expiration Timer setting in the Customize menu. diff --git a/website_sale_cart_expire/static/description/index.html b/website_sale_cart_expire/static/description/index.html index d1bea70974..f04acbf7b0 100644 --- a/website_sale_cart_expire/static/description/index.html +++ b/website_sale_cart_expire/static/description/index.html @@ -367,7 +367,7 @@

Website Sale Cart Expire

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: AGPL-3 OCA/e-commerce Translate me on Weblate Try me on Runbot

+

Beta License: AGPL-3 OCA/e-commerce Translate me on Weblate Try me on Runbot

Allows to automatically cancel carts without activity after a configurable time.

Table of contents

@@ -385,13 +385,15 @@

Website Sale Cart Expire

Configuration

Go to Website > Settings and set a delay for Expire Carts settings.

+

A cart expiration timer can be displayed on the website by enabling the +Cart Expiration Timer setting in the Customize menu.

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.

+feedback.

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

@@ -423,7 +425,7 @@

Maintainers

promote its widespread use.

Current maintainer:

ivantodorovich

-

This module is part of the OCA/e-commerce project on GitHub.

+

This module is part of the OCA/e-commerce project on GitHub.

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

diff --git a/website_sale_cart_expire/static/src/js/website_sale_cart_expire.js b/website_sale_cart_expire/static/src/js/website_sale_cart_expire.js new file mode 100644 index 0000000000..a78b39bffc --- /dev/null +++ b/website_sale_cart_expire/static/src/js/website_sale_cart_expire.js @@ -0,0 +1,119 @@ +odoo.define("website_sale_cart_expire", (require) => { + "use strict"; + + const publicWidget = require("web.public.widget"); + const time = require("web.time"); + + /** + * Cart Expire Timer widget. + * + * Displays a countdown timer for the cart expiration date. + */ + publicWidget.registry.WebsiteSaleCartExpireTimer = publicWidget.Widget.extend({ + selector: ".my_cart_expiration", + + /** + * @override + */ + start: async function () { + await this._super.apply(this, arguments); + this._setExpirationDate(this.$el.data("order-expire-date")); + const remainingMs = this._getRemainingMs(); + if (remainingMs > 0) { + this._renderTimer(remainingMs); + this._startTimer(); + } + // Attempts to hook into the cart quantity widget to update the expiration date + // whenever it changes. + this.$el.siblings(".my_cart_quantity").on( + "DOMSubtreeModified", + _.debounce(() => this._refreshExpirationDate(), 250) + ); + }, + /** + * @override + */ + destroy: function () { + this.$el.remove(); + this._stopTimer(); + return this._super.apply(this, arguments); + }, + /** + * Sets the timer target date. + * + * @param {String|Date} expireDate + */ + _setExpirationDate: function (expireDate) { + if (typeof expireDate === "string") { + expireDate = time.str_to_datetime(expireDate); + } + this.expireDate = expireDate ? moment(expireDate) : false; + }, + /** + * @returns {Number} + */ + _getRemainingMs: function () { + return this.expireDate ? this.expireDate.diff(moment()) : 0; + }, + /** + * Starts the timer. + */ + _startTimer: function () { + this._stopTimer(); + this.timer = setInterval(this._refreshTimer.bind(this), 1000); + }, + /** + * Stops the timer. + */ + _stopTimer: function () { + if (this.timer) { + clearInterval(this.timer); + } + }, + /** + * Refreshes the countdown timer. + * It destroys itself if the countdown reaches 0. + */ + _refreshTimer: function () { + const remainingMs = this._getRemainingMs(); + this._renderTimer(remainingMs); + if (remainingMs <= 0) { + this._stopTimer(); + this._refreshExpirationDate(); + } + }, + /** + * Updates the remaining time on the dom + */ + _renderTimer: function (remainingMs) { + const remainingMsRounded = Math.ceil(remainingMs / 1000) * 1000; + // Don't show the timer if remaining time is less than 1 hour + if (remainingMsRounded >= 3600000) { + return this.$el.hide(); + } + this.$el.show(); + // Format the countdown timer + const remainingStr = moment.utc(remainingMsRounded).format("mm:ss"); + if (remainingStr !== this.$el.text()) { + this.$el.text(remainingStr); + } + }, + /** + * Updates the expiration date by reading from backend + */ + _refreshExpirationDate: async function () { + const expireDate = await this._rpc({route: "/shop/cart/get_expire_date"}); + this._setExpirationDate(expireDate); + const remainingMs = this._getRemainingMs(); + if (remainingMs > 0) { + this._renderTimer(remainingMs); + this._startTimer(); + this.$el.show(); + } else { + this._stopTimer(); + this.$el.hide(); + } + return this.expireDate; + }, + }); +}); diff --git a/website_sale_cart_expire/views/templates.xml b/website_sale_cart_expire/views/templates.xml new file mode 100644 index 0000000000..8ec4d59d73 --- /dev/null +++ b/website_sale_cart_expire/views/templates.xml @@ -0,0 +1,25 @@ + + + + + + + From a46ffeb9a9cb6e9513236a943bd5e3e1bd5a9534 Mon Sep 17 00:00:00 2001 From: "A. Hochuli" Date: Mon, 24 Apr 2023 18:54:41 +0200 Subject: [PATCH 08/16] [MIG] website_sale_cart_expire: Migration to 16.0 [Fix] Use new payment.payment_provider_demo for test cases [Fix] Change acquirer_id to provider_id for test cases --- website_sale_cart_expire/README.rst | 10 +++++----- website_sale_cart_expire/__manifest__.py | 2 +- .../i18n/website_sale_cart_expire.pot | 14 +++++++------- .../static/description/index.html | 6 +++--- .../tests/test_website_sale_cart_expire.py | 4 ++-- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/website_sale_cart_expire/README.rst b/website_sale_cart_expire/README.rst index 8a84ecd16f..86f41589bc 100644 --- a/website_sale_cart_expire/README.rst +++ b/website_sale_cart_expire/README.rst @@ -14,13 +14,13 @@ Website Sale Cart Expire :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fe--commerce-lightgray.png?logo=github - :target: https://github.com/OCA/e-commerce/tree/15.0/website_sale_cart_expire + :target: https://github.com/OCA/e-commerce/tree/16.0/website_sale_cart_expire :alt: OCA/e-commerce .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/e-commerce-15-0/e-commerce-15-0-website_sale_cart_expire + :target: https://translation.odoo-community.org/projects/e-commerce-16-0/e-commerce-16-0-website_sale_cart_expire :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/113/15.0 + :target: https://runbot.odoo-community.org/runbot/113/16.0 :alt: Try me on Runbot |badge1| |badge2| |badge3| |badge4| |badge5| @@ -46,7 +46,7 @@ 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 `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -86,6 +86,6 @@ Current `maintainer `__: |maintainer-ivantodorovich| -This module is part of the `OCA/e-commerce `_ project on GitHub. +This module is part of the `OCA/e-commerce `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/website_sale_cart_expire/__manifest__.py b/website_sale_cart_expire/__manifest__.py index e870716265..5bc5b64239 100644 --- a/website_sale_cart_expire/__manifest__.py +++ b/website_sale_cart_expire/__manifest__.py @@ -5,7 +5,7 @@ { "name": "Website Sale Cart Expire", "summary": "Cancel carts without activity after a configurable time", - "version": "15.0.1.1.0", + "version": "16.0.1.0.0", "author": "Camptocamp, Odoo Community Association (OCA)", "maintainers": ["ivantodorovich"], "website": "https://github.com/OCA/e-commerce", diff --git a/website_sale_cart_expire/i18n/website_sale_cart_expire.pot b/website_sale_cart_expire/i18n/website_sale_cart_expire.pot index e2f7c43c60..0eff313adb 100644 --- a/website_sale_cart_expire/i18n/website_sale_cart_expire.pot +++ b/website_sale_cart_expire/i18n/website_sale_cart_expire.pot @@ -4,7 +4,7 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 15.0\n" +"Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" "Last-Translator: \n" "Language-Team: \n" @@ -20,11 +20,6 @@ msgid "" " " msgstr "" -#. module: website_sale_cart_expire -#: model:ir.model,name:website_sale_cart_expire.model_sale_order -msgid "Allowing sale_order_type to work with website_sale." -msgstr "" - #. module: website_sale_cart_expire #: model_terms:ir.ui.view,arch_db:website_sale_cart_expire.res_config_settings_view_form msgid "Automatically cancel carts without activity after a period of time" @@ -44,6 +39,7 @@ msgid "Cart Expire Date" msgstr "" #. module: website_sale_cart_expire +#. odoo-python #: code:addons/website_sale_cart_expire/models/website.py:0 #, python-format msgid "Cart expired" @@ -70,6 +66,11 @@ msgstr "" msgid "Expire Delay" msgstr "" +#. module: website_sale_cart_expire +#: model:ir.model,name:website_sale_cart_expire.model_sale_order +msgid "Sales Order" +msgstr "" + #. module: website_sale_cart_expire #: model:ir.model.fields,help:website_sale_cart_expire.field_sale_order__cart_expire_date msgid "Technical field: The date this cart will automatically expire" @@ -83,7 +84,6 @@ msgstr "" #. module: website_sale_cart_expire #: model:ir.actions.server,name:website_sale_cart_expire.ir_cron_cart_expire_ir_actions_server #: model:ir.cron,cron_name:website_sale_cart_expire.ir_cron_cart_expire -#: model:ir.cron,name:website_sale_cart_expire.ir_cron_cart_expire msgid "Website: Expire Carts" msgstr "" diff --git a/website_sale_cart_expire/static/description/index.html b/website_sale_cart_expire/static/description/index.html index f04acbf7b0..0afb120093 100644 --- a/website_sale_cart_expire/static/description/index.html +++ b/website_sale_cart_expire/static/description/index.html @@ -367,7 +367,7 @@

Website Sale Cart Expire

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: AGPL-3 OCA/e-commerce Translate me on Weblate Try me on Runbot

+

Beta License: AGPL-3 OCA/e-commerce Translate me on Weblate Try me on Runbot

Allows to automatically cancel carts without activity after a configurable time.

Table of contents

@@ -393,7 +393,7 @@

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.

+feedback.

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

@@ -425,7 +425,7 @@

Maintainers

promote its widespread use.

Current maintainer:

ivantodorovich

-

This module is part of the OCA/e-commerce project on GitHub.

+

This module is part of the OCA/e-commerce project on GitHub.

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

diff --git a/website_sale_cart_expire/tests/test_website_sale_cart_expire.py b/website_sale_cart_expire/tests/test_website_sale_cart_expire.py index fb31891e34..d50c7fdcf0 100644 --- a/website_sale_cart_expire/tests/test_website_sale_cart_expire.py +++ b/website_sale_cart_expire/tests/test_website_sale_cart_expire.py @@ -32,10 +32,10 @@ def setUpClass(cls): def _create_payment_transaction(self, order): self.tx_counter += 1 - acquirer = self.env.ref("payment.payment_acquirer_test") + provider = self.env.ref("payment.payment_provider_demo") return self.env["payment.transaction"].create( { - "acquirer_id": acquirer.id, + "provider_id": provider.id, "reference": f"{order.name}-{self.tx_counter}", "amount": order.amount_total, "currency_id": order.currency_id.id, From 41495c3b34259b1041f5d902606a082579c4d81f Mon Sep 17 00:00:00 2001 From: Ivorra78 Date: Thu, 3 Aug 2023 18:10:28 +0000 Subject: [PATCH 09/16] Added translation using Weblate (Spanish) --- website_sale_cart_expire/i18n/es.po | 94 +++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 website_sale_cart_expire/i18n/es.po diff --git a/website_sale_cart_expire/i18n/es.po b/website_sale_cart_expire/i18n/es.po new file mode 100644 index 0000000000..a9bd9e382e --- /dev/null +++ b/website_sale_cart_expire/i18n/es.po @@ -0,0 +1,94 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * website_sale_cart_expire +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#. module: website_sale_cart_expire +#: model_terms:ir.ui.view,arch_db:website_sale_cart_expire.res_config_settings_view_form +msgid "" +"Expire Carts\n" +" " +msgstr "" + +#. module: website_sale_cart_expire +#: model_terms:ir.ui.view,arch_db:website_sale_cart_expire.res_config_settings_view_form +msgid "Automatically cancel carts without activity after a period of time" +msgstr "" + +#. module: website_sale_cart_expire +#: model:ir.model.fields,help:website_sale_cart_expire.field_res_config_settings__cart_expire_delay +#: model:ir.model.fields,help:website_sale_cart_expire.field_website__cart_expire_delay +msgid "" +"Automatically cancel website orders after the given time.\n" +"Set to 0 to disable this feature." +msgstr "" + +#. module: website_sale_cart_expire +#: model:ir.model.fields,field_description:website_sale_cart_expire.field_sale_order__cart_expire_date +msgid "Cart Expire Date" +msgstr "" + +#. module: website_sale_cart_expire +#. odoo-python +#: code:addons/website_sale_cart_expire/models/website.py:0 +#, python-format +msgid "Cart expired" +msgstr "" + +#. module: website_sale_cart_expire +#: model_terms:ir.ui.view,arch_db:website_sale_cart_expire.res_config_settings_view_form +msgid "Cart is cancelled after" +msgstr "" + +#. module: website_sale_cart_expire +#: model_terms:ir.ui.view,arch_db:website_sale_cart_expire.res_config_settings_view_form +msgid "Carts are cancelled after this delay." +msgstr "" + +#. module: website_sale_cart_expire +#: model:ir.model,name:website_sale_cart_expire.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: website_sale_cart_expire +#: model:ir.model.fields,field_description:website_sale_cart_expire.field_res_config_settings__cart_expire_delay +#: model:ir.model.fields,field_description:website_sale_cart_expire.field_website__cart_expire_delay +msgid "Expire Delay" +msgstr "" + +#. module: website_sale_cart_expire +#: model:ir.model,name:website_sale_cart_expire.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: website_sale_cart_expire +#: model:ir.model.fields,help:website_sale_cart_expire.field_sale_order__cart_expire_date +msgid "Technical field: The date this cart will automatically expire" +msgstr "" + +#. module: website_sale_cart_expire +#: model:ir.model,name:website_sale_cart_expire.model_website +msgid "Website" +msgstr "" + +#. module: website_sale_cart_expire +#: model:ir.actions.server,name:website_sale_cart_expire.ir_cron_cart_expire_ir_actions_server +#: model:ir.cron,cron_name:website_sale_cart_expire.ir_cron_cart_expire +msgid "Website: Expire Carts" +msgstr "" + +#. module: website_sale_cart_expire +#: model_terms:ir.ui.view,arch_db:website_sale_cart_expire.res_config_settings_view_form +msgid "hours." +msgstr "" From 4cd754c4087dde4e0233deb4216a4486e5044ef8 Mon Sep 17 00:00:00 2001 From: Ivorra78 Date: Thu, 3 Aug 2023 18:11:25 +0000 Subject: [PATCH 10/16] Translated using Weblate (Spanish) Currently translated at 100.0% (14 of 14 strings) Translation: e-commerce-16.0/e-commerce-16.0-website_sale_cart_expire Translate-URL: https://translation.odoo-community.org/projects/e-commerce-16-0/e-commerce-16-0-website_sale_cart_expire/es/ --- website_sale_cart_expire/README.rst | 15 ++++--- website_sale_cart_expire/i18n/es.po | 37 +++++++++++------ .../static/description/index.html | 40 ++++++++++--------- 3 files changed, 54 insertions(+), 38 deletions(-) diff --git a/website_sale_cart_expire/README.rst b/website_sale_cart_expire/README.rst index 86f41589bc..854206a586 100644 --- a/website_sale_cart_expire/README.rst +++ b/website_sale_cart_expire/README.rst @@ -2,10 +2,13 @@ Website Sale Cart Expire ======================== -.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:2a3356f020ea8b4b3a30b6a302c5a2f0c723d56b494bcc388ed2430b856b9b41 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status @@ -19,11 +22,11 @@ Website Sale Cart Expire .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png :target: https://translation.odoo-community.org/projects/e-commerce-16-0/e-commerce-16-0-website_sale_cart_expire :alt: Translate me on Weblate -.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/113/16.0 - :alt: Try me on Runbot +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/e-commerce&target_branch=16.0 + :alt: Try me on Runboat -|badge1| |badge2| |badge3| |badge4| |badge5| +|badge1| |badge2| |badge3| |badge4| |badge5| Allows to automatically cancel carts without activity after a configurable time. @@ -45,7 +48,7 @@ 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 +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. diff --git a/website_sale_cart_expire/i18n/es.po b/website_sale_cart_expire/i18n/es.po index a9bd9e382e..ce3f8d07e8 100644 --- a/website_sale_cart_expire/i18n/es.po +++ b/website_sale_cart_expire/i18n/es.po @@ -6,25 +6,34 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" -"Last-Translator: Automatically generated\n" +"PO-Revision-Date: 2023-08-03 20:09+0000\n" +"Last-Translator: Ivorra78 \n" "Language-Team: none\n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" "Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" #. module: website_sale_cart_expire #: model_terms:ir.ui.view,arch_db:website_sale_cart_expire.res_config_settings_view_form msgid "" "Expire Carts\n" -" " +" " msgstr "" +"Carro expirado\n" +" " #. module: website_sale_cart_expire #: model_terms:ir.ui.view,arch_db:website_sale_cart_expire.res_config_settings_view_form msgid "Automatically cancel carts without activity after a period of time" msgstr "" +"Cancelar automáticamente carritos sin actividad después de un período de " +"tiempo" #. module: website_sale_cart_expire #: model:ir.model.fields,help:website_sale_cart_expire.field_res_config_settings__cart_expire_delay @@ -33,62 +42,64 @@ msgid "" "Automatically cancel website orders after the given time.\n" "Set to 0 to disable this feature." msgstr "" +"Cancelar automáticamente los pedidos del sitio web después del tiempo dado.\n" +"Establecer en 0 para desactivar esta función." #. module: website_sale_cart_expire #: model:ir.model.fields,field_description:website_sale_cart_expire.field_sale_order__cart_expire_date msgid "Cart Expire Date" -msgstr "" +msgstr "Fecha de caducidad del carro" #. module: website_sale_cart_expire #. odoo-python #: code:addons/website_sale_cart_expire/models/website.py:0 #, python-format msgid "Cart expired" -msgstr "" +msgstr "Carro caducado" #. module: website_sale_cart_expire #: model_terms:ir.ui.view,arch_db:website_sale_cart_expire.res_config_settings_view_form msgid "Cart is cancelled after" -msgstr "" +msgstr "El carro ha sido cancelado después" #. module: website_sale_cart_expire #: model_terms:ir.ui.view,arch_db:website_sale_cart_expire.res_config_settings_view_form msgid "Carts are cancelled after this delay." -msgstr "" +msgstr "Los carros se cancelan después de este plazo." #. module: website_sale_cart_expire #: model:ir.model,name:website_sale_cart_expire.model_res_config_settings msgid "Config Settings" -msgstr "" +msgstr "Ajustes de Configuración" #. module: website_sale_cart_expire #: model:ir.model.fields,field_description:website_sale_cart_expire.field_res_config_settings__cart_expire_delay #: model:ir.model.fields,field_description:website_sale_cart_expire.field_website__cart_expire_delay msgid "Expire Delay" -msgstr "" +msgstr "Expiración retardada" #. module: website_sale_cart_expire #: model:ir.model,name:website_sale_cart_expire.model_sale_order msgid "Sales Order" -msgstr "" +msgstr "Órdenes de venta" #. module: website_sale_cart_expire #: model:ir.model.fields,help:website_sale_cart_expire.field_sale_order__cart_expire_date msgid "Technical field: The date this cart will automatically expire" -msgstr "" +msgstr "Campo técnico: La fecha en que este carro caducará automáticamente" #. module: website_sale_cart_expire #: model:ir.model,name:website_sale_cart_expire.model_website msgid "Website" -msgstr "" +msgstr "Página Web" #. module: website_sale_cart_expire #: model:ir.actions.server,name:website_sale_cart_expire.ir_cron_cart_expire_ir_actions_server #: model:ir.cron,cron_name:website_sale_cart_expire.ir_cron_cart_expire msgid "Website: Expire Carts" -msgstr "" +msgstr "Página web: Carros caducados" #. module: website_sale_cart_expire #: model_terms:ir.ui.view,arch_db:website_sale_cart_expire.res_config_settings_view_form msgid "hours." -msgstr "" +msgstr "horas." diff --git a/website_sale_cart_expire/static/description/index.html b/website_sale_cart_expire/static/description/index.html index 0afb120093..3352817f1b 100644 --- a/website_sale_cart_expire/static/description/index.html +++ b/website_sale_cart_expire/static/description/index.html @@ -1,20 +1,20 @@ - + - + Website Sale Cart Expire