Googleスプレッドシートで作る社員管理システム

2019年2月23日
systemuser

 これまでGoogleスプレッドシートを使って、チャットアプリ用語集検索アプリをご紹介してきました。少しの検証ではありますが、Webアプリを作る上でGoogle Apps Scriptはそこそこ使えるものだと思います。今はメールやカレンダーなどのビジネスアプリを提供しているGoogle Appsですが、今後本格的なデータベースが登場してくると、企業にとっては基幹系システムが完全に移行でき、管理コストも大幅に削減できるようになるでしょう。そんな時代のためにGASで本格的なシステムを作る準備をしておいても良いかもしれませんね。ということで、今回は少しシステムっぽい社員管理システムというのを考えてみたいと思います。

社員管理システムとは
 最近は副業を解禁する企業も徐々に増えてきましたが、今後複数の企業に所属する方も増えていくのではないでしょうか。雇い入れる企業からすると、色々な契約形態の方を採用する局面は増えるでしょう。その際に勤務形態や支払方法など、一人ひとり条件の異なる内容を管理するのは結構大変です。申請書を一人ひとり手打ちでExcelに登録していく、というのもとても面倒ですね。そこでGoogleスプレッドシート+GASで社員管理の仕組みをWebアプリでシステム化してみましょう。ここだけ見ると普通の社内システムとさほど変わりがありませんが、これがほぼタダで出来るのがGASの魅力です。また、少しシステムっぽくするために、いくつか画面を用意します。画面とそれぞれの目的は以下の通りです。

①社員管理用スプレッドシート
社員情報を管理するスプレッドシートを1枚だけ準備しておきます。後からご紹介しますが、このシステムは1つのスプレッドシートを複数の画面で操作するようになっています。取りあえずはここに社員情報を入力しておきます。

②社員一覧画面
この画面ではスプレッドシートに登録されている社員情報を一覧で表示します。リストにして一覧化することで、誰が登録されているのかがこの画面でわかります。一覧画面はインデックスのようにして、詳細は個別の画面で操作する、というのは一般的なシステムの作りですね。社員が増えてくると、検索窓などを付けることも可能です。この場合は画面を開くとスプレッドシートに登録されている全ての社員情報を一覧にしています。

③新規登録画面
新たに社員を追加するための登録画面です。名前や連絡先、Facebookのアドレスや電話番号など、社員の属性になるものは一通り登録できるようにしています。登録と言ってもスプレッドシートに書き込むだけなのですが、たとえば管理者が承認するまで保留する、と言ったワークフローなども、作ることは可能です。

因みにここで登録される内容は、全て一覧画面に表示されるとは限りません。情報を絞って、見せる範囲に制限を設けています。社員情報は個人情報の塊です。データ属性によって、誰に何を閲覧させるのかということも予め考えてシステムを設計する必要がありますね。

④社員詳細照会・修正画面
個々の社員の詳細情報を確認し修正するための画面です。この画面は、表示した際に入力ボックスに既に情報が埋まっています。さらに入力フォームとなっていますので、そのまま修正してもう一度登録すれば、データとして反映されるようになっています。③の新規登録画面とレイアウトはほとんど同じですが、該当の社員情報を更新できるように従業員Noをキーにマッチングさせています。

この画面はどこから来るかというと、一覧画面の名前部分がクリッカブルとなっており、リンクを押すとその社員の修正画面に遷移する、という方式にしています。上の一覧では分かりにくかったですが、下の画像の名前の部分ですね。「コンサル2号」という名前を選択すると、↑の詳細画面に飛んでいます。別の社員の名前を選択すると、その選択された社員情報の修正ページに飛んでいく、という仕組みですね。

とても簡単ですが、以上を組み合わせると社員情報の管理が簡単に出来るようになります。

ご承知の通り、実はこのシステムは一つのスプレッドシートを複数の画面で操作するようになっています。もう少しシステムっぽく解説してみると、スプレッドシートはデータの位置づけとして使っているだけであり、各画面はUIやUXを意識して表示または登録機能に特化させ、ある画面で必要な情報のみをスプレッドシートから取得する、という処理をGASのプログラムで制御しているのです。全体像を示すと、以下のような形になっています。黒い矢印がデータの流れを示しています。画面遷移はそれぞれの画面URLでリンクを張っているだけです。スプレッドシートは同じものを指しているため、常に最新化された状態が各画面に表示され、更新出来ることになるのです。

