구글 Apps Script로 후원신청서 만들기

반응형

구글 Apps Script로 입력폼을 만들고, 입력된 데이터들이 구글 스프레드 시트에 저장되도록 하는 스크립트를 구현해 보았습니다.

 

Apps Script는 두 개의 파일로 작동합니다.

Code.gs라는 이름의 스크립트 파일과 웹화면을 통해 보여질 index.html이 그것입니다.

 

※ 이 스크립트는 기본적으로 구글 스프레드 시트의 Apps Script를 다룰 줄 아는 사람을 대상으로 하기 때문에 스크립트 적용 이전단계에 대해서는 설명하지 않습니다.

 

Code.gs

 
// 스프레드시트 ID와 시트 이름, 서명이미지 저장 폴더 ID를 변수로 선언
var SPREADSHEET_ID = ' YOUR_SPREADSHEET_ID';
var SHEET_NAME = '시트1';
var FOLDER_ID = ' YOUR_FOLDER_ID';

function doGet() {
  return HtmlService.createHtmlOutputFromFile('index');
}

function sendUserData(name, gender, ssn_a, ssn_b, cphone_n, phone_n, address, e_mail, s_term, s_type, s_money, s_how, s_item, s_item_q, s_unit, s_sector, s_why, s_path, receipt1, receipt2, a_date, signature) {
  var sheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(SHEET_NAME);
  var imageUrl = saveSignatureToDrive(signature, name);
  sheet.appendRow([
    name, gender, ssn_a, ssn_b,
    "'" + cphone_n, // 앞에 작은 따옴표 추가
    "'" + phone_n,  // 앞에 작은 따옴표 추가
    address, e_mail, s_term, s_type, s_money, s_how,
    s_item, s_item_q, s_unit, s_sector, s_why, s_path,
    receipt1, receipt2, a_date, '=IMAGE("' + imageUrl + '")'
  ]);
}

function saveSignatureToDrive(signature, name) {
  var folder = DriveApp.getFolderById(FOLDER_ID);
  var blob = Utilities.newBlob(Utilities.base64Decode(signature.split(',')[1]), 'image/png', name + '_signature.png');
  var file = folder.createFile(blob);
  var fileId = file.getId();
}

 

index.html

<!DOCTYPE html>
<html>
<head>
  <base target="_top">
  <title>부민노인복지관 후원신청서</title>
  <style>
    #sig {
      background-color: #e0e0e0;
      border: 1px solid black;
      width: 500px;
      height: 300px;
    }

    #s_item_q, #ssn_a {
      width: 6em;
    }

    #ssn_b {
      width: 7em;
    }

    #savingMessage {
      display: none;
      position: fixed;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      background-color: white;
      border: 1px solid black;
      padding: 20px;
      z-index: 1000;
    }

  </style>
