Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[17.0][MIG] agreement_legal : Migration to 17.0 #40

Open
wants to merge 76 commits into
base: 17.0
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
6912027
[ADD] agreement_legal migration
ygol Mar 29, 2019
1214f4c
[FIX] flake8
ygol Mar 29, 2019
744b100
[FIX] flake8(2)
ygol Mar 29, 2019
30079bd
[FIX] agreement_legal: remove account dependency
ygol Apr 2, 2019
c4ecf1e
[FIX] ooops
ygol Apr 2, 2019
83e501a
[FIX] handling code field + partner_id
ygol Apr 2, 2019
da3ed29
[UPD] remove field reference and use field code
ygol Apr 2, 2019
b900079
[ADD] custom dynamic parties
ygol Apr 11, 2019
3ebe13e
[FIX] flake8
ygol Apr 12, 2019
89a168b
[FIX] pylint
ygol Apr 12, 2019
604dba1
[IMP] add default html value for parties
ygol Apr 16, 2019
51f8e5f
[UPD] flake8
ygol Apr 16, 2019
a9c2bab
[FIX][12.0] Menu Icon.
murtuzasaleh Apr 18, 2019
aa3ada6
[FIX] Icon in settings
max3903 Apr 30, 2019
7a7cb49
[FIX] agreement_legal: module name
max3903 May 1, 2019
2645da5
[FIX] Issue #323 + Cleanup
max3903 May 7, 2019
6245150
Update agreement_section.py
max3903 May 7, 2019
edd7185
[FIX] Duplicate label
max3903 May 8, 2019
787e546
[FIX] Clauses are copied from the sections
max3903 May 8, 2019
e579763
[FIX] agreement_legal: Copy
max3903 May 13, 2019
134586b
[FIX] View priority
max3903 May 15, 2019
ce460fe
[FIX] Issue #331
max3903 May 17, 2019
d72563d
[IMP] agreement_legal: add readonly group
max3903 May 20, 2019
98d1cec
[UPD] update agreement demo data with compulsory fields
ygol May 29, 2019
e4664dc
[UPD] move data to demo keyword
ygol May 29, 2019
0eb0b1c
[UPD] PR review's remarks
ygol May 29, 2019
c1bba82
Update agreement_legal/__manifest__.py
ygol May 29, 2019
10de94f
[UPD] update agreement demo data with compulsory fields
ygol May 29, 2019
b2aec98
[FIX] Issue #342
ygol Jun 5, 2019
3bad69b
[IMP] agreement_serviceprofile: add stage
brian10048 Jul 18, 2019
6dbd666
Update agreement_legal/models/agreement.py
ygol Sep 11, 2019
1994ee8
[IMP] move some notions from agreement_legal to agreement
gurneyalex Sep 27, 2019
5aa28fa
[IMP] New icon for agreement modules (svg file in agreement_legal)
marcelsavegnago Feb 15, 2020
b9f4585
[FIX] agreement_legal: Change company_contact_id domain
Tardo Feb 17, 2020
e7b1152
[FIX] agreement_legal: Change partner_id domain in agreement view
Tardo Feb 18, 2020
5ac0aa1
[FIX] company_partner_id and partner_id have the same label: Partner.
MiquelRForgeFlow Feb 18, 2020
c9a87f7
[IMP] agreement,agreement_legal: support activities
Mar 6, 2020
918be3f
[FIX] agreement_legal
murtuzasaleh May 5, 2020
92ad5f2
[12.0][FIX] agreement_legal: Error when mass action on list view
newtratip Feb 18, 2021
3342980
[14.0][MIG] agreement_legal (Version 12.0 to 14.0)
patrickrwilson May 4, 2021
da2698a
[FIX] Removed old demo file
patrickrwilson Jul 2, 2021
4705621
[14.0][ENH] agreement_legal
newtratip Jul 16, 2021
0a8ef52
[FIX] agreement_legal: Change create_new_version() function in agreem…
victoralmau Oct 8, 2021
a326d18
[FIX] agreement_legal: Change image field name in view.
victoralmau Oct 15, 2021
9b4961d
[IMP] agreement_legal: Replace the created_by + date_created fields.
victoralmau Oct 19, 2021
9c5ed8f
[IMP] agreement_legal: Add agreement list button
olgamarcocb Nov 17, 2021
bafd805
[IMP] agreement_legal: Add res_partner security group
olgamarcocb Oct 14, 2021
11b250c
Added translation using Weblate (Spanish (Argentina))
ibuioli Feb 6, 2022
d20a13f
Translated using Weblate (Spanish (Argentina))
ibuioli Feb 6, 2022
9fae612
[MIG] agreement_legal: Migration to 15.0
olgamarcocb May 11, 2022
40d4e5e
[IMP] agreement_legal: black, isort, prettier
etobella Aug 31, 2022
81ecd14
[MIG] agreement_legal: Finish migration to 15.0
etobella Aug 31, 2022
593a52b
[UPD] Update agreement_legal.pot
Oct 1, 2022
4375378
[UPD] README.rst
OCA-git-bot Oct 1, 2022
6e95108
[IMP] agreement_legal: pre-commit stuff
urvisha-serpentcs Jan 13, 2023
735fca0
[MIG] agreement_legal: Migration to 16.0
urvisha-serpentcs Jan 13, 2023
31a89fe
[IMP] fix pre-commit issue
urvisha-serpentcs Jan 13, 2023
20180a0
[UPD] Update agreement_legal.pot
Jun 8, 2023
e3769cd
[UPD] README.rst
OCA-git-bot Jun 8, 2023
88c17e6
Update translation files
weblate Jun 8, 2023
44eac8a
[IMP] agreement_legal: Define warning days cron and filter
olgamarcocb Oct 15, 2021
168d0c7
[FIX] agreement_legal: Fix alert, improve test coverage
etobella Dec 13, 2021
f7b015c
[IMP] agreement_legal: create in batch
len-foss May 27, 2023
cd3ee82
[REF] agreement_legal: split logic and view code functions
len-foss May 27, 2023
4b05ae4
[MIG] agreement_legal: cleanup
aktiv-heli-kantawala Jun 5, 2023
c1afb01
[UPD] Update agreement_legal.pot
Jun 26, 2023
811c7ee
agreement_legal 16.0.2.0.0
OCA-git-bot Jun 26, 2023
873ed55
Update translation files
weblate Jun 26, 2023
8ff4c4b
Translated using Weblate (Spanish)
Ivorra78 Jul 20, 2023
cab9709
[UPD] README.rst
OCA-git-bot Sep 3, 2023
18ec467
Update translation files
weblate Oct 9, 2023
b7bee49
Translated using Weblate (French)
gregory-moka Nov 8, 2023
2d7f978
Added translation using Weblate (Italian)
mymage May 2, 2024
160ce4c
Translated using Weblate (Portuguese (Brazil))
SottomaiorMacedoTec Jul 6, 2024
c7fb879
[IMP] agreement_legal: pre-commit auto fixes
Sep 20, 2024
4a99e92
[17.0][MIG] agreement_legal: Migration to 17.0
Sep 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions agreement_legal/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
================
Agreements Legal
================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:0295ff399e3360ceab87b6befafe455990f071e7cca04046a03e5529f21757c2
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |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%2Fagreement-lightgray.png?logo=github
:target: https://github.com/OCA/agreement/tree/17.0/agreement_legal
:alt: OCA/agreement
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/agreement-17-0/agreement-17-0-agreement_legal
: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/agreement&target_branch=17.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

This module allows you to manage agreements, letter of intent and
contract content. The module is meant to be used by the legal team of a
company and to allow them to define sections, clauses and templates with
their respective content that can be dynamic.

Based on the template, an agreement can be created and the pdf document
generated.

The agreement would go through a workflow to finally become a contract
with the customer signature.

**Table of contents**

.. contents::
:local:

Configuration
=============

To configure this module:

- Go to Agreement > Configuration > Templates
- Create a new template with sections and clauses and their respective
content
- Go to Agreement > Configuration > Stages
- Create and reorder stages to match your process

Usage
=====

To use this module:

- Go to Agreement > Agreements
- Create a new agreement
- Select a template
- Follow the process to get the required approval
- Send the invitation to the customer to review and sign the agreement

Known issues / Roadmap
======================

- Split the module to remove the dependencies on sale and account and
provide the same feature in extra modules (agreement_sale,
agreement_account, agreement_purchase)

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/agreement/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 <https://github.com/OCA/agreement/issues/new?body=module:%20agreement_legal%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

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

Credits
=======

Authors
-------

* Pavlov Media
* Open Source Integrators
* Yves Goldberg (Ygol Internetwork)

Contributors
------------

- Patrick Wilson <pwilson@pavlovmedia.com>
- Bhavesh Odedra <bodedra@opensourceintegrators.com>
- Wolfgang Hall <whall@opensourceintegrators.com>
- Maxime Chambreuil <mchambreuil@opensourceintegrators.com>
- Sandip Mangukiya <smangukiya@opensourceintegrators.com>
- Yves Goldberg <yves@ygol.com>
- Tharathip Chaweewongphan <tharathipc@ecosoft.co.th>
- Italo LOPES <italo.lopes@camptocamp.com>

Other credits
-------------

The development of this module has been financially supported by:

- Pavlov Media
- Open Source Integrators
- Yves Goldberg

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-max3903| image:: https://github.com/max3903.png?size=40px
:target: https://github.com/max3903
:alt: max3903
.. |maintainer-ygol| image:: https://github.com/ygol.png?size=40px
:target: https://github.com/ygol
:alt: ygol

Current `maintainers <https://odoo-community.org/page/maintainer-role>`__:

|maintainer-max3903| |maintainer-ygol|

