코딩 노트

그룹웨어 - 근태관리 테이블 및 CRU 생성, 백엔드 + 프론트엔드 본문

파이널프로젝트

그룹웨어 - 근태관리 테이블 및 CRU 생성, 백엔드 + 프론트엔드

newbyeol 2023. 11. 8. 10:32

그룹웨어

우리 프로젝트는 모두 리엑트로 진행된다.

 

내가 맡은 테이블은 근태 관리 테이블이다.

empNo를 외래키로 사용한다.

근태

- 출, 퇴근시 버튼을 클릭해서 버튼 누른 시간을 입력 후 기록한다.

- 근무 상태(정상근무, 휴무일근무, 휴무, 지각, 조퇴, 결근,...)이 나온다.

- 출근 시간이 9시 이후면 지각, 퇴근 시간이 6시 이전이면 조퇴, 출근과 퇴근 시간이 null이면 결근, 주말이면 휴무, 주말에 출퇴근 기록이 있으면 휴무일근무, 출근 시간이 9시 이전, 퇴근 시간이 6시 이후면 정상근무

- CRU만 있으면 된다. 삭제는 필요 없다.

 

데이터베이스 구문

날짜를 숫자로 표시하는 코드

select to_char(to_date('2023-10-19','yyyy-mm-dd'),'D') from dual;

 

 

근태

-- 근태 시퀀스
create sequence attend_seq;

-- 근태 테이블
-- 출근은 insert, 퇴근은 update
create table attend (
emp_no REFERENCES emp(emp_no) ON DELETE CASCADE,
attend_no number primary key,
attend_start DATE DEFAULT null,
attend_end DATE DEFAULT null
);

 

출근(등록)

-- 출근은 insert
insert into attend(emp_no, ATTEND_NO, ATTEND_START) values(11, attend_seq.nextval, sysdate);

 

퇴근(수정)

-- 퇴근은 update
UPDATE attend
SET attend_end = SYSDATE -- 현재 날짜로 퇴근 시간 업데이트
WHERE emp_no = 11 -- 11번 사원의 사원 번호
  AND TRUNC(attend_start) = TRUNC(SYSDATE);

 

-- 출근전 : attend_start_time과 attend_end_time이 모두 null이다
-- 출근중 : attend_start_time은 있는데 attend_end_time이 null이다
-- 퇴근완료 : 둘다 있는 경우

select 
    attend_no, emp_no, attend_start, attend_end,
    (attend_end - attend_start) * 24 * 60 "근무시간",
    case 
        when attend_start is null and attend_end is null then 'N'
        when attend_end is null then 'R'
        else 'Y'
    end "근무상태"
from attend;

 

특정 기간에 대한 조회구문(날짜 구간 생성)

select 
    trunc(to_date('2023-11-05', 'YYYY-MM-DD') + (LEVEL-1)) AS DT
from dual
connect by 
    LEVEL <= to_date('2023-11-12', 'YYYY-MM-DD') 
        - to_date('2023-11-05', 'YYYY-MM-DD') + 1;

 

월별로 조회할 수 있는 검색 구문(해당 일 근무시간이 나와야 하고 emp_no로 조회)

select * from (
    select * from attend where attend.emp_no = 11
) ATT
right outer join (
    select 
        trunc(to_date('2023-10-01', 'YYYY-MM-DD') + (LEVEL-1)) AS DT
    from dual
    connect by 
        LEVEL <= to_date('2023-10-31', 'YYYY-MM-DD') 
            - to_date('2023-10-01', 'YYYY-MM-DD') + 1
)TMP on to_char(TMP.DT, 'YYYY-MM-DD') = to_char(ATT.attend_start, 'YYYY-MM-DD')
order by TMP.DT asc;

 

근무시간만 추가돼서 나오는 구문

select 
    attend_no, emp_no, attend_start, attend_end,
    (attend_end - attend_start) * 24 "근무시간"
from attend;

 

