add user phone

This commit is contained in:
2026-03-14 20:12:48 -06:00
parent bc9bd09d24
commit efc9836c11
13 changed files with 245 additions and 43 deletions

View File

@@ -35,6 +35,7 @@ public class DataInitializer implements CommandLineRunner {
admin.setPassword(passwordEncoder.encode("admin123")); admin.setPassword(passwordEncoder.encode("admin123"));
admin.setEmail("admin@petshop.com"); admin.setEmail("admin@petshop.com");
admin.setFullName("Admin User"); admin.setFullName("Admin User");
admin.setPhone("000-000-1000");
admin.setRole(User.Role.ADMIN); admin.setRole(User.Role.ADMIN);
admin.setActive(true); admin.setActive(true);
admin = userRepository.save(admin); admin = userRepository.save(admin);
@@ -51,6 +52,10 @@ public class DataInitializer implements CommandLineRunner {
admin.setEmail("admin@petshop.com"); admin.setEmail("admin@petshop.com");
updated = true; updated = true;
} }
if (admin.getPhone() == null || admin.getPhone().isEmpty()) {
admin.setPhone("000-000-1000");
updated = true;
}
if (admin.getActive() == null) { if (admin.getActive() == null) {
admin.setActive(true); admin.setActive(true);
updated = true; updated = true;
@@ -75,6 +80,7 @@ public class DataInitializer implements CommandLineRunner {
staff.setPassword(passwordEncoder.encode("staff123")); staff.setPassword(passwordEncoder.encode("staff123"));
staff.setEmail("staff@petshop.com"); staff.setEmail("staff@petshop.com");
staff.setFullName("Staff User"); staff.setFullName("Staff User");
staff.setPhone("000-000-1001");
staff.setRole(User.Role.STAFF); staff.setRole(User.Role.STAFF);
staff.setActive(true); staff.setActive(true);
staff = userRepository.save(staff); staff = userRepository.save(staff);
@@ -91,6 +97,10 @@ public class DataInitializer implements CommandLineRunner {
staff.setEmail("staff@petshop.com"); staff.setEmail("staff@petshop.com");
updated = true; updated = true;
} }
if (staff.getPhone() == null || staff.getPhone().isEmpty()) {
staff.setPhone("000-000-1001");
updated = true;
}
if (staff.getActive() == null) { if (staff.getActive() == null) {
staff.setActive(true); staff.setActive(true);
updated = true; updated = true;
@@ -115,6 +125,7 @@ public class DataInitializer implements CommandLineRunner {
customer.setPassword(passwordEncoder.encode("customer123")); customer.setPassword(passwordEncoder.encode("customer123"));
customer.setEmail("customer@petshop.com"); customer.setEmail("customer@petshop.com");
customer.setFullName("Test Customer"); customer.setFullName("Test Customer");
customer.setPhone("000-000-1002");
customer.setRole(User.Role.CUSTOMER); customer.setRole(User.Role.CUSTOMER);
customer.setActive(true); customer.setActive(true);
customer = userRepository.save(customer); customer = userRepository.save(customer);
@@ -131,6 +142,10 @@ public class DataInitializer implements CommandLineRunner {
customer.setEmail("customer@petshop.com"); customer.setEmail("customer@petshop.com");
updated = true; updated = true;
} }
if (customer.getPhone() == null || customer.getPhone().isEmpty()) {
customer.setPhone("000-000-1002");
updated = true;
}
if (customer.getActive() == null) { if (customer.getActive() == null) {
customer.setActive(true); customer.setActive(true);
updated = true; updated = true;

View File

@@ -74,11 +74,19 @@ public class AuthController {
return ResponseEntity.status(HttpStatus.CONFLICT).body(error); return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
} }
String phone = trimToNull(request.getPhone());
if (phone != null && userRepository.findByPhone(phone).isPresent()) {
Map<String, String> error = new HashMap<>();
error.put("message", "Phone already exists");
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
}
User user = new User(); User user = new User();
user.setUsername(request.getUsername()); user.setUsername(request.getUsername());
user.setPassword(passwordEncoder.encode(request.getPassword())); user.setPassword(passwordEncoder.encode(request.getPassword()));
user.setEmail(request.getEmail()); user.setEmail(request.getEmail());
user.setFullName(request.getFullName()); user.setFullName(request.getFullName());
user.setPhone(phone);
user.setRole(User.Role.CUSTOMER); user.setRole(User.Role.CUSTOMER);
user.setActive(true); user.setActive(true);
@@ -93,6 +101,7 @@ public class AuthController {
savedUser.getId(), savedUser.getId(),
savedUser.getUsername(), savedUser.getUsername(),
savedUser.getEmail(), savedUser.getEmail(),
savedUser.getPhone(),
savedUser.getRole().name(), savedUser.getRole().name(),
token token
)); ));
@@ -145,6 +154,7 @@ public class AuthController {
user.getUsername(), user.getUsername(),
user.getEmail(), user.getEmail(),
user.getFullName(), user.getFullName(),
user.getPhone(),
user.getAvatarUrl(), user.getAvatarUrl(),
user.getRole().name(), user.getRole().name(),
employeeStore != null ? employeeStore.getStore().getStoreId() : null, employeeStore != null ? employeeStore.getStore().getStoreId() : null,
@@ -180,6 +190,20 @@ public class AuthController {
user.setFullName(request.getFullName()); user.setFullName(request.getFullName());
} }
if (request.getPhone() != null) {
String phone = trimToNull(request.getPhone());
if (!java.util.Objects.equals(phone, user.getPhone())) {
if (phone != null && userRepository.findByPhone(phone)
.filter(existing -> !existing.getId().equals(user.getId()))
.isPresent()) {
Map<String, String> error = new HashMap<>();
error.put("message", "Phone already exists");
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
}
user.setPhone(phone);
}
}
if (request.getPassword() != null && !request.getPassword().isEmpty()) { if (request.getPassword() != null && !request.getPassword().isEmpty()) {
user.setPassword(passwordEncoder.encode(request.getPassword())); user.setPassword(passwordEncoder.encode(request.getPassword()));
invalidateToken = true; invalidateToken = true;
@@ -190,6 +214,7 @@ public class AuthController {
} }
User updatedUser = userRepository.save(user); User updatedUser = userRepository.save(user);
userBusinessLinkageService.syncLinkedRecords(updatedUser);
EmployeeStore employeeStore = resolveEmployeeStore(updatedUser); EmployeeStore employeeStore = resolveEmployeeStore(updatedUser);
@@ -198,6 +223,7 @@ public class AuthController {
updatedUser.getUsername(), updatedUser.getUsername(),
updatedUser.getEmail(), updatedUser.getEmail(),
updatedUser.getFullName(), updatedUser.getFullName(),
updatedUser.getPhone(),
updatedUser.getAvatarUrl(), updatedUser.getAvatarUrl(),
updatedUser.getRole().name(), updatedUser.getRole().name(),
employeeStore != null ? employeeStore.getStore().getStoreId() : null, employeeStore != null ? employeeStore.getStore().getStoreId() : null,
@@ -215,6 +241,14 @@ public class AuthController {
.orElse(null); .orElse(null);
} }
private String trimToNull(String value) {
if (value == null) {
return null;
}
String trimmed = value.trim();
return trimmed.isEmpty() ? null : trimmed;
}
@PostMapping("/me/avatar") @PostMapping("/me/avatar")
public ResponseEntity<?> uploadAvatar(@RequestParam("avatar") MultipartFile file) { public ResponseEntity<?> uploadAvatar(@RequestParam("avatar") MultipartFile file) {
User user = getAuthenticatedUser(); User user = getAuthenticatedUser();

View File

@@ -14,6 +14,9 @@ public class ProfileUpdateRequest {
@Size(max = 100, message = "Full name must not exceed 100 characters") @Size(max = 100, message = "Full name must not exceed 100 characters")
private String fullName; private String fullName;
@Size(max = 20, message = "Phone must not exceed 20 characters")
private String phone;
@Size(min = 6, message = "Password must be at least 6 characters") @Size(min = 6, message = "Password must be at least 6 characters")
private String password; private String password;
@@ -41,6 +44,14 @@ public class ProfileUpdateRequest {
this.fullName = fullName; this.fullName = fullName;
} }
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getPassword() { public String getPassword() {
return password; return password;
} }
@@ -57,12 +68,13 @@ public class ProfileUpdateRequest {
return Objects.equals(username, that.username) && return Objects.equals(username, that.username) &&
Objects.equals(email, that.email) && Objects.equals(email, that.email) &&
Objects.equals(fullName, that.fullName) && Objects.equals(fullName, that.fullName) &&
Objects.equals(phone, that.phone) &&
Objects.equals(password, that.password); Objects.equals(password, that.password);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(username, email, fullName, password); return Objects.hash(username, email, fullName, phone, password);
} }
@Override @Override
@@ -71,6 +83,7 @@ public class ProfileUpdateRequest {
"username='" + username + '\'' + "username='" + username + '\'' +
", email='" + email + '\'' + ", email='" + email + '\'' +
", fullName='" + fullName + '\'' + ", fullName='" + fullName + '\'' +
", phone='" + phone + '\'' +
", password='" + password + '\'' + ", password='" + password + '\'' +
'}'; '}';
} }

View File

@@ -22,6 +22,10 @@ public class RegisterRequest {
@Size(max = 100, message = "Full name must not exceed 100 characters") @Size(max = 100, message = "Full name must not exceed 100 characters")
private String fullName; private String fullName;
@NotBlank(message = "Phone is required")
@Size(max = 20, message = "Phone must not exceed 20 characters")
private String phone;
public String getUsername() { public String getUsername() {
return username; return username;
} }
@@ -54,6 +58,14 @@ public class RegisterRequest {
this.fullName = fullName; this.fullName = fullName;
} }
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
@@ -62,12 +74,13 @@ public class RegisterRequest {
return Objects.equals(username, that.username) && return Objects.equals(username, that.username) &&
Objects.equals(password, that.password) && Objects.equals(password, that.password) &&
Objects.equals(email, that.email) && Objects.equals(email, that.email) &&
Objects.equals(fullName, that.fullName); Objects.equals(fullName, that.fullName) &&
Objects.equals(phone, that.phone);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(username, password, email, fullName); return Objects.hash(username, password, email, fullName, phone);
} }
@Override @Override
@@ -77,6 +90,7 @@ public class RegisterRequest {
", password='" + password + '\'' + ", password='" + password + '\'' +
", email='" + email + '\'' + ", email='" + email + '\'' +
", fullName='" + fullName + '\'' + ", fullName='" + fullName + '\'' +
", phone='" + phone + '\'' +
'}'; '}';
} }
} }

