バーコードスキャナを使ってkintoneでトレースを実現する

著者名:近本 昌也

目次

caution
警告

Moment.jsはメンテナンスモードになり、 日付処理できる代替ライブラリへの移行 (External link) が推奨されています。
代替ライブラリのひとつ Luxon (External link) については、 kintoneカスタマイズでの導入方法の紹介記事 があります。

はじめに

ユニテック・ジャパン株式会社のワイヤレスバーコードスキャナMS910とkintoneのモバイルアプリでトラッキングを実現するTipsを紹介します。

概要

本Tipsでは「バーコードマスター」と「バーコード送信アプリ」の2つのアプリを用意します。
MS910の詳細は 製品概要 (External link) を確認ください。

連携イメージ

  1. 「倉庫A」から「倉庫B」に商品を輸送します。
  2. kintoneの「バーコード送信アプリ」をモバイルで表示し、現在所在地を「倉庫B」に変更して輸送された商品のバーコードを読み取ります(文字列複数行フィールドに改行区切りで登録)
  3. レコードを保存すると「バーコードマスター」に登録されている商品の現在所在地が更新されます。

下準備

  • MS910の購入および設定
  • バーコード送信アプリの作成
  • バーコードマスターの作成

MS910の購入および設定

製品概要 (External link) のクイック接続ガイドを参照しながら、お手持ちのモバイル端末とBluetooth接続をします。接続確認にはメモなどの文字入力可能なアプリを実行してください。
ターミネータ(バーコード読み取り時の最後に付加されるコマンドのこと)には「Enter」を設定してください。

バーコード送信アプリ

フォーム設定例

フィールドタイプ フィールド名(例) フィールドコード 備考
日付 日付 date
チェックボックス 送信済み sentcheck
ユーザー選択 送信者 sentuser
ドロップダウン 送信する現在所在地 location オプション
  • 東京倉庫
  • 大阪倉庫
  • 松山倉庫
文字列(複数行) 送信するバーコード一覧 barcode 改行区切りでバーコードを入力するフィールドになります。
ラベル - - 送信したデータ
テーブル Table
日時 送信日時 sentdate テーブル内フィールド
文字列(1行) 送信したバーコード sentbarcode テーブル内フィールド
文字列(1行) 送信した所在地 sentlocation テーブル内フィールド
JavaScriptカスタマイズ

「PC用のJavaScriptファイル」と「モバイル用のJavaScriptファイル」にそれぞれ同じファイルをアップします。
詳細設定から「JavaScript / CSSによるカスタマイズ」を開き、 Cybozu CDN から次のライブラリとサンプル用のファイルを順番に指定します。

PC用のJavaScriptファイルおよびモバイル用のJavaScriptファイル

  • jQuery
    https://js.cybozu.com/jquery/2.2.4/jquery.min.js(version 2.2.4を利用)
  • Spin.js
    https://js.cybozu.com/spinjs/2.3.2/spin.min.js(version 2.3.2を利用)
  • Moment.js
    https://js.cybozu.com/momentjs/2.14.1/moment-with-locales.min.js(version 2.14.1を利用)
  • location.js
    ソースコードは JavaScriptソースコード を参照

バーコードマスター

フォーム設定例

フィールドタイプ フィールド名(例) フィールドコード 備考
ドロップダウン 現在所在地 location オプション
  • 東京倉庫
  • 大阪倉庫
  • 松山倉庫
文字列(1行) バーコード barcode
文字列(1行) 商品名 itemname
添付ファイル 商品画像 itemimage
ラベル - - 所在地の変更履歴
テーブル Table
日時 変更日時 date テーブル内フィールド
ユーザー選択 変更者 user テーブル内フィールド
ログインユーザーを指定
文字列(1行) 変更後の所在地 new_location テーブル内フィールド
JavaScriptカスタマイズ

バーコードマスターでは不要です。

完成イメージ(モバイル画面)

  1. バーコードマスター レコード詳細画面の更新前
    マスターの商品が大阪倉庫になっていることを確認します。

  2. バーコード送信アプリ レコード追加画面①
    「送信する現在所在地」フィールドを変更し、「送信するバーコード一覧」フィールドにフォーカスを当てた状態でMS910でバーコードをスキャンすると、スキャンしたバーコードが改行区切りで入力されます。
    対象のバーコードをすべて入力した後、保存ボタンをタップします。

  3. バーコード送信アプリ レコード追加画面②
    バーコードマスターのレコードの更新が完了すると、アラートが表示されます。

  4. バーコードマスター レコード詳細画面
    現在所在地が変更され、サブテーブルの変更履歴が更新されていることを確認できます。

