承認これくしょん

my black histories

Google Apps Script + jQuery Mobileでスマホ用簡易タイムレコーダーを作った

最近ブラック企業に関する報道の影響か、自分自身の手帳等にも出社・退社時刻を記録すべきという主張を目にします。
とはいえ正直なところ面倒なので、スマホから記録できる簡易タイムレコーダーを作りました。

使い方

ボタンを押すだけ

仕様

jQuery MobileのクライアントからGETリクエストを送信すると、Google Apps ScriptのWebアプリが受け取ってスプレッドシートに記録し、JSONPで結果を返します。
クライアントはGoogle ドライブホスティング機能で公開するようにしました。そのためレンタルサーバ等は不要です。

作り方

  • 記録先スプレッドシートを準備
    • こんな感じの「template」シートを作成

      f:id:old_horizon:20140429222733p:plain

      スクリプトはこれを月初めにコピーし、「YYYY/MM」にリネームして記録します。
    • E, F, J列の書式を「時間」に
    • J1に「=SUM(F:F)」、J2に「=SUM(G:G)」
  • コードを書く(以下参照)
  • クライアント一式をGoogle ドライブで「ウェブ上で一般公開」→生成されたホスティングURLにアクセスして使う

コード

サーバー (Google Apps Script)
function doGet(e) {
  var scriptProperties = PropertiesService.getScriptProperties();
  var book = SpreadsheetApp.openById(scriptProperties.getProperty("bookId"));
  var now = new Date();
  var nowStr = Utilities.formatDate(now, "JST", "yyyy/MM/dd HH:mm:ss");
  var values = [];
  var response = {};
  
  switch(e.parameters.mode[0]) {
    case "checkin":
      var name = Utilities.formatDate(now, "JST", "yyyy/MM");
      var sheet = book.getSheetByName(name);
      // if sheet does not exist
      if (!sheet) {
        var sheet = book.getSheetByName("template").copyTo(book).setName(name);
        sheet.getRange(1, 1).setValue(name);
        book.setActiveSheet(sheet);
      }
      values[0] = Utilities.formatDate(now, "JST", "yyyy/MM/dd");
      values[1] = "=TEXT(R[0]C[-1],\"ddd\")";
      values[2] = nowStr;
      sheet.appendRow(values);
      response["status"] = "success";
      break;
    case "checkout":
      var sheet = book.getActiveSheet();
      var lastRow = sheet.getLastRow();
      values[0] = nowStr;
      values[1] = "=IF(R[0]C[-1]-R[0]C[-2]>=" + scriptProperties.getProperty("breakLimit") + "/24," + scriptProperties.getProperty("breakHours") + "/24,0)";
      values[2] = "=R[0]C[-2]-R[0]C[-3]-R[0]C[-1]";
      values[3] = "=IF(R[0]C[-1]-" + scriptProperties.getProperty("workHours") + "/24>0,R[0]C[-1]-" + scriptProperties.getProperty("workHours") + "/24,0)";
      sheet.getRange(lastRow, 4, 1, 4).setValues([values]);
      response["status"] = "success";
      break;
    default:
      response["status"] = "failed";
  }
  // return jsonp
  return ContentService.createTextOutput(e.parameters.callback + "(" + JSON.stringify(response) + ")").setMimeType(ContentService.MimeType.JAVASCRIPT);
}

スクリプトプロパティ

プロパティ
bookId 記録先スプレッドシートのキー
breakLimit 休憩時間が付与される最低労働時間(単位:時間)
breakHours 休憩時間(単位:時間)
workHours 所定労働時間(単位:時間)
クライアント (jQuery Mobile)
  • timeRecorder.js
var end = "終業時刻 (HH:mm)";

var exec = function(event) {
    if (window.confirm(event.data.name + "を記録しますか?")) {
        $.mobile.loading("show", {
            textVisible: true,
            textonly: false
        });
        $.ajax({
            url: "https://script.google.com/macros/s/(スクリプトのキー)/exec?mode=" + event.data.mode,
            type: "GET",
            dataType: "jsonp",
            timeout: 10000,
            success: function(data, status) {
                        if (data.status == "success") {
                            $.mobile.loading("hide");
                            alert("記録しました");
                        } else {
                            $.mobile.loading("hide");
                            alert("不正なリクエストです");
                        }
                    },
            error: function(data) {
                        $.mobile.loading("hide");
                        alert("通信エラーが発生しました");
            }
        });
    }
}


$(document).bind("pageinit", function() {
    var update = function() {
        var now = moment();
        var day = now.format("YYYY/MM/DD");
        $("#day").text(day);
        
        var current = now.format("HH:mm:ss");
        $("#current").text(current);
        
        var rest = moment(day + " " + end);
        rest = moment.utc(rest.diff(now)).format("HH:mm:ss");
        $("#rest").text(rest);
    }
    update();
    setInterval(update, 1000);
    $("#checkin").on("click", {mode: "checkin", name: "出勤"}, exec);
    $("#checkout").on("click", {mode: "checkout", name: "退勤"}, exec);
});
  • index.html
<!DOCTYPE html>
<html>
<head>
<title>タイムレコーダー</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="//code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.css" />
<script src="//code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="//code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.5.1/moment.min.js"></script>
<script src="timeRecorder.js"></script>
</head>
<body>
<div data-role="page" data-theme="b" id="index">
<div data-role="header">
<h1>タイムレコーダー</h1>
</div>
<div role="main" class="ui-content">
<table data-role="table" id="data">
<thead>
<tr>
<th>日付</th>
<th>時刻</th>
<th>終業まで</th>
</tr>
</thead>
<tbody>
<tr>
<td><span id="day"></span></td>
<td><span id="current"></span></td>
<td><span id="rest"></span></td>
</tr>
</tbody>
</table>
<a class="ui-btn" href="#" id="checkin">出勤</a>
<a class="ui-btn" href="#" id="checkout">退勤</a>
</div>
</body>
</html>

メモ

Google Apps Script
  • ScriptProperties/UserPropertiesは廃止されたのでPropertiesServiceを使えとのこと
  • しかしPropertiesService.getUserProperties()で取得できるユーザープロパティはスクリプトエディタのUIからは編集できない、一方でスクリプトプロパティは編集できるという仕様 ^1
  • doGetの引数に入るparametersは配列になってるのか、[0]をつけないとswitch文でコケる?
  • Range#setNumberFormatでセルの書式を設定できるけど、24時間以上が表示できるか微妙な感じ
jQuery Mobile/JavaScript
  • $(document).ready()ではなくpageinit()を使えとのこと
  • 時刻計算にmoment.jsを使いました