一時的にデータを保存するためにフィールドを使う ~勤怠管理アプリを例に~

著者名: 今村 健一郎 (External link) (サイボウズ ソリューション営業部Si-G)

目次

はじめに

皆様、初めまして。
サイボウズの技術営業担当の今村と申します。
よろしくお願いします!

kintoneのJavaScriptプログラムを書けると、営業時にも便利です。
買うか迷われているお客様に、サンプルプログラムを提供したり、サクッと作ってデモを披露すると当然ながら好印象になります。
『商談の押しの一手はJavaScriptが左右する!』かもしれません。

今回は、カスタマイズビューを利用してタイムカードアプリを作成します。
その中でも、「休憩時間」を算出する方法を紹介します。

デモ環境

デモ環境で実際に動作を確認できます。

ログイン情報は cybozu developer networkデモ環境で確認してください。

完成イメージ

タイムカードアプリでは次のようなシンプルなものを作ってみました。

出社してから退社するまでの間、仕事に没頭し続けている人はすばらしいとは思いますが、だいぶ希有な人材かと思います。
人間の集中力の限界は一般的に90分といわれており、休憩時間を有効に活用することも大切ですよね。
勤怠管理アプリでは、出退勤の時刻を打刻する以外に、総休憩時間を管理する必要があります。
ただし、休憩時間は毎回の休憩開始/終了の時間までは管理せず、一日に取得した休憩時間の総和だけを残すという前提です。

ポイントは「休憩開始時間をどう管理するか?」です。
休憩終了までは、保持しておきたいけど、休憩終了時に時間差計算で使ったら、もういらない!という一時利用データです。

つまり、休憩終了後は「休憩開始」時間などゴミのようなものです。
そんなものはわざわざ蓄積したくないですよね。
一時保存して、復帰時の「休憩時間」を算出したらすぐに捨てましょう!

kintoneアプリ作成

アプリの作成

以下のフィールドを配置したタイムカードアプリを作成します。
アプリの作成方法については、「 アプリをはじめから作成する (External link) 」を参照してください。

従業員名簿
フィールドの種類 フィールド名 フィールドコード 備考
文字列(1行) 社員番号 社員番号
  • 「値の重複を禁止する」にチェックする
  • 「タイムカード」アプリに値がコピーされるフィールド
文字列(1行) 氏名 氏名
  •  「タイムカード」アプリに値がコピーされるフィールド
文字列(1行) フリガナ フリガナ
文字列(1行) アカウント名 アカウント名
  • kintoneのログインIDを入力する
    「タイムカード」アプリでは、ログインしているユーザーのIDと「アカウント名」フィールドの値が一致するレコードを従業員情報として利用します。
タイムカード
フィールドの種類 フィールド名 フィールドコード 備考
ルックアップ 社員番号 社員番号
  • 「関連付けるアプリ」に、作成した「従業員名簿」アプリを指定する
  • 「コピー元のレコードの選択時に表示するフィールド」に「氏名」「アカウント名」を指定する
文字列(1行) 氏名 氏名
文字列(1行) アカウント名 アカウント名
日付 日付 日付
時刻 出勤 出勤
  •  「レコード登録時の時刻を初期値にする」のチェックを外す
時刻 退勤 退勤
  • 「レコード登録時の時刻を初期値にする」のチェックを外す
数値 休憩[分] 休憩
チェックボックス 休憩中 休憩中
  • 「フィールド名を表示しない」にチェックする
  • 「項目と順番」に「休憩中」を設定する
日時 休憩開始時刻(計算用) 休憩開始
  • 「フィールド名を表示しない」にチェックする
  • 「レコード登録時の時刻を初期値にする」のチェックを外す
文字列(1行) 備考(ワークフローURL) 備考

カスタマイズビューの作成

「タイムカード」アプリに作成します。

タイムカード画面に出退勤や休憩時間を打刻するボタンを配置するため、カスタマイズビュー(カスタマイズ形式の一覧)を作成します。
カスタマイズ形式の一覧の作成方法については、「 一覧を設定する (External link) 」を参照してください。

カスタマイズビューの「HTML」には、以下の内容を入力してください。
また、「ページネーションを表示する」のチェックは外してください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<!--
 * 勤怠管理 タイムカード
 * Copyright (c) 2014 Cybozu
 *
 * Licensed under the MIT License
 * https://opensource.org/license/mit/
-->
<title>サンプル</title>
<meta charset='utf-8' />

