From 918d2c634993ffbe4236996ba55960bdc77ce883 Mon Sep 17 00:00:00 2001 From: Ted Salmon Date: Thu, 12 Jan 2017 13:16:25 -0800 Subject: [PATCH 001/196] [ADD] product_contract: Create module * Add contract functionality to `product.templates` * Add logic to create contracts from `sale.order` that contains contract products. --- product_contract/README.rst | 66 +++++++++++++++++++ product_contract/__init__.py | 5 ++ product_contract/__manifest__.py | 23 +++++++ product_contract/i18n/de.po | 51 ++++++++++++++ product_contract/i18n/es.po | 51 ++++++++++++++ product_contract/i18n/fr.po | 51 ++++++++++++++ product_contract/i18n/hr.po | 51 ++++++++++++++ product_contract/i18n/hr_HR.po | 52 +++++++++++++++ product_contract/i18n/it.po | 51 ++++++++++++++ product_contract/i18n/nl.po | 51 ++++++++++++++ product_contract/i18n/pt_BR.po | 51 ++++++++++++++ product_contract/i18n/tr_TR.po | 51 ++++++++++++++ product_contract/models/__init__.py | 7 ++ product_contract/models/product_template.py | 23 +++++++ product_contract/models/sale_order.py | 27 ++++++++ product_contract/models/sale_order_line.py | 14 ++++ product_contract/tests/__init__.py | 6 ++ .../tests/test_product_template.py | 31 +++++++++ product_contract/tests/test_sale_order.py | 34 ++++++++++ .../views/product_template_view.xml | 29 ++++++++ 20 files changed, 725 insertions(+) create mode 100644 product_contract/README.rst create mode 100644 product_contract/__init__.py create mode 100644 product_contract/__manifest__.py create mode 100644 product_contract/i18n/de.po create mode 100644 product_contract/i18n/es.po create mode 100644 product_contract/i18n/fr.po create mode 100644 product_contract/i18n/hr.po create mode 100644 product_contract/i18n/hr_HR.po create mode 100644 product_contract/i18n/it.po create mode 100644 product_contract/i18n/nl.po create mode 100644 product_contract/i18n/pt_BR.po create mode 100644 product_contract/i18n/tr_TR.po create mode 100644 product_contract/models/__init__.py create mode 100644 product_contract/models/product_template.py create mode 100644 product_contract/models/sale_order.py create mode 100644 product_contract/models/sale_order_line.py create mode 100644 product_contract/tests/__init__.py create mode 100644 product_contract/tests/test_product_template.py create mode 100644 product_contract/tests/test_sale_order.py create mode 100644 product_contract/views/product_template_view.xml diff --git a/product_contract/README.rst b/product_contract/README.rst new file mode 100644 index 0000000000..6319dcf55a --- /dev/null +++ b/product_contract/README.rst @@ -0,0 +1,66 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +================ +Product Contract +================ + +This module adds support for products to be linked to contract templates. +It also adds functionality to automatically create a contract, from the template, +when a ``sale.order`` contains a product that implements a contract. + +Usage +===== + +To use this module, you need to: + +#. Go to Sales -> Products and select or create a product. +#. Check "Is a contract" and select the contract template related to the + product + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/110/10.0 + +Known issues / Roadmap +====================== + +* None + +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 smash it by providing detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Ted Salmon + + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +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. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/product_contract/__init__.py b/product_contract/__init__.py new file mode 100644 index 0000000000..44db863b6e --- /dev/null +++ b/product_contract/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import models diff --git a/product_contract/__manifest__.py b/product_contract/__manifest__.py new file mode 100644 index 0000000000..e1d4d8ac65 --- /dev/null +++ b/product_contract/__manifest__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + 'name': 'Product Contract', + 'version': '10.0.1.0.0', + 'category': 'Contract Management', + 'license': 'AGPL-3', + 'author': "LasLabs, " + "Odoo Community Association (OCA)", + 'website': 'https://laslabs.com', + 'depends': [ + 'contract', + 'product', + 'sale', + ], + 'data': [ + 'views/product_template_view.xml', + ], + 'installable': True, + 'application': False, +} diff --git a/product_contract/i18n/de.po b/product_contract/i18n/de.po new file mode 100644 index 0000000000..3b62b5447b --- /dev/null +++ b/product_contract/i18n/de.po @@ -0,0 +1,51 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_contract +# +# Translators: +# OCA Transbot , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-04-27 02:40+0000\n" +"PO-Revision-Date: 2017-04-27 02:40+0000\n" +"Last-Translator: OCA Transbot , 2017\n" +"Language-Team: German (https://www.transifex.com/oca/teams/23907/de/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: de\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_sale_order_line_contract_id +msgid "Contract" +msgstr "Vertrag" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_contract_template_id +#: model:ir.model.fields,field_description:product_contract.field_product_template_contract_template_id +msgid "Contract Template" +msgstr "" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_is_contract +#: model:ir.model.fields,field_description:product_contract.field_product_template_is_contract +msgid "Is a contract" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_product_template +msgid "Product Template" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order_line +msgid "Sales Order Line" +msgstr "" diff --git a/product_contract/i18n/es.po b/product_contract/i18n/es.po new file mode 100644 index 0000000000..a89a0a1226 --- /dev/null +++ b/product_contract/i18n/es.po @@ -0,0 +1,51 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_contract +# +# Translators: +# OCA Transbot , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-04-27 02:40+0000\n" +"PO-Revision-Date: 2017-04-27 02:40+0000\n" +"Last-Translator: OCA Transbot , 2017\n" +"Language-Team: Spanish (https://www.transifex.com/oca/teams/23907/es/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: es\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_sale_order_line_contract_id +msgid "Contract" +msgstr "Contrato" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_contract_template_id +#: model:ir.model.fields,field_description:product_contract.field_product_template_contract_template_id +msgid "Contract Template" +msgstr "" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_is_contract +#: model:ir.model.fields,field_description:product_contract.field_product_template_is_contract +msgid "Is a contract" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_product_template +msgid "Product Template" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order_line +msgid "Sales Order Line" +msgstr "" diff --git a/product_contract/i18n/fr.po b/product_contract/i18n/fr.po new file mode 100644 index 0000000000..1efcaf7ce5 --- /dev/null +++ b/product_contract/i18n/fr.po @@ -0,0 +1,51 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_contract +# +# Translators: +# leemannd , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-04-27 02:40+0000\n" +"PO-Revision-Date: 2017-04-27 02:40+0000\n" +"Last-Translator: leemannd , 2017\n" +"Language-Team: French (https://www.transifex.com/oca/teams/23907/fr/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: fr\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_sale_order_line_contract_id +msgid "Contract" +msgstr "Contrat" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_contract_template_id +#: model:ir.model.fields,field_description:product_contract.field_product_template_contract_template_id +msgid "Contract Template" +msgstr "" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_is_contract +#: model:ir.model.fields,field_description:product_contract.field_product_template_is_contract +msgid "Is a contract" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_product_template +msgid "Product Template" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order_line +msgid "Sales Order Line" +msgstr "" diff --git a/product_contract/i18n/hr.po b/product_contract/i18n/hr.po new file mode 100644 index 0000000000..7f2b4f8c1e --- /dev/null +++ b/product_contract/i18n/hr.po @@ -0,0 +1,51 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_contract +# +# Translators: +# Bole , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-04-27 02:40+0000\n" +"PO-Revision-Date: 2017-04-27 02:40+0000\n" +"Last-Translator: Bole , 2017\n" +"Language-Team: Croatian (https://www.transifex.com/oca/teams/23907/hr/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: hr\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_sale_order_line_contract_id +msgid "Contract" +msgstr "Ugovor" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_contract_template_id +#: model:ir.model.fields,field_description:product_contract.field_product_template_contract_template_id +msgid "Contract Template" +msgstr "" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_is_contract +#: model:ir.model.fields,field_description:product_contract.field_product_template_is_contract +msgid "Is a contract" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_product_template +msgid "Product Template" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order_line +msgid "Sales Order Line" +msgstr "" diff --git a/product_contract/i18n/hr_HR.po b/product_contract/i18n/hr_HR.po new file mode 100644 index 0000000000..922d93fb16 --- /dev/null +++ b/product_contract/i18n/hr_HR.po @@ -0,0 +1,52 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_contract +# +# Translators: +# Bole , 2017 +# OCA Transbot , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-04-27 02:40+0000\n" +"PO-Revision-Date: 2017-04-27 02:40+0000\n" +"Last-Translator: OCA Transbot , 2017\n" +"Language-Team: Croatian (Croatia) (https://www.transifex.com/oca/teams/23907/hr_HR/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: hr_HR\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_sale_order_line_contract_id +msgid "Contract" +msgstr "Ugovor" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_contract_template_id +#: model:ir.model.fields,field_description:product_contract.field_product_template_contract_template_id +msgid "Contract Template" +msgstr "Predložak ugovora" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_is_contract +#: model:ir.model.fields,field_description:product_contract.field_product_template_is_contract +msgid "Is a contract" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_product_template +msgid "Product Template" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order_line +msgid "Sales Order Line" +msgstr "" diff --git a/product_contract/i18n/it.po b/product_contract/i18n/it.po new file mode 100644 index 0000000000..982bdf76c5 --- /dev/null +++ b/product_contract/i18n/it.po @@ -0,0 +1,51 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_contract +# +# Translators: +# Lorenzo Battistini , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-04-27 02:40+0000\n" +"PO-Revision-Date: 2017-04-27 02:40+0000\n" +"Last-Translator: Lorenzo Battistini , 2017\n" +"Language-Team: Italian (https://www.transifex.com/oca/teams/23907/it/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: it\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_sale_order_line_contract_id +msgid "Contract" +msgstr "Contratto" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_contract_template_id +#: model:ir.model.fields,field_description:product_contract.field_product_template_contract_template_id +msgid "Contract Template" +msgstr "Template di contratto" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_is_contract +#: model:ir.model.fields,field_description:product_contract.field_product_template_is_contract +msgid "Is a contract" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_product_template +msgid "Product Template" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order_line +msgid "Sales Order Line" +msgstr "" diff --git a/product_contract/i18n/nl.po b/product_contract/i18n/nl.po new file mode 100644 index 0000000000..4c309a2502 --- /dev/null +++ b/product_contract/i18n/nl.po @@ -0,0 +1,51 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_contract +# +# Translators: +# Erwin van der Ploeg , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-04-27 02:40+0000\n" +"PO-Revision-Date: 2017-04-27 02:40+0000\n" +"Last-Translator: Erwin van der Ploeg , 2017\n" +"Language-Team: Dutch (https://www.transifex.com/oca/teams/23907/nl/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: nl\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_sale_order_line_contract_id +msgid "Contract" +msgstr "Contract" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_contract_template_id +#: model:ir.model.fields,field_description:product_contract.field_product_template_contract_template_id +msgid "Contract Template" +msgstr "" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_is_contract +#: model:ir.model.fields,field_description:product_contract.field_product_template_is_contract +msgid "Is a contract" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_product_template +msgid "Product Template" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order_line +msgid "Sales Order Line" +msgstr "" diff --git a/product_contract/i18n/pt_BR.po b/product_contract/i18n/pt_BR.po new file mode 100644 index 0000000000..592c6aaecb --- /dev/null +++ b/product_contract/i18n/pt_BR.po @@ -0,0 +1,51 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_contract +# +# Translators: +# Albert Vonpupp , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-04-27 02:40+0000\n" +"PO-Revision-Date: 2017-04-27 02:40+0000\n" +"Last-Translator: Albert Vonpupp , 2017\n" +"Language-Team: Portuguese (Brazil) (https://www.transifex.com/oca/teams/23907/pt_BR/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: pt_BR\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_sale_order_line_contract_id +msgid "Contract" +msgstr "Contrato" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_contract_template_id +#: model:ir.model.fields,field_description:product_contract.field_product_template_contract_template_id +msgid "Contract Template" +msgstr "" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_is_contract +#: model:ir.model.fields,field_description:product_contract.field_product_template_is_contract +msgid "Is a contract" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_product_template +msgid "Product Template" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order_line +msgid "Sales Order Line" +msgstr "" diff --git a/product_contract/i18n/tr_TR.po b/product_contract/i18n/tr_TR.po new file mode 100644 index 0000000000..5bcb2f7edd --- /dev/null +++ b/product_contract/i18n/tr_TR.po @@ -0,0 +1,51 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_contract +# +# Translators: +# Ediz Duman , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-04-27 02:40+0000\n" +"PO-Revision-Date: 2017-04-27 02:40+0000\n" +"Last-Translator: Ediz Duman , 2017\n" +"Language-Team: Turkish (Turkey) (https://www.transifex.com/oca/teams/23907/tr_TR/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: tr_TR\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_sale_order_line_contract_id +msgid "Contract" +msgstr "Sözleşme" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_contract_template_id +#: model:ir.model.fields,field_description:product_contract.field_product_template_contract_template_id +msgid "Contract Template" +msgstr "" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_is_contract +#: model:ir.model.fields,field_description:product_contract.field_product_template_is_contract +msgid "Is a contract" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_product_template +msgid "Product Template" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order_line +msgid "Sales Order Line" +msgstr "" diff --git a/product_contract/models/__init__.py b/product_contract/models/__init__.py new file mode 100644 index 0000000000..388717d23f --- /dev/null +++ b/product_contract/models/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import product_template +from . import sale_order +from . import sale_order_line diff --git a/product_contract/models/product_template.py b/product_contract/models/product_template.py new file mode 100644 index 0000000000..fd7e00d4c8 --- /dev/null +++ b/product_contract/models/product_template.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models + + +class ProductTemplate(models.Model): + _inherit = 'product.template' + + is_contract = fields.Boolean('Is a contract') + contract_template_id = fields.Many2one( + comodel_name='account.analytic.contract', + string='Contract Template', + ) + + @api.onchange('is_contract') + def _change_is_contract(self): + """ Clear the relation to contract_template_id when downgrading + product from contract + """ + if not self.is_contract: + self.contract_template_id = False diff --git a/product_contract/models/sale_order.py b/product_contract/models/sale_order.py new file mode 100644 index 0000000000..45f2eec77d --- /dev/null +++ b/product_contract/models/sale_order.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, models + + +class SaleOrder(models.Model): + _inherit = 'sale.order' + + @api.multi + def action_confirm(self): + """ If we have a contract in the order, set it up """ + for rec in self: + order_lines = self.mapped('order_line').filtered( + lambda r: r.product_id.is_contract + ) + for line in order_lines: + contract_tmpl = line.product_id.contract_template_id + contract = self.env['account.analytic.account'].create({ + 'name': '%s Contract' % rec.name, + 'partner_id': rec.partner_id.id, + 'contract_template_id': contract_tmpl.id, + }) + line.contract_id = contract.id + contract.recurring_create_invoice() + return super(SaleOrder, self).action_confirm() diff --git a/product_contract/models/sale_order_line.py b/product_contract/models/sale_order_line.py new file mode 100644 index 0000000000..c76c10595b --- /dev/null +++ b/product_contract/models/sale_order_line.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class SaleOrderLine(models.Model): + _inherit = 'sale.order.line' + + contract_id = fields.Many2one( + comodel_name='account.analytic.account', + string='Contract' + ) diff --git a/product_contract/tests/__init__.py b/product_contract/tests/__init__.py new file mode 100644 index 0000000000..e5fbe249ab --- /dev/null +++ b/product_contract/tests/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import test_product_template +from . import test_sale_order diff --git a/product_contract/tests/test_product_template.py b/product_contract/tests/test_product_template.py new file mode 100644 index 0000000000..2938cc7f9f --- /dev/null +++ b/product_contract/tests/test_product_template.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo.tests.common import TransactionCase + + +class TestProductTemplate(TransactionCase): + + def setUp(self): + super(TestProductTemplate, self).setUp() + self.product = self.env.ref( + 'product.product_product_4_product_template' + ) + self.contract = self.env['account.analytic.contract'].create({ + 'name': 'Test', + 'recurring_rule_type': 'yearly', + 'recurring_interval': 12345, + }) + + def test_change_is_contract(self): + """ It should verify that the contract_template_id is removed + when is_contract is False """ + self.product.is_contract = True + self.product.contract_template_id = self.contract.id + self.product.is_contract = False + self.product._change_is_contract() + self.assertEquals( + len(self.product.contract_template_id), + 0 + ) diff --git a/product_contract/tests/test_sale_order.py b/product_contract/tests/test_sale_order.py new file mode 100644 index 0000000000..7ca4c09990 --- /dev/null +++ b/product_contract/tests/test_sale_order.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from mock import MagicMock +from odoo.tests.common import TransactionCase + + +class TestSaleOrder(TransactionCase): + + def setUp(self): + super(TestSaleOrder, self).setUp() + self.product = self.env.ref('product.product_product_1') + self.sale = self.env.ref('sale.sale_order_2') + self.contract = self.env['account.analytic.contract'].create({ + 'name': 'Test', + 'recurring_rule_type': 'yearly', + 'recurring_interval': 12345, + }) + self.product.product_tmpl_id.is_contract = True + self.product.product_tmpl_id.contract_template_id = self.contract.id + + def test_action_done(self): + """ It should create a contract when the sale for a contract is set + to done for the first time """ + self.env['account.analytic.account']._patch_method( + 'create', MagicMock() + ) + self.sale.action_confirm() + self.env['account.analytic.account'].create.assert_called_once_with({ + 'name': '%s Contract' % self.sale.name, + 'partner_id': self.sale.partner_id.id, + 'contract_template_id': self.contract.id, + }) diff --git a/product_contract/views/product_template_view.xml b/product_contract/views/product_template_view.xml new file mode 100644 index 0000000000..46c2e05c52 --- /dev/null +++ b/product_contract/views/product_template_view.xml @@ -0,0 +1,29 @@ + + + + + + + + account.invoice.select.contract + product.template + + + +
+ +
+
+ + + +
+
+ +
From c64c0ac1bd6f1ce229560c332c0bc2fba15b2e4e Mon Sep 17 00:00:00 2001 From: Dave Lasley Date: Thu, 25 May 2017 17:51:46 -0700 Subject: [PATCH 002/196] [FIX] product_contract: Fix mock usage in tests --- product_contract/i18n/es.po | 17 ++++---- product_contract/i18n/fi.po | 51 +++++++++++++++++++++++ product_contract/i18n/hi_IN.po | 51 +++++++++++++++++++++++ product_contract/i18n/hr.po | 14 +++---- product_contract/i18n/hr_HR.po | 16 +++---- product_contract/i18n/nl.po | 17 ++++---- product_contract/i18n/nl_NL.po | 51 +++++++++++++++++++++++ product_contract/i18n/pt.po | 51 +++++++++++++++++++++++ product_contract/i18n/pt_BR.po | 11 ++--- product_contract/i18n/ru.po | 51 +++++++++++++++++++++++ product_contract/i18n/tr.po | 51 +++++++++++++++++++++++ product_contract/tests/test_sale_order.py | 5 +++ 12 files changed, 350 insertions(+), 36 deletions(-) create mode 100644 product_contract/i18n/fi.po create mode 100644 product_contract/i18n/hi_IN.po create mode 100644 product_contract/i18n/nl_NL.po create mode 100644 product_contract/i18n/pt.po create mode 100644 product_contract/i18n/ru.po create mode 100644 product_contract/i18n/tr.po diff --git a/product_contract/i18n/es.po b/product_contract/i18n/es.po index a89a0a1226..d712dd0e0a 100644 --- a/product_contract/i18n/es.po +++ b/product_contract/i18n/es.po @@ -4,13 +4,14 @@ # # Translators: # OCA Transbot , 2017 +# enjolras , 2018 msgid "" msgstr "" "Project-Id-Version: Odoo Server 10.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-04-27 02:40+0000\n" -"PO-Revision-Date: 2017-04-27 02:40+0000\n" -"Last-Translator: OCA Transbot , 2017\n" +"POT-Creation-Date: 2018-02-10 03:15+0000\n" +"PO-Revision-Date: 2018-02-10 03:15+0000\n" +"Last-Translator: enjolras , 2018\n" "Language-Team: Spanish (https://www.transifex.com/oca/teams/23907/es/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -27,25 +28,25 @@ msgstr "Contrato" #: model:ir.model.fields,field_description:product_contract.field_product_product_contract_template_id #: model:ir.model.fields,field_description:product_contract.field_product_template_contract_template_id msgid "Contract Template" -msgstr "" +msgstr "Plantilla de contrato" #. module: product_contract #: model:ir.model.fields,field_description:product_contract.field_product_product_is_contract #: model:ir.model.fields,field_description:product_contract.field_product_template_is_contract msgid "Is a contract" -msgstr "" +msgstr "Es un contrato" #. module: product_contract #: model:ir.model,name:product_contract.model_product_template msgid "Product Template" -msgstr "" +msgstr "Plantilla de producto" #. module: product_contract #: model:ir.model,name:product_contract.model_sale_order msgid "Sales Order" -msgstr "" +msgstr "Pedido de venta" #. module: product_contract #: model:ir.model,name:product_contract.model_sale_order_line msgid "Sales Order Line" -msgstr "" +msgstr "Línea de pedido de venta" diff --git a/product_contract/i18n/fi.po b/product_contract/i18n/fi.po new file mode 100644 index 0000000000..9daef38d79 --- /dev/null +++ b/product_contract/i18n/fi.po @@ -0,0 +1,51 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_contract +# +# Translators: +# Jarmo Kortetjärvi , 2018 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-03-10 01:44+0000\n" +"PO-Revision-Date: 2018-03-10 01:44+0000\n" +"Last-Translator: Jarmo Kortetjärvi , 2018\n" +"Language-Team: Finnish (https://www.transifex.com/oca/teams/23907/fi/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: fi\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_sale_order_line_contract_id +msgid "Contract" +msgstr "Contract" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_contract_template_id +#: model:ir.model.fields,field_description:product_contract.field_product_template_contract_template_id +msgid "Contract Template" +msgstr "Sopimusmalli" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_is_contract +#: model:ir.model.fields,field_description:product_contract.field_product_template_is_contract +msgid "Is a contract" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_product_template +msgid "Product Template" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order_line +msgid "Sales Order Line" +msgstr "" diff --git a/product_contract/i18n/hi_IN.po b/product_contract/i18n/hi_IN.po new file mode 100644 index 0000000000..fe5f0e87df --- /dev/null +++ b/product_contract/i18n/hi_IN.po @@ -0,0 +1,51 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_contract +# +# Translators: +# Ashish Deshmukh , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-08-17 01:05+0000\n" +"PO-Revision-Date: 2017-08-17 01:05+0000\n" +"Last-Translator: Ashish Deshmukh , 2017\n" +"Language-Team: Hindi (India) (https://www.transifex.com/oca/teams/23907/hi_IN/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: hi_IN\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_sale_order_line_contract_id +msgid "Contract" +msgstr "अनुबंध" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_contract_template_id +#: model:ir.model.fields,field_description:product_contract.field_product_template_contract_template_id +msgid "Contract Template" +msgstr "अनुबंध टेम्पलेट" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_is_contract +#: model:ir.model.fields,field_description:product_contract.field_product_template_is_contract +msgid "Is a contract" +msgstr "एक अनुबंध है" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_product_template +msgid "Product Template" +msgstr "प्रोडक्ट टेम्पलेट" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order +msgid "Sales Order" +msgstr "बिक्री आदेश" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order_line +msgid "Sales Order Line" +msgstr "बिक्री आदेश पंक्ति" diff --git a/product_contract/i18n/hr.po b/product_contract/i18n/hr.po index 7f2b4f8c1e..2cfaafaeff 100644 --- a/product_contract/i18n/hr.po +++ b/product_contract/i18n/hr.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 10.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-04-27 02:40+0000\n" -"PO-Revision-Date: 2017-04-27 02:40+0000\n" +"POT-Creation-Date: 2018-02-10 03:15+0000\n" +"PO-Revision-Date: 2018-02-10 03:15+0000\n" "Last-Translator: Bole , 2017\n" "Language-Team: Croatian (https://www.transifex.com/oca/teams/23907/hr/)\n" "MIME-Version: 1.0\n" @@ -27,25 +27,25 @@ msgstr "Ugovor" #: model:ir.model.fields,field_description:product_contract.field_product_product_contract_template_id #: model:ir.model.fields,field_description:product_contract.field_product_template_contract_template_id msgid "Contract Template" -msgstr "" +msgstr "Predložak ugovora" #. module: product_contract #: model:ir.model.fields,field_description:product_contract.field_product_product_is_contract #: model:ir.model.fields,field_description:product_contract.field_product_template_is_contract msgid "Is a contract" -msgstr "" +msgstr "Je ugovor" #. module: product_contract #: model:ir.model,name:product_contract.model_product_template msgid "Product Template" -msgstr "" +msgstr "Predložak proizvoda" #. module: product_contract #: model:ir.model,name:product_contract.model_sale_order msgid "Sales Order" -msgstr "" +msgstr "Ponuda" #. module: product_contract #: model:ir.model,name:product_contract.model_sale_order_line msgid "Sales Order Line" -msgstr "" +msgstr "Stavka ponude" diff --git a/product_contract/i18n/hr_HR.po b/product_contract/i18n/hr_HR.po index 922d93fb16..1248f3f275 100644 --- a/product_contract/i18n/hr_HR.po +++ b/product_contract/i18n/hr_HR.po @@ -3,15 +3,15 @@ # * product_contract # # Translators: -# Bole , 2017 # OCA Transbot , 2017 +# Bole , 2017 msgid "" msgstr "" "Project-Id-Version: Odoo Server 10.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-04-27 02:40+0000\n" -"PO-Revision-Date: 2017-04-27 02:40+0000\n" -"Last-Translator: OCA Transbot , 2017\n" +"POT-Creation-Date: 2017-06-17 01:39+0000\n" +"PO-Revision-Date: 2017-06-17 01:39+0000\n" +"Last-Translator: Bole , 2017\n" "Language-Team: Croatian (Croatia) (https://www.transifex.com/oca/teams/23907/hr_HR/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -34,19 +34,19 @@ msgstr "Predložak ugovora" #: model:ir.model.fields,field_description:product_contract.field_product_product_is_contract #: model:ir.model.fields,field_description:product_contract.field_product_template_is_contract msgid "Is a contract" -msgstr "" +msgstr "Je ugovor" #. module: product_contract #: model:ir.model,name:product_contract.model_product_template msgid "Product Template" -msgstr "" +msgstr "Predložak proizvoda" #. module: product_contract #: model:ir.model,name:product_contract.model_sale_order msgid "Sales Order" -msgstr "" +msgstr "Prodajni nalog" #. module: product_contract #: model:ir.model,name:product_contract.model_sale_order_line msgid "Sales Order Line" -msgstr "" +msgstr "Stavka prodajnog naloga" diff --git a/product_contract/i18n/nl.po b/product_contract/i18n/nl.po index 4c309a2502..bdc4ca24b9 100644 --- a/product_contract/i18n/nl.po +++ b/product_contract/i18n/nl.po @@ -4,13 +4,14 @@ # # Translators: # Erwin van der Ploeg , 2017 +# lfreeke , 2018 msgid "" msgstr "" "Project-Id-Version: Odoo Server 10.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-04-27 02:40+0000\n" -"PO-Revision-Date: 2017-04-27 02:40+0000\n" -"Last-Translator: Erwin van der Ploeg , 2017\n" +"POT-Creation-Date: 2018-01-06 03:17+0000\n" +"PO-Revision-Date: 2018-01-06 03:17+0000\n" +"Last-Translator: lfreeke , 2018\n" "Language-Team: Dutch (https://www.transifex.com/oca/teams/23907/nl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -27,25 +28,25 @@ msgstr "Contract" #: model:ir.model.fields,field_description:product_contract.field_product_product_contract_template_id #: model:ir.model.fields,field_description:product_contract.field_product_template_contract_template_id msgid "Contract Template" -msgstr "" +msgstr "Contractsjabloon" #. module: product_contract #: model:ir.model.fields,field_description:product_contract.field_product_product_is_contract #: model:ir.model.fields,field_description:product_contract.field_product_template_is_contract msgid "Is a contract" -msgstr "" +msgstr "Is een contract" #. module: product_contract #: model:ir.model,name:product_contract.model_product_template msgid "Product Template" -msgstr "" +msgstr "Productsjabloon" #. module: product_contract #: model:ir.model,name:product_contract.model_sale_order msgid "Sales Order" -msgstr "" +msgstr "Verkooporder" #. module: product_contract #: model:ir.model,name:product_contract.model_sale_order_line msgid "Sales Order Line" -msgstr "" +msgstr "Verkooporderregel" diff --git a/product_contract/i18n/nl_NL.po b/product_contract/i18n/nl_NL.po new file mode 100644 index 0000000000..31a87e0924 --- /dev/null +++ b/product_contract/i18n/nl_NL.po @@ -0,0 +1,51 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_contract +# +# Translators: +# Peter Hageman , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-06-09 01:10+0000\n" +"PO-Revision-Date: 2017-06-09 01:10+0000\n" +"Last-Translator: Peter Hageman , 2017\n" +"Language-Team: Dutch (Netherlands) (https://www.transifex.com/oca/teams/23907/nl_NL/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: nl_NL\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_sale_order_line_contract_id +msgid "Contract" +msgstr "Contract" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_contract_template_id +#: model:ir.model.fields,field_description:product_contract.field_product_template_contract_template_id +msgid "Contract Template" +msgstr "Contractsjabloon" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_is_contract +#: model:ir.model.fields,field_description:product_contract.field_product_template_is_contract +msgid "Is a contract" +msgstr "Is een contract" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_product_template +msgid "Product Template" +msgstr "Productsjabloon" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order +msgid "Sales Order" +msgstr "Verkooporder" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order_line +msgid "Sales Order Line" +msgstr "Verkooporderregel" diff --git a/product_contract/i18n/pt.po b/product_contract/i18n/pt.po new file mode 100644 index 0000000000..edcf09df85 --- /dev/null +++ b/product_contract/i18n/pt.po @@ -0,0 +1,51 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_contract +# +# Translators: +# Pedro Castro Silva , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-07-15 01:24+0000\n" +"PO-Revision-Date: 2017-07-15 01:24+0000\n" +"Last-Translator: Pedro Castro Silva , 2017\n" +"Language-Team: Portuguese (https://www.transifex.com/oca/teams/23907/pt/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: pt\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_sale_order_line_contract_id +msgid "Contract" +msgstr "Contrato" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_contract_template_id +#: model:ir.model.fields,field_description:product_contract.field_product_template_contract_template_id +msgid "Contract Template" +msgstr "Modelo de Contrato" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_is_contract +#: model:ir.model.fields,field_description:product_contract.field_product_template_is_contract +msgid "Is a contract" +msgstr "É um Contrato" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_product_template +msgid "Product Template" +msgstr "Modelo de Produto" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order +msgid "Sales Order" +msgstr "Encomenda de Venda" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order_line +msgid "Sales Order Line" +msgstr "Linha de Encomenda de Venda" diff --git a/product_contract/i18n/pt_BR.po b/product_contract/i18n/pt_BR.po index 592c6aaecb..cbe238852c 100644 --- a/product_contract/i18n/pt_BR.po +++ b/product_contract/i18n/pt_BR.po @@ -3,14 +3,15 @@ # * product_contract # # Translators: -# Albert Vonpupp , 2017 +# OCA Transbot , 2017 +# falexandresilva , 2017 msgid "" msgstr "" "Project-Id-Version: Odoo Server 10.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-04-27 02:40+0000\n" -"PO-Revision-Date: 2017-04-27 02:40+0000\n" -"Last-Translator: Albert Vonpupp , 2017\n" +"POT-Creation-Date: 2017-06-13 02:40+0000\n" +"PO-Revision-Date: 2017-06-13 02:40+0000\n" +"Last-Translator: falexandresilva , 2017\n" "Language-Team: Portuguese (Brazil) (https://www.transifex.com/oca/teams/23907/pt_BR/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -43,7 +44,7 @@ msgstr "" #. module: product_contract #: model:ir.model,name:product_contract.model_sale_order msgid "Sales Order" -msgstr "" +msgstr "Pedido de compras" #. module: product_contract #: model:ir.model,name:product_contract.model_sale_order_line diff --git a/product_contract/i18n/ru.po b/product_contract/i18n/ru.po new file mode 100644 index 0000000000..f78f50ad95 --- /dev/null +++ b/product_contract/i18n/ru.po @@ -0,0 +1,51 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_contract +# +# Translators: +# nek, 2018 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-03-17 03:26+0000\n" +"PO-Revision-Date: 2018-03-17 03:26+0000\n" +"Last-Translator: nek, 2018\n" +"Language-Team: Russian (https://www.transifex.com/oca/teams/23907/ru/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: ru\n" +"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_sale_order_line_contract_id +msgid "Contract" +msgstr "Договор" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_contract_template_id +#: model:ir.model.fields,field_description:product_contract.field_product_template_contract_template_id +msgid "Contract Template" +msgstr "Шаблон Договора" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_is_contract +#: model:ir.model.fields,field_description:product_contract.field_product_template_is_contract +msgid "Is a contract" +msgstr "Это Договор" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_product_template +msgid "Product Template" +msgstr "Шаблон Продукта" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order_line +msgid "Sales Order Line" +msgstr "" diff --git a/product_contract/i18n/tr.po b/product_contract/i18n/tr.po new file mode 100644 index 0000000000..9887463f68 --- /dev/null +++ b/product_contract/i18n/tr.po @@ -0,0 +1,51 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_contract +# +# Translators: +# Ediz Duman , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-05-13 09:15+0000\n" +"PO-Revision-Date: 2017-05-13 09:15+0000\n" +"Last-Translator: Ediz Duman , 2017\n" +"Language-Team: Turkish (https://www.transifex.com/oca/teams/23907/tr/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: tr\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_sale_order_line_contract_id +msgid "Contract" +msgstr "Sözleşme" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_contract_template_id +#: model:ir.model.fields,field_description:product_contract.field_product_template_contract_template_id +msgid "Contract Template" +msgstr "Sözleşme Şablonu" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_is_contract +#: model:ir.model.fields,field_description:product_contract.field_product_template_is_contract +msgid "Is a contract" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_product_template +msgid "Product Template" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order_line +msgid "Sales Order Line" +msgstr "" diff --git a/product_contract/tests/test_sale_order.py b/product_contract/tests/test_sale_order.py index 7ca4c09990..98b174ff53 100644 --- a/product_contract/tests/test_sale_order.py +++ b/product_contract/tests/test_sale_order.py @@ -20,6 +20,11 @@ def setUp(self): self.product.product_tmpl_id.is_contract = True self.product.product_tmpl_id.contract_template_id = self.contract.id + def tearDown(self): + self.env['account.analytic.account']._revert_method( + 'create', + ) + def test_action_done(self): """ It should create a contract when the sale for a contract is set to done for the first time """ From dd84dceacd860559aa28660d698a4c002f92260d Mon Sep 17 00:00:00 2001 From: Florent THOMAS Date: Sun, 1 Apr 2018 19:21:52 +0200 Subject: [PATCH 003/196] [FIX] contract_sale_generation: it doesn't create sales (#141) * Change the method called in the view * Complete the create_invoice method * Bump version + authoring * Correct bad call of method Small Documentation * Add super call in python test * FIX bad field names causing bad quantities in sale.order.line --- product_contract/i18n/de.po | 4 +- product_contract/i18n/es.po | 4 +- product_contract/i18n/fi.po | 4 +- product_contract/i18n/fr.po | 16 ++++---- product_contract/i18n/hi_IN.po | 7 ++-- product_contract/i18n/hr.po | 7 ++-- product_contract/i18n/hr_HR.po | 10 +++-- product_contract/i18n/it.po | 4 +- product_contract/i18n/nl.po | 4 +- product_contract/i18n/nl_NL.po | 7 ++-- product_contract/i18n/product_contract.pot | 47 ++++++++++++++++++++++ product_contract/i18n/pt.po | 4 +- product_contract/i18n/pt_BR.po | 7 ++-- product_contract/i18n/ru.po | 8 ++-- product_contract/i18n/tr.po | 16 ++++---- product_contract/i18n/tr_TR.po | 7 ++-- product_contract/tests/test_sale_order.py | 1 + 17 files changed, 108 insertions(+), 49 deletions(-) create mode 100644 product_contract/i18n/product_contract.pot diff --git a/product_contract/i18n/de.po b/product_contract/i18n/de.po index 3b62b5447b..00697a55d7 100644 --- a/product_contract/i18n/de.po +++ b/product_contract/i18n/de.po @@ -1,7 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * product_contract -# +# # Translators: # OCA Transbot , 2017 msgid "" @@ -12,10 +12,10 @@ msgstr "" "PO-Revision-Date: 2017-04-27 02:40+0000\n" "Last-Translator: OCA Transbot , 2017\n" "Language-Team: German (https://www.transifex.com/oca/teams/23907/de/)\n" +"Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: de\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. module: product_contract diff --git a/product_contract/i18n/es.po b/product_contract/i18n/es.po index d712dd0e0a..1fd8a1a181 100644 --- a/product_contract/i18n/es.po +++ b/product_contract/i18n/es.po @@ -1,7 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * product_contract -# +# # Translators: # OCA Transbot , 2017 # enjolras , 2018 @@ -13,10 +13,10 @@ msgstr "" "PO-Revision-Date: 2018-02-10 03:15+0000\n" "Last-Translator: enjolras , 2018\n" "Language-Team: Spanish (https://www.transifex.com/oca/teams/23907/es/)\n" +"Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: es\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. module: product_contract diff --git a/product_contract/i18n/fi.po b/product_contract/i18n/fi.po index 9daef38d79..3467c265a7 100644 --- a/product_contract/i18n/fi.po +++ b/product_contract/i18n/fi.po @@ -1,7 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * product_contract -# +# # Translators: # Jarmo Kortetjärvi , 2018 msgid "" @@ -12,10 +12,10 @@ msgstr "" "PO-Revision-Date: 2018-03-10 01:44+0000\n" "Last-Translator: Jarmo Kortetjärvi , 2018\n" "Language-Team: Finnish (https://www.transifex.com/oca/teams/23907/fi/)\n" +"Language: fi\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: fi\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. module: product_contract diff --git a/product_contract/i18n/fr.po b/product_contract/i18n/fr.po index 1efcaf7ce5..59f399b345 100644 --- a/product_contract/i18n/fr.po +++ b/product_contract/i18n/fr.po @@ -1,21 +1,23 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * product_contract -# +# # Translators: # leemannd , 2017 +# David BEAL, 2018 +# Fabien Bourgeois , 2018 msgid "" msgstr "" "Project-Id-Version: Odoo Server 10.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-04-27 02:40+0000\n" -"PO-Revision-Date: 2017-04-27 02:40+0000\n" -"Last-Translator: leemannd , 2017\n" +"POT-Creation-Date: 2018-05-19 02:01+0000\n" +"PO-Revision-Date: 2018-05-19 02:01+0000\n" +"Last-Translator: Fabien Bourgeois , 2018\n" "Language-Team: French (https://www.transifex.com/oca/teams/23907/fr/)\n" +"Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: fr\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #. module: product_contract @@ -27,7 +29,7 @@ msgstr "Contrat" #: model:ir.model.fields,field_description:product_contract.field_product_product_contract_template_id #: model:ir.model.fields,field_description:product_contract.field_product_template_contract_template_id msgid "Contract Template" -msgstr "" +msgstr "Modèle de contrat" #. module: product_contract #: model:ir.model.fields,field_description:product_contract.field_product_product_is_contract @@ -43,7 +45,7 @@ msgstr "" #. module: product_contract #: model:ir.model,name:product_contract.model_sale_order msgid "Sales Order" -msgstr "" +msgstr "Vente" #. module: product_contract #: model:ir.model,name:product_contract.model_sale_order_line diff --git a/product_contract/i18n/hi_IN.po b/product_contract/i18n/hi_IN.po index fe5f0e87df..e55f00ea69 100644 --- a/product_contract/i18n/hi_IN.po +++ b/product_contract/i18n/hi_IN.po @@ -1,7 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * product_contract -# +# # Translators: # Ashish Deshmukh , 2017 msgid "" @@ -11,11 +11,12 @@ msgstr "" "POT-Creation-Date: 2017-08-17 01:05+0000\n" "PO-Revision-Date: 2017-08-17 01:05+0000\n" "Last-Translator: Ashish Deshmukh , 2017\n" -"Language-Team: Hindi (India) (https://www.transifex.com/oca/teams/23907/hi_IN/)\n" +"Language-Team: Hindi (India) (https://www.transifex.com/oca/teams/23907/" +"hi_IN/)\n" +"Language: hi_IN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: hi_IN\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. module: product_contract diff --git a/product_contract/i18n/hr.po b/product_contract/i18n/hr.po index 2cfaafaeff..5ac9378959 100644 --- a/product_contract/i18n/hr.po +++ b/product_contract/i18n/hr.po @@ -1,7 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * product_contract -# +# # Translators: # Bole , 2017 msgid "" @@ -12,11 +12,12 @@ msgstr "" "PO-Revision-Date: 2018-02-10 03:15+0000\n" "Last-Translator: Bole , 2017\n" "Language-Team: Croatian (https://www.transifex.com/oca/teams/23907/hr/)\n" +"Language: hr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: hr\n" -"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" #. module: product_contract #: model:ir.model.fields,field_description:product_contract.field_sale_order_line_contract_id diff --git a/product_contract/i18n/hr_HR.po b/product_contract/i18n/hr_HR.po index 1248f3f275..2766a63166 100644 --- a/product_contract/i18n/hr_HR.po +++ b/product_contract/i18n/hr_HR.po @@ -1,7 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * product_contract -# +# # Translators: # OCA Transbot , 2017 # Bole , 2017 @@ -12,12 +12,14 @@ msgstr "" "POT-Creation-Date: 2017-06-17 01:39+0000\n" "PO-Revision-Date: 2017-06-17 01:39+0000\n" "Last-Translator: Bole , 2017\n" -"Language-Team: Croatian (Croatia) (https://www.transifex.com/oca/teams/23907/hr_HR/)\n" +"Language-Team: Croatian (Croatia) (https://www.transifex.com/oca/teams/23907/" +"hr_HR/)\n" +"Language: hr_HR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: hr_HR\n" -"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" #. module: product_contract #: model:ir.model.fields,field_description:product_contract.field_sale_order_line_contract_id diff --git a/product_contract/i18n/it.po b/product_contract/i18n/it.po index 982bdf76c5..010897f63b 100644 --- a/product_contract/i18n/it.po +++ b/product_contract/i18n/it.po @@ -1,7 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * product_contract -# +# # Translators: # Lorenzo Battistini , 2017 msgid "" @@ -12,10 +12,10 @@ msgstr "" "PO-Revision-Date: 2017-04-27 02:40+0000\n" "Last-Translator: Lorenzo Battistini , 2017\n" "Language-Team: Italian (https://www.transifex.com/oca/teams/23907/it/)\n" +"Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: it\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. module: product_contract diff --git a/product_contract/i18n/nl.po b/product_contract/i18n/nl.po index bdc4ca24b9..4c8db3eb93 100644 --- a/product_contract/i18n/nl.po +++ b/product_contract/i18n/nl.po @@ -1,7 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * product_contract -# +# # Translators: # Erwin van der Ploeg , 2017 # lfreeke , 2018 @@ -13,10 +13,10 @@ msgstr "" "PO-Revision-Date: 2018-01-06 03:17+0000\n" "Last-Translator: lfreeke , 2018\n" "Language-Team: Dutch (https://www.transifex.com/oca/teams/23907/nl/)\n" +"Language: nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: nl\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. module: product_contract diff --git a/product_contract/i18n/nl_NL.po b/product_contract/i18n/nl_NL.po index 31a87e0924..517781e3ed 100644 --- a/product_contract/i18n/nl_NL.po +++ b/product_contract/i18n/nl_NL.po @@ -1,7 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * product_contract -# +# # Translators: # Peter Hageman , 2017 msgid "" @@ -11,11 +11,12 @@ msgstr "" "POT-Creation-Date: 2017-06-09 01:10+0000\n" "PO-Revision-Date: 2017-06-09 01:10+0000\n" "Last-Translator: Peter Hageman , 2017\n" -"Language-Team: Dutch (Netherlands) (https://www.transifex.com/oca/teams/23907/nl_NL/)\n" +"Language-Team: Dutch (Netherlands) (https://www.transifex.com/oca/" +"teams/23907/nl_NL/)\n" +"Language: nl_NL\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: nl_NL\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. module: product_contract diff --git a/product_contract/i18n/product_contract.pot b/product_contract/i18n/product_contract.pot new file mode 100644 index 0000000000..154aed4dc6 --- /dev/null +++ b/product_contract/i18n/product_contract.pot @@ -0,0 +1,47 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_contract +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.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: product_contract +#: model:ir.model.fields,field_description:product_contract.field_sale_order_line_contract_id +msgid "Contract" +msgstr "" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_contract_template_id +#: model:ir.model.fields,field_description:product_contract.field_product_template_contract_template_id +msgid "Contract Template" +msgstr "" + +#. module: product_contract +#: model:ir.model.fields,field_description:product_contract.field_product_product_is_contract +#: model:ir.model.fields,field_description:product_contract.field_product_template_is_contract +msgid "Is a contract" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_product_template +msgid "Product Template" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: product_contract +#: model:ir.model,name:product_contract.model_sale_order_line +msgid "Sales Order Line" +msgstr "" + diff --git a/product_contract/i18n/pt.po b/product_contract/i18n/pt.po index edcf09df85..0ca84dea84 100644 --- a/product_contract/i18n/pt.po +++ b/product_contract/i18n/pt.po @@ -1,7 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * product_contract -# +# # Translators: # Pedro Castro Silva , 2017 msgid "" @@ -12,10 +12,10 @@ msgstr "" "PO-Revision-Date: 2017-07-15 01:24+0000\n" "Last-Translator: Pedro Castro Silva , 2017\n" "Language-Team: Portuguese (https://www.transifex.com/oca/teams/23907/pt/)\n" +"Language: pt\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: pt\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. module: product_contract diff --git a/product_contract/i18n/pt_BR.po b/product_contract/i18n/pt_BR.po index cbe238852c..75fd5d80e4 100644 --- a/product_contract/i18n/pt_BR.po +++ b/product_contract/i18n/pt_BR.po @@ -1,7 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * product_contract -# +# # Translators: # OCA Transbot , 2017 # falexandresilva , 2017 @@ -12,11 +12,12 @@ msgstr "" "POT-Creation-Date: 2017-06-13 02:40+0000\n" "PO-Revision-Date: 2017-06-13 02:40+0000\n" "Last-Translator: falexandresilva , 2017\n" -"Language-Team: Portuguese (Brazil) (https://www.transifex.com/oca/teams/23907/pt_BR/)\n" +"Language-Team: Portuguese (Brazil) (https://www.transifex.com/oca/" +"teams/23907/pt_BR/)\n" +"Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: pt_BR\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #. module: product_contract diff --git a/product_contract/i18n/ru.po b/product_contract/i18n/ru.po index f78f50ad95..bc7bdcbb6c 100644 --- a/product_contract/i18n/ru.po +++ b/product_contract/i18n/ru.po @@ -1,7 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * product_contract -# +# # Translators: # nek, 2018 msgid "" @@ -12,11 +12,13 @@ msgstr "" "PO-Revision-Date: 2018-03-17 03:26+0000\n" "Last-Translator: nek, 2018\n" "Language-Team: Russian (https://www.transifex.com/oca/teams/23907/ru/)\n" +"Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: ru\n" -"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n" +"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n" +"%100>=11 && n%100<=14)? 2 : 3);\n" #. module: product_contract #: model:ir.model.fields,field_description:product_contract.field_sale_order_line_contract_id diff --git a/product_contract/i18n/tr.po b/product_contract/i18n/tr.po index 9887463f68..03f07329f4 100644 --- a/product_contract/i18n/tr.po +++ b/product_contract/i18n/tr.po @@ -1,21 +1,21 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * product_contract -# +# # Translators: # Ediz Duman , 2017 msgid "" msgstr "" "Project-Id-Version: Odoo Server 10.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-05-13 09:15+0000\n" -"PO-Revision-Date: 2017-05-13 09:15+0000\n" +"POT-Creation-Date: 2018-04-21 01:48+0000\n" +"PO-Revision-Date: 2018-04-21 01:48+0000\n" "Last-Translator: Ediz Duman , 2017\n" "Language-Team: Turkish (https://www.transifex.com/oca/teams/23907/tr/)\n" +"Language: tr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: tr\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #. module: product_contract @@ -33,19 +33,19 @@ msgstr "Sözleşme Şablonu" #: model:ir.model.fields,field_description:product_contract.field_product_product_is_contract #: model:ir.model.fields,field_description:product_contract.field_product_template_is_contract msgid "Is a contract" -msgstr "" +msgstr "Sözleşmeli" #. module: product_contract #: model:ir.model,name:product_contract.model_product_template msgid "Product Template" -msgstr "" +msgstr "Ürün Şablonu" #. module: product_contract #: model:ir.model,name:product_contract.model_sale_order msgid "Sales Order" -msgstr "" +msgstr "Satış Siparişi" #. module: product_contract #: model:ir.model,name:product_contract.model_sale_order_line msgid "Sales Order Line" -msgstr "" +msgstr "Satış Sipariş Satırı" diff --git a/product_contract/i18n/tr_TR.po b/product_contract/i18n/tr_TR.po index 5bcb2f7edd..7c93e6e4f9 100644 --- a/product_contract/i18n/tr_TR.po +++ b/product_contract/i18n/tr_TR.po @@ -1,7 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * product_contract -# +# # Translators: # Ediz Duman , 2017 msgid "" @@ -11,11 +11,12 @@ msgstr "" "POT-Creation-Date: 2017-04-27 02:40+0000\n" "PO-Revision-Date: 2017-04-27 02:40+0000\n" "Last-Translator: Ediz Duman , 2017\n" -"Language-Team: Turkish (Turkey) (https://www.transifex.com/oca/teams/23907/tr_TR/)\n" +"Language-Team: Turkish (Turkey) (https://www.transifex.com/oca/teams/23907/" +"tr_TR/)\n" +"Language: tr_TR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: tr_TR\n" "Plural-Forms: nplurals=1; plural=0;\n" #. module: product_contract diff --git a/product_contract/tests/test_sale_order.py b/product_contract/tests/test_sale_order.py index 98b174ff53..61f33858c2 100644 --- a/product_contract/tests/test_sale_order.py +++ b/product_contract/tests/test_sale_order.py @@ -24,6 +24,7 @@ def tearDown(self): self.env['account.analytic.account']._revert_method( 'create', ) + super(TestSaleOrder, self).tearDown() def test_action_done(self): """ It should create a contract when the sale for a contract is set From 0bc5c73503eb2caf9e72d96a677662098dc85f70 Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Wed, 31 Oct 2018 16:21:51 +0100 Subject: [PATCH 004/196] [MIG] - Product Contract Migration to 12.0 --- product_contract/__manifest__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/product_contract/__manifest__.py b/product_contract/__manifest__.py index e1d4d8ac65..b5b5cac556 100644 --- a/product_contract/__manifest__.py +++ b/product_contract/__manifest__.py @@ -1,13 +1,15 @@ # -*- coding: utf-8 -*- # Copyright 2017 LasLabs Inc. +# Copyright 2018 ACSONE SA/NV. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { 'name': 'Product Contract', - 'version': '10.0.1.0.0', + 'version': '12.0.1.0.0', 'category': 'Contract Management', 'license': 'AGPL-3', 'author': "LasLabs, " + "ACSONE SA/NV, " "Odoo Community Association (OCA)", 'website': 'https://laslabs.com', 'depends': [ From 4e815cb450bc32bc646a52c0c464227112d6aa01 Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Wed, 31 Oct 2018 16:23:34 +0100 Subject: [PATCH 005/196] [IMP] - Product with is_contract can be only of type service --- product_contract/models/product_template.py | 15 ++++++-- product_contract/tests/__init__.py | 2 +- product_contract/tests/test_product.py | 34 +++++++++++++++++++ .../tests/test_product_template.py | 31 ----------------- product_contract/tests/test_sale_order.py | 2 -- 5 files changed, 47 insertions(+), 37 deletions(-) create mode 100644 product_contract/tests/test_product.py delete mode 100644 product_contract/tests/test_product_template.py diff --git a/product_contract/models/product_template.py b/product_contract/models/product_template.py index fd7e00d4c8..1016c60c87 100644 --- a/product_contract/models/product_template.py +++ b/product_contract/models/product_template.py @@ -1,8 +1,10 @@ # -*- coding: utf-8 -*- # Copyright 2017 LasLabs Inc. +# Copyright 2018 ACSONE SA/NV. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from odoo import api, fields, models +from odoo import api, fields, models, _ +from odoo.exceptions import ValidationError class ProductTemplate(models.Model): @@ -10,8 +12,7 @@ class ProductTemplate(models.Model): is_contract = fields.Boolean('Is a contract') contract_template_id = fields.Many2one( - comodel_name='account.analytic.contract', - string='Contract Template', + comodel_name='account.analytic.contract', string='Contract Template' ) @api.onchange('is_contract') @@ -21,3 +22,11 @@ def _change_is_contract(self): """ if not self.is_contract: self.contract_template_id = False + + @api.constrains('is_contract', 'type') + def _check_contract_product_type(self): + """ + Contract product should be service type + """ + if self.is_contract and self.type != 'service': + raise ValidationError(_("Contract product should be service type")) diff --git a/product_contract/tests/__init__.py b/product_contract/tests/__init__.py index e5fbe249ab..b4af49c0af 100644 --- a/product_contract/tests/__init__.py +++ b/product_contract/tests/__init__.py @@ -2,5 +2,5 @@ # Copyright 2017 LasLabs Inc. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from . import test_product_template +from . import test_product from . import test_sale_order diff --git a/product_contract/tests/test_product.py b/product_contract/tests/test_product.py new file mode 100644 index 0000000000..3676dc057c --- /dev/null +++ b/product_contract/tests/test_product.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# Copyright 2018 ACSONE SA/NV. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo.tests.common import TransactionCase +from odoo.exceptions import ValidationError + + +class TestProductTemplate(TransactionCase): + def setUp(self): + super(TestProductTemplate, self).setUp() + self.service_product = self.env.ref('product.product_product_1') + self.consu_product = self.env.ref('product.product_product_5') + self.contract = self.env['account.analytic.contract'].create( + {'name': 'Test'} + ) + + def test_change_is_contract(self): + """ It should verify that the contract_template_id is removed + when is_contract is False """ + self.service_product.is_contract = True + self.service_product.contract_template_id = self.contract.id + self.service_product.is_contract = False + self.service_product.product_tmpl_id._change_is_contract() + self.assertEquals(len(self.service_product.contract_template_id), 0) + + def test_check_contract_product_type(self): + """ + It should raise ValidationError on change of is_contract to True + for consu product + """ + with self.assertRaises(ValidationError): + self.consu_product.is_contract = True diff --git a/product_contract/tests/test_product_template.py b/product_contract/tests/test_product_template.py deleted file mode 100644 index 2938cc7f9f..0000000000 --- a/product_contract/tests/test_product_template.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2017 LasLabs Inc. -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - -from odoo.tests.common import TransactionCase - - -class TestProductTemplate(TransactionCase): - - def setUp(self): - super(TestProductTemplate, self).setUp() - self.product = self.env.ref( - 'product.product_product_4_product_template' - ) - self.contract = self.env['account.analytic.contract'].create({ - 'name': 'Test', - 'recurring_rule_type': 'yearly', - 'recurring_interval': 12345, - }) - - def test_change_is_contract(self): - """ It should verify that the contract_template_id is removed - when is_contract is False """ - self.product.is_contract = True - self.product.contract_template_id = self.contract.id - self.product.is_contract = False - self.product._change_is_contract() - self.assertEquals( - len(self.product.contract_template_id), - 0 - ) diff --git a/product_contract/tests/test_sale_order.py b/product_contract/tests/test_sale_order.py index 61f33858c2..f8a90e3489 100644 --- a/product_contract/tests/test_sale_order.py +++ b/product_contract/tests/test_sale_order.py @@ -14,8 +14,6 @@ def setUp(self): self.sale = self.env.ref('sale.sale_order_2') self.contract = self.env['account.analytic.contract'].create({ 'name': 'Test', - 'recurring_rule_type': 'yearly', - 'recurring_interval': 12345, }) self.product.product_tmpl_id.is_contract = True self.product.product_tmpl_id.contract_template_id = self.contract.id From 1ad3de1908f7aa9aa458c91621ef26efb8ceba29 Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Wed, 31 Oct 2018 17:07:47 +0100 Subject: [PATCH 006/196] [IMP] - Change dependencies to contract_sale --- product_contract/__manifest__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/product_contract/__manifest__.py b/product_contract/__manifest__.py index b5b5cac556..970265ff3a 100644 --- a/product_contract/__manifest__.py +++ b/product_contract/__manifest__.py @@ -13,9 +13,8 @@ "Odoo Community Association (OCA)", 'website': 'https://laslabs.com', 'depends': [ - 'contract', 'product', - 'sale', + 'contract_sale', ], 'data': [ 'views/product_template_view.xml', From 2f1f6743fa1bae07339a7631edf09da840ef34aa Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Wed, 31 Oct 2018 18:10:08 +0100 Subject: [PATCH 007/196] [IMP] - Add recurrence fields to product template and sale order line --- product_contract/__manifest__.py | 3 +- product_contract/models/product_template.py | 24 +++++++ product_contract/models/sale_order.py | 12 +++- product_contract/models/sale_order_line.py | 49 ++++++++++++++- product_contract/tests/__init__.py | 2 + product_contract/tests/test_sale_order.py | 2 + .../tests/test_sale_order_line.py | 37 +++++++++++ product_contract/views/product_template.xml | 44 +++++++++++++ .../views/product_template_view.xml | 29 --------- product_contract/views/sale_order.xml | 63 +++++++++++++++++++ 10 files changed, 231 insertions(+), 34 deletions(-) create mode 100644 product_contract/tests/test_sale_order_line.py create mode 100644 product_contract/views/product_template.xml delete mode 100644 product_contract/views/product_template_view.xml create mode 100644 product_contract/views/sale_order.xml diff --git a/product_contract/__manifest__.py b/product_contract/__manifest__.py index 970265ff3a..f369b8251f 100644 --- a/product_contract/__manifest__.py +++ b/product_contract/__manifest__.py @@ -17,7 +17,8 @@ 'contract_sale', ], 'data': [ - 'views/product_template_view.xml', + 'views/product_template.xml', + 'views/sale_order.xml', ], 'installable': True, 'application': False, diff --git a/product_contract/models/product_template.py b/product_contract/models/product_template.py index 1016c60c87..1d25620197 100644 --- a/product_contract/models/product_template.py +++ b/product_contract/models/product_template.py @@ -15,6 +15,30 @@ class ProductTemplate(models.Model): comodel_name='account.analytic.contract', string='Contract Template' ) + recurring_rule_type = fields.Selection( + [ + ('daily', 'Day(s)'), + ('weekly', 'Week(s)'), + ('monthly', 'Month(s)'), + ('monthlylastday', 'Month(s) last day'), + ('yearly', 'Year(s)'), + ], + default='monthly', + string='Recurrence', + help="Specify Interval for automatic invoice generation.", + ) + recurring_invoicing_type = fields.Selection( + [('pre-paid', 'Pre-paid'), ('post-paid', 'Post-paid')], + default='pre-paid', + string='Invoicing type', + help="Specify if process date is 'from' or 'to' invoicing date", + ) + recurring_interval = fields.Integer( + default=1, + string='Repeat Every', + help="Repeat every (Days/Week/Month/Year)", + ) + @api.onchange('is_contract') def _change_is_contract(self): """ Clear the relation to contract_template_id when downgrading diff --git a/product_contract/models/sale_order.py b/product_contract/models/sale_order.py index 45f2eec77d..443eaaff72 100644 --- a/product_contract/models/sale_order.py +++ b/product_contract/models/sale_order.py @@ -2,12 +2,22 @@ # Copyright 2017 LasLabs Inc. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from odoo import api, models +from odoo import fields, api, models class SaleOrder(models.Model): _inherit = 'sale.order' + is_contract = fields.Boolean( + string='Is a contract', compute="_compute_is_contract" + ) + + @api.depends('order_line') + def _compute_is_contract(self): + self.is_contract = any( + self.order_line.mapped('is_contract') + ) + @api.multi def action_confirm(self): """ If we have a contract in the order, set it up """ diff --git a/product_contract/models/sale_order_line.py b/product_contract/models/sale_order_line.py index c76c10595b..23da9c1c9c 100644 --- a/product_contract/models/sale_order_line.py +++ b/product_contract/models/sale_order_line.py @@ -1,14 +1,57 @@ # -*- coding: utf-8 -*- # Copyright 2017 LasLabs Inc. +# Copyright 2017 ACSONE SA/NV. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from odoo import fields, models +from odoo import api, fields, models class SaleOrderLine(models.Model): _inherit = 'sale.order.line' + is_contract = fields.Boolean( + string='Is a contract', related="product_id.is_contract" + ) contract_id = fields.Many2one( - comodel_name='account.analytic.account', - string='Contract' + comodel_name='account.analytic.account', string='Contract' + ) + recurring_rule_type = fields.Selection( + [ + ('daily', 'Day(s)'), + ('weekly', 'Week(s)'), + ('monthly', 'Month(s)'), + ('monthlylastday', 'Month(s) last day'), + ('yearly', 'Year(s)'), + ], + default='monthly', + string='Recurrence', + help="Specify Interval for automatic invoice generation.", + copy=False, + ) + recurring_invoicing_type = fields.Selection( + [('pre-paid', 'Pre-paid'), ('post-paid', 'Post-paid')], + default='pre-paid', + string='Invoicing type', + help="Specify if process date is 'from' or 'to' invoicing date", + copy=False, ) + recurring_interval = fields.Integer( + default=1, + string='Repeat Every', + help="Repeat every (Days/Week/Month/Year)", + copy=False, + ) + date_start = fields.Date(string='Date Start', default=fields.Date.today()) + date_end = fields.Date(string='Date End', index=True) + recurring_next_date = fields.Date( + default=fields.Date.today(), copy=False, string='Date of Next Invoice' + ) + + @api.onchange('product_id') + def onchange_product(self): + if self.product_id.is_contract: + self.recurring_rule_type = self.product_id.recurring_rule_type + self.recurring_invoicing_type = ( + self.product_id.recurring_invoicing_type + ) + self.recurring_interval = self.product_id.recurring_interval diff --git a/product_contract/tests/__init__.py b/product_contract/tests/__init__.py index b4af49c0af..4766d5eabe 100644 --- a/product_contract/tests/__init__.py +++ b/product_contract/tests/__init__.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- # Copyright 2017 LasLabs Inc. +# Copyright 2018 ACSONE SA/NV. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from . import test_product from . import test_sale_order +from . import test_sale_order_line diff --git a/product_contract/tests/test_sale_order.py b/product_contract/tests/test_sale_order.py index f8a90e3489..e8f666020f 100644 --- a/product_contract/tests/test_sale_order.py +++ b/product_contract/tests/test_sale_order.py @@ -27,6 +27,8 @@ def tearDown(self): def test_action_done(self): """ It should create a contract when the sale for a contract is set to done for the first time """ + + self.assertTrue(self.sale.is_contract) self.env['account.analytic.account']._patch_method( 'create', MagicMock() ) diff --git a/product_contract/tests/test_sale_order_line.py b/product_contract/tests/test_sale_order_line.py new file mode 100644 index 0000000000..1f10905990 --- /dev/null +++ b/product_contract/tests/test_sale_order_line.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Copyright 2018 ACSONE SA/NV. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from mock import MagicMock +from odoo.tests.common import TransactionCase + + +class TestSaleOrder(TransactionCase): + + def setUp(self): + super(TestSaleOrder, self).setUp() + self.product = self.env.ref('product.product_product_1') + self.sale = self.env.ref('sale.sale_order_2') + self.contract = self.env['account.analytic.contract'].create({ + 'name': 'Test', + }) + self.product.product_tmpl_id.is_contract = True + self.sale_order_line = self.sale.order_line.filtered( + lambda l: l.product_id == self.product + ) + + def test_onchange_product(self): + """ It should get recurrence invoicing info to the sale line from + its product """ + self.assertEqual( + self.sale_order_line.recurring_rule_type, + self.product.recurring_rule_type + ) + self.assertEqual( + self.sale_order_line.recurring_interval, + self.product.recurring_interval + ) + self.assertEqual( + self.sale_order_line.recurring_invoicing_type, + self.product.recurring_invoicing_type + ) diff --git a/product_contract/views/product_template.xml b/product_contract/views/product_template.xml new file mode 100644 index 0000000000..9eb3b38fc8 --- /dev/null +++ b/product_contract/views/product_template.xml @@ -0,0 +1,44 @@ + + + + + + + + account.invoice.select.contract + product.template + + + +
+ +
+
+ + + + + +
+
+ +
diff --git a/product_contract/views/product_template_view.xml b/product_contract/views/product_template_view.xml deleted file mode 100644 index 46c2e05c52..0000000000 --- a/product_contract/views/product_template_view.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - account.invoice.select.contract - product.template - - - -
- -
-
- - - -
-
- -
diff --git a/product_contract/views/sale_order.xml b/product_contract/views/sale_order.xml new file mode 100644 index 0000000000..f27c40250b --- /dev/null +++ b/product_contract/views/sale_order.xml @@ -0,0 +1,63 @@ + + + + + + + + sale.order.form (in product_contract) + sale.order + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 8c826fbe4e856ccab2dad8e50eddffdb7fe4a84c Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Fri, 2 Nov 2018 14:34:05 +0100 Subject: [PATCH 008/196] [IMP] - Create contract on sale order confirmation - On Sale Order confirmation, a contract is created for each contract template used on sale order lines - A not finished contract can be mentioned on sale order line - A sale order line linked to a contract will update it and don't create a new one if it had the same template --- product_contract/models/sale_order.py | 65 ++++++++--- product_contract/models/sale_order_line.py | 62 ++++++++++- product_contract/tests/__init__.py | 1 - product_contract/tests/test_sale_order.py | 102 +++++++++++++----- .../tests/test_sale_order_line.py | 37 ------- product_contract/views/sale_order.xml | 18 ++++ 6 files changed, 205 insertions(+), 80 deletions(-) delete mode 100644 product_contract/tests/test_sale_order_line.py diff --git a/product_contract/models/sale_order.py b/product_contract/models/sale_order.py index 443eaaff72..4b356f320e 100644 --- a/product_contract/models/sale_order.py +++ b/product_contract/models/sale_order.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # Copyright 2017 LasLabs Inc. +# Copyright 2018 ACSONE SA/NV. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from odoo import fields, api, models @@ -9,29 +10,61 @@ class SaleOrder(models.Model): _inherit = 'sale.order' is_contract = fields.Boolean( - string='Is a contract', compute="_compute_is_contract" + string='Is a contract', compute='_compute_is_contract' ) + contract_count = fields.Integer(compute='_compute_contract_count') @api.depends('order_line') def _compute_is_contract(self): - self.is_contract = any( - self.order_line.mapped('is_contract') - ) + self.is_contract = any(self.order_line.mapped('is_contract')) @api.multi def action_confirm(self): """ If we have a contract in the order, set it up """ - for rec in self: - order_lines = self.mapped('order_line').filtered( - lambda r: r.product_id.is_contract + contract_env = self.env['account.analytic.account'] + for rec in self.filtered('is_contract'): + line_to_create_contract = rec.order_line.filtered( + lambda r: not r.contract_id ) - for line in order_lines: - contract_tmpl = line.product_id.contract_template_id - contract = self.env['account.analytic.account'].create({ - 'name': '%s Contract' % rec.name, - 'partner_id': rec.partner_id.id, - 'contract_template_id': contract_tmpl.id, - }) - line.contract_id = contract.id - contract.recurring_create_invoice() + for contract_template in line_to_create_contract.mapped( + 'product_id.contract_template_id' + ): + order_lines = line_to_create_contract.filtered( + lambda r: r.product_id.contract_template_id + == contract_template + ) + contract = contract_env.create( + { + 'name': '{template_name}: {sale_name}'.format( + template_name=contract_template.name, + sale_name=rec.name, + ), + 'partner_id': rec.partner_id.id, + 'recurring_invoices': True, + 'contract_template_id': contract_template.id, + } + ) + contract._onchange_contract_template_id() + order_lines.create_contract_line(contract) + order_lines.write({'contract_id': contract.id}) + line_to_update_contract = rec.order_line.filtered('contract_id') + for line in line_to_update_contract: + line.create_contract_line(line.contract_id) return super(SaleOrder, self).action_confirm() + + @api.multi + @api.depends("order_line") + def _compute_contract_count(self): + for rec in self: + rec.contract_count = len(rec.order_line.mapped('contract_id')) + + @api.multi + def action_show_contracts(self): + self.ensure_one() + action = self.env.ref( + "contract.action_account_analytic_sale_overdue_all" + ).read()[0] + action["domain"] = [ + ("id", "in", self.order_line.mapped('contract_id').ids) + ] + return action diff --git a/product_contract/models/sale_order_line.py b/product_contract/models/sale_order_line.py index 23da9c1c9c..b275e67e9b 100644 --- a/product_contract/models/sale_order_line.py +++ b/product_contract/models/sale_order_line.py @@ -3,7 +3,8 @@ # Copyright 2017 ACSONE SA/NV. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from odoo import api, fields, models +from odoo import api, fields, models, _ +from odoo.exceptions import ValidationError class SaleOrderLine(models.Model): @@ -13,7 +14,13 @@ class SaleOrderLine(models.Model): string='Is a contract', related="product_id.is_contract" ) contract_id = fields.Many2one( - comodel_name='account.analytic.account', string='Contract' + comodel_name='account.analytic.account', string='Contract', copy=False + ) + contract_template_id = fields.Many2one( + comodel_name='account.analytic.contract', + string='Contract Template', + related='product_id.product_tmpl_id.contract_template_id', + readonly=True ) recurring_rule_type = fields.Selection( [ @@ -55,3 +62,54 @@ def onchange_product(self): self.product_id.recurring_invoicing_type ) self.recurring_interval = self.product_id.recurring_interval + + @api.multi + def _prepare_contract_line_values(self, contract): + self.ensure_one() + return { + 'sequence': self.sequence, + 'product_id': self.product_id.id, + 'name': self.name, + 'quantity': self.product_uom_qty, + 'uom_id': self.product_uom.id, + 'price_unit': self.price_unit, + 'discount': self.discount, + 'recurring_next_date': self.recurring_next_date + or fields.Date.today(), + 'date_end': self.date_end, + 'date_start': self.date_start or fields.Date.today(), + 'recurring_interval': self.recurring_interval, + 'recurring_invoicing_type': self.recurring_invoicing_type, + 'recurring_rule_type': self.recurring_rule_type, + 'contract_id': contract.id, + } + + @api.multi + def create_contract_line(self, contract): + contract_line = self.env['account.analytic.invoice.line'] + for rec in self: + contract_line.create(rec._prepare_contract_line_values(contract)) + + @api.constrains('contract_id') + def _check_contract_sale_partner(self): + for rec in self: + if rec.contract_id: + if rec.order_id.partner_id != rec.contract_id.partner_id: + raise ValidationError( + _( + "Sale Order and contract should be " + "linked to the same partner" + ) + ) + + @api.constrains('product_id', 'contract_id') + def _check_contract_sale_contract_template(self): + for rec in self: + if rec.contract_id: + if ( + rec.contract_template_id + != rec.contract_id.contract_template_id + ): + raise ValidationError( + _("Contract product has different contract template") + ) diff --git a/product_contract/tests/__init__.py b/product_contract/tests/__init__.py index 4766d5eabe..9d85581f33 100644 --- a/product_contract/tests/__init__.py +++ b/product_contract/tests/__init__.py @@ -5,4 +5,3 @@ from . import test_product from . import test_sale_order -from . import test_sale_order_line diff --git a/product_contract/tests/test_sale_order.py b/product_contract/tests/test_sale_order.py index e8f666020f..36e690b1c0 100644 --- a/product_contract/tests/test_sale_order.py +++ b/product_contract/tests/test_sale_order.py @@ -2,39 +2,93 @@ # Copyright 2017 LasLabs Inc. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from mock import MagicMock from odoo.tests.common import TransactionCase +from odoo.exceptions import ValidationError class TestSaleOrder(TransactionCase): - def setUp(self): super(TestSaleOrder, self).setUp() - self.product = self.env.ref('product.product_product_1') + self.product1 = self.env.ref('product.product_product_1') + self.product2 = self.env.ref('product.product_product_2') self.sale = self.env.ref('sale.sale_order_2') - self.contract = self.env['account.analytic.contract'].create({ - 'name': 'Test', - }) - self.product.product_tmpl_id.is_contract = True - self.product.product_tmpl_id.contract_template_id = self.contract.id - - def tearDown(self): - self.env['account.analytic.account']._revert_method( - 'create', + self.contract_template1 = self.env['account.analytic.contract'].create( + {'name': 'Template 1'} + ) + self.contract_template2 = self.env['account.analytic.contract'].create( + {'name': 'Template 2'} + ) + self.product1.write( + { + 'is_contract': True, + 'contract_template_id': self.contract_template1.id, + } + ) + self.product2.write( + { + 'is_contract': True, + 'contract_template_id': self.contract_template2.id, + } + ) + self.order_line1 = self.sale.order_line.filtered( + lambda l: l.product_id == self.product1 ) - super(TestSaleOrder, self).tearDown() - - def test_action_done(self): - """ It should create a contract when the sale for a contract is set - to done for the first time """ + def test_compute_is_contract(self): + """Sale Order should have is_contract true if one of its lines is + contract""" self.assertTrue(self.sale.is_contract) - self.env['account.analytic.account']._patch_method( - 'create', MagicMock() + + def test_action_confirm(self): + """ It should create a contract for each contract template used in + order_line """ + self.sale.action_confirm() + contracts = self.sale.order_line.mapped('contract_id') + self.assertEqual(len(contracts), 2) + self.assertEqual( + self.order_line1.contract_id.contract_template_id, + self.contract_template1, ) + + def test_sale_contract_count(self): + """It should count contracts as many different contract template used + in order_line""" self.sale.action_confirm() - self.env['account.analytic.account'].create.assert_called_once_with({ - 'name': '%s Contract' % self.sale.name, - 'partner_id': self.sale.partner_id.id, - 'contract_template_id': self.contract.id, - }) + self.assertEqual(self.sale.contract_count, 2) + + def test_onchange_product(self): + """ It should get recurrence invoicing info to the sale line from + its product """ + self.assertEqual( + self.order_line1.recurring_rule_type, + self.product1.recurring_rule_type, + ) + self.assertEqual( + self.order_line1.recurring_interval, + self.product1.recurring_interval, + ) + self.assertEqual( + self.order_line1.recurring_invoicing_type, + self.product1.recurring_invoicing_type, + ) + + def test_check_contract_sale_partner(self): + contract2 = self.env['account.analytic.account'].create( + { + 'name': 'Contract', + 'contract_template_id': self.contract_template2.id, + 'partner_id': self.sale.partner_id.id, + } + ) + with self.assertRaises(ValidationError): + self.order_line1.contract_id = contract2 + + def test_check_contract_sale_contract_template(self): + contract1 = self.env['account.analytic.account'].create( + { + 'name': 'Contract', + 'contract_template_id': self.contract_template1.id, + } + ) + with self.assertRaises(ValidationError): + self.order_line1.contract_id = contract1 diff --git a/product_contract/tests/test_sale_order_line.py b/product_contract/tests/test_sale_order_line.py deleted file mode 100644 index 1f10905990..0000000000 --- a/product_contract/tests/test_sale_order_line.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2018 ACSONE SA/NV. -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - -from mock import MagicMock -from odoo.tests.common import TransactionCase - - -class TestSaleOrder(TransactionCase): - - def setUp(self): - super(TestSaleOrder, self).setUp() - self.product = self.env.ref('product.product_product_1') - self.sale = self.env.ref('sale.sale_order_2') - self.contract = self.env['account.analytic.contract'].create({ - 'name': 'Test', - }) - self.product.product_tmpl_id.is_contract = True - self.sale_order_line = self.sale.order_line.filtered( - lambda l: l.product_id == self.product - ) - - def test_onchange_product(self): - """ It should get recurrence invoicing info to the sale line from - its product """ - self.assertEqual( - self.sale_order_line.recurring_rule_type, - self.product.recurring_rule_type - ) - self.assertEqual( - self.sale_order_line.recurring_interval, - self.product.recurring_interval - ) - self.assertEqual( - self.sale_order_line.recurring_invoicing_type, - self.product.recurring_invoicing_type - ) diff --git a/product_contract/views/sale_order.xml b/product_contract/views/sale_order.xml index f27c40250b..70397c32ea 100644 --- a/product_contract/views/sale_order.xml +++ b/product_contract/views/sale_order.xml @@ -12,9 +12,27 @@ sale.order + + + + + + + Date: Fri, 2 Nov 2018 16:26:51 +0100 Subject: [PATCH 009/196] [IMP] - Link contract line to sale order line --- product_contract/models/__init__.py | 1 + product_contract/models/contract_line.py | 16 ++++++++++++++++ product_contract/models/sale_order.py | 5 ++++- product_contract/models/sale_order_line.py | 1 + 4 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 product_contract/models/contract_line.py diff --git a/product_contract/models/__init__.py b/product_contract/models/__init__.py index 388717d23f..a275fa2175 100644 --- a/product_contract/models/__init__.py +++ b/product_contract/models/__init__.py @@ -2,6 +2,7 @@ # Copyright 2017 LasLabs Inc. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import contract_line from . import product_template from . import sale_order from . import sale_order_line diff --git a/product_contract/models/contract_line.py b/product_contract/models/contract_line.py new file mode 100644 index 0000000000..998da169cc --- /dev/null +++ b/product_contract/models/contract_line.py @@ -0,0 +1,16 @@ +# Copyright 2017 LasLabs Inc. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + + +from odoo import api, fields, models, _ + + +class AccountAnalyticInvoiceLine(models.Model): + _inherit = 'account.analytic.invoice.line' + + sale_order_line_id = fields.Many2one( + comodel_name="sale.order.line", + string="Sale Order Line", + required=False, + copy=False, + ) diff --git a/product_contract/models/sale_order.py b/product_contract/models/sale_order.py index 4b356f320e..a2e8487d43 100644 --- a/product_contract/models/sale_order.py +++ b/product_contract/models/sale_order.py @@ -64,7 +64,10 @@ def action_show_contracts(self): action = self.env.ref( "contract.action_account_analytic_sale_overdue_all" ).read()[0] + contracts = self.env['account.analytic.invoice.line'].search([ + ('sale_order_line', 'in', self.order_line.ids) + ]).mapped('contract_id') action["domain"] = [ - ("id", "in", self.order_line.mapped('contract_id').ids) + ("id", "in", contracts.ids) ] return action diff --git a/product_contract/models/sale_order_line.py b/product_contract/models/sale_order_line.py index b275e67e9b..1c13fdad5e 100644 --- a/product_contract/models/sale_order_line.py +++ b/product_contract/models/sale_order_line.py @@ -82,6 +82,7 @@ def _prepare_contract_line_values(self, contract): 'recurring_invoicing_type': self.recurring_invoicing_type, 'recurring_rule_type': self.recurring_rule_type, 'contract_id': contract.id, + 'sale_order_line_id': self.id, } @api.multi From a47a634e43de9065b3f1332818d95eb67ebe38f3 Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Fri, 2 Nov 2018 17:13:36 +0100 Subject: [PATCH 010/196] [FIX] - Remove recurring_next_date from sale order line recurring_next_date should be computed by contract line to get default value --- product_contract/models/contract_line.py | 2 +- product_contract/models/sale_order.py | 14 +++++++------- product_contract/models/sale_order_line.py | 13 ++++++------- product_contract/views/sale_order.xml | 3 --- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/product_contract/models/contract_line.py b/product_contract/models/contract_line.py index 998da169cc..101e544cd3 100644 --- a/product_contract/models/contract_line.py +++ b/product_contract/models/contract_line.py @@ -2,7 +2,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import api, fields, models, _ +from odoo import fields, models class AccountAnalyticInvoiceLine(models.Model): diff --git a/product_contract/models/sale_order.py b/product_contract/models/sale_order.py index a2e8487d43..e53fbb9189 100644 --- a/product_contract/models/sale_order.py +++ b/product_contract/models/sale_order.py @@ -26,6 +26,7 @@ def action_confirm(self): line_to_create_contract = rec.order_line.filtered( lambda r: not r.contract_id ) + line_to_update_contract = rec.order_line.filtered('contract_id') for contract_template in line_to_create_contract.mapped( 'product_id.contract_template_id' ): @@ -47,7 +48,6 @@ def action_confirm(self): contract._onchange_contract_template_id() order_lines.create_contract_line(contract) order_lines.write({'contract_id': contract.id}) - line_to_update_contract = rec.order_line.filtered('contract_id') for line in line_to_update_contract: line.create_contract_line(line.contract_id) return super(SaleOrder, self).action_confirm() @@ -64,10 +64,10 @@ def action_show_contracts(self): action = self.env.ref( "contract.action_account_analytic_sale_overdue_all" ).read()[0] - contracts = self.env['account.analytic.invoice.line'].search([ - ('sale_order_line', 'in', self.order_line.ids) - ]).mapped('contract_id') - action["domain"] = [ - ("id", "in", contracts.ids) - ] + contracts = ( + self.env['account.analytic.invoice.line'] + .search([('sale_order_line_id', 'in', self.order_line.ids)]) + .mapped('contract_id') + ) + action["domain"] = [("id", "in", contracts.ids)] return action diff --git a/product_contract/models/sale_order_line.py b/product_contract/models/sale_order_line.py index 1c13fdad5e..70e868c4f5 100644 --- a/product_contract/models/sale_order_line.py +++ b/product_contract/models/sale_order_line.py @@ -20,7 +20,7 @@ class SaleOrderLine(models.Model): comodel_name='account.analytic.contract', string='Contract Template', related='product_id.product_tmpl_id.contract_template_id', - readonly=True + readonly=True, ) recurring_rule_type = fields.Selection( [ @@ -50,9 +50,6 @@ class SaleOrderLine(models.Model): ) date_start = fields.Date(string='Date Start', default=fields.Date.today()) date_end = fields.Date(string='Date End', index=True) - recurring_next_date = fields.Date( - default=fields.Date.today(), copy=False, string='Date of Next Invoice' - ) @api.onchange('product_id') def onchange_product(self): @@ -74,8 +71,6 @@ def _prepare_contract_line_values(self, contract): 'uom_id': self.product_uom.id, 'price_unit': self.price_unit, 'discount': self.discount, - 'recurring_next_date': self.recurring_next_date - or fields.Date.today(), 'date_end': self.date_end, 'date_start': self.date_start or fields.Date.today(), 'recurring_interval': self.recurring_interval, @@ -87,9 +82,13 @@ def _prepare_contract_line_values(self, contract): @api.multi def create_contract_line(self, contract): + contract_line_env = self.env['account.analytic.invoice.line'] contract_line = self.env['account.analytic.invoice.line'] for rec in self: - contract_line.create(rec._prepare_contract_line_values(contract)) + contract_line |= contract_line_env.create( + rec._prepare_contract_line_values(contract) + ) + contract_line._onchange_date_start() @api.constrains('contract_id') def _check_contract_sale_partner(self): diff --git a/product_contract/views/sale_order.xml b/product_contract/views/sale_order.xml index 70397c32ea..4443c39d86 100644 --- a/product_contract/views/sale_order.xml +++ b/product_contract/views/sale_order.xml @@ -56,7 +56,6 @@ - @@ -68,8 +67,6 @@ attrs="{'column_invisible': [('parent.is_contract', '=', False)]}"/> - From f1d9b28bfe300a573a6ca6494eebcb00b7caca84 Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Mon, 5 Nov 2018 11:40:58 +0100 Subject: [PATCH 011/196] [IMP] - Contract product are ignored on invoicing process - Sale order line for contract product pass to nothing to invoice on order confirmation - Contract Invoices are linked to sale order line --- product_contract/models/contract_line.py | 11 +++++- product_contract/models/sale_order.py | 6 ++- product_contract/models/sale_order_line.py | 11 ++++++ product_contract/tests/test_sale_order.py | 43 ++++++++++++++++++++++ 4 files changed, 68 insertions(+), 3 deletions(-) diff --git a/product_contract/models/contract_line.py b/product_contract/models/contract_line.py index 101e544cd3..23fcb30687 100644 --- a/product_contract/models/contract_line.py +++ b/product_contract/models/contract_line.py @@ -2,7 +2,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import fields, models +from odoo import api, fields, models class AccountAnalyticInvoiceLine(models.Model): @@ -14,3 +14,12 @@ class AccountAnalyticInvoiceLine(models.Model): required=False, copy=False, ) + + @api.multi + def _prepare_invoice_line(self, invoice_id): + res = super(AccountAnalyticInvoiceLine, self)._prepare_invoice_line( + invoice_id + ) + if self.sale_order_line_id: + res['sale_line_ids'] = [(6, 0, [self.sale_order_line_id.id])] + return res diff --git a/product_contract/models/sale_order.py b/product_contract/models/sale_order.py index e53fbb9189..3a38c1160d 100644 --- a/product_contract/models/sale_order.py +++ b/product_contract/models/sale_order.py @@ -24,9 +24,11 @@ def action_confirm(self): contract_env = self.env['account.analytic.account'] for rec in self.filtered('is_contract'): line_to_create_contract = rec.order_line.filtered( - lambda r: not r.contract_id + lambda r: not r.contract_id and r.product_id.is_contract + ) + line_to_update_contract = rec.order_line.filtered( + lambda r: r.contract_id and r.product_id.is_contract ) - line_to_update_contract = rec.order_line.filtered('contract_id') for contract_template in line_to_create_contract.mapped( 'product_id.contract_template_id' ): diff --git a/product_contract/models/sale_order_line.py b/product_contract/models/sale_order_line.py index 70e868c4f5..6b0dc1f872 100644 --- a/product_contract/models/sale_order_line.py +++ b/product_contract/models/sale_order_line.py @@ -113,3 +113,14 @@ def _check_contract_sale_contract_template(self): raise ValidationError( _("Contract product has different contract template") ) + + def _compute_invoice_status(self): + super(SaleOrderLine, self)._compute_invoice_status() + for line in self.filtered('contract_id'): + line.invoice_status = 'no' + + @api.multi + def invoice_line_create(self, invoice_id, qty): + return super( + SaleOrderLine, self.filtered(lambda l: not l.contract_id) + ).invoice_line_create(invoice_id, qty) diff --git a/product_contract/tests/test_sale_order.py b/product_contract/tests/test_sale_order.py index 36e690b1c0..b3b505e8e5 100644 --- a/product_contract/tests/test_sale_order.py +++ b/product_contract/tests/test_sale_order.py @@ -73,6 +73,8 @@ def test_onchange_product(self): ) def test_check_contract_sale_partner(self): + """Can't link order line to a partner contract different then the + order one""" contract2 = self.env['account.analytic.account'].create( { 'name': 'Contract', @@ -84,6 +86,8 @@ def test_check_contract_sale_partner(self): self.order_line1.contract_id = contract2 def test_check_contract_sale_contract_template(self): + """Can't link order line to a contract with different contract + template then the product one""" contract1 = self.env['account.analytic.account'].create( { 'name': 'Contract', @@ -92,3 +96,42 @@ def test_check_contract_sale_contract_template(self): ) with self.assertRaises(ValidationError): self.order_line1.contract_id = contract1 + + def test_no_contract_proudct(self): + """it should create contract for only product contract""" + self.product1.is_contract = False + self.sale.action_confirm() + self.assertFalse(self.order_line1.contract_id) + + def test_sale_order_line_invoice_status(self): + """Sale order line for contract product should have nothing to + invoice as status""" + self.sale.action_confirm() + self.assertEqual(self.order_line1.invoice_status, 'no') + + def test_sale_order_invoice_status(self): + """Sale order with only contract product should have nothing to + invoice status directtly""" + self.sale.order_line.filtered( + lambda line: not line.product_id.is_contract + ).unlink() + self.sale.action_confirm() + self.assertEqual(self.sale.invoice_status, 'no') + + def test_sale_order_create_invoice(self): + """Should not invoice contract product on sale order create invoice""" + self.product2.is_contract = False + self.product2.invoice_policy = 'order' + self.sale.action_confirm() + self.sale.action_invoice_create() + self.assertEqual(len(self.sale.invoice_ids), 1) + invoice_line = self.sale.invoice_ids.invoice_line_ids.filtered( + lambda line: line.product_id.is_contract + ) + self.assertEqual(len(invoice_line), 0) + + def test_link_contract_invoice_to_sale_order(self): + """It should link contract invoice to sale order""" + self.sale.action_confirm() + invoice = self.order_line1.contract_id.recurring_create_invoice() + self.assertTrue(invoice in self.sale.invoice_ids) From 5b45e9083d8960867db2ca8851e109ca09a643be Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Mon, 5 Nov 2018 11:44:46 +0100 Subject: [PATCH 012/196] [FIX] - Default value for date_start applied on product change --- product_contract/models/sale_order_line.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/product_contract/models/sale_order_line.py b/product_contract/models/sale_order_line.py index 6b0dc1f872..b0d14ee233 100644 --- a/product_contract/models/sale_order_line.py +++ b/product_contract/models/sale_order_line.py @@ -48,7 +48,7 @@ class SaleOrderLine(models.Model): help="Repeat every (Days/Week/Month/Year)", copy=False, ) - date_start = fields.Date(string='Date Start', default=fields.Date.today()) + date_start = fields.Date(string='Date Start') date_end = fields.Date(string='Date End', index=True) @api.onchange('product_id') @@ -59,6 +59,7 @@ def onchange_product(self): self.product_id.recurring_invoicing_type ) self.recurring_interval = self.product_id.recurring_interval + self.date_start = fields.Date.today() @api.multi def _prepare_contract_line_values(self, contract): From 830dfffa5b3960da8e789a22d085f5c395bd7313 Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Mon, 5 Nov 2018 14:35:38 +0100 Subject: [PATCH 013/196] [ADD] - Add readme directory --- product_contract/readme/CONTRIBUTORS.rst | 2 ++ product_contract/readme/DESCRIPTION.rst | 5 +++++ product_contract/readme/USAGE.rst | 6 ++++++ 3 files changed, 13 insertions(+) create mode 100644 product_contract/readme/CONTRIBUTORS.rst create mode 100644 product_contract/readme/DESCRIPTION.rst create mode 100644 product_contract/readme/USAGE.rst diff --git a/product_contract/readme/CONTRIBUTORS.rst b/product_contract/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..2db29075ba --- /dev/null +++ b/product_contract/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Ted Salmon +* Souheil Bejaoui diff --git a/product_contract/readme/DESCRIPTION.rst b/product_contract/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..e620eb278e --- /dev/null +++ b/product_contract/readme/DESCRIPTION.rst @@ -0,0 +1,5 @@ +This module adds support for products to be linked to contract templates. + +A contract is created on ``sale.order`` confirmation for each different template used in sale order line where recurrence details are set too. + +Contract product are ignored on invoicing process and pass to nothing to invoice directly. diff --git a/product_contract/readme/USAGE.rst b/product_contract/readme/USAGE.rst new file mode 100644 index 0000000000..e1380d0f3d --- /dev/null +++ b/product_contract/readme/USAGE.rst @@ -0,0 +1,6 @@ +To use this module, you need to: + +#. Go to Sales -> Products and select or create a product. +#. Check "Is a contract" and select the contract template related to the + product +#. Define default recurrence rules \ No newline at end of file From e8b7ad0f5e0ebc3d0e40734730055b3a6b2eb46b Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Mon, 5 Nov 2018 16:04:53 +0100 Subject: [PATCH 014/196] [IMP] - Onchange contract product on contract contract and contract template --- product_contract/models/__init__.py | 1 + .../models/abstract_contract_line.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 product_contract/models/abstract_contract_line.py diff --git a/product_contract/models/__init__.py b/product_contract/models/__init__.py index a275fa2175..4d43b3eac4 100644 --- a/product_contract/models/__init__.py +++ b/product_contract/models/__init__.py @@ -2,6 +2,7 @@ # Copyright 2017 LasLabs Inc. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import abstract_contract_line from . import contract_line from . import product_template from . import sale_order diff --git a/product_contract/models/abstract_contract_line.py b/product_contract/models/abstract_contract_line.py new file mode 100644 index 0000000000..1bfe4c8c89 --- /dev/null +++ b/product_contract/models/abstract_contract_line.py @@ -0,0 +1,18 @@ +# Copyright 2018 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, models, fields + + +class AccountAbstractAnalyticContractLine(models.AbstractModel): + _inherit = 'account.abstract.analytic.contract.line' + + @api.onchange('product_id') + def onchange_product(self): + if self.product_id.is_contract: + self.recurring_rule_type = self.product_id.recurring_rule_type + self.recurring_invoicing_type = ( + self.product_id.recurring_invoicing_type + ) + self.recurring_interval = self.product_id.recurring_interval + self.date_start = fields.Date.today() From d892adca90e0b707800ff424422d937f70939f07 Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Mon, 5 Nov 2018 16:19:03 +0100 Subject: [PATCH 015/196] [FIX] - Change website to OCA repository url and prefix module name with Recurring --- product_contract/__manifest__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/product_contract/__manifest__.py b/product_contract/__manifest__.py index f369b8251f..097690917e 100644 --- a/product_contract/__manifest__.py +++ b/product_contract/__manifest__.py @@ -4,14 +4,14 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { - 'name': 'Product Contract', + 'name': 'Recurring - Product Contract', 'version': '12.0.1.0.0', 'category': 'Contract Management', 'license': 'AGPL-3', 'author': "LasLabs, " "ACSONE SA/NV, " "Odoo Community Association (OCA)", - 'website': 'https://laslabs.com', + 'website': 'https://github.com/oca/contract', 'depends': [ 'product', 'contract_sale', From 0d60645cb5fdd2469f8790bdac6f925db7bd387a Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Mon, 5 Nov 2018 18:05:53 +0100 Subject: [PATCH 016/196] [FIX] - Compute recurring_next_date before create contract line --- product_contract/models/sale_order_line.py | 10 +++++++++- product_contract/tests/test_sale_order.py | 20 +++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/product_contract/models/sale_order_line.py b/product_contract/models/sale_order_line.py index b0d14ee233..2284a33ac5 100644 --- a/product_contract/models/sale_order_line.py +++ b/product_contract/models/sale_order_line.py @@ -64,6 +64,7 @@ def onchange_product(self): @api.multi def _prepare_contract_line_values(self, contract): self.ensure_one() + contract_line_env = self.env['account.analytic.invoice.line'] return { 'sequence': self.sequence, 'product_id': self.product_id.id, @@ -74,6 +75,13 @@ def _prepare_contract_line_values(self, contract): 'discount': self.discount, 'date_end': self.date_end, 'date_start': self.date_start or fields.Date.today(), + 'recurring_next_date': + contract_line_env._compute_first_recurring_next_date( + self.date_start or fields.Date.today(), + self.recurring_invoicing_type, + self.recurring_rule_type, + self.recurring_interval, + ), 'recurring_interval': self.recurring_interval, 'recurring_invoicing_type': self.recurring_invoicing_type, 'recurring_rule_type': self.recurring_rule_type, @@ -89,7 +97,7 @@ def create_contract_line(self, contract): contract_line |= contract_line_env.create( rec._prepare_contract_line_values(contract) ) - contract_line._onchange_date_start() + return contract_line @api.constrains('contract_id') def _check_contract_sale_partner(self): diff --git a/product_contract/tests/test_sale_order.py b/product_contract/tests/test_sale_order.py index b3b505e8e5..e60cffff98 100644 --- a/product_contract/tests/test_sale_order.py +++ b/product_contract/tests/test_sale_order.py @@ -16,7 +16,25 @@ def setUp(self): {'name': 'Template 1'} ) self.contract_template2 = self.env['account.analytic.contract'].create( - {'name': 'Template 2'} + { + 'name': 'Template 2', + 'recurring_invoice_line_ids': [ + ( + 0, + 0, + { + 'product_id': self.product2.id, + 'name': 'Services from #START# to #END#', + 'quantity': 1, + 'uom_id': self.product2.uom_id.id, + 'price_unit': 100, + 'discount': 50, + 'recurring_rule_type': 'yearly', + 'recurring_interval': 1, + }, + ) + ], + } ) self.product1.write( { From 11a4b6279c3f22fd3d1a287c29c98541e1707768 Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Thu, 15 Nov 2018 11:53:01 +0100 Subject: [PATCH 017/196] [IMP] - Upsell/Downsell contract from sale order --- product_contract/__manifest__.py | 6 +-- product_contract/models/product_template.py | 1 - product_contract/models/sale_order.py | 1 - product_contract/models/sale_order_line.py | 9 ++++- product_contract/tests/test_product.py | 1 - product_contract/tests/test_sale_order.py | 43 ++++++++++++++++++++- product_contract/views/sale_order.xml | 17 ++++---- 7 files changed, 61 insertions(+), 17 deletions(-) diff --git a/product_contract/__manifest__.py b/product_contract/__manifest__.py index 097690917e..fc85782116 100644 --- a/product_contract/__manifest__.py +++ b/product_contract/__manifest__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 LasLabs Inc. # Copyright 2018 ACSONE SA/NV. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). @@ -12,10 +11,7 @@ "ACSONE SA/NV, " "Odoo Community Association (OCA)", 'website': 'https://github.com/oca/contract', - 'depends': [ - 'product', - 'contract_sale', - ], + 'depends': ['product', 'contract_sale'], 'data': [ 'views/product_template.xml', 'views/sale_order.xml', diff --git a/product_contract/models/product_template.py b/product_contract/models/product_template.py index 1d25620197..b10ef3de87 100644 --- a/product_contract/models/product_template.py +++ b/product_contract/models/product_template.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 LasLabs Inc. # Copyright 2018 ACSONE SA/NV. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). diff --git a/product_contract/models/sale_order.py b/product_contract/models/sale_order.py index 3a38c1160d..ad6f969024 100644 --- a/product_contract/models/sale_order.py +++ b/product_contract/models/sale_order.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 LasLabs Inc. # Copyright 2018 ACSONE SA/NV. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). diff --git a/product_contract/models/sale_order_line.py b/product_contract/models/sale_order_line.py index 2284a33ac5..b04345405b 100644 --- a/product_contract/models/sale_order_line.py +++ b/product_contract/models/sale_order_line.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 LasLabs Inc. # Copyright 2017 ACSONE SA/NV. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). @@ -51,6 +50,13 @@ class SaleOrderLine(models.Model): date_start = fields.Date(string='Date Start') date_end = fields.Date(string='Date End', index=True) + contract_line_id = fields.Many2one( + comodel_name="account.analytic.invoice.line", + string="Contract Line to replace", + required=False, + copy=False, + ) + @api.onchange('product_id') def onchange_product(self): if self.product_id.is_contract: @@ -97,6 +103,7 @@ def create_contract_line(self, contract): contract_line |= contract_line_env.create( rec._prepare_contract_line_values(contract) ) + rec.contract_line_id.stop(rec.date_start) return contract_line @api.constrains('contract_id') diff --git a/product_contract/tests/test_product.py b/product_contract/tests/test_product.py index 3676dc057c..76d1aef842 100644 --- a/product_contract/tests/test_product.py +++ b/product_contract/tests/test_product.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 LasLabs Inc. # Copyright 2018 ACSONE SA/NV. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). diff --git a/product_contract/tests/test_sale_order.py b/product_contract/tests/test_sale_order.py index e60cffff98..37feb78bfe 100644 --- a/product_contract/tests/test_sale_order.py +++ b/product_contract/tests/test_sale_order.py @@ -1,9 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright 2017 LasLabs Inc. +# Copyright 2018 ACSONE SA/NV # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from odoo.tests.common import TransactionCase from odoo.exceptions import ValidationError +from odoo.fields import Date class TestSaleOrder(TransactionCase): @@ -51,6 +52,36 @@ def setUp(self): self.order_line1 = self.sale.order_line.filtered( lambda l: l.product_id == self.product1 ) + self.contract = self.env["account.analytic.account"].create( + { + "name": "Test Contract 2", + "partner_id": self.sale.partner_id.id, + "pricelist_id": + self.sale.partner_id.property_product_pricelist.id, + "recurring_invoices": True, + "contract_type": "purchase", + "contract_template_id": self.contract_template1.id, + "recurring_invoice_line_ids": [ + ( + 0, + 0, + { + "product_id": self.product1.id, + "name": "Services from #START# to #END#", + "quantity": 1, + "uom_id": self.product1.uom_id.id, + "price_unit": 100, + "discount": 50, + "recurring_rule_type": "monthly", + "recurring_interval": 1, + "date_start": "2016-02-15", + "recurring_next_date": "2016-02-29", + }, + ) + ], + } + ) + self.contract_line = self.contract.recurring_invoice_line_ids[0] def test_compute_is_contract(self): """Sale Order should have is_contract true if one of its lines is @@ -153,3 +184,13 @@ def test_link_contract_invoice_to_sale_order(self): self.sale.action_confirm() invoice = self.order_line1.contract_id.recurring_create_invoice() self.assertTrue(invoice in self.sale.invoice_ids) + + def test_contract_upsell(self): + """Should stop contract line at sale order line start date""" + self.order_line1.contract_id = self.contract + self.order_line1.contract_line_id = self.contract_line + self.order_line1.date_start = "2018-01-01" + self.sale.action_confirm() + self.assertEqual( + self.contract_line.date_end, Date.to_date("2018-01-01") + ) diff --git a/product_contract/views/sale_order.xml b/product_contract/views/sale_order.xml index 4443c39d86..b24f7fe6db 100644 --- a/product_contract/views/sale_order.xml +++ b/product_contract/views/sale_order.xml @@ -25,14 +25,17 @@ - - - - + + + + + Date: Mon, 19 Nov 2018 16:00:48 +0100 Subject: [PATCH 018/196] [ADD] - Add renewal process with termination notice --- .../models/abstract_contract_line.py | 9 ++++ product_contract/models/product_template.py | 20 +++++++ product_contract/models/sale_order_line.py | 25 +++++++-- product_contract/tests/test_sale_order.py | 27 +++++++++- product_contract/views/product_template.xml | 53 +++++++++++++------ product_contract/views/sale_order.xml | 6 ++- 6 files changed, 117 insertions(+), 23 deletions(-) diff --git a/product_contract/models/abstract_contract_line.py b/product_contract/models/abstract_contract_line.py index 1bfe4c8c89..2013cc155b 100644 --- a/product_contract/models/abstract_contract_line.py +++ b/product_contract/models/abstract_contract_line.py @@ -16,3 +16,12 @@ def onchange_product(self): ) self.recurring_interval = self.product_id.recurring_interval self.date_start = fields.Date.today() + self.is_auto_renew = self.product_id.is_auto_renew + self.auto_renew_interval = self.product_id.auto_renew_interval + self.auto_renew_rule_type = self.product_id.auto_renew_rule_type + self.termination_notice_interval = ( + self.product_id.termination_notice_interval + ) + self.termination_notice_rule_type = ( + self.product_id.termination_notice_rule_type + ) diff --git a/product_contract/models/product_template.py b/product_contract/models/product_template.py index b10ef3de87..d3fd0b5592 100644 --- a/product_contract/models/product_template.py +++ b/product_contract/models/product_template.py @@ -37,6 +37,26 @@ class ProductTemplate(models.Model): string='Repeat Every', help="Repeat every (Days/Week/Month/Year)", ) + is_auto_renew = fields.Boolean(string="Auto Renew", default=False) + auto_renew_interval = fields.Integer( + default=1, + string='Renew Every', + help="Renew every (Days/Week/Month/Year)", + ) + auto_renew_rule_type = fields.Selection( + [('monthly', 'Month(s)'), ('yearly', 'Year(s)')], + default='yearly', + string='Renewal type', + help="Specify Interval for automatic renewal.", + ) + termination_notice_interval = fields.Integer( + default=1, string='Termination Notice Before' + ) + termination_notice_rule_type = fields.Selection( + [('daily', 'Day(s)'), ('weekly', 'Week(s)'), ('monthly', 'Month(s)')], + default='monthly', + string='Termination Notice type', + ) @api.onchange('is_contract') def _change_is_contract(self): diff --git a/product_contract/models/sale_order_line.py b/product_contract/models/sale_order_line.py index b04345405b..7caaf7a3d2 100644 --- a/product_contract/models/sale_order_line.py +++ b/product_contract/models/sale_order_line.py @@ -47,8 +47,8 @@ class SaleOrderLine(models.Model): help="Repeat every (Days/Week/Month/Year)", copy=False, ) - date_start = fields.Date(string='Date Start') - date_end = fields.Date(string='Date End', index=True) + date_start = fields.Date(string='Date Start',) + date_end = fields.Date(string='Date End',) contract_line_id = fields.Many2one( comodel_name="account.analytic.invoice.line", @@ -56,6 +56,11 @@ class SaleOrderLine(models.Model): required=False, copy=False, ) + is_auto_renew = fields.Boolean( + string="Auto Renew", + related="product_id.is_auto_renew", + readonly=True, + ) @api.onchange('product_id') def onchange_product(self): @@ -65,7 +70,14 @@ def onchange_product(self): self.product_id.recurring_invoicing_type ) self.recurring_interval = self.product_id.recurring_interval - self.date_start = fields.Date.today() + self.date_start = self.date_start or fields.Date.today() + if self.product_id.is_auto_renew: + self.date_end = self.date_start + self.env[ + 'account.analytic.invoice.line' + ].get_relative_delta( + self.product_id.auto_renew_rule_type, + self.product_id.auto_renew_interval, + ) @api.multi def _prepare_contract_line_values(self, contract): @@ -91,6 +103,13 @@ def _prepare_contract_line_values(self, contract): 'recurring_interval': self.recurring_interval, 'recurring_invoicing_type': self.recurring_invoicing_type, 'recurring_rule_type': self.recurring_rule_type, + 'is_auto_renew': self.product_id.is_auto_renew, + 'auto_renew_interval': self.product_id.auto_renew_interval, + 'auto_renew_rule_type': self.product_id.auto_renew_rule_type, + 'termination_notice_interval': + self.product_id.termination_notice_interval, + 'termination_notice_rule_type': + self.product_id.termination_notice_rule_type, 'contract_id': contract.id, 'sale_order_line_id': self.id, } diff --git a/product_contract/tests/test_sale_order.py b/product_contract/tests/test_sale_order.py index 37feb78bfe..09f9d9a913 100644 --- a/product_contract/tests/test_sale_order.py +++ b/product_contract/tests/test_sale_order.py @@ -40,6 +40,7 @@ def setUp(self): self.product1.write( { 'is_contract': True, + 'is_auto_renew': True, 'contract_template_id': self.contract_template1.id, } ) @@ -52,6 +53,7 @@ def setUp(self): self.order_line1 = self.sale.order_line.filtered( lambda l: l.product_id == self.product1 ) + self.order_line1.date_start = '2018-01-01' self.contract = self.env["account.analytic.account"].create( { "name": "Test Contract 2", @@ -88,9 +90,14 @@ def test_compute_is_contract(self): contract""" self.assertTrue(self.sale.is_contract) + def test_action_confirm_auto_renew_without_date_end(self): + with self.assertRaises(ValidationError): + self.sale.action_confirm() + def test_action_confirm(self): """ It should create a contract for each contract template used in order_line """ + self.order_line1.onchange_product() self.sale.action_confirm() contracts = self.sale.order_line.mapped('contract_id') self.assertEqual(len(contracts), 2) @@ -102,12 +109,14 @@ def test_action_confirm(self): def test_sale_contract_count(self): """It should count contracts as many different contract template used in order_line""" + self.order_line1.onchange_product() self.sale.action_confirm() self.assertEqual(self.sale.contract_count, 2) def test_onchange_product(self): """ It should get recurrence invoicing info to the sale line from its product """ + self.order_line1.onchange_product() self.assertEqual( self.order_line1.recurring_rule_type, self.product1.recurring_rule_type, @@ -120,6 +129,10 @@ def test_onchange_product(self): self.order_line1.recurring_invoicing_type, self.product1.recurring_invoicing_type, ) + self.assertEqual( + self.order_line1.date_end, + Date.to_date('2019-01-01'), + ) def test_check_contract_sale_partner(self): """Can't link order line to a partner contract different then the @@ -155,6 +168,7 @@ def test_no_contract_proudct(self): def test_sale_order_line_invoice_status(self): """Sale order line for contract product should have nothing to invoice as status""" + self.order_line1.onchange_product() self.sale.action_confirm() self.assertEqual(self.order_line1.invoice_status, 'no') @@ -164,6 +178,7 @@ def test_sale_order_invoice_status(self): self.sale.order_line.filtered( lambda line: not line.product_id.is_contract ).unlink() + self.order_line1.onchange_product() self.sale.action_confirm() self.assertEqual(self.sale.invoice_status, 'no') @@ -171,6 +186,7 @@ def test_sale_order_create_invoice(self): """Should not invoice contract product on sale order create invoice""" self.product2.is_contract = False self.product2.invoice_policy = 'order' + self.order_line1.onchange_product() self.sale.action_confirm() self.sale.action_invoice_create() self.assertEqual(len(self.sale.invoice_ids), 1) @@ -181,6 +197,7 @@ def test_sale_order_create_invoice(self): def test_link_contract_invoice_to_sale_order(self): """It should link contract invoice to sale order""" + self.order_line1.onchange_product() self.sale.action_confirm() invoice = self.order_line1.contract_id.recurring_create_invoice() self.assertTrue(invoice in self.sale.invoice_ids) @@ -189,8 +206,14 @@ def test_contract_upsell(self): """Should stop contract line at sale order line start date""" self.order_line1.contract_id = self.contract self.order_line1.contract_line_id = self.contract_line - self.order_line1.date_start = "2018-01-01" + self.contract_line.date_end = "2019-01-01" + self.contract_line.is_auto_renew = "2019-01-01" + self.order_line1.date_start = "2018-06-01" + self.order_line1.onchange_product() self.sale.action_confirm() self.assertEqual( - self.contract_line.date_end, Date.to_date("2018-01-01") + self.contract_line.date_end, Date.to_date("2018-06-01") + ) + self.assertFalse( + self.contract_line.is_auto_renew ) diff --git a/product_contract/views/product_template.xml b/product_contract/views/product_template.xml index 9eb3b38fc8..0ff9f33fcc 100644 --- a/product_contract/views/product_template.xml +++ b/product_contract/views/product_template.xml @@ -19,24 +19,45 @@ - - - + + + + + - + + + + diff --git a/product_contract/views/sale_order.xml b/product_contract/views/sale_order.xml index b24f7fe6db..8493716d45 100644 --- a/product_contract/views/sale_order.xml +++ b/product_contract/views/sale_order.xml @@ -34,6 +34,8 @@ + - + - + Date: Mon, 19 Nov 2018 16:53:35 +0100 Subject: [PATCH 019/196] [IMP] - compute date end onchange date start for auto-renew sale order lines --- product_contract/models/sale_order_line.py | 16 +++++++++++++++- product_contract/views/product_template.xml | 1 + 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/product_contract/models/sale_order_line.py b/product_contract/models/sale_order_line.py index 7caaf7a3d2..3bef2d8584 100644 --- a/product_contract/models/sale_order_line.py +++ b/product_contract/models/sale_order_line.py @@ -71,7 +71,7 @@ def onchange_product(self): ) self.recurring_interval = self.product_id.recurring_interval self.date_start = self.date_start or fields.Date.today() - if self.product_id.is_auto_renew: + if self.is_auto_renew: self.date_end = self.date_start + self.env[ 'account.analytic.invoice.line' ].get_relative_delta( @@ -79,6 +79,20 @@ def onchange_product(self): self.product_id.auto_renew_interval, ) + @api.onchange('date_start') + def onchange_date_start(self): + for rec in self: + if rec.is_auto_renew: + if not self.date_start: + rec.date_end = False + else: + self.date_end = self.date_start + self.env[ + 'account.analytic.invoice.line' + ].get_relative_delta( + self.product_id.auto_renew_rule_type, + self.product_id.auto_renew_interval, + ) + @api.multi def _prepare_contract_line_values(self, contract): self.ensure_one() diff --git a/product_contract/views/product_template.xml b/product_contract/views/product_template.xml index 0ff9f33fcc..92bac211d9 100644 --- a/product_contract/views/product_template.xml +++ b/product_contract/views/product_template.xml @@ -21,6 +21,7 @@ Date: Mon, 26 Nov 2018 15:17:24 +0100 Subject: [PATCH 020/196] [IMP] - link contract line and its successor in upsel case --- product_contract/models/sale_order_line.py | 39 ++++++++++++---------- product_contract/tests/test_sale_order.py | 19 ++++++----- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/product_contract/models/sale_order_line.py b/product_contract/models/sale_order_line.py index 3bef2d8584..4a41c2a6d5 100644 --- a/product_contract/models/sale_order_line.py +++ b/product_contract/models/sale_order_line.py @@ -47,8 +47,8 @@ class SaleOrderLine(models.Model): help="Repeat every (Days/Week/Month/Year)", copy=False, ) - date_start = fields.Date(string='Date Start',) - date_end = fields.Date(string='Date End',) + date_start = fields.Date(string='Date Start') + date_end = fields.Date(string='Date End') contract_line_id = fields.Many2one( comodel_name="account.analytic.invoice.line", @@ -57,9 +57,7 @@ class SaleOrderLine(models.Model): copy=False, ) is_auto_renew = fields.Boolean( - string="Auto Renew", - related="product_id.is_auto_renew", - readonly=True, + string="Auto Renew", related="product_id.is_auto_renew", readonly=True ) @api.onchange('product_id') @@ -107,23 +105,20 @@ def _prepare_contract_line_values(self, contract): 'discount': self.discount, 'date_end': self.date_end, 'date_start': self.date_start or fields.Date.today(), - 'recurring_next_date': - contract_line_env._compute_first_recurring_next_date( - self.date_start or fields.Date.today(), - self.recurring_invoicing_type, - self.recurring_rule_type, - self.recurring_interval, - ), + 'recurring_next_date': contract_line_env._compute_first_recurring_next_date( + self.date_start or fields.Date.today(), + self.recurring_invoicing_type, + self.recurring_rule_type, + self.recurring_interval, + ), 'recurring_interval': self.recurring_interval, 'recurring_invoicing_type': self.recurring_invoicing_type, 'recurring_rule_type': self.recurring_rule_type, 'is_auto_renew': self.product_id.is_auto_renew, 'auto_renew_interval': self.product_id.auto_renew_interval, 'auto_renew_rule_type': self.product_id.auto_renew_rule_type, - 'termination_notice_interval': - self.product_id.termination_notice_interval, - 'termination_notice_rule_type': - self.product_id.termination_notice_rule_type, + 'termination_notice_interval': self.product_id.termination_notice_interval, + 'termination_notice_rule_type': self.product_id.termination_notice_rule_type, 'contract_id': contract.id, 'sale_order_line_id': self.id, } @@ -133,10 +128,18 @@ def create_contract_line(self, contract): contract_line_env = self.env['account.analytic.invoice.line'] contract_line = self.env['account.analytic.invoice.line'] for rec in self: - contract_line |= contract_line_env.create( + new_contract_line = contract_line_env.create( rec._prepare_contract_line_values(contract) ) - rec.contract_line_id.stop(rec.date_start) + contract_line |= new_contract_line + if rec.contract_line_id: + rec.contract_line_id.stop(rec.date_start) + rec.contract_line_id.successor_contract_line_id = ( + new_contract_line + ) + new_contract_line.predecessor_contract_line_id = ( + self.contract_line_id.id + ) return contract_line @api.constrains('contract_id') diff --git a/product_contract/tests/test_sale_order.py b/product_contract/tests/test_sale_order.py index 09f9d9a913..de46f93cdf 100644 --- a/product_contract/tests/test_sale_order.py +++ b/product_contract/tests/test_sale_order.py @@ -58,8 +58,7 @@ def setUp(self): { "name": "Test Contract 2", "partner_id": self.sale.partner_id.id, - "pricelist_id": - self.sale.partner_id.property_product_pricelist.id, + "pricelist_id": self.sale.partner_id.property_product_pricelist.id, "recurring_invoices": True, "contract_type": "purchase", "contract_template_id": self.contract_template1.id, @@ -129,10 +128,7 @@ def test_onchange_product(self): self.order_line1.recurring_invoicing_type, self.product1.recurring_invoicing_type, ) - self.assertEqual( - self.order_line1.date_end, - Date.to_date('2019-01-01'), - ) + self.assertEqual(self.order_line1.date_end, Date.to_date('2019-01-01')) def test_check_contract_sale_partner(self): """Can't link order line to a partner contract different then the @@ -214,6 +210,13 @@ def test_contract_upsell(self): self.assertEqual( self.contract_line.date_end, Date.to_date("2018-06-01") ) - self.assertFalse( - self.contract_line.is_auto_renew + self.assertFalse(self.contract_line.is_auto_renew) + new_contract_line = self.env['account.analytic.invoice.line'].search( + [('sale_order_line_id', '=', self.order_line1.id)] + ) + self.assertEqual( + self.contract_line.successor_contract_line_id, new_contract_line + ) + self.assertEqual( + new_contract_line.predecessor_contract_line_id, self.contract_line ) From 8b2cbf7892c405b78b109dd9fbb400dbf9335e3c Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Wed, 28 Nov 2018 18:37:31 +0100 Subject: [PATCH 021/196] [FIX] - on upsel, contract line should stop day - 1 --- product_contract/__manifest__.py | 9 ++------ product_contract/models/sale_order_line.py | 24 ++++++++++++++-------- product_contract/tests/test_sale_order.py | 2 +- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/product_contract/__manifest__.py b/product_contract/__manifest__.py index fc85782116..d1dc37ee1b 100644 --- a/product_contract/__manifest__.py +++ b/product_contract/__manifest__.py @@ -7,15 +7,10 @@ 'version': '12.0.1.0.0', 'category': 'Contract Management', 'license': 'AGPL-3', - 'author': "LasLabs, " - "ACSONE SA/NV, " - "Odoo Community Association (OCA)", + 'author': "LasLabs, " "ACSONE SA/NV, " "Odoo Community Association (OCA)", 'website': 'https://github.com/oca/contract', 'depends': ['product', 'contract_sale'], - 'data': [ - 'views/product_template.xml', - 'views/sale_order.xml', - ], + 'data': ['views/product_template.xml', 'views/sale_order.xml'], 'installable': True, 'application': False, } diff --git a/product_contract/models/sale_order_line.py b/product_contract/models/sale_order_line.py index 4a41c2a6d5..07195dcc7a 100644 --- a/product_contract/models/sale_order_line.py +++ b/product_contract/models/sale_order_line.py @@ -2,6 +2,7 @@ # Copyright 2017 ACSONE SA/NV. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from dateutil.relativedelta import relativedelta from odoo import api, fields, models, _ from odoo.exceptions import ValidationError @@ -105,20 +106,23 @@ def _prepare_contract_line_values(self, contract): 'discount': self.discount, 'date_end': self.date_end, 'date_start': self.date_start or fields.Date.today(), - 'recurring_next_date': contract_line_env._compute_first_recurring_next_date( - self.date_start or fields.Date.today(), - self.recurring_invoicing_type, - self.recurring_rule_type, - self.recurring_interval, - ), + 'recurring_next_date': + contract_line_env._compute_first_recurring_next_date( + self.date_start or fields.Date.today(), + self.recurring_invoicing_type, + self.recurring_rule_type, + self.recurring_interval, + ), 'recurring_interval': self.recurring_interval, 'recurring_invoicing_type': self.recurring_invoicing_type, 'recurring_rule_type': self.recurring_rule_type, 'is_auto_renew': self.product_id.is_auto_renew, 'auto_renew_interval': self.product_id.auto_renew_interval, 'auto_renew_rule_type': self.product_id.auto_renew_rule_type, - 'termination_notice_interval': self.product_id.termination_notice_interval, - 'termination_notice_rule_type': self.product_id.termination_notice_rule_type, + 'termination_notice_interval': + self.product_id.termination_notice_interval, + 'termination_notice_rule_type': + self.product_id.termination_notice_rule_type, 'contract_id': contract.id, 'sale_order_line_id': self.id, } @@ -133,7 +137,9 @@ def create_contract_line(self, contract): ) contract_line |= new_contract_line if rec.contract_line_id: - rec.contract_line_id.stop(rec.date_start) + rec.contract_line_id.stop( + rec.date_start - relativedelta(days=1) + ) rec.contract_line_id.successor_contract_line_id = ( new_contract_line ) diff --git a/product_contract/tests/test_sale_order.py b/product_contract/tests/test_sale_order.py index de46f93cdf..5409ebc392 100644 --- a/product_contract/tests/test_sale_order.py +++ b/product_contract/tests/test_sale_order.py @@ -208,7 +208,7 @@ def test_contract_upsell(self): self.order_line1.onchange_product() self.sale.action_confirm() self.assertEqual( - self.contract_line.date_end, Date.to_date("2018-06-01") + self.contract_line.date_end, Date.to_date("2018-05-31") ) self.assertFalse(self.contract_line.is_auto_renew) new_contract_line = self.env['account.analytic.invoice.line'].search( From b2295bf467760d4afdb78e09bba75acc55a3bd28 Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Thu, 29 Nov 2018 12:09:04 +0100 Subject: [PATCH 022/196] [FIX] - fix onchange --- product_contract/models/sale_order_line.py | 66 ++++++++++++---------- product_contract/tests/test_sale_order.py | 3 +- 2 files changed, 39 insertions(+), 30 deletions(-) diff --git a/product_contract/models/sale_order_line.py b/product_contract/models/sale_order_line.py index 07195dcc7a..836f993782 100644 --- a/product_contract/models/sale_order_line.py +++ b/product_contract/models/sale_order_line.py @@ -63,39 +63,55 @@ class SaleOrderLine(models.Model): @api.onchange('product_id') def onchange_product(self): - if self.product_id.is_contract: - self.recurring_rule_type = self.product_id.recurring_rule_type - self.recurring_invoicing_type = ( - self.product_id.recurring_invoicing_type - ) - self.recurring_interval = self.product_id.recurring_interval - self.date_start = self.date_start or fields.Date.today() - if self.is_auto_renew: - self.date_end = self.date_start + self.env[ - 'account.analytic.invoice.line' - ].get_relative_delta( - self.product_id.auto_renew_rule_type, - self.product_id.auto_renew_interval, + contract_line_env = self.env['account.analytic.invoice.line'] + for rec in self: + if rec.product_id.is_contract: + rec.recurring_rule_type = rec.product_id.recurring_rule_type + rec.recurring_invoicing_type = ( + rec.product_id.recurring_invoicing_type ) + rec.recurring_interval = rec.product_id.recurring_interval + rec.date_start = rec.date_start or fields.Date.today() + if rec.is_auto_renew: + rec.date_end = ( + rec.date_start + + contract_line_env.get_relative_delta( + rec.product_id.auto_renew_rule_type, + rec.product_id.auto_renew_interval, + ) + ) @api.onchange('date_start') def onchange_date_start(self): for rec in self: if rec.is_auto_renew: - if not self.date_start: + if not rec.date_start: rec.date_end = False else: - self.date_end = self.date_start + self.env[ + rec.date_end = rec.date_start + self.env[ 'account.analytic.invoice.line' ].get_relative_delta( - self.product_id.auto_renew_rule_type, - self.product_id.auto_renew_interval, + rec.product_id.auto_renew_rule_type, + rec.product_id.auto_renew_interval, ) @api.multi def _prepare_contract_line_values(self, contract): self.ensure_one() - contract_line_env = self.env['account.analytic.invoice.line'] + recurring_next_date = self.env[ + 'account.analytic.invoice.line' + ]._compute_first_recurring_next_date( + self.date_start or fields.Date.today(), + self.recurring_invoicing_type, + self.recurring_rule_type, + self.recurring_interval, + ) + termination_notice_interval = ( + self.product_id.termination_notice_interval + ) + termination_notice_rule_type = ( + self.product_id.termination_notice_rule_type + ) return { 'sequence': self.sequence, 'product_id': self.product_id.id, @@ -106,23 +122,15 @@ def _prepare_contract_line_values(self, contract): 'discount': self.discount, 'date_end': self.date_end, 'date_start': self.date_start or fields.Date.today(), - 'recurring_next_date': - contract_line_env._compute_first_recurring_next_date( - self.date_start or fields.Date.today(), - self.recurring_invoicing_type, - self.recurring_rule_type, - self.recurring_interval, - ), + 'recurring_next_date': recurring_next_date, 'recurring_interval': self.recurring_interval, 'recurring_invoicing_type': self.recurring_invoicing_type, 'recurring_rule_type': self.recurring_rule_type, 'is_auto_renew': self.product_id.is_auto_renew, 'auto_renew_interval': self.product_id.auto_renew_interval, 'auto_renew_rule_type': self.product_id.auto_renew_rule_type, - 'termination_notice_interval': - self.product_id.termination_notice_interval, - 'termination_notice_rule_type': - self.product_id.termination_notice_rule_type, + 'termination_notice_interval': termination_notice_interval, + 'termination_notice_rule_type': termination_notice_rule_type, 'contract_id': contract.id, 'sale_order_line_id': self.id, } diff --git a/product_contract/tests/test_sale_order.py b/product_contract/tests/test_sale_order.py index 5409ebc392..73715d1201 100644 --- a/product_contract/tests/test_sale_order.py +++ b/product_contract/tests/test_sale_order.py @@ -54,11 +54,12 @@ def setUp(self): lambda l: l.product_id == self.product1 ) self.order_line1.date_start = '2018-01-01' + pricelist = self.sale.partner_id.property_product_pricelist.id self.contract = self.env["account.analytic.account"].create( { "name": "Test Contract 2", "partner_id": self.sale.partner_id.id, - "pricelist_id": self.sale.partner_id.property_product_pricelist.id, + "pricelist_id": pricelist, "recurring_invoices": True, "contract_type": "purchase", "contract_template_id": self.contract_template1.id, From ebfeda157755f823c790976f7ce27f883609507e Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Thu, 29 Nov 2018 12:32:22 +0100 Subject: [PATCH 023/196] [IMP] - hide recurring_invoicing_type if recurring_rule_type is monthlylastday for the monthlylastday case, pre-paid is logicly impossible, if monthlylastday is set, we consider only post-paid case --- product_contract/views/product_template.xml | 25 ++++++++++++--------- product_contract/views/sale_order.xml | 24 ++++++++++---------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/product_contract/views/product_template.xml b/product_contract/views/product_template.xml index 92bac211d9..dcc54caedb 100644 --- a/product_contract/views/product_template.xml +++ b/product_contract/views/product_template.xml @@ -26,16 +26,21 @@ - - - + + + + + + diff --git a/product_contract/views/sale_order.xml b/product_contract/views/sale_order.xml index 8493716d45..d5a75d0273 100644 --- a/product_contract/views/sale_order.xml +++ b/product_contract/views/sale_order.xml @@ -40,30 +40,30 @@ + - - - - + - + + + + - + Date: Fri, 30 Nov 2018 17:16:55 +0100 Subject: [PATCH 024/196] [FIX] - include date_end in the period if the product is_autorenew --- product_contract/models/sale_order_line.py | 15 ++++++++++----- product_contract/tests/test_sale_order.py | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/product_contract/models/sale_order_line.py b/product_contract/models/sale_order_line.py index 836f993782..af93f9bf01 100644 --- a/product_contract/models/sale_order_line.py +++ b/product_contract/models/sale_order_line.py @@ -79,6 +79,7 @@ def onchange_product(self): rec.product_id.auto_renew_rule_type, rec.product_id.auto_renew_interval, ) + - relativedelta(days=1) ) @api.onchange('date_start') @@ -88,11 +89,15 @@ def onchange_date_start(self): if not rec.date_start: rec.date_end = False else: - rec.date_end = rec.date_start + self.env[ - 'account.analytic.invoice.line' - ].get_relative_delta( - rec.product_id.auto_renew_rule_type, - rec.product_id.auto_renew_interval, + rec.date_end = ( + rec.date_start + + self.env[ + 'account.analytic.invoice.line' + ].get_relative_delta( + rec.product_id.auto_renew_rule_type, + rec.product_id.auto_renew_interval, + ) + - relativedelta(days=1) ) @api.multi diff --git a/product_contract/tests/test_sale_order.py b/product_contract/tests/test_sale_order.py index 73715d1201..721059998b 100644 --- a/product_contract/tests/test_sale_order.py +++ b/product_contract/tests/test_sale_order.py @@ -129,7 +129,7 @@ def test_onchange_product(self): self.order_line1.recurring_invoicing_type, self.product1.recurring_invoicing_type, ) - self.assertEqual(self.order_line1.date_end, Date.to_date('2019-01-01')) + self.assertEqual(self.order_line1.date_end, Date.to_date('2018-12-31')) def test_check_contract_sale_partner(self): """Can't link order line to a partner contract different then the From 941a0726db4dad76eff24873c5fa5d537d049c0f Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Mon, 3 Dec 2018 13:13:10 +0100 Subject: [PATCH 025/196] [IMP] - _prepare_contract_value for sale order confirm --- product_contract/models/sale_order.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/product_contract/models/sale_order.py b/product_contract/models/sale_order.py index ad6f969024..2ae9b4e42f 100644 --- a/product_contract/models/sale_order.py +++ b/product_contract/models/sale_order.py @@ -17,6 +17,18 @@ class SaleOrder(models.Model): def _compute_is_contract(self): self.is_contract = any(self.order_line.mapped('is_contract')) + @api.multi + def _prepare_contract_value(self, contract_template): + self.ensure_one() + return { + 'name': '{template_name}: {sale_name}'.format( + template_name=contract_template.name, sale_name=self.name + ), + 'partner_id': self.partner_id.id, + 'recurring_invoices': True, + 'contract_template_id': contract_template.id, + } + @api.multi def action_confirm(self): """ If we have a contract in the order, set it up """ @@ -36,15 +48,7 @@ def action_confirm(self): == contract_template ) contract = contract_env.create( - { - 'name': '{template_name}: {sale_name}'.format( - template_name=contract_template.name, - sale_name=rec.name, - ), - 'partner_id': rec.partner_id.id, - 'recurring_invoices': True, - 'contract_template_id': contract_template.id, - } + rec._prepare_contract_value(contract_template) ) contract._onchange_contract_template_id() order_lines.create_contract_line(contract) From 5e1688d819d30c0c4684085858e5cd9b81f85ca4 Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Tue, 4 Dec 2018 12:57:13 +0100 Subject: [PATCH 026/196] [REM] - Remove unused method --- product_contract/models/__init__.py | 1 - .../models/abstract_contract_line.py | 27 ------------------- product_contract/models/sale_order_line.py | 4 +-- 3 files changed, 2 insertions(+), 30 deletions(-) delete mode 100644 product_contract/models/abstract_contract_line.py diff --git a/product_contract/models/__init__.py b/product_contract/models/__init__.py index 4d43b3eac4..a275fa2175 100644 --- a/product_contract/models/__init__.py +++ b/product_contract/models/__init__.py @@ -2,7 +2,6 @@ # Copyright 2017 LasLabs Inc. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from . import abstract_contract_line from . import contract_line from . import product_template from . import sale_order diff --git a/product_contract/models/abstract_contract_line.py b/product_contract/models/abstract_contract_line.py deleted file mode 100644 index 2013cc155b..0000000000 --- a/product_contract/models/abstract_contract_line.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2018 ACSONE SA/NV -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import api, models, fields - - -class AccountAbstractAnalyticContractLine(models.AbstractModel): - _inherit = 'account.abstract.analytic.contract.line' - - @api.onchange('product_id') - def onchange_product(self): - if self.product_id.is_contract: - self.recurring_rule_type = self.product_id.recurring_rule_type - self.recurring_invoicing_type = ( - self.product_id.recurring_invoicing_type - ) - self.recurring_interval = self.product_id.recurring_interval - self.date_start = fields.Date.today() - self.is_auto_renew = self.product_id.is_auto_renew - self.auto_renew_interval = self.product_id.auto_renew_interval - self.auto_renew_rule_type = self.product_id.auto_renew_rule_type - self.termination_notice_interval = ( - self.product_id.termination_notice_interval - ) - self.termination_notice_rule_type = ( - self.product_id.termination_notice_rule_type - ) diff --git a/product_contract/models/sale_order_line.py b/product_contract/models/sale_order_line.py index af93f9bf01..61094b2be6 100644 --- a/product_contract/models/sale_order_line.py +++ b/product_contract/models/sale_order_line.py @@ -72,7 +72,7 @@ def onchange_product(self): ) rec.recurring_interval = rec.product_id.recurring_interval rec.date_start = rec.date_start or fields.Date.today() - if rec.is_auto_renew: + if rec.product_id.is_auto_renew: rec.date_end = ( rec.date_start + contract_line_env.get_relative_delta( @@ -85,7 +85,7 @@ def onchange_product(self): @api.onchange('date_start') def onchange_date_start(self): for rec in self: - if rec.is_auto_renew: + if rec.product_id.is_auto_renew: if not rec.date_start: rec.date_end = False else: From e322338ab610d8b672ac31ccaefeb26f17302cb0 Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Thu, 13 Dec 2018 21:46:11 +0100 Subject: [PATCH 027/196] [IMP] - get contract user from sale order user --- product_contract/models/sale_order.py | 1 + 1 file changed, 1 insertion(+) diff --git a/product_contract/models/sale_order.py b/product_contract/models/sale_order.py index 2ae9b4e42f..5a7bfd188f 100644 --- a/product_contract/models/sale_order.py +++ b/product_contract/models/sale_order.py @@ -27,6 +27,7 @@ def _prepare_contract_value(self, contract_template): 'partner_id': self.partner_id.id, 'recurring_invoices': True, 'contract_template_id': contract_template.id, + 'user_id': self.user_id.id, } @api.multi From a98d1f4cdf8c4fe17f1ed555e75b73ac6ca81701 Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Fri, 14 Dec 2018 15:55:12 +0100 Subject: [PATCH 028/196] [IMP] - show related sale orders in contract form --- product_contract/__manifest__.py | 6 ++++- product_contract/models/__init__.py | 1 + product_contract/models/contract.py | 35 +++++++++++++++++++++++++++++ product_contract/views/contract.xml | 29 ++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 product_contract/models/contract.py create mode 100644 product_contract/views/contract.xml diff --git a/product_contract/__manifest__.py b/product_contract/__manifest__.py index d1dc37ee1b..aa0f3b742f 100644 --- a/product_contract/__manifest__.py +++ b/product_contract/__manifest__.py @@ -10,7 +10,11 @@ 'author': "LasLabs, " "ACSONE SA/NV, " "Odoo Community Association (OCA)", 'website': 'https://github.com/oca/contract', 'depends': ['product', 'contract_sale'], - 'data': ['views/product_template.xml', 'views/sale_order.xml'], + 'data': [ + 'views/contract.xml', + 'views/product_template.xml', + 'views/sale_order.xml' + ], 'installable': True, 'application': False, } diff --git a/product_contract/models/__init__.py b/product_contract/models/__init__.py index a275fa2175..6ec5fa0304 100644 --- a/product_contract/models/__init__.py +++ b/product_contract/models/__init__.py @@ -2,6 +2,7 @@ # Copyright 2017 LasLabs Inc. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import contract from . import contract_line from . import product_template from . import sale_order diff --git a/product_contract/models/contract.py b/product_contract/models/contract.py new file mode 100644 index 0000000000..56279cef0d --- /dev/null +++ b/product_contract/models/contract.py @@ -0,0 +1,35 @@ +# Copyright 2018 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models +from odoo.tools.translate import _ + + +class AccountAnalyticAccount(models.Model): + _name = 'account.analytic.account' + _inherit = 'account.analytic.account' + + sale_order_count = fields.Integer(compute="_compute_sale_order_count") + + @api.depends('recurring_invoice_line_ids') + def _compute_sale_order_count(self): + for rec in self: + rec.sale_order_count = len( + rec.recurring_invoice_line_ids.mapped( + 'sale_order_line_id.order_id' + ) + ) + + @api.multi + def action_view_sales_orders(self): + self.ensure_one() + orders = self.recurring_invoice_line_ids.mapped( + 'sale_order_line_id.order_id' + ) + return { + "name": _("Sales Orders"), + "view_mode": "tree,form", + "res_model": "sale.order", + "type": "ir.actions.act_window", + "domain": [("id", "in", orders.ids)], + } diff --git a/product_contract/views/contract.xml b/product_contract/views/contract.xml new file mode 100644 index 0000000000..abf53e6e7c --- /dev/null +++ b/product_contract/views/contract.xml @@ -0,0 +1,29 @@ + + + + + + account.analytic.account + + + + + + + + + From d6215e0cbd4d570163a0bb82a1914f03730369a0 Mon Sep 17 00:00:00 2001 From: Thomas Binsfeld Date: Tue, 18 Dec 2018 14:08:31 +0100 Subject: [PATCH 029/196] [REF] Contract Product: invoice in prepare_invoice_line is optional --- product_contract/models/contract_line.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/product_contract/models/contract_line.py b/product_contract/models/contract_line.py index 23fcb30687..e067929d80 100644 --- a/product_contract/models/contract_line.py +++ b/product_contract/models/contract_line.py @@ -16,9 +16,9 @@ class AccountAnalyticInvoiceLine(models.Model): ) @api.multi - def _prepare_invoice_line(self, invoice_id): + def _prepare_invoice_line(self, invoice_id=False): res = super(AccountAnalyticInvoiceLine, self)._prepare_invoice_line( - invoice_id + invoice_id=invoice_id ) if self.sale_order_line_id: res['sale_line_ids'] = [(6, 0, [self.sale_order_line_id.id])] From e2bf0fdbe4bac0c29b980d3df6fabfe796536752 Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Thu, 20 Dec 2018 22:57:15 +0100 Subject: [PATCH 030/196] [FIX] - fix flake8 --- product_contract/__init__.py | 1 - product_contract/readme/USAGE.rst | 2 +- product_contract/tests/__init__.py | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/product_contract/__init__.py b/product_contract/__init__.py index 44db863b6e..c6339a004a 100644 --- a/product_contract/__init__.py +++ b/product_contract/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 LasLabs Inc. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). diff --git a/product_contract/readme/USAGE.rst b/product_contract/readme/USAGE.rst index e1380d0f3d..1ed5d47186 100644 --- a/product_contract/readme/USAGE.rst +++ b/product_contract/readme/USAGE.rst @@ -3,4 +3,4 @@ To use this module, you need to: #. Go to Sales -> Products and select or create a product. #. Check "Is a contract" and select the contract template related to the product -#. Define default recurrence rules \ No newline at end of file +#. Define default recurrence rules diff --git a/product_contract/tests/__init__.py b/product_contract/tests/__init__.py index 9d85581f33..9dad07bdb2 100644 --- a/product_contract/tests/__init__.py +++ b/product_contract/tests/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 LasLabs Inc. # Copyright 2018 ACSONE SA/NV. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). From 36638eb1baf1d9a29f1c6636c35978d03c9ca845 Mon Sep 17 00:00:00 2001 From: Thomas Binsfeld Date: Fri, 21 Dec 2018 16:07:40 +0100 Subject: [PATCH 031/196] [ADD] Product Contract: payment term --- product_contract/models/sale_order.py | 1 + 1 file changed, 1 insertion(+) diff --git a/product_contract/models/sale_order.py b/product_contract/models/sale_order.py index 5a7bfd188f..6c92feefe0 100644 --- a/product_contract/models/sale_order.py +++ b/product_contract/models/sale_order.py @@ -28,6 +28,7 @@ def _prepare_contract_value(self, contract_template): 'recurring_invoices': True, 'contract_template_id': contract_template.id, 'user_id': self.user_id.id, + 'payment_term_id': self.payment_term_id.id, } @api.multi From 88287884421bda1cb19ce2b7f685984d09939fd9 Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Fri, 28 Dec 2018 12:36:19 +0100 Subject: [PATCH 032/196] [IMP] - update invoice_line vals only if it is not null --- product_contract/models/contract_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/product_contract/models/contract_line.py b/product_contract/models/contract_line.py index e067929d80..0a7a4015ab 100644 --- a/product_contract/models/contract_line.py +++ b/product_contract/models/contract_line.py @@ -20,6 +20,6 @@ def _prepare_invoice_line(self, invoice_id=False): res = super(AccountAnalyticInvoiceLine, self)._prepare_invoice_line( invoice_id=invoice_id ) - if self.sale_order_line_id: + if self.sale_order_line_id and res: res['sale_line_ids'] = [(6, 0, [self.sale_order_line_id.id])] return res From a8b5e46a7dcc96f02780e562762d87aac7644895 Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Thu, 3 Jan 2019 11:51:44 +0100 Subject: [PATCH 033/196] [FIX] - fix unit tests --- product_contract/tests/test_sale_order.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/product_contract/tests/test_sale_order.py b/product_contract/tests/test_sale_order.py index 721059998b..d3c29572ce 100644 --- a/product_contract/tests/test_sale_order.py +++ b/product_contract/tests/test_sale_order.py @@ -2,6 +2,7 @@ # Copyright 2018 ACSONE SA/NV # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from dateutil.relativedelta import relativedelta from odoo.tests.common import TransactionCase from odoo.exceptions import ValidationError from odoo.fields import Date @@ -61,7 +62,7 @@ def setUp(self): "partner_id": self.sale.partner_id.id, "pricelist_id": pricelist, "recurring_invoices": True, - "contract_type": "purchase", + "contract_type": "sale", "contract_template_id": self.contract_template1.id, "recurring_invoice_line_ids": [ ( @@ -203,8 +204,8 @@ def test_contract_upsell(self): """Should stop contract line at sale order line start date""" self.order_line1.contract_id = self.contract self.order_line1.contract_line_id = self.contract_line - self.contract_line.date_end = "2019-01-01" - self.contract_line.is_auto_renew = "2019-01-01" + self.contract_line.date_end = Date.today() + relativedelta(months=4) + self.contract_line.is_auto_renew = True self.order_line1.date_start = "2018-06-01" self.order_line1.onchange_product() self.sale.action_confirm() From 26db0d3ec55ba4d8f62643d39634cdbbab194e95 Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Thu, 10 Jan 2019 17:03:30 +0100 Subject: [PATCH 034/196] [IMP] - Add unit test --- product_contract/models/contract_line.py | 20 +++++++++++++ product_contract/tests/test_sale_order.py | 35 +++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/product_contract/models/contract_line.py b/product_contract/models/contract_line.py index 0a7a4015ab..ca5cd9a5c9 100644 --- a/product_contract/models/contract_line.py +++ b/product_contract/models/contract_line.py @@ -23,3 +23,23 @@ def _prepare_invoice_line(self, invoice_id=False): if self.sale_order_line_id and res: res['sale_line_ids'] = [(6, 0, [self.sale_order_line_id.id])] return res + + @api.onchange('product_id') + def _onchange_product_id_recurring_info(self): + for rec in self: + rec.date_start = fields.Date.today() + if rec.product_id.is_contract: + rec.recurring_rule_type = rec.product_id.recurring_rule_type + rec.recurring_invoicing_type = ( + rec.product_id.recurring_invoicing_type + ) + rec.recurring_interval = rec.product_id.recurring_interval + rec.is_auto_renew = rec.product_id.is_auto_renew + rec.auto_renew_interval = rec.product_id.auto_renew_interval + rec.auto_renew_rule_type = rec.product_id.auto_renew_rule_type + rec.termination_notice_interval = ( + rec.product_id.termination_notice_interval + ) + rec.termination_notice_rule_type = ( + rec.product_id.termination_notice_rule_type + ) diff --git a/product_contract/tests/test_sale_order.py b/product_contract/tests/test_sale_order.py index d3c29572ce..a13aa121d2 100644 --- a/product_contract/tests/test_sale_order.py +++ b/product_contract/tests/test_sale_order.py @@ -222,3 +222,38 @@ def test_contract_upsell(self): self.assertEqual( new_contract_line.predecessor_contract_line_id, self.contract_line ) + + def test_onchange_product_id_recurring_info(self): + self.product2.write( + { + 'recurring_rule_type': 'monthly', + 'recurring_invoicing_type': 'pre-paid', + 'recurring_interval': '2', + 'is_auto_renew': True, + 'auto_renew_interval': '6', + 'auto_renew_rule_type': 'monthly', + 'termination_notice_interval': '6', + 'termination_notice_rule_type': 'weekly', + } + ) + self.contract_line.write( + { + 'date_start': Date.today(), + 'date_end': Date.today() + relativedelta(years=1), + 'recurring_next_date': Date.today(), + 'product_id': self.product2.id, + } + ) + self.contract_line._onchange_product_id_recurring_info() + self.assertEqual(self.contract_line.recurring_rule_type, 'monthly') + self.assertEqual( + self.contract_line.recurring_invoicing_type, 'pre-paid' + ) + self.assertEqual(self.contract_line.recurring_interval, 2) + self.assertEqual(self.contract_line.is_auto_renew, True) + self.assertEqual(self.contract_line.auto_renew_interval, 6) + self.assertEqual(self.contract_line.auto_renew_rule_type, 'monthly') + self.assertEqual(self.contract_line.termination_notice_interval, 6) + self.assertEqual( + self.contract_line.termination_notice_rule_type, 'weekly' + ) From 8789fc13d2a64b684649d613fa6211b03ecd61e8 Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Tue, 15 Jan 2019 13:37:17 +0100 Subject: [PATCH 035/196] [REF] - predecessor_contract_line set in create process --- product_contract/models/sale_order_line.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/product_contract/models/sale_order_line.py b/product_contract/models/sale_order_line.py index 61094b2be6..a99f96d3d7 100644 --- a/product_contract/models/sale_order_line.py +++ b/product_contract/models/sale_order_line.py @@ -101,7 +101,9 @@ def onchange_date_start(self): ) @api.multi - def _prepare_contract_line_values(self, contract): + def _prepare_contract_line_values( + self, contract, predecessor_contract_line + ): self.ensure_one() recurring_next_date = self.env[ 'account.analytic.invoice.line' @@ -138,6 +140,7 @@ def _prepare_contract_line_values(self, contract): 'termination_notice_rule_type': termination_notice_rule_type, 'contract_id': contract.id, 'sale_order_line_id': self.id, + 'predecessor_contract_line_id': predecessor_contract_line.id, } @api.multi @@ -145,20 +148,19 @@ def create_contract_line(self, contract): contract_line_env = self.env['account.analytic.invoice.line'] contract_line = self.env['account.analytic.invoice.line'] for rec in self: - new_contract_line = contract_line_env.create( - rec._prepare_contract_line_values(contract) - ) - contract_line |= new_contract_line if rec.contract_line_id: rec.contract_line_id.stop( rec.date_start - relativedelta(days=1) ) + new_contract_line = contract_line_env.create( + rec._prepare_contract_line_values(contract, + rec.contract_line_id) + ) + if rec.contract_line_id: rec.contract_line_id.successor_contract_line_id = ( new_contract_line ) - new_contract_line.predecessor_contract_line_id = ( - self.contract_line_id.id - ) + contract_line |= new_contract_line return contract_line @api.constrains('contract_id') From 79ca8a9b8e456e22ca8b2ffaa14d652b38796143 Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Mon, 21 Jan 2019 19:02:48 +0100 Subject: [PATCH 036/196] [IMP] - Simplify sale order line creation for contract product --- product_contract/__manifest__.py | 4 +- product_contract/models/contract_line.py | 6 +- product_contract/models/product_template.py | 20 +---- product_contract/models/sale_order_line.py | 82 ++++++++++----------- product_contract/tests/test_sale_order.py | 19 ++--- product_contract/views/product_template.xml | 18 +---- product_contract/views/sale_order.xml | 14 +--- 7 files changed, 59 insertions(+), 104 deletions(-) diff --git a/product_contract/__manifest__.py b/product_contract/__manifest__.py index aa0f3b742f..5f11289426 100644 --- a/product_contract/__manifest__.py +++ b/product_contract/__manifest__.py @@ -7,7 +7,9 @@ 'version': '12.0.1.0.0', 'category': 'Contract Management', 'license': 'AGPL-3', - 'author': "LasLabs, " "ACSONE SA/NV, " "Odoo Community Association (OCA)", + 'author': "LasLabs, " + "ACSONE SA/NV, " + "Odoo Community Association (OCA)", 'website': 'https://github.com/oca/contract', 'depends': ['product', 'contract_sale'], 'data': [ diff --git a/product_contract/models/contract_line.py b/product_contract/models/contract_line.py index ca5cd9a5c9..1e0cd4fef5 100644 --- a/product_contract/models/contract_line.py +++ b/product_contract/models/contract_line.py @@ -33,10 +33,10 @@ def _onchange_product_id_recurring_info(self): rec.recurring_invoicing_type = ( rec.product_id.recurring_invoicing_type ) - rec.recurring_interval = rec.product_id.recurring_interval + rec.recurring_interval = 1 rec.is_auto_renew = rec.product_id.is_auto_renew - rec.auto_renew_interval = rec.product_id.auto_renew_interval - rec.auto_renew_rule_type = rec.product_id.auto_renew_rule_type + rec.auto_renew_interval = rec.product_id.default_qty + rec.auto_renew_rule_type = rec.product_id.recurring_rule_type rec.termination_notice_interval = ( rec.product_id.termination_notice_interval ) diff --git a/product_contract/models/product_template.py b/product_contract/models/product_template.py index d3fd0b5592..f73cc3afdf 100644 --- a/product_contract/models/product_template.py +++ b/product_contract/models/product_template.py @@ -13,7 +13,7 @@ class ProductTemplate(models.Model): contract_template_id = fields.Many2one( comodel_name='account.analytic.contract', string='Contract Template' ) - + default_qty = fields.Integer(string="Default Quantity") recurring_rule_type = fields.Selection( [ ('daily', 'Day(s)'), @@ -23,7 +23,7 @@ class ProductTemplate(models.Model): ('yearly', 'Year(s)'), ], default='monthly', - string='Recurrence', + string='Invoice Every', help="Specify Interval for automatic invoice generation.", ) recurring_invoicing_type = fields.Selection( @@ -32,23 +32,7 @@ class ProductTemplate(models.Model): string='Invoicing type', help="Specify if process date is 'from' or 'to' invoicing date", ) - recurring_interval = fields.Integer( - default=1, - string='Repeat Every', - help="Repeat every (Days/Week/Month/Year)", - ) is_auto_renew = fields.Boolean(string="Auto Renew", default=False) - auto_renew_interval = fields.Integer( - default=1, - string='Renew Every', - help="Renew every (Days/Week/Month/Year)", - ) - auto_renew_rule_type = fields.Selection( - [('monthly', 'Month(s)'), ('yearly', 'Year(s)')], - default='yearly', - string='Renewal type', - help="Specify Interval for automatic renewal.", - ) termination_notice_interval = fields.Integer( default=1, string='Termination Notice Before' ) diff --git a/product_contract/models/sale_order_line.py b/product_contract/models/sale_order_line.py index a99f96d3d7..049f778c13 100644 --- a/product_contract/models/sale_order_line.py +++ b/product_contract/models/sale_order_line.py @@ -31,8 +31,7 @@ class SaleOrderLine(models.Model): ('yearly', 'Year(s)'), ], default='monthly', - string='Recurrence', - help="Specify Interval for automatic invoice generation.", + string='Invoice Every', copy=False, ) recurring_invoicing_type = fields.Selection( @@ -42,12 +41,6 @@ class SaleOrderLine(models.Model): help="Specify if process date is 'from' or 'to' invoicing date", copy=False, ) - recurring_interval = fields.Integer( - default=1, - string='Repeat Every', - help="Repeat every (Days/Week/Month/Year)", - copy=False, - ) date_start = fields.Date(string='Date Start') date_end = fields.Date(string='Date End') @@ -57,48 +50,42 @@ class SaleOrderLine(models.Model): required=False, copy=False, ) - is_auto_renew = fields.Boolean( - string="Auto Renew", related="product_id.is_auto_renew", readonly=True - ) @api.onchange('product_id') def onchange_product(self): contract_line_env = self.env['account.analytic.invoice.line'] for rec in self: if rec.product_id.is_contract: + rec.product_uom_qty = rec.product_id.default_qty rec.recurring_rule_type = rec.product_id.recurring_rule_type rec.recurring_invoicing_type = ( rec.product_id.recurring_invoicing_type ) - rec.recurring_interval = rec.product_id.recurring_interval rec.date_start = rec.date_start or fields.Date.today() - if rec.product_id.is_auto_renew: - rec.date_end = ( - rec.date_start - + contract_line_env.get_relative_delta( - rec.product_id.auto_renew_rule_type, - rec.product_id.auto_renew_interval, - ) - - relativedelta(days=1) + rec.date_end = ( + rec.date_start + + contract_line_env.get_relative_delta( + rec.product_id.recurring_rule_type, + int(rec.product_uom_qty), ) + - relativedelta(days=1) + ) - @api.onchange('date_start') + @api.onchange('date_start', 'product_uom_qty', 'recurring_rule_type') def onchange_date_start(self): for rec in self: - if rec.product_id.is_auto_renew: - if not rec.date_start: - rec.date_end = False - else: - rec.date_end = ( - rec.date_start - + self.env[ - 'account.analytic.invoice.line' - ].get_relative_delta( - rec.product_id.auto_renew_rule_type, - rec.product_id.auto_renew_interval, - ) - - relativedelta(days=1) + if not rec.date_start: + rec.date_end = False + else: + rec.date_end = ( + rec.date_start + + self.env[ + 'account.analytic.invoice.line' + ].get_relative_delta( + rec.recurring_rule_type, int(rec.product_uom_qty) ) + - relativedelta(days=1) + ) @api.multi def _prepare_contract_line_values( @@ -111,7 +98,7 @@ def _prepare_contract_line_values( self.date_start or fields.Date.today(), self.recurring_invoicing_type, self.recurring_rule_type, - self.recurring_interval, + int(self.product_uom_qty), ) termination_notice_interval = ( self.product_id.termination_notice_interval @@ -123,19 +110,31 @@ def _prepare_contract_line_values( 'sequence': self.sequence, 'product_id': self.product_id.id, 'name': self.name, - 'quantity': self.product_uom_qty, + # The quantity on the generated contract line is 1, as it + # correspond to the most common use cases: + # - quantity on the SO line = number of periods sold and unit + # price the price of one period, so the + # total amount of the SO corresponds to the planned value + # of the contract; in this case the quantity on the contract + # line must be 1 + # - quantity on the SO line = number of hours sold, + # automatic invoicing of the actual hours through a variable + # quantity formula, in which case the quantity on the contract + # line is not used + # Other use cases are easy to implement by overriding this method. + 'quantity': 1.0, 'uom_id': self.product_uom.id, 'price_unit': self.price_unit, 'discount': self.discount, 'date_end': self.date_end, 'date_start': self.date_start or fields.Date.today(), 'recurring_next_date': recurring_next_date, - 'recurring_interval': self.recurring_interval, + 'recurring_interval': 1, 'recurring_invoicing_type': self.recurring_invoicing_type, 'recurring_rule_type': self.recurring_rule_type, 'is_auto_renew': self.product_id.is_auto_renew, - 'auto_renew_interval': self.product_id.auto_renew_interval, - 'auto_renew_rule_type': self.product_id.auto_renew_rule_type, + 'auto_renew_interval': self.product_uom_qty, + 'auto_renew_rule_type': self.product_id.recurring_rule_type, 'termination_notice_interval': termination_notice_interval, 'termination_notice_rule_type': termination_notice_rule_type, 'contract_id': contract.id, @@ -153,8 +152,9 @@ def create_contract_line(self, contract): rec.date_start - relativedelta(days=1) ) new_contract_line = contract_line_env.create( - rec._prepare_contract_line_values(contract, - rec.contract_line_id) + rec._prepare_contract_line_values( + contract, rec.contract_line_id + ) ) if rec.contract_line_id: rec.contract_line_id.successor_contract_line_id = ( diff --git a/product_contract/tests/test_sale_order.py b/product_contract/tests/test_sale_order.py index a13aa121d2..3318065933 100644 --- a/product_contract/tests/test_sale_order.py +++ b/product_contract/tests/test_sale_order.py @@ -41,7 +41,7 @@ def setUp(self): self.product1.write( { 'is_contract': True, - 'is_auto_renew': True, + 'default_qty': 12, 'contract_template_id': self.contract_template1.id, } ) @@ -55,6 +55,7 @@ def setUp(self): lambda l: l.product_id == self.product1 ) self.order_line1.date_start = '2018-01-01' + self.order_line1.product_uom_qty = 12 pricelist = self.sale.partner_id.property_product_pricelist.id self.contract = self.env["account.analytic.account"].create( { @@ -91,10 +92,6 @@ def test_compute_is_contract(self): contract""" self.assertTrue(self.sale.is_contract) - def test_action_confirm_auto_renew_without_date_end(self): - with self.assertRaises(ValidationError): - self.sale.action_confirm() - def test_action_confirm(self): """ It should create a contract for each contract template used in order_line """ @@ -122,10 +119,6 @@ def test_onchange_product(self): self.order_line1.recurring_rule_type, self.product1.recurring_rule_type, ) - self.assertEqual( - self.order_line1.recurring_interval, - self.product1.recurring_interval, - ) self.assertEqual( self.order_line1.recurring_invoicing_type, self.product1.recurring_invoicing_type, @@ -228,10 +221,8 @@ def test_onchange_product_id_recurring_info(self): { 'recurring_rule_type': 'monthly', 'recurring_invoicing_type': 'pre-paid', - 'recurring_interval': '2', 'is_auto_renew': True, - 'auto_renew_interval': '6', - 'auto_renew_rule_type': 'monthly', + 'default_qty': 12, 'termination_notice_interval': '6', 'termination_notice_rule_type': 'weekly', } @@ -249,9 +240,9 @@ def test_onchange_product_id_recurring_info(self): self.assertEqual( self.contract_line.recurring_invoicing_type, 'pre-paid' ) - self.assertEqual(self.contract_line.recurring_interval, 2) + self.assertEqual(self.contract_line.recurring_interval, 1) self.assertEqual(self.contract_line.is_auto_renew, True) - self.assertEqual(self.contract_line.auto_renew_interval, 6) + self.assertEqual(self.contract_line.auto_renew_interval, 12) self.assertEqual(self.contract_line.auto_renew_rule_type, 'monthly') self.assertEqual(self.contract_line.termination_notice_interval, 6) self.assertEqual( diff --git a/product_contract/views/product_template.xml b/product_contract/views/product_template.xml index dcc54caedb..fd26d5555b 100644 --- a/product_contract/views/product_template.xml +++ b/product_contract/views/product_template.xml @@ -29,30 +29,16 @@ - + - + + + + contract.contract customer form view (in contract) + + contract.contract + + primary + + + + Customer + {'default_customer_rank': 1, 'default_supplier_rank': 0, + 'res_partner_search_mode': 'customer', 'show_vat': True} + + + + [('type', '=', 'sale')] + + + + + + contract.contract supplier form view (in contract) + + contract.contract + + primary + + + + Supplier + {'default_customer_rank': False, 'default_supplier_rank': 1, + 'res_partner_search_mode': 'supplier', 'show_vat': True} + + + + [('type', '=', 'purchase')] + + + + + + + contract.contract list view (in contract) + contract.contract + + + + + + + + + + + + + + contract.contract search view (in contract) + contract.contract + + + + + + + + + + + + + + + + + + + + + + + + Customer Contracts + contract.contract + list,form + [('contract_type', '=', 'sale')] + + {'is_contract':1, + 'search_default_not_finished':1, + 'default_contract_type': 'sale'} + + + +

