From 4ec3167d31462ccba30ef7bced5684b6d1ebff5e Mon Sep 17 00:00:00 2001 From: Mohsen Karimi Date: Wed, 5 Mar 2025 22:02:51 +0100 Subject: [PATCH] fix: allow admin to allow all leaves Signed-off-by: Mohsen Karimi --- .../api/leave/repository/LeaveRepository.java | 3 +- .../rest/controller/LeaveController.java | 2 +- .../api/leave/service/LeaveService.java | 33 +++++++++++-------- .../teamwize/api/user/domain/entity/User.java | 3 +- .../domain/request/UserCreateRequest.java | 5 ++- .../domain/request/UserUpdateRequest.java | 6 ++++ .../user/domain/response/UserResponse.java | 10 ++++-- .../api/user/service/UserService.java | 12 +++++-- ...0305-2010-add-joined-at-to-users-table.xml | 16 +++++++++ 9 files changed, 66 insertions(+), 24 deletions(-) create mode 100644 src/main/resources/db/changelog/20250305-2010-add-joined-at-to-users-table.xml diff --git a/src/main/java/app/teamwize/api/leave/repository/LeaveRepository.java b/src/main/java/app/teamwize/api/leave/repository/LeaveRepository.java index 76292af..913bcbd 100644 --- a/src/main/java/app/teamwize/api/leave/repository/LeaveRepository.java +++ b/src/main/java/app/teamwize/api/leave/repository/LeaveRepository.java @@ -18,8 +18,7 @@ public interface LeaveRepository extends BaseJpaRepository, JpaSpecificationExecutor { @EntityGraph(attributePaths = {"user", "user.team", "user.avatar", "type", "activatedType"}, type = EntityGraph.EntityGraphType.FETCH) - Optional findByUserIdAndId(Long userId, Long id); - + Optional findByOrganizationIdAndId(Long organizationId, Long id); @EntityGraph(attributePaths = {"user", "user.team", "user.avatar", "type", "activatedType"}, type = EntityGraph.EntityGraphType.FETCH) Page findByOrganizationIdAndUserId(Long organizationId, Long userId, Pageable page); diff --git a/src/main/java/app/teamwize/api/leave/rest/controller/LeaveController.java b/src/main/java/app/teamwize/api/leave/rest/controller/LeaveController.java index fc5ae1c..7903ab3 100644 --- a/src/main/java/app/teamwize/api/leave/rest/controller/LeaveController.java +++ b/src/main/java/app/teamwize/api/leave/rest/controller/LeaveController.java @@ -94,7 +94,7 @@ public LeaveResponse updateDayOff(@PathVariable Long id, @RequestBody LeaveUpdat @GetMapping("{id}") public LeaveResponse getDayOff(@PathVariable Long id) throws LeaveNotFoundException { - return leaveMapper.toLeaveResponse(leaveService.getLeave(securityService.getUserId(), id)); + return leaveMapper.toLeaveResponse(leaveService.getLeave(securityService.getUserOrganizationId(), id)); } @PostMapping("check") diff --git a/src/main/java/app/teamwize/api/leave/service/LeaveService.java b/src/main/java/app/teamwize/api/leave/service/LeaveService.java index 771df7d..bd753a2 100644 --- a/src/main/java/app/teamwize/api/leave/service/LeaveService.java +++ b/src/main/java/app/teamwize/api/leave/service/LeaveService.java @@ -25,6 +25,7 @@ import app.teamwize.api.organization.domain.entity.Organization; import app.teamwize.api.organization.exception.OrganizationNotFoundException; import app.teamwize.api.organization.service.OrganizationService; +import app.teamwize.api.user.domain.UserRole; import app.teamwize.api.user.domain.entity.User; import app.teamwize.api.user.exception.UserNotFoundException; import app.teamwize.api.user.service.UserService; @@ -117,19 +118,25 @@ public Page getLeaves(Long organizationId, Long userId, PaginationRequest @Transactional - public Leave updateLeave(Long organizationId, Long userId, Long id, LeaveUpdateCommand request) throws LeaveNotFoundException, LeaveUpdateStatusFailedException, UserNotFoundException { - var user = userService.getUser(organizationId, userId); - var leave = getById(userId, id); + public Leave updateLeave(Long organizationId, Long approverId, Long id, LeaveUpdateCommand request) throws LeaveNotFoundException, LeaveUpdateStatusFailedException, UserNotFoundException { + var approverUser = userService.getUser(organizationId, approverId); + if (approverUser.getRole() != UserRole.ORGANIZATION_ADMIN && approverUser.getRole() != UserRole.TEAM_ADMIN) { + throw new LeaveUpdateStatusFailedException("Leave update failed because user is not authorized to update leave, id = " + id); + } + var leave = leaveRepository.findByOrganizationIdAndId(organizationId, id).orElseThrow(() -> new LeaveNotFoundException("Leave not found with id: " + id)); + if (approverUser.getRole() == UserRole.TEAM_ADMIN && !leave.getUser().getTeam().getId().equals(approverUser.getTeam().getId())) { + throw new LeaveUpdateStatusFailedException("Leave update failed because user is not authorized to update leave, id = " + id); + } if (leave.getStatus() != LeaveStatus.PENDING) { throw new LeaveUpdateStatusFailedException("Leave update failed because it is not in pending status, id = " + id); } leave.setStatus(request.status()); - eventService.emmit(organizationId, new LeaveStatusUpdatedEvent(new LeaveEventPayload(leave), new UserEventPayload(user))); + eventService.emmit(organizationId, new LeaveStatusUpdatedEvent(new LeaveEventPayload(leave), new UserEventPayload(approverUser))); return leaveRepository.update(leave); } - public Leave getLeave(Long userId, Long id) throws LeaveNotFoundException { - return getById(userId, id); + public Leave getLeave(Long organizationId, Long id) throws LeaveNotFoundException { + return getById(organizationId, id); } public Float getTotalDuration(Long organizationId, Long userId, LeavePolicyActivatedTypeId activatedTypeId, LeaveStatus status) { @@ -137,14 +144,14 @@ public Float getTotalDuration(Long organizationId, Long userId, LeavePolicyActiv return sum != null ? sum : 0f; } - private Leave getById(Long userId, Long id) throws LeaveNotFoundException { - return leaveRepository.findByUserIdAndId(userId, id).orElseThrow(() -> new LeaveNotFoundException("Leave not found with id: " + id)); + private Leave getById(Long OrganizationId, Long id) throws LeaveNotFoundException { + return leaveRepository.findByOrganizationIdAndId(OrganizationId, id).orElseThrow(() -> new LeaveNotFoundException("Leave not found with id: " + id)); } public List getLeaveBalance(Long organizationId, Long userId) throws UserNotFoundException, LeavePolicyNotFoundException { var user = userService.getUser(organizationId, userId); var policy = leavePolicyService.getLeavePolicy(organizationId, user.getLeavePolicy().getId()); - var startedAt = user.getCreatedAt(); + var startedAt = user.getJoinedAt(); var result = new ArrayList(); for (var activatedType : policy.getActivatedTypes()) { var usedAmount = this.getTotalDuration(organizationId, userId, activatedType.getId(), LeaveStatus.ACCEPTED); @@ -153,9 +160,9 @@ public List getLeaveBalance(Long organizationId, Long userId) case PER_MONTH -> Period.between(startedAt.atZone(user.getTimeZoneId()).toLocalDate(), LocalDate.now()).toTotalMonths() * activatedType.getAmount(); case PER_YEAR -> - (Period.between(startedAt.atZone(user.getTimeZoneId()).toLocalDate(), LocalDate.now()).toTotalMonths() / 12) * activatedType.getAmount(); + (Period.between(startedAt.atZone(user.getTimeZoneId()).toLocalDate(), LocalDate.now()).toTotalMonths() / 12f) * activatedType.getAmount(); }; - result.add(new UserLeaveBalance(activatedType, usedAmount.longValue(), totalAmount, startedAt.atZone(user.getTimeZoneId()).toLocalDate())); + result.add(new UserLeaveBalance(activatedType, usedAmount.longValue(), (long) totalAmount, startedAt.atZone(user.getTimeZoneId()).toLocalDate())); } return result; } @@ -206,6 +213,4 @@ public LeaveCheckResult checkRequestedLeave(Long organizationId, Long userId, Le ); } -} -// Range 17 April to 25 April -// we want to know the leave request that started between 17 to 25 April or ended between 17 to 25 April \ No newline at end of file +} \ No newline at end of file diff --git a/src/main/java/app/teamwize/api/user/domain/entity/User.java b/src/main/java/app/teamwize/api/user/domain/entity/User.java index 9b704b7..cc9c68a 100644 --- a/src/main/java/app/teamwize/api/user/domain/entity/User.java +++ b/src/main/java/app/teamwize/api/user/domain/entity/User.java @@ -13,6 +13,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; +import java.time.Instant; import java.time.ZoneId; import java.util.Objects; @@ -43,7 +44,7 @@ public class User extends BaseAuditEntity { private UserStatus status; @ManyToOne(fetch = FetchType.LAZY) private Asset avatar; - + private Instant joinedAt; @ManyToOne private LeavePolicy leavePolicy; diff --git a/src/main/java/app/teamwize/api/user/domain/request/UserCreateRequest.java b/src/main/java/app/teamwize/api/user/domain/request/UserCreateRequest.java index 9bac259..129d87d 100644 --- a/src/main/java/app/teamwize/api/user/domain/request/UserCreateRequest.java +++ b/src/main/java/app/teamwize/api/user/domain/request/UserCreateRequest.java @@ -2,6 +2,8 @@ import app.teamwize.api.user.domain.UserRole; +import java.time.Instant; + public record UserCreateRequest( String email, String firstName, @@ -12,5 +14,6 @@ public record UserCreateRequest( String timezone, String country, Long teamId, - Long leavePolicyId) { + Long leavePolicyId, + Instant joinedAt) { } \ No newline at end of file diff --git a/src/main/java/app/teamwize/api/user/domain/request/UserUpdateRequest.java b/src/main/java/app/teamwize/api/user/domain/request/UserUpdateRequest.java index ee7d1b5..59c9cd1 100644 --- a/src/main/java/app/teamwize/api/user/domain/request/UserUpdateRequest.java +++ b/src/main/java/app/teamwize/api/user/domain/request/UserUpdateRequest.java @@ -3,10 +3,13 @@ import app.teamwize.api.base.validator.PhoneNumber; import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.Data; import org.openapitools.jackson.nullable.JsonNullable; +import java.time.Instant; + @Data public class UserUpdateRequest { @@ -27,4 +30,7 @@ public class UserUpdateRequest { private JsonNullable leavePolicyId; private JsonNullable teamId; + + @NotNull + private JsonNullable joinedAt; } diff --git a/src/main/java/app/teamwize/api/user/domain/response/UserResponse.java b/src/main/java/app/teamwize/api/user/domain/response/UserResponse.java index 56dd024..c540e4f 100644 --- a/src/main/java/app/teamwize/api/user/domain/response/UserResponse.java +++ b/src/main/java/app/teamwize/api/user/domain/response/UserResponse.java @@ -3,14 +3,15 @@ import app.teamwize.api.assets.domain.model.response.AssetCompactResponse; import app.teamwize.api.leave.rest.model.response.LeavePolicyCompactResponse; +import app.teamwize.api.organization.domain.response.OrganizationCompactResponse; import app.teamwize.api.team.domain.response.TeamCompactResponse; import app.teamwize.api.user.domain.UserRole; import app.teamwize.api.user.domain.UserStatus; -import app.teamwize.api.organization.domain.response.OrganizationCompactResponse; - import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; +import java.time.Instant; + public record UserResponse( @Nonnull Long id, @@ -25,6 +26,9 @@ public record UserResponse( @Nonnull OrganizationCompactResponse organization, @Nonnull TeamCompactResponse team, @Nonnull AssetCompactResponse avatar, - @Nonnull LeavePolicyCompactResponse leavePolicy + @Nonnull LeavePolicyCompactResponse leavePolicy, + @Nonnull Instant joinedAt, + @Nonnull Instant createdAt, + @Nonnull Instant updatedAt ) { } \ No newline at end of file diff --git a/src/main/java/app/teamwize/api/user/service/UserService.java b/src/main/java/app/teamwize/api/user/service/UserService.java index 02a9e61..951ab65 100644 --- a/src/main/java/app/teamwize/api/user/service/UserService.java +++ b/src/main/java/app/teamwize/api/user/service/UserService.java @@ -33,6 +33,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.Instant; + import static app.teamwize.api.user.repository.UserSpecifications.*; @Slf4j @@ -87,7 +89,8 @@ public User createOrganizationAdmin(Long organizationId, Long teamId, AdminUserC .setCountry(request.country()) .setTeam(team) .setOrganization(organization) - .setLeavePolicy(leavePolicy); + .setLeavePolicy(leavePolicy) + .setJoinedAt(Instant.now()); user.setTeam(team); user.setPassword(passwordEncoder.encode(request.password())); return userRepository.merge(user); @@ -116,7 +119,8 @@ public User createUser(Long organizationId, Long inviterUserId, UserCreateReques .setCountry(request.country()) .setTeam(team) .setOrganization(organization) - .setLeavePolicy(leavePolicy); + .setLeavePolicy(leavePolicy) + .setJoinedAt(request.joinedAt() == null ? Instant.now() : request.joinedAt()); if (request.password() != null && !request.password().isBlank()) { user.setPassword(passwordEncoder.encode(request.password())); @@ -145,6 +149,10 @@ public User partiallyUpdateUser(Long organizationId, Long userId, UserUpdateRequ request.getPhone().ifPresent(user::setPhone); } + if (request.getJoinedAt() != null) { + request.getJoinedAt().ifPresent(user::setJoinedAt); + } + if (request.getAvatarAssetId() != null && request.getAvatarAssetId().isPresent()) { var asset = assetService.getAsset(organizationId, request.getAvatarAssetId().get()); user.setAvatar(asset); diff --git a/src/main/resources/db/changelog/20250305-2010-add-joined-at-to-users-table.xml b/src/main/resources/db/changelog/20250305-2010-add-joined-at-to-users-table.xml new file mode 100644 index 0000000..4b9c395 --- /dev/null +++ b/src/main/resources/db/changelog/20250305-2010-add-joined-at-to-users-table.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + \ No newline at end of file