</head>
<body>
  <p>부민노인복지관 후원신청서</p>
  <form id="donationForm">
    <label>이름</label>
    <input type="text" id="name" name="name" placeholder="이름" required><br>

    <label>성별</label>
    <input type="radio" name="gender" value="남">남자
    <input type="radio" name="gender" value="여">여자<br>

    <label>주민등록번호</label>
    <input type="text" id="ssn_a" name="ssn_a" size="6" maxlength="6" required>-
    <input type="password" id="ssn_b" name="ssn_b" size="7" maxlength="7" required><br>

    <label>휴대전화</label>
    <input type="text" id="cphone_n" name="cphone_n" size="11" maxlength="11"> ※ 숫자만 입력<br>

    <label>일반전화</label>
    <input type="text" id="phone_n" name="phone_n" size="11" maxlength="11"> ※ 숫자만 입력<br>

    <label>주소</label>
    <input type="text" id="address" name="address" size="30"><br>

    <label>이메일</label>
    <input type="text" id="e_mail" name="e_mail" size="30"><br>

    <label>후원방식</label>
    <input type="radio" name="s_term" value="정기">정기
    <input type="radio" name="s_term" value="비정기">비정기<br>

    <label>후원구분</label>
    <input type="radio" name="s_type" value="지정">지정
    <input type="radio" name="s_type" value="비지정">비지정<br>

    <label>후원금</label>
    <input type="text" id="s_money" name="s_money"><br>

    <label>후원방법</label>
    <input type="radio" name="s_how" value="자동이체">자동이체
    <input type="radio" name="s_how" value="현금납부">현금납부
    <input type="radio" name="s_how" value="CMS">CMS 이체
    <input type="radio" name="s_how" value="기타">기타<br>

    <label>후원품</label>
    (품명) <input type="text" id="s_item" name="s_item"> /
    (수량) <input type="number" id="s_item_q" name="s_item_q" size="4"> /
    (단위) <select id="s_unit" name="s_unit">
            <option value="">---</option>
            <option value="개"></option>
            <option value="세트">세트</option>
            <option value="Box">Box</option>  
            <option value="건"></option>  
            <option value="Kg">Kg</option>  
            <option value="명"></option>  
            <option value="포"></option>  
            <option value="g">g(그램)</option>  
            <option value="리터">리터</option>  
            <option value="대"></option>  
            <option value="장"></option>  
            <option value="통"></option>  
            <option value="판"></option>  
            <option value="병"></option>  
            <option value="마리">마리</option>  
            <option value="봉지">봉지</option>  
            <option value="팩"></option>  
            <option value="묶음">묶음</option>  
            <option value="그루">그루</option>  
            <option value="송이">송이</option>  
            <option value="포기">포기</option>  
            <option value="자루">자루</option>  
            <option value="인분">인분</option>  
            <option value="짝"></option>  
            <option value="포대">포대</option>  
            <option value="기타">기타</option>  
    </select><br>

    <label>후원분야</label>
    <select id="s_sector" name="s_sector">
      <option value="">---------</option>
      <option value="밑반찬지원">밑반찬지원</option>
      <option value="저소득노인 무료급식사업">저소득노인 무료급식사업</option>
      <option value="위기 및 취약노인 사업">위기 및 취약노인 사업</option>
      <option value="지역복지 연계사업">지역복지 연계사업</option>
      <option value="명절 정나눔 행사">명절 정나눔 행사</option>
      <option value="기타">기타</option>
    </select><br>

    <label>참여동기</label>
    <select id="s_why" name="s_why">
      <option value="">---------</option>
      <option value="경제적 여유가 있어서">경제적 여유가 있어서</option>
      <option value="불우한 이웃을 돕기위해">불우한 이웃을 돕기위해</option>
      <option value="지역사회복지를 위해">지역사회복지를 위해</option>
      <option value="종교적 신념으로">종교적 신념으로</option>
      <option value="기타">기타</option>
    </select><br>

    <label>참여경로</label>
    <select id="s_path" name="s_path">
      <option value="">---------</option>
      <option value="기존 후원자/자원봉사자 소개를 통해">기존 후원자/자원봉사자 소개를 통해</option>
      <option value="복지관 소식지나 안내물을 통해">복지관 소식지나 안내물을 통해</option>
      <option value="언론매체를 통해">언론매체를 통해(TV, 라디오, 신문 등)</option>
      <option value="특정 행사 및 기념일 축하">특정 행사 및 기념일 축하</option>
      <option value="기타">기타</option>
    </select><br>

    <label>후원금영수증 발급</label><br>
      <input type="checkbox" name="receipt1" value="후원금영수증 필요">후원금 영수증 필요<br>
      <input type="checkbox" name="receipt2" value="국세청 연말정산 간소화 후원내역 제공 동의">국세청 연말정산 간소화 후원내역 제공 동의 <br>

    <label>신청일자</label>
    <input type="date" id="a_date" name="a_date"><br>

    <label>서명</label><br>
    <canvas id="sig"></canvas><br>
    <button type="button" id="clearSig">서명 지우기</button><br><br>

    <button type="button" onclick="submitData()">제출</button>
  </form>

  <div id="savingMessage">저장 중입니다...</div>

  <script>
    var signaturePad;

    function setupSignatureBox() {
      var canvas = document.getElementById("sig");
      canvas.width = 500;  // 명시적으로 크기 설정
      canvas.height = 300; // 명시적으로 크기 설정
      signaturePad = new SignaturePad(canvas);
    }

    function clearSignature() {
      signaturePad.clear();
    }

    function submitData() {
      const form = document.getElementById('donationForm');
      const formData = new FormData(form);

      // 필수 입력 필드 확인
      if (!formData.get('name') || !formData.get('ssn_a') || !formData.get('ssn_b') || !formData.get('cphone_n') || signaturePad.isEmpty()) {
        alert('성명, 주민등록번호, 서명은 필수 입력 항목입니다.');
        return;
      }

      const data = {
        name: formData.get('name'),
        gender: formData.get('gender'),
        ssn_a: formData.get('ssn_a'),
        ssn_b: formData.get('ssn_b'),
        cphone_n: formData.get('cphone_n'),
        phone_n: formData.get('phone_n'),
        address: formData.get('address'),
        e_mail: formData.get('e_mail'),
        s_term: formData.get('s_term'),
        s_type: formData.get('s_type'),
        s_money: formData.get('s_money'),
        s_how: formData.get('s_how'),
        s_item: formData.get('s_item'),
        s_item_q: formData.get('s_item_q'),
        s_unit: formData.get('s_unit'),
        s_sector: formData.get('s_sector'),
        s_why: formData.get('s_why'),
        s_path: formData.get('s_path'),
        receipt1: formData.get('receipt1') ? '필요' : '불필요',
        receipt2: formData.get('receipt2') ? '동의' : '미동의',
        a_date: formData.get('a_date'),
        signature: signaturePad.toDataURL()
      };

      document.getElementById('savingMessage').style.display = 'block'; // 저장 중 메시지 표시

      google.script.run.withSuccessHandler(() => {
        document.getElementById('savingMessage').style.display = 'none'; // 저장 중 메시지 숨기기
        alert('데이터가 성공적으로 저장되었습니다.');
      }).withFailureHandler(error => {
        document.getElementById('savingMessage').style.display = 'none'; // 저장 중 메시지 숨기기
        console.error('Error:', error);
        alert('데이터 저장 중 오류가 발생했습니다. 다시 입력해주세요.');
      }).sendUserData(
        data.name, data.gender, data.ssn_a, data.ssn_b, data.cphone_n, data.phone_n,
        data.address, data.e_mail, data.s_term, data.s_type, data.s_money, data.s_how,
        data.s_item, data.s_item_q, data.s_unit, data.s_sector, data.s_why, data.s_path,
        data.receipt1, data.receipt2, data.a_date, data.signature
      );
    }

    document.getElementById("clearSig").addEventListener("click", clearSignature);
    document.addEventListener("DOMContentLoaded", setupSignatureBox);
  </script>