JavaScriptソースコード

  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
/*
 * バーコードスキャナ とkintoneの連携
 * Copyright (c) 2016 Cybozu
 *
 * Licensed under the MIT License
 * https://opensource.org/license/mit/
 */
jQuery.noConflict();
(function($) {
  'use strict';

  // Spinner
  const Spin = {
    spinner: new Spinner({
      lines: 13,
      length: 28,
      width: 14,
      radius: 42,
      scale: 1,
      corners: 1,
      color: '#FFF',
      opacity: 0.25,
      rotate: 0,
      direction: 1,
      speed: 1,
      trail: 60,
      fps: 20,
      zIndex: 2e9,
      className: 'spinner',
      top: '50%',
      left: '50%',
      shadow: false,
      hwaccel: false,
      position: 'fixed'
    }),
    showSpinner: function() {
      if ($('.kintone-spinner').length === 0) {
        // spinner back ground
        const spin_bg_div = $('<div id ="kintone-spin-bg" class="kintone-spinner"></div>');
        $(document.body).append(spin_bg_div);

        $(spin_bg_div).css({
          position: 'fixed',
          top: '0px',
          left: '0px',
          'z-index': '500',
          width: '100%',
          height: '1000%',
          'background-color': '#000',
          opacity: '0.5',
          filter: 'alpha(opacity=50)',
          '-ms-filter': 'alpha(opacity=50)'
        });
      }
      $('.kintone-spinner').show();
      this.spinner.spin($('html')[0]);
    },
    hideSpinner: function() {
      $('.kintone-spinner').hide();
      this.spinner.stop();
    }
  };

  function getRecords(appId, resolve, reject, barcodes) {
    const limit = 500;
    const params = {
      app: appId,
      query: 'barcode in ' + barcodes + 'order by $id asc limit ' + limit
    };

    return kintone.api(kintone.api.url('/k/v1/records', true), 'GET', params).then((resp) => {

      if (resp.records.length === 0) {
        return reject('There are no records in response');
      }
      return resolve(resp.records);
    }, (error) => {
      // error
      return reject(error.message);
    });
  }

  function changeRecords(event, records) {
    const newrecords = [];
    let obj = {};
    for (let i = 0; i < records.length; i++) {
      obj.updateKey = {
        field: 'barcode',
        value: records[i].barcode.value
      };

      records[i].Table.value.unshift(
        {
          value: {
            date: {
              type: 'DATETIME',
              value: moment().format()
            },
            user: {
              type: 'USER_SELECT',
              value: [
                {
                  code: kintone.getLoginUser().code
                }
              ]
            },
            new_location: {
              type: 'SINGLE_LINE_TEXT',
              value: event.record.location.value
            }
          }
        }
      );
      obj.record = {
        location: {
          value: event.record.location.value
        },
        Table: {
          value: records[i].Table.value
        }
      };
      newrecords.push(obj);
      obj = {};
    }
    return newrecords;
  }

  function putRecords(appId, records) {
    const params = {
      app: appId,
      records: records
    };

    return kintone.api(kintone.api.url('/k/v1/records', true), 'PUT', params).then((resp) => {
      if (resp.records.length === 0) {
        return Promise.reject('There are no records in response');
      }
      const array = [];
      for (let i = 0; i < resp.records.length; i++) {
        array.push(resp.records[i].id);
      }
      const text = array.join(',');
      const textbox = 'Records were updated! \n' +
                          'appId:' + appId + '\n' +// MasterAppId
                          'recordId:' + text;
      return Promise.resolve(textbox);
    }, (error) => {
      // error
      return Promise.reject(new Error(error.message));
    });
  }

  function getTableArray(event) {
    const new_array = [];
    const table_array = event.record.barcode.value.split('\n');
    const location_val = event.record.location.value;
    if (!event.record.Table.value[0].value.sentdate.value &&
           !event.record.Table.value[0].value.sentbarcode.value &&
           !event.record.Table.value[0].value.sentlocation.value) {
      event.record.Table.value.shift();
    }

    let obj = {};
    for (let i = 0; i < table_array.length; i++) {
      if (table_array[i] !== '') {
        obj = {
          value: {
            sentdate: {
              type: 'DATETIME',
              value: moment().format()
            },
            sentbarcode: {
              type: 'SINGLE_LINE_TEXT',
              value: table_array[i]
            },
            sentlocation: {
              type: 'SINGLE_LINE_TEXT',
              value: location_val
            }
          }
        };
        new_array.push(obj);
      }
    }
    return new_array;
  }

  kintone.events.on(['app.record.create.submit', 'app.record.edit.submit',
    'mobile.app.record.create.submit', 'mobile.app.record.edit.submit'], (event) => {
    Spin.showSpinner();
    const appId = '1';
    const barcodes = event.record.barcode.value;
    const rep_barcodes = '("' + barcodes.replace(/\r\n|\r|\n/g, '","') + '")';

    return new kintone.Promise((resolve, reject) => {
      getRecords(appId, resolve, reject, rep_barcodes);

    }).then((records) => {
      const newrecords = changeRecords(event, records);
      return putRecords(appId, newrecords);

    }).then((textbox) => {
      const array_barcodes = getTableArray(event);
      event.record.Table.value = event.record.Table.value.concat(array_barcodes);
      event.record.sentcheck.value = ['sent check'];
      alert(textbox);
      Spin.hideSpinner();
      return event;

    }).catch((error) => {
      alert(error);
      Spin.hideSpinner();
      return false;
    });
  });

  kintone.events.on(['app.record.edit.show', 'mobile.app.record.edit.show',
    'app.record.create.show', 'mobile.app.record.create.show'], (event) => {
    event.record.sentcheck.disabled = true;
    return event;
  });
})(jQuery);