지정한 기간으로 날짜가 생성되고 근무시간이 추가돼서 나오는 구문(조회 완성 구문)

SELECT
    TMP.DT,
    ATT.attend_no,
    ATT.emp_no,
    ATT.attend_start,
    ATT.attend_end,
    NVL((ATT.attend_end - ATT.attend_start) * 24, 0) AS "working_times"
FROM (
    SELECT
        trunc(to_date('2023-10-01', 'YYYY-MM-DD') + (LEVEL - 1)) AS DT
    FROM dual
    CONNECT BY LEVEL <= to_date('2023-10-31', 'YYYY-MM-DD') - to_date('2023-10-01', 'YYYY-MM-DD') + 1
) TMP
LEFT JOIN (
    SELECT *
    FROM attend
    WHERE emp_no = 11
) ATT ON to_char(TMP.DT, 'YYYY-MM-DD') = to_char(ATT.attend_start, 'YYYY-MM-DD')
ORDER BY TMP.DT desc;

 

출퇴근 더미데이터(주말구분x)

----- 11번 사원 2023년 9월 한달간 더미 데이터
INSERT INTO attend(emp_no, attend_no, attend_start, attend_end)
SELECT 11, attend_seq.nextval,
       TO_DATE('2023-09-' || LPAD(level, 2, '0') || ' 09:00:00', 'YYYY-MM-DD HH24:MI:SS'),
       TO_DATE('2023-09-' || LPAD(level, 2, '0') || ' 18:00:00', 'YYYY-MM-DD HH24:MI:SS')
FROM dual
CONNECT BY level <= 30;
----- 11번 사원 2023년 10월 한달간 더미 데이터
INSERT INTO attend(emp_no, attend_no, attend_start, attend_end)
SELECT 11, attend_seq.nextval,
       TO_DATE('2023-10-' || LPAD(level, 2, '0') || ' 09:00:00', 'YYYY-MM-DD HH24:MI:SS'),
       TO_DATE('2023-10-' || LPAD(level, 2, '0') || ' 18:00:00', 'YYYY-MM-DD HH24:MI:SS')
FROM dual
CONNECT BY level <= 31;
----- 11번 사원 2023년 12월 한달간 더미 데이터
INSERT INTO attend(emp_no, attend_no, attend_start, attend_end)
SELECT 107, attend_seq.nextval,
       TO_DATE('2023-12-' || LPAD(level, 2, '0') || ' 09:00:00', 'YYYY-MM-DD HH24:MI:SS'),
       TO_DATE('2023-12-' || LPAD(level, 2, '0') || ' 18:00:00', 'YYYY-MM-DD HH24:MI:SS')
FROM dual
CONNECT BY level <= 31;
;

 

시퀀스 번호 생성 구문

-- 시퀀스 번호 생성 구문
select attend_seq.nextval from dual;

 

emp_no로 조회하는 구문

-- emp_no를 이용하여 오늘자 출근 내역을 불러오는 명령   
select * from attend where emp_no = 107 and trunc(attend_start) = trunc(sysdate)
    
-- emp_no로 모든일자 근태내역 조회    
select * from attend where emp_no = 107;

 

사용자가 년, 월을 선택(입력)하여 근무내역 조회 (1일부터 말일까지)