View File

@@ -6,16 +6,18 @@ public class RegisterResponse {
private Long id; private Long id;
private String username; private String username;
private String email; private String email;
private String phone;
private String role; private String role;
private String token; private String token;
public RegisterResponse() { public RegisterResponse() {
} }
public RegisterResponse(Long id, String username, String email, String role, String token) { public RegisterResponse(Long id, String username, String email, String phone, String role, String token) {
this.id = id; this.id = id;
this.username = username; this.username = username;
this.email = email; this.email = email;
this.phone = phone;
this.role = role; this.role = role;
this.token = token; this.token = token;
} }
@@ -44,6 +46,14 @@ public class RegisterResponse {
this.email = email; this.email = email;
} }
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getRole() { public String getRole() {
return role; return role;
} }
@@ -68,13 +78,14 @@ public class RegisterResponse {
return Objects.equals(id, that.id) && return Objects.equals(id, that.id) &&
Objects.equals(username, that.username) && Objects.equals(username, that.username) &&
Objects.equals(email, that.email) && Objects.equals(email, that.email) &&
Objects.equals(phone, that.phone) &&
Objects.equals(role, that.role) && Objects.equals(role, that.role) &&
Objects.equals(token, that.token); Objects.equals(token, that.token);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(id, username, email, role, token); return Objects.hash(id, username, email, phone, role, token);
} }
@Override @Override
@@ -83,6 +94,7 @@ public class RegisterResponse {
"id=" + id + "id=" + id +
", username='" + username + '\'' + ", username='" + username + '\'' +
", email='" + email + '\'' + ", email='" + email + '\'' +
", phone='" + phone + '\'' +
", role='" + role + '\'' + ", role='" + role + '\'' +
", token='" + token + '\'' + ", token='" + token + '\'' +
'}'; '}';

View File

@@ -7,6 +7,7 @@ public class UserInfoResponse {
private String username; private String username;
private String email; private String email;
private String fullName; private String fullName;
private String phone;
private String avatarUrl; private String avatarUrl;
private String role; private String role;
private Long storeId; private Long storeId;
@@ -15,11 +16,12 @@ public class UserInfoResponse {
public UserInfoResponse() { public UserInfoResponse() {
} }
public UserInfoResponse(Long id, String username, String email, String fullName, String avatarUrl, String role, Long storeId, String storeName) { public UserInfoResponse(Long id, String username, String email, String fullName, String phone, String avatarUrl, String role, Long storeId, String storeName) {
this.id = id; this.id = id;
this.username = username; this.username = username;
this.email = email; this.email = email;
this.fullName = fullName; this.fullName = fullName;
this.phone = phone;
this.avatarUrl = avatarUrl; this.avatarUrl = avatarUrl;
this.role = role; this.role = role;
this.storeId = storeId; this.storeId = storeId;
@@ -58,6 +60,14 @@ public class UserInfoResponse {
this.fullName = fullName; this.fullName = fullName;
} }
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getAvatarUrl() { public String getAvatarUrl() {
return avatarUrl; return avatarUrl;
} }
@@ -99,6 +109,7 @@ public class UserInfoResponse {
Objects.equals(username, that.username) && Objects.equals(username, that.username) &&
Objects.equals(email, that.email) && Objects.equals(email, that.email) &&
Objects.equals(fullName, that.fullName) && Objects.equals(fullName, that.fullName) &&
Objects.equals(phone, that.phone) &&
Objects.equals(avatarUrl, that.avatarUrl) && Objects.equals(avatarUrl, that.avatarUrl) &&
Objects.equals(role, that.role) && Objects.equals(role, that.role) &&
Objects.equals(storeId, that.storeId) && Objects.equals(storeId, that.storeId) &&
@@ -107,7 +118,7 @@ public class UserInfoResponse {
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(id, username, email, fullName, avatarUrl, role, storeId, storeName); return Objects.hash(id, username, email, fullName, phone, avatarUrl, role, storeId, storeName);
} }
@Override @Override
@@ -117,6 +128,7 @@ public class UserInfoResponse {
", username='" + username + '\'' + ", username='" + username + '\'' +
", email='" + email + '\'' + ", email='" + email + '\'' +
", fullName='" + fullName + '\'' + ", fullName='" + fullName + '\'' +
", phone='" + phone + '\'' +
", avatarUrl='" + avatarUrl + '\'' + ", avatarUrl='" + avatarUrl + '\'' +
", role='" + role + '\'' + ", role='" + role + '\'' +
", storeId=" + storeId + ", storeId=" + storeId +

View File

@@ -21,6 +21,9 @@ public class UserRequest {
@Email(message = "Invalid email format") @Email(message = "Invalid email format")
private String email; private String email;
@Size(max = 20, message = "Phone must not exceed 20 characters")
private String phone;
@NotNull(message = "Role is required") @NotNull(message = "Role is required")
private User.Role role; private User.Role role;
@@ -58,6 +61,14 @@ public class UserRequest {
this.email = email; this.email = email;
} }
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public User.Role getRole() { public User.Role getRole() {
return role; return role;
} }
@@ -83,13 +94,14 @@ public class UserRequest {
Objects.equals(password, that.password) && Objects.equals(password, that.password) &&
Objects.equals(fullName, that.fullName) && Objects.equals(fullName, that.fullName) &&
Objects.equals(email, that.email) && Objects.equals(email, that.email) &&
Objects.equals(phone, that.phone) &&
role == that.role && role == that.role &&
Objects.equals(active, that.active); Objects.equals(active, that.active);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(username, password, fullName, email, role, active); return Objects.hash(username, password, fullName, email, phone, role, active);
} }
@Override @Override
@@ -99,6 +111,7 @@ public class UserRequest {
", password='" + password + '\'' + ", password='" + password + '\'' +
", fullName='" + fullName + '\'' + ", fullName='" + fullName + '\'' +
", email='" + email + '\'' + ", email='" + email + '\'' +
", phone='" + phone + '\'' +
", role=" + role + ", role=" + role +
", active=" + active + ", active=" + active +
'}'; '}';

View File

@@ -8,6 +8,7 @@ public class UserResponse {
private String username; private String username;
private String fullName; private String fullName;
private String email; private String email;
private String phone;
private String role; private String role;
private Boolean active; private Boolean active;
private LocalDateTime createdAt; private LocalDateTime createdAt;
@@ -16,11 +17,12 @@ public class UserResponse {
public UserResponse() { public UserResponse() {
} }
public UserResponse(Long id, String username, String fullName, String email, String role, Boolean active, LocalDateTime createdAt, LocalDateTime updatedAt) { public UserResponse(Long id, String username, String fullName, String email, String phone, String role, Boolean active, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id; this.id = id;
this.username = username; this.username = username;
this.fullName = fullName; this.fullName = fullName;
this.email = email; this.email = email;
this.phone = phone;
this.role = role; this.role = role;
this.active = active; this.active = active;
this.createdAt = createdAt; this.createdAt = createdAt;
@@ -59,6 +61,14 @@ public class UserResponse {
this.email = email; this.email = email;
} }
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getRole() { public String getRole() {
return role; return role;
} }
@@ -96,12 +106,12 @@ public class UserResponse {
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
UserResponse that = (UserResponse) o; UserResponse that = (UserResponse) o;
return Objects.equals(id, that.id) && Objects.equals(username, that.username) && Objects.equals(fullName, that.fullName) && Objects.equals(email, that.email) && Objects.equals(role, that.role) && Objects.equals(active, that.active) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); return Objects.equals(id, that.id) && Objects.equals(username, that.username) && Objects.equals(fullName, that.fullName) && Objects.equals(email, that.email) && Objects.equals(phone, that.phone) && Objects.equals(role, that.role) && Objects.equals(active, that.active) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(id, username, fullName, email, role, active, createdAt, updatedAt); return Objects.hash(id, username, fullName, email, phone, role, active, createdAt, updatedAt);
} }
@Override @Override
@@ -111,6 +121,7 @@ public class UserResponse {
", username='" + username + '\'' + ", username='" + username + '\'' +
", fullName='" + fullName + '\'' + ", fullName='" + fullName + '\'' +
", email='" + email + '\'' + ", email='" + email + '\'' +
", phone='" + phone + '\'' +
", role='" + role + '\'' + ", role='" + role + '\'' +
", active=" + active + ", active=" + active +
", createdAt=" + createdAt + ", createdAt=" + createdAt +

View File

@@ -27,6 +27,9 @@ public class User {
@Column(length = 100) @Column(length = 100)
private String fullName; private String fullName;
@Column(length = 20)
private String phone;
@Column(length = 255) @Column(length = 255)
private String avatarUrl; private String avatarUrl;
@@ -55,12 +58,13 @@ public class User {
public User() { public User() {
} }
public User(Long id, String username, String password, String email, String fullName, String avatarUrl, Role role, Boolean active, Integer tokenVersion, LocalDateTime createdAt, LocalDateTime updatedAt) { public User(Long id, String username, String password, String email, String fullName, String phone, String avatarUrl, Role role, Boolean active, Integer tokenVersion, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id; this.id = id;
this.username = username; this.username = username;
this.password = password; this.password = password;
this.email = email; this.email = email;
this.fullName = fullName; this.fullName = fullName;
this.phone = phone;
this.avatarUrl = avatarUrl; this.avatarUrl = avatarUrl;
this.role = role; this.role = role;
this.active = active; this.active = active;
@@ -109,6 +113,14 @@ public class User {
this.fullName = fullName; this.fullName = fullName;
} }
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getAvatarUrl() { public String getAvatarUrl() {
return avatarUrl; return avatarUrl;
} }
@@ -178,6 +190,7 @@ public class User {
", password='" + password + '\'' + ", password='" + password + '\'' +
", email='" + email + '\'' + ", email='" + email + '\'' +
", fullName='" + fullName + '\'' + ", fullName='" + fullName + '\'' +
", phone='" + phone + '\'' +
", avatarUrl='" + avatarUrl + '\'' + ", avatarUrl='" + avatarUrl + '\'' +
", role=" + role + ", role=" + role +
", active=" + active + ", active=" + active +

View File

@@ -14,9 +14,13 @@ import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> { public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username); Optional<User> findByUsername(String username);
Optional<User> findByEmail(String email); Optional<User> findByEmail(String email);
Optional<User> findByPhone(String phone);
boolean existsByUsername(String username); boolean existsByUsername(String username);
@Query("SELECT u FROM User u WHERE " + @Query("SELECT u FROM User u WHERE " +
"LOWER(u.username) LIKE LOWER(CONCAT('%', :q, '%'))") "LOWER(u.username) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
"LOWER(COALESCE(u.fullName, '')) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
"LOWER(COALESCE(u.email, '')) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
"LOWER(COALESCE(u.phone, '')) LIKE LOWER(CONCAT('%', :q, '%'))")
Page<User> searchUsers(@Param("q") String query, Pageable pageable); Page<User> searchUsers(@Param("q") String query, Pageable pageable);
} }

View File

@@ -25,88 +25,116 @@ public class UserBusinessLinkageService {
@Transactional @Transactional
public Employee ensureLinkedEmployee(User user) { public Employee ensureLinkedEmployee(User user) {
// Check if already linked
if (user.getId() != null) { if (user.getId() != null) {
var existing = employeeRepository.findByUserId(user.getId()); var existing = employeeRepository.findByUserId(user.getId());
if (existing.isPresent()) { if (existing.isPresent()) {
return existing.get(); return syncEmployee(existing.get(), user);
} }
} }
// Check for email matches
List<Employee> emailMatches = employeeRepository.findAllByEmail(user.getEmail()); List<Employee> emailMatches = employeeRepository.findAllByEmail(user.getEmail());
// If exactly one match exists and has no userId, link it
if (emailMatches.size() == 1) { if (emailMatches.size() == 1) {
Employee employee = emailMatches.get(0); Employee employee = emailMatches.get(0);
if (employee.getUserId() == null) { if (employee.getUserId() == null) {
employee.setUserId(user.getId()); employee.setUserId(user.getId());
return employeeRepository.save(employee); return syncEmployee(employee, user);
} }
} }
// Otherwise create a new linked Employee
Employee newEmployee = new Employee(); Employee newEmployee = new Employee();
newEmployee.setUserId(user.getId()); newEmployee.setUserId(user.getId());
newEmployee.setEmail(user.getEmail()); newEmployee.setEmail(user.getEmail());
// Split fullName into firstName and lastName
String[] nameParts = splitFullName(user.getFullName()); String[] nameParts = splitFullName(user.getFullName());
newEmployee.setFirstName(nameParts[0]); newEmployee.setFirstName(nameParts[0]);
newEmployee.setLastName(nameParts[1]); newEmployee.setLastName(nameParts[1]);
// Set required fields with deterministic values newEmployee.setPhone(normalizePhone(user.getPhone(), "000-000-0000"));
newEmployee.setPhone("000-000-0000");
newEmployee.setIsActive(true); newEmployee.setIsActive(true);
// Map role based on user role
if (user.getRole() == User.Role.ADMIN) { if (user.getRole() == User.Role.ADMIN) {
newEmployee.setRole("Manager"); newEmployee.setRole("Manager");
} else if (user.getRole() == User.Role.STAFF) { } else if (user.getRole() == User.Role.STAFF) {
newEmployee.setRole("Staff"); newEmployee.setRole("Staff");
} else { } else {
newEmployee.setRole("Staff"); // fallback newEmployee.setRole("Staff");
} }
return employeeRepository.save(newEmployee); return syncEmployee(newEmployee, user);
} }
@Transactional @Transactional
public Customer ensureLinkedCustomer(User user) { public Customer ensureLinkedCustomer(User user) {
// Check if already linked
if (user.getId() != null) { if (user.getId() != null) {
var existing = customerRepository.findByUserId(user.getId()); var existing = customerRepository.findByUserId(user.getId());
if (existing.isPresent()) { if (existing.isPresent()) {
return existing.get(); return syncCustomer(existing.get(), user);
} }
} }
// Check for email matches
List<Customer> emailMatches = customerRepository.findAllByEmail(user.getEmail()); List<Customer> emailMatches = customerRepository.findAllByEmail(user.getEmail());
// If exactly one match exists and has no userId, link it
if (emailMatches.size() == 1) { if (emailMatches.size() == 1) {
Customer customer = emailMatches.get(0); Customer customer = emailMatches.get(0);
if (customer.getUserId() == null) { if (customer.getUserId() == null) {
customer.setUserId(user.getId()); customer.setUserId(user.getId());
return customerRepository.save(customer); return syncCustomer(customer, user);
} }
} }
// Otherwise create a new linked Customer
Customer newCustomer = new Customer(); Customer newCustomer = new Customer();
newCustomer.setUserId(user.getId()); newCustomer.setUserId(user.getId());
newCustomer.setEmail(user.getEmail()); newCustomer.setEmail(user.getEmail());
// Split fullName into firstName and lastName
String[] nameParts = splitFullName(user.getFullName()); String[] nameParts = splitFullName(user.getFullName());
newCustomer.setFirstName(nameParts[0]); newCustomer.setFirstName(nameParts[0]);
newCustomer.setLastName(nameParts[1]); newCustomer.setLastName(nameParts[1]);
// Set required fields with deterministic values newCustomer.setPhone(normalizePhone(user.getPhone(), "000-000-0001"));
newCustomer.setPhone("000-000-0001");
return customerRepository.save(newCustomer); return syncCustomer(newCustomer, user);
}
@Transactional
public void syncLinkedRecords(User user) {
if (user.getRole() == User.Role.CUSTOMER) {
ensureLinkedCustomer(user);
return;
}
ensureLinkedEmployee(user);
}
private Employee syncEmployee(Employee employee, User user) {
employee.setUserId(user.getId());
employee.setEmail(user.getEmail());
String[] nameParts = splitFullName(user.getFullName());
employee.setFirstName(nameParts[0]);
employee.setLastName(nameParts[1]);
employee.setPhone(normalizePhone(user.getPhone(), employee.getPhone()));
if (user.getRole() == User.Role.ADMIN) {
employee.setRole("Manager");
} else {
employee.setRole("Staff");
}
return employeeRepository.save(employee);
}
private Customer syncCustomer(Customer customer, User user) {
customer.setUserId(user.getId());
customer.setEmail(user.getEmail());
String[] nameParts = splitFullName(user.getFullName());
customer.setFirstName(nameParts[0]);
customer.setLastName(nameParts[1]);
customer.setPhone(normalizePhone(user.getPhone(), customer.getPhone()));
return customerRepository.save(customer);
}
private String normalizePhone(String phone, String fallback) {
if (phone == null || phone.trim().isEmpty()) {
return fallback;
}
return phone.trim();
} }
private String[] splitFullName(String fullName) { private String[] splitFullName(String fullName) {
@@ -118,11 +146,9 @@ public class UserBusinessLinkageService {
int spaceIndex = trimmed.indexOf(' '); int spaceIndex = trimmed.indexOf(' ');
if (spaceIndex == -1) { if (spaceIndex == -1) {
// Single token
return new String[]{trimmed, "User"}; return new String[]{trimmed, "User"};
} }
// Multiple tokens
String firstName = trimmed.substring(0, spaceIndex).trim(); String firstName = trimmed.substring(0, spaceIndex).trim();
String lastName = trimmed.substring(spaceIndex + 1).trim(); String lastName = trimmed.substring(spaceIndex + 1).trim();

View File

@@ -11,6 +11,9 @@ import org.springframework.data.domain.Pageable;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;
import static org.springframework.http.HttpStatus.CONFLICT;
@Service @Service
public class UserService { public class UserService {
@@ -48,17 +51,15 @@ public class UserService {
user.setPassword(passwordEncoder.encode(request.getPassword())); user.setPassword(passwordEncoder.encode(request.getPassword()));
user.setFullName(request.getFullName()); user.setFullName(request.getFullName());
user.setEmail(request.getEmail()); user.setEmail(request.getEmail());
user.setPhone(trimToNull(request.getPhone()));
user.setRole(request.getRole()); user.setRole(request.getRole());
user.setActive(request.getActive() != null ? request.getActive() : true); user.setActive(request.getActive() != null ? request.getActive() : true);
validateUniquePhone(user.getPhone(), null);
user = userRepository.save(user); user = userRepository.save(user);
// Create or link business entity based on role userBusinessLinkageService.syncLinkedRecords(user);
if (user.getRole() == User.Role.STAFF || user.getRole() == User.Role.ADMIN) {
userBusinessLinkageService.ensureLinkedEmployee(user);
} else if (user.getRole() == User.Role.CUSTOMER) {
userBusinessLinkageService.ensureLinkedCustomer(user);
}
return mapToResponse(user); return mapToResponse(user);
} }
@@ -80,6 +81,11 @@ public class UserService {
} }
user.setFullName(request.getFullName()); user.setFullName(request.getFullName());
user.setEmail(request.getEmail()); user.setEmail(request.getEmail());
String phone = trimToNull(request.getPhone());
if (!java.util.Objects.equals(user.getPhone(), phone)) {
validateUniquePhone(phone, user.getId());
}
user.setPhone(phone);
user.setRole(request.getRole()); user.setRole(request.getRole());
user.setActive(request.getActive() != null ? request.getActive() : true); user.setActive(request.getActive() != null ? request.getActive() : true);
if (invalidateToken) { if (invalidateToken) {
@@ -87,6 +93,7 @@ public class UserService {
} }
user = userRepository.save(user); user = userRepository.save(user);
userBusinessLinkageService.syncLinkedRecords(user);
return mapToResponse(user); return mapToResponse(user);
} }
@@ -109,10 +116,30 @@ public class UserService {
response.setUsername(user.getUsername()); response.setUsername(user.getUsername());
response.setFullName(user.getFullName()); response.setFullName(user.getFullName());
response.setEmail(user.getEmail()); response.setEmail(user.getEmail());
response.setPhone(user.getPhone());
response.setRole(user.getRole().toString()); response.setRole(user.getRole().toString());
response.setActive(user.getActive()); response.setActive(user.getActive());
response.setCreatedAt(user.getCreatedAt()); response.setCreatedAt(user.getCreatedAt());
response.setUpdatedAt(user.getUpdatedAt()); response.setUpdatedAt(user.getUpdatedAt());
return response; return response;
} }
private void validateUniquePhone(String phone, Long currentUserId) {
if (phone == null || phone.isBlank()) {
return;
}
userRepository.findByPhone(phone)
.filter(existing -> !existing.getId().equals(currentUserId))
.ifPresent(existing -> {
throw new ResponseStatusException(CONFLICT, "Phone already exists");
});
}
private String trimToNull(String value) {
if (value == null) {
return null;
}
String trimmed = value.trim();
return trimmed.isEmpty() ? null : trimmed;
}
} }

View File

@@ -0,0 +1,8 @@
ALTER TABLE users
ADD COLUMN phone VARCHAR(20) NULL AFTER fullName;
UPDATE users u
LEFT JOIN customer c ON c.user_id = u.id
LEFT JOIN employee e ON e.user_id = u.id
SET u.phone = COALESCE(NULLIF(c.phone, ''), NULLIF(e.phone, ''), u.phone)
WHERE u.phone IS NULL OR u.phone = '';