社員管理システムの仕組み

では上記のシステムが、スプレッドシートとGASでどのようにして作ることが出来るのかをご紹介していきます。

社員管理用スプレッドシート
 上記でも画像を載せましたが、セルの横軸を項目、縦を社員の登録情報として表を作っておきます。項目はこの場合以下のようなものを設定しています。
No、氏名、契約形態、所属会社、支払方法、権限、役割、プロフィール、タグ、稼働可能時間、連絡用メールアドレス、Facebookアドレス、NDA締結日、契約状態、電話番号、備考、更新ユーザー、更新日

Google Apps Script
このシートで作成するスクリプトは、GASファイル1本、HTMLファイル3本です。まずはGASファイルから解説していきます。

・GASファイル(stuff.gs)
 以下がファイルの中身です。doGet(e)という関数の中にif文が沢山並んでいますね。。実はこれはURLパラメータによってどの画面から来た通信なのかを判別し、それぞれの画面に従って制御を分けるためにこのようなことをしています。1つのプログラムで色々な画面を操作するため、混乱しないようにこのようにしているのですね。そして一覧表示などの画面表示処理はHTMLファイル内で行っていきますので、新規登録と変更時の処理がこのGASで行う処理の大半となっています。URLのパラメータを判定している中で、”postData”と”modifyData”という部分がそれぞれ新規登録、変更時の処理になっています。その上にある”view”、”inputStuff”、”modify”の分岐は一覧画面、入力画面、修正画面にそれぞれ遷移する処理のみを記載しています。

//スプレッドシート名指定
var id =[シートのIDを指定];
var sheet = SpreadsheetApp.openById(id).getSheetByName([シート名を指定]);

//セッション情報からユーザID(メールアドレス)を取得
var objUser = Session.getActiveUser();
var mail = objUser.getEmail();
//タイムスタンプ
var time = Utilities.formatDate( new Date(), 'Asia/Tokyo', 'yyyy/MM/dd HH:mm:ss');

var selectNo;
function doPost(e){
    doGet(e);
    return HtmlService.createTemplateFromFile("index").evaluate(); 
}

function doGet(e){
  if (e.parameter.name == undefined) {
    return HtmlService.createTemplateFromFile("index").evaluate(); 
  }
  if (e.parameter.name == 'view') {
    return HtmlService.createTemplateFromFile("index").evaluate(); 
  }else if (e.parameter.name == 'inputStuff'){
    return HtmlService.createTemplateFromFile("inputStuff").evaluate(); 
  }else if (e.parameter.name == 'modify'){
    selectNo = e.parameter.no;
    return HtmlService.createTemplateFromFile("modify").evaluate(); 
  }else if (e.parameter.name == 'postData'){
    //値の取得・設定
    no = sheet.getLastRow()-1;
    name = e.parameter.sname;
    commit = e.parameter.commit;
    company = e.parameter.company;
    payroll = e.parameter.payroll;
    roll = e.parameter.roll;
    post = e.parameter.post;
    profile = e.parameter.profile;
    tag = e.parameter.tag;
    avalable = e.parameter.avalable;
    address = e.parameter.address;
    facebook = e.parameter.facebook;
    ndadate = e.parameter.ndadate;
    status = e.parameter.status;
    tel = e.parameter.tel;
    remarks = e.parameter.remarks;
    adduser = mail;
    timestamp = time;
    
    //Getした値を配列にする
    var array = [ no,name,commit,company,payroll,roll,post,profile,tag,avalable,address,facebook,ndadate,status,tel,remarks,adduser,timestamp];

    //シートに配列を書き込み
    sheet.appendRow(array);
    return HtmlService.createTemplateFromFile("index").evaluate(); 

    //修正画面より
  }else if (e.parameter.name == 'modifyData'){
    //値の取得・設定
    stuffNo = e.parameter.stuffNo;
    name = e.parameter.sname;
    commit = e.parameter.commit;
    company = e.parameter.company;
    payroll = e.parameter.payroll;
    roll = e.parameter.roll;
    post = e.parameter.post;
    profile = e.parameter.profile;
    tag = e.parameter.tag;
    avalable = e.parameter.avalable;
    address = e.parameter.address;
    facebook = e.parameter.facebook;
    ndadate = e.parameter.ndadate;
    //d = new Date(e.parameter.ndadate);
    //ndadate = Utilities.formatDate(d, 'Asia/Tokyo', 'yyyy年M月d日');
    status = e.parameter.status;
    tel = e.parameter.tel;
    remarks = e.parameter.remarks;
    adduser = mail;
    timestamp = time;
    
    //Getした値を配列にして該当行を全て書き換える
    var array = [[stuffNo,name,commit,company,payroll,roll,post,profile,tag,avalable,address,facebook,ndadate,status,tel,remarks,adduser,timestamp]];
    //シートに配列を書き込み
    var i = parseInt(stuffNo) + 1;
    sheet.getRange(i, 1 , 1 , array[0].length).setValues(array);
    return HtmlService.createTemplateFromFile("index").evaluate(); 
  }
}

