
10차시: 실전 프로젝트와 배포
완전한 업무 자동화 프로젝트 만들기와 보안 관리
📚 학습 목표
- 실전 프로젝트: 종합 업무 관리 시스템 구축하기
- 스크립트 속성(Properties)으로 설정 관리하기
- 에러 처리와 로깅 시스템 구축하기
- 보안 관리와 베스트 프랙티스 배우기
🎯 실전 프로젝트: 종합 업무 관리 시스템
1
프로젝트 구조 설계
모든 배운 내용을 활용한 완전한 시스템을 만들어봅시다.
시스템 기능:
• 직원 관리 (등록, 수정, 검색)
• 근태 관리 (출퇴근 기록)
• 급여 계산 및 명세서 발송
• 일일/주간/월간 보고서 자동 생성
• 자동 백업 및 알림
• 직원 관리 (등록, 수정, 검색)
• 근태 관리 (출퇴근 기록)
• 급여 계산 및 명세서 발송
• 일일/주간/월간 보고서 자동 생성
• 자동 백업 및 알림
🚀 실습 1: 설정 관리 시스템
2
Properties Service로 설정 저장
중요한 설정값을 안전하게 저장하고 관리합니다:
설정초기화() 함수를 먼저 실행하여 기본 설정을 저장하세요!
/**
* 설정 관리 시스템
*/
// 설정 초기화
function 설정초기화() {
let properties = PropertiesService.getScriptProperties();
// 기본 설정값
let 설정 = {
'회사명': '우리회사',
'관리자이메일': Session.getActiveUser().getEmail(),
'근무시간_시작': '09:00',
'근무시간_종료': '18:00',
'기본급여': '2500000',
'백업주기': '매일',
'보고서발송시간': '09:00'
};
properties.setProperties(설정);
Logger.log("설정이 초기화되었습니다.");
SpreadsheetApp.getActiveSpreadsheet().toast(
"기본 설정이 저장되었습니다!",
"완료",
3
);
}
// 설정 읽기
function 설정읽기(키) {
let properties = PropertiesService.getScriptProperties();
return properties.getProperty(키);
}
// 설정 쓰기
function 설정쓰기(키, 값) {
let properties = PropertiesService.getScriptProperties();
properties.setProperty(키, 값);
}
// 모든 설정 보기
function 설정목록보기() {
let properties = PropertiesService.getScriptProperties();
let 모든설정 = properties.getProperties();
Logger.log("=== 현재 설정 ===");
for (let 키 in 모든설정) {
Logger.log(키 + ": " + 모든설정[키]);
}
return 모든설정;
}
// 설정 편집 UI
function 설정편집창() {
let ui = SpreadsheetApp.getUi();
let 현재설정 = 설정목록보기();
let 메시지 = "현재 설정:\n\n";
for (let 키 in 현재설정) {
메시지 += 키 + ": " + 현재설정[키] + "\n";
}
ui.alert('시스템 설정', 메시지, ui.ButtonSet.OK);
}
3
에러 처리와 로깅 시스템
체계적인 에러 처리와 로그 관리 시스템을 만듭니다:
로그시트초기화() 함수를 실행하여 로그 시스템을 준비하세요!
/**
* 로깅 시스템
*/
// 로그 시트 생성 및 초기화
function 로그시트초기화() {
let ss = SpreadsheetApp.getActiveSpreadsheet();
let 로그시트 = ss.getSheetByName("시스템로그");
if (!로그시트) {
로그시트 = ss.insertSheet("시스템로그");
} else {
로그시트.clear();
}
// 헤더
로그시트.getRange("A1:E1").setValues([
["시간", "유형", "함수명", "메시지", "상세"]
]);
로그시트.getRange("A1:E1")
.setBackground("#4285f4")
.setFontColor("#ffffff")
.setFontWeight("bold");
로그시트.setFrozenRows(1);
로그시트.autoResizeColumns(1, 5);
Logger.log("로그 시트가 초기화되었습니다.");
}
// 로그 기록 함수
function 로그기록(유형, 함수명, 메시지, 상세) {
try {
let ss = SpreadsheetApp.getActiveSpreadsheet();
let 로그시트 = ss.getSheetByName("시스템로그");
if (!로그시트) {
로그시트초기화();
로그시트 = ss.getSheetByName("시스템로그");
}
let 시간 = Utilities.formatDate(
new Date(),
"Asia/Seoul",
"yyyy-MM-dd HH:mm:ss"
);
let 마지막행 = 로그시트.getLastRow();
로그시트.getRange(마지막행 + 1, 1, 1, 5).setValues([
[시간, 유형, 함수명, 메시지, 상세 || ""]
]);
// 유형별 색상
let 색상 = "#ffffff";
if (유형 === "ERROR") 색상 = "#ffcdd2";
else if (유형 === "WARNING") 색상 = "#fff9c4";
else if (유형 === "SUCCESS") 색상 = "#c8e6c9";
로그시트.getRange(마지막행 + 1, 1, 1, 5).setBackground(색상);
} catch (e) {
Logger.log("로그 기록 실패: " + e.message);
}
}
// 안전한 함수 실행 래퍼
function 안전실행(함수명, 함수, ...인자) {
try {
로그기록("INFO", 함수명, "함수 시작");
let 결과 = 함수(...인자);
로그기록("SUCCESS", 함수명, "함수 완료");
return 결과;
} catch (e) {
로그기록("ERROR", 함수명, e.message, e.stack);
// 관리자에게 에러 알림
let 관리자이메일 = 설정읽기('관리자이메일');
if (관리자이메일) {
GmailApp.sendEmail(
관리자이메일,
"⚠️ 시스템 오류 발생",
"함수: " + 함수명 + "\n" +
"오류: " + e.message + "\n" +
"시간: " + new Date()
);
}
throw e;
}
}
// 사용 예시
function 테스트함수() {
안전실행("테스트함수", function() {
Logger.log("테스트 실행 중...");
// 실제 작업 수행
return "완료";
});
}💼 실습 2: 종합 업무 관리 시스템
4
직원 관리 시스템
직원 정보를 관리하는 시스템을 만듭니다:
직원시트초기화() 후 직원등록() 함수로 직원을 등록하세요!
/**
* 직원 관리 시스템
*/
// 직원 시트 초기화
function 직원시트초기화() {
let ss = SpreadsheetApp.getActiveSpreadsheet();
let 직원시트 = ss.getSheetByName("직원명부");
if (!직원시트) {
직원시트 = ss.insertSheet("직원명부");
} else {
직원시트.clear();
}
// 헤더
let 헤더 = [["사번", "이름", "부서", "직급", "이메일",
"입사일", "급여", "상태"]];
직원시트.getRange("A1:H1").setValues(헤더);
직원시트.getRange("A1:H1")
.setBackground("#1a73e8")
.setFontColor("#ffffff")
.setFontWeight("bold")
.setHorizontalAlignment("center");
직원시트.setFrozenRows(1);
직원시트.autoResizeColumns(1, 8);
로그기록("INFO", "직원시트초기화", "직원 시트 생성 완료");
}
// 직원 등록
function 직원등록() {
let ui = SpreadsheetApp.getUi();
// 사용자 입력 받기
let 이름응답 = ui.prompt('직원 등록', '이름:', ui.ButtonSet.OK_CANCEL);
if (이름응답.getSelectedButton() !== ui.Button.OK) return;
let 부서응답 = ui.prompt('직원 등록', '부서:', ui.ButtonSet.OK_CANCEL);
if (부서응답.getSelectedButton() !== ui.Button.OK) return;
let 이메일응답 = ui.prompt('직원 등록', '이메일:', ui.ButtonSet.OK_CANCEL);
if (이메일응답.getSelectedButton() !== ui.Button.OK) return;
// 데이터 저장
let ss = SpreadsheetApp.getActiveSpreadsheet();
let 직원시트 = ss.getSheetByName("직원명부");
if (!직원시트) {
직원시트초기화();
직원시트 = ss.getSheetByName("직원명부");
}
let 마지막행 = 직원시트.getLastRow();
let 사번 = "EMP" + String(마지막행).padStart(4, '0');
let 입사일 = Utilities.formatDate(new Date(), "Asia/Seoul", "yyyy-MM-dd");
let 기본급여 = 설정읽기('기본급여') || "2500000";
직원시트.getRange(마지막행 + 1, 1, 1, 8).setValues([[
사번,
이름응답.getResponseText(),
부서응답.getResponseText(),
"사원",
이메일응답.getResponseText(),
입사일,
기본급여,
"재직"
]]);
로그기록("SUCCESS", "직원등록",
이름응답.getResponseText() + " 등록 완료");
ui.alert('등록 완료',
'직원이 등록되었습니다.\n사번: ' + 사번,
ui.ButtonSet.OK);
}
// 직원 검색
function 직원검색() {
let ui = SpreadsheetApp.getUi();
let 검색어응답 = ui.prompt('직원 검색',
'이름 또는 사번:',
ui.ButtonSet.OK_CANCEL);
if (검색어응답.getSelectedButton() !== ui.Button.OK) return;
let 검색어 = 검색어응답.getResponseText();
let ss = SpreadsheetApp.getActiveSpreadsheet();
let 직원시트 = ss.getSheetByName("직원명부");
if (!직원시트) {
ui.alert('오류', '직원 명부가 없습니다.', ui.ButtonSet.OK);
return;
}
let 데이터 = 직원시트.getDataRange().getValues();
let 결과 = [];
for (let i = 1; i < 데이터.length; i++) {
let 행 = 데이터[i];
if (행[0].toString().includes(검색어) ||
행[1].toString().includes(검색어)) {
결과.push(행);
}
}
if (결과.length === 0) {
ui.alert('검색 결과', '검색 결과가 없습니다.', ui.ButtonSet.OK);
} else {
let 메시지 = "검색 결과 (" + 결과.length + "건):\n\n";
for (let i = 0; i < 결과.length; i++) {
메시지 += "사번: " + 결과[i][0] + "\n";
메시지 += "이름: " + 결과[i][1] + "\n";
메시지 += "부서: " + 결과[i][2] + "\n\n";
}
ui.alert('검색 결과', 메시지, ui.ButtonSet.OK);
}
}
5
근태 관리 시스템
출퇴근 기록을 관리하는 시스템을 만듭니다:
근태시트초기화() 후 출근체크()와 퇴근체크()로 근태를 관리하세요!
/**
* 근태 관리 시스템
*/
// 근태 시트 초기화
function 근태시트초기화() {
let ss = SpreadsheetApp.getActiveSpreadsheet();
let 근태시트 = ss.getSheetByName("근태기록");
if (!근태시트) {
근태시트 = ss.insertSheet("근태기록");
} else {
근태시트.clear();
}
let 헤더 = [["날짜", "사번", "이름", "출근시간",
"퇴근시간", "근무시간", "상태"]];
근태시트.getRange("A1:G1").setValues(헤더);
근태시트.getRange("A1:G1")
.setBackground("#4285f4")
.setFontColor("#ffffff")
.setFontWeight("bold");
근태시트.setFrozenRows(1);
}
// 출근 기록
function 출근체크() {
let ui = SpreadsheetApp.getUi();
let 사번응답 = ui.prompt('출근', '사번을 입력하세요:', ui.ButtonSet.OK_CANCEL);
if (사번응답.getSelectedButton() !== ui.Button.OK) return;
let 사번 = 사번응답.getResponseText();
// 직원 확인
let ss = SpreadsheetApp.getActiveSpreadsheet();
let 직원시트 = ss.getSheetByName("직원명부");
let 직원데이터 = 직원시트.getDataRange().getValues();
let 직원정보 = null;
for (let i = 1; i < 직원데이터.length; i++) {
if (직원데이터[i][0] === 사번) {
직원정보 = 직원데이터[i];
break;
}
}
if (!직원정보) {
ui.alert('오류', '등록되지 않은 사번입니다.', ui.ButtonSet.OK);
return;
}
// 근태 기록
let 근태시트 = ss.getSheetByName("근태기록");
if (!근태시트) {
근태시트초기화();
근태시트 = ss.getSheetByName("근태기록");
}
let 현재시간 = new Date();
let 날짜 = Utilities.formatDate(현재시간, "Asia/Seoul", "yyyy-MM-dd");
let 시간 = Utilities.formatDate(현재시간, "Asia/Seoul", "HH:mm");
let 마지막행 = 근태시트.getLastRow();
근태시트.getRange(마지막행 + 1, 1, 1, 7).setValues([[
날짜,
사번,
직원정보[1],
시간,
"",
"",
"출근"
]]);
로그기록("INFO", "출근체크", 직원정보[1] + " 출근 기록");
ui.alert('출근 완료',
직원정보[1] + '님\n출근 시간: ' + 시간,
ui.ButtonSet.OK);
}
// 퇴근 기록
function 퇴근체크() {
let ui = SpreadsheetApp.getUi();
let 사번응답 = ui.prompt('퇴근', '사번을 입력하세요:', ui.ButtonSet.OK_CANCEL);
if (사번응답.getSelectedButton() !== ui.Button.OK) return;
let 사번 = 사번응답.getResponseText();
let ss = SpreadsheetApp.getActiveSpreadsheet();
let 근태시트 = ss.getSheetByName("근태기록");
// 오늘 출근 기록 찾기
let 데이터 = 근태시트.getDataRange().getValues();
let 오늘 = Utilities.formatDate(new Date(), "Asia/Seoul", "yyyy-MM-dd");
let 찾은행 = -1;
for (let i = 데이터.length - 1; i >= 1; i--) {
if (데이터[i][0] === 오늘 && 데이터[i][1] === 사번) {
찾은행 = i + 1;
break;
}
}
if (찾은행 === -1) {
ui.alert('오류', '오늘 출근 기록이 없습니다.', ui.ButtonSet.OK);
return;
}
let 현재시간 = new Date();
let 퇴근시간 = Utilities.formatDate(현재시간, "Asia/Seoul", "HH:mm");
근태시트.getRange(찾은행, 5).setValue(퇴근시간);
근태시트.getRange(찾은행, 7).setValue("퇴근");
// 근무시간 계산
let 출근시간 = 데이터[찾은행-1][3];
let 근무시간 = 계산근무시간(출근시간, 퇴근시간);
근태시트.getRange(찾은행, 6).setValue(근무시간 + "시간");
로그기록("INFO", "퇴근체크", 데이터[찾은행-1][2] + " 퇴근 기록");
ui.alert('퇴근 완료',
'퇴근 시간: ' + 퇴근시간 + '\n' +
'근무 시간: ' + 근무시간 + '시간',
ui.ButtonSet.OK);
}
function 계산근무시간(출근, 퇴근) {
let 출근분 = parseInt(출근.split(':')[0]) * 60 + parseInt(출근.split(':')[1]);
let 퇴근분 = parseInt(퇴근.split(':')[0]) * 60 + parseInt(퇴근.split(':')[1]);
return ((퇴근분 - 출근분) / 60).toFixed(1);
}
6
통합 메뉴 시스템
모든 기능을 하나의 메뉴로 통합합니다:
/**
* 통합 메뉴 시스템
*/
function onOpen() {
let ui = SpreadsheetApp.getUi();
ui.createMenu('💼 업무 관리 시스템')
.addSubMenu(ui.createMenu('👥 직원 관리')
.addItem('직원 시트 초기화', '직원시트초기화')
.addItem('직원 등록', '직원등록')
.addItem('직원 검색', '직원검색'))
.addSeparator()
.addSubMenu(ui.createMenu('⏰ 근태 관리')
.addItem('근태 시트 초기화', '근태시트초기화')
.addItem('출근 체크', '출근체크')
.addItem('퇴근 체크', '퇴근체크'))
.addSeparator()
.addSubMenu(ui.createMenu('⚙️ 시스템 설정')
.addItem('설정 초기화', '설정초기화')
.addItem('설정 보기', '설정편집창')
.addItem('로그 시트 초기화', '로그시트초기화'))
.addSeparator()
.addItem('📊 전체 시스템 초기화', '전체시스템초기화')
.addItem('📖 사용 가이드', '사용가이드보기')
.addToUi();
}
// 전체 시스템 초기화
function 전체시스템초기화() {
let ui = SpreadsheetApp.getUi();
let 응답 = ui.alert(
'전체 시스템 초기화',
'모든 시트를 초기화하시겠습니까?\n\n' +
'• 직원 명부\n• 근태 기록\n• 시스템 로그\n• 설정',
ui.ButtonSet.YES_NO
);
if (응답 !== ui.Button.YES) return;
try {
설정초기화();
로그시트초기화();
직원시트초기화();
근태시트초기화();
로그기록("SUCCESS", "전체시스템초기화", "시스템 초기화 완료");
ui.alert('완료',
'모든 시스템이 초기화되었습니다.\n\n' +
'1. 설정 메뉴에서 회사 정보를 입력하세요.\n' +
'2. 직원 등록 메뉴에서 직원을 추가하세요.\n' +
'3. 근태 관리를 시작하세요.',
ui.ButtonSet.OK);
} catch (e) {
로그기록("ERROR", "전체시스템초기화", e.message);
ui.alert('오류', '초기화 중 오류가 발생했습니다.', ui.ButtonSet.OK);
}
}
// 사용 가이드
function 사용가이드보기() {
let ui = SpreadsheetApp.getUi();
let 가이드 =
"📚 업무 관리 시스템 사용 가이드\n\n" +
"【 시작하기 】\n" +
"1. '전체 시스템 초기화' 메뉴를 실행하세요\n" +
"2. 직원 관리 > 직원 등록으로 직원을 추가하세요\n\n" +
"【 근태 관리 】\n" +
"• 출근 시: 근태 관리 > 출근 체크\n" +
"• 퇴근 시: 근태 관리 > 퇴근 체크\n\n" +
"【 자동화 】\n" +
"• 일일 보고서는 매일 자동 발송됩니다\n" +
"• 백업은 자동으로 생성됩니다\n\n" +
"【 문의 】\n" +
"시스템 로그 시트에서 오류를 확인할 수 있습니다.";
ui.alert('사용 가이드', 가이드, ui.ButtonSet.OK);
}'AppSheet' 카테고리의 다른 글
| 명함관리 - 폰으로 찍어서 구글시트에 저장 (3) | 2025.12.24 |
|---|---|
| 구글 앱스크립트 11차시 - 통합 메뉴와 최종 마무리 (4) | 2025.12.21 |
| 구글 앱스크립트 9차시 - 구글 드라이브와 외부 API (3) | 2025.12.20 |
| 구글 앱스크립트 8차시 - 이메일 자동화와 트리거 (1) | 2025.12.20 |
| 구글 앱스크립트 7차시 - 사용자 정의 함수와 메뉴 (2) | 2025.12.20 |