반응형

연차신청 기능
연차 조회기능

 

현재의 데이터베이스는 직원의 연차에 대해 저장하는 컬럼이 아무데도 없으므로 데이터베이스 부터 수정해주었다.

서비스 테이블구조

직원테이블에 직원이 몇개의 연차를 가지고있는지를 저장하기위해서 연차 컬럼을 추가해주었고.

신청한연차를 기록하기위해서 연차기록테이블을 만들어주었다. 

올해입사한 직원은 11일 그외에는 15일 이라는 규정을 상수로 만들까하다가 변경이 생기는경우

데이터베이스에서 모두 처리해주기 위해서 정책테이블을 만들어주었다. 

연차에대한 규정은 annualLeave(정책구분) - newEmployee , experienced (정책이름) 으로 조회해서 정책내용으로 받아올수있다. 나름 트리 구조나 카테고리같은 구조를 생각하고 만들었는데 효용성이 있는지는 잘 모르겠다.

나중에 정책구분도 따로 테이블이 생긴다면 외래키로 조회해서

연차관련 정책을 한번에 받아온다던가 하는데에 쓰일것같아서 만들어봤다.

 

연차세팅

우선은 직원들의 연차를 초기화 시킬수있는 기능을 만들었다.

 

Controller

@RestController
@RequiredArgsConstructor
@RequestMapping("/annual-leave")
public class AnnualLeaveController {

    private final AnnualLeaveService service;

    @PutMapping("/set-annual-leave")
    public void setAnnualLeave(){
        service.setAnnualLeave();
    }
}

 

Service

@Service
@RequiredArgsConstructor
public class AnnualLeaveService {
    private final MemberRepository memberRepository;
    private final CompanyPolicyRepository policyRepository;

    @Transactional
    public void setAnnualLeave() {
        int newEmployee = getAnnualLeavePolicy("newEmployee");
        int experienced = getAnnualLeavePolicy("experienced");
        memberRepository.findAll().stream().forEach(employee -> {
            //올해
            int thisYear = LocalDate.now().getYear();
            //직원의 입사연도정보
            int employeeStartYear = employee.getStartDate().getYear();
            if(employeeStartYear == thisYear){
                employee.setAnnualLeave(newEmployee);
            }else{
                employee.setAnnualLeave(experienced);
            }
        });
    }

    private int getAnnualLeavePolicy(String employeeStatus){
        String policyGubn = "annualLeave";
        return Integer.parseInt(policyRepository.findByPolicyGubnAndPolicyName(policyGubn,employeeStatus)
                .orElseThrow(()->new IllegalArgumentException("없는 정책입니다."))
                .getPolicyContent());
    }
}

Repository

public interface MemberRepository extends JpaRepository<Employee,Long> {
    Employee findByName(String name);
}
public interface CompanyPolicyRepository extends JpaRepository<CompanyPolicy,Long> {
    Optional<CompanyPolicy> findByPolicyGubnAndPolicyName(String policyGubn, String policyName);
}

 

이미 만들어서사용했던 MemberRepository를 사용해서 직원데이터를 조회하는데 사용하고

정책 테이블이 새로생겨서 정책을 조회할수있는 CompanyPolicyRepository를 만들어주었다. 

그리고 정책내용을 정책구분과,정책이름으로 조회할수있게 추상메서드를 만들어줬다.

 

해당 컨트롤러로 요청을보내면 직원데이터의 입사일을 보고 신입은 11일 그외에는 15일로 세팅이된다.

 

연차신청 

Controller

@RestController
@RequiredArgsConstructor
@RequestMapping("/annual-leave")
public class AnnualLeaveController {

    private final AnnualLeaveService service;

    @PutMapping("/set-annual-leave")
    public void setAnnualLeave(){
        service.setAnnualLeave();
    }

    @PostMapping("/register")
    public void registerAL(@Valid @RequestBody ALRequest request) {
        service.registerAl(request);
    }
}

DTO

@Data
public class ALRequest {
    private Long employeeId;
    @FutureOrPresent // 현재거나이후의 시간이어야한다.
    private LocalDate startDate;
    @FutureOrPresent
    private LocalDate endDate;

    @AssertTrue(message = "endDate는 startDate보다 이후여야 합니다.") // endDate는 startDate의 이어야한다.
    public boolean isDateCheck(){
        if(startDate.isBefore(endDate)){
            return true;
        }
        return false;
    }
}

 

DTO에는 검증을하기위해서 어노테이션과 검증용 메서드를 만들었다.

이걸로 파라미터로 들어오는 값들을 간단하게 검증하고 해당조건을 만족하지못하는경우 예외를 발생시킨다.

Service

@Service
@RequiredArgsConstructor
public class AnnualLeaveService {
    private final MemberRepository memberRepository;
    private final AnnualLeaveRepository annualLeaveRepository;
    private final CompanyPolicyRepository policyRepository;

