코딩 노트
그룹웨어 - 근태관리 테이블 및 CRU 생성, 백엔드 + 프론트엔드 본문
그룹웨어
우리 프로젝트는 모두 리엑트로 진행된다.
내가 맡은 테이블은 근태 관리 테이블이다.
근태
- 출, 퇴근시 버튼을 클릭해서 버튼 누른 시간을 입력 후 기록한다.
- 근무 상태(정상근무, 휴무일근무, 휴무, 지각, 조퇴, 결근,...)이 나온다.
- 출근 시간이 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;