diff --git a/src/main/java/com/iemr/admin/controller/username/UsernameRenameController.java b/src/main/java/com/iemr/admin/controller/username/UsernameRenameController.java new file mode 100644 index 00000000..907311f9 --- /dev/null +++ b/src/main/java/com/iemr/admin/controller/username/UsernameRenameController.java @@ -0,0 +1,64 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ +package com.iemr.admin.controller.username; + +import javax.ws.rs.core.MediaType; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import com.iemr.admin.model.username.UsernameRenameRequest; +import com.iemr.admin.service.username.UsernameRenameService; +import com.iemr.admin.utils.mapper.OutputMapper; +import com.iemr.admin.utils.response.OutputResponse; + +import io.swagger.v3.oas.annotations.Operation; +import jakarta.servlet.http.HttpServletRequest; + +@RestController +@RequestMapping(value = "/username") +public class UsernameRenameController { + private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); + + @Autowired + private UsernameRenameService usernameRenameService; + + @Operation(summary = "Rename a username and repoint its audit records") + @RequestMapping(value = "/renameUsername", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON, headers = "Authorization") + public String renameUsername(@RequestBody UsernameRenameRequest renameRequest, HttpServletRequest request) { + OutputResponse response = new OutputResponse(); + try { + logger.info("renameUsername received request"); + response.setResponse(OutputMapper.gsonWithoutExpose().toJson(usernameRenameService.rename(renameRequest))); + } catch (Exception e) { + logger.error("renameUsername failed", e); + response.setError(e); + } + logger.info("renameUsername sending response"); + return response.toString(); + } +} diff --git a/src/main/java/com/iemr/admin/model/username/UsernameRenameRequest.java b/src/main/java/com/iemr/admin/model/username/UsernameRenameRequest.java new file mode 100644 index 00000000..b0f49982 --- /dev/null +++ b/src/main/java/com/iemr/admin/model/username/UsernameRenameRequest.java @@ -0,0 +1,73 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ +package com.iemr.admin.model.username; + +public class UsernameRenameRequest { + private Integer userID; + + private String oldUserName; + private String newUserName; + + private String newEmployeeId; + + private boolean updateContactFields = true; + + public Integer getUserID() { + return userID; + } + + public void setUserID(Integer userID) { + this.userID = userID; + } + + public String getOldUserName() { + return oldUserName; + } + + public void setOldUserName(String oldUserName) { + this.oldUserName = oldUserName; + } + + public String getNewUserName() { + return newUserName; + } + + public void setNewUserName(String newUserName) { + this.newUserName = newUserName; + } + + public String getNewEmployeeId() { + return newEmployeeId; + } + + public void setNewEmployeeId(String newEmployeeId) { + this.newEmployeeId = newEmployeeId; + } + + public boolean isUpdateContactFields() { + return updateContactFields; + } + + public void setUpdateContactFields(boolean updateContactFields) { + this.updateContactFields = updateContactFields; + } +} diff --git a/src/main/java/com/iemr/admin/model/username/UsernameRenameResponse.java b/src/main/java/com/iemr/admin/model/username/UsernameRenameResponse.java new file mode 100644 index 00000000..993f7490 --- /dev/null +++ b/src/main/java/com/iemr/admin/model/username/UsernameRenameResponse.java @@ -0,0 +1,81 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ +package com.iemr.admin.model.username; + +public class UsernameRenameResponse { + private String oldUserName; + private String newUserName; + + private String oldEmployeeId; + private String newEmployeeId; + + private boolean userNameUpdated; + private boolean employeeIdUpdated; + + public boolean isUserNameUpdated() { + return userNameUpdated; + } + + public void setUserNameUpdated(boolean userNameUpdated) { + this.userNameUpdated = userNameUpdated; + } + + public boolean isEmployeeIdUpdated() { + return employeeIdUpdated; + } + + public void setEmployeeIdUpdated(boolean employeeIdUpdated) { + this.employeeIdUpdated = employeeIdUpdated; + } + + public String getOldEmployeeId() { + return oldEmployeeId; + } + + public void setOldEmployeeId(String oldEmployeeId) { + this.oldEmployeeId = oldEmployeeId; + } + + public String getNewEmployeeId() { + return newEmployeeId; + } + + public void setNewEmployeeId(String newEmployeeId) { + this.newEmployeeId = newEmployeeId; + } + + public String getOldUserName() { + return oldUserName; + } + + public void setOldUserName(String oldUserName) { + this.oldUserName = oldUserName; + } + + public String getNewUserName() { + return newUserName; + } + + public void setNewUserName(String newUserName) { + this.newUserName = newUserName; + } +} diff --git a/src/main/java/com/iemr/admin/repository/username/UsernameAuditTables.java b/src/main/java/com/iemr/admin/repository/username/UsernameAuditTables.java new file mode 100644 index 00000000..7afffacd --- /dev/null +++ b/src/main/java/com/iemr/admin/repository/username/UsernameAuditTables.java @@ -0,0 +1,120 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ +package com.iemr.admin.repository.username; + +import java.util.List; +import java.util.regex.Pattern; + +public final class UsernameAuditTables { + private UsernameAuditTables() { + } + + private static final Pattern SQL_IDENTIFIER = Pattern.compile("\\w+(\\.\\w+)?"); + + private static String identifier(String name) { + if (name == null || !SQL_IDENTIFIER.matcher(name).matches()) { + throw new IllegalArgumentException("Illegal SQL identifier: " + name); + } + return name; + } + + public static final class AuditTable { + private final String qualifiedName; + private final String createdByColumn; + private final String modifiedByColumn; + private final String primaryKeyColumn; + private final String renameSql; + + public AuditTable(String qualifiedName, String createdByColumn, String modifiedByColumn, + String primaryKeyColumn) { + this.qualifiedName = identifier(qualifiedName); + this.createdByColumn = identifier(createdByColumn); + this.modifiedByColumn = identifier(modifiedByColumn); + this.primaryKeyColumn = identifier(primaryKeyColumn); + this.renameSql = String.format( + "UPDATE %1$s SET %2$s = :newUserName, %3$s = :newUserName " + + "WHERE %4$s IN (SELECT %4$s FROM (SELECT %4$s FROM %1$s WHERE %2$s = :oldUserName)" + + " AS temp)", + this.qualifiedName, this.createdByColumn, this.modifiedByColumn, this.primaryKeyColumn); + } + + public String getRenameSql() { + return renameSql; + } + + public String getQualifiedName() { + return qualifiedName; + } + + public String getCreatedByColumn() { + return createdByColumn; + } + + public String getModifiedByColumn() { + return modifiedByColumn; + } + + public String getPrimaryKeyColumn() { + return primaryKeyColumn; + } + } + + private static AuditTable pascal(String qualifiedName, String primaryKeyColumn) { + return new AuditTable(qualifiedName, "CreatedBy", "ModifiedBy", primaryKeyColumn); + } + + private static AuditTable snake(String qualifiedName, String primaryKeyColumn) { + return new AuditTable(qualifiedName, "created_by", "updated_by", primaryKeyColumn); + } + + public static final List TABLES = List.of( + pascal("db_identity.i_beneficiarydetails_rmnch", "beneficiaryDetails_RmnchId"), + pascal("db_identity.i_beneficiaryfamilymapping", "BenFamilyMapId"), + pascal("db_identity.i_beneficiarydetails", "BeneficiaryDetailsId"), + pascal("db_identity.i_beneficiarymapping", "BenMapId"), + pascal("db_identity.i_beneficiaryidentity", "BenIdentityId"), + pascal("db_identity.i_householddetails", "houseHoldDetailsId"), + pascal("db_identity.i_beneficiaryimage", "BenImageId"), + pascal("db_identity.i_beneficiaryaddress", "BenAddressID"), + pascal("db_identity.i_beneficiaryservicemapping", "BenServiceMapID"), + pascal("db_identity.m_beneficiaryregidmapping", "BenRegId"), + pascal("db_identity.i_bornbirthdeatils", "BornBirthDeatilsId"), + pascal("db_identity.i_beneficiarycontacts", "BenContactsID"), + pascal("db_identity.i_beneficiaryconsent", "BenConsentID"), + pascal("db_identity.i_benfamilytag", "BenFamilyTagId"), + + snake("db_iemr.eligible_couple_tracking", "id"), + snake("db_iemr.t_pregnant_woman_register", "id"), + snake("db_iemr.t_eligible_couple_register", "id"), + snake("db_iemr.t_delivery_outcome", "id"), + snake("db_iemr.t_infant_register", "id"), + snake("db_iemr.t_pnc_visit", "ID"), + snake("db_iemr.t_anc_visit", "ID"), + snake("db_iemr.t_child_register", "ID"), + snake("db_iemr.t_pmsma", "id"), + + pascal("db_iemr.t_cbacdetails", "id"), + pascal("db_iemr.t_pnccare", "id"), + pascal("db_iemr.t_anccare", "ID"), + pascal("db_iemr.t_benvisitdetail", "BenVisitID"), + pascal("db_iemr.t_childvaccinedetail1", "ID")); +} diff --git a/src/main/java/com/iemr/admin/repository/username/UsernameRenameRepository.java b/src/main/java/com/iemr/admin/repository/username/UsernameRenameRepository.java new file mode 100644 index 00000000..e0a30889 --- /dev/null +++ b/src/main/java/com/iemr/admin/repository/username/UsernameRenameRepository.java @@ -0,0 +1,143 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ +package com.iemr.admin.repository.username; + +import java.math.BigInteger; +import java.util.List; + +import org.springframework.stereotype.Repository; + +import com.iemr.admin.repository.username.UsernameAuditTables.AuditTable; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.persistence.Query; + +@Repository +public class UsernameRenameRepository { + /** + * Schema, table and column names cannot be bound as parameters, so they are + * interpolated. Every identifier is checked against this before it reaches a + * statement, so a value that is not a plain SQL identifier can never be + * concatenated in. All caller-supplied data is bound, never interpolated. + */ + private static final String COUNT_BY_USER_NAME = + "SELECT COUNT(*) FROM db_iemr.m_user WHERE UserName = :value AND UserID <> :excludeUserID"; + + private static final String COUNT_BY_EMPLOYEE_ID = + "SELECT COUNT(*) FROM db_iemr.m_user WHERE EmployeeID = :value AND UserID <> :excludeUserID"; + + private static final String SET_USER_NAME = + "UPDATE db_iemr.m_user SET UserName = :newUserName WHERE UserID = :userID"; + + private static final String SET_USER_NAME_AND_CONTACTS = + "UPDATE db_iemr.m_user SET UserName = :newUserName, EmergencyContactNo = :newUserName," + + " ContactNo = :newUserName WHERE UserID = :userID"; + + private static final String SET_EMPLOYEE_ID = + "UPDATE db_iemr.m_user SET EmployeeID = :newEmployeeId WHERE UserID = :userID"; + + private static final String SET_USER_NAME_AND_EMPLOYEE_ID = + "UPDATE db_iemr.m_user SET UserName = :newUserName, EmployeeID = :newEmployeeId WHERE UserID = :userID"; + + private static final String SET_USER_NAME_CONTACTS_AND_EMPLOYEE_ID = + "UPDATE db_iemr.m_user SET UserName = :newUserName, EmergencyContactNo = :newUserName," + + " ContactNo = :newUserName, EmployeeID = :newEmployeeId WHERE UserID = :userID"; + + @PersistenceContext + private EntityManager entityManager; + + public long renameInTable(AuditTable table, String oldUserName, String newUserName) { + Query query = entityManager.createNativeQuery(table.getRenameSql()); + query.setParameter("newUserName", newUserName); + query.setParameter("oldUserName", oldUserName); + return query.executeUpdate(); + } + + public String currentUserName(Integer userID) { + return single("SELECT UserName FROM db_iemr.m_user WHERE UserID = :userID", userID); + } + + public String currentEmployeeId(Integer userID) { + return single("SELECT EmployeeID FROM db_iemr.m_user WHERE UserID = :userID", userID); + } + + private String single(String sql, Integer userID) { + Query query = entityManager.createNativeQuery(sql); + query.setParameter("userID", userID); + List rows = query.getResultList(); + return rows.isEmpty() ? null : (String) rows.get(0); + } + + public long renameUserRow(Integer userID, String newUserName, String newEmployeeId, + boolean updateContactFields) { + String sql = selectUserUpdate(newUserName, newEmployeeId, updateContactFields); + if (sql == null) { + return 0; + } + + Query query = entityManager.createNativeQuery(sql); + query.setParameter("userID", userID); + if (newUserName != null) { + query.setParameter("newUserName", newUserName); + } + if (newEmployeeId != null) { + query.setParameter("newEmployeeId", newEmployeeId); + } + return query.executeUpdate(); + } + + private static String selectUserUpdate(String newUserName, String newEmployeeId, boolean updateContactFields) { + if (newUserName == null) { + return newEmployeeId == null ? null : SET_EMPLOYEE_ID; + } + if (newEmployeeId == null) { + return updateContactFields ? SET_USER_NAME_AND_CONTACTS : SET_USER_NAME; + } + return updateContactFields ? SET_USER_NAME_CONTACTS_AND_EMPLOYEE_ID : SET_USER_NAME_AND_EMPLOYEE_ID; + } + + public boolean userNameTaken(String userName, Integer excludeUserID) { + return countMatching(COUNT_BY_USER_NAME, userName, excludeUserID) > 0; + } + + public boolean employeeIdTaken(String employeeId, Integer excludeUserID) { + return countMatching(COUNT_BY_EMPLOYEE_ID, employeeId, excludeUserID) > 0; + } + + private long countMatching(String sql, String value, Integer excludeUserID) { + Query query = entityManager.createNativeQuery(sql); + query.setParameter("value", value); + query.setParameter("excludeUserID", excludeUserID); + return toLong(query.getSingleResult()); + } + + private long toLong(Object result) { + if (result == null) { + return 0L; + } + if (result instanceof BigInteger bigInteger) { + return bigInteger.longValue(); + } + return ((Number) result).longValue(); + } +} diff --git a/src/main/java/com/iemr/admin/service/username/UsernameRenameService.java b/src/main/java/com/iemr/admin/service/username/UsernameRenameService.java new file mode 100644 index 00000000..84d8b789 --- /dev/null +++ b/src/main/java/com/iemr/admin/service/username/UsernameRenameService.java @@ -0,0 +1,29 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ +package com.iemr.admin.service.username; + +import com.iemr.admin.model.username.UsernameRenameRequest; +import com.iemr.admin.model.username.UsernameRenameResponse; + +public interface UsernameRenameService { + UsernameRenameResponse rename(UsernameRenameRequest request) throws Exception; +} diff --git a/src/main/java/com/iemr/admin/service/username/UsernameRenameServiceImpl.java b/src/main/java/com/iemr/admin/service/username/UsernameRenameServiceImpl.java new file mode 100644 index 00000000..e22c905b --- /dev/null +++ b/src/main/java/com/iemr/admin/service/username/UsernameRenameServiceImpl.java @@ -0,0 +1,158 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ +package com.iemr.admin.service.username; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.iemr.admin.model.username.UsernameRenameRequest; +import com.iemr.admin.model.username.UsernameRenameResponse; +import com.iemr.admin.repository.username.UsernameAuditTables; +import com.iemr.admin.repository.username.UsernameAuditTables.AuditTable; +import com.iemr.admin.repository.username.UsernameRenameRepository; + +@Service +public class UsernameRenameServiceImpl implements UsernameRenameService { + private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); + + private static final int MAX_USERNAME_LENGTH = 20; + private static final int MAX_EMPLOYEE_ID_LENGTH = 20; + + private static final int MAX_CONTACT_LENGTH = 12; + + @Autowired + private UsernameRenameRepository usernameRenameRepository; + + @Override + @Transactional(rollbackFor = Exception.class) + public UsernameRenameResponse rename(UsernameRenameRequest request) throws Exception { + validate(request); + + String oldUserName = request.getOldUserName(); + String newUserName = request.getNewUserName(); + String newEmployeeId = request.getNewEmployeeId(); + logger.info("Username rename starting: userID {}, userNameChanging {}, employeeIdChanging {}", + request.getUserID(), newUserName != null, newEmployeeId != null); + + UsernameRenameResponse response = newResponse(request); + response.setOldEmployeeId(usernameRenameRepository.currentEmployeeId(request.getUserID())); + + long rowsUpdated = usernameRenameRepository.renameUserRow(request.getUserID(), newUserName, newEmployeeId, + request.isUpdateContactFields()); + + if (newUserName != null) { + for (AuditTable table : UsernameAuditTables.TABLES) { + rowsUpdated += usernameRenameRepository.renameInTable(table, oldUserName, newUserName); + } + } + + logger.info("Username rename complete: {} rows updated", rowsUpdated); + return response; + } + + private UsernameRenameResponse newResponse(UsernameRenameRequest request) { + UsernameRenameResponse response = new UsernameRenameResponse(); + response.setOldUserName(request.getOldUserName()); + response.setNewUserName(request.getNewUserName()); + response.setNewEmployeeId(request.getNewEmployeeId()); + response.setUserNameUpdated(request.getNewUserName() != null); + response.setEmployeeIdUpdated(request.getNewEmployeeId() != null); + return response; + } + + private void validate(UsernameRenameRequest request) throws Exception { + if (request == null) { + throw new IllegalArgumentException("Request body is required"); + } + + if (request.getUserID() == null) { + throw new IllegalArgumentException("User ID is required"); + } + + String storedUserName = usernameRenameRepository.currentUserName(request.getUserID()); + if (storedUserName == null) { + throw new IllegalArgumentException("No user found with ID " + request.getUserID()); + } + + String oldUserName = trimToNull(request.getOldUserName()); + if (oldUserName != null && !oldUserName.equals(storedUserName)) { + throw new IllegalArgumentException("User " + request.getUserID() + " is now named " + storedUserName + + ", not " + oldUserName + ". Reload the user list and try again."); + } + request.setOldUserName(storedUserName); + oldUserName = storedUserName; + + request.setNewUserName(resolveNewUserName(request, oldUserName)); + request.setNewEmployeeId(resolveNewEmployeeId(request)); + + if (request.getNewUserName() == null && request.getNewEmployeeId() == null) { + throw new IllegalArgumentException("Nothing to update — enter a new username or a new employee ID"); + } + } + + private String resolveNewUserName(UsernameRenameRequest request, String oldUserName) throws Exception { + String newUserName = trimToNull(request.getNewUserName()); + if (newUserName == null || newUserName.equals(oldUserName)) { + return null; + } + if (newUserName.length() > MAX_USERNAME_LENGTH) { + throw new IllegalArgumentException( + "New username exceeds " + MAX_USERNAME_LENGTH + " characters (m_user.UserName limit)"); + } + if (request.isUpdateContactFields() && newUserName.length() > MAX_CONTACT_LENGTH) { + throw new IllegalArgumentException("New username exceeds " + MAX_CONTACT_LENGTH + + " characters and cannot be written to ContactNo. Either shorten it or " + + "turn off updating contact numbers."); + } + if (usernameRenameRepository.userNameTaken(newUserName, request.getUserID())) { + throw new IllegalArgumentException("Username " + newUserName + " is already in use"); + } + return newUserName; + } + + private String resolveNewEmployeeId(UsernameRenameRequest request) throws Exception { + String newEmployeeId = trimToNull(request.getNewEmployeeId()); + if (newEmployeeId == null + || newEmployeeId.equals(usernameRenameRepository.currentEmployeeId(request.getUserID()))) { + return null; + } + if (newEmployeeId.length() > MAX_EMPLOYEE_ID_LENGTH) { + throw new IllegalArgumentException( + "New employee ID exceeds " + MAX_EMPLOYEE_ID_LENGTH + " characters (m_user.EmployeeID limit)"); + } + if (usernameRenameRepository.employeeIdTaken(newEmployeeId, request.getUserID())) { + throw new IllegalArgumentException("Employee ID " + newEmployeeId + " is already in use"); + } + return newEmployeeId; + } + + private String trimToNull(String value) { + if (value == null) { + return null; + } + String trimmed = value.trim(); + return trimmed.isEmpty() ? null : trimmed; + } +}