    @Transactional
    public void registerAl(ALRequest request) {
        LocalDate startDate = request.getStartDate();
        LocalDate endDate = request.getEndDate();
        Employee employee = memberRepository.findById(request.getEmployeeId())
                .orElseThrow(() -> new IllegalArgumentException("직원 정보가없습니다. "));
        //팀 휴가 사전보고일을 조회해서 사전보고일보다 늦게 보고시 에러처리
        Integer annualLeaveBefore = employee.getTeam().getAnnualLeaveBefore();
        long between = ChronoUnit.DAYS.between(LocalDate.now(), startDate);
        if (between < 0 || between < annualLeaveBefore) {
            throw new IllegalArgumentException(String.format("휴가 신청이 불가능합니다. %d 일 전에 팀에 보고해야 합니다.", annualLeaveBefore));
        }
        //연차를 신청한기간에 이미 연차등록이있으면 예외처리
        List<AnnualLeaveRegister> annualLeaveRegisters = employee.getAnnualLeaveRegisters();
        annualLeaveRegisters.stream().forEach(it -> {
            LocalDate empStartDate = it.getStartDate();
            LocalDate empEndDate = it.getEndDate();
            if (!(endDate.isBefore(empStartDate) || startDate.isAfter(empEndDate))) {
                throw new IllegalArgumentException("신청한 휴가 기간이 이미 신청된 휴가 기간과 겹칩니다.");
            }
        });
        //연차 등록정보를 등록
        annualLeaveRepository.save(new AnnualLeaveRegister(employee, startDate, endDate));
        //연차를사용한 직원의 연차를 차감.
        int annualLeaveDays = (int) ChronoUnit.DAYS.between(startDate, endDate) + 1;
        //연차 신청기간이 직원의 잔여 연차보다 크면 예외처리
        if (annualLeaveDays > employee.getAnnualLeave()) {
            throw new IllegalArgumentException(
                    String.format("휴가 신청이 불가능합니다. 남은 연차 (%d) 신청한연차기간 (%d)", employee.getAnnualLeave(),
                            annualLeaveDays));
        }
        employee.useAL(annualLeaveDays);
    }

    @Transactional
    public void setAnnualLeave() {
        int newEmployee = getAnnualLeavePolicy("newEmployee");
        int experienced = getAnnualLeavePolicy("experienced");
        memberRepository.findAll().stream().forEach(employee -> {
            //올해
            int thisYear = LocalDate.now().getYear();
            //직원의 입사연도정보
            int employeeStartYear = employee.getStartDate().getYear();
            if (employeeStartYear == thisYear) {
                employee.setAnnualLeave(newEmployee);
            } else {
                employee.setAnnualLeave(experienced);
            }
        });
    }

    private int getAnnualLeavePolicy(String employeeStatus) {
        String policyGubn = "annualLeave";
        return Integer.parseInt(policyRepository.findByPolicyGubnAndPolicyName(policyGubn, employeeStatus)
                .orElseThrow(() -> new IllegalArgumentException("없는 정책입니다."))
                .getPolicyContent());
    }
}

 

코드가 길어져서 분리할까 했는데 우선은 포스팅용으로 한번에 볼수있게 두었다.

정리하면 이렇게 될것같다.

@Transactional
public void registerAl(ALRequest request) {
    LocalDate startDate = request.getStartDate();
    LocalDate endDate = request.getEndDate();
    Employee employee = memberRepository.findById(request.getEmployeeId())
            .orElseThrow(() -> new IllegalArgumentException("직원 정보가없습니다. "));
    //팀 휴가 사전보고일을 조회해서 사전보고일보다 늦게 보고시 에러처리
    validateExceedAdvancedReportDate(employee, startDate);
    //연차를 신청한기간에 이미 연차등록이있으면 예외처리
    validateALRedundant(employee, endDate, startDate);
    //연차 등록정보를 등록
    annualLeaveRepository.save(new AnnualLeaveRegister(employee, startDate, endDate));
    int annualLeaveDays = (int) ChronoUnit.DAYS.between(startDate, endDate) + 1;
    //연차 신청기간이 직원의 잔여 연차보다 크면 예외처리
    validateEmployeeRemainAL(annualLeaveDays, employee);
    //연차를사용한 직원의 연차를 차감.
    employee.useAL(annualLeaveDays);
}

 

저런 검증들도 객체를 분리하고 최대한 파라미터를 통일해서 하나의 메서드에서 처리할수있도록 나중에는 수정해야지..

 

이렇게 해주면 예외로두었던 케이스들을 커버할수있다.

코딩 전 미리 작성한 readme

예외 발생시 리턴해주는 객체도 처리해주었던데로 잘 리턴된다 

스프링부트 예외처리

 

스프링부트 예외처리 - @RestControllerAdvice

@RestControllerAdvice 스프링부트에서 @RestController를 이용해서 api를 개발하는경우에 예외처리시 이용한다. 예외를 처리하고 응답객체를 리턴해줄수있다. api응답으로 에러객체를만들어서 보내줄수있

colazoa.tistory.com

 

연차조회

연차를 조회하는 기능은 간단하게 구현했다. 테이블을 수정하면서 직원객체가 휴가의 일수를 가지고있기때문에

직원을 조회해서 가지고있는 휴가를 리턴해주면된다.

 

//Controller
@GetMapping
public ResponseAnnualLeave getAnnualLeave(Integer employeeId) {
    return service.getAnnualLeave(employeeId);
}
//DTO
@Data
@AllArgsConstructor
public class ResponseAnnualLeave {
    private Integer annualLeave;
}

//Service
 @Transactional(readOnly = true)
 public ResponseAnnualLeave getAnnualLeave(Integer employeeId) {
     Employee employee = memberRepository.findById(Long.valueOf(employeeId))
             .orElseThrow(() -> new IllegalArgumentException("직원 정보가없습니다. "));
     return new ResponseAnnualLeave(employee.getAnnualLeave());
 }

응답을 그냥 Integer로 받아도 괜찮지만 나중에 요구사항이 추가되어서 추가로 필드가 더 필요해질수도있고 

응답이나 요청을 스네이크 케이스로 변경해달라고하거나 할때

@JsonNaming어노테이션으로 유연하게 대처할수있을것같아서 응답객체를 만들어서 사용해주었다.

 

연차를 신청하고 조회할수있는 기능을 api에 추가해주었다. 

연차정보가 생기면서 다른 기능의 요구사항에 변경이 생겼는데 이 기능은 다음 포스팅에서 정리해보겠다.

 

 

반응형

+ Recent posts