<style type="text/css">
  body, ul, li {margin:0; padding:0; list-style:none;}
/*日時表示*/
  div.date-time {
    height:50px;
    padding:10px;
    background:#44810F;
    border-bottom:1px solid #fff;
    color:#fff;
  }
  .year {width:150px; text-align:center; virtical-align:bottom; }
  .date {width:150px; text-align:center;}
  .time {font-size:200%;}
  /*ボタン*/
  div.buttons {
    /*color:#fff;*/
    height:170px;
    padding-top:30px;
    background:#77C434;
  }
  .buttons li {
    float:left;
    width:120px;
    text-align:center;
  }
  .buttons li span{
    display:block;
    background:#3BB111;
    margin:auto;
    font-size:150%;
    color:#ffffff;
    opacity:0.3;
    width:100px;
    height:50px;
    line-height:50px;
    border:1px solid #BDE7A2;
    border-radius:7px;
    box-shadow:1px 1px 4px 0 #4CAA2A, 0px 1px 1px 0 #91D877 inset;
  }
  .buttons li input{
    display:block;
    background:#3BB111;
    margin:auto;
    font-size:150%;
    color:#ffffff;
    opacity:1.0;
    width:100px;
    height:50px;
    line-height:50px;
    border:1px solid #BDE7A2;
    border-radius:7px;
    box-shadow:1px 1px 4px 0 #4CAA2A, 0px 1px 1px 0 #91D877 inset;
  }
</style>

<div class='date-time'>
  <table>
    <tr>
      <td class='year' id='year'>2014</td>
      <td rowspan='2' class='time' id='time'>16:42:03</td>
    </tr>
    <tr>
      <td class='date' id='date'>4/15 Tue</td>
    </tr>
  </table>
</div>
<div class='buttons'>
  <ul>
    <li><span id='syukkin'>出勤</span></li>
    <li><span id='taikin'>退勤</span></li>
    <li><span id='kyuukei'>休憩</span></li>
    <li><span id='fukki'>復帰</span></li>
  </ul>
</div>

JavaScriptカスタマイズ

TimeCard.js

タイムカードを表示するJavaScriptファイルです。
48行目のCUSTOM_VIEW_IDは、作成したカスタマイズビューIDに変更してください。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
/*
 * 勤怠管理 タイムカード
 * Copyright (c) 2014 Cybozu
 *
 * Licensed under the MIT License
 * https://opensource.org/license/mit/
 */
