diff --git a/oca_dependencies.txt b/oca_dependencies.txt new file mode 100644 index 0000000000..2b632b9924 --- /dev/null +++ b/oca_dependencies.txt @@ -0,0 +1,3 @@ +# See https://github.com/OCA/odoo-community.org/blob/master/website/Contribution/CONTRIBUTING.rst#oca_dependencies-txt +e-commerce +partner-contact diff --git a/setup/website_contact_lastname/odoo/addons/website_contact_lastname b/setup/website_contact_lastname/odoo/addons/website_contact_lastname new file mode 120000 index 0000000000..c444f62428 --- /dev/null +++ b/setup/website_contact_lastname/odoo/addons/website_contact_lastname @@ -0,0 +1 @@ +../../../../website_contact_lastname \ No newline at end of file diff --git a/setup/website_contact_lastname/setup.py b/setup/website_contact_lastname/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/website_contact_lastname/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/website_contact_lastname/__init__.py b/website_contact_lastname/__init__.py new file mode 100644 index 0000000000..ea6bb1f80b --- /dev/null +++ b/website_contact_lastname/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2023 Manuel Regidor +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import controllers +from . import models diff --git a/website_contact_lastname/__manifest__.py b/website_contact_lastname/__manifest__.py new file mode 100644 index 0000000000..48fda778d2 --- /dev/null +++ b/website_contact_lastname/__manifest__.py @@ -0,0 +1,23 @@ +# Copyright 2023 Manuel Regidor +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + "name": "Website Contact Lastname", + "version": "15.0.1.0.0", + "category": "Website", + "website": "https://github.com/OCA/website", + "author": "Sygel Technology," "Odoo Community Association (OCA)", + "license": "AGPL-3", + "application": False, + "installable": True, + "depends": [ + "auth_signup", + "website_account_fiscal_position_partner_type", + "partner_firstname", + ], + "data": [ + "views/templates.xml", + "views/auth_signup_login_templates.xml", + "views/portal_templates.xml", + ], +} diff --git a/website_contact_lastname/controllers/__init__.py b/website_contact_lastname/controllers/__init__.py new file mode 100644 index 0000000000..294141a404 --- /dev/null +++ b/website_contact_lastname/controllers/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2023 Manuel Regidor +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import main +from . import portal diff --git a/website_contact_lastname/controllers/main.py b/website_contact_lastname/controllers/main.py new file mode 100644 index 0000000000..ab0cdb9f37 --- /dev/null +++ b/website_contact_lastname/controllers/main.py @@ -0,0 +1,159 @@ +# Copyright 2023 Manuel Regidor +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import _ +from odoo.http import request + +from odoo.addons.auth_signup.controllers.main import AuthSignupHome +from odoo.addons.website_sale.controllers.main import WebsiteSale + + +class WebsiteSale(WebsiteSale): + def _get_mandatory_fields_shipping(self, country_id=False): + req = super()._get_mandatory_fields_shipping(country_id) + req += ["firstname"] + if "name" in req: + req.remove("name") + return req + + def _get_mandatory_fields_billing(self, country_id=False): + req = super()._get_mandatory_fields_billing(country_id) + req.append("firstname") + if request.env.context.get("fiscal_position_type") == "b2c": + req.append("lastname") + # Name is removed as this field cannot be explicitly edited + # It is edited in the backend through the fields first name + # and lastname + if "name" in req: + req.remove("name") + return req + + def values_postprocess(self, order, mode, values, errors, error_msg): + new_values, errors, error_msg = super(WebsiteSale, self).values_postprocess( + order=order, mode=mode, values=values, errors=errors, error_msg=error_msg + ) + new_values.update( + { + "firstname": values.get("firstname") or "", + "lastname": values.get("lastname") or "", + } + ) + # Name is removed as this field cannot be explicitly edited + # It is edited in the backend through the fields first name + # and lastname + new_values.pop("name") + return new_values, errors, error_msg + + def checkout_form_validate(self, mode, all_form_values, data): + required_fields = [ + f for f in (all_form_values.get("field_required") or "").split(",") if f + ] + # Name is removed as this field cannot be explicitly edited + # It is edited in the backend through the fields first name + # and lastname + if "name" in required_fields: + required_fields.remove("name") + all_form_values["field_required"] = ",".join(required_fields) + error, error_message = super().checkout_form_validate( + mode, all_form_values, data + ) + if data.get("partner_id"): + partner_su = ( + request.env["res.partner"] + .sudo() + .browse(int(data["partner_id"])) + .exists() + ) + can_edit_vat = ( + partner_su.parent_id.can_edit_vat() + if partner_su.parent_id + else partner_su.can_edit_vat() + ) + firstname_change = ( + partner_su + and "firstname" in data + and data["firstname"] != partner_su.firstname + ) + if firstname_change and not can_edit_vat: + error["firstname"] = "error" + error_message.append( + _( + "Changing your name is not allowed once invoices have been" + " issued for your account. Please contact us directly for " + "this operation." + ) + ) + # When lastname field in form is empty, its values is False + # When lastname field in backend is empty, its values is '' + # In that case, if both are compared, they are considered to be + # different so data['lastname'] != partner_su.lastname would + # return True although no real change would have been applied. + lastname_change = ( + partner_su + and "lastname" in data + and data["lastname"] != partner_su.lastname + and (data["lastname"] or partner_su.lastname not in ["", False]) + ) + if lastname_change and not can_edit_vat: + error["lastname"] = "error" + error_message.append( + _( + "Changing your last name is not allowed once invoices have" + " been issued for your account. Please contact us directly" + " for this operation." + ) + ) + + # Prevent change the partner name, lastname if + # it is an internal user. + if (firstname_change or lastname_change) and not all( + partner_su.user_ids.mapped("share") + ): + error.update( + { + "firstname": "error" if firstname_change else None, + "lastname": "error" if lastname_change else None, + } + ) + error_message.append( + _( + "If you are ordering for an external person, please place " + "your order via the backend. If you wish to change your " + "name or last name, please do so in the account settings " + "or contact your administrator." + ) + ) + return error, error_message + + +class AuthSignupHome(AuthSignupHome): + def get_auth_signup_qcontext(self): + qcontext = super().get_auth_signup_qcontext() + qcontext.update( + { + k: v + for (k, v) in request.params.items() + if k + in { + "lastname", + } + } + ) + if "name" in qcontext: + qcontext["firstname"] = qcontext["name"] + if ( + "error" not in qcontext + and request.httprequest.method == "POST" + and qcontext.get("fiscal_position_type") == "b2c" + and not qcontext.get("lastname") + ): + qcontext["error"] = _("Lastname is required for B2C users.") + return qcontext + + def _prepare_signup_values(self, qcontext): + values = super()._prepare_signup_values(qcontext) + if "firstname" in qcontext: + values["firstname"] = qcontext["firstname"] + if "lastname" in qcontext: + values["lastname"] = qcontext["lastname"] + return values diff --git a/website_contact_lastname/controllers/portal.py b/website_contact_lastname/controllers/portal.py new file mode 100644 index 0000000000..e99afd139f --- /dev/null +++ b/website_contact_lastname/controllers/portal.py @@ -0,0 +1,63 @@ +# Copyright 2023 Manuel Regidor +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import _ +from odoo.http import request + +from odoo.addons.portal.controllers.portal import CustomerPortal + + +class CustomerPortal(CustomerPortal): + + CustomerPortal.OPTIONAL_BILLING_FIELDS += [ + "firstname", + "lastname", + ] + + def details_form_validate(self, data): + context = dict(request.env.context) + # 'name' field cannont be updated as it would recompute fields + # firstname and lastname. That is why it is set in context + # that changes in field name need to be ignored. + context.update({"name_ignore": True}) + request.env.context = context + error, error_message = super().details_form_validate(data) + partner = request.env.user.partner_id + if partner.can_edit_vat(): + if "firstname" in data and not data.get("Name"): + error["firstname"] = "error" + error_message.append(_("Firstname is mandatory.")) + if ( + "lastname" in data + and not data.get("lastname") + and ( + (data.get("fiscal_position_type") == "b2c") + or ( + partner.fiscal_position_type == "b2c" + and not data.get("fiscal_position_type") == "b2b" + ) + ) + ): + error["lastname"] = "error" + error["fiscal_position_type"] = "error" + error_message.append(_("Lastname is mandatory for B2C users.")) + else: + if "firstname" in data and data.get("firstname") != partner.firstname: + error["firstname"] = "error" + error_message.append( + _( + "Changing Name is not allowed once document(s) have been " + "issued for your account. Please contact us directly for " + "this operation." + ) + ) + if "lastname" in data and data.get("lastname") != partner.lastname: + error["lastname"] = "error" + error_message.append( + _( + "Changing Lastname is not allowed once document(s) have " + "been issued for your account. Please contact us directly " + "for this operation." + ) + ) + return error, error_message diff --git a/website_contact_lastname/models/__init__.py b/website_contact_lastname/models/__init__.py new file mode 100644 index 0000000000..a227d7676d --- /dev/null +++ b/website_contact_lastname/models/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2023 Manuel Regidor +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import res_users +from . import res_partner diff --git a/website_contact_lastname/models/res_partner.py b/website_contact_lastname/models/res_partner.py new file mode 100644 index 0000000000..943a233393 --- /dev/null +++ b/website_contact_lastname/models/res_partner.py @@ -0,0 +1,26 @@ +# Copyright 2023 Manuel Regidor +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl-3). + +from odoo import api, models +from odoo.http import request + + +class Partner(models.Model): + _inherit = "res.partner" + + @api.model + def signup_retrieve_info(self, token): + res = super().signup_retrieve_info(token) + partner = self._signup_retrieve_partner(token, raise_exception=True) + res.update( + { + "name": partner.firstname, + "lastname": partner.lastname, + } + ) + return res + + def write(self, vals): + if vals.get("name") and request.env.context.get("name_ignore"): + vals.pop("name") + return super().write(vals) diff --git a/website_contact_lastname/models/res_users.py b/website_contact_lastname/models/res_users.py new file mode 100644 index 0000000000..01138a8976 --- /dev/null +++ b/website_contact_lastname/models/res_users.py @@ -0,0 +1,37 @@ +# Copyright 2023 Manuel Regidor +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl-3). + +from odoo import api, models + + +class ResUsers(models.Model): + _inherit = "res.users" + + @api.model + def signup(self, values, token=None): + if token: + partner = self.env["res.partner"]._signup_retrieve_partner( + token, check_validity=True, raise_exception=True + ) + partner_user = partner.user_ids and partner.user_ids[0] or False + # Don't update firstname and lastname if partner + # related to user exists (i.e. when resetting password) + if partner_user: + values.pop("firstname", None) + values.pop("lastname", None) + return super().signup(values, token) + + def _create_user_from_template(self, values): + user = super()._create_user_from_template(values) + # Because of the way the name is computed, it is important to + # reset the values so the final name is correct. + # It cannot be done before (the way would be setting the value of name + # if param. values to '') as the funcion _create_user_from_template + # checks that values contains a value for 'name' + user.write( + { + "firstname": values.get("firstname"), + "lastname": values.get("lastname"), + } + ) + return user diff --git a/website_contact_lastname/readme/CONFIGURE.rst b/website_contact_lastname/readme/CONFIGURE.rst new file mode 100644 index 0000000000..05dcf8709a --- /dev/null +++ b/website_contact_lastname/readme/CONFIGURE.rst @@ -0,0 +1 @@ +No configuration needed. diff --git a/website_contact_lastname/readme/CONTRIBUTORS.rst b/website_contact_lastname/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..9a9de12727 --- /dev/null +++ b/website_contact_lastname/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* `Sygel `_: + + * Manuel Regidor diff --git a/website_contact_lastname/readme/DESCRIPTION.rst b/website_contact_lastname/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..f77181f0df --- /dev/null +++ b/website_contact_lastname/readme/DESCRIPTION.rst @@ -0,0 +1,2 @@ +This module allows users to introduce and edit their lastname +from the portal. diff --git a/website_contact_lastname/readme/USAGE.rst b/website_contact_lastname/readme/USAGE.rst new file mode 100644 index 0000000000..ebb8b7fb10 --- /dev/null +++ b/website_contact_lastname/readme/USAGE.rst @@ -0,0 +1,2 @@ +Lastname field is mandatory for B2C users in portal. +It can be left blank when the user is B2B. diff --git a/website_contact_lastname/static/description/icon.png b/website_contact_lastname/static/description/icon.png new file mode 100644 index 0000000000..207fb7ad55 Binary files /dev/null and b/website_contact_lastname/static/description/icon.png differ diff --git a/website_contact_lastname/views/auth_signup_login_templates.xml b/website_contact_lastname/views/auth_signup_login_templates.xml new file mode 100644 index 0000000000..24da1d08d6 --- /dev/null +++ b/website_contact_lastname/views/auth_signup_login_templates.xml @@ -0,0 +1,23 @@ + + + + + diff --git a/website_contact_lastname/views/portal_templates.xml b/website_contact_lastname/views/portal_templates.xml new file mode 100644 index 0000000000..26566f8654 --- /dev/null +++ b/website_contact_lastname/views/portal_templates.xml @@ -0,0 +1,42 @@ + + + + + diff --git a/website_contact_lastname/views/templates.xml b/website_contact_lastname/views/templates.xml new file mode 100644 index 0000000000..f56ab28416 --- /dev/null +++ b/website_contact_lastname/views/templates.xml @@ -0,0 +1,43 @@ + + + + +