八丈島のホテルで、運用費用０円の伝票システムアプリ作って、業務改善した話。

**※2019年05月29日作成の記事です。**

はじめまして!
いつも皆さんの面白い記事を読んで、学んだり励みにしたり楽しんだりしてます！ありがとうございます！


このたび東京都八丈島のホテル、[リードパーク＆リゾート八丈島](http://www.lidohotels.jp/park/)で伝票システムアプリを作らせてもらいました！


このシステムアプリの全機能はブログの記事で動画を交えて説明しています。
（QiitaじゃTwitter経由でしかアップできないため）


[全機能説明ページはこちら](https://yoji0806.hateblo.jp/entry/2019/05/27/%E6%A5%AD%E5%8B%99%E7%94%A8%E3%83%BB%E4%BC%9D%E7%A5%A8%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AE%E8%AA%AC%E6%98%8E)

# 謝辞
### ホテルの皆様
今回自分がこんな貴重な機会を得られ、最後まで作ることができたのは、寛容で柔軟なホテル支配人・レストランリーダー・スタッフの皆様のお陰だと本当に思います。

最初は遅延もあったり、レシート２枚出てきたりしてましたが、毎日使用後に多くのフィードバックを得られたので開発がとても捗りました。
「楽しい！」「今までで１番使いやすい」などの声は本当に嬉しいです:laughing:
### Firebase(Google)さん
ありがとうFirebaseとGoogleさん!!!
お陰でサーバーレス・運用費０円で開発できました。
今度 Pixel 3a 買います

# 作ったもの　　---画像---

![G7MnKPM.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266096/903c1cc3-fbad-e588-d1c7-252f455d7a6f.jpeg)


![H1WDvTT.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266096/ee2db6bd-dafa-e8c0-c160-4fa9be480880.jpeg)


# 作ったもの　　---動画---

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">投稿用　注文① <a href="https://t.co/diXsgs4DQX">pic.twitter.com/diXsgs4DQX</a></p>&mdash; 山本 燿司 (@baske0806) <a href="https://twitter.com/baske0806/status/1133554495332311040?ref_src=twsrc%5Etfw">2019年5月29日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>




# 作ったもの　　---図---
![注文受付^B伝票アプリ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266096/9e6d23b5-830b-8899-2caf-8f8499dd729b.png)
👆この拙い図で表しきれてませんが、僕が作った業務アプリは、

 * スタッフが客席で注文をタブレットで入力 → **DBに記入**
 * バーカウンターの端末(Fire HD10)がDBの変更を**瞬時にリッスン**、DBからデータを取って、Bluetooth経由でプリンターに送信　→ **レシート印刷**
 



というものです。
これを**Android** 上で**Kotlin** & **Firebase Realtime Database**でつくりました。
（技術や機械の選定理由は後ほど）


# ちょっと自己紹介

自分は1998年生まれで、神戸大学の経済学部に通っていて現在は休学中です。
もともと文系でプログラミングとは無縁でしたが、半年ほど前に **Kotlin** のチュートリアル本から学び始めて今に至ります。Kotlin大好き。
次は **Flutter** & **Firebase** でなにか作って公開しようとおもいます。

この後は東京に行ってどこかで働きたいと思ってます！
（ご縁があればよろしくお願いします:raising_hand_tone1:）



# このアプリを作ることになった経緯
もともとこのホテルのレストランで住み込みバイトで働いていて、空いた時間に個人開発でアプリを作ろうと思ってました。


> ![r01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266096/9814216d-f413-85e5-5c63-c01282f3a94d.jpeg)
ホテルのレストラン: [公式ホームページ](http://www.lidohotels.jp/park/)より

このホテルは最大200名様分の客席があり、レストランは

 + 全１４９席
 + 個室宴会場（４０席）　ある大きいレストランです。



ここで働いているうちに色々な改善点があるなと思うようになってきました。
その内の一つ（かつ最大）が、 **ドリンクの注文 → 席に運ぶ** までの流れと、**伝票計算の処理**です。

レストランの業務は開店・閉店作業を除くと主に、以下の作業です。

1. お客様を席まで案内　→　最初のドリンク注文をとる
* 各お客様のペースに合わせて食事配膳（和食のコース料理で、約７種類）&空いた皿を下げる
* ドリンクの追加注文を受けるたび、作業中断でドリンクを作り、持っていく。

この①、③のドリンク作業がネックでした。

従来はドリンクの注文の度、
![従来のホテルレストラン図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266096/3839c468-744b-5ce6-39e3-2e47542180e7.png)


このように、**１人のスタッフが一連の作業を全て**行っていました。
夕食の時間帯は３つあって(**18時~/18時30分~/19時~**)、それぞれの時間帯で**約20～50名**ほどが一気に来られます。スタッフは１席ごとの注文で１往復するので、最初のドリンクを早く捌かないとその後の全ての作業が遅れてしまいます。
追加注文の際も、作業が中断されるので効率が落ちてしまいます。

そしてお客が全員帰ると、**各伝票に各ドリンクの単価を記入**して、**電卓で合計を計算**、記入していました。（多いときは100人分ほど）

この作業をなくしたいと思い、試作品を作って、支配人・ホテルリーダーに見せると即決で「試してみよう」となり、開発が決まりました！


# 導入後の作業フロー
![新しいホテルレストラン図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266096/99ee545f-40db-2a45-9409-1ee21fcd2a5d.png)

各スタッフが端末(Fire 7)をポケットに入れ、バーカウンターに通信用の端末１台と、サーマルプリンター１台を置いています。

`注文を受けるスタッフ`は、バーカウンターに行く必要はなく、注文入力後、次の席に行ってまた注文を受けられます。
`バーカウンターのスタッフ`は次々と印刷されるレシートを見て、ドリンクを作り、お盆にレシートと一緒にドリンクを置いていきます。
そして`手の空いているスタッフ`が、そのお盆を持ち、レシートに記されている席まで運びます。(レシートはどんどん重ねて挟んでいく。)

このように、**作業の分業化**ができるようになりました！！
さらに**単価もレシートに記載されていおり、最後に１枚１枚伝票を計算する必要もなくなりました**！

# 使用した技術・機器の選定理由
## ハード面

### Why Fire7 ？
自分がここのホテルに来る前、他の伝票サービスを導入する試みがあったらしく、もともとFire 7 はありました。（結局導入には至らなかったそうです。）
それに、ホテルのソムリエエプロンのポッケに丁度フィットします:v:

### Why（サーマルプリンター） StarPrnt TSP650||？
スター精密は安心できるし、SDKもしっかりしていて、尚且つ安価なのが決めてでした。（4万円弱）
このモデルはBluetooth通信に対応していて、印刷速度も１番早かったのでこれに決めました。

## ソフト面

### Why Android？
もともとFire端末があり、自分もAndridを学び始めたばっかだったから。
（Fire OSは、独自といってますがほぼAndroid。）
この偶然はラッキーでした。

### Why Kotlin?
自分が学び始めたのがKotlinからだったから。
（Javaと100%相互互換なので結果的にJavaも段々わかってきた。）

### Why Firebase？
最初はAWSかAzureと思っていたのですが、Firebaseは無料枠（しかも大容量）があり、それにDBの性能が最高でこれだけで十分だったからです。(とくにリアルタイム同期とオフライン処理)
Firebaseの無料枠が、AWS（Amazon）・Azure（Microsoft）との競争の中で勝つための策だとしたらAWS, Azureにも感謝です。


👇１００名ほどのディナー１日で、この容量なので、まだまだまだ余裕があります。
<img width="702" alt="2019-05-29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266096/2c20edbe-ad41-01eb-eda1-3bf365e97110.png">


### Why Firebase RealtimeDB？
これは、FireStoreと悩んだのですが結局RealtimeDBにしました。理由は、

* Realtime DBは Key-Valueなので、高速。
* FireStoreの特徴の強力なクエリは必要なかった。
* ~~ベータ版なのでちょっと気が引ける。~~

でしたが、よく考えると お客130人前後の注文情報なんて**500B**ぐらいで済むので、遅延とかはそこまで変わらないと思います。

それに最近知ったのですが、**FireStoreの方はGoogleのインフラをフル活用**していて、今後GoogleはFirestore推しでいくらしいです。
~~ですが、β版というのが気になりました。~~


次の個人開発からはFirestoreを使います。

`※追記`
@chronicle さんのご指摘で、見落としに気付きました。ありがとうございます！
**Firestoreは現在正規版で、GCPのサービス品質保証契約（SLA）も適用されてます！ どんどん使いましょう！**



# 課題解決
ここからは、開発の過程でつまづいた所、その解決策を書いていきます。

## レシート１枚で全ての情報を表す。
分業化するにあたって、`注文を受ける人`・`ドリンクを作る人`・`運ぶ人`はレシート１枚を通して情報伝達を行う必要があります。必要な情報は、
**ドリンクを作るのに**

* ドリンク名（もしくは商品名）
* その飲み方（お湯割りやロックなど）
* あればオプション（「常温で」、「レモンつけて」など）

**テーブルまで運ぶのに**

* テーブル番号

**会計用に**

* そのテーブルの全注文履歴と合計金額
* 各ドリンク（商品）の単価
* お部屋番号（会計は部屋付けなので）

以上の情報を１枚のレシートに入れなければなりません。
最初はStarPrntのSDKを使えば、なんか良い感じに自動で割り振ってくれると思ってました（希望的観測）。

**が、実際は自分でテンプレートを作ったり、行数・フォントサイズを調整**する必要がありました。

これはこれでめっちゃ楽しかったです。まず普段のレシートに目が行くようになって、「あ～これ手抜いてるな」とか「すげぇ！どうやって！？」みたいに思うところが増えました。

コードを貼るとめちゃ長くなるので、別記事でレシートの作り方はまとめます。


>`※追記※書きました！`
[【誰得】普段目にする、レシート印刷の実装方法](https://qiita.com/Yoji0806/items/fdb45341b0c1a55ae94d)


少し言うと、縦横を揃えるためにすべての文字を全角にして、計算して空白を適切な数入れたり、１行に収められるようにしたりしました。👇
（半角のサイズは全角の半分でないため、混ざっているとややこしい。）

```kotlin

   var order = Transliterator.getInstance("Halfwidth-Fullwidth").transliterate(orderList[x])

   if (order.count() >= 18){
         order = order.substring(0, 18)
    }

   val size = order.count()
   val rest = 22 - size
   data = (order + "　".repeat(rest - 3) + qList[x] + "\n")    
```


超はしょって、試行錯誤の末こうすることで解決しました。
![レシートの.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266096/b28948be-aef6-ad84-921f-f1225ed4360e.jpeg)

以下の点に気を付けました。

* お客様にとって重要な情報は**大きく太く**（ここはお客様の年齢層も高く、見やすいレイアウトを心がけた）
* お客にとって重要でない情報（日時、作り方、端末番号）は、小さく明記
* **注文ごとをブロックで分け**、どの注文を作れば良いのか、わかるようにした。（常に１番下のブロックのドリンクを作る）

これで、`お客`は何を頼んで合計は何円かわかるし、`ドリンクを作る人`も何をどのように作るのか、`運ぶ人`もどこに運ぶのかを一目でわかるようになりました！

注文ごとをブロックで分けるには**DBの設計**を見直す必要がありました。
DB設計で気をつけたことは後述しますが、これは**柔軟なNoSQLモデル**だからこそ簡単にできたのだと思います。


## プリンターの排他制御

これはめちゃくちゃ悩みました。まず、排他制御自体を知らなかったので探そうにもGoogleは教えてくれず、、、だったのですが[MENTA](https://menta.work/)というサービスでメンターの方に相談するとすぐに解決しました。

Kotlin(Java)ではとっても簡単で、

```kotlin
@Syncronized
fun a (){
//処理
}
```
👆このように関数の上に `@Syncronized`を入力するだけで排他制御が実装できます。

あとはコードの設計を見直して、排他制御を実装した関数内で
**DBからデータの読み取り → レシート情報作り → 印刷 → 初期化**
の処理を行うようにします。
１３台同時に注文してもきちんと印刷されました:relaxed:

## DBの設計
Firebase RealtimeDBでは、リレイショナルなモデルではなく、データをJSONツリー型で保持するNoSQLモデルです。

自分はリレイショナルなDBをほとんど触ったことがなかったので、逆に変な違和感とかはなかったです。
このモデルで大事なことは２つで、

* 余計なデータまで読み込まないように、**深くネストしない**
* 効率よく必要なデータを見つけるための、**平坦化(非正規化）**

です。さらにこのアプリでは、DB設計を考えるにあたって

* **各注文ごとをブロックで分ける**（理由は上記）
* 簡単にレシート印刷できるようにする

という事を考えながら設計し、以下のようにしました👇！
（１１番テーブルの注文のみ）

![db設計.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266096/36efacc4-a204-4ee6-5c42-6e6bd829f6c5.png)

RealtimeDBでは、最大３４回ネストできるのですが、ここでは５回に抑えることができました。

そしてどのデータもテーブル番号 (**T**able **N**umber) （画像では　**TN 11**）と紐づけることで、注文２回目以降は、テーブル番号入力後、すぐにメニュー画面へ遷移でき、途中でデータの削除や数量変更などもできます。

また、一番上の**"Checker"ノード**を作ることで、、効率的にレシートを印刷できます。
色々なクラスからレシートを印刷する必要があるのですが（注文履歴変更後や、確認用など）、それも以下のコードで実装できます。👇

```kotlin

val table = "11" //実際のコードでは、この値をintentで渡している。

//実際には、トランザクション処理
val mDatabase = Firebase.getInstance().getReference("Checker")
mDatabase.child("TN $table").child("check").setValue(false)
mDatabase.child("TN $table").child("check").setValue(true)
```

それに、プリンター横の端末は、**"Checker"ノードにリスナーを設置し、**"check"の値がfalse → trueになるのを監視するだけでいいので、こうしています👇

```kotlin
 val check: Boolean = p0.child("check").value.toString().toBoolean()
 val tablet = p0.child("tablet").value.toString()
  
 if (!oldValue && check){    //oldValueはリスナー外で定義している
       val tableNum = p0.key.toString()
       readData(tableNum, tablet)  //テーブルの情報を全部読み取り、レシート印刷
  }

 oldValue = check
```
時々レシートが２枚印刷される問題は、この実装で解決しました。

## 直感的なUI
前に導入しようとしていた伝票システムは、全体的に単色で、メニューの選択画面が文字のリストで、とても見にくかったとのことでした。

なので自分は**極力文字を少なく・画像やベクター図を多く、見やすい色やフォントサイズ**を心がけました。
また、１つの画面で全ての操作を終えるのはなく、**各画面で行う操作は１つ**にしました。

スタッフは画面毎の**単純な質問に答えるように**操作していきます。

（例えば、日本酒を選択すると`おちょこの数`を質問し、ワインだと`グラスの数`、焼酎だと`飲み方`、ソフトドリンクだと、`氷の有無`や`アイスorホット`を質問します。）

👇下は、焼酎のボトルを注文するときの画面遷移
![UI一覧.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266096/80c07339-693f-7562-0b36-baa68e724f74.jpeg)

**流れるように素早い操作**が可能なように心がけましたが、そうすると一方でミスが増える可能性がでてきます。
なので、**できるだけミスを無くすため**に、また、お客様の**急な変更に対応**できるように（「やっぱビール３つで！」みたいな）、
注文が３種類以上の時は自動的に`【注文内容は大丈夫？】`画面に遷移するようにしました！
ここの画面から、商品の追加や数量変更・削除も可能です。


<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">Qiita投稿用 <a href="https://t.co/Dx0tktsMii">pic.twitter.com/Dx0tktsMii</a></p>&mdash; 山本 燿司 (@baske0806) <a href="https://twitter.com/baske0806/status/1133707670618136577?ref_src=twsrc%5Etfw">2019年5月29日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>




## コツコツ改善 ①SoftKeyboardのフォーカス調節 
あとは、小さなことなんですが、注文の最後の画面では、EditTextのフォーカスをデフォルトで外しているのですが、`メモを残す`ボタンタップ時にソフトキーボードがでてくるようにしました！

![キーボード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266096/6075553d-1fd6-a755-2b9b-910c9f088ec9.png)


このコード👇で、ソフトキーボードを出す実装ができます。

```kotlin
fun showSoftKeyboard(view:View) {
  if (view.requestFocus())
  {
    val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
    imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT)
  }
}
```
[：参考ページ](http://www.androidtutorialshub.com/how-to-hide-and-show-soft-keyboard-in-android/)　【How to Hide and Show Soft Keyboard in Android】

## コツコツ改善 ②ホーム画面整理
これは、システム関係ないのですが、もし誤操作でホーム画面に戻ってしまっても、一目でどれが伝票システムアプリかわかるようにしました。（完全人力）
![ホーム画面.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266096/90c9ead9-7282-abe4-f660-3e52f35f8b7e.jpeg)




# 終わりに
ハード機器の選定から、DB設計、UIまで、全部自分１人でやると色々わかってきますが、１番思うことは、自分のレベルの低さです。（本当に）

そして、**フィードバックの大切さ**。個人開発のアプリでは今回のような直接的なフィードバックがない分、色々なLogや、ユーザーのデータが相当すると思うので、次に活かしたいです。

あとは**メンターの大切さ**。今回自分は登録だけして使いませんでしたが、英語版では[CodeMentor](https://www.codementor.io/)、日本語版では[MENTA](https://menta.work/)などがあるので、積極的に利用していきたいです。


最後まで読んで頂いてとても嬉しいです！
ありがとうございました！