GCP/Apps Script

Apps Script로 Google Sheets에서 보고서 양식의 조회저장 기능 구현

whistory 2022. 10. 11. 10:59
반응형

 

요구사항으로

Google Sheets 를 통해 보고서 형식의 조회/입력 양식 개발건이 들어왔다.

 

 

 

 

조회는 원래 문서번호에 dropdown을 생성하여 변경 시 조회하도록 구성하였으나,

Trigger의 속도가 느려, 문서 번호를 Sidebar에서 선택하는 방식으로 수정하였다.

 

 

 

조회 시 : BigQuery에서 데이터를 select 한 후 각각의 셀에 값 할당.

 

저장 시 : 조회할 영역별로 데이터를 저장.

// 미팅목적
const meetingInfo           = sheet.getRange("E13:BY15").getValues();

insert query문을 string으로 생성하여 저장하도록 구한함

// 미팅목적
for (i in meetingInfo) {
  valueText += meetingInfo[i][0] + '", "'   // 성상
            +  meetingInfo[i][8] + '", "'   // 세부성상
            +  meetingInfo[i][18] + '", "'; // 미팅목적 agenda
}

 

 

1. 보고서를 선택할 수 있는 Sidebar 열기

/**
 * 문서번호 선택 Sidebar 열기
 * @param {string} sheetUrl   = 해당 function을 호출한 sheet의 URL
 */
function getReportData(sheetUrl, docNo) {
  openSideBar(sheetUrl, "doc");
}

2. SideBar 생성

/**
 * Side bar를 열어서 검색
 * @param {string} sheetUrl = 해당 function을 호출한 sheet의 URL
 * @param {string} gubun    = 구분자 ( member / bp )
 */
function openSideBar(sheetUrl, gubun) {
  const masterSheetName = reportListSheetName;
  const title           = "문서 선택";
  const masterRange     = "A11:F";

  const sheet = SpreadsheetApp.openByUrl(sheetUrl).getSheetByName(masterSheetName);
  const data  = sheet.getRange(masterRange + sheet.getLastRow()).getValues();

  var loopHtml = "";
  var param, displayValue; 

  // 문서선택 할 경우, 중복제거하여 리스트 표출
  if ( gubun == "doc" ) {
    const map = new Map();
    for ( const character of data ) {
      map.set(JSON.stringify(character), character);
    }
    const distinctData = [...map.values()];
    for ( j in distinctData ) {
      param        = distinctData[j][0];
      displayValue = `${distinctData[j][0]} / ${common.convertDateFormat(distinctData[j][1], "-")} / ${distinctData[j][5]}`;

      loopHtml += '<div class="itemKeys"><li style=list-style:none;>';
      loopHtml += '<a onClick="selectVal(\\''+param+'\\')">';
      loopHtml += '<font class="itemName" size="2">' + displayValue + '</font></a>';
      loopHtml += '</li></div>';
    }

  // 고객, 직원 선택 시
  } else { 
    for (i in data) {
      if ( gubun == "member" ) {
        param        = data[i][0] + "_" + data[i][1] + "_" + data[i][3] + "_" + data[i][4];
        displayValue = "(" + data[i][0] + ") " + data[i][1] + " " + data[i][3];
      } else if ( gubun == "bp" ) {
        param        = data[i][0] + "_" + data[i][1];
        displayValue = data[i][0] + " (" + data[i][1] + ")";
      }

      loopHtml += '<div class="itemKeys"><li style=list-style:none;>';
      loopHtml += '<a onClick="selectVal(\\''+param+'\\')">';
      loopHtml += '<font class="itemName" size="2">' + displayValue + '</font></a>';
      loopHtml += '</li></div>';
    }
  }

  var html = HtmlService.createHtmlOutput('<html> '
                                        + ' <head> '
                                        + '   <style> '
                                        + '     .itemKeys {padding-bottom:5px;padding-top:5px;cursor: pointer;} '
                                        + '     .itemKeys:hover {background:#ECECEC;padding-bottom:7px;padding-top:7px;cursor: pointer;} '
                                        + '   </style> '
                                        + '   <script type="text/javascript"> '
                                        + '     function closeModal() { google.script.host.close(); }'
                                        + '     function goToSheet(val) { google.script.run.setSelectedValue(val, "' + sheetUrl + '", "' + gubun + '"); }'
                                        + '     function selectVal(val) { '
                                        + '       goToSheet(val); '
                                        + '     } '
                                        + '     function filter() { '
                                        + '     	var value, name, item, i; '
                                        + '     	value = document.getElementById("value").value.toUpperCase().trim(); '
                                        + '     	item = document.getElementsByClassName("itemKeys"); '
                                        + '     	for( i=0; i<item.length; i++ ) { '
                                        + '     		name = item[i].getElementsByClassName("itemName"); '
                                        + '     		if ( name[0].innerHTML.toUpperCase().indexOf(value) > -1 ) { '
                                        + '     			item[i].style.display = "flex"; '
                                        + '     		} else { '
                                        + '     			item[i].style.display = "none"; '
                                        + '     		} '
                                        + '     	} '
                                        + '     } '
                                        + '   </script> '
                                        + ' </head> '
                                        + ' <body> '
                                        + '   <div style=position:fixed;background-color:white;width:100%;top:0;line-height:2rem;>'
                                        + '     <input onkeyup="filter()" type="text" id="value" placeholder="검색어 입력" style="width: 150px;"> '
                                        + '     <input type="button" value="닫기" onclick="closeModal()" /> '
                                        + '   </div> '
                                        + '   <br />'
                                        + '   <div>'
                                        +       loopHtml 
                                        + '   </div>'
                                        + ' </body> '
                                        + ' </html> ')
                      .setTitle(title)
                      .setWidth(500).setHeight(800);

  SpreadsheetApp.getUi().showSidebar(html);
}

 