(function() {
  // 定数、変数定義、関数定義
  'use strict';

  // サーバーの時間を取得する
  function getUTCDateByServer() {
    const r = new XMLHttpRequest();
    return (r)
      ? (r.open('HEAD', '#', false), r.send(null), new Date(r.getResponseHeader('Date'))) : null;
  }

  // 指定された文字の指定された長さのゼロサプレスを取得する
  function zero_suppress(s, len) {
    let str = '';
    for (let i = 0; i < len; i += 1) {
      str += '0';
    }
    str += s;
    return str.substr(str.length - len, len);
  }

  // 時計に表示する関数
  function set_digitTime() {
    const dt = new Date(getUTCDateByServer());
    const DAY = ['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT'];
    const yyyy = dt.getFullYear();
    const MM = zero_suppress(dt.getMonth() + 1, 2);
    const dd = zero_suppress(dt.getDate(), 2);
    const hh = zero_suppress(dt.getHours(), 2);
    const mm = zero_suppress(dt.getMinutes(), 2);
    const ss = zero_suppress(dt.getSeconds(), 2);

    $('#year')[0].innerHTML = yyyy;
    $('#date')[0].innerHTML = MM + '/' + dd + ' ' + DAY[dt.getDay()];
    $('#time')[0].innerHTML = hh + ':' + mm + ':' + ss;
  }

  // イベントハンドラ実行
  const events = ['app.record.index.show'];
  kintone.events.on(events, (event) => {
    if (event.viewId !== CUSTOM_VIEW_ID) {
      return;
    }
    // タイムカード
    // アカウント=ログインユーザー、日付=今日 のレコードをすべて取得
    const appId = kintone.app.getId();
    const user = kintone.getLoginUser();
    const qryInfo = 'アカウント名 = "' + user.code + '" and 日付 = TODAY() order by 出勤 desc limit 1';
    const params = {
      app: appId,
      query: qryInfo,
      fields: [
        '社員番号',
        'アカウント名',
        '日付',
        '出勤',
        '退勤',
        '休憩中'
      ]
    };
    kintone.api(kintone.api.url('/k/v1/records', true), 'GET', params, (resp) => {

      // resp['records'] の長さが0 or 1以上の場合で状態を変える
      // 今日の レコード数 = 0
      if (resp.records.length === 0) {
        $('#syukkin').replaceWith('<input id=\'syukkin\' type=\'button\' value=\'出勤\'>');
        $('#syukkin').on('click', clickFunction);
      } else if (resp.records.length === 1) {
        // 今日の レコード数 >= 1
        if (resp.records[0]['退勤'].value === null) {
          if (resp.records[0]['休憩中'].value[0] === '休憩中') {
            $('#fukki').replaceWith('<input id=\'fukki\' type=\'button\' value=\'復帰\'>');
            $('#fukki').on('click', clickFunction);
          } else {
            $('#kyuukei').replaceWith('<input id=\'kyuukei\' type=\'button\' value=\'休憩\'>');
            $('#kyuukei').on('click', clickFunction);

            $('#taikin').replaceWith('<input id=\'taikin\' type=\'button\' value=\'退勤\'>');
            $('#taikin').on('click', clickFunction);
          }
        } else {
          $('#syukkin').replaceWith('<input id=\'syukkin\' type=\'button\' value=\'出勤\'>');
          $('#syukkin').on('click', clickFunction);
        }
      }

    });

    // クリック関数
    function clickFunction() {
      const id = $(this).attr('id');
      switch (id) {
        case 'syukkin': syukkin_func(); break;
        case 'taikin': taikin_func(); break;
        case 'kyuukei': kyuukei_func(); break;
        case 'fukki': fukki_func(); break;
      }
    }

    // 出勤関数
    function syukkin_func() {
      // ルックアップ先のID取得、従業員情報の取得
      const applookId = kintone.app.getLookupTargetAppId('社員番号');
      const qryInfoSyukkin = 'アカウント名= "' + user.code + '" order by レコード番号 desc limit 1';
      const paramsSyukkin = {
        app: applookId,
        query: qryInfoSyukkin,
        fields: [
          '社員番号',
          '氏名',
          'アカウント名'
        ]
      };
      kintone.api(kintone.api.url('/k/v1/records', true), 'GET', paramsSyukkin, (resp) => {
        // 従業員情報の取得
        const employeeNo = resp.records[0]['社員番号'].value;
        const employeeName = resp.records[0]['氏名'].value;
        const employeeAccount = resp.records[0]['アカウント名'].value;
        // 日時取得
        const dt = new Date(getUTCDateByServer());
        const yyyy = dt.getFullYear();
        const MM = zero_suppress(dt.getMonth() + 1, 2);
        const dd = zero_suppress(dt.getDate(), 2);
        const hh = zero_suppress(dt.getHours(), 2);
        const mm = zero_suppress(dt.getMinutes(), 2);
        const syukkin_date = yyyy + '-' + MM + '-' + dd;
        const syukkin_time = hh + ':' + mm;

        const body = {
          app: appId,
          record: {
            社員番号: {
              value: employeeNo
            },
            氏名: {
              value: employeeName
            },
            アカウント名: {
              value: employeeAccount
            },
            日付: {
              value: syukkin_date
            },
            出勤: {
              value: syukkin_time
            }
          }
        };
        // 出退勤アプリにrecord登録
        kintone.api(kintone.api.url('/k/v1/record', true), 'POST', body).then(location.reload());
      });
    }

    // 休憩関数
    function kyuukei_func() {
      // レコード取得
      const qryInfoKyuukei = 'アカウント名 = "' + user.code + '" order by レコード番号 desc limit 1';
      const paramsKyuukei = {
        app: appId,
        query: qryInfoKyuukei,
        fields: ['レコード番号']
      };
      kintone.api(kintone.api.url('/k/v1/records', true), 'GET', paramsKyuukei, (resp) => {
        // 従業員情報の取得
        const employeeRecId = resp.records[0]['レコード番号'].value;
        // 日時取得
        const dt = new Date(getUTCDateByServer());
        const yyyy = dt.getFullYear();
        const MM = zero_suppress(dt.getMonth() + 1, 2);
        const dd = zero_suppress(dt.getDate(), 2);
        const hh = zero_suppress(dt.getHours(), 2);
        const mm = zero_suppress(dt.getMinutes(), 2);
        const kyuukei_starttime = yyyy + '-' + MM + '-' + dd + 'T' + hh + ':' + mm + ':00+09:00';

        const body = {
          app: appId,
          id: employeeRecId,
          record: {
            休憩中: {
              value: ['休憩中']
            },
            休憩開始: {
              value: kyuukei_starttime
            }
          }
        };
        // レコード更新へ
        kintone.api(kintone.api.url('/k/v1/record', true), 'PUT', body).then(location.reload());
      });
    }

    // 復帰関数
    function fukki_func() {
      // レコード取得
      const qryInfoFukki = 'アカウント名 = "' + user.code + '" order by レコード番号 desc limit 1';
      const paramsFukki = {
        app: appId,
        query: qryInfoFukki,
        fields: [
          'レコード番号',
          '休憩開始',
          '休憩'
        ]
      };

      kintone.api(kintone.api.url('/k/v1/records', true), 'GET', paramsFukki, (resp) => {
        // 従業員情報の取得
        const employeeRecId = resp.records[0]['レコード番号'].value;
        const kyuukei_starttime = resp.records[0]['休憩開始'].value;
        let kyuukei_time_total = resp.records[0]['休憩'].value;

        // 日時取得
        const dt = new Date(getUTCDateByServer());
        const yyyy = dt.getFullYear();
        const MM = zero_suppress(dt.getMonth() + 1, 2);
        const dd = zero_suppress(dt.getDate(), 2);
        const hh = zero_suppress(dt.getHours(), 2);
        const mm = zero_suppress(dt.getMinutes(), 2);
        const kyuukei_endtime = yyyy + '-' + MM + '-' + dd + 'T' + hh + ':' + mm + ':00+09:00';

        const ST = new Date(kyuukei_starttime);
        const ET = new Date(kyuukei_endtime);
        let kyuukei_time;
        if (ET - ST !== 0) {
          kyuukei_time = Math.round((ET - ST) / (1000 * 60));
        } else {
          kyuukei_time = 0;
        } // 分を表す

        kyuukei_time_total = String(Number(kyuukei_time_total) + Number(kyuukei_time)); // 休憩時間トータル

        const body = {
          app: appId,
          id: employeeRecId,
          record: {
            休憩中: {
              value: []
            },
            休憩開始: {
              value: null
            },
            休憩: {
              value: kyuukei_time_total
            }
          }
        };

        // レコード更新へ
        kintone.api(kintone.api.url('/k/v1/record', true), 'PUT', body).then(location.reload());
      });
    }

    // 退勤関数
    function taikin_func() {
      // レコード取得
      const qryInfotTaikin = 'アカウント名 = "' + user.code + '" order by レコード番号 desc limit 1';
      const paramsTaikin = {
        app: appId,
        query: qryInfotTaikin,
        fields: [
          'レコード番号',
          '日付',
          '出勤',
          '休憩'
        ]
      };

      kintone.api(kintone.api.url('/k/v1/records', true), 'GET', paramsTaikin, (resp) => {
        // 従業員情報の取得
        const employeeRecId = resp.records[0]['レコード番号'].value;
        const syukkin_day = resp.records[0]['日付'].value;
        const syukkin_time = resp.records[0]['出勤'].value;
        const kyuukei_time_total = resp.records[0]['休憩'].value;
        // 日時取得
        const dt = new Date(getUTCDateByServer());
        const yyyy = dt.getFullYear();
        const MM = zero_suppress(dt.getMonth() + 1, 2);
        const dd = zero_suppress(dt.getDate(), 2);
        const hh = zero_suppress(dt.getHours(), 2);
        const mm = zero_suppress(dt.getMinutes(), 2);
        const taikin_time = hh + ':' + mm;

        const syukkin_daytime = syukkin_day + 'T' + syukkin_time + ':00+09:00';
        const taikin_daytime = yyyy + '-' + MM + '-' + dd + 'T' + taikin_time + ':00+09:00';
        const ST = new Date(syukkin_daytime);
        const TT = new Date(taikin_daytime);
        let kousoku_time;
        if (TT - ST !== 0) {
          kousoku_time = Math.round((TT - ST) / (1000 * 60));
        } else {
          kousoku_time = 0;
        }
        const jitsudou_time = Number(kousoku_time) - Number(kyuukei_time_total);

        const body = {
          app: appId,
          id: employeeRecId,
          record: {
            退勤: {
              value: taikin_time
            },
            拘束時間: {
              value: kousoku_time
            },
            実働時間: {
              value: jitsudou_time
            }
          }
        };

        // レコード更新へ
        console.log('出勤' + syukkin_time);
        console.log('退勤' + taikin_time);
        console.log('拘束時間' + String(kousoku_time));
        console.log('実働時間' + String(jitsudou_time));
        kintone.api(kintone.api.url('/k/v1/record', true), 'PUT', body).then(location.reload());
      });
    }

    // 1000ミリ秒ごとにset_digitTime()を繰り返す
    set_digitTime();
    setInterval(set_digitTime, 1000);
  });

  // レコード編集画面を開いた際に、時刻フィールドは閲覧Onlyになるように
  // イベントハンドラ実行
  kintone.events.on('app.record.edit.show', (resp) => {
    resp.record['休憩'].disabled = true;
    return resp;
  });

})();