function getNo(){
  return selectNo;
}

//表示用日付を返す関数
function getViewDate (date) {
  if(date==""){
    return "";
  }else{
    d = new Date(date);
    return Utilities.formatDate( d, 'Asia/Tokyo', 'yyyy/M/d');
  }
};

・一覧画面(index.html)
 また少し長いファイルとなっていますが、大きく3つのブロックになっています。一つはデザインの制御とヘッダー情報を表示する部分、その下にあるシートのIDを指定している部分からが社員情報を一覧で表示している処理になります。”<?”と”?>”で囲まれた部分がスクリプトになっている部分で、指定したスプレッドを参照し、一覧のヘッダー部分、ボディ(データ)部分を出力しています。さらにその下にも”<? ?>”の部分がありますが、ここは2つ目のブロックから呼び出される関数群になっています。関数ですのでstuff.gsにまとめることもできますが、ここでは分かりやすく同一ファイル内に記載しています。

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
  <style>
  #header-fixed{
	border: 5px solid #fff;    /* 表示領域を白枠で囲う */
    position: fixed;            /* ヘッダーの固定 */
    padding:10px 0 20px;       /* 上10px、下20pxをあける */
    top:  0px;                   /* 位置(上0px) */
    left: 10px;                  /* 位置(右0px) */
    width: 100%;                /* 横幅100% */
    height:70px;                /* 縦幅70px */
    background-color:#FFF      /* バックの色 */
  }
  #content{
    top:  80px;                   /* 位置(上0px) */
    left: 0px;                  /* 位置(右0px) */
    padding:110px 0 0px;
    width: 100%;                /* 横幅100% */
    background-color: "#000000" /* バックの色 */
  }
  #menu{
    font-size : 20px;
    border : 1px;
    padding : 10px 20px 0px 0px;
  }
  thead {
    display: block;
  }
  tbody {
    overflow-x: hidden;
    overflow-y: scroll;
  }
</style>
<?
  url = "https://script.google.com/macros/s/[App-ID]";
?>
  <div id='header-fixed'>
    <font size="5pt" color="#5555ff">社員管理システム  </font>
    <a id='menu' href="<?=url?>/exec?name=view">再表示</a>
    <a id='menu' href="<?=url?>/exec?name=inputStuff">社員登録</a>    
<!--    <font size='5px'> 只今の時刻<span id="clock_time"></span>です。</font> -->
  </div>