This module is part of the `OCA/agreement <https://github.com/OCA/agreement/tree/17.0/agreement_legal>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
5 changes: 5 additions & 0 deletions agreement_legal/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Copyright (C) 2018 - TODAY, Pavlov Media
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from . import models
from . import wizards
47 changes: 47 additions & 0 deletions agreement_legal/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright (C) 2018 - TODAY, Pavlov Media
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

{
"name": "Agreements Legal",
"summary": "Manage Agreements, LOI and Contracts",
"author": "Pavlov Media, "
"Open Source Integrators, "
"Yves Goldberg (Ygol Internetwork), "
"Odoo Community Association (OCA)",
"website": "https://github.com/OCA/agreement",
"category": "Partner",
"license": "AGPL-3",
"version": "17.0.2.0.0",
"depends": ["contacts", "agreement", "product", "web"],
"data": [
"data/cron.xml",
"data/ir_sequence.xml",
"data/agreement_stage.xml",
"data/agreement_type.xml",
"security/res_groups.xml",
"security/ir.model.access.csv",
"report/agreement.xml",
"views/res_config_settings.xml",
"views/agreement_appendix.xml",
"views/agreement_clause.xml",
"views/agreement_recital.xml",
"views/agreement_section.xml",
"views/agreement_stages.xml",
"views/agreement_type.xml",
"views/agreement_subtype.xml",
"views/res_partner.xml",
"views/agreement.xml",
"views/menu.xml",
"wizards/create_agreement_wizard.xml",
],
"demo": ["demo/demo.xml"],
"assets": {
"web.assets_backend": [
"agreement_legal/static/src/js/**/*",
"agreement_legal/static/src/xml/**/*",
],
},
"application": True,
"development_status": "Beta",
"maintainers": ["max3903", "ygol"],
}
64 changes: 64 additions & 0 deletions agreement_legal/data/agreement_stage.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<odoo>

<record id="agreement_stage_new" model="agreement.stage">
<field name="name">New</field>
<field name="sequence">10</field>
<field name="stage_type">agreement</field>
</record>

<record id="agreement_stage_draft" model="agreement.stage">
<field name="name">Draft</field>
<field name="sequence">20</field>
<field name="stage_type">agreement</field>
</record>

<record id="agreement_stage_reviewed" model="agreement.stage">
<field name="name">Reviewed</field>
<field name="sequence">30</field>
<field name="stage_type">agreement</field>
</record>

<record id="agreement_stage_negotiation" model="agreement.stage">
<field name="name">Negotiation</field>
<field name="sequence">40</field>
<field name="stage_type">agreement</field>
</record>

<record id="agreement_stage_out" model="agreement.stage">
<field name="name">Out for Customer Signature</field>
<field name="sequence">50</field>
<field name="stage_type">agreement</field>
</record>

<record id="agreement_stage_internal" model="agreement.stage">
<field name="name">Waiting Internal Signature</field>
<field name="sequence">60</field>
<field name="stage_type">agreement</field>
</record>

<record id="agreement_stage_active" model="agreement.stage">
<field name="name">Active</field>
<field name="sequence">70</field>
<field name="stage_type">agreement</field>
</record>

<record id="agreement_stage_expired" model="agreement.stage">
<field name="name">Expired</field>
<field name="sequence">80</field>
<field name="stage_type">agreement</field>
</record>

<record id="agreement_stage_terminated" model="agreement.stage">
<field name="name">Terminated</field>
<field name="sequence">90</field>
<field name="fold">True</field>
<field name="stage_type">agreement</field>
</record>

<record id="agreement_stage_cancelled" model="agreement.stage">
<field name="name">Cancelled</field>
<field name="sequence">100</field>
<field name="fold">True</field>
<field name="stage_type">agreement</field>
</record>
</odoo>
15 changes: 15 additions & 0 deletions agreement_legal/data/agreement_type.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<odoo>

<record id="agreement_type_agreement" model="agreement.type">
<field name="name">Agreement</field>
</record>

<record id="agreement_type_contract" model="agreement.type">
<field name="name">Contract</field>
</record>

<record id="agreement_type_loi" model="agreement.type">
<field name="name">Letter of Intent</field>
</record>

</odoo>
25 changes: 25 additions & 0 deletions agreement_legal/data/cron.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo noupdate="1">
<!-- Add warning days -->
<record id="mail_activity_review_agreement" model="mail.activity.type">
<field name="name">Agreement needs a review</field>
<field name="summary">note</field>
<field name="category">default</field>
<field name="res_model">agreement</field>
<field name="icon">fa-tasks</field>
<field name="delay_count">0</field>
</record>
<!--Test warning days -->
<record model="ir.cron" forcecreate="True" id="ir_cron_test_acc_move_except">
<field name="name">Agreement: Check to Review Days</field>
<field name="model_id" ref="agreement_legal.model_agreement" />
<field name="state">code</field>
<field name="code">model._alert_to_review_date()</field>
<field name="user_id" ref="base.user_root" />
<field name="interval_number">20</field>
<field name="interval_type">minutes</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False" />
<field name="active" eval="True" />
</record>
</odoo>
12 changes: 12 additions & 0 deletions agreement_legal/data/ir_sequence.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<odoo noupdate="1">

<!-- Sequence for agreement -->
<record id="seq_agreement" model="ir.sequence">
<field name="name">Agreements</field>
<field name="code">agreement</field>
<field name="prefix">AG</field>
<field name="padding">3</field>
<field name="company_id" eval="False" />
</record>

</odoo>
40 changes: 40 additions & 0 deletions agreement_legal/demo/demo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!--
© 2019 Ygol Internetwork (yves@ygol.com)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo noupdate="1">

<record id="agreement.market1" model="agreement">
<field name="description">Hardware IT (C2C-IT0042)</field>
<field name="agreement_type_id" ref="agreement_type_agreement" />
</record>

<record id="agreement.market2" model="agreement">
<field name="description">Fiber access office Lausanne (C2C-IT0043)</field>
<field name="agreement_type_id" ref="agreement_type_contract" />
</record>

<record id="agreement.market3" model="agreement">
<field name="description">Vétérinaire (AGR-VETO001)</field>
<field name="agreement_type_id" ref="agreement_type_loi" />
</record>

<record id="agreement.market4" model="agreement">
<field
name="description"
>Wazo IPBX deployment and maintenance (AGR-TEL001)</field>
<field name="agreement_type_id" ref="agreement_type_loi" />
</record>

<record id="agreement.market5" model="agreement">
<field name="description">SIP Phones supply (BUY-VOIP012)</field>
<field name="agreement_type_id" ref="agreement_type_agreement" />
</record>

<record id="agreement.market6" model="agreement">
<field name="is_template">True</field>
<field name="description">SIP-ISDN gateways (BUY-VOIP013)</field>
<field name="agreement_type_id" ref="agreement_legal.agreement_type_contract" />
</record>

</odoo>
1,599 changes: 1,599 additions & 0 deletions agreement_legal/i18n/agreement_legal.pot

Large diffs are not rendered by default.

1,833 changes: 1,833 additions & 0 deletions agreement_legal/i18n/es.po

Large diffs are not rendered by default.

1,644 changes: 1,644 additions & 0 deletions agreement_legal/i18n/es_AR.po

Large diffs are not rendered by default.

2,012 changes: 2,012 additions & 0 deletions agreement_legal/i18n/fr.po

Large diffs are not rendered by default.

1,600 changes: 1,600 additions & 0 deletions agreement_legal/i18n/it.po

Large diffs are not rendered by default.

1,823 changes: 1,823 additions & 0 deletions agreement_legal/i18n/pt.po

Large diffs are not rendered by default.

1,821 changes: 1,821 additions & 0 deletions agreement_legal/i18n/pt_BR.po

Large diffs are not rendered by default.

1,768 changes: 1,768 additions & 0 deletions agreement_legal/i18n/pt_PT.po

Large diffs are not rendered by default.

1,809 changes: 1,809 additions & 0 deletions agreement_legal/i18n/zh_CN.po

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions agreement_legal/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from . import res_config_settings
from . import agreement_stage
from . import agreement
from . import agreement_appendix
from . import agreement_clause
from . import agreement_line
from . import agreement_recital
from . import agreement_section
from . import agreement_type
from . import agreement_subtype
from . import res_partner
482 changes: 482 additions & 0 deletions agreement_legal/models/agreement.py

Large diffs are not rendered by default.

90 changes: 90 additions & 0 deletions agreement_legal/models/agreement_appendix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Copyright (C) 2018 - TODAY, Pavlov Media
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import api, fields, models


class AgreementAppendix(models.Model):
_name = "agreement.appendix"
_description = "Agreement Appendices"
_order = "sequence"

name = fields.Char(required=True)
title = fields.Char(
required=True, help="The title is displayed on the PDF. The name is not."
)
sequence = fields.Integer(default=10)
content = fields.Html()
dynamic_content = fields.Html(
compute="_compute_dynamic_content", help="compute dynamic Content"
)
agreement_id = fields.Many2one("agreement", string="Agreement", ondelete="cascade")
active = fields.Boolean(
default=True,
help="If unchecked, it will allow you to hide this appendix without "
"removing it.",
)

# Dynamic field editor
field_id = fields.Many2one(
"ir.model.fields",
string="Field",
help="""Select target field from the related document model. If it is a
relationship field you will be able to select a target field at the
destination of the relationship.""",
)
sub_object_id = fields.Many2one(
"ir.model",
string="Sub-model",
help="""When a relationship field is selected as first field, this
field shows the document model the relationship goes to.""",
)
sub_model_object_field_id = fields.Many2one(
"ir.model.fields",
string="Sub-field",
help="""When a relationship field is selected as first field, this
field lets you select the target field within the destination document
model (sub-model).""",
)
default_value = fields.Char(
help="Optional value to use if the target field is empty."
)
copyvalue = fields.Char(
string="Placeholder Expression",
help="""Final placeholder expression, to be copy-pasted in the desired
template field.""",
)

@api.onchange("field_id", "sub_model_object_field_id", "default_value")
def onchange_copyvalue(self):
self.sub_object_id = False
self.copyvalue = False
if self.field_id and not self.field_id.relation:
self.copyvalue = "{{{{object.{} or {}}}}}".format(
self.field_id.name, self.default_value or "''"
)
self.sub_model_object_field_id = False
if self.field_id and self.field_id.relation:
self.sub_object_id = self.env["ir.model"].search(
[("model", "=", self.field_id.relation)]
)[0]
if self.sub_model_object_field_id:
self.copyvalue = "{{{{object.{}.{} or {}}}}}".format(
self.field_id.name,
self.sub_model_object_field_id.name,
self.default_value or "''",
)

# compute the dynamic content for jinja expression
def _compute_dynamic_content(self):
MailTemplates = self.env["mail.template"]
for appendix in self:
lang = (
appendix.agreement_id
and appendix.agreement_id.partner_id.lang
or "en_US"
)
content = MailTemplates.with_context(lang=lang)._render_template(
appendix.content, "agreement.appendix", [appendix.id]
)[appendix.id]
appendix.dynamic_content = content
92 changes: 92 additions & 0 deletions agreement_legal/models/agreement_clause.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Copyright (C) 2018 - TODAY, Pavlov Media
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import api, fields, models


class AgreementClause(models.Model):
_name = "agreement.clause"
_description = "Agreement Clauses"
_order = "sequence"

name = fields.Char(required=True)
title = fields.Char(help="The title is displayed on the PDF. The name is not.")
sequence = fields.Integer()
agreement_id = fields.Many2one("agreement", string="Agreement", ondelete="cascade")
temp_agreement_id = fields.Many2one(
"agreement", string="Temp Agreement", help="This field help to filter section."
)
section_id = fields.Many2one(
"agreement.section", string="Section", ondelete="cascade"
)
content = fields.Html(string="Clause Content")
dynamic_content = fields.Html(
compute="_compute_dynamic_content", help="compute dynamic Content"
)
active = fields.Boolean(
default=True,
help="If unchecked, it will allow you to hide the agreement without "
"removing it.",
)

# Dynamic field editor
field_id = fields.Many2one(
"ir.model.fields",
string="Field",
help="""Select target field from the related document model. If it is a
relationship field you will be able to select a target field at the
destination of the relationship.""",
)
sub_object_id = fields.Many2one(
"ir.model",
string="Sub-model",
help="""When a relationship field is selected as first field, this
field shows the document model the relationship goes to.""",
)
sub_model_object_field_id = fields.Many2one(
"ir.model.fields",
string="Sub-field",
help="""When a relationship field is selected as first field, this
field lets you select the target field within the destination document
model (sub-model).""",
)
default_value = fields.Char(
help="Optional value to use if the target field is empty."
)
copyvalue = fields.Char(
string="Placeholder Expression",
help="""Final placeholder expression, to be copy-pasted in the desired
template field.""",
)

@api.onchange("field_id", "sub_model_object_field_id", "default_value")
def onchange_copyvalue(self):
self.sub_object_id = False
self.copyvalue = False
if self.field_id and not self.field_id.relation:
self.copyvalue = "{{{{object.{} or {}}}}}".format(
self.field_id.name, self.default_value or "''"
)
self.sub_model_object_field_id = False
if self.field_id and self.field_id.relation:
self.sub_object_id = self.env["ir.model"].search(
[("model", "=", self.field_id.relation)]
)[0]
if self.sub_model_object_field_id:
self.copyvalue = "{{{{object.{}.{} or {}}}}}".format(
self.field_id.name,
self.sub_model_object_field_id.name,
self.default_value or "''",
)

# compute the dynamic content for jinja expression
def _compute_dynamic_content(self):
MailTemplates = self.env["mail.template"]
for clause in self:
lang = (
clause.agreement_id and clause.agreement_id.partner_id.lang or "en_US"
)
content = MailTemplates.with_context(lang=lang)._render_template(
clause.content, "agreement.clause", [clause.id]
)[clause.id]
clause.dynamic_content = content
20 changes: 20 additions & 0 deletions agreement_legal/models/agreement_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright (C) 2018 - TODAY, Pavlov Media
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import api, fields, models


class AgreementLine(models.Model):
_name = "agreement.line"
_description = "Agreement Lines"

product_id = fields.Many2one("product.product", string="Product")
name = fields.Char(string="Description", required=True)
agreement_id = fields.Many2one("agreement", string="Agreement", ondelete="cascade")
qty = fields.Float(string="Quantity")
uom_id = fields.Many2one("uom.uom", string="Unit of Measure", required=True)

@api.onchange("product_id")
def _onchange_product_id(self):
self.name = self.product_id.name
self.uom_id = self.product_id.uom_id.id
86 changes: 86 additions & 0 deletions agreement_legal/models/agreement_recital.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Copyright (C) 2018 - TODAY, Pavlov Media
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import api, fields, models


class AgreementRecital(models.Model):
_name = "agreement.recital"
_description = "Agreement Recitals"
_order = "sequence"

name = fields.Char(required=True)
title = fields.Char(help="The title is displayed on the PDF. The name is not.")
sequence = fields.Integer(default=10)
content = fields.Html()
dynamic_content = fields.Html(
compute="_compute_dynamic_content", help="compute dynamic Content"
)
agreement_id = fields.Many2one("agreement", string="Agreement", ondelete="cascade")
active = fields.Boolean(
default=True,
help="If unchecked, it will allow you to hide this recital without "
"removing it.",
)

# Dynamic field editor
field_id = fields.Many2one(
"ir.model.fields",
string="Field",
help="""Select target field from the related document model. If it is a
relationship field you will be able to select a target field at the
destination of the relationship.""",
)
sub_object_id = fields.Many2one(
"ir.model",
string="Sub-model",
help="""When a relationship field is selected as first field, this
field shows the document model the relationship goes to.""",
)
sub_model_object_field_id = fields.Many2one(
"ir.model.fields",
string="Sub-field",
help="""When a relationship field is selected as first field, this
field lets you select the target field within the destination document
model (sub-model).""",
)
default_value = fields.Char(
help="Optional value to use if the target field is empty."
)
copyvalue = fields.Char(
string="Placeholder Expression",
help="""Final placeholder expression, to be copy-pasted in the desired
template field.""",
)

@api.onchange("field_id", "sub_model_object_field_id", "default_value")
def onchange_copyvalue(self):
self.sub_object_id = False
self.copyvalue = False
if self.field_id and not self.field_id.relation:
self.copyvalue = "{{{{object.{} or {}}}}}".format(
self.field_id.name, self.default_value or "''"
)
self.sub_model_object_field_id = False
if self.field_id and self.field_id.relation:
self.sub_object_id = self.env["ir.model"].search(
[("model", "=", self.field_id.relation)]
)[0]
if self.sub_model_object_field_id:
self.copyvalue = "{{{{object.{}.{} or {}}}}}".format(
self.field_id.name,
self.sub_model_object_field_id.name,
self.default_value or "''",
)

# compute the dynamic content for jinja expression
def _compute_dynamic_content(self):
MailTemplates = self.env["mail.template"]
for recital in self:
lang = (
recital.agreement_id and recital.agreement_id.partner_id.lang or "en_US"
)
content = MailTemplates.with_context(lang=lang)._render_template(
recital.content, "agreement.recital", [recital.id]
)[recital.id]
recital.dynamic_content = content
89 changes: 89 additions & 0 deletions agreement_legal/models/agreement_section.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Copyright (C) 2018 - TODAY, Pavlov Media
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import api, fields, models


class AgreementSection(models.Model):
_name = "agreement.section"
_description = "Agreement Sections"
_order = "sequence"

name = fields.Char(required=True)
title = fields.Char(help="The title is displayed on the PDF. The name is not.")
sequence = fields.Integer()
agreement_id = fields.Many2one("agreement", string="Agreement", ondelete="cascade")
clauses_ids = fields.One2many(
"agreement.clause", "section_id", string="Clauses", copy=True
)
content = fields.Html(string="Section Content")
dynamic_content = fields.Html(
compute="_compute_dynamic_content", help="compute dynamic Content"
)
active = fields.Boolean(
default=True,
help="If unchecked, it will allow you to hide the agreement without "
"removing it.",
)

# Dynamic field editor
field_id = fields.Many2one(
"ir.model.fields",
string="Field",
help="""Select target field from the related document model. If it is a
relationship field you will be able to select a target field at the
destination of the relationship.""",
)
sub_object_id = fields.Many2one(
"ir.model",
string="Sub-model",
help="""When a relationship field is selected as first field, this
field shows the document model the relationship goes to.""",
)
sub_model_object_field_id = fields.Many2one(
"ir.model.fields",
string="Sub-field",
help="""When a relationship field is selected as first field, this
field lets you select the target field within the destination document
model (sub-model).""",
)
default_value = fields.Char(
help="Optional value to use if the target field is empty."
)
copyvalue = fields.Char(
string="Placeholder Expression",
help="""Final placeholder expression, to be copy-pasted in the desired
template field.""",
)

@api.onchange("field_id", "sub_model_object_field_id", "default_value")
def onchange_copyvalue(self):
self.sub_object_id = False
self.copyvalue = False
if self.field_id and not self.field_id.relation:
self.copyvalue = "{{{{object.{} or {}}}}}".format(
self.field_id.name, self.default_value or "''"
)
self.sub_model_object_field_id = False
if self.field_id and self.field_id.relation:
self.sub_object_id = self.env["ir.model"].search(
[("model", "=", self.field_id.relation)]
)[0]
if self.sub_model_object_field_id:
self.copyvalue = "{{{{object.{}.{} or {}}}}}".format(
self.field_id.name,
self.sub_model_object_field_id.name,
self.default_value or "''",
)

# compute the dynamic content for jinja expression
def _compute_dynamic_content(self):
MailTemplates = self.env["mail.template"]
for section in self:
lang = (
section.agreement_id and section.agreement_id.partner_id.lang or "en_US"
)
content = MailTemplates.with_context(lang=lang)._render_template(
section.content, "agreement.section", [section.id]
)[section.id]
section.dynamic_content = content
27 changes: 27 additions & 0 deletions agreement_legal/models/agreement_stage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Copyright (C) 2018 - TODAY, Pavlov Media
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import fields, models


# Main Stage on the Agreement
class AgreementStage(models.Model):
_name = "agreement.stage"
_description = "Agreement Stages"
_order = "sequence"

# General
name = fields.Char(string="Stage Name", required=True)
sequence = fields.Integer(default="1", required=False)
fold = fields.Boolean(
string="Is Folded",
required=False,
help="This stage is folded in the kanban view by default.",
)
stage_type = fields.Selection(
[("agreement", "Agreement")], string="Type", required=True
)
active = fields.Boolean(default=True)
readonly = fields.Boolean(
default=False, help="The agreement can not edit if set Readonly = True."
)
13 changes: 13 additions & 0 deletions agreement_legal/models/agreement_subtype.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright (C) 2018 - TODAY, Pavlov Media
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import fields, models


class AgreementSubtype(models.Model):
_name = "agreement.subtype"
_description = "Agreement Subtypes"

name = fields.Char(string="Sub-Type Name", required=True)
agreement_type_id = fields.Many2one("agreement.type", string="Agreement Type")
active = fields.Boolean(default=True)
17 changes: 17 additions & 0 deletions agreement_legal/models/agreement_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright (C) 2018 - TODAY, Pavlov Media
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import fields, models


class AgreementType(models.Model):
_inherit = "agreement.type"
_description = "Agreement Types"

agreement_subtypes_ids = fields.One2many(
"agreement.subtype", "agreement_type_id", string="Sub-Types"
)
review_user_id = fields.Many2one(
"res.users", help="User assigned automatically the activity on review date"
)
review_days = fields.Integer()
37 changes: 37 additions & 0 deletions agreement_legal/models/res_config_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Copyright (C) 2018 - TODAY, Open Source Integrators
# 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"

module_agreement_maintenance = fields.Boolean(
string="Manage maintenance agreements and contracts."
)
module_agreement_mrp = fields.Boolean(
string="Link your manufacturing orders to an agreement."
)
module_agreement_project = fields.Boolean(
string="Link your projects and tasks to an agreement."
)
module_agreement_repair = fields.Boolean(
string="Link your repair orders to an agreement."
)
module_agreement_rma = fields.Boolean(string="Link your RMAs to an agreement.")
module_agreement_sale = fields.Boolean(
string="Create an agreement when the sale order is confirmed."
)
module_agreement_sale_subscription = fields.Boolean(
string="Link your subscriptions to an agreement."
)
module_agreement_stock = fields.Boolean(
string="Link your pickings to an agreement."
)
module_fieldservice_agreement = fields.Boolean(
string="Link your Field Service orders and equipments to an agreement."
)
module_agreement_helpdesk = fields.Boolean(
string="Link your Helpdesk tickets to an agreement."
)
29 changes: 29 additions & 0 deletions agreement_legal/models/res_partner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Copyright (C) 2018 - TODAY, Pavlov Media
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import api, fields, models


class Partner(models.Model):
_inherit = "res.partner"

agreement_ids = fields.One2many("agreement", "partner_id", string="Agreements")
agreements_count = fields.Integer(compute="_compute_agreements_count")

@api.depends("agreement_ids")
def _compute_agreements_count(self):
domain = [("partner_id", "in", self.ids)]
res = self.env["agreement"].read_group(
domain=domain, fields=["partner_id"], groupby=["partner_id"]
)
agreement_dict = {x["partner_id"][0]: x["partner_id_count"] for x in res}
for rec in self:
rec.agreements_count = agreement_dict.get(rec.id, 0)

def action_open_agreement(self):
self.ensure_one()
action = self.env.ref("agreement.agreement_action")
result = action.read()[0]
result["domain"] = [("partner_id", "=", self.id)]
result["context"] = {"default_partner_id": self.id}
return result
3 changes: 3 additions & 0 deletions agreement_legal/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"
7 changes: 7 additions & 0 deletions agreement_legal/readme/CONFIGURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
To configure this module:

- Go to Agreement \> Configuration \> Templates
- Create a new template with sections and clauses and their respective
content
- Go to Agreement \> Configuration \> Stages
- Create and reorder stages to match your process
8 changes: 8 additions & 0 deletions agreement_legal/readme/CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
- Patrick Wilson \<<pwilson@pavlovmedia.com>\>
- Bhavesh Odedra \<<bodedra@opensourceintegrators.com>\>
- Wolfgang Hall \<<whall@opensourceintegrators.com>\>
- Maxime Chambreuil \<<mchambreuil@opensourceintegrators.com>\>
- Sandip Mangukiya \<<smangukiya@opensourceintegrators.com>\>
- Yves Goldberg \<<yves@ygol.com>\>
- Tharathip Chaweewongphan \<<tharathipc@ecosoft.co.th>\>
- Italo LOPES \<<italo.lopes@camptocamp.com>\>
5 changes: 5 additions & 0 deletions agreement_legal/readme/CREDITS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
The development of this module has been financially supported by:

- Pavlov Media
- Open Source Integrators
- Yves Goldberg
10 changes: 10 additions & 0 deletions agreement_legal/readme/DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
This module allows you to manage agreements, letter of intent and
contract content. The module is meant to be used by the legal team of a
company and to allow them to define sections, clauses and templates with
their respective content that can be dynamic.

Based on the template, an agreement can be created and the pdf document
generated.

The agreement would go through a workflow to finally become a contract
with the customer signature.
3 changes: 3 additions & 0 deletions agreement_legal/readme/ROADMAP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- Split the module to remove the dependencies on sale and account and
provide the same feature in extra modules (agreement_sale,
agreement_account, agreement_purchase)
7 changes: 7 additions & 0 deletions agreement_legal/readme/USAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
To use this module:

- Go to Agreement \> Agreements
- Create a new agreement
- Select a template
- Follow the process to get the required approval
- Send the invitation to the customer to review and sign the agreement
183 changes: 183 additions & 0 deletions agreement_legal/report/agreement.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
<odoo>

<record id="partner_agreement_contract_document" model="ir.actions.report">
<field name="name">Agreement</field>
<field name="model">agreement</field>
<field name="binding_model_id" ref="model_agreement" />
<field name="report_type">qweb-pdf</field>
<field name="report_name">agreement_legal.report_agreement_document</field>
<field name="report_file">agreement_legal.report_agreement_document</field>
<field name="binding_view_types">list,form</field>
</record>

<record id="partner_agreement_contract_document_preview" model="ir.actions.report">
<field name="name">Agreement Preview</field>
<field name="model">agreement</field>
<field name="binding_model_id" ref="model_agreement" />
<field name="report_type">qweb-html</field>
<field name="report_name">agreement_legal.report_agreement_document</field>
<field name="report_file">agreement_legal.report_agreement_document</field>
<field name="binding_view_types">list,form</field>
</record>

<template id="report_agreement_document">
<t t-name="agreement.report_agreement_document">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="web.external_layout">
<div class="page">
<h1 t-field="doc.name" />
<div name="description">
<span t-field="doc.dynamic_description" />
</div>
<h2>Parties</h2>
<div name="parties">
<t t-if="doc.use_parties_content">
<p t-field="doc.dynamic_parties" />
</t>
</div>
<t t-if="not doc.use_parties_content">
<h3>Company Information</h3>
<div name="company_address">
<address
t-field="doc.company_id.partner_id"
t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": True}'
/>
</div>
<div name="company_contact">
Represented by <span
t-field="doc.company_contact_id.name"
/>.
</div>
<h3>Partner Information</h3>
<div name="partner_address">
<address
t-field="doc.partner_id"
t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": True}'
/>
</div>
<div name="partner_contact">
Represented by <span
t-field="doc.partner_contact_id.name"
/>.
</div>
</t>
<h2>Agreement</h2>
<h3>Recitals</h3>
<table class="table table-condensed">
<tbody>
<tr>
<td>
<ol>
<li
t-foreach="doc.recital_ids"
t-as="r"
>
<t t-if="r.title">
<h3 t-field="r.title" />
</t>
<p t-field="r.dynamic_content" />
</li>
</ol>
</td>
</tr>
</tbody>
</table>
<table class="table table-condensed">
<tbody class="section_tbody">
<tr>
<td>
<ol>
<li
t-foreach="doc.sections_ids"
t-as="s"
>
<t t-if="s.title">
<h3 t-field="s.title" />
</t>
<p t-field="s.dynamic_content" />
<ol>
<li
t-foreach="s.clauses_ids"
t-as="c"
>
<t t-if="c.title">
<h4 t-field="c.title" />
</t>
<p
t-field="c.dynamic_content"
/>
</li>
</ol>
</li>
</ol>
</td>
</tr>
</tbody>
</table>
<t t-if="special_term">
<h2>Special Terms</h2>
<div name="special_term">
<p t-field="doc.dynamic_special_terms" />
</div>
</t>
<h2>Signatures</h2>
<table class="table table-condensed">
<theader>
<tr>
<th>Partner</th>
<th>Company</th>
</tr>
</theader>
<tbody class="section_tbody">
<tr>
<td>
<p t-field="doc.partner_id" />
<p>By: </p>
<p>
Name: <span
t-field="doc.partner_contact_id.name"
/>
</p>
<p>
Title: <span
t-field="doc.partner_contact_id.function"
/>
</p>
<p>Date: </p>
</td>
<td>
<p t-field="doc.company_id.partner_id" />
<p>By: </p>
<p>
Name: <span
t-field="doc.company_contact_id.name"
/>
</p>
<p>
Title: <span
t-field="doc.company_contact_id.function"
/>
</p>
<p>Date: </p>
</td>
</tr>
</tbody>
</table>
</div>
<div t-foreach="doc.appendix_ids" t-as="a">
<div class="page">
<h1
t-field="a.title"
style="page-break-before: always;"
/>
<p t-field="a.dynamic_content" />
</div>
</div>
</t>
</t>
</t>
</t>
</template>

</odoo>
26 changes: 26 additions & 0 deletions agreement_legal/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_agreement_stage_readonly,stage readonly,model_agreement_stage,group_agreement_readonly,1,0,0,0
access_agreement_stage_manager,stage manager,model_agreement_stage,group_agreement_manager,1,1,1,1
access_agreement_appendix_readonly,appendix readonly,model_agreement_appendix,group_agreement_readonly,1,0,0,0
access_agreement_appendix_allusers,appendix all users,model_agreement_appendix,group_agreement_user,1,1,1,1
access_agreement_appendix_manager,appendix manager,model_agreement_appendix,group_agreement_manager,1,1,1,1
access_agreement_subtype_readonly,subtype readonly,model_agreement_subtype,group_agreement_readonly,1,0,0,0
access_agreement_subtype_manager,subtype manager,model_agreement_subtype,group_agreement_manager,1,1,1,1
access_agreement_section_readonly,section readonly,model_agreement_section,group_agreement_readonly,1,0,0,0
access_agreement_section_allusers,section all users,model_agreement_section,group_agreement_user,1,1,1,1
access_agreement_section_manager,section manager,model_agreement_section,group_agreement_manager,1,1,1,1
access_agreement_clause_readonly,clause readonly,model_agreement_clause,group_agreement_readonly,1,0,0,0
access_agreement_clause_allusers,clause all users,model_agreement_clause,group_agreement_user,1,1,1,1
access_agreement_clause_manager,clause manager,model_agreement_clause,group_agreement_manager,1,1,1,1
access_agreement_line_readonly,agreement line readonly,model_agreement_line,group_agreement_readonly,1,0,0,0
access_agreement_line_allusers,agreement line all users,model_agreement_line,group_agreement_user,1,1,1,1
access_agreement_line_manager,agreement line manager,model_agreement_line,group_agreement_manager,1,1,1,1
access_agreement_recital_readonly,recital readonly,model_agreement_recital,group_agreement_readonly,1,0,0,0
access_agreement_recital_allusers,recital all users,model_agreement_recital,group_agreement_user,1,1,1,1
access_agreement_recital_manager,recital manager,model_agreement_recital,group_agreement_manager,1,1,1,1
access_create_agreement_wizard_allusers,create agreement wizard users,model_create_agreement_wizard,group_agreement_user,1,1,1,1
access_agreement_readonly,agreement readonly,model_agreement,group_agreement_readonly,1,0,0,0
access_agreement_allusers,agreement all users,model_agreement,group_agreement_user,1,1,1,0
access_agreement_manager,agreement manager,model_agreement,group_agreement_manager,1,1,1,1
access_agreement_type_readonly,type readonly,model_agreement_type,group_agreement_readonly,1,0,0,0
access_agreement_type_manager,type manager,model_agreement_type,group_agreement_manager,1,1,1,1
43 changes: 43 additions & 0 deletions agreement_legal/security/res_groups.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<odoo>
<record id="module_agreement_legal_category" model="ir.module.category">
<field name="name">Agreement</field>
<field name="sequence">17</field>
</record>

<!-- Readonly group -->
<record id="group_agreement_readonly" model="res.groups">
<field name="name">Read-Only Users</field>
<field
name="category_id"
ref="agreement_legal.module_agreement_legal_category"
/>
<field
name="implied_ids"
eval="[(4, ref('base.group_user')), (4, ref('agreement.group_use_agreement_type')), (4, ref('agreement.group_use_agreement_template'))]"
/>
</record>

<!-- User group -->
<record id="group_agreement_user" model="res.groups">
<field name="name">User</field>
<field
name="category_id"
ref="agreement_legal.module_agreement_legal_category"
/>
<field name="implied_ids" eval="[(4, ref('group_agreement_readonly'))]" />
</record>

<!-- Manager group -->
<record id="group_agreement_manager" model="res.groups">
<field name="name">Manager</field>
<field
name="category_id"
ref="agreement_legal.module_agreement_legal_category"
/>
<field name="implied_ids" eval="[(4, ref('group_agreement_user'))]" />
<field
name="users"
eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"
/>
</record>
</odoo>
Binary file added agreement_legal/static/description/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
237 changes: 237 additions & 0 deletions agreement_legal/static/description/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
481 changes: 481 additions & 0 deletions agreement_legal/static/description/index.html

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions agreement_legal/static/src/js/form_view.esm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/** @odoo-module **/

import {FormController} from "@web/views/form/form_controller";
import {formView} from "@web/views/form/form_view";
import {registry} from "@web/core/registry";
import {useService} from "@web/core/utils/hooks";

export class AgreementFormController extends FormController {
setup() {
super.setup();
this.action = useService("action");
this.canCreateTemplate = this.props.context.default_is_template || false;
}
onClickCreateFromTemplate() {
this.action.doAction("agreement_legal.create_agreement_from_template_action");
}
}

export const AgreementFormView = {
...formView,
Controller: AgreementFormController,
};

registry.category("views").add("agreement_template_form", AgreementFormView);
25 changes: 25 additions & 0 deletions agreement_legal/static/src/js/kanban_view.esm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/** @odoo-module **/

import {KanbanController} from "@web/views/kanban/kanban_controller";
import {kanbanView} from "@web/views/kanban/kanban_view";
import {registry} from "@web/core/registry";
import {useService} from "@web/core/utils/hooks";

export class AgreementKanbanController extends KanbanController {
setup() {
super.setup();
this.action = useService("action");
this.canCreateTemplate = this.props.context.default_is_template || false;
}
onClickCreateFromTemplate() {
this.action.doAction("agreement_legal.create_agreement_from_template_action");
}
}

export const AgreementKanbanView = {
...kanbanView,
Controller: AgreementKanbanController,
buttonTemplate: "agreement_legal.KanbanViewButtons",
};

registry.category("views").add("agreement_template_kanban", AgreementKanbanView);
25 changes: 25 additions & 0 deletions agreement_legal/static/src/js/list_view.esm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/** @odoo-module **/

import {ListController} from "@web/views/list/list_controller";
import {listView} from "@web/views/list/list_view";
import {registry} from "@web/core/registry";
import {useService} from "@web/core/utils/hooks";

export class AgreementListController extends ListController {
setup() {
super.setup();
this.action = useService("action");
this.canCreateTemplate = this.props.context.default_is_template || false;
}
onClickCreateFromTemplate() {
this.action.doAction("agreement_legal.create_agreement_from_template_action");
}
}

export const AgreementListView = {
...listView,
Controller: AgreementListController,
buttonTemplate: "agreement_legal.ListView",
};

registry.category("views").add("agreement_template_tree", AgreementListView);
21 changes: 21 additions & 0 deletions agreement_legal/static/src/xml/agreement_form_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates>

<t t-inherit="web.FormView" t-inherit-mode="extension">
<xpath
expr="//*[@class='btn btn-secondary o_form_button_create']"
position="after"
>
<t t-if="props.resModel === 'agreement'">
<t t-call="agreement_legal.TemplateButton" />
</t>
</xpath>
<xpath
expr="//*[@class='btn btn-secondary o_form_button_create']"
position="attributes"
>
<attribute name="t-if">canCreateTemplate</attribute>
</xpath>
</t>

</templates>
24 changes: 24 additions & 0 deletions agreement_legal/static/src/xml/agreement_kanban_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates>

<t
t-name="agreement_legal.KanbanViewButtons"
t-inherit="web.KanbanView.Buttons"
t-inherit-mode="primary"
>
<div role="toolbar" position="inside">
<t t-call="agreement_legal.TemplateButton" />
</div>
</t>
<t
t-name="agreement_legal.KanbanView"
t-inherit="web.KanbanView"
t-inherit-mode="primary"
>
<xpath expr="//button[hasclass('o-kanban-button-new')]" position="attributes">
<attribute name="t-if">!noCreate and canCreateTemplate</attribute>
</xpath>
</t>


</templates>
17 changes: 17 additions & 0 deletions agreement_legal/static/src/xml/agreement_list_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates>
<t
t-name="agreement_legal.ListView"
t-inherit="web.ListView.Buttons"
t-inherit-mode="primary"
>
<xpath expr="//div[hasclass('o_list_buttons')]" position="attributes">
<attribute name="t-if">canCreateTemplate</attribute>
</xpath>
<xpath expr="//div[hasclass('o_list_buttons')]" position="after">
<t t-call="agreement_legal.TemplateButton" />
</xpath>
</t>


</templates>
15 changes: 15 additions & 0 deletions agreement_legal/static/src/xml/agreement_template.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates>

<t t-name="agreement_legal.TemplateButton">
<button
type="button"
t-if="!canCreateTemplate"
t-on-click="onClickCreateFromTemplate"
class="create_agreement_from_template btn btn-primary mx-1"
>
Create From Template
</button>
</t>

</templates>
9 changes: 9 additions & 0 deletions agreement_legal/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# License LGPLv3.0 or later (https://www.gnu.org/licenses/lgpl-3.0.en.html).

from . import test_agreement
from . import test_agreement_appendix
from . import test_agreement_clause
from . import test_agreement_line
from . import test_agreement_recital
from . import test_agreement_section
from . import test_create_agreement_wizard
163 changes: 163 additions & 0 deletions agreement_legal/tests/test_agreement.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)

from datetime import timedelta

from lxml import etree

from odoo import fields
from odoo.tests.common import TransactionCase


class TestAgreement(TransactionCase):
def setUp(self):
super().setUp()
self.test_customer = self.env["res.partner"].create({"name": "TestCustomer"})
self.agreement_type = self.env["agreement.type"].create(
{"name": "Test Agreement Type", "domain": "sale"}
)
self.test_agreement = self.env["agreement"].create(
{
"name": "TestAgreement",
"description": "Test",
"special_terms": "Test",
"partner_id": self.test_customer.id,
"start_date": fields.Date.today(),
"end_date": fields.Date.today() + timedelta(days=365),
"state": "active",
}
)

# TEST 01: Set 'Field' for dynamic placeholder, test onchange method
def test_onchange_copyvalue(self):
agreement_01 = self.test_agreement
field_01 = self.env["ir.model.fields"].search(
[("model", "=", "agreement"), ("name", "=", "active")]
)
agreement_01.field_id = field_01.id
agreement_01.onchange_copyvalue()
self.assertEqual(agreement_01.copyvalue, "{{object.active or ''}}")

# TEST 02: Set related 'Field' for dynamic placeholder to
# test onchange method
def test_onchange_copyvalue2(self):
agreement_01 = self.test_agreement
field_01 = self.env["ir.model.fields"].search(
[("model", "=", "agreement"), ("name", "=", "agreement_type_id")]
)
sub_field_01 = self.env["ir.model.fields"].search(
[("model", "=", "agreement.type"), ("name", "=", "active")]
)
agreement_01.field_id = field_01.id
agreement_01.onchange_copyvalue()
self.assertEqual(agreement_01.sub_object_id.model, "agreement.type")
agreement_01.sub_model_object_field_id = sub_field_01.id
agreement_01.onchange_copyvalue()
self.assertEqual(
agreement_01.copyvalue, "{{object.agreement_type_id.active or ''}}"
)

# TEST 03: Create New Version
def test_create_new_version(self):
agreement_01 = self.test_agreement
agreement_01.create_new_version()
old_agreement = self.env["agreement"].search(
[("code", "=", agreement_01.code + "-V1"), ("active", "=", False)]
)
self.assertEqual(len(old_agreement), 1)
new_agreement = self.env["agreement"].search(
[("name", "=", "TestAgreement"), ("version", "=", 2)]
)
self.assertEqual(len(new_agreement), 1)

# TEST 04: Create New Agreement
def test_create_new_agreement(self):
agreement_01 = self.test_agreement
agreement_01.create_new_agreement()
new_agreement = self.env["agreement"].search([("name", "=", "New")])
self.assertEqual(len(new_agreement), 1)

# TEST 05: Test Description Dynamic Field
def test_compute_dynamic_description(self):
agreement_01 = self.test_agreement
agreement_01.description = "{{object.name}}"
self.assertEqual(agreement_01.dynamic_description, "TestAgreement")

# TEST 06: Test Parties Dynamic Field
def test_compute_dynamic_parties(self):
agreement_01 = self.test_agreement
agreement_01.parties = "{{object.name}}"
self.assertEqual(agreement_01.dynamic_parties, "<p>TestAgreement</p>")

# TEST 07: Test Special Terms Dynamic Field
def test_compute_dynamic_special_terms(self):
agreement_01 = self.test_agreement
agreement_01.special_terms = "{{object.name}}"
self.assertEqual(agreement_01.dynamic_special_terms, "TestAgreement")

# TEST 02: Check Read Stages
def test_read_group_stage_ids(self):
agreement_01 = self.test_agreement
self.assertEqual(
agreement_01._read_group_stage_ids(self.env["agreement.stage"], [], "id"),
self.env["agreement.stage"].search(
[("stage_type", "=", "agreement")], order="id"
),
)

# Test fields_view_get
def test_agreement_fields_view_get(self):
res = self.env["agreement"].get_view(
view_id=self.ref("agreement_legal.partner_agreement_form_view"),
view_type="form",
)
doc = etree.XML(res["arch"])
field = doc.xpath("//field[@name='partner_contact_id']")
self.assertEqual(field[0].get("readonly", ""), "bool(readonly)")

def test_action_create_new_version(self):
self.test_agreement.create_new_version()
self.assertEqual(self.test_agreement.state, "draft")
self.assertEqual(len(self.test_agreement.previous_version_agreements_ids), 1)

def test_cron(self):
self.agreement_type.write(
{"review_user_id": self.env.user.id, "review_days": 0}
)
self.agreement_type.flush_recordset()
self.test_agreement.write({"agreement_type_id": self.agreement_type.id})
self.test_agreement.flush_recordset()
self.test_agreement.invalidate_recordset()
self.assertFalse(
self.env["mail.activity"].search_count(
[
("res_id", "=", self.test_agreement.id),
("res_model", "=", self.test_agreement._name),
]
)
)
self.env["agreement"]._alert_to_review_date()
self.assertFalse(
self.env["mail.activity"].search_count(
[
("res_id", "=", self.test_agreement.id),
("res_model", "=", self.test_agreement._name),
]
)
)
self.test_agreement.to_review_date = fields.Date.today()
self.env["agreement"]._alert_to_review_date()
self.assertTrue(
self.env["mail.activity"].search_count(
[
("res_id", "=", self.test_agreement.id),
("res_model", "=", self.test_agreement._name),
]
)
)

def test_partner_action(self):
action = self.test_agreement.partner_id.action_open_agreement()
self.assertIn(
self.test_agreement, self.env[action["res_model"]].search(action["domain"])
)
self.assertEqual(1, self.test_agreement.partner_id.agreements_count)
66 changes: 66 additions & 0 deletions agreement_legal/tests/test_agreement_appendix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)

from datetime import timedelta

from odoo import fields
from odoo.tests.common import TransactionCase


class TestAgreementAppendices(TransactionCase):
def setUp(self):
super().setUp()
self.test_customer = self.env["res.partner"].create({"name": "TestCustomer"})
self.agreement_type = self.env["agreement.type"].create(
{"name": "Test Agreement Type", "domain": "sale"}
)
self.test_agreement = self.env["agreement"].create(
{
"name": "TestAgreement",
"description": "Test",
"special_terms": "Test",
"partner_id": self.test_customer.id,
"start_date": fields.Date.today(),
"end_date": fields.Date.today() + timedelta(days=365),
}
)
self.test_appendices = self.env["agreement.appendix"].create(
{
"name": "TestAppendices",
"title": "Test",
"content": "Test",
"agreement_id": self.test_agreement.id,
}
)

# TEST 01: Set 'Field' for dynamic placeholder, test onchange method
def test_onchange_copyvalue(self):
appendix_01 = self.test_appendices
field_01 = self.env["ir.model.fields"].search(
[("model", "=", "agreement.appendix"), ("name", "=", "active")]
)
appendix_01.field_id = field_01.id
appendix_01.onchange_copyvalue()
self.assertEqual(appendix_01.copyvalue, "{{object.active or ''}}")

# TEST 02: Set related 'Field' for dynamic placeholder to
# test onchange method
def test_onchange_copyvalue2(self):
appendix_01 = self.test_appendices
field_01 = self.env["ir.model.fields"].search(
[("model", "=", "agreement.appendix"), ("name", "=", "agreement_id")]
)
sub_field_01 = self.env["ir.model.fields"].search(
[("model", "=", "agreement"), ("name", "=", "active")]
)
appendix_01.field_id = field_01.id
appendix_01.onchange_copyvalue()
self.assertEqual(appendix_01.sub_object_id.model, "agreement")
appendix_01.sub_model_object_field_id = sub_field_01.id
appendix_01.onchange_copyvalue()
self.assertEqual(appendix_01.copyvalue, "{{object.agreement_id.active or ''}}")

# TEST 03: Test Dynamic Field
def test_compute_dynamic_content(self):
appendix_01 = self.test_appendices
appendix_01.content = "{{object.name}}"
self.assertEqual(appendix_01.dynamic_content, "<p>TestAppendices</p>")
66 changes: 66 additions & 0 deletions agreement_legal/tests/test_agreement_clause.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)

from datetime import timedelta

from odoo import fields
from odoo.tests.common import TransactionCase


class TestAgreementClauses(TransactionCase):
def setUp(self):
super().setUp()
self.test_customer = self.env["res.partner"].create({"name": "TestCustomer"})
self.agreement_type = self.env["agreement.type"].create(
{"name": "Test Agreement Type", "domain": "sale"}
)
self.test_agreement = self.env["agreement"].create(
{
"name": "TestAgreement",
"description": "Test",
"special_terms": "Test",
"partner_id": self.test_customer.id,
"start_date": fields.Date.today(),
"end_date": fields.Date.today() + timedelta(days=365),
}
)
self.test_clause = self.env["agreement.clause"].create(
{
"name": "TestClause",
"title": "Test",
"content": "Test",
"agreement_id": self.test_agreement.id,
}
)

# TEST 01: Set 'Field' for dynamic placeholder, test onchange method
def test_onchange_copyvalue(self):
clause_01 = self.test_clause
field_01 = self.env["ir.model.fields"].search(
[("model", "=", "agreement.clause"), ("name", "=", "active")]
)
clause_01.field_id = field_01.id
clause_01.onchange_copyvalue()
self.assertEqual(clause_01.copyvalue, "{{object.active or ''}}")

# TEST 02: Set related 'Field' for dynamic placeholder to
# test onchange method
def test_onchange_copyvalue2(self):
clause_01 = self.test_clause
field_01 = self.env["ir.model.fields"].search(
[("model", "=", "agreement.clause"), ("name", "=", "agreement_id")]
)
sub_field_01 = self.env["ir.model.fields"].search(
[("model", "=", "agreement"), ("name", "=", "active")]
)
clause_01.field_id = field_01.id
clause_01.onchange_copyvalue()
self.assertEqual(clause_01.sub_object_id.model, "agreement")
clause_01.sub_model_object_field_id = sub_field_01.id
clause_01.onchange_copyvalue()
self.assertEqual(clause_01.copyvalue, "{{object.agreement_id.active or ''}}")

# TEST 03: Test Dynamic Field
def test_compute_dynamic_content(self):
clause_01 = self.test_clause
clause_01.content = "{{object.name}}"
self.assertEqual(clause_01.dynamic_content, "<p>TestClause</p>")
42 changes: 42 additions & 0 deletions agreement_legal/tests/test_agreement_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)

from datetime import timedelta

from odoo import fields
from odoo.tests.common import TransactionCase


class TestAgreementLine(TransactionCase):
def setUp(self):
super().setUp()
self.test_customer = self.env["res.partner"].create({"name": "TestCustomer"})
self.agreement_type = self.env["agreement.type"].create(
{"name": "Test Agreement Type", "domain": "sale"}
)
self.test_agreement = self.env["agreement"].create(
{
"name": "TestAgreement",
"description": "Test",
"special_terms": "Test",
"partner_id": self.test_customer.id,
"start_date": fields.Date.today(),
"end_date": fields.Date.today() + timedelta(days=365),
}
)
self.test_product1 = self.env["product.product"].create({"name": "TEST1"})
self.test_product2 = self.env["product.product"].create({"name": "TEST2"})
self.test_line = self.env["agreement.line"].create(
{
"product_id": self.test_product1.id,
"name": "Test",
"uom_id": 1,
"agreement_id": self.test_agreement.id,
}
)

# TEST 01: Set line product onchange method
def test_onchange_product_id(self):
line_01 = self.test_line
line_01.product_id = self.test_product2.id
line_01._onchange_product_id()
self.assertEqual(line_01.name, "TEST2")
66 changes: 66 additions & 0 deletions agreement_legal/tests/test_agreement_recital.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)

from datetime import timedelta

from odoo import fields
from odoo.tests.common import TransactionCase


class TestAgreementRectical(TransactionCase):
def setUp(self):
super().setUp()
self.test_customer = self.env["res.partner"].create({"name": "TestCustomer"})
self.agreement_type = self.env["agreement.type"].create(
{"name": "Test Agreement Type", "domain": "sale"}
)
self.test_agreement = self.env["agreement"].create(
{
"name": "TestAgreement",
"description": "Test",
"special_terms": "Test",
"partner_id": self.test_customer.id,
"start_date": fields.Date.today(),
"end_date": fields.Date.today() + timedelta(days=365),
}
)
self.test_recital = self.env["agreement.recital"].create(
{
"name": "TestRecital",
"title": "Test",
"content": "Test",
"agreement_id": self.test_agreement.id,
}
)

# TEST 01: Set 'Field' for dynamic placeholder, test onchange method
def test_onchange_copyvalue(self):
recital_01 = self.test_recital
field_01 = self.env["ir.model.fields"].search(
[("model", "=", "agreement.recital"), ("name", "=", "active")]
)
recital_01.field_id = field_01.id
recital_01.onchange_copyvalue()
self.assertEqual(recital_01.copyvalue, "{{object.active or ''}}")

# TEST 02: Set related 'Field' for dynamic placeholder to
# test onchange method
def test_onchange_copyvalue2(self):
recital_01 = self.test_recital
field_01 = self.env["ir.model.fields"].search(
[("model", "=", "agreement.recital"), ("name", "=", "agreement_id")]
)
sub_field_01 = self.env["ir.model.fields"].search(
[("model", "=", "agreement"), ("name", "=", "active")]
)
recital_01.field_id = field_01.id
recital_01.onchange_copyvalue()
self.assertEqual(recital_01.sub_object_id.model, "agreement")
recital_01.sub_model_object_field_id = sub_field_01.id
recital_01.onchange_copyvalue()
self.assertEqual(recital_01.copyvalue, "{{object.agreement_id.active or ''}}")

# TEST 03: Test Dynamic Field
def test_compute_dynamic_content(self):
recital_01 = self.test_recital
recital_01.content = "{{object.name}}"
self.assertEqual(recital_01.dynamic_content, "<p>TestRecital</p>")
66 changes: 66 additions & 0 deletions agreement_legal/tests/test_agreement_section.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)

from datetime import timedelta

from odoo import fields
from odoo.tests.common import TransactionCase


class TestAgreementSection(TransactionCase):
def setUp(self):
super().setUp()
self.test_customer = self.env["res.partner"].create({"name": "TestCustomer"})
self.agreement_type = self.env["agreement.type"].create(
{"name": "Test Agreement Type", "domain": "sale"}
)
self.test_agreement = self.env["agreement"].create(
{
"name": "TestAgreement",
"description": "Test",
"special_terms": "Test",
"partner_id": self.test_customer.id,
"start_date": fields.Date.today(),
"end_date": fields.Date.today() + timedelta(days=365),
}
)
self.test_section = self.env["agreement.section"].create(
{
"name": "TestSection",
"title": "Test",
"content": "Test",
"agreement_id": self.test_agreement.id,
}
)

# TEST 01: Set 'Field' for dynamic placeholder, test onchange method
def test_onchange_copyvalue(self):
section_01 = self.test_section
field_01 = self.env["ir.model.fields"].search(
[("model", "=", "agreement.section"), ("name", "=", "active")]
)
section_01.field_id = field_01.id
section_01.onchange_copyvalue()
self.assertEqual(section_01.copyvalue, "{{object.active or ''}}")

# TEST 02: Set related 'Field' for dynamic placeholder to
# test onchange method
def test_onchange_copyvalue2(self):
section_01 = self.test_section
field_01 = self.env["ir.model.fields"].search(
[("model", "=", "agreement.section"), ("name", "=", "agreement_id")]
)
sub_field_01 = self.env["ir.model.fields"].search(
[("model", "=", "agreement"), ("name", "=", "active")]
)
section_01.field_id = field_01.id
section_01.onchange_copyvalue()
self.assertEqual(section_01.sub_object_id.model, "agreement")
section_01.sub_model_object_field_id = sub_field_01.id
section_01.onchange_copyvalue()
self.assertEqual(section_01.copyvalue, "{{object.agreement_id.active or ''}}")

# TEST 03: Test Dynamic Field
def test_compute_dynamic_content(self):
section_01 = self.test_section
section_01.content = "{{object.name}}"
self.assertEqual(section_01.dynamic_content, "<p>TestSection</p>")
74 changes: 74 additions & 0 deletions agreement_legal/tests/test_create_agreement_wizard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)

from odoo.tests.common import TransactionCase


class TestCreateAgreementWizard(TransactionCase):
def setUp(self):
super().setUp()
self.agreement_type = self.env["agreement.type"].create(
{"name": "Test Agreement Type", "domain": "sale"}
)
# Create Agreement Template
self.agreement_template = self.env["agreement"].create(
{
"name": "Test Agreement Template",
"description": "Test",
"state": "active",
"agreement_type_id": self.agreement_type.id,
"is_template": True,
}
)
# Create Recital
self.env["agreement.recital"].create(
{
"name": "Test Recital",
"title": "Test",
"content": "Test",
"agreement_id": self.agreement_template.id,
}
)
# Create Section
self.section = self.env["agreement.section"].create(
{
"name": "Test Section",
"title": "Test",
"content": "Test",
"agreement_id": self.agreement_template.id,
}
)
# Create Clause
self.env["agreement.clause"].create(
{
"name": "Test Clause",
"title": "Test",
"content": "Test",
"agreement_id": self.agreement_template.id,
"section_id": self.section.id,
}
)
# Create Appendix
self.env["agreement.appendix"].create(
{
"name": "Test Appendices",
"title": "Test",
"content": "Test",
"agreement_id": self.agreement_template.id,
}
)

# Test create agreement from template
def test_create_agreement(self):
template = self.agreement_template
wizard = self.env["create.agreement.wizard"].create(
{"template_id": self.agreement_template.id, "name": "Test Agreement"}
)
res = wizard.create_agreement()
agreement = self.env[res["res_model"]].browse(res["res_id"])
self.assertEqual(agreement.template_id, template)
self.assertEqual(agreement.is_template, False)
self.assertEqual(agreement.recital_ids.name, template.recital_ids.name)
self.assertEqual(agreement.sections_ids.name, template.sections_ids.name)
self.assertEqual(agreement.clauses_ids.name, template.clauses_ids.name)
self.assertEqual(agreement.clauses_ids.section_id, agreement.sections_ids)
self.assertEqual(agreement.appendix_ids.name, template.appendix_ids.name)
622 changes: 622 additions & 0 deletions agreement_legal/views/agreement.xml

Large diffs are not rendered by default.

137 changes: 137 additions & 0 deletions agreement_legal/views/agreement_appendix.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<odoo>

<!-- Agreement Appendix List View-->
<record model="ir.ui.view" id="agreement_appendix_tree">
<field name="name">Agreement Appendix Tree</field>
<field name="model">agreement.appendix</field>
<field name="arch" type="xml">
<tree default_order="agreement_id, sequence">
<field name="sequence" widget="handle" />
<field name="name" />
<field name="title" />
<field name="agreement_id" />
<field name="active" column_invisible="1" />
</tree>
</field>
</record>

<!-- Agreement Appendix Form View -->
<record model="ir.ui.view" id="agreement_appendix_form">
<field name="name">Agreement Appendix Form</field>
<field name="model">agreement.appendix</field>
<field name="arch" type="xml">
<form string="Appendix">
<sheet>
<div class="oe_button_box" name="button_box" />
<widget
name="web_ribbon"
title="Archived"
bg_color="bg-danger"
invisible="active == True"
/>
<field name="active" invisible="1" />
<div class="oe_title">
<label for="name" class="oe_edit_only" />
<h1><field name="name" /></h1>
</div>
<group>
<group>
<field name="title" />
<field name="agreement_id" />
</group>
<group>
<field name="sequence" />
</group>
</group>
<field name="content" widget="html" />
<group class="oe_edit_only">
<group>
<field
name="field_id"
domain="[('model_id', '=', 'agreement.appendix'),
('ttype', '!=', 'one2many'),
('ttype', '!=', 'many2many')]"
/>
<field name="sub_object_id" readonly="1" />
<field
name="sub_model_object_field_id"
domain="[('model_id', '=', sub_object_id),
('ttype', '!=', 'one2many'),
('ttype', '!=', 'many2many')]"
required="sub_object_id != False"
readonly="sub_object_id == False"
/>
<field name="default_value" />
<field name="copyvalue" />
</group>
<p>
This section (on the left) allows you to add dynamic fields inside the content.
<ol>
<li>Select the appendix field</li>
<li>Select the sub-field</li>
<li>Enter the default value if the field is empty</li>
<li
>Copy and paste the placeholder expression in the content</li>
</ol>
</p>
</group>
</sheet>
</form>
</field>
</record>

<!-- Agreement Appendix Search View -->
<record model="ir.ui.view" id="agreement_appendix_search">
<field name="name">Agreement Appendix Search</field>
<field name="model">agreement.appendix</field>
<field name="arch" type="xml">
<search string="Appendix">
<field name="name" />
<filter
name="group_agreement"
icon="terp-partner"
context="{'group_by':'agreement_id'}"
/>
</search>
</field>
</record>

<!-- Actions opening views on models -->
<record model="ir.actions.act_window" id="agreement_appendix_action">
<field name="name">Appendices</field>
<field name="res_model">agreement.appendix</field>
<field name="view_mode">tree,form</field>
</record>

<!-- Agreement Appendix List View 2 (Call this view from agreement)-->
<record model="ir.ui.view" id="agreement_appendix_tree2">
<field name="name">Agreement Appendix Tree2</field>
<field name="model">agreement.appendix</field>
<field name="inherit_id" ref="agreement_appendix_tree" />
<field name="mode">primary</field>
<field name="priority">999</field>
<field name="arch" type="xml">
<tree position="attributes">
<attribute name="default_order">sequence</attribute>
</tree>
<field name="agreement_id" position="attributes">
<attribute name="column_invisible">1</attribute>
</field>
</field>
</record>

<!-- Agreement Appendix Form View 2 (Call this view from agreement)-->
<record model="ir.ui.view" id="agreement_appendix_form2">
<field name="name">Agreement Appendix Form2</field>
<field name="model">agreement.appendix</field>
<field name="inherit_id" ref="agreement_appendix_form" />
<field name="mode">primary</field>
<field name="priority">999</field>
<field name="arch" type="xml">
<field name="agreement_id" position="attributes">
<attribute name="invisible">1</attribute>
</field>
</field>
</record>

</odoo>
155 changes: 155 additions & 0 deletions agreement_legal/views/agreement_clause.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
<odoo>

<!-- Agreement Clause List View-->
<record model="ir.ui.view" id="partner_agreement_clause_list_view">
<field name="name">Agreement Clause List</field>
<field name="model">agreement.clause</field>
<field name="arch" type="xml">
<tree default_order="agreement_id, sequence">
<field name="sequence" widget="handle" />
<field name="name" />
<field name="title" />
<field name="agreement_id" />
<field name="section_id" />
<field name="active" column_invisible="1" />
</tree>
</field>
</record>

<!-- Agreement Clause Form View -->
<record model="ir.ui.view" id="partner_agreement_clause_form_view">
<field name="name">Agreement clause Form</field>
<field name="model">agreement.clause</field>
<field name="arch" type="xml">
<form string="Clause">
<sheet>
<div class="oe_button_box" name="button_box" />
<widget
name="web_ribbon"
title="Archived"
bg_color="bg-danger"
invisible="active == True"
/>
<field name="active" invisible="1" />
<div class="oe_title">
<label for="name" class="oe_edit_only" />
<h1><field name="name" /></h1>
</div>
<group>
<group>
<field name="title" />
<field name="agreement_id" />
<field
name="section_id"
domain="[('agreement_id', '=', agreement_id)]"
/>
</group>
<group>
<field name="sequence" />
</group>
</group>
<field name="content" widget="html" />
<group class="oe_edit_only">
<group>
<field
name="field_id"
domain="[('model_id', '=', 'agreement.clause'),
('ttype', '!=', 'one2many'),
('ttype', '!=', 'many2many')]"
/>
<field name="sub_object_id" readonly="1" />
<field
name="sub_model_object_field_id"
domain="[('model_id', '=', sub_object_id),
('ttype', '!=', 'one2many'),
('ttype', '!=', 'many2many')]"
readonly="sub_object_id == False"
required="sub_object_id != False"
/>
<field name="default_value" />
<field name="copyvalue" />
</group>
<p>
This section (on the left) allows you to add dynamic fields inside the content.
<ol>
<li>Select the clause field</li>
<li>Select the sub-field</li>
<li>Enter the default value if the field is empty</li>
<li
>Copy and paste the placeholder expression in the content</li>
</ol>
</p>
</group>
</sheet>
</form>
</field>
</record>

<!-- Agreement Clause Search View -->
<record model="ir.ui.view" id="agreement_clause_search_view">
<field name="name">Agreement Clause Search</field>
<field name="model">agreement.clause</field>
<field name="arch" type="xml">
<search string="Clause">
<field name="name" />
<filter
name="group_agreement"
icon="terp-partner"
context="{'group_by':'agreement_id'}"
/>
<filter
name="group_section"
icon="terp-partner"
context="{'group_by':'section_id'}"
/>
</search>
</field>
</record>

<!-- Actions opening views on models -->
<record model="ir.actions.act_window" id="partner_agreement_action_clause">
<field name="name">Clauses</field>
<field name="res_model">agreement.clause</field>
<field name="view_mode">tree,form</field>
</record>

<!-- Agreement Clause List View 2 (Call this view from agreement)-->
<record model="ir.ui.view" id="partner_agreement_clause_list_view2">
<field name="name">Agreement Clause List2</field>
<field name="model">agreement.clause</field>
<field name="inherit_id" ref="partner_agreement_clause_list_view" />
<field name="mode">primary</field>
<field name="priority">999</field>
<field name="arch" type="xml">
<tree position="attributes">
<attribute name="default_order">sequence</attribute>
</tree>
<field name="agreement_id" position="attributes">
<attribute name="column_invisible">1</attribute>
</field>
</field>
</record>

<!-- Agreement Clause Form View 2 (Call this view from agreement)-->
<record model="ir.ui.view" id="partner_agreement_clause_form_view2">
<field name="name">Agreement Clause Form2</field>
<field name="model">agreement.clause</field>
<field name="inherit_id" ref="partner_agreement_clause_form_view" />
<field name="mode">primary</field>
<field name="priority">999</field>
<field name="arch" type="xml">
<field name="agreement_id" position="after">
<field name="temp_agreement_id" invisible="1" />
</field>
<field name="agreement_id" position="attributes">
<attribute name="invisible">1</attribute>
</field>
<field name="section_id" position="attributes">
<attribute
name="domain"
>[('agreement_id', '=', temp_agreement_id)]</attribute>
</field>
</field>
</record>

</odoo>
137 changes: 137 additions & 0 deletions agreement_legal/views/agreement_recital.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<odoo>

<!-- Agreement Recital List View-->
<record model="ir.ui.view" id="agreement_recital_tree">
<field name="name">Agreement Recital Tree</field>
<field name="model">agreement.recital</field>
<field name="arch" type="xml">
<tree default_order="agreement_id, sequence">
<field name="sequence" widget="handle" />
<field name="name" />
<field name="title" />
<field name="agreement_id" />
<field name="active" column_invisible="1" />
</tree>
</field>
</record>

<!-- Agreement Recital Form View -->
<record model="ir.ui.view" id="agreement_recital_form">
<field name="name">Agreement Recital Form</field>
<field name="model">agreement.recital</field>
<field name="arch" type="xml">
<form string="Recital">
<sheet>
<div class="oe_button_box" name="button_box" />
<widget
name="web_ribbon"
title="Archived"
bg_color="bg-danger"
invisible="active == True"
/>
<field name="active" invisible="1" />
<div class="oe_title">
<label for="name" class="oe_edit_only" />
<h1><field name="name" /></h1>
</div>
<group>
<group>
<field name="title" />
<field name="agreement_id" />
</group>
<group>
<field name="sequence" />
</group>
</group>
<field name="content" widget="html" />
<group class="oe_edit_only">
<group>
<field
name="field_id"
domain="[('model_id', '=', 'agreement.recital'),
('ttype', '!=', 'one2many'),
('ttype', '!=', 'many2many')]"
/>
<field name="sub_object_id" readonly="1" />
<field
name="sub_model_object_field_id"
domain="[('model_id', '=', sub_object_id),
('ttype', '!=', 'one2many'),
('ttype', '!=', 'many2many')]"
readonly="sub_object_id == False"
required="sub_object_id != False"
/>
<field name="default_value" />
<field name="copyvalue" />
</group>
<p>
This section (on the left) allows you to add dynamic fields inside the content.
<ol>
<li>Select the recital field</li>
<li>Select the sub-field</li>
<li>Enter the default value if the field is empty</li>
<li
>Copy and paste the placeholder expression in the content</li>
</ol>
</p>
</group>
</sheet>
</form>
</field>
</record>

<!-- Agreement Recital Search View -->
<record model="ir.ui.view" id="agreement_recital_search">
<field name="name">Agreement Recital Search</field>
<field name="model">agreement.recital</field>
<field name="arch" type="xml">
<search string="Recitals">
<field name="name" />
<filter
name="group_agreement"
icon="terp-partner"
context="{'group_by':'agreement_id'}"
/>
</search>
</field>
</record>

<!-- Actions opening views on models -->
<record model="ir.actions.act_window" id="agreement_recital_action">
<field name="name">Recitals</field>
<field name="res_model">agreement.recital</field>
<field name="view_mode">tree,form</field>
</record>

<!-- Agreement Recital List View 2 (Call this view from agreement)-->
<record model="ir.ui.view" id="agreement_recital_tree2">
<field name="name">Agreement Recital Tree2</field>
<field name="model">agreement.recital</field>
<field name="inherit_id" ref="agreement_recital_tree" />
<field name="mode">primary</field>
<field name="priority">999</field>
<field name="arch" type="xml">
<tree position="attributes">
<attribute name="default_order">sequence</attribute>
</tree>
<field name="agreement_id" position="attributes">
<attribute name="column_invisible">1</attribute>
</field>
</field>
</record>

<!-- Agreement Recital Form View 2 (Call this view from agreement)-->
<record model="ir.ui.view" id="agreement_recital_form2">
<field name="name">Agreement Recital Form2</field>
<field name="model">agreement.recital</field>
<field name="inherit_id" ref="agreement_recital_form" />
<field name="mode">primary</field>
<field name="priority">999</field>
<field name="arch" type="xml">
<field name="agreement_id" position="attributes">
<attribute name="invisible">1</attribute>
</field>
</field>
</record>

</odoo>
156 changes: 156 additions & 0 deletions agreement_legal/views/agreement_section.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<odoo>

<!-- Agreement Sections List View-->
<record model="ir.ui.view" id="partner_agreement_section_list_view">
<field name="name">Agreement Section List</field>
<field name="model">agreement.section</field>
<field name="arch" type="xml">
<tree default_order="agreement_id, sequence">
<field name="sequence" widget="handle" />
<field name="name" />
<field name="title" />
<field name="agreement_id" />
<field name="active" column_invisible="1" />
</tree>
</field>
</record>

<!-- Agreement Sections Form View -->
<record model="ir.ui.view" id="partner_agreement_section_form_view">
<field name="name">Agreement Section Form</field>
<field name="model">agreement.section</field>
<field name="arch" type="xml">
<form string="Section">
<sheet>
<div class="oe_button_box" name="button_box" />
<widget
name="web_ribbon"
title="Archived"
bg_color="bg-danger"
invisible="active == True"
/>
<field name="active" invisible="1" />
<div class="oe_title">
<label for="name" class="oe_edit_only" />
<h1><field name="name" /></h1>
</div>
<group>
<group>
<field name="title" />
<field name="agreement_id" />
</group>
<group>
<field name="sequence" />
</group>
</group>
<notebook>
<page string="Content">
<field name='content' nolabel="1" />
<group class="oe_edit_only">
<group>
<field
name="field_id"
domain="[('model_id', '=', 'agreement.section'),
('ttype', '!=', 'one2many'),
('ttype', '!=', 'many2many')]"
/>
<field name="sub_object_id" readonly="1" />
<field
name="sub_model_object_field_id"
domain="[('model_id', '=', sub_object_id),
('ttype', '!=', 'one2many'),
('ttype', '!=', 'many2many')]"
readonly="sub_object_id == False"
required="sub_object_id != False"
/>
<field name="default_value" />
<field name="copyvalue" />
</group>
<p>
This section (on the left) allows you to add dynamic fields inside the content.
<ol>
<li>Select the section field</li>
<li>Select the sub-field</li>
<li
>Enter the default value if the field is empty</li>
<li
>Copy and paste the placeholder expression in the content</li>
</ol>
</p>
</group>
</page>
<page string="Clauses">
<field
name="clauses_ids"
nolabel="1"
context="{'default_section_id': id, 'default_agreement_id': agreement_id}"
>
<tree>
<field name="sequence" widget="handle" />
<field name="name" />
<field name="title" />
</tree>
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>

<!-- Agreement Section Search View -->
<record model="ir.ui.view" id="partner_agreement_section_search_view">
<field name="name">Agreement Section Search</field>
<field name="model">agreement.section</field>
<field name="arch" type="xml">
<search string="Section">
<field name="name" />
<filter
name="group_agreement"
string="Agreements"
icon="terp-partner"
context="{'group_by':'agreement_id'}"
/>
</search>
</field>
</record>

<!-- Actions opening views on models -->
<record model="ir.actions.act_window" id="partner_agreement_action_section">
<field name="name">Agreement Sections</field>
<field name="res_model">agreement.section</field>
<field name="view_mode">tree,form</field>
</record>

<!-- Agreement Sections List View 2 (Call this view from agreement)-->
<record model="ir.ui.view" id="partner_agreement_section_list_view2">
<field name="name">Agreement Section List2</field>
<field name="model">agreement.section</field>
<field name="inherit_id" ref="partner_agreement_section_list_view" />
<field name="mode">primary</field>
<field name="priority">999</field>
<field name="arch" type="xml">
<tree position="attributes">
<attribute name="default_order">sequence</attribute>
</tree>
<field name="agreement_id" position="attributes">
<attribute name="column_invisible">1</attribute>
</field>
</field>
</record>

<!-- Agreement Sections Form View 2 (Call this view from agreement)-->
<record model="ir.ui.view" id="partner_agreement_section_form_view2">
<field name="name">Agreement Section Form2</field>
<field name="model">agreement.section</field>
<field name="inherit_id" ref="partner_agreement_section_form_view" />
<field name="mode">primary</field>
<field name="priority">999</field>
<field name="arch" type="xml">
<field name="agreement_id" position="attributes">
<attribute name="invisible">1</attribute>
</field>
</field>
</record>

</odoo>
77 changes: 77 additions & 0 deletions agreement_legal/views/agreement_stages.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<odoo>

<!-- Agreement Stage List View-->
<record model="ir.ui.view" id="partner_agreement_stage_list_view">
<field name="name">Agreement Stage List</field>
<field name="model">agreement.stage</field>
<field name="arch" type="xml">
<tree default_order="sequence, name">
<field name="sequence" widget="handle" />
<field name="name" />
<field name="stage_type" />
<field name="active" widget="boolean_toggle" />
</tree>
</field>
</record>

<!-- Agreement Stage Form View -->
<record model="ir.ui.view" id="partner_agreement_stage_form_view">
<field name="name">Agreement Stage Form</field>
<field name="model">agreement.stage</field>
<field name="arch" type="xml">
<form string="Agreements Stage Form">
<sheet>
<widget
name="web_ribbon"
title="Archived"
bg_color="bg-danger"
invisible="active == True"
/>
<div class="oe_title">
<label for="name" class="oe_edit_only" />
<h1><field name="name" /></h1>
</div>
<group>
<field name="sequence" />
<field name="stage_type" />
<field name="fold" />
<field name="readonly" />
<field name="active" invisible="1" />
</group>
</sheet>
</form>
</field>
</record>

<!-- Agreement Stage Search View -->
<record model="ir.ui.view" id="partner_agreement_stage_search_view">
<field name="name">Agreement Stage Search</field>
<field name="model">agreement.stage</field>
<field name="arch" type="xml">
<search string="Agreements Stage Search">
<field name="name" />
<separator />
<filter
name="archived"
string="Archived"
domain="[('active', '=', False)]"
/>
<group name="groupby">
<filter
name="type_groupby"
string="Type"
context="{'group_by': 'stage_type'}"
/>
</group>
</search>
</field>
</record>

<!-- Actions opening views on models -->
<record model="ir.actions.act_window" id="partner_agreement_action_stage">
<field name="name">Agreement Stage</field>
<field name="res_model">agreement.stage</field>
<field name="view_mode">tree,form</field>
</record>

</odoo>
Loading