「タイムカード」アプリにカスタマイズを設定します。

このカスタマイズでは、JavaScriptライブラリを使っているので、それらもkintoneに適用します。
TimeCard.js と合わせて、「JavaScript / CSSでカスタマイズ」画面にて、以下のライブラリURLを「PC用のJavaScriptファイル」に設定してください。

  • jQuery 2.2.0
    https://js.cybozu.com/jquery/2.2.0/jquery.min.js
caution
警告

TimeCard.jsより先にjQueryを指定してください。

「休憩時間」算出の流れ

今回は、「休憩開始」時間を『日時フィールド』に一時保存して「休憩時間」を算出する方法で実装しました。
手順は以下です。

  1. 「休憩」ボタンをクリックする。
    「休憩中」ステータスになり、「休憩開始」時間が日時フィールドに保存される。

  2. 「復帰」ボタンをクリックする。
    「休憩中」ステータスは解除され、ボタンクリック時の「休憩終了」時間から手順1で取得してあった「休憩開始」時間の差分が「休憩[分]」に登録される。
    「休憩開始」時間の値はクリアされる。

日付フィールドを編集不可にする

編集画面では「日時フィールド」は編集できないようにフィールドの属性を操作しています。

サンプルコードの解説

「休憩」ボタンのクリックイベントで行う処理

