-
Notifications
You must be signed in to change notification settings - Fork 2
Wnioski o kwalifikacje pracownikow #52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d1f000b
f8811f2
73bdd19
dfd66e7
2758593
b80a420
f70f378
942da4e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package pl.edu.agh.project_manager.controller.dto.approvals; | ||
|
|
||
| import pl.edu.agh.project_manager.domain.entity.user.Qualification; | ||
|
|
||
| import java.util.UUID; | ||
|
|
||
| public record QualificationDetailsResponse( | ||
| UUID qualificationId, | ||
| String qualificationName | ||
| ) { | ||
| public static QualificationDetailsResponse from(Qualification qualification) { | ||
| return new QualificationDetailsResponse( | ||
| qualification.getId(), | ||
| qualification.getSkill().getName() | ||
| ); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package pl.edu.agh.project_manager.controller.dto.approvals; | ||
|
|
||
|
|
||
| import java.util.UUID; | ||
|
|
||
| public record QualificationRequestResponse( | ||
| UUID userId, | ||
| String employeeName, | ||
| String employeeSurname, | ||
| int qualificationsCount | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package pl.edu.agh.project_manager.controller.dto.approvals; | ||
|
|
||
| public enum QualificationUpdateAction { | ||
| ACCEPT, | ||
| REJECT | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package pl.edu.agh.project_manager.controller.dto.approvals; | ||
|
|
||
| import jakarta.validation.constraints.NotNull; | ||
|
|
||
| import java.util.UUID; | ||
|
|
||
| public record QualificationUpdateRequest ( | ||
| @NotNull(message = "Identyfikator kwalifikacji jest wymagany") | ||
| UUID qualificationId, | ||
|
|
||
| @NotNull(message = "Akcja (ACCEPT/REJECT/EMPTY) jest wymagana") | ||
| QualificationUpdateAction action | ||
| ) { | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package pl.edu.agh.project_manager.security.access; | ||
|
|
||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Component; | ||
| import pl.edu.agh.project_manager.repository.user.UserRepository; | ||
|
|
||
| import java.util.UUID; | ||
|
|
||
| @Component("qualificationSecurity") | ||
| @RequiredArgsConstructor | ||
| public class QualificationSecurity { | ||
| private final UserRepository userRepository; | ||
|
|
||
| public boolean isManagerForUser(UUID managerId, UUID userId) { | ||
| return userRepository.findById(userId) | ||
| .map(user -> user.getSupervisor() != null && | ||
| user.getSupervisor().getId().equals(managerId)) | ||
| .orElse(false); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| package pl.edu.agh.project_manager.service.approval; | ||
|
|
||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
| import pl.edu.agh.project_manager.controller.dto.approvals.QualificationDetailsResponse; | ||
| import pl.edu.agh.project_manager.controller.dto.approvals.QualificationRequestResponse; | ||
| import pl.edu.agh.project_manager.controller.dto.approvals.QualificationUpdateAction; | ||
| import pl.edu.agh.project_manager.controller.dto.approvals.QualificationUpdateRequest; | ||
| import pl.edu.agh.project_manager.domain.entity.user.Qualification; | ||
| import pl.edu.agh.project_manager.domain.entity.user.Skill; | ||
| import pl.edu.agh.project_manager.domain.entity.user.User; | ||
| import pl.edu.agh.project_manager.domain.enums.QualificationStatus; | ||
| import pl.edu.agh.project_manager.domain.exception.ApiErrorCode; | ||
| import pl.edu.agh.project_manager.domain.exception.ApplicationException; | ||
| import pl.edu.agh.project_manager.repository.user.QualificationRepository; | ||
| import pl.edu.agh.project_manager.repository.user.UserRepository; | ||
|
|
||
| import java.util.List; | ||
| import java.util.UUID; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class QualificationManagementService { | ||
| private final QualificationRepository qualificationRepository; | ||
| private final UserRepository userRepository; | ||
|
|
||
| @Transactional(readOnly = true) | ||
| public List<QualificationRequestResponse> getRecordsForManager(UUID managerId) { | ||
| List<Qualification> qualifications = qualificationRepository.findAllByUserSupervisorIdAndStatus(managerId, QualificationStatus.WAITING); | ||
|
|
||
| return qualifications.stream() | ||
| .collect(Collectors.groupingBy(Qualification::getUser)) | ||
| .entrySet().stream() | ||
| .map(entry -> { | ||
| User user = entry.getKey(); | ||
| int count = entry.getValue().size(); | ||
|
|
||
| return new QualificationRequestResponse( | ||
| user.getId(), | ||
| user.getName(), | ||
| user.getSurname(), | ||
| count | ||
| ); | ||
| }) | ||
| .toList(); | ||
| } | ||
|
|
||
| @Transactional | ||
| public void updateRequests(UUID managerId, List<QualificationUpdateRequest> requests) { | ||
| var activeRequestsMap = requests.stream() | ||
| .collect(Collectors.toMap(QualificationUpdateRequest::qualificationId, r -> r)); | ||
|
|
||
| if (activeRequestsMap.isEmpty()) return; | ||
|
|
||
| List<UUID> ids = List.copyOf(activeRequestsMap.keySet()); | ||
|
|
||
| List<Qualification> allowedQualifications = qualificationRepository.findAllByIdInAndUserSupervisorId(ids, managerId); | ||
|
|
||
| if (allowedQualifications.size() != ids.size()) { | ||
| throw new ApplicationException(ApiErrorCode.QUALIFICATION_OWNER_NOT_SUBORDINATE); | ||
| } | ||
|
|
||
| for (Qualification qualification : allowedQualifications) { | ||
| validateWaitingStatus(qualification); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Jeżeli chcemy tytlko WAITING to można też odrazu z bazy pobrać tylko kwalifikacje z WAITING.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. z uwagi na to, że wyżej ma miejsce taka walidacja: if (allowedQualifications.size() != ids.size()) {
throw new ApplicationException(ApiErrorCode.QUALIFICATION_OWNER_NOT_SUBORDINATE);
}to rozmiary mogłyby się nie zgadzać m.in. przez wyciągnięcie samych rekordów ze statusem WAITING, chyba najprościej jest to rozwiązać wyciągając wszystkie i sprawdzać na dalszym etapie czy coś ma inny status niż waiting |
||
|
|
||
| var action = activeRequestsMap.get(qualification.getId()).action(); | ||
|
|
||
| switch (action) { | ||
| case QualificationUpdateAction.ACCEPT -> qualification.accept(); | ||
| case QualificationUpdateAction.REJECT -> qualification.reject(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Transactional(readOnly = true) | ||
| public List<QualificationDetailsResponse> getPendingForUser(UUID userId) { | ||
| if (!userRepository.existsById(userId)) { | ||
| throw new ApplicationException(ApiErrorCode.USER_NOT_FOUND); | ||
| } | ||
|
|
||
| return qualificationRepository.findAllByUserIdAndStatus(userId, QualificationStatus.WAITING) | ||
| .stream() | ||
| .map(QualificationDetailsResponse::from) | ||
| .toList(); | ||
| } | ||
|
|
||
| private void validateWaitingStatus(Qualification qualification) { | ||
| if (qualification.getStatus() != QualificationStatus.WAITING) { | ||
| throw new ApplicationException(ApiErrorCode.INVALID_QUALIFICATION_STATE); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| interface TablePageShellProps { | ||
| title: string; | ||
| description?: string; | ||
| children: React.ReactNode; | ||
| isLoading?: boolean; | ||
| } | ||
|
|
||
| export const TablePageShell = ({ title, description, children, isLoading }: TablePageShellProps) => { | ||
| return ( | ||
| <div className="flex-1 flex flex-col space-y-6 w-full"> | ||
| <div> | ||
| <h1 className="text-2xl font-bold tracking-tight">{title}</h1> | ||
| {description && <p className="text-muted-foreground text-sm">{description}</p>} | ||
| </div> | ||
|
|
||
| <div className="flex-1 flex flex-col rounded-md border bg-white shadow-sm"> | ||
| {isLoading ? ( | ||
| <div className="flex flex-1 items-center justify-center min-h-[400px]"> | ||
| <p className="text-sm animate-pulse text-muted-foreground">Ładowanie...</p> | ||
| </div> | ||
| ) : ( | ||
| children | ||
| )} | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nie powinniśmy rzucać ACCESS_DENIED w sekcji z logiką biznesową, a raczej taki rodzaj błędu tutaj niezbyt pasuje.