mirror of
https://github.com/OCA/contract.git
synced 2025-02-13 17:57:24 +02:00
[IMP] contract: Portal
This commit is contained in:
committed by
Francisco Ivan Anton Prieto
parent
2869d5d26e
commit
ea928628bb
@@ -1,2 +1,3 @@
|
|||||||
|
from . import controllers
|
||||||
from . import models
|
from . import models
|
||||||
from . import wizards
|
from . import wizards
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
# Copyright 2016-2017 LasLabs Inc.
|
# Copyright 2016-2017 LasLabs Inc.
|
||||||
# Copyright 2018-2019 ACSONE SA/NV
|
# Copyright 2018-2019 ACSONE SA/NV
|
||||||
# Copyright 2020 Tecnativa - Pedro M. Baeza
|
# Copyright 2020 Tecnativa - Pedro M. Baeza
|
||||||
|
# Copyright 2020 Tecnativa - Víctor Martínez
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -15,7 +16,7 @@
|
|||||||
"license": "AGPL-3",
|
"license": "AGPL-3",
|
||||||
"author": "Tecnativa, ACSONE SA/NV, Odoo Community Association (OCA)",
|
"author": "Tecnativa, ACSONE SA/NV, Odoo Community Association (OCA)",
|
||||||
"website": "https://github.com/oca/contract",
|
"website": "https://github.com/oca/contract",
|
||||||
"depends": ["base", "account", "product"],
|
"depends": ["base", "account", "product", "portal"],
|
||||||
"external_dependencies": {"python": ["dateutil"]},
|
"external_dependencies": {"python": ["dateutil"]},
|
||||||
"data": [
|
"data": [
|
||||||
"security/groups.xml",
|
"security/groups.xml",
|
||||||
@@ -43,6 +44,8 @@
|
|||||||
"views/res_partner_view.xml",
|
"views/res_partner_view.xml",
|
||||||
"views/res_config_settings.xml",
|
"views/res_config_settings.xml",
|
||||||
"views/contract_terminate_reason.xml",
|
"views/contract_terminate_reason.xml",
|
||||||
|
"views/contract_portal_templates.xml",
|
||||||
],
|
],
|
||||||
|
"demo": ["demo/assets.xml"],
|
||||||
"installable": True,
|
"installable": True,
|
||||||
}
|
}
|
||||||
|
|||||||
3
contract/controllers/__init__.py
Normal file
3
contract/controllers/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
from . import main
|
||||||
99
contract/controllers/main.py
Normal file
99
contract/controllers/main.py
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
# Copyright 2020 Tecnativa - Víctor Martínez
|
||||||
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
from odoo import _, http
|
||||||
|
from odoo.exceptions import AccessError, MissingError
|
||||||
|
from odoo.http import request
|
||||||
|
|
||||||
|
from odoo.addons.portal.controllers.portal import CustomerPortal, pager as portal_pager
|
||||||
|
|
||||||
|
|
||||||
|
class PortalContract(CustomerPortal):
|
||||||
|
def _prepare_portal_layout_values(self):
|
||||||
|
values = super()._prepare_portal_layout_values()
|
||||||
|
model = "contract.contract"
|
||||||
|
values["contract_count"] = 0
|
||||||
|
if request.env[model].check_access_rights("read", raise_exception=False):
|
||||||
|
values["contract_count"] = request.env[model].search_count([])
|
||||||
|
return values
|
||||||
|
|
||||||
|
def _contract_get_page_view_values(self, contract, access_token, **kwargs):
|
||||||
|
values = {
|
||||||
|
"page_name": "Contracts",
|
||||||
|
"contract": contract,
|
||||||
|
}
|
||||||
|
return self._get_page_view_values(
|
||||||
|
contract, access_token, values, "my_contracts_history", False, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_filter_domain(self, kw):
|
||||||
|
return []
|
||||||
|
|
||||||
|
@http.route(
|
||||||
|
["/my/contracts", "/my/contracts/page/<int:page>"],
|
||||||
|
type="http",
|
||||||
|
auth="user",
|
||||||
|
website=True,
|
||||||
|
)
|
||||||
|
def portal_my_contracts(
|
||||||
|
self, page=1, date_begin=None, date_end=None, sortby=None, **kw
|
||||||
|
):
|
||||||
|
values = self._prepare_portal_layout_values()
|
||||||
|
contract_obj = request.env["contract.contract"]
|
||||||
|
domain = self._get_filter_domain(kw)
|
||||||
|
searchbar_sortings = {
|
||||||
|
"date": {"label": _("Date"), "order": "recurring_next_date desc"},
|
||||||
|
"name": {"label": _("Name"), "order": "name desc"},
|
||||||
|
"code": {"label": _("Reference"), "order": "code desc"},
|
||||||
|
}
|
||||||
|
# default sort by order
|
||||||
|
if not sortby:
|
||||||
|
sortby = "date"
|
||||||
|
order = searchbar_sortings[sortby]["order"]
|
||||||
|
# count for pager
|
||||||
|
contract_count = contract_obj.search_count(domain)
|
||||||
|
# pager
|
||||||
|
pager = portal_pager(
|
||||||
|
url="/my/contracts",
|
||||||
|
url_args={
|
||||||
|
"date_begin": date_begin,
|
||||||
|
"date_end": date_end,
|
||||||
|
"sortby": sortby,
|
||||||
|
},
|
||||||
|
total=contract_count,
|
||||||
|
page=page,
|
||||||
|
step=self._items_per_page,
|
||||||
|
)
|
||||||
|
# content according to pager and archive selected
|
||||||
|
contracts = contract_obj.search(
|
||||||
|
domain, order=order, limit=self._items_per_page, offset=pager["offset"]
|
||||||
|
)
|
||||||
|
request.session["my_contracts_history"] = contracts.ids[:100]
|
||||||
|
values.update(
|
||||||
|
{
|
||||||
|
"date": date_begin,
|
||||||
|
"contracts": contracts,
|
||||||
|
"page_name": "Contracts",
|
||||||
|
"pager": pager,
|
||||||
|
"default_url": "/my/contracts",
|
||||||
|
"searchbar_sortings": searchbar_sortings,
|
||||||
|
"sortby": sortby,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return request.render("contract.portal_my_contracts", values)
|
||||||
|
|
||||||
|
@http.route(
|
||||||
|
["/my/contracts/<int:contract_contract_id>"],
|
||||||
|
type="http",
|
||||||
|
auth="public",
|
||||||
|
website=True,
|
||||||
|
)
|
||||||
|
def portal_my_contract_detail(self, contract_contract_id, access_token=None, **kw):
|
||||||
|
try:
|
||||||
|
contract_sudo = self._document_check_access(
|
||||||
|
"contract.contract", contract_contract_id, access_token
|
||||||
|
)
|
||||||
|
except (AccessError, MissingError):
|
||||||
|
return request.redirect("/my")
|
||||||
|
values = self._contract_get_page_view_values(contract_sudo, access_token, **kw)
|
||||||
|
return request.render("contract.portal_contract_page", values)
|
||||||
@@ -62,6 +62,8 @@
|
|||||||
%endif
|
%endif
|
||||||
<p></p>
|
<p></p>
|
||||||
</div>
|
</div>
|
||||||
|
<p></p>
|
||||||
|
<a href="${object.get_base_url()}/my/contracts/${object.id}?access_token=${object.access_token}" target="_blank" style="background-color:#875A7B;padding: 8px 16px 8px 16px; text-decoration: none; color: #fff; border-radius: 5px; font-size:13px;">View contract</a>
|
||||||
</div>
|
</div>
|
||||||
]]></field>
|
]]></field>
|
||||||
</record>
|
</record>
|
||||||
|
|||||||
12
contract/demo/assets.xml
Normal file
12
contract/demo/assets.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<!-- License AGPL-3.0 (http://www.gnu.org/licenses/agpl.html). -->
|
||||||
|
<odoo>
|
||||||
|
<template id="contract_frontend_demo" inherit_id="web.assets_frontend">
|
||||||
|
<xpath expr="//script[last()]" position="after">
|
||||||
|
<script
|
||||||
|
type="text/javascript"
|
||||||
|
src="/contract/static/src/js/contract_portal_tour.js"
|
||||||
|
/>
|
||||||
|
</xpath>
|
||||||
|
</template>
|
||||||
|
</odoo>
|
||||||
@@ -21,6 +21,7 @@ class ContractContract(models.Model):
|
|||||||
"mail.activity.mixin",
|
"mail.activity.mixin",
|
||||||
"contract.abstract.contract",
|
"contract.abstract.contract",
|
||||||
"contract.recurrency.mixin",
|
"contract.recurrency.mixin",
|
||||||
|
"portal.mixin",
|
||||||
]
|
]
|
||||||
|
|
||||||
active = fields.Boolean(default=True,)
|
active = fields.Boolean(default=True,)
|
||||||
@@ -113,6 +114,19 @@ class ContractContract(models.Model):
|
|||||||
track_visibility="onchange",
|
track_visibility="onchange",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _compute_access_url(self):
|
||||||
|
for record in self:
|
||||||
|
record.access_url = "/my/contracts/{}".format(record.id)
|
||||||
|
|
||||||
|
def action_preview(self):
|
||||||
|
"""Invoked when 'Preview' button in contract form view is clicked."""
|
||||||
|
self.ensure_one()
|
||||||
|
return {
|
||||||
|
"type": "ir.actions.act_url",
|
||||||
|
"target": "self",
|
||||||
|
"url": self.get_portal_url(),
|
||||||
|
}
|
||||||
|
|
||||||
def _inverse_partner_id(self):
|
def _inverse_partner_id(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
if not rec.invoice_partner_id:
|
if not rec.invoice_partner_id:
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
To view discount field in contract line, you need to set *Discount on lines* in
|
To view discount field in contract line, you need to set *Discount on lines* in
|
||||||
user access rights.
|
user access rights.
|
||||||
|
|
||||||
|
Contracts can be viewed on the portal (list and detail) if the user logged into the portal is a follower of the contract.
|
||||||
|
|||||||
@@ -2,3 +2,5 @@ This module enables contracts management with recurring
|
|||||||
invoicing functions. Also you can print and send by email contract report.
|
invoicing functions. Also you can print and send by email contract report.
|
||||||
|
|
||||||
It works for customer contract and supplier contracts.
|
It works for customer contract and supplier contracts.
|
||||||
|
|
||||||
|
Contracts are shown in portal.
|
||||||
|
|||||||
@@ -23,3 +23,9 @@
|
|||||||
#. Contract templates can be created from the Configuration -> Contracts -> Contract Templates menu.
|
#. Contract templates can be created from the Configuration -> Contracts -> Contract Templates menu.
|
||||||
They allow to define default journal, price list and lines when creating a contract.
|
They allow to define default journal, price list and lines when creating a contract.
|
||||||
To use it, just select the template on the contract and fields will be filled automatically.
|
To use it, just select the template on the contract and fields will be filled automatically.
|
||||||
|
|
||||||
|
* Contracts appear in portal to following users in every contract:
|
||||||
|
|
||||||
|
.. image:: ../static/src/screenshots/portal-my.png
|
||||||
|
.. image:: ../static/src/screenshots/portal-list.png
|
||||||
|
.. image:: ../static/src/screenshots/portal-detail.png
|
||||||
|
|||||||
@@ -8,6 +8,14 @@
|
|||||||
name="domain_force"
|
name="domain_force"
|
||||||
>['|',('company_id','=',False),('company_id','in',company_ids)]</field>
|
>['|',('company_id','=',False),('company_id','in',company_ids)]</field>
|
||||||
</record>
|
</record>
|
||||||
|
<record id="rule_contract_contract_portal" model="ir.rule">
|
||||||
|
<field name="name">Contract contract portal</field>
|
||||||
|
<field name="model_id" ref="model_contract_contract" />
|
||||||
|
<field
|
||||||
|
name="domain_force"
|
||||||
|
>[('message_partner_ids', 'in', [user.partner_id.id])]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('base.group_portal'))]" />
|
||||||
|
</record>
|
||||||
<record id="rule_contract_line_multi_company" model="ir.rule">
|
<record id="rule_contract_line_multi_company" model="ir.rule">
|
||||||
<field name="name">Contract line multi-company</field>
|
<field name="name">Contract line multi-company</field>
|
||||||
<field name="model_id" ref="model_contract_line" />
|
<field name="model_id" ref="model_contract_line" />
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
"contract_template_user","Recurring user","model_contract_template","account.group_account_invoice",1,0,0,0
|
"contract_template_user","Recurring user","model_contract_template","account.group_account_invoice",1,0,0,0
|
||||||
"contract_manager","Recurring manager","model_contract_contract","account.group_account_manager",1,1,1,1
|
"contract_manager","Recurring manager","model_contract_contract","account.group_account_manager",1,1,1,1
|
||||||
"contract_user","Recurring user","model_contract_contract","account.group_account_invoice",1,0,0,0
|
"contract_user","Recurring user","model_contract_contract","account.group_account_invoice",1,0,0,0
|
||||||
|
"contract_portal","Recurring portal","model_contract_contract","base.group_portal",1,0,0,0
|
||||||
"contract_line_manager","Recurring manager","model_contract_line","account.group_account_manager",1,1,1,1
|
"contract_line_manager","Recurring manager","model_contract_line","account.group_account_manager",1,1,1,1
|
||||||
"contract_line_user","Recurring user","model_contract_line","account.group_account_invoice",1,0,0,0
|
"contract_line_user","Recurring user","model_contract_line","account.group_account_invoice",1,0,0,0
|
||||||
|
"contract_line_portal","Recurring portal","model_contract_line","base.group_portal",1,0,0,0
|
||||||
"contract_template_line_manager","Recurring manager","model_contract_template_line","account.group_account_manager",1,1,1,1
|
"contract_template_line_manager","Recurring manager","model_contract_template_line","account.group_account_manager",1,1,1,1
|
||||||
"contract_template_line_user","Recurring user","model_contract_template_line","account.group_account_invoice",1,0,0,0
|
"contract_template_line_user","Recurring user","model_contract_template_line","account.group_account_invoice",1,0,0,0
|
||||||
|
|||||||
|
23
contract/static/src/js/contract_portal_tour.js
Normal file
23
contract/static/src/js/contract_portal_tour.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
odoo.define("contract.tour", function(require) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var tour = require("web_tour.tour");
|
||||||
|
|
||||||
|
tour.register(
|
||||||
|
"contract_portal_tour",
|
||||||
|
{
|
||||||
|
test: true,
|
||||||
|
url: "/my",
|
||||||
|
},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
content: "Go /my/contracts url",
|
||||||
|
trigger: 'a[href*="/my/contracts"]',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: "Go to Contract item",
|
||||||
|
trigger: ".tr_contract_link:eq(0)",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
from . import test_contract
|
from . import test_contract
|
||||||
|
from . import test_portal
|
||||||
|
|||||||
@@ -2302,3 +2302,8 @@ class TestContract(TestContractBase):
|
|||||||
# Assign same currency as computed one
|
# Assign same currency as computed one
|
||||||
self.contract2.currency_id = currency_cad.id
|
self.contract2.currency_id = currency_cad.id
|
||||||
self.assertFalse(self.contract2.manual_currency_id)
|
self.assertFalse(self.contract2.manual_currency_id)
|
||||||
|
|
||||||
|
def test_contract_action_preview(self):
|
||||||
|
action = self.contract.action_preview()
|
||||||
|
self.assertIn("/my/contracts/", action["url"])
|
||||||
|
self.assertIn("access_token=", action["url"])
|
||||||
|
|||||||
31
contract/tests/test_portal.py
Normal file
31
contract/tests/test_portal.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Copyright 2020 Tecnativa - Víctor Martínez
|
||||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl)
|
||||||
|
|
||||||
|
import odoo.tests
|
||||||
|
from odoo import http
|
||||||
|
|
||||||
|
|
||||||
|
@odoo.tests.tagged("post_install", "-at_install")
|
||||||
|
class TestContractPortal(odoo.tests.HttpCase):
|
||||||
|
def test_tour(self):
|
||||||
|
partner = self.env["res.partner"].create({"name": "partner test contract"})
|
||||||
|
contract = self.env["contract.contract"].create(
|
||||||
|
{"name": "Test Contract", "partner_id": partner.id}
|
||||||
|
)
|
||||||
|
user_portal = self.env.ref("base.demo_user0")
|
||||||
|
contract.message_subscribe(partner_ids=user_portal.partner_id.ids)
|
||||||
|
self.phantom_js(
|
||||||
|
"/",
|
||||||
|
"odoo.__DEBUG__.services['web_tour.tour'].run('contract_portal_tour')",
|
||||||
|
"odoo.__DEBUG__.services['web_tour.tour'].tours.contract_portal_tour.ready",
|
||||||
|
login="portal",
|
||||||
|
)
|
||||||
|
# Contract access
|
||||||
|
self.authenticate("portal", "portal")
|
||||||
|
http.root.session_store.save(self.session)
|
||||||
|
url_contract = "/my/contracts/{}?access_token={}".format(
|
||||||
|
contract.id, contract.access_token,
|
||||||
|
)
|
||||||
|
self.assertEqual(self.url_open(url=url_contract).status_code, 200)
|
||||||
|
contract.message_unsubscribe(partner_ids=user_portal.partner_id.ids)
|
||||||
|
self.assertEqual(self.url_open(url=url_contract).status_code, 200)
|
||||||
@@ -66,6 +66,7 @@
|
|||||||
attrs="{'invisible': [('is_terminated','=',False)]}"
|
attrs="{'invisible': [('is_terminated','=',False)]}"
|
||||||
groups="contract.can_terminate_contract"
|
groups="contract.can_terminate_contract"
|
||||||
/>
|
/>
|
||||||
|
<button type="object" string="Preview" name="action_preview" />
|
||||||
</header>
|
</header>
|
||||||
<sheet string="Contract">
|
<sheet string="Contract">
|
||||||
<field name="active" invisible="1" />
|
<field name="active" invisible="1" />
|
||||||
|
|||||||
274
contract/views/contract_portal_templates.xml
Normal file
274
contract/views/contract_portal_templates.xml
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<!-- Copyright 2020 Tecnativa - Víctor Martínez
|
||||||
|
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
|
||||||
|
<odoo>
|
||||||
|
<template
|
||||||
|
id="portal_my_home_menu_contract"
|
||||||
|
name="Portal layout : Contract menu entries"
|
||||||
|
inherit_id="portal.portal_breadcrumbs"
|
||||||
|
priority="35"
|
||||||
|
>
|
||||||
|
<xpath expr="//ol[hasclass('o_portal_submenu')]" position="inside">
|
||||||
|
<li
|
||||||
|
t-if="page_name == 'Contracts'"
|
||||||
|
t-attf-class="breadcrumb-item #{'active ' if not contract else ''}"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
t-if="contract"
|
||||||
|
t-attf-href="/my/contracts?{{ keep_query() }}"
|
||||||
|
>Contracts</a>
|
||||||
|
<t t-else="">Contracts</t>
|
||||||
|
</li>
|
||||||
|
<li t-if="contract" class="breadcrumb-item active">
|
||||||
|
<t t-esc="contract.name" />
|
||||||
|
</li>
|
||||||
|
</xpath>
|
||||||
|
</template>
|
||||||
|
<template
|
||||||
|
id="portal_my_home_contract"
|
||||||
|
name="Portal My Home : Contract entries"
|
||||||
|
inherit_id="portal.portal_my_home"
|
||||||
|
priority="30"
|
||||||
|
>
|
||||||
|
<xpath expr="//div[hasclass('o_portal_docs')]" position="inside">
|
||||||
|
<t t-call="portal.portal_docs_entry" t-if="contract_count">
|
||||||
|
<t t-set="title">Contracts</t>
|
||||||
|
<t t-set="url" t-value="'/my/contracts'" />
|
||||||
|
<t t-set="count" t-value="contract_count" />
|
||||||
|
</t>
|
||||||
|
</xpath>
|
||||||
|
</template>
|
||||||
|
<template id="portal_my_contracts" name="My Contracts">
|
||||||
|
<t t-call="portal.portal_layout">
|
||||||
|
<t t-set="breadcrumbs_searchbar" t-value="True" />
|
||||||
|
<t t-call="portal.portal_searchbar">
|
||||||
|
<t t-set="title">Contracts</t>
|
||||||
|
</t>
|
||||||
|
<t t-if="contracts" t-call="portal.portal_table">
|
||||||
|
<thead>
|
||||||
|
<tr class="active">
|
||||||
|
<th>Contract #</th>
|
||||||
|
<th class='d-none d-md-table-cell'>Date</th>
|
||||||
|
<th class='d-none d-md-table-cell'>Date end</th>
|
||||||
|
<th class='text-right'>Reference</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<t t-foreach="contracts" t-as="contract">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a
|
||||||
|
t-att-href="contract.get_portal_url()"
|
||||||
|
t-attf-class="tr_contract_link"
|
||||||
|
t-att-title="contract.name"
|
||||||
|
>
|
||||||
|
<t t-esc="contract.name" />
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td class="d-none d-md-table-cell">
|
||||||
|
<span t-field="contract.recurring_next_date" />
|
||||||
|
</td>
|
||||||
|
<td class="d-none d-md-table-cell">
|
||||||
|
<span t-field="contract.date_end" />
|
||||||
|
</td>
|
||||||
|
<td class='text-right'>
|
||||||
|
<span t-field="contract.code" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</t>
|
||||||
|
</tbody>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
<template id="portal_contract_page" name="My Contract">
|
||||||
|
<t t-call="portal.portal_layout">
|
||||||
|
<t t-set="o_portal_fullwidth_alert">
|
||||||
|
<t t-call="portal.portal_back_in_edit_mode">
|
||||||
|
<t
|
||||||
|
t-set="backend_url"
|
||||||
|
t-value="'/web#return_label=Website&model=contract.contract&id=%s&view_type=form' % (contract.id)"
|
||||||
|
/>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
<t t-call="portal.portal_record_layout">
|
||||||
|
<t t-set="card_header">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<span>
|
||||||
|
Contract Order - <span t-field="contract.name" />
|
||||||
|
</span>
|
||||||
|
</h5>
|
||||||
|
</t>
|
||||||
|
<t t-set="card_body">
|
||||||
|
<div id="general_information">
|
||||||
|
<div class="row mt4">
|
||||||
|
<div
|
||||||
|
t-if="contract.partner_id"
|
||||||
|
class="col-12 col-md-6 mb-4 mb-md-0"
|
||||||
|
>
|
||||||
|
<h6>
|
||||||
|
<strong>Customer:</strong>
|
||||||
|
</h6>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col flex-grow-0 pr-3">
|
||||||
|
<img
|
||||||
|
t-if="contract.partner_id.image_128"
|
||||||
|
class="rounded-circle mt-1 o_portal_contact_img"
|
||||||
|
t-att-src="image_data_uri(contract.partner_id.image_128)"
|
||||||
|
alt="Contact"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
t-else=""
|
||||||
|
class="rounded-circle mt-1 o_portal_contact_img"
|
||||||
|
src="/web/static/src/img/user_menu_avatar.png"
|
||||||
|
alt="Contact"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col pl-sm-0">
|
||||||
|
<address
|
||||||
|
t-field="contract.partner_id"
|
||||||
|
t-options='{"widget": "contact", "fields": ["name", "email", "phone"]}'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div t-if="contract.user_id" class="col-12 col-md-6">
|
||||||
|
<h6>
|
||||||
|
<strong>Responsible:</strong>
|
||||||
|
</h6>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col flex-grow-0 pr-3">
|
||||||
|
<img
|
||||||
|
t-if="contract.user_id.image_128"
|
||||||
|
class="rounded-circle mt-1 o_portal_contact_img"
|
||||||
|
t-att-src="image_data_uri(contract.user_id.image_128)"
|
||||||
|
alt="Contact"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
t-else=""
|
||||||
|
class="rounded-circle mt-1 o_portal_contact_img"
|
||||||
|
src="/web/static/src/img/user_menu_avatar.png"
|
||||||
|
alt="Contact"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col pl-sm-0">
|
||||||
|
<address
|
||||||
|
t-field="contract.user_id"
|
||||||
|
t-options='{"widget": "contact", "fields": ["name", "email", "phone"]}'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt32" id="product_information">
|
||||||
|
<div class="col-12 col-md-6 mb-4 mb-md-0">
|
||||||
|
<div t-if="contract.code" class="row mb-2 mb-sm-1">
|
||||||
|
<div class="col-12 col-sm-4">
|
||||||
|
<strong>Reference</strong>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-8">
|
||||||
|
<span t-field="contract.code" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
t-if="contract.recurring_next_date"
|
||||||
|
class="row mb-2 mb-sm-1"
|
||||||
|
>
|
||||||
|
<div class="col-12 col-sm-4">
|
||||||
|
<strong>Date of Next Invoice</strong>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-8">
|
||||||
|
<span
|
||||||
|
t-field="contract.recurring_next_date"
|
||||||
|
t-options='{"widget": "date"}'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div t-if="contract.date_end" class="row mb-2 mb-sm-1">
|
||||||
|
<div class="col-12 col-sm-4">
|
||||||
|
<strong>Date end</strong>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-8">
|
||||||
|
<span
|
||||||
|
t-field="contract.date_end"
|
||||||
|
t-options='{"widget": "date"}'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="item_details">
|
||||||
|
<table class="table table-sm" id="sales_order_table">
|
||||||
|
<thead class="bg-100">
|
||||||
|
<tr>
|
||||||
|
<th class="text-left">Description</th>
|
||||||
|
<th class="text-right">Recurrence</th>
|
||||||
|
<th class="text-right">Date of next invoice</th>
|
||||||
|
<th class="text-right">Last date invoice</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="contract_tbody">
|
||||||
|
<t
|
||||||
|
t-foreach="contract.contract_line_ids"
|
||||||
|
t-as="line"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td id="line_name">
|
||||||
|
<span t-field="line.name" />
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
<span
|
||||||
|
t-field="line.recurring_interval"
|
||||||
|
/>
|
||||||
|
<t
|
||||||
|
t-if="line.recurring_rule_type=='daily'"
|
||||||
|
>Day(s)</t>
|
||||||
|
<t
|
||||||
|
t-if="line.recurring_rule_type=='weekly'"
|
||||||
|
>Week(s)</t>
|
||||||
|
<t
|
||||||
|
t-if="line.recurring_rule_type=='monthly'"
|
||||||
|
>Month(s)</t>
|
||||||
|
<t
|
||||||
|
t-if="line.recurring_rule_type=='monthlylastday'"
|
||||||
|
>Month(s) last day</t>
|
||||||
|
<t
|
||||||
|
t-if="line.recurring_rule_type=='quarterly'"
|
||||||
|
>Quarter(s)</t>
|
||||||
|
<t
|
||||||
|
t-if="line.recurring_rule_type=='semesterly'"
|
||||||
|
>Semester(s)</t>
|
||||||
|
<t
|
||||||
|
t-if="line.recurring_rule_type=='yearly'"
|
||||||
|
>Year(s)</t>
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
<span
|
||||||
|
t-field="line.recurring_next_date"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
<span
|
||||||
|
t-field="line.last_date_invoiced"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</t>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
<!-- chatter -->
|
||||||
|
<div id="contract_communication" class="mt-4">
|
||||||
|
<h2>Communication</h2>
|
||||||
|
<t t-call="portal.message_thread">
|
||||||
|
<t t-set="object" t-value="contract" />
|
||||||
|
<t t-set="token" t-value="contract.access_token" />
|
||||||
|
<t t-set="pid" t-value="pid" />
|
||||||
|
<t t-set="hash" t-value="hash" />
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
</odoo>
|
||||||
Reference in New Issue
Block a user