From c542be3cb4151f2d1d155ab302c20b6966c72337 Mon Sep 17 00:00:00 2001 From: Miquel Alzanillas Date: Thu, 24 Jul 2025 15:19:48 +0200 Subject: [PATCH 1/6] [17.0][ADD] agreement_sign_oca: New module --- agreement_sign_oca/README.rst | 107 +++++ agreement_sign_oca/__init__.py | 1 + agreement_sign_oca/__manifest__.py | 22 + agreement_sign_oca/data/data.xml | 10 + agreement_sign_oca/demo/sign_oca_template.xml | 21 + agreement_sign_oca/i18n/es.po | 85 ++++ agreement_sign_oca/models/__init__.py | 4 + agreement_sign_oca/models/agreement.py | 156 ++++++ agreement_sign_oca/models/res_company.py | 27 ++ .../models/res_config_settings.py | 23 + agreement_sign_oca/models/sign_oca_request.py | 64 +++ agreement_sign_oca/oca_dependencies.txt | 1 + agreement_sign_oca/pyproject.toml | 3 + agreement_sign_oca/readme/CONFIGURE.md | 1 + agreement_sign_oca/readme/CONTRIBUTORS.md | 2 + agreement_sign_oca/readme/DESCRIPTION.md | 4 + agreement_sign_oca/readme/USAGE.md | 6 + .../static/description/icon.png | Bin 0 -> 9455 bytes .../static/description/index.html | 452 ++++++++++++++++++ agreement_sign_oca/tests/__init__.py | 4 + .../tests/test_agreement_sign_oca.py | 324 +++++++++++++ agreement_sign_oca/views/agreement_views.xml | 39 ++ .../views/res_config_settings_view.xml | 21 + .../views/sign_oca_request_views.xml | 24 + 24 files changed, 1401 insertions(+) create mode 100644 agreement_sign_oca/README.rst create mode 100644 agreement_sign_oca/__init__.py create mode 100644 agreement_sign_oca/__manifest__.py create mode 100644 agreement_sign_oca/data/data.xml create mode 100644 agreement_sign_oca/demo/sign_oca_template.xml create mode 100644 agreement_sign_oca/i18n/es.po create mode 100644 agreement_sign_oca/models/__init__.py create mode 100644 agreement_sign_oca/models/agreement.py create mode 100644 agreement_sign_oca/models/res_company.py create mode 100644 agreement_sign_oca/models/res_config_settings.py create mode 100644 agreement_sign_oca/models/sign_oca_request.py create mode 100644 agreement_sign_oca/oca_dependencies.txt create mode 100644 agreement_sign_oca/pyproject.toml create mode 100644 agreement_sign_oca/readme/CONFIGURE.md create mode 100644 agreement_sign_oca/readme/CONTRIBUTORS.md create mode 100644 agreement_sign_oca/readme/DESCRIPTION.md create mode 100644 agreement_sign_oca/readme/USAGE.md create mode 100644 agreement_sign_oca/static/description/icon.png create mode 100644 agreement_sign_oca/static/description/index.html create mode 100644 agreement_sign_oca/tests/__init__.py create mode 100644 agreement_sign_oca/tests/test_agreement_sign_oca.py create mode 100644 agreement_sign_oca/views/agreement_views.xml create mode 100644 agreement_sign_oca/views/res_config_settings_view.xml create mode 100644 agreement_sign_oca/views/sign_oca_request_views.xml diff --git a/agreement_sign_oca/README.rst b/agreement_sign_oca/README.rst new file mode 100644 index 00000000..51c3a8c4 --- /dev/null +++ b/agreement_sign_oca/README.rst @@ -0,0 +1,107 @@ +================== +Agreement Sign Oca +================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:42b4bf1e90c19330c7df5578c2ff9840a447d24944ea3ec12efe52febe2a310d + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsign-lightgray.png?logo=github + :target: https://github.com/OCA/sign/tree/17.0/agreement_sign_oca + :alt: OCA/sign +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/sign-17-0/sign-17-0-agreement_sign_oca + :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/sign&target_branch=17.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Create signature request from any agreement. When full signed agreement +will be moved automatically to defined stage attaching a copy of the +signed document. Also a smart-button will be displayed on the +agreement's form view showing the linked Sign Requests. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +1. Go to company settings and set "Signed Agreements Stage" + +Usage +===== + +Sign Request Creation Process + +1. Navigate to an existing Agreement. +2. Click the "Request Signature" button to generate a new sign request. +3. From the new Sign Request, click the "Configure Document" smart + button to set the position for signatures and other dynamic fields. +4. Once configured, return to the request and click the "Send" button. +5. After the document is fully signed, the related Agreement will be + automatically moved to the stage defined in the company settings. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* APSL Nagarro + +Contributors +------------ + +- `APSL Nagarro `__ + + - Miquel Alzanillas + +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-miquelalzanillas| image:: https://github.com/miquelalzanillas.png?size=40px + :target: https://github.com/miquelalzanillas + :alt: miquelalzanillas + +Current `maintainer `__: + +|maintainer-miquelalzanillas| + +This module is part of the `OCA/sign `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/agreement_sign_oca/__init__.py b/agreement_sign_oca/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/agreement_sign_oca/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/agreement_sign_oca/__manifest__.py b/agreement_sign_oca/__manifest__.py new file mode 100644 index 00000000..a8e02f8b --- /dev/null +++ b/agreement_sign_oca/__manifest__.py @@ -0,0 +1,22 @@ +# Copyright 2025 APSL-Nagarro - Miquel Alzanillas +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +{ + "name": "Agreement Sign Oca", + "version": "17.0.1.0.0", + "category": "Agreement", + "website": "https://github.com/OCA/sign", + "author": "APSL Nagarro, Odoo Community Association (OCA)", + "license": "AGPL-3", + "depends": ["sign_oca", "agreement_legal"], + "data": [ + "views/agreement_views.xml", + "views/res_config_settings_view.xml", + "views/sign_oca_request_views.xml", + "data/data.xml", + ], + "demo": [ + "demo/sign_oca_template.xml", + ], + "installable": True, + "maintainers": ["miquelalzanillas"], +} diff --git a/agreement_sign_oca/data/data.xml b/agreement_sign_oca/data/data.xml new file mode 100644 index 00000000..1840b7b0 --- /dev/null +++ b/agreement_sign_oca/data/data.xml @@ -0,0 +1,10 @@ + + + + Agreement Company Signatory Person + expression + {{object.company_signed_user_id.partner_id.id}} + + diff --git a/agreement_sign_oca/demo/sign_oca_template.xml b/agreement_sign_oca/demo/sign_oca_template.xml new file mode 100644 index 00000000..8652e7cd --- /dev/null +++ b/agreement_sign_oca/demo/sign_oca_template.xml @@ -0,0 +1,21 @@ + + + Agreement + + + + + + + + 1 + 10 + 10 + 10 + 10 + + + diff --git a/agreement_sign_oca/i18n/es.po b/agreement_sign_oca/i18n/es.po new file mode 100644 index 00000000..439f6878 --- /dev/null +++ b/agreement_sign_oca/i18n/es.po @@ -0,0 +1,85 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * agreement_sign_oca +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-07-25 11:03+0000\n" +"PO-Revision-Date: 2025-07-25 11:03+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: agreement_sign_oca +#: model:ir.model,name:agreement_sign_oca.model_agreement +#: model:ir.model.fields,field_description:agreement_sign_oca.field_sign_oca_request__agreement_id +#: model_terms:ir.ui.view,arch_db:agreement_sign_oca.sign_oca_request_search_view +msgid "Agreement" +msgstr "Acuerdo" + +#. module: agreement_sign_oca +#: model:ir.model,name:agreement_sign_oca.model_res_company +msgid "Companies" +msgstr "Compañías" + +#. module: agreement_sign_oca +#: model:ir.model,name:agreement_sign_oca.model_res_config_settings +msgid "Config Settings" +msgstr "Ajustes de configuración" + +#. module: agreement_sign_oca +#: model_terms:ir.ui.view,arch_db:agreement_sign_oca.agreement_sign_view_form +msgid "Create adn sent signature request for this agreement." +msgstr "" + +#. module: agreement_sign_oca +#: model:ir.model.fields,field_description:agreement_sign_oca.field_res_company__agreement_sign_oca_template_id +#: model:ir.model.fields,field_description:agreement_sign_oca.field_res_config_settings__agreement_sign_oca_template_id +msgid "Default Agreement Sign Template" +msgstr "" + +#. module: agreement_sign_oca +#: model_terms:ir.ui.view,arch_db:agreement_sign_oca.agreement_sign_view_form +msgid "Request Signature" +msgstr "Solicitud de firma" + +#. module: agreement_sign_oca +#: model_terms:ir.ui.view,arch_db:agreement_sign_oca.res_config_settings_view_form +msgid "Sign Oca" +msgstr "" + +#. module: agreement_sign_oca +#: model:ir.model,name:agreement_sign_oca.model_sign_oca_request +msgid "Sign Request" +msgstr "Solicitud de firma" + +#. module: agreement_sign_oca +#: model:ir.model.fields,field_description:agreement_sign_oca.field_agreement__sign_request_ids +msgid "Sign Requests" +msgstr "Solicitudes de Firma" + +#. module: agreement_sign_oca +#: model:ir.model.fields,field_description:agreement_sign_oca.field_agreement__sign_request_count +msgid "Sign request count" +msgstr "Solicitudes de Firmas" + +#. module: agreement_sign_oca +#: model_terms:ir.ui.view,arch_db:agreement_sign_oca.agreement_sign_view_form +msgid "Signature Requests" +msgstr "" + +#. module: agreement_sign_oca +#: model:ir.model.fields,field_description:agreement_sign_oca.field_res_company__agreement_sign_oca_signed_stage_id +#: model:ir.model.fields,field_description:agreement_sign_oca.field_res_config_settings__agreement_sign_oca_signed_stage_id +msgid "Signed Agreements Stage" +msgstr "Etapa para acuerdos firmados" + +#. module: agreement_sign_oca +#: model:ir.model.fields,field_description:agreement_sign_oca.field_agreement__signed_contract +msgid "Signed Document" +msgstr "Documento firmado" \ No newline at end of file diff --git a/agreement_sign_oca/models/__init__.py b/agreement_sign_oca/models/__init__.py new file mode 100644 index 00000000..1c1067e3 --- /dev/null +++ b/agreement_sign_oca/models/__init__.py @@ -0,0 +1,4 @@ +from . import agreement +from . import res_company +from . import res_config_settings +from . import sign_oca_request diff --git a/agreement_sign_oca/models/agreement.py b/agreement_sign_oca/models/agreement.py new file mode 100644 index 00000000..015d0dcc --- /dev/null +++ b/agreement_sign_oca/models/agreement.py @@ -0,0 +1,156 @@ +# Copyright 2023-2024 Tecnativa - Víctor Martínez +# Copyright 2025 - APSL-Nagarro - Miquel Alzanillas +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +import base64 + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class Agreement(models.Model): + _inherit = "agreement" + + # Disable log to avoid 'Not implemented...' errors on ORM write operations + signed_contract = fields.Binary(string="Signed Document", tracking=False) + # This field is stored as a help to filter by. + sign_request_ids = fields.One2many( + comodel_name="sign.oca.request", + inverse_name="agreement_id", + string="Sign Requests", + ) + sign_request_count = fields.Integer( + string="Sign request count", + compute="_compute_sign_request_count", + compute_sudo=True, + store=True, + ) + + @api.depends("sign_request_ids") + def _compute_sign_request_count(self): + request_data = self.env["sign.oca.request"].read_group( + [("agreement_id", "in", self.ids)], + ["agreement_id"], + ["agreement_id"], + ) + mapped_data = { + x["agreement_id"][0]: x["agreement_id_count"] for x in request_data + } + for item in self: + item.sign_request_count = mapped_data.get(item.id, 0) + + def action_send_for_signature(self): + self.ensure_one() + signers_list = [] + if not self.partner_contact_id: + raise ValidationError( + _("The agreement must have an assigned contact (counterparty).") + ) + if not self.partner_contact_id.email: + raise ValidationError( + _( + """The agreement's counterparty contact + does not have an email configured.""" + ) + ) + report = self.env.ref("agreement_legal.partner_agreement_contract_document") + pdf_document, content_type = self.env["ir.actions.report"]._render_qweb_pdf( + report.report_name, self.ids + ) + customer_role = self.env.ref( + "sign_oca.sign_role_customer", raise_if_not_found=False + ) + if not customer_role: + raise ValidationError( + _( + """The 'Customer' role for the signature + was not found. Please update 'agreement' module.""" + ) + ) + company_signer_role = self.env.ref( + "agreement_sign_oca.role_agreement_signer", raise_if_not_found=False + ) + if not company_signer_role: + raise ValidationError( + _( + """The 'Agreement Company Signatory Person' role for the signature + was not found. Please update 'agreement_sign_oca' module.""" + ) + ) + if self.partner_contact_id: + signers_list.append( + ( + 0, + 0, + { + "role_id": customer_role.id, + "partner_id": self.partner_contact_id.id, + }, + ) + ) + else: + raise ValidationError( + _( + """Please set a Primary Contact in order to set the + signatory person of the counterpart in this document""" + ) + ) + + if not self.company_contact_id: + raise ValidationError( + _( + """Please set a Company Primary Contact in order to set + the signatory person of the company in this document""" + ) + ) + if not self.company_contact_id.user_ids: + raise ValidationError( + _("Please create a user for ther company signatory person") + ) + else: + signers_list.append( + ( + 0, + 0, + { + "role_id": company_signer_role.id, + "partner_id": self.company_contact_id.id, + }, + ) + ) + + sign_request_vals = { + "name": self.name, + "user_id": self.env.user.id, + "data": base64.b64encode(pdf_document), + "record_ref": f"agreement,{self.id}", + "signer_ids": signers_list, + } + sign_request = self.env["sign.oca.request"].create(sign_request_vals) + action = self.env["ir.actions.act_window"]._for_xml_id( + "sign_oca.sign_oca_request_act_window" + ) + action.update( + { + "views": [ + [self.env.ref("sign_oca.sign_oca_request_form_view").id, "form"] + ], + "res_id": sign_request.id, + } + ) + return action + + def action_view_sign_requests(self): + self.ensure_one() + result = self.env["ir.actions.act_window"]._for_xml_id( + "sign_oca.sign_oca_request_act_window" + ) + result["domain"] = [("id", "in", self.sign_request_ids.ids)] + ctx = dict(self.env.context) + ctx.update( + { + "default_agreement_id": self.id, + "search_default_agreement_id": self.id, + } + ) + result["context"] = ctx + return result diff --git a/agreement_sign_oca/models/res_company.py b/agreement_sign_oca/models/res_company.py new file mode 100644 index 00000000..63e5832b --- /dev/null +++ b/agreement_sign_oca/models/res_company.py @@ -0,0 +1,27 @@ +# Copyright 2023 Tecnativa - Víctor Martínez +# Copyright 2025 - APSL-Nagarro - Miquel Alzanillas +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = "res.company" + + def _default_signed_stage(self): + default_active_stage = self.env.ref( + "agreement_legal.agreement_stage_active", raise_if_not_found=False + ) + return default_active_stage + + agreement_sign_oca_template_id = fields.Many2one( + comodel_name="sign.oca.template", + domain="[('model_id.model', '=', 'agreement')]", + string="Default Agreement Sign Template", + ) + agreement_sign_oca_signed_stage_id = fields.Many2one( + comodel_name="agreement.stage", + default=_default_signed_stage, + string="Signed Agreements Stage", + required=True, + ) diff --git a/agreement_sign_oca/models/res_config_settings.py b/agreement_sign_oca/models/res_config_settings.py new file mode 100644 index 00000000..803760a9 --- /dev/null +++ b/agreement_sign_oca/models/res_config_settings.py @@ -0,0 +1,23 @@ +# Copyright 2023 Tecnativa - Víctor Martínez +# Copyright 2025 - APSL-Nagarro - Miquel Alzanillas +# License AGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + agreement_sign_oca_template_id = fields.Many2one( + comodel_name="sign.oca.template", + related="company_id.agreement_sign_oca_template_id", + string="Default Agreement Sign Template", + readonly=False, + ) + + agreement_sign_oca_signed_stage_id = fields.Many2one( + comodel_name="agreement.stage", + related="company_id.agreement_sign_oca_signed_stage_id", + string="Signed Agreements Stage", + readonly=False, + ) diff --git a/agreement_sign_oca/models/sign_oca_request.py b/agreement_sign_oca/models/sign_oca_request.py new file mode 100644 index 00000000..7aa42614 --- /dev/null +++ b/agreement_sign_oca/models/sign_oca_request.py @@ -0,0 +1,64 @@ +# Copyright 2023 Tecnativa - Víctor Martínez +# Copyright 2025 - APSL-Nagarro - Miquel Alzanillas +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +from odoo import api, fields, models + + +class SignOcaRequest(models.Model): + _inherit = "sign.oca.request" + + # This field is required for the inverse of maintenance.equipment. + agreement_id = fields.Many2one( + comodel_name="agreement", + compute="_compute_agreement_id", + string="Agreement", + readonly=True, + store=True, + ) + + @api.depends("record_ref") + def _compute_agreement_id(self): + for item in self.filtered( + lambda x: x.record_ref and x.record_ref._name == "agreement" + ): + item.agreement_id = item.record_ref.id + + def action_send_signed_request(self): + res = super().action_send_signed_request() + customer_role = self.env.ref( + "sign_oca.sign_role_customer", raise_if_not_found=False + ) + company_signer_role = self.env.ref( + "agreement_sign_oca.role_agreement_signer", raise_if_not_found=False + ) + + for request in self: + if request.state == "signed" and request.agreement_id and request.data: + signed_stage_id = ( + request.env.company.agreement_sign_oca_signed_stage_id.id + ) + signed_partner_on = False + signer_partner_contact_id = False + signed_company_on = False + signer_company_contact_id = False + for signer in request.signer_ids: + if signer.role_id == customer_role: + signer_partner_contact_id = signer.partner_id.id + signed_partner_on = signer.signed_on + elif signer.role_id == company_signer_role: + if signer.partner_id.user_ids: + signer_company_contact_id = signer.partner_id.user_ids[0].id + signed_company_on = signer.signed_on + vals = { + "partner_signed_date": signed_partner_on, + "partner_signed_user_id": signer_partner_contact_id, + "company_signed_date": signed_company_on, + "company_signed_user_id": signer_company_contact_id, + "stage_id": signed_stage_id, + "signed_contract": request.data, + "signed_contract_filename": request.name, + } + vals = {k: v for k, v in vals.items() if v} + if vals: + request.agreement_id.sudo().write(vals) + return res diff --git a/agreement_sign_oca/oca_dependencies.txt b/agreement_sign_oca/oca_dependencies.txt new file mode 100644 index 00000000..76b6d5a0 --- /dev/null +++ b/agreement_sign_oca/oca_dependencies.txt @@ -0,0 +1 @@ +maintenance diff --git a/agreement_sign_oca/pyproject.toml b/agreement_sign_oca/pyproject.toml new file mode 100644 index 00000000..4231d0cc --- /dev/null +++ b/agreement_sign_oca/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/agreement_sign_oca/readme/CONFIGURE.md b/agreement_sign_oca/readme/CONFIGURE.md new file mode 100644 index 00000000..3298e45a --- /dev/null +++ b/agreement_sign_oca/readme/CONFIGURE.md @@ -0,0 +1 @@ +1. Go to company settings and set "Signed Agreements Stage" diff --git a/agreement_sign_oca/readme/CONTRIBUTORS.md b/agreement_sign_oca/readme/CONTRIBUTORS.md new file mode 100644 index 00000000..60601749 --- /dev/null +++ b/agreement_sign_oca/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +- [APSL Nagarro](https://nagarro.com) + - Miquel Alzanillas diff --git a/agreement_sign_oca/readme/DESCRIPTION.md b/agreement_sign_oca/readme/DESCRIPTION.md new file mode 100644 index 00000000..a1df0efb --- /dev/null +++ b/agreement_sign_oca/readme/DESCRIPTION.md @@ -0,0 +1,4 @@ +Create signature request from any agreement. +When full signed agreement will be moved automatically to defined stage attaching a copy of the signed document. +Also a smart-button will be displayed on the agreement's form view showing the +linked Sign Requests. diff --git a/agreement_sign_oca/readme/USAGE.md b/agreement_sign_oca/readme/USAGE.md new file mode 100644 index 00000000..420d5681 --- /dev/null +++ b/agreement_sign_oca/readme/USAGE.md @@ -0,0 +1,6 @@ +Sign Request Creation Process +1. Navigate to an existing Agreement. +2. Click the "Request Signature" button to generate a new sign request. +3. From the new Sign Request, click the "Configure Document" smart button to set the position for signatures and other dynamic fields. +4. Once configured, return to the request and click the "Send" button. +5. After the document is fully signed, the related Agreement will be automatically moved to the stage defined in the company settings. diff --git a/agreement_sign_oca/static/description/icon.png b/agreement_sign_oca/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 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/agreement_sign_oca/static/description/index.html b/agreement_sign_oca/static/description/index.html new file mode 100644 index 00000000..be7e06c4 --- /dev/null +++ b/agreement_sign_oca/static/description/index.html @@ -0,0 +1,452 @@ + + + + + +Agreement Sign Oca + + + +
+