「休憩」ボタンクリックのイベントをトリガーとして実施される処理箇所のソースは以下です。

cybozu環境のサーバー時間を取得する関数を用意

クライアント端末の時刻を基にすると、端末間での誤差が生じたり、利用ユーザー側で時刻操作等が可能になってしまいます。
そのため、打刻に利用する時間は、cybozu環境のサーバー時間を利用します。

13
14
15
16
17
function getUTCDateByServer() {
  const r = new XMLHttpRequest();
  return (r)
    ? (r.open('HEAD', '#', false), r.send(null), new Date(r.getResponseHeader('Date'))) : null;
}
「休憩」ボタンクリック時に実行されるハンドラー

カスタムビューの「休憩」ボタンクリック時は、ログインIDと現在の日付を利用してレコードを絞り込み、アプリID(appId)と絞り込んだ対象のレコードID(employeeRecId)を基に以下の処理を行っています。

  1. 「休憩中」チェックボックスにチェックを入れる。
  2. 「休憩開始」日時フィールドに、"yyyy-MM-ddThh:mm:00+09:00" というISO8601に準拠した日時フォーマットでサーバー時間を登録する。

なお、前段階で「出勤していて休憩に入っていない」という状態の特定は行っています。

162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
function kyuukei_func() {
  // レコード取得
  const qryInfoKyuukei = 'アカウント名 = "' + user.code + '" order by レコード番号 desc limit 1';
  const paramsKyuukei = {
    app: appId,
    query: qryInfoKyuukei,
    fields: ['レコード番号']
  };
  kintone.api(kintone.api.url('/k/v1/records', true), 'GET', paramsKyuukei, (resp) => {
    // 従業員情報の取得
    const employeeRecId = resp.records[0]['レコード番号'].value;
    // 日時取得
    const dt = new Date(getUTCDateByServer());
    const yyyy = dt.getFullYear();
    const MM = zero_suppress(dt.getMonth() + 1, 2);
    const dd = zero_suppress(dt.getDate(), 2);
    const hh = zero_suppress(dt.getHours(), 2);
    const mm = zero_suppress(dt.getMinutes(), 2);
    const kyuukei_starttime = yyyy + '-' + MM + '-' + dd + 'T' + hh + ':' + mm + ':00+09:00';

    const body = {
      app: appId,
      id: employeeRecId,
      record: {
        休憩中: {
          value: ['休憩中']
        },
        休憩開始: {
          value: kyuukei_starttime
        }
      }
    };
    // レコード更新へ
    kintone.api(kintone.api.url('/k/v1/record', true), 'PUT', body).then(location.reload());
  });
}