3. Sidebar 닫은 후 event 처리

/**
 * 사이드바 선택 후 처리
 * @param {string} val        = 선택된 값
 * @param {string} sheetUrl   = 해당 function을 호출한 sheet의 URL
 * @param {string} gubun      = 구분자 ( member / bp )
 * @param {string} plant_code = 해당 function을 호출한 sheet의 사업장코드
 */
function setSelectedValueFromClient(val, sheetUrl, gubun, plant_code) {
  const html = HtmlService.createHtmlOutput("<script>google.script.host.close();</script>");
  SpreadsheetApp.getUi().showSidebar(html);

  const sheetName = reportSheetName;
  const sheet     = SpreadsheetApp.openByUrl(sheetUrl).getSheetByName(sheetName);
  const vals      = val.split("_");

  if ( gubun == "doc" ) {
    setReportData(sheetUrl, null, val, plant_code);
  } 
}

4. 리포트 데이터 조회해서 셀에 값 채우기

const reportSheetName = "reportSheet";
const tb_report       = "reportTable";

/**
 * 데이터 조회해서 값 채우기
 * @param {string} sheetUrl    = 해당 function을 호출한 sheet의 URL
 * @param {obj}    spreadSheet = 해당 function을 호출한 sheet
 * @param {string} docNo       = 조회할문서번호
 * @param {string} plant_code  = 사업장번호
 */
