[IMP] partner_profiles_portal: new portal structures data management
refactors the way a portal user can edit the data of the structures he is affiliated with. All main, public and position profiles data are gathered in one "structure" form. Adds several navigation improvement (back to structures list, validation message, ...)
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
"name": "partner_profiles_portal",
|
"name": "partner_profiles_portal",
|
||||||
"version": "12.0.2.0.0",
|
"version": "12.0.2.1.0",
|
||||||
"author": "Elabore",
|
"author": "Elabore",
|
||||||
"website": "https://elabore.coop",
|
"website": "https://elabore.coop",
|
||||||
"maintainer": "Stéphan Sainléger",
|
"maintainer": "Stéphan Sainléger",
|
||||||
@@ -26,8 +26,8 @@
|
|||||||
"data": [
|
"data": [
|
||||||
"security/members_security.xml",
|
"security/members_security.xml",
|
||||||
"views/portal_home_template.xml",
|
"views/portal_home_template.xml",
|
||||||
"views/portal_my_profiles_template.xml",
|
"views/portal_my_structures_template.xml",
|
||||||
"views/portal_partner_profile_template.xml",
|
"views/portal_partner_structure_template.xml",
|
||||||
"views/portal_my_account.xml",
|
"views/portal_my_account.xml",
|
||||||
"views/res_partner_view.xml",
|
"views/res_partner_view.xml",
|
||||||
],
|
],
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from . import portal_my_profiles
|
from . import portal_my_structures
|
||||||
from . import portal_partner_profile
|
from . import portal_structure_profile
|
||||||
from . import portal_my_account
|
from . import portal_my_account
|
@@ -5,49 +5,38 @@ from odoo.http import request
|
|||||||
from odoo.addons.portal.controllers.portal import CustomerPortal, pager as portal_pager
|
from odoo.addons.portal.controllers.portal import CustomerPortal, pager as portal_pager
|
||||||
|
|
||||||
|
|
||||||
class CustomerPortalMyProfiles(CustomerPortal):
|
class CustomerPortalMyStructures(CustomerPortal):
|
||||||
|
|
||||||
def _get_domain_my_profiles(self, user):
|
def _get_domain_my_structures(self, user):
|
||||||
if user.partner_id.other_contact_ids:
|
if user.partner_id.other_contact_ids:
|
||||||
main_profile_ids = user.partner_id.other_contact_ids.filtered(
|
main_structure_ids = user.partner_id.other_contact_ids.mapped("parent_id")
|
||||||
"edit_structure_main_profile"
|
return [("id", "in", main_structure_ids.ids),
|
||||||
).mapped("parent_id")
|
("is_company", "=", True)
|
||||||
public_profile_ids = user.partner_id.other_contact_ids.filtered(
|
|
||||||
"edit_structure_public_profile"
|
|
||||||
).mapped("parent_id.public_profile_id")
|
|
||||||
return [
|
|
||||||
"|",
|
|
||||||
"|",
|
|
||||||
("contact_id", "=", user.partner_id.id),
|
|
||||||
("id", "in", main_profile_ids.ids),
|
|
||||||
("id", "in", public_profile_ids.ids),
|
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
return [("contact_id", "=", user.partner_id.id)]
|
return None
|
||||||
|
|
||||||
def _prepare_portal_layout_values(self):
|
def _prepare_portal_layout_values(self):
|
||||||
values = super(CustomerPortalMyProfiles, self)._prepare_portal_layout_values()
|
values = super(CustomerPortalMyStructures, self)._prepare_portal_layout_values()
|
||||||
values["profile_count"] = request.env["res.partner"].search_count(
|
domain = self._get_domain_my_structures(request.env.user)
|
||||||
self._get_domain_my_profiles(request.env.user)
|
values["structure_count"] = request.env["res.partner"].search_count(domain) if domain else 0
|
||||||
)
|
|
||||||
return values
|
return values
|
||||||
|
|
||||||
@http.route(
|
@http.route(
|
||||||
["/my/profiles", "/my/profiles/page/<int:page>"],
|
["/my/structures", "/my/structures/page/<int:page>"],
|
||||||
type="http",
|
type="http",
|
||||||
auth="user",
|
auth="user",
|
||||||
website=True,
|
website=True,
|
||||||
)
|
)
|
||||||
def portal_my_profiles(
|
def portal_my_structures(
|
||||||
self, page=1, date_begin=None, date_end=None, sortby=None, **kw
|
self, page=1, date_begin=None, date_end=None, sortby=None, **kw
|
||||||
):
|
):
|
||||||
values = self._prepare_portal_layout_values()
|
values = self._prepare_portal_layout_values()
|
||||||
profile = request.env["res.partner"]
|
structure = request.env["res.partner"]
|
||||||
domain = self._get_domain_my_profiles(request.env.user)
|
domain = self._get_domain_my_structures(request.env.user)
|
||||||
|
|
||||||
searchbar_sortings = {
|
searchbar_sortings = {
|
||||||
"name": {"label": _("Name"), "order": "name"},
|
"name": {"label": _("Name"), "order": "name"},
|
||||||
"partner_profile": {"label": _("Profile Type"), "order": "partner_profile"},
|
|
||||||
"parent_id": {"label": _("Company"), "order": "parent_id"},
|
"parent_id": {"label": _("Company"), "order": "parent_id"},
|
||||||
}
|
}
|
||||||
if not sortby:
|
if not sortby:
|
||||||
@@ -57,35 +46,35 @@ class CustomerPortalMyProfiles(CustomerPortal):
|
|||||||
# archive groups - Default Group By 'create_date'
|
# archive groups - Default Group By 'create_date'
|
||||||
archive_groups = self._get_archive_groups("res.partner", domain)
|
archive_groups = self._get_archive_groups("res.partner", domain)
|
||||||
|
|
||||||
# profiles count
|
# structures count
|
||||||
profile_count = profile.search_count(domain)
|
structure_count = structure.search_count(domain) if domain else 0
|
||||||
# pager
|
# pager
|
||||||
pager = portal_pager(
|
pager = portal_pager(
|
||||||
url="/my/profiles",
|
url="/my/structures",
|
||||||
url_args={"sortby": sortby},
|
url_args={"sortby": sortby},
|
||||||
total=profile_count,
|
total=structure_count,
|
||||||
page=page,
|
page=page,
|
||||||
step=self._items_per_page,
|
step=self._items_per_page,
|
||||||
)
|
)
|
||||||
|
|
||||||
# content according to pager and archive selected
|
# content according to pager and archive selected
|
||||||
profiles = profile.search(
|
structures = structure.search(
|
||||||
domain,
|
domain,
|
||||||
order=order,
|
order=order,
|
||||||
limit=self._items_per_page,
|
limit=self._items_per_page,
|
||||||
offset=pager["offset"],
|
offset=pager["offset"],
|
||||||
)
|
) if domain else None
|
||||||
request.session["my_profiles_history"] = profiles.ids[:100]
|
request.session["my_structures_history"] = structures.ids[:100] if structures else None
|
||||||
|
|
||||||
values.update(
|
values.update(
|
||||||
{
|
{
|
||||||
"profiles": profiles,
|
"structures": structures,
|
||||||
"page_name": "profile",
|
"page_name": "structure",
|
||||||
"archive_groups": archive_groups,
|
"archive_groups": archive_groups,
|
||||||
"default_url": "/my/profiles",
|
"default_url": "/my/structures",
|
||||||
"pager": pager,
|
"pager": pager,
|
||||||
"searchbar_sortings": searchbar_sortings,
|
"searchbar_sortings": searchbar_sortings,
|
||||||
"sortby": sortby,
|
"sortby": sortby,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return request.render("partner_profiles_portal.portal_my_profiles", values)
|
return request.render("partner_profiles_portal.portal_my_structures", values)
|
@@ -1,130 +0,0 @@
|
|||||||
# Copyright 2020 Lokavaluto ()
|
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
||||||
from odoo import http, tools, _
|
|
||||||
from odoo.exceptions import AccessError, MissingError
|
|
||||||
from odoo.http import request
|
|
||||||
from odoo.addons.portal.controllers.portal import CustomerPortal
|
|
||||||
|
|
||||||
|
|
||||||
class CustomerPortalPartnerProfile(CustomerPortal):
|
|
||||||
|
|
||||||
def _profile_get_page_view_values(self, profile, access_token, **kwargs):
|
|
||||||
values = {
|
|
||||||
"page_name": "profile",
|
|
||||||
"profile": profile,
|
|
||||||
}
|
|
||||||
return self._get_page_view_values(
|
|
||||||
profile, access_token, values, "my_profiles_history", False, **kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
def _details_profile_form_validate(self, data, profile_id):
|
|
||||||
error = dict()
|
|
||||||
error_message = []
|
|
||||||
# nickname uniqueness
|
|
||||||
if data.get("nickname") and request.env["res.partner"].sudo().search(
|
|
||||||
[
|
|
||||||
("name", "=", data.get("nickname")),
|
|
||||||
("partner_profile.ref", "=", "partner_profile_public"),
|
|
||||||
("id", "!=", profile_id),
|
|
||||||
]
|
|
||||||
):
|
|
||||||
error["nickname"] = "error"
|
|
||||||
error_message.append(
|
|
||||||
_("This nickname is already used, please find an other idea.")
|
|
||||||
)
|
|
||||||
|
|
||||||
# email validation
|
|
||||||
if data.get("email") and not tools.single_email_re.match(data.get("email")):
|
|
||||||
error["email"] = "error"
|
|
||||||
error_message.append(
|
|
||||||
_("Invalid Email! Please enter a valid email address.")
|
|
||||||
)
|
|
||||||
return error, error_message
|
|
||||||
|
|
||||||
def _get_profile_fields(self):
|
|
||||||
fields = [
|
|
||||||
"nickname",
|
|
||||||
"function",
|
|
||||||
"phone",
|
|
||||||
"mobile",
|
|
||||||
"email",
|
|
||||||
"website_url",
|
|
||||||
"street",
|
|
||||||
"street2",
|
|
||||||
"city",
|
|
||||||
"country_id",
|
|
||||||
"zipcode",
|
|
||||||
]
|
|
||||||
return fields
|
|
||||||
|
|
||||||
def _get_page_saving_values(self, profile, kw):
|
|
||||||
profile_fields = self._get_profile_fields()
|
|
||||||
values = {key: kw[key] for key in profile_fields if key in kw}
|
|
||||||
values.update(
|
|
||||||
{
|
|
||||||
"name": values.pop("nickname", profile.name),
|
|
||||||
"zip": values.pop("zipcode", ""),
|
|
||||||
"website": values.pop("website_url", ""),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return values
|
|
||||||
|
|
||||||
def _get_page_opening_values(self):
|
|
||||||
# Just retrieve the values to display for Selection fields
|
|
||||||
countries = request.env["res.country"].sudo().search([])
|
|
||||||
values = {
|
|
||||||
"countries": countries,
|
|
||||||
}
|
|
||||||
return values
|
|
||||||
|
|
||||||
@http.route(
|
|
||||||
["/my/profile/<int:profile_id>", "/my/profile/save"],
|
|
||||||
type="http",
|
|
||||||
auth="user",
|
|
||||||
website=True,
|
|
||||||
)
|
|
||||||
def portal_my_profile(
|
|
||||||
self, profile_id=None, access_token=None, redirect=None, **kw
|
|
||||||
):
|
|
||||||
# The following condition is to transform profile_id to an int, as it is sent as a string from the templace "portal_my_profile"
|
|
||||||
# TODO: find a better way to retrieve the profile_id at form submit step
|
|
||||||
if not isinstance(profile_id, int):
|
|
||||||
profile_id = int(profile_id)
|
|
||||||
|
|
||||||
# Check that the user has the right to see this profile
|
|
||||||
try:
|
|
||||||
profile_sudo = self._document_check_access(
|
|
||||||
"res.partner", profile_id, access_token
|
|
||||||
)
|
|
||||||
except (AccessError, MissingError):
|
|
||||||
return request.redirect("/my/profiles")
|
|
||||||
|
|
||||||
values = self._profile_get_page_view_values(profile_sudo, access_token, **kw)
|
|
||||||
values.update(
|
|
||||||
{
|
|
||||||
"error": {},
|
|
||||||
"error_message": [],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if kw and request.httprequest.method == "POST":
|
|
||||||
# the user has clicked in the Save button to save new data
|
|
||||||
error, error_message = self._details_profile_form_validate(kw, profile_id)
|
|
||||||
values.update({"error": error, "error_message": error_message})
|
|
||||||
values.update(kw)
|
|
||||||
if not error:
|
|
||||||
profile = request.env["res.partner"].browse(profile_id)
|
|
||||||
values = self._get_page_saving_values(profile, kw)
|
|
||||||
profile.sudo().write(values)
|
|
||||||
if redirect:
|
|
||||||
return request.redirect(redirect)
|
|
||||||
return request.redirect("/my/profiles")
|
|
||||||
|
|
||||||
# This is just the form page opening. We send all the data needed for the form fields
|
|
||||||
values.update(self._get_page_opening_values())
|
|
||||||
values.update(
|
|
||||||
{
|
|
||||||
"profile_id": profile_id, # Sent in order to retrieve it at submit time
|
|
||||||
"redirect": redirect
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return request.render("partner_profiles_portal.portal_my_profile", values)
|
|
198
partner_profiles_portal/controllers/portal_structure_profile.py
Normal file
198
partner_profiles_portal/controllers/portal_structure_profile.py
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
# Copyright 2020 Lokavaluto ()
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
import base64
|
||||||
|
from odoo import http, tools, _
|
||||||
|
from odoo.exceptions import AccessError, MissingError
|
||||||
|
from odoo.http import request
|
||||||
|
from odoo.addons.portal.controllers.portal import CustomerPortal
|
||||||
|
|
||||||
|
|
||||||
|
class CustomerPortalStructureProfile(CustomerPortal):
|
||||||
|
|
||||||
|
def _structure_get_page_view_values(self, structure, access_token, **kwargs):
|
||||||
|
values = {
|
||||||
|
"page_name": "structure",
|
||||||
|
"structure": structure,
|
||||||
|
}
|
||||||
|
return self._get_page_view_values(
|
||||||
|
structure, access_token, values, "my_structures_history", False, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
def _details_structure_form_validate(self, data, structure_id):
|
||||||
|
error = dict()
|
||||||
|
error_message = []
|
||||||
|
# public name uniqueness
|
||||||
|
if data.get("public_name") and request.env["res.partner"].sudo().search(
|
||||||
|
[
|
||||||
|
("name", "=", data.get("public_name")),
|
||||||
|
("is_public_profile", "=", True),
|
||||||
|
("contact_id", "!=", structure_id),
|
||||||
|
]
|
||||||
|
):
|
||||||
|
error["public_name"] = "error"
|
||||||
|
error_message.append(
|
||||||
|
_("This public name is already used, please find an other idea.")
|
||||||
|
)
|
||||||
|
|
||||||
|
# email validation
|
||||||
|
if data.get("email") and not tools.single_email_re.match(data.get("email")):
|
||||||
|
error["email"] = "error"
|
||||||
|
error_message.append(
|
||||||
|
_("Invalid Email! Please enter a valid email address.")
|
||||||
|
)
|
||||||
|
return error, error_message
|
||||||
|
|
||||||
|
def _get_main_profile_fields(self):
|
||||||
|
'''Provides all the fields that must fill the structure's main profile.
|
||||||
|
All of them MUST start with "main_".'''
|
||||||
|
fields = [
|
||||||
|
"main_name",
|
||||||
|
"main_street",
|
||||||
|
"main_street2",
|
||||||
|
"main_zip",
|
||||||
|
"main_city",
|
||||||
|
"main_country_id",
|
||||||
|
"main_phone",
|
||||||
|
"main_mobile",
|
||||||
|
"main_email",
|
||||||
|
"main_website",
|
||||||
|
]
|
||||||
|
return fields
|
||||||
|
|
||||||
|
def _get_public_profile_fields(self):
|
||||||
|
'''Provides all the fields that must fill the structure's public profile.
|
||||||
|
All of them MUST start with "public_".'''
|
||||||
|
fields = [
|
||||||
|
"public_name",
|
||||||
|
"public_street2",
|
||||||
|
"public_street",
|
||||||
|
"public_zip",
|
||||||
|
"public_city",
|
||||||
|
"public_phone",
|
||||||
|
"public_mobile",
|
||||||
|
"public_email",
|
||||||
|
"public_website",
|
||||||
|
]
|
||||||
|
return fields
|
||||||
|
|
||||||
|
def _get_position_profile_fields(self):
|
||||||
|
'''Provides all the fields that must fill the structure's position profile of the user.
|
||||||
|
All of them MUST start with "position_".'''
|
||||||
|
fields = [
|
||||||
|
"position_function",
|
||||||
|
"position_phone",
|
||||||
|
"position_email",
|
||||||
|
]
|
||||||
|
return fields
|
||||||
|
|
||||||
|
def _transform_in_res_partner_fields(self, kw, profile_fields, prefix=""):
|
||||||
|
'''Transforms kw's values in res_partner fields and values'''
|
||||||
|
return {key[len(prefix):]: kw[key] for key in profile_fields if key in kw}
|
||||||
|
|
||||||
|
def _get_page_saving_main_values(self, kw):
|
||||||
|
profile_fields = self._get_main_profile_fields()
|
||||||
|
values = self._transform_in_res_partner_fields(kw, profile_fields, "main_")
|
||||||
|
if 'logo' in kw:
|
||||||
|
image = kw.get('logo')
|
||||||
|
if image:
|
||||||
|
image = image.read()
|
||||||
|
image = base64.b64encode(image)
|
||||||
|
values.update({
|
||||||
|
'image': image
|
||||||
|
})
|
||||||
|
return values
|
||||||
|
|
||||||
|
def _get_page_saving_public_values(self, kw):
|
||||||
|
profile_fields = self._get_public_profile_fields()
|
||||||
|
values = self._transform_in_res_partner_fields(kw, profile_fields, "public_")
|
||||||
|
return values
|
||||||
|
|
||||||
|
def _get_page_saving_position_values(self, kw):
|
||||||
|
profile_fields = self._get_position_profile_fields()
|
||||||
|
values = self._transform_in_res_partner_fields(kw, profile_fields, "position_")
|
||||||
|
return values
|
||||||
|
|
||||||
|
def _get_page_opening_values(self):
|
||||||
|
# Just retrieve the values to display for Selection fields
|
||||||
|
countries = request.env["res.country"].sudo().search([])
|
||||||
|
values = {
|
||||||
|
"countries": countries,
|
||||||
|
}
|
||||||
|
return values
|
||||||
|
|
||||||
|
@http.route(
|
||||||
|
["/my/structure/<int:structure_id>", "/my/structure/save"],
|
||||||
|
type="http",
|
||||||
|
auth="user",
|
||||||
|
website=True,
|
||||||
|
)
|
||||||
|
def portal_my_structure(
|
||||||
|
self,structure_id=None, access_token=None, redirect=None, **kw
|
||||||
|
):
|
||||||
|
# The following condition is to transform profile_id to an int, as it is sent as a string from the templace "portal_my_profile"
|
||||||
|
# TODO: find a better way to retrieve the profile_id at form submit step
|
||||||
|
if not isinstance(structure_id, int):
|
||||||
|
structure_id = int(structure_id)
|
||||||
|
|
||||||
|
# Check that the user has the right to see this profile
|
||||||
|
try:
|
||||||
|
structure_sudo = self._document_check_access(
|
||||||
|
"res.partner", structure_id, access_token
|
||||||
|
)
|
||||||
|
except (AccessError, MissingError):
|
||||||
|
return request.redirect("/my/structures")
|
||||||
|
|
||||||
|
Partner = request.env["res.partner"]
|
||||||
|
partner_id = request.env.user.partner_id
|
||||||
|
main_profile = Partner.browse(structure_id)
|
||||||
|
public_profile = Partner.browse(main_profile.public_profile_id.id)
|
||||||
|
position_profile = Partner.search(
|
||||||
|
[
|
||||||
|
("parent_id", "=", structure_id),
|
||||||
|
("contact_id", "=", partner_id.id),
|
||||||
|
("is_position_profile", "=", True),
|
||||||
|
("active", "=", True)
|
||||||
|
],
|
||||||
|
limit=1
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
values = self._structure_get_page_view_values(structure_sudo, access_token, **kw)
|
||||||
|
values.update(
|
||||||
|
{
|
||||||
|
"error": {},
|
||||||
|
"error_message": [],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if kw and request.httprequest.method == "POST":
|
||||||
|
# the user has clicked in the Save button to save new data
|
||||||
|
error, error_message = self._details_structure_form_validate(kw, structure_id)
|
||||||
|
values.update({"error": error, "error_message": error_message})
|
||||||
|
values.update(kw)
|
||||||
|
if not error:
|
||||||
|
# Update main profile
|
||||||
|
new_values = self._get_page_saving_main_values(kw)
|
||||||
|
main_profile.sudo().write(new_values)
|
||||||
|
# Update public profile
|
||||||
|
new_values = self._get_page_saving_public_values(kw)
|
||||||
|
public_profile.sudo().write(new_values)
|
||||||
|
# Update position profile
|
||||||
|
new_values = self._get_page_saving_position_values(kw)
|
||||||
|
position_profile.sudo().write(new_values)
|
||||||
|
# End of updates
|
||||||
|
if redirect:
|
||||||
|
return request.redirect(redirect)
|
||||||
|
return request.redirect("/my/structures")
|
||||||
|
|
||||||
|
# This is just the form page opening. We send all the data needed for the form fields
|
||||||
|
can_edit_structure = partner_id in main_profile.can_edit_structure_profiles_ids
|
||||||
|
values.update(self._get_page_opening_values())
|
||||||
|
values.update(
|
||||||
|
{
|
||||||
|
"structure_id": structure_id, # Sent in order to retrieve it at submit time
|
||||||
|
"public_profile": public_profile,
|
||||||
|
"position_profile": position_profile,
|
||||||
|
"can_edit_structure": can_edit_structure,
|
||||||
|
"redirect": "/my/structure/" + str(structure_id) + "?success=True"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return request.render("partner_profiles_portal.portal_structure", values)
|
@@ -10,44 +10,37 @@ _logger = logging.getLogger(__name__)
|
|||||||
class res_partner(models.Model):
|
class res_partner(models.Model):
|
||||||
_inherit = "res.partner"
|
_inherit = "res.partner"
|
||||||
|
|
||||||
edit_structure_main_profile = fields.Boolean(
|
edit_structure_profiles = fields.Boolean(
|
||||||
string=_("Manage structure's main profile")
|
string="Manage structure's profiles"
|
||||||
)
|
)
|
||||||
edit_structure_public_profile = fields.Boolean(
|
can_edit_structure_profiles_ids = fields.Many2many(
|
||||||
string=_("Manage structure's public profile")
|
|
||||||
)
|
|
||||||
can_edit_main_profile_ids = fields.Many2many(
|
|
||||||
"res.partner",
|
"res.partner",
|
||||||
relation="res_partner_main_profile_rel",
|
relation="res_partner_main_profile_rel",
|
||||||
column1="partner_id",
|
column1="partner_id",
|
||||||
column2="profile_id",
|
column2="profile_id",
|
||||||
store=True,
|
store=True,
|
||||||
compute="_compute_can_edit",
|
compute="_compute_can_read_edit",
|
||||||
string="Can edit main profile",
|
string="Can edit struture profiles",
|
||||||
)
|
)
|
||||||
can_edit_public_profile_ids = fields.Many2many(
|
child_main_contact_ids = fields.Many2many(
|
||||||
"res.partner",
|
"res.partner",
|
||||||
relation="res_partner_public_profile_rel",
|
relation="res_partner_child_contacts_rel",
|
||||||
column1="partner_id",
|
column1="partner_id",
|
||||||
column2="profile_id",
|
column2="profile_id",
|
||||||
store=True,
|
store=True,
|
||||||
compute="_compute_can_edit",
|
compute="_compute_can_read_edit",
|
||||||
string="Can edit public profile",
|
string="Can read structure profiles",
|
||||||
)
|
)
|
||||||
|
|
||||||
@api.depends(
|
@api.depends(
|
||||||
"other_contact_ids",
|
"other_contact_ids",
|
||||||
"other_contact_ids.edit_structure_main_profile",
|
"other_contact_ids.edit_structure_profiles",
|
||||||
"other_contact_ids.edit_structure_public_profile",
|
|
||||||
"child_ids",
|
"child_ids",
|
||||||
"child_ids.edit_structure_main_profile",
|
"child_ids.edit_structure_profiles",
|
||||||
"child_ids.edit_structure_public_profile",
|
|
||||||
)
|
)
|
||||||
def _compute_can_edit(self):
|
def _compute_can_read_edit(self):
|
||||||
for partner in self:
|
for partner in self:
|
||||||
partner.can_edit_main_profile_ids = partner.child_ids.filtered(
|
partner.can_edit_structure_profiles_ids = partner.child_ids.filtered(
|
||||||
"edit_structure_main_profile"
|
"edit_structure_profiles"
|
||||||
).mapped("contact_id")
|
).mapped("contact_id")
|
||||||
partner.can_edit_public_profile_ids = partner.child_ids.filtered(
|
partner.child_main_contact_ids = partner.child_ids.mapped("contact_id")
|
||||||
"edit_structure_public_profile"
|
|
||||||
).mapped("contact_id")
|
|
@@ -1,11 +1,24 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<record model="ir.rule" id="res_partner_portal_members_rule">
|
<record model="ir.rule" id="res_partner_portal_members_read_rule">
|
||||||
<field name="name">res_partner: portal: read/write access on my profiles</field>
|
<field name="name">res_partner: portal: read access on my structures</field>
|
||||||
<field name="model_id" ref="base.model_res_partner" />
|
<field name="model_id" ref="base.model_res_partner" />
|
||||||
<field name="domain_force">['|','|',('contact_id', '=', user.partner_id.id),
|
<field name="domain_force">['|','|',('contact_id', '=', user.partner_id.id),
|
||||||
('can_edit_main_profile_ids', 'in', [user.partner_id.id]),
|
('child_main_contact_ids', 'in', [user.partner_id.id]),
|
||||||
('contact_id.can_edit_public_profile_ids', 'in', [user.partner_id.id])]</field>
|
('contact_id.child_main_contact_ids', 'in', [user.partner_id.id])]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('base.group_portal'))]" />
|
||||||
|
<field name="perm_read" eval="True" />
|
||||||
|
<field name="perm_write" eval="False" />
|
||||||
|
<field name="perm_create" eval="False" />
|
||||||
|
<field name="perm_unlink" eval="False" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.rule" id="res_partner_portal_members_write_rule">
|
||||||
|
<field name="name">res_partner: portal: write access on my structures</field>
|
||||||
|
<field name="model_id" ref="base.model_res_partner" />
|
||||||
|
<field name="domain_force">['|','|',('contact_id', '=', user.partner_id.id),
|
||||||
|
('can_edit_structure_profiles_ids', 'in', [user.partner_id.id]),
|
||||||
|
('contact_id.can_edit_structure_profiles_ids', 'in', [user.partner_id.id])]</field>
|
||||||
<field name="groups" eval="[(4, ref('base.group_portal'))]" />
|
<field name="groups" eval="[(4, ref('base.group_portal'))]" />
|
||||||
<field name="perm_read" eval="True" />
|
<field name="perm_read" eval="True" />
|
||||||
<field name="perm_write" eval="True" />
|
<field name="perm_write" eval="True" />
|
||||||
|
@@ -14,9 +14,10 @@
|
|||||||
My information
|
My information
|
||||||
</button>
|
</button>
|
||||||
</a>
|
</a>
|
||||||
<a t-attf-href="/my/profiles">
|
<br />
|
||||||
|
<a t-attf-href="/my/structures">
|
||||||
<button class="btn btn-primary mb8">
|
<button class="btn btn-primary mb8">
|
||||||
Consult my profiles
|
My structures
|
||||||
</button>
|
</button>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,97 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<odoo>
|
|
||||||
<template id="portal_my_profiles" name="My Profiles">
|
|
||||||
<t t-call="portal.portal_layout">
|
|
||||||
<t t-set="breadcrumbs_searchbar" t-value="True" />
|
|
||||||
|
|
||||||
<t t-call="portal.portal_searchbar">
|
|
||||||
<t t-set="title">Profiles</t>
|
|
||||||
</t>
|
|
||||||
<div class="oe_structure" id="oe_structure_portal_my_profiles_1" />
|
|
||||||
<t t-if="not profiles">
|
|
||||||
<div class="alert alert-warning mt8" role="alert">
|
|
||||||
There are no profiles.
|
|
||||||
</div>
|
|
||||||
</t>
|
|
||||||
<h3>
|
|
||||||
<br />
|
|
||||||
My profiles
|
|
||||||
</h3>
|
|
||||||
<t t-if="profiles" t-call="portal.portal_table">
|
|
||||||
<thead>
|
|
||||||
<tr class="active">
|
|
||||||
<th>
|
|
||||||
<span class='d-none d-md-inline'>Profile name</span>
|
|
||||||
</th>
|
|
||||||
<th class="text-right">Profile type</th>
|
|
||||||
<th class="text-right">Position</th>
|
|
||||||
<th class="text-right">Company</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr t-foreach="profiles" t-as="profile">
|
|
||||||
<t t-if="not profile.is_company">
|
|
||||||
<td>
|
|
||||||
<a t-attf-href="/my/profile/#{profile.id}?{{ keep_query() }}">
|
|
||||||
<span t-field="profile.name" />
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td class="text-right">
|
|
||||||
<span t-field="profile.partner_profile" />
|
|
||||||
</td>
|
|
||||||
<td class="text-right">
|
|
||||||
<span t-field="profile.function" />
|
|
||||||
</td>
|
|
||||||
<td class="text-right">
|
|
||||||
<span t-field="profile.parent_id" />
|
|
||||||
</td>
|
|
||||||
</t>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</t>
|
|
||||||
<p style="font-style:italic; font-size:smaller">
|
|
||||||
<b>Public profile :</b>
|
|
||||||
profiles that might be available to tierce applications (annuary for instance).
|
|
||||||
<br />
|
|
||||||
<b>Position profiles :</b>
|
|
||||||
profiles that indicate your belonging to an organization, and the role you have in.
|
|
||||||
</p>
|
|
||||||
<h3>
|
|
||||||
<br />
|
|
||||||
My organizations' profiles
|
|
||||||
</h3>
|
|
||||||
<t t-if="profiles" t-call="portal.portal_table">
|
|
||||||
<thead>
|
|
||||||
<tr class="active">
|
|
||||||
<th>
|
|
||||||
<span class='d-none d-md-inline'>Profile name</span>
|
|
||||||
</th>
|
|
||||||
<th class="text-right">Profile type</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr t-foreach="profiles" t-as="profile">
|
|
||||||
<t t-if="profile.is_company">
|
|
||||||
<td>
|
|
||||||
<a t-attf-href="/my/profile/#{profile.id}?{{ keep_query() }}">
|
|
||||||
<span t-field="profile.name" />
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td class="text-right">
|
|
||||||
<span t-field="profile.partner_profile" />
|
|
||||||
</td>
|
|
||||||
</t>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</t>
|
|
||||||
<p style="font-style:italic; font-size:smaller">
|
|
||||||
<b>Main profiles :</b>
|
|
||||||
internal and private profiles, used for membership management and internal communication.
|
|
||||||
<br />
|
|
||||||
<b>Public profiles :</b>
|
|
||||||
profiles that might be available to tierce applications (annuary for instance).
|
|
||||||
</p>
|
|
||||||
<div class="oe_structure" id="oe_structure_portal_my_profiles_2" />
|
|
||||||
</t>
|
|
||||||
</template>
|
|
||||||
</odoo>
|
|
@@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<template id="portal_my_structures" name="My Structures">
|
||||||
|
<t t-call="portal.portal_layout">
|
||||||
|
<t t-set="breadcrumbs_searchbar" t-value="True" />
|
||||||
|
<t t-call="portal.portal_searchbar">
|
||||||
|
<t t-set="title">My Structures</t>
|
||||||
|
</t>
|
||||||
|
<h3>
|
||||||
|
My structures
|
||||||
|
</h3>
|
||||||
|
<div class="oe_structure" id="oe_structure_portal_my_structures_1" />
|
||||||
|
<t t-if="not structures">
|
||||||
|
<div class="alert alert-warning mt8" role="alert">
|
||||||
|
You are not linked with any structure.
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
<t t-if="structures" t-call="portal.portal_table">
|
||||||
|
<thead>
|
||||||
|
<tr class="active">
|
||||||
|
<th>
|
||||||
|
<span class='d-none d-md-inline'>Structure name</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr t-foreach="structures" t-as="structure">
|
||||||
|
<td>
|
||||||
|
<a t-attf-href="/my/structure/#{structure.id}?{{ keep_query() }}">
|
||||||
|
<span t-field="structure.name" />
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
</odoo>
|
@@ -1,189 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<odoo>
|
|
||||||
<template id="portal_my_profile" name="My Profile">
|
|
||||||
<t t-call="portal.portal_layout">
|
|
||||||
<t t-set="o_portal_fullwidth_alert" groups="profile.group_profile_user">
|
|
||||||
<t t-call="portal.portal_back_in_edit_mode">
|
|
||||||
<t t-set="backend_url" t-value="'/web#return_label=Website&model=res.partner&id=%s&view_type=form' % (profile.id)" />
|
|
||||||
</t>
|
|
||||||
</t>
|
|
||||||
<t t-call="portal.portal_record_layout">
|
|
||||||
<t t-set="card_header">
|
|
||||||
<h5 class="mb-0">
|
|
||||||
<strong>
|
|
||||||
<span t-field="profile.name" />
|
|
||||||
</strong>
|
|
||||||
<small class="text-muted">
|
|
||||||
-
|
|
||||||
<span t-field="profile.partner_profile" />
|
|
||||||
</small>
|
|
||||||
</h5>
|
|
||||||
<div t-if="profile.is_position_profile">
|
|
||||||
<h5 class="mb-0">
|
|
||||||
<span t-field="profile.parent_id" />
|
|
||||||
</h5>
|
|
||||||
<br />
|
|
||||||
<ul class="col-12 col-md-6 pb-2" style="list-style-type:none">
|
|
||||||
<li>
|
|
||||||
<span t-field="profile.street" />
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span t-field="profile.zip" />
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span t-field="profile.city" />
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span t-field="profile.country_id" />
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</t>
|
|
||||||
<t t-set="card_body">
|
|
||||||
<div class="oe_structure" id="oe_structure_portal_my_profile_1" />
|
|
||||||
<!-- Body for Position partner profiles-->
|
|
||||||
<div t-if="profile.is_position_profile">
|
|
||||||
<form action="/my/profile/save" method="post">
|
|
||||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()" />
|
|
||||||
<div class="row o_portal_details">
|
|
||||||
<div class="col-lg-12">
|
|
||||||
<div class="col-lg-12">
|
|
||||||
<div t-if="error_message" class="alert alert-danger" role="alert">
|
|
||||||
<t t-foreach="error_message" t-as="err">
|
|
||||||
<t t-esc="err" />
|
|
||||||
<br />
|
|
||||||
</t>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<h3>
|
|
||||||
<br />
|
|
||||||
Job position
|
|
||||||
</h3>
|
|
||||||
<div class="row">
|
|
||||||
<div t-attf-class="form-group #{error.get('function') and 'o_has_error' or ''} col-xl-12">
|
|
||||||
<input type="text" name="function" t-attf-class="form-control #{error.get('function') and 'is-invalid' or ''}" t-att-value="function or profile.function" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<h3>
|
|
||||||
<br />
|
|
||||||
Contact information
|
|
||||||
</h3>
|
|
||||||
<div class="row">
|
|
||||||
<div t-attf-class="form-group #{error.get('phone') and 'o_has_error' or ''} col-xl-6">
|
|
||||||
<label class="col-form-label" for="phone">Phone</label>
|
|
||||||
<input type="tel" name="phone" t-attf-class="form-control #{error.get('phone') and 'is-invalid' or ''}" t-att-value="phone or profile.phone" />
|
|
||||||
</div>
|
|
||||||
<div t-attf-class="form-group #{error.get('mobile') and 'o_has_error' or ''} col-xl-6">
|
|
||||||
<label class="col-form-label" for="mobile">Mobile</label>
|
|
||||||
<input type="tel" name="mobile" t-attf-class="form-control #{error.get('mobile') and 'is-invalid' or ''}" t-att-value="mobile or profile.mobile" />
|
|
||||||
</div>
|
|
||||||
<div t-attf-class="form-group #{error.get('email') and 'o_has_error' or ''} col-xl-6">
|
|
||||||
<label class="col-form-label" for="email">Email</label>
|
|
||||||
<input type="email" name="email" t-attf-class="form-control #{error.get('email') and 'is-invalid' or ''}" t-att-value="email or profile.email" />
|
|
||||||
</div>
|
|
||||||
<div t-attf-class="form-group #{error.get('website_url') and 'o_has_error' or ''}col-xl-6">
|
|
||||||
<label class="col-form-label" for="website_url">Website</label>
|
|
||||||
<input type="text" name="website_url" t-attf-class="form-control #{error.get('website') and 'is-invalid' or ''}" t-att-value="website_url or profile.website" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input type="hidden" name="profile_id" t-att-value="profile_id" />
|
|
||||||
<input type="hidden" name="redirect" t-att-value="redirect" />
|
|
||||||
<div class="clearfix">
|
|
||||||
<button type="submit" class="btn btn-primary float-right mb32 ">
|
|
||||||
Save
|
|
||||||
<span class="fa fa-long-arrow-right" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<!-- Body for Public partner profiles-->
|
|
||||||
<div t-if="profile.is_main_profile or profile.is_public_profile">
|
|
||||||
<form action="/my/profile/save" method="post">
|
|
||||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()" />
|
|
||||||
<div class="row o_portal_details">
|
|
||||||
<div class="col-lg-12">
|
|
||||||
<div name="errors" class="col-lg-12">
|
|
||||||
<div t-if="error_message" class="alert alert-danger" role="alert">
|
|
||||||
<t t-foreach="error_message" t-as="err">
|
|
||||||
<t t-esc="err" />
|
|
||||||
<br />
|
|
||||||
</t>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div name="nickname" class="row">
|
|
||||||
<div t-attf-class="form-group #{error.get('nickname') and 'o_has_error' or ''} col-xl-12">
|
|
||||||
<label class="col-form-label" for="nickname">Name / Nickname</label>
|
|
||||||
<input type="text" name="nickname" t-attf-class="form-control #{error.get('nickname') and 'is-invalid' or ''}" t-att-value="nickname or profile.name" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<h3>
|
|
||||||
<br />
|
|
||||||
Contact information
|
|
||||||
</h3>
|
|
||||||
<div name="contact_info_1" class="row">
|
|
||||||
<div name="street" t-attf-class="form-group #{error.get('street') and 'o_has_error' or ''} col-xl-6">
|
|
||||||
<label class="col-form-label" for="street">Street</label>
|
|
||||||
<input type="text" name="street" t-attf-class="form-control #{error.get('street') and 'is-invalid' or ''}" t-att-value="street or profile.street" />
|
|
||||||
</div>
|
|
||||||
<div name="street2" t-attf-class="form-group #{error.get('street2') and 'o_has_error' or ''} col-xl-6">
|
|
||||||
<label class="col-form-label" for="street2">Street 2</label>
|
|
||||||
<input type="text" name="street2" t-attf-class="form-control #{error.get('street2') and 'is-invalid' or ''}" t-att-value="street2 or profile.street2" />
|
|
||||||
</div>
|
|
||||||
<div name="zip" t-attf-class="form-group #{error.get('zipcode') and 'o_has_error' or ''} col-xl-6">
|
|
||||||
<label class="col-form-label" for="zipcode">Zip / Postal Code</label>
|
|
||||||
<input type="text" name="zipcode" t-attf-class="form-control #{error.get('zipcode') and 'is-invalid' or ''}" t-att-value="zipcode or profile.zip" />
|
|
||||||
</div>
|
|
||||||
<div name="city" t-attf-class="form-group #{error.get('city') and 'o_has_error' or ''} col-xl-6">
|
|
||||||
<label class="col-form-label" for="city">City</label>
|
|
||||||
<input type="text" name="city" t-attf-class="form-control #{error.get('city') and 'is-invalid' or ''}" t-att-value="city or profile.city" />
|
|
||||||
</div>
|
|
||||||
<div name="country" t-attf-class="form-group #{error.get('country_id') and 'o_has_error' or ''} col-xl-6">
|
|
||||||
<label class="col-form-label" for="country_id">Country</label>
|
|
||||||
<select name="country_id" t-attf-class="form-control">
|
|
||||||
<option value="">Country...</option>
|
|
||||||
<t t-foreach="countries or []" t-as="country">
|
|
||||||
<option t-att-value="country.id" t-att-selected="country.id == int(country_id) if country_id else country.id == profile.country_id.id">
|
|
||||||
<t t-esc="country.name" />
|
|
||||||
</option>
|
|
||||||
</t>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div name="contact_info_2" class="row">
|
|
||||||
<div name="phone" t-attf-class="form-group #{error.get('phone') and 'o_has_error' or ''} col-xl-6">
|
|
||||||
<label class="col-form-label" for="phone">Phone</label>
|
|
||||||
<input type="tel" name="phone" t-attf-class="form-control #{error.get('phone') and 'is-invalid' or ''}" t-att-value="phone or profile.phone" />
|
|
||||||
</div>
|
|
||||||
<div name="mobile" t-attf-class="form-group #{error.get('mobile') and 'o_has_error' or ''} col-xl-6">
|
|
||||||
<label class="col-form-label" for="mobile">Mobile</label>
|
|
||||||
<input type="tel" name="mobile" t-attf-class="form-control #{error.get('mobile') and 'is-invalid' or ''}" t-att-value="mobile or profile.mobile" />
|
|
||||||
</div>
|
|
||||||
<div name="email" t-attf-class="form-group #{error.get('email') and 'o_has_error' or ''} col-xl-6">
|
|
||||||
<label class="col-form-label" for="email">Email</label>
|
|
||||||
<input type="email" name="email" t-attf-class="form-control #{error.get('email') and 'is-invalid' or ''}" t-att-value="email or profile.email" />
|
|
||||||
</div>
|
|
||||||
<div name="website" t-attf-class="form-group #{error.get('website_url') and 'o_has_error' or ''} col-xl-6">
|
|
||||||
<label class="col-form-label" for="website_url">Website</label>
|
|
||||||
<input type="text" name="website_url" t-attf-class="form-control #{error.get('website_url') and 'is-invalid' or ''}" t-att-value="website_url or profile.website" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input type="hidden" name="profile_id" t-att-value="profile_id" />
|
|
||||||
<input type="hidden" name="redirect" t-att-value="redirect" />
|
|
||||||
<div class="clearfix">
|
|
||||||
<button type="submit" class="btn btn-primary float-right mb32 ">
|
|
||||||
Save
|
|
||||||
<span class="fa fa-long-arrow-right" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="oe_structure" id="oe_structure_portal_my_profile_2" />
|
|
||||||
</t>
|
|
||||||
</t>
|
|
||||||
</t>
|
|
||||||
</template>
|
|
||||||
</odoo>
|
|
@@ -0,0 +1,316 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<template id="portal_structure" name="Structure Details">
|
||||||
|
<t t-call="portal.portal_layout">
|
||||||
|
<t t-set="o_portal_fullwidth_alert" groups="profile.group_profile_user">
|
||||||
|
<t t-call="portal.portal_back_in_edit_mode">
|
||||||
|
<t t-set="backend_url"
|
||||||
|
t-value="'/web#return_label=Website&model=res.partner&id=%s&view_type=form' % (structure.id)" />
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
<t t-set="additional_title">My Structure Details</t>
|
||||||
|
<div style="text-align:right">
|
||||||
|
<br />
|
||||||
|
<a href="/my/structures">
|
||||||
|
<span class="fa fa-arrow-left" /> Back to my structures list </a>
|
||||||
|
</div>
|
||||||
|
<form action="/my/structure/save" method="post" enctype="multipart/form-data">
|
||||||
|
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()" />
|
||||||
|
<div class="row o_portal_details">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<h1 style="text-align: center;">
|
||||||
|
<span t-field="structure.name" /> details </h1>
|
||||||
|
<div t-if="success" class="alert alert-success py-1 mb-2">
|
||||||
|
<i class="fa fa-fw fa-check-circle" /> Data saved! </div>
|
||||||
|
<div t-if="error_message" role="alert" class="col-lg-12 alert alert-danger">
|
||||||
|
<t t-foreach="error_message" t-as="err">
|
||||||
|
<t t-esc="err" />
|
||||||
|
<br />
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
<!-- ################# -->
|
||||||
|
<!-- MAIN PROFILE DATA -->
|
||||||
|
<!-- ################# -->
|
||||||
|
<div class="row">
|
||||||
|
<div id="name"
|
||||||
|
t-attf-class="form-group #{error.get('main_name') and 'o_has_error' or ''} col-xl-12">
|
||||||
|
<label class="col-form-label" for="main_name">Name: </label>
|
||||||
|
<input t-if="can_edit_structure" type="text" name="main_name"
|
||||||
|
required="True"
|
||||||
|
t-attf-class="form-control #{error.get('main_name') and 'is-invalid' or ''}"
|
||||||
|
t-att-value="main_name or structure.name" />
|
||||||
|
<span t-if="not can_edit_structure" t-field="structure.name" />
|
||||||
|
</div>
|
||||||
|
<div t-attf-class="col-xl-12" id="logo">
|
||||||
|
<label class="col-form-label">Logo: </label>
|
||||||
|
<div class="row">
|
||||||
|
<div t-attf-class="col-xl-2">
|
||||||
|
<i>Current logo:</i>
|
||||||
|
<div name="image" t-field="structure.image"
|
||||||
|
t-options="{"widget": "image", "preview_image": "image_512", "class": "d-block mx-auto mb16"}" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group form-field form-field-binary"
|
||||||
|
data-model-field="false" data-optional="true"
|
||||||
|
t-attf-class="col-xl-2" t-if="can_edit_structure">
|
||||||
|
<i>New logo:</i>
|
||||||
|
<i>
|
||||||
|
<input type="file" name="logo" multiple="false"
|
||||||
|
data-show-upload="true" data-show-caption="true"
|
||||||
|
accept="image/*" />
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="contact">
|
||||||
|
<br />
|
||||||
|
<h3>
|
||||||
|
Contact information
|
||||||
|
</h3>
|
||||||
|
<div class="row" id="adress">
|
||||||
|
<div
|
||||||
|
t-attf-class="form-group #{error.get('main_street') and 'o_has_error' or ''} col-xl-12">
|
||||||
|
<label class="col-form-label" for="main_street">Street: </label>
|
||||||
|
<input t-if="can_edit_structure" type="text" name="main_street"
|
||||||
|
placeholder="Street"
|
||||||
|
t-attf-class="form-control #{error.get('main_street') and 'is-invalid' or ''}"
|
||||||
|
t-att-value="main_street or structure.street" />
|
||||||
|
<span t-if="not can_edit_structure" t-field="structure.street" />
|
||||||
|
<input t-if="can_edit_structure" type="text" name="main_street2"
|
||||||
|
placeholder="Street 2 "
|
||||||
|
t-attf-class="form-control #{error.get('main_street2') and 'is-invalid' or ''}"
|
||||||
|
t-att-value="main_street2 or structure.street2" />
|
||||||
|
<span t-if="not can_edit_structure"> - </span>
|
||||||
|
<span t-if="not can_edit_structure" t-field="structure.street2" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
t-attf-class="form-group #{error.get('main_zip') and 'o_has_error' or ''} col-xl-6">
|
||||||
|
<label class="col-form-label" for="main_zip">Zip /
|
||||||
|
Postal Code: </label>
|
||||||
|
<input t-if="can_edit_structure" type="text" name="main_zip"
|
||||||
|
t-attf-class="form-control #{error.get('main_zip') and 'is-invalid' or ''}"
|
||||||
|
t-att-value="main_zip or structure.zip" />
|
||||||
|
<span t-if="not can_edit_structure" t-field="structure.zip" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
t-attf-class="form-group #{error.get('main_city') and 'o_has_error' or ''} col-xl-6">
|
||||||
|
<label class="col-form-label" for="main_city">City: </label>
|
||||||
|
<input t-if="can_edit_structure" type="text" name="main_city"
|
||||||
|
t-attf-class="form-control #{error.get('main_city') and 'is-invalid' or ''}"
|
||||||
|
t-att-value="main_city or structure.city" />
|
||||||
|
<span t-if="not can_edit_structure" t-field="structure.city" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
t-attf-class="form-group #{error.get('main_country_id') and 'o_has_error' or ''} col-xl-6">
|
||||||
|
<label class="col-form-label" for="main_country_id">Country: </label>
|
||||||
|
<select t-if="can_edit_structure" name="main_country_id"
|
||||||
|
t-attf-class="form-control #{error.get('main_country_id') and 'is-invalid' or ''}">
|
||||||
|
<option value="">Country...</option>
|
||||||
|
<t t-foreach="countries or []" t-as="country">
|
||||||
|
<option t-att-value="country.id"
|
||||||
|
t-att-selected="country.id == int(main_country_id) if main_country_id else country.id == structure.country_id.id">
|
||||||
|
<t t-esc="country.name" />
|
||||||
|
</option>
|
||||||
|
</t>
|
||||||
|
</select>
|
||||||
|
<span t-if="not can_edit_structure"
|
||||||
|
t-field="structure.country_id" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row" id="other_contact_data">
|
||||||
|
<div
|
||||||
|
t-attf-class="form-group #{error.get('main_phone') and 'o_has_error' or ''} col-xl-6">
|
||||||
|
<label class="col-form-label" for="main_phone">Phone: </label>
|
||||||
|
<input t-if="can_edit_structure" type="tel" name="main_phone"
|
||||||
|
t-attf-class="form-control #{error.get('main_phone') and 'is-invalid' or ''}"
|
||||||
|
t-att-value="main_phone or structure.phone" />
|
||||||
|
<span t-if="not can_edit_structure" t-field="structure.phone" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
t-attf-class="form-group #{error.get('main_mobile') and 'o_has_error' or ''} col-xl-6">
|
||||||
|
<label class="col-form-label" for="main_mobile">Mobile: </label>
|
||||||
|
<input t-if="can_edit_structure" type="tel" name="main_mobile"
|
||||||
|
t-attf-class="form-control #{error.get('main_mobile') and 'is-invalid' or ''}"
|
||||||
|
t-att-value="main_mobile or structure.mobile" />
|
||||||
|
<span t-if="not can_edit_structure" t-field="structure.mobile" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
t-attf-class="form-group #{error.get('main_email') and 'o_has_error' or ''} col-xl-6">
|
||||||
|
<label class="col-form-label" for="main_email">Email: </label>
|
||||||
|
<input t-if="can_edit_structure" type="email" name="main_email"
|
||||||
|
required="True"
|
||||||
|
t-attf-class="form-control #{error.get('main_email') and 'is-invalid' or ''}"
|
||||||
|
t-att-value="main_email or structure.email" />
|
||||||
|
<span t-if="not can_edit_structure" t-field="structure.email" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
t-attf-class="form-group #{error.get('main_website') and 'o_has_error' or ''} col-xl-6">
|
||||||
|
<label class="col-form-label" for="main_website">Website: </label>
|
||||||
|
<input t-if="can_edit_structure" type="text" name="main_website"
|
||||||
|
placeholder="e.g. https://odoo.com"
|
||||||
|
t-attf-class="form-control #{error.get('main_website') and 'is-invalid' or ''}"
|
||||||
|
t-att-value="main_website or structure.website" />
|
||||||
|
<span t-if="not can_edit_structure" t-field="structure.website" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- ##################### -->
|
||||||
|
<!-- POSITION PROFILE DATA -->
|
||||||
|
<!-- ##################### -->
|
||||||
|
<div id="position_data">
|
||||||
|
<br />
|
||||||
|
<h3>
|
||||||
|
Your position in the structure
|
||||||
|
</h3>
|
||||||
|
<div class="row" id="position_function">
|
||||||
|
<div
|
||||||
|
t-attf-class="form-group #{error.get('position_function') and 'o_has_error' or ''} col-xl-6">
|
||||||
|
<label class="col-form-label" for="position_function">Function: </label>
|
||||||
|
<input type="text" name="position_function"
|
||||||
|
t-attf-class="form-control #{error.get('position_function') and 'is-invalid' or ''}"
|
||||||
|
t-att-value="position_function or position_profile.function" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row" id="position_contact">
|
||||||
|
<div
|
||||||
|
t-attf-class="form-group #{error.get('position_email') and 'o_has_error' or ''} col-xl-6">
|
||||||
|
<label class="col-form-label" for="position_email">Email Pro: </label>
|
||||||
|
<input type="email" name="position_email"
|
||||||
|
t-attf-class="form-control #{error.get('position_email') and 'is-invalid' or ''}"
|
||||||
|
t-att-value="position_email or position_profile.email" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
t-attf-class="form-group #{error.get('position_phone') and 'o_has_error' or ''} col-xl-6">
|
||||||
|
<label class="col-form-label" for="position_phone">Phone Pro: </label>
|
||||||
|
<input type="tel" name="position_phone"
|
||||||
|
t-attf-class="form-control #{error.get('position_phone') and 'is-invalid' or ''}"
|
||||||
|
t-att-value="position_phone or position_profile.phone" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<!-- ################### -->
|
||||||
|
<!-- PUBLIC PROFILE DATA -->
|
||||||
|
<!-- ################### -->
|
||||||
|
<div class="s_card card bg-white w-100" id="public">
|
||||||
|
<h3 class="card-header">
|
||||||
|
Public contact information
|
||||||
|
</h3>
|
||||||
|
<div class="card-body">
|
||||||
|
<p> The following information are public information that might be
|
||||||
|
used in tierce applications (annuary for instance).<br /> You
|
||||||
|
can customize them (and be anonymous for instance) to publicly
|
||||||
|
show whatever you need or want. </p>
|
||||||
|
<div class="row">
|
||||||
|
<div
|
||||||
|
t-attf-class="form-group #{error.get('public_name') and 'o_has_error' or ''} col-xl-12">
|
||||||
|
<label class="col-form-label" for="public_name">Public name: </label>
|
||||||
|
<input t-if="can_edit_structure" type="text"
|
||||||
|
name="public_name" required="True"
|
||||||
|
t-attf-class="form-control #{error.get('public_name') and 'is-invalid' or ''}"
|
||||||
|
t-att-value="public_name or public_profile.name" />
|
||||||
|
<span t-if="not can_edit_structure"
|
||||||
|
t-field="public_profile.name" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
t-attf-class="form-group #{error.get('public_street') and 'o_has_error' or ''} col-xl-12">
|
||||||
|
<label class="col-form-label" for="public_street">Street: </label>
|
||||||
|
<input t-if="can_edit_structure" type="text"
|
||||||
|
name="public_street" placeholder="Street"
|
||||||
|
t-attf-class="form-control #{error.get('public_street') and 'is-invalid' or ''}"
|
||||||
|
t-att-value="public_street or public_profile.street" />
|
||||||
|
<span t-if="not can_edit_structure"
|
||||||
|
t-field="public_profile.street" />
|
||||||
|
<input t-if="can_edit_structure" type="text"
|
||||||
|
name="public_street2"
|
||||||
|
placeholder="Street 2"
|
||||||
|
t-attf-class="form-control #{error.get('public_street2') and 'is-invalid' or ''}"
|
||||||
|
t-att-value="public_street2 or public_profile.street2" />
|
||||||
|
<span t-if="not can_edit_structure"> - </span>
|
||||||
|
<span t-if="not can_edit_structure"
|
||||||
|
t-field="public_profile.street2" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
t-attf-class="form-group #{error.get('public_zip') and 'o_has_error' or ''} col-xl-6">
|
||||||
|
<label class="col-form-label" for="public_zip">Zip / Postal
|
||||||
|
Code: </label>
|
||||||
|
<input t-if="can_edit_structure" type="text"
|
||||||
|
name="public_zip"
|
||||||
|
t-attf-class="form-control #{error.get('public_zip') and 'is-invalid' or ''}"
|
||||||
|
t-att-value="public_zip or public_profile.zip" />
|
||||||
|
<span t-if="not can_edit_structure"
|
||||||
|
t-field="public_profile.zip" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
t-attf-class="form-group #{error.get('public_city') and 'o_has_error' or ''} col-xl-6">
|
||||||
|
<label class="col-form-label" for="public_city">City: </label>
|
||||||
|
<input t-if="can_edit_structure" type="text"
|
||||||
|
name="public_city"
|
||||||
|
t-attf-class="form-control #{error.get('public_city') and 'is-invalid' or ''}"
|
||||||
|
t-att-value="public_city or public_profile.city" />
|
||||||
|
<span t-if="not can_edit_structure"
|
||||||
|
t-field="public_profile.city" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div
|
||||||
|
t-attf-class="form-group #{error.get('public_phone') and 'o_has_error' or ''} col-xl-6">
|
||||||
|
<label class="col-form-label" for="public_phone">Phone: </label>
|
||||||
|
<input t-if="can_edit_structure" type="tel"
|
||||||
|
name="public_phone"
|
||||||
|
t-attf-class="form-control #{error.get('public_phone') and 'is-invalid' or ''}"
|
||||||
|
t-att-value="public_phone or public_profile.phone" />
|
||||||
|
<span t-if="not can_edit_structure"
|
||||||
|
t-field="public_profile.phone" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
t-attf-class="form-group #{error.get('public_mobile') and 'o_has_error' or ''} col-xl-6">
|
||||||
|
<label class="col-form-label" for="public_mobile">Mobile: </label>
|
||||||
|
<input t-if="can_edit_structure" type="tel"
|
||||||
|
name="public_mobile"
|
||||||
|
t-attf-class="form-control #{error.get('public_mobile') and 'is-invalid' or ''}"
|
||||||
|
t-att-value="public_mobile or public_profile.mobile" />
|
||||||
|
<span t-if="not can_edit_structure"
|
||||||
|
t-field="public_profile.mobile" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
t-attf-class="form-group #{error.get('public_email') and 'o_has_error' or ''} col-xl-6">
|
||||||
|
<label class="col-form-label" for="public_email">Email: </label>
|
||||||
|
<input t-if="can_edit_structure" type="email"
|
||||||
|
name="public_email"
|
||||||
|
t-attf-class="form-control #{error.get('public_email') and 'is-invalid' or ''}"
|
||||||
|
t-att-value="public_email or public_profile.email" />
|
||||||
|
<span t-if="not can_edit_structure"
|
||||||
|
t-field="public_profile.email" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
t-attf-class="form-group #{error.get('public_website') and 'o_has_error' or ''} col-xl-6">
|
||||||
|
<label class="col-form-label" for="public_website">Website: </label>
|
||||||
|
<input t-if="can_edit_structure" type="text"
|
||||||
|
name="public_website"
|
||||||
|
t-attf-class="form-control #{error.get('public_website') and 'is-invalid' or ''}"
|
||||||
|
t-att-value="public_website or public_profile.website" />
|
||||||
|
<span t-if="not can_edit_structure"
|
||||||
|
t-field="public_profile.website" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="structure_id" t-att-value="structure_id" />
|
||||||
|
<input type="hidden" name="redirect" t-att-value="redirect" />
|
||||||
|
<div style="text-align:right;">
|
||||||
|
<button type="submit"
|
||||||
|
class="btn btn-primary ">Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div style="text-align:right">
|
||||||
|
<br />
|
||||||
|
<a href="/my/structures">
|
||||||
|
<span class="fa fa-arrow-left" /> Back to my structures list </a>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
</odoo>
|
@@ -9,22 +9,26 @@
|
|||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<!-- Main display -->
|
<!-- Main display -->
|
||||||
<xpath expr="//group[@name='profile_status']" position="after">
|
<xpath expr="//group[@name='profile_status']" position="after">
|
||||||
<group name="structure_access_rights" attrs="{'invisible': ['|', ('is_position_profile','=',False), ('parent_id','=',False)]}">
|
<group name="structure_access_rights"
|
||||||
<field name="edit_structure_main_profile" />
|
attrs="{'invisible': ['|', ('is_position_profile','=',False), ('parent_id','=',False)]}">
|
||||||
<field name="edit_structure_public_profile" />
|
<field name="edit_structure_profiles" />
|
||||||
</group>
|
</group>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
|
||||||
<!-- page Contacts & Adresses -->
|
<!-- page Contacts & Adresses -->
|
||||||
<xpath expr="//field[@name='child_ids']/form/sheet/group/group/field[@name='comment']" position="before">
|
<xpath
|
||||||
<field name="edit_structure_main_profile" attrs="{'invisible': [('is_position_profile','=',False)]}" />
|
expr="//field[@name='child_ids']/form/sheet/group/group/field[@name='comment']"
|
||||||
<field name="edit_structure_public_profile" attrs="{'invisible': [('is_position_profile','=',False)]}" />
|
position="before">
|
||||||
|
<field name="edit_structure_profiles"
|
||||||
|
attrs="{'invisible': [('is_position_profile','=',False)]}" />
|
||||||
</xpath>
|
</xpath>
|
||||||
|
|
||||||
<!-- page Other Positions -->
|
<!-- page Other Positions -->
|
||||||
<xpath expr="//field[@name='other_contact_ids']/form/sheet/group/group/field[@name='parent_id']" position="after">
|
<xpath
|
||||||
<field name="edit_structure_main_profile" attrs="{'invisible': [('is_position_profile','=',False)]}" />
|
expr="//field[@name='other_contact_ids']/form/sheet/group/group/field[@name='parent_id']"
|
||||||
<field name="edit_structure_public_profile" attrs="{'invisible': [('is_position_profile','=',False)]}" />
|
position="after">
|
||||||
|
<field name="edit_structure_profiles"
|
||||||
|
attrs="{'invisible': [('is_position_profile','=',False)]}" />
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
Reference in New Issue
Block a user