diff --git a/sale_contract_by_template/__init__.py b/sale_contract_by_template/__init__.py
new file mode 100644
index 000000000..0650744f6
--- /dev/null
+++ b/sale_contract_by_template/__init__.py
@@ -0,0 +1 @@
+from . import models
diff --git a/sale_contract_by_template/__manifest__.py b/sale_contract_by_template/__manifest__.py
new file mode 100644
index 000000000..19d8c8d71
--- /dev/null
+++ b/sale_contract_by_template/__manifest__.py
@@ -0,0 +1,23 @@
+# Copyright 2022 Oihane Crucelaegui - AvanzOSC
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
+
+{
+ "name": "Sale Contract",
+ "version": "14.0.1.0.0",
+ "category": "Sales",
+ "license": "AGPL-3",
+ "author": "AvanzOSC",
+ "website": "https://github.com/avanzosc/sale-addons",
+ "depends": [
+ "contract",
+ "contract_sale",
+ "sale",
+ ],
+ "excludes": [],
+ "data": [
+ "views/contract_contract_views.xml",
+ "views/product_views.xml",
+ "views/sale_order_views.xml",
+ ],
+ "installable": True,
+}
diff --git a/sale_contract_by_template/i18n/es.po b/sale_contract_by_template/i18n/es.po
new file mode 100644
index 000000000..6edfdd260
--- /dev/null
+++ b/sale_contract_by_template/i18n/es.po
@@ -0,0 +1,91 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * sale_contract_by_template
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 14.0+e\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2022-11-23 14:07+0000\n"
+"PO-Revision-Date: 2022-11-23 14:07+0000\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: sale_contract_by_template
+#: model:ir.model,name:sale_contract_by_template.model_contract_contract
+msgid "Contract"
+msgstr "Contrato"
+
+#. module: sale_contract_by_template
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order__contract_count
+msgid "Contract Count"
+msgstr "Nº de contratos"
+
+#. module: sale_contract_by_template
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_product__contract_tmpl_id
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_template__contract_tmpl_id
+msgid "Contract Template"
+msgstr "Plantilla de contrato"
+
+#. module: sale_contract_by_template
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order__contract_ids
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order_line__contract_ids
+#: model_terms:ir.ui.view,arch_db:sale_contract_by_template.sale_order_view_form
+msgid "Contracts"
+msgstr "Contratos"
+
+#. module: sale_contract_by_template
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_contract_contract__display_name
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_product__display_name
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_template__display_name
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order__display_name
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order_line__display_name
+msgid "Display Name"
+msgstr "Nombre mostrado"
+
+#. module: sale_contract_by_template
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_contract_contract__id
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_product__id
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_template__id
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order__id
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order_line__id
+msgid "ID"
+msgstr ""
+
+#. module: sale_contract_by_template
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_contract_contract____last_update
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_product____last_update
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_template____last_update
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order____last_update
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order_line____last_update
+msgid "Last Modified on"
+msgstr "Última Modificación en"
+
+#. module: sale_contract_by_template
+#: model:ir.model,name:sale_contract_by_template.model_product_product
+msgid "Product"
+msgstr "Producto"
+
+#. module: sale_contract_by_template
+#: model:ir.model,name:sale_contract_by_template.model_product_template
+msgid "Product Template"
+msgstr "Plantilla de producto"
+
+#. module: sale_contract_by_template
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_contract_contract__sale_line_id
+msgid "Sale Order Line"
+msgstr "Línea de pedido de venta"
+
+#. module: sale_contract_by_template
+#: model:ir.model,name:sale_contract_by_template.model_sale_order
+msgid "Sales Order"
+msgstr "Pedido de venta"
+
+#. module: sale_contract_by_template
+#: model:ir.model,name:sale_contract_by_template.model_sale_order_line
+msgid "Sales Order Line"
+msgstr "Línea de pedido de venta"
diff --git a/sale_contract_by_template/i18n/fr.po b/sale_contract_by_template/i18n/fr.po
new file mode 100644
index 000000000..ad087c267
--- /dev/null
+++ b/sale_contract_by_template/i18n/fr.po
@@ -0,0 +1,91 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * sale_contract_by_template
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 14.0+e\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2022-11-23 14:06+0000\n"
+"PO-Revision-Date: 2022-11-23 14:06+0000\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: sale_contract_by_template
+#: model:ir.model,name:sale_contract_by_template.model_contract_contract
+msgid "Contract"
+msgstr "Contrat"
+
+#. module: sale_contract_by_template
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order__contract_count
+msgid "Contract Count"
+msgstr ""
+
+#. module: sale_contract_by_template
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_product__contract_tmpl_id
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_template__contract_tmpl_id
+msgid "Contract Template"
+msgstr ""
+
+#. module: sale_contract_by_template
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order__contract_ids
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order_line__contract_ids
+#: model_terms:ir.ui.view,arch_db:sale_contract_by_template.sale_order_view_form
+msgid "Contracts"
+msgstr ""
+
+#. module: sale_contract_by_template
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_contract_contract__display_name
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_product__display_name
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_template__display_name
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order__display_name
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order_line__display_name
+msgid "Display Name"
+msgstr "Nom à afficher"
+
+#. module: sale_contract_by_template
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_contract_contract__id
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_product__id
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_template__id
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order__id
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order_line__id
+msgid "ID"
+msgstr ""
+
+#. module: sale_contract_by_template
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_contract_contract____last_update
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_product____last_update
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_template____last_update
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order____last_update
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order_line____last_update
+msgid "Last Modified on"
+msgstr "Dernière modification le"
+
+#. module: sale_contract_by_template
+#: model:ir.model,name:sale_contract_by_template.model_product_product
+msgid "Product"
+msgstr "Article"
+
+#. module: sale_contract_by_template
+#: model:ir.model,name:sale_contract_by_template.model_product_template
+msgid "Product Template"
+msgstr "Modèle d'article"
+
+#. module: sale_contract_by_template
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_contract_contract__sale_line_id
+msgid "Sale Order Line"
+msgstr ""
+
+#. module: sale_contract_by_template
+#: model:ir.model,name:sale_contract_by_template.model_sale_order
+msgid "Sales Order"
+msgstr "Bon de commande"
+
+#. module: sale_contract_by_template
+#: model:ir.model,name:sale_contract_by_template.model_sale_order_line
+msgid "Sales Order Line"
+msgstr "Ligne de bons de commande"
diff --git a/sale_contract_by_template/i18n/pt.po b/sale_contract_by_template/i18n/pt.po
new file mode 100644
index 000000000..a4116caff
--- /dev/null
+++ b/sale_contract_by_template/i18n/pt.po
@@ -0,0 +1,91 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * sale_contract_by_template
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 14.0+e\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2022-11-23 14:06+0000\n"
+"PO-Revision-Date: 2022-11-23 14:06+0000\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: sale_contract_by_template
+#: model:ir.model,name:sale_contract_by_template.model_contract_contract
+msgid "Contract"
+msgstr "Contrato"
+
+#. module: sale_contract_by_template
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order__contract_count
+msgid "Contract Count"
+msgstr ""
+
+#. module: sale_contract_by_template
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_product__contract_tmpl_id
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_template__contract_tmpl_id
+msgid "Contract Template"
+msgstr ""
+
+#. module: sale_contract_by_template
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order__contract_ids
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order_line__contract_ids
+#: model_terms:ir.ui.view,arch_db:sale_contract_by_template.sale_order_view_form
+msgid "Contracts"
+msgstr ""
+
+#. module: sale_contract_by_template
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_contract_contract__display_name
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_product__display_name
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_template__display_name
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order__display_name
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order_line__display_name
+msgid "Display Name"
+msgstr "Nome a Apresentar"
+
+#. module: sale_contract_by_template
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_contract_contract__id
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_product__id
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_template__id
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order__id
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order_line__id
+msgid "ID"
+msgstr ""
+
+#. module: sale_contract_by_template
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_contract_contract____last_update
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_product____last_update
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_template____last_update
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order____last_update
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order_line____last_update
+msgid "Last Modified on"
+msgstr "Última Modificação Em"
+
+#. module: sale_contract_by_template
+#: model:ir.model,name:sale_contract_by_template.model_product_product
+msgid "Product"
+msgstr "Artigo"
+
+#. module: sale_contract_by_template
+#: model:ir.model,name:sale_contract_by_template.model_product_template
+msgid "Product Template"
+msgstr "Modelo de Artigo"
+
+#. module: sale_contract_by_template
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_contract_contract__sale_line_id
+msgid "Sale Order Line"
+msgstr ""
+
+#. module: sale_contract_by_template
+#: model:ir.model,name:sale_contract_by_template.model_sale_order
+msgid "Sales Order"
+msgstr "Ordem de Vendas"
+
+#. module: sale_contract_by_template
+#: model:ir.model,name:sale_contract_by_template.model_sale_order_line
+msgid "Sales Order Line"
+msgstr "Linhas da Ordem de Vendas"
diff --git a/sale_contract_by_template/i18n/sale_contract_by_template.pot b/sale_contract_by_template/i18n/sale_contract_by_template.pot
new file mode 100644
index 000000000..9cd0a629a
--- /dev/null
+++ b/sale_contract_by_template/i18n/sale_contract_by_template.pot
@@ -0,0 +1,91 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * sale_contract_by_template
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 14.0+e\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2022-11-23 14:06+0000\n"
+"PO-Revision-Date: 2022-11-23 14:06+0000\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: sale_contract_by_template
+#: model:ir.model,name:sale_contract_by_template.model_contract_contract
+msgid "Contract"
+msgstr ""
+
+#. module: sale_contract_by_template
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order__contract_count
+msgid "Contract Count"
+msgstr ""
+
+#. module: sale_contract_by_template
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_product__contract_tmpl_id
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_template__contract_tmpl_id
+msgid "Contract Template"
+msgstr ""
+
+#. module: sale_contract_by_template
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order__contract_ids
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order_line__contract_ids
+#: model_terms:ir.ui.view,arch_db:sale_contract_by_template.sale_order_view_form
+msgid "Contracts"
+msgstr ""
+
+#. module: sale_contract_by_template
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_contract_contract__display_name
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_product__display_name
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_template__display_name
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order__display_name
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order_line__display_name
+msgid "Display Name"
+msgstr ""
+
+#. module: sale_contract_by_template
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_contract_contract__id
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_product__id
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_template__id
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order__id
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order_line__id
+msgid "ID"
+msgstr ""
+
+#. module: sale_contract_by_template
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_contract_contract____last_update
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_product____last_update
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_product_template____last_update
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order____last_update
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_sale_order_line____last_update
+msgid "Last Modified on"
+msgstr ""
+
+#. module: sale_contract_by_template
+#: model:ir.model,name:sale_contract_by_template.model_product_product
+msgid "Product"
+msgstr ""
+
+#. module: sale_contract_by_template
+#: model:ir.model,name:sale_contract_by_template.model_product_template
+msgid "Product Template"
+msgstr ""
+
+#. module: sale_contract_by_template
+#: model:ir.model.fields,field_description:sale_contract_by_template.field_contract_contract__sale_line_id
+msgid "Sale Order Line"
+msgstr ""
+
+#. module: sale_contract_by_template
+#: model:ir.model,name:sale_contract_by_template.model_sale_order
+msgid "Sales Order"
+msgstr ""
+
+#. module: sale_contract_by_template
+#: model:ir.model,name:sale_contract_by_template.model_sale_order_line
+msgid "Sales Order Line"
+msgstr ""
diff --git a/sale_contract_by_template/models/__init__.py b/sale_contract_by_template/models/__init__.py
new file mode 100644
index 000000000..38dcbd5d4
--- /dev/null
+++ b/sale_contract_by_template/models/__init__.py
@@ -0,0 +1,4 @@
+from . import contract_contract
+from . import product
+from . import sale_order
+from . import sale_order_line
diff --git a/sale_contract_by_template/models/contract_contract.py b/sale_contract_by_template/models/contract_contract.py
new file mode 100644
index 000000000..f3585c209
--- /dev/null
+++ b/sale_contract_by_template/models/contract_contract.py
@@ -0,0 +1,13 @@
+# Copyright 2022 Oihane Crucelaegui - AvanzOSC
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
+
+from odoo import fields, models
+
+
+class ContractContract(models.Model):
+ _inherit = "contract.contract"
+
+ sale_line_id = fields.Many2one(
+ comodel_name="sale.order.line",
+ string="Sale Order Line",
+ )
diff --git a/sale_contract_by_template/models/product.py b/sale_contract_by_template/models/product.py
new file mode 100644
index 000000000..646dd8235
--- /dev/null
+++ b/sale_contract_by_template/models/product.py
@@ -0,0 +1,53 @@
+# Copyright 2022 Oihane Crucelaegui - AvanzOSC
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
+
+from odoo import api, fields, models
+
+
+class ProductTemplate(models.Model):
+ _inherit = "product.template"
+
+ contract_tmpl_id = fields.Many2one(
+ comodel_name="contract.template",
+ string="Contract Template",
+ compute="_compute_contract_template",
+ inverse="_inverse_contract_template",
+ search="_search_contract_template",
+ )
+
+ @api.depends_context("company")
+ @api.depends("product_variant_ids", "product_variant_ids.contract_tmpl_id")
+ def _compute_contract_template(self):
+ # Depends on force_company context because contract_tmpl_id is company_dependent
+ # on the product_product
+ unique_variants = self.filtered(
+ lambda template: len(template.product_variant_ids) == 1
+ )
+ for template in unique_variants:
+ template.contract_tmpl_id = template.product_variant_ids.contract_tmpl_id
+ for template in self - unique_variants:
+ template.contract_tmpl_id = False
+
+ def _inverse_contract_template(self):
+ for template in self:
+ if len(template.product_variant_ids) == 1:
+ template.product_variant_ids.contract_tmpl_id = (
+ template.contract_tmpl_id
+ )
+
+ def _search_contract_template(self, operator, value):
+ products = self.env["product.product"].search(
+ [("contract_tmpl_id", operator, value)], limit=None
+ )
+ return [("id", "in", products.mapped("product_tmpl_id").ids)]
+
+
+class ProductProduct(models.Model):
+ _inherit = "product.product"
+
+ contract_tmpl_id = fields.Many2one(
+ comodel_name="contract.template",
+ string="Contract Template",
+ domain="[('contract_type', '=', 'sale')]",
+ company_dependent=True,
+ )
diff --git a/sale_contract_by_template/models/sale_order.py b/sale_contract_by_template/models/sale_order.py
new file mode 100644
index 000000000..7edaf38f6
--- /dev/null
+++ b/sale_contract_by_template/models/sale_order.py
@@ -0,0 +1,71 @@
+# Copyright 2022 Oihane Crucelaegui - AvanzOSC
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
+
+from odoo import api, fields, models
+
+
+class SaleOrder(models.Model):
+ _inherit = "sale.order"
+
+ contract_count = fields.Integer(
+ string="Contract Count",
+ compute="_compute_contract_id",
+ readonly=True,
+ )
+ contract_ids = fields.Many2many(
+ comodel_name="contract.contract",
+ string="Contracts",
+ compute="_compute_contract_id",
+ readonly=True,
+ copy=False,
+ )
+
+ @api.depends("order_line.contract_ids")
+ def _compute_contract_id(self):
+ for order in self:
+ contracts = order.mapped("order_line.contract_ids")
+ order.contract_ids = contracts
+ order.contract_count = len(contracts)
+
+ def action_view_contract(self):
+ contracts = self.mapped("contract_ids")
+ action = self.env["ir.actions.actions"]._for_xml_id(
+ "contract.action_customer_contract"
+ )
+ if len(contracts) > 1:
+ action["domain"] = [("id", "in", contracts.ids)]
+ elif len(contracts) == 1:
+ form_view = [
+ (
+ self.env.ref("contract.contract_contract_customer_form_view").id,
+ "form",
+ )
+ ]
+ if "views" in action:
+ action["views"] = form_view + [
+ (state, view) for state, view in action["views"] if view != "form"
+ ]
+ else:
+ action["views"] = form_view
+ action["res_id"] = contracts.id
+ else:
+ action = {"type": "ir.actions.act_window_close"}
+
+ context = {}
+ if len(self) == 1:
+ context.update(
+ {
+ "default_partner_id": self.partner_id.id,
+ "default_user_id": self.user_id.id,
+ }
+ )
+ action["context"] = context
+ return action
+
+ def _action_confirm(self):
+ """On SO confirmation, some lines should generate a contract."""
+ result = super()._action_confirm()
+ if len(self.company_id) == 1:
+ # All orders are in the same company
+ self.order_line.sudo()._contract_generation()
+ return result
diff --git a/sale_contract_by_template/models/sale_order_line.py b/sale_contract_by_template/models/sale_order_line.py
new file mode 100644
index 000000000..30cada2bf
--- /dev/null
+++ b/sale_contract_by_template/models/sale_order_line.py
@@ -0,0 +1,74 @@
+# Copyright 2022 Oihane Crucelaegui - AvanzOSC
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
+
+# EL MODULO contract_sale_generation crea un campo llamado contract_line_id en las
+# líneas de pedido, pero el funcionamiento es a la inversa de lo que queremos.
+# Así que hay que recordar no llamarlo igual, por si acaso.
+
+from odoo import api, fields, models
+
+
+class SaleOrderLine(models.Model):
+ _inherit = "sale.order.line"
+
+ contract_ids = fields.One2many(
+ comodel_name="contract.contract",
+ inverse_name="sale_line_id",
+ string="Contracts",
+ )
+
+ # no trigger product_id.invoice_policy to avoid retroactively changing SO
+ @api.depends("qty_invoiced", "qty_delivered", "product_uom_qty", "order_id.state")
+ def _get_to_invoice_qty(self):
+ super()._get_to_invoice_qty()
+ for line in self:
+ if line.product_id.contract_tmpl_id:
+ line.qty_to_invoice = 0
+
+ def _prepare_contract(self):
+ self.ensure_one()
+ contract = self.env["contract.contract"].new(
+ {
+ "contract_type": "sale",
+ "name": self.display_name,
+ "partner_id": self.order_partner_id.id,
+ "invoice_partner_id": self.order_id.partner_invoice_id.id,
+ "company_id": self.company_id.id,
+ "user_id": self.salesman_id.id,
+ "contract_template_id": self.product_id.contract_tmpl_id.id,
+ "sale_line_id": self.id,
+ "line_recurrence": True,
+ }
+ )
+ contract._onchange_partner_id()
+ if self.order_id.partner_invoice_id:
+ contract.invoice_partner_id = self.order_id.partner_invoice_id.id
+ if self.order_id.pricelist_id:
+ contract.pricelist_id = (self.order_id.pricelist_id.id,)
+ if self.order_id.payment_term_id:
+ contract.payment_term_id = self.order_id.payment_term_id.id
+ if self.order_id.fiscal_position_id:
+ contract.fiscal_position_id = self.order_id.fiscal_position_id.id
+ # Get other contract values from template onchange
+ contract._onchange_contract_template_id()
+ return contract._convert_to_write(contract._cache)
+
+ def _prepare_contract_values(self):
+ contracts_values = []
+ for sale_line in self:
+ for _cnt in range(int(sale_line.product_uom_qty)):
+ contracts_values.append(sale_line._prepare_contract())
+ return contracts_values
+
+ @api.model_create_multi
+ def create(self, vals_list):
+ lines = super().create(vals_list)
+ for line in lines:
+ if line.state == "sale" and line.product_id.contract_tmpl_id:
+ line.sudo()._contract_generation()
+ return lines
+
+ def _contract_generation(self):
+ contract_values = self._prepare_contract_values()
+ contracts = self.env["contract.contract"].create(contract_values)
+ return contracts
diff --git a/sale_contract_by_template/views/contract_contract_views.xml b/sale_contract_by_template/views/contract_contract_views.xml
new file mode 100644
index 000000000..3465f19f3
--- /dev/null
+++ b/sale_contract_by_template/views/contract_contract_views.xml
@@ -0,0 +1,12 @@
+
+
+
+ contract.contract
+
+
+
+
+
+
+
+
diff --git a/sale_contract_by_template/views/product_views.xml b/sale_contract_by_template/views/product_views.xml
new file mode 100644
index 000000000..1d2d31ac6
--- /dev/null
+++ b/sale_contract_by_template/views/product_views.xml
@@ -0,0 +1,15 @@
+
+
+
+ product.template
+
+
+
+
+
+
+
+
diff --git a/sale_contract_by_template/views/sale_order_views.xml b/sale_contract_by_template/views/sale_order_views.xml
new file mode 100644
index 000000000..23ef0856f
--- /dev/null
+++ b/sale_contract_by_template/views/sale_order_views.xml
@@ -0,0 +1,33 @@
+
+
+
+ sale.order
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/setup/sale_contract_by_template/odoo/addons/sale_contract_by_template b/setup/sale_contract_by_template/odoo/addons/sale_contract_by_template
new file mode 120000
index 000000000..233dcd1ee
--- /dev/null
+++ b/setup/sale_contract_by_template/odoo/addons/sale_contract_by_template
@@ -0,0 +1 @@
+../../../../sale_contract_by_template
\ No newline at end of file
diff --git a/setup/sale_contract_by_template/setup.py b/setup/sale_contract_by_template/setup.py
new file mode 100644
index 000000000..28c57bb64
--- /dev/null
+++ b/setup/sale_contract_by_template/setup.py
@@ -0,0 +1,6 @@
+import setuptools
+
+setuptools.setup(
+ setup_requires=['setuptools-odoo'],
+ odoo_addon=True,
+)