본문 바로가기
AppSheet

구글 앱스크립트 10차시 - 실전 프로젝트와 배포

by 에버리치60 2025. 12. 20.

구글 앱스크립트 10차시 - 실전 프로젝트와 배포

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);
}