</body>
</html>

 

입력 폼은 아래 링크와 같이 보여지게 됩니다.

https://script.google.com/a/macros/bmsenior.org/s/AKfycbxOwy1AJqVZypRUhlJtNl3xzWDSm4mUWhKxtBwI0_FrT66QLX_6ruxFn0h0WR4JUYrA3w/exec

 

https://script.google.com/a/macros/bmsenior.org/s/AKfycbxOwy1AJqVZypRUhlJtNl3xzWDSm4mUWhKxtBwI0_FrT66QLX_6ruxFn0h0WR4JUYrA3w/exec

 

script.google.com

 

반응형

Apps Script를 활용해 스프레드 시트로 데이터 전송하기

반응형

우리가 입력하는 데이터를 구글 스프레드 시트에 저장되게 하려면 어떻게 해야할까요?

기본적으로 구글 설문지 폼을 사용하면 스프레드 시트에 저장되는 것을 확인할 수 있습니다.

이걸 내가 원하는 대로 할 수는 없을까요?

 

일반적으로 생각했을 때 2개의 서식이 필요하단 사실은 떠올릴 수 있을 것입니다.

첫번째로 데이터를 입력하는 서식이며, 이는 웹 화면을 통해 보여질 것입니다.

구글 스프레드시트의 Apps Script에서는 기본값으로 index.html을 설정해두고 있습니다.

두번째로 입력된 데이터가 스프레드 시트에 차곡차곡 쌓여야할 것입니다. 

이를 위한 스프레드 시트 파일(SpreadSheet)이 필요합니다.

 

그런데 하나가 더 필요합니다. 

이 두 파일을 연결할 연결고리, 그것이 Apps Script 입니다.

Code.gs가 바로 그것입니다.

 

자, 정리하자면, 데이터입력을 담당하는 index.html, 데이터를 받아서 저장할 SpreadSheet, 마지막으로 이 둘을 연결하는 Code.gs가 필요합니다.

 

이때 필요한 가장 기본적인 코드를 정리해보겠습니다.

 

  1. index.html

 

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">


    <!-- 아래 스크립트를 통해 구글 스프레드 시트에 저장하게 됩니다. -->
    <script>
      function onSuccess(){
        alert("저장완료");
      }


      function resister() {
        let inputEl = document.getElementsByTagName("input");
        let inputData = [];
        for(element of inputEl) {
          inputData.push(element.value);
        }


        google.script.run.withSuccessHandler(onSuccess).sendUserData(inputData);
      }
    </script>
    <!-- 여기까지 -->
  </head>


  <body>
    <p>데이터 입력</p>
    <!-- 아래 input 태그를 통해 데이터가 입력되며, resister()를 통해 등록됩니다. -->
    <label>종류</label>
    <input type="text" name="type"><br>


    <label>수량</label>
    <input type="number" name="number"><br>


    <label>유효기간</label>
    <input type="date" name="deadline"><br>


    <button onclick="resister()">등록</button>
    <!-- 여기까지. 위에 input은 더 많이 추가해도 됩니다. -->
  </body>
 