-- 사용자가 년, 월을 선택(입력)하여 근무내역 조회 (1일부터 말일까지)
SELECT
    TO_CHAR(TMP.DT, 'YYYY-MM-DD') AS dt,
    ATT.attend_no,
    ATT.emp_no,
    ATT.attend_start,
    ATT.attend_end,
    NVL((ATT.attend_end - ATT.attend_start) * 24, 0) AS working_times,
    CASE
        WHEN TO_NUMBER(TO_CHAR(ATT.attend_start, 'HH24')) > 9 THEN '지각'
        WHEN TO_NUMBER(TO_CHAR(ATT.attend_end, 'HH24')) < 18 THEN '조퇴'
        WHEN TO_CHAR(TMP.DT, 'Dy', 'NLS_DATE_LANGUAGE=American') IN ('Sun', 'Sat') THEN 
            CASE WHEN EXISTS (
                SELECT 1 
                FROM attend
                WHERE TO_CHAR(attend_start, 'YYYY-MM-DD') = TO_CHAR(TMP.DT, 'YYYY-MM-DD')
                  AND TO_CHAR(attend_end, 'YYYY-MM-DD') = TO_CHAR(TMP.DT, 'YYYY-MM-DD')
                  AND emp_no = 32
            ) THEN '휴무일근무' ELSE '휴무' END
        WHEN ATT.attend_start IS NULL AND ATT.attend_end IS NULL THEN '결근'
        ELSE '정상근무'
    END AS attend_status
FROM (
    SELECT
        TRUNC(TO_DATE('2023-11', 'YYYY-MM'), 'MM') + (LEVEL - 1) AS DT
    FROM dual
    CONNECT BY
        LEVEL <= LAST_DAY(TO_DATE('2023-11', 'YYYY-MM'))
                  - TRUNC(TO_DATE('2023-11', 'YYYY-MM')) + 1
) TMP
LEFT JOIN (
    SELECT *
    FROM attend
    WHERE TO_CHAR(attend_start, 'YYYY-MM') = '2023-11'
      AND emp_no = 32
) ATT ON TO_CHAR(TMP.DT, 'YYYY-MM-DD') = TO_CHAR(ATT.attend_start, 'YYYY-MM-DD')
ORDER BY TMP.DT DESC;

 

사용자가 년, 월을 선택(입력)하여 근무내역 조회(1일부터 오늘 일자까지)

-- 사용자가 년, 월을 선택(입력)하여 근무내역 조회 (1일부터 오늘 일자까지)
SELECT
    TO_CHAR(TMP.DT, 'YYYY-MM-DD') AS dt,
    ATT.attend_no,
    ATT.emp_no,
    ATT.attend_start,
    ATT.attend_end,
    NVL((ATT.attend_end - ATT.attend_start) * 24, 0) AS working_times,
    CASE
        WHEN TO_NUMBER(TO_CHAR(ATT.attend_start, 'HH24')) > 9 THEN '지각'
        WHEN TO_NUMBER(TO_CHAR(ATT.attend_end, 'HH24')) < 18 THEN '조퇴'
        WHEN TO_CHAR(TMP.DT, 'Dy', 'NLS_DATE_LANGUAGE=American') IN ('Sun', 'Sat') THEN 
            CASE WHEN EXISTS (
                SELECT 1 
                FROM attend
                WHERE TO_CHAR(attend_start, 'YYYY-MM-DD') = TO_CHAR(TMP.DT, 'YYYY-MM-DD')
                  AND TO_CHAR(attend_end, 'YYYY-MM-DD') = TO_CHAR(TMP.DT, 'YYYY-MM-DD')
                  AND emp_no = 32
            ) THEN '휴무일근무' ELSE '휴무' END
        WHEN ATT.attend_start IS NULL AND ATT.attend_end IS NULL THEN '결근'
        ELSE '정상근무'
    END AS attend_status
FROM (
    SELECT
        TRUNC(TO_DATE('2023-11', 'YYYY-MM'), 'MM') + (LEVEL - 1) AS DT
    FROM dual
    CONNECT BY LEVEL <= 
        CASE 
            WHEN TO_CHAR(TO_DATE('2023-11', 'YYYY-MM'), 'YYYY-MM') = TO_CHAR(SYSDATE, 'YYYY-MM') 
                THEN TO_NUMBER(TO_CHAR(SYSDATE, 'DD'))
            ELSE TO_NUMBER(TO_CHAR(LAST_DAY(TO_DATE('2023-11', 'YYYY-MM')), 'DD'))
        END
) TMP
LEFT JOIN (
    SELECT *
    FROM attend
    WHERE TO_CHAR(attend_start, 'YYYY-MM') = '2023-11'
      AND emp_no = 32
) ATT ON TO_CHAR(TMP.DT, 'YYYY-MM-DD') = TO_CHAR(ATT.attend_start, 'YYYY-MM-DD')
ORDER BY TMP.DT DESC;

 