ソースコードの説明

スピナーの表示

処理中にSpin.showSpinner()Spin.hideSpinner()を使い分けることでスピナーの表示/非表示を制御します。

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
// Spinner
  const Spin = {
    spinner: new Spinner({
      lines: 13,
      length: 28,
      width: 14,
      radius: 42,
      scale: 1,
      corners: 1,
      color: '#FFF',
      opacity: 0.25,
      rotate: 0,
      direction: 1,
      speed: 1,
      trail: 60,
      fps: 20,
      zIndex: 2e9,
      className: 'spinner',
      top: '50%',
      left: '50%',
      shadow: false,
      hwaccel: false,
      position: 'fixed'
    }),
    showSpinner: function() {
      if ($('.kintone-spinner').length === 0) {
        // spinner back ground
        const spin_bg_div = $('<div id ="kintone-spin-bg" class="kintone-spinner"></div>');
        $(document.body).append(spin_bg_div);

        $(spin_bg_div).css({
          position: 'fixed',
          top: '0px',
          left: '0px',
          'z-index': '500',
          width: '100%',
          height: '1000%',
          'background-color': '#000',
          opacity: '0.5',
          filter: 'alpha(opacity=50)',
          '-ms-filter': 'alpha(opacity=50)'
        });
      }
      $('.kintone-spinner').show();
      this.spinner.spin($('html')[0]);
    },
    hideSpinner: function() {
      $('.kintone-spinner').hide();
      this.spinner.stop();
    }
  };

バーコードマスターからレコードの取得

「送信したバーコード一覧」フィールドの値を改行区切りで分け、「バーコードマスター」の「バーコード」フィールドと一致したすべてのレコードを取得します(参考: 複数のレコードを取得する )。
appIdにはマスターのアプリ番号を記載してください。

64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
  function getRecords(appId, resolve, reject, barcodes) {
    const limit = 500;
    const params = {
      app: appId,
      query: 'barcode in ' + barcodes + 'order by $id asc limit ' + limit
    };

    return kintone.api(kintone.api.url('/k/v1/records', true), 'GET', params).then((resp) => {

      if (resp.records.length === 0) {
        return reject('There are no records in response');
      }
      return resolve(resp.records);
    }, (error) => {
      // error
      return reject(error.message);
    });
  }

取得したレコード情報の書き換え

取得したレコードの情報に現在所在地のデータと変更履歴用のサブテーブルのデータを追加します。
unshift()を利用することでサブテーブルの一番上に最新の変更内容を表示できます。

 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
  function changeRecords(event, records) {
    const newrecords = [];
    let obj = {};
    for (let i = 0; i < records.length; i++) {
      obj.updateKey = {
        field: 'barcode',
        value: records[i].barcode.value
      };

      records[i].Table.value.unshift(
        {
          value: {
            date: {
              type: 'DATETIME',
              value: moment().format()
            },
            user: {
              type: 'USER_SELECT',
              value: [
                {
                  code: kintone.getLoginUser().code
                }
              ]
            },
            new_location: {
              type: 'SINGLE_LINE_TEXT',
              value: event.record.location.value
            }
          }
        }
      );
      obj.record = {
        location: {
          value: event.record.location.value
        },
        Table: {
          value: records[i].Table.value
        }
      };
      newrecords.push(obj);
      obj = {};
    }
    return newrecords;
  }