</html>



  1. Code.gs
  • index.html과 소통하기 위해 필요한 함수
function doGet() {
  return HtmlService.createHtmlOutputFromFile('index');
}

 

  • 아래 3개 중 필요에 따라 선택
function sendUserData(inputData) {
  let ss = SpreadsheetApp.getActiveSheet();
  ss.appendRow(inputData);
}
▲ 구글 스프레드 시트에서 바로 App Script를 만든 경우로 시트가 하나 뿐인 경우.
  별도의 지정없이 바로 작동합니다.
function sendUserData(inputData) {
  let sheetName = '시트1'; // 시트 이름을 변수로 선언
  let ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
  ss.appendRow(inputData);
}
▲ 시트가 여러개인 경우 시트명을 변수로 지정토록 하였습니다.
  ’시트1’을 수정하세요.
function sendUserData(inputData) {
  // 스프레드시트 파일 ID 및 시트 이름 설정
  const spreadsheetId = 'YOUR_SPREADSHEET_ID'; // 여기에 스프레드시트 파일 ID를 입력
  const sheetName = '시트1';


  // 스프레드시트 및 시트 가져오기
  const spreadsheet = SpreadsheetApp.openById(spreadsheetId);
  const sheet = spreadsheet.getSheetByName(sheetName);


  // 데이터 추가
  sheet.appendRow(inputData);
}
▲ 파일명(고유ID)와 시트명을 모두 변수로 설정합니다.
  ’YOUR_SPREADSHEET_ID’에 스프레드 시트 파일의 ID를 직접 입력합니다.
  ’시트1’에 시트명을 직접 입력합니다.



여기까지가 기본적인 처리방법입니다.

index.html을 수정함으로써 입력화면을 좀더 다양하게 꾸밀 수 있으며, 스프레드 시트로 보낼 데이터도 추가할 수 있을 것입니다.



반응형

폰트 저작권 이슈에 대한 점검

반응형

폰트 저작권에 대해서는 이제 많이들 인지하고 있을 것이다.

엄연히 유료폰트를 구입하지 않고 사용하는 것은 불법이다.

하지만 모든 직원의 컴퓨터를 조회하는 것은 쉽지 않은 일이며, 단지 직원교육만으로 문제하 해결되지는 않는다.

snpo2013.blog.me/222052067231

 