sysdate로 이번 달 오늘 일자까지 근무내역 조회

-- sysdate로 이번 달 오늘 일자까지 근무내역 조회
SELECT
    TO_CHAR(TMP.DT, 'YYYY-MM-DD') AS dt,
    ATT.attend_no,
    ATT.emp_no,
    ATT.attend_start,
    ATT.attend_end,
    NVL((ATT.attend_end - ATT.attend_start) * 24, 0) AS working_times,
    CASE
        WHEN TO_NUMBER(TO_CHAR(ATT.attend_start, 'HH24')) > 9 THEN '지각'
        WHEN TO_NUMBER(TO_CHAR(ATT.attend_end, 'HH24')) < 18 THEN '조퇴'
        WHEN TO_CHAR(TMP.DT, 'Dy', 'NLS_DATE_LANGUAGE=American') IN ('Sun', 'Sat') THEN 
            CASE WHEN EXISTS (
                SELECT 1 
                FROM attend
                WHERE TO_CHAR(attend_start, 'YYYY-MM-DD') = TO_CHAR(TMP.DT, 'YYYY-MM-DD')
                  AND TO_CHAR(attend_end, 'YYYY-MM-DD') = TO_CHAR(TMP.DT, 'YYYY-MM-DD')
                  AND emp_no = 32
            ) THEN '휴무일근무' ELSE '휴무' END
        WHEN ATT.attend_start IS NULL AND ATT.attend_end IS NULL THEN '결근'
        ELSE '정상근무'
    END AS attend_status
FROM (
    SELECT
        TRUNC(SYSDATE, 'MM') + (LEVEL - 1) AS DT
    FROM dual
    CONNECT BY
        LEVEL <= TO_NUMBER(TO_CHAR(SYSDATE, 'DD'))
) TMP
LEFT JOIN (
    SELECT *
    FROM attend
    WHERE TO_CHAR(attend_start, 'YYYY-MM') = TO_CHAR(SYSDATE, 'YYYY-MM')
      AND emp_no = 32
) ATT ON TO_CHAR(TMP.DT, 'YYYY-MM-DD') = TO_CHAR(ATT.attend_start, 'YYYY-MM-DD')
ORDER BY TMP.DT DESC;

 

백엔드 부분

AttendDto 클래스를 생성한다.

import lombok.AllArgsConstructor;

import lombok.Builder;

import lombok.Data;

import lombok.NoArgsConstructor;

 

@Data @Builder @AllArgsConstructor @NoArgsConstructor

public class AttendDto {

private int empNo; //사원번호 외래키

private int attendNo; //근태번호 프키

private String attendStart; //출근시간

private String attendEnd; //퇴근시간

}

 

 

 

attned-mapper.xml 구문

<?xml version="1.0" encoding="UTF-8"?>

 

<!DOCTYPE mapper

PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

"https://mybatis.org/dtd/mybatis-3-mapper.dtd">

 

<mapper namespace="attend">

 

<!-- C 등록 -->

<!-- 출근 버튼 눌렀을 때 -->

<insert id="save">