function setReportData(sheetUrl, spreadSheet, docNo, plant_code) {
  try {
    // 조회를 위한 변수 설정  
    const sheetName = reportSheetName;
    const datasetId = GLOBAL_DATASET_ID;
    const tableId   = tb_report;

    var sheet;
    if ( common.isNull(spreadSheet) ) {
      sheet = SpreadsheetApp.openByUrl(sheetUrl).getSheetByName(sheetName);
    } else {
      sheet = spreadSheet;
    }
    
    const columns     = 'doc_num, write_date, check_date, ...  mti_memo';
    const whereState  = 'doc_num = "' + docNo + '"';
    const queryString = 'SELECT ' + columns + ' FROM `'
                      +  GLOBAL_PROJECT_ID + "." + datasetId + "." + tableId + '` '
                      + ' WHERE status = "Y" AND ' + whereState ;

    const getValues = common.queryExcute(queryString);
    const values    = getValues.f;

		// 값채우기 시작

    sheet.getRange("AR2:BB2").setValue(values[0].v);    // 문서번호
    sheet.getRange("BC2:BI2").setValue(values[1].v);    // 작성일자
    sheet.getRange("BJ2:BP2").setValue(values[2].v);    // 승인일자
    sheet.getRange("BR2:BY2").setValue(values[3].v);    // 팀장

			...

    sheet.getRange("E18:R18").setValue(values[18].v);   // 고객기본정보 - 지역
    sheet.getRange("S18:AR18").setValue(values[19].v);  // 고객기본정보 - 고객명
    sheet.getRange("AS18:BA18").setValue(values[20].v); // 고객기본정보 - 고객코드
    sheet.getRange("BB18:BI18").setValue(values[21].v); // 고객기본정보 - 고객유형

			... 
  
    sheet.getRange("A79:BY82").setValue(values[240].v); // 팀장검토결과

		// 값채우기 끝

    // number format  
    const formatInfo = ['AB25:AF34'
                          , 'AH25:AL34'
                          , 'AX25:BB34'
                          , 'BM25:BQ34'
                          , 'AE44:AI48'
                          , 'AK44:AO48'
                          , 'AR44:AU48'
                          , 'AY70:BC74'
                          , 'BE70:BI74'
                        ];

    for ( var i in formatInfo ) {
      const applyRange = formatInfo[i];
      sheet.getRange(applyRange).setNumberFormat("#,##0").setHorizontalAlignment("right");
    }
    
    const rangeUnitTon = ['AG25:AG34'   // 고객별거래현황 - 총배출량
                          , 'AM25:AM34' // 거래량 - 총배출량
                          , 'AJ44:AJ48' // 가격제안 - 반입량
                          , 'BD70:BD74' // 경쟁영업정보 - 반입량
                          ];
    const rangeUnitWonPerTon = ['BC25:BD34' // 고객별거래현황 - 처리단가
                              , 'BR25:BS34' // 고객별거래현황 - 운반단가
                              , 'AP44:AQ48' // 가격제안 - 반입단가
                              , 'BJ70:BK74' // 경쟁영업정보 - 반입단가
                              ];
    const rangeUnitYear = "AY44:AY48";    // 가격제안 - 계약기간

    if ( values[21].v == "배출처" ) {
      const nextCell          = sheet.getRange("BJ18:BQ18");  // 기존신규
      const nextNextCell      = sheet.getRange("BR18:BY18");  // 영업단계
      const validationRange1  = "X5:X60";                     // 기존신규
      const validationRange2  = "Y5:Y60";                     // 영업단계
      const masterSheet       = SpreadsheetApp.openByUrl(sheetUrl).getSheetByName(masterSheetName);
      const targetRange1      = masterSheet.getRange(validationRange1);
      const targetRange2      = masterSheet.getRange(validationRange2);
      const validationRule1   = SpreadsheetApp.newDataValidation().requireValueInRange(targetRange1).build();
      const validationRule2   = SpreadsheetApp.newDataValidation().requireValueInRange(targetRange2).build();
      nextCell.setDataValidation(validationRule1);
      nextNextCell.setDataValidation(validationRule2);
    }
    // 단위 세팅
    sheet.getRangeList(rangeUnitTon).setValue("톤");
    sheet.getRangeList(rangeUnitWonPerTon).setValue("원/톤");
    sheet.getRange(rangeUnitYear).setValue("년");
  } catch (error) {
    var msg01 = error.message;
    common.alertMessage("❌ Error", "Error Message : " + msg01);
    Logger.log(msg01);
  }
}

5. 리포트 저장하기

/**
 * 저장하기
 * @param {string} sheetUrl   = 해당 function을 호출한 sheet의 URL
 * @param {string} plant_code = 해당 function을 호출한 sheet의 사업장코드
 */