[시민사회 저작권 문제 예방 캠페인 #1] 유료폰트 삭제하기

​저작권 문제 예방 Tips첫 번째, 유료폰트 삭제하기두 번째, 라이선스 확인하기세 번째, 외주 제작 시 유...

blog.naver.com

 

이에 한국저작권보호원에서는 내 컴퓨터에 설치된 폰트를 검색하고 기본폰트가 아닌 것들에 대해 삭제를 지원하고 있다. 

www.kcopa.or.kr/lay1/program/S1T239C242/checktool/intro.do

 

홈 >정보자료>SW 점검도구>점검도구 다운받기

현재 페이지에 대하여 얼마나 만족하십니까? 평가

www.kcopa.or.kr

여기서 Font(교육기관용) > 내PC폰트 점검기를 다운 받아서 검사하면 된다. 

다운받은 "내 PC 폰트 점검기(20191111).zip"의 압축을 풀면, "내PC폰트점검기(사용전공지확인).exe" 파일이 나오는데, 이걸 더블클릭해 실행하면, Windows OS의 기본폰트와 MS 오피스, 한컴 오피스 폰트의 기본 폰트를 제외한 모든 폰트의 목록을 보여준다.


이후 직접 유료로 구입하였거나, 무료가 확실한 폰트를 제외한 나머지를 선택하면, 삭제를 할 수 있다.

한번씩 점검한다면, 유료폰트에 사용에 대한 문제를 어느 정도 예방할 수 있지 않나 한다.

반응형

코로나19 상황에 대한 전화설문조사 결과

반응형

부산시 서구 부민노인복지관을 이용하고 있는 162명의 노인을 대상으로 코로나19 상황에 대한 전화설문을 실시하였다.

조사기간은 2020420일부터 430일까지 11일간이었으며 그 결과는 다음과 같다.

 

1. 일반적 특성

1.1 집단 구분

- 재가 대상자: 60(37.04%)

- 평생교육 이용자: 102(62.96%)

 

1.2 성별

- 남성 74(45.68%)

- 여성 88(54.62%)

 

1.3 평균연령: 74.57

 

2. 만족도 및 욕구

2.1 불편한 점, 필요한 것

전체의 48.76%(79)가 가장 불편한 점으로 외출불가를 꼽았으며, 필요한 것으로는 32(19.75%)가 마스크, 손세정제 등 안전위생용품을 선택하였다.

 

2.2 안부전화에 대한 만족도

- 만족 56(93.33%)

- 불만족 4(6.67%)

재가 서비스 이용자만을 대상으로 조사한 안부전화에 대한 만족도는 전체의 93.3%(56)가 만족한다고 응답하였다.

 

3. 정보격차

3.1 가정 내 WiFi 환경

- 사용가능 51(31.48%)

- 사용불가능 111(68.52%)

한편 재가 대상자의 81.67%(49), 평생교육 이용자의 60.78%(62)가 가정내 WiFi 환경이 없다고 응답하여 노인의 경우 낮은 WiFi 보급률과 이로 인한 정보격차가 발생할 수 있음을 확인할 수 있었음

또한 재가 대상자와 평생교육 이용자 간에도 20%에 달하는 차이가 있어 집단간 격차도 벌어지고 있었음

 

3.2 유튜브 활용

- 가능 47(29.01%)

- 불가능 115(70.99%)

마찬가지로 재가 대상자의 13.33%(8), 평생교육 이용자의 38.24%(39)만이 유튜브를 활용할 수 있다고 응답해 낮은 활용율을 보였으며, 가정 내 WiFi 환경이 구축된 경우 68.63%(35)가 유튜브를 활용할 수 있다고 응답한(χ2=11.37, df=1, p<0.01). 반면, 그렇지 않은 경우 86.09%(99)이 유튜브를 활용할 수 없다고 응답해, WiFi 환경이 유튜브 같은 정보 활용능력(literacy)에서 유의미한 분포의 차이가 있는 것을 확인할 수 있었다(χ2=56.72, df=1, p<0.001).

반응형

QR코드를 활용한 출입자 관리 - 노인복지관용

반응형

이미 회원등록을 한 상태에서 코로나19 상황에 대응해 출입자 관리를 용이하게 하기 위해 간단한 엑셀 파일을 만들어보았습니다.
시중에서 파는 저렴한 QR 스캐너만 있으면 간단히 사용이 가능합니다.

입력된 수식 등이 훼손되는 것을 막기 위해 스캔한 데이터가 입력되는 곳을 제외하고는 선택조차 되지 않도록 시트를 보호해두었습니다.

참고로, QR코드에 개인정보를 담을 경우 외부 유출 우려가 있어, 회원번호를 기반으로 작동하도록 만들었습니다.

 

[ QR코드 생성기 v1.0.1.xlsm ] -------------------------------------

이 파일은 회원번호를 기반으로 QR코드를 생성하는 파일입니다.
Visual Basic으로 만들어진 부분이 있어, 사용전 보안경고가 뜹니다.
인터넷에서 따왔습니다.

여기에서 [콘텐츠 사용]을 눌러주셔야 정상적으로 QR코드를 만들 수 있습니다.

회원 정보 QR 코드 / 회원번호 기반
체온 측정용 QR 코드

[ QR CODE 방문자관리 v2.1.1.xlsx ] -------------------------------

출입구에서 이 파일을 열어, 회원의 QR코드를 스캔하고, 체온 QR코드를 스캔하면, 엑셀에 저장된 회원정보를 바탕으로 출입자 정보를 입력해줍니다.

(회원정보)
우선 회원정보탭에, 기관에 등록된 회원정보를 입력해줍니다.

등록회원 정보


(방문자 관리)
최초 이용시 B5 셀에 커서를 두고, 스캐너로 QR코드를 스캔하면 순서대로 스캔된 정보가 입력됩니다. 
입력된 QR코드 정보(회원정보)를 바탕으로 (회원정보)탭의 데이터와 비교해 일치하는 경우
성명, 연락처, 프로그램명을 자동으로 입력해줍니다.
출입시각은 최초 스캔 된 시각을 자동으로 입력합니다.
만일, 자동입력이 안되거나 시간이 안맞는 경우,
[ 파일 > 옵션 > 수식 ]에서  반복 계산 사용(I)를 체크해주셔야만 합니다.

방문자 관리 시트

 

 

★ 현재 아래 파일들은 "부산시노인종합복지관협회"를 통해서 검토중에 있습니다.

QR CODE 방문자관리 v2.1.1.xlsx
0.34MB

2020. 7. 2. update

 

QR코드 생성기 v1.0.1.xlsm
0.39MB
체온QR코드.pdf
0.47MB

 

※ 시트가 보호되어 있습니다. 수식을 수정하기 위해 시트보호를 해제하고자 하시는 경우, 비밀번호는 "QNALS"입니다.

반응형