From 9d7d859cb1cda6500f5acc48292ab029d22f0931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phan=20Sainl=C3=A9ger?= Date: Tue, 21 Mar 2023 15:37:53 +0100 Subject: [PATCH 01/28] [FIX] partner_profiles: add missing add-on dependency --- partner_profiles/__manifest__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/partner_profiles/__manifest__.py b/partner_profiles/__manifest__.py index b9273da..6f363ff 100644 --- a/partner_profiles/__manifest__.py +++ b/partner_profiles/__manifest__.py @@ -14,6 +14,7 @@ "depends": [ "base", "calendar", + "contacts", "partner_contact_in_several_companies", ], "qweb": [ -- 2.49.1 From ffff6e683c596da8adbf2d3e8431c186155c78de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phan=20Sainl=C3=A9ger?= Date: Tue, 21 Mar 2023 16:51:52 +0100 Subject: [PATCH 02/28] [IMP] partner_profiles: filter public profiles in name_search answer --- partner_profiles/__manifest__.py | 3 +-- partner_profiles/models/res_partner.py | 9 +++++++++ partner_profiles/views/calendar_event_view.xml | 16 ---------------- 3 files changed, 10 insertions(+), 18 deletions(-) delete mode 100644 partner_profiles/views/calendar_event_view.xml diff --git a/partner_profiles/__manifest__.py b/partner_profiles/__manifest__.py index 6f363ff..5197889 100644 --- a/partner_profiles/__manifest__.py +++ b/partner_profiles/__manifest__.py @@ -3,7 +3,7 @@ { "name": "partner_profiles", - "version": "12.0.1.0.5", + "version": "12.0.1.1.0", "author": "Elabore", "website": "https://elabore.coop", "maintainer": "Stéphan Sainléger", @@ -27,7 +27,6 @@ "data": [ "security/ir.model.access.csv", "views/res_partner_view.xml", - "views/calendar_event_view.xml", "views/partner_profile_view.xml", "data/partner_profile_data.xml", "data/res_partner_data.xml", diff --git a/partner_profiles/models/res_partner.py b/partner_profiles/models/res_partner.py index 3ed8cdc..efe625f 100644 --- a/partner_profiles/models/res_partner.py +++ b/partner_profiles/models/res_partner.py @@ -189,6 +189,15 @@ class res_partner(models.Model): when a partner is attached to him. """ return ['title'] + @api.model + def name_search(self, name='', args=None, operator='ilike', limit=100): + """ Remove public profile partners from the name_search results""" + if not args: + args = [("is_public_profile", "=", False)] + else: + args.append(("is_public_profile", "=", False)) + return super(res_partner, self).name_search(name, args, operator, limit) + ################################################################################## ## Planned actions ################################################################################## diff --git a/partner_profiles/views/calendar_event_view.xml b/partner_profiles/views/calendar_event_view.xml deleted file mode 100644 index 9d07631..0000000 --- a/partner_profiles/views/calendar_event_view.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - partner.profiles.calendar.event.view - calendar.event - - 99 - - - [('is_public_profile', '=', False)] - - - - - \ No newline at end of file -- 2.49.1 From eb21e1914edfd5962ad22e96bfe1b47b51e01676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phan=20Sainl=C3=A9ger?= Date: Wed, 22 Mar 2023 23:25:11 +0100 Subject: [PATCH 03/28] [IMP] partner_profiles: delete profiles when main partner deleted When a main partner is unlinked, all the linked profiles (public profile and position profiles) are deleted. --- partner_profiles/__manifest__.py | 2 +- partner_profiles/models/res_partner.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/partner_profiles/__manifest__.py b/partner_profiles/__manifest__.py index 5197889..0561b9f 100644 --- a/partner_profiles/__manifest__.py +++ b/partner_profiles/__manifest__.py @@ -3,7 +3,7 @@ { "name": "partner_profiles", - "version": "12.0.1.1.0", + "version": "12.0.1.2.0", "author": "Elabore", "website": "https://elabore.coop", "maintainer": "Stéphan Sainléger", diff --git a/partner_profiles/models/res_partner.py b/partner_profiles/models/res_partner.py index efe625f..d691d45 100644 --- a/partner_profiles/models/res_partner.py +++ b/partner_profiles/models/res_partner.py @@ -18,6 +18,7 @@ class res_partner(models.Model): translate=False, readonly=False, ) + contact_id = fields.Many2one(ondelete="cascade") is_main_profile = fields.Boolean(compute="_compute_profile_booleans", store=True) is_public_profile = fields.Boolean(compute="_compute_profile_booleans", store=True) is_position_profile = fields.Boolean( @@ -31,7 +32,6 @@ class res_partner(models.Model): compute="_compute_public_profile_id", string="Public profile", store=True, - ondelete="cascade", ) # If current partner is Main partner, this field indicates what its position profiles are. @@ -117,6 +117,16 @@ class res_partner(models.Model): res = super(res_partner, self).create(vals) return res + @api.multi + def unlink(self): + for partner in self: + if partner.is_company: + # Delete position profiles linked to the company main profile + child_ids = self.env["res.partner"].search([("parent_id", "=", partner.id), ("is_position_profile", "=", True)]) + for child in child_ids: + child.unlink() + return super(res_partner, self).unlink() + @api.model def search_position_partners(self, profile): if profile: -- 2.49.1 From 6f7629aba8eaac1887e48d1b346e7496080e4149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phan=20Sainl=C3=A9ger?= Date: Tue, 18 Apr 2023 09:26:35 +0200 Subject: [PATCH 04/28] [IMP] partner_profiles: remove useless fields in public profile view Hides from the partner public form view the fields and data not considered as relevant for public profile. The public profile aims to protect the contact data, but not to replace the other ones which are considered as "administrative" data. --- partner_profiles/__manifest__.py | 2 +- partner_profiles/models/res_partner.py | 2 - partner_profiles/views/res_partner_view.xml | 54 +++++++++++++++++++-- 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/partner_profiles/__manifest__.py b/partner_profiles/__manifest__.py index 0561b9f..ba116ea 100644 --- a/partner_profiles/__manifest__.py +++ b/partner_profiles/__manifest__.py @@ -3,7 +3,7 @@ { "name": "partner_profiles", - "version": "12.0.1.2.0", + "version": "12.0.2.0.0", "author": "Elabore", "website": "https://elabore.coop", "maintainer": "Stéphan Sainléger", diff --git a/partner_profiles/models/res_partner.py b/partner_profiles/models/res_partner.py index d691d45..3980df1 100644 --- a/partner_profiles/models/res_partner.py +++ b/partner_profiles/models/res_partner.py @@ -157,7 +157,6 @@ class res_partner(models.Model): if self.is_company: fields = [ "name", - "function", "phone", "mobile", "email", @@ -168,7 +167,6 @@ class res_partner(models.Model): "country_id", "zip", "is_company", - "lang", ] else: fields = ["name"] diff --git a/partner_profiles/views/res_partner_view.xml b/partner_profiles/views/res_partner_view.xml index 1f62cf2..a7476db 100644 --- a/partner_profiles/views/res_partner_view.xml +++ b/partner_profiles/views/res_partner_view.xml @@ -47,10 +47,6 @@ - - - - {'invisible': [('is_company','=', False)]} @@ -92,6 +88,56 @@ domain="[('is_company', '=', True),('is_main_profile','=', True)]" context="{'default_partner_profile': 1, 'default_is_company': True, 'show_vat': True}" /> + + + + + + {'invisible': [('is_public_profile','=', True)]} + + + {'invisible': [('is_public_profile','=', True)]} + + + {'invisible': [('is_public_profile','=', True)]} + + + + + + + + + + + + + + + {'invisible': [('is_public_profile','=', True)]} + -- 2.49.1 From 69de81a9e011f05259b1193e9e0a2f726e9b581c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phan=20Sainl=C3=A9ger?= Date: Tue, 18 Apr 2023 18:10:06 +0200 Subject: [PATCH 05/28] [IMP] partner_profiles_portal: refactoring of user information Merge the main and public information edition in /my/account portal page. --- partner_profiles_portal/__manifest__.py | 3 +- .../controllers/__init__.py | 3 +- .../controllers/portal_my_account.py | 142 +++++++++++ .../views/portal_home_template.xml | 8 +- .../views/portal_my_account.xml | 234 ++++++++++++++++++ 5 files changed, 385 insertions(+), 5 deletions(-) create mode 100644 partner_profiles_portal/controllers/portal_my_account.py create mode 100644 partner_profiles_portal/views/portal_my_account.xml diff --git a/partner_profiles_portal/__manifest__.py b/partner_profiles_portal/__manifest__.py index 3be738b..57b1583 100644 --- a/partner_profiles_portal/__manifest__.py +++ b/partner_profiles_portal/__manifest__.py @@ -3,7 +3,7 @@ { "name": "partner_profiles_portal", - "version": "12.0.1.0.2", + "version": "12.0.2.0.0", "author": "Elabore", "website": "https://elabore.coop", "maintainer": "Stéphan Sainléger", @@ -28,6 +28,7 @@ "views/portal_home_template.xml", "views/portal_my_profiles_template.xml", "views/portal_partner_profile_template.xml", + "views/portal_my_account.xml", "views/res_partner_view.xml", ], # only loaded in demonstration mode diff --git a/partner_profiles_portal/controllers/__init__.py b/partner_profiles_portal/controllers/__init__.py index 6042fcc..9866964 100644 --- a/partner_profiles_portal/controllers/__init__.py +++ b/partner_profiles_portal/controllers/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- from . import portal_my_profiles -from . import portal_partner_profile \ No newline at end of file +from . import portal_partner_profile +from . import portal_my_account \ No newline at end of file diff --git a/partner_profiles_portal/controllers/portal_my_account.py b/partner_profiles_portal/controllers/portal_my_account.py new file mode 100644 index 0000000..79d4b06 --- /dev/null +++ b/partner_profiles_portal/controllers/portal_my_account.py @@ -0,0 +1,142 @@ +# -*- coding: utf-8 -*- +import base64 +from odoo import tools +from odoo.http import request, route +from odoo.addons.portal.controllers.portal import CustomerPortal + +class CustomerPortalMyProfile(CustomerPortal): + + def _get_mandatory_main_fields(self): + return ["main_name", "main_email"] + + def _get_optional_main_fields(self): + return ["main_street", "main_street2", "main_city", "main_country_id", "main_phone", "main_mobile", "main_zip", "main_state_id", "main_website"] + + def _get_mandatory_public_fields(self): + return ["public_name"] + + def _get_optional_public_fields(self): + return ["public_email", "public_street", "public_street2", "public_city", "public_phone", "public_mobile", "public_zip", "public_website"] + + def _get_special_fields(self): + return ["main_logo"] + + def _transform_in_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 _retrieve_main_values(self, data): + main_fields = self._get_mandatory_main_fields() + self._get_optional_main_fields() + values = self._transform_in_partner_fields(data, main_fields, "main_") + if 'main_logo' in data: + image = data.get('main_logo') + if image: + image = image.read() + image = base64.b64encode(image) + values.update({ + 'image': image + }) + return values + + def _retrieve_public_values(self, data): + public_fields = self._get_mandatory_public_fields() + self._get_optional_public_fields() + values = self._transform_in_partner_fields(data, public_fields, "public_") + return values + + def _get_page_opening_values(self): + # Just retrieve the values to display for Selection fields + countries = request.env["res.country"].sudo().search([]) + states = request.env['res.country.state'].sudo().search([]) + values = { + "countries": countries, + 'states': states, + } + return values + + @route(["/my/account"], type="http", auth="user", website=True) + def account(self, redirect=None, **post): + values = self._prepare_portal_layout_values() + user = request.env.user + partner = user.partner_id + public_partner = partner.public_profile_id + values.update({ + 'error': {}, + 'error_message': [], + }) + + if post and request.httprequest.method == 'POST': + error, error_message = self.details_form_validate(post) + values.update({'error': error, 'error_message': error_message}) + values.update(post) + if not error: + # Save main profile values + values = self._retrieve_main_values(post) + partner.sudo().write(values) + # Save public profile values + public_values = self._retrieve_public_values(post) + if len(public_values) > 0: + public_partner.sudo().write(public_values) + # Email change generates a change of user's login + if post.get("main_email", user.login) != user.login: + user.login = post["main_email"] + return request.redirect("/web/session/logout") + if redirect: + return request.redirect(redirect) + return request.redirect('/my/home') + + values.update(self._get_page_opening_values()) + values.update({ + 'partner': partner, + 'public_partner': public_partner, + 'has_check_vat': hasattr(request.env['res.partner'], 'check_vat'), + 'redirect': "/my/account?success=True", + 'page_name': 'my_details', + }) + + response = request.render("portal.portal_my_details", values) + response.headers['X-Frame-Options'] = 'DENY' + + return response + + + + def details_form_validate(self, data): + error = dict() + error_message = [] + + # Validation + for field_name in self._get_mandatory_main_fields() + self._get_mandatory_public_fields(): + if not data.get(field_name): + error[field_name] = 'missing' + + # email validation + if data.get('main_email') and not tools.single_email_re.match(data.get('main_email')): + error["main_email"] = 'error' + error_message.append(_('Invalid Email! Please enter a valid email address.')) + if data.get('public_email') and not tools.single_email_re.match(data.get('public_email')): + error["public_email"] = 'error' + error_message.append(_('Invalid Public Email! Please enter a valid public email address.')) + + # 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", "!=", request.env.user.partner_id.id), + ] + ): + error["public_name"] = "error" + error_message.append( + _("This public name is already used, please find an other idea.") + ) + + # error message for empty required fields + if [err for err in error.values() if err == 'missing']: + error_message.append('Some required fields are empty.') + + unknown = [k for k in data if k not in self._get_mandatory_main_fields() + self._get_optional_main_fields() + self._get_mandatory_public_fields() + self._get_optional_public_fields() + self._get_special_fields()] + if unknown: + error['common'] = 'Unknown field' + error_message.append("Unknown field '%s'" % ','.join(unknown)) + + return error, error_message \ No newline at end of file diff --git a/partner_profiles_portal/views/portal_home_template.xml b/partner_profiles_portal/views/portal_home_template.xml index 3d14b73..84e09bc 100644 --- a/partner_profiles_portal/views/portal_home_template.xml +++ b/partner_profiles_portal/views/portal_home_template.xml @@ -1,15 +1,17 @@ -