function saveReportData(sheetUrl, plant_code) {
  try {
    const sheetName = reportSheetName;
    const datasetId = GLOBAL_DATASET_ID;
    const tableId   = tb_report;
    const sheet     = SpreadsheetApp.openByUrl(sheetUrl).getSheetByName(sheetName);

    // 작성자 정보 필수값 체크
    const writerInfo = sheet.getRange("E7:AA7").getValues();
    if ( common.isNull(writerInfo[0][0]) )  {
      common.alertMissParam("작성자 정보");
      openSideBar(sheetUrl, "member");
      return;
    }
    
    // 고객명 필수값 체크
    const customerInfoUp = sheet.getRange("E18:BY18").getValues();
    if ( common.isNull(customerInfoUp[0][14]) )  {
      common.alertMissParam("고객 정보");
      openSideBar(sheetUrl, "bp");
      return;
    }
   
    // 미팅목적
    const meetingInfo           = sheet.getRange("E13:BY15").getValues(); 
    // 고객기본정보 아래
    const customerInfoDown      = sheet.getRange("E22:BY22").getValues();
    // 고객별 거래현황
    const customerDealInfo      = sheet.getRange("E25:BY34").getValues(); 
    // 고객진단정보
    const customerDiagnosis     = sheet.getRange("E37:BY37").getValues();
    // 가격제안
    const priceOffer            = sheet.getRange("E44:BY48").getValues();
    // 가격외제안
    const priceOfferOther       = sheet.getRange("E51:BY51").getValues();
    // 후속계획
    const followPlan            = sheet.getRange("E57:BY58").getValues();
    // 경쟁업체정보
    const competitorInfo        = sheet.getRange("E64:BY66").getValues();
    // 경쟁영업정보
    const competitiveSalesInfo  = sheet.getRange("E70:BY74").getValues();
    // 팀장 검토 결과
    const leaderComment         = sheet.getRange("A79:BY82").getValues();

    // 문서번호
    var docNo       = sheet.getRange("AR2:BB2").getValue();
    // 작성일자
    const saveTime  = common.getDateTime("BQ");
    var write_date  = saveTime.substring(0, 10);
    var base_year   = write_date.substring(0,4);
    // 로그인유저 정보
    const userInfo  = common.getUserInfo();

    var values;

    // 수정일 경우
    if ( !common.isNull(docNo) ) {

      var res = common.confirmMessage("⚠️확인", docNo + " 를 수정하시겠습니까?");
      if ( res == false ) {
        common.alertMessage("⚠️알림", "저장을 취소하였습니다.");
        return;
      }

      const columns     = "write_date, check_date, checker, create_dt, create_user";
      const whereState  = 'doc_num = "'+docNo+'"';
      const queryString = 'SELECT ' + columns + ' FROM `'
                        +  GLOBAL_PROJECT_ID + "." + datasetId + "." + tableId + '` '
                        + ' WHERE status = "Y" AND ' + whereState ;

      const getValues = common.queryExcute(queryString);
      values          = getValues.f;

      // update
      const updateQuery = 'UPDATE `' + GLOBAL_PROJECT_ID + "." + datasetId + "." + tableId + '` SET  '
                        + ' status = "N" '
                        + ' , update_dt ="' + saveTime + '" '
                        + ' , update_user ="' + userInfo.userId + '" '
                        + ' WHERE status = "Y" AND ' + whereState ;

      const updateResult = common.queryExcute(updateQuery);
      if ( !updateResult ) {
        common.alertMessage("❌ 실패", "데이터 처리 실패. 관리자 문의 필요.");
        return;
      }
    }

    var queryString = 'SELECT IFNULL(MAX(CAST(SUBSTR(doc_num, 9, 4) AS INT64)) + 1, 1) AS maxno FROM `' 
                    +  GLOBAL_PROJECT_ID + "." + datasetId + "." + tableId 
                    + '` WHERE status = "Y" AND plant_code = "' + plant_code + '" AND base_year = "' + base_year + '"';
    const maxDocNo = common.queryExcute(queryString);                    

    const insertState = "INSERT INTO `" + GLOBAL_PROJECT_ID + "." + datasetId + "." + tableId + '` VALUES '

    const plantName = plant_code=="AB"  ? "A사업장" : 
                     (plant_code=="AC"  ? "B사업장" : 
                     (plant_code=="AD"  ? "C사업장" : 
                     (plant_code=="AA"  ? "D사업장" : 
                                            "E사업장" )));

    var check_date  = "null";
    var checker     = "";
    var update_dt   = "null";
    var update_user = "";
    var create_dt;

    if ( common.isNull(docNo) ) {
      docNo       = plantName + "-" + base_year + "-" + common.lpad(maxDocNo.f[0].v, 4, "0");
      create_dt   = saveTime;
      create_user = userInfo.userId;
    } else {
      write_date  = values[0].v;         // 작성일 엎어치기
      check_date  = common.isNull(values[1].v) ? "null" : `"${values[1].v}"`;  // 확인일 엎어치기
      checker     = values[2].v;         // 확인자 엎어치기
      create_dt   = values[3].v;         // 등록일 엎어치기
      create_user = values[4].v;         // 등록자 엎어치기
      update_dt   = `"${saveTime}"`;     // 수정일
      update_user = userInfo.userId;     // 수정자
    }

    var valueText = '("';

    valueText += docNo      + '", "'    // 신규문서번호
              +  base_year  + '", "'    // 기준년도
              +  plant_code + '", "'    // 사업장코드
              +  write_date + '", '     // 작성일
              +  check_date + ', "'     // 확인일
              +  checker    + '", "';   // 확인자

    // 작성자정보
    valueText += writerInfo[0][0] + '", "'    // 사번
              +  writerInfo[0][8] + '", "'    // 이름
              +  writerInfo[0][13] + '", "'   // 직책
              +  writerInfo[0][18] + '", "'   // 직급
              +  plantName + ' 사업장", "'; // 소속매립장
  
    // 미팅목적
    for (i in meetingInfo) {
      valueText += meetingInfo[i][0] + '", "'   // 성상
                +  meetingInfo[i][8] + '", "'   // 세부성상
                +  meetingInfo[i][18] + '", "'; // 미팅목적 agenda
    }

    // 고객기본정보 위
    valueText += customerInfoUp[0][0] + '", "'    // 지역
              +  customerInfoUp[0][14] + '", "'   // 고객명
              +  customerInfoUp[0][40] + '", "'   // 고객코드
              +  customerInfoUp[0][49] + '", "'   // 고객유형
              +  customerInfoUp[0][57] + '", "'   // 기존신규
              +  customerInfoUp[0][65] + '", "';  // 영업단계
		...

    // 후속계획
    for (i in followPlan) {
      valueText += (common.isNull(followPlan[i][0]) ? 'null' : `"${common.convertDateFormat(followPlan[i][0], "-")}"`)     + ', "'    // 재방문일정 - DATE
                +  followPlan[i][7] + '", "'      // 목적agenda
                +  followPlan[i][28] + '", "'     // 영업계획
                +  followPlan[i][50] + '", ';     // 지원요구사항
    }
    
		...

    // 팀장 검토 결과
    valueText += leaderComment[0][0] + '", "';  // 팀장검토결과
    
    // 기타
    valueText += 'Y" , "'             // status
              +  create_dt  + '", "'  // create_dt
              +  create_user + '", '  // create_user
              +  update_dt  + ', "'   // create_dt
              +  update_user + '" '   // create_user

    valueText += ')';

    var insertResult = common.queryExcute(insertState + valueText);
    if ( !insertResult ) {
      common.alertMessage("❌ 실패", "데이터 처리 실패. 관리자 문의 필요.");
    } else {
      common.alertMessage("✔️ 성공", "저장에 성공하였습니다.");
      sheet.getRange("AR2:BB2").setValue(docNo);
      sheet.getRange("BC2:BI2").setValue(write_date);

    }
  } catch (error) {
    var msg01 = error.message;
    common.alertMessage("❌ Error", "Error Message : " + msg01);
    Logger.log(msg01);
  }
}

 

반응형