バーコードマスターのレコード更新

変更したレコードの情報を基にバーコードマスターのレコードを更新します(参考: 複数のレコードを更新する )。
成功時にはアラートで対象アプリの番号とレコード番号を表示します。

128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
  function putRecords(appId, records) {
    const params = {
      app: appId,
      records: records
    };

    return kintone.api(kintone.api.url('/k/v1/records', true), 'PUT', params).then((resp) => {
      if (resp.records.length === 0) {
        return Promise.reject('There are no records in response');
      }
      const array = [];
      for (let i = 0; i < resp.records.length; i++) {
        array.push(resp.records[i].id);
      }
      const text = array.join(',');
      const textbox = 'Records were updated! \n' +
                          'appId:' + appId + '\n' +// MasterAppId
                          'recordId:' + text;
      return Promise.resolve(textbox);
    }, (error) => {
      // error
      return Promise.reject(new Error(error.message));
    });
  }

バーコード送信アプリのレコード更新用配列の作成

バーコード送信アプリ内に送信したバーコードを履歴としてサブテーブルに残します。
誤送信の場合や送信したかどうかを確認したいときに利用します。

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
  function getTableArray(event) {
    const new_array = [];
    const table_array = event.record.barcode.value.split('\n');
    const location_val = event.record.location.value;
    if (!event.record.Table.value[0].value.sentdate.value &&
           !event.record.Table.value[0].value.sentbarcode.value &&
           !event.record.Table.value[0].value.sentlocation.value) {
      event.record.Table.value.shift();
    }

    let obj = {};
    for (let i = 0; i < table_array.length; i++) {
      if (table_array[i] !== '') {
        obj = {
          value: {
            sentdate: {
              type: 'DATETIME',
              value: moment().format()
            },
            sentbarcode: {
              type: 'SINGLE_LINE_TEXT',
              value: table_array[i]
            },
            sentlocation: {
              type: 'SINGLE_LINE_TEXT',
              value: location_val
            }
          }
        };
        new_array.push(obj);
      }
    }
    return new_array;
  }

イベント別関数の呼び出し

モバイルとPCの両方に保存実行前イベントでこれまでの関数を呼び出します。
一部処理に待ち合わせが必要なため、kintone.Promiseオブジェクトを使用しました。

その他には次の処理を加えています。

  • 文字列複数行で入力されたバーコードをquery用に変換する処理
  • バーコード送信アプリ内の「送信済み」フィールドにチェックを入れる処理と非活性にする処理
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
  kintone.events.on(['app.record.create.submit', 'app.record.edit.submit',
    'mobile.app.record.create.submit', 'mobile.app.record.edit.submit'], (event) => {
    Spin.showSpinner();
    const appId = '1';
    const barcodes = event.record.barcode.value;
    const rep_barcodes = '("' + barcodes.replace(/\r\n|\r|\n/g, '","') + '")';

    return new kintone.Promise((resolve, reject) => {
      getRecords(appId, resolve, reject, rep_barcodes);

    }).then((records) => {
      const newrecords = changeRecords(event, records);
      return putRecords(appId, newrecords);

    }).then((textbox) => {
      const array_barcodes = getTableArray(event);
      event.record.Table.value = event.record.Table.value.concat(array_barcodes);
      event.record.sentcheck.value = ['sent check'];
      alert(textbox);
      Spin.hideSpinner();
      return event;

    }).catch((error) => {
      alert(error);
      Spin.hideSpinner();
      return false;
    });
  });

  kintone.events.on(['app.record.edit.show', 'mobile.app.record.edit.show',
    'app.record.create.show', 'mobile.app.record.create.show'], (event) => {
    event.record.sentcheck.disabled = true;
    return event;
  });

注意事項

  • 本Tipsはkintoneのモバイルアプリ「kintone 1.0.7」、iOS 10.0.2で動作確認を行いました。
  • MS910に関する制限や、対応端末に関しては 製品概要 (External link) を参照ください。

おわりに

いかがだったでしょうか?
今回のTipsではkintoneの標準のモバイルアプリとバーコードリーダーを活用し、トラッキングの実現を紹介しました。

kintoneの標準の機能を組み合わせることで安価に在庫管理や棚卸業務を実現できそうです。