「復帰」のクリックイベントで行う処理

「復帰」ボタンクリックのイベントをトリガーとして実施される処理箇所のソースは以下です。
「休憩」ボタンクリック時の実施内容よりも多くの処理が含まれています。

  1. 「休憩中」チェックボックスのチェックを外す。
  2. 現在の「休憩[分]」の値を取得する。
  3. 「休憩開始」日時フィールドの値を取得する。
  4. 現在のサーバー時刻を取得する。
  5. 今回の休憩時間を算出する。(手順4の値 - 手順3の値)
  6. 手順2の値に手順5の値を足したものを最新の「休憩[分]」の値としてセットする。
  7. 「休憩開始」日時フィールドの値をクリアする。
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
function fukki_func() {
  // レコード取得
  const qryInfoFukki = 'アカウント名 = "' + user.code + '" order by レコード番号 desc limit 1';
  const paramsFukki = {
    app: appId,
    query: qryInfoFukki,
    fields: [
      'レコード番号',
      '休憩開始',
      '休憩'
    ]
  };

  kintone.api(kintone.api.url('/k/v1/records', true), 'GET', paramsFukki, (resp) => {
    // 従業員情報の取得
    const employeeRecId = resp.records[0]['レコード番号'].value;
    const kyuukei_starttime = resp.records[0]['休憩開始'].value;
    let kyuukei_time_total = resp.records[0]['休憩'].value;

    // 日時取得
    const dt = new Date(getUTCDateByServer());
    const yyyy = dt.getFullYear();
    const MM = zero_suppress(dt.getMonth() + 1, 2);
    const dd = zero_suppress(dt.getDate(), 2);
    const hh = zero_suppress(dt.getHours(), 2);
    const mm = zero_suppress(dt.getMinutes(), 2);
    const kyuukei_endtime = yyyy + '-' + MM + '-' + dd + 'T' + hh + ':' + mm + ':00+09:00';

    const ST = new Date(kyuukei_starttime);
    const ET = new Date(kyuukei_endtime);
    let kyuukei_time;
    if (ET - ST !== 0) {
      kyuukei_time = Math.round((ET - ST) / (1000 * 60));
    } else {
      kyuukei_time = 0;
    } // 分を表す

    kyuukei_time_total = String(Number(kyuukei_time_total) + Number(kyuukei_time)); // 休憩時間トータル

    const body = {
      app: appId,
      id: employeeRecId,
      record: {
        休憩中: {
          value: []
        },
        休憩開始: {
          value: null
        },
        休憩: {
          value: kyuukei_time_total
        }
      }
    };

    // レコード更新へ
    kintone.api(kintone.api.url('/k/v1/record', true), 'PUT', body).then(location.reload());
  });
}

「休憩」フィールドの直接編集を不可にする

JavaScript APIを利用する場合は、ログインユーザーのアカウントでJavaScript APIを通じてフィールドの値を操作するということが必須になります。
ただし、今回の「休憩」フィールドのように、「復帰」ボタンクリックのタイミングで休憩時間(分)を計算し入力されてほしいが、GUI画面から直接編集することは不可にしてほしいという要望は多くあります。
この場合、現状のkintone標準機能ではAPI操作とGUI操作のどちらでも同一のアクセス権付与しかできないため、以下のようにフィールドの属性を操作して、GUIからは操作できないようにするというしくみが必要になります。

334
335
336
337
kintone.events.on('app.record.edit.show', (resp) => {
  resp.record['休憩'].disabled = true;
  return resp;
});

これは、たとえばBPMと組み合わせることで、申請者 → 上長へとステップが変わる時に、入力可能なフォームを変えたいというような要望に対応できます。
このように、標準機能で実現できない部分は、ちょっとしたカスタマイズでファストに対応できるのがkintoneのいい所ですね!

おわりに

今回は、営業という立場から話多めにTipsの記載をしました。
JavaScriptをちょこちょこ触りながら営業周りをしているという立場だからこそ、業務に役立つプログラムを考えることもできるかなと思っています。

このタイムカードアプリだけでも、kintone APIを活用した話題はいろいろあるので、また少しずつ、紹介できていければよいなと思っています。
kintoneでプログラミングを楽しみながら、そして、皆様と触れ合う機会を楽しみながら、一緒にkintone界隈を盛り上げていければよいなと思っています。

今後ともよろしくお願いします。