diff --git a/account_invoice_validation_queued/README.rst b/account_invoice_validation_queued/README.rst new file mode 100644 index 000000000000..ca1cd6ef67d9 --- /dev/null +++ b/account_invoice_validation_queued/README.rst @@ -0,0 +1,118 @@ +================================== +Enqueue account invoice validation +================================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:52e5a113d773cd9be743169e32d06e4a7e45ac6ff87b6262b2fb96ee70aa18d9 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png + :target: https://odoo-community.org/page/development-status + :alt: Production/Stable +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Faccount--invoicing-lightgray.png?logo=github + :target: https://github.com/OCA/account-invoicing/tree/16.0/account_invoice_validation_queued + :alt: OCA/account-invoicing +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/account-invoicing-16-0/account-invoicing-16-0-account_invoice_validation_queued + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/account-invoicing&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to enqueue in several jobs the account validation process +to be executed in paralell on background, which is normally done serially and +on foreground. + +Each invoice creates a job for performing the validation. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +#. Jobs are enqueued in the channel ``root.account_invoice_validation_queued``, + so you must adjust your + `Odoo configuration `_ + according this. +#. If you want to see queued jobs, you need "Job Queue / Job Queue Manager" + permission in your user. +#. Configure your invoice/refund sequences as "Standard" instead of "No gap", + or you'll have concurrent updates problems. + +Usage +===== + +#. Go to *Invoicing > Customers > Invoices* or + *Invoicing > Vendors > Bills*. +#. Mark at least one check on the left part of one draft invoice in the + list view. +#. Click on *Action > Post entries*. +#. On the dialog popup that appears, click on "Enqueue Validation". +#. Once enqueued, and having the "Job Queue Manager" permission, you can go to + the invoice, and see the tab "Validation Jobs". A list with all the jobs + related to that invoice can be found there. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Tecnativa + +Contributors +~~~~~~~~~~~~ + +* `Tecnativa `__: + + * Pedro M. Baeza + * Manuel Calero + +* Saran Lim. + +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-pedrobaeza| image:: https://github.com/pedrobaeza.png?size=40px + :target: https://github.com/pedrobaeza + :alt: pedrobaeza + +Current `maintainer `__: + +|maintainer-pedrobaeza| + +This module is part of the `OCA/account-invoicing `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/account_invoice_validation_queued/__init__.py b/account_invoice_validation_queued/__init__.py new file mode 100644 index 000000000000..7588e52c8cc5 --- /dev/null +++ b/account_invoice_validation_queued/__init__.py @@ -0,0 +1,4 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models +from . import wizards diff --git a/account_invoice_validation_queued/__manifest__.py b/account_invoice_validation_queued/__manifest__.py new file mode 100644 index 000000000000..ac57e8472632 --- /dev/null +++ b/account_invoice_validation_queued/__manifest__.py @@ -0,0 +1,22 @@ +# Copyright 2019-2020 Tecnativa - Pedro M. Baeza +# Copyright 2020 Tecnativa - Manuel Calero +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Enqueue account invoice validation", + "version": "16.0.1.0.0", + "category": "Accounting", + "license": "AGPL-3", + "author": "Tecnativa, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/account-invoicing", + "depends": ["account", "queue_job"], + "data": [ + "data/queue_job.xml", + "views/queue_job_views.xml", + "views/account_invoice_views.xml", + "wizards/validate_account_move_view.xml", + ], + "installable": True, + "development_status": "Production/Stable", + "maintainers": ["pedrobaeza"], +} diff --git a/account_invoice_validation_queued/data/queue_job.xml b/account_invoice_validation_queued/data/queue_job.xml new file mode 100644 index 000000000000..23713f274efa --- /dev/null +++ b/account_invoice_validation_queued/data/queue_job.xml @@ -0,0 +1,15 @@ + + + + + Invoice Open Job + + + + + + + action_invoice_open_job + + + diff --git a/account_invoice_validation_queued/i18n/account_invoice_validation_queued.pot b/account_invoice_validation_queued/i18n/account_invoice_validation_queued.pot new file mode 100644 index 000000000000..ca32fd727f90 --- /dev/null +++ b/account_invoice_validation_queued/i18n/account_invoice_validation_queued.pot @@ -0,0 +1,53 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_invoice_validation_queued +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.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: account_invoice_validation_queued +#: model_terms:ir.ui.view,arch_db:account_invoice_validation_queued.view_queue_job_account_invoice_validation +msgid "Cancel" +msgstr "" + +#. module: account_invoice_validation_queued +#: model_terms:ir.ui.view,arch_db:account_invoice_validation_queued.validate_account_move_view +msgid "Enqueue Validation" +msgstr "" + +#. module: account_invoice_validation_queued +#: model:ir.model,name:account_invoice_validation_queued.model_account_move +msgid "Journal Entry" +msgstr "" + +#. module: account_invoice_validation_queued +#: model_terms:ir.ui.view,arch_db:account_invoice_validation_queued.view_queue_job_account_invoice_validation +msgid "Requeue" +msgstr "" + +#. module: account_invoice_validation_queued +#: model:ir.model,name:account_invoice_validation_queued.model_validate_account_move +msgid "Validate Account Move" +msgstr "" + +#. module: account_invoice_validation_queued +#: model:ir.model.fields,field_description:account_invoice_validation_queued.field_account_bank_statement_line__validation_job_ids +#: model:ir.model.fields,field_description:account_invoice_validation_queued.field_account_move__validation_job_ids +#: model:ir.model.fields,field_description:account_invoice_validation_queued.field_account_payment__validation_job_ids +#: model_terms:ir.ui.view,arch_db:account_invoice_validation_queued.view_move_form +msgid "Validation Jobs" +msgstr "" + +#. module: account_invoice_validation_queued +#: code:addons/account_invoice_validation_queued/wizards/validate_account_move.py:0 +#, python-format +msgid "You can't enqueue invoices with different dates." +msgstr "" diff --git a/account_invoice_validation_queued/i18n/ca.po b/account_invoice_validation_queued/i18n/ca.po new file mode 100644 index 000000000000..21bb9cb7907b --- /dev/null +++ b/account_invoice_validation_queued/i18n/ca.po @@ -0,0 +1,54 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_invoice_validation_queued +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: ca\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: account_invoice_validation_queued +#: model_terms:ir.ui.view,arch_db:account_invoice_validation_queued.view_queue_job_account_invoice_validation +msgid "Cancel" +msgstr "" + +#. module: account_invoice_validation_queued +#: model_terms:ir.ui.view,arch_db:account_invoice_validation_queued.validate_account_move_view +msgid "Enqueue Validation" +msgstr "" + +#. module: account_invoice_validation_queued +#: model:ir.model,name:account_invoice_validation_queued.model_account_move +msgid "Journal Entry" +msgstr "" + +#. module: account_invoice_validation_queued +#: model_terms:ir.ui.view,arch_db:account_invoice_validation_queued.view_queue_job_account_invoice_validation +msgid "Requeue" +msgstr "" + +#. module: account_invoice_validation_queued +#: model:ir.model,name:account_invoice_validation_queued.model_validate_account_move +msgid "Validate Account Move" +msgstr "" + +#. module: account_invoice_validation_queued +#: model:ir.model.fields,field_description:account_invoice_validation_queued.field_account_bank_statement_line__validation_job_ids +#: model:ir.model.fields,field_description:account_invoice_validation_queued.field_account_move__validation_job_ids +#: model:ir.model.fields,field_description:account_invoice_validation_queued.field_account_payment__validation_job_ids +#: model_terms:ir.ui.view,arch_db:account_invoice_validation_queued.view_move_form +msgid "Validation Jobs" +msgstr "" + +#. module: account_invoice_validation_queued +#: code:addons/account_invoice_validation_queued/wizards/validate_account_move.py:0 +#, python-format +msgid "You can't enqueue invoices with different dates." +msgstr "" diff --git a/account_invoice_validation_queued/i18n/es.po b/account_invoice_validation_queued/i18n/es.po new file mode 100644 index 000000000000..876b95df032d --- /dev/null +++ b/account_invoice_validation_queued/i18n/es.po @@ -0,0 +1,79 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_invoice_validation_queued +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-08-29 23:11+0000\n" +"PO-Revision-Date: 2023-10-09 07:44+0000\n" +"Last-Translator: Ivorra78 \n" +"Language-Team: \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: account_invoice_validation_queued +#: model_terms:ir.ui.view,arch_db:account_invoice_validation_queued.view_queue_job_account_invoice_validation +msgid "Cancel" +msgstr "Cancelar" + +#. module: account_invoice_validation_queued +#: model_terms:ir.ui.view,arch_db:account_invoice_validation_queued.validate_account_move_view +msgid "Enqueue Validation" +msgstr "Encolar validación" + +#. module: account_invoice_validation_queued +#: model:ir.model,name:account_invoice_validation_queued.model_account_move +msgid "Journal Entry" +msgstr "Entrada diaria" + +#. module: account_invoice_validation_queued +#: model_terms:ir.ui.view,arch_db:account_invoice_validation_queued.view_queue_job_account_invoice_validation +msgid "Requeue" +msgstr "Volver a poner en cola" + +#. module: account_invoice_validation_queued +#: model:ir.model,name:account_invoice_validation_queued.model_validate_account_move +msgid "Validate Account Move" +msgstr "Validar movimientos contables" + +#. module: account_invoice_validation_queued +#: model:ir.model.fields,field_description:account_invoice_validation_queued.field_account_bank_statement_line__validation_job_ids +#: model:ir.model.fields,field_description:account_invoice_validation_queued.field_account_move__validation_job_ids +#: model:ir.model.fields,field_description:account_invoice_validation_queued.field_account_payment__validation_job_ids +#: model_terms:ir.ui.view,arch_db:account_invoice_validation_queued.view_move_form +msgid "Validation Jobs" +msgstr "Trabajos de validación" + +#. module: account_invoice_validation_queued +#: code:addons/account_invoice_validation_queued/wizards/validate_account_move.py:0 +#, python-format +msgid "You can't enqueue invoices with different dates." +msgstr "No puede encolar facturas de diferentes fechas." + +#~ msgid "Journal Entries" +#~ msgstr "Asientos contables" + +#~ msgid "Confirm the selected invoices" +#~ msgstr "Confirmar las facturas seleccionadas" + +#~ msgid "Invoice" +#~ msgstr "Factura" + +#~ msgid "" +#~ "There's already an enqueued job for validating the invoice #%s. Please " +#~ "wait until it's finished or remove it from the selection." +#~ msgstr "" +#~ "Ya hay un trabajo encolado para validar la factura #%s. Por favor espere " +#~ "hasta que haya finalizado o elimine la factura de la selección." + +#~ msgid "Queue Job" +#~ msgstr "Trabajo en cola" + +#~ msgid "Quotation" +#~ msgstr "Presupuesto" diff --git a/account_invoice_validation_queued/models/__init__.py b/account_invoice_validation_queued/models/__init__.py new file mode 100644 index 000000000000..06d06dffb2a0 --- /dev/null +++ b/account_invoice_validation_queued/models/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import account_move diff --git a/account_invoice_validation_queued/models/account_move.py b/account_invoice_validation_queued/models/account_move.py new file mode 100644 index 000000000000..0076597e59f5 --- /dev/null +++ b/account_invoice_validation_queued/models/account_move.py @@ -0,0 +1,24 @@ +# Copyright 2019-2020 Tecnativa - Pedro M. Baeza +# Copyright 2020 Tecnativa - Manuel Calero +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class AccountMove(models.Model): + _inherit = "account.move" + + validation_job_ids = fields.Many2many( + comodel_name="queue.job", + column1="invoice_id", + column2="job_id", + string="Validation Jobs", + relation="account_move_validation_job_rel", + copy=False, + ) + + def action_invoice_open_job(self): + self.ensure_one() + if self.state != "draft": + return + return self._post() diff --git a/account_invoice_validation_queued/readme/CONFIGURE.rst b/account_invoice_validation_queued/readme/CONFIGURE.rst new file mode 100644 index 000000000000..c359915139e8 --- /dev/null +++ b/account_invoice_validation_queued/readme/CONFIGURE.rst @@ -0,0 +1,8 @@ +#. Jobs are enqueued in the channel ``root.account_invoice_validation_queued``, + so you must adjust your + `Odoo configuration `_ + according this. +#. If you want to see queued jobs, you need "Job Queue / Job Queue Manager" + permission in your user. +#. Configure your invoice/refund sequences as "Standard" instead of "No gap", + or you'll have concurrent updates problems. diff --git a/account_invoice_validation_queued/readme/CONTRIBUTORS.rst b/account_invoice_validation_queued/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000000..24cbe0a10953 --- /dev/null +++ b/account_invoice_validation_queued/readme/CONTRIBUTORS.rst @@ -0,0 +1,6 @@ +* `Tecnativa `__: + + * Pedro M. Baeza + * Manuel Calero + +* Saran Lim. \ No newline at end of file diff --git a/account_invoice_validation_queued/readme/DESCRIPTION.rst b/account_invoice_validation_queued/readme/DESCRIPTION.rst new file mode 100644 index 000000000000..0a73a87339e4 --- /dev/null +++ b/account_invoice_validation_queued/readme/DESCRIPTION.rst @@ -0,0 +1,5 @@ +This module allows to enqueue in several jobs the account validation process +to be executed in paralell on background, which is normally done serially and +on foreground. + +Each invoice creates a job for performing the validation. diff --git a/account_invoice_validation_queued/readme/USAGE.rst b/account_invoice_validation_queued/readme/USAGE.rst new file mode 100644 index 000000000000..747cb7295c36 --- /dev/null +++ b/account_invoice_validation_queued/readme/USAGE.rst @@ -0,0 +1,9 @@ +#. Go to *Invoicing > Customers > Invoices* or + *Invoicing > Vendors > Bills*. +#. Mark at least one check on the left part of one draft invoice in the + list view. +#. Click on *Action > Post entries*. +#. On the dialog popup that appears, click on "Enqueue Validation". +#. Once enqueued, and having the "Job Queue Manager" permission, you can go to + the invoice, and see the tab "Validation Jobs". A list with all the jobs + related to that invoice can be found there. diff --git a/account_invoice_validation_queued/static/description/icon.png b/account_invoice_validation_queued/static/description/icon.png new file mode 100644 index 000000000000..3a0328b516c4 Binary files /dev/null and b/account_invoice_validation_queued/static/description/icon.png differ diff --git a/account_invoice_validation_queued/static/description/index.html b/account_invoice_validation_queued/static/description/index.html new file mode 100644 index 000000000000..452749a17466 --- /dev/null +++ b/account_invoice_validation_queued/static/description/index.html @@ -0,0 +1,459 @@ + + + + + +Enqueue account invoice validation + + + +
+