+ Click to create a new contract. +

+
+
+ + + list + + + + + + form + + + + + + + Supplier Contracts + contract.contract + list,form + [('contract_type', '=', 'purchase')] + + {'is_contract':1, + 'search_default_not_finished':1, + 'default_contract_type': 'purchase'} + + + +

+ Click to create a new contract. +

+
+
+ + + list + + + + + + form + + + + + + + + Contracts + + + + diff --git a/contract/views/contract_line.xml b/contract/views/contract_line.xml new file mode 100644 index 0000000000..88f6b73f92 --- /dev/null +++ b/contract/views/contract_line.xml @@ -0,0 +1,216 @@ + + + + + contract.line + + primary + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + contract.line customer form view (in contract) + + contract.line + + primary + + + + [('sale_ok', '=', True)] + + + + + + contract.line supplier form view (in contract) + + contract.line + + primary + + + + [('purchase_ok', '=', True)] + + + + True + + + + + + + contract.line list view (in contract) + contract.line + + + + + + + + + + + + + + + + + + + + + + + + + + + contract.line supplier list view (in contract) + + contract.line + primary + + + + + True + + + + + + + contract.line report tree view (in contract) + contract.line + + + + + + + + + + + + + + + + + + + + + + + + contract.line search view (in contract) + contract.line + + + + + + + + + + + + + + + + + + + + Supplier Contract Lines + contract.line + list,form + [('contract_id.contract_type', '=', 'purchase')] + + {'search_default_group_by_contract': 1} + + + + + + + Customer Contract Lines + contract.line + list,form + [('contract_id.contract_type', '=', 'sale')] + + {'search_default_group_by_contract': 1} + + + + + + + Customer Contract lines + + + + + + Supplier Contract lines + + + + + diff --git a/contract/views/contract_portal_templates.xml b/contract/views/contract_portal_templates.xml new file mode 100644 index 0000000000..1d9a927f4b --- /dev/null +++ b/contract/views/contract_portal_templates.xml @@ -0,0 +1,346 @@ + + + + + + + + diff --git a/contract/views/contract_tag.xml b/contract/views/contract_tag.xml new file mode 100644 index 0000000000..0148f75371 --- /dev/null +++ b/contract/views/contract_tag.xml @@ -0,0 +1,45 @@ + + + + + contract.tag + +
+ + + + + +
+
+
+ + contract.tag + + + + + + + + + Contract Tags + contract.tag + list,form + + + Contract Tag + + + + +
diff --git a/contract/views/contract_template.xml b/contract/views/contract_template.xml new file mode 100644 index 0000000000..8980f58202 --- /dev/null +++ b/contract/views/contract_template.xml @@ -0,0 +1,171 @@ + + + + + contract.template form view (in contract) + contract.template + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

