Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@
public interface LeaveRepository extends BaseJpaRepository<Leave, Long>, JpaSpecificationExecutor<Leave> {

@EntityGraph(attributePaths = {"user", "user.team", "user.avatar", "type", "activatedType"}, type = EntityGraph.EntityGraphType.FETCH)
Optional<Leave> findByUserIdAndId(Long userId, Long id);

Optional<Leave> findByOrganizationIdAndId(Long organizationId, Long id);

@EntityGraph(attributePaths = {"user", "user.team", "user.avatar", "type", "activatedType"}, type = EntityGraph.EntityGraphType.FETCH)
Page<Leave> findByOrganizationIdAndUserId(Long organizationId, Long userId, Pageable page);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
33 changes: 19 additions & 14 deletions src/main/java/app/teamwize/api/leave/service/LeaveService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -117,34 +118,40 @@ public Page<Leave> 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) {
var sum = leaveRepository.countByOrganizationIdAndUserIdAndTypeId(organizationId, userId, activatedTypeId, status);
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<UserLeaveBalance> 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<UserLeaveBalance>();
for (var activatedType : policy.getActivatedTypes()) {
var usedAmount = this.getTotalDuration(organizationId, userId, activatedType.getId(), LeaveStatus.ACCEPTED);
Expand All @@ -153,9 +160,9 @@ public List<UserLeaveBalance> 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;
}
Expand Down Expand Up @@ -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
}
3 changes: 2 additions & 1 deletion src/main/java/app/teamwize/api/user/domain/entity/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.time.Instant;
import java.time.ZoneId;
import java.util.Objects;

Expand Down Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import app.teamwize.api.user.domain.UserRole;

import java.time.Instant;

public record UserCreateRequest(
String email,
String firstName,
Expand All @@ -12,5 +14,6 @@ public record UserCreateRequest(
String timezone,
String country,
Long teamId,
Long leavePolicyId) {
Long leavePolicyId,
Instant joinedAt) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -27,4 +30,7 @@ public class UserUpdateRequest {
private JsonNullable<Long> leavePolicyId;

private JsonNullable<Long> teamId;

@NotNull
private JsonNullable<Instant> joinedAt;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
) {
}
12 changes: 10 additions & 2 deletions src/main/java/app/teamwize/api/user/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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()));
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.5.xsd">

<changeSet id="20250305-2010-add-joined-at-to-users-table" author="M.Karimi">
<addColumn tableName="users">
<column name="joined_at" type="TIMESTAMP WITHOUT TIME ZONE" defaultValueDate="NOW()">
<constraints nullable="false"/>
</column>
</addColumn>
</changeSet>

</databaseChangeLog>
Loading