Enqueue account invoice validation

+ + +

Production/Stable License: AGPL-3 OCA/account-invoicing Translate me on Weblate Try me on Runboat

+

This module allows to enqueue in several jobs the account validation process +to be executed in paralell on background, which is normally done serially and +on foreground.

+

Each invoice creates a job for performing the validation.

+

Table of contents

+ +
+

Configuration

+
    +
  1. Jobs are enqueued in the channel root.account_invoice_validation_queued, +so you must adjust your +Odoo configuration +according this.
  2. +
  3. If you want to see queued jobs, you need “Job Queue / Job Queue Manager” +permission in your user.
  4. +
  5. Configure your invoice/refund sequences as “Standard” instead of “No gap”, +or you’ll have concurrent updates problems.
  6. +
+
+
+

Usage

+
    +
  1. Go to Invoicing > Customers > Invoices or +Invoicing > Vendors > Bills.
  2. +
  3. Mark at least one check on the left part of one draft invoice in the +list view.
  4. +
  5. Click on Action > Post entries.
  6. +
  7. On the dialog popup that appears, click on “Enqueue Validation”.
  8. +
  9. Once enqueued, and having the “Job Queue Manager” permission, you can go to +the invoice, and see the tab “Validation Jobs”. A list with all the jobs +related to that invoice can be found there.
  10. +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • Tecnativa
  • +