insert into attend(emp_no, ATTEND_NO, ATTEND_START) values(#{empNo}, attend_seq.nextval, sysdate)

</insert>

 

<!-- U 수정 -->

<!-- 퇴근 버튼 눌렀을 때 -->

<update id="update">

update attend set attend_end = sysdate where emp_no = #{empNo}

</update>

 

<!-- R 목록, 상세 -->

<!-- 출근전, 출근중, 퇴근완료 -->

 

<!-- 사번별로 근태 조회 -->

 

</mapper>

AttendDao 구문

public interface AttendDao {

//사번으로 근태목록 조회

//출근(등록)

void insert(AttendDto attendDto);

//퇴근(수정)

void update(int attendNo, AttendDto attendDto);

}

AttendDaoImpl 구문

@Repository

public class AttendDaoImpl implements AttendDao {

 

@Autowired SqlSession sqlSession;

 

//출근

@Override

public void insert(AttendDto attendDto) {

sqlSession.insert("attend.save", attendDto);

}

 

//퇴근

@Override

public void update(int empNo, AttendDto attendDto) {

Map<String, Object> param = Map.of("empNo", empNo, "attendDto", attendDto);

sqlSession.update("attend.update", param);

}

 

}

AttendRestController 구문

@Slf4j

@Tag(name="근태 관리", description = "근태 관리를 위한 컨트롤러")

@CrossOrigin

@RestController

@RequestMapping("/attend")

public class AttendRestController {

 

@Autowired

private AttendDao attendDao;

 

//출근시간 찍히기

@PostMapping("/") //등록

public void insert(@RequestBody AttendDto attendDto) {

attendDao.insert(attendDto);

}

 

//퇴근시간 찍히기

// @PutMapping - 전체 수정

@PatchMapping("/{empNo}") // 일부 수정

public void update(@RequestBody AttendDto attendDto, @PathVariable int empNo) {

attendDao.update(empNo, attendDto);

}

}

사원번호로 조회를 할 때 dt도 나오고 조회는 되지만 모든 데이터가 null로 받아지는 오류가 발생했다.

AttendWorkingTimesVO 

@NoArgsConstructor @AllArgsConstructor @Builder @Data

public class AttendWorkingTimesVO {

private int attendNo; //근태번호

private int empNo; //사번 번호 검색할 때 필요한 외래키

private Date dt, attendBeginDate, attendEndDate;

private Date attendStart, attendEnd;

private int workingTimes; //근무시간

}

AttendDao 

//한 달 간격으로, 모두 나올 필요가 없음 애초에 가장 최근부터 한 달만 보이게, 날짜 선택이 가능하게

List<AttendWorkingTimesVO>selectListByEmpNo(AttendWorkingTimesVO VO);

AttendDaoImpl

@Override

public List<AttendWorkingTimesVO> selectListByEmpNo(AttendWorkingTimesVO VO) {

return sqlSession.selectList("attend.selectListByEmpNo",VO);

}

AttendRestController

// @GetMapping - 조회

@PostMapping("/find/")

public List<AttendWorkingTimesVO> find(@RequestBody AttendWorkingTimesVO VO){

log.debug("vo = {}",VO);

return attendDao.selectListByEmpNo(VO);

 

}

attend-mapper.xml

<!-- R 목록, 상세 -->

<!-- 사번별로 지정한 기간으로 날짜가 생성되고, 근무시간이 추가돼서 나오는 구문 -->

<select id="selectListByEmpNo" resultType="AttendWorkingTimesVO">

select

tmp.dt,

att.attend_no,

att.emp_no,

att.attend_start,

att.attend_end,

nvl((att.attend_end - att.attend_start) * 24, 0) as working_times

from (

select

trunc(to_date(#{attendBeginDate}, 'YYYY-MM-DD') + (level - 1)) as dt

from dual

<![CDATA[

connect by level

<= to_date(#{attendEndDate}, 'YYYY-MM-DD') - to_date(#{attendBeginDate}, 'YYYY-MM-DD') + 1

]]>

) tmp

left join (

select *

from attend

where emp_no = #{empNo}

) att on to_char(tmp.dt, 'YYYY-MM-DD') = to_char(att.attend_start, 'YYYY-MM-DD')

order by tmp.dt desc

</select>

이렇게 코드를 썼을 때의 문제점이 있다.

1. VO를 만들 때 조회할 때 쓸 데이터와 복합 검색할 때 쓸 데이터(검색어 입력하는 데이터?)를 같이 VO에 쓰면 안 된다.

 

2. attendBeginDate와 attendEndDate를 Date 타입으로 하였지만 사용자가 입력하는 것이기 때문에 String 타입으로 설정하여야 한다.

 

문제점을 찾기 위해 진행했던 테스트 구문

AttendWorkingTimesTest 파일 생성

@Slf4j

@SpringBootTest

public class AttendWorkingTimesTest {

 

@Autowired

private SqlSession sqlSession;

 

@Test

public void test() {

// Map<String, Object> params = new HashMap<>();

// params.put("empNo", 11);

// params.put("attendBeginDate", "2023-10-01");

// params.put("attendEndDate", "2023-10-31");

// List<AttendWorkingTimesVO> list = sqlSession.selectList("attend.selectListByEmpNo", params);

 

AttendWorkingSearchVO awtvo = AttendWorkingSearchVO.builder()

.empNo(11)

.attendBeginDate("2023-10-01")

.attendEndDate("2023-10-31")

.build();

List<AttendWorkingTimesVO> list = sqlSession.selectList("attend.selectListByEmpNo", awtvo);

log.debug("list size = {}", list.size());

for(AttendWorkingTimesVO vo : list) {

log.debug("vo = {}", vo);

}

}

 

}

attend-mappet.xml 구문 추가

<!-- R 목록, 상세 -->

<!-- 사번별로 지정한 기간으로 날짜가 생성되고, 근무시간이 추가돼서 나오는 구문 -->

<resultMap type="AttendWorkingTimesVO" id="awtVO">

<result column="dt" property="dt"/>

<result column="attend_no" property="attendNo"/>

<result column="emp_no" property="empNo"/>

<result column="attend_start" property="attendStart"/>

<result column="attend_end" property="attendEnd"/>

<result column="working_times" property="workingTimes"/>

</resultMap>

<select id="selectListByEmpNo" resultMap="awtVO">

SELECT

TMP.DT,

ATT.attend_no,

ATT.emp_no,

ATT.attend_start,

ATT.attend_end,

NVL((ATT.attend_end - ATT.attend_start) * 24, 0) AS "working_times"

FROM (

SELECT

trunc(to_date(#{attendBeginDate}, 'YYYY-MM-DD') + (LEVEL - 1)) AS DT

FROM dual

<![CDATA[

CONNECT BY LEVEL <= to_date(#{attendEndDate}, 'YYYY-MM-DD') - to_date(#{attendBeginDate}, 'YYYY-MM-DD') + 1

]]>

) TMP

LEFT JOIN (

SELECT *

FROM attend

WHERE emp_no = #{empNo}

) ATT ON to_char(TMP.DT, 'YYYY-MM-DD') = to_char(ATT.attend_start, 'YYYY-MM-DD')

ORDER BY TMP.DT desc

</select>

문제점 수정 후 완성된 조회 구문

AttendWorkingTimesVO 수정

@NoArgsConstructor @AllArgsConstructor @Builder @Data

public class AttendWorkingTimesVO {

private int attendNo; //근태번호

private int empNo; //사번 번호 검색할 때 필요한 외래키

private Date dt;

private Date attendStart, attendEnd;

private int workingTimes; //근무시간

}

 

AttendWorkingSearchVO 생성

@Data @Builder @NoArgsConstructor @AllArgsConstructor

public class AttendWorkingSearchVO {

private int empNo;

private String attendBeginDate, attendEndDate; //사용자가 검색할 날짜

}

AttendDao 수정

public interface AttendDao {

//사번으로 근태목록 조회

//출근(등록)

void insert(AttendDto attendDto);

//퇴근(수정)

void update(int attendNo, AttendDto attendDto);

//한 달 간격으로, 모두 나올 필요가 없음 애초에 가장 최근부터 한 달만 보이게, 날짜 선택이 가능하게

List<AttendWorkingTimesVO>selectListByEmpNo(AttendWorkingSearchVO VO); //수정

}

AttendDaoImpl 수정

@Override

public List<AttendWorkingTimesVO> selectListByEmpNo(AttendWorkingSearchVO VO) {

return sqlSession.selectList("attend.selectListByEmpNo",VO);

}

attend-mapper.xml 수정 

 

<!-- R 목록, 상세 -->

<!-- 사번별로 지정한 기간으로 날짜가 생성되고, 근무시간이 추가돼서 나오는 구문 -->

<select id="selectListByEmpNo" resultType="AttendWorkingTimesVO">

SELECT

TMP.DT,

ATT.attend_no,

ATT.emp_no,

ATT.attend_start,

ATT.attend_end,

NVL((ATT.attend_end - ATT.attend_start) * 24, 0) AS "working_times"

FROM (

SELECT

trunc(to_date(#{attendBeginDate}, 'YYYY-MM-DD') + (LEVEL - 1)) AS DT

FROM dual

<![CDATA[

CONNECT BY LEVEL <= to_date(#{attendEndDate}, 'YYYY-MM-DD') - to_date(#{attendBeginDate}, 'YYYY-MM-DD') + 1

]]>

) TMP

LEFT JOIN (

SELECT *

FROM attend

WHERE emp_no = #{empNo}

) ATT ON to_char(TMP.DT, 'YYYY-MM-DD') = to_char(ATT.attend_start, 'YYYY-MM-DD')

ORDER BY TMP.DT desc

</select>

AttendRestController 수정

// @GetMapping - 조회

@PostMapping("/find/")

public List<AttendWorkingTimesVO> find(@RequestBody AttendWorkingSearchVO VO){

List<AttendWorkingTimesVO> list = attendDao.selectListByEmpNo(VO);

return list;

}

근태관리 백엔드 총 완성 구문

AttendDto

@Data @Builder @AllArgsConstructor @NoArgsConstructor

public class AttendDto {

private int empNo; //사원번호 외래키

private int attendNo; //근태번호 프키

private Date attendStart; //출근시간

private Date attendEnd; //퇴근시간

}

AttendWorkingTimesVO

@NoArgsConstructor @AllArgsConstructor @Builder @Data

public class AttendWorkingTimesVO {

private int attendNo; //근태번호

private int empNo; //사번 번호 검색할 때 필요한 외래키

private Date dt;

private Date attendStart, attendEnd;

private int workingTimes; //근무시간

}

AttendWorkingSearchVO

@Data @Builder @NoArgsConstructor @AllArgsConstructor

public class AttendWorkingSearchVO {

private int empNo;

private String attendBeginDate, attendEndDate; //사용자가 검색할 날짜

}

attend-mapper.xml

<?xml version="1.0" encoding="UTF-8"?>

 

<!DOCTYPE mapper

PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

"https://mybatis.org/dtd/mybatis-3-mapper.dtd">

 

<mapper namespace="attend">

 

<!-- C 등록 -->

<!-- 출근 버튼 눌렀을 때 -->

<insert id="save">

insert into attend(emp_no, ATTEND_NO, ATTEND_START) values(#{empNo}, attend_seq.nextval, sysdate)

</insert>

 

<!-- U 수정 -->

<!-- 퇴근 버튼 눌렀을 때 -->

<update id="update">

update attend set attend_end = sysdate where emp_no = #{empNo}

and trunc(attend_start) = trunc(sysdate)

</update>

 

<!-- R 목록, 상세 -->

<!-- 사번별로 지정한 기간으로 날짜가 생성되고, 근무시간이 추가돼서 나오는 구문 -->

<select id="selectListByEmpNo" resultType="AttendWorkingTimesVO">

SELECT

TMP.DT,

ATT.attend_no,

ATT.emp_no,

ATT.attend_start,

ATT.attend_end,

NVL((ATT.attend_end - ATT.attend_start) * 24, 0) AS "working_times"

FROM (

SELECT

trunc(to_date(#{attendBeginDate}, 'YYYY-MM-DD') + (LEVEL - 1)) AS DT

FROM dual

<![CDATA[

CONNECT BY LEVEL <= to_date(#{attendEndDate}, 'YYYY-MM-DD') - to_date(#{attendBeginDate}, 'YYYY-MM-DD') + 1

]]>

) TMP

LEFT JOIN (

SELECT *

FROM attend

WHERE emp_no = #{empNo}

) ATT ON to_char(TMP.DT, 'YYYY-MM-DD') = to_char(ATT.attend_start, 'YYYY-MM-DD')

ORDER BY TMP.DT desc

</select>

</mapper>

AttendDao

public interface AttendDao {

//사번으로 근태목록 조회

//출근(등록)

void insert(AttendDto attendDto);

//퇴근(수정)

void update(int attendNo, AttendDto attendDto);

//한 달 간격으로, 모두 나올 필요가 없음 애초에 가장 최근부터 한 달만 보이게, 날짜 선택이 가능하게

List<AttendWorkingTimesVO>selectListByEmpNo(AttendWorkingSearchVO VO);

}

AttendDaoImpl

@Repository

public class AttendDaoImpl implements AttendDao {

 

@Autowired SqlSession sqlSession;

 

//출근

@Override

public void insert(AttendDto attendDto) {

sqlSession.insert("attend.save", attendDto);

}

 

//퇴근

@Override

public void update(int empNo, AttendDto attendDto) {

Map<String, Object> param = Map.of("empNo", empNo, "attendDto", attendDto);

sqlSession.update("attend.update", param);

}

 

@Override

public List<AttendWorkingTimesVO> selectListByEmpNo(AttendWorkingSearchVO VO) {

return sqlSession.selectList("attend.selectListByEmpNo",VO);

}

 

}

AttendRestController

@Slf4j

@Tag(name="근태 관리", description = "근태 관리를 위한 컨트롤러")

@CrossOrigin

@RestController

@RequestMapping("/attend")

public class AttendRestController {

 

@Autowired

private AttendDao attendDao;

 

//출근시간 찍히기

@PostMapping("/") //등록

public void insert(@RequestBody AttendDto attendDto) {

attendDao.insert(attendDto);

}

 

//퇴근시간 찍히기

// @PutMapping - 전체 수정

@PatchMapping("/{empNo}") // 일부 수정

public void update(@RequestBody AttendDto attendDto, @PathVariable int empNo) {

attendDao.update(empNo, attendDto);

}

 

 

// @GetMapping - 조회

@PostMapping("/find/")

public List<AttendWorkingTimesVO> find(@RequestBody AttendWorkingSearchVO VO){

List<AttendWorkingTimesVO> list = attendDao.selectListByEmpNo(VO);

return list;

}

 

}

//Spring Boot에서 사용하는 기본 Jackson 변환도구를 변경하는 설정

@Configuration

public class JacksonConfiguration {

 

//신규 Bean을 중요(Primary)하게 등록하여 기존 코드를 대체

@Bean

@Primary

public ObjectMapper objectMapper() {

ObjectMapper mapper = new ObjectMapper();

mapper.setTimeZone(TimeZone.getDefault());//시간 지역을 시스템 기본값으로 설정

//mapper.setTimeZone(TimeZone.getTimeZone("Asia/Seoul"));//시간 지역을 인위적으로 설정

mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));//날짜 변환 기본 포맷을 설정

return mapper;

}

 

}

-- 퇴근 시각 누락된 레코드를 확인하여 자동으로 결근 처리
UPDATE attendance
SET
    end_time = TO_DATE('YYYY-MM-DD 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), -- 퇴근 시각을 00:00:00으로 설정
    work_hours = 0 -- 근무 시간을 0으로 설정
WHERE
    end_time IS NULL AND TRUNC(work_date) = current_date;