Agreement Sign Oca

+ + +

Beta License: AGPL-3 OCA/sign Translate me on Weblate Try me on Runboat

+

Create signature request from any agreement. When full signed agreement +will be moved automatically to defined stage attaching a copy of the +signed document. Also a smart-button will be displayed on the +agreement’s form view showing the linked Sign Requests.

+

Table of contents

+ +
+

Configuration

+
    +
  1. Go to company settings and set “Signed Agreements Stage”
  2. +
+
+
+

Usage

+

Sign Request Creation Process

+
    +
  1. Navigate to an existing Agreement.
  2. +
  3. Click the “Request Signature” button to generate a new sign request.
  4. +
  5. From the new Sign Request, click the “Configure Document” smart +button to set the position for signatures and other dynamic fields.
  6. +
  7. Once configured, return to the request and click the “Send” button.
  8. +
  9. After the document is fully signed, the related Agreement will be +automatically moved to the stage defined in the company settings.
  10. +
+
+
+

Bug Tracker

+

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

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • APSL Nagarro
  • +
+
+
+

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:

+

miquelalzanillas

+

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

+

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

+
+
+
+ + diff --git a/agreement_sign_oca/tests/__init__.py b/agreement_sign_oca/tests/__init__.py new file mode 100644 index 00000000..4c12411f --- /dev/null +++ b/agreement_sign_oca/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2023 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import test_agreement_sign_oca diff --git a/agreement_sign_oca/tests/test_agreement_sign_oca.py b/agreement_sign_oca/tests/test_agreement_sign_oca.py new file mode 100644 index 00000000..bccde6ea --- /dev/null +++ b/agreement_sign_oca/tests/test_agreement_sign_oca.py @@ -0,0 +1,324 @@ +# Copyright 2023-2024 Tecnativa - Víctor Martínez +# Copyright 2025 - APSL-Nagarro - Miquel Alzanillas +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import base64 + +from odoo.exceptions import UserError, ValidationError +from odoo.tests.common import new_test_user + +from odoo.addons.base.tests.common import BaseCommon + + +class TestAgreementSignOca(BaseCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.company = cls.env.company + cls.template = cls.env.ref( + "agreement_sign_oca.sign_oca_template_agreement_legal_demo" + ) + cls.model_agreement = cls.env.ref("agreement.model_agreement") + cls.user_a = new_test_user( + cls.env, + login="test-user-a", + groups="{},{},{}".format( + "agreement_legal.group_agreement_manager", + "sign_oca.sign_oca_group_user", + "base.group_partner_manager", + ), + ) + # Create a partner for the test agreement + cls.partner_a = cls.env["res.partner"].create( + {"name": "Test Partner A", "email": "partner.a@test.com"} + ) + cls.subpartner_a = cls.env["res.partner"].create( + { + "name": "SubPartner A", + "email": "sub.partner.a@test.com", + "parent_id": cls.partner_a.id, + } + ) + cls.company_signatory_person = cls.env["res.partner"].create( + { + "name": "Company Signatory", + "email": "company.signer@test.com", + "parent_id": cls.env.company.partner_id.id, + } + ) + cls.user_company = new_test_user( + cls.env, + login="test-user-company-signatory", + partner_id=cls.company_signatory_person.id, + groups="base.group_no_one", + ) + # Set a default to make it compatible with hr_maintenance + cls.agreement_model = cls.env["agreement"].with_context( + default_agreement_assign_to="other" + ) + cls.agreement_a = cls.agreement_model.with_user(cls.user_a).create( + { + "name": "Test agreement A", + "assigned_user_id": cls.user_a.id, + "partner_id": cls.partner_a.id, # Assign partner + "partner_contact_id": cls.subpartner_a.id, + "company_contact_id": cls.company_signatory_person.id, + } + ) + cls.user_b = new_test_user( + cls.env, + login="test-user-b", + groups="{},{},{}".format( + "agreement_legal.group_agreement_manager", + "sign_oca.sign_oca_group_user", + "base.group_partner_manager", + ), + ) + cls.partner_b = cls.env["res.partner"].create( + {"name": "Test Partner B", "email": "partner.b@test.com"} + ) + cls.subpartner_b = cls.env["res.partner"].create( + { + "name": "SubPartner B", + "email": "sub.partner.b@test.com", + "parent_id": cls.partner_b.id, + } + ) + cls.agreement_b = cls.agreement_model.with_user(cls.user_b).create( + { + "name": "Test agreement B", + "assigned_user_id": cls.user_b.id, + "partner_id": cls.partner_b.id, + "partner_contact_id": cls.subpartner_b.id, + "company_contact_id": cls.company_signatory_person.id, + } + ) + cls.active_stage = cls.env["agreement.stage"].create( + {"name": "Active", "stage_type": "agreement"} + ) + cls.company.agreement_sign_oca_signed_stage_id = cls.active_stage.id + + def test_action_send_for_signature(self): + """Test the action to send an agreement for signature (success case).""" + self.assertEqual(self.agreement_a.sign_request_count, 0) + action = self.agreement_a.action_send_for_signature() + self.sign_request = self.env["sign.oca.request"].browse(action["res_id"]) + self.assertEqual(self.agreement_a.sign_request_count, 1) + self.assertEqual( + self.sign_request.id, self.sign_request.agreement_id.sign_request_ids.id + ) + self.assertEqual(action["views"][0][1], "form") + sign_request = self.agreement_a.sign_request_ids + self.assertTrue(sign_request) + self.assertEqual(sign_request.name, self.agreement_a.name) + self.assertTrue(sign_request.data) + self.assertEqual(len(sign_request.signer_ids), 2) + self.assertIn(self.subpartner_a, sign_request.signer_ids.mapped("partner_id")) + self.assertIn( + self.company_signatory_person, sign_request.signer_ids.mapped("partner_id") + ) + + def test_action_send_for_signature_no_partner(self): + """Test that the action raises an error if the agreement has no partner.""" + agreement_no_partner = self.agreement_model.create( + { + "name": "Test agreement no partner", + } + ) + with self.assertRaises( + UserError, msg="The agreement must have an assigned contact (counterparty)." + ): + agreement_no_partner.action_send_for_signature() + + def test_action_send_for_signature_no_partner_email(self): + """Test that the action raises an error if the partner has no email.""" + self.agreement_a.partner_contact_id.email = False + with self.assertRaises( + UserError, + msg="""The agreement's counterparty contact + does not have an email configured.""", + ): + self.agreement_a.action_send_for_signature() + # Restore email for other tests + self.agreement_a.partner_contact_id.email = "partner.a@test.com" + # Test missing contact + self.agreement_a.partner_contact_id = False + with self.assertRaises(ValidationError): + self.agreement_a.action_send_for_signature() + + def test_action_send_signed_request(self): + """Test that when a request is signed, the agreement is updated.""" + dummy_pdf_content = base64.b64encode(b"This is a signed PDF.") + customer_role = self.env.ref("sign_oca.sign_role_customer") + company_signer_role = self.env.ref("agreement_sign_oca.role_agreement_signer") + sign_request = self.env["sign.oca.request"].create( + { + "data": dummy_pdf_content, + "name": "Signed Agreement A.pdf", + "record_ref": f"agreement,{self.agreement_a.id}", + "state": "sent", + "signer_ids": [ + ( + 0, + 0, + { + "role_id": customer_role.id, + "partner_id": self.subpartner_a.id, + }, + ), + ( + 0, + 0, + { + "role_id": company_signer_role.id, + "partner_id": self.company_signatory_person.id, + }, + ), + ], + } + ) + # Simulate signing + for signer in sign_request.signer_ids: + signer.signed_on = "2023-01-01 12:00:00" + sign_request.state = "signed" + self.assertNotEqual(self.agreement_a.stage_id, self.active_stage) + sign_request.action_send_signed_request() + self.assertEqual(self.agreement_a.stage_id, self.active_stage) + self.assertEqual(self.agreement_a.signed_contract, dummy_pdf_content) + self.assertEqual(self.agreement_a.signed_contract_filename, sign_request.name) + self.assertTrue(self.agreement_a.partner_signed_date) + self.assertEqual(self.agreement_a.partner_signed_user_id, self.subpartner_a) + self.assertTrue(self.agreement_a.company_signed_date) + self.assertEqual(self.agreement_a.company_signed_user_id, self.user_company) + + def test_action_send_signed_request_state_not_signed(self): + """Test that agreement is not updated if request is not signed.""" + sign_request = self.env["sign.oca.request"].create( + { + "data": base64.b64encode(b"PDF content"), + "name": "Test.pdf", + "record_ref": f"agreement,{self.agreement_a.id}", + "state": "sent", # Not signed + } + ) + original_stage = self.agreement_a.stage_id + sign_request.action_send_signed_request() + self.assertEqual(self.agreement_a.stage_id, original_stage) + self.assertFalse(self.agreement_a.signed_contract) + + def test_action_send_signed_request_no_agreement(self): + """Test that nothing happens if request has no agreement.""" + sign_request = self.env["sign.oca.request"].create( + { + "data": base64.b64encode(b"PDF content"), + "name": "Test.pdf", + "record_ref": False, # No agreement + "state": "signed", + } + ) + # This should not raise an error and not modify any agreement + sign_request.action_send_signed_request() + # Check that agreement_a is untouched + self.assertFalse(self.agreement_a.signed_contract) + + def test_action_send_signed_request_no_data(self): + """Test that agreement is not updated if request has no data.""" + sign_request = self.env["sign.oca.request"].create( + { + "data": False, # No data + "name": "Test.pdf", + "record_ref": f"agreement,{self.agreement_a.id}", + "state": "signed", + } + ) + original_stage = self.agreement_a.stage_id + sign_request.action_send_signed_request() + self.assertEqual(self.agreement_a.stage_id, original_stage) + self.assertFalse(self.agreement_a.signed_contract) + + def test_action_send_signed_request_company_signer_no_user(self): + """Test when company signer partner has no user.""" + company_signer_no_user = self.env["res.partner"].create( + { + "name": "Company Signer No User", + "email": "signer.no.user@test.com", + "parent_id": self.env.company.partner_id.id, + } + ) + self.assertFalse(company_signer_no_user.user_ids) + dummy_pdf_content = base64.b64encode(b"This is a signed PDF.") + customer_role = self.env.ref("sign_oca.sign_role_customer") + company_signer_role = self.env.ref("agreement_sign_oca.role_agreement_signer") + sign_request = self.env["sign.oca.request"].create( + { + "data": dummy_pdf_content, + "name": "Signed Agreement A.pdf", + "record_ref": f"agreement,{self.agreement_a.id}", + "state": "sent", + "signer_ids": [ + ( + 0, + 0, + { + "role_id": customer_role.id, + "partner_id": self.subpartner_a.id, + }, + ), + ( + 0, + 0, + { + "role_id": company_signer_role.id, + "partner_id": company_signer_no_user.id, + }, + ), + ], + } + ) + # Simulate signing + for signer in sign_request.signer_ids: + signer.signed_on = "2023-01-01 12:00:00" + sign_request.state = "signed" + sign_request.action_send_signed_request() + self.assertEqual(self.agreement_a.stage_id, self.active_stage) + self.assertEqual(self.agreement_a.signed_contract, dummy_pdf_content) + self.assertTrue(self.agreement_a.partner_signed_date) + self.assertEqual(self.agreement_a.partner_signed_user_id, self.subpartner_a) + self.assertTrue(self.agreement_a.company_signed_date) + # company_signed_user_id should not be set + self.assertFalse(self.agreement_a.company_signed_user_id) + + def test_action_send_signed_request_only_customer_signer(self): + """Test when there is only a customer signer.""" + dummy_pdf_content = base64.b64encode(b"This is a signed PDF.") + customer_role = self.env.ref("sign_oca.sign_role_customer") + sign_request = self.env["sign.oca.request"].create( + { + "data": dummy_pdf_content, + "name": "Signed Agreement B.pdf", + "record_ref": f"agreement,{self.agreement_b.id}", + "state": "sent", + "signer_ids": [ + ( + 0, + 0, + { + "role_id": customer_role.id, + "partner_id": self.subpartner_b.id, + }, + ), + ], + } + ) + # Simulate signing + for signer in sign_request.signer_ids: + signer.signed_on = "2023-01-01 12:00:00" + sign_request.state = "signed" + sign_request.action_send_signed_request() + self.assertEqual(self.agreement_b.stage_id, self.active_stage) + self.assertEqual(self.agreement_b.signed_contract, dummy_pdf_content) + self.assertTrue(self.agreement_b.partner_signed_date) + self.assertEqual(self.agreement_b.partner_signed_user_id, self.subpartner_b) + # Company fields should not be set + self.assertFalse(self.agreement_b.company_signed_date) + self.assertFalse(self.agreement_b.company_signed_user_id) diff --git a/agreement_sign_oca/views/agreement_views.xml b/agreement_sign_oca/views/agreement_views.xml new file mode 100644 index 00000000..89abe2d9 --- /dev/null +++ b/agreement_sign_oca/views/agreement_views.xml @@ -0,0 +1,39 @@ + + + + agreement.legal.form + agreement + + + + + + + + + + + diff --git a/agreement_sign_oca/views/res_config_settings_view.xml b/agreement_sign_oca/views/res_config_settings_view.xml new file mode 100644 index 00000000..1b2d5635 --- /dev/null +++ b/agreement_sign_oca/views/res_config_settings_view.xml @@ -0,0 +1,21 @@ + + + + + res.config.settings + + + + + + + + + + + + + + + diff --git a/agreement_sign_oca/views/sign_oca_request_views.xml b/agreement_sign_oca/views/sign_oca_request_views.xml new file mode 100644 index 00000000..070c14f5 --- /dev/null +++ b/agreement_sign_oca/views/sign_oca_request_views.xml @@ -0,0 +1,24 @@ + + + + sign.oca.request.search + sign.oca.request + + + + + + + + + + + From 0f1df5daec4e76b6b522f3e15fc3092c74ec9a99 Mon Sep 17 00:00:00 2001 From: oca-ci Date: Sat, 16 Aug 2025 09:53:11 +0000 Subject: [PATCH 2/6] [UPD] Update agreement_sign_oca.pot --- .../i18n/agreement_sign_oca.pot | 142 ++++++++++++++++++ agreement_sign_oca/i18n/es.po | 62 +++++++- 2 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 agreement_sign_oca/i18n/agreement_sign_oca.pot diff --git a/agreement_sign_oca/i18n/agreement_sign_oca.pot b/agreement_sign_oca/i18n/agreement_sign_oca.pot new file mode 100644 index 00000000..8fc3fb6e --- /dev/null +++ b/agreement_sign_oca/i18n/agreement_sign_oca.pot @@ -0,0 +1,142 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * agreement_sign_oca +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.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: agreement_sign_oca +#: model:ir.model,name:agreement_sign_oca.model_agreement +#: model:ir.model.fields,field_description:agreement_sign_oca.field_sign_oca_request__agreement_id +#: model_terms:ir.ui.view,arch_db:agreement_sign_oca.sign_oca_request_search_view +msgid "Agreement" +msgstr "" + +#. module: agreement_sign_oca +#: model:ir.model,name:agreement_sign_oca.model_res_company +msgid "Companies" +msgstr "" + +#. module: agreement_sign_oca +#: model:ir.model,name:agreement_sign_oca.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: agreement_sign_oca +#: model_terms:ir.ui.view,arch_db:agreement_sign_oca.agreement_sign_view_form +msgid "Create adn sent signature request for this agreement." +msgstr "" + +#. module: agreement_sign_oca +#: model:ir.model.fields,field_description:agreement_sign_oca.field_res_company__agreement_sign_oca_template_id +#: model:ir.model.fields,field_description:agreement_sign_oca.field_res_config_settings__agreement_sign_oca_template_id +msgid "Default Agreement Sign Template" +msgstr "" + +#. module: agreement_sign_oca +#. odoo-python +#: code:addons/agreement_sign_oca/models/agreement.py:0 +#, python-format +msgid "Please create a user for ther company signatory person" +msgstr "" + +#. module: agreement_sign_oca +#. odoo-python +#: code:addons/agreement_sign_oca/models/agreement.py:0 +#, python-format +msgid "" +"Please set a Company Primary Contact in order to set\n" +" the signatory person of the company in this document" +msgstr "" + +#. module: agreement_sign_oca +#. odoo-python +#: code:addons/agreement_sign_oca/models/agreement.py:0 +#, python-format +msgid "" +"Please set a Primary Contact in order to set the\n" +" signatory person of the counterpart in this document" +msgstr "" + +#. module: agreement_sign_oca +#: model_terms:ir.ui.view,arch_db:agreement_sign_oca.agreement_sign_view_form +msgid "Request Signature" +msgstr "" + +#. module: agreement_sign_oca +#: model_terms:ir.ui.view,arch_db:agreement_sign_oca.res_config_settings_view_form +msgid "Sign Oca" +msgstr "" + +#. module: agreement_sign_oca +#: model:ir.model,name:agreement_sign_oca.model_sign_oca_request +msgid "Sign Request" +msgstr "" + +#. module: agreement_sign_oca +#: model:ir.model.fields,field_description:agreement_sign_oca.field_agreement__sign_request_ids +msgid "Sign Requests" +msgstr "" + +#. module: agreement_sign_oca +#: model:ir.model.fields,field_description:agreement_sign_oca.field_agreement__sign_request_count +msgid "Sign request count" +msgstr "" + +#. module: agreement_sign_oca +#: model_terms:ir.ui.view,arch_db:agreement_sign_oca.agreement_sign_view_form +msgid "Signature Requests" +msgstr "" + +#. module: agreement_sign_oca +#: model:ir.model.fields,field_description:agreement_sign_oca.field_res_company__agreement_sign_oca_signed_stage_id +#: model:ir.model.fields,field_description:agreement_sign_oca.field_res_config_settings__agreement_sign_oca_signed_stage_id +msgid "Signed Agreements Stage" +msgstr "" + +#. module: agreement_sign_oca +#: model:ir.model.fields,field_description:agreement_sign_oca.field_agreement__signed_contract +msgid "Signed Document" +msgstr "" + +#. module: agreement_sign_oca +#. odoo-python +#: code:addons/agreement_sign_oca/models/agreement.py:0 +#, python-format +msgid "" +"The 'Agreement Company Signatory Person' role for the signature\n" +" was not found. Please update 'agreement_sign_oca' module." +msgstr "" + +#. module: agreement_sign_oca +#. odoo-python +#: code:addons/agreement_sign_oca/models/agreement.py:0 +#, python-format +msgid "" +"The 'Customer' role for the signature\n" +" was not found. Please update 'agreement' module." +msgstr "" + +#. module: agreement_sign_oca +#. odoo-python +#: code:addons/agreement_sign_oca/models/agreement.py:0 +#, python-format +msgid "The agreement must have an assigned contact (counterparty)." +msgstr "" + +#. module: agreement_sign_oca +#. odoo-python +#: code:addons/agreement_sign_oca/models/agreement.py:0 +#, python-format +msgid "" +"The agreement's counterparty contact\n" +" does not have an email configured." +msgstr "" diff --git a/agreement_sign_oca/i18n/es.po b/agreement_sign_oca/i18n/es.po index 439f6878..d0b4b859 100644 --- a/agreement_sign_oca/i18n/es.po +++ b/agreement_sign_oca/i18n/es.po @@ -10,6 +10,7 @@ msgstr "" "PO-Revision-Date: 2025-07-25 11:03+0000\n" "Last-Translator: \n" "Language-Team: \n" +"Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" @@ -43,6 +44,31 @@ msgstr "" msgid "Default Agreement Sign Template" msgstr "" +#. module: agreement_sign_oca +#. odoo-python +#: code:addons/agreement_sign_oca/models/agreement.py:0 +#, python-format +msgid "Please create a user for ther company signatory person" +msgstr "" + +#. module: agreement_sign_oca +#. odoo-python +#: code:addons/agreement_sign_oca/models/agreement.py:0 +#, python-format +msgid "" +"Please set a Company Primary Contact in order to set\n" +" the signatory person of the company in this document" +msgstr "" + +#. module: agreement_sign_oca +#. odoo-python +#: code:addons/agreement_sign_oca/models/agreement.py:0 +#, python-format +msgid "" +"Please set a Primary Contact in order to set the\n" +" signatory person of the counterpart in this document" +msgstr "" + #. module: agreement_sign_oca #: model_terms:ir.ui.view,arch_db:agreement_sign_oca.agreement_sign_view_form msgid "Request Signature" @@ -82,4 +108,38 @@ msgstr "Etapa para acuerdos firmados" #. module: agreement_sign_oca #: model:ir.model.fields,field_description:agreement_sign_oca.field_agreement__signed_contract msgid "Signed Document" -msgstr "Documento firmado" \ No newline at end of file +msgstr "Documento firmado" + +#. module: agreement_sign_oca +#. odoo-python +#: code:addons/agreement_sign_oca/models/agreement.py:0 +#, python-format +msgid "" +"The 'Agreement Company Signatory Person' role for the signature\n" +" was not found. Please update 'agreement_sign_oca' module." +msgstr "" + +#. module: agreement_sign_oca +#. odoo-python +#: code:addons/agreement_sign_oca/models/agreement.py:0 +#, python-format +msgid "" +"The 'Customer' role for the signature\n" +" was not found. Please update 'agreement' module." +msgstr "" + +#. module: agreement_sign_oca +#. odoo-python +#: code:addons/agreement_sign_oca/models/agreement.py:0 +#, python-format +msgid "The agreement must have an assigned contact (counterparty)." +msgstr "" + +#. module: agreement_sign_oca +#. odoo-python +#: code:addons/agreement_sign_oca/models/agreement.py:0 +#, python-format +msgid "" +"The agreement's counterparty contact\n" +" does not have an email configured." +msgstr "" From ae51eb209a7ef5ce1b0f285bddba0ff7764de482 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Sat, 16 Aug 2025 09:55:22 +0000 Subject: [PATCH 3/6] [BOT] post-merge updates --- agreement_sign_oca/README.rst | 8 +++-- .../static/description/index.html | 30 +++++++++++-------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/agreement_sign_oca/README.rst b/agreement_sign_oca/README.rst index 51c3a8c4..888a406b 100644 --- a/agreement_sign_oca/README.rst +++ b/agreement_sign_oca/README.rst @@ -1,3 +1,7 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + ================== Agreement Sign Oca ================== @@ -7,13 +11,13 @@ Agreement Sign Oca !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:42b4bf1e90c19330c7df5578c2ff9840a447d24944ea3ec12efe52febe2a310d + !! source digest: sha256:cc0a2fd9720e3faff92770a668bb9f508142c75f4a03369df2b0fe094a81ee00 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status :alt: Beta -.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png +.. |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%2Fsign-lightgray.png?logo=github diff --git a/agreement_sign_oca/static/description/index.html b/agreement_sign_oca/static/description/index.html index be7e06c4..3b3ead66 100644 --- a/agreement_sign_oca/static/description/index.html +++ b/agreement_sign_oca/static/description/index.html @@ -3,7 +3,7 @@ -Agreement Sign Oca +README.rst -
-