+
+
+

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:

+

pedrobaeza

+

This module is part of the OCA/account-invoicing project on GitHub.

+

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

+
+
+
+ + diff --git a/account_invoice_validation_queued/tests/__init__.py b/account_invoice_validation_queued/tests/__init__.py new file mode 100644 index 000000000000..b41e35fb3b26 --- /dev/null +++ b/account_invoice_validation_queued/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import test_account_invoice_validation_queued diff --git a/account_invoice_validation_queued/tests/test_account_invoice_validation_queued.py b/account_invoice_validation_queued/tests/test_account_invoice_validation_queued.py new file mode 100644 index 000000000000..9279b5283728 --- /dev/null +++ b/account_invoice_validation_queued/tests/test_account_invoice_validation_queued.py @@ -0,0 +1,54 @@ +# Copyright 2019 Tecnativa - Pedro M. Baeza +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import Command +from odoo.tests import TransactionCase + + +class TestAccountInvoiceValidationQueued(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.wizard_obj = cls.env["validate.account.move"] + cls.queue_obj = cls.env["queue.job"] + + cls.partner = cls.env["res.partner"].create({"name": "Test partner"}) + cls.account = cls.env["account.account"].create( + { + "name": "Test account", + "code": "TEST.AIVQ", + "account_type": "equity", + } + ) + cls.invoice = cls.env["account.move"].create( + { + "partner_id": cls.partner.id, + "move_type": "out_invoice", + "invoice_line_ids": [ + Command.create( + { + "name": "Test product", + "account_id": cls.account.id, + "price_unit": 20.0, + "quantity": 1.0, + } + ) + ], + } + ) + + def test_queue_validation(self): + wizard = self.wizard_obj.with_context( + active_ids=self.invoice.ids, + ).create({}) + prev_jobs = self.queue_obj.search([]) + wizard.enqueue_invoice_confirm() + current_jobs = self.queue_obj.search([]) + jobs = current_jobs - prev_jobs + self.assertEqual(len(jobs), 1) + self.assertTrue(self.invoice.validation_job_ids) + + def test_validation(self): + # Execute method directly for checking if validation is done + self.invoice.action_invoice_open_job() + self.assertEqual(self.invoice.state, "posted") diff --git a/account_invoice_validation_queued/views/account_invoice_views.xml b/account_invoice_validation_queued/views/account_invoice_views.xml new file mode 100644 index 000000000000..e6fb86b320e0 --- /dev/null +++ b/account_invoice_validation_queued/views/account_invoice_views.xml @@ -0,0 +1,28 @@ + + + + + account.move + + + + + + + + + + diff --git a/account_invoice_validation_queued/views/queue_job_views.xml b/account_invoice_validation_queued/views/queue_job_views.xml new file mode 100644 index 000000000000..8549414e0148 --- /dev/null +++ b/account_invoice_validation_queued/views/queue_job_views.xml @@ -0,0 +1,31 @@ + + + + + queue.job + + + + + + + + + + diff --git a/setup/account_invoice_validation_queued/odoo/addons/account_invoice_validation_queued b/setup/account_invoice_validation_queued/odoo/addons/account_invoice_validation_queued new file mode 120000 index 000000000000..cc322328eea9 --- /dev/null +++ b/setup/account_invoice_validation_queued/odoo/addons/account_invoice_validation_queued @@ -0,0 +1 @@ +../../../../account_invoice_validation_queued \ No newline at end of file diff --git a/setup/account_invoice_validation_queued/setup.py b/setup/account_invoice_validation_queued/setup.py new file mode 100644 index 000000000000..28c57bb64031 --- /dev/null +++ b/setup/account_invoice_validation_queued/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)