Source code for user_management.domain.entities.user

"""
        Aggregate Root representing a user in the NextGenHealth system.

        Central entity for identity management, enforcing validation,
        security, and auditability.
"""
from datetime import date, timedelta, datetime, timezone
import re
from uuid import UUID

from user_management.domain.enums import UserRole, UserStatus
from user_management.domain.exceptions import (InvalidUUIDError,
                                               InvalidEmailError,
                                               InvalidNameError,
                                               InvalidPhoneNumberError,
                                               InvalidDateOfBirthError,
                                               InvalidUserRoleError,
                                               InvalidUserStatusError,
                                               InvalidCreatedAtError,
                                               InvalidUpdatedAtError, InvalidPasswordError)
from user_management.domain.validation.user import UserValidator
from user_management.domain.value_objects import UserCredentials

from user_management.domain.specifications.user import (
    ValidNameSpecification,
    ValidEmailSpecification,
    ValidPhoneE164Specification,
    ValidDateOfBirthSpecification
)


[docs] class User: """ Aggregate Root representing a user in the NextGenHealth system. Central entity for identity management, enforcing validation, security, and auditability. """
[docs] def __init__(self, uuid: UUID, email: str, first_name: str, last_name: str, phone: str, date_of_birth: date, user_role: UserRole, user_status: UserStatus, created_at: datetime, updated_at: datetime, credentials: UserCredentials ): UserValidator.validate(uuid, email, first_name, last_name, phone, date_of_birth, user_role, user_status, created_at, updated_at) self.uuid = uuid self.email = email self.first_name = first_name self.last_name = last_name self.phone = phone self.date_of_birth = date_of_birth self.user_role = user_role self.user_status = user_status self.created_at = created_at self.updated_at = updated_at self._credentials = credentials
[docs] def is_password_valid(self, provided: str) -> bool: return self._credentials.is_valid(provided)
[docs] def change_password(self, current_password: str, new_password: str): """ Changes the user's password if the current password is correct and the new password is valid and different. Args: current_password (str): The user's current password new_password (str): The desired new password Raises: InvalidPasswordError: If current password is wrong or new password is invalid """ if not self.is_password_valid(current_password): raise InvalidPasswordError("Current password is incorrect") new_credentials = UserCredentials.with_new_password(current_password, new_password) self._credentials = new_credentials self.updated_at = datetime.now(timezone.utc)
[docs] def change_user_role(self, new_role: UserRole): """ Changes the user's role to a new valid role. This method ensures the new role is a valid UserRole enum member. The decision of *who* can perform this action belongs to the Use Case layer. Args: new_role (UserRole): The new role to assign Raises: InvalidUserRoleError: If the role is invalid """ if not isinstance(new_role, UserRole): raise InvalidUserRoleError("Invalid user role") self.user_role = new_role self.updated_at = datetime.now(timezone.utc)
[docs] def change_user_status(self, new_status: UserStatus): if not isinstance(new_status, UserStatus): raise InvalidUserStatusError("Invalid user status") self.user_status = new_status self.updated_at = datetime.now(timezone.utc)
[docs] def update_basic_profile(self, first_name: str, last_name: str, email: str, phone: str, date_of_birth: date): """ Updates the user's basic profile information using domain specifications. Validates inputs via Specification Pattern before applying changes. Updates updated_at timestamp on success. Args: first_name (str): New first name last_name (str): New last name email (str): New email address phone (str): New phone number in E.164 format date_of_birth (date): New date of birth Raises: InvalidNameError: If first or last name fails specification InvalidEmailError: If email fails specification InvalidPhoneNumberError: If phone fails specification InvalidDateOfBirthError: If date_of_birth fails specification """ # Validate using specifications if not ValidNameSpecification().is_satisfied_by(first_name): raise InvalidNameError("First name does not satisfy naming rules") if not ValidNameSpecification().is_satisfied_by(last_name): raise InvalidNameError("Last name does not satisfy naming rules") if not ValidEmailSpecification().is_satisfied_by(email): raise InvalidEmailError("Email does not satisfy format rules") if not ValidPhoneE164Specification().is_satisfied_by(phone): raise InvalidPhoneNumberError("Phone number does not follow E.164 format") if not ValidDateOfBirthSpecification().is_satisfied_by(date_of_birth): raise InvalidDateOfBirthError("Date of birth is not valid") # Apply changes self.first_name = first_name self.last_name = last_name self.email = email self.phone = phone self.date_of_birth = date_of_birth self.updated_at = datetime.now(timezone.utc)