킥오프 — 결정과 이유
사이트의 「개발팀 킥오프」 화면에서 그대로 뽑았다. 규칙에 이유를 함께 적어 둔 까닭이 있다. 이유 없는 규칙은 개발 중에 “합리적으로” 바뀐다. 규칙을 바꾸어야겠다 싶으면 여기 적힌 이유부터 반박해야 한다.
휴가·출장·교육 조회 API — 막힘 — 이게 열려야 진행
왜 필요한가 — 지금 콘솔은 데모 DB를 직접 읽는다. 읽기 전용 MCP가 SERVAREA_ID=300에 고정돼 있어 고객사 운영에는 그대로 쓸 수 없다. 운영은 제품 JSON API(SYS/API json, 서버 간 POST)로 옮겨야 한다.
확인한 사실 — 엔드포인트가 없다는 것을 확인했다(2026-08-11). JSON API 컨트롤러 URL 24개를 전수 확인한 결과 근태 쪽은 근태기 적재·PC-OFF 처리뿐이고, 조회 계열은 조직·직원·SSO·발령·결재다. 가장 가까운 결재 3종도 못 쓴다 — 기안자·결재자 조건이 박혀 개인 단위이고, 기간 필터가 기안일(APPL_YMD)이며, 휴가 기간·휴가코드가 컬럼이 아니라 렌더링된 문서 본문(CLOB) 안에 있다.
할 일 — 신규 개발이 필요하고, 그것이 이 연동의 실제 착수 조건이다. 아래에 별도로 구현해 두었다 — 제품 반영 시점(브랜치·릴리스·WAS 재기동·고객사 설치본)이 정해져야 넣을 수 있다. 앱 쪽은 lib/oracle.ts 하나만 갈아끼우면 되도록 어댑터로 끊어 두었다.
별도 구현 — 제품 미반영
제품에 넣을 신규 API 한 벌을 별도로 구현했다. 반영·배포는 하지 않았다. (lcy_google/docs/api-proposal/)
만든 것
TaaCalendarApi_SQL.xml— 신규 매퍼 SQL 3개 — 권한 확인, 일정 조회(휴가·출장·교육 한 번에), 호출 기록(CMMT_MSG_LOG)TaaCalendarController.java— POST /getTaaCalendarApiList.do — 기존 24개 URL과 같은 모양TaaCalendarServiceImpl.java— 입력 확인 → 키 확인 → 조회 → 기록TaaCalendarMapper.java— 매퍼 인터페이스sample-request.json / sample-response.json— 요청·응답 형태option-TAA_CAL_INF_YN.md— 가정한 신규 옵션(연동 사용여부·사이트ID·인터페이스 키)의 정의README.md— 왜 새로 만드는지, 무엇을 판단했는지, 무엇을 확인하지 못했는지
내린 판단
- 엔드포인트는 하나로 묶고 kindCds로 갈래를 고른다. 셋으로 나누면 키 확인도 세 번, 기간·조직 조건도 세 곳에서 관리해야 한다.
- 조회 범위는 연동 계정의 조직조회권한(AUTT_SRCH_BASE 111 + ORGF_LINE)이 정한다. 제품 홈 화면이 쓰는 바로 그 패턴이다. 요청의 orgCd는 그 안에서 더 좁히기만 한다 — 키가 새도 전사 인사정보가 통째로 나가지 않는다.
- 조직은 하위조직을 포함하지 않는다. 부서를 고르면 그 부서에 직접 속한 직원만 나가고 하위 부서는 나가지 않는다. inclOrgYn 파라미터를 아예 없앴다(SQL·서비스·샘플요청 모두) — 받아 두면 언젠가 Y가 들어와 범위가 조용히 넓어진다. 콘솔의 「하위 조직 포함」 체크박스와 includeSubOrg 설정도 함께 없애고 사실만 적었다.
- 하위조직을 안 쓴다고 ORGF_LINE을 빼면 안 된다. orgScope의 ORGF_LINE·ORG_LINE LIKE는 「요청 조직의 하위」가 아니라 「연동 계정에게 권한이 있는 조직의 하위」를 계산하는 자리다. 권한 경계라 없애면 권한 밖 인사정보가 새어 나간다 — 이름이 닮아 혼동하기 쉬운 지점이라 못박아 둔다.
- 옵션 1426(HIDE_LEAV_CDS)은 API에서 적용한다. 미표시 코드는 응답에 넣지 않고, 마스킹 코드는 MASK_YN으로 표시해 내보낸다. 연동 쪽에 규칙을 복사하면 제품과 기준이 갈린다.
- 기간은 겹치면 뽑는다. 지금 콘솔의 데모 조회는 시작일만 봐서 달을 걸친 휴가가 빠지는데, 이 API에서는 고쳤다.
- 상태 기본값은 결재요청(20)·결재완료(30). 임시저장·반려는 캘린더에 나가면 안 된다.
- 조회 실패와 빈 결과를 구분한다. 빈 배열을 "휴가가 없다"로 읽으면 이미 등록한 일정을 몽땅 지운다.
- 변경·취소 통지 API는 만들지 않는다(2026-08-12 결정). 예전에는 「통지가 없어서 어쩔 수 없이 대조한다」고 적어 두었는데 틀린 이해였다. 5240에는 삭제도 변경도 없고 취소만 있으며, 취소는 휴가취소 결재가 따로 돌아 취소승인 시 원건이 원복된다. 원복되면 상태 필터(기본 30, 결재중 포함 시 20,30)에서 빠지므로 다음 조회 응답에서 그 건이 사라진다. 즉 대조는 통지가 없어 고른 차선이 아니라 이 구조에 맞는 방식이다 — 훅도 변경분 조회도 만들 필요가 없다. 대조할 때의 안전장치는 change 항목에 있다.
- 근무일정은 연동하지 않는다(2026-08-12 결정). 매일 값이 있어(기본근무·유일·휴무 포함) 직원 수 × 365일이 그대로 쌓여 휴가가 묻힌다 — 연동량 과다가 이유이지 구현 난도가 아니다. WKTYPE 분기를 SQL·서비스·샘플요청에서 뺐고 화면 체크박스와 Branch 타입에서도 없앴다. 되살릴 일이 생기면 UNION 하나를 더하는 일이 아니다 — 근무일정조회 화면은 신청 테이블의 합집합이 아니라 일자별 우선순위를 적용해 해결한 결과 1건을 보여준다(조직 근무계획과 개인 신청이 겹치면 개인 신청 우선, 변경은 변경일자 이후만 반영 — 근태 매뉴얼 확인). 그대로 합치면 화면에 1건인 날이 캘린더에 2건으로 뜨고, 그 규칙을 SQL에 재구현하면 제품과 갈린다.
아직 확인하지 못한 것
- 실행해 보지 않았다 — 컴파일도 쿼리 실행도 하지 않았다. 검증하려면 kiwibox 빌드 환경과 WAS가 필요하다.
- 조직 범위 계산이 행마다 ORGF_LINE을 부르는 구조라, 직원 수가 많은 고객사에서 느려질 수 있다. 실데이터 실행 계획을 재 봐야 하고, 그러려면 위 항목과 같은 조건(kiwibox 빌드 환경·WAS)이 필요하다. 제품에 넣기 전에 재는 것이 순서다. 하위조직 불포함으로 정리했다고 이 비용이 사라지지 않는다 — 줄어든 것은 요청 조직 쪽 LIKE 하나뿐이고, 권한 범위를 계산하는 ORGF_LINE은 그대로다.
OAuth 동의화면 게시·검증 — 막힘 — 이게 열려야 진행
왜 필요한가 — 동의화면이 「테스트」 상태면 테스터 목록에 넣은 계정만 로그인된다. 목록은 100명이 한도이고, 이 상태에서 받은 리프레시 토큰은 7일 뒤 만료된다 — 주기 동기화가 일주일마다 끊긴다는 뜻이다. 고객사 담당자가 화면대로 따라와도 마지막 로그인에서 막히면 앞 단계가 모두 헛일이 된다.
확인한 사실 — 테스터가 아닌 개인 지메일(tykwon210@gmail.com)로 로그인하니 「액세스 차단됨: insapien.co.kr은(는) Google 인증 절차를 완료하지 않았습니다 / 403 오류: access_denied」로 끊겼다. 화면 갈무리는 refer/액세스차단.png에 있다. 이 차단의 원인은 「테스트」 상태 자체이지 스코프 등급이 아니다 — 프로덕션으로 게시하면 등급과 무관하게 없어지고, 7일 만료도 함께 풀린다. 검증은 게시의 선행 조건이 아니다. 미검증으로 게시하면 「확인되지 않은 앱」 경고가 뜨고 승인 계정이 누적 100명으로 묶일 뿐이며, 이 한도는 되돌릴 수 없다(구글 Manage App Audience).
이 앱이 요청하는 스코프는 calendar.app.created 하나다(lib/google.ts). 이 앱이 만든 보조 캘린더에만 접근하고 담당자의 기존 일정은 읽지 못한다. 구글은 캘린더 스코프의 민감도 등급을 공개 문서에 싣지 않는다 — Cloud Console 「데이터 액세스」 페이지가 비민감/민감/제한으로 자동 분류해 보여주는 것이 유일한 판정처다. 아직 그 라벨을 보지 못했다.
할 일 — ① Cloud Console 「데이터 액세스」에서 calendar.app.created의 분류 라벨을 확인한다. 비민감이면 게시로 끝이고, 민감이면 개인정보처리방침·서비스 약관 주소, 도메인 소유 확인, 사용 흐름 영상을 갖춰 검증을 신청해야 한다. 이 한 줄이 남은 일정을 가른다.
② 라벨 확인과 무관하게 동의화면을 지금 프로덕션으로 게시한다. 검증 결과를 기다릴 이유가 없다 — 게시가 403과 7일 만료를 즉시 해소한다.
③ OAuth 클라이언트 소유 주체를 정한다. 게시·검증이 필요한지 자체가 여기서 갈린다. (가) 고객사가 자기 워크스페이스에서 만들면 대상을 「내부」로 둘 수 있어 게시도 검증도 테스터 한도도 7일 만료도 전부 없다 — 대신 인사담당이 GCP를 만져야 한다. (나) 고객사가 개인 지메일이면 워크스페이스 조직이 없어 「내부」를 고를 수 없다. 외부 + 프로덕션 게시가 강제된다. (다) 5240이 소유하면 고객사 직원이 조직 외부이므로 역시 외부 강제이고, 미검증 100명이 전 고객사 합산 평생 한도라 두세 곳이면 소진된다 — 즉 5240 소유를 택하는 순간 검증은 선택이 아니라 필수가 된다. 인사담당의 GCP 부담을 없애는 대가로 5240이 검증을 떠안는 맞교환이며, 이 결정이 (가)와 (다) 중 하나를 고르는 일이다.
직원 ↔ 구글 계정 매핑 — 확인 필요
왜 필요한가 — 직원 각자에게 일정을 보내려면 5240 직원과 구글 계정을 이을 열쇠가 필요하다. 그런데 그 열쇠가 계정 종류에 따라 다르다 — 워크스페이스면 회사 메일 주소가 곧 구글 계정이지만, 개인 지메일 조직에서는 회사가 아예 모르는 직원 개인 주소다. 이 둘은 성격이 전혀 다른 문제라 같은 항목으로 묶어 두면 잘못 판단하게 된다.
확인한 사실 — PRCT_STAFF에서 EMAIL·EMAIL_ADDR·MAIL·EMAIL_ID·E_MAIL·EMAIL_ADR을 모두 조회해 봤으나 하나도 없다(대조군 STAFF_ID 조회는 정상). 사용자 계정 테이블로 짐작되는 것들은 MCP 가드에 막혀 확인하지 못했다.
워크스페이스는 회사 메일이 곧 구글 계정이라 5240이 그 주소만 확보하면 되고, 관리 콘솔 계정 목록과 대조해 틀린 주소를 걸러낼 수도 있다. 개인 지메일 조직은 다르다 — 회사 인사기록에 없는 정보이므로 직원에게서 직접 받아야 하고, 받은 주소가 실제 그 직원의 구글 계정인지 확인할 방법이 구독 성공 여부밖에 없다. 개인정보이므로 수집 근거와 보관 위치도 따로 정해야 한다.
「공유 캘린더 방식은 매핑이 필요 없다」는 앞선 판단은 워크스페이스에서만 참이다. 개인 지메일 조직에서는 공유 캘린더도 대상 지정에 직원 지메일 주소가 필요하다 — 하나씩 초대하거나 「링크가 있는 모든 사용자」 공개로 열어야 하고, 공개로 열면 링크 유출을 막을 수 없다. 이 차이는 methods.ts의 shared.accountNote와 2단계 화면에 반영했다.
할 일 — 워크스페이스 쪽 — 회사 메일 주소가 다른 테이블에 있는지 확인한다. 이것만 열리면 매핑은 사실상 끝난다.
개인 지메일 쪽 — 초대냐 링크 공개냐는 유출 위험을 누가 감수하느냐의 문제라 5240이 대신 고를 수 없어, 2단계에서 고객사가 직접 고르게 만들었다(설정 shareMode, 미선택이면 막는 항목으로 뜨고 4단계 안내·공지문이 고른 대로 바뀐다). 남은 것은 초대를 골랐을 때 주소를 어디에 보관하느냐다 — 5240 인사기록에 직원 개인 계정을 넣을지, 연동 콘솔에만 둘지가 갈림길이고 어느 쪽이든 개인정보 취급 방침이 따라붙는다. 지금은 인사담당이 구글 캘린더 공유 목록에 직접 넣는 것으로 두어 5240이 개인 주소를 보관하지 않는다.
도메인 전체 위임은 개인 지메일 조직에서 애초에 불가능하다(위임할 도메인이 없다). 즉 「직원 각자 캘린더에 자동으로」는 워크스페이스 전용 경로이고, 개인 지메일 조직의 최선은 공유 캘린더 + 구독이다. 이 사실을 매핑 논의의 전제로 둔다.
서비스영역 ID 파라미터화 — 확인 필요
왜 필요한가 — 고객사마다 SERVAREA_ID가 다르다. 구글 연동은 고객사별로 서는 것이라 서비스영역마다 연동 설정 한 벌(연동함)이 있어야 작동한다 — 설정도 구글 클라이언트·토큰도 캘린더도 고객사 것이다. 파일 한 벌에 값만 바꿔 넣으면 두 번째 고객사를 붙이는 순간 첫 고객사의 토큰을 덮어쓴다.
확인한 사실 — 콘솔 쪽은 끝냈다(2026-08-12). 서비스영역 ID를 키로 연동함을 나눠 보관하고(data/tenants.json + data/tenants/<ID>/settings.json·google.json), 머리말에서 고객사를 전환하며, /tenants에서 만들고 지운다. 설정·구글 설정을 읽고 쓰는 함수는 모두 서비스영역 ID를 인자로 받게 바꿔 기본값을 없앴다 — 빠뜨리면 컴파일이 실패하므로 조용히 데모를 읽는 일이 생기지 않는다. 조회 문장의 SERVAREA_ID도 설정값을 따른다. 구글 OAuth는 state에 서비스영역 ID를 실어, 동의하는 동안 다른 연동함으로 바꿔 두어도 토큰이 남의 고객사 자리에 저장되지 않는다.
남은 것은 조회 경로다. 읽기 전용 MCP 가드가 리터럴 300을 요구한다(가드 테스트에서 '100'도 바인드 변수도 거절). 그래서 300이 아닌 연동함은 설정을 미리 채워 둘 수는 있어도 미리보기에 실제 휴가가 뜨지 않는다 — 그 판정을 oracle.ts의 demoOnly() 한 곳에 모아 두고, 화면에는 「제품 API가 열린 뒤」라는 까닭으로 띄운다(막힘 목록에도 「5240 자료 읽기」로 올라간다).
할 일 — 위 api 항목(제품 조회 API)이 열리면 이 제약이 함께 풀린다. lib/oracle.ts를 API 호출로 갈아끼우고 demoOnly()를 지우면 끝이다 — 콘솔 쪽에 더 고칠 것은 없다. API 요청에 실을 사이트ID(서비스영역)는 연동함의 servareaId를 그대로 보낸다.
옵션 1426 값 검증 — 확인 필요
왜 필요한가 — 무엇을 캘린더에서 감출지는 옵션 1426(HIDE_LEAV_CDS)이 정한다. 연동은 이 값을 그대로 따르므로, 값이 틀리면 감춰야 할 휴가가 그대로 나간다.
확인한 사실 — 2026-08-12 데모 서비스영역을 다시 조회했다. CNFG_VAL01(미표시)은 230,250이고 둘 다 휴가코드표에 있다 — 육아휴가·난임휴가(유급). 지금은 미상 코드가 없다.
예전에 적어 둔 「239는 코드표에 없다(250의 오타로 보인다)」는 그 뒤 값이 바뀌어 더는 현재 데이터를 가리키지 않는다. 다만 그때 관찰한 것이 무의미해진 것은 아니다 — 이 값이 사람 손으로 바뀌고, 코드표에 없는 값이 들어가도 아무도 막지 않는다는 사실이 그대로 드러났기 때문이다. 점검 절차가 필요하다는 근거는 오히려 이 변화가 뒷받침한다. 콘솔은 미상 코드를 무시하고 경고로 표시한다.
마스킹 쪽은 비어 있다. CNFG_VAL02(마스킹 코드)도 CNFG_VAL03(마스킹 표시명)도 값이 없어, 마스킹 경로는 실데이터로 한 번도 확인된 적이 없다. 화면에도 「이름을 가리고 나가는 휴가: 없음」으로만 나온다.
할 일 — ① 고객사 도입 시 1426 값이 실제 휴가코드와 맞는지 점검하는 절차를 둔다. 출산·유산·난임·경조처럼 민감한 종류가 빠져 있지 않은지도 함께 본다. 값이 바뀌는 물건이므로 도입 때 한 번 보고 끝낼 일이 아니다.
② 마스킹 경로를 실제 값으로 한 번 검증한다. CNFG_VAL02에 코드를 넣고 CNFG_VAL03에 표시명을 넣은 뒤, 미리보기에 그 이름으로 나오는지 확인한다. 지금은 값이 비어 있어 코드가 도는지 알 수 없다.
③ 마스킹 표시명이 비어 있을 때 무엇으로 내보낼지 정한다. 지금 화면은 「휴가 종류 전체명」이라는 안내 문구로 자리를 메우고 있을 뿐, 실제로 캘린더에 무엇을 쓸지는 정해져 있지 않다.
연동 계정의 조직조회권한 — 확인 필요
왜 필요한가 — 조회는 연동 계정의 조직조회권한 범위 안에서만 이뤄진다. 화면에서 전사를 골라도 권한 밖 직원은 나가지 않는다 — 즉 권한 설정이 곧 동기화 범위다.
확인한 사실 — 리버스 문서로 확인한 제품 동작이다. 데모는 DB 직접 조회라 이 제약이 드러나지 않는다.
할 일 — 연동용 계정을 어느 조직까지 열어 줄지 고객사와 정하고, 계정 발급 절차를 정리한다.
취소 반영 — 준비됨
왜 필요한가 — 휴가가 취소되면 캘린더에서도 없애야 한다. 담당자가 뒤늦게 어떤 코드를 미표시로 바꾸면 이미 나간 일정도 찾아 없애야 한다.
확인한 사실 — 5240에는 삭제도 변경도 없다. 취소만 있고, 취소한 뒤에는 새로 결재를 받는다. 부분 취소는 없다 — 사흘 중 하루만 무르는 일이 없으므로 한 신청건은 통째로 살아 있거나 통째로 없다. 신청건 하나가 캘린더 일정 하나에 그대로 대응한다.
취소 구조를 데이터로 확인했다(2026-08-12). 취소는 휴가 테이블이 아니라 결재취소 신청서(EAPT_CANCL_REQ)에 따로 쌓이고, 취소할 신청서를 REF_APPL_CD·REF_REQ_NO로 가리킨다. 그래서 취소신청이 결재를 거치는 동안에도 휴가 테이블에는 아무것도 늘지 않는다 — 「취소했더니 일정이 하나 더 생긴다」는 걱정은 근거가 없었다.
취소승인이 나면 원건이 어떻게 되는지도 데모 데이터로 대조했다. 취소신청서 1578(승인) → 원건 1572 상태 40, 101255(승인) → 원건 101254 상태 40. 즉 취소승인 시 원건이 반려(40)로 바뀌어 상태 필터에서 저절로 빠진다. SQL에 조건을 더할 필요가 없다. 취소신청이 결재중인 건(101246)의 원건 101022는 30 그대로였다 — 취소는 승인된 것만 반영한다는 규칙과 맞는다.
휴가 테이블에는 휴가신청(1022) 말고 조퇴/외출(1122)·경조휴가(1123)도 함께 쌓인다(EAPT_RULE 확인). 셋 다 캘린더에 내보낸다 — 조퇴/외출도 시스템 구조상 휴가이기 때문이다. 무엇을 감출지는 오로지 옵션 1426이 정한다. 그래서 신청서 종류로 거르는 조건도, EAPT_REQ 조인도 필요 없다. 지금처럼 TAAT_LEAV_REQ를 통째로 읽는 것이 맞다.
할 일 — 규칙은 다 정해졌다. 남은 것은 대조를 붙일 때 지킬 것들이다.
① 이 연동이 만든 일정만 지운다. 이벤트마다 표식(신청번호·갈래·직원ID)을 남겨 두고, 표식 없는 일정은 사람이 손으로 넣은 것으로 보고 건드리지 않는다.
② 조회가 실패하면 아무것도 지우지 않는다. 정상 응답으로 0건이 오는 경우도 의심한다 — 연동 계정의 조직조회권한이 좁아지거나 조직이 개편되면 오류 없이 빈 결과가 오고, 그대로 대조하면 전 직원 일정이 한꺼번에 지워진다. 삭제 예정 건수가 기존 일정의 일정 비율을 넘으면 멈추고 담당자에게 알린다.
③ 기간 창 안만 대조한다. 창은 오늘 기준으로 매번 다시 계산되므로 어제 창에 있던 과거 일정이 오늘은 창 밖으로 빠진다. 창 밖까지 대조하면 「응답에 없다」는 이유로 멀쩡한 과거 일정을 지운다.
④ 캘린더에서 일정이 사라지는 시점은 취소를 낸 때가 아니라 취소승인이 난 때다. 그 사이에는 원래 일정이 그대로 남는다 — 제품 규칙대로이고, 표시를 따로 붙이지 않기로 했다. 담당자가 「취소했는데 왜 아직 있나」로 물어올 수 있으니 안내에 적어 둔다.
실행 주기와 실패 통지 방법은 내보내기를 붙일 때 함께 정한다(progress.ts의 SYNC_READY).
출장·교육 어댑터 — 확인 필요
왜 필요한가 — 갈래가 서로 다른 신청 마스터에 쌓인다. 갈래마다 읽는 코드가 따로 필요하다.
확인한 사실 — 휴가(TAAT_LEAV_REQ)만 콘솔에 붙어 있다. 출장·교육은 화면에 체크박스만 있고 「어댑터 준비 중」으로 표시된다. 근무일정은 연동 대상에서 빠졌다(연동량 과다 — API 항목 참고).
오래 남아 있던 「교육은 무엇을 실을지부터 정해야 한다」를 2026-08-12에 정리했다. 리버스 문서로 확인하니 교육은 신청 마스터가 둘이고, 셋째 것은 캘린더에 올릴 수가 없다. ① EDUT_REQ(개설된 차수에 신청) — 기간이 이 테이블에 없어 EDUT_COURSN에서 종료일을, EDUT_COURS에서 과정명을 가져와야 한다. ② EDUT_HST2(과정명 직접입력, 외부 교육) — 시작일·종료일이 자체에 있다. ③ EDUT_PLAN_REQ·PLAN_REQ2(교육계획신청, 수요조사)는 수강예정 「년월」만 있고 날짜가 없다 — 확정 일정이 아니라 캘린더에 올릴 수 없어 뺐다. 데모 건수는 EDUT_REQ 2, EDUT_HST2 10, EDUT_PLAN_REQ 3이다.
출장은 TAAT_BSTRIP_REQ 하나로 끝나고 시작·종료 일시가 모두 있다(데모 17건). API 제안본에는 처음부터 들어 있었다.
제안본 SQL에 교육 갈래를 더했다(EDUT_REQ + EDUT_HST2 두 UNION). 교육 과정명은 공통코드가 아니라 자유 문자열이라 CMMF_CODE_ML로 못 뽑는다 — 갈래마다 TYPE_NM_RAW에 이름을 담아 올리고 바깥에서 NVL(TYPE_NM_RAW, CMMF_CODE_ML(...))로 받도록 고쳤다. 다만 실행해 본 것은 아니다(제안본 전체가 미실행).
할 일 — ① 콘솔 쪽 어댑터를 붙인다. 다중 고객사(servareaId) 리팩터링이 도는 중이라 lib/leave.ts와 미리보기 화면을 함께 건드려야 해서 아직 손대지 않았다. 리팩터링이 자리 잡은 뒤에 붙이는 것이 순서다.
② 교육에는 최종수정일시 컬럼이 없다. EDUT_REQ·EDUT_HST2 어디에도 없어 CHG_DATE를 NULL로 내보낸다 — 증분 비교가 교육에는 듣지 않고 대조로만 맞춰야 한다. change 항목의 ④(CHG_DATE로 바뀐 건만 고른다)가 교육에는 적용되지 않는다는 뜻이다.
③ 표시 규칙이 휴가와 같은지 확인한다. 상태 필터(기본 30, 결재중 포함 시 20,30)는 세 갈래가 같은 EAP_STATUS_CD를 쓰므로 그대로 간다. 휴가의 옵션 1426 같은 가림 규칙은 출장·교육에 두지 않기로 했다(2026-08-12 결정) — 출장지·교육 과정명은 그대로 나간다.
④ 교육 건수를 실데이터로 재 본다. 데모는 열 몇 건이지만, 전사 교육이 잦은 고객사라면 근무일정에서 겪은 것과 같은 물음을 다시 받는다 — 캘린더가 교육으로 덮이지 않는지.
옵션 1426 읽기 경로 — 확인 필요
왜 필요한가 — 연동이 제품과 같은 기준을 쓰려면 옵션값을 실행할 때마다 다시 읽어야 한다.
확인한 사실 — CFGT_VAL에서 CNFG_ID=HIDE_LEAV_CDS를 읽어 미표시(CNFG_VAL01)·마스킹(CNFG_VAL02)·마스킹 표시명(CNFG_VAL03) 세 항목을 그대로 적용하는 것까지 동작 확인했다. 미리보기 화면이 현재값을 읽기 전용으로 보여준다.
다시 보다가 구멍을 하나 찾았다. 지금은 콘솔이 DB를 직접 읽어 마스킹 표시명을 얻지만, API 경로로 옮기면 연동은 CFGT_VAL을 직접 읽지 못한다. 그런데 API 제안본은 MASK_YN(마스킹 대상인가)만 내보내고 표시명은 내보내지 않았다 — 그대로 옮기면 「가려야 한다」는 것만 알고 「무엇으로 바꿔 쓸지」는 모르는 상태가 되어 마스킹이 반쪽이 된다. 콘솔이 DB를 직접 읽고 있어서 여태 드러나지 않던 구멍이다.
제안본 SQL에 MASK_NM을 더해 표시명도 함께 내보내도록 고쳤다(sample-response·README 동반 수정). 다만 이것도 실행해 본 것은 아니다 — 제안본 전체가 미실행 상태다.
할 일 — API 경로로 옮길 때 이 조회도 함께 옮긴다. 기준을 연동 쪽에 복사해 두지 않는다. 옮긴 뒤 MASK_NM이 실제로 응답에 실려 오는지, 값이 비어 있을 때 연동이 무엇으로 내보내는지 확인한다(값 검증 항목 ③과 같은 결정이다).