<!-- ↑↑↑メニュー↑↑↑-->
<script>
function clock()
{
    // 現在日時を表すインスタンスを取得
//    var now = new Date();
//    document.getElementById("clock_time").innerHTML = now;
}
setInterval(clock, 1000);
</script>
      <?
        myMail = Session.getActiveUser().getEmail();
        output.append('<font size="2px" color="#5555ff">ログインユーザー:');
        output.append( myMail);
      ?>
  <div id='content'>
      <?
        //スタッフリストのスプレッドIDを指定
       var id =[シートのIDを指定];
       var mySheet = SpreadsheetApp.openById(id).getSheetByName("スタッフ");
        var endrow = mySheet.getLastRow();
        var headData = mySheet.getRange("A1:N1").getValues();
        var myData = mySheet.getRange(2, 1 , endrow-1 , 14).getValues();
        output.append('<table border="1" cellspacing="0" cellpadding="5" bordercolor="#333333" style="table-layout:fixed;width:100%;">');
        output.append('<thead><div fixed>');
        output.append('<colgroup>');
        output.append('<col style="width:3%;"><col style="width:7%;"><col><col><col><col><col><col style="width:20%;"><col style="width:10%;"><col><col style="width:10%;"><col style="width:15%;"><col style="width:5%;"><col>');
        output.append('</colgroup>');
        output.append('<tr>');
        headprefix = '<th bgcolor="#5555ff"><font color="#FFFFFF" size="2px">';
        headbackfix = '</font></th>';
        
        //ヘッダー情報取得
        no = headData[0][0];
        name = headData[0][1];
        commit = headData[0][2];
        company = headData[0][3];
        payroll = headData[0][4];
        roll = headData[0][5];
        post = headData[0][6];
        profile = headData[0][7];
        tag = headData[0][8];
        avalable = headData[0][9];
        address = headData[0][10];
        facebook = headData[0][11];
        ndadate = headData[0][12];
        status = headData[0][13];
        
        //テーブルヘッダー作成
        output.append(headprefix + no + headbackfix);
        output.append(headprefix + name + headbackfix);
        output.append(headprefix + commit + headbackfix);
        output.append(headprefix + company + headbackfix);
        output.append(headprefix + payroll + headbackfix);
        output.append(headprefix + roll + headbackfix);
        output.append(headprefix + post + headbackfix);
        output.append(headprefix + profile + headbackfix);
        output.append(headprefix + tag + headbackfix);
        output.append(headprefix + avalable + headbackfix);
        output.append(headprefix + address + headbackfix);
        output.append(headprefix + facebook + headbackfix);
        output.append(headprefix + ndadate + headbackfix);
        output.append(headprefix + status + headbackfix);
        output.append('</tr>');
        output.append('</thead></div>');
        
        //テーブルボディの作成
        output.append('<tbody>');
        y=0;
        for(var i=0; i<myData.length; i++){
          no = myData[i][y];
          name = myData[i][y+1];
          commit = myData[i][y+2];
          company = myData[i][y+3];
          payroll = myData[i][y+4];
          roll = myData[i][y+5];
          post = myData[i][y+6];
          profile = myData[i][y+7];
          tag = myData[i][y+8];
          avalable = myData[i][y+9];
          address = myData[i][y+10];
          facebook = myData[i][y+11];
          ndadate = myData[i][y+12];
          status = myData[i][y+13];
          
      headprefix = '<td style="word-wrap:break-word;"><font color="#000000" size="1px">';
          headbackfix = '</font></td>';
          
          if (status === "解約" || status === "退職"){
            output.append('<tr bgcolor = "#333333">');
          }else if(status === "停止中"){
            output.append('<tr bgcolor = "#AAAAAA">');
          }else{
            output.append('<tr bgcolor = "#FFFFFF">');
          }
          output.append(headprefix + no + headbackfix);
          output.append(headprefix + getModifyUrl(no,name) + headbackfix);
          output.append(headprefix + commit + headbackfix);
          output.append(headprefix + company + headbackfix);
          output.append(headprefix + payroll + headbackfix);
          output.append(headprefix + roll + headbackfix);
          output.append(headprefix + post + headbackfix);
          output.append(headprefix + getLink(profile) + headbackfix);
          output.append(headprefix + tag + headbackfix);
          output.append(headprefix + avalable + headbackfix);
          output.append(headprefix + getMailAddress(address) + headbackfix);
          output.append(headprefix + getLink(facebook) + headbackfix);
          output.append(headprefix + getViewDate(ndadate) + headbackfix);
          output.append(headprefix + status + headbackfix);
          output.append('</tr>');
        }
        output.append('</tbody>');
        output.append('</table>');
      ?>
      <?
        //上記から参照する関数群
        function escape_html (string) {
          if(typeof string !== 'string') {
            return string;
          }
          return string.replace(/[&'`"<>]/g, function(match) {
          return {
          '&': '&',
          "'": ''',
          '`': '`',
          '"': '"',
          '<': '<',
          '>': '>',
          }[match]
          });
        }

        //修正画面リンクを埋め込む関数
        function getModifyUrl (no, name) {
          var modifyUrl = url + "/exec?name=modify&no=" + no;
          return '<a href="' + modifyUrl + '" target="_top">' + name + '</a>';
        };

     //URLリンクを埋め込む関数
        function getLink (stringUrl) {
          var pattern = 'http';
          if(stringUrl.indexOf(pattern) === 0){
            return '<a href="' + stringUrl + '" target="_blank">' + stringUrl + '</a>';
          }else{
            return stringUrl;
          }
        };

        //メールリンクを埋め込む関数
        function getMailAddress (stringMail) {
          if(stringMail.indexOf('@') != -1){
            return '<a href="mailto:' + stringMail + '">' + stringMail + '</a>';
          }else{
            return stringMail;
          }
        };

  ?>
    </div>
</body>
</html>

・新規登録画面(inputStuff.html)
 続いて登録画面です。このファイルはHTMLファイルの要素のみで、スクリプトは一切含んでいません。考えてみれば、登録処理はgasファイルで行っていることと、新規登録ですので画面を表示させる上で登録済の情報も表示する必要もありません。ただの空っぽな画面を用意して、情報を送信すれば良いだけなのです。ただし一連のシステムとして組み込むために、この画面にはstuff.gsを経由して遷移するようにしています。

<!DOCTYPE html>
<html>
  <body>
  
  <style>
  input {
    width: 100%;
    max-width: 400px; /* レスポンシブの場合の対策 */
    box-sizing: border-box; /* [borde-box]で右の飛び出しを回避 */
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
  }
  
  textarea {
    width: 100%;
    max-width: 400px; 
    box-sizing: border-box;
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
  }
  
  input[type="submit"]  {
    max-width: 200px; /*送信ボタンの幅を指定*/
  }
  
  table.input{
    font-size: 2px;
  }
  #menu{
    font-size : 20px;
    border : 1px;
    padding : 10px 20px 0px 0px;
  }
  
  </style>
<?
  url = "https://script.google.com/macros/s/[App-ID]";
?>
  
  <div id='header-fixed'>
    <font size="5pt" color="#5555ff">社員登録  </font><a id='menu' href="<?=url?>/exec?name=view" target="_top">社員一覧</a>
  </div>

<form method='POST' action='<?=url?>/exec?name=postData' id='form' target="_top">
<table border="1" cellspacing="0" cellpadding="5" bordercolor="#333333" id="input">
<thead>
  <tr bgcolor="#aaaaff">
    <th>項目名</th><th>内容</th>
  </tr>
</thead>
<tbody>
  <tr>
    <td>氏名</td><td><input type="text" name="sname" size="40" maxlength="20"></td>
  </tr>
  <tr>
    <td>契約形態</td>
	<td>
		<select name="commit">
		<option value="業務請負">業務請負</option>
		<option value="従業員">従業員</option>
		<option value="インターン">インターン</option>
		<option value="プロボノ">プロボノ</option>
		</select>	  
	</td>
  </tr>
  <tr>
    <td>所属会社</td><td><input type="text" name="company" size="40" maxlength="20"></td>
  </tr>
  <tr>
    <td>支払方法</td>
	<td>
		<select name="payroll">
		<option value="現金振込み">現金振込み</option>
		<option value="Amazonポイント">Amazonポイント</option>
		<option value="商品券">商品券</option>
		<option value="その他">その他</option>
		</select>
	</td>
  </tr>
  <tr>
    <td>権限</td>
	<td>
		<select name="roll">
		<option value="一般副業">一般副業</option>
		<option value="ボードメンバー">ボードメンバー</option>
		</select>
	</td>
  </tr>
  <tr>
    <td>体制上の役割</td><td><input type="text" name="post" size="40" maxlength="20"></td>
  </tr>
  <tr>
    <td>プロフィール(URLなど)</td><td><input type="text" name="profile" size="140" maxlength="120"></td>
  </tr>
  <tr>
    <td>スキル<br>(カンマ区切りで)</td><td><input type="text" name="tag" size="140" maxlength="120"></td>
  </tr>
  <tr>
    <td>稼働可能時間</td>
	<td>
		<select name="avalable">
		<option value="平日のみ">平日のみ</option>
		<option value="休日のみ">休日のみ</option>
		<option value="平日夜間のみ">平日夜間のみ</option>
		<option value="いつでも">いつでも</option>
		<option value="調整次第">調整次第</option>
		</select>
	</td>
  </tr>
  <tr>
    <td>連絡用メールアドレス</td><td><input type="text" name="address" size="100" maxlength="80"></td>
  </tr>
  <tr>
    <td>Facebookアドレス</td><td><input type="text" name="facebook" size="100" maxlength="80"></td>
  </tr>
  <tr>
    <td>NDA締結日</td><td><input type="text" name="ndadate" size="40" maxlength="20"></td>
  </tr>
  <tr>
    <td>契約状態</td>
	<td>
		<select name="status">
		<option value="稼動中">稼動中</option>
		<option value="停止中">停止中</option>
		<option value="退職">退職</option>
		<option value="解約">解約</option>
		<option value="登録中">登録中</option>
		</select>
	</td>
  </tr>
  <tr>
    <td>電話番号</td><td><input type="text" name="tel" size="40" maxlength="20"></td>
  </tr>
  <tr>
    <td>備考</td><td><input type="text" name="remarks" size="40" maxlength="20"></td>
  </tr>
</tbody>
</table>
<input type= 'submit' value="登録" id='botton'>
</form>
</body>
</html>

・修正画面(modify.html)
 最後は修正画面です。このファイルは既存情報を修正するためにスプレッドシートから情報を取得したうえで表示しているため、少々複雑です。しかも一覧画面と違って、入力フォームの中に情報を表示させていることと、さらにプルダウンの場合にはデフォルトで選択した状態を再現するようにhtmlを制御しているため、似たようなタグが沢山出てきています。そのままコピーすれば使えますので、一つ一つの意味はそれほど知る必要はないでしょう。

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

  <body>
  
  <style>
  input {
    width: 100%;
    max-width: 400px; /* レスポンシブの場合の対策 */
    box-sizing: border-box; /* [borde-box]で右の飛び出しを回避 */
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
  }
  
  textarea {
    width: 100%;
    max-width: 400px; 
    box-sizing: border-box;
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
  }
  
  input[type="submit"]  {
    max-width: 200px; /*送信ボタンの幅を指定*/
  }
  
  table.input{
    font-size: 2px;
  }
  #menu{
    font-size : 20px;
    border : 1px;
    padding : 10px 20px 0px 0px;
  }
  
</style>
  
  <div id='header-fixed'>
    <font size="5pt" color="#5555ff">登録社員照会/変更  </font>
    <a id='menu' href="https://script.google.com/macros/s/[App-ID]/exec?name=view" target="_top">社員一覧</a>
  </div>

<?
    //パラメータリストから従業員Noを取得
   selectNo = getNo();
    var id =[シートID];
	var mySheet = SpreadsheetApp.openById(id).getSheetByName([シートID]);
	var endrow = mySheet.getLastRow();
	var myData = mySheet.getRange(2, 1 , endrow-1 , 16).getValues();

    y=0;
	for(var i=0; i<myData.length; i++){
	  //従業員Noが同じレコードを取り出し
	  if (selectNo == myData[i][y]){
          stuffNo =  myData[i][y];    //従業員No
		  name = myData[i][y+1];      //氏名
		  commit = myData[i][y+2];    //契約形態
		  company = myData[i][y+3];   //所属会社
		  payroll = myData[i][y+4];   //支払方法
		  roll = myData[i][y+5];      //権限
		  post = myData[i][y+6];      //役割
		  profile = myData[i][y+7];   //プロフィール
		  tag = myData[i][y+8];       //タグ
		  avalable = myData[i][y+9];  //稼働可能時間帯
		  address = myData[i][y+10];  //連絡用メールアドレス
		  facebook = myData[i][y+11]; //Facebookアドレス
		  ndadate = getViewDate(myData[i][y+12]);  //NDA締結日
		  status = myData[i][y+13];   //契約状態
		  tel = myData[i][y+14];      //電話番号
		  remarks = myData[i][y+15];  //備考
		  break;
	  }
	}
    
?>

<form method='POST' action='<?=url?>/exec?name=modifyData' id='form' target="_top">
<table border="1" cellspacing="0" cellpadding="5" bordercolor="#333333" id="input">
<thead>
  <tr bgcolor="#aaaaff">
    <th>項目名</th><th>内容</th>
  </tr>
</thead>
<tbody>
  <tr>
    <td>従業員No</td><td><input type="text" name="stuffNo" size="20" maxlength="10" value="<?=stuffNo?>"  readonly="readonly"></td>
  </tr>
  <tr>
    <td>氏名</td><td><input type="text" name="sname" size="40" maxlength="20" value="<?=name?>"></td>
  </tr>
  <tr>
    <td>契約形態</td>
	<td>
        <select name="commit" id="commit">
        <?
        if(commit=="業務請負"){
          output.append('<option value="業務請負" selected>業務請負</option>');
        }else{
          output.append('<option value="業務請負">業務請負</option>');
        }
        if(commit=="従業員"){
          output.append('<option value="従業員" selected>従業員</option>');
        }else{
          output.append('<option value="従業員">従業員</option>');
        }
        if(commit=="インターン"){
          output.append('<option value="インターン" selected>インターン</option>');
        }else{
          output.append('<option value="インターン">インターン</option>');
        }
        if(commit=="プロボノ"){
          output.append('<option value="プロボノ" selected>プロボノ</option>');
        }else{
          output.append('<option value="プロボノ">プロボノ</option>');
        }
        ?>
        </select>
	</td>
  </tr>
  <tr>
    <td>所属会社</td><td><input type="text" name="company" size="40" maxlength="20" value="<?=company?>"></td>
  </tr>
  <tr>
    <td>支払方法</td>
	<td>
		<select name="payroll" id="payroll">
        <?
        if(payroll=="現金振込み"){
          output.append('<option value="現金振込み" selected>現金振込み</option>');
        }else{
          output.append('<option value="現金振込み">現金振込み</option>');
        }
        if(payroll=="Amazonポイント"){
          output.append('<option value="Amazonポイント" selected>Amazonポイント</option>');
        }else{
          output.append('<option value="Amazonポイント">Amazonポイント</option>');
        }
        if(payroll=="商品券"){
          output.append('<option value="商品券" selected>商品券</option>');
        }else{
          output.append('<option value="商品券">商品券</option>');
        }
        if(payroll=="その他"){
          output.append('<option value="その他" selected>その他</option>');
        }else{
          output.append('<option value="その他">その他</option>');
        }
        ?>
		</select>
	</td>
  </tr>
  <tr>
    <td>権限</td>
	<td>
		<select name="roll">
        <?
        if(roll=="一般副業"){
          output.append('<option value="一般副業" selected>一般副業</option>');
        }else{
          output.append('<option value="一般副業">一般副業</option>');
        }
        if(roll=="ボードメンバー"){
          output.append('<option value="ボードメンバー" selected>ボードメンバー</option>');
        }else{
          output.append('<option value="ボードメンバー">ボードメンバー</option>');
        }
        ?>
		</select>
	</td>
  </tr>
  <tr>
    <td width="200">体制上の役割<br>(BA/経営企画/総務など)</td><td><input type="text" name="post" size="40" maxlength="20" value="<?=post?>"></td>
  </tr>
  <tr>
    <td>プロフィール(URLなど)</td><td><input type="text" name="profile" size="240" maxlength="220" value="<?=profile?>"></td>
  </tr>
  <tr>
    <td>タグ<br>(カンマ区切りで)</td><td><input type="text" name="tag" size="140" maxlength="120" value="<?=tag?>"></td>
  </tr>
  <tr>
    <td>稼働可能時間</td>
	<td>
		<select name="avalable">
        <?
        if(roll=="平日のみ"){
          output.append('<option value="平日のみ" selected>平日のみ</option>');
        }else{
          output.append('<option value="平日のみ">平日のみ</option>');
        }
        if(roll=="休日のみ"){
          output.append('<option value="休日のみ" selected>休日のみ</option>');
        }else{
          output.append('<option value="休日のみ">休日のみ</option>');
        }
        if(roll=="平日夜間のみ"){
          output.append('<option value="平日夜間のみ" selected>平日夜間のみ</option>');
        }else{
          output.append('<option value="平日夜間のみ">平日夜間のみ</option>');
        }
        if(roll=="いつでも"){
          output.append('<option value="いつでも" selected>いつでも</option>');
        }else{
          output.append('<option value="いつでも">いつでも</option>');
        }
        if(roll=="調整次第"){
          output.append('<option value="調整次第" selected>調整次第</option>');
        }else{
          output.append('<option value="調整次第">調整次第</option>');
        }
        ?>
		</select>
	</td>
  </tr>
  <tr>
    <td>連絡用メールアドレス</td><td><input type="text" name="address" size="100" maxlength="80" value="<?=address?>"></td>
  </tr>
  <tr>
    <td>Facebookアドレス</td><td><input type="text" name="facebook" size="100" maxlength="80" value=<?=facebook?>></td>
  </tr>
  <tr>
    <td>NDA締結日</td><td><input type="text" name="ndadate" size="40" maxlength="20" value="<?=ndadate?>"></td>
  </tr>
  <tr>
    <td>契約状態</td>
	<td>
		<select name="status">
        <?
        if(status=="稼動中"){
          output.append('<option value="稼動中" selected>稼動中</option>');
        }else{
          output.append('<option value="稼動中">稼動中</option>');
        }
        if(status=="停止中"){
          output.append('<option value="停止中" selected>停止中</option>');
        }else{
          output.append('<option value="停止中">停止中</option>');
        }
        if(status=="退職"){
          output.append('<option value="退職" selected>退職</option>');
        }else{
          output.append('<option value="退職">退職</option>');
        }
        if(status=="解約"){
          output.append('<option value="解約" selected>解約</option>');
        }else{
          output.append('<option value="調整次第">調整次第</option>');
        }
        if(status=="登録中"){
          output.append('<option value="登録中" selected>登録中</option>');
        }else{
          output.append('<option value="登録中">登録中</option>');
        }
        ?>
		</select>
	</td>
  </tr>
  <tr>
    <td>電話番号</td><td><input type="text" name="tel" size="40" maxlength="20" value="<?=tel?>"></td>
  </tr>
  <tr>
    <td>備考</td><td><input type="text" name="remarks" size="40" maxlength="20" value="<?=remarks?>"></td>
  </tr>
</tbody>
</table>
<input type= 'submit' value="登録" id='botton'>
</form>
</body>
</html>

いかがでしょうか。システムっぽくしたために少々複雑に見えてしまったかもしれません。これらのプログラム群をもう少し簡単に説明すると、以下のような構造になっています。stuff.gsというGASファイルがURLパラメータに応じて処理を振り分け、表示したり登録したりする個々の処理に権限委譲をするコントローラー役になっているのですね。

そもそもWebアプリにする必要があるのか?
 ここまで見て疑問に感じる方もいらっしゃるでしょうが、スプレッドシートで普通に出来ることを、Webアプリにして参照と更新をわざわざ分解してまで作業する意味はあるのでしょうか。私は実際これをゼロから作るのに丸2日程度かかりました。色々な処理を入れてはみたものの、行き着く先はスプレッドシートです。直接登録・修正すれば同じ目的は出来たはずです。
 なぜWebアプリが良いのか、その理由をスプレッドシートを直接操作する場合と対比することでメリットを整理してみます。

・アクセス制御が効かなくなる
 例えば自分の登録情報のみ更新したいのに、意図しないうちに他人の情報(セル)を書き換えてしまう可能性があります。消してしまったらアウト。復元するのに手間もかかってしまいます。
・情報の正統性が担保できなくなる
 直接入力する場合、情報が一部しか入力されていなかったり、メールアドレスなのにメール形式でないなど、入力不備があると使えないデータになってしまう可能性があります。
・悪意に対してデータが防御出来ない
 共有範囲を広げると、無関係の人にまで全員の個人情報が見れてしまったり、悪意をもって情報を搾取・消去される可能性が制御できません。管理者以外にアクセスさせるのは危険です。
・Webアプリでこれらを制御可能
 アクセス権限や情報開示範囲などはプログラムで制御可能となるため、管理者以外の不特定多数で共有することも可能です。さらには登録オペレーションなどをマニュアル化すれば第三者に業務を委託することも可能になり、管理業務が効率的になります。
・シートの存在は隠したまま
 Webアプリのみ公開するため、誤操作のリスクも低減できるうえ、入力チェックを設けて正当性を担保することが可能です。

ざっと揚げ出しただけでも、このようなメリットがあると言えます。そして情報が大量になる場合のメリットだけでなく、いかにストレスフリーで利用者に操作させるか、組織の中にボトルネックを作らないかということが実現できるようになるのです。Webシステムというのはそのような貢献が大きいのですね。

Leave a comment