社内のスマホ端末管理にTSUTAYAレジ風システムを導入した(予定)

# はじめに
この記事は1年近く前に投稿された「[社内の備品貸出にTSUTAYAのレジ風システムを導入した](http://qiita.com/tabbyz/items/f42840504ecc6ff4f827)」を ~~ぱくって~~ リスペクトして作ったシステムのお話です。
クライアントはAndroid(Kotlin)、サーバーはGASを使うことで短期間(2,3日くらい)で作成できた。

システム名は「**たんまつ管理さん**」

(こらそこ、ダサいとか言わない)

*なお、この記事投稿時点ではまだ運用してないです。*

# システムの構成図

![system.001.jpeg](https://qiita-image-store.s3.amazonaws.com/0/76726/3869e8b3-73f9-4a80-c05a-0054a1a479d9.jpeg)

- サーバー側の窓口をGASで実装。
- GASがGoogleSpreadSheetと連携することで端末のデータを保存したり更新したりする
- GASのスクリプト定期実行を利用して貸出期限切れがないかを定期的にチェック。期限切れがあれば即メールが飛ぶようになってる(組織によってはSlackとかでもいいかも)
- サーバーを社内に置いていないのでスマホが社内ネットワークに繋がっていないと〜的なことが一切ない。LTEで繋がる。

# スマホを識別する仕組み
まんまリスペクトした記事を参考にしました。

が！弊社のテプラが古いためQRコードを出力できず、PCのフリーサイトでQRコードを作成＆印刷→テープでぺたりとかいう~~クソダサな~~対応になりそう。
新しいテプラを買ってくれないか交渉予定。
そんなわけでここは写真なしです。。

仕様的には、「`<デバイス名>, <デバイス識別子>`」といった形で、カンマ区切りで情報を並べる感じになってます。`Galaxy Nexus 7(OS:5.1.1), 12`みたいな感じ

# 会員カード（社員証でおk）
弊社の社員証の裏にはQRコードがプリントされており、そこには

- 氏名
- メールアドレス
- 会社名
- 社員番号

といった情報が書かれている。使わない手はない！ってことでこいつを利用。会員カード作る手間も、配布の手間もなくなって一石二鳥

# クライアントアプリ
ソースは[ここ](https://github.com/kiuchikeisuke/DeviceKanriSan)。ただし**GASのURLはそれぞれ異なる関係上、修正する必要がある**。詳しくはREADME.mdを参照。

## 最初の画面

![login.png](https://qiita-image-store.s3.amazonaws.com/0/76726/e8b76c64-dbad-b93b-14f7-9468163756ed.png)

## ログイン画面

![qr_login.png](https://qiita-image-store.s3.amazonaws.com/0/76726/3a86ed18-d78f-77cb-4417-3cd357357b62.png)

## メイン画面

![main.png](https://qiita-image-store.s3.amazonaws.com/0/76726/a2feca27-672c-3fcf-d5af-8acc0e3cc7d6.png)

## 借用、返却画面

![rentaled.png](https://qiita-image-store.s3.amazonaws.com/0/76726/2dc3b605-599a-506e-9e1c-d5f124f64dcd.png)

![returned.png](https://qiita-image-store.s3.amazonaws.com/0/76726/6a32a8b6-02c5-8dd4-c246-306a81f56e2a.png)


## 端末一覧画面

![list_rental.png](https://qiita-image-store.s3.amazonaws.com/0/76726/3a63a53f-8a16-39b5-91ab-cb9a2c626825.png)

## QRコード読みとりのあれこれ
QRコード読み取りには[zxing](https://github.com/zxing/zxing)を使用、、したんですが、Android端末自体のフォーカス性能がiPhoneと比べ劣っているせいなのか、QRコードを読み込むのにどうしても時間がかかるのがストレス。
QRコード読み取りの実装自体は[こちらの記事](http://osa030.hatenablog.com/entry/2015/10/14/190350)を参考にして実装。

## 端末一覧画面のあれこれ
参考にした「ネコレジ」にない機能としては、端末一覧の機能。この画面では全端末の状態が一覧できる。
レンタル可能なら白色背景、レンタル中は黄色、返却期限が過ぎているものは赤色になる。
一応申し訳程度の検索機能も用意はしてみたものの、And/Or検索できないという~~クソ~~仕様。端末数がめちゃ多くなったらいるのかなぁ〜・・・。

![list_rental.png](https://qiita-image-store.s3.amazonaws.com/0/76726/3a63a53f-8a16-39b5-91ab-cb9a2c626825.png)

## Master/Slaveビルド
もう一つ「ネコレジ」にない機能として、Slaveビルドという設定をbuild.gradleで設けている。


```groovy:build.gradle
    flavorDimensions "default"
    productFlavors {
        master {
            buildConfigField("jp.devicekanrisan.utils.commons.AppType", "APP_TYPE", "jp.devicekanrisan.utils.commons.AppType.MASTER")
            dimension "default"
        }
        slave {
            buildConfigField("jp.devicekanrisan.utils.commons.AppType", "APP_TYPE", "jp.devicekanrisan.utils.commons.AppType.SLAVE")
            dimension "default"
        }
    }
```

この設定が作用してるのは下のコード

```kotlin:LoginFragment.kt
    override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        if (BuildConfig.APP_TYPE == AppType.MASTER) {
            login_button.setOnClickListener {
                QRCodeReaderActivity.launch(context, navigateMessageId = R.string.navigate_message_login)
                activity.finish()
            }
        } else {
            login_button.visibility = View.GONE
        }
        list_button.setOnClickListener {
            DeviceListActivity.launch(context)
        }
    }
```

つまるところ、Slaveビルドだとこうなる

![login_slave.png](https://qiita-image-store.s3.amazonaws.com/0/76726/4ac11d5e-6f1f-46f8-f539-4633f0698b82.png)

Slaveビルドしたアプリは配布して端末状態を好きな時に確認できるように、Masterビルドしたアプリは端末置き場付近で貸出機として利用することができる。
Masterビルドしたものを配布してしまうと端末置き場に端末を返さなくても貸出、返却処理が行えてしまうという問題が出て来てしまうのでこのように対応。

## 通信APIはSwaggerで作成
過去に何回か記事でも取り上げた[Swagger](https://swagger.io/)でサクッとAPIを実装。残念ながらサーバー側(GAS)のgeneratorはないので今回はクライアント側のみの利用。それだけでも十分楽チン。
GASをサーバーとして使う場合、pathは`exec/`を指定しないといけないのが注意。というのもAndroid側（というよりはRetrofit2側）の仕様なのか、ルートパス指定だとアノテーションが`@GET("") @POST("")`みたいな感じで出力されてしまい、パスを認識してくれなかった。

今回作成したAPIは[Gistに公開](https://gist.github.com/kiuchikeisuke/7b7f38867231c748c81bc8cb377a27ec)してあるので参考に。

# サーバー
## GoogleActionScript
GASのいいところはサーバーレスでサーバーっぽいことが簡単に書けるところ。
詳しいサーバーの実装は[Gistに公開](https://gist.github.com/kiuchikeisuke/422e23b57948a4fed3c6b579b4201cf1)したのでこちらを参考に。
やってることはdoPostとdoGetでそれぞれJSONを受け取り、JSONでデータを返してるだけ。

またGASの特徴として定期実行も簡単に行える。それを利用して、１時間ごとに期限切れの人がいないかをチェックし、期限切れをしていたらメールを通知するようにしている。
具体的には以下の部分

```js:main.gs
function timer() {
  checkReturnDate();
}

function checkReturnDate() {
  var now = new Date();
  var dataAll = getData();
  for( var i = 1; i < dataAll.length; i++) {
    var data = dataAll[i]
    if (data[Const.statusColumnIndex - 1] == "using" && now.getTime() > data[Const.returnDateColumnIndex - 1].getTime()) {
      sendNotify(data);
    }
  }
}

function sendNotify(data) {
  //通知を行う(mail)
  var address = data[Const.mailAddressColumnIndex - 1];
  var subject = "端末「" + data[Const.deviceNameColumnIndex - 1] + "」の返却期限が過ぎています!!";
  var body = "端末管理BOTからの連絡です\n\n" + data[Const.userNameColumnIndex - 1] 
  + "さんの借りている以下の端末は返却期限を過ぎています。可及的速やかに返却処理を行ってください\n【端末】\n"
  + data[Const.deviceNameColumnIndex - 1] + "\n\n\n※なお、この通知は返却しない限り１時間おきに実行されます";
  MailApp.sendEmail(address, subject, body)
}

function getData() {
  return data = getSheet().getDataRange().getValues();
}

/* シートを取得する */
function getSheet() {
  if (getSheet.instance) { return getSheet.instance; }
  var sheet = SpreadsheetApp.openById(Const.spreadSheetId).getSheetByName(Const.sheetName);
  return sheet;
}
```

ここではtimerメソッドを定期的に実行している。