#START#: Start date of the + invoiced period +

+

#END#: End date of the invoiced period

+

#INVOICEMONTHNAME#: Invoice month name of the invoiced period

+
+
+
+
+
+
+
+
+ + + contract.template list view (in contract) + contract.template + + + + + + + + + + + contract.template search view (in contract) + contract.template + + + + + + + + + + + + + + + Contract Templates + contract.template + list,form + + +

+ Click to create a new contract template. +

+
+
+ +
diff --git a/contract/views/contract_template_line.xml b/contract/views/contract_template_line.xml new file mode 100644 index 0000000000..fdcebc7fc8 --- /dev/null +++ b/contract/views/contract_template_line.xml @@ -0,0 +1,86 @@ + + + + + contract.template.line + +
+
+ + + + + + + + + + + + + + + + + + + + + diff --git a/contract/views/res_config_settings.xml b/contract/views/res_config_settings.xml new file mode 100644 index 0000000000..a9f2f282dd --- /dev/null +++ b/contract/views/res_config_settings.xml @@ -0,0 +1,18 @@ + + + + + res.config.settings + + + + + + + + diff --git a/contract/views/res_partner_view.xml b/contract/views/res_partner_view.xml new file mode 100644 index 0000000000..8e4a18ae21 --- /dev/null +++ b/contract/views/res_partner_view.xml @@ -0,0 +1,64 @@ + + + + + + res.partner + + + + + + + + + + + res.partner + + + + + + + + diff --git a/contract/wizards/__init__.py b/contract/wizards/__init__.py new file mode 100644 index 0000000000..2e56cd6d03 --- /dev/null +++ b/contract/wizards/__init__.py @@ -0,0 +1 @@ +from . import contract_manually_create_invoice diff --git a/contract/wizards/contract_manually_create_invoice.py b/contract/wizards/contract_manually_create_invoice.py new file mode 100644 index 0000000000..199efefa56 --- /dev/null +++ b/contract/wizards/contract_manually_create_invoice.py @@ -0,0 +1,86 @@ +# Copyright 2019 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models +from odoo.exceptions import ( + AccessDenied, + AccessError, + MissingError, + UserError, + ValidationError, +) + + +class ContractManuallyCreateInvoice(models.TransientModel): + _name = "contract.manually.create.invoice" + _description = "Contract Manually Create Invoice Wizard" + + invoice_date = fields.Date(required=True) + contract_to_invoice_count = fields.Integer( + compute="_compute_contract_to_invoice_ids" + ) + contract_to_invoice_ids = fields.Many2many( + comodel_name="contract.contract", + compute="_compute_contract_to_invoice_ids", + ) + contract_type = fields.Selection( + selection=[("sale", "Customer"), ("purchase", "Supplier")], + default="sale", + required=True, + ) + + @api.depends("invoice_date") + def _compute_contract_to_invoice_ids(self): + if not self.invoice_date: + # trick to show no invoice when no date has been entered yet + contract_to_invoice_domain = [("id", "=", False)] + else: + contract_to_invoice_domain = self.env[ + "contract.contract" + ]._get_contracts_to_invoice_domain(self.invoice_date) + self.contract_to_invoice_ids = self.env["contract.contract"].search( + contract_to_invoice_domain + [("contract_type", "=", self.contract_type)] + ) + self.contract_to_invoice_count = len(self.contract_to_invoice_ids) + + def action_show_contract_to_invoice(self): + self.ensure_one() + return { + "type": "ir.actions.act_window", + "name": self.env._("Contracts to invoice"), + "res_model": "contract.contract", + "domain": [("id", "in", self.contract_to_invoice_ids.ids)], + "view_mode": "list,form", + "context": self.env.context, + } + + def create_invoice(self): + self.ensure_one() + invoices = self.env["account.move"] + for contract in self.contract_to_invoice_ids: + try: + invoices |= contract.recurring_create_invoice() + except ( + AccessDenied, + AccessError, + MissingError, + UserError, + ValidationError, + ) as oe: + raise UserError( + self.env._( + "Failed to process the contract %(name)s [id: %(id)s]:\n%(ue)s", + name=contract.name, + id=contract.id, + ue=repr(oe), + ) + ) from oe + + return { + "type": "ir.actions.act_window", + "name": self.env._("Invoices"), + "res_model": "account.move", + "domain": [("id", "in", invoices.ids)], + "view_mode": "list,form", + "context": self.env.context, + } diff --git a/contract/wizards/contract_manually_create_invoice.xml b/contract/wizards/contract_manually_create_invoice.xml new file mode 100644 index 0000000000..37c8f93d1f --- /dev/null +++ b/contract/wizards/contract_manually_create_invoice.xml @@ -0,0 +1,86 @@ + + + + + contract.manually.create.invoice + +
+ + + + + + + + +
+
+
+
+
+ + Manually Invoice Sale Contracts + contract.manually.create.invoice + form + {'default_contract_type': 'sale'} + new + + + Manually Invoice Sale Contracts + + + + + + + Manually Invoice Purchase Contracts + contract.manually.create.invoice + form + {'default_contract_type': 'purchase'} + new + + + Manually Invoice Purchase Contracts + + + + + +
diff --git a/contract_line_successor/README.rst b/contract_line_successor/README.rst new file mode 100644 index 0000000000..9140543811 --- /dev/null +++ b/contract_line_successor/README.rst @@ -0,0 +1,170 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +======================= +Contract Line Successor +======================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:d0875078450d894287efb30f5acc2557d0290d7e2e1a1e3c17a2e54a281a79ae + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/license-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%2Fcontract-lightgray.png?logo=github + :target: https://github.com/OCA/contract/tree/19.0/contract_line_successor + :alt: OCA/contract +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/contract-19-0/contract-19-0-contract_line_successor + :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/contract&target_branch=19.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +| **Contract Line Successor** extends ``contract.line`` model to support + advanced contract lifecycle management, including suspension, + successor planning, cancellation, and renewal. +| It provides a flexible and robust framework for managing complex + contract line scenarios in a clean and structured way. + +Features +-------- + +- **Successor and Predecessor Management** + + - Link contract lines with successor and predecessor lines. + - Plan successors automatically or manually after a stop or + suspension. + +- **Contract Line Lifecycle States** + + - Manage contract lines with the following computed states: + + - ``Upcoming`` + - ``In-Progress`` + - ``To Renew`` + - ``Upcoming Close`` + - ``Closed`` + - ``Canceled`` + +- **Lifecycle Operations** + + - Stop a contract line. + - Plan a successor for a contract line. + - Stop and plan a successor in one operation (useful for + suspensions). + - Cancel and un-cancel contract lines. + - Renew contract lines automatically (new line or extension). + +- **Auto-Renewal Handling** + + - Auto-renewal based on company settings (extend existing line or + create a new one). + - Cron job to automate renewal of eligible contract lines. + +- **Data Integrity and Validation** + + - Prevent invalid successor or predecessor configurations. + - Validate state transitions and date overlaps. + - Ensure clean renewal and cancellation workflows. + +- **Audit Trail** + + - Automatic posting of chatter messages for lifecycle events like + stops, renewals, suspensions, cancellations, etc. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +- | **Auto-Renewal Strategy** + | In the company settings, define whether renewing a contract line: + + - Extends the current line (updates ``date_end``), + - or creates a new successor contract line. + + | Field: + | ``Company > Configuration > Contracts > Create new contract line at renewal`` + +- | **Scheduled Actions** + | Ensure the scheduled action ``Contract Line: Auto Renew`` is + activated if you want automatic renewal without manual + intervention. + +Usage +===== + +1. **Select a contract** and enable the option **"Recurrence at line + level?"**. +2. Once enabled, you will have access to several actions at the contract + line level: + + - **Stop** a contract line and optionally **plan a successor**. + - **Handle temporary suspensions** and **resume** the contract line + after the suspension period. + - **Cancel** and **un-cancel** contract lines if necessary. + - **Renew** contract lines either by **extending** the current line + or by **creating a new successor line** automatically. + +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 +------- + +* ACSONE SA/NV + +Contributors +------------ + +- Souheil Bejaoui souheil.bejaoui@acsone.eu (ACSONE SA/NV) + +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-sbejaoui| image:: https://github.com/sbejaoui.png?size=40px + :target: https://github.com/sbejaoui + :alt: sbejaoui + +Current `maintainer `__: + +|maintainer-sbejaoui| + +This module is part of the `OCA/contract `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/contract_line_successor/__init__.py b/contract_line_successor/__init__.py new file mode 100644 index 0000000000..aee8895e7a --- /dev/null +++ b/contract_line_successor/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizards diff --git a/contract_line_successor/__manifest__.py b/contract_line_successor/__manifest__.py new file mode 100644 index 0000000000..0c9d6e0e7f --- /dev/null +++ b/contract_line_successor/__manifest__.py @@ -0,0 +1,24 @@ +# Copyright 2018 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Contract Line Successor", + "version": "19.0.1.0.0", + "license": "AGPL-3", + "author": "ACSONE SA/NV,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/contract", + "depends": ["contract"], + "development_status": "Production/Stable", + "maintainers": ["sbejaoui"], + "data": [ + "data/contract_renew_cron.xml", + "security/ir.model.access.csv", + "views/contract_template.xml", + "views/contract_contract.xml", + "views/contract_template_line.xml", + "views/contract_line.xml", + "wizards/contract_line_wizard.xml", + "views/res_config_settings.xml", + ], + "demo": [], +} diff --git a/contract_line_successor/data/contract_renew_cron.xml b/contract_line_successor/data/contract_renew_cron.xml new file mode 100644 index 0000000000..aea0e67fb7 --- /dev/null +++ b/contract_line_successor/data/contract_renew_cron.xml @@ -0,0 +1,12 @@ + + + + Renew Contract lines + + code + model.cron_renew_contract_line() + + 1 + days + + diff --git a/contract_line_successor/i18n/contract_line_successor.pot b/contract_line_successor/i18n/contract_line_successor.pot new file mode 100644 index 0000000000..c89295fc0f --- /dev/null +++ b/contract_line_successor/i18n/contract_line_successor.pot @@ -0,0 +1,539 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * contract_line_successor +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.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: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "A canceled contract line can't be set to auto-renew" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "A contract line with a successor can't be set to auto-renew" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "A contract line with a successor must have a end date" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "An auto-renew line must have a end date" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Are you sure you want to cancel this line" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_auto_renew +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template_line__is_auto_renew +msgid "Auto Renew" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Auto renew interval should be different then 0" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_search_view +msgid "Auto-renew" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_plan_successor_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_plan_successor_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_uncancel_form_view +msgid "Cancel" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_cancel_allowed +msgid "Cancel allowed?" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Cancel not allowed for this line" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__canceled +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_search_view +msgid "Canceled" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_contract__line_recurrence +msgid "" +"Check this if you want to control recurrence at the line level instead of " +"for the whole contract." +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__closed +msgid "Closed" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_res_company +msgid "Companies" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_contract_contract +msgid "Contract" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_contract_line +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__contract_line_id +msgid "Contract Line" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_contract_line_wizard +msgid "Contract Line Wizard" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_line__predecessor_contract_line_id +msgid "Contract Line origin of this one." +msgstr "" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_contract_template +msgid "Contract Template" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_contract_template_line +msgid "Contract Template Line" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Contract line Un-canceled: %s" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Contract line and its predecessor overlapped" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Contract line and its successor overlapped" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Contract line canceled: %s" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "" +"Contract line for %(product)s\n" +" stopped:
\n" +" - End: %(old_end)s -- %(new_end)s\n" +" " +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "" +"Contract line for %(product)s\n" +" planned a successor:
\n" +" - Start: %(new_date_start)s\n" +"
\n" +" - End: %(new_date_end)s\n" +" " +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "" +"Contract line for %(product)s\n" +" renewed:
\n" +" - Start: %(new_date_start)s\n" +"
\n" +" - End: %(new_date_end)s\n" +" " +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "" +"Contract line for %(product)s\n" +" suspended:
\n" +" - Suspension Start: %(new_date_start)s\n" +"
\n" +" - Suspension End: %(new_date_end)s\n" +" " +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Contract line must be canceled before delete" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_res_company__create_new_line_at_contract_line_renew +#: model:ir.model.fields,field_description:contract_line_successor.field_res_config_settings__create_new_line_at_contract_line_renew +msgid "Create New Line At Contract Line Renew" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__create_uid +msgid "Created by" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__create_date +msgid "Created on" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__date_end +msgid "Date End" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__date_start +msgid "Date Start" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__auto_renew_rule_type__daily +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__termination_notice_rule_type__daily +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__auto_renew_rule_type__daily +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__termination_notice_rule_type__daily +msgid "Day(s)" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__display_name +msgid "Display Name" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__id +msgid "ID" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_res_company__create_new_line_at_contract_line_renew +#: model:ir.model.fields,help:contract_line_successor.field_res_config_settings__create_new_line_at_contract_line_renew +msgid "" +"If checked, a new line will be generated at contract line renew and linked " +"to the original one as successor. The default behavior is to extend the end " +"date of the contract by a new subscription period" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_line__successor_contract_line_id +msgid "" +"In case of restart after suspension, this field contain the new contract " +"line created." +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__in-progress +msgid "In-progress" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_contract__is_auto_renew +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__is_auto_renew +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template__is_auto_renew +msgid "Is Auto Renew" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_form_view +msgid "Is suspension without end date" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__write_date +msgid "Last Updated on" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__manual_renew_needed +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__manual_renew_needed +msgid "Manual Renew Needed" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__auto_renew_rule_type__monthly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__termination_notice_rule_type__monthly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__auto_renew_rule_type__monthly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__termination_notice_rule_type__monthly +msgid "Month(s)" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__recurring_next_date +msgid "Next Invoice Date" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Plan Start" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_plan_successor_allowed +msgid "Plan successor allowed?" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Plan successor not allowed for this line" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_form_view +msgid "Predecessor & Successor Lines" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__predecessor_contract_line_id +msgid "Predecessor Contract Line" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_contract__line_recurrence +msgid "Recurrence at line level?" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Renew" +msgstr "" + +#. module: contract_line_successor +#: model:ir.actions.server,name:contract_line_successor.contract_line_cron_for_renew_ir_actions_server +msgid "Renew Contract lines" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__auto_renew_interval +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template_line__auto_renew_interval +msgid "Renew Every" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_line__auto_renew_interval +#: model:ir.model.fields,help:contract_line_successor.field_contract_template_line__auto_renew_interval +msgid "Renew every (Days/Weeks/Months/Years)" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_template_line_form_view +msgid "Renewal" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__auto_renew_rule_type +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template_line__auto_renew_rule_type +msgid "Renewal type" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_line__auto_renew_rule_type +#: model:ir.model.fields,help:contract_line_successor.field_contract_template_line__auto_renew_rule_type +msgid "Specify interval for automatic renewal." +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__state +msgid "State" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Stop" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_form_view +msgid "Stop Date" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Stop Plan Successor" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_stop_allowed +msgid "Stop allowed?" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Stop not allowed for this line" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_stop_plan_successor_allowed +msgid "Stop/Plan successor allowed?" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Stop/Plan successor not allowed for this line" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__successor_contract_line_id +msgid "Successor Contract Line" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_plan_successor_form_view +msgid "Suspension End Date" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_plan_successor_form_view +msgid "Suspension Start Date" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__termination_notice_interval +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template_line__termination_notice_interval +msgid "Termination Notice Before" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__termination_notice_date +msgid "Termination Notice Date" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__termination_notice_rule_type +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template_line__termination_notice_rule_type +msgid "Termination Notice type" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_line__manual_renew_needed +#: model:ir.model.fields,help:contract_line_successor.field_contract_line_wizard__manual_renew_needed +msgid "" +"This flag is used to make a difference between a definitive stopand " +"temporary one for which a user is not able to plan asuccessor in advance" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__to-renew +msgid "To renew" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_un_cancel_allowed +msgid "Un-Cancel allowed?" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Un-cancel" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Un-cancel not allowed for this line" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__upcoming +msgid "Upcoming" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__upcoming-close +msgid "Upcoming Close" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_plan_successor_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_plan_successor_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_uncancel_form_view +msgid "Validate" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__auto_renew_rule_type__weekly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__termination_notice_rule_type__weekly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__auto_renew_rule_type__weekly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__termination_notice_rule_type__weekly +msgid "Week(s)" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__auto_renew_rule_type__yearly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__auto_renew_rule_type__yearly +msgid "Year(s)" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "You can't delay a contract line invoiced at least one time." +msgstr "" diff --git a/contract_line_successor/i18n/it.po b/contract_line_successor/i18n/it.po new file mode 100644 index 0000000000..6113517cd7 --- /dev/null +++ b/contract_line_successor/i18n/it.po @@ -0,0 +1,580 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * contract_line_successor +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-07-01 13:25+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\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 5.10.4\n" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "A canceled contract line can't be set to auto-renew" +msgstr "" +"Una riga di contratto annullato non può essere impostata per l'auto-rinnovo" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "A contract line with a successor can't be set to auto-renew" +msgstr "" +"Una riga di contratto con successore non può essere impostata per l'auto-" +"rinnovo" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "A contract line with a successor must have a end date" +msgstr "Una riga di contratto con successore deve avere una data fine" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "An auto-renew line must have a end date" +msgstr "Una riga di auto-rinnovo deve avere una data fine" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Are you sure you want to cancel this line" +msgstr "Sei sicuro di voler annullare questa riga" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_auto_renew +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template_line__is_auto_renew +msgid "Auto Renew" +msgstr "Rinnovo automatico" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Auto renew interval should be different then 0" +msgstr "L'intervallo di rinnovo automatico deve essere diverso da 0" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_search_view +msgid "Auto-renew" +msgstr "Rinnovo automatico" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_plan_successor_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_plan_successor_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_uncancel_form_view +msgid "Cancel" +msgstr "Annulla" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_cancel_allowed +msgid "Cancel allowed?" +msgstr "Annullamento permesso?" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Cancel not allowed for this line" +msgstr "Annullamento non permesso per questa riga" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__canceled +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_search_view +msgid "Canceled" +msgstr "Annullata" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_contract__line_recurrence +msgid "" +"Check this if you want to control recurrence at the line level instead of " +"for the whole contract." +msgstr "" +"Selezionare questa opzione se si vuole controllare la ricorrenza a livello " +"di riga al posto dell'intero contratto." + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__closed +msgid "Closed" +msgstr "Chiusa" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_res_company +msgid "Companies" +msgstr "Aziende" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_res_config_settings +msgid "Config Settings" +msgstr "Impostazioni configurazione" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_contract_contract +msgid "Contract" +msgstr "Contratto" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_contract_line +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__contract_line_id +msgid "Contract Line" +msgstr "Riga contratto" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_contract_line_wizard +msgid "Contract Line Wizard" +msgstr "Procedura guidata riga contratto" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_line__predecessor_contract_line_id +msgid "Contract Line origin of this one." +msgstr "Riga contratto origine di questa." + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_contract_template +msgid "Contract Template" +msgstr "Modello di contratto" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_contract_template_line +msgid "Contract Template Line" +msgstr "Riga modello di contratto" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Contract line Un-canceled: %s" +msgstr "Riga contratto recuperata: %s" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Contract line and its predecessor overlapped" +msgstr "Riga contratto e sua precedente sovrapposte" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Contract line and its successor overlapped" +msgstr "Riga contratto e sua seguente sovrapposte" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Contract line canceled: %s" +msgstr "Riga contratto annullata: %s" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "" +"Contract line for %(product)s\n" +" stopped:
\n" +" - End: %(old_end)s -- " +"%(new_end)s\n" +" " +msgstr "" +"Riga contratto per %(product)s\n" +" arrestata:
\n" +" - Fine:%(old_end)s -- " +"%(new_end)s\n" +" " + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "" +"Contract line for %(product)s\n" +" planned a successor:
\n" +" - Start: %(new_date_start)s\n" +"
\n" +" - End: %(new_date_end)s\n" +" " +msgstr "" +"Riga contratto per %(product)s\n" +" pianificata come successore:
\n" +" - Inizio: %(new_date_start)s\n" +"
\n" +" - Fine: %(new_date_end)s\n" +" " + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "" +"Contract line for %(product)s\n" +" renewed:
\n" +" - Start: %(new_date_start)s\n" +"
\n" +" - End: %(new_date_end)s\n" +" " +msgstr "" +"Riga contratto per %(product)s\n" +" rinnovata:
\n" +" - Inizio: %(new_date_start)s\n" +"
\n" +" - Fine: %(new_date_end)s\n" +" " + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "" +"Contract line for %(product)s\n" +" suspended:
\n" +" - Suspension Start: %(new_date_start)s\n" +"
\n" +" - Suspension End: %(new_date_end)s\n" +" " +msgstr "" +"Riga contratto per %(product)s\n" +" sospesa:
\n" +" - Inizio sospensione: %(new_date_start)s\n" +"
\n" +" - Fine sospensione: %(new_date_end)s\n" +" " + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Contract line must be canceled before delete" +msgstr "La riga contratto deve essere annullata prima di essere eliminata" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_res_company__create_new_line_at_contract_line_renew +#: model:ir.model.fields,field_description:contract_line_successor.field_res_config_settings__create_new_line_at_contract_line_renew +msgid "Create New Line At Contract Line Renew" +msgstr "Crea nuova riga al rinnovo linea di contratto" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__create_uid +msgid "Created by" +msgstr "Creato da" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__create_date +msgid "Created on" +msgstr "Creato il" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__date_end +msgid "Date End" +msgstr "Data fine" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__date_start +msgid "Date Start" +msgstr "Data inizio" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__auto_renew_rule_type__daily +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__termination_notice_rule_type__daily +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__auto_renew_rule_type__daily +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__termination_notice_rule_type__daily +msgid "Day(s)" +msgstr "Giorno(i)" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__display_name +msgid "Display Name" +msgstr "Nome visualizzato" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__id +msgid "ID" +msgstr "ID" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_res_company__create_new_line_at_contract_line_renew +#: model:ir.model.fields,help:contract_line_successor.field_res_config_settings__create_new_line_at_contract_line_renew +msgid "" +"If checked, a new line will be generated at contract line renew and linked " +"to the original one as successor. The default behavior is to extend the end " +"date of the contract by a new subscription period" +msgstr "" +"Se selezionato, una nuova riga verrà generata al rinnovo della riga di " +"contratto e collegata all'originale come successore. Il comportamento " +"predefinito è quello di estendere la data di fine del contratto per un nuovo " +"periodo di abbonamento" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_line__successor_contract_line_id +msgid "" +"In case of restart after suspension, this field contain the new contract " +"line created." +msgstr "" +"In caso di riavvio dopo la sospensione, questo campo conterrà la nuova riga " +"di contratto creata." + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__in-progress +msgid "In-progress" +msgstr "In corso" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_contract__is_auto_renew +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__is_auto_renew +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template__is_auto_renew +msgid "Is Auto Renew" +msgstr "Rinnovo automatico" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_form_view +msgid "Is suspension without end date" +msgstr "È una sospensione senza data fine" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__write_uid +msgid "Last Updated by" +msgstr "Ultimo aggiornamento di" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__write_date +msgid "Last Updated on" +msgstr "Ultimo aggiornamento il" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__manual_renew_needed +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__manual_renew_needed +msgid "Manual Renew Needed" +msgstr "Rinnovo manuale necessario" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__auto_renew_rule_type__monthly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__termination_notice_rule_type__monthly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__auto_renew_rule_type__monthly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__termination_notice_rule_type__monthly +msgid "Month(s)" +msgstr "Mese(i)" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__recurring_next_date +msgid "Next Invoice Date" +msgstr "Data prossima fattura" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Plan Start" +msgstr "Inizio piano" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_plan_successor_allowed +msgid "Plan successor allowed?" +msgstr "Pianificazione successore permessa?" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Plan successor not allowed for this line" +msgstr "Pianificazione successore non permessa per questa riga" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_form_view +msgid "Predecessor & Successor Lines" +msgstr "Righe precedente e successiva" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__predecessor_contract_line_id +msgid "Predecessor Contract Line" +msgstr "Riga contratto predecessore" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_contract__line_recurrence +msgid "Recurrence at line level?" +msgstr "Ricorrenza a livello riga?" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Renew" +msgstr "Rinnova" + +#. module: contract_line_successor +#: model:ir.actions.server,name:contract_line_successor.contract_line_cron_for_renew_ir_actions_server +msgid "Renew Contract lines" +msgstr "Rinnova righe contratto" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__auto_renew_interval +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template_line__auto_renew_interval +msgid "Renew Every" +msgstr "Rinnovare ogni" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_line__auto_renew_interval +#: model:ir.model.fields,help:contract_line_successor.field_contract_template_line__auto_renew_interval +msgid "Renew every (Days/Weeks/Months/Years)" +msgstr "Rinnovare ogni (giorni/settimane/mesi/anni)" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_template_line_form_view +msgid "Renewal" +msgstr "Rinnovo" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__auto_renew_rule_type +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template_line__auto_renew_rule_type +msgid "Renewal type" +msgstr "Tipo rinnovo" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_line__auto_renew_rule_type +#: model:ir.model.fields,help:contract_line_successor.field_contract_template_line__auto_renew_rule_type +msgid "Specify interval for automatic renewal." +msgstr "Specificare l'intervallo per il rinnovo automatico." + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__state +msgid "State" +msgstr "Stato" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Stop" +msgstr "Fine" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_form_view +msgid "Stop Date" +msgstr "Data fine" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Stop Plan Successor" +msgstr "Fermare pianificazione successore" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_stop_allowed +msgid "Stop allowed?" +msgstr "Arresto permesso?" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Stop not allowed for this line" +msgstr "Arresto non permesso per questa riga" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_stop_plan_successor_allowed +msgid "Stop/Plan successor allowed?" +msgstr "Arresto/pianificazione del successore permessi?" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Stop/Plan successor not allowed for this line" +msgstr "Arresto/pianificazione del successore non permessi per questa riga" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__successor_contract_line_id +msgid "Successor Contract Line" +msgstr "Riga contratto successore" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_plan_successor_form_view +msgid "Suspension End Date" +msgstr "Data fine sospensione" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_plan_successor_form_view +msgid "Suspension Start Date" +msgstr "Data inizio sospensione" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__termination_notice_interval +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template_line__termination_notice_interval +msgid "Termination Notice Before" +msgstr "Notifica termine prima di" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__termination_notice_date +msgid "Termination Notice Date" +msgstr "Data notifica termine" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__termination_notice_rule_type +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template_line__termination_notice_rule_type +msgid "Termination Notice type" +msgstr "Tipo notifica termine" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_line__manual_renew_needed +#: model:ir.model.fields,help:contract_line_successor.field_contract_line_wizard__manual_renew_needed +msgid "" +"This flag is used to make a difference between a definitive stopand " +"temporary one for which a user is not able to plan asuccessor in advance" +msgstr "" +"Questo campo è usato per distinguere tra una interruzione permanente e una " +"temporanea per la quale l'utente non può al momento definire cosa seguirà" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__to-renew +msgid "To renew" +msgstr "Da rinnovare" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_un_cancel_allowed +msgid "Un-Cancel allowed?" +msgstr "Ri-attivazione permessa?" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Un-cancel" +msgstr "Ri-attivare" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Un-cancel not allowed for this line" +msgstr "Ri-attivazione non permessa per questa riga" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__upcoming +msgid "Upcoming" +msgstr "Imminente" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__upcoming-close +msgid "Upcoming Close" +msgstr "Chiusura imminente" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_plan_successor_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_plan_successor_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_uncancel_form_view +msgid "Validate" +msgstr "Valida" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__auto_renew_rule_type__weekly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__termination_notice_rule_type__weekly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__auto_renew_rule_type__weekly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__termination_notice_rule_type__weekly +msgid "Week(s)" +msgstr "Settimana(e)" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__auto_renew_rule_type__yearly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__auto_renew_rule_type__yearly +msgid "Year(s)" +msgstr "Anno(i)" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "You can't delay a contract line invoiced at least one time." +msgstr "" +"Non è possibile ritardare una riga contratto fatturata almeno una volta." diff --git a/contract_line_successor/i18n/pt_BR.po b/contract_line_successor/i18n/pt_BR.po new file mode 100644 index 0000000000..a8edbee51a --- /dev/null +++ b/contract_line_successor/i18n/pt_BR.po @@ -0,0 +1,541 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * contract_line_successor +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: pt_BR\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: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "A canceled contract line can't be set to auto-renew" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "A contract line with a successor can't be set to auto-renew" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "A contract line with a successor must have a end date" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "An auto-renew line must have a end date" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Are you sure you want to cancel this line" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_auto_renew +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template_line__is_auto_renew +msgid "Auto Renew" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Auto renew interval should be different then 0" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_search_view +msgid "Auto-renew" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_plan_successor_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_plan_successor_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_uncancel_form_view +msgid "Cancel" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_cancel_allowed +msgid "Cancel allowed?" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Cancel not allowed for this line" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__canceled +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_search_view +msgid "Canceled" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_contract__line_recurrence +msgid "" +"Check this if you want to control recurrence at the line level instead of " +"for the whole contract." +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__closed +msgid "Closed" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_res_company +msgid "Companies" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_contract_contract +msgid "Contract" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_contract_line +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__contract_line_id +msgid "Contract Line" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_contract_line_wizard +msgid "Contract Line Wizard" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_line__predecessor_contract_line_id +msgid "Contract Line origin of this one." +msgstr "" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_contract_template +msgid "Contract Template" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_contract_template_line +msgid "Contract Template Line" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Contract line Un-canceled: %s" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Contract line and its predecessor overlapped" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Contract line and its successor overlapped" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Contract line canceled: %s" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "" +"Contract line for %(product)s\n" +" stopped:
\n" +" - End: %(old_end)s -- " +"%(new_end)s\n" +" " +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "" +"Contract line for %(product)s\n" +" planned a successor:
\n" +" - Start: %(new_date_start)s\n" +"
\n" +" - End: %(new_date_end)s\n" +" " +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "" +"Contract line for %(product)s\n" +" renewed:
\n" +" - Start: %(new_date_start)s\n" +"
\n" +" - End: %(new_date_end)s\n" +" " +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "" +"Contract line for %(product)s\n" +" suspended:
\n" +" - Suspension Start: %(new_date_start)s\n" +"
\n" +" - Suspension End: %(new_date_end)s\n" +" " +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Contract line must be canceled before delete" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_res_company__create_new_line_at_contract_line_renew +#: model:ir.model.fields,field_description:contract_line_successor.field_res_config_settings__create_new_line_at_contract_line_renew +msgid "Create New Line At Contract Line Renew" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__create_uid +msgid "Created by" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__create_date +msgid "Created on" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__date_end +msgid "Date End" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__date_start +msgid "Date Start" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__auto_renew_rule_type__daily +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__termination_notice_rule_type__daily +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__auto_renew_rule_type__daily +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__termination_notice_rule_type__daily +msgid "Day(s)" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__display_name +msgid "Display Name" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__id +msgid "ID" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_res_company__create_new_line_at_contract_line_renew +#: model:ir.model.fields,help:contract_line_successor.field_res_config_settings__create_new_line_at_contract_line_renew +msgid "" +"If checked, a new line will be generated at contract line renew and linked " +"to the original one as successor. The default behavior is to extend the end " +"date of the contract by a new subscription period" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_line__successor_contract_line_id +msgid "" +"In case of restart after suspension, this field contain the new contract " +"line created." +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__in-progress +msgid "In-progress" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_contract__is_auto_renew +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__is_auto_renew +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template__is_auto_renew +msgid "Is Auto Renew" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_form_view +msgid "Is suspension without end date" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__write_date +msgid "Last Updated on" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__manual_renew_needed +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__manual_renew_needed +msgid "Manual Renew Needed" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__auto_renew_rule_type__monthly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__termination_notice_rule_type__monthly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__auto_renew_rule_type__monthly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__termination_notice_rule_type__monthly +msgid "Month(s)" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__recurring_next_date +msgid "Next Invoice Date" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Plan Start" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_plan_successor_allowed +msgid "Plan successor allowed?" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Plan successor not allowed for this line" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_form_view +msgid "Predecessor & Successor Lines" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__predecessor_contract_line_id +msgid "Predecessor Contract Line" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_contract__line_recurrence +msgid "Recurrence at line level?" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Renew" +msgstr "" + +#. module: contract_line_successor +#: model:ir.actions.server,name:contract_line_successor.contract_line_cron_for_renew_ir_actions_server +msgid "Renew Contract lines" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__auto_renew_interval +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template_line__auto_renew_interval +msgid "Renew Every" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_line__auto_renew_interval +#: model:ir.model.fields,help:contract_line_successor.field_contract_template_line__auto_renew_interval +msgid "Renew every (Days/Weeks/Months/Years)" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_template_line_form_view +msgid "Renewal" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__auto_renew_rule_type +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template_line__auto_renew_rule_type +msgid "Renewal type" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_line__auto_renew_rule_type +#: model:ir.model.fields,help:contract_line_successor.field_contract_template_line__auto_renew_rule_type +msgid "Specify interval for automatic renewal." +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__state +msgid "State" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Stop" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_form_view +msgid "Stop Date" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Stop Plan Successor" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_stop_allowed +msgid "Stop allowed?" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Stop not allowed for this line" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_stop_plan_successor_allowed +msgid "Stop/Plan successor allowed?" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Stop/Plan successor not allowed for this line" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__successor_contract_line_id +msgid "Successor Contract Line" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_plan_successor_form_view +msgid "Suspension End Date" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_plan_successor_form_view +msgid "Suspension Start Date" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__termination_notice_interval +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template_line__termination_notice_interval +msgid "Termination Notice Before" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__termination_notice_date +msgid "Termination Notice Date" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__termination_notice_rule_type +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template_line__termination_notice_rule_type +msgid "Termination Notice type" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_line__manual_renew_needed +#: model:ir.model.fields,help:contract_line_successor.field_contract_line_wizard__manual_renew_needed +msgid "" +"This flag is used to make a difference between a definitive stopand " +"temporary one for which a user is not able to plan asuccessor in advance" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__to-renew +msgid "To renew" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_un_cancel_allowed +msgid "Un-Cancel allowed?" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Un-cancel" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Un-cancel not allowed for this line" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__upcoming +msgid "Upcoming" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__upcoming-close +msgid "Upcoming Close" +msgstr "" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_plan_successor_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_plan_successor_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_uncancel_form_view +msgid "Validate" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__auto_renew_rule_type__weekly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__termination_notice_rule_type__weekly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__auto_renew_rule_type__weekly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__termination_notice_rule_type__weekly +msgid "Week(s)" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__auto_renew_rule_type__yearly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__auto_renew_rule_type__yearly +msgid "Year(s)" +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "You can't delay a contract line invoiced at least one time." +msgstr "" diff --git a/contract_line_successor/i18n/sv.po b/contract_line_successor/i18n/sv.po new file mode 100644 index 0000000000..27a53d6101 --- /dev/null +++ b/contract_line_successor/i18n/sv.po @@ -0,0 +1,577 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * contract_line_successor +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-08-05 14:21+0000\n" +"Last-Translator: jakobkrabbe \n" +"Language-Team: none\n" +"Language: sv\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 5.10.4\n" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "A canceled contract line can't be set to auto-renew" +msgstr "En avbruten avtalslinje kan inte ställas in på automatisk förnyelse" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "A contract line with a successor can't be set to auto-renew" +msgstr "" +"En avtalsrad med en efterträdare kan inte ställas in på automatisk förnyelse" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "A contract line with a successor must have a end date" +msgstr "En avtalsrad med en efterträdare måste ha ett slutdatum" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "An auto-renew line must have a end date" +msgstr "En linje för automatisk förnyelse måste ha ett slutdatum" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Are you sure you want to cancel this line" +msgstr "Är du säker på att du vill avbryta denna linje" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_auto_renew +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template_line__is_auto_renew +msgid "Auto Renew" +msgstr "Auto Förnya" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Auto renew interval should be different then 0" +msgstr "Intervallet för automatisk förnyelse bör vara annorlunda än 0" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_search_view +msgid "Auto-renew" +msgstr "Förnya automatiskt" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_plan_successor_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_plan_successor_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_uncancel_form_view +msgid "Cancel" +msgstr "Avbryt" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_cancel_allowed +msgid "Cancel allowed?" +msgstr "Annullering tillåten?" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Cancel not allowed for this line" +msgstr "Avbryt inte tillåtet för denna linje" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__canceled +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_search_view +msgid "Canceled" +msgstr "Annullerad" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_contract__line_recurrence +msgid "" +"Check this if you want to control recurrence at the line level instead of " +"for the whole contract." +msgstr "" +"Markera detta om du vill kontrollera återfall på radnivå i stället för för " +"hela kontraktet." + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__closed +msgid "Closed" +msgstr "Stängt" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_res_company +msgid "Companies" +msgstr "Företag" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_res_config_settings +msgid "Config Settings" +msgstr "Konfigureringsinställningar" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_contract_contract +msgid "Contract" +msgstr "Kontrakt" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_contract_line +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__contract_line_id +msgid "Contract Line" +msgstr "Kontraktsrad" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_contract_line_wizard +msgid "Contract Line Wizard" +msgstr "Assistent för kontraktsrader" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_line__predecessor_contract_line_id +msgid "Contract Line origin of this one." +msgstr "Kontraktsrader ursprunget till den här." + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_contract_template +msgid "Contract Template" +msgstr "Mall för avtal" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_contract_template_line +msgid "Contract Template Line" +msgstr "Kontraktsmall linje" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Contract line Un-canceled: %s" +msgstr "Kontraktsrad ej avbruten: %s" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Contract line and its predecessor overlapped" +msgstr "Kontraktsraden och dess föregångare överlappade varandra" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Contract line and its successor overlapped" +msgstr "Kontraktsraden och dess efterföljare överlappade varandra" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Contract line canceled: %s" +msgstr "Kontraktslinje avbruten: %s" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "" +"Contract line for %(product)s\n" +" stopped:
\n" +" - End: %(old_end)s -- %(new_end)s\n" +" " +msgstr "" +"Avtalsrad för %(product)s\n" +" stoppad:
\n" +" - Slut: %(old_end)s -- " +"%(new_end)s\n" +" " + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "" +"Contract line for %(product)s\n" +" planned a successor:
\n" +" - Start: %(new_date_start)s\n" +"
\n" +" - End: %(new_date_end)s\n" +" " +msgstr "" +"Kontraktsrad för %(product)s\n" +" planerade en efterträdare:
\n" +" - Start: %(new_date_start)s\n" +"
\n" +" - Slut: %(new_date_end)s\n" +" " + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "" +"Contract line for %(product)s\n" +" renewed:
\n" +" - Start: %(new_date_start)s\n" +"
\n" +" - End: %(new_date_end)s\n" +" " +msgstr "" +"Avtalsrad för %(product)s\n" +" förnyas:
\n" +" - Start: %(new_date_start)s\n" +"
\n" +" - Slut: %(new_date_end)s\n" +" " + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "" +"Contract line for %(product)s\n" +" suspended:
\n" +" - Suspension Start: %(new_date_start)s\n" +"
\n" +" - Suspension End: %(new_date_end)s\n" +" " +msgstr "" +"Kontraktsrad för %(product)s.\n" +" avstängd:
\n" +" - Avstängningens start: %(new_date_start)s\n" +"
\n" +" - Avstängningens slut: %(new_date_end)s\n" +" " + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Contract line must be canceled before delete" +msgstr "Kontraktsraden måste annulleras innan den raderas" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_res_company__create_new_line_at_contract_line_renew +#: model:ir.model.fields,field_description:contract_line_successor.field_res_config_settings__create_new_line_at_contract_line_renew +msgid "Create New Line At Contract Line Renew" +msgstr "Skapa ny linje vid förnyelse av kontraktslinje" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__create_uid +msgid "Created by" +msgstr "Skapad av" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__create_date +msgid "Created on" +msgstr "Skapad på" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__date_end +msgid "Date End" +msgstr "Datum Slut" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__date_start +msgid "Date Start" +msgstr "Datum Start" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__auto_renew_rule_type__daily +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__termination_notice_rule_type__daily +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__auto_renew_rule_type__daily +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__termination_notice_rule_type__daily +msgid "Day(s)" +msgstr "Dag(ar)" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__display_name +msgid "Display Name" +msgstr "Visningsnamn" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__id +msgid "ID" +msgstr "ID" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_res_company__create_new_line_at_contract_line_renew +#: model:ir.model.fields,help:contract_line_successor.field_res_config_settings__create_new_line_at_contract_line_renew +msgid "" +"If checked, a new line will be generated at contract line renew and linked " +"to the original one as successor. The default behavior is to extend the end " +"date of the contract by a new subscription period" +msgstr "" +"Om det är markerat kommer en ny rad att genereras vid förnyelse av " +"avtalsraden och länkas till den ursprungliga raden som efterträdare. " +"Standardbeteendet är att förlänga avtalets slutdatum med en ny " +"prenumerationsperiod" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_line__successor_contract_line_id +msgid "" +"In case of restart after suspension, this field contain the new contract " +"line created." +msgstr "" +"Vid omstart efter avbrytande innehåller detta fält den nya avtalslinje som " +"skapats." + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__in-progress +msgid "In-progress" +msgstr "Pågående arbete" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_contract__is_auto_renew +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__is_auto_renew +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template__is_auto_renew +msgid "Is Auto Renew" +msgstr "Är Auto Renew" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_form_view +msgid "Is suspension without end date" +msgstr "Är avstängning utan slutdatum" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__write_uid +msgid "Last Updated by" +msgstr "Senast uppdaterad av" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__write_date +msgid "Last Updated on" +msgstr "Senast uppdaterad den" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__manual_renew_needed +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__manual_renew_needed +msgid "Manual Renew Needed" +msgstr "Manuell förnyelse behövs" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__auto_renew_rule_type__monthly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__termination_notice_rule_type__monthly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__auto_renew_rule_type__monthly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__termination_notice_rule_type__monthly +msgid "Month(s)" +msgstr "Månad(er)" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__recurring_next_date +msgid "Next Invoice Date" +msgstr "Nästa fakturadatum" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Plan Start" +msgstr "Starta plan" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_plan_successor_allowed +msgid "Plan successor allowed?" +msgstr "Plan efterträdare tillåtet?" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Plan successor not allowed for this line" +msgstr "Planföljare inte tillåten för denna linje" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_form_view +msgid "Predecessor & Successor Lines" +msgstr "Föregångare och efterträdare" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__predecessor_contract_line_id +msgid "Predecessor Contract Line" +msgstr "Föregångare Kontraktslinje" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_contract__line_recurrence +msgid "Recurrence at line level?" +msgstr "Återfall på linjenivå?" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Renew" +msgstr "Förnya" + +#. module: contract_line_successor +#: model:ir.actions.server,name:contract_line_successor.contract_line_cron_for_renew_ir_actions_server +msgid "Renew Contract lines" +msgstr "Förnya kontraktsrader" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__auto_renew_interval +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template_line__auto_renew_interval +msgid "Renew Every" +msgstr "Förnya varje" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_line__auto_renew_interval +#: model:ir.model.fields,help:contract_line_successor.field_contract_template_line__auto_renew_interval +msgid "Renew every (Days/Weeks/Months/Years)" +msgstr "Förnya varje (Dagar/Veckor/Månader/år)" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_template_line_form_view +msgid "Renewal" +msgstr "Förnyelse" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__auto_renew_rule_type +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template_line__auto_renew_rule_type +msgid "Renewal type" +msgstr "Typ av förnyelse" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_line__auto_renew_rule_type +#: model:ir.model.fields,help:contract_line_successor.field_contract_template_line__auto_renew_rule_type +msgid "Specify interval for automatic renewal." +msgstr "Ange intervall för automatisk förnyelse." + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__state +msgid "State" +msgstr "Stat" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Stop" +msgstr "Stopp" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_form_view +msgid "Stop Date" +msgstr "Stoppdatum" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Stop Plan Successor" +msgstr "Stoppa planens efterträdare" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_stop_allowed +msgid "Stop allowed?" +msgstr "Stopp tillåtet?" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Stop not allowed for this line" +msgstr "Stopp inte tillåtet för denna linje" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_stop_plan_successor_allowed +msgid "Stop/Plan successor allowed?" +msgstr "Stop/Plan efterträdare tillåten?" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Stop/Plan successor not allowed for this line" +msgstr "Stop/Plan efterträdare inte tillåtet för denna linje" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__successor_contract_line_id +msgid "Successor Contract Line" +msgstr "Efterföljande kontraktslinje" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_plan_successor_form_view +msgid "Suspension End Date" +msgstr "Slutdatum för upphävande" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_plan_successor_form_view +msgid "Suspension Start Date" +msgstr "Startdatum för avstängning" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__termination_notice_interval +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template_line__termination_notice_interval +msgid "Termination Notice Before" +msgstr "Meddelande om uppsägning före" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__termination_notice_date +msgid "Termination Notice Date" +msgstr "Datum för meddelande om uppsägning" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__termination_notice_rule_type +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template_line__termination_notice_rule_type +msgid "Termination Notice type" +msgstr "Typ av uppsägningsmeddelande" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_line__manual_renew_needed +#: model:ir.model.fields,help:contract_line_successor.field_contract_line_wizard__manual_renew_needed +msgid "" +"This flag is used to make a difference between a definitive stopand " +"temporary one for which a user is not able to plan asuccessor in advance" +msgstr "" +"Denna flagga används för att skilja mellan ett definitivt stopp och ett " +"tillfälligt stopp för vilket användaren inte kan planera en efterföljare i " +"förväg" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__to-renew +msgid "To renew" +msgstr "Att förnya" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_un_cancel_allowed +msgid "Un-Cancel allowed?" +msgstr "Återuppta avbokning tillåten?" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Un-cancel" +msgstr "Återkalla" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Un-cancel not allowed for this line" +msgstr "Avbeställning inte tillåten för denna linje" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__upcoming +msgid "Upcoming" +msgstr "Kommande" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__upcoming-close +msgid "Upcoming Close" +msgstr "Kommande stängning" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_plan_successor_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_plan_successor_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_uncancel_form_view +msgid "Validate" +msgstr "Validera" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__auto_renew_rule_type__weekly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__termination_notice_rule_type__weekly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__auto_renew_rule_type__weekly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__termination_notice_rule_type__weekly +msgid "Week(s)" +msgstr "Vecka(n)" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__auto_renew_rule_type__yearly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__auto_renew_rule_type__yearly +msgid "Year(s)" +msgstr "År(en)" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "You can't delay a contract line invoiced at least one time." +msgstr "Du kan inte fördröja ett avtal som fakturerats minst en gång." diff --git a/contract_line_successor/i18n/tr.po b/contract_line_successor/i18n/tr.po new file mode 100644 index 0000000000..009454a6bf --- /dev/null +++ b/contract_line_successor/i18n/tr.po @@ -0,0 +1,555 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * contract_line_successor +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-07-11 21:25+0000\n" +"Last-Translator: Betül Öğmen \n" +"Language-Team: none\n" +"Language: tr\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 5.10.4\n" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "A canceled contract line can't be set to auto-renew" +msgstr "" +"İptal edilen bir sözleşme satırı otomatik olarak yenilenecek şekilde " +"ayarlanamaz" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "A contract line with a successor can't be set to auto-renew" +msgstr "" +"Bir halefi olan bir sözleşme satırı otomatik olarak yenilenecek şekilde " +"ayarlanamaz" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "A contract line with a successor must have a end date" +msgstr "Bir halefi olan bir sözleşme satırının bir bitiş tarihi olmalıdır" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "An auto-renew line must have a end date" +msgstr "Otomatik yenileme satırının bitiş tarihi olmalıdır" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Are you sure you want to cancel this line" +msgstr "Bu satır iptal etmek istediğinizden emin misiniz" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_auto_renew +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template_line__is_auto_renew +msgid "Auto Renew" +msgstr "Otomatik Yenilenme" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Auto renew interval should be different then 0" +msgstr "Otomatik yenileme aralığı 0'dan farklı olmalıdır" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_search_view +msgid "Auto-renew" +msgstr "Otomatik-yineleme" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_plan_successor_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_plan_successor_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_uncancel_form_view +msgid "Cancel" +msgstr "İptal" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_cancel_allowed +msgid "Cancel allowed?" +msgstr "İptal etme izni var mı?" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Cancel not allowed for this line" +msgstr "Bu satır için iptale izin verilmiyor" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__canceled +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_search_view +msgid "Canceled" +msgstr "İptal Edildi" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_contract__line_recurrence +msgid "" +"Check this if you want to control recurrence at the line level instead of " +"for the whole contract." +msgstr "" +"Tekrarlamayı tüm sözleşme için değil, satır düzeyinde kontrol etmek " +"istiyorsanız bunu işaretleyin." + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__closed +msgid "Closed" +msgstr "Kapanmış" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_res_company +msgid "Companies" +msgstr "Şirketler" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_res_config_settings +msgid "Config Settings" +msgstr "Yapılandırma Ayarları" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_contract_contract +msgid "Contract" +msgstr "Abonelik" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_contract_line +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__contract_line_id +msgid "Contract Line" +msgstr "Abonelik Satırı" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_contract_line_wizard +msgid "Contract Line Wizard" +msgstr "Sözleşme Satırı Sihirbazı" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_line__predecessor_contract_line_id +msgid "Contract Line origin of this one." +msgstr "Bunun Abonelik Satırı menşei." + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_contract_template +msgid "Contract Template" +msgstr "Sözleşme Şablonu" + +#. module: contract_line_successor +#: model:ir.model,name:contract_line_successor.model_contract_template_line +msgid "Contract Template Line" +msgstr "Abonelik Şablonu Satırı" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Contract line Un-canceled: %s" +msgstr "Abonelik satırı İptal edilmedi: %s" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Contract line and its predecessor overlapped" +msgstr "Abonelik satırı ve selefi çakıştı" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Contract line and its successor overlapped" +msgstr "Abonelik satırı ve halefi çakıştı" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Contract line canceled: %s" +msgstr "Abonelik satırı iptal edildi: %s" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "" +"Contract line for %(product)s\n" +" stopped:
\n" +" - End: %(old_end)s -- %(new_end)s\n" +" " +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "" +"Contract line for %(product)s\n" +" planned a successor:
\n" +" - Start: %(new_date_start)s\n" +"
\n" +" - End: %(new_date_end)s\n" +" " +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "" +"Contract line for %(product)s\n" +" renewed:
\n" +" - Start: %(new_date_start)s\n" +"
\n" +" - End: %(new_date_end)s\n" +" " +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "" +"Contract line for %(product)s\n" +" suspended:
\n" +" - Suspension Start: %(new_date_start)s\n" +"
\n" +" - Suspension End: %(new_date_end)s\n" +" " +msgstr "" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Contract line must be canceled before delete" +msgstr "Silmeden önce sözleşme satırı iptal edilmelidir" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_res_company__create_new_line_at_contract_line_renew +#: model:ir.model.fields,field_description:contract_line_successor.field_res_config_settings__create_new_line_at_contract_line_renew +msgid "Create New Line At Contract Line Renew" +msgstr "Sözleşme Satırında Yenileme Satırı Oluştur" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__create_uid +msgid "Created by" +msgstr "Oluşturan" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__create_date +msgid "Created on" +msgstr "Oluşturulma" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__date_end +msgid "Date End" +msgstr "Bitiş Tarihi" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__date_start +msgid "Date Start" +msgstr "Başlangıç Tarihi" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__auto_renew_rule_type__daily +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__termination_notice_rule_type__daily +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__auto_renew_rule_type__daily +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__termination_notice_rule_type__daily +msgid "Day(s)" +msgstr "Gün(ler)" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__display_name +msgid "Display Name" +msgstr "Görünüm Adı" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__id +msgid "ID" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_res_company__create_new_line_at_contract_line_renew +#: model:ir.model.fields,help:contract_line_successor.field_res_config_settings__create_new_line_at_contract_line_renew +msgid "" +"If checked, a new line will be generated at contract line renew and linked " +"to the original one as successor. The default behavior is to extend the end " +"date of the contract by a new subscription period" +msgstr "" +"İşaretlenirse, sözleşme satırı yenilemesinde yeni bir satır oluşturulur ve " +"halefi olarak orijinal satıra bağlanır. Varsayılan davranış, sözleşmenin " +"bitiş tarihini yeni bir abonelik dönemi kadar uzatmaktır" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_line__successor_contract_line_id +msgid "" +"In case of restart after suspension, this field contain the new contract " +"line created." +msgstr "" +"Askıya alındıktan sonra yeniden başlatma durumunda, bu alan oluşturulan yeni " +"sözleşme satırını içerir." + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__in-progress +msgid "In-progress" +msgstr "Devam etmekte" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_contract__is_auto_renew +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__is_auto_renew +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template__is_auto_renew +msgid "Is Auto Renew" +msgstr "Otomatik Yenileme" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_form_view +msgid "Is suspension without end date" +msgstr "Bitiş tarihi olmadan askıya alma" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__write_uid +msgid "Last Updated by" +msgstr "Son Güncelleyen" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__write_date +msgid "Last Updated on" +msgstr "Son Güncelleme" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__manual_renew_needed +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__manual_renew_needed +msgid "Manual Renew Needed" +msgstr "Manuel Yenileme Gerekli" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__auto_renew_rule_type__monthly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__termination_notice_rule_type__monthly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__auto_renew_rule_type__monthly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__termination_notice_rule_type__monthly +msgid "Month(s)" +msgstr "Ay(lar)" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line_wizard__recurring_next_date +msgid "Next Invoice Date" +msgstr "Sonraki Fatura Tarihi" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Plan Start" +msgstr "Plan Başlangıcı" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_plan_successor_allowed +msgid "Plan successor allowed?" +msgstr "Plan halefine izin veriliyor mu?" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Plan successor not allowed for this line" +msgstr "Bu satır için plan halefine izin verilmiyor" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_form_view +msgid "Predecessor & Successor Lines" +msgstr "" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__predecessor_contract_line_id +msgid "Predecessor Contract Line" +msgstr "Önceki Sözleşme Satırı" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_contract__line_recurrence +msgid "Recurrence at line level?" +msgstr "Satır düzeyinde yineleme?" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Renew" +msgstr "Yenile" + +#. module: contract_line_successor +#: model:ir.actions.server,name:contract_line_successor.contract_line_cron_for_renew_ir_actions_server +msgid "Renew Contract lines" +msgstr "Sözleşme Satırlarını Yenile" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__auto_renew_interval +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template_line__auto_renew_interval +msgid "Renew Every" +msgstr "Hep Yenile" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_line__auto_renew_interval +#: model:ir.model.fields,help:contract_line_successor.field_contract_template_line__auto_renew_interval +msgid "Renew every (Days/Weeks/Months/Years)" +msgstr "Yenile Her (Gün / Hafta / Ay / Yıl)" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_template_line_form_view +msgid "Renewal" +msgstr "Yenileme" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__auto_renew_rule_type +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template_line__auto_renew_rule_type +msgid "Renewal type" +msgstr "Yenilenme türü" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_line__auto_renew_rule_type +#: model:ir.model.fields,help:contract_line_successor.field_contract_template_line__auto_renew_rule_type +msgid "Specify interval for automatic renewal." +msgstr "Otomatik yenileme için Aralığı Belirtin." + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__state +msgid "State" +msgstr "Durum" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Stop" +msgstr "Durdur" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_form_view +msgid "Stop Date" +msgstr "Bitiş Tarihi" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Stop Plan Successor" +msgstr "Plan Halefini Durdur" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_stop_allowed +msgid "Stop allowed?" +msgstr "Durdurulmasına izin verildi mi?" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Stop not allowed for this line" +msgstr "Bu satırın durdurulmasına izin verilmedi" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_stop_plan_successor_allowed +msgid "Stop/Plan successor allowed?" +msgstr "Durdurma/Plan halefine izin veriliyor mu?" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Stop/Plan successor not allowed for this line" +msgstr "Bu satırda Durdurma/Plan halefine izin verilmiyor" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__successor_contract_line_id +msgid "Successor Contract Line" +msgstr "Halef Abonelik Satırı" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_plan_successor_form_view +msgid "Suspension End Date" +msgstr "Askıya Alma Bitiş Tarihi" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_plan_successor_form_view +msgid "Suspension Start Date" +msgstr "Askıya Alma Başlangıç Tarihi" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__termination_notice_interval +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template_line__termination_notice_interval +msgid "Termination Notice Before" +msgstr "Fesih Bildirimi Öncesi" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__termination_notice_date +msgid "Termination Notice Date" +msgstr "Fesih Bildirimi Tarihi" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__termination_notice_rule_type +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_template_line__termination_notice_rule_type +msgid "Termination Notice type" +msgstr "Fesih Bildirimi Türü" + +#. module: contract_line_successor +#: model:ir.model.fields,help:contract_line_successor.field_contract_line__manual_renew_needed +#: model:ir.model.fields,help:contract_line_successor.field_contract_line_wizard__manual_renew_needed +msgid "" +"This flag is used to make a difference between a definitive stopand " +"temporary one for which a user is not able to plan asuccessor in advance" +msgstr "" +"Bu bayrak, kullanıcının önceden bir halefi planlayamadığı kesin durdurma ile " +"geçici durdurma arasında bir fark yaratmak için kullanılır" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__to-renew +msgid "To renew" +msgstr "Yenilenecek" + +#. module: contract_line_successor +#: model:ir.model.fields,field_description:contract_line_successor.field_contract_line__is_un_cancel_allowed +msgid "Un-Cancel allowed?" +msgstr "İptalin geri alımına izin veriliyor mu?" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_contract_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_tree_view +msgid "Un-cancel" +msgstr "İptal geri al" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "Un-cancel not allowed for this line" +msgstr "İptal geri almaya bu satır için izin verilmiyor" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__upcoming +msgid "Upcoming" +msgstr "Yaklaşan" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__state__upcoming-close +msgid "Upcoming Close" +msgstr "Yaklaşan Kapanış" + +#. module: contract_line_successor +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_plan_successor_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_stop_plan_successor_form_view +#: model_terms:ir.ui.view,arch_db:contract_line_successor.contract_line_wizard_uncancel_form_view +msgid "Validate" +msgstr "Doğrula" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__auto_renew_rule_type__weekly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__termination_notice_rule_type__weekly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__auto_renew_rule_type__weekly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__termination_notice_rule_type__weekly +msgid "Week(s)" +msgstr "Hafta(lar)" + +#. module: contract_line_successor +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_line__auto_renew_rule_type__yearly +#: model:ir.model.fields.selection,name:contract_line_successor.selection__contract_template_line__auto_renew_rule_type__yearly +msgid "Year(s)" +msgstr "Yıl(lar)" + +#. module: contract_line_successor +#. odoo-python +#: code:addons/contract_line_successor/models/contract_line.py:0 +msgid "You can't delay a contract line invoiced at least one time." +msgstr "En az bir kez faturalandırılan bir abonelik satırını geciktiremezsiniz." diff --git a/contract_line_successor/models/__init__.py b/contract_line_successor/models/__init__.py new file mode 100644 index 0000000000..1d56e976d7 --- /dev/null +++ b/contract_line_successor/models/__init__.py @@ -0,0 +1,6 @@ +from . import contract_contract +from . import contract_line +from . import contract_template +from . import contract_template_line +from . import res_company +from . import res_config_settings diff --git a/contract_line_successor/models/contract_contract.py b/contract_line_successor/models/contract_contract.py new file mode 100644 index 0000000000..d14ec8ec25 --- /dev/null +++ b/contract_line_successor/models/contract_contract.py @@ -0,0 +1,25 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class ContractContract(models.Model): + _inherit = "contract.contract" + + line_recurrence = fields.Boolean(default=True) + + @api.depends("contract_line_ids.date_end", "contract_line_ids.is_canceled") + def _compute_date_end(self): + for contract in self: + contract.date_end = False + date_end = contract.contract_line_ids.filtered( + lambda line: not line.is_canceled + ).mapped("date_end") + if date_end and all(date_end): + contract.date_end = max(date_end) + + def _convert_contract_lines(self, contract): + new_lines = super()._convert_contract_lines(contract) + new_lines._onchange_is_auto_renew() + return new_lines diff --git a/contract_line_successor/models/contract_line.py b/contract_line_successor/models/contract_line.py new file mode 100644 index 0000000000..d2cbb8a34d --- /dev/null +++ b/contract_line_successor/models/contract_line.py @@ -0,0 +1,850 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from datetime import timedelta + +from dateutil.relativedelta import relativedelta +from markupsafe import Markup + +from odoo import api, fields, models +from odoo.exceptions import ValidationError + +from .contract_line_constraints import get_allowed + + +class ContractLine(models.Model): + _inherit = "contract.line" + + termination_notice_date = fields.Date( + compute="_compute_termination_notice_date", + store=True, + copy=False, + ) + successor_contract_line_id = fields.Many2one( + comodel_name="contract.line", + string="Successor Contract Line", + required=False, + readonly=True, + index=True, + copy=False, + help="In case of restart after suspension, this field contain the new " + "contract line created.", + ) + predecessor_contract_line_id = fields.Many2one( + comodel_name="contract.line", + string="Predecessor Contract Line", + required=False, + readonly=True, + index=True, + copy=False, + help="Contract Line origin of this one.", + ) + manual_renew_needed = fields.Boolean( + default=False, + help="This flag is used to make a difference between a definitive stop" + "and temporary one for which a user is not able to plan a" + "successor in advance", + ) + is_plan_successor_allowed = fields.Boolean( + string="Plan successor allowed?", compute="_compute_allowed" + ) + is_stop_plan_successor_allowed = fields.Boolean( + string="Stop/Plan successor allowed?", compute="_compute_allowed" + ) + is_stop_allowed = fields.Boolean(string="Stop allowed?", compute="_compute_allowed") + is_cancel_allowed = fields.Boolean( + string="Cancel allowed?", compute="_compute_allowed" + ) + is_un_cancel_allowed = fields.Boolean( + string="Un-Cancel allowed?", compute="_compute_allowed" + ) + state = fields.Selection( + selection=[ + ("upcoming", "Upcoming"), + ("in-progress", "In-progress"), + ("to-renew", "To renew"), + ("upcoming-close", "Upcoming Close"), + ("closed", "Closed"), + ("canceled", "Canceled"), + ], + compute="_compute_state", + search="_search_state", + ) + + @api.depends( + "date_end", + "termination_notice_rule_type", + "termination_notice_interval", + ) + def _compute_termination_notice_date(self): + for rec in self: + if rec.date_end: + rec.termination_notice_date = rec.date_end - self.get_relative_delta( + rec.termination_notice_rule_type, + rec.termination_notice_interval, + ) + else: + rec.termination_notice_date = False + + @api.depends( + "date_start", + "date_end", + "last_date_invoiced", + "is_auto_renew", + "successor_contract_line_id", + "predecessor_contract_line_id", + "is_canceled", + ) + def _compute_allowed(self): + for rec in self: + rec.update( + { + "is_plan_successor_allowed": False, + "is_stop_plan_successor_allowed": False, + "is_stop_allowed": False, + "is_cancel_allowed": False, + "is_un_cancel_allowed": False, + } + ) + if rec.date_start: + allowed = get_allowed( + rec.date_start, + rec.date_end, + rec.last_date_invoiced, + rec.is_auto_renew, + rec.successor_contract_line_id, + rec.predecessor_contract_line_id, + rec.is_canceled, + ) + if allowed: + rec.update( + { + "is_plan_successor_allowed": allowed.plan_successor, + "is_stop_plan_successor_allowed": ( + allowed.stop_plan_successor + ), + "is_stop_allowed": allowed.stop, + "is_cancel_allowed": allowed.cancel, + "is_un_cancel_allowed": allowed.uncancel, + } + ) + + @api.constrains("is_auto_renew", "successor_contract_line_id", "date_end") + def _check_allowed(self): + """ + logical impossible combination: + * a line with is_auto_renew True should have date_end and + couldn't have successor_contract_line_id + * a line without date_end can't have successor_contract_line_id + + """ + for rec in self: + if rec.is_auto_renew: + if rec.successor_contract_line_id: + raise ValidationError( + self.env._( + "A contract line with a successor can't " + "be set to auto-renew" + ) + ) + if not rec.date_end: + raise ValidationError( + self.env._("An auto-renew line must have a end date") + ) + else: + if not rec.date_end and rec.successor_contract_line_id: + raise ValidationError( + self.env._( + "A contract line with a successor must have a end date" + ) + ) + + @api.constrains("successor_contract_line_id", "date_end") + def _check_overlap_successor(self): + for rec in self: + if rec.date_end and rec.successor_contract_line_id: + if rec.date_end >= rec.successor_contract_line_id.date_start: + raise ValidationError( + self.env._("Contract line and its successor overlapped") + ) + + @api.constrains("predecessor_contract_line_id", "date_start") + def _check_overlap_predecessor(self): + for rec in self: + if ( + rec.predecessor_contract_line_id + and rec.predecessor_contract_line_id.date_end + ): + if rec.date_start <= rec.predecessor_contract_line_id.date_end: + raise ValidationError( + self.env._("Contract line and its predecessor overlapped") + ) + + @api.depends( + "is_canceled", + "date_start", + "date_end", + "is_auto_renew", + "manual_renew_needed", + "termination_notice_date", + "successor_contract_line_id", + ) + def _compute_state(self): + today = fields.Date.context_today(self) + for rec in self: + rec.state = False + if rec.display_type: + continue + if rec.is_canceled: + rec.state = "canceled" + continue + + if rec.date_start and rec.date_start > today: + # Before period + rec.state = "upcoming" + continue + if ( + rec.date_start + and rec.date_start <= today + and (not rec.date_end or rec.date_end >= today) + ): + # In period + if ( + rec.termination_notice_date + and rec.termination_notice_date < today + and not rec.is_auto_renew + and not rec.manual_renew_needed + ): + rec.state = "upcoming-close" + else: + rec.state = "in-progress" + continue + if rec.date_end and rec.date_end < today: + # After + if ( + rec.manual_renew_needed + and not rec.successor_contract_line_id + or rec.is_auto_renew + ): + rec.state = "to-renew" + else: + rec.state = "closed" + + @api.model + def _get_state_domain(self, state): + today = fields.Date.context_today(self) + if state == "upcoming": + return [ + "&", + ("date_start", ">", today), + ("is_canceled", "=", False), + ] + if state == "in-progress": + return [ + "&", + "&", + "&", + ("date_start", "<=", today), + ("is_canceled", "=", False), + "|", + ("date_end", ">=", today), + ("date_end", "=", False), + "|", + ("is_auto_renew", "=", True), + "&", + ("is_auto_renew", "=", False), + ("termination_notice_date", ">", today), + ] + if state == "to-renew": + return [ + "&", + "&", + ("is_canceled", "=", False), + ("date_end", "<", today), + "|", + "&", + ("manual_renew_needed", "=", True), + ("successor_contract_line_id", "=", False), + ("is_auto_renew", "=", True), + ] + if state == "upcoming-close": + return [ + "&", + "&", + "&", + "&", + "&", + ("date_start", "<=", today), + ("is_auto_renew", "=", False), + ("manual_renew_needed", "=", False), + ("is_canceled", "=", False), + ("termination_notice_date", "<", today), + ("date_end", ">=", today), + ] + if state == "closed": + return [ + "&", + "&", + "&", + ("is_canceled", "=", False), + ("date_end", "<", today), + ("is_auto_renew", "=", False), + "|", + "&", + ("manual_renew_needed", "=", True), + ("successor_contract_line_id", "!=", False), + ("manual_renew_needed", "=", False), + ] + if state == "canceled": + return [("is_canceled", "=", True)] + if not state: + return [("display_type", "!=", False)] + + @api.model + def _search_state(self, operator, value): + states = [ + "upcoming", + "in-progress", + "to-renew", + "upcoming-close", + "closed", + "canceled", + False, + ] + if operator == "=": + return self._get_state_domain(value) + if operator == "!=": + domain = [] + for state in states: + if state != value: + if domain: + domain.insert(0, "|") + domain.extend(self._get_state_domain(state)) + return domain + if operator == "in": + domain = [] + for state in value: + if domain: + domain.insert(0, "|") + domain.extend(self._get_state_domain(state)) + return domain + + if operator == "not in": + if set(value) == set(states): + return [("id", "=", False)] + return self._search_state( + "in", [state for state in states if state not in value] + ) + + @api.model + def _get_first_date_end( + self, date_start, auto_renew_rule_type, auto_renew_interval + ): + return ( + date_start + + self.get_relative_delta(auto_renew_rule_type, auto_renew_interval) + - relativedelta(days=1) + ) + + @api.onchange( + "date_start", + "is_auto_renew", + "auto_renew_rule_type", + "auto_renew_interval", + ) + def _onchange_is_auto_renew(self): + """Date end should be auto-computed if a contract line is set to + auto_renew""" + for rec in self.filtered("is_auto_renew"): + if rec.date_start: + rec.date_end = self._get_first_date_end( + rec.date_start, + rec.auto_renew_rule_type, + rec.auto_renew_interval, + ) + + @api.constrains("is_canceled", "is_auto_renew") + def _check_auto_renew_canceled_lines(self): + for rec in self: + if rec.is_canceled and rec.is_auto_renew: + raise ValidationError( + self.env._("A canceled contract line can't be set to auto-renew") + ) + + def _delay(self, delay_delta): + """ + Delay a contract line + :param delay_delta: delay relative delta + :return: delayed contract line + """ + for rec in self: + if rec.last_date_invoiced: + raise ValidationError( + self.env._( + "You can't delay a contract line invoiced at least one time." + ) + ) + new_date_start = rec.date_start + delay_delta + if rec.date_end: + new_date_end = rec.date_end + delay_delta + else: + new_date_end = False + new_recurring_next_date = self.get_next_invoice_date( + new_date_start, + rec.recurring_invoicing_type, + rec.recurring_invoicing_offset, + rec.recurring_rule_type, + rec.recurring_interval, + max_date_end=new_date_end, + ) + rec.write( + { + "date_start": new_date_start, + "date_end": new_date_end, + "recurring_next_date": new_recurring_next_date, + } + ) + + def _prepare_value_for_stop(self, date_end, manual_renew_needed): + self.ensure_one() + return { + "date_end": date_end, + "is_auto_renew": False, + "manual_renew_needed": manual_renew_needed, + "recurring_next_date": self.get_next_invoice_date( + self.next_period_date_start, + self.recurring_invoicing_type, + self.recurring_invoicing_offset, + self.recurring_rule_type, + self.recurring_interval, + max_date_end=date_end, + ), + } + + def stop(self, date_end, manual_renew_needed=False, post_message=True): + """ + Put date_end on contract line + We don't consider contract lines that end's before the new end date + :param date_end: new date end for contract line + :return: True + """ + if not all(self.mapped("is_stop_allowed")): + raise ValidationError(self.env._("Stop not allowed for this line")) + for rec in self: + if date_end < rec.date_start: + rec.cancel() + else: + if not rec.date_end or rec.date_end > date_end: + old_date_end = rec.date_end + rec.write( + rec._prepare_value_for_stop(date_end, manual_renew_needed) + ) + if post_message: + msg = Markup( + self.env._( + """Contract line for %(product)s + stopped:
+ - End: %(old_end)s -- %(new_end)s + """ + ) + ) % { + "product": rec.name, + "old_end": old_date_end, + "new_end": rec.date_end, + } + rec.contract_id.message_post(body=msg) + else: + rec.write( + { + "is_auto_renew": False, + "manual_renew_needed": manual_renew_needed, + } + ) + return True + + def _prepare_value_for_plan_successor( + self, date_start, date_end, is_auto_renew, recurring_next_date=False + ): + self.ensure_one() + if not recurring_next_date: + recurring_next_date = self.get_next_invoice_date( + date_start, + self.recurring_invoicing_type, + self.recurring_invoicing_offset, + self.recurring_rule_type, + self.recurring_interval, + max_date_end=date_end, + ) + new_vals = self.read()[0] + new_vals.pop("id", None) + new_vals.pop("last_date_invoiced", None) + values = self._convert_to_write(new_vals) + values["date_start"] = date_start + values["date_end"] = date_end + values["recurring_next_date"] = recurring_next_date + values["is_auto_renew"] = is_auto_renew + values["predecessor_contract_line_id"] = self.id + return values + + def plan_successor( + self, + date_start, + date_end, + is_auto_renew, + recurring_next_date=False, + post_message=True, + ): + """ + Create a copy of a contract line in a new interval + :param date_start: date_start for the successor_contract_line + :param date_end: date_end for the successor_contract_line + :param is_auto_renew: is_auto_renew option for successor_contract_line + :param recurring_next_date: recurring_next_date for the + successor_contract_line + :return: successor_contract_line + """ + contract_line = self.env["contract.line"] + for rec in self: + if not rec.is_plan_successor_allowed: + raise ValidationError( + self.env._("Plan successor not allowed for this line") + ) + rec.is_auto_renew = False + new_line = self.create( + rec._prepare_value_for_plan_successor( + date_start, date_end, is_auto_renew, recurring_next_date + ) + ) + rec.successor_contract_line_id = new_line + contract_line |= new_line + if post_message: + msg = Markup( + self.env._( + """Contract line for %(product)s + planned a successor:
+ - Start: %(new_date_start)s +
+ - End: %(new_date_end)s + """ + ) + ) % { + "product": rec.name, + "new_date_start": new_line.date_start, + "new_date_end": new_line.date_end, + } + rec.contract_id.message_post(body=msg) + return contract_line + + def stop_plan_successor(self, date_start, date_end, is_auto_renew): + """ + Stop a contract line for a defined period and start it later + Cases to consider: + * contract line end's before the suspension period: + -> apply stop + * contract line start before the suspension period and end in it + -> apply stop at suspension start date + -> apply plan successor: + - date_start: suspension.date_end + - date_end: date_end + (contract_line.date_end + - suspension.date_start) + * contract line start before the suspension period and end after it + -> apply stop at suspension start date + -> apply plan successor: + - date_start: suspension.date_end + - date_end: date_end + (suspension.date_end + - suspension.date_start) + * contract line start and end's in the suspension period + -> apply delay + - delay: suspension.date_end - contract_line.date_start + * contract line start in the suspension period and end after it + -> apply delay + - delay: suspension.date_end - contract_line.date_start + * contract line start and end after the suspension period + -> apply delay + - delay: suspension.date_end - suspension.start_date + :param date_start: suspension start date + :param date_end: suspension end date + :param is_auto_renew: is the new line is set to auto_renew + :return: created contract line + """ + if not all(self.mapped("is_stop_plan_successor_allowed")): + raise ValidationError( + self.env._("Stop/Plan successor not allowed for this line") + ) + contract_line = self.env["contract.line"] + for rec in self: + if rec.date_start >= date_start: + if rec.date_start < date_end: + delay = (date_end - rec.date_start) + timedelta(days=1) + else: + delay = (date_end - date_start) + timedelta(days=1) + rec._delay(delay) + contract_line |= rec + else: + if rec.date_end and rec.date_end < date_start: + rec.stop(date_start, post_message=False) + elif ( + rec.date_end + and rec.date_end > date_start + and rec.date_end < date_end + ): + new_date_start = date_end + relativedelta(days=1) + new_date_end = ( + date_end + (rec.date_end - date_start) + relativedelta(days=1) + ) + rec.stop( + date_start - relativedelta(days=1), + manual_renew_needed=True, + post_message=False, + ) + contract_line |= rec.plan_successor( + new_date_start, + new_date_end, + is_auto_renew, + post_message=False, + ) + else: + new_date_start = date_end + relativedelta(days=1) + if rec.date_end: + new_date_end = ( + rec.date_end + + (date_end - date_start) + + relativedelta(days=1) + ) + else: + new_date_end = rec.date_end + + rec.stop( + date_start - relativedelta(days=1), + manual_renew_needed=True, + post_message=False, + ) + contract_line |= rec.plan_successor( + new_date_start, + new_date_end, + is_auto_renew, + post_message=False, + ) + msg = Markup( + self.env._( + """Contract line for %(product)s + suspended:
+ - Suspension Start: %(new_date_start)s +
+ - Suspension End: %(new_date_end)s + """ + ) + ) % { + "product": rec.name, + "new_date_start": date_start, + "new_date_end": date_end, + } + rec.contract_id.message_post(body=msg) + return contract_line + + def cancel(self): + if not all(self.mapped("is_cancel_allowed")): + raise ValidationError(self.env._("Cancel not allowed for this line")) + for contract in self.mapped("contract_id"): + lines = self.filtered(lambda line, c=contract: line.contract_id == c) + msg = Markup( + self.env._( + "Contract line canceled: %s", + "
- ".join( + [f"{name}" for name in lines.mapped("name")] + ), + ) + ) + contract.message_post(body=msg) + self.mapped("predecessor_contract_line_id").write( + {"successor_contract_line_id": False} + ) + return self.write({"is_canceled": True, "is_auto_renew": False}) + + def uncancel(self, recurring_next_date): + if not all(self.mapped("is_un_cancel_allowed")): + raise ValidationError(self.env._("Un-cancel not allowed for this line")) + for contract in self.mapped("contract_id"): + lines = self.filtered(lambda line, c=contract: line.contract_id == c) + msg = Markup( + self.env._( + "Contract line Un-canceled: %s", + "
- ".join( + [f"{name}" for name in lines.mapped("name")] + ), + ) + ) + contract.message_post(body=msg) + for rec in self: + if rec.predecessor_contract_line_id: + predecessor_contract_line = rec.predecessor_contract_line_id + assert not predecessor_contract_line.successor_contract_line_id + predecessor_contract_line.successor_contract_line_id = rec + rec.is_canceled = False + rec.recurring_next_date = recurring_next_date + return True + + def action_uncancel(self): + self.ensure_one() + context = { + "default_contract_line_id": self.id, + "default_recurring_next_date": fields.Date.context_today(self), + } + context.update(self.env.context) + view_id = self.env.ref( + "contract_line_successor.contract_line_wizard_uncancel_form_view" + ).id + return { + "type": "ir.actions.act_window", + "name": "Un-Cancel Contract Line", + "res_model": "contract.line.wizard", + "view_mode": "form", + "views": [(view_id, "form")], + "target": "new", + "context": context, + } + + def action_plan_successor(self): + self.ensure_one() + context = { + "default_contract_line_id": self.id, + "default_is_auto_renew": self.is_auto_renew, + } + context.update(self.env.context) + view_id = self.env.ref( + "contract_line_successor.contract_line_wizard_plan_successor_form_view" + ).id + return { + "type": "ir.actions.act_window", + "name": "Plan contract line successor", + "res_model": "contract.line.wizard", + "view_mode": "form", + "views": [(view_id, "form")], + "target": "new", + "context": context, + } + + def action_stop(self): + self.ensure_one() + context = { + "default_contract_line_id": self.id, + "default_date_end": self.date_end, + } + context.update(self.env.context) + view_id = self.env.ref( + "contract_line_successor.contract_line_wizard_stop_form_view" + ).id + return { + "type": "ir.actions.act_window", + "name": "Terminate contract line", + "res_model": "contract.line.wizard", + "view_mode": "form", + "views": [(view_id, "form")], + "target": "new", + "context": context, + } + + def action_stop_plan_successor(self): + self.ensure_one() + context = { + "default_contract_line_id": self.id, + "default_is_auto_renew": self.is_auto_renew, + } + context.update(self.env.context) + view_id = self.env.ref( + "contract_line_successor.contract_line_wizard_stop_plan_successor_form_view" + ).id + return { + "type": "ir.actions.act_window", + "name": "Suspend contract line", + "res_model": "contract.line.wizard", + "view_mode": "form", + "views": [(view_id, "form")], + "target": "new", + "context": context, + } + + def _get_renewal_new_date_end(self): + self.ensure_one() + date_start = self.date_end + relativedelta(days=1) + date_end = self._get_first_date_end( + date_start, self.auto_renew_rule_type, self.auto_renew_interval + ) + return date_end + + def _renew_create_line(self, date_end): + self.ensure_one() + date_start = self.date_end + relativedelta(days=1) + is_auto_renew = self.is_auto_renew + self.stop(self.date_end, post_message=False) + new_line = self.plan_successor( + date_start, date_end, is_auto_renew, post_message=False + ) + return new_line + + def _renew_extend_line(self, date_end): + self.ensure_one() + self.date_end = date_end + return self + + def renew(self): + res = self.env["contract.line"] + for rec in self: + company = rec.contract_id.company_id + date_end = rec._get_renewal_new_date_end() + date_start = rec.date_end + relativedelta(days=1) + if company.create_new_line_at_contract_line_renew: + new_line = rec._renew_create_line(date_end) + else: + new_line = rec._renew_extend_line(date_end) + res |= new_line + msg = Markup( + self.env._( + """Contract line for %(product)s + renewed:
+ - Start: %(new_date_start)s +
+ - End: %(new_date_end)s + """ + ) + ) % { + "product": rec.name, + "new_date_start": date_start, + "new_date_end": date_end, + } + rec.contract_id.message_post(body=msg) + return res + + @api.model + def _contract_line_to_renew_domain(self): + return [ + ("is_auto_renew", "=", True), + ("is_canceled", "=", False), + ("termination_notice_date", "<=", fields.Date.context_today(self)), + ] + + @api.model + def cron_renew_contract_line(self): + domain = self._contract_line_to_renew_domain() + to_renew = self.search(domain) + to_renew.renew() + + def unlink(self): + """stop unlink uncnacled lines""" + for record in self: + if not (record.is_canceled or record.display_type): + self._trigger_validation_error() + return super().unlink() + + def _trigger_validation_error(self): + raise ValidationError( + self.env._("Contract line must be canceled before delete") + ) + + @api.constrains("is_auto_renew", "auto_renew_interval") + def _check_auto_renew_interval(self): + for rec in self: + if rec.is_auto_renew and not rec.auto_renew_interval: + raise ValidationError( + self.env._("Auto renew interval should be different then 0") + ) diff --git a/contract_line_successor/models/contract_line_constraints.py b/contract_line_successor/models/contract_line_constraints.py new file mode 100644 index 0000000000..eeaba307bb --- /dev/null +++ b/contract_line_successor/models/contract_line_constraints.py @@ -0,0 +1,429 @@ +# Copyright 2018 ACSONE SA/NV. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import itertools +from collections import namedtuple + +from odoo.fields import Date + +Criteria = namedtuple( + "Criteria", + [ + "when", # Contract line relatively to today (BEFORE, IN, AFTER) + "has_date_end", # Is date_end set on contract line (bool) + "has_last_date_invoiced", # Is last_date_invoiced set on contract line + "is_auto_renew", # Is is_auto_renew set on contract line (bool) + "has_successor", # Is contract line has_successor (bool) + "predecessor_has_successor", + # Is contract line predecessor has successor (bool) + # In almost of the cases + # contract_line.predecessor.successor == contract_line + # But at cancel action, + # contract_line.predecessor.successor == False + # This is to permit plan_successor on predecessor + # If contract_line.predecessor.successor != False + # and contract_line is canceled, we don't allow uncancel + # else we re-link contract_line and its predecessor + "canceled", # Is contract line canceled (bool) + ], +) +Allowed = namedtuple( + "Allowed", + ["plan_successor", "stop_plan_successor", "stop", "cancel", "uncancel"], +) + + +def _expand_none(criteria): + variations = [] + for attribute, value in criteria._asdict().items(): + if value is None: + if attribute == "when": + variations.append(["BEFORE", "IN", "AFTER"]) + else: + variations.append([True, False]) + else: + variations.append([value]) + return itertools.product(*variations) + + +def _add(matrix, criteria, allowed): + """Expand None values to True/False combination""" + for c in _expand_none(criteria): + matrix[c] = allowed + + +CRITERIA_ALLOWED_DICT = { + Criteria( + when="BEFORE", + has_date_end=True, + has_last_date_invoiced=False, + is_auto_renew=True, + has_successor=False, + predecessor_has_successor=None, + canceled=False, + ): Allowed( + plan_successor=False, + stop_plan_successor=True, + stop=True, + cancel=True, + uncancel=False, + ), + Criteria( + when="BEFORE", + has_date_end=True, + has_last_date_invoiced=False, + is_auto_renew=False, + has_successor=True, + predecessor_has_successor=None, + canceled=False, + ): Allowed( + plan_successor=False, + stop_plan_successor=False, + stop=True, + cancel=True, + uncancel=False, + ), + Criteria( + when="BEFORE", + has_date_end=True, + has_last_date_invoiced=False, + is_auto_renew=False, + has_successor=False, + predecessor_has_successor=None, + canceled=False, + ): Allowed( + plan_successor=True, + stop_plan_successor=True, + stop=True, + cancel=True, + uncancel=False, + ), + Criteria( + when="BEFORE", + has_date_end=False, + has_last_date_invoiced=False, + is_auto_renew=False, + has_successor=False, + predecessor_has_successor=None, + canceled=False, + ): Allowed( + plan_successor=False, + stop_plan_successor=True, + stop=True, + cancel=True, + uncancel=False, + ), + Criteria( + when="IN", + has_date_end=True, + has_last_date_invoiced=False, + is_auto_renew=True, + has_successor=False, + predecessor_has_successor=None, + canceled=False, + ): Allowed( + plan_successor=False, + stop_plan_successor=True, + stop=True, + cancel=True, + uncancel=False, + ), + Criteria( + when="IN", + has_date_end=True, + has_last_date_invoiced=False, + is_auto_renew=False, + has_successor=True, + predecessor_has_successor=None, + canceled=False, + ): Allowed( + plan_successor=False, + stop_plan_successor=False, + stop=True, + cancel=True, + uncancel=False, + ), + Criteria( + when="IN", + has_date_end=True, + has_last_date_invoiced=False, + is_auto_renew=False, + has_successor=False, + predecessor_has_successor=None, + canceled=False, + ): Allowed( + plan_successor=True, + stop_plan_successor=True, + stop=True, + cancel=True, + uncancel=False, + ), + Criteria( + when="IN", + has_date_end=False, + has_last_date_invoiced=False, + is_auto_renew=False, + has_successor=False, + predecessor_has_successor=None, + canceled=False, + ): Allowed( + plan_successor=False, + stop_plan_successor=True, + stop=True, + cancel=True, + uncancel=False, + ), + Criteria( + when="BEFORE", + has_date_end=True, + has_last_date_invoiced=True, + is_auto_renew=True, + has_successor=False, + predecessor_has_successor=None, + canceled=False, + ): Allowed( + plan_successor=False, + stop_plan_successor=True, + stop=True, + cancel=False, + uncancel=False, + ), + Criteria( + when="BEFORE", + has_date_end=True, + has_last_date_invoiced=True, + is_auto_renew=False, + has_successor=True, + predecessor_has_successor=None, + canceled=False, + ): Allowed( + plan_successor=False, + stop_plan_successor=False, + stop=True, + cancel=False, + uncancel=False, + ), + Criteria( + when="BEFORE", + has_date_end=True, + has_last_date_invoiced=True, + is_auto_renew=False, + has_successor=False, + predecessor_has_successor=None, + canceled=False, + ): Allowed( + plan_successor=True, + stop_plan_successor=True, + stop=True, + cancel=False, + uncancel=False, + ), + Criteria( + when="BEFORE", + has_date_end=False, + has_last_date_invoiced=True, + is_auto_renew=False, + has_successor=False, + predecessor_has_successor=None, + canceled=False, + ): Allowed( + plan_successor=False, + stop_plan_successor=True, + stop=True, + cancel=False, + uncancel=False, + ), + Criteria( + when="IN", + has_date_end=True, + has_last_date_invoiced=True, + is_auto_renew=True, + has_successor=False, + predecessor_has_successor=None, + canceled=False, + ): Allowed( + plan_successor=False, + stop_plan_successor=True, + stop=True, + cancel=False, + uncancel=False, + ), + Criteria( + when="IN", + has_date_end=True, + has_last_date_invoiced=True, + is_auto_renew=False, + has_successor=True, + predecessor_has_successor=None, + canceled=False, + ): Allowed( + plan_successor=False, + stop_plan_successor=False, + stop=True, + cancel=False, + uncancel=False, + ), + Criteria( + when="IN", + has_date_end=True, + has_last_date_invoiced=True, + is_auto_renew=False, + has_successor=False, + predecessor_has_successor=None, + canceled=False, + ): Allowed( + plan_successor=True, + stop_plan_successor=True, + stop=True, + cancel=False, + uncancel=False, + ), + Criteria( + when="IN", + has_date_end=False, + has_last_date_invoiced=True, + is_auto_renew=False, + has_successor=False, + predecessor_has_successor=None, + canceled=False, + ): Allowed( + plan_successor=False, + stop_plan_successor=True, + stop=True, + cancel=False, + uncancel=False, + ), + Criteria( + when="AFTER", + has_date_end=True, + has_last_date_invoiced=None, + is_auto_renew=True, + has_successor=False, + predecessor_has_successor=None, + canceled=False, + ): Allowed( + plan_successor=False, + stop_plan_successor=False, + stop=True, + cancel=False, + uncancel=False, + ), + Criteria( + when="AFTER", + has_date_end=True, + has_last_date_invoiced=None, + is_auto_renew=False, + has_successor=True, + predecessor_has_successor=None, + canceled=False, + ): Allowed( + plan_successor=False, + stop_plan_successor=False, + stop=False, + cancel=False, + uncancel=False, + ), + Criteria( + when="AFTER", + has_date_end=True, + has_last_date_invoiced=None, + is_auto_renew=False, + has_successor=False, + predecessor_has_successor=None, + canceled=False, + ): Allowed( + plan_successor=True, + stop_plan_successor=False, + stop=True, + cancel=False, + uncancel=False, + ), + Criteria( + when=None, + has_date_end=None, + has_last_date_invoiced=None, + is_auto_renew=None, + has_successor=None, + predecessor_has_successor=False, + canceled=True, + ): Allowed( + plan_successor=False, + stop_plan_successor=False, + stop=False, + cancel=False, + uncancel=True, + ), + Criteria( + when=None, + has_date_end=None, + has_last_date_invoiced=None, + is_auto_renew=None, + has_successor=None, + predecessor_has_successor=True, + canceled=True, + ): Allowed( + plan_successor=False, + stop_plan_successor=False, + stop=False, + cancel=False, + uncancel=False, + ), +} +criteria_allowed_dict = {} + +for c in CRITERIA_ALLOWED_DICT: + _add(criteria_allowed_dict, c, CRITERIA_ALLOWED_DICT[c]) + + +def compute_when(date_start, date_end): + today = Date.today() + if today < date_start: + return "BEFORE" + if date_end and today > date_end: + return "AFTER" + return "IN" + + +def compute_criteria( + date_start, + date_end, + has_last_date_invoiced, + is_auto_renew, + successor_contract_line_id, + predecessor_contract_line_id, + is_canceled, +): + return Criteria( + when=compute_when(date_start, date_end), + has_date_end=bool(date_end), + has_last_date_invoiced=bool(has_last_date_invoiced), + is_auto_renew=is_auto_renew, + has_successor=bool(successor_contract_line_id), + predecessor_has_successor=bool( + predecessor_contract_line_id.successor_contract_line_id + ), + canceled=is_canceled, + ) + + +def get_allowed( + date_start, + date_end, + has_last_date_invoiced, + is_auto_renew, + successor_contract_line_id, + predecessor_contract_line_id, + is_canceled, +): + criteria = compute_criteria( + date_start, + date_end, + has_last_date_invoiced, + is_auto_renew, + successor_contract_line_id, + predecessor_contract_line_id, + is_canceled, + ) + if criteria in criteria_allowed_dict: + return criteria_allowed_dict[criteria] + return False diff --git a/contract_line_successor/models/contract_template.py b/contract_line_successor/models/contract_template.py new file mode 100644 index 0000000000..f1bf079d1b --- /dev/null +++ b/contract_line_successor/models/contract_template.py @@ -0,0 +1,17 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class ContractTemplate(models.Model): + _inherit = "contract.template" + + is_auto_renew = fields.Boolean(compute="_compute_is_auto_renew") + + @api.depends("contract_line_ids.is_auto_renew") + def _compute_is_auto_renew(self): + for record in self: + record.is_auto_renew = all( + line.is_auto_renew for line in record.contract_line_ids + ) diff --git a/contract_line_successor/models/contract_template_line.py b/contract_line_successor/models/contract_template_line.py new file mode 100644 index 0000000000..3d15f06023 --- /dev/null +++ b/contract_line_successor/models/contract_template_line.py @@ -0,0 +1,35 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ContractTemplateLine(models.Model): + _inherit = "contract.template.line" + + is_auto_renew = fields.Boolean(string="Auto Renew", default=False) + auto_renew_interval = fields.Integer( + string="Renew Every", + default=1, + help="Renew every (Days/Weeks/Months/Years)", + ) + auto_renew_rule_type = fields.Selection( + [ + ("daily", "Day(s)"), + ("weekly", "Week(s)"), + ("monthly", "Month(s)"), + ("yearly", "Year(s)"), + ], + default="yearly", + string="Renewal type", + help="Specify interval for automatic renewal.", + ) + termination_notice_interval = fields.Integer( + default=1, + string="Termination Notice Before", + ) + termination_notice_rule_type = fields.Selection( + [("daily", "Day(s)"), ("weekly", "Week(s)"), ("monthly", "Month(s)")], + default="monthly", + string="Termination Notice type", + ) diff --git a/contract_line_successor/models/res_company.py b/contract_line_successor/models/res_company.py new file mode 100644 index 0000000000..36079fc811 --- /dev/null +++ b/contract_line_successor/models/res_company.py @@ -0,0 +1,15 @@ +# Copyright 2019 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = "res.company" + + create_new_line_at_contract_line_renew = fields.Boolean( + help="If checked, a new line will be generated at contract line renew " + "and linked to the original one as successor. The default " + "behavior is to extend the end date of the contract by a new " + "subscription period", + ) diff --git a/contract_line_successor/models/res_config_settings.py b/contract_line_successor/models/res_config_settings.py new file mode 100644 index 0000000000..e5f628a2a6 --- /dev/null +++ b/contract_line_successor/models/res_config_settings.py @@ -0,0 +1,18 @@ +# Copyright 2019 ACSONE SA/NV +# 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" + + create_new_line_at_contract_line_renew = fields.Boolean( + related="company_id.create_new_line_at_contract_line_renew", + readonly=False, + string="Create New Line At Contract Line Renew", + help="If checked, a new line will be generated at contract line renew " + "and linked to the original one as successor. The default " + "behavior is to extend the end date of the contract by a new " + "subscription period", + ) diff --git a/contract_line_successor/pyproject.toml b/contract_line_successor/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/contract_line_successor/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/contract_line_successor/readme/CONFIGURE.md b/contract_line_successor/readme/CONFIGURE.md new file mode 100644 index 0000000000..a1a9bab2ff --- /dev/null +++ b/contract_line_successor/readme/CONFIGURE.md @@ -0,0 +1,10 @@ +- **Auto-Renewal Strategy** + In the company settings, define whether renewing a contract line: + - Extends the current line (updates `date_end`), + - or creates a new successor contract line. + + Field: + `Company > Configuration > Contracts > Create new contract line at renewal` +- **Scheduled Actions** + Ensure the scheduled action `Contract Line: Auto Renew` is activated if you + want automatic renewal without manual intervention. diff --git a/contract_line_successor/readme/CONTRIBUTORS.md b/contract_line_successor/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..ac775f4d05 --- /dev/null +++ b/contract_line_successor/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Souheil Bejaoui (ACSONE SA/NV) diff --git a/contract_line_successor/readme/DESCRIPTION.md b/contract_line_successor/readme/DESCRIPTION.md new file mode 100644 index 0000000000..f6a68a92b9 --- /dev/null +++ b/contract_line_successor/readme/DESCRIPTION.md @@ -0,0 +1,40 @@ +**Contract Line Successor** extends `contract.line` model to support advanced +contract lifecycle management, including suspension, successor planning, +cancellation, and renewal. +It provides a flexible and robust framework for managing complex contract +line scenarios in a clean and structured way. + +## Features + +- **Successor and Predecessor Management** + - Link contract lines with successor and predecessor lines. + - Plan successors automatically or manually after a stop or suspension. + +- **Contract Line Lifecycle States** + - Manage contract lines with the following computed states: + - `Upcoming` + - `In-Progress` + - `To Renew` + - `Upcoming Close` + - `Closed` + - `Canceled` + +- **Lifecycle Operations** + - Stop a contract line. + - Plan a successor for a contract line. + - Stop and plan a successor in one operation (useful for suspensions). + - Cancel and un-cancel contract lines. + - Renew contract lines automatically (new line or extension). + +- **Auto-Renewal Handling** + - Auto-renewal based on company settings (extend existing line or create a new one). + - Cron job to automate renewal of eligible contract lines. + +- **Data Integrity and Validation** + - Prevent invalid successor or predecessor configurations. + - Validate state transitions and date overlaps. + - Ensure clean renewal and cancellation workflows. + +- **Audit Trail** + - Automatic posting of chatter messages for lifecycle events like stops, + renewals, suspensions, cancellations, etc. diff --git a/contract_line_successor/readme/USAGE.md b/contract_line_successor/readme/USAGE.md new file mode 100644 index 0000000000..be99092bd6 --- /dev/null +++ b/contract_line_successor/readme/USAGE.md @@ -0,0 +1,6 @@ +1. **Select a contract** and enable the option **"Recurrence at line level?"**. +2. Once enabled, you will have access to several actions at the contract line level: + - **Stop** a contract line and optionally **plan a successor**. + - **Handle temporary suspensions** and **resume** the contract line after the suspension period. + - **Cancel** and **un-cancel** contract lines if necessary. + - **Renew** contract lines either by **extending** the current line or by **creating a new successor line** automatically. diff --git a/contract_line_successor/security/ir.model.access.csv b/contract_line_successor/security/ir.model.access.csv new file mode 100644 index 0000000000..aae8a4dc7c --- /dev/null +++ b/contract_line_successor/security/ir.model.access.csv @@ -0,0 +1,2 @@ +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" +"contract_line_wizard","contract_line_wizard","model_contract_line_wizard","account.group_account_manager",1,1,1,1 diff --git a/contract_line_successor/static/description/icon.png b/contract_line_successor/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 zcmW++2RxMjAAjx~&dlBk9S+%}OXg)AGE&Cb*&}d0jUxM@u(PQx^-s)697TX`ehR4?GS^qbkof1cslKgkU)h65qZ9Oc=ml_0temigYLJfnz{IDzUf>bGs4N!v3=Z3jMq&A#7%rM5eQ#dc?k~! zVpnB`o+K7|Al`Q_U;eD$B zfJtP*jH`siUq~{KE)`jP2|#TUEFGRryE2`i0**z#*^6~AI|YzIWy$Cu#CSLW3q=GA z6`?GZymC;dCPk~rBS%eCb`5OLr;RUZ;D`}um=H)BfVIq%7VhiMr)_#G0N#zrNH|__ zc+blN2UAB0=617@>_u;MPHN;P;N#YoE=)R#i$k_`UAA>WWCcEVMh~L_ zj--gtp&|K1#58Yz*AHCTMziU1Jzt_jG0I@qAOHsk$2}yTmVkBp_eHuY$A9)>P6o~I z%aQ?!(GqeQ-Y+b0I(m9pwgi(IIZZzsbMv+9w{PFtd_<_(LA~0H(xz{=FhLB@(1&qHA5EJw1>>=%q2f&^X>IQ{!GJ4e9U z&KlB)z(84HmNgm2hg2C0>WM{E(DdPr+EeU_N@57;PC2&DmGFW_9kP&%?X4}+xWi)( z;)z%wI5>D4a*5XwD)P--sPkoY(a~WBw;E~AW`Yue4kFa^LM3X`8x|}ZUeMnqr}>kH zG%WWW>3ml$Yez?i%)2pbKPI7?5o?hydokgQyZsNEr{a|mLdt;X2TX(#B1j35xPnPW z*bMSSOauW>o;*=kO8ojw91VX!qoOQb)zHJ!odWB}d+*K?#sY_jqPdg{Sm2HdYzdEx zOGVPhVRTGPtv0o}RfVP;Nd(|CB)I;*t&QO8h zFfekr30S!-LHmV_Su-W+rEwYXJ^;6&3|L$mMC8*bQptyOo9;>Qb9Q9`ySe3%V$A*9 zeKEe+b0{#KWGp$F+tga)0RtI)nhMa-K@JS}2krK~n8vJ=Ngm?R!9G<~RyuU0d?nz# z-5EK$o(!F?hmX*2Yt6+coY`6jGbb7tF#6nHA zuKk=GGJ;ZwON1iAfG$E#Y7MnZVmrY|j0eVI(DN_MNFJmyZ|;w4tf@=CCDZ#5N_0K= z$;R~bbk?}TpfDjfB&aiQ$VA}s?P}xPERJG{kxk5~R`iRS(SK5d+Xs9swCozZISbnS zk!)I0>t=A<-^z(cmSFz3=jZ23u13X><0b)P)^1T_))Kr`e!-pb#q&J*Q`p+B6la%C zuVl&0duN<;uOsB3%T9Fp8t{ED108<+W(nOZd?gDnfNBC3>M8WE61$So|P zVvqH0SNtDTcsUdzaMDpT=Ty0pDHHNL@Z0w$Y`XO z2M-_r1S+GaH%pz#Uy0*w$Vdl=X=rQXEzO}d6J^R6zjM1u&c9vYLvLp?W7w(?np9x1 zE_0JSAJCPB%i7p*Wvg)pn5T`8k3-uR?*NT|J`eS#_#54p>!p(mLDvmc-3o0mX*mp_ zN*AeS<>#^-{S%W<*mz^!X$w_2dHWpcJ6^j64qFBft-o}o_Vx80o0>}Du;>kLts;$8 zC`7q$QI(dKYG`Wa8#wl@V4jVWBRGQ@1dr-hstpQL)Tl+aqVpGpbSfN>5i&QMXfiZ> zaA?T1VGe?rpQ@;+pkrVdd{klI&jVS@I5_iz!=UMpTsa~mBga?1r}aRBm1WS;TT*s0f0lY=JBl66Upy)-k4J}lh=P^8(SXk~0xW=T9v*B|gzIhN z>qsO7dFd~mgxAy4V?&)=5ieYq?zi?ZEoj)&2o)RLy=@hbCRcfT5jigwtQGE{L*8<@Yd{zg;CsL5mvzfDY}P-wos_6PfprFVaeqNE%h zKZhLtcQld;ZD+>=nqN~>GvROfueSzJD&BE*}XfU|H&(FssBqY=hPCt`d zH?@s2>I(|;fcW&YM6#V#!kUIP8$Nkdh0A(bEVj``-AAyYgwY~jB zT|I7Bf@%;7aL7Wf4dZ%VqF$eiaC38OV6oy3Z#TER2G+fOCd9Iaoy6aLYbPTN{XRPz z;U!V|vBf%H!}52L2gH_+j;`bTcQRXB+y9onc^wLm5wi3-Be}U>k_u>2Eg$=k!(l@I zcCg+flakT2Nej3i0yn+g+}%NYb?ta;R?(g5SnwsQ49U8Wng8d|{B+lyRcEDvR3+`O{zfmrmvFrL6acVP%yG98X zo&+VBg@px@i)%o?dG(`T;n*$S5*rnyiR#=wW}}GsAcfyQpE|>a{=$Hjg=-*_K;UtD z#z-)AXwSRY?OPefw^iI+ z)AXz#PfEjlwTes|_{sB?4(O@fg0AJ^g8gP}ex9Ucf*@_^J(s_5jJV}c)s$`Myn|Kd z$6>}#q^n{4vN@+Os$m7KV+`}c%4)4pv@06af4-x5#wj!KKb%caK{A&Y#Rfs z-po?Dcb1({W=6FKIUirH&(yg=*6aLCekcKwyfK^JN5{wcA3nhO(o}SK#!CINhI`-I z1)6&n7O&ZmyFMuNwvEic#IiOAwNkR=u5it{B9n2sAJV5pNhar=j5`*N!Na;c7g!l$ z3aYBqUkqqTJ=Re-;)s!EOeij=7SQZ3Hq}ZRds%IM*PtM$wV z@;rlc*NRK7i3y5BETSKuumEN`Xu_8GP1Ri=OKQ$@I^ko8>H6)4rjiG5{VBM>B|%`&&s^)jS|-_95&yc=GqjNo{zFkw%%HHhS~e=s zD#sfS+-?*t|J!+ozP6KvtOl!R)@@-z24}`9{QaVLD^9VCSR2b`b!KC#o;Ki<+wXB6 zx3&O0LOWcg4&rv4QG0)4yb}7BFSEg~=IR5#ZRj8kg}dS7_V&^%#Do==#`u zpy6{ox?jWuR(;pg+f@mT>#HGWHAJRRDDDv~@(IDw&R>9643kK#HN`!1vBJHnC+RM&yIh8{gG2q zA%e*U3|N0XSRa~oX-3EAneep)@{h2vvd3Xvy$7og(sayr@95+e6~Xvi1tUqnIxoIH zVWo*OwYElb#uyW{Imam6f2rGbjR!Y3`#gPqkv57dB6K^wRGxc9B(t|aYDGS=m$&S!NmCtrMMaUg(c zc2qC=2Z`EEFMW-me5B)24AqF*bV5Dr-M5ig(l-WPS%CgaPzs6p_gnCIvTJ=Y<6!gT zVt@AfYCzjjsMEGi=rDQHo0yc;HqoRNnNFeWZgcm?f;cp(6CNylj36DoL(?TS7eU#+ z7&mfr#y))+CJOXQKUMZ7QIdS9@#-}7y2K1{8)cCt0~-X0O!O?Qx#E4Og+;A2SjalQ zs7r?qn0H044=sDN$SRG$arw~n=+T_DNdSrarmu)V6@|?1-ZB#hRn`uilTGPJ@fqEy zGt(f0B+^JDP&f=r{#Y_wi#AVDf-y!RIXU^0jXsFpf>=Ji*TeqSY!H~AMbJdCGLhC) zn7Rx+sXw6uYj;WRYrLd^5IZq@6JI1C^YkgnedZEYy<&4(z%Q$5yv#Boo{AH8n$a zhb4Y3PWdr269&?V%uI$xMcUrMzl=;w<_nm*qr=c3Rl@i5wWB;e-`t7D&c-mcQl7x! zZWB`UGcw=Y2=}~wzrfLx=uet<;m3~=8I~ZRuzvMQUQdr+yTV|ATf1Uuomr__nDf=X zZ3WYJtHp_ri(}SQAPjv+Y+0=fH4krOP@S&=zZ-t1jW1o@}z;xk8 z(Nz1co&El^HK^NrhVHa-_;&88vTU>_J33=%{if;BEY*J#1n59=07jrGQ#IP>@u#3A z;!q+E1Rj3ZJ+!4bq9F8PXJ@yMgZL;>&gYA0%_Kbi8?S=XGM~dnQZQ!yBSgcZhY96H zrWnU;k)qy`rX&&xlDyA%(a1Hhi5CWkmg(`Gb%m(HKi-7Z!LKGRP_B8@`7&hdDy5n= z`OIxqxiVfX@OX1p(mQu>0Ai*v_cTMiw4qRt3~NBvr9oBy0)r>w3p~V0SCm=An6@3n)>@z!|o-$HvDK z|3D2ZMJkLE5loMKl6R^ez@Zz%S$&mbeoqH5`Bb){Ei21q&VP)hWS2tjShfFtGE+$z zzCR$P#uktu+#!w)cX!lWN1XU%K-r=s{|j?)Akf@q#3b#{6cZCuJ~gCxuMXRmI$nGtnH+-h z+GEi!*X=AP<|fG`1>MBdTb?28JYc=fGvAi2I<$B(rs$;eoJCyR6_bc~p!XR@O-+sD z=eH`-ye})I5ic1eL~TDmtfJ|8`0VJ*Yr=hNCd)G1p2MMz4C3^Mj?7;!w|Ly%JqmuW zlIEW^Ft%z?*|fpXda>Jr^1noFZEwFgVV%|*XhH@acv8rdGxeEX{M$(vG{Zw+x(ei@ zmfXb22}8-?Fi`vo-YVrTH*C?a8%M=Hv9MqVH7H^J$KsD?>!SFZ;ZsvnHr_gn=7acz z#W?0eCdVhVMWN12VV^$>WlQ?f;P^{(&pYTops|btm6aj>_Uz+hqpGwB)vWp0Cf5y< zft8-je~nn?W11plq}N)4A{l8I7$!ks_x$PXW-2XaRFswX_BnF{R#6YIwMhAgd5F9X zGmwdadS6(a^fjHtXg8=l?Rc0Sm%hk6E9!5cLVloEy4eh(=FwgP`)~I^5~pBEWo+F6 zSf2ncyMurJN91#cJTy_u8Y}@%!bq1RkGC~-bV@SXRd4F{R-*V`bS+6;W5vZ(&+I<9$;-V|eNfLa5n-6% z2(}&uGRF;p92eS*sE*oR$@pexaqr*meB)VhmIg@h{uzkk$9~qh#cHhw#>O%)b@+(| z^IQgqzuj~Sk(J;swEM-3TrJAPCq9k^^^`q{IItKBRXYe}e0Tdr=Huf7da3$l4PdpwWDop%^}n;dD#K4s#DYA8SHZ z&1!riV4W4R7R#C))JH1~axJ)RYnM$$lIR%6fIVA@zV{XVyx}C+a-Dt8Y9M)^KU0+H zR4IUb2CJ{Hg>CuaXtD50jB(_Tcx=Z$^WYu2u5kubqmwp%drJ6 z?Fo40g!Qd<-l=TQxqHEOuPX0;^z7iX?Ke^a%XT<13TA^5`4Xcw6D@Ur&VT&CUe0d} z1GjOVF1^L@>O)l@?bD~$wzgf(nxX1OGD8fEV?TdJcZc2KoUe|oP1#=$$7ee|xbY)A zDZq+cuTpc(fFdj^=!;{k03C69lMQ(|>uhRfRu%+!k&YOi-3|1QKB z z?n?eq1XP>p-IM$Z^C;2L3itnbJZAip*Zo0aw2bs8@(s^~*8T9go!%dHcAz2lM;`yp zD=7&xjFV$S&5uDaiScyD?B-i1ze`+CoRtz`Wn+Zl&#s4&}MO{@N!ufrzjG$B79)Y2d3tBk&)TxUTw@QS0TEL_?njX|@vq?Uz(nBFK5Pq7*xj#u*R&i|?7+6# z+|r_n#SW&LXhtheZdah{ZVoqwyT{D>MC3nkFF#N)xLi{p7J1jXlmVeb;cP5?e(=f# zuT7fvjSbjS781v?7{)-X3*?>tq?)Yd)~|1{BDS(pqC zC}~H#WXlkUW*H5CDOo<)#x7%RY)A;ShGhI5s*#cRDA8YgqG(HeKDx+#(ZQ?386dv! zlXCO)w91~Vw4AmOcATuV653fa9R$fyK8ul%rG z-wfS zihugoZyr38Im?Zuh6@RcF~t1anQu7>#lPpb#}4cOA!EM11`%f*07RqOVkmX{p~KJ9 z^zP;K#|)$`^Rb{rnHGH{~>1(fawV0*Z#)}M`m8-?ZJV<+e}s9wE# z)l&az?w^5{)`S(%MRzxdNqrs1n*-=jS^_jqE*5XDrA0+VE`5^*p3CuM<&dZEeCjoz zR;uu_H9ZPZV|fQq`Cyw4nscrVwi!fE6ciMmX$!_hN7uF;jjKG)d2@aC4ropY)8etW=xJvni)8eHi`H$%#zn^WJ5NLc-rqk|u&&4Z6fD_m&JfSI1Bvb?b<*n&sfl0^t z=HnmRl`XrFvMKB%9}>PaA`m-fK6a0(8=qPkWS5bb4=v?XcWi&hRY?O5HdulRi4?fN zlsJ*N-0Qw+Yic@s0(2uy%F@ib;GjXt01Fmx5XbRo6+n|pP(&nodMoap^z{~q ziEeaUT@Mxe3vJSfI6?uLND(CNr=#^W<1b}jzW58bIfyWTDle$mmS(|x-0|2UlX+9k zQ^EX7Nw}?EzVoBfT(-LT|=9N@^hcn-_p&sqG z&*oVs2JSU+N4ZD`FhCAWaS;>|wH2G*Id|?pa#@>tyxX`+4HyIArWDvVrX)2WAOQff z0qyHu&-S@i^MS-+j--!pr4fPBj~_8({~e1bfcl0wI1kaoN>mJL6KUPQm5N7lB(ui1 zE-o%kq)&djzWJ}ob<-GfDlkB;F31j-VHKvQUGQ3sp`CwyGJk_i!y^sD0fqC@$9|jO zOqN!r!8-p==F@ZVP=U$qSpY(gQ0)59P1&t@y?5rvg<}E+GB}26NYPp4f2YFQrQtot5mn3wu_qprZ=>Ig-$ zbW26Ws~IgY>}^5w`vTB(G`PTZaDiGBo5o(tp)qli|NeV( z@H_=R8V39rt5J5YB2Ky?4eJJ#b`_iBe2ot~6%7mLt5t8Vwi^Jy7|jWXqa3amOIoRb zOr}WVFP--DsS`1WpN%~)t3R!arKF^Q$e12KEqU36AWwnCBICpH4XCsfnyrHr>$I$4 z!DpKX$OKLWarN7nv@!uIA+~RNO)l$$w}p(;b>mx8pwYvu;dD_unryX_NhT8*Tj>BTrTTL&!?O+%Rv;b?B??gSzdp?6Uug9{ zd@V08Z$BdI?fpoCS$)t4mg4rT8Q_I}h`0d-vYZ^|dOB*Q^S|xqTV*vIg?@fVFSmMpaw0qtTRbx} z({Pg?#{2`sc9)M5N$*N|4;^t$+QP?#mov zGVC@I*lBVrOU-%2y!7%)fAKjpEFsgQc4{amtiHb95KQEwvf<(3T<9-Zm$xIew#P22 zc2Ix|App^>v6(3L_MCU0d3W##AB0M~3D00EWoKZqsJYT(#@w$Y_H7G22M~ApVFTRHMI_3be)Lkn#0F*V8Pq zc}`Cjy$bE;FJ6H7p=0y#R>`}-m4(0F>%@P|?7fx{=R^uFdISRnZ2W_xQhD{YuR3t< z{6yxu=4~JkeA;|(J6_nv#>Nvs&FuLA&PW^he@t(UwFFE8)|a!R{`E`K`i^ZnyE4$k z;(749Ix|oi$c3QbEJ3b~D_kQsPz~fIUKym($a_7dJ?o+40*OLl^{=&oq$<#Q(yyrp z{J-FAniyAw9tPbe&IhQ|a`DqFTVQGQ&Gq3!C2==4x{6EJwiPZ8zub-iXoUtkJiG{} zPaR&}_fn8_z~(=;5lD-aPWD3z8PZS@AaUiomF!G8I}Mf>e~0g#BelA-5#`cj;O5>N Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/contract_line_successor/static/description/index.html b/contract_line_successor/static/description/index.html new file mode 100644 index 0000000000..a695dde8fb --- /dev/null +++ b/contract_line_successor/static/description/index.html @@ -0,0 +1,525 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Contract Line Successor

+ +

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

+
+
Contract Line Successor extends contract.line model to support +advanced contract lifecycle management, including suspension, +successor planning, cancellation, and renewal.
+
It provides a flexible and robust framework for managing complex +contract line scenarios in a clean and structured way.
+
+
+

Features

+
    +
  • Successor and Predecessor Management
      +
    • Link contract lines with successor and predecessor lines.
    • +
    • Plan successors automatically or manually after a stop or +suspension.
    • +
    +
  • +
  • Contract Line Lifecycle States
      +
    • Manage contract lines with the following computed states:
        +
      • Upcoming
      • +
      • In-Progress
      • +
      • To Renew
      • +
      • Upcoming Close
      • +
      • Closed
      • +
      • Canceled
      • +
      +
    • +
    +
  • +
  • Lifecycle Operations
      +
    • Stop a contract line.
    • +
    • Plan a successor for a contract line.
    • +
    • Stop and plan a successor in one operation (useful for +suspensions).
    • +
    • Cancel and un-cancel contract lines.
    • +
    • Renew contract lines automatically (new line or extension).
    • +
    +
  • +
  • Auto-Renewal Handling
      +
    • Auto-renewal based on company settings (extend existing line or +create a new one).
    • +
    • Cron job to automate renewal of eligible contract lines.
    • +
    +
  • +
  • Data Integrity and Validation
      +
    • Prevent invalid successor or predecessor configurations.
    • +
    • Validate state transitions and date overlaps.
    • +
    • Ensure clean renewal and cancellation workflows.
    • +
    +
  • +
  • Audit Trail
      +
    • Automatic posting of chatter messages for lifecycle events like +stops, renewals, suspensions, cancellations, etc.
    • +
    +
  • +
+

Table of contents

+ +
+

Configuration

+
    +
  • +
    Auto-Renewal Strategy
    +
    In the company settings, define whether renewing a contract line:
    +
    +
      +
    • Extends the current line (updates date_end),
    • +
    • or creates a new successor contract line.
    • +
    +
    +
    Field:
    +
    Company > Configuration > Contracts > Create new contract line at renewal
    +
    +
  • +
  • +
    Scheduled Actions
    +
    Ensure the scheduled action Contract Line: Auto Renew is +activated if you want automatic renewal without manual +intervention.
    +
    +
  • +
+
+
+

Usage

+
    +
  1. Select a contract and enable the option “Recurrence at line +level?”.
  2. +
  3. Once enabled, you will have access to several actions at the contract +line level:
      +
    • Stop a contract line and optionally plan a successor.
    • +
    • Handle temporary suspensions and resume the contract line +after the suspension period.
    • +
    • Cancel and un-cancel contract lines if necessary.
    • +
    • Renew contract lines either by extending the current line +or by creating a new successor line automatically.
    • +
    +
  4. +
+
+
+

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.

+
+ +
+
+

Authors

+
    +
  • ACSONE SA/NV
  • +
+
+
+

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:

+

sbejaoui

+

This module is part of the OCA/contract project on GitHub.

+

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

+
+
+
+ + diff --git a/contract_line_successor/tests/__init__.py b/contract_line_successor/tests/__init__.py new file mode 100644 index 0000000000..3c02082d69 --- /dev/null +++ b/contract_line_successor/tests/__init__.py @@ -0,0 +1 @@ +from . import test_contract diff --git a/contract_line_successor/tests/test_contract.py b/contract_line_successor/tests/test_contract.py new file mode 100644 index 0000000000..c78964155b --- /dev/null +++ b/contract_line_successor/tests/test_contract.py @@ -0,0 +1,866 @@ +# Copyright 2018 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from datetime import timedelta + +from dateutil.relativedelta import relativedelta + +from odoo.exceptions import ValidationError + +from odoo.addons.contract.tests.test_contract import ( + TestContract, + to_date, +) + + +class TestContractSuccessor(TestContract): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.contract.company_id.create_new_line_at_contract_line_renew = True + cls.line_vals.update({"is_auto_renew": False}) + + def test_date_end(self): + """recurring next date for a contract is the min for all lines""" + self.acct_line.date_end = "2018-01-01" + self.acct_line.copy() + self.acct_line.write({"date_end": False, "is_auto_renew": False}) + self.assertFalse(self.contract.date_end) + + def test_cancel_contract_line(self): + """It should raise a validation error""" + self.acct_line.cancel() + with self.assertRaises(ValidationError): + self.acct_line.stop(self.today) + + def test_stop_contract_line(self): + """It should put end to the contract line""" + self.acct_line.write( + { + "date_start": self.today, + "recurring_next_date": self.today, + "date_end": self.today + relativedelta(months=7), + "is_auto_renew": True, + } + ) + self.acct_line.stop(self.today + relativedelta(months=5)) + self.assertEqual(self.acct_line.date_end, self.today + relativedelta(months=5)) + + def test_stop_upcoming_contract_line(self): + """It should put end to the contract line""" + self.acct_line.write( + { + "date_start": self.today + relativedelta(months=3), + "recurring_next_date": self.today + relativedelta(months=3), + "date_end": self.today + relativedelta(months=7), + "is_auto_renew": True, + } + ) + self.acct_line.stop(self.today) + self.assertEqual(self.acct_line.date_end, self.today + relativedelta(months=7)) + self.assertTrue(self.acct_line.is_canceled) + + def test_stop_past_contract_line(self): + """Past contract line are ignored on stop""" + self.acct_line.write( + {"date_end": self.today + relativedelta(months=5), "is_auto_renew": True} + ) + self.acct_line.stop(self.today + relativedelta(months=7)) + self.assertEqual(self.acct_line.date_end, self.today + relativedelta(months=5)) + + def test_stop_contract_line_without_date_end(self): + """Past contract line are ignored on stop""" + self.acct_line.write({"date_end": False, "is_auto_renew": False}) + self.acct_line.stop(self.today + relativedelta(months=7)) + self.assertEqual(self.acct_line.date_end, self.today + relativedelta(months=7)) + + def test_stop_wizard(self): + self.acct_line.write( + { + "date_start": self.today, + "recurring_next_date": self.today, + "date_end": self.today + relativedelta(months=5), + "is_auto_renew": True, + } + ) + wizard = self.env["contract.line.wizard"].create( + { + "date_end": self.today + relativedelta(months=3), + "contract_line_id": self.acct_line.id, + } + ) + wizard.stop() + self.assertEqual(self.acct_line.date_end, self.today + relativedelta(months=3)) + self.assertFalse(self.acct_line.is_auto_renew) + + def test_stop_plan_successor_contract_line_0(self): + successor_contract_line = self.acct_line.copy( + { + "date_start": self.today + relativedelta(months=5), + "recurring_next_date": self.today + relativedelta(months=5), + } + ) + self.acct_line.write( + { + "successor_contract_line_id": successor_contract_line.id, + "is_auto_renew": False, + "date_end": self.today, + } + ) + suspension_start = self.today + relativedelta(months=5) + suspension_end = self.today + relativedelta(months=6) + with self.assertRaises(ValidationError): + self.acct_line.stop_plan_successor(suspension_start, suspension_end, True) + + def test_stop_plan_successor_contract_line_1(self): + """ + * contract line end's before the suspension period: + -> apply stop + """ + suspension_start = self.today + relativedelta(months=5) + suspension_end = self.today + relativedelta(months=6) + start_date = self.today + end_date = self.today + relativedelta(months=4) + self.acct_line.write( + { + "date_start": start_date, + "recurring_next_date": start_date, + "date_end": end_date, + } + ) + self.acct_line.stop_plan_successor(suspension_start, suspension_end, True) + self.assertEqual(self.acct_line.date_end, end_date) + new_line = self.env["contract.line"].search( + [("predecessor_contract_line_id", "=", self.acct_line.id)] + ) + self.assertFalse(new_line) + + def test_stop_plan_successor_contract_line_2(self): + """ + * contract line start before the suspension period and end in it + -> apply stop at suspension start date + -> apply plan successor: + - date_start: suspension.date_end + - date_end: suspension.date_end + (contract_line.date_end + - suspension.date_start) + """ + suspension_start = self.today + relativedelta(months=3) + suspension_end = self.today + relativedelta(months=5) + start_date = self.today + end_date = self.today + relativedelta(months=4) + self.acct_line.write( + { + "date_start": start_date, + "recurring_next_date": start_date, + "date_end": end_date, + } + ) + self.acct_line.stop_plan_successor(suspension_start, suspension_end, True) + self.assertEqual( + self.acct_line.date_end, suspension_start - relativedelta(days=1) + ) + new_line = self.env["contract.line"].search( + [("predecessor_contract_line_id", "=", self.acct_line.id)] + ) + self.assertTrue(new_line) + new_date_end = ( + suspension_end + (end_date - suspension_start) + relativedelta(days=1) + ) + self.assertEqual(new_line.date_start, suspension_end + relativedelta(days=1)) + self.assertEqual(new_line.date_end, new_date_end) + self.assertTrue(self.acct_line.manual_renew_needed) + + def test_stop_plan_successor_contract_line_3(self): + """ + * contract line start before the suspension period and end after it + -> apply stop at suspension start date + -> apply plan successor: + - date_start: suspension.date_end + - date_end: suspension.date_end + (suspension.date_end + - suspension.date_start) + """ + suspension_start = self.today + relativedelta(months=3) + suspension_end = self.today + relativedelta(months=5) + start_date = self.today + end_date = self.today + relativedelta(months=6) + self.acct_line.write( + { + "date_start": start_date, + "recurring_next_date": start_date, + "date_end": end_date, + } + ) + self.acct_line.stop_plan_successor(suspension_start, suspension_end, True) + self.assertEqual( + self.acct_line.date_end, suspension_start - relativedelta(days=1) + ) + new_line = self.env["contract.line"].search( + [("predecessor_contract_line_id", "=", self.acct_line.id)] + ) + self.assertTrue(new_line) + new_date_end = ( + end_date + (suspension_end - suspension_start) + relativedelta(days=1) + ) + self.assertEqual(new_line.date_start, suspension_end + relativedelta(days=1)) + self.assertEqual(new_line.date_end, new_date_end) + self.assertTrue(self.acct_line.manual_renew_needed) + + def test_stop_plan_successor_contract_line_3_without_end_date(self): + """ + * contract line start before the suspension period and end after it + -> apply stop at suspension start date + -> apply plan successor: + - date_start: suspension.date_end + - date_end: suspension.date_end + (suspension.date_end + - suspension.date_start) + """ + suspension_start = self.today + relativedelta(months=3) + suspension_end = self.today + relativedelta(months=5) + start_date = self.today + end_date = False + self.acct_line.write( + { + "date_start": start_date, + "recurring_next_date": start_date, + "date_end": end_date, + "is_auto_renew": False, + } + ) + self.acct_line.stop_plan_successor(suspension_start, suspension_end, False) + self.assertEqual( + self.acct_line.date_end, suspension_start - relativedelta(days=1) + ) + new_line = self.env["contract.line"].search( + [("predecessor_contract_line_id", "=", self.acct_line.id)] + ) + self.assertTrue(new_line) + self.assertEqual(new_line.date_start, suspension_end + relativedelta(days=1)) + self.assertFalse(new_line.date_end) + self.assertTrue(self.acct_line.manual_renew_needed) + + def test_stop_plan_successor_contract_line_4(self): + """ + * contract line start and end's in the suspension period + -> apply delay + - delay: suspension.date_end - contract_line.end_date + """ + suspension_start = self.today + relativedelta(months=2) + suspension_end = self.today + relativedelta(months=5) + start_date = self.today + relativedelta(months=3) + end_date = self.today + relativedelta(months=4) + self.acct_line.write( + { + "date_start": start_date, + "recurring_next_date": start_date, + "date_end": end_date, + } + ) + self.acct_line.stop_plan_successor(suspension_start, suspension_end, True) + self.assertEqual( + self.acct_line.date_start, + start_date + (suspension_end - start_date) + timedelta(days=1), + ) + self.assertEqual( + self.acct_line.date_end, + end_date + (suspension_end - start_date) + timedelta(days=1), + ) + new_line = self.env["contract.line"].search( + [("predecessor_contract_line_id", "=", self.acct_line.id)] + ) + self.assertFalse(new_line) + + def test_stop_plan_successor_contract_line_5(self): + """ + * contract line start in the suspension period and end after it + -> apply delay + - delay: suspension.date_end - contract_line.date_start + """ + suspension_start = self.today + relativedelta(months=2) + suspension_end = self.today + relativedelta(months=5) + start_date = self.today + relativedelta(months=3) + end_date = self.today + relativedelta(months=6) + self.acct_line.write( + { + "date_start": start_date, + "recurring_next_date": start_date, + "date_end": end_date, + } + ) + self.acct_line.stop_plan_successor(suspension_start, suspension_end, True) + self.assertEqual( + self.acct_line.date_start, + start_date + (suspension_end - start_date) + timedelta(days=1), + ) + self.assertEqual( + self.acct_line.date_end, + end_date + (suspension_end - start_date) + timedelta(days=1), + ) + new_line = self.env["contract.line"].search( + [("predecessor_contract_line_id", "=", self.acct_line.id)] + ) + self.assertFalse(new_line) + + def test_stop_plan_successor_contract_line_5_without_date_end(self): + """ + * contract line start in the suspension period and end after it + -> apply delay + - delay: suspension.date_end - contract_line.date_start + """ + suspension_start = self.today + relativedelta(months=2) + suspension_end = self.today + relativedelta(months=5) + start_date = self.today + relativedelta(months=3) + end_date = False + self.acct_line.write( + { + "date_start": start_date, + "recurring_next_date": start_date, + "date_end": end_date, + "is_auto_renew": False, + } + ) + self.acct_line.stop_plan_successor(suspension_start, suspension_end, True) + self.assertEqual( + self.acct_line.date_start, + start_date + (suspension_end - start_date) + timedelta(days=1), + ) + self.assertFalse(self.acct_line.date_end) + new_line = self.env["contract.line"].search( + [("predecessor_contract_line_id", "=", self.acct_line.id)] + ) + self.assertFalse(new_line) + + def test_stop_plan_successor_contract_line_6(self): + """ + * contract line start and end after the suspension period + -> apply delay + - delay: suspension.date_end - suspension.start_date + """ + suspension_start = self.today + relativedelta(months=2) + suspension_end = self.today + relativedelta(months=3) + start_date = self.today + relativedelta(months=4) + end_date = self.today + relativedelta(months=6) + self.acct_line.write( + { + "date_start": start_date, + "recurring_next_date": start_date, + "date_end": end_date, + } + ) + self.acct_line.stop_plan_successor(suspension_start, suspension_end, True) + self.assertEqual( + self.acct_line.date_start, + start_date + (suspension_end - suspension_start) + timedelta(days=1), + ) + self.assertEqual( + self.acct_line.date_end, + end_date + (suspension_end - suspension_start) + timedelta(days=1), + ) + new_line = self.env["contract.line"].search( + [("predecessor_contract_line_id", "=", self.acct_line.id)] + ) + self.assertFalse(new_line) + + def test_stop_plan_successor_contract_line_6_without_date_end(self): + """ + * contract line start and end after the suspension period + -> apply delay + - delay: suspension.date_end - suspension.start_date + """ + suspension_start = self.today + relativedelta(months=2) + suspension_end = self.today + relativedelta(months=3) + start_date = self.today + relativedelta(months=4) + end_date = False + self.acct_line.write( + { + "date_start": start_date, + "recurring_next_date": start_date, + "date_end": end_date, + "is_auto_renew": False, + } + ) + self.acct_line.stop_plan_successor(suspension_start, suspension_end, True) + self.assertEqual( + self.acct_line.date_start, + start_date + (suspension_end - suspension_start) + timedelta(days=1), + ) + self.assertFalse(self.acct_line.date_end) + new_line = self.env["contract.line"].search( + [("predecessor_contract_line_id", "=", self.acct_line.id)] + ) + self.assertFalse(new_line) + + def test_stop_plan_successor_wizard(self): + suspension_start = self.today + relativedelta(months=2) + suspension_end = self.today + relativedelta(months=3) + start_date = self.today + relativedelta(months=4) + end_date = self.today + relativedelta(months=6) + self.acct_line.write( + { + "date_start": start_date, + "recurring_next_date": start_date, + "date_end": end_date, + } + ) + wizard = self.env["contract.line.wizard"].create( + { + "date_start": suspension_start, + "date_end": suspension_end, + "is_auto_renew": False, + "contract_line_id": self.acct_line.id, + } + ) + wizard.stop_plan_successor() + self.assertEqual( + self.acct_line.date_start, + start_date + (suspension_end - suspension_start) + timedelta(days=1), + ) + self.assertEqual( + self.acct_line.date_end, + end_date + (suspension_end - suspension_start) + timedelta(days=1), + ) + new_line = self.env["contract.line"].search( + [("predecessor_contract_line_id", "=", self.acct_line.id)] + ) + self.assertFalse(new_line) + + def test_plan_successor_contract_line(self): + self.acct_line.write( + { + "date_start": self.today, + "recurring_next_date": self.today, + "date_end": self.today + relativedelta(months=3), + "is_auto_renew": False, + } + ) + self.acct_line.plan_successor( + self.today + relativedelta(months=5), + self.today + relativedelta(months=7), + True, + ) + new_line = self.env["contract.line"].search( + [("predecessor_contract_line_id", "=", self.acct_line.id)] + ) + self.assertFalse(self.acct_line.is_auto_renew) + self.assertTrue(new_line.is_auto_renew) + self.assertTrue(new_line, "should create a new contract line") + self.assertEqual(new_line.date_start, self.today + relativedelta(months=5)) + self.assertEqual(new_line.date_end, self.today + relativedelta(months=7)) + + def test_overlap(self): + self.acct_line.write( + { + "date_start": self.today, + "recurring_next_date": self.today, + "date_end": self.today + relativedelta(months=3), + "is_auto_renew": False, + } + ) + self.acct_line.plan_successor( + self.today + relativedelta(months=5), + self.today + relativedelta(months=7), + True, + ) + new_line = self.env["contract.line"].search( + [("predecessor_contract_line_id", "=", self.acct_line.id)] + ) + with self.assertRaises(ValidationError): + new_line.date_start = self.today + relativedelta(months=2) + with self.assertRaises(ValidationError): + self.acct_line.date_end = self.today + relativedelta(months=6) + + def test_plan_successor_wizard(self): + self.acct_line.write( + { + "date_start": self.today, + "recurring_next_date": self.today, + "date_end": self.today + relativedelta(months=2), + "is_auto_renew": False, + } + ) + wizard = self.env["contract.line.wizard"].create( + { + "date_start": self.today + relativedelta(months=3), + "date_end": self.today + relativedelta(months=5), + "is_auto_renew": True, + "contract_line_id": self.acct_line.id, + } + ) + wizard.plan_successor() + new_line = self.env["contract.line"].search( + [("predecessor_contract_line_id", "=", self.acct_line.id)] + ) + self.assertFalse(self.acct_line.is_auto_renew) + self.assertTrue(new_line.is_auto_renew) + self.assertTrue(new_line, "should create a new contract line") + self.assertEqual(new_line.date_start, self.today + relativedelta(months=3)) + self.assertEqual(new_line.date_end, self.today + relativedelta(months=5)) + + def test_cancel(self): + self.acct_line.write( + {"date_end": self.today + relativedelta(months=5), "is_auto_renew": True} + ) + self.acct_line.cancel() + self.assertTrue(self.acct_line.is_canceled) + self.assertFalse(self.acct_line.is_auto_renew) + with self.assertRaises(ValidationError): + self.acct_line.is_auto_renew = True + self.acct_line.uncancel(self.today) + self.assertFalse(self.acct_line.is_canceled) + + def test_uncancel_wizard(self): + self.acct_line.cancel() + self.assertTrue(self.acct_line.is_canceled) + wizard = self.env["contract.line.wizard"].create( + {"recurring_next_date": self.today, "contract_line_id": self.acct_line.id} + ) + wizard.uncancel() + self.assertFalse(self.acct_line.is_canceled) + + def test_cancel_uncancel_with_predecessor(self): + suspension_start = self.today + relativedelta(months=3) + suspension_end = self.today + relativedelta(months=5) + start_date = self.today + end_date = self.today + relativedelta(months=4) + self.acct_line.write( + { + "date_start": start_date, + "recurring_next_date": start_date, + "date_end": end_date, + } + ) + self.acct_line.stop_plan_successor(suspension_start, suspension_end, True) + self.assertEqual( + self.acct_line.date_end, suspension_start - relativedelta(days=1) + ) + new_line = self.env["contract.line"].search( + [("predecessor_contract_line_id", "=", self.acct_line.id)] + ) + self.assertEqual(self.acct_line.successor_contract_line_id, new_line) + new_line.cancel() + self.assertTrue(new_line.is_canceled) + self.assertFalse(self.acct_line.successor_contract_line_id) + self.assertEqual(new_line.predecessor_contract_line_id, self.acct_line) + new_line.uncancel(suspension_end + relativedelta(days=1)) + self.assertFalse(new_line.is_canceled) + self.assertEqual(self.acct_line.successor_contract_line_id, new_line) + self.assertEqual( + new_line.recurring_next_date, + suspension_end + relativedelta(days=1), + ) + + def test_cancel_uncancel_with_predecessor_has_successor(self): + suspension_start = self.today + relativedelta(months=6) + suspension_end = self.today + relativedelta(months=7) + start_date = self.today + end_date = self.today + relativedelta(months=8) + self.acct_line.write( + { + "date_start": start_date, + "recurring_next_date": start_date, + "date_end": end_date, + } + ) + self.acct_line.stop_plan_successor(suspension_start, suspension_end, True) + new_line = self.env["contract.line"].search( + [("predecessor_contract_line_id", "=", self.acct_line.id)] + ) + new_line.cancel() + suspension_start = self.today + relativedelta(months=4) + suspension_end = self.today + relativedelta(months=5) + self.acct_line.stop_plan_successor(suspension_start, suspension_end, True) + with self.assertRaises(ValidationError): + new_line.uncancel(suspension_end) + + def test_check_has_not_date_end_has_successor(self): + self.acct_line.write({"date_end": False, "is_auto_renew": False}) + with self.assertRaises(ValidationError): + self.acct_line.plan_successor( + to_date("2016-03-01"), to_date("2016-09-01"), False + ) + + def test_check_has_not_date_end_is_auto_renew(self): + with self.assertRaises(ValidationError): + self.acct_line.write({"date_end": False, "is_auto_renew": True}) + + def test_check_has_successor_is_auto_renew(self): + with self.assertRaises(ValidationError): + self.acct_line.plan_successor( + to_date("2016-03-01"), to_date("2018-09-01"), False + ) + + def test_search_contract_line_to_renew(self): + self.acct_line.write({"date_end": self.today, "is_auto_renew": True}) + line_1 = self.acct_line.copy({"date_end": self.today + relativedelta(months=1)}) + line_2 = self.acct_line.copy({"date_end": self.today - relativedelta(months=1)}) + line_3 = self.acct_line.copy({"date_end": self.today - relativedelta(months=2)}) + line_4 = self.acct_line.copy({"date_end": self.today + relativedelta(months=2)}) + to_renew = self.acct_line.search( + self.acct_line._contract_line_to_renew_domain() + ) + self.assertEqual(set(to_renew), {self.acct_line, line_1, line_2, line_3}) + self.acct_line.cron_renew_contract_line() + self.assertTrue(self.acct_line.successor_contract_line_id) + self.assertTrue(line_1.successor_contract_line_id) + self.assertTrue(line_2.successor_contract_line_id) + self.assertTrue(line_3.successor_contract_line_id) + self.assertFalse(line_4.successor_contract_line_id) + + def test_renew_create_new_line(self): + date_start = self.today - relativedelta(months=9) + date_end = date_start + relativedelta(months=12) - relativedelta(days=1) + self.acct_line.write( + { + "is_auto_renew": True, + "date_start": date_start, + "recurring_next_date": date_start, + "date_end": self.today, + } + ) + self.acct_line._onchange_is_auto_renew() + self.assertEqual(self.acct_line.date_end, date_end) + new_line = self.acct_line.renew() + self.assertFalse(self.acct_line.is_auto_renew) + self.assertTrue(new_line.is_auto_renew) + self.assertEqual(new_line.date_start, date_start + relativedelta(months=12)) + self.assertEqual(new_line.date_end, date_end + relativedelta(months=12)) + + def test_renew_extend_original_line(self): + self.contract.company_id.create_new_line_at_contract_line_renew = False + date_start = self.today - relativedelta(months=9) + date_end = date_start + relativedelta(months=12) - relativedelta(days=1) + self.acct_line.write( + { + "is_auto_renew": True, + "date_start": date_start, + "recurring_next_date": date_start, + "date_end": self.today, + } + ) + self.acct_line._onchange_is_auto_renew() + self.assertEqual(self.acct_line.date_end, date_end) + self.acct_line.renew() + self.assertTrue(self.acct_line.is_auto_renew) + self.assertEqual(self.acct_line.date_start, date_start) + self.assertEqual(self.acct_line.date_end, date_end + relativedelta(months=12)) + + def test_unlink(self): + with self.assertRaises(ValidationError): + self.acct_line.unlink() + + def test_contract_line_state(self): + lines = self.env["contract.line"] + # upcoming + lines |= self.acct_line.copy( + { + "date_start": self.today + relativedelta(months=3), + "recurring_next_date": self.today + relativedelta(months=3), + "date_end": self.today + relativedelta(months=5), + } + ) + # in-progress + lines |= self.acct_line.copy( + { + "date_start": self.today, + "recurring_next_date": self.today, + "date_end": self.today + relativedelta(months=5), + } + ) + # in-progress + lines |= self.acct_line.copy( + { + "date_start": self.today, + "recurring_next_date": self.today, + "date_end": self.today + relativedelta(months=5), + "manual_renew_needed": True, + } + ) + # to-renew + lines |= self.acct_line.copy( + { + "date_start": self.today - relativedelta(months=5), + "recurring_next_date": self.today - relativedelta(months=5), + "date_end": self.today - relativedelta(months=2), + "manual_renew_needed": True, + } + ) + # upcoming-close + lines |= self.acct_line.copy( + { + "date_start": self.today - relativedelta(months=5), + "recurring_next_date": self.today - relativedelta(months=5), + "date_end": self.today + relativedelta(days=20), + "is_auto_renew": False, + } + ) + # closed + lines |= self.acct_line.copy( + { + "date_start": self.today - relativedelta(months=5), + "recurring_next_date": self.today - relativedelta(months=5), + "date_end": self.today - relativedelta(months=2), + "is_auto_renew": False, + } + ) + # canceled + lines |= self.acct_line.copy( + { + "date_start": self.today - relativedelta(months=5), + "recurring_next_date": self.today - relativedelta(months=5), + "date_end": self.today - relativedelta(months=2), + "is_canceled": True, + } + ) + # section + lines |= self.env["contract.line"].create( + { + "contract_id": self.contract.id, + "display_type": "line_section", + "name": "Test section", + } + ) + states = [ + "upcoming", + "in-progress", + "to-renew", + "upcoming-close", + "closed", + "canceled", + False, + ] + self.assertEqual(set(lines.mapped("state")), set(states)) + # Test search method + lines.flush_recordset() # Needed for computed stored fields + # like termination_notice_date + for state in states: + lines = self.env["contract.line"].search([("state", "=", state)]) + self.assertTrue(lines, state) + self.assertTrue(state in lines.mapped("state"), state) + lines = self.env["contract.line"].search([("state", "!=", state)]) + self.assertFalse(state in lines.mapped("state"), state) + lines = self.env["contract.line"].search([("state", "in", states)]) + self.assertEqual(set(lines.mapped("state")), set(states)) + lines = self.env["contract.line"].search([("state", "in", [])]) + self.assertFalse(lines.mapped("state")) + lines = self.env["contract.line"].search([("state", "not in", [])]) + self.assertEqual(set(lines.mapped("state")), set(states)) + lines = self.env["contract.line"].search([("state", "not in", states)]) + self.assertFalse(lines.mapped("state")) + state2 = ["upcoming", "in-progress"] + lines = self.env["contract.line"].search([("state", "not in", state2)]) + self.assertEqual(set(lines.mapped("state")), set(states) - set(state2)) + + def test_check_auto_renew_contract_line_with_successor(self): + """ + A contract line with a successor can't be set to auto-renew + """ + successor_contract_line = self.acct_line.copy() + with self.assertRaises(ValidationError): + self.acct_line.write( + { + "is_auto_renew": True, + "successor_contract_line_id": successor_contract_line.id, + } + ) + + def test_check_no_date_end_contract_line_with_successor(self): + """ + A contract line with a successor must have a end date + """ + successor_contract_line = self.acct_line.copy() + with self.assertRaises(ValidationError): + self.acct_line.write( + { + "date_end": False, + "successor_contract_line_id": successor_contract_line.id, + } + ) + + def test_delay_invoiced_contract_line(self): + self.acct_line.write( + {"last_date_invoiced": self.acct_line.date_start + relativedelta(days=1)} + ) + with self.assertRaises(ValidationError): + self.acct_line._delay(relativedelta(months=1)) + + def test_cancel_invoiced_contract_line(self): + self.acct_line.write( + {"last_date_invoiced": self.acct_line.date_start + relativedelta(days=1)} + ) + with self.assertRaises(ValidationError): + self.acct_line.cancel() + + def test_action_uncancel(self): + action = self.acct_line.action_uncancel() + self.assertEqual( + action["context"]["default_contract_line_id"], self.acct_line.id + ) + + def test_action_plan_successor(self): + action = self.acct_line.action_plan_successor() + self.assertEqual( + action["context"]["default_contract_line_id"], self.acct_line.id + ) + + def test_action_stop(self): + action = self.acct_line.action_stop() + self.assertEqual( + action["context"]["default_contract_line_id"], self.acct_line.id + ) + + def test_action_stop_plan_successor(self): + action = self.acct_line.action_stop_plan_successor() + self.assertEqual( + action["context"]["default_contract_line_id"], self.acct_line.id + ) + + def test_stop_at_last_date_invoiced(self): + self.contract.journal_id.create( + { + "name": "Test Journal", + "code": "TEST", + "type": "sale", + } + ) + self.contract.recurring_create_invoice() + self.assertTrue(self.acct_line.recurring_next_date) + self.acct_line.stop(self.acct_line.last_date_invoiced) + self.assertFalse(self.acct_line.recurring_next_date) + + def test_check_last_date_invoiced_before_next_invoice_date(self): + with self.assertRaises(ValidationError): + self.acct_line.write( + { + "date_start": "2019-01-01", + "date_end": "2019-12-01", + "recurring_next_date": "2019-01-01", + "last_date_invoiced": "2019-06-01", + } + ) + + def test_stop_and_update_recurring_invoice_date(self): + self.acct_line.write( + { + "date_start": "2019-01-01", + "date_end": "2019-12-31", + "recurring_next_date": "2020-01-01", + "recurring_invoicing_type": "post-paid", + "recurring_rule_type": "yearly", + } + ) + self.acct_line.stop(to_date("2019-05-31")) + self.assertEqual(self.acct_line.date_end, to_date("2019-05-31")) + self.assertEqual(self.acct_line.recurring_next_date, to_date("2019-06-01")) + + def test_contract_template_is_auto_renew(self): + contract_template = self.env["contract.template"].create( + { + "name": "Template Auto Renew Test", + "contract_line_ids": [ + (0, 0, {"name": "Line 1", "is_auto_renew": True}), + (0, 0, {"name": "Line 2", "is_auto_renew": True}), + ], + } + ) + self.assertTrue(contract_template.is_auto_renew) + contract_template.contract_line_ids[1].write({"is_auto_renew": False}) + self.assertFalse(contract_template.is_auto_renew) diff --git a/contract_line_successor/views/contract_contract.xml b/contract_line_successor/views/contract_contract.xml new file mode 100644 index 0000000000..3ac792370d --- /dev/null +++ b/contract_line_successor/views/contract_contract.xml @@ -0,0 +1,73 @@ + + + + + contract.contract + + + + + + + + + + +