Merge PR #669 into 14.0

Signed-off-by simahawk
This commit is contained in:
OCA-git-bot
2021-07-01 06:39:11 +00:00
66 changed files with 17483 additions and 0 deletions

142
agreement_legal/README.rst Normal file
View File

@@ -0,0 +1,142 @@
================
Agreements Legal
================
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |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%2Fcontract-lightgray.png?logo=github
:target: https://github.com/OCA/contract/tree/12.0/agreement_legal
:alt: OCA/contract
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/contract-12-0/contract-12-0-agreement_legal
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/110/12.0
:alt: Try me on Runbot
|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
* Define Field using widget domain but having partial_use option true:
* For Ex:
* <field name="field_domain" widget="domain" nolabel="1"
* options="{'model': 'agreement.recital',
* 'partial_use': True}"/>
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/contract/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
`feedback <https://github.com/OCA/contract/issues/new?body=module:%20agreement_legal%0Aversion:%2012.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>
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/contract <https://github.com/OCA/contract/tree/12.0/agreement_legal>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@@ -0,0 +1,4 @@
# Copyright (C) 2018 - TODAY, Pavlov Media
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import models

View File

@@ -0,0 +1,43 @@
# 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/contract",
"category": "Partner",
"license": "AGPL-3",
"version": "14.0.1.0.0",
"depends": ["contacts", "agreement", "product"],
"data": [
"data/ir_sequence.xml",
"data/module_category.xml",
"data/agreement_stage.xml",
"data/agreement_type.xml",
"data/demo.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/agreement_renewaltype.xml",
"views/agreement_increasetype.xml",
"views/res_partner.xml",
"views/agreement.xml",
"views/menu.xml",
],
"demo": ["demo/demo.xml"],
"application": True,
"development_status": "Beta",
"maintainers": ["max3903", "ygol"],
}

View File

@@ -0,0 +1,57 @@
<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_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>

View File

@@ -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>

View File

@@ -0,0 +1,123 @@
<!--
© 2019 Ygol Internetwork (yves@ygol.com)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo noupdate="1">
<function name="write" model="ir.model.data">
<function name="search" model="ir.model.data">
<value eval="[('module', '=', 'agreement'), ('name', '=', 'market1')]" />
</function>
<value eval="{'noupdate': False}" />
</function>
<record id="agreement.market1" model="agreement">
<field name="description">Hardware IT (C2C-IT0042)</field>
<field name="agreement_type_id" ref="agreement_type_agreement" />
</record>
<function name="write" model="ir.model.data">
<function name="search" model="ir.model.data">
<value eval="[('module', '=', 'agreement'), ('name', '=', 'market1')]" />
</function>
<value eval="{'noupdate': True}" />
</function>
<function name="write" model="ir.model.data">
<function name="search" model="ir.model.data">
<value eval="[('module', '=', 'agreement'), ('name', '=', 'market2')]" />
</function>
<value eval="{'noupdate': False}" />
</function>
<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>
<function name="write" model="ir.model.data">
<function name="search" model="ir.model.data">
<value eval="[('module', '=', 'agreement'), ('name', '=', 'market2')]" />
</function>
<value eval="{'noupdate': True}" />
</function>
<function name="write" model="ir.model.data">
<function name="search" model="ir.model.data">
<value eval="[('module', '=', 'agreement'), ('name', '=', 'market3')]" />
</function>
<value eval="{'noupdate': False}" />
</function>
<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>
<function name="write" model="ir.model.data">
<function name="search" model="ir.model.data">
<value eval="[('module', '=', 'agreement'), ('name', '=', 'market3')]" />
</function>
<value eval="{'noupdate': True}" />
</function>
<function name="write" model="ir.model.data">
<function name="search" model="ir.model.data">
<value eval="[('module', '=', 'agreement'), ('name', '=', 'market4')]" />
</function>
<value eval="{'noupdate': False}" />
</function>
<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>
<function name="write" model="ir.model.data">
<function name="search" model="ir.model.data">
<value eval="[('module', '=', 'agreement'), ('name', '=', 'market4')]" />
</function>
<value eval="{'noupdate': True}" />
</function>
<function name="write" model="ir.model.data">
<function name="search" model="ir.model.data">
<value eval="[('module', '=', 'agreement'), ('name', '=', 'market5')]" />
</function>
<value eval="{'noupdate': False}" />
</function>
<record id="agreement.market5" model="agreement">
<field name="description">SIP Phones supply (BUY-VOIP012)</field>
<field name="agreement_type_id" ref="agreement_type_loi" />
</record>
<function name="write" model="ir.model.data">
<function name="search" model="ir.model.data">
<value eval="[('module', '=', 'agreement'), ('name', '=', 'market5')]" />
</function>
<value eval="{'noupdate': True}" />
</function>
<function name="write" model="ir.model.data">
<function name="search" model="ir.model.data">
<value eval="[('module', '=', 'agreement'), ('name', '=', 'market6')]" />
</function>
<value eval="{'noupdate': False}" />
</function>
<record id="agreement.market6" model="agreement">
<field name="description">SIP-ISDN gateways (BUY-VOIP013)</field>
<field name="agreement_type_id" ref="agreement_type_contract" />
</record>
<function name="write" model="ir.model.data">
<function name="search" model="ir.model.data">
<value eval="[('module', '=', 'agreement'), ('name', '=', 'market6')]" />
</function>
<value eval="{'noupdate': True}" />
</function>
</odoo>

View File

@@ -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>

View File

@@ -0,0 +1,8 @@
<odoo>
<record id="agreement" model="ir.module.category">
<field name="name">Agreement</field>
<field name="sequence">80</field>
</record>
</odoo>

View File

@@ -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>

File diff suppressed because it is too large Load Diff

1768
agreement_legal/i18n/es.po Normal file

File diff suppressed because it is too large Load Diff

2004
agreement_legal/i18n/fr.po Normal file

File diff suppressed because it is too large Load Diff

1772
agreement_legal/i18n/pt.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import (
res_config_settings,
agreement_stage,
agreement,
agreement_appendix,
agreement_clause,
agreement_line,
agreement_recital,
agreement_section,
agreement_type,
agreement_subtype,
res_partner,
product_template,
agreement_renewaltype,
agreement_increasetype,
)

View File

@@ -0,0 +1,416 @@
# 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 Agreement(models.Model):
_inherit = "agreement"
name = fields.Char(string="Title", required=True)
version = fields.Integer(
string="Version",
default=1,
copy=False,
help="The versions are used to keep track of document history and "
"previous versions can be referenced.",
)
revision = fields.Integer(
string="Revision",
default=0,
copy=False,
help="The revision will increase with every save event.",
)
description = fields.Text(
string="Description", tracking=True, help="Description of the agreement"
)
dynamic_description = fields.Text(
compute="_compute_dynamic_description",
string="Dynamic Description",
help="Compute dynamic description",
)
start_date = fields.Date(
string="Start Date", tracking=True, help="When the agreement starts."
)
end_date = fields.Date(
string="End Date", tracking=True, help="When the agreement ends."
)
color = fields.Integer(string="Color")
active = fields.Boolean(
string="Active",
default=True,
help="If unchecked, it will allow you to hide the agreement without "
"removing it.",
)
company_signed_date = fields.Date(
string="Signed on",
tracking=True,
help="Date the contract was signed by Company.",
)
partner_signed_date = fields.Date(
string="Signed on (Partner)",
tracking=True,
help="Date the contract was signed by the Partner.",
)
term = fields.Integer(
string="Term (Months)",
tracking=True,
help="Number of months this agreement/contract is in effect with the "
"partner.",
)
expiration_notice = fields.Integer(
string="Exp. Notice (Days)",
tracking=True,
help="Number of Days before expiration to be notified.",
)
change_notice = fields.Integer(
string="Change Notice (Days)",
tracking=True,
help="Number of Days to be notified before changes.",
)
special_terms = fields.Text(
string="Special Terms",
tracking=True,
help="Any terms that you have agreed to and want to track on the "
"agreement/contract.",
)
dynamic_special_terms = fields.Text(
compute="_compute_dynamic_special_terms",
string="Dynamic Special Terms",
help="Compute dynamic special terms",
)
code = fields.Char(
string="Reference",
required=True,
default=lambda self: _("New"),
tracking=True,
copy=False,
help="ID used for internal contract tracking.",
)
increase_type_id = fields.Many2one(
"agreement.increasetype",
string="Increase Type",
tracking=True,
help="The amount that certain rates may increase.",
)
termination_requested = fields.Date(
string="Termination Requested Date",
tracking=True,
help="Date that a request for termination was received.",
)
termination_date = fields.Date(
string="Termination Date",
tracking=True,
help="Date that the contract was terminated.",
)
reviewed_date = fields.Date(string="Reviewed Date", tracking=True)
reviewed_user_id = fields.Many2one("res.users", string="Reviewed By", tracking=True)
approved_date = fields.Date(string="Approved Date", tracking=True)
approved_user_id = fields.Many2one("res.users", string="Approved By", tracking=True)
currency_id = fields.Many2one("res.currency", string="Currency")
partner_id = fields.Many2one(
"res.partner",
string="Partner",
required=False,
copy=True,
help="The customer or vendor this agreement is related to.",
)
partner_contact_id = fields.Many2one(
"res.partner",
string="Partner Contact",
copy=True,
help="The primary partner contact (If Applicable).",
)
partner_contact_phone = fields.Char(
related="partner_contact_id.phone", string="Partner Phone"
)
partner_contact_email = fields.Char(
related="partner_contact_id.email", string="Partner Email"
)
company_contact_id = fields.Many2one(
"res.partner",
string="Company Contact",
copy=True,
help="The primary contact in the company.",
)
company_contact_phone = fields.Char(
related="company_contact_id.phone", string="Phone"
)
company_contact_email = fields.Char(
related="company_contact_id.email", string="Email"
)
use_parties_content = fields.Boolean(
string="Use parties content", help="Use custom content for parties"
)
company_partner_id = fields.Many2one(
related="company_id.partner_id", string="Company's Partner"
)
def _get_default_parties(self):
deftext = """
<h3>Company Information</h3>
<p>
${object.company_id.partner_id.name or ''}.<br>
${object.company_id.partner_id.street or ''} <br>
${object.company_id.partner_id.state_id.code or ''}
${object.company_id.partner_id.zip or ''}
${object.company_id.partner_id.city or ''}<br>
${object.company_id.partner_id.country_id.name or ''}.<br><br>
Represented by <b>${object.company_contact_id.name or ''}.</b>
</p>
<p></p>
<h3>Partner Information</h3>
<p>
${object.partner_id.name or ''}.<br>
${object.partner_id.street or ''} <br>
${object.partner_id.state_id.code or ''}
${object.partner_id.zip or ''} ${object.partner_id.city or ''}<br>
${object.partner_id.country_id.name or ''}.<br><br>
Represented by <b>${object.partner_contact_id.name or ''}.</b>
</p>
"""
return deftext
parties = fields.Html(
string="Parties",
tracking=True,
default=_get_default_parties,
help="Parties of the agreement",
)
dynamic_parties = fields.Html(
compute="_compute_dynamic_parties",
string="Dynamic Parties",
help="Compute dynamic parties",
)
agreement_type_id = fields.Many2one(
tracking=True,
)
agreement_subtype_id = fields.Many2one(
"agreement.subtype",
string="Agreement Sub-type",
tracking=True,
help="Select the sub-type of this agreement. Sub-Types are related to "
"agreement types.",
)
product_ids = fields.Many2many("product.template", string="Products & Services")
assigned_user_id = fields.Many2one(
"res.users",
string="Assigned To",
tracking=True,
help="Select the user who manages this agreement.",
)
company_signed_user_id = fields.Many2one(
"res.users",
string="Signed By",
tracking=True,
help="The user at our company who authorized/signed the agreement or "
"contract.",
)
partner_signed_user_id = fields.Many2one(
"res.partner",
string="Signed By (Partner)",
tracking=True,
help="Contact on the account that signed the agreement/contract.",
)
parent_agreement_id = fields.Many2one(
"agreement",
string="Parent Agreement",
help="Link this agreement to a parent agreement. For example if this "
"agreement is an amendment to another agreement. This list will "
"only show other agreements related to the same account.",
)
renewal_type_id = fields.Many2one(
"agreement.renewaltype",
string="Renewal Type",
tracking=True,
help="Describes what happens after the contract expires.",
)
recital_ids = fields.One2many(
"agreement.recital", "agreement_id", string="Recitals", copy=True
)
sections_ids = fields.One2many(
"agreement.section", "agreement_id", string="Sections", copy=True
)
clauses_ids = fields.One2many("agreement.clause", "agreement_id", string="Clauses")
appendix_ids = fields.One2many(
"agreement.appendix", "agreement_id", string="Appendices", copy=True
)
previous_version_agreements_ids = fields.One2many(
"agreement",
"parent_agreement_id",
string="Previous Versions",
copy=False,
domain=[("active", "=", False)],
)
child_agreements_ids = fields.One2many(
"agreement",
"parent_agreement_id",
string="Child Agreements",
copy=False,
domain=[("active", "=", True)],
)
line_ids = fields.One2many(
"agreement.line", "agreement_id", string="Products/Services", copy=False
)
state = fields.Selection(
[("draft", "Draft"), ("active", "Active"), ("inactive", "Inactive")],
default="draft",
tracking=True,
)
notification_address_id = fields.Many2one(
"res.partner",
string="Notification Address",
help="The address to send notificaitons to, if different from "
"customer address.(Address Type = Other)",
)
signed_contract_filename = fields.Char(string="Filename")
signed_contract = fields.Binary(string="Signed Document", tracking=True)
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(
string="Default Value",
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.""",
)
# compute the dynamic content for mako expression
def _compute_dynamic_description(self):
MailTemplates = self.env["mail.template"]
for agreement in self:
lang = agreement.partner_id.lang or "en_US"
description = MailTemplates.with_context(lang=lang)._render_template(
agreement.description, "agreement", [agreement.id]
)
des = ""
for i in description:
des += description[i]
agreement.dynamic_description = des
def _compute_dynamic_parties(self):
MailTemplates = self.env["mail.template"]
for agreement in self:
lang = agreement.partner_id.lang or "en_US"
parties = MailTemplates.with_context(lang=lang)._render_template(
agreement.parties, "agreement", [agreement.id]
)
agreement.dynamic_parties = parties
def _compute_dynamic_special_terms(self):
MailTemplates = self.env["mail.template"]
for agreement in self:
lang = agreement.partner_id.lang or "en_US"
special_terms = MailTemplates.with_context(lang=lang)._render_template(
agreement.special_terms, "agreement", [agreement.id]
)
agreement.dynamic_special_terms = special_terms
@api.onchange("field_id", "sub_model_object_field_id", "default_value")
def onchange_copyvalue(self):
self.sub_object_id = False
self.copyvalue = False
self.sub_object_id = 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 "''",
)
# Used for Kanban grouped_by view
@api.model
def _read_group_stage_ids(self, stages, domain, order):
stage_ids = self.env["agreement.stage"].search(
[("stage_type", "=", "agreement")]
)
return stage_ids
stage_id = fields.Many2one(
"agreement.stage",
string="Stage",
group_expand="_read_group_stage_ids",
help="Select the current stage of the agreement.",
tracking=True,
index=True,
)
# Create New Version Button
def create_new_version(self):
for rec in self:
if not rec.state == "draft":
# Make sure status is draft
rec.state = "draft"
default_vals = {
"name": "{} - OLD VERSION".format(rec.name),
"active": False,
"parent_agreement_id": rec.id,
"version": rec.version,
"code": rec.code + "-V" + str(rec.version),
}
# Make a current copy and mark it as old
rec.copy(default=default_vals)
# Increment the Version
rec.version = rec.version + 1
def create_new_agreement(self):
self.ensure_one()
default_vals = {
"name": "New",
"active": True,
"version": 1,
"revision": 0,
"state": "draft",
"stage_id": self.env.ref("agreement_legal.agreement_stage_new").id,
}
res = self.copy(default=default_vals)
res.sections_ids.mapped("clauses_ids").write({"agreement_id": res.id})
return {
"res_model": "agreement",
"type": "ir.actions.act_window",
"view_mode": "form",
"view_type": "form",
"res_id": res.id,
}
@api.model
def create(self, vals):
if vals.get("code", _("New")) == _("New"):
vals["code"] = self.env["ir.sequence"].next_by_code("agreement") or _("New")
if not vals.get("stage_id"):
vals["stage_id"] = self.env.ref("agreement_legal.agreement_stage_new").id
return super().create(vals)
# Increments the revision on each save action
def write(self, vals):
vals["revision"] = self.revision + 1
return super().write(vals)

View File

@@ -0,0 +1,98 @@
# 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(string="Name", required=True)
title = fields.Char(
string="Title",
required=True,
help="The title is displayed on the PDF. The name is not.",
)
sequence = fields.Integer(string="Sequence", default=10)
content = fields.Html(string="Content")
dynamic_content = fields.Html(
compute="_compute_dynamic_content",
string="Dynamic Content",
help="compute dynamic Content",
)
agreement_id = fields.Many2one("agreement", string="Agreement", ondelete="cascade")
active = fields.Boolean(
string="Active",
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(
string="Default Value",
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
self.sub_object_id = 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 mako 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.dynamic_content = content

View File

@@ -0,0 +1,97 @@
# 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(string="Name", required=True)
title = fields.Char(
string="Title", help="The title is displayed on the PDF. The name is not."
)
sequence = fields.Integer(string="Sequence")
agreement_id = fields.Many2one("agreement", string="Agreement", ondelete="cascade")
section_id = fields.Many2one(
"agreement.section", string="Section", ondelete="cascade"
)
content = fields.Html(string="Clause Content")
dynamic_content = fields.Html(
compute="_compute_dynamic_content",
string="Dynamic Content",
help="compute dynamic Content",
)
active = fields.Boolean(
string="Active",
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(
string="Default Value",
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
self.sub_object_id = 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 mako 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.dynamic_content = content

View File

@@ -0,0 +1,24 @@
# Copyright (C) 2018 - TODAY, Pavlov Media
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
# Main Agreement Increase Type Records Model
class AgreementIncreaseType(models.Model):
_name = "agreement.increasetype"
_description = "Agreement Increase Type"
# General
name = fields.Char(
string="Title",
required=True,
help="Increase types describe any increases that may happen during "
"the contract.",
)
description = fields.Text(
string="Description", required=True, help="Description of the renewal type."
)
increase_percent = fields.Integer(
string="Increase Percentage", help="Percentage that the amount will increase."
)

View File

@@ -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

View File

@@ -0,0 +1,94 @@
# 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(string="Name", required=True)
title = fields.Char(
string="Title",
help="The title is displayed on the PDF." "The name is not.",
)
sequence = fields.Integer(string="Sequence", default=10)
content = fields.Html(string="Content")
dynamic_content = fields.Html(
compute="_compute_dynamic_content",
string="Dynamic Content",
help="compute dynamic Content",
)
agreement_id = fields.Many2one("agreement", string="Agreement", ondelete="cascade")
active = fields.Boolean(
string="Active",
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(
string="Default Value",
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
self.sub_object_id = 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 mako 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.dynamic_content = content

View File

@@ -0,0 +1,21 @@
# Copyright (C) 2018 - TODAY, Pavlov Media
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
# Main Agreement Section Records Model
class AgreementRenewalType(models.Model):
_name = "agreement.renewaltype"
_description = "Agreement Renewal Type"
# General
name = fields.Char(
string="Title",
required=True,
help="Renewal types describe what happens after the "
"agreement/contract expires.",
)
description = fields.Text(
string="Description", required=True, help="Description of the renewal type."
)

View File

@@ -0,0 +1,96 @@
# 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(string="Name", required=True)
title = fields.Char(
string="Title", help="The title is displayed on the PDF. The name is not."
)
sequence = fields.Integer(string="Sequence")
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",
string="Dynamic Content",
help="compute dynamic Content",
)
active = fields.Boolean(
string="Active",
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(
string="Default Value",
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
self.sub_object_id = 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 mako 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.dynamic_content = content

View File

@@ -0,0 +1,24 @@
# Copyright (C) 2018 - TODAY, Pavlov Media
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
# Main Agreement Section Records Model
class AgreementStage(models.Model):
_name = "agreement.stage"
_description = "Agreement Stages"
_order = "sequence"
# General
name = fields.Char(string="Stage Name", required=True)
description = fields.Text(string="Description", required=False)
sequence = fields.Integer(string="Sequence", 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
)

View File

@@ -0,0 +1,12 @@
# Copyright (C) 2018 - TODAY, Pavlov Media
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
# Main Agreement Status Records Model
class AgreementStatus(models.Model):
_name = "agreement.type"
# General
name = fields.Char(string="Title", required=True)

View File

@@ -0,0 +1,12 @@
# 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="Name", required=True)
agreement_type_id = fields.Many2one("agreement.type", string="Agreement Type")

View File

@@ -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 AgreementType(models.Model):
_inherit = "agreement.type"
_description = "Agreement Types"
agreement_subtypes_ids = fields.One2many(
"agreement.subtype", "agreement_type_id", string="Subtypes"
)

View File

@@ -0,0 +1,10 @@
# Copyright (C) 2018 - TODAY, Pavlov Media
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class Product(models.Model):
_inherit = "product.template"
agreements_ids = fields.Many2many("agreement", string="Agreements")

View File

@@ -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."
)

View File

@@ -0,0 +1,10 @@
# Copyright (C) 2018 - TODAY, Pavlov Media
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class Partner(models.Model):
_inherit = "res.partner"
agreement_ids = fields.One2many("agreement", "partner_id", string="Agreements")

View File

@@ -0,0 +1,6 @@
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

View File

@@ -0,0 +1,6 @@
* 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>

View File

@@ -0,0 +1,5 @@
The development of this module has been financially supported by:
* Pavlov Media
* Open Source Integrators
* Yves Goldberg

View File

@@ -0,0 +1,9 @@
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.

View File

@@ -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)

View File

@@ -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

View File

@@ -0,0 +1,179 @@
<odoo>
<record id="partner_agreement_contract_document" model="ir.actions.report">
<field name="name">Agreement</field>
<field name="model">agreement</field>
<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>
</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="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>
</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>

View File

@@ -0,0 +1,29 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
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_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,0
access_agreement_recital_manager,recital manager,model_agreement_recital,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,0
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,0
access_agreement_clause_manager,clause manager,model_agreement_clause,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,0
access_agreement_appendix_manager,appendix manager,model_agreement_appendix,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,0
access_agreement_line_manager,agreement line manager,model_agreement_line,group_agreement_manager,1,1,1,1
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_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
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_renewaltype_readonly,renewaltype readonly,model_agreement_renewaltype,group_agreement_readonly,1,0,0,0
access_agreement_renewaltype_manager,renewaltype manager,model_agreement_renewaltype,group_agreement_manager,1,1,1,1
access_agreement_increasetype_readonly,increasetype readonly,model_agreement_increasetype,group_agreement_readonly,1,0,0,0
access_agreement_increasetype_manager,increasetype manager,model_agreement_increasetype,group_agreement_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_agreement_readonly agreement readonly model_agreement group_agreement_readonly 1 0 0 0
3 access_agreement_allusers agreement all users model_agreement group_agreement_user 1 1 1 0
4 access_agreement_manager agreement manager model_agreement group_agreement_manager 1 1 1 1
5 access_agreement_recital_readonly recital readonly model_agreement_recital group_agreement_readonly 1 0 0 0
6 access_agreement_recital_allusers recital all users model_agreement_recital group_agreement_user 1 1 1 0
7 access_agreement_recital_manager recital manager model_agreement_recital group_agreement_manager 1 1 1 1
8 access_agreement_section_readonly section readonly model_agreement_section group_agreement_readonly 1 0 0 0
9 access_agreement_section_allusers section all users model_agreement_section group_agreement_user 1 1 1 0
10 access_agreement_section_manager section manager model_agreement_section group_agreement_manager 1 1 1 1
11 access_agreement_clause_readonly clause readonly model_agreement_clause group_agreement_readonly 1 0 0 0
12 access_agreement_clause_allusers clause all users model_agreement_clause group_agreement_user 1 1 1 0
13 access_agreement_clause_manager clause manager model_agreement_clause group_agreement_manager 1 1 1 1
14 access_agreement_appendix_readonly appendix readonly model_agreement_appendix group_agreement_readonly 1 0 0 0
15 access_agreement_appendix_allusers appendix all users model_agreement_appendix group_agreement_user 1 1 1 0
16 access_agreement_appendix_manager appendix manager model_agreement_appendix group_agreement_manager 1 1 1 1
17 access_agreement_line_readonly agreement line readonly model_agreement_line group_agreement_readonly 1 0 0 0
18 access_agreement_line_allusers agreement line all users model_agreement_line group_agreement_user 1 1 1 0
19 access_agreement_line_manager agreement line manager model_agreement_line group_agreement_manager 1 1 1 1
20 access_agreement_stage_readonly stage readonly model_agreement_stage group_agreement_readonly 1 0 0 0
21 access_agreement_stage_manager stage manager model_agreement_stage group_agreement_manager 1 1 1 1
22 access_agreement_type_readonly type readonly model_agreement_type group_agreement_readonly 1 0 0 0
23 access_agreement_type_manager type manager model_agreement_type group_agreement_manager 1 1 1 1
24 access_agreement_subtype_readonly subtype readonly model_agreement_subtype group_agreement_readonly 1 0 0 0
25 access_agreement_subtype_manager subtype manager model_agreement_subtype group_agreement_manager 1 1 1 1
26 access_agreement_renewaltype_readonly renewaltype readonly model_agreement_renewaltype group_agreement_readonly 1 0 0 0
27 access_agreement_renewaltype_manager renewaltype manager model_agreement_renewaltype group_agreement_manager 1 1 1 1
28 access_agreement_increasetype_readonly increasetype readonly model_agreement_increasetype group_agreement_readonly 1 0 0 0
29 access_agreement_increasetype_manager increasetype manager model_agreement_increasetype group_agreement_manager 1 1 1 1

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8" ?>
<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="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="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="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 not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,237 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="100mm"
height="100mm"
viewBox="0 0 100 100"
version="1.1"
id="svg8"
sodipodi:docname="icon.svg"
inkscape:version="0.92.2 5c3e80d, 2017-08-06"
inkscape:export-filename="/Users/marcelsavegnago/Documents/id42/LOGOS/icon.png"
inkscape:export-xdpi="91.867622"
inkscape:export-ydpi="91.867622">
<defs
id="defs2">
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath4562">
<rect
ry="4.1577382"
y="13.040181"
x="-124.92113"
height="98.866074"
width="99.811012"
id="rect4564"
style="fill:#aa0000;stroke-width:0.26458332"
clip-path="none" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath4578">
<rect
ry="4.1577382"
y="1.5119057"
x="-120.00744"
height="97.930626"
width="99.811012"
id="rect4580"
style="fill:#aa0000;stroke-width:0.26458332" />
</clipPath>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.7"
inkscape:cx="404.85241"
inkscape:cy="231.79398"
inkscape:document-units="mm"
inkscape:current-layer="layer2"
showgrid="false"
inkscape:snap-page="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1280"
inkscape:window-height="702"
inkscape:window-x="12"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:snap-global="false"
inkscape:measure-start="187.143,62.8571"
inkscape:measure-end="188.571,-1.42857">
<sodipodi:guide
position="27.285156,77.130766"
orientation="-0.70710678,0.70710678"
id="guide4619"
inkscape:locked="false" />
<sodipodi:guide
position="65.956845,10.583333"
orientation="-0.70710678,0.70710678"
id="guide4621"
inkscape:locked="false" />
<sodipodi:guide
position="67.704985,78.09933"
orientation="-0.70710678,0.70710678"
id="guide4623"
inkscape:locked="false" />
<sodipodi:guide
position="62.407434,24.722431"
orientation="-0.70710678,0.70710678"
id="guide4625"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-197)"
style="display:inline" />
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Layer 2"
style="display:inline">
<rect
y="0"
x="0"
height="25.164532"
width="100"
id="rect4535"
style="fill:#2483c5;stroke-width:0.13272627;fill-opacity:0.80149812"
ry="4.9136906"
inkscape:export-xdpi="91.327972"
inkscape:export-ydpi="91.327972" />
<rect
ry="4.9136906"
style="fill:#24245c;stroke-width:0.13272627;fill-opacity:1"
id="rect4566"
width="100"
height="25.164532"
x="0.18899068"
y="74.958138"
inkscape:export-xdpi="91.327972"
inkscape:export-ydpi="91.327972" />
<rect
style="fill:#2483c5;stroke-width:0.26458332;fill-opacity:1"
id="rect914"
width="99.811012"
height="97.930626"
x="0.18898809"
y="1.1339295"
ry="4.1577382"
inkscape:export-xdpi="91.327972"
inkscape:export-ydpi="91.327972" />
<path
inkscape:connector-curvature="0"
d="m -94.801154,22.164916 -27.663136,26.566584 2.26786,51.64648 h 37.697532 17.674503 l 18.256764,-17.499341 -0.357726,-12.039111 0.103556,-35.709692 -12.367577,-11.943386 -13.921757,0.298048 -13.752518,-1.096522"
style="display:inline;opacity:0.47600002;fill:#550000;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4576"
clip-path="url(#clipPath4578)"
transform="matrix(1,0,0,1.0477127,120.21264,-5.0610747)"
inkscape:export-xdpi="91.327972"
inkscape:export-ydpi="91.327972"
sodipodi:nodetypes="ccccccccccc" />
<g
id="g1099"
transform="matrix(0.15694819,0,0,0.15694819,22.442357,17.453291)">
<g
id="g1034">
<g
id="g1032">
<g
id="g1030">
<path
id="path1014"
d="m 249.5054,92.823 h 53.791 l 0.005,62.757 27.271,-42.343 V 77.743 c 0,-2.168 -0.863,-4.247 -2.396,-5.787 L 258.6124,2.395 C 257.0794,0.863 254.9994,0 252.8274,0 H 37.741399 c -13.534,0 -24.547,11.008 -24.547,24.543 v 370.914 c 0,13.534 11.012,24.543 24.547,24.543 H 306.0244 c 13.533,0 24.545,-11.009 24.545,-24.543 V 290.15 l -27.264,41.721 0.004,60.854 H 40.468399 V 27.274 H 239.3744 v 55.417 c 0,5.599 4.535,10.132 10.131,10.132 z"
inkscape:connector-curvature="0"
style="fill:#ffffff" />
<path
id="path1016"
d="m 348.90644,221.22282 -9.86485,-9.16236 c -1.61064,-1.4946 -4.12524,-1.40313 -5.62182,0.20718 l -75.50518,81.27788 -10.21401,-9.48701 75.50535,-81.27888 c 1.49674,-1.61128 1.40428,-4.12606 -0.20619,-5.62165 l -9.86467,-9.16334 c -1.61047,-1.49559 -4.12721,-1.40346 -5.62182,0.20718 L 194.4577,309.90372 c -0.4803,0.5173 -0.81352,1.15314 -0.96811,1.84192 l -10.49398,47.16303 c -0.30606,1.3771 0.13989,2.81815 1.17596,3.77596 1.0354,0.96175 2.50407,1.30178 3.85321,0.89486 l 46.26272,-13.93733 c 0.67777,-0.20139 1.28716,-0.58393 1.7696,-1.10186 L 349.11051,226.83903 c 1.49852,-1.60388 1.40425,-4.11998 -0.20407,-5.61621 z"
inkscape:connector-curvature="0"
style="fill:#ffffff" />
<path
id="path1018"
d="m 395.23055,171.35435 -35.76922,-33.22883 c -1.60867,-1.49426 -4.1264,-1.4023 -5.62182,0.20718 l -34.29286,36.91594 c -1.49459,1.61064 -1.40363,4.12821 0.20685,5.62379 l 35.76956,33.22685 c 1.6085,1.49526 4.12721,1.40347 5.62182,-0.20717 l 34.2917,-36.91513 c 1.49756,-1.61013 1.40444,-4.12705 -0.20603,-5.62263 z"
inkscape:connector-curvature="0"
style="fill:#ffffff" />
<path
id="path1020"
d="m 83.563,109.13658 h 78 c 5.523,0 10,-4.478 10,-10.000003 0,-5.522 -4.477,-10 -10,-10 h -78 c -5.523,0 -10,4.478 -10,10 0,5.522003 4.479,10.000003 10,10.000003 z"
inkscape:connector-curvature="0"
style="fill:#ffffff" />
<path
id="path1022"
d="m 231.563,149.13658 c 0,-5.522 -4.479,-10 -10,-10 h -138 c -5.523,0 -10,4.478 -10,10 0,5.522 4.477,10 10,10 h 138 c 5.523,0 10,-4.478 10,-10 z"
inkscape:connector-curvature="0"
style="fill:#ffffff" />
<path
id="path1024"
d="m 83.563,209.13658 h 78 c 5.523,0 10,-4.478 10,-10 0,-5.522 -4.477,-10 -10,-10 h -78 c -5.523,0 -10,4.478 -10,10 0,5.522 4.479,10 10,10 z"
inkscape:connector-curvature="0"
style="fill:#ffffff" />
<path
id="path1026"
d="m 83.563,259.13658 h 78 c 5.523,0 10,-4.478 10,-10 0,-5.521 -4.477,-10 -10,-10 h -78 c -5.523,0 -10,4.479 -10,10 0,5.522 4.479,10 10,10 z"
inkscape:connector-curvature="0"
style="fill:#ffffff" />
</g>
</g>
</g>
<g
id="g1036" />
<g
id="g1038" />
<g
id="g1040" />
<g
id="g1042" />
<g
id="g1044" />
<g
id="g1046" />
<g
id="g1048" />
<g
id="g1050" />
<g
id="g1052" />
<g
id="g1054" />
<g
id="g1056" />
<g
id="g1058" />
<g
id="g1060" />
<g
id="g1062" />
<g
id="g1064" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@@ -0,0 +1,481 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.15.1: http://docutils.sourceforge.net/" />
<title>Agreements Legal</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="agreements-legal">
<h1 class="title">Agreements Legal</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/contract/tree/12.0/agreement_legal"><img alt="OCA/contract" src="https://img.shields.io/badge/github-OCA%2Fcontract-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/contract-12-0/contract-12-0-agreement_legal"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/110/12.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p>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.</p>
<p>Based on the template, an agreement can be created and the pdf document generated.</p>
<p>The agreement would go through a workflow to finally become a contract with the
customer signature.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#configuration" id="id1">Configuration</a></li>
<li><a class="reference internal" href="#usage" id="id2">Usage</a></li>
<li><a class="reference internal" href="#known-issues-roadmap" id="id3">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id4">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id5">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id6">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id7">Contributors</a></li>
<li><a class="reference internal" href="#other-credits" id="id8">Other credits</a></li>
<li><a class="reference internal" href="#maintainers" id="id9">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#id1">Configuration</a></h1>
<p>To configure this module:</p>
<ul class="simple">
<li>Go to Agreement &gt; Configuration &gt; Templates</li>
<li>Create a new template with sections and clauses and their respective content</li>
<li>Go to Agreement &gt; Configuration &gt; Stages</li>
<li>Create and reorder stages to match your process</li>
</ul>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#id2">Usage</a></h1>
<p>To use this module:</p>
<ul class="simple">
<li>Go to Agreement &gt; Agreements</li>
<li>Create a new agreement</li>
<li>Select a template</li>
<li>Follow the process to get the required approval</li>
<li>Send the invitation to the customer to review and sign the agreement</li>
<li>Define Field using widget domain but having partial_use option true:</li>
<li>For Ex:</li>
<li>&lt;field name=”field_domain” widget=”domain” nolabel=”1”</li>
<li>options=”{model: agreement.recital,</li>
<li>partial_use: True}”/&gt;</li>
</ul>
</div>
<div class="section" id="known-issues-roadmap">
<h1><a class="toc-backref" href="#id3">Known issues / Roadmap</a></h1>
<ul class="simple">
<li>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)</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id4">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/contract/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/contract/issues/new?body=module:%20agreement_legal%0Aversion:%2012.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#id5">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id6">Authors</a></h2>
<ul class="simple">
<li>Pavlov Media</li>
<li>Open Source Integrators</li>
<li>Yves Goldberg (Ygol Internetwork)</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id7">Contributors</a></h2>
<ul class="simple">
<li>Patrick Wilson &lt;<a class="reference external" href="mailto:pwilson&#64;pavlovmedia.com">pwilson&#64;pavlovmedia.com</a>&gt;</li>
<li>Bhavesh Odedra &lt;<a class="reference external" href="mailto:bodedra&#64;opensourceintegrators.com">bodedra&#64;opensourceintegrators.com</a>&gt;</li>
<li>Wolfgang Hall &lt;<a class="reference external" href="mailto:whall&#64;opensourceintegrators.com">whall&#64;opensourceintegrators.com</a>&gt;</li>
<li>Maxime Chambreuil &lt;<a class="reference external" href="mailto:mchambreuil&#64;opensourceintegrators.com">mchambreuil&#64;opensourceintegrators.com</a>&gt;</li>
<li>Sandip Mangukiya &lt;<a class="reference external" href="mailto:smangukiya&#64;opensourceintegrators.com">smangukiya&#64;opensourceintegrators.com</a>&gt;</li>
<li>Yves Goldberg &lt;<a class="reference external" href="mailto:yves&#64;ygol.com">yves&#64;ygol.com</a>&gt;</li>
</ul>
</div>
<div class="section" id="other-credits">
<h2><a class="toc-backref" href="#id8">Other credits</a></h2>
<p>The development of this module has been financially supported by:</p>
<ul class="simple">
<li>Pavlov Media</li>
<li>Open Source Integrators</li>
<li>Yves Goldberg</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id9">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>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.</p>
<p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainers</a>:</p>
<p><a class="reference external" href="https://github.com/max3903"><img alt="max3903" src="https://github.com/max3903.png?size=40px" /></a> <a class="reference external" href="https://github.com/ygol"><img alt="ygol" src="https://github.com/ygol.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/contract/tree/12.0/agreement_legal">OCA/contract</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,85 @@
odoo.define("agreement_legal.domain_widget_ext", function (require) {
"use strict";
var basic_fields = require("web.basic_fields");
var DomainSelector = require("web.DomainSelector");
var session = require("web.session");
var core = require("web.core");
var qweb = core.qweb;
basic_fields.FieldDomain.include({
/**
* Init
*/
init: function () {
this._super.apply(this, arguments);
// Add Additional options
this.partialUse = this.nodeOptions.partial_use || false;
},
// ----------------------------------------------------------------------
// Private
// ----------------------------------------------------------------------
/**
* @private
* @override _render from AbstractField
* @returns {Deferred}
*/
_render: function () {
// If there is no model, only change the non-domain-selector content
if (!this._domainModel) {
this._replaceContent();
return $.when();
}
// Convert char value to array value
var value = this.value || "[]";
// Create the domain selector or change the value of the current
// one...
var def = null;
if (this.domainSelector) {
def = this.domainSelector.setDomain(value);
} else {
this.domainSelector = new DomainSelector(
this,
this._domainModel,
value,
{
readonly: this.mode === "readonly" || this.inDialog,
filters: this.fsFilters,
debugMode: session.debug,
partialUse: this.partialUse || false,
}
);
def = this.domainSelector.prependTo(this.$el);
}
// ... then replace the other content (matched records, etc)
return def.then(this._replaceContent.bind(this));
},
/**
* Render the field DOM except for the domain selector part. The full
* field DOM is composed of a DIV which contains the domain selector
* widget, followed by other content. This other content is handled by
* this method.
*
* @private
*/
_replaceContent: function () {
if (this._$content) {
this._$content.remove();
}
this._$content = $(
qweb.render("FieldDomain.content", {
hasModel: Boolean(this._domainModel),
isValid: Boolean(this._isValidForModel),
nbRecords: this.record.specialData[this.name].nbRecords || 0,
inDialogEdit: this.inDialog && this.mode === "edit",
partialUse: this.partialUse || false,
})
);
this._$content.appendTo(this.$el);
},
});
});

View File

@@ -0,0 +1,298 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates id="template" xml:space="preserve">
<t t-name="FieldDomain.content">
<t t-if="partialUse">
<div t-if="hasModel" class="o_field_domain_panel">
</div>
</t>
<t t-if="!partialUse">
<div t-if="hasModel" class="o_field_domain_panel">
<i
class="fa fa-arrow-right"
role="img"
aria-label="Domain"
title="Domain"
/>
<button
t-if="isValid"
class="btn btn-sm btn-secondary o_domain_show_selection_button"
type="button"
>
<t t-esc="nbRecords" /> record(s)
</button>
<span t-else="" class="text-warning" role="alert"><i
class="fa fa-exclamation-triangle"
role="img"
aria-label="Warning"
title="Warning"
/> Invalid domain</span>
<button
t-if="inDialogEdit"
class="btn btn-sm btn-primary o_field_domain_dialog_button"
>Edit Domain</button>
</div>
<div t-else="">Select a model to add a filter.</div>
</t>
</t>
<div
aria-atomic="true"
t-name="DomainSelector"
t-attf-class="o_domain_node o_domain_tree o_domain_selector #{widget.readonly ? 'o_read_mode' : 'o_edit_mode'}"
>
<t t-if="widget.options.partialUse">
<t t-if="widget.children.length === 0">
<span>SMatch <strong>all records</strong></span>
<button
t-if="!widget.readonly"
class="btn btn-sm btn-primary o_domain_add_first_node_button"
><i class="fa fa-plus" /> Add filter</button>
</t>
<t t-else="">
<div class="o_domain_tree_header">
<t
t-if="widget.children.length === 1"
>Please navigate below and select field:</t>
<t t-else="">
<span>SSMatch records with</span>
<t t-call="DomainTree.OperatorSelector" />
<span>of the following rules:</span>
</t>
</div>
<div class="o_domain_node_children_container" />
</t>
</t>
<t t-if="!widget.options.partialUse">
<t t-if="widget.children.length === 0">
<span>Match <strong>all records</strong></span>
<button
t-if="!widget.readonly"
class="btn btn-sm btn-primary o_domain_add_first_node_button"
><i class="fa fa-plus" /> Add filter</button>
</t>
<t t-else="">
<div class="o_domain_tree_header">
<t
t-if="widget.children.length === 1"
>Match records with the following rule:</t>
<t t-else="">
<span>Match records with</span>
<t t-call="DomainTree.OperatorSelector" />
<span>of the following rules:</span>
</t>
</div>
<div class="o_domain_node_children_container" />
</t>
<label
t-if="widget.debug &amp;&amp; !widget.readonly"
class="o_domain_debug_container"
>
<span class="small"># Code editor</span>
<input type="text" class="o_domain_debug_input" />
</label>
</t>
</div>
<t t-name="DomainNode.ControlPanel">
<t t-if="widget.options.partialUse">
<div
t-if="!widget.readonly &amp;&amp; !widget.noControlPanel"
class="o_domain_node_control_panel"
role="toolbar"
aria-label="Domain node"
>
</div>
</t>
<t t-if="!widget.options.partialUse">
<div
t-if="!widget.readonly &amp;&amp; !widget.noControlPanel"
class="o_domain_node_control_panel"
role="toolbar"
aria-label="Domain node"
>
<button
class="btn o_domain_delete_node_button"
title="Delete node"
aria-label="Delete node"
><i class="fa fa-times" /></button>
<button
class="btn o_domain_add_node_button"
title="Add node"
aria-label="Add node"
><i class="fa fa-plus-circle" /></button>
<button
class="btn o_domain_add_node_button"
title="Add branch"
aria-label="Add branch"
data-branch="1"
><i class="fa fa-ellipsis-h" /></button>
</div>
</t>
</t>
<div
t-name="DomainLeaf"
t-attf-class="o_domain_node o_domain_leaf o_domain_selector_row #{widget.readonly ? 'o_read_mode' : 'o_edit_mode'}"
>
<t t-call="DomainNode.ControlPanel" />
<div t-if="!widget.readonly" class="o_domain_leaf_edition">
<!-- field selector will be instantiated here -->
<t t-if="!widget.options.partialUse">
<div> <!-- used for flex stretching -->
<select class="o_domain_leaf_operator_select o_input">
<option
t-foreach="widget.operators"
t-as="key"
t-att-value="key"
t-att-selected="widget.displayOperator === key ? 'selected' : None"
>
<t t-esc="key_value" />
</option>
</select>
</div>
<div
t-attf-class="o_ds_value_cell#{_.contains(['set', 'not set'], widget.displayOperator) ? ' d-none' : ''}"
>
<t t-if="widget.selectionChoices !== null">
<select class="o_domain_leaf_value_input o_input">
<option
t-foreach="widget.selectionChoices"
t-as="val"
t-att-value="val[0]"
t-att-selected="_.contains(val, widget.displayValue) ? 'selected' : None"
>
<t t-esc="val[1]" />
</option>
</select>
</t>
<t t-else="">
<t t-if="_.contains(['in', 'not in'], widget.operator)">
<div class="o_domain_leaf_value_input">
<span
class="badge badge-pill"
t-foreach="widget.displayValue"
t-as="val"
>
<t t-esc="val" /> <i
class="o_domain_leaf_value_remove_tag_button fa fa-times"
t-att-data-value="val"
role="img"
aria-label="Remove tag"
title="Remove tag"
/>
</span>
</div>
<div class="o_domain_leaf_value_tags">
<input
placeholder="Add new value"
type="text"
class="o_input"
/>
<button
class="btn btn-sm btn-primary fa fa-plus o_domain_leaf_value_add_tag_button"
aria-label="Add tag"
title="Add tag"
/>
</div>
</t>
<t t-else="">
<input
class="o_domain_leaf_value_input o_input"
type="text"
t-att-value="widget.displayValue"
/>
</t>
</t>
</div>
</t>
</div>
<div t-else="" class="o_domain_leaf_info">
<!-- field selector will be instantiated here -->
<t t-if="_.isString(widget.value)">
<span class="o_domain_leaf_operator"><t
t-esc="widget.operator_mapping[widget.operator]"
/></span>
<span class="o_domain_leaf_value text-primary">"<t
t-esc="widget.value"
/>"</span>
</t>
<t t-if="_.isArray(widget.value)">
<span class="o_domain_leaf_operator"><t
t-esc="widget.operator_mapping[widget.operator]"
/></span>
<t t-foreach="widget.value" t-as="v">
<span class="o_domain_leaf_value text-primary">"<t
t-esc="v"
/>"</span>
<t t-if="!v_last"> or </t>
</t>
</t>
<t t-if="_.isNumber(widget.value)">
<span class="o_domain_leaf_operator"><t
t-esc="widget.operator_mapping[widget.operator]"
/></span>
<span class="o_domain_leaf_value text-primary"><t
t-esc="widget.value"
/></span>
</t>
<t t-if="_.isBoolean(widget.value)">
is
<t
t-if="widget.operator === '=' &amp;&amp; widget.value === false || widget.operator === '!=' &amp;&amp; widget.value === true"
>not</t>
set
</t>
</div>
</div>
<div
aria-atomic="true"
t-name="ModelFieldSelector"
t-attf-class="o_field_selector#{!widget.options.readonly ? ' o_edit_mode o_input' : ''}"
>
<div class="o_field_selector_value" tabindex="0" />
<t t-if="!widget.options.partialUse">
<div class="o_field_selector_controls" tabindex="0">
<i
role="alert"
class="fa fa-exclamation-triangle o_field_selector_warning d-none"
title="Invalid field chain"
aria-label="Invalid field chain"
/>
</div>
</t>
<div
t-if="!widget.options.readonly"
class="o_field_selector_popover d-none"
tabindex="0"
>
<div class="o_field_selector_popover_header text-center">
<i
class="fa fa-arrow-left o_field_selector_popover_option o_field_selector_prev_page"
title="Previous"
role="img"
aria-label="Previous"
/>
<div class="o_field_selector_title" />
<i
class="fa fa-times o_field_selector_popover_option o_field_selector_close"
title="Close"
role="img"
aria-label="Close"
/>
</div>
<div class="o_field_selector_popover_body">
<ul class="o_field_selector_page" />
</div>
<div
t-if="widget.options.debugMode"
class="o_field_selector_popover_footer"
>
<input type="text" class="o_input" />
</div>
</div>
</div>
</templates>

View File

@@ -0,0 +1,8 @@
# 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

View File

@@ -0,0 +1,131 @@
# 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 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>{" + str(agreement_01.id) + ": '</p><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,
"{" + str(agreement_01.id) + ": '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",
),
)

View File

@@ -0,0 +1,81 @@
# 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>{" + str(appendix_01.id) + ": '</p><p>TestAppendices</p>'}",
)

View File

@@ -0,0 +1,81 @@
# 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>{" + str(clause_01.id) + ": '</p><p>TestClause</p>'}",
)

View File

@@ -0,0 +1,45 @@
# 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")

View File

@@ -0,0 +1,81 @@
# 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>{" + str(recital_01.id) + ": '</p><p>TestRecital</p>'}",
)

View File

@@ -0,0 +1,81 @@
# 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>{" + str(section_01.id) + ": '</p><p>TestSection</p>'}",
)

View File

@@ -0,0 +1,556 @@
<odoo>
<!-- Agreement List View-->
<record model="ir.ui.view" id="partner_agreement_list_view">
<field name="name">Agreement List</field>
<field name="model">agreement</field>
<field name="arch" type="xml">
<tree string="Agreements" default_order='name'>
<field name="name" />
<field name="partner_id" />
<field name="company_id" />
<field name="parent_agreement_id" />
<field name="agreement_type_id" />
<field name="agreement_subtype_id" />
<field name="active" invisible="1" />
</tree>
</field>
</record>
<!-- Agreement Form View -->
<record model="ir.ui.view" id="partner_agreement_form_view">
<field name="name">Agreement Form</field>
<field name="model">agreement</field>
<field name="priority" eval="1" />
<field name="arch" type="xml">
<form string="Agreement">
<header>
<button
string="New Version"
type="object"
name="create_new_version"
class="oe_highlight"
attrs="{'invisible': [('state', '=', 'active')]}"
/>
<button
string="New Agreement"
type="object"
name="create_new_agreement"
class="oe_highlight"
attrs="{'invisible': [('is_template', '=', False)]}"
/>
<field
name="stage_id"
widget="statusbar"
clickable="True"
options="{'fold_field': 'fold'}"
domain="[('stage_type', '=', 'agreement')]"
/>
</header>
<sheet>
<div class="oe_button_box" name="button_box" />
<div class="oe_title">
<label
for="name"
class="oe_edit_only"
string="Agreement Name"
/>
<h1>
<field name="name" />
</h1>
</div>
<group string="General">
<group>
<field name="code" readonly="1" />
<field
name="parent_agreement_id"
domain="[('partner_id', '=', partner_id)]"
/>
<field name="is_template" />
</group>
<group>
<field
name="agreement_type_id"
widget="selection"
required="True"
/>
<field
name="agreement_subtype_id"
widget="selection"
domain="[('agreement_type_id', '=', agreement_type_id)]"
/>
<field
name="assigned_user_id"
attrs="{'invisible': [('is_template', '=', True)], 'readonly':[('is_template', '=', True)]}"
/>
<field name="active" invisible="1" />
<field name="state" invisible="1" />
</group>
</group>
<group string="Description">
<field name="description" required="True" nolabel="1" />
</group>
<group class="oe_edit_only">
<group>
<field
name="field_id"
domain="[('model_id', '=', active_model),
('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')]"
attrs="{'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 description and special terms.
<ol>
<li>Select the agreement 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 description or the special terms</li>
</ol>
</p>
</group>
<group name="parties" string="Parties">
<group name="cust_parties">
<field
name="use_parties_content"
string="Use custom content"
/>
</group>
<group name="cust_parties">
<p>
This section (on the left) allows you to replace the default listing of the parties with custom dynamic content.
</p>
</group>
<group name="partner" string="Partner">
<div class="o_address_format">
<field
name="partner_id"
context="{'show_address': 1}"
options="{&quot;always_reload&quot;: True}"
/>
</div>
</group>
<group name="company" string="Company">
<div class="o_address_format">
<field
name="company_id"
readonly="1"
context="{'show_address': 1}"
options="{&quot;always_reload&quot;: True}"
/>
</div>
<field name="company_partner_id" invisible="1" />
</group>
<group name="partner_left" string="Primary Contact">
<field
name="partner_contact_id"
domain="[('parent_id', '=', partner_id)]"
nolabel="1"
/>
<field
name="partner_contact_phone"
widget="phone"
readonly="1"
nolabel="1"
/>
<field
name="partner_contact_email"
widget="email"
readonly="1"
nolabel="1"
/>
</group>
<group name="contact_right" string="Primary Contact">
<field
name="company_contact_id"
domain="[('parent_id', '=', company_partner_id)]"
nolabel="1"
/>
<field
name="company_contact_phone"
widget="phone"
readonly="1"
nolabel="1"
/>
<field
name="company_contact_email"
widget="email"
readonly="1"
nolabel="1"
/>
</group>
</group>
<group
name="parties_content"
string="Parties Content"
attrs="{'invisible':[('use_parties_content', '=', False)]}"
>
<field name="parties" nolabel="1" />
</group>
<group name="term_information">
<group name="termdates_left" string="Term Dates">
<field
name="start_date"
attrs="{'required': [('is_template', '=', False)], 'invisible': [('is_template', '=', True)]}"
/>
<field
name="end_date"
attrs="{'required': [('is_template', '=', False)], 'invisible': [('is_template', '=', True)]}"
/>
<field name="expiration_notice" />
<field name="change_notice" />
<field
name="notification_address_id"
domain="['|', ('parent_id', '=', partner_id), ('parent_id', '=', company_partner_id)]"
/>
<field name="termination_requested" />
<field name="termination_date" />
</group>
</group>
<group string="Special Terms">
<field name="special_terms" nolabel="1" />
</group>
<notebook>
<page name="structure" string="Structure">
<div>
<button
name="%(partner_agreement_contract_document_preview)d"
string="Preview"
type="action"
class="oe_highlight"
/>
<button
name="%(partner_agreement_contract_document)d"
string="Print"
type="action"
class="oe_highlight"
/>
</div>
<separator string="Recitals" />
<field
name="recital_ids"
default_order="sequence"
nolabel="1"
context="{'default_agreement': active_id}"
/>
<separator string="Sections" />
<field
name="sections_ids"
default_order='sequence'
nolabel="1"
context="{'default_agreement': active_id}"
/>
<separator string="Clauses" />
<field
name="clauses_ids"
default_order='clause_id, sequence'
nolabel="1"
context="{'default_agreement': active_id}"
/>
<separator string="Appendices" />
<field
name="appendix_ids"
default_order='sequence'
nolabel="1"
context="{'default_agreement': active_id}"
/>
</page>
<page name="signature" string="Signatures">
<group>
<group string="Partner">
<field name="partner_signed_date" />
<field
name="partner_signed_user_id"
domain="[('parent_id', '=', partner_id)]"
/>
</group>
<group string="Company">
<field name="company_signed_date" />
<field name="company_signed_user_id" />
<field
name="signed_contract"
filename="signed_contract_filename"
/>
<field
name="signed_contract_filename"
invisible="1"
/>
</group>
</group>
</page>
<page name="products" string="Products/Services">
<field name="line_ids" nolabel="1">
<tree editable="top">
<field name="product_id" />
<field name="name" />
<field name="qty" />
<field name="uom_id" groups="uom.group_uom" />
</tree>
</field>
</page>
<page name="child_agreements" string="Child Agreements">
<field name="child_agreements_ids">
<tree default_order='version desc'>
<field name="name" />
<field name="version" />
<field name="revision" />
</tree>
</field>
</page>
<page name="old_versions" string="Revisions">
<field
name="previous_version_agreements_ids"
string="Previouse Versions"
>
<tree default_order='version desc'>
<field name="name" />
<field name="version" />
<field name="revision" />
</tree>
</field>
</page>
<page name="performance" string="Performance">
<p
>This section is a place where financial records will show the current performance of this agreement.</p>
<p>Perhaps include invoices with total vs costs? </p>
</page>
</notebook>
<group string="Administration">
<div>
<p>Reviewed by <field
name="reviewed_user_id"
class="oe_inline"
/> on <field
name="reviewed_date"
class="oe_inline"
/>.</p>
<p>Approved by <field
name="approved_user_id"
class="oe_inline"
/> on <field
name="approved_date"
class="oe_inline"
/>.</p>
</div>
</group>
<footer>
Version: <field name="version" readonly="True" />.<field
name="revision"
readonly="True"
/>
| Created By: <field name="create_uid" readonly="True" />
| Created On: <field name="create_date" readonly="True" />
</footer>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" />
<field name="activity_ids" widget="mail_activity" />
<field name="message_ids" widget="mail_thread" />
</div>
</form>
</field>
</record>
<!-- Agreement Kanban View -->
<record id="view_project_agreement_kanban" model="ir.ui.view">
<field name="name">Agreement Kanban</field>
<field name="model">agreement</field>
<field name="arch" type="xml">
<kanban default_group_by="stage_id">
<field name="color" />
<field name="assigned_user_id" />
<templates>
<t t-name="kanban-box">
<div
t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click"
>
<div class="oe_kanban_content">
<div class="o_kanban_record_top">
<div class="o_kanban_record_headings">
<strong class="o_kanban_record_title">
<field name="name" />
</strong><br />
<div
class="o_kanban_record_subtitle text-muted"
>
<field name="partner_id" />
<t
t-if="record.end_date.raw_value and record.end_date.raw_value lt (new Date())"
t-set="red"
>oe_kanban_text_red</t>
<div t-attf-class="#{red || ''}">
<i><field name="end_date" /></i>
</div>
</div>
</div>
<div
class="o_dropdown_kanban dropdown"
groups="base.group_user"
>
<a
role="button"
class="dropdown-toggle btn"
data-toggle="dropdown"
href="#"
>
<span
class="fa fa-ellipsis-v"
aria-hidden="true"
title="Icon"
/>
</a>
<ul
class="dropdown-menu"
role="menu"
aria-labelledby="dLabel"
>
<li t-if="widget.editable"><a
type="edit"
>Edit</a></li>
<li class="divider" />
<li
class="dropdown-header"
>Record's Colour</li>
<li>
<ul
class="oe_kanban_colorpicker"
data-field="color"
/>
</li>
</ul>
</div>
</div>
<div class="o_kanban_record_body">
<field name="agreement_type_id" /> - <field
name="agreement_subtype_id"
/>
</div>
<div class="o_kanban_record_bottom">
<div class="oe_kanban_bottom_left">
V: <field name="version" />
</div>
<div class="oe_kanban_bottom_right">
<img
t-att-src="kanban_image('res.users', 'image_small', record.assigned_user_id.raw_value)"
t-att-title="record.assigned_user_id.value"
width="36"
height="36"
class="oe_kanban_avatar"
alt="user &amp; picture"
/>
</div>
</div>
</div>
<div class="oe_clear" />
</div>
</t>
</templates>
</kanban>
</field>
</record>
<!-- Agreement Search View -->
<record model="ir.ui.view" id="partner_agreement_search_view">
<field name="name">Agreement Search</field>
<field name="model">agreement</field>
<field name="arch" type="xml">
<search string="Agreement Search">
<field name="name" />
<field name="partner_id" />
<field name="agreement_type_id" />
<field name="agreement_subtype_id" />
<filter
name="filter_non_template"
string="Non-Templates"
domain="[('active','=',True),('is_template', '=', False)]"
/>
<filter
name="filter_inactive"
string="Archived"
domain="[('active','=',False)]"
/>
<filter
name="filter_templates"
string="Templates"
domain="[('active','=',True),('is_template', '=', True)]"
/>
<filter
name="group_partner_id"
string="Partners"
icon="terp-partner"
context="{'group_by':'partner_id'}"
/>
<filter
name="group_status"
string="Status"
icon="terp-partner"
context="{'group_by':'state'}"
/>
</search>
</field>
</record>
<!-- Agreement Reporting -->
<record id="agreement_graph_view" model="ir.ui.view">
<field name="name">agreement.graph</field>
<field name="model">agreement</field>
<field name="arch" type="xml">
<graph string="Agreements" type="bar">
<field name="stage_id" type="row" />
</graph>
</field>
</record>
<record id="agreement_pivot_view" model="ir.ui.view">
<field name="name">agreement.pivot</field>
<field name="model">agreement</field>
<field name="arch" type="xml">
<pivot string="Agreements" display_quantity="true">
<field name="stage_id" type="row" />
</pivot>
</field>
</record>
<record id="action_agreement_report_order" model="ir.actions.act_window">
<field name="name">Agreements</field>
<field name="res_model">agreement</field>
<field name="view_mode">graph,pivot</field>
</record>
<!-- actions opening views on models -->
<record model="ir.actions.act_window" id="agreement_dashboard_agreement">
<field name="name">Agreements</field>
<field name="res_model">agreement</field>
<field name="domain">[('is_template', '=', False)]</field>
<field name="view_mode">kanban,tree,form</field>
</record>
<record model="ir.actions.act_window" id="agreement_operations_agreement">
<field name="name">Agreements</field>
<field name="res_model">agreement</field>
<field name="domain">[('is_template', '=', False)]</field>
<field name="view_mode">tree,form</field>
</record>
<record model="ir.actions.act_window" id="partner_agreement_agreement_templates">
<field name="name">Templates</field>
<field name="res_model">agreement</field>
<field name="domain">[('is_template', '=', True)]</field>
<!-- <field name="context">[('is_template', '=', True)]</field> -->
<field name="view_mode">tree,kanban,form</field>
</record>
</odoo>

View File

@@ -0,0 +1,106 @@
<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 string="Appendices" default_order='agreement_id, sequence'>
<field name="sequence" widget="handle" />
<field name="name" />
<field name="title" />
<field name="agreement_id" />
<field name="active" 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"
attrs="{'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', '=', active_model),
('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')]"
attrs="{'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 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>
</odoo>

View File

@@ -0,0 +1,116 @@
<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 string="Clauses" 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" 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"
attrs="{'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', '=', active_model),
('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')]"
attrs="{'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>
</odoo>

View File

@@ -0,0 +1,44 @@
<odoo>
<!-- Agreement Increase Type List View-->
<record model="ir.ui.view" id="partner_agreement_increasetype_list_view">
<field name="name">Agreement Increase Type List</field>
<field name="model">agreement.increasetype</field>
<field name="arch" type="xml">
<tree default_order='name'>
<field name="name" />
<field name="description" />
<field name="increase_percent" />
</tree>
</field>
</record>
<!-- Agreement Increase Type Form View -->
<record model="ir.ui.view" id="partner_agreement_increasetype_form_view">
<field name="name">Agreement Increase Type Form</field>
<field name="model">agreement.increasetype</field>
<field name="arch" type="xml">
<form string="Agreements Type Form">
<sheet>
<group>
<field name="name" />
</group>
<group>
<field name="increase_percent" />
</group>
<group string="Description">
<field name="description" nolabel="1" />
</group>
</sheet>
</form>
</field>
</record>
<!-- Actions opening views on models -->
<record model="ir.actions.act_window" id="partner_agreement_action_increasetype">
<field name="name">Agreement Increase Type</field>
<field name="res_model">agreement.increasetype</field>
<field name="view_mode">tree,form</field>
</record>
</odoo>

View File

@@ -0,0 +1,106 @@
<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 string="Recitals" default_order='agreement_id, sequence'>
<field name="sequence" widget="handle" />
<field name="name" />
<field name="title" />
<field name="agreement_id" />
<field name="active" 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"
attrs="{'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', '=', active_model),
('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')]"
attrs="{'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>
</odoo>

View File

@@ -0,0 +1,40 @@
<odoo>
<!-- Agreement Renewal Type List View-->
<record model="ir.ui.view" id="partner_agreement_renewaltype_list_view">
<field name="name">Agreement Renewal Type List</field>
<field name="model">agreement.renewaltype</field>
<field name="arch" type="xml">
<tree default_order='name'>
<field name="name" />
<field name="description" />
</tree>
</field>
</record>
<!-- Agreement Renewal Type Form View -->
<record model="ir.ui.view" id="partner_agreement_renewaltype_form_view">
<field name="name">Agreement Renewal Type Form</field>
<field name="model">agreement.renewaltype</field>
<field name="arch" type="xml">
<form string="Agreements Type Form">
<sheet>
<group>
<field name="name" />
</group>
<group string="Description">
<field name="description" nolabel="1" />
</group>
</sheet>
</form>
</field>
</record>
<!-- Actions opening views on models -->
<record model="ir.actions.act_window" id="partner_agreement_action_renewaltype">
<field name="name">Agreement Renewal Type</field>
<field name="res_model">agreement.renewaltype</field>
<field name="view_mode">tree,form</field>
</record>
</odoo>

View File

@@ -0,0 +1,125 @@
<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 string="Sections" default_order='agreement_id, sequence'>
<field name="sequence" widget="handle" />
<field name="name" />
<field name="title" />
<field name="agreement_id" />
<field name="active" 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"
attrs="{'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', '=', active_model),
('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')]"
attrs="{'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': active_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>
</odoo>

View File

@@ -0,0 +1,44 @@
<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" string="Stage Name" />
<field name="stage_type" />
</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>
<div class="oe_title">
<label for="name" class="oe_edit_only" string="Stage Name" />
<h1><field name="name" string="Stage Name" /></h1>
</div>
<group>
<field name="sequence" />
<field name="stage_type" />
<field name="fold" />
</group>
</sheet>
</form>
</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>

View File

@@ -0,0 +1,44 @@
<odoo>
<!-- Agreement Sub Type List View-->
<record model="ir.ui.view" id="partner_agreement_subtype_list_view">
<field name="name">Agreement Subtype List</field>
<field name="model">agreement.subtype</field>
<field name="arch" type="xml">
<tree default_order='name'>
<field name="name" string="Sub-Type Name" />
<field name="agreement_type_id" string="Agreement Type" />
</tree>
</field>
</record>
<!-- Agreement Sub Type Form View -->
<record model="ir.ui.view" id="partner_agreement_subtype_form_view">
<field name="name">Agreement Sub Type Form</field>
<field name="model">agreement.subtype</field>
<field name="arch" type="xml">
<form string="Agreement Sub-Types">
<sheet>
<div class="oe_title">
<label for="name" class="oe_edit_only" />
<h1><field name="name" /></h1>
</div>
<group>
<group>
<field name="agreement_type_id" />
</group>
<group />
</group>
</sheet>
</form>
</field>
</record>
<!-- Actions opening views on models -->
<record model="ir.actions.act_window" id="partner_agreement_action_subtype">
<field name="name">Agreement Sub-Types</field>
<field name="res_model">agreement.subtype</field>
<field name="view_mode">tree,form</field>
</record>
</odoo>

View File

@@ -0,0 +1,38 @@
<odoo>
<!-- Agreement Type List View-->
<record model="ir.ui.view" id="partner_agreement_type_list_view">
<field name="name">Agreement Type List</field>
<field name="model">agreement.type</field>
<field name="inherit_id" ref="agreement.agreement_type_list_view" />
<field name="arch" type="xml">
<field name="name" position="after">
<field name="agreement_subtypes_ids" string="Sub-Types" />
</field>
</field>
</record>
<!-- Agreement Type Form View -->
<record model="ir.ui.view" id="partner_agreement_type_form_view">
<field name="name">Agreement Type Form</field>
<field name="model">agreement.type</field>
<field name="inherit_id" ref="agreement.agreement_type_form_view" />
<field name="arch" type="xml">
<xpath expr="//sheet" position="inside">
<field name="agreement_subtypes_ids" nolabel="1">
<tree editable="bottom">
<field name="name" />
</tree>
</field>
</xpath>
</field>
</record>
<!-- Actions opening views on models -->
<record model="ir.actions.act_window" id="partner_agreement_action_type">
<field name="name">Agreement Types</field>
<field name="res_model">agreement.type</field>
<field name="view_mode">tree,form</field>
</record>
</odoo>

View File

@@ -0,0 +1,167 @@
<odoo>
<!-- Remove top menu from agreement module -->
<!-- <delete model="ir.ui.menu" id="agreement.agreement_menu" /> -->
<!-- Top menu item -->
<menuitem
name="Agreements"
id="agreement.agreement_menu"
web_icon="agreement_legal,static/description/icon.png"
sequence="80"
action="agreement_dashboard_agreement"
groups="agreement_legal.group_agreement_readonly"
/>
<!-- Dashboard -->
<menuitem
name="Dashboard"
id="agreement_dashboard"
parent="agreement.agreement_menu"
sequence="10"
/>
<menuitem
name="Agreements"
id="dashboard_agreements"
parent="agreement_dashboard"
sequence="10"
action="agreement_dashboard_agreement"
/>
<!-- Operations -->
<menuitem
name="Operations"
id="agreement_operations"
parent="agreement.agreement_menu"
sequence="20"
/>
<menuitem
name="Agreements"
id="operations_agreements"
parent="agreement_operations"
sequence="10"
action="agreement_operations_agreement"
/>
<!-- Master Data -->
<menuitem
name="Master Data"
id="agreement_masterdata"
parent="agreement.agreement_menu"
sequence="30"
/>
<menuitem
name="Contents"
id="agreement_data_contents"
parent="agreement_masterdata"
sequence="10"
/>
<menuitem
name="Recitals"
id="agreement_recitals"
parent="agreement_data_contents"
sequence="10"
action="agreement_recital_action"
/>
<menuitem
name="Clauses"
id="agreement_clauses"
parent="agreement_data_contents"
sequence="20"
action="partner_agreement_action_clause"
/>
<menuitem
name="Sections"
id="agreement_sections"
parent="agreement_data_contents"
sequence="30"
action="partner_agreement_action_section"
/>
<menuitem
name="Appendices"
id="agreement_appendices"
parent="agreement_data_contents"
sequence="40"
action="agreement_appendix_action"
/>
<menuitem
name="Products"
id="agreement_products"
sequence="100"
parent="agreement_masterdata"
action="product.product_template_action"
/>
<!-- Reporting -->
<menuitem
name="Reporting"
id="agreement_reporting"
sequence="40"
parent="agreement.agreement_menu"
groups="agreement_legal.group_agreement_manager"
/>
<menuitem
name="Agreements"
id="agreement_agreement_reporting"
sequence="10"
parent="agreement_reporting"
action="action_agreement_report_order"
/>
<!-- Configuration -->
<menuitem
name="Configuration"
id="agreement_configuration"
sequence="50"
parent="agreement.agreement_menu"
groups="agreement_legal.group_agreement_manager"
/>
<menuitem
name="Settings"
id="agreement_settings"
sequence="10"
parent="agreement_configuration"
action="agreement_legal.action_agreement_config"
/>
<menuitem
name="Templates"
id="template"
parent="agreement_configuration"
sequence="20"
action="partner_agreement_agreement_templates"
/>
<menuitem
name="Renewal Types"
id="agreement_renewaltype"
parent="agreement_configuration"
sequence="30"
action="partner_agreement_action_renewaltype"
/>
<menuitem
name="Increase Types"
id="agreement_increamenttypes"
parent="agreement_configuration"
sequence="31"
action="partner_agreement_action_increasetype"
/>
<menuitem
name="Stages"
id="agreement_stages"
parent="agreement_configuration"
sequence="40"
action="partner_agreement_action_stage"
/>
<menuitem
name="Types"
id="agreement_types"
parent="agreement_configuration"
sequence="50"
action="partner_agreement_action_type"
/>
<menuitem
name="Sub-Types"
id="agreement_subtypes"
parent="agreement_configuration"
sequence="60"
action="partner_agreement_action_subtype"
/>
</odoo>

View File

@@ -0,0 +1,195 @@
<odoo>
<record id="res_config_settings_view_form" model="ir.ui.view">
<field name="name">res.config.settings.view.form.agreement</field>
<field name="model">res.config.settings</field>
<field name="priority" eval="40" />
<field name="inherit_id" ref="base.res_config_settings_view_form" />
<field name="arch" type="xml">
<xpath expr="//div[hasclass('settings')]" position="inside">
<div
class="app_settings_block"
data-string="Agreements"
string="Agreements"
data-key="agreement_legal"
groups="agreement_legal.group_agreement_manager"
>
<h2>Operations</h2>
<div class="row mt16 o_settings_container">
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="group_uom" />
</div>
<div class="o_setting_right_pane">
<label for="group_uom" />
<div class="text-muted">
Sell and purchase products in different units of measure
</div>
<div class="content-group">
<div
class="mt16"
attrs="{'invisible': [('group_uom', '=', False)]}"
>
<button
name="%(uom.product_uom_form_action)d"
icon="fa-arrow-right"
type="action"
string="Units Of Measure"
class="btn-link"
/>
</div>
</div>
</div>
</div>
</div>
<h2>Advanced Features</h2>
<div class="row mt16 o_settings_container">
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="module_agreement_sale" />
</div>
<div class="o_setting_right_pane">
<label for="module_agreement_sale" string="Sales" />
<div class="text-muted">
Create an agreement when the sales order is confirmed
</div>
</div>
</div>
<div
class="col-xs-12 col-md-6 o_setting_box"
attrs="{'invisible': [('module_agreement_sale', '!=', True)]}"
>
<div class="o_setting_left_pane">
<field name="module_agreement_sale_subscription" />
</div>
<div class="o_setting_right_pane">
<label
for="module_agreement_sale_subscription"
string="Sale Subscriptions"
/>
<div class="text-muted">
Link your subscriptions to an agreement
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="module_fieldservice_agreement" />
</div>
<div class="o_setting_right_pane">
<label
for="module_fieldservice_agreement"
string="Field Service"
/>
<div class="text-muted">
Link your Field Service orders and equipments to an agreement
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="module_agreement_stock" />
</div>
<div class="o_setting_right_pane">
<label
for="module_agreement_stock"
string="Inventory"
/>
<div class="text-muted">
Link your transfers to an agreement
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="module_agreement_rma" />
</div>
<div class="o_setting_right_pane">
<label for="module_agreement_rma" string="Returns" />
<div class="text-muted">
Link your returns to an agreement
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="module_agreement_maintenance" />
</div>
<div class="o_setting_right_pane">
<label
for="module_agreement_maintenance"
string="Maintenance"
/>
<div class="text-muted">
Manage maintenance agreements and contracts
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="module_agreement_mrp" />
</div>
<div class="o_setting_right_pane">
<label
for="module_agreement_mrp"
string="Manufacturing"
/>
<div class="text-muted">
Link your manufacturing orders to an agreement
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="module_agreement_repair" />
</div>
<div class="o_setting_right_pane">
<label for="module_agreement_repair" string="Repair" />
<div class="text-muted">
Link your repair orders to an agreement
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="module_agreement_project" />
</div>
<div class="o_setting_right_pane">
<label
for="module_agreement_project"
string="Project"
/>
<div class="text-muted">
Link your projects and tasks to an agreement
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="module_agreement_helpdesk" />
</div>
<div class="o_setting_right_pane">
<label
for="module_agreement_helpdesk"
string="Helpdesk"
/>
<div class="text-muted">
Link your Helpdesk tickets to an agreement
</div>
</div>
</div>
</div>
</div>
</xpath>
</field>
</record>
<record id="action_agreement_config" model="ir.actions.act_window">
<field name="name">Settings</field>
<field name="res_model">res.config.settings</field>
<field name="view_mode">form</field>
<field name="target">inline</field>
<field name="context">{'module': 'agreement'}</field>
</record>
</odoo>

View File

@@ -0,0 +1,17 @@
<odoo>
<record model="ir.ui.view" id="partner_form">
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form" />
<field name="arch" type="xml">
<xpath expr="//page[@name='internal_notes']" position="after">
<page name="agreement" string="Agreements">
<group>
<field name="agreement_ids" nolabel="1" />
</group>
</page>
</xpath>
</field>
</record>
</odoo>