구글시트 작업 이력 자동 기록 방법 | Apps Script 9편
도입 — 문제 상황과 이번 편에서 다룰 내용
입출고 담당자가 바뀌어도 누가 저장했는지 바로 확인할 수 있어야 창고 작업 책임 소재가 분명해집니다. 구글시트로 재고를 관리하다 보면 “이 수량 누가 바꿨습니까?”, “언제 출고 처리됐습니까?” 같은 질문이 자주 나옵니다. 기본 기능만으로는 셀 변경 이력을 찾기가 번거롭고, 여러 사람이 동시에 작업하면 누가 무엇을 했는지 한눈에 보기 어렵습니다. 이번 글에서는 이런 상황을 줄이기 위해 구글시트 작업 이력 자동 기록 방법을 Apps Script로 구현하는 실무용 패턴을 소개합니다.
이 글은 구글시트로 창고 재고관리 시스템 만들기 | Apps Script 자동화를 시작으로, 입출고 자동화·집계·리포트·랙 배치도, 바코드 스캔, 출고 서머리, 대시보드까지 만들어 온 시리즈의 9편입니다. 이미 입고·출고·재고 집계 자동화는 되어 있고, 담당자가 버튼만 누르면 기록이 저장되는 상태라고 가정합니다. 여기에 누가 언제 무엇을 입력했는지를 자동으로 쌓아 두는 LOG 시트를 추가해, 문제가 생겼을 때 되짚을 수 있는 구글시트 Apps Script 로그 남기기 구조를 만드는 것이 이번 편의 목표입니다.
LOG 시트 구조 설계 — 과하지 않게, 필요한 정보만
구글시트 사용자 작업 기록 자동화를 설계할 때 가장 먼저 정해야 할 것은 “무엇을 남길 것인가”입니다. 실무에서 여러 창고를 운영하면서 정리해 본 결과, 재고·출고 업무를 추적하는 데 필요한 최소 정보는 다음 여섯 가지면 충분했습니다.
- TIMESTAMP: 언제 작업이 발생했는지
- USER: 누가 실행했는지
- ACTION: 어떤 동작인지 (예: PUTAWAY_SAVE, PICKING_SAVE 등)
- TARGET: 어떤 대상을 건드렸는지 (예: 주문번호, 팔레트 ID 등)
- BEFORE: 변경 전 값 요약
- AFTER: 변경 후 값 요약
이 정도만 있어도 “이 주문 재고를 누가 언제 조정했는지”, “특정 사용자 작업 패턴이 어떤지”를 충분히 조회할 수 있습니다. 개인정보는 최소화하는 편이 안전하므로, 이름 대신 구글 계정 이메일 또는 사내 ID 한 줄만 남기도록 설계하는 것을 권장합니다. 외부 공유가 잦은 환경이라면 사내 ID를 따로 관리하는 편이 더 적절했습니다.
LOG 시트는 손으로 만들지 않아도 됩니다. 아래 createLogSheet_() 가 시트와 여섯 개 머리글을 없을 때만 만들어 주고, 이미 있으면 그대로 씁니다. 이 LOG 시트는 입력은 Apps Script만 담당하고, 사용자는 직접 수정하지 않는 조회 전용으로 둡니다. 필요하면 구글시트 대시보드 만들기 Apps Script: 창고 현황 한눈에 보는 방법처럼 별도 대시보드 시트에서 필터나 피벗으로 최근 이력만 요약해 보여 주면 됩니다.
LOG 시트와 기본 설정 코드 — 사용자 식별과 보관 기준 한 번에
LOG 시트를 만들었다면, 이제 구글시트 Apps Script에서 공통으로 쓸 설정을 모아 두는 부분이 필요합니다. 특히 어떤 시트에 로그를 남길지, 사용자를 이메일로 남길지 ID로 남길지, 로그를 최대 몇 행까지 보관할지를 한 곳에서 관리하는 것이 유지보수에 유리했습니다.
이 코드는 LOG 시트 이름과 사용자 식별 방식을 한 번에 모아 두는 설정입니다.
붙여넣을 위치: 구글시트 → 확장 프로그램 → Apps Script → Code.gs 최상단.
붙여넣은 뒤 할 일: 저장(⌘S 또는 Ctrl+S) 후 함수 목록에서 testCreateLogSheet_ 를 한 번 실행합니다.
// 여기만 본인 환경에 맞게 바꾸세요
const LOG_CONFIG = { // → 이 편 전용 설정 모음
SHEET_LOG: 'LOG', // → 작업 이력 시트 이름
SHEET_SETTINGS: 'SETTINGS', // → 2편에서 만든 설정 시트(ID 매핑용)
USER_MODE: 'EMAIL', // → 'EMAIL' 또는 'ID'
SETTINGS_ID_COL: 1, // → SETTINGS A열: 사내 ID
SETTINGS_EMAIL_COL: 2, // → SETTINGS B열: 구글 계정 이메일
HEADER: ['TIMESTAMP', 'USER', 'ACTION', 'TARGET', 'BEFORE', 'AFTER'], // → LOG 머리글
MAX_LOG_ROWS: 5000, // → LOG에 남겨 둘 최대 데이터 행 수
ARCHIVE_PREFIX: 'LOG_', // → 보관 시트 접두어 (LOG_2026-08)
TIMEZONE: 'America/New_York' // → 보관 시트 이름을 정할 때 쓰는 시간대
};
// LOG 시트가 없으면 머리글까지 만들어 준다. 있으면 그대로 쓴다.
function createLogSheet_() { // → LOG 시트 준비
const ss = SpreadsheetApp.getActive(); // → 현재 스프레드시트
let sh = ss.getSheetByName(LOG_CONFIG.SHEET_LOG); // → 기존 LOG 시트
if (!sh) { // → 없으면
sh = ss.insertSheet(LOG_CONFIG.SHEET_LOG); // → 새로 만든다
}
const head = sh.getRange(1, 1, 1, LOG_CONFIG.HEADER.length); // → 머리글 범위
if (String(head.getValues()[0][0] || '').trim() !== LOG_CONFIG.HEADER[0]) {
head.setValues([LOG_CONFIG.HEADER]).setFontWeight('bold'); // → 머리글 쓰기
sh.setFrozenRows(1); // → 첫 줄 고정
}
return sh; // → 시트 객체 반환
}
// 로그에 남길 사용자 이름. EMAIL 모드면 이메일, ID 모드면 SETTINGS의 사내 ID.
function getLogUser_() { // → 사용자 식별
const email = String(Session.getActiveUser().getEmail() || '').trim(); // → 세션 이메일
if (LOG_CONFIG.USER_MODE !== 'ID') { // → EMAIL 모드면
return email || 'UNKNOWN'; // → 이메일 그대로
}
const sh = SpreadsheetApp.getActive().getSheetByName(LOG_CONFIG.SHEET_SETTINGS);
if (!sh || sh.getLastRow() < 2 || !email) { // → 매핑표가 없으면
return email || 'UNKNOWN'; // → 이메일로 대신한다
}
const wide = Math.max(LOG_CONFIG.SETTINGS_ID_COL, LOG_CONFIG.SETTINGS_EMAIL_COL);
const rows = sh.getRange(2, 1, sh.getLastRow() - 1, wide).getValues(); // → 매핑표 읽기
for (let i = 0; i < rows.length; i++) { // → 한 줄씩 확인
const mail = String(rows[i][LOG_CONFIG.SETTINGS_EMAIL_COL - 1] || '').trim();
if (mail.toLowerCase() === email.toLowerCase()) { // → 대소문자 무시하고 비교
const id = String(rows[i][LOG_CONFIG.SETTINGS_ID_COL - 1] || '').trim();
if (id) return id; // → 사내 ID 사용
}
}
return email; // → 매핑에 없으면 이메일
}
function testCreateLogSheet_() { // → 편집기에서 실행할 확인용 함수
const sh = createLogSheet_(); // → 시트 준비
Logger.log('LOG 머리글: ' + JSON.stringify(
sh.getRange(1, 1, 1, LOG_CONFIG.HEADER.length).getValues()[0])); // → 로그로 확인
Logger.log('현재 사용자: ' + getLogUser_()); // → 어떤 이름으로 남는지 확인
}제대로 됐는지 확인하는 법: testCreateLogSheet_ 를 실행하면 실행 로그에 ["TIMESTAMP","USER","ACTION","TARGET","BEFORE","AFTER"] 와 현재 사용자 이름이 찍힙니다. 시트 탭에도 LOG 가 생겨 있으면 준비가 끝난 것입니다.
writeLog 함수 — 모든 저장 함수에서 공통으로 호출
이제 핵심인 LOG 기록 함수입니다. 목표는 단순합니다. 입고·출고·재고 조정 등 어떤 저장 함수에서도 한 줄짜리 공통 함수만 호출하면 자동으로 LOG 시트에 기록이 남도록 만드는 것입니다. 이렇게 해 두면 저장 로직이 바뀌어도 writeLog 부분은 재사용할 수 있습니다.
이 코드는 한 번의 작업을 LOG 시트 마지막 행에 추가로 써 넣습니다.
붙여넣을 위치: 같은 Apps Script 프로젝트의 Code.gs, getLogUser_ 아래쪽.
붙여넣은 뒤 할 일: 저장 후 한 번 testWriteLog_() 를 실행해 권한을 허용합니다.
// 작업 한 건을 LOG 시트에 한 줄 추가한다.
// getLastRow() + 1 에 직접 쓰면 두 사람이 같은 순간 저장할 때 같은 줄에 덮어써 로그가
// 사라진다. appendRow 는 구글이 줄을 잡아 주므로 동시에 불러도 겹치지 않는다.
function writeLog_(action, target, beforeText, afterText) { // → 이력 한 줄 기록
const sh = createLogSheet_(); // → LOG 시트(없으면 만들어 준다)
sh.appendRow([ // → 마지막 줄 다음에 안전하게 추가
new Date(), // → TIMESTAMP
getLogUser_(), // → USER
String(action || ''), // → ACTION
String(target || ''), // → TARGET
(beforeText === undefined || beforeText === null) ? '' : String(beforeText), // → BEFORE
(afterText === undefined || afterText === null) ? '' : String(afterText) // → AFTER
]);
}
function testWriteLog_() { // → 편집기에서 실행할 확인용 함수
writeLog_('TEST_ACTION', 'TEST_TARGET', '이전 값 예시', '이후 값 예시'); // → 한 줄 남기기
const sh = createLogSheet_(); // → LOG 시트
const last = sh.getRange(sh.getLastRow(), 1, 1, LOG_CONFIG.HEADER.length).getValues()[0];
Logger.log('방금 남은 줄: ' + JSON.stringify(last)); // → 로그로 확인
}동작 확인 방법: 편집기에서 testWriteLog_ 를 선택해 실행하면, 실행 로그에 방금 남은 줄이 그대로 찍히고 LOG 시트 마지막 행에 TEST_ACTION 한 줄이 늘어납니다. 두 번 실행하면 두 줄이 되는지도 함께 보세요. 한 줄만 늘어난다면 appendRow 가 아니라 특정 행에 덮어쓰고 있는 것입니다.
로그 보관용 archiveLog 함수 — LOG 시트가 너무 커지지 않게
실무에서 몇 달만 지나도 LOG 시트는 금방 수만 행이 됩니다. 구글시트는 행이 많아지면 필터, 정렬, 피벗이 눈에 띄게 느려집니다. 그래서 일정 행 수를 넘으면 오래된 기록을 월별 보관(LOG_YYYY-MM) 시트로 옮기는 구조를 함께 두는 것이 좋았습니다.
이 코드는 LOG 시트의 데이터 행 수가 MAX_LOG_ROWS 를 넘으면, 가장 오래된 행부터 초과분을 보관 시트로 옮깁니다. 보관 시트 이름은 그 줄이 기록된 달을 기준으로 정합니다. 정리한 날짜로 이름을 지으면 3월에 남은 로그가 LOG_2026-08 에 들어가 버려 월별 보관이라고 부를 수 없기 때문입니다. 한 번에 여러 달치를 옮기면 LOG_2026-03, LOG_2026-04 처럼 달마다 나뉘어 들어갑니다.
붙여넣을 위치: writeLog_ 함수 바로 아래.
붙여넣은 뒤 할 일: testArchiveLog_() 를 실행해 보관 시트 생성 여부를 확인합니다.
// LOG가 너무 커지면 오래된 줄을 '그 줄이 기록된 달' 시트로 옮긴다.
// 정리한 날짜로 이름을 지으면 3월 로그가 LOG_2026-08 에 섞여 월별 보관이 아니게 된다.
function archiveLog_() { // → 오래된 로그 보관
const ss = SpreadsheetApp.getActive(); // → 현재 스프레드시트
const sh = ss.getSheetByName(LOG_CONFIG.SHEET_LOG); // → LOG 시트
if (!sh) return 0; // → 없으면 할 일 없음
const dataRows = sh.getLastRow() - 1; // → 머리글을 뺀 데이터 줄 수
if (dataRows <= LOG_CONFIG.MAX_LOG_ROWS) return 0; // → 아직 정리할 필요 없음
const moveCount = dataRows - LOG_CONFIG.MAX_LOG_ROWS; // → 옮길 줄 수
const cols = LOG_CONFIG.HEADER.length; // → 열 개수
const values = sh.getRange(2, 1, moveCount, cols).getValues(); // → 가장 오래된 줄부터
const byMonth = {}; // → 달별로 나눠 담는다
values.forEach(function (row) { // → 한 줄씩
const d = (row[0] instanceof Date) ? row[0] : new Date(row[0]); // → TIMESTAMP
const key = isNaN(d.getTime()) // → 날짜로 못 읽으면
? 'UNKNOWN' // → 따로 모은다
: Utilities.formatDate(d, LOG_CONFIG.TIMEZONE, 'yyyy-MM'); // → 기록된 달
if (!byMonth[key]) byMonth[key] = []; // → 처음 보는 달이면 방 만들기
byMonth[key].push(row); // → 그 달에 넣기
});
Object.keys(byMonth).sort().forEach(function (key) { // → 달 순서대로
const name = LOG_CONFIG.ARCHIVE_PREFIX + key; // → 예: LOG_2026-03
let arc = ss.getSheetByName(name); // → 보관 시트 찾기
if (!arc) { // → 없으면
arc = ss.insertSheet(name); // → 만들고
arc.getRange(1, 1, 1, cols).setValues([LOG_CONFIG.HEADER]).setFontWeight('bold');
arc.setFrozenRows(1); // → 머리글 고정
}
const rows = byMonth[key]; // → 그 달의 줄들
arc.getRange(arc.getLastRow() + 1, 1, rows.length, cols).setValues(rows); // → 이어 붙이기
});
sh.deleteRows(2, moveCount); // → 옮긴 줄은 지운다(비우면 빈 줄이 남는다)
Logger.log('보관 ' + moveCount + '줄 → ' + Object.keys(byMonth).sort().join(', '));
return moveCount; // → 옮긴 줄 수 반환
}
function testArchiveLog_() { // → 편집기에서 실행할 확인용 함수
Logger.log('옮긴 줄 수: ' + archiveLog_()); // → 결과를 로그로 출력
}동작 확인 방법: MAX_LOG_ROWS 를 잠깐 5 정도로 낮추고 testWriteLog_ 를 예닐곱 번 실행한 뒤 testArchiveLog_() 를 실행해 보세요. 시트 탭에 LOG_2026-08 형식의 시트가 생기고 LOG 시트 위쪽 오래된 줄이 그쪽으로 옮겨가 있으면 정상입니다. 확인이 끝나면 MAX_LOG_ROWS 를 다시 5000으로 돌려 놓으세요.
저장 함수에 로그 끼워 넣기 — Lock과 숫자 검증까지 한 번에
이제 실제 저장 함수에 로그를 붙여야 비로소 구글시트 입출고 이력 추적이 완성됩니다. 1편에서 만든 savePutaway, 2편에서 만든 savePicking 이 이미 있습니다. 그 함수들을 뜯어고치는 대신 감싸는 함수를 하나 두는 방식을 씁니다. 원래 함수는 한 글자도 건드리지 않고, 사이드바가 부르는 이름만 바꾸면 되기 때문에 되돌리기도 쉽습니다. 아래는 savePutaway 를 감싼 예시입니다.
실무에서 특히 중요했던 부분은 세 가지였습니다.
- 여러 사용자가 동시에 저장해도 데이터가 꼬이지 않도록
LockService로 잠그는 것 - 저장 전에 필수 값(주문번호, 품목, 위치 등)과 수량 숫자를 검증하는 것
- 저장이 정상 처리된 행에만 writeLog를 호출해, 실패 건은 로그에 남기지 않는 것
아래 예시는 입고 적치 저장 함수에 로그를 추가한 패턴입니다.
붙여넣을 위치: archiveLog_ 함수 아래. 1편 코드는 그대로 둡니다.
붙여넣은 뒤 할 일: 1편 사이드바(Sidebar.html)에서 .savePutaway(data) 라고 되어 있는 줄을 .savePutawayWithLog(data) 로 한 곳만 바꿉니다.
// 1편의 savePutaway 를 감싸서 이력만 덧붙인다. 1편 코드는 한 글자도 고치지 않는다.
// 사이드바에서 부르는 이름만 savePutaway → savePutawayWithLog 로 바꾸면 된다.
function savePutawayWithLog(data) { // → 적치 저장 + 이력 기록
const container = String((data && data.container) || '').trim(); // → 컨테이너 번호
const model = String((data && data.model) || '').trim(); // → 모델
const before = readInboundStatus_(container); // → 바뀌기 전 상태를 먼저 읽어 둔다
const result = savePutaway(data); // → 1편의 저장 함수를 그대로 실행
// (여기서 오류가 나면 로그도 남지 않는다)
writeLog_( // → 저장이 끝난 뒤에만 기록
'PUTAWAY_SAVE', // → ACTION
container + ' / ' + model, // → TARGET
'STATUS=' + (before || '(빈칸)'), // → BEFORE
'STATUS=' + readInboundStatus_(container) + // → AFTER
', LOC=' + String((data && data.location) || '') +
', QTY=' + String((data && data.qty) || '')
);
return result; // → 사이드바에 원래 결과를 그대로 돌려준다
}
// INBOUND에서 이 컨테이너의 현재 상태값(F열)을 읽는다. BEFORE/AFTER 에 쓴다.
function readInboundStatus_(container) { // → 상태 읽기
if (!container) return ''; // → 값이 없으면 빈 문자열
const sh = SpreadsheetApp.getActive().getSheetByName('INBOUND'); // → 1편의 입고 시트
if (!sh || sh.getLastRow() < 2) return ''; // → 없거나 비어 있으면
const rows = sh.getRange(2, 1, sh.getLastRow() - 1, 6).getValues(); // → A~F열
for (let i = 0; i < rows.length; i++) { // → 한 줄씩 찾기
if (String(rows[i][0]).trim() === container) { // → A열 CONTAINER가 같으면
return String(rows[i][5] || '').trim(); // → F열 STATUS 반환
}
}
return ''; // → 못 찾으면 빈 문자열
}
function onOpen() { // → 시트를 열 때 자동 실행
const menu = SpreadsheetApp.getUi().createMenu('창고 도구'); // → 시리즈 공용 메뉴
addLogMenu_(menu); // → 이 편 항목 붙이기
menu.addToUi(); // → 시트에 붙이기
}
function addLogMenu_(menu) { // → 합칠 때는 이 함수만 부른다
menu.addItem('오래된 로그 보관', 'archiveLog_'); // → 메뉴 항목
}동작 확인 방법: 사이드바에서 적치를 한 건 저장해 보세요. LOCATIONS 에 재고 한 줄이 쌓이고 INBOUND 상태가 PUTAWAY_DONE 으로 바뀌는 것은 1편과 똑같고, 여기에 더해 LOG 시트에 PUTAWAY_SAVE 한 줄이 남습니다. BEFORE 에 STATUS=(빈칸), AFTER 에 STATUS=PUTAWAY_DONE, LOC=…, QTY=… 이 들어 있으면 구글시트 입출고 이력 추적이 제대로 붙은 것입니다.
여러 편을 한 프로젝트에 합칠 때: 앞 편 코드와 같은 Apps Script 프로젝트에 그냥 붙이면 onOpen()이 두 번 선언되어 나중 것만 살아남고 앞 편 메뉴가 사라집니다. 그래서 이 시리즈는 메뉴 이름을 창고 도구 하나로 통일하고, 각 편의 항목을 addLogMenu_(menu) 같은 도우미 함수로 떼어 두었습니다. 합칠 때는 이 편의 onOpen()만 지우고, 1편의 onOpen() 안에 addLogMenu_(menu); 한 줄만 더하면 됩니다. 그러면 창고 도구 메뉴 하나에 입고·출고·재고·랙 배치도·바코드 스캔·Load 서머리·대시보드·작업 이력이 모두 모입니다. 출고 저장 함수에서도 같은 패턴으로 writeLog_('PICKING_SAVE', …)를 호출하도록 확장하면 됩니다.
실무 팁 — 개인정보, 성능, 오류 메시지를 다루는 방법
구글시트 사용자 작업 기록 자동화를 실제 업무에 적용하면서 느낀 부분을 정리하면 다음과 같습니다.
첫째, 개인정보 최소화가 중요합니다. USER 칼럼에 이메일 전체를 남기면 추적은 편하지만, 외부 공유 리포트에 LOG 시트가 포함될 수 있는 환경이라면 부담이 될 수 있습니다. 그런 경우 USER_MODE 를 ID 로 두면 2편에서 만든 SETTINGS 시트의 A열(사내 ID)·B열(이메일) 매핑표를 읽어 LOG에는 ID만 남기는 구성을 추천합니다. 문제가 생겼을 때만 관리자가 매핑표를 통해 실제 사용자를 확인하는 방식입니다.
둘째, LOG 시트는 조회 전용 + 숨김 처리를 권장합니다. 현장 담당자가 실수로 LOG 시트에서 행을 지우거나 수정하면, 나중에 원인 분석이 어려워집니다. LOG 시트는 숨김으로 두고, 조회용 시트에서 FILTER 함수나 Apps Script로 필요한 구간만 가져오는 것이 안전했습니다. 예를 들어 주문 상세 화면 옆에 “이 주문 관련 LOG만 보여 주는” 작은 표를 만들어 두면, 구글시트 출고 서머리 자동화 방법 같은 리포트와 자연스럽게 연동할 수 있습니다.
셋째, 로그를 너무 세분화하지 않는 것이 성능 유지에 도움이 됩니다. 버튼 한 번 클릭에 로그 여러 줄을 남기기 시작하면 행 수가 기하급수적으로 늘어납니다. 일반적인 재고·출고 업무에서는 “버튼 한 번 = 로그 한 줄”이 가장 관리하기 좋았습니다. 예외적으로 대량 조정 작업처럼 중요한 이벤트에는 BEFORE/AFTER를 조금 더 상세하게 남기되, 여전히 한 줄로 요약하는 수준에서 잡는 것을 권장합니다.
마지막으로, 오류는 숨기지 말고 행 번호와 이유를 함께 알려 주는 것이 현장에서 가장 반응이 좋았습니다. 1편·2편의 저장 함수가 이미 “수량은 1 이상의 숫자여야 합니다”처럼 이유를 붙여 오류를 던지고 있고, 감싸는 함수는 그 오류를 그대로 통과시킵니다. 저장이 실패하면 writeLog_ 줄까지 가지 못하므로 실패한 작업이 이력에 남지 않는다는 점도 함께 기억해 두시면 좋습니다. 개인적으로는 저장 실패 건을 LOG 시트에 따로 남기기보다는, 화면 팝업으로만 안내하고 실제 저장이 이루어진 건만 LOG에 쌓는 편이 분석과 관리에 더 깔끔했습니다.
맺음말 — 오늘 LOG 시트부터 만들어 두기
구글시트로 재고를 운영하다 보면 “이 값, 누가 언제 바꿨습니까?”라는 질문은 피하기 어렵습니다. LOG 시트와 Apps Script를 이용해 자동으로 작업 이력을 남기기 시작하면, 이런 질문에 몇 초 만에 답할 수 있고, 담당자 간 책임 공방도 크게 줄어듭니다. 이번 글에서 정리한 구글시트 작업 이력 자동 기록 방법은 한 번 구조만 잡아 두면, 이후 입고·출고·조정 등 어떤 저장 함수에도 writeLog_ 한 줄을 추가하는 것만으로 손쉽게 확장할 수 있습니다.
바로 지금 할 수 있는 행동은 하나입니다. 재고 관리 구글시트를 열어 LOG 시트를 만들고, 이 글의 LOG_CONFIG, createLogSheet_, getLogUser_, writeLog_, archiveLog_ 코드를 차례로 붙여 넣은 뒤 testWriteLog_() 를 한 번 실행해 보시기 바랍니다. LOG 시트에 첫 테스트 행이 찍히는 순간부터, 그 시트는 더 이상 “누가 바꿨는지 애매한 재고표”가 아니라, 언제든 근거를 되짚을 수 있는 업무 시스템이 됩니다.