Agreement Sign Oca

+
+ + +Odoo Community Association + +
+

Agreement Sign Oca

-

Beta License: AGPL-3 OCA/sign Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/sign Translate me on Weblate Try me on Runboat

Create signature request from any agreement. When full signed agreement will be moved automatically to defined stage attaching a copy of the signed document. Also a smart-button will be displayed on the @@ -389,13 +394,13 @@

Agreement Sign Oca

-

Configuration

+

Configuration

  1. Go to company settings and set “Signed Agreements Stage”
-

Usage

+

Usage

Sign Request Creation Process

  1. Navigate to an existing Agreement.
  2. @@ -408,7 +413,7 @@

    Usage

-

Bug Tracker

+

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 @@ -416,15 +421,15 @@

Bug Tracker

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

-

Credits

+

Credits

-

Authors

+

Authors

  • APSL Nagarro
-

Contributors

+

Contributors

-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association @@ -448,5 +453,6 @@

Maintainers

+
From 70de502e48536f56ba99a1eb8e102749e2237c49 Mon Sep 17 00:00:00 2001 From: mymage Date: Mon, 18 Aug 2025 09:41:40 +0000 Subject: [PATCH 4/6] Added translation using Weblate (Italian) --- agreement_sign_oca/i18n/it.po | 143 ++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 agreement_sign_oca/i18n/it.po diff --git a/agreement_sign_oca/i18n/it.po b/agreement_sign_oca/i18n/it.po new file mode 100644 index 00000000..1ef290bd --- /dev/null +++ b/agreement_sign_oca/i18n/it.po @@ -0,0 +1,143 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * agreement_sign_oca +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\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" + +#. module: agreement_sign_oca +#: model:ir.model,name:agreement_sign_oca.model_agreement +#: model:ir.model.fields,field_description:agreement_sign_oca.field_sign_oca_request__agreement_id +#: model_terms:ir.ui.view,arch_db:agreement_sign_oca.sign_oca_request_search_view +msgid "Agreement" +msgstr "" + +#. module: agreement_sign_oca +#: model:ir.model,name:agreement_sign_oca.model_res_company +msgid "Companies" +msgstr "" + +#. module: agreement_sign_oca +#: model:ir.model,name:agreement_sign_oca.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: agreement_sign_oca +#: model_terms:ir.ui.view,arch_db:agreement_sign_oca.agreement_sign_view_form +msgid "Create adn sent signature request for this agreement." +msgstr "" + +#. module: agreement_sign_oca +#: model:ir.model.fields,field_description:agreement_sign_oca.field_res_company__agreement_sign_oca_template_id +#: model:ir.model.fields,field_description:agreement_sign_oca.field_res_config_settings__agreement_sign_oca_template_id +msgid "Default Agreement Sign Template" +msgstr "" + +#. module: agreement_sign_oca +#. odoo-python +#: code:addons/agreement_sign_oca/models/agreement.py:0 +#, python-format +msgid "Please create a user for ther company signatory person" +msgstr "" + +#. module: agreement_sign_oca +#. odoo-python +#: code:addons/agreement_sign_oca/models/agreement.py:0 +#, python-format +msgid "" +"Please set a Company Primary Contact in order to set\n" +" the signatory person of the company in this document" +msgstr "" + +#. module: agreement_sign_oca +#. odoo-python +#: code:addons/agreement_sign_oca/models/agreement.py:0 +#, python-format +msgid "" +"Please set a Primary Contact in order to set the\n" +" signatory person of the counterpart in this document" +msgstr "" + +#. module: agreement_sign_oca +#: model_terms:ir.ui.view,arch_db:agreement_sign_oca.agreement_sign_view_form +msgid "Request Signature" +msgstr "" + +#. module: agreement_sign_oca +#: model_terms:ir.ui.view,arch_db:agreement_sign_oca.res_config_settings_view_form +msgid "Sign Oca" +msgstr "" + +#. module: agreement_sign_oca +#: model:ir.model,name:agreement_sign_oca.model_sign_oca_request +msgid "Sign Request" +msgstr "" + +#. module: agreement_sign_oca +#: model:ir.model.fields,field_description:agreement_sign_oca.field_agreement__sign_request_ids +msgid "Sign Requests" +msgstr "" + +#. module: agreement_sign_oca +#: model:ir.model.fields,field_description:agreement_sign_oca.field_agreement__sign_request_count +msgid "Sign request count" +msgstr "" + +#. module: agreement_sign_oca +#: model_terms:ir.ui.view,arch_db:agreement_sign_oca.agreement_sign_view_form +msgid "Signature Requests" +msgstr "" + +#. module: agreement_sign_oca +#: model:ir.model.fields,field_description:agreement_sign_oca.field_res_company__agreement_sign_oca_signed_stage_id +#: model:ir.model.fields,field_description:agreement_sign_oca.field_res_config_settings__agreement_sign_oca_signed_stage_id +msgid "Signed Agreements Stage" +msgstr "" + +#. module: agreement_sign_oca +#: model:ir.model.fields,field_description:agreement_sign_oca.field_agreement__signed_contract +msgid "Signed Document" +msgstr "" + +#. module: agreement_sign_oca +#. odoo-python +#: code:addons/agreement_sign_oca/models/agreement.py:0 +#, python-format +msgid "" +"The 'Agreement Company Signatory Person' role for the signature\n" +" was not found. Please update 'agreement_sign_oca' module." +msgstr "" + +#. module: agreement_sign_oca +#. odoo-python +#: code:addons/agreement_sign_oca/models/agreement.py:0 +#, python-format +msgid "" +"The 'Customer' role for the signature\n" +" was not found. Please update 'agreement' module." +msgstr "" + +#. module: agreement_sign_oca +#. odoo-python +#: code:addons/agreement_sign_oca/models/agreement.py:0 +#, python-format +msgid "The agreement must have an assigned contact (counterparty)." +msgstr "" + +#. module: agreement_sign_oca +#. odoo-python +#: code:addons/agreement_sign_oca/models/agreement.py:0 +#, python-format +msgid "" +"The agreement's counterparty contact\n" +" does not have an email configured." +msgstr "" From 2685ec35efa1a69ae497969e921fc9b19d57d9e2 Mon Sep 17 00:00:00 2001 From: Bhavesh Heliconia Date: Mon, 18 Aug 2025 16:46:38 +0530 Subject: [PATCH 5/6] [IMP] agreement_sign_oca: pre-commit auto fixes --- agreement_sign_oca/views/agreement_views.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agreement_sign_oca/views/agreement_views.xml b/agreement_sign_oca/views/agreement_views.xml index 89abe2d9..b7e736f4 100644 --- a/agreement_sign_oca/views/agreement_views.xml +++ b/agreement_sign_oca/views/agreement_views.xml @@ -19,7 +19,7 @@ /> -