事例:株式会社ライライ様


既存システムのコスト削減が喫緊の課題に

  Topへ↓

ライライ様は、さまざまな撮影現場で活躍されているスタッフと映像プロダクションの間に立ち、両者をマッチングする業務を主に担っておられます。
映像の撮影現場はさまざまなスキルが臨機応援にスタッフに求められる場です。現場によって必要な作業を見定め、その作業に長じたスタッフを迅速に現場に紹介できるライライ様のビジネスは、映像プロダクションに高い価値をもたらします。
ただし、その分、作業指示や報告や請求書の発行作業に煩雑な事務作業が発生していました。それらを管理するための既存のシステムでは膨大な運用コストがかかっており、今回、kintoneを中心とした仕組みに切り替えることで、コストの削減を実現しました。

コスト削減を踏まえた業務要件のヒアリング

  Topへ↑

お客様(映像プロダクション)からのご発注を請け、それを案件として作成する。さらにスタッフの空き状況を見ながら案件にスタッフを割り当てる。スタッフはその案件の現場に入り、作業が終われば交通費や経費や作業内容を報告する。その報告内容を基にライライ社はスタッフの支払い書を発行し、映像プロダクションに対する請求書を発行する。
ご発注から請求までの流れは単純に書くとこのような感じです。

一方、映像撮影の現場の業務は多種多様です。ライライ様はそれらの業務や作業による金額設定を細かく定めています。たとえば、スタッフの習熟度による単価設定も二十種類以上の段階を設けるなど。
そうした業務を基にした金額は支払書や請求書に確実に反映させなければなりません。

ライライ様はそれまで、Salesforce+SVF Cloudを使った仕組みでビジネスを回しておられました。
その仕組みを回すために、各撮影現場に派遣されたスタッフは各々で作業報告を行わなければなりません。作業報告のプラットフォームとして、ライライ様はSalesForceを採用されていました。そのため、Salesforceにスタッフの数だけアカウントを作成し、スタッフはSalesforceにログインして報告を行っていたのです。
すると当然、月々のコストは相当な額にのぼります。
月々の支払額が負担になっていたこと。Salesforceの年次契約の更新日が迫っていたこと。さらにSalesForceの中で多くのカスタマイズが必要になっていて、その費用の見積もりももらっていたこと。それらを考えた結果、システム刷新の判断をくだしました。そこから、弊社へのご依頼につながりました。

この時点でライライ様はkintoneを採用すると決めておられました。まずkintoneを使ってアカウントのコストを減らすこと。それが最優先のミッションだったのです。
もちろん、kintoneの導入によって事務の方の各種計算や発行事務の負担を減らすことも求められていました。

kintoneとWordPressと帳票Onlineを提案

  Topへ↑

ライライ様から弊社にご連絡をいただいたのは2021/1/28。すぐにZoomで打ち合わせを行い、さらに2/3には訪問して状況を伺いました。
その際、Salesforce社に年間契約の延長か停止かの連絡期限が3/12に迫っていることを伺いました。そこでSalesforceを解約すると連絡した場合、4/12には契約が終わってSalesforceが使えなくなります。
Salesforceからデータを移行させつつ、kintoneの特性を生かしたアプリを設計する。さらにスタッフが報告できるためのプラットフォームを作る。そしてSVF Cloudで出力していた帳票とほぼ同じレイアウトの帳票を出す。それらをSalesforceの契約が切れるまでの間に作り上げる必要がありました。

そこで弊社は、2月の上旬にはスタッフの報告手段をkinconeにした提案書(42P)を提示し、すぐに着手にかかりました。
その後さらに打ち合わせを進めた結果、スタッフとの連絡手段はkinconeでは難しいことが分かりました。そこでスタッフとの連絡手段をWordPressに切り替えた提案書(44P)を提示したのは2月の中旬でした。その際、帳票発行の仕組みはアイ・コン社の帳票Onlineで提案しました。

弊社にとっては、メンバーを増やしてから初の案件

  Topへ↑

ライライ様からご連絡をいただく約4週間前。年明けから弊社は二人のメンバーを迎えました。
ライライ様の案件は二人のメンバーに活躍してもらう良いチャンスです。
まず、前の現場でWordPressを扱ったことがあったメンバーに一連のウェブサイトの構築をお任せしました。さらに帳票Onlineの設計はもう一人のメンバーに携わってもらうことにしました。
また、Salesforceからのデータ移行プログラムは代表が作りました。

今回、スタッフのアカウントはすべてWordPressにまとめました。ライライ様の場合、Salesforceに約100名分のスタッフアカウントを割り当てていたことによる月額コストが経営を圧迫していました。そのため、kintoneにもスタッフのアカウントを持たせず、別のやり方を検討したいというのがライライ様のご要望でした。
当初はkinconeで可能かと思いましたが、連携する情報の多さからWordPressに切り替えました。
WordPress内にスタッフのみが見られるページを設け、そこに作業開始時間、終了時間、交通費や作業内容といったこまごました項目をすべて打刻してもらうようにしました。
スタッフが打刻した瞬間、入力した内容はkintoneの作業報告アプリに取り込まれるよう、phpで開発を加えました。

kintoneの設計にあたり、Salesforceからの移行を考える必要がありました。そのため、案件管理や顧客管理、スタッフ管理などについてはSalesforceのフィールドを連携できるように設計しました。
さらに、作業報告アプリはWordPressからの連携に加え、スタッフの支払書や映像プロダクションへの請求書に出力できるような考慮が必要でした。

帳票発行の一連の仕組みについて、ライライ様がMacをお使いだったことから思わぬ遠回りをしてしまいました。そのいきさつについては
この記事に記載しています。
結論として、何とか帳票も出せるようになりました。

もう一つ苦労したのは、さまざまな計算です。内部の計算ロジックはSalesforceの内部でカスタマイズされていたものを流用しました。
ただ、Salesforceの場合、内部のカスタマイズ言語はAPEXというJavaベースの言語です。私もAPEXはあまり詳しくなく、新たに入ったメンバーも同じ。
彼には帳票を設計しながら、この計算ロジックを読み解き、なおかつ計算ロジックをkintoneのJavaScriptに適用してもらう作業に従事してもらいました。

導入してから

  Topへ↑

正直にいうと、Salesforceの契約が終わってすぐの月は、帳票出力の運用がまだ不安定なままでした。その際、ライライ様にはご迷惑をおかけしました。
今では上記のリンクに書いたような帳票発行の運用方法を変え、帳票は出せています。また、スタッフの打刻も順調に動いています。

現在は2ndフェーズの開発の真っ最中です。
他の種類の仕事を担っているスタッフさんの打刻ページや、NaviTimeの実装、Rakumo/Google Calendarとのスケジュール連動を行っています。

三島様より

導入からの日々について三島様はこう語ってくださいました。

「経理作業・スタッフ管理等PC作業の自動化は、弊社の長年の課題でした。
特に、請求・報酬金額のまとめ作業は、チェック項目が細かく時間がかかる上に、手作業だとミスも多くなるので、少しでも負担を減らしたいと考えておりました。
Kintone導入後は、スタッフの打刻から帳票作成までの時間が大幅にカットされました。
弊社の規定はかなり細かいため、まだシステムの確認・改修作業は続きますが、それらが終われば、他業務に人員を割けるようになるので、会社全体としての成長につながるのではないかと考えています。
これからもアクアビット様と打ち合わせを重ねて、より良い形にしていきたいと思っております。」

まとめ

弊社にとってライライ様の案件は弊社メンバーが加わって初めての取り組みだったこともあってとても印象に残っています。

私が帳票発行の設計で迷走しかけた後も、変わらずお付き合いくださっていることに感謝いたします。
三島さんはやり取りのレスポンスが速く、同じテンポを感じています。
まずは2ndフェーズを早く完了させてしまいます。

ライライ様のご紹介

商号 株式会社ライライ
本社 〒105-0021 東京都港区東新橋2-7-3 BIZMARKS 新橋汐留 2F
TEL 03-6874-4642
代表者 代表取締役 中田 平
従業員数 従業員3名 / 請負登録スタッフ70名
設立 2017年7月24日
資本金 100万円
ウェブサイト http://www.rightright.co.jp/

事例:某弁護士法人様


事務所の立ち上げにあたって、多忙な先生とスタッフの連携に備える

  Topへ↓

利益相反(コンフリクト)の点で具体的な名前を出せないのですが、某弁護士法人様は、高度な専門知識を駆使した法律事務所としてご活躍しておられます。
弊社が初めてお話をいただいた時、事務所開設に向けた準備を行っている最中でした。
代表社員弁護士の先生は、事務所の立ち上げ前から弁護士として幅広く活動されており、そのスケジュールは多忙の一言に尽きました。
スタッフを雇用し、事務所として立ち上げる中、スタッフと先生のコミュニケーションをいかに円滑に回していくかは、喫緊の課題でもありました。
弊社がkintoneを提案した結果、最初の立ち上げ時からスタッフと先生のコミュニケーションロスを抑えながらの運営ができました。

いかにして先生の予定や業務を共有するかのヒアリング

  Topへ↑

弁護士とは多忙な職業という印象を持っていましたが、代表の先生がこなすスケジュールの多忙さは私の想像を超えていました。
まさに分刻みという言葉がふさわしい先生のご予定をスタッフとどのように共有するか。予定の共有をせずに事務所の運用は回りません。
先生がご相談したのが、鎌倉に事務所を構えておられるヒトノコト(https://hitonokoto.com/)の渡辺みさきさんでした。
渡辺みさきさんが、なぜ事務所のサポートをしようと思ったかは、代表の先生やオフィスメンバーが働き易い職場を作りたいと思い、そのためには情報共有が欠かせないと判断されたからだそうです。
そこで渡辺みさきさんは、弊社代表の私と知己であったことから、システム構築のご相談を弊社に持ち掛けていただきました。

弊社代表がお話を伺ったところ、代表の先生の多忙なスケジュール管理を行うには工夫しなければ難しいと感じました。
当初、ヒトノコトの渡辺さんはCybozu Officeをご提案しようとされていました。ですが、お話を伺ってみたところ、他システムとの連動を行わずにスケジュール共有を行うのは難しいと判断しました。そこでkintoneをご提案しました。

kintoneについては、弊社は諸アプリの作成には携わっていません。こちらはヒトノコトの渡辺さんから別の方にご依頼していただきました。
その方が事務所の運営に必要な各種アプリを構築し、事務所の方が使えるように作業を行っていただきました。
弊社は標準機能では難しい他システムとの連動に注力し、並列で開発する体制を組んでいただきました。

kintoneとTwillioとGoogle Calendarとカレンダーの連動を提案

  Topへ↑

先生はiPhoneを駆使して連絡をとっておられます。スケジュール管理もiPhoneの中に入っているカレンダーが中心でした。
ところが、kintoneはカレンダーとの連動ができません。なぜならiPhoneのカレンダーは外部との連動をそもそも許可していないからです。ただ、唯一連携ができたのがGoogle Calendarです。そのiPhone内の連携オプションを利用し、Google Celanderとkintoneの連動を開発することによって、kintoneとカレンダーの連動を実現しました。
これによって、先生がiPhoneのカレンダーに入れた予定をすぐにスタッフはkintoneでGoogle Calendarからの予定を取得する事で共有でき、スタッフが入れた予定も先生が取得できるようになりました。
なお、事務所が立ち上ってすぐ、もう一人弁護士の先生が入所されたのですが、その先生にも同じような仕組みで予定の共有を可能にしました。

もう一つ、考慮しなければならなかったのが、事務所から先生に連絡を行う場合です。例えば事務所に先生宛の電話があった場合、その要件をすぐに先生に伝える必要があります。
先生からは、kintoneの通知ではなくiPhoneのSMSで連絡をもらったほうがありがたい、というご希望がありました。
そこで、kintoneのアプリ上にボタンを設置しました。そのボタンをスタッフが押すことで、アプリに格納した情報を先生のiPhoneにSMSとして飛ばすように設定しました。その連携にあたってはJavaScriptでTwillioにAPIリクエストを投げることで実現させました。

カレンダーの表示を見やすくするための工夫

  Topへ↑

苦労したのは、多忙な先生と予定を合わせることです。というのも、先生の端末をお借りしての操作が必要となったからです。カレンダーとGoogle Celandarの連動設定、Google Calendarの設定、さらに内部のGoogle Cloud Platform上での認証設定などを行う必要があったからです。ガソリンスタンドの休憩スペースで落ち合って作業したのは良い思い出です。
先生の激務のさなか、分で刻まれるスケジュールの合間を縫っての作業でしたが、それ以外にお手を煩わせることがなかったのは幸いでした。
もう一人の先生の端末も後日事務所にお伺いして設定することができました。

もう一つ苦労した点は、カレンダーの見え方です。スタッフの方が日・週・月で先生の予定を把握するためのデザインや色の設定などを工夫しました。
特に二人の先生が抱えたそれぞれの予定に加え、数名のスタッフが入れた予定など、多くの予定が混在する情報をどのように整理するか、色分けの部分は何度か打ち合わせを行わせていただきました。

それと、Google Calendarへのアクセスの際にGoogleから認証を求められますが、このやり方については、動画でマニュアルを作成し、スタッフの皆さんにご理解をいただけるように努めました。
当初はスタッフからの連携方法や先生の端末操作などの操作に慣れていただく必要があり、その間は先生のお手間を煩わせたこともあったかと思います。が、無事に弊社の手を離れて今に至ります。

導入してから

  Topへ↑

弊社には時折Twillioからの通知が来ますが、今のところ弊社の手を離れ、自立して動いてくれているようです。
多忙な先生のスケジュール管理にもkintoneが活躍できる場を見いだせたことは、弊社にとっても貴重な知見となりました。

渡辺様より

今の状況について渡辺様はこう語ってくださいました。

「新入社員としてジョインしたメンバーも、最初は使うのをためらっていたけど、使って見たら便利だという事がわかったので使っている、との話も聞いています。
自分で使い勝手を考えて、ある機能を使っているようなので、いいサイクルになってきているのではないかと想像しています。」

まとめ

弊社にとって某弁護士法人様は、iPhoneのカレンダーをどうkintoneにつなげるのか、という点で工夫したことで印象に残っています。

また、多忙な皆さんのスケジュールの合間に伺ったタイミングで的確に作業を終わらせなければならず、とてもメリハリの利いた開発になったことでも思い出に残ります。
今も順調に動いているということで、安心しています。

某弁護士法人様のご紹介

情報については、非公開です。

事例:株式会社テル・コーポレーション様


既存基幹システムで対応できない周辺のシステム管理が課題に

  Topへ↓

テル・コーポレーション様は、分譲マンションの開発・販売業務、分譲戸建て住宅の開発・販売業務、居住用・事業用物件の賃貸管理業務、土地・建物の売買仲介業務、不動産の有効利用コンサルタント業務など、幅広い不動産事業を展開されています。1988年の創業以来、着実に業績を積み上げてこられました。
契約や販売など、業務の基幹となる部分は賃貸管理システムで長年運用されていましたが、賃貸管理システムで補えない周辺の管理コストが煩雑になってきました。
もともと、テル・コーポレーション様は社内の連絡手段についてはCybozu Officeのオンプレミス版をお使いでした。周辺の情報共有手段を同じサイボウズ社のkintoneにすることで、近しいインターフェースを持ったシステムで回すことを検討されておりました。
その結果、周辺の管理をkintone中心とした仕組みに切り替えることで、業務の改善を果たすことができました。

煩雑な社内フローをkintoneに置き換えるためのヒアリング

  Topへ↑

不動産事業を行う中で、管理が必要な業務は契約や支払だけではありません。
例えば入居されている方からのクレームや要望事項も発生します。また、解約時の諸手続きや、オーナーや仲介業者との金銭の授受、鍵や駐車/駐輪場の管理、保険業者や内装業者、行政とのやりとりも必要になってきます。
テル・コーポレーション様がもともと使われていた賃貸管理システムは、そうした周辺の管理まで対応していませんでした。そのため、Excelを使って管理しており、煩雑な運用をもたらしていました。例えば紙に出力したExcelを基にしたクレーム報告書を社内で回覧し、押印欄に閲覧者が印鑑を押印するワークフローの存在など。
例えばクレームが発生した場合、その状況は物件や入居者の状況、事象によって千差万別です。それらを内部で情報として共有し、きちんとした指示を出す必要があります。

今回、弊社にいただいたご依頼は、Excelで管理していた周辺の業務を全てkintoneに集約すること。それとCybozu Officeをオンプレミス版からクラウド版に移管することでした。

今回はシステムのご担当者様と頻繁に連絡を取り合いながら作業しました。そのため、要件定義の細かい部分にまで弊社で踏み込む必要がありませんでした。

kintoneとRepotoneU Excelを提案

  Topへ↑

今回のご依頼で特筆すべきは、既存のExcelで運営していたクレーム報告書をどのようにkintoneで実現するかでした。
クレーム報告書の体裁を保ちながら、内部の仕組みをkintoneに置き換える。そのような方法を採った理由は、テル・コーポレーション様の社内にITに不慣れな方がいらっしゃるため、と伺いました。
そのため、kintoneからクレーム報告書を同じような体裁で出せるようにしました。押印欄を設け、実際の疑似印鑑も出力できるように。

そこでRepotoneU Excelを提案しました。RepotoneU Excelは帳票のテンプレートを登録することで、出力項目とのマッピングが可能です。
その際、テンプレートとなるExcelには拡張子が.xlsm、つまりマクロが可能なワークブックを使用しました。それによって、kintone内で生成されたExcelにマクロが埋め込まれるようにしたのが工夫点です。そのExcelをkintoneからダウンロードし、御担当者様が開いた時点でExcelマクロを動作させることによって、RepotoneU Excelの機能だけでは実現できなかったさまざまな表現を可能にしました。
例えば、あるクレーム報告書の場合、約20カ所の押印欄がありました。これも、kintoneの日時フィールドとユーザー選択項目を組み合わせた結果を疑似印鑑として表示させることで実現しました。
なお、マクロの実行にあたっては、テル・コーポレーション様のセキュリティ・ポリシーに合わせた形で実行できるようにカスタマイズしたことも付け加えておきます。

コロナがまん延する中での開発

  Topへ↑

テル・コーポレーション様に初めてお伺いしたのは2020/3/9のことでした。すでにコロナウィルスが世間に深い影響を与え始めていた時期です。
ただ、開発にあたってコロナはあまり影響を与えませんでした。それは、打ち合わせのほとんどをオンラインで完結できたためです。

テル・コーポレーション様はコロナ前からリモートワークへの切り替えをお考えでした。それがコロナによって拍車がかかり、オンラインでの打ち合わせの状況が整いつつあったことが功を奏しました。

さらに、やりとりの記載にあたってはご担当者様と何度か認識を合わせておいたため、複雑なやりとりもkintoneのスペースだけで完結できました。
社内の状況に精通したご担当者様とのやりとりによって、そこまで苦労せずに開発を進めることができたのは良かったです。結果、Zoomなどのオンライン・ミーティングすら二、三回で済みました。
私がテル・コーポレーション様を訪れたのは、初回の打ち合わせを除けば一回のみ(2020/9/28)です。それも、Cybozu Officeのオンプレミス版からクラウド版にデータを移行する作業が主でした。この時はすでに本来の開発は大体終わり、運用に移っていました。

開発で苦労した点があるとすれば、kintoneのプロセス管理を駆使していたため、エラーが発生した場合の再現が難しかったことです。グループやロールや組織、役職も多く作成しましたし、プロセス管理のステータスの数もたくさん作成しました。
特に、プロセスが進んだタイミングや保存時でアプリ間のデータ処理やスペースをまたいだデータ連動を複雑に組んでいたため、不具合があった際の調査に時間がかかり、苦労しました。

導入してから

  Topへ↑

プロセス管理のフロー修正や、賃貸管理システムとの連動にあたっての修正など、最初の目的を達成した後もさまざまな修正が発生しました。
が、今年の秋になって賃貸管理システムの入れ替えも終わったことで、いったん当初の目的は終わりました。

今後も新しくなった賃貸管理システムと協力して、kintoneの運用を行っていただく予定です。
今は、ご御担当者によってkintoneのアプリを次々と作成していただいている状況です。

代表取締役 浅井輝彦様より

代表取締役の浅井輝彦社長はこのように語ってくださいました。

「kintoneの導入によって、紙の書類が少なくなり、時間を効率よく使えるようになりました。それによって、テレワークもスムーズに導入出来ました。ありがとうございました。

株式会社テル・コーポレーション 
代表取締役 浅井 輝彦」

まとめ

弊社にとってコロナ禍においてもリモートワークを駆使して作り上げたことで、テル・コーポレーション様の作業は印象に残っています。
特に、リアルで運用されていた帳票をそっくり再現しながら、kintoneでプロセス管理を使いこなせたことでも、弊社にとって貴重な事例となりました。
あらためて、テル・コーポレーション様の皆さま、ありがとうございました。

テル・コーポレーション様のご紹介

商号 株式会社テル・コーポレーション
本社 〒151-0053 渋谷区代々木四丁目42番19号 テルコーポレーションビル
TEL 03-5302-5311
FAX 03-5302-5312
代表者 代表取締役 浅井 輝彦
設立 昭和63年
ウェブサイト https://teru-co.co.jp/

kintone Café 神奈川 Vol.10を開催しました


十二月二十一日にkintone Café 神奈川 Vol.10を開催しました。

qloba上の公式サイトでの開催報告。
https://kintonecafe-kanagawa.qloba.com/activities/11229

今年の春先に行ったVol.7の中で、これからはkintone Café 神奈川は3カ月おきに開催する、と申し上げました。
今回の開催でなんとかその約束を守れました。まず、ご参加者の皆様やスタッフの皆様に感謝いたします。

今回のVol.10は、運営側はほぼリアルで、そしてほとんどの参加者の方にはオンラインで参加してもらいました。

今回のkintone Caféは、kintoneのショートカットキーの解説から、kintoneインポートチャレンジへ。
さらに災害支援チームや支援協定の活動のご説明から、開発フレームワークのGAMENIへの想いへ。

このように多彩な内容だったので、テーマを「LOVE YOUR 多様性(仮)」としました。
テーマが絞りきれない内容だったからです。
言うまでもなく、「LOVE YOUR 〜」というのは、今年のCybozu Days 2021のテーマ「LOVE YOUR CHAOS」を踏襲しています。
まあ、代表の私にとっては、違和感のないタイトルでした。

今回の司会は前回と前々回に続いて山田さん。
そして、お約束のkintone Caféの理念を述べる前口上は代表の長井が担当しました。

さて、トップバッターに立っていただいたのは、ほぼこのために高知から来てくださった「じゅんちゃん」こと片岡さんです。
今回のCaféで会場としてお借りした福祉パルたま。なんと20数年前、新婚当時のじゅんちゃんが愛の巣を築いた住まいのすぐ近くだったのです。
なんとしてもこの数奇なご縁を語りたかったじゅんちゃん。震えますね!

さて、そんなじゅんちゃんが語ってくださった内容は「kintoneでもショートカットキー」。kintoneのショートカットキーがテーマです。
これ、実はなかなか簡単なようで奥が深いのです。Ctrl+Sは保存。レコード詳細画面でCtrl+Eを押すと編集モードにできることは皆さんもご存じですよね。ですが、皆さん、一覧画面でもショートカットキーが使えることを知っていたでしょうか。
私はあまり使っていませんでした。せいぜいCtrl+Cで新規追加するくらい。
レコード詳細画面でCtrl+KやJを押せば、レコードを移動できるのは知ってましたが、レコード一覧画面でも使えるとは!

他にもGoogle ChromeやWindowsでのショートカットキーなど、とてもダメになりました。私も結構知らないショートカットキーがありました。特にグラフィックボードの再起動などは役に立ちそう。

続いては「kintone インポートチャレンジ」です。
11月のkintone Café JAPAN 2021で好評を博したこの企画は、代表の私もチャレンジャーとして挑みました。
その鬼のような罠に満ちたExcelには手を焼かされたものです。
その時Excelの出題や進行をしてくださったのがけいんさん。今回はその時と攻守を逆転させ、私が出題者。けいんさんがチャレンジャー。
さらに、今回は複数の方にチャレンジしてもらったら面白いかな、と思い、事前に手を挙げてくださった原田さんにもチャレンジャーをお願いしました。
いや、面白かった。
私もアプリに権限をつけ忘れるチョンボをしましたが、それにもめげず完走したけいんさんに拍手です。初めてのチャレンジだった原田さんにも難しさを実感していただけたようで。

さて、続いては「GAMENIの秘密」と題して、福岡から久米さんにオンラインで入ってもらいました。
毎年恒例のkintone Advent Calendar。そこで久米さんが今回のCaféの前日にアップしてくれていたのがGAMENIについてです。
GAMENIとは、わが国のシステム開発会社にこそ、もっとkintoneを扱ってほしいという願いから生まれたkintoneの開発フレームワークです。久米さんの想いに、代表の私も熱く賛同していました。今回のCaféが行われるのが記事をアップした翌日というご縁もあり、久米さんに語ってもらいました。
カスタマイズ言語が多く登場したので、kintoneの非技術者の方には難しかったはずです。が、これも多様性ということでご容赦ください。

最後は「サイボウズ災害支援チームとの取り組み」を原田さんから。
原田さんが所属している横浜市社会福祉協議会とサイボウズ災害支援チームの間で災害支援協定が締結されたそうです。そこで原田さんからは社会福祉協議会の仕組みや災害支援協定の重要性などを語っていただきました。今回はオンラインでサイボウズ社の柴田さんにも参加してくださいました。柴田さんからも災害支援チームの取り組みをご紹介くださいました。
こうした取り組みは地震や噴火などを控えたわが国にとって大切です。
各地の社会福祉協議会ではkintoneを使った取り組みを広げており、そこでアプリの改修やスタッフへの説明を行ってくれる方を求めているそうです。われこそはという方、ぜひとも。代表の私も可能な限りお手伝いしたいと思いました。

いやぁ、ご参加の皆様、スタッフの皆様、ありがとうございました。会場を手配し、司会進行を務めてくださった山田さん、会場にお越しいただいたけいんさん、原田さん、藤村さん。そして高知からお越しくださったじゅんちゃん。会場に来てくださった大竹さん。オンラインの場で参加してくださった松尾さん、加藤さん。オンラインで登壇してくださった久米さん、柴田さん。そのほかの皆様もありがとうございます。
来年も今年と同じペースで開催したいと思います。また3カ月後にお会いできればと思います!

懇親会はスタッフ四人とじゅんちゃんとで美味しく楽しく過ごしました!


私、Mac方面については素人なのですが、、、


kintone Advent Calendar 2021の17日目の記事です。

  Topへ↓

今年も弊社はkintoneの案件を多く請け負わせてもらいました。ありがたい限りです。
今の状況に甘んじず、引き続き精進を重ねなければ。
というのも、まだまだ精進が足りん!と界隈からどやされそうな案件があったので。

kintoneに没頭するのはいいけれど、窓とリンゴの違いは考えておかないとね。
というわけで、本記事ではMac環境に詳しくなかった私が設計に失敗しかけた話と、その後のリカバリ方法をご紹介したいと思います。
なお、この失敗は私の未熟さが全てなので、責任は全て私にあります。私以外の誰れ〜も悪くないのです。

のようなクラウドシステム。その利点はいろいろ挙げられると思います。中でも真っ先に挙げられるのが、端末の心配をせずに済むことではないでしょうか。
モダンブラウザさえインストールされていれば大抵の機能は動きます。一昔前のシステムでは必須だった、やれライブラリが古いとか、やれ依存関係がどうとか、やれドライバがどうとか。そんなもの気にしなくても導入できる。それがクラウドシステムの魅力です。
リンゴが大好きだろうが、窓から見る世界が全てだろうが、クラウドシステムは無関係に動いてくれます。
端末の差異を気にせずに済むようになったことで、システム開発のコストは下がりました。
スマホやタブレットがあれば外出先でも自由自在にシステムにアクセスできる。そんなクラウドシステムの恩恵を多くの人が受けられるようになりました。

私、すっかりこの前提に甘えてしまっていたようです。
私が大学を卒業した1996年。ちょうど、Windows 95が爆発的に世間に普及した年です。同時に、インターネットが新たなツールとして世の中にデビューした頃でもあります。
実は私、それ以来パソコンに関しては一貫してWindowsしか使っていません。
技術者として独立した2006年から、ずっとMacとは無縁でした。Macの操作を知らなくてもシステム・エンジニアのフリができたのです。なぜなら仕事の上ではWindowsだけで事足りてきたから。キャリアの現場でMacに遭遇したのはせいぜい三台ぐらい。

私のMac音痴はkintoneを扱い始めた2011年秋からも変わらずでした。お客様にMac使いがいらっしゃらないので、Macに疎い私の無知がバレる恐れはありません。
ところがついに今年、私はMacがらみでプチ失敗してしまいました。


2.複雑な帳票を帳票Onlineで設計

  Topへ↑

お客様からSalesForceをkintoneに移管したいとのお話があり、早速とりかかった弊社。
そのお客様は、SalesForceとSVF Cloudをつないで帳票を発行していました。そして、その帳票は複雑でした。明細行には、複数のサブテーブルの情報をつないで出す必要がありました。セルの結合もあちこちに潜んでいます。開発には相当の困難が予想されました。
下図は帳票のうちの一つです。


そこで弊社は、アイ・コンさんの帳票Online for kintoneをお客様に提案しました。帳票Onlineであれば、セル結合や複数のサブテーブルの明細行への表示もできるようです。自在な改ページや印鑑埋め込みなども含めて機能を満たしていると判断しました。

帳票設計にあたっては、帳票Onlineの帳票を設計するための風神レポートの開発キット(プロフェッショナル版)を弊社で購入しました。
弊社メンバーが頑張った結果、帳票についてはおおかたの設計ができました。

3.開発はしたけれど

  Topへ↑

ところが、ここから開発が迷走してしまったのです。
なぜ迷ってしまったのか。以下に理由を書きます。

下図は迷走の途中で作ったフロー図です。上からずっとフローが流れてきて、請求管理のアプリまでやってきています。
この請求管理アプリですが、複数のサブテーブルを持っています。上流のアプリから作業明細や交通費などのデータが流れてくるため、複数のサブテーブルで管理する必要がありました。
このサブテーブルの情報を明細テーブル内に複数表示させるのが今回の帳票です。先にアップした通りですね。


ここでまず、帳票Onlineの仕様が私の前に立ちふさがりました。帳票Onlineは帳票を出力するにあたって、アプリで登録されているデータを加工して出力できない仕様なのです。
ところが、今回の帳票はなかなかに複雑な制御を行っているため、kintoneアプリにはない項目をCSVに含める必要がありました。

つまり、帳票Onlineの標準機能では帳票が出せないのです……。

4.帳票ルックアップの導入とMacの制約

  Topへ↑

そこで私が考えたのは、風神レポートに別売りで用意されている「ルックアップ機能」を利用することです。
「ルックアップ機能」をインストールすると、そのパソコン上で任意のフォルダを監視できます。そのフォルダにCSVが格納されれば、帳票Online用の帳票設計ファイルを通して帳票を出力できます。

つまり、やるべきなのは請求管理アプリから整形したCSVを出力すること。
ただし、請求管理アプリが複雑なので、標準機能で帳票Online用のCSVは出せません。そのため、JavaScriptのライブラリを使い、CSVを出力しました。

ここで問題になったのは、「ルックアップ機能」がインストールできるのはWindows PCに限られていたことです。
ところが、お客様はMacでビジネスを回しておられました。そして、お客様のもとにあるWindows端末は一台のみでした。
かなりリスクがありましたが、私はこの時点で帳票Online+風神レポート+「ルックアップ機能」の構成を提案しました。これがプチ失敗の元。

そもそもMacに関してはもう一つの制約がありました。今回、風神レポートで帳票を設計してくれたメンバーはMac使いでした。ところが、風神レポートの開発キットはWindowsでしか動きません。
幸い、弊社で余っていた端末がサテライトオフィスに置いてありましたが、それによってメンバーは帳票設計の作業のためだけに通勤を余儀なくされました。
この時点で、すでにMacを甘く見ていた私の思惑があちこちで崩れかかっていました。それに気づかずプチ失敗へと突き進む私。

また、お客様がGoogle Driveをお使いだったので、「ルックアップ機能」の監視フォルダをGoogle Driveのフォルダに設定しました。ところがこれがうまく動きません。そのかわりにお客様にOneDriveのアカウントを作ってもらい、OneDriveのフォルダを監視フォルダにしました。すると、OneDriveのフォルダにCSVファイルを格納すると、自動的に帳票が出力できるようになりました。
さらに、生成したPDFファイルを格納するフォルダを任意のOneDriveのフォルダとして設定しました。その設定が功を奏し、Mac端末とWindows端末がOneDriveフォルダを介してつながりました。

この時、お客様からは風神レポートが出力した帳票をファイルとしてお客様に送付したいとのご要望もいただいていました。
そこでCube PDFを導入し、出力した帳票を自動的にPDFファイルとして生成する機能を考えました。Cube PDFでは出力時にどうしても確認ダイアログが出てしまいます。それを回避するため、Cube VPという仮想プリンタの機能を導入しました。Cube PDF LiteとCube VPを組み合わせると、確認のダイアログを省いてPDFが生成できます。この時の仕組みは以下の通りです。

ここまでで、いったんお客様がMacのOneDriveのフォルダにCSVをアップすれば、お客様の望む帳票が出せるようになりました。しかもMac端末のOneDriveを見れば、出力した帳票がPDFファイルとして見られるようにも。

この時点で以下のような仕組みが組みあがりました。

これで、お客様の最低限のご要望は満たせたのです。間にWindowsマシンが挟まっているとはいえ、Mac端末でCSVをインプットし、Mac端末でアウトプットされたPDFを見られるようになりました。

5.出力はできたけれど

  Topへ↑

ところが、この方式で数日テストしてみたところ、お客様にとって、このやり方は意に沿うものではありませんでした。普段Macを使っているのに、その時だけWindows端末を使うのは非効率だと。
たとえWindows PCの操作のために移動する必要がないとしても、Windows PCの電源のON/OFFのためにオフィスに行く必要が生じる。これによって、リモートワークもできない、という強いご要望をいただきました。
この「リモートワークができない」というご指摘は私に重くのしかかりました。クラウドシステムを提供する会社にとって、リモートワークができないというのは、あってはならない事態です。

Mac端末に「ルックアップ機能」がインストールできれば丸く収まったはず。ですが、仕様である以上どうもできません。これはMacリスクを甘く見ていた私の失策です。なんとかしなければ。
ここで私はとても悩みました。お客様の言い分ももっとも。ここを解決しなければシステム・エンジニアの名が廃る。

お客様がもともと使っていたSVF Cloudのライセンスがまだ有効なはずなので、それを使えないか、というご意見もいただきました。
そこで、SVF Cloudの開発元であるウイングアーク1st社にも連絡を入れました。
ここでSVF Cloudを導入していれば、あるいはうまく行ったのかもしれません。ですが、私はその判断を簡単に下せない事情を抱えていました。
まず、SVF Cloud for kintoneが諸般の事情で販売終了していたことです。これによってプラグインを使って開発工数を減らす道は閉ざされました。ということは、SVF CloudのAPIを一から学び、さらに帳票に当て込む作業が発生するのです。その工数や予算はどこから捻出すればよいのでしょう。しかもSVF Cloud for kintoneが販売終了した経緯が不透明で、今後の販売再開の見込みがない以上、kintoneを主に扱う弊社がSVFの知見を蓄えるメリットがあるのか、とも悩みました。ウイングアーク1st社がkintoneへ再び熱意を持ってもらえばよいのですが(再び持っていただきたいです!)。
さらに、SVF Cloudを導入すると、本案件を機会に帳票Onlineの設計スキルを弊社メンバーに学んでもらおうとした目論見が崩れてしまいます。さらに開発キットを購入した投資額も本案件で回収できなくなる見込みが高い。

さて、ここが知恵の絞りどころ。ここで妙案を出さねば。
悩んでいた私に、ひらめきが降りてきました。このひらめきで全てがうまくまとまりました。

6.構成をもう一度検討し直す

  Topへ↑

私の解決法は請求管理アプリではなく、もう一つ別に帳票発行専用のアプリを作ることでした。
問題の発端は、請求管理アプリの構成が複雑で帳票Onlineでは出せなかったことにありました。
ならば。もう一つアプリを作って、そのアプリを帳票Onlineで適切に扱えるようなデータ構成にすればよい。
こう書いてみると簡単ですが、思考が袋小路に迷い込むと単純な発想が出てこないものです。

ここで以下のように変更しました。

ここで新たに作った帳票発行用アプリのレコードの構成は、帳票Onlineの仕様に完全に沿っています。明細行まで含め、レコード単位で帳票Onlineが解釈できるデータに分解しています。
そのレコードは、請求管理アプリのデータを加工し、生成するプログラムを作ってあげればよい。それはJavaScriptで実装できます。
また、帳票発行用アプリは発行専用です。そのため、データを貯める必要がありません。毎回、請求管理アプリでボタンが押される度に、帳票発行用アプリのレコードを全て削除し、その後でレコードを新規に追加する仕様にしました。
その際、JavaScriptの中ではいくつかのPromise処理が必要になりました。ですが、それは普段からプログラミングしている処理なのでさほど悩まずにできました。

その結果、お客様はMac端末のブラウザ上で帳票が出せるようになりました。間に挟んでいたWindowsは不要になったのです。もちろん、移動も不要で、リモートワークも可能です。
その代償として、購入した「ルックアップ機能」は不要になってしまいましたが、風神レポート設計の工数やスキルが無駄になるよりはよっぽどよかったと思います。何よりもお客様の求める運用が実現できましたし。

7.まとめ

Topへ↑

それにしても、kintoneがMacでも動くから、とMacを甘く見ていました。
よくよく考えると、kintoneにはしばしば周辺の仕組みが絡んできます。特にハードウエアを含めた構成の場合は。ドライバ、ライブラリなど、Macが絡むとうまく行かないことはよくありますよね。
Excel VBAマクロもそうです。Windowsでは動いていたマクロがMacでは動かないこともよくあります。そもそもファイルシステムが違うので仕方がないのですが。
今回は私がMac端末のことを真摯に考えていなかったことに原因があります。あわやプチ失敗しかけました。

まあ、結果として無事に動いたのでよかったです。
今ではアイ・コンさんのツールはブラウザ上で問題なく動いてくれています。複雑な帳票が設計・導入できたのも帳票Online+風神レポートのおかげ。
帳票Onlineは今年の弊社が関わった別案件でもドットインパクトプリンタで日々の帳票を出力してくれています。

帳票にはいろいろあります。中には複雑なものも。
今後もまだまだ業界によっては帳票出力が必要になることでしょう。そして、さまざまなパターンでの出力が求められるでしょう。
今回の件を通して、弊社は帳票設計と出力のスキルを豊かに蓄えられました。今後もさまざまなパターンでの対応ができるよう切磋琢磨したいと思います。
もしkintoneと帳票出力でお困りの方がいらっしゃいましたら、ご遠慮なくご連絡をくださいませ。帳票Online for kintoneで自在に設計してお目にかけます。


kintone Café JAPAN 2021でミッションに挑みました


11/13に催されたkintone Café JAPAN 2021は、全国のkintoneユーザーや技術者有志が集まるイベントです。有志によって運営されています。
毎回、さまざまな趣向をこらしたイベントが行われ、kintoneを愛する人々の知見と刺激と交流を生んでいます。

今回のJAPANでも複数のセッションが企画されていました。私は冒頭のセッション
Excelインポートミッション!君はこのトラップに気付けるか!?
のミッションに挑戦者として指名されました。技術者代表として。

このミッションの具体的な進行を私が運営者に教えてもらったのは、JAPANイベントが始まる30分前。
指令されたのは、Excelとkintoneを画面共有で皆さんに見せながら、その場でExcelに仕込まれた不正データを見抜き、データを整えた上でkintoneにインポートするミッション。

私にとって幸いだったことは、初っ端が私ではなかったことです。
最初の挑戦者であるキンスキ松井さんに課された10分間のミッション。これはまさにスリルに満ちていました。
頻発するエラーに四苦八苦される松井さんの姿を見ながら、私はミッションの雰囲気を把握することができました。


さて、私の出番です。私に割り当てられた時間は15分。技術者枠なのでさらに難易度はアップしています。
スタートの合図とともに、割り振られたスペース上のアプリとインポート対象のExcelを開きます。
すると、松井さんのミッションにはなかったルックアップやチェックボックスフィールドが加わっていました。さらには、Excel上での住所連結、氏名分割までも。
結局15分ではミッションをクリアできず、二回の延長をお願いして約20分を要してしまいました。
ただ、皆さんにはとても盛り上がってもらえたようです。Twitter上でたくさんの応援や感動したとのお褒めもいただきました。ありがとうございました。


このセッション、出た本人がいうのもなんですが、神企画だったと思います。
観覧されている皆さんにとっては、手に汗を握るスリルがあり、データをインポートする方法の知見がたまります。ミッションで使われるExcelのデータも一見それほど複雑ではないため、kintoneに触れて間もない方でも理解できる配慮がなされています。
さらに、ミッションを課された私にとっても、奔放なExcelデータをkintoneに取り込む経験がたまります。

実際、システム開発を営んでいると、今回のミッションで提供されたようなExcelに遭遇することはしばしばあります。
都道府県の列に見知らぬ県が混ざっていたり、誤字やスペースが混ざっていたりのデータはザラにあります。選択肢にない値や異体字、さらには違う文字コードのデータが混ざっていて文字化けする場合もあります。厄介なことに目には見えない改行コードが混ざっていることもあります。

普通、そうしたデータのインポートには時間をかけて対応します。入念に移行のデータを検証し、準備する時間を確保するのがセオリーです。
そうした移行に関する手続きは、昨年のkintone Advent Calendarで以下の記事としてアップしたのでご参考にしていただければ。
kintoneにシステム移したいんや

ただしこの記事に書いたような、移行の作業にじっくりと専念ができるかどうかは何ともいえません。そうはお客様が許してくれない場合があるからです。
例えばお客様先で利用を始める段になって、システムにバグが出ることもあります。それがプログラムのミスならまだいいのです。kintoneは修正が比較的容易にできるのですから。
問題はデータに不備があったり、アプリの設計と食い違っていたりした場合です。しかも、こちらの移行設計のミスならまだしも、お客様が直前で項目を修正したり、今までに発生していなかったデータがCSVに紛れ込んでいたりするからタチが悪い。
訪問したお客様先のその場で設計変更が必要になり、その場でデータを旧システムからもらい、その場でデータを加工して取り込む状況に追い込まれることだってありえます。

さすがに、お客様先で今回のミッションのように15分の制限時間を課されることはないでしょう。
ですが、技術者としては現場での応急対応が求められることは想定しておかないと。
アウェイの場での孤独な作業。お客様からの無言の期待と圧力を感じつつ、その場でExcelのデータとkintoneアプリの構成を比較して正しいデータに直す。それには臨機応変な対応が必要ですし、さまざまなエラーのパターンを同時に考えながら手を動かすことも必要です。こうした場数を経験しておくと後で技術者のキャリアの上で役に立ちます。
私も何回か、現場で加工したデータを取り込みなおしたことがあります。

こうしたやり方は従来のウォーターフォール型の開発では許されません。アジャイル開発やスクラム開発でもあまり推奨されないはずです。ですが、kintoneは症状の度合いによってこうしたLive感に満ちた開発・修正が可能です。むしろ、そこに真価を発揮します。
今回のミッションは、その経験を思い起こさせてくれました。また、出た当初のkintoneに惹かれた気持ちも思い出させてくれました。
このミッションにチャレンジすることは、kintone技術者としてのスキルを鍛えてくれるはずです。

Twitter上でもこのミッションを自社の研修に使ってみたいというつぶやきを見かけました。弊社でも同様にミッションを課してみようと思います。

司会の「けいん」さん
@soulxoxo

サポートの「よしみ」さん
@yoshiminxkay

一人目の挑戦者の「松井」さん
@kinsukicom

ガヤりの「とうげ」さん
@touge_moved

の皆様、ありがとうございました。あと、観覧者の皆様、声援ともどもありがとうございました。

時間が取れるかどうかはわかりませんが、今回のミッションの中で行った処理については、ゆくゆくは記事にしたいと思います。
なの、ケインさんがnoteでミッションに必要なアプリやExcel一式をダウンロードして使えるようにしてくださっています。

続いてのセッション『それkintoneで作って下さい!駄菓子屋さんの棚卸システムを1時間で仕上げちゃおう!
は、まさにkintoneのファストシステムの良い例です。
ファストシステム。最近ではこの言葉は使われていませんが、kintoneが出た当時はファストシステムと銘打たれていました。迅速に構築のできるシステムであることを訴えて。
今回のセッションでは、要件定義をもとに基本設計とアプリ構築を同時に行いながら、棚卸しシステムのプロトタイプを一時間で作り上げてしまいました。
このセッションは弊社のメンバーにぜひ見てもらいたいと思いました。従来のシステム開発なら、持ち帰って検討して要件定義した後に実装するため、早くても三週間はかかっていたでしょう。それが一時間でできてしまうkintoneの性能と機能をフルに活用した良い例です。

続いてのセッション『教えて!みんながkintoneから学んだこと』も良かったです。
先日のcybozu daysでは、kintone SIGNPOSTが正式にお披露目されました。現場の課題をどのようにシステム要件にし、それをシステムとして組み上げるか。その際はシステム開発に留まらず、コンセプトや社内のビジネスフローの再構築にまで話を広げる必要があります。その中では体制やコンセプトの整理も求められるでしょう。
現場の声としてあがった本セッションの内容は、kintone SIGNPOSTの生きた事例として参考になりました。

続いてのセッション『チャレンジ!きんとん関数ドリル』は、
ここ二年間でkintoneに追加された機能として特筆される関数を取り上げていました。このセッションではkintoneの関数の奥深さを味わうことができました。
私は出題された10問のうち6問しか合わず、関数についての理解の浅さを露見させてしまいました。
なんでもJavaScriptで解決してしまおうとする考えから切り替えていかなければ。
それにしても、松田さんによる関数の分析はお見事としか言いようがありません。ここまで理解してこそkintoneエバンジェリスト。私も精進しなければ。

この後もkintone SIGNPOSTの紹介や懇親会などがありました。が、実家にいた私は両親と食事に出かけたため、この後の懇親会には参加していません。さぞや盛り上がったことでしょう。

また来年も開催されると思うので、楽しみにしたいと思います。

重ねて皆様、ありがとうございました。

当日のTwitterまとめはこちらです。


kintone Café 神奈川 Vol.9を開催しました


九月二十八日にkintone Café 神奈川 Vol.9を開催しました。

qloba上の公式サイト。
https://kintonecafe-kanagawa.qloba.com/activities/11229

前々回のVol.7の中で、これからはkintone Café 神奈川は3カ月おきに開催する、と申し上げました。
なんとかその約束を守れました。まず、ご参加者の皆様やスタッフの皆様に感謝いたします。

今回は、まだリアルとオンラインのハイブリッド開催には時期尚早ということで、完全オンラインで開催しました。

今回のテーマは「使って/買ってよかったプラグイン」とさせていただきました。
kintoneの良さは、プラグインを活用することでより輝きを増す。そのことに異論のあるかたはいらっしゃらないでしょう。
kintoneが単体で弱い部分をプラグインで補う。例えばJavaScriptによる開発は、プログラマーやシステム・エンジニアなら可能です。でも、そうではない方がkintoneをよりよく使うには、開発ベンダーに開発を依頼するか、プラグインを導入するかのどちらかです。


清水さんのセッション「顧客管理アプリと使ってよかったプラグイン」は、その生きた実例でした。導入プラグインはgusuku CustomineとCalendar Plusの二つのみ。ですが、その二つをとことん使い込み、活用しつくすことで業務を回しておられます。
Customineの設定の実画面や、デモを利用したkintoneのカスタマイズをノンコーディングで行う様子は、まさにkintoneの可能性を見せてくれました。たとえプログラミングができなくても、プラグインを使えば何でもできてしまう。その可能性に満ちたセッションでした。
実は清水さんがkintoneを使っていこうと決意して、初めて参加したコミュニティこそ、私が主宰したkintone Café だったのですね。そのご縁に感慨もありました。清水さん、ありがとうございました。


続いて鈴木さんのセッション「私のイチ推しプラグイン」です。656アプリを誰でも業務カイゼン、みんなで業務カイゼンの名のもとに、自由に使ってもらう。それだけでも見事ですが、読み込み済みのプラグインは83。
それほどの数のプラグインを使いこなしているだけあり、検索プラグインの各社ごとの比較はとても参考になるものでした。そして、計算式プラグインの詳細な紹介。これもまさに見事。開発ベンダーの目からみても、価格と機能を考えると、自社で開発して提供するより、はじめからプラグインで提案した方が良いと思わせるものでした。見事でした。鈴木さん、ありがとうございました。


続いて、LTです。まずは藤村さんよりプラグインの選び方について。藤村さんは昨年のkintone Hackの本選に出場されています。プラグインは数多くありますが、なんでも取り入れるのではなく、「やりたいこと」を意識する。そしてプラグインの内容をきちんと吟味した上で選ぶ。その大切さを教えてくださいました。
このことはkintoneを使う上で学ぶべきことです。藤村さん、ありがとうございました。


続いては金春さん。御存じアールスリーインスティテュートでgusuku Customineの製品を統括されています。金春さんからはgusuku Customineをなぜ作ったのか。今までの発展と、これからの可能性について教わりました。清水さんのセッションでもCustomineの威力は分かりました。実は弊社でもお客様にgusuku Customineをお勧めし、短納期で複雑な案件を納品にこぎつけた経験もあり、開発ベンダーであっても可能性を知っています。なぜCustomineを作ったかというのは私も初めて伺いましたし、サポートや頻繁な機能追加にもかかわらず値上げをしていないなど、金春さんの本気度とgusuku Customineの可能性を感じました。金春さん、ありがとうございました。


続いては吉田さん。kintoneエバンジェリストとしてkintone Café 浜松を主宰されています。浜松は全国のkintone Caféでも断トツの開催回数を誇っています。
吉田さんが話してくださったのはfreee for kintoneについて。会計クラウドであるfreeeとkintoneをつなぐプラグインですが、freeeの充実したAPIリファレンスの紹介や、freee for kintoneを使うことでかえってカスタマイズが必要になることもあるなど、開発者の実体験に即した内容でした。弊社もfreeeは良く扱っているだけに、とても分かりました。吉田さん、ありがとうございました。


最後に弊社代表の長井です。コロナ陽性からの回復途上にある中、リハビリテーションもかねての参加。LTの内容は数日前に遭難した山の中で考えました。そもそも開発ベンダーとして、JavaScriptやプラグイン開発も行うのは当然ですが、場合によっては既存のプラグインを提案することもありではないか。
その観点から、弊社としての判断基準を語ってみました。もっとも忘れてはならないのは、あくまでもお客様のご希望を優先することです。開発ベンダーとしてはなるべく自社で開発したい気持ちは良く分かりますが、場合によっては既存のプラグインを使うこともありではないか、ということを話しました。

その後の質疑応答の時間でも皆様から積極的な質問を頂きました。プラグインをテーマにしたことに手応えを感じた時間でした。

21時からは、ご希望する方で懇親会。ここでもプラグインの活用や今後のkintone Café、10月のkintoneアップデートなど、多彩な話題で盛り上がりました。22時半まで。

いやぁ、ご参加の皆様、スタッフの皆様、ありがとうございました。また次は3カ月後の開催を目指して手応えを感じました。またお会いしましょう!


チーム応援カフェ Vol.2に登壇しました



前日のサイボウズユーザーフェスティバルに続いて、チーム応援カフェVol.2にも登壇しました。

非営利団体のIT導入の課題を解決するための交流会です。
https://npo.cybozu.co.jp/blog/post/149/

Vol.1の開催前日に今回の登壇依頼をいただきました。

2018/5/16にチーム応援ライセンス開始記念セミナーで弊社代表が登壇しました。
サイボウズ社の記事
弊社の記事

今回はその時に話した自治会のIT化をベースに、現状とその後の課題を含めてお話しました。

当時登壇してから三年。今の世界はコロナウィルスによるリモートワークが普通になっています。
それもあって、非営利団体の運営においても対面ではなくリモートワークによる運営が一般的になりつつある、わけではなさそうです。
自治会は年齢層など、コロナだからと言って簡単にリモートワークに踏み切れない要因があります。

今回はそのあたりも含めてお話させていただきました。
また、kintoneを自治会で活用することについても、前回より踏み込んで書いてみました。

スライドはこちらです。

今回は40名弱の方にご参加いただいていましたが、年齢層が高い方へのIT導入について活発なご質問をさまざまな方からいただきました。
そのほかにもさまざまなご質問が多様な角度から飛び交い、皆様の関心度の高さを感じました。ありがとうございます。

これについては各自治体も課題として挙げておられるようです。代表の方にもそうしたご相談がちょくちょく来はじめています。
そこで今回は提案を含めてみました。

各自治体がセミナーを自治会向けに開催し、その中で弊社がセミナーを開催させていただくのはいかがでしょうか、と。

今の世の中の在り方はコロナによって流動的になっています。ビジネスも業種によっては大幅なダメージを被っています。
自治会についてもあり方や運営について、より変化が求められることは間違いないでしょう。
弊社も引き続きこの方向で何が出来るのかを考えていきたいと思います。

ご参加の皆様、サイボウズ社の皆様、ありがとうございました。


サイボウズユーザフェスティバルに登壇してきました


先日、7/20にサイボウズユーザーフェスティバルが開催されました。
https://page.cybozu.co.jp/-/userfes

弊社代表が、その中のセッション「kintoneエバンジェリストが厳選 教えて!今知っておくべき活用術ランキング」に登壇したので、そのレポートをお送りしたいと思います。

今、kintoneエバンジェリストは全国で19名が活動しています。
このセッションでは、その19名のエバンジェリスト全員がアイデアを持ち寄り、kintoneを使う上でこれだけは知っておいた方がよいと考える活用術のうち投票で上位に選ばれた五つを紹介しました。

紹介するのは、弊社代表も含めた四名のkintoneエバンジェリスト。サイボウズ東京オフィスと福岡オフィスの二拠点に二名ずつに分かれて連携しながら、オンラインで配信する。
そんな試みにあたって、弊社代表は東京オフィス側で登壇しました。

コロナ以降、オンライン配信は普通になりつつあります。代表もZoomなどで配信は何度も行っています。個人で簡単に配信することができるようになってきたのです。

ですが、今回のサイボウズユーザーフェスティバルは、きちんと配信用のコンソール卓が用意された本格的な配信です。東京と福岡で交互に連携をとりながら、タイミングを合わせて配信するにはそれなりの装備が必要です。
こうしたきちんとした配信は、代表は昨年の12月にCybozu Days 2020 Osakaで経験済みです。とはいえ、やはり緊張しますね。今回、最初は少しだけ硬かったように思います。

でも、大丈夫。東京にはニ名の、福岡では一名のサイボウズ社の方がフォローしてくださったことで順調に進みました。ありがとうございます。

さて、今回のセッションで選ばれた五つの活用術は、投票で上位に選ばれただけのことはあります。皆様にとってためになったのではないでしょうか。

「意外と知らない?!グラフの便利術!」
グラフで凡例をクリックすることで表示を切り替えられる。実はこれ、代表も知らなかった機能でした。
kintoneの標準機能にもまだまだ知らない便利な隠し技があるのかも。そう思わせてくれるすてきな活用術でした。

「迷子にならないためのkintone活用術」
こちらは代表が挙げた活用術です。ポータルのカスタマイズは奥が深く、弊社もまだまだこれからの部分です。今後、皆さんから多彩な技が報告され、ノウハウがたまっていくだろうと思っています。
私の前に用意されたモニターの文字がよく見えず、一緒に登壇した松田さんからの振りに見当はずれの返しをしてしまった気がしますが、そこはうまく松田さんがフォローしてくださいました。ありがとうございます。

「エバだからこそ伝えたい!kintone前提で考えないこと」
これも重要な活用術です。あえてkintoneを使わないのに活用術!?
私たちも構築する際、ついkintoneで完結させようと頑張ってしまいます。実際、ユーザー様からもなるべくkintoneで操作をまとめたいとのご要望をいただくからです。
ですが、無理やりkintone上でカスタマイズしてしまうことが正しいとは限らない。そうなのです。そこに完璧な正解はありません。
代表自身、あらためて深く考えさせられました。

「kintoneの活用情報に出会うコツ」
サイボウズユーザーフェスティバルの前日に、kintoneコミュニティ案内版が正式にリリースされました。弊社代表の記事が最初にアップされた五本の記事に選んでいただいています。
kintoneのコミュニティは活発です。並み居るPaaS/SaaSの中でも屈指だと言えます。ですが、活発であるが故に、増えてきたコミュニティをどのように乗りこなしていくかは迷います。
初めての方にとってまずTwitterでハッシュタグをつけて発信することの大切さも含め、とっかかりとなる情報がお届けできたのではないかと思います。

「標準機能で「計算」する際に知っておくべきポイント」
kintoneの関数を使いこなし、関数だけでデータを加工してしまうお見事な皆さん。いわゆる関数芸の使い手の方々です。
最近のアップデートで拡充されたkintoneの関数は、関数を究める楽しみを与えてくれました。

ですが、関数もコツを抑えておかねば、困ったことになってしまいます。Excelのように。
Excelを個人が囲い込んで育てるとアンタッチャブルな闇ブックが多数生産される。まさにブラックボックスのように。
代表もかつて、某銀行の中で無数のExcelツールを整理し、汎用化する仕事に携わっていました。Excelは簡単にダークサイドに堕ちるのです。
kintoneも同じ。この活用術は、kintoneを闇堕ちさせないための貴重な情報が込められていました。

「kintoneの禁断カスタマイズに気をつけて!!」
これもまたkintoneを使う上で陥りがちなワナです。
HTMLとJavaScriptとCSSで表示するシステムであるが故に、やろうと思えばどんなことだってできてしまいます。
ですが、無思慮にDOMを変えてしまうと、kintoneのバージョンアップに伴ってそれらのカスタマイズがバグを生む可能性は十分にあります。
これは、技術者のサガとして突っ走ってしまいがち。その懸念をきちんとした形でエバンジェリストから発信できたことはよかったと思います。

「ルックアップフィールドの落とし穴に気をつけて!!」
この活用術は、私が動画の作成も含めて担当しました。自分で録画したとはいえ、動画にナレーションを充てるタイミングが不安でしたが、何とかやり遂げられたように思います。
ルックアップフィールドこそは、kintoneを初めて扱うシステムエンジニアがまず戸惑う部分です。kintoneはルックアップを理解できれば大丈夫。
その際に陥りがちなワナを説明できたのではないかと思います。

さて、配信も無事に終わりました。まずは安心。
こちらは当日のスライドです。

この日は上に書いた通り、サイボウズ社の本社からの発信でした。
そのため、代表と一緒にうちの社のメンバーにサイボウズ社を案内できるよい機会でした。今年から参画してもらってからというもの、一度もつれてきていなかったので。

サイボウズ社のオフィスに広がる遊び心にあふれた設えの数々。そこから、サイボウズ社の社風や考え方の一端を感じてもらえるのではないかと思いました。
kintoneを単なるツールとして扱ってほしくない。機能とその背景となる考え方についても理解し、愛着を持ってほしい。

いわゆるよくあるオフィスとはかけ離れた様子に、興味津々のうちのメンバーたち。今回は二人のパートナー企業の方にも来ていただきました。
サイボウズ社の社風や、kintoneの可能性について理解してもらえたのであればよいのですが。

ただ、フェスティバルの日は弊社の毎週行っている対面開発の日に充てさせてもらいました。それもあって、登壇の直前まで内部で打ち合わせを行っていましたし、終わった後もそれぞれの作業のフォローに勤しんでいました。
それもあって、代表もメンバーもサイボウズユーザーフェスティバルのセッションはあまり聞けていません。

ですが、代表が登壇している間はセッションに集中してくれていたと聞いています。
うちのメンバーがサイボウズ社やkintoneに愛着を持ってくれたと確信しています。そしてもちろん、サイボウズユーザーフェスティバルをご覧いただいた皆様も!
皆様!ありがとうございました!

そうした見学の機会も含め、サイボウズ社のうっし-さん、かんちゃん、関根さん、配信ご担当の方、ありがとうございました!


kintone Café 浜松 Vol.31に参加してきました



先日、7/9にkintone Café 浜松 Vol.31に参加してきました。

代表の長井は先日のkintone Café 神奈川 Vol.8で語った「kintoneを活用するためのデータの持ち方」をLTバージョンに短縮して。

そしてうちの青木さんからはkintone機能改善の要望を。まだkintoneに触れはじめてそれほどたっていない立場からの思いが伝わりました。
kintoneの標準の検索機能についてはなんとかならないか、というのは私の思いでもあります。

他にもIntegromatとkintoneの連携について、中西さんと松尾さんがLTを続けてくださいまして。

https://note.com/matsuo_atsushi/n/ncfd0bcfcf31e

mizusawaさんのLTも連携事例がとても参考になりました。

私にとって今回のkintone Café 浜松 Vol.31は、うちの青木さんから「kintoneをやっていて初めて楽しいと思った」という言葉が出たことが全てです。
うれしいですね。

この日はうちのメンバーが揃っての対面開発の日。私と青木さんが同じ場からオンラインで参加したのですが、まさに青木さんの喜びを眼前にできました。

また今度のkintone CaféでもLTをしてもらおうと思っています。


kintone Café 神奈川 Vol.8を開催しました


六月二十二日にkintone Café神奈川を開催しました。

qloba上の公式サイト。
https://kintonecafe-kanagawa.qloba.com/activities/10622

前回のVol.7の中で、これからはkintone Café 神奈川は3カ月おきに開催する、と申し上げました。
なんとかその約束を守れました。まず、ご参加者の皆様やスタッフの皆様に感謝いたします。

今回は、上のサイトにも書いた通り、リアルとオンラインのハイブリッド開催を目指しました。
コロナが世を騒がし始めてからというもの、リアルで人と会う機会は極めて限られてきました。そのため、そろそろリアルでも開催したい。そう思っていました。
もちろん、コロナのワクチンが行き渡った後も、人々がリモートを手放すことはないはずです。ということは、リアルとリモートの両方を兼ねたイベントが主流になるに違いない。
今回ハイブリッドでやりたいというスタッフの山田さんの提案に私も乗らせていただきました。

今回のテーマは「kintoneとSalesforceを比較して、データの持ち方を考える」とさせていただきました。
kintoneの良さは、同じ分野で競う他のシステムと比べてみることでより一層際立つことでしょう。
むしろ、お互いの得意分野や長所を生かしつつ、適材適所で利用していけばよいのですから。

その意味でも、Salesforceを活用してのSIer活動をされていらっしゃる植本さんの発表は興味深いものがありました。
特に、Salesforceはその名前からしてもSalesに強いことは明らかです。SFA(Sales Force Automation)とは、まさにSalesforceの代名詞。
それらの強みを発表いただいたことで、なるほど、という反応がありました。
やはり、成り立ちや強みを生かして使えるところは使っていけばよいのです。
植本さん、ありがとうございました。


続いて、弊社代表長井より、「kintoneを活用するためのデータの持ち方」の発表でした。
kintoneもSalesforceもを導入するためには、データの持ち方の知識が欠かせません。
そこでkintoneのアプリがどのようなデータ構造から成り立っているかを話させていただきました。

https://slides.com/yoshikazunagai/kintone-cafe-kanagawa-vol-8-2

技術者であればデータベースを扱う機会は多いはずです。中でもSQLを使っての構築は多くの現場でいまだに現役だと思います。
ですが、kintoneを扱っていると、kintoneを意識することなくデータが扱えてしまいます。ですが、最適なデータの構造でアプリを構築するには、SQLの構造を理解していることが望ましいです。

その観点から発表させていただきました。ありがたいことに、皆様からの反応を頂けたのは良かったです。ちょっとマスクとマイクがこすれて異音を生じていたのは私の不覚です。もうしわけございませんでした。

さて、今日は緊急事態宣言が明けたとはいえ、神奈川県ではまん延防止法が適用されていました。そのため、会場に入るなり、20時までしか会場が使えないことが発覚しました。時間内に終わらせるため、私のセッションはかなり速足で語りました。
さらに続けてLTは二名の方に語ってもらいました。
一人目は弊社の武塙さんです。今年の春先にSalesforceからkintoneへの移管案件を手掛けた弊社。武塙さんはその時の経験からフィールド名の移管ルールについて実務経験者としての苦労を語ってくださいました。こうやって発表し、登壇する経験は今後もやっていってほしいですね。確実に自信につながりますから。
二人目はkintoneエバンジェリストでもあるkintone Café 浜松を主催されている吉田さんからです。シェルスクリプトも交えながらのSalesforceからkintoneの移管事例を語ってくださいました。特に添付ファイルの扱いについては、皆さん苦労されているようです。(弊社も一昨年に手掛けたSalesforceからkintoneの移管案件ではboxを組み合わせてURLでkintoneに移管しました)
お二人のご参加によって、今回のテーマがより定まったと思います。

20時で一旦会場を締めた後は、リアルの場からオンライン上の懇親会につなげるための場所探しを。ですが、武蔵小杉近辺の店はほとんどしまっており、残念ながらつなげられそうな場所は見つかりませんでした。そこで、主催の長井のみ現場に残り、某ビルの中からつながせて参加しました。
懇親会でもkintoneの深イィ話で盛り上がり、kintone Café の醍醐味である懇親会を存分に楽しみました。

いやぁ、ご参加の皆様、スタッフの皆様、ありがとうございました。会場を手配し、司会進行を務めてくださった山田さん、会場にお越しいただいた松尾さん、原田さん。オンラインの場で参加してくださった加藤さん、渋屋さん(植本さんのご紹介やテーマのご提案ありがとうございます)、鈴木さん(スタッフへようこそ)。また3カ月後にお会いできればと思います!


kintone Café 神奈川 Vol.7を開催しました



3/26にkintone Café 神奈川 Vol.7を開催しました。

開催の内容については、翌日に早くも渋屋さんがブログとしてアップしてくださいました。
https://100athlon.com/kintone-cafe-kanagawa-7/
松尾さんもアップしてくださいました。
https://note.com/matsuo_atsushi/n/nce32e0c0786b

私も渋屋さんと同じ感想ですね。松尾さんのセッションでFileMakerがkintoneの競合どころか、お互いの足りない部分を補い合える製品であることを知ったのは大収穫でした。23年前、私が初めて持ち帰りの仕事を受けたのはFileMakerを使った仕事だったというのに、全く状況を追えていなかった不明を恥じました。データ量や帳票などが整備されているのはさすが歴史のあるソフトウエアですね。
渋屋さんのセッションもテーマを深めるための冒頭の問題提議としてピッタリでした。最初にテーマに沿ったセッションを話してくださったことで、皆さんがkintone Caféの雰囲気になじめたと思っています。今回はzoomのチャットがとても盛り上がり、コメントも次々と書き連ねられたのですが、渋屋さんのセッションのおかげだと思っています。これは有効ですね。

LTは瀧村さん、松村さん、加藤さんと3人の方に登壇していただきました。
瀧村さんは、自己紹介をkintoneの基本機能と拡張機能を駆使した、まさに豪華絢爛kintone絵巻のような内容でした。わずかな間にkintoneの魅力を詰め込むところなど素晴らしい。エバンジェリストとして参考になりました。
松村さんは上海からご参加。kintoneを製造業に提案することの難しさと課題について素晴らしい問題提起をしてくださいました。中国のkintone事情にも触れたセッションに興味はつきませんでした。私も製造業へのkintone導入の提案については何度も跳ね返されています。このテーマで今後、セッションをお願いしたいくらいです。
加藤さんはkintone Café神奈川を立ち上げた方ですが、当日の朝にLTをお願いしたところ、快諾してくださいました。プラグインをあらためて紹介してくださったことで、あらためて浮き彫りになったのは、kintoneの標準機能の弱さです。まさに今回のテーマにふさわしく、kintoneでできること、できないことがプラグインからみえてきたように思います。
登壇してくださった皆様、ありがとうございました。

今回は、開催にあたっていくつか運用を変えました。最も大きい変更は、私自身が受け持つセッションをやめたことです。
今回、私は司会・進行に徹しました。セッションもLTにも登壇せず、冒頭の『kintone Caféとは?』を話したくらいで。
『kintone Caféとは?』では、あらためて過去のkintone Caféの開催状況を調べ直し、地図にして皆さんにお見せしました。地図を作りながら感じたのは、神奈川の開催回数の少なさです。

今まで私が携わったkintone Café 神奈川 Vol.2からVol.6までは、私が運営にも携わり、司会と進行を行うとともに、セッションも受け持っていました。
その体制のままだと、私が忙しくなると開催できず、開催間隔も延びます。
以前から神奈川の開催回数は少ないと感じていたのですが、今回、地図を作ることでそれを実感しました。それも私の忙しさのせいです。

そこで、前回のVol.6では、スタッフ体制を組みました。そのスタッフ体制を維持したまま、今回のVol.7を企画しました。企画にあたっても、最初から私は登壇もLTもしないと決めていました。
今回は、テーマも渋屋さんの発案です。

冒頭の『kintone Caféとは?』では、今後のkintone Café 神奈川は三カ月おきに開催する、と宣言しました。それもスタッフの皆さんのご協力があってこそ行えます。
次回以降、私は司会・進行すらしないかもしれません。

今回は懇親会の中でもスタッフを募りました。その結果、お二人の方が手を挙げてくださり、お一方はすでにやりとりもさせていただいています。また、当初、神奈川を立ち上げてくださった加藤さんにもあらためて入ってもらいました。
これでスタッフは私を含めて六人以上です。むやみに増やすのも考えものなので、これ以上募るかどうかは分かりません。ですが、次回以降は私がいなくても開催はできるのではないかと思います。

私の中で印象に残ったのは、懇親会で松田さんがおっしゃったひとことです。
今はオンラインなので地域別に分ける意味合いが薄れている。だから、主催者の色や個性が重要になるのではないか。とても良い意見で、参考になりました。ありがとうございます。
これからのkintone Café 神奈川も、これからは多様な色を出しつつも、どこかで私なりの特色を出しつつけてられるよう、考え続けていきたいと思います。

今回、ご参加してくださった皆様、誠にありがとうございました。


事例:北新海運様


kintoneを導入したことによる日常業務の効率化

  Topへ↓

北新海運様は、海運の国内運送を主に担っておられます。
海外の船がコンテナバースに到着し、荷揚げされたコンテナ。この荷物を国内のお客様に運ぶのが北新海運様の業務です。

従来はExcelで運用されていたお客様への請求業務や、ドライバーへの指示、ドライバーの稼働実績の取りまとめ。
それらをリリース間もないkintoneに切り替えることで、運用工数の削減を実現しました。

Excelによる単価管理やドライバーへの指示業務の煩雑化

  Topへ↑

お客様からのご発注を受け、それをドライバーへの運行指示書に印刷する。そして、その結果をお客様への請求書として発行する。ご発注から請求までの流れは単純に書くとこのような感じです。
ところが、お客様からの発送指示は千差万別。その発送場所によって単価が変わります。また、コンテナのフィートによっても単価が変わります。
配車指示を的確に行わねば、効率的な運用はできません。
また、案件ごとに単価が変わるので、その単価を的確に反映した請求書を発行し、同時にお客様によっては請求データを一覧表として発行しなければなりません。
そうした複雑な業務が伴う海運業務を行うには、Excelでは限界が生じていました。

また、ドライバーが従事した業務によって手当を計算する作業も負担になっていました。
複数の場所で荷を下ろす場合や、積み場所の変化。さらには、構内業務に従事する場合には別の計算方法が必要です。そして、それらが組み合わさった場合の作業もきちんと集計せねばなりません。そうした複雑な作業実績の集計は業務の負担となっていました。

リリースしたばかりのkintoneを提案し、採用。

  Topへ↑

Excelから離れ、新たなシステムを構築しなければ。
北新海運様で事務を統括する立場にあった紀村様は、旧知のデザイナー様に相談しました。
相談を受けた株式会社アトリエmの室井様は、そのご相談を受け、弊社にご相談くださいました。

その当時、まさにリリースされたのがクラウドサービス「cybozu.com」とそのサービスの一つであるkintoneでした。私はこのkintoneのβテスターであり、kintoneがリリースされたタイミングで導入できる機会を待っていました。そこにいただいたのが北新海運様のお話。
2012年の正月明けに北新海運様にお伺いし、紀村様にご要望を伺いました。
そしてご要望がkintoneで実現できると判断し、kintoneをご提案しました。

社長様は初めて会った私と、出来たばかりのサービスであるkintoneを信頼してくださいました。そして、ご発注にいたりました。
それが2012年1月26日のことです。

APIのリファレンスがなかった時期ゆえの苦労

  Topへ↑

私の手元には、この当時にサイボウズさんから頂いたMicrosoft Wordの「kintone API マニュアル」があります。表紙にはV0.1と書かれています。
この当時、リリースされたばかりのkintoneにはすでにAPIが実装されていました。とはいえ、レコードの追加・取得・更新・削除ぐらいしかできませんでした。今のリクエスト方法やレスポンス結果とそう仕組みは変わらないとはいえ、今のようなリファレンスは整っておらず、ウェブ上にブログなどの情報もほとんどありませんでした。
加えて、当時の私はRest APIを使った実装の経験がなく、ノウハウも持っていませんでした。

他にも問題はありました。それは、帳票をどうやって出すか、でした。
当時はまだプラグインの仕組みがありません。つまり、今のようなサードパーティーさんによる帳票発行プラグインもありません。
kintoneを使ってどうすれば帳票を出せるのか。まずその調査から始めました。

当時、すでに私は某案件でWeb上のphpライブラリ(TCPDF+FPDI)を使って帳票発行を実装した経験がありました。
そもそも、北新海運様からお話を伺った当初は、Accessによる実装という選択肢も有力でした。kintoneとAccessのどちらを採用するか決めかねていました。
Accessのレポートで帳票を出すのか、それともウェブ上でphpを使って帳票を出すのか。
私はそのどちらでもなく、Excelを使うことにしました。

VBA(Excelマクロ)からRest APIのリクエストを出し、その結果を受け取れれば、Excelで帳票が出せるのではないか。
私はそう判断し、ウェブ上であれこれと調べ、何とか実装にこぎつけられました。

結果、2012年の4月から運用を開始しました。ですが、当初は何度も調整が発生しました。
私自身、まだkintoneを完全に理解出来ておらず、ルックアップフィールドをはじめとしたフィールドの載せ替えなども何度も行いました。
夏前まではさまざまな調整が発生しましたし、帳票の発行についてもレイアウトや表示書式などなんども調整しました。おそらく全て落ち着くまでは1年はかかったように思います。

導入して9年

  Topへ↑

その後、当初の実装フェーズには含めていなかったドライバーの実績集計についても実装を終え、そちらも運用を開始しました。
さらに、北新海運様の業務拡張による別会社設立の際も、同じ仕組みの上に若干のカスタマイズを加えて実装し、運用を開始しました。

以来九年、Excelによる帳票発行の仕組みも含め、北新海運ともう一社の仕組みは動いています。

紀村様より

導入からの9年について、紀村様はこう語ってくださいました。

「アクアビット長井さんの原稿を拝見し、9年前の導入当時を思い出します。

弊社 有限会社北新海運は今年で創業17年目の会社になります。
9年前、私は入社3年目で事務全般を担当しておりました。その作業内容は受注・売上・請求だけではなく運賃管理・ドライバーの売上管理、それに伴う給料計算に至るまで殆ど全てでした。さらに車両の増車に伴う業務負担もあり現行をやり過ごすことが出来たとしても近い将来、必ず困った事態に陥る事は目に見えていたのです。

とはいえ当時リーマンショックからやっと立ち直ったばかりの小さな会社、そして海上コンテナ業界は輸出・輸入という特殊な世界のため専門ソフトがない中、システム導入を専門会社に依頼すれば何百万単位の金額が出てくる事は以前に在籍していた他社で見聞きし、十分に分かっていることでした。
コロナ禍における在宅勤務による環境の大きな変化でシステムが予算的にも身近になった昨今と違い、当時はシステムゼロからの依頼は予算の乏しい会社にとっては大きなハードルだったのです。

そこで広告・宣伝・HP作成等々の会社を経営なさっている友人にその問題点を相談をしたところ、アクアビットの長井さんをご紹介いただきました。
長井さんには何度も打ち合わせには来社、この業界の業務の流れをいちから丁寧に聞いてくださり、夜遅くなることもしばしばありました。

当時は長井さんが個人事業主ということもあり少し不安ではありましたが、kintoneを使ってのシステム構築でコストを非常に下げられるご提案を頂いたことで、初期投資として仮にリスクがあっても最小限にとどめられる事も想定し社長が決断して下さいました。
導入事例の中では1年程かかったとの文面がありましたが、kintoneの業務作業を並行・チェックをしながら手直しをし、追加リクエストもあったので、実は2年位はかかったと思います。その後、長井さんとのやり取りも年々少なくなってきましたが、それは業務システムが問題なく動いている証拠だと思います…いや、実はリクエストはまだまだありますが、kintoneバージョンアップがそこまでいっていないというのも本音です(笑)。

大手企業が作るシステムも大事ですが、実は中小企業の“小”の会社こそがこの様なシステムをすぐにでも必要としていると思います。初期投資が大きいために二の足を踏むオーナーさんも沢山いらっしゃると思います。そのなかには直ぐにでも使えるシステムが欲しい中高齢のアナログ世代の方々が相当いらっしゃいます。少なくともコンテナ輸送業界はそうです。
その様な会社を直ぐにフォロー出来るのがkintoneだと私は思います。
そして、この業界を少しでも知る長井さんの様なアドバイザー的な方がもっと増えることも今は大事かと考えます。

長井さんにはずっと以前よりkintoneの講習会参加を勧められておりましたが、今だ実現に至っておりません。コロナ禍収束の折には講習参加を真剣に考えたいと思っている今日この頃です。
有限会社北新海運 紀村多栄」

溝木様より

また、北新海運の溝木社長からはこのようなありがたいお言葉をいただきました。抜粋して掲載させていただきます。

「弊社も今年で創業16年になります。
長井様には色々なご相談に乗って頂き大変助かっております。
最初のころは、私はExcel、Wordも分からず、パソコンには色々苦労しました。

そこでお知り合いになったのが長井様で御座います。
弊社にとって何か良い業務方法ないか、何回も同じことを打つのではなく一回で作業を終わらす方法、当時、長井様は色々な方法を考えて下さいました、弊社の要望を聞きながら猛勉強をされたと思います。弊社の職業柄大変だったと思います。とても使いやすく助かっております。

専門的なことは今でも分かりませんが、長井様のおかげで業務が短縮になったのは確かです、継続は力なりとありますが、今では弊社にとって最高のパートナーです。
私がこれをやりたいとお話すると、私の考えが分かっているかのようにパソコンExcel、kintoneを組んでくれます。

昔の人は、「人は必要だから出会うと言います」必要でないのなら一生出会う
ことはないでしょう。
弊社もこれから色々なことに挑戦していきます、その度に長井様にお願いする
ことが多いと思います。
これからも末長く宜しくお願い致します。

有限会社北新海運 
代表取締役 溝木昭夫」

まとめ

涙が出そうになるほど、感動しました。
お二方より頂いた文章は、段落を整えた以外はなにも直していません。私と弊社にとってとても心強く、嬉しいお言葉です。

私も今までにいくつもの失敗をしてきました。特に、kintoneに出会う前、ホームページ制作を手掛けていた時期は。何度も。
でもこうしたお言葉をいただけると頑張ってきてよかったと思えますし、これからも頑張ろうと思えます。また、kintoneが私のスタイルに合っていたのだと強く思います。
北新海運の皆様、本当にありがとうございます。また、ご紹介を頂いたアトリエmの室井様、本当にありがとうございます。

北新海運様のご紹介

商号 有限会社 北新海運
本社 〒272-0522 千葉県市川市宮久保5-9-7
   
本社営業所 〒143-0001 東京都大田区東海5-4-1 大井海貨5号上屋北側1階
TEL 03-5755-7813
FAX 03-5755-7814
代表者 取締役 溝木 昭夫
従業員数 25名
設立 2005年(平成17年)9月28日
資本金 500万円
ウェブサイト http://www.hokushinkaiun.com/

kintoneが今年、拡がるために


はじめに

あけましておめでとうございます。
今年もよろしくお願いします。

弊社の年頭の抱負はこちらにアップした通りです。
その中で、kintoneを軸にするという項で、今年も弊社はkintoneに注力する旨を書きました。

そんな中、年始の2日に家族とワインを飲みながら団らんしていました。
テレビには家族が観たい「逃げるは恥だが役に立つ ガンバレ人類!新春スペシャル!! 」が映っていました。
そこに登場したテーマがコロナであり、選択式夫婦別姓であり、リモートワークであり、働き方でした。

私はドラマを観ながら、これってサイボウズさんの打ち出しとるテーマやんか、と目をみはりました。

昨年、弊社にとって顕著にkintone案件が増えました。また、旧システムからkintoneにシステムを移行したいとの案件のご依頼も増えました(ブログ)。
おそらく、その流れは今年も続いていくことでしょう。
それは、サイボウズさんの掲げる理念が世の中に広まったこととkintoneの魅力が世の中に行き渡りつつある証しだと思っています。
もともと、そうした働き方を変えたいとの機運は世に行き渡りつつありましたが、昨年のコロナはそれを促したのだと思っています。

ただ、kintoneはまだ世の中で誰もが知っている存在になっていません。
弊社のレベルでは顕著に案件が増えたとはいえ、それはもともと弊社が受けていた案件数が少なかっただけの話。
弊社の尺度ではなく、世の中全体でのkintoneのシェアを考えると、kintoneにはもっと広まる余地も拡がる伸びしろもあるはずです。
今年はその流れがより速まり、より拡がるはずです。より多様な業種や規模の企業にkintoneが採用されることでしょう。

ただ、それを口で予想するだけで済ませてはなりません。
自らが率先してその流れをより速め、広げていくことがkintoneエバンジェリストの役目であり、サイボウズ・オフィシャルパートナーとしての存在意義だと思っています。

そこで、kintoneに何が必要か。それを年頭に考えてみました。

kintoneの弱点

まず、kintoneに足りないのは、規模でしょうか。
今のところ、大規模案件や中小以上の規模の基幹システム案件に対して、kintoneを勧められる状態にはありません。
というのも、
https://jp.cybozu.help/k/ja/admin/limitation/limit.html
にも書かれているとおり、100万件以上のレコード件数に対しての品質保証が明記されていないからです。

過去データを保存する期間は、法令によると受発注や仕訳だと七年が定められているようです。
月間15000件の伝票が発生するお客様の場合、一年で180,000件。七年で1,260,000件のデータが発生します。
つまり法令上の保存レコード件数をkintoneが満たせないと見なされる可能性があります。

US版のKintoneは完全にAWSが基盤となったそうですから、オートスケールには対応しているはずです。

日本版のkintoneについても、今、サイボウズさんではNecoプロジェクトとManekiプロジェクトが進行中だそうです。


その成果を待ちたいと思います。

kintoneはもう一つ、アカウント管理回りにも改善の余地がありそうです。
例えば、大企業の複雑かつ大きな組織が一斉に組織改編を行う場合を考えてみましょう。周知のとおり、Cybozu.comのアカウント設定では組織の事前設定ができます。とはいえ、なかなか思った通りにいきません。ま、弊社はその部分の作業をお客様に委ねてしまっているのですが。
あと、ゲストスペース内アプリの権限設定の際に組織やグループが使えないのも不便だと思っています。

あと、スペースやレコード上のコメント機能も、ChatWorkやSlack並みの使いやすさをお客様から求められることがままあります。
kintoneの良さは、コミュニケーションとデータの融合にあると思うので、コミュニケーション機能のより一層の拡充は期待したいですね。

技術者としてすべきこと

さて、新年からクレクレマンのような要望を書いてしまいました。
とはいえ、昨年のkintoneに行われたバージョンアップの頻度は私たちの期待を満たしてくれています。
おそらく上に述べたようなことは私が言うまでもなく、サイボウズさんでも考えてくださっているはずです。
となると、こちらとしてはただ要望をいうだけではダメですよね。それだとクレクレマンに堕ちてしまう。
ユーザーや開発者の立場から広めるための働きかけを行わなければ。

例えば、kintoneのキャッチフレーズを考えるというのはどうでしょう。
kintoneを使えば何が良いのか。何が変わるのか。言葉を尽くしてそれを語るのはたやすいと思います。
ですが、一言でkintoneの良さを語るのは難しい。シンプルにズバリと本質をつく言葉を今年は考えたいですね。

また、ユーザーがシステムを作る手段としてkintoneの認知度は相当高まってきました。
また、年末にテレビCMが始まったことで、さらに認知度は上がっていくことでしょう。
とはいえ、技術者に対しての認知度はイマイチです。
さまざまなシステムを作る技術者の皆さんにこそ、kintoneの認知度を上げ、採用してもらわねば。

昨年、kintoneエバンジェリストとしてのインタビューでもその想いは語りました。
そこでも語った通り、わが国のシステム開発の生産性はまだまだ伸ばせる余地があると思います。統制のための統制、仕様のための仕様、ドキュメントのためのドキュメントではなく、設計から実装までの各フェーズが共通のフォーマットで流れるような仕組み。
海外のサービスの開発速度が速いのは、それができているからではないでしょうか。
わが国の場合、ミスが許されない文化性の違いもあるのでしょう。ですが、これからはバグや仕様を恐れない開発手法があってもよいと思います。
kintoneは画面や設計がプラットホームとして共通なので、共通言語で語れる部分も多く、スクラッチ開発よりもやりやすいはずです。

ただ、各アプリの連携やシステム全体の設計についてはkintoneは不得手ですよね。それを自動的に作れるようなツールが作りたいと常々考えています。各アプリを横断したER図や機能連携図を簡単に作れるようなツール。
これができれば、お客様との仕様確認も楽になるし、開発者側でもkintoneを導入する機運はさらに高まるはずです。それがわが国のシステム開発の生産性を上げられればいうことはありません。

弊社も昨年は、土壇場での仕様変更に何度も苦しみました。
ですが、その都度、kintoneの良さを生かしてすぐにリカバリできました。
今年はそのリカバリ手段をより研ぎ澄まし、要件定義に時間をかけずに、なおかつ、バグや仕様変更にもすぐに対応できるような体制を作りたいです。
それには、自社であらゆるパターンに対応できるJavaScriptやプラグインのストックをより多く作ることでしょうね。
その中で汎用的に出せそうなソースコードはブログなどで公開することで、エコシステムに貢献できればと考えています。

あと、今後は大手企業様の事例も増えるでしょう。
サブスクリプションの課金にも抵抗がないお客様には、積極的にサードパーティー製のプラグインを提案していこうと思います。
そのあたりは柔軟に取り組みたいですね。

あとは、ブログやYouTube、SNSなどの手段以外に、どうやって認知度を広めるかですよね。
kintoneにもともと興味を持ってくださっている方は、上記のようなメディアに来てくださいます。
ですが、世の中にはkintoneの存在すら知らない人がまだまだいらっしゃいます。そうした方にどうやって広めていくか。
おそらくkintoneのテレビCMはそうした意図で作成されたのだと思います。

あとは、私たちがどうテレビCMを補完するような発信ができるかですね。
おそらく私の場合は技術者向けのイベントや、SIerさんへの内部発信など、今まで取り組んできたことを深めていくのが良さそうです。
それと、弊社の場合、「自治会 IT」で検索すると一番上に登場しています(サイボウズさんの記事ですが)。

そのアドバンテージを生かし、今年は自治会やNPOにkintoneを告知するような手立てを考えていきたいと思います。
自治会やNPOといえば、比較的年配の方が活躍されています。年配の方は、ブログやYouTube、SNSに触れることも少ないように思います。そうした方へのアピールですね。
例えば市役所と組んだり、広報誌で告知したりといった手段で、実際に赴いてkintoneをアピールするのはどうか、と考えています。
おそらくそうした皆さんにkintoneを説明するには、システム用語を極限まで減らすなどの配慮が必要でしょう。

コロナで果たしてそうした機会がいただけるかどうかは不明です。が、チャレンジしてみたいと考えています。

kintoneが今年、拡がるためにはほかにも思いついた手段があれば試してみようと思います。


kintoneにシステム移したいんや


kintone Advent Calendar 2020の22日目の記事です。

  Topへ↓

おかげさまで今年はkintoneの案件が大幅に増え、ありがたい限りです。引き続き、頑張っていきまっせ^+++^

さて、案件が増えたのはめっちゃうれしいんやけど、今年は今までと比べ、案件の傾向に少し変化がありましてん。その傾向っちゅうのは旧システムからkintoneへ移行する案件の増加ですねん。
もちろん、昨年までも移行案件は請けてました。でも、今年はC/S(Cliend/Server)型の旧システムからkintoneへの移行案件をぎょうさんご依頼いただきまして。その数はめっちゃ増えましたわ。

おそらく今後もこうした移行案件は増えていくと思てますねん。そやさかい今年のkintone Advent Calendarは7回目の参加にして、初めて連携開発ではなく、移行について書いてみようと思とります。
これからkintoneへのシステム移行を手掛ける方の参考になればうれしいねん。

システム移行の建前と本音

  Topへ↑

システムを移行する目的はお客様によって千差万別です。
でも、システムを移行すると決断するのは経営層、少なくとも部署の責任者でっしゃろ?

この時、経営層と現場でシステムを扱う方の見る視点は違います。
経営層は人件費の削減や処理効率の改善に重きを置きます。一方で現場の視点は日々の業務の負担を軽減することと、業務を止めないことに向きがちです。

その時、建前上では業務改善を同時に行いながらシステム導入も行う、という意見が出ます。そりゃせやんなぁ。わてら提案側にとっても業務改善を錦の御旗に立てたほうが提案は通りやすいっちゅうもんで。

そやけど、往々にして現場の本音とは従来の業務フローを変えずに、なおかつ業務負担が軽くなることにあると思とります。業務改善によってオペレーションが大きく変わるのはいややなあ。新たなやり方になれるまでが大変やわ。てなわけで抵抗を覚えがちです。

わてがこの時に心がけとるんは、営業の相手である経営層や上層部とのお話がまとまったら、なるべく早いうちに現場の業務担当に挨拶し、現場の方とのコミュニケーションを増やし、現場の本音を伺いながら開発することですねん。
この時の建前と本音と取り違えると、あとあとまでボタンの掛け違いがえらいこっちゃになります。
また、営業目線でお客様の上層部とだけ話をしていると、現場のニーズが後から後から噴出してがっちゃがちゃになります。わては何度もこれでえらい目に遭っとります。

構築の順番

  Topへ↑

現場の方にとっては、慣れ親しんだ旧システムからkintoneという未知のシステムに触れるわけです。不安になって当然やんなぁ。
そやからわてはkintoneの良さ、つまり現場の方々が簡単にシステムが触れまっせ、簡単に設定ができまっせ、ということを早めにご説明します。

このあたりは旧来のシステム開発のセオリーとは違いますわな。要件定義、内部/外部設計、コーディング、単体テスト、結合テストが終わったあたりでようやくマニュアルが用意され、ユーザー受け入れテストで現場の方が触る。それが旧来のシステム開発。

わてはkintoneの場合はより早いうちに現場の方に触ってもらうことを重視します。フィールドコードは触らんといて、プラグインやJavaScriptはいじらんといて、という点だけお願いし、あとは積極的に使ってもらいます
その結果、kintoneってうちらでも触れるんや、現場でもシステム開発に参加してええんや、という実感を持ってもらえればもうしめたもんですわ。
この場合の現場というのは現場の統括者ではなく、文字通り日次の業務で手を動かしている方全員のことやからね。

これで現場の方からkintoneへの警戒感や拒否感が取り除けて、システム開発へ参加しているという実感を持ってもろたら、kintoneのファンにだってなってもらえます。そうなればシステム導入の成功は約束されたも同然ですねん。

その時、最後になるまでシステムを見せへん、という手法は逆効果。
まず移行が順調にできていることを示すためにも、一番やりやすいマスタ系からアプリを作っていくとよろしおま。
マスタは業務の基本であり、なおかつマスタアプリ自体には複雑なロジックはそれほど必要ないよって、導入側にとっても取り掛かりやすいはずですわ。
あと、早めにお客様にシステムに関わってもらうことによって、kintoneが苦手な部分をわかってもらうのも重要!なんでかいうたら、システム導入間近になっての仕様変更で断れるからやねん。

また、フィールドの追加や編集も自在にできるkintoneの特性がお客様に喜ばれるのもこの時。もちろん、ビジネスロジックに関わりの少ないフィールドを除いてやで。
例えば分析用に得意先種別を増やしたいねん、というご要望にもさくっと対応できるkintone。このかっこええ姿をアピールするだけで、好感度アップは間違いなしや。

また、ここで現場の方に新旧両方のシステムにマスタの入力を行ってもらえれば、次に述べるデータ移行の手間が大幅に減ります。ほんまやで。

データ移行の基本

  Topへ↑

kintoneへのシステム移行でいっちゃん肝心なんは、おそらくこの部分だと思とります。
kintoneはご存じの通り、簡単にアプリが構築できますやろ。そやから、元のシステムのデータの項目を再現するのはそれほど難しくない。そないに思うかたもおるんとちゃいますか?
ところが、ここを甘く見とったら後で苦労しますねん。

とくに、もともとのシステムの仕様で、マスタ上で管理する項目が制限されとったら要注意です。
入れたい情報を入れるべき項目がない。その場合、おうおうにしてお客様は備考欄にあれこれ詰め込みますねん。苦肉の策で本来ならメールアドレスやURLを入れるべき欄にまで雑多な情報を入れてしまいます。
kintoneでは簡単にフィールドを追加できまっしゃろ?でも、おうおうにして旧システムでは項目を追加するのに別途費用がかかります。だから、そないな状態になってしまうんですわ。
こうした雑多な情報がどこで管理されとるんか。その情報をkinotneのどの項目に移すんか。きちんと体系化されたデータとして活用したいんやったら、お客様ときちんと押さえとかなあきません。

次に、マスタをルックアップで呼び出す際の仕様は早めに定めとかなあきまへんで。御存じの通り、ルックアップでは関連付けるアプリを設定しますよって。
そしたら、コピー元のフィールドも設定せんならん。ルックアップフィールドではフィールドに文字を入力することで、候補を事前に絞り込むことができます。ただしコピー元のフィールドに設定した値に限りますけどな。

例えば得意先マスタの得意先コードを指定しておくと、マスタの得意先コードで検索が可能です。ですが、一度ルックアップフィールドをこさえた後にお客様から得意先名でも検索したい、と言われたらもうバンザイせなあきません。なんでかちゅうたら、コピー元のフィールドは一度設定すると変更でけへんよって。
もしどうしても変更の必要が生じたら、新たにルックアップフィールドを追加せんならんのです。もちろん古いルックアップのデータから新しいルックアップにデータを移さなあかん。そんなんいけずやんかぁ。

また、検索したい対象は得意先名だけに限りません。よくいわれるのは着信時の電話番号の末尾四桁ですわ。それ以外にもフリガナやらなんやら。つまり複数の項目で検索したいというご要望が出てきますねん。
この時は、わては検索キーっちゅう項目を設け、複数の項目の値を連結させとります。
この時はスペースなしでがっちゃんこしてまうとうまく検索がでけへんようになるから、半角スペースなどを間に挟んで連結するとよろしい。

さらに、その時は文字数にも気ぃ付けましょう。コピー元のフィールドに指定できるのは文字列(1行)です。
こちらのヘルプにも書かれとるけど、文字列(1行)の文字数に制限はあらへん。
ところが、今のページにはこうも書かれとったよね。
値の重複の禁止を設定すると、入力できる文字数が全角または半角で64文字までに制限されます、てな感じ。なんでやねん。

では、値の重複の禁止を設定せんかったらええんちゃうん?そう思いたなるやん?え?ならへん? いや、なってぇや。
なぜなら、ヘルプには以下のように書かとるからやねん。
既存のレコードを更新する場合、CSVファイルを読み込んで一括更新する方法が便利やと思うですわ。
けど、ヘルプにはこうも書かれとるねん。

こないな制限があったら、もうあかん。更新がでけへんのですわ。
APIで更新する場合も同じですわ。
ここのリファレンスにも。
システムの移行には、データの更新が欠かせませんわな。CSVで更新する場合も、APIで更新する場合も。
ルックアップ項目を更新するためには、値の重複の禁止せなあきません。ちゅうことは、連結した文字列の文字数が64文字を越えんようにせんならんのです。つまり、移行元のシステムの項目の文字数を考慮にいれなあかんちゅうわけですわ。難儀やなぁ。

もう一つ、移行にあたって注意しておかなあかんことがあります。
それは住所データの扱いです。日本の住所の場合、以下の4つの項目からなっとります。

  • 都道府県
  • 郡市町村
  • 住所(町・字・地番)
  • 建物・マンション名

これが旧システムでも四つに分かれとったら問題ないんよ。
そやけど、住所1、住所2という感じで二つだけのフィールドでしか管理されていない場合、えらいこっちゃになります。

なんでかゆうたら、それを分割する作業が発生するからですわ。分割?そんなんせんでええわ。てゆわれたかて、後々の分析のこと考えたらそうもゆわれへん。正味、お客様に後々の分析のことを考えてと提案してみたら、kintoneでは分割して管理するお客様が多いんですわ。
例えば旧システムの住所1に「東京都中央区日本橋2−7−1」。住所2に「東京日本橋タワー」の値が入っていたとしますやろ。そしたら、住所1から「東京都」「中央区」「日本橋2-7-1」を分割せんならんことになります。さて、ぼんやったらどないする?

わてはそんなとき、VBA(Excelマクロ)を活用しますねん。
その中でこちらのサイトを参考に正規表現から分割しますねん。
この正規表現をVBAの中に仕込んだら、都道府県と郡市町村と住所(町・字・地番)を分割できるんよ。
以下に簡単やけどコードを掲示してみたから、よかったらみてみて。必ず本番では使う前にテストしたってぇやぁ。もちろん当方では一切の責任は負われへんから。

Sub ConvertSplitAddressData()
    Dim regExp              As Object           '正規表現オブジェクト
    Dim strPattern          As String           '正規表現パターン
    Dim lngRowCounter       As Long             '行カウンター
    Dim intRegMatchCount    As Object           '結果
    Const clngMaxRowCount   As Long = 65535     '行の末尾
    Const cintTargetColumn  As Integer = 18     '結合された住所列番号
    Const cintPrefColumn    As Integer = 14     '結果の都道府県を格納する列番号
    Const cintCityColumn    As Integer = 15     '結果の郡市町村を格納する列番号
    Const cintAddressColumn As Integer = 16     '結果の住所を格納する列番号
    Const cintAnotherColumn As Integer = 17     '結果の建物を格納する列番号

    Set regExp = CreateObject("VBScript.RegExp")
    strPattern = "(...??[都道府県])((?:旭川|伊達|石狩|盛岡|奥州|田村|南相馬|那須塩原|東村山|武蔵村山|羽村|十日町|上越|富山|野々市|大町|蒲郡|四日市|姫路|大和郡山|廿日市|下松|岩国|田川|大村)市|.+?郡(?:玉村|大町|.+?)[町村]|.+?市.+?区|.+?[市区町村])(.+)"
    With regExp
        .Pattern = strPattern
        .IgnoreCase = True
        .Global = True
        For lngRowCounter = 2 To clngMaxRowCount
            If ActiveSheet.Cells(lngRowCounter, cintAddressColumn).Value = "" Then
                Set intRegMatchCount = .Execute(ActiveSheet.Cells(lngRowCounter, cintTargetColumn))
                If intRegMatchCount.Count > 0 Then
                    ActiveSheet.Cells(lngRowCounter, cintPrefColumn).Value = Trim(intRegMatchCount(0).SubMatches(0))
                    ActiveSheet.Cells(lngRowCounter, cintCityColumn).Value = Trim(intRegMatchCount(0).SubMatches(1))
                    If InStr(intRegMatchCount(0).SubMatches(2), " ") > 0 Then                '建物以降が全角空白で分割されている場合
                        ActiveSheet.Cells(lngRowCounter, cintAddressColumn).Value = Trim(Mid(intRegMatchCount(0).SubMatches(2), 1, InStr(intRegMatchCount(0).SubMatches(2), " ")))
                        ActiveSheet.Cells(lngRowCounter, cintAnotherColumn).Value = Trim(Mid(intRegMatchCount(0).SubMatches(2), InStr(intRegMatchCount(0).SubMatches(2), " ") + 1, 100))
                    ElseIf InStr(intRegMatchCount(0).SubMatches(2), " ") > 0 Then             '建物以降が半角空白で分割されている場合
                        ActiveSheet.Cells(lngRowCounter, cintAddressColumn).Value = Trim(Mid(intRegMatchCount(0).SubMatches(2), 1, InStr(intRegMatchCount(0).SubMatches(2), " ")))
                        ActiveSheet.Cells(lngRowCounter, cintAnotherColumn).Value = Trim(Mid(intRegMatchCount(0).SubMatches(2), InStr(intRegMatchCount(0).SubMatches(2), " ") + 1, 100))
                    Else                                                                      '建物がないか続けて入力されている場合
                        ActiveSheet.Cells(lngRowCounter, cintAddressColumn).Value = Trim(intRegMatchCount(0).SubMatches(2))
                    End If
                End If
            End If
        Next lngRowCounter
    End With
    Set intRegMatchCount = Nothing
    Set regExp = Nothing
End Sub

データ移行の方法

  Topへ↑

kintoneにデータを取り込む方法はいくつかあります。そやけど、だいたいはCSVによる一括登録・更新か、APIでの登録/更新かcli-kintoneの三択ちゃうやろか。
わてはCSV経由で取り込むことがほとんどですわ。
なんでかいうたら、APIやとHTTP Client Tool for kintoneを使う場合でも、何かのプログラムで書く場合でも、コードのマッピングの記述が面倒になるからやねん。
あと、一リクエストでも100件しか登録や更新ができないAPIの仕様制限があるやんか。そやから、100万件をこえるデータを移行する場合、一日のAPIリクエスト回数制限にも引っかかってしまうんや。
cli-kintoneは便利やねんけど、上と同じくマッピングの部分が手間なのであまり使用しとりません。ただ、余談すると、Bashとcli-kintoneを使った効率化は追及せなあかんと思とります。そやから弊社としてはcli-kintoneを使ったツールの作成ははよ進めよ思てます。
ただ、それでも画面上でマッピングを確認できるcsv取込の方が安全ちゃうかと思います。ここはよぉ考えてみてください。その時、旧システムが出すcsvの見出し項目名とkintoneのフィールドラベルは合わせといたほうが移行マッピングが圧倒的に楽になるで。

先に挙げた住所や備考以外にも、データの加工はあちこちのフィールドで発生しますやろ。それらはめっちゃ面倒やと思うねん。それらをチェックとか加工とかせなあかんから。例えば下のような感じやね。

  • 項目の値の中に半角カンマは入っていないか。
  • 項目の値の中にダブルクォーテーションなどは入っていないか。
  • 項目の値の前後に空白は入っていないか。
  • 旧システムから出力されたファイルの文字コードは統一されているか。(複数のシステムからそれぞれ出力して取り込む場合、Shift-JISとUTF-8が混在しているとkintone側で文字化けの恐れあり)
  • ルックアップの値はkintoneの関連付けるアプリの参照アプリのコピー元のフィールドに等しいか。
  • 電話番号や郵便番号、メールアドレスやURLの書式は正しいか。
  • 日付書式は正しいか。(元データがyyyymmdd形式の場合、空白セルがあると空白に変換してくれずエラーになる等)
  • データの重複はないか。
  • kintoneのラジオボタン/チェックボックス/複数選択/ドロップダウンの選択肢に等しい値のみが含まれているか。

もしでけるんやったら、VBAマクロやcli-kintoneなんかでツールを作ったほうがええと思うで。
あと、ツールの作成と簡単に書いとるけど、旧システムによって項目がまちまちなんは、わかりますやろ?旧システムごとに工数と時間を使うから、わては移行ツールの作成には消極的で、あまりやってへんかったわ。

そやけど、毎度Excelの関数を駆使して移行データを作るんはもうしんどい。わても実は今年手掛けた数々の移行の中で、一つだけめっちゃ苦労した移行がありましてん。それをしおに、めんどいかもしれんけど、移行ツールを作ったほうがええなぁと痛感しましたわ。

ただし、早めにマスタの項目を固め、マスタアプリを作り、初回のデータ移行がでけたところで、それ以降は本番までお客様にマスタデータの入力をしてくれまへんやろか?とお願いしたほうがよろし。もしそれがでけたらマスタの移行ツールはいらんからね。
ま、それでもトランザクションデータの移行に関しては何かしら作らんといかんけどね。

なんでかいうたら、マスタのデータは生き物やねん。お客様によってガーっと追加され、あちこちでバァーっと更新されますやろ。
しかも住所の更新が起こるし、しかも項目が連結されている住所データやったら、さっき書いたみたいに分割の作業が毎度いるんでっせ?
それに、本番移行直前でせーので一回でガバっと取り込めば済むほどシステム移行は甘いわけやあらへん。

もちろん、どっかの時点で初回分を取り込み、定期的に差分データを取り込み、最後に移行日までの残り差分がきれいに取り込めた場合は楽ですわ。そやけど、そうした移行の運用ができるのは、こっち側が旧システムにアクセスできる場合だけやねん。

遠方のお客様で、しかもVPNでつながれへんような旧システムの場合、データの取り出しはお客様にご依頼するほかありませんやろ。
お客様のやり方によっては項目に抜け漏れがあるやろし、項目の順番が違っていたりします。ましてや差分データの時間の基準もあいまいになってしまいますねん。そやから、きれいなデータを毎度もらえないと考えなあきません。
そやから、移行ツールを作らんでええ場合は、旧システムにこちらから簡単にアクセスできる場合に限ったほうがええね。もしそないにアクセス出来るんやったら、毎回Excelのフィルターや並び替えや置換などを駆使しても円滑に移行できると思うけどね。
ただ、実際はそうでない場合が多いから、そないなリスクを考えたら、最初に工数と時間をかけてでもお客様専用の移行ツールを作り、それを運用したほうが格段に楽やと思うんやけどどない?

データ移行のTIPS

  Topへ↑

なんぼかTIPSを列挙しておきますわ。

  • データの本番移行までは、取り込み時には変更履歴はオフにしといたほうがええで。
    設定は、アプリの設定→高度な設定→変更履歴のチェックを外す。
    そうしないとサイボウズさんに注意されまっさかい。また、変更履歴だけで契約のディスク使用量を軽く超過してしまいますねん。(一度、うっかりしていて100万件のデータを何度も取り込みなおし、サイボウズさんに注意されてもうた。※EvaCamp 2020で言いそびれた失敗談の一つ)
  • 可能であれば、本番移行後は旧システムのトランザクションデータ(売上や仕入などの伝票データ)は、kintoneでも別アプリにした方がええで。
    しかも、旧システムのトランザクションデータのフィールドはルックアップをなくし、文字列(1行)と数値のみにしといたほうが楽やで。なんでかいうたら、過去分のデータはマスタデータの変更などで古い値として入っとるからやねん。過去のルックアップはその時のマスタデータの値を再現して保存しといて、てな具合にご要望もろたら、移行作業はめっちゃごっつい苦行になってまう。さっきも書いたけど、ルックアップのコピー元のフィールドはマスタにある値やなかったらエラーになるからね。そやから、ここは初めのころに決めておいた方がええかも。
    旧システムの部分については別アプリに分け、ルックアップを外してしまえば、旧システムの生データをそのまま取り込むだけ済むさかい、ごっつい楽ですわ。
    そやけど、一つだけ注意しとかな。アプリを分けた場合、新旧両方のアプリをまたいだ分析に対応できませんやろ。その場合は、自分でカスタマイズビューを作成するか、トヨクモさんのDataCollectのようなプラグインを活用するとよろし。便利でっせー。
  • 添付ファイルの移行が必要な場合は先にも挙げたcli-kintoneを使うとええで。また、要件定義の段階でDropboxやboxなどのオンラインストレージの移行を提案しとったら、kintoneではそちらのストレージとの連携を行えばええから、あんじょういきまっせ。
    オンラインストレージへのデータ移行と整理作業は、お客様にお願いしといたほうがええやろし。
    2019年のわてのAdvent Calendarもご参考にしたってやー。
  • C/S側の旧システムは、たいがいレイアウトが小さくまとまっとるやろ。それに比べるとkintoneのフラットデザインは、項目の間の余白もがっつりとられとるし、そこは一目で情報が見られる旧システムに劣ると思われるかもしれへん。
    無理やりJavaScriptでレイアウトを調整したったりもしたけど、それはあんまり本筋のやり方ちゃうしなぁ。
    これも早い段階で現場の担当者と話を詰めといたほうがええで。
    あ、もう一個。デザインについてはkintoneのアプリの設定→デザインテーマの設定でブラックを選ぶとお客様の印象が変わるかもしれんで。一度試すとよろしおま。
  • Excel上でデータの加工を行う時、何使てます?フィルタ? もしそやったら、スライサーの機能とか試すと幸せになれるで。
    スライサーはこの記事が参考になるんちゃうかな。
  • kintoneの難儀な仕様ってあるやんか。例えば英単語の一部分の文字だけで検索でけへんとか。
    ヘルプにもこない書かれとるし。
    もしそれやったら、Excelのフィルタ使たらええねん。まあJavaScriptで
    一文字検索の機能を作ってもええし、プラグインでもええのんあるけどね。
  • C/S側の旧システムのユニークの項目は早めにお客様と共通認識をもっといたほうがええ。さっきも書いたけど、お客様からデータをもらう際は、旧システムのデータ出力方法を把握して、その設定や出力操作は完全に共通認識をもっといたほうがええ。あと、これも上で書いたけど、旧システムのデータを先に修正できるのならお客様に頼んで修正しといたほうがええ。

まとめ

Topへ↑

ちゅうわけで、本記事は皆様のkintoneへの移行のお役に、そしてご参考になればよろし思ぅて書きました。もちろん、これは弊社にとっても参考にすべき自社のノウハウですわ。
弊社も今年は一件、移行で苦労した案件がありましてん。だからこそ、一度ノウハウを言語化すべきやなぁと思ったんよ。それがきっかけです。
また、弊社の新規の案件の際にもお客様にも読んでもらお思ぅて、ここで知見を共有したいと思います。
なので、今後も折をみてアップデートを重ねていこうと思います。よろしゅうお願いいたします
また、こんなTipsや失敗談などありましたら、ガァーっとご意見をお寄せください。また本稿の内容に不備があった場合も遠慮せんとご指摘ください。

本稿が旧システムからkintoneへの移行をお考えの皆様にとってちょっとでも手助けになったら幸せやわ。


Cybozu OfficeのスケジュールをCalendar Plusで(だいぶ)再現!


カレンダーPlus Advent Calendar 2020の5日目の記事です。

  Topへ↓

先日のCybozu Days 2020 Osakaの後、Calendar PlusのAdvent Calendarに空きがあると聞き、やってみましょうか?と言ってしまいまして。
言った以上はエントリーせねば。というわけで翌日、神戸で商談する前に中華街で飯を食べながら記事にエントリー宣言。

さて、エントリーしたのはいいが、何を書けばええんやろ?しかもエントリーしたタイミングは前日やし(^^;)。これはひょっとして勇み足をやらかしてしまったのではなかろうか。

迷いを振り切った私が商談に臨むと、なんとお客様がCybozu Officeのスケジュールを愛用しており、それをkintoneに載せ替えたいとのこと。しかもkintone標準の標準のカレンダー機能は表現に乏しく、Calendar Plusを検討してみたけど、プラグイン設定の色設定の種類が少なくてなぁ。と悩みを吐露されまして。商談からネタが転がってきた瞬間です。
これはCybozu Officeのスケジュールアプリの画面です。

私は今までも何回かCalendar Plusは提案してきました。けれど、本格的なユーザーではないし、弊社のkintone納入実績からするとCalendar Plusの納品実績は一割もありません。熟練のユーザーではないのです。

が、Calendar Plus JavaScript APIを使ったカスタマイズの経験はあります。API使えば、ある程度ならCybozu Officeの体裁にCalendar Plusを合わせられるんちゃうの?という見切り発車でした。

Calendar Plusのプラグイン設定画面

  Topへ↑

うーむ、確かにCalendar Plusの色設定は12種類か。

そして、お客様のCybozu Officeのスケジュールの色設定で設けた種別は25種類。つまり倍。

確かにお客様がkintoneへの移行をためらわれるのもわかるなあ。

アプリを作る

  Topへ↑

じゃあ、ちょっと試しにアプリを作ってみよう。こんな感じで。

あらよっと、Cybozu Officeと同じようにレコードを登録してみました。さて、Calendar Plusで見るとどうなる・・・?

あかん・・・Calendar Plus側の色設定をしていないのですべて同じ緑地になってる。
しかも予定の状況がカレンダー上に表示されておらず、直行なのか直帰なのかが一目で区別できない。
やはり見にくいなあ・・・・これは、Calendar Plus JavaScript APIの出番かな。

コーダーモード発動

  Topへ↑

ということで、ガリガリとコーディングすること2時間。うん、こんな感じになりました。


というわけで最低限、記事にできるところまでこぎつけたのでいったん寝ます。アップ当日になってしまいましたが、AM2時前なのにもう眠い。年や。

プログラム開示

  Topへ↑
さて、アップ当日の朝、旅に出かける前に少しだけ記事を書きまして。
ここにコードを開示しておきます。すみません。洗練されたコードとは程遠くて・・・・。

(function () {
  "use strict"

  // 関数本体
  const calenderformat = function (event) {
    // Calendar Plusのイベントリスナー定義
    const calendareventslist = ["cp.event.show"];
    // kintoneのイベントの書き方と似ててわかりみ
    calendarplus.events.on(calendareventslist, function (calendarevent) {
      // kintoneのイベントのjson(上記イベントで対象データループしてくれます)
      const record = calendarevent.record;
      // kintoneのイベントの要素
      const element = calendarevent.element;
      // テーブルだけkintoneのイベントの要素
      const tdelement = document.getElementsByClassName("fc-event-container");
      // Cybozu Officeの時刻表示に合わせるため(ださいかも)
      const starttime =
        ("00" + new Date(record.開始時刻.value).getHours()).slice(-2) +
        ":" +
        ("00" + new Date(record.開始時刻.value).getMinutes()).slice(-2);
      const endtime =
        ("00" + new Date(record.終了時刻.value).getHours()).slice(-2) +
        ":" +
        ("00" + new Date(record.終了時刻.value).getMinutes()).slice(-2);
      // 左端の行高がCalendar Plusでは縮まるため、Cybozu Officeの行高っぽくする
      tdelement[0].style.minHeight = "90px";
      tdelement[1].style.minHeight = "90px";
      tdelement[2].style.minHeight = "90px";
      tdelement[3].style.minHeight = "90px";
      tdelement[4].style.minHeight = "90px";
      // リンクするためのurl
      const baseurl =
        kintone.api.url("/k/").replace(".json", "") +
        kintone.app.getId() +
        "/show#record=" +
        record.$id.value;
      // 予定の状況にあわせてSwitch(ださい)
      switch (record.予定の状況.value) {
        case "ーーー":
          // 枠の背景色
          element[0].style.backgroundColor = "#FFFFFF";
          // 文字色(本当はhoverとかの定義も必要。そもそもcssにまとめたほうがよい)
          element[0].style.color = "#006bac";
          // 折り返してくれなかったので折り返し(そもそもcssにまとめたほうがよい)
          element[0].style.whiteSpace = "break-spaces";

          // 終日フラグかどうか
          if (record.終日.value.length > 0) {
            // 頭に・をつける
            element[0].innerText = "・" + element[0].innerText;
          } else {
            // 頭に時刻をつけて改行
            element[0].innerHTML =
              starttime +
              "-" +
              endtime +
              "
 " +
              '<a href="' +
              baseurl +
              '">' +
              record.予定.value +
              "</a>";
          }
          break;
        case "直行":
          // 枠の背景色
          element[0].style.backgroundColor = "#ebf7ef";
          // 枠の線色
          element[0].style.borderColor = "#d8f2e0";
          // 文字色(本当はhoverとかの定義も必要。そもそもcssにまとめたほうがよい)
          element[0].style.color = "#006bac";
          // 折り返してくれなかったので折り返し(そもそもcssにまとめたほうがよい)
          element[0].style.whiteSpace = "break-spaces";
          // 終日フラグかどうか
          if (record.終日.value.length > 0) {
            // 頭に・をつけ、予定の状況を枠で囲む
            element[0].innerHTML =
              "・<span style="background-color:#32a759;color:#fff;" class="scheduleMarkEventMenu">" +
              record.予定の状況.value +
              "</span>" +
              record.予定.value;
          } else {
            // 頭に時刻をつけ、予定の状況を枠で囲む(以下コメントは省略)
            element[0].innerHTML =
              starttime +
              "-" +
              endtime +
              "
 " +
              '<a href="' +
              baseurl +
              '">' +
              '<span style="background-color:#32a759;color:#fff;" class="scheduleMarkEventMenu">' +
              record.予定の状況.value +
              "</span>" +
              record.予定.value +
              "</a>";
          }
          break;
        case "直帰":
        case "直行/直帰":
          element[0].style.backgroundColor = "#e8f2fc";
          element[0].style.borderColor = "#dae8f7";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#3182dc;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "転送":
        case "納品":
        case "設定":
        case "設置":
        case "移設":
          element[0].style.backgroundColor = "#f4fce8";
          element[0].style.borderColor = "#e5f2d3";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#83cb26;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "点検":
          element[0].style.backgroundColor = "#fff4e3";
          element[0].style.borderColor = "#fae8cd";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#ef9201;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "デモ":
        case "営業":
        case "商談":
        case "打合せ":
        case "来客":
          element[0].style.backgroundColor = "#faf6f2";
          element[0].style.borderColor = "#f2e9df";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#c3a88b;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "当番":
          element[0].style.backgroundColor = "#fcfae8";
          element[0].style.borderColor = "#f2efd3";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#dfc506;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "休み":
        case "有休":
        case "リフ":
        case "代休":
          element[0].style.backgroundColor = "#ffebeb";
          element[0].style.borderColor = "#fcdede";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#f44848;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "午前半休":
        case "午後半休":
          element[0].style.backgroundColor = "#fff2f5";
          element[0].style.borderColor = "#f7e9d2";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#f3a4b4;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "土曜当番出勤":
        case "出張":
        case "会議":
          element[0].style.backgroundColor = "#f5edfc";
          element[0].style.borderColor = "#ebe1f5";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#b592d8;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "土曜振休":
          element[0].style.backgroundColor = "#ffebeb";
          element[0].style.borderColor = "#fcdede";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#f44848;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
      }
      return calendarevent;
    });
  };
  // kintone自体のイベント
  const eventslist = ["app.record.index.show"];
  kintone.events.on(eventslist, (event) => {
    // ビューが一致したら本体の関数呼び出し
    if (event.viewId === 5543487) {
      calenderformat(event);
    }
    return event;
  });
})(jQuery);

あと、cssも全体のフォントに関する設定と予定の状況の枠に対して設定しています。

div#calendarPlus {
    font-family:"ヒラギノ角ゴ Pro W3", "Hiragino Kaku Gothic Pro", ヒラギノ角ゴシック, "Hiragino Sans", メイリオ, Meiryo, "MS Pゴシック", "MS PGothic", sans-serif;
    font-size:14.4px;
    font-weight:400;
}
    
div#calendarPlus span.scheduleMarkEventMenu{
    display: inline-block;
    padding: 1px 2px;
    color: #fff;
    margin: 0 1px 2px 1px;
    line-height: 1.2em;
    border-radius: 2px;
    font-size: 12px;
    vertical-align: middle;
}
    

まとめ

  Topへ↑

てなわけで、日中は記事のことは一切忘れて徳島のあちこちをめぐっておりました。で、21時前に実家に帰ってきました。
では、早速記事の続きを。うん、あらためて確認しても表示はこんな感じで大丈夫かと。

本当は実装すべきなのは週だけでなく、他の日や月でも同じように表示できるか試さねばならないですね。また、Cybozu Officeのスケジュールアプリの日時と曜日の行見出しの部分はCalendar Plusでは再現していません。
まあこんな感じですが、記事の主旨は満たせたと判断し、今宵はここまでにしてアップしたいと思います。

あとは、金曜日に商談したお客様がこの実装でご満足いただけるかどうかでしょうか。なにしろ、お客様に商談の御礼のメールを送るより先に、こちらの記事をアップしてしまったので(^^;)。おいおい。

本稿がCybozu Officeからkintoneへの移行をお考えの皆様にとって少しでも手助けになれば幸せです。

もしお困りの際は弊社までご連絡くださいませ。

当エントリーの参考にさせていただいたブログ

  Topへ↑

最後になりましたが、このエントリー作成にあたり、以下のサイトからの情報を参考にさせていただきました。
ラジカルブリッジの斎藤さんにはいつも感謝です。内容にやばい内容が含まれていたら直しますね。
ありがとうございました。

 Calendar Plus JavaScript APIのリファレンス


RPA界の技術者の皆様にお話しをしました



今月、唯一のお話の機会をいただきました。
お招きくださったのはPeaceful Morning株式会社様です。

Peaceful Morning株式会社様は、RPAの界隈のメディアを運営し発信しておられると同時に、RPAに知見のある大勢の技術者を擁し、RPAのツールやサービスを提供されています。
Peaceful Morningという社名にも見られる通り、従来の働き方に一石を投じる理念は、私が推すkintoneやサイボウズ社にも通ずるところがあります。
https://peaceful-morning.com/

今回は、一時間程度のお時間をいただきました。
一時間のすべてを座学形式ではなく、技術者の皆様から事前に質問を募り、それに答える構成でした。

事前にいただいた皆さんの質問内容を拝見すると、kintoneとは何かを知りたいというのは当然として、機能の詳細よりもむしろkintoneの概要や競合ツール、さらにコロナ禍におけるkintoneの需要や今後の展望など、kintoneを扱うことの可能性にご興味がおありの様子でした。
また、kintoneを扱うことによる働き方の変化や、エバンジェリストである私自身がkintoneを推す理由など、技術者としてのキャリアの可能性の一つとしてkintoneを検討したいとの意識を感じました。

事前の質問だけでも十二問に達し、質問に答えるだけで一時間が過ぎることは確実でした。
そのため、kintoneエバンジェリストとして登壇する私は、kintoneそのものの機能よりも、私の個人的な思いや、kintoneのどこに可能性があるのかといった本質的な部分を語ろうと決めました。

折悪しく本番直前になって私のPCの処理が終わらず、kintoneの画面イメージが映せなかったため、kintoneの紹介すらも口頭のみで済ませてしまいましたが(終わりの方で処理が終わったので画面共有で3,4分ほどお見せできました)。

私からの回答の中では、kintoneはREST APIが使えればほかのシステムとも連動が可能とお伝えしました。RPAでいうとUiPathはREST APIが扱えるので、kintoneとは相性が良いですよ、ということも。
おそらくRPA界の皆様には、データベースのバック/フロント構築が簡単にできるツールですよ、という視点で深く掘り下げたほうがより響いたのかもしれません。

参加者の皆様からのアンケートも開示していただきました。
kintoneの画面の動きやRPAとの連動の実例をもっと見たかったというご意見もありました。当然ですね。私の不手際があり、失礼いたしました。
また、私のkintone愛を感じた、といったご意見も数点ほどいただきました。それはまさに狙い通りのご感想だったのでうれしかったです。これを機会にkintoneに興味を持ってもらえればなおさらうれしいです。
RPAとkintoneの連携事例はまだ世に出ていないと思っているので、皆さんと一緒に事例を増やしていきたいです。

こうやって分野の違う技術者の方を前に語るといつも感じることがあります。
それは、kintoneのすそ野がユーザー部門には広がっているとはいえ、まだまだ技術者への浸透には至っていないということです。
技術者の中にはスクラッチ開発に価値を感じる方もいらっしゃれば、基盤から構築してすべてを統制することに意義を認めていらっしゃる方もいます。
もちろん、それは人それぞれなので自由です。ですが、No-Code/Law Codeといった従来のシステム開発の概念と違うツールへの使わず嫌いがあるとすればもったいないと思うのです。

ここ一年、私の中でエバンジェリストとしての活動の主眼の一つを技術者さんへの伝道に置こうと決めました。
それは、私がkintoneに賭けようと思った時期を見つめなおしたことで気づいたことです。
その時期、私には常駐現場を渡り歩く中で感じた既存のシステム開発への疑問が渦巻いていました。それは、人月換算や多層契約によるSES業務の課題、膨大なドキュメントの山、そして作った設計書や仕様書やプログラムが現場のオペレーターさんに直接届かないもどかしさなどほんの一部でしかなく、とても悶々としていました。
その時期の私がkintoneに感じた可能性とそれに賭けようとしたことで、今の私はあります。
それを考えると、技術者の方々が今の開発環境やツールから別のステップに進みたいと感じたときに、kintoneを開発ツールの一つとして選択肢に含めてもらえれば、私がkintoneに身を投じたモチベーションも共有できると考えたのです。

RPA界の技術者の皆様には、また機会があればkintoneを中心としたPaaS/SaaSの可能性について語ってみたいですね。今度はきちんと画面の動きもお見せしながら。今回の処理の遅れを機会にノートPCを新調したことですし。

まずは、今回のご参加者の皆様、ありがとうございました。
また、セミナーを運営してくださったPeaceful Morning株式会社の皆様、ありがとうございました。


freee & kintone BizTech Hackの第一期を終えて


9/17に、freee & kintone BizTech Hackの第四回目が行われました。
(freeeさんからのレポート)。
私は久々にオンラインハンズオン講師を務めました。第一回目以来です。

4/24に行われた第一回で感じた感想は、以下の記事に書きました。
https://www.akvabit.jp/freee-kintone-biztech-hackでオンラインハンズオンの講師を務めました

第二回と第三回では、ハンズオンのメンターとしてサポート役を務めさせていただきました。また、ビジネスセッションの登壇者として、SaaSの隆盛について、基幹システムの最後の砦である会計にもクラウドが使われるようになってきた現状を語りました。

こうしたオンラインハンズオンでの登壇経験や、メンターとしての参加は、私の経験値をかなり高めてくれました。おそらくは知名度も。
そうした活動をSNSに載せる中で、何人かのお客様には「kintone以外にもfreeeにも詳しいんだよね?」という嬉しいお言葉まで。

何よりも私が持っていた経理業務への苦手意識が、この四回の登壇を通して払拭できたように思います。

おそらくはfreeeの設計思想が、貸方借方に象徴される伝統的な簿記ではなく、トランザクションデータとしての受発注伝票に似た形式で組み立てられていたことが大きいと思います。

なんや、普通のデータのように扱えるんや、という安心。
これが学べたのは私にとっても大きいですね。

また、APIキーではなく、きっちりとしたOAuthの仕組みに則った認証が求められることによって、私の知見を高めてくれました。ハンズオンをやる以上、理解していなければ。
これはかけがえのない財産になってくれそうです。

今も、とあるkintoneとfreeeの連携案件で、kintoneから投げたwebhookを、freee側でリフレッシュトークンを使い、都度の認証なしに連動する仕組みがを作っています。その時ももOAuthの知見が生かされました。

四回目のハンズオンでは、参加者の方から、今後のハンズオンはこういうのを期待している、と言った前向きな意見をいただきました。
四回目にして初めてこうしたご意見が頂けたのは嬉しいです。講師冥利に尽きます。
今回で第一期は終わりますが、次回に繋げたいですよね。
他にもお褒めのお言葉をいろいろといただけたことですし。

実は今回の登壇は、別案件をいくつも並行ですすめている中の登壇でした。かなり疲労が溜まった中での登壇でした。でも、オンラインの恩恵を受け、無事にやり遂げられました。疲れてても喋りではなんとかやり遂げられる自信がついたのも大きいです。

おそらくはまだ、これからも来年も、オンラインの上のイベントが主流になると思います。
オンラインハンズオンという挑戦が無事に終わったことで、私の自信になりましたし、私も次なるチャレンジに挑んでみたくなりました。
例えば、先日作った私のアバターを出してみたり。

最後になりましたが、ご参加者の皆さん、メンターやハンズオンを行っていただいた皆さん、ありがとうございました。


弊社はCYBOZU DAYS 2020に出展いたします。


9月が始まった今日、
弊社にとって三つのニュースを発表できる機会が到来しました。

一つ目は、
CYBOZU DAYS 2020への出展です。
2020年11月に千葉の幕張メッセで開催されるCYBOZU DAYS 2020のシルバースポンサーとしてブースを出展いたします。
弊社とPolaris Infotech株式会社(https://www.polarit.co/)との共同出展の形をとらせて頂きます。

URL:
https://cybozuconf.com/
日時:
2020/11/11-2020/11/13
場所:
幕張メッセ 国際展示場1-3ホール
(弊社の出展ブースの場所はすでに決まっていますが、詳細は当日お越しになられた際に配布される地図をご覧くださいませ)

来場方法:
JR京葉線 – 海浜幕張駅 (東京駅から約30分、蘇我駅から約12分)から徒歩約5分
JR総武線・京成線 – 幕張本郷駅(秋葉原駅から約40分)から「幕張メッセ中央」行きバスで、約17分
出展内容:
お楽しみに

Cybozu Daysは、毎年大勢のお客様がお越しになります。今年はコロナ禍のため、感染対策の中で開催されます。
若干の制約事項もあるでしょうが、来場された方は必ず楽しめますし、持ち帰って糧としていただけるだけの内容になっています。これは毎年参加している代表が心から実感していることです。
是非、来場のお申込みをお待ちしております。

二つ目は、
弊社の代表がインタビューを受けた記事がkintone エバンジェリストのサイトで公開されました。
https://www.kintone-eva.com/
代表は今までも何度かインタビューされた経験や、記事に登場したことがあります。
ですが、今回のインタビューは、kintoneを通した代表自身の生き方にまで踏み込みましたので、とても思い入れがあります。

こちらもお読みいただければと思います。

最後は、
弊社のロゴの刷新です。
akbを組み合わせた従来のロゴの色合いをkintoneに近づけたものです。
このデザインは弊社代表の妻と長女によるものです。

長女も現在、デザイン専門学校生ですが、弊社の社名のakbの文字を組み合わせつつ、同時に弊社の代表の頭文字「N」を表したデザインは、デザインやプレゼンテーションのプロの方からもおほめ頂きました。

よりkintoneにデザインを近づけたことで、kintoneに賭けようとする弊社の意気込みを感じて頂ければと思います。

まだ発表していませんが、この夏には
・社是
・企業理念
・経営理念
・9つ(ナイン)のない
・アクアビットに合わない方
という五つの指針も作成しています。

また機会があればご紹介したいと思います。

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


freee & kintone BizTech Hackでオンラインハンズオンの講師を務めました


4/24(金)に行われた「freee & kintone BizTech hack」で、セミナーとハンズオンを弊社代表が担当させていただきました。
今回、初めての開催でありながら、全てがzoom上で行われました。100%ピュアのオンラインです。

ハンズオンとは講師が壇上に立ち、スクリーンに映した作業の一部始終を参加者に見てもらいながら、参加者にも同じ操作を再現してもらうセミナーの形態です。

ハンズオンは、どこか一つでも参加者が手順を誤るとうまく動きません。ですから通常は数名のメンターやサポートスタッフが会場を巡回し、参加者が課題を完遂できる様に導きます。
それでも多くの方が途中で脱落してしまいます。
私も参加したハンズオンのうち、完遂できなかったことがあります。

今回はそのハンズオンをオンライン上で行いました。つまり、難しさはさらに上がります。

弊社代表は、参加者を会場に集めてのハンズオン講師の経験は持っていましたが、オンラインハンズオンの講師は初めてです。
初めてだったのは弊社代表だけではありません。運営側の全員がほぼ未経験でした。
そのため、リハーサルは三回行いました。

皆さんにどうやったらわかりやすく内容を伝えられるか。どうやれば皆さんにハンズオンの課題を完遂してもらえるか。
手順を省くことは許されません。参加者がどこまで理解しているかも予断できません。

矛盾のない内容になっているか。話す速度は大丈夫か。提供するプログラムは破綻していないか。
リハーサルではそうした確認を行い、運営者の皆さんから指摘してもらいました。KPT法にのっとり、keep、problem、tryを繰り返しつつ。

結果として、15名の参加者の皆さんに最後まで完遂してもらうことができました。これも運営の皆さんと参加者の皆さんのご協力のたまものです。ありがとうございました。
公式開催報告:(https://fk-biztech.qloba.com/activities/7256

私にとって、今回の経験はとても得難いものでした。
ハンズオンの講師は、内容を完全に理解していなければ務まりません。
今回、ハンズオンでOAuthの仕組みを扱うにあたり、今まで何となく扱っていたOAuthについてさらに理解を深めました。
学びを通して自分の中にOAuthの仕組みをきちんと落とし込めたことは、今後の業務にとって助けとなるはずです。

ハンズオンの前のセミナーでお話しした通り、これからはSaaS同士の連携が欠かせません。
セミナー資料:(https://slides.com/yoshikazunagai/freee-kintone-biztech-hack-seminar
既存の構成から、新たな仕組みへと。
技術者に求められる役割はこれからも多岐にわたることでしょう。私たち技術者が学ぶべきことは多く、しかも新たな技術を取り入れねばなりません。

今後もオンラインでハンズオン講師を務める機会はあるでしょう。
私もできる限り、力を尽くしたいと思います。
より多くの人にエコシステムを使ってもらうために。より多くの技術者にオープンプラットホームの世界で活躍してもらうために。

すでに今回、ご好評をいただいたことで、次回の開催が決まっています。
freee & kintone BizTech Hackの二回目が5/22に行われます。
申込サイト:(https://fk-biztech.qloba.com/
次回は私は登壇しませんが、スタッフとして皆さんをサポートさせてもらえればと思います。
よろしくお願いします。


kintone Café 神奈川 Vol.6を開催して得た気づき


4/2にkintone Café 神奈川 Vol.6を開催しました。

公式の開催報告はしかるべき場所に書かせていただきました。
こちら

関連するツイートのまとめサイトも作成させていただきました。
Twitterはこちら
zoomグループチャットはこちら

代表である私が登壇の際に語った
kintoneの導入はお客様にファンになってもらいながら!
サイボウズ社のチーム応援ライセンスについて
については、上に挙げた開催報告の中で補足させていただいております。

ここでは、完全オンラインで開催することで気づいたことを書きたいと思います。

まず、開催にあたって大変お世話になったkintone大好きYouTuberこと、サイボウズ社の松井さんにはお礼を申し上げなければ。
松井さんからは、zoomでの運用のノウハウや、YouTube Live配信のノウハウを教えていただきました。当日も開催の間、さまざまなフォローを行ってくださいました。
最終的に、リアル開催からオンライン開催に切り替える決断をしたのも松井さんの一言がダメ押しとなりました。色々とありがとうございました。

あと、当日はYouTube Liveで同時配信を行いました。
配信された結果の動画は、松田さんが編集を買って出て下さいました。助かりました。ありがとうございます。
その動画リストはこちらに載せてくださっているようです。

さて、今回は私にとってはじめてのオンライン・イベントの主催でした。
リアルのイベントでは何度か主催を経験してきましたが、オンライン・イベントは初めての経験。同じ技術イベントとはいえ、やるべき作業は違います。

一言でいうと、事前と最中の忙しさの違い、でしょうか。
リアルのイベントでは事前の準備が大変ですが、開催の間はそれほどやることはありません。
一方、オンラインのイベントは事前の準備より、開催中のほうが大変です。
その部分をお知らせできれば。

オンライン・イベントの場合、事前にやるべき事は大きく分けて三つあります。
一つ目はzoomのミーティングの準備です。今回のzoomのミーティングはウェビナーではなく、ミーティングを選びました。ウェビナーだと観覧者の皆さまがギャラリービューに出ないからです。
ミーティングは「新しいミーティングをスケジュールする」で日時指定で予約すれば大丈夫です。
二つ目は、YouTube Liveの配信の許可です。その方法をご理解いただくには、キンスキ松井さんのブログをご覧いただくのが早いです。
なお、YouTubeアカウントに対してLive配信をを行う場合、24時間ほどの準備が必要だそうです。その許可を行うため、アカウントへLive配信の許可を申請する作業は前もって行っておきましょう。
その後の配信枠の予約などの処理は、キンスキ松井さんのブログに書かれているので、そちらをご覧ください。
三つめは、イベントの告知と招待です。それはリアルのイベントでも同じです。なので、qloba、connpass、peatix、doorkeeperなどとSNSを組み合わせて利用されるとよいでしょう。

続いて、本番中に行う作業です。これがオンライン・イベントでは大変なのです。私は今回、その事に気づきました。
まず、zoom内で司会と進行を行わねばなりません。恰好よく言うとファシリテーションです。
ただ、それはまだいいのです。たとえ今回、私が登壇のセッションを二つ受け持っていたとしても。

本当に大変なのは、皆さんがオンライン上でみるzoomの画面を制御する作業です。それは大きく分けるとギャラリービューにするか、スピーカービューにするか、という二つです。
zoomは喋っている人を自動的に判別し、その人にフォーカスを当ててくれます。
ギャラリービューは喋っている人にフォーカスを当てても、パネルの大きさは他の人と一緒です。例えば16名が参加している場合も4×4のままです。
ところがスピーカービューは話している人の枠が大きくとられ、その他の人は上部に4枠ほど出るだけです。

ここを適切に切り替えておかないと、話している人の表情が見えにくいのです。とくに、話者が画面共有をすると、その間は切り替えることができないので要注意。YouTube Liveは、ホストが設定するビューによって見え方が大きく変わってしまいます。
それにはzoomのスポットライトビデオの機能を使い、話者をロックオンしておく必要があります。この機能はキンスキの松井さんのブログをご参考になさってください。

また、こうしたイベント中の細かい制御は、zoomのホストか共同ホストでないとできないため、事前に複数の運営スタッフ間で権限を委譲しておくことをお勧めします。
ちなみに、zoomからYouTube Liveの配信制御は共同ホストではできません。ホストしかできないので注意しましょう。
もう一つ、本番中にやるべきこと。それは、画面と音声の制御です。参加者の誰かがミュートし忘れ、そのせいでセッション中に大きな物音が響いた場合、スピーカーの話が邪魔されてしまいます。その時にもホストか共同ホストが該当の方のミュートをOnにすることでつつがなく進められます。これも複数人でやり方を共有しておくべきですね。
なお、zoomでは、参加者によるミュート制御の可/不可を設定できます。ただ、不可にしてしまうと話者以外の方が喋りたい時に喋れなません。そのため、可にしておいた方がよさそうです。

やるべきことはまだあります。イベント中のTweetの盛り上げです。リアルのイベントでは、実況も含めたTweetが欠かせません。だからハッシュタグを冒頭に掲示するのです。
ですが、zoomにはミーティング内チャットという機能があります。
参加者にどちらで呟いてもらうか。これは難しいところです。
ミーティング内チャットの場合、同じユーザーインターフェースにチャットボタンがあり、そこから呟けます。そしてその内容はミーティングの参加者に限定されます。だから参加者の皆さんにとってはつぶやくためのハードルは低いです。
ただ、主催者の立場に立ってみると、ミーティング内チャットは、zoomの内に閉じてしまいます。
イベントの発信力を考えるとTwitterのほうが外へのアピールとなりやすく、Twitterで発信して欲しいという気になります。
実際、今回のkintone Café 神奈川 Vol.6ではzoomのミーティング内チャットは451件も呟かれ、43人の最大同時参加者があったのに、38人の方に呟いてもらえました。一方のTwitterは250件と、約1.8倍の差が出ました。
もちろん、zoomでもミーティング内チャットを無効にすることができます。ただ、それを禁止すると、肝心のイベントの盛り上がりに逆効果となりかねません。本末転倒もいいところです。
zoom APIを使い、ミーティング内チャットをTwitterに流せないか、調べてみましたが、どうも難しいように思えます。そもそもzoom内のチャットを外に公開してもよいか、事前に参加者の許可を取っておくべきだともいえます。
ここは、事前にTwitterやミーティング内チャットの盛り上げ役を何人かにお願いしておいたほうが良いと思います。また、ミーティング内チャットは手動で外部に保存できますので、それを定期的にTweetしてもらうのも一つの方法でしょう。
あと、今回はYouTube Liveも同時で配信しました。ですが、YouTube Liveのは双方向の関係が薄くなるため、開始直前まで配信URLを告知しませんでした。これも、本番が始まると忘れてしまいますので、拡散して下さる方を事前に決めておいた方がよいです。今回はキンスキの松井さんにフォローしてもらえました。

主催者がやるべきことはまだあります。それは、間が空いた時の埋め方です。
kintone Café 神奈川 Vol.6でも30分ほど最後にフリートークの時間ができました。
この時に質疑を行うよう促したのですが、まだまだオンライン・イベントには皆さん慣れていないのか、そこで質問を投げて下さる方は少ないようです。
他のオンライン・イベントにも何度か参加したのですが、リアルのイベントに比べると、主催者からの懇願の視線にさらされないためか、積極的に発言する方がなかなか現れないようです。
今回は冒頭でzoomの挙手機能や、はい/いいえでアンケートを取る機能を試してみたのですが、運営側スタッフでその集計や挙手へ反応する運営を打ち合わせ損ねていました。ここはぜひ、事前にコンセンサスを取っておくべきです。

結局、今回は5/9のイベントやkintone Caféの開催場所などのネタで間は持たせました。
決して皆様、話さないわけでは、ないのです。お酒が入った懇親会では、比較的に話が盛り上がりました。そのため、先にアルコールを許可しておくのも方法の一つかもしれません。
事前に質問を受け付けておき、それをネタにするというのも一つでしょう。
この部分は、これからノウハウを貯めてゆくしかないと思われます。
また、何人かに質問を投げて下さるように頼んでおくのも一つの手です。いわゆるサクラのように。

イベントの終了後にやるべきこともあります。
まず、YouTube Liveの配信停止です。今回もキンスキの松井さんにフォローしてもらわなければ、懇親会までLive配信を続けてしまったかもしれません。
そしてもう一つは、開催レポートの執筆です。
YouTubeを見直せば、内容については文章に起こすことはさほど難しくありません。
ですが、その記事に出す写真。これは案外見落としがちになると思います。
イベントの全体を表す画像をどう撮るのか。私は、ギャラリービューをスクリーンショットを撮る以外の写真が思いつきませんでした。

そんな形で、イベントの開催中は、かなり集中する仕事が多いのです。
さらにいうと、ディスプレイという狭い視野の中で作業が複数発生するため、マルチタスクに慣れていないと厳しいです。代表の私の場合、ノートパソコン一台と、Facebook messengerやイベントに関係ないお客様とのやり取りをiPad miniでこなしていました。おそらくイベント用には複数のディスプレイを併用して使いこなすしかないのでしょう。
オンラインのイベント主催とは、このようになれないうちは難しいかもしれません。

ですが、新型コロナ・ウィルスの猛威が衰えを知らない以上、オンラインのイベントはこれからも催されるはずです。ファシリテーションのスキルも求められるに違いありません。そのためには、運営上の工夫の余地はまだあるはず。
弊社も私も、引き続きノウハウの獲得に努力していきたいと思います。

最後に今回、zoomで参加して下さった皆様、YouTubeで参加して下さった皆様、事前と最中と事後にご協力くださった皆さま、本当にありがとうございました。
次回は5/9にkintone Café Online Vol.1が開催されます。
告知サイト
そこでまたお会いできればと思います。

代表の私はfreee株式会社とサイボウズ株式会社の共催イベント
freee & kintone BizTech hackでオンラインのハンズオン講師というさらに難易度の高いイベントに挑戦します。(※すでに満席なのです)
そうした挑戦を通して得たノウハウは、皆様にも還元していきたいと考えております。
今後ともよろしくお願いいたします。


ワーキングツリーにはkintoneとboxをお飾り!


kintone Advent Calendar 2019の24日目の記事です。

  Topへ↓

ん?この著者、この間もAdvent Calendarでみたで? はい。二度目の登場です。

kintoneは優れたツールですが完全ではない?
なーんてディスられても動じず、欠点を正直に認めるのもkintoneの憎めないところ。そう思ってやまない著者です。

kintoneの欠点のいくつかはすぐに挙げられます。
例えばブラウザーベースで動いているので、ファイルアップロードの作業が面倒、とか。
添付ファイルフィールドに画像データを放り込みまくると、一ユーザーあたり5GBの容量の制限が足かせになってくる、とか。

そんな限界を解消するためのささやかなクリスマスプレゼントを皆様にお届けしたいと思います。
なに、ちょっとした贈り物です。クリスマスツリーによくぶら下がっている箱のような。
箱・・・つまりboxです。
今回の記事では私の2019年の失敗事例も公開しているので、ひょっとしたら皆さまのご参考になるかもしれません。

box for kintoneのご紹介

  Topへ↑

世の中にオンラインストレージ製品はたくさんありますよね。その中でもboxが存在感を出しているのはご存じでしょうか。

kintoneとboxの連携はbox for kintoneというプラグインとして公開されています。
それを使えばブラウザー上でアップロードなどせず、ドラッグ&ドロップでブラウザー上から操作できちゃうのです。

クライアントツールのbox Driveをインストールすれば、Windowsのエクスプローラと同じ操作でbox上にファイルをアップロードできてしまう。なんて優れもの。

boxを使うと無尽蔵(契約プランによる)を誇る容量にファイルを置きまくり。
それをkintoneの画面上からに自由に呼び出せる。素晴らしい!
上に書いたkintoneの弱点を周囲のツールが補ってくれるいい例です。
本稿もそうしたkintoneを補ってくれる一つの例としてお役に立てれば幸いです。お日柄もよいので。

box for kintoneの使い方は、Cybozu developer networkにも出ています。
https://developer.cybozu.io/hc/ja/articles/205070124-Box-for-kintone その記事に従えば、簡単にbox for kintoneを導入できるはずです。

ところが、この記事で書かれているのは、一つのレコードに一つのフォルダーを対応させるところまで。
kintoneでちょっとしたシステムを作ろうとすれば複数アプリにまたがった構成が必要です。それに応じてboxのフォルダー構成も複数の階層にまたがってしまいます。

合同会社アクアビットダム設計なる会社

  Topへ↑

たとえば、大阪と東京に支店がある合同会社アクアビットダム設計があったとします。
この会社はダムを独自の技術で製造し、お客様にお納めしている設定です。

受注システムをkintoneで構築するにあたり、
大阪支店[組織]の
長井何某[個人]が担当する
担当案件[案件]の
施行状況[施工]と
湛水状況[湛水]を管理すると仮にしましょう(工程はしょりすぎ。ちなみに最後の工程は水を貯める工程です)。

他に顧客マスタがあるでしょうがここは割愛。また、[組織]と[個人]はアプリではなく、kintoneのアカウントを使用する想定です。
この場合3アプリですね。

ここでご注文からの流れをkintoneで管理したとしましょう。
各アプリの連動はkintoneのアクション機能を使ったとします。

ダム完成までにはさまざまな状況を報告していかねばなりません。するとダムの進捗に合わせて写真が大量に溜まっていきます。
kintoneの添付ファイルフィールドにファイルをアップしていると、すぐに容量が危うくなりかねません。
ここでboxの出番です。

ここでboxで写真を管理しようとした場合、box内のフォルダー構成はこのようになると思います。

さて、先ほどご紹介したbox for kintoneを思い出してみましょう。
プラグイン設定画面にルートフォルダーのIDを設定していましたね。

つまり、アプリ自体にルートフォルダーのみを作る仕様。
それって、どのレコードであろうと共通で1つのフォルダーだけ、、、
いやいや多層boxと多層アプリでは対応できないのはちょっと、、、
結論! box for kintoneだとちょっとキツイかも。

合同会社アクアビットダム設計にboxを

  Topへ↑

ということで、本稿では多段階にわたるboxの連動例をお伝えしたいと思います。また、その時にしでかしてしまった失敗と、そのリカバリ例もお伝えしたいと思います。

まず、話を簡単にするため、合同会社アクアビットダム設計としてのルートフォルダーを設定しておきましょう。

さらに、支店ごとにフォルダーを設定し、支店の配下に担当ごとのフォルダーも生成しておくと話が早いですね。

実際のboxのフォルダー構成はこんな感じ。

ここでルートフォルダーのフォルダーIDを取得しておきます。boxの画面から取れます。

その状態で、案件アプリに新規レコードを登録します。
案件アプリの項目には案件の主管支店と、案件の主担当を指定するフィールドも忘れずに。もちろん必須項目として。

なぜ必須項目にするのでしょう。
その理由は、レコードが保存成功後、案件フォルダーを作る際にどこのフォルダーの配下に作成するか決めなければならないためです。

boxのフォルダー生成APIについて

  Topへ↑

ここでboxの仕様を押さえておきましょうか。
サービスの仕様を確認するには、APIから逆引きしたほうが理解しやすい。いわゆる技術者あるあるです。
boxのAPIはこちらのサイトをご覧になると良いでしょう。
https://ja.developer.box.com ・・developerサイトトップ
https://ja.developer.box.com/reference ・・APIレファレンス

boxのフォルダー作成の項を読むと、親であるフォルダーのIDがパラメーターとして必須のようです。
編集時には親フォルダーのIDは必須ではなくなりますが、もしフォルダーの場所を移動する際は親フォルダーのIDは指定しなければなりません。

案件フォルダーを作る際は、親となる担当者フォルダーのIDを把握しておかねばなりません。
そしてその上の支店フォルダーも。

つまり、案件レコードの保存のタイミングで行うべきことは、まず、そのレコードの支店フィールドの値に等しい支店フォルダーを検索することです。
その際、基準となるのはルートフォルダーです。

ルートフォルダーの下にある支店フォルダーを検索し、そのIDを特定します。
間髪入れずに支店フォルダーの配下にある担当者フォルダーを検索します。
これは同一担当者が複数支店にフォルダーを持っている場合など、運用も考慮していますが、支店フォルダーのIDを内部で保持できるのであれば、いきなり担当者フォルダーから検索してもよいです。
重要なのは案件フォルダーを作成するにはその親フォルダーのIDを事前に必ず保持しておくことです。

先ほど、kintoneの案件アプリの支店と担当者の両フィールドは必須でなければならないとしたのには、そういう理由があったのです。
これら二つのフィールドの値がないと、案件を保存する際に生成されるべき案件フォルダーの保存先が迷子になってしまうので。

JavaScriptで実装してみた

  Topへ↑

続いてはいよいよboxの操作を行います。

その前に本稿ではboxの権限周りには踏み込まないことを言っておきます。
OAuthについては、もともとbox  for  kintoneで用意されていたclient IDを使用します。本当はbox内でアプリを作成し、そのアプリ内で設定した権限を認証しなければならないのですが。
box for kintoneに甘えてしまいましょう。

ついでにpromise処理が考慮されたAPI実行部分もbox for kintoneの処理を流用させていただきましょう。

処理の大まかな順序としては以下の通りです。
ただ、のちに述べますが、このコードは動きません。なのでコードは画像として参考程度に載せます。

まず、イベントが動くタイミングはapp.record.create.submit.successです。新規作成処理成功後ですね。

処理の都合上、この中で別のアプリに更新を行い、その結果が成功した場合にboxフォルダー生成処理を呼び出しています。

boxフォルダー生成処理では、まずルートフォルダーから支店フォルダーを検索します。
続いて支店フォルダーから担当者フォルダーを検索します。

ここでboxの検索の仕様が立ちふさがってきます。
boxの検索仕様として、対象の種類、生成時刻、オーナーIDや親フォルダーIDなどは指定できるのですが、肝心の文字列を完全一致で検索できないのです。queryというパラメータがあるにもかかわらず、そこに指定した文字列は曖昧検索として処理されてしまうのです。
APIレファレンス

つまり、親フォルダーに属する検索対象が複数ありうる場合、検索文字列に工夫が必要です。例えば姓名の間にスペースが入る場合など。
「長井 権兵衛」と「長井 主水」が対象のフォルダー配下にあって「長井 権兵衛」を検索したい場合、queryに「長井 権兵衛」を設定してもマッチしません。
ではどうやればよいか。
スペースの前後の文字列で検索するのです。
この場合、「長井」または「権兵衛」で検索します。すると前者は二件がヒットし、後者は一件がヒットします。
その結果を再度ループして回し、一件ごとにname属性の値が検索文字列に一致するかを確認する。
そのような面倒な処理がboxの検索には必要です。

このコードも実際は使っていませんが、軽く提示します。

これで、担当者フォルダーIDまで求められました。

boxのフォルダー生成と検索にまつわる問題

  Topへ↑

続いてはフォルダーの生成に移りましょう!
APIレファレンス

生成にあたっては、名前と親フォルダーのIDを指定するだけです。

これで、案件レコードが保存されたら案件フォルダーを作成するところまでができました。

ここで当初想定していたboxの構成を見てみましょう。
案件フォルダーの配下に「提案状況」「施工状況」「湛水状況」の三フォルダーがあります。
このフォルダーの生成にも実は厄介な問題が潜んでいます。

例えば、案件のレコードが保存されました。そして案件フォルダーが生成されました。
そしたら、アクション機能によって施工状況アプリにレコードをコピーし、施工状況アプリでもレコードが保存された瞬間、案件レコードの時と同じように施工状況フォルダーを生成すればええんちゃうの?と思ったでしょう。

ところが、フォルダーを生成するには親のフォルダーの指定が必須です。
親フォルダー、つまり案件フォルダーをフォルダー生成処理の直前で検索してフォルダーIDを取得しなければなりません。
ところがこの親フォルダーの検索取得にはひとつハードルが控えています。そのハードルとは、コンテンツが生成されてから検索可能となるまでに時間がかかる、というものです。
boxはなんらかのコンテンツが作成されてから、それが検索可能となるまでにbox内部でindexを構築しており、それに時間が掛かるのです。
boxの APIレファレンスには以下のように書かれています。

つまり、案件フォルダーの生成からすぐ、施工状況アプリのレコードを保存した場合、親となるべきフォルダーが検索できないため、親フォルダーの指定ができないのです。

boxを多層構造でkintoneと連動させる場合、この仕様上の制限は現状では避けられません。

この制限を回避するため、発想を切り替えました。
つまり、案件フォルダーが生成された後、同時に配下のフォルダーも作ってしまうのです。

box APIでは、フォルダー生成が成功した時点で返り値として生成されたフォルダーのIDが得られます。このIDを使えば配下のフォルダーも即時に生成できます。

このコードも実際は使っていませんが、軽く提示します。

この下の処理ではさらに生成した案件フォルダーのURLを取得し、そのURLやフォルダーIDをkintoneの2アプリに更新して設定しています。

ここまででkintoneのapp.record.create.submit.successイベントを見てきました。その結果、実装ができそうです。
テストでもフォルダーが意図通りに生成されました。開発用のPCでも、お客様のご担当者様のPCでも。

バグ大魔王降臨!

  Topへ↑

ところが! やったと思った安心のかげに潜むのが落とし穴。バグが出てしまったのです。
テストではうまく動いていたのに、いざ本番になるとうまくいかない。なんということでしょう!

実は、その根本的な原因は今もなお究明できていません。
事象としてはboxにAPIリクエストを投げた後、何も戻ってこないのにプログラムが終了してしまいます。httpレスポンスすら帰ってこずに。
それも終了する場所がまちまちなのが始末が悪い。複雑なPromiseの構造に加え、referredを混在させたことにも問題があったのかもしれません。
この不具合がやっかいなのは、boxからのレスポンスを待つ間、app.record.create.submit.successの結果が完了できないことにあります。その間、ブラウザーは固まってしまい、kintoneを利用されている皆様にはただ困惑が。

そして、この不具合の原因がブラウザーにあるのか、box側にあるのか、kintone内部にあるのか、それともPCのスペックにあるのか。はたまたネットワーク環境によるものなのか。いまだに分っていません。
ただ、ブラウザー上でレスポンスを待つ運用はまずい、という悔いだけは骨身に沁みました。
私はその原因を追究するよりもお客様の運用を円滑に進めることを優先しました。
その決断として、ブラウザーに依存する実装を止めました。

AWSへ処理を移管

  Topへ↑

では、どうすればよいか。

幸いなことにkintoneにはWebhookという機能が備わっています。Webhookには、レコード保存時にWebhookのリクエストを受け付けてくれるWebhook URLを設定できます。

私がWebhook URLとして設定したのはAWSのAPI Gatewayで設定したURLでした。
API Gatewayについての説明は割愛しますが、kintoneから受け取ったWebhookのリクエストに含まれるJSONを読み取り、それを後続の処理に渡すことができます。
後続の処理にはAWS Lambdaを選びましたので、同じAWS上で処理が連携できます。

AWS LambdaではNode.jsを使い、ほぼkintoneのkintone.app.create.submit.successで実装したのに近いコーディングを行いました。
box Node SDKがAWS Lambdaから簡単に使用できるので、それを使えば似たような実装ができるのです。
ただし、boxのアプリは一から作る必要があります。設定もあれこれ行う必要が生じました。
最初、こちらのブログの力も借りました。ありがとうございました。
Lambda関数のコードを以下に掲示します。なお、このコードは動いているものを基にいろいろといじっているので参考になると思います。

/**
 * This sample demonstrates how to call Box APIs from a Lambda function using the Box Node SDK.
 *
 * For step-by-step instructions on how to create and authorize a Box application,
 * see https://github.com/box/samples/tree/master/box-node-lambda-sample.
 */
const BoxSDK = require('box-node-sdk');                                                // Node.jsのbox-node-sdkモジュールを呼び出す
const request = require('request');                                                    // Node.jsのrequestモジュールを呼び出す
const boxConfig = JSON.parse(process.env.BOX_CONFIG);                                  // AWS Lambdaの環境変数のBOX_CONFIGの値をJSONで扱えるように

boxConfig.boxAppSettings.appAuth.keyID = boxConfig.boxAppSettings.appAuth.publicKeyID; // 9行目で取り出したkeyIDにpublicKeyIDを代入

const sdk = new BoxSDK(boxConfig.boxAppSettings);                                      // 9行目で取り出したboxAppSettingsをsdkに代入

/**
 * Create a service account client that performs actions in the context of the specified
 * enterprise.  The app has a unique service account in each enterprise that authorizes the app.
 * The service account contains any app-specific content for that enterprise.
 * Depending on the scopes selected, it can also create and manage app users or managed users
 * in that enterprise.
 *
 * The client will automatically create and refresh the service account access token, as needed.
 */
const client = sdk.getAppAuthClient('enterprise', boxConfig.enterpriseID);             // boxアプリが適用できるアカウントのグローバル設定を管理

var DOMAIN = '*********.cybozu.com'; //kintone環境のドメイン                            // *****はご使用のkintoneのサブドメインを
var APP_ID_1287 = 1287;   //案件管理アプリのアプリID
var BASE_URL = "https://" + DOMAIN + '/k/v1/';
var APITOKEN_1287 =  "kintoonkaramottekitatookunwokokoniiretene";                      // kintoneの案件アプリのAPIトークン
var headers_1287 = {'X-Cybozu-API-Token': APITOKEN_1287};                              // リクエストで使用するヘッダ
var FolderId;
var updaterecordid_1287;

exports.handler = (event, context, callback) => {                                      // eventはkintoneのWebhookからAPI Gatewayを経由したレコード情報
                                                                                       // contextはLambda関数に関する情報
    const API_BASE_PATH = 'https://api.box.com/2.0';                                   // box Node SDKの文法に準拠

    // targetnameはコンテンツの文字列
    // typeはコンテンツの対象。本稿の場合はfolder
    // content_typesは検索対象とするプロパティ。本稿の場合はname
    // limitは検索結果として戻す件数。
    // idsは親フォルダーのフォルダーID
    // methodは本稿では全てGETなので使用していない
    // dataは本稿の場合検索対象(支店,担当者,案件No)のうち、担当者の場合["担当名"]で渡ってくる。
    function searchFolder(targetname, type, content_types, limit, ids, method, data, success, error) {  //157,159,161行目から呼び出されて検索処理を実施
        if (data !== undefined) {                                 // dataが指定されている場合
            if (data[0] === "担当名") {                            // dataの配列の最初の要素が"担当名"の場合
                targetname = targetname.split(' ')[0];           // 受け取るtargetnameは「長井 権兵衛」の様に全角スペースで区切られた姓名なので姓を取得
            }
        }

        return new Promise(function (resolve, reject) {           // Promiseを設定
            client.search.query(                                  // 24行目でclientとして承認されたbox Node SDKのsearchクラスのquery関数を呼び出し
                "\"" + targetname + "\"",                         // 最初のパラメーターは検索対象文字列。文字列なのでエスケープした""で囲む。でも曖昧検索
                {                                                 
                    fields: 'id,name,modified_at,extension,permissions,collections',  //検索結果として返すコンテンツのプロパティ
                    type: type,                                                       //folder
                    content_types: content_types,                                     //検索対象はnameプロパティ
                    limit: limit,                                                     //結果として返す件数
                    ancestor_folder_ids: ids,                                         //親フォルダーID 
                    offset: 0                                                         //オフセットしないので0
                })
                .then(function(results){                                              //結果が取得されたのでこのPromiseチェーンへ
                    if (data !== undefined) {                                         //dataが指定されている場合
                        if (data[0] === "担当名") {                                   //dataの配列の最初の要素が"担当名"の場合
                            for (var i = 0; i < results.entries.length; i++) {        //戻り値の件数分(limitで指定した件数分)
                                if (results.entries[i].name === data[1]) {            //戻り値のnameプロパティがdataの2番目の要素(担当名)か
                                    resolve(results.entries[i].id);                   //Promiseは完了したと戻り値のidプロパティ(フォルダーID)を返す
                                }
                            }
                        }
                    } else {
                        resolve(results.entries[0].id);                               //Promiseは完了したと戻り値のidプロパティ(フォルダーID)を返す
                    }
                })
                .catch(function(error){ // エラーの場合
                    reject(error);
                });
        });
    }

    // createParamは生成フォルダー名と親フォルダーIDが含まれたJSONオブジェクト
    function postFolder(createParam) {                     //176行目から呼び出されてフォルダー生成処理を実施
        return new Promise(function (resolve, reject) {    // Promiseを設定
            client.folders.create(createParam.parent.id, createParam.name)  //24行目でclientとして承認されたbox Node SDKのfolderクラスのcreate関数を呼出
                                                                            //1つ目は親フォルダーID、2つ目は生成するフォルダーの名称 
                .then(function(results){                                    //85行目の処理が成功したのでこのPromiseチェーンへ
                    var ankenid = results.id;                               //生成したフォルダーIDを以下のforeach内で使うためにankenidに代入 
                    var subfolders = [                                      //生成した案件フォルダーの配下に作成する三つのフォルダー名を配列にしています
                        "提案資料",
                        "施工状況",
                        "湛水状況"
                    ];

                    var promiseset = [];                                    //三つのフォルダーの生成が終わるまで待つPromiseを三つ作るので配列を設定
                    subfolders.forEach(function(val,index,ar){              //89行目で生成した配列の各要素をループします
                        promiseset[index] = new Promise( function( resolve, reject ) {  //95行目で生成した配列にPromiseを設定します。
                            client.folders.create(ankenid, val)             //24行目でclientとして承認されたbox Node SDKのfolderクラスのcreate関数を呼出 
                                                                            //1つ目は親フォルダーID(案件フォルダー)、2つ目は生成するサブフォルダーの名称
                                .then(function(results){                    //98行目の処理が成功したのでこのPromiseチェーンへ
                                    resolve(results.id);                    //97行目のPromiseは完了したと戻り値のidプロパティ(フォルダーID)を返す
                                }).catch(function(error){                   //98行目の処理が失敗したのでこのPromiseチェーンへ
                                    reject(error);                          //97行目のPromiseは失敗したとエラーオブジェクトを返す
                                });
                        });
                    });
                    Promise.all( promiseset )                               //97行目で設定した三つのPromiseが全て完了したらここに来る
                        .then( function ( message ) {
                        resolve(ankenid);                                   //84行目のPromiseは完了したと戻り値のidプロパティ(フォルダーID)を返す
                    })
                        .catch( function ( reason ) {                       //97行目で設定した三つのPromiseのどれかが失敗したらここに来る
                            console.log( reason ) ; // "失敗!!"
                        reject(false);                                      //84行目のPromiseは失敗したとエラーオブジェクトを返す
                    });
                })
                .catch(function(error){                                     //85行目の処理は失敗したらここに来る
                    reject(error);                                          //84行目のPromiseは失敗したとエラーオブジェクトを返す
                });
        });
    }

    // boxフォルダーIDは更新対象となるフォルダーID
    // createParamは更新フォルダー名と親フォルダーIDが含まれたJSONオブジェクト
    function putFolder(boxフォルダーID, updateParam) {             //165行目から呼び出されてフォルダー更新処理を実施
        return new Promise(function (resolve, reject) {         // Promiseを設定
            client.folders.update(boxフォルダーID, updateParam)    //24行目でclientとして承認されたbox Node SDKのfolderクラスのupdate関数を呼出
                                                                //1つ目は対象となるフォルダーID、2つ目は更新するフォルダー情報の含まれたJSONオブジェクト 
                .then(function(results){                        //126行目の処理が成功したのでこのPromiseチェーンへ
                    resolve(results.id);                        //125行目のPromiseは完了したと戻り値のidプロパティ(フォルダーID)を返す
                })
                .catch(function(error){                         //126行目の処理が失敗したのでこのPromiseチェーンへ
                    reject(error);                              //125行目のPromiseは失敗したとエラーオブジェクトを返す
                });
        });
    }

    function getFolderURL(createdid) {                          //178行目から呼び出されてフォルダーの共有処理を実施
        return new Promise(function (resolve, reject) {         // Promiseを設定
            client.folders.update(createdid, {shared_link: client.accessLevels.OPEN}) 
                                                                //24行目でclientとして承認されたbox Node SDKのfolderクラスのupdate関数を呼出
                                                                //1つ目は対象となるフォルダーID、2つ目は更新するフォルダーのプロパティ(共有設定)
                .then(function(results){                        //139行目の処理が成功したのでこのPromiseチェーンへ
                    resolve(results.shared_link.url);           //138行目のPromiseは完了したと戻り値の共有URLプロパティ(リンクURL)を返す
                })
                .catch(function(error){                         //139行目の処理が失敗したのでこのPromiseチェーンへ
                    reject(error);                              //138行目のPromiseは失敗したとエラーオブジェクトを返す
                });
        });
    }
    function createBoxFolder(支店, 担当者, 案件No, boxフォルダーID) {  //212行目から呼び出されてフォルダーの共有処理を実施
        return new Promise(function (resolve,reject) {             // Promiseを設定
            var rootfolder = "12345678910";                        //boxのフォルダー制御のルートとなるフォルダーのフォルダーIDを静的に代入
            var ownerbranchfolder;                                 //支店フォルダーのフォルダーID
            var personinchargefolder;                              //担当者フォルダーのフォルダーID
            var createParam;                                       //searchFolder関数へはダミーオブジェクト。putfolderとpostfolderへはJSONオブジェクト

            searchFolder(支店, "folder", "name", 10, rootfolder, 'GET', createParam).then(function (branchfolderid) {  //45行目へ
                ownerbranchfolder = branchfolderid;                           //searchFolderからの返り値を上位スコープのownerbranchfolderへ代入
                searchFolder(担当者, "folder", "name", 10, ownerbranchfolder, 'GET', ["担当名",担当者]).then(function (personfolderid) {  //45行目へ
                    personinchargefolder = personfolderid;                    //searchFolderからの返り値を上位スコープのpersoninchargefolderへ代入
                    searchFolder(案件No, "folder", "name", 10, personinchargefolder, 'GET', createParam).then(function (projectfolderid) {  //45行目へ
                        var name = "案件No" + " " + 案件No;                    //生成/更新する案件フォルダーの名称を設定する
                        createParam = {name: name, parent: {id: personinchargefolder}};  //案件フォルダーの設定情報をJSONオブジェクトに組み立てる
                        if (projectfolderid.length > 0) {                     //161行目で案件フォルダーが存在した場合(フォルダー情報更新)
                            putFolder(projectfolderid, createParam).then(function (updatedid) {  //124行目へ
                                FolderId = updatedid;                         //161行目の処理で得た更新したフォルダーIDをスコープ外の168行で使うため
                                getFolderURL(updatedid).then(function (updatedurl) {             //137行目へ
                                    resolve(FolderId+"****"+updatedurl);      //151行目のPromise完了をフォルダーIDと共有URLプロパティ(リンクURL)で返す
                                }).catch(function(error){                     //167行目の処理が失敗したのでこのPromiseチェーンへ
                                    reject(error);                            //151行目のPromiseは失敗したとエラーオブジェクトを返す
                                });
                            }).catch(function(error){                         //165行目の処理が失敗したのでこのPromiseチェーンへ
                                reject(error);                                //151行目のPromiseは失敗したとエラーオブジェクトを返す
                            });
                        } else {
                            postFolder(createParam).then(function (createdid) {       //83行目へ
                                FolderId = createdid;                                 //生成したフォルダーIDを以下の179行目で使うためにFolderIdに代入
                                getFolderURL(createdid).then(function (createdurl) {  //137行目へ
                                    resolve(FolderId+"****"+createdurl);      //151行目のPromise完了をフォルダーIDと共有URLプロパティ(リンクURL)で返す
                                }).catch(function(error){                             //178行目の処理が失敗したのでこのPromiseチェーンへ
                                    reject(error);                                    //151行目のPromiseは失敗したとエラーオブジェクトを返す
                                });
                            }, function(res) {                                        //176行目のフォルダー生成処理でrejectレスポンスが返った場合
                                if (res.status && res.status === 409) {               //176行目のフォルダー生成処理でrejectレスポンスが409返った場合
                                    if (res.context_info                              //176行目のフォルダー生成処理でrejectレスポンスが競合を示した場合
                                        && res.context_info.conflicts
                                        && res.context_info.conflicts.length > 0) {
                                        return;                                       //150行目のcreateBoxFolder関数を終える
                                    }
                                }
                            }).catch(function(error){                                 //176行目のフォルダー生成処理でエラーが帰った場合
                                reject(error);                                        //151行目のPromiseは失敗したとエラーオブジェクトを返す
                            });
                        }
                    }).catch(function (error) {                                       //161行目のフォルダー検索処理でエラーが帰った場合
                        // 非同期処理失敗。呼ばれない
                        console.log(error);
                    });
                }).catch(function (error) {                                           //159行目のフォルダー検索処理でエラーが帰った場合
                    // 非同期処理失敗。呼ばれない
                    console.log(error);
                });
            }).catch(function (error) {                                               //157行目のフォルダー検索処理でエラーが帰った場合
                // 非同期処理失敗。呼ばれない
                console.log(error);
            });
        });
    }

    var recordjson = JSON.parse(event.body);                                 //34行目で受け取ったkintoneのWebhookのレコード情報をJSON形式で扱えるように
    updaterecordid_1287 = recordjson.record.レコード番号.value;               //210行目のレコードデータの「レコード番号」フィールドの値を代入
    createBoxFolder(recordjson.record.支店.value[0].code,                    //150行目へ
                    recordjson.record.担当者.value, 
                    recordjson.record.案件No.value, 
                    recordjson.record.boxフォルダーID.value).then(function(idurl) {
        if (idurl) {                               //212行目のcreateBoxFolderの戻り値(168、179行目で値設定)
            var targetrecordids = [updaterecordid_1287+"**"+APP_ID_1287];   //211行目で設定したレコード番号と27行目で設定したアプリID
            var kintonepromiseset = [];                                     //kintoneのレコードアップデートが終わるまで待つPromiseの配列を設定
            targetrecordids.forEach(function(val,index,ar){                 //217行目で生成した配列の各要素(本稿では1つ)をループします
                kintonepromiseset[index] = new Promise( function( resolve, reject ) {  //218行目で生成した配列にPromiseを設定します。
                    var body_post = {                                                  //kintoneの既存案件アプリを更新するレコードを組み立てます。
                        app: val.split("**")[1],                                       //217行目で設定した配列の**で区切られた右側(アプリID)
                        id: val.split("**")[0],                                        //217行目で設定した配列の**で区切られた左側(レコード番号)
                        record: {
                            boxフォルダーID: {
                                value: idurl.split("****")[0]                       //212行目のcreateBoxFolderの戻り値の****で区切られた左のフォルダーID
                            },
                            表示: {
                                value: idurl.split("****")[1].replace("*******.box.com","app.box.com")
                                         //212行目のcreateBoxFolderの戻り値の****で区切られた右のURL(契約のboxのサブドメインをapp.box.comに置換の必要あり)
                            }
                        }
                    };
                    var options_getsalesamount = {                            //リクエストのbody部分を組み立てます。
                        url: BASE_URL + 'record.json',                        //28行目で設定したURLのルートと一行レコードの更新なのでrecord.jsonを連結
                        method: 'PUT',                                        //更新なのでPUT
                        headers: headers_1287,                                //30行目で設定したAPIトークン
                        'Content-Type': 'application/json',                   //リクエストのボディ部分のタイプ
                        json: body_post                                       //221行目で設定したボディ部分
                    }
                    //レコードを取得
                    request(options_getsalesamount, function (error, response, body) {    //Node.jsのrequestモジュールで234行のリクエストを送信
                        if (error) {                                                      //242行目の値がerrorだったら
                            console.log('Error: ' + error.message);
                            reject();                                                     //220行目のPromiseは失敗したとエラーオブジェクトを返す
                        }
                        console.log("kintone recordput:succcess"+val);
                        resolve();                                                        //220行目のPromise完了を返す
                    });
                });
            });
            Promise.all( kintonepromiseset )                                              //220行目で設定したPromiseが全て完了したらここに来る(本稿は1つ)
                .then( function ( message ) {                                             //252行目の処理が成功したのでこのPromiseチェーンへ
                    context.done(null, {text: "kintone POST and Box Folder Create success!"});  //Lambdaの処理結果をログとして残す
            })
                .catch( function ( reason ) {
                    context.done(null, {text: "Box Folder Create failed!"});              //Lambdaの処理結果としてエラーログ
                return;
            });
        } else {
            context.done(null, {text: "Box Folder Create failed!"});                      //Lambdaの処理結果としてエラーログ
        }
    }, function(res) {                                                                    //212行目の返り値がrejectで戻ってきた場合
        context.done(null, {text: "Box Folder Create failed!"});                          //Lambdaの処理結果としてエラーログ
        return false;                                                                     //212行目の結果としてfalseを返す
    });
};

 

なんとか実装

  Topへ↑

いずれにせよ、私が2019年に出した唯一の大きなバグがこれでした。
結局、バグが出てから実運用にこぎつけるまでにさらに二カ月ほどの時間をいただきました。お客様にも多大なご迷惑をおかけしてしまいました。

これが実装できたことで、案件アプリにレコードを登録した時点で、Webhookが発動し、AWS API GatewayからAWS Lambdaを介してboxへのフォルダー生成と、レコードに対応するboxのURLとフォルダー番号をkintoneの該当レコードに登録することができました。

kintoneの画面上にboxのフォルダーを出す部分はbox for kintoneの内部にも書かれている通りです。
実際それを使わせていただいています。ありがとうございます。
以下にコードを載せていますが、疲れてきたのでコード内のコメントは割愛します。ごめんなさい。

(function() {
    'use strict';

    var BOX_CLIENT_ID = 'wkgp4k64whsha8mwvg7k5k63cim82mmv';   //sample_plugin_default
    // localStorage
    var LOCAL_STORAGE_PREFIX = 'kintone.plugin.' + BOX_CLIENT_ID;
    var LOCAL_STORAGE_JUDGED_ALLOW_ACCESS = LOCAL_STORAGE_PREFIX + '.judgedAllowAccess';

    var config = [];

    var BOX_EMBED_WIDTH = 840;
    var BOX_EMBED_HEIGHT = 420;

    var getUrl = function(path) {
        var matchedGuestSpacePath = location.pathname.match(/^\/k\/(guest\/\d+\/)/);
        var guestSpacePath = '';
        if (matchedGuestSpacePath !== null && matchedGuestSpacePath.length === 2) {
            guestSpacePath = matchedGuestSpacePath[1]; // "guest//"
        }
        var apiPath = '/k/' + guestSpacePath + path;
        return apiPath;
    };

    var boxApi = {
        clientInfo: {'provider': 'box', 'client': BOX_CLIENT_ID},

        getAccessToken: function() {
            // add a hash parameter for distinguishing OAuth redirect
            var delimiter = (location.hash.indexOf('#') === 0) ? '&' : '#';
            location.hash += delimiter + BOX_CLIENT_ID + '.oauth_redirect=true';
            kintone.oauth.redirectToAuthenticate(this.clientInfo, location.href);
        },
        hasAccessToken: function() {
            return kintone.oauth.hasAccessToken(boxApi.clientInfo);
        }
    };

    var validateConfig = function(record) {
        config['folderId'] = '0';//Box親フォルダーID
        config['keyFld'] = '顧客名';//kintoneキーフィールド
        config['boxUrl'] = '表示';//Box共有リンクの格納先
        config['boxFolderId'] = "boxフォルダーID";
        config['access'] = 'Open';//Box共有リンクのアクセス権[Collaborator/Company/Open]
        config['prohibitToDownload'] = 'false';//コラボレータにのみダウンロードを許可する

        if (!config) {return false; }
        return true;
    };

    var decorateBoxLinkField = function(boxUrl) {

        var boxLinkPattern = /^https:\/\/([a-zA-Z0-9]+).box.(com|net)(\/s\/[a-z0-9]+)$/;
        var match = boxUrl.match(boxLinkPattern);
        if (!match) {
            return;
        }
        var iframeSrc =
            'https://app.box.com/embed_widget/000000000000' +
            match[3] +
            '?theme=gray' +
            '&show_parent_path=no' +
            '&show_item_feed_actions=no' +
            '&partner_id=233';

        var elEmbed = kintone.app.record.getFieldElement(config.boxUrl);
        if (elEmbed === null) {return; }
        $(elEmbed).empty();

        var width = BOX_EMBED_WIDTH;
        var height = BOX_EMBED_HEIGHT;

        $(elEmbed).parent().css({
            'width': (width + 100) + 'px',
            'height': 'auto',
            'background-color': 'rgba( 255, 255, 255, 0 )'
        });
        var embedIframe = $('', {
            src: iframeSrc,
            width: width,
            height: height,
            frameborder: '0',
            allowfullscreen: 'true',
            allowscriptaccess: 'always'
        });
        $(elEmbed).append(embedIframe);
    };

    var judgedAllowAccessFlag = {
        isSet: function() {
            return (localStorage.getItem(LOCAL_STORAGE_JUDGED_ALLOW_ACCESS) !== null);
        },

        set: function() {
            localStorage.setItem(LOCAL_STORAGE_JUDGED_ALLOW_ACCESS, 'true');
        },

        remove: function() {
            localStorage.removeItem(LOCAL_STORAGE_JUDGED_ALLOW_ACCESS);
        }
    };

    kintone.events.on('app.record.detail.show', function(e) {
        if (validateConfig(e.record)) {
            var boxUrl = e.record[config.boxUrl].value;
            if (!e.record[config.boxUrl].value) {

                var elEmbed = kintone.app.record.getFieldElement(config.boxUrl);
                if (elEmbed === null) {return null; }
                $(elEmbed).empty();

            } else {
                decorateBoxLinkField(boxUrl);
            }
        }

        return e;
    });

    var checkAccessToken = function() {
        var oauth_redirect_param = BOX_CLIENT_ID + '.oauth_redirect=true';
        if (location.hash.indexOf(oauth_redirect_param) !== -1) {
            judgedAllowAccessFlag.set();

            // remove a hash parameter
            location.hash = location.hash.replace(oauth_redirect_param, '');

            var t = setInterval(function() {
                if (location.hash.indexOf(oauth_redirect_param) !== -1) {
                    // cancel button was clicked
                    clearInterval(t);
                    location.href = getUrl(kintone.app.getId() + '/');
                }
            }, 500);
        } else if (!judgedAllowAccessFlag.isSet() || !boxApi.hasAccessToken()) {
            kintone.oauth.clearAccessToken(boxApi.clientInfo, function(body, status, headers) {
                boxApi.getAccessToken();
                return null;
            });
        }
    };

    kintone.events.on('app.record.create.show', function(e) {
        if (validateConfig(e.record)) {
            checkAccessToken();
            e.record[config.boxUrl]['disabled'] = true;
            e.record[config.boxFolderId]['disabled'] = true;
        }

        return e;
    });

    kintone.events.on('app.record.edit.show', function(e) {
        if (validateConfig(e.record)) {
            if (!e.record[config.boxUrl].value) {
                checkAccessToken();
            } else {
//                e.record[config.keyFld]['disabled'] = true;
                e.record[config.boxFolderId]['disabled'] = true;
            }
            e.record[config.boxUrl]['disabled'] = true;
        }
        return e;
    });

    kintone.events.on('app.record.index.edit.show', function(e) {
        if (validateConfig(e.record)) {
            e.record[config.boxUrl]['disabled'] = true;
            e.record[config.keyFld]['disabled'] = true;
            e.record[config.boxFolderId]['disabled'] = true;
        }
        return e;
    });
})();

 

 

まとめ

  Topへ↑

実案件ではさらに凝った実装(フォルダー数も階層も本稿の例よりさらに多い)が施されています。
そして、古くboxが設定されていないレコードには手作業がたまに発生しているものの、実運用に乗っています。
この記事ではそれ以上の情報を出すことはお客様の業務に関わるのでここまでにしとうございます。

本稿がkintoneを運用している皆様にとって少しの手助けになれば幸せです。

kintone上で大量の添付ファイルに困っていらっしゃる方や、社内ファイルサーバーからの移行でお困りの方。他のPaaSからkintoneへ移行する作業があって、添付ファイルの扱いにお困りの方。
弊社では本稿のようなboxとkintoneの連動事例を何例も手掛けております。お困りの際はおっしゃってくださいませ。

最後に蛇足ですが、boxの案件で例に挙げた三つのダムは、私が実際に訪れてダムカードを入手した場所です。


当エントリーの参考にさせていただいたブログ

  Topへ↑

最後になりましたが、このエントリー作成にあたり、以下の2サイトからの情報を参考にさせていただきました。ありがとうございました。

 box APIレファレンス
 AWS Lambda上でBox Node SDKを利用する-九龍堂雑録


コーチングのグラフってkintoneで出せるんやって!


kintone2 Advent Calendar 2019の5日目の記事です。

  Topへ↓

突然ですが皆さん、コーチングって聞いたことがありますか?
あっ! そこのあなた、ページはそのままに!
これは間違いなくkintone Advent Calendarの記事ですから。
ほら!

きとみちゃん楽しいですよね!
https://kintone.cybozu.co.jp/jp/kitomi/

日々、お仕事に励むきとみちゃん。
きとみちゃんとお仕事をする仲間はとっても個性が豊か。

ちょっぴりあわてんぼうでドジっ子のきとみちゃんがkintoneに救われる姿は微笑ましいです。
ちなみに私は巻物で見積書を出してくださる麻呂な方が好きです。この方のお名前はなんでおじゃる?

さて、きとみちゃんがお仕事をする上で助けになる手法はkintoneの他にもさまざまなものがあります。

その中の一つが冒頭に書いたコーチングなのです。

コーチングを一言で言い表すなら、
・相手の学習や成長、変化を促し、相手の潜在能力を解放させ、最大限に力を発揮させる。
でしょうか。

詳しくはWikipediaの「コーチング」
をご覧くださいませ。

ビジネスにフォーカスを当てたコーチングの歴史はまだまだ浅いです。
ここでお伝えしておかなければならないのは、自己啓発セミナーとは違う、ということです。

と、偉そうにウンチクを述べる私ですが、コーチングを受けた経験は人生で1,2回だけ。
では、そんな私がkintone Advent Calendarで何を語るというのでしょうか。

結論を先に書いちゃうと、kintoneでこんなグラフを作ってみましょう!
ということなんですね。

グラフとデータのご説明

  Topへ↑

上に登場したのは四つの傾向を円グラフにしたものです。
それぞれの傾向の文字列にマウスを合わせると、事前に登録しておいたキーワードが出てくる。
これ、実は以前、お客様に依頼されて作ったkintoneにChart.jsを組み込んだグラフ生成の仕掛けです。

私はコーチングには無知です。
ですから、kintoneに入力画面を作り、その結果を集計することで、設問に応じた四つの傾向が算出できる、ということを知ったときは新鮮でした。

お客様によれば、
相手をほめる場合の、個人に響くキーワードは4つの傾向に分けられる
だそうです。

それに合わせて、こんな入力画面を作ってみました。

仮に20問の設問としています。それぞれの4つの傾向ごとに5問を設問しました。
それぞれの問いごとに
・よく当てはまる
・当てはまる
・当てはまらない
・まったく当てはまらない
の4種類の答えをラジオボタンで設定しています。

もちろん、さらに設問数を増やすことも可能ですし、設問数を自在に増減させたい、というご要望もあるでしょうね。
その場合はサブテーブルを使えばよさそうです。
この記事ではサブテーブルではなく、20問に固定したバージョンでお届けしてみます!

実際の内容

  Topへ↑

はい。ではアプリの設定画面です。フォームはこんな感じ。

一番左の文字列フィールドは設問の文字列を入力します。
フィールドコードは上から順にquestion_1からquestion_20としています。

真ん中のドロップダウンフィールドは4つの傾向を選びます。
フィールドコードは上から順にtrend_1からtrend_20としています。

右のラジオボタンフィールドはそれぞれの答えを入力する欄です。
フィールドコードは上から順にanswer_1からanswer_20としています。

で、続いてはグラフを表示するカスタマイズビューを設定してみましょう。

こんな感じですね。

続いてはロジックです。
実は、このグラフを作るには以下の二つのJavaScriptファイルを設定するだけ。

上に設定したのは、Chart.jsです。
Cybozu Developer Network
からCDNのページに移動してもらえれば。

そこのChart.jsに書かれているURLをコピーし、上の画面の
から

に貼って保存するだけ! きとみちゃんでもできますよね?
htttps://がダブらないようにだけ気を付けて!

続いてグラフ表示のロジックです

  Topへ↑

では続いてきとみちゃんとグラフ.jsの内容を。
ここからはVisual Studio Codeの画面にコメントを入れています。




ちょっと見にくいので、直に貼ったコードも提供します。右にスクロールしてくださいね。

(function () {
  "use strict";

  // 一覧ページ
  kintone.events.on('app.record.index.show', function(event) {                       //一覧画面表示時の定型文です
    var record = event.records[0];
    var itemcount = 20;
    var 1_Score = 0;
    var 2_Score = 0;
    var 3_Score = 0;
    var 4_Score = 0;
    var selectedScore = 0;
    var dataLabelPlugin = {                                                          //ここは以下の162行目で呼び出されるチャートのプラグインコンフィグで呼び出される部分です。
      afterDatasetsDraw: function (Chart, easing) {                                  //afterDatasetsDrawとはプラグインコアAPIとして呼び出されるChart.js内部のフックです。要は描画後です。
        var ctx = Chart.ctx;                                                         //チャートが描画されている対象のDOM要素です。157行目で定義され、159行目でChartオブジェクトに渡されます。
        Chart.data.datasets.forEach(function (dataset, i) {                          //対象チャートをループしています。データは77行目で一種類で指定していますのでループは一回のみです。               
          var meta = Chart.getDatasetMeta(i);                                        //チャートのメタデータを取得しています。データやラベルも含まれています。
          if (!meta.hidden) {                                                        //チャートのhiddenプロパティがTrueの場合そもそもチャートが描画されません。
            meta.data.forEach(function (element, index) {                            //メタ要素のデータをループします。今回は4種類ですね。
              ctx.fillStyle = 'rgb(0, 0, 0)';                                        //円グラフの中の文字の色です。rgb(0, 0, 0)は黒を表しています。
              var fontSize = 16;                                                    //36-37行目で文字の場所を設定するためのフォントサイズを16pxで設定しています。表示フォントのサイズとは別に。
              ctx.font = "24px \"Helvetica Neue\", Helvetica, Arial, sans-serif";    //これが実際に描画される文字のフォント情報です

              var sum  = function(arr) {                                             //ここでは対象となるデータの合計値を返します。4種類のデータの合計です。
                  return arr.reduce(function(prev, current, i, arr) {
                      return prev+current;
                  });
              };
              var percentString = ((dataset.data[index] / sum(dataset.data))*100).toFixed(1) + "%";  //それぞれのデータの値を全体の合計で割り、パーセントの文字列を構築します。
              var dataString = Chart.data.labels[index];                                             //それぞれのデータのラベルです。79行目で定義した4つの傾向のラベルですね。 
              ctx.textAlign = 'center';
              ctx.textBaseline = 'middle';

              var padding = 5;
              var position = element.tooltipPosition();
              ctx.fillText(dataString, position.x, position.y - (fontSize / 2) - padding);           //30行目で設定したラベルの値を計算した位置に表示します。
              ctx.fillText(percentString, position.x, position.y - (fontSize / 2) - padding + 35);   //29行目で設定した値のパーセントの文字列を計算した位置に表示します。
            });
          }
        });
      }
    };
    for (var i=1 ; i<=itemcount ; i++){                                                              //ここから76行目までは大人の事情でいろいろとあいまいですがお許しを
      switch( record['answer_' + i]['value'] ) {                                                     //要するに20レコードの設問の答えを基に四つの傾向に加算しているのです
        case 'よく当てはまる':
          selectedScore = 係数は内緒よ♪;
          break;
        case '当てはまる':
          selectedScore = 係数は内緒よ♪;
          break;
        case '当てはまらない':
          selectedScore = 係数は内緒よ♪;
          break;
        case 'まったく当てはまらない':
          selectedScore = 係数は内緒よ♪;
          break;
      }
      switch( record['trend_' + i]['value'] ) {
        case '一つ目の傾向':
          1_Score = 1_Score + selectedScore + 山藤ゆりさんに教えてもらった魔法の値を加えるの♪;           //要するに20レコードの設問の答えを基に四つの傾向に重みづけしているのです
          break;
        case '二つ目の傾向':
          2_Score = 2_Score + selectedScore + 山藤ゆりさんに教えてもらった魔法の値を加えるの♪;
          break;
        case '三つ目の傾向':
          3_Score = 3_Score + selectedScore + 山藤ゆりさんに教えてもらった魔法の値を加えるの♪;
          break;
        case '四つ目の傾向':
          4_Score = 4_Score + selectedScore + 山藤ゆりさんに教えてもらった魔法の値を加えるの♪;
          break;
      }
    }
    1_Score = ロジック関数は内緒よ♪(1_Score);
    2_Score = ロジック関数は内緒よ♪(2_Score);
    3_Score = ロジック関数は内緒よ♪(3_Score);
    4_Score = ロジック関数は内緒よ♪(4_Score);                                          //さらに四つの傾向に値を秘密ロジックで精緻化しています。この辺も大人の事情が絡んでいます。
    var pieChartData = {                                                             //161行目でChartオブジェクトに渡されるデータとラベルと背景色のホバー色や枠の組み合わせです。四要素です。   
      labels : ["リーダー合理系","アイディア活動系","ヘルプ支援系","クール分析系"],       //ラベルですね。四つの要素に分かれています。
      datasets : [                                                                   //四つの要素のそれぞれの色の指定です。
        {
          backgroundColor: [
            '#ff6384',
            '#36a2eb',
            '#cc65fe',
            '#ffce56'
          ],
          hoverBackgroundColor: [
              "#FF2384",
              "#3662EB",
              "#cc25fe",
              "#FF8E56"
          ],
          hoverBorderColor: [
              "#000000",
              "#000000",
              "#000000",
              "#000000"
          ],
          hoverBorderWidth: [
              2,
              2,
              2,
              2
          ],
          data : [1_Score,2_Score,3_Score,4_Score]                                   //四つの要素の値です。大人の事情で実際の回答から複雑に計算された結果が格納されます。
        }
      ]
    }
    var tooltipkeyword = {                                                           //ここは四つの傾向ごとに176行目で乱数を設定し、任意のキーワードを表示するようにしています。
      type : [
        {
          word : [
            '同業者もあの人を噂している',
            '他の部署でも話題になっている',
            '○○さんしかできない',
            '自分で判断し、動ける人',
            '部署のメンバーに信頼されている',
            'あのひとには任せられる'
          ],
          title : "任せる、難題、未知の分野、他に頼めない、誰にもできない"
        },
        {
          word : [
            '発想がおもしろい!!',
            '一緒にいるだけで楽しい!!',
            'さすがアイデアマン!!',
            'すばらしいサービス精神!!',
            'うちの部署のムードメーカー!!',
            'その自由な発想がうらやましい!!'
          ],
          title : "自由にして、思いっきり、楽しく、面白く、みんなでいっしょ"
        },
        {
          word : [
            'みんなが働きに感謝している',
            '縁の下の力持ち',
            '一緒にいて落ち着く',
            '丁寧で親切で信頼できる',
            '細かいところによく気が付く',
            '相手の気持ちを分かってくれる'
          ],
          title : "感謝、ありがとう、仲良く、話し合い、相手の気持ち"
        },
        {
          word : [
            '詳しく業務を理解している',
            '商品のことをよく知っている',
            'わが社のことになんでも詳しい',
            'うちの課の歩く辞書',
            'あの人に聞けば間違いない',
            'このデータ量は大したもの'
          ],
          title : "情報、正確、正しく分析、予定通り、計画通り"
        }
      ]
    }
    var canvas = document.getElementById('canvas').getContext("2d");                 //Chartが描画されるDOM要素を指定するChart.jsの定型文です。id="canvas"はカスタマイズビューで指定しました。
    canvas.canvas.height = 256;                                                      //描画される領域の高さを指定しています。
    var test_chart = new Chart(canvas, {                                             //ここでChartオブジェクトをインスタンスとして実体化させています。
      type: 'pie',                                                                   //type: 'pie'はグラフの種類ですね。円グラフです。
      data: pieChartData,                                                            //77行目で定義したデータの実態です。
      plugins: [dataLabelPlugin],                                                    //プラグインコンフィグで関数を呼び出すことができます。その関数は13行目をご参照ください。
      options: {                                                                     //ここからはオプション情報です。
        animation: {
          animateRotate: true,
          animateScale: true
        },
        tooltips: {
          titleFontSize: 48,
          bodyFontSize: 36,
          callbacks: {
            label: function (tooltipItem, data){                                     //ここは描画後にマウスカーソルが乗った時の事前に内部でtooltipItemに定義された情報を基に値を返します。
                return pieChartData["datasets"][0]['data'][tooltipItem['index']] + "ポイント"       //77行目で定義されたデータから該当するデータを表示し
                  + "  キーワード → " + tooltipkeyword["type"][tooltipItem['index']]["title"];      //さらにキーワードとして109行目で定義された四つの傾向のタイトルを表示します。
            },
            afterLabel: function (tooltipItem, data){                                //172行目のラベルの後に別の情報を表示させるにはafterLabelツールチップコールバックが呼び出せます。
                  return "「" + tooltipkeyword["type"][tooltipItem['index']]["word"][Math.floor(Math.random()*(6-0)+0)] + "」";
            }                                                                        //さらにテキストとして109行目で定義された四つの傾向の文言のオブジェクトから乱数で選ばれた文言を表示します。
          }
        },
      }
    });
  });
})();

あとはこのJavaScriptファイルを

にのようにアップロードしていただければ。

どうでしょう。kintoneのデータにChart.jsを組み合わせるだけで、
kintoneのデータを分析することができてしまうのです。

Chart.jsにはさまざまなグラフが用意されているので、
kintoneの標準グラフでは表現できないことも可能です。

コーチング用の分析ツールとしても使えてしまうkintoneの奥深さを楽しんでいただけたらきとみちゃんも喜ぶはずです!
よかったら以下にChart.jsの公式サイトのリンクも貼っているのでご参考になさってくださいね。

当エントリの参考にさせていただいたブログ

  Topへ↑

最後になりましたが、このエントリ作成にあたり、以下の2サイトおよび、コーチングについて教えて頂いたお客様からの情報を参考にさせていただきました。ありがとうございました。

 Chart.jsドキュメント翻訳
 Chart.js公式サイト


kintone Café 東京 Vol.9を開催しました


11/15にkintone Café 東京 Vol.9を開催しました。

公式の開催報告はしかるべき場所に書かせていただきました。
こちら

関連するツイートのまとめサイトも作成させていただきました。
こちら

なので、ここでは代表である私が登壇の際に語った
kintone Caféとは?スライド
kintoneを簡単にご紹介スライド
Cybozu Days 2019のkintone周りをフィードバックスライド
をさらに補足するように、開催であらためて感じた思いを書かせていただきます。

3月末のkintone Café 広島に登壇した時、今までkintone Caféで話したことのなかった自治会を取り上げました。
今までは技術に即した内容を話すことが多かったのですが、技術に触れないkintone Caféの登壇が私に新鮮でした。

8月末に開催したkintone Café 東京 Vol.8 @多摩では、プロジェクト・アスノートの松田さんと共催しました。
それを機に私の話す内容を思い切って初期化し、kintoneを一から語ってみました。当然技術ネタは封印。

技術ネタだと、kintone Caféに来てくださった方がついて来れない可能性があります。当然、反応も薄くなります。
そもそもkintoneエバンジェリストとは、技術うんぬんではなく、kintoneの良さを広めることにあるのではないか。
私の中でkintone Café神奈川を何度か行う中で迷いが生じていましたが、直近の二回のkintone Caféで修正することができました。

今回のkintone Café 東京 Vol.9は開催要項にユーザー向けをうたっていました。
その一方で、今回の参加者の中には私の知る限り、かなりのスキルを持つ技術者も6,7名はいました。

そうした方々に対し、kintoneの初歩を語ることに意味はあるのか。
私はあると判断しました。
むしろ、技術者であるほど、kintoneが新鮮に映るはず。そうした意味でもユーザー向けの内容でよかったと思います。

今回、会場を提供してくださったのはクロス・ヘッド株式会社様。System Integrateの豊富な経験をお持ちです。
クロス・ヘッド様の会場をお借りしながら、技術に触れず、ユーザー向けの内容にすることに若干のためらいもありました。
ですが、kintone Caféを通しての皆様の反応は上々で、七割以上の方が懇親会に参加してくださいました。その事からも、ユーザー寄りで行く、との方向性は続けようと思いました。

私の登壇では、そもそもなぜkintoneをユーザーに勧めるのか、という観点で一生懸命語ってみたつもりです。
さらに、Cybozu Days 2019 in 東京で発表された内容を報告しました。

今回、一緒に登壇したkintone大好きキンスキ YouTuberの松井さんは、私のPCトラブルによる順番交代にも動じず、見事な登壇を務めてくださいました。そればかりかサイボウズさんならではの事例を提供してくださいました。

私に続いて登壇してくださった情報親方の東野さんは、Cybozu Days 2019で発表され、来場された方々に感心されたkintone導入ガイドブックの制作について、マニュアル制作のノウハウも惜しげなく披露してくださいました。

トリを務めてくださったTeruさんは、登壇を公開できないリスクを押してkintoneが最大に活きる業務改善の生の事例を語ってくださいました。kintoneの紹介から導入、そして業務改善効果に至るまで、今回のCaféを締めるにふさわしい内容でした。

あらためて、こうした地道な活動が、今後につながると確信できた1日でした。
12/7にはkintone Café JAPAN(サイト)が予定されています。そこでもきっと実のある内容が得られる事でしょう。
今回来てくださった35+αの皆様、登壇してくださった3名の仲間。会場を提供してくださったクロス・ヘッド株式会社様。皆さま本当にありがとうございました。


Cybozu Days 2019 in 東京に行きました


2019/11/7、11/8の幕張はある気配に包まれていました。
そこではCybozu Days 2019 in 東京が催されていたのです。
ただし、幕張に満ちていた気配とは、熱気ではなく禍々しい気でした。

今年のテーマは「モンスターへの挑戦状」
そのテーマ通り、モンスターたちに占拠されたパビリオン。会場全体が禍々しく彩られています。
その中にはちらほらと勇者のイラストも見受けられます。ですが、空間の大部分はモンスターに占められたまま。

そんな会場にも、参加者が増えていくにつれモンスターに立ち向かう機運が満ちていきます。
勇者とはすべての参加者のこと。皆でモンスターに立ち向かうのが今年のCybozu Daysのミッションなのです。

では、私たちが立ち向かうモンスターとは何でしょうか。
そのヒントは既にサイトにも動画で流されていました。そして、二日目のメインセッションでも青野社長から語られました。
モンスターとは目に見えません。そもそも存在すらしません。
モンスターとは私たち自身が作り上げてしまった存在なのです。

戦後の高度経済成長の結果、成功体験として日本中に刷り込まれてしまった観念。その代表が年功序列や終身雇用です。
そうした観念から派生したモンスターたち。
毎日決まった場所に通勤し、背広とネクタイの着用が正しいとする「通念」が蔓延り、その背後には一度きりの人生を「カイシャ」に捧げさせようと蠢くモンスターの闇が見え隠れします。

そうしたもろもろのモンスターたちと一緒に戦う私たちのために、七カ所に分かれたセッション会場のあちこちで、二日間にわたっていろいろなセッションが行われました。

初日のProduct Keynoteでは、GAROON、kintoneを軸に、これまでのアップデートと今後のアップデート予定が披露されました。
チーム応援ライセンスは対象アカウントが三倍に増え、危うく私の口から歓声が飛び出そうに。
LGWAN対応やアメリカでのkintone状況など、今後に期待が持てそうな発表が続きます。

keynoteの前には「俺キン」のセッションがありました。
俺はこんな風にモンスターと戦ってきた、と発表する勇者たちの姿は尊いです。私も場外から戦いの軌跡に聞き惚れました。
モンスターとの戦い方は勇者によってまちまち。あえてKINTONEに乗って登場する掟破りの登場もまたよし。

Keynoteの次は、kintone hiveです。
毎年聞いていますが、全国で知らぬうちに育ってしまったモンスターたちと戦う姿には、共感と感動があります。
例えるなら上出来のRPGのプレイ動画を大勢の勇者とともに観戦する臨場感のような。

私はhiveの前、各社のブースをダッシュで巡りました。
中でもfreee社のブースに長居していました。freee社で今度kintoneとfreeeのAPI連携についてお話しするためです。
その情報を入手し、freee社のみなさんと会話をして勉強。今後にもつなげていくためにも。

hiveの後は、kintone Café in Cybozu Daysにも参加しました。
Cybozu Daysが終わってすぐ、お膝元でのkintone Café 東京 Vol.9を主宰する私としては、全国各地にある勇者の集うギルドのあり方から学べるもの多かったこと。
kintone Café開催の意義や目的を考えるきっかけにもなりました。

このセッションと同じ時間には開発者向けのセッションがありました。
私はとても行きたかったけれど、kintone Caféのセッションを選びました。
まだまだ私もか弱い勇者なので、分身の術が会得できておらず悔しい。いずれ公開される動画を待ってみます。
なお、このセッションから冒険仲間に情報親方が加わってくれました。以後は行動をともに。

続いてはkintone hack Nightです。このセッションも見逃せません。
勇者として最前線に出てモンスターと戦うには、スキルを磨かねば。それを披露する場こそhack Night。
勇者の出で立ちに身を包んだ六名の勇者がおのおのの磨き上げたスキルを披露します。そして採点。
採点の間、魔の司会者ウッシーが召喚したモンスターが登場し、勇者にさらなる試練を与えます。あるモンスターは18世紀二ポーンの「コウメダユウ」に姿をかえ、華麗なる滑り芸を駆使して勇者に襲いかかります。あるいは21世紀の「ワタナベケン」がレスラーの出で立ちで絶叫して勇者の行く手を遮るのです。
勇者、危うし!
奮闘もむなしく、魔法に魅入られ笑わされた勇者には激烈なゴムパッチンが顔面に炸裂。容赦なくHPを削っていきます。

毎年、勇者の激闘から学ぶところは多いです。
私的には、AR世界の振る舞いをkintoneに結びつけた勇者「ウィル」さんのセッションがインパクト大でした。
が、優勝はヲタ芸とkintoneの組み合わせで満場の笑いをさらった勇者「つっきー」さんの逆転勝利。いや、楽しかった!

一日目『最軽量のマネジメント』を出版されたばかりのサイボウズ社副社長の山田さんがサイボウズ商店の店頭に立たれていたので、著書を購入し一緒に写真を撮っていただきました。
そして、情報親方と飯を食って締め。
ルーラを唱え、二時間以上かけて町田の宿屋に戻りまして。

さて、二日目。
いきなりの「SEKAI NO OWARI」の曲にバイオリンをフィーチャーしたバンドが登場し、素晴らしい二日目を予感させます。

二日目は、働き方をメインに据えたセッションが主です。
メインセッションでは青野社長の書籍「会社というモンスターが、僕たちを不幸にしているのかもしれない。」を編集したphp研究所の方と青野社長の対談から始まりました。
今回のCybozu Days 2019のテーマ「モンスターへの挑戦状」のもとになったというこの書を、私はまだ読めていません。ですが、この対談を聞くと、早く読まねば、という気にさせられます。
私が常々感じていた日本を覆う思い込みの数々。

タカマツナナさんとの対談も参考になりました。
お笑い芸人として活動する中、活動を若年層への政治参加を促すものに変えるなど、自在に活動を行うタカマツさんの意気込みが面白かったです。
タカマツさんもまた、芸人の立場から世の中のモンスターと戦っているのでしょう。

そのタカマツナナさんの後に登場したのが麹町中学校の工藤校長との対談です。
工藤校長は、宿題や委員会など学校生活につき物のさまざまな制度に抜本的な改革を加えたことでよく知られています。
一人一人の生徒のことを考えた結果、そうした制度や行事で必要のないものは廃止も辞さない。そうした伝統的に伝わる制度の多くは、実は前例主義に基づいた思い込みのモンスターの生み出した幻想にすぎない。そう力強く言い切る工藤校長こそ、教育界にさっそうと現れた勇者の中の勇者なのかもしれません。

この日の私は、作業もしなければならず、昨日以上にブース巡りができませんでした。用意してくださった電源席で、作業に没頭。

続いてのセッションはサイボウズ社に中途入社した社員が語る、サイボウズ社内の情報共有のあり方です。
私もお客様とともにモンスター退治の旅に出る時、早い時期にこのモンスターと戦います。
そもそも、情報共有に妙なブロックがあると、kintoneという武器もうまく機能しません。
どこまでの情報をkintoneにアップしてもらえるのかによって導入の成否も分かれるのではないか。そう思います。

その視点でみたこのセッションはまさに驚きです。
社内経営会議の生の声まで含めて全社員に公開するというのですからすごい。
ですが、私も妙な秘密主義は面倒なので、この点はとても勇気づけられます。
また、経営者の方にもサイボウズさんはここまでやってますよ、という根拠を得られたことも大きかった。

続いてのセッションは医療介護業界の四天王がパネリストとなって、kintone導入のあれこれを語ってくださるセッションです。
四天王の内、三人はCybozu Days 2019の前日にkintone エバンジェリストに就任した方で、まさに豪華な陣容です。
実際、医療業界へのクラウド導入は、クラウドの認知のバロメーターです。
妻が歯科診療所を経営している私にとっても、とても興味深いセッションでした。

その次のセッションも、クラウドの認知のバロメーターの一つです。
自治体への導入にあたっては、LGWANとの連携がセットでないと情報の流通が滞ります。
神戸市と市川市の事例ですが、かつて神戸市役所と芦屋市役所で入力オペレーターをした経験がある私にとって、神戸市の事例には興奮しました。神戸市が主宰するワークショップには芦屋市の方も参加されているとか。
私の来し方を考えると感慨が湧きます。この分野でも今後は協力をしていければうれしいと思いました。
「来なくてもいい市役所」の実現に向けて。

そしてすでに最終セッションへ。
その前に、アーセスさんのブースに立ち寄りました。
お客様がチーム応援ライセンスに申請していて、申請が通ればアーセスさんのプラグインを使う提案もお客様にしていました。なので、KOUTEIやKOYOMIについてあらためてお話を聞きに伺ったのです。

最終セッションは『五体不満足』で知られる乙武氏の講演。そして青野社長との対談です。
乙武氏もまた、世に跋扈するモンスターと戦い続けてきた勇者です。とにかく目の力が強く、意思のみなぎった面構え。表情は豊かに、雄弁に語ってくださいます。不満足な五体で表現できない分を言葉と顔と喉だけで。
私も話者の端くれとしてとても刺激を受けました。スピーカーたるものこうありたいものです。
美しいものは、と問われた乙武氏が「覚悟」と答えた姿が、まさに美しい。モンスターと戦い続けるには覚悟がいります。その覚悟こそ、勇者のまとうべき究極の鎧なのかもしれません。
著書『四肢奮迅』を出されたばかりだというので、私も手に取ってみようと思いました。

冒険の旅に終わりはありません。
とはいえ、四人の勇者と幕張で飲みかわし、いったん私にとってのCybozu Days 2019 in 東京は幕を閉じました。
三日続けて幕張と町田を往復したのでHPとMPはすっからかん。でも、その替わりKP(気力)は満タンに充填されました。

初日の禍々しい気配からは一変、幕張の夜にはさわやかな風が吹いていました。ここ、幕張の地からモンスターは退けられたのです。

ただし、RPGの世界ではラスボスを倒してもゲームは永遠に続いていきます。私たちの人生もそうであるように。
まだまだモンスターは世に棲んでいるのです。

そうしたモンスターを退治する冒険の旅路では、仲間との交流が欠かせません。
今回、こうした英雄たちの交流の場を作っていただいたサイボウズさんには感謝です。
スタッフの皆様、登壇者の皆様、参加者の皆様、ありがとうございました。
これからもモンスターと戦い続けていきましょう!


kintone EvaCamp 2019に参加しました


令和元年11月6日。
幕張の某ホテルに代表の私はいました。
翌日から二日間にわたって開催されたCybozu Days 2019 東京を控え、kintone エバンジェリストが集うパーティーに参加するためです。

会場の窓からは、美しい光景が広がっていました。
夕日の色合いを背後に黒くそびえる富士山。
足元にはZOZOマリンスタジアムがまばゆく輝いています。
マリンスタジアムのそばには、明日からの祝祭に備え、ひっそりとうずくまる幕張メッセが。
素晴らしく印象に刻まれる光景でした。

その日の様子は
こちら
の記事にアップされています。
まさに、Cybozu Days の前日にふさわしく、意気みなぎる時間でした。
すばらしい場所と時間をご用意くださったサイボウズ社の皆様には感謝です。

サイボウズさんは、今までもCybozu Daysの前日にはこうした場をご用意してくださっていました。
そして、私も時間の許す限り参加してきました。
ところが、今回のEvaCampはその中でも特に意義あるものです。
今までは、サイボウズ公認 kintone エバンジェリストといっても、お墨付きサイトがありませんでした。
なので、私たちエバンジェリストは自分で発信するしかエバンジェリストを認知してもらうすべがなかったのです。

今回、公式サイトが設けられ、エバンジェリストが名実ともに認められたことは大きな変化といえましょう。
こちらに一覧が

2014年8月よりkintone エバンジェリストに任命された私。
すでに5年の月日が経過しました。
ところが、最初の二年間の私のエバンジェリストとしての活動は、自他ともに満足のいくものではありませんでした。

Cybozu Daysですらかろうじて一日、しかも数時間参加できる程度。
他のあらゆる展示会には参加すらできませんでした。
もちろん、新規kintone案件も数える程度。
cybozu developer networkに記事を書き、毎年年末のkintone advent calendarに書き、たまにkintone Café を開き、登壇する程度。

そんな私は、2017年の秋に常駐現場を卒業し、すでに2015年の春に果たしていた法人化もあいまって、ようやく活動できる時間を手に入れられました。

おかげさまで昨年の秋辺りからkintone案件が増え、今では社業の6~7割を占めるまでになっています。そして毎日、kintoneのことを考えています。

今回、kintone エバンジェリストとして継続するかどうか。決断を求められましたが迷う余地はありません。速攻で継続を決意しました。
出だしの二年の空白を埋めるためにも。
その間、活動の鈍い私に業を煮やしながらも見放さないでいてくれたサイボウズ社の担当者様のご厚意に報いるためにも。
そして、私自身が望むワークライフバランスのためにも。

世にいろいろなサービスはありますが、私にとってサービスの背景にある哲学に最も共感できるサービスがkintoneなのです。
このサービスを提案し、使ってもらうことで社会になにがしかの貢献ができていると思える。
それって働き方を考える上でとても大切だと思えます。特に技術者には。

まだまだ世の中には固定観念という目に見えないモンスターが跋扈しています。
それを一つ一つなくしていくために、私は求められる限り、kintoneを薦め、広めていこうと思います。


kintone Café 東京 Vol.8 @多摩を開催しました


8/30にkintone Café 東京 Vol.8 @多摩を開催しました。

公式の開催報告はしかるべき場所に書かせていただきました。(こちら

ツイートのまとめサイトも作成させていただきました。(こちら

なので、ここでは代表である私が登壇の際に語った「スライド」をさらに補足するように、開催であらためて感じた思いを書かせていただきます。

本稿を書こうとして、前回アクアビット長井として主催したkintone Caféを調べたところ、2017年3月のkintone Café 神奈川 Vol.5までさかのぼることがわかりました。つまり、前回の開催から2年半、kintone Caféを主催していませんでした。空きすぎです。間を空けすぎたことに忸怩たる思いです。

2年半の間、もちろん手をこまねいていたわけではありません。ツイートもたくさんつぶやきましたし、kintone Advent Calendarにも毎年書いています。DevRelにも参加し、弊社ブログ記事にもkintoneのことは書いています。他のkintone Caféでは登壇もしましたし、お客様の主催するセミナーでも登壇もしました。代表がエバンジェリストとして全く何もしていないとは思いません。kintone Café神奈川もなんどか話を持ち掛けては立ち消えを繰り返し、それなりの開催に向けての準備は進めました。

でも、結局は開催できなかったことに変わりありません。kintone Caféに限らず、セミナーは自らが行うべき。そう思っています。特にkintone開発が弊社の業務の主流になっている今では、一日のほとんどをkintoneの事を考えていたわけですから。これは怠慢と言われても仕方ありません。

何が原因だったか。結局、代表自身がkintone Caféのテーマについて、どう開催するか迷いが生じていたの正直なところです。技術寄りの内容で開催するのか、ユーザー寄りの内容にするのか。技術寄りにしたところで、どこまで伝わるのか。スキルは座学やハンズオンでどこまで伝えられるのか。そもそもkintoneエバンジェリストとは、技術うんぬんではなく、kintoneの良さを広めることにあるのではないか。私の中でコンセプトが右往左往していました。

今回、弊社のサテライトオフィスで開催したのは、オフィスの大家さんから開催要望があったからでした。そして要望の中で「そもそも論」を聞きたい、ということでした。つまりkintoneとはそもそも何か、という地点から話を起こす必要がありました。都合のよいことにkintone Café東京を今運営しているメンバーの松田さんはkintoneを軸とした業務改善を推進されておられます。そうした風もあり、私の登壇資料も技術のことは触れず、ユーザー向けの内容にしてみました。

今回のkintone Café 東京 Vol.8 @多摩は開催要項にユーザー向けをうたっていたこともあり、6,7名はkintoneについて触ったことがなく、そうした意味でもユーザー向けの内容でよかったと思います。私の登壇でもそもそもなぜkintoneをユーザーに勧めるのか、という観点で熱く語りました。他の方のスライドも、そうした点でテーマが絞られていたようです。

今回、久しぶりに主催してみて、ユーザー寄りで行く、との方向性は続けようと思いました。ただ、ハンズオンはやれればやりたい。一緒に登壇したエバンジェリストの新妻さんからも、「データ」「プロセス」「コミュニケーション」の三つの柱で語るのは、初期のkintoneのコンセプト。今や概念で語るのではなく、直接ハンズオンで魅力を伝えることの必要性を説かれました。仰る通りで、次回はハンズオンにも取り掛からねば、と思います。

今回、一緒に登壇した情報親方の東野さんも、これからkintoneが導入されていく中で必ず使われるであろう資料を軽く紹介してくださいました。やはりユーザー導入の推進こそが鍵なのでしょう。

お客様より恵比寿と武蔵小杉でもkintone Caféを開催して欲しいというご要望をいただいています。具体的に会場も確保できています。今年はあと二回、開催できればと思います。その時私が、どこまで技術を語りたいという誘惑に耐え、ユーザー目線の内容に踏みとどまれるか。肝に銘じたいと思います。まずはkintone Caféを主催できる自信を取り戻せたことが、今回、私の中で最も得難い収穫だったと思います。

今回来てくださった14名の皆様、登壇してくださった3名の仲間。皆様、本当にありがとうございました。


kintone Café 広島 vol.12 @福山に登壇しました


3月1日に開催されたこちらのイベントに弊社代表である私長井が参加し、登壇して参りました。

昨年秋のkintone Café Japanの懇親会で結ばれた今回のご縁。今年に入ってから示されたCaféのお題は「小さい組織でのkintone活用術!」だとか。
https://kintone-cafe-hiroshima.connpass.com/event/119652/

であらば、私が話すべきテーマは自治会しかあるまい、と思いました。昨年六月、サイボウズ社においてチーム応援ライセンスの記念セミナーに登壇しました。その際に取り上げた主な話題はCybozu LiveからCybozu Officeへの移行。kintoneにはわずかしか触れられませんでした。ところが、自治会業務にとってkintoneは欠かせないサービスなのです。ならば、今回のkintone Café 広島は私が思い描くテーマを深める良い機会となるはず。

今回、参加者の中にはkintoneを触った事のない方もいらっしゃるとか。であるなら、私は技術に深入りすまい。kintoneを導入するにあたっての前提だけを語ろうと思いました。自治会の業務は、他の小さい組織のモデルケースにもなるだろうし。技術的な観点や実際のkintoneの使用デモは、他の登壇者久米さん、山川さんが触れてくれるはず、との期待をこめて。

そんな思いで資料をまとめ始めたものの語るべきことは多い。チーム応援ライセンスの概要にも触れておきたい。そして私に与えられた時間は二十分。なのにスライドの枚数はどんどん増えてゆき、ついには六十枚を突破。

福山駅から久米さん夫妻とタクシーに乗り、会場である猪原歯科さんについたのは開演15分前。入るなり、中の洗練された様子に感嘆することしきり。私の妻も歯科診療所を経営しているのですが、規模が違いすぎます。副院長である猪原健先生が自らご案内してくださるという光栄にも恵まれまして、私たちは数名で院内を見学させていただきました。ただただ感嘆しながら。

うちの妻の歯科診療室はユニット一台。妻が受付と医師も兼ねています。ところが猪原歯科さんはパーティションで区切られた四つのユニットと三つの個室診療室を擁しています。さらには内科診療室や言語療法室なども備わっており、そのすべてが駐車場から完全バリアフリーというのがすごい。入り口の待合室のソファーも素晴らしいし、何よりもオープンキッチンの存在が異彩を放っています。このオープンキッチンは「摂食・嚥下リハビリ中の患者様・ご家族様に対して、嚥下食や高カロリー食の調理方法等をレクチャーするキッチン設備」と謳われています。私は今までも妻の関係からあちこちの歯科医院に訪れましたが、オープンキッチンが備わった歯科診療所は初めてみました。さらに、最新型のデジタル印象採得からセラミックの最終修復物まで作り上げてしまうという優れモノの機械が鎮座し、私はただただ感嘆の言葉しか出ません。歯科技工士さんも院内で常駐されており、猪原歯科さんが院内の管理にkintoneを活用されている理由がわかった気がします。

猪原歯科さんは1946年に設立してから70年以上の歴史を持っているとのこと(サイト)。妻の実家の歯医者も借地にさえしていなければ60年の歴史は誇れていただろうに。とその解体を見守った私に、複雑な思いが去来しました。

さて、kintone Café 広島 vol.12の開催です。安藤さんの司会によって始まり、トップバッターは私。安藤さんが今回の参加者の皆様にkintoneの認知度を伺ってくださったところ、技術面で深入りしないほうがよい、との判断は当たったようでした。ところが、しゃべっているうちに話し始めた時間を忘れてしまい、なんとか60枚近くのスライドを語ったものの、時間を超過するという凡ミス。冒頭のアンケートでkintoneをほぼ使ったことのない方が多かったため、予定していなかったアプリのデモにまで話を広げたのはやりすぎでした。

まあ、笑ってもらいたいところで受けがとれたのは自分の中で認めてもよいかも。あと、昨年のチーム応援ライセンスの記念セミナーで登壇した際も思ったのですが、自治会というネタは刺さる人とそうでない人に分かれます。今回は数人の方が熱心にうなづいてくださっていました。ただ、そうでない方は反応が薄い。私の反省点として、自治会という対象は中小企業にも通ずる、という論点をもう少し強調するべきでした。
https://slides.com/yoshikazunagai/kintonecafe_hiroshima_vol12/fullscreen

私の後に登壇してくださったのは福岡からご参加の久米さん。スペースの使い方、というテーマで、マルチアカウントを操りながらのコメントや通知のやりとりの実例を披露していただきました。コミュニケーションこそが重要との私の主張を深めてもらい、皆さまもよりご理解が進んだのではないかと。私自身、今後の登壇では一人で全部こなす可能性もあります。そのためにもマルチアカウントを駆使したデモは習得しないと。
https://www.slideshare.net/jkume/20190301-kintone-caf-hiroshimavol12

さらに続いては岡山からご参加の山川さんによるウェブ連携のデモ。kintoneの内容をwordpressなどのCMSに表示させる実例をシンプルな例で説明してくださいました。実はデータベースだけの導入でもkintoneは業務改善に大きな効果を発揮します。それがシンプルな実例として見られたのは良かった。私も実装経験はあり、記事にも書いた事があります。サイト内のカタログが簡単に修正できるデモは、参考になりました。
https://www.slideshare.net/secret/9hlG9PwUCFI2cM

私が伸ばしてしまった時間を後のお二人がきっちり時間内に収めたのはさすが。とっても感謝です。

最後に質疑応答の時間では、私が触れたポータルのカスタマイズなどにご質問を頂戴しました。ポータルの整備こそ、リテラシーの壁を越えてkintoneを広めるための要点という私の見込みに自信が持てました。
https://www.kintonecafe.com/activities/4327!

夜の懇親会では、広島といえば海の幸、という事で楽しい会話においしい料理とお酒に癒やされました。kintone Caféの懇親会は毎回、楽しみなのです。

ご参加の皆様、運営の皆様、猪原歯科の皆様、誠にありがとうございました!


EBISU Tech Night Vol.5にて登壇しました


昨日、EBISU Tech Night Vol.5 で登壇を求められ、喋ってきました。
お題は「kintone + MONACAでモバイルアプリ開発!」
まあ、タイトルはストレートです。

EBISU Tech NightはAWSやcybozu.comの凄腕技術者が集うイベントです。今まで私はVol.2でも、Vol.4でも登壇の機会をいただきました。今回が三度目です。

今回はモバイルアプリ開発というお題で挑んでみました。AWSやcybozu.comを主戦場とする技術者とはいえ、ほとんどの方が開発するシステムはPCで使用するシステムのはず。モバイルアプリはあまり経験されていないのでは。そんな予測で臨んだ今回でしたが、冒頭で「MONACAを知っているでしょうか」と三十名程の聴衆者に伺ってみたところ、ご存じなのはお一人だけでした。手を挙げなかった方を含めても一割ほどの認知でしょうか。この分野でメジャーなXamarinを例に挙げたとしても同じ程度だったかもしれません。

モバイルアプリ開発への認知度がその程度とはいえ、今後もモバイルアプリが求められる確度はかなり高い。これに異をとなえる方はそうはいないはず。モバイルの処理能力が飛躍的に向上した今、PCのアプリに対し優位に立てるのは画面の大きさと、複数アプリの同時表示という点ぐらい。

ところがモバイル開発は技術者にとって鬼門でした。少なくとも私には。かつてのフィーチャーフォンでどれぐらい苦労したか。それこそが環境差異。かつてのフィーチャーフォンが廃れてしまった一因には、キャリアごとに開発環境や言語が違ったため、アプリの開発工数がかかった事情も無視できないと思います。そして今もまた「iOS」と「Android」の間でシェアを巡る争いとバージョンアップによる無数の環境が生まれています。

わたしはこうした環境差異を吸収するために費やす時間は無駄だと考えています。もっと機能そのものをビジネスにフィットさせるべきだろうと。そのためにもMONACAのような簡単に環境の差を乗り越えてアプリをビルドするツールはもっと世に知られるべきだと。

今回はkintoneから取得したデータを元にアプリの画面を描画し、最後はkintoneにデータを書き込むところまでお見せしました。モバイルアプリのデータベースは、Local StorageやSession StorageやWeb SQLやIndexed DBやSQLiteが主流です。が、kintoneを使うことで、データベース周りが簡単に作りこめます。kintoneに加え、なおかつモバイルアプリも簡単に作れる。私が示したかったこととはそれです。

そうした意図で語った一部始終、どこまで皆さんに伝わったかはともかく、環境の差異を回避する策の一環として伝わったのであればひとまずは成功です。惜しむべきは、笑いを織り込むまでにスライドを練られずに壇上に上がったことでしょうか。

来年も引き続きEBISU Tech Nightは行われるとか。すでに司会の方から四回目の登壇のご依頼もいただきました。引き続き精進しつつ、皆さんを引き付けられるような登壇を目指します。

スライド


ライフログのkintone盛り alasql仕込みのGoogle Chart添え


1.読まなくてもいい献立の前書き

  目次へ↓

唐突ですが皆さん、ライフログって言葉覚えてますか? 何!忘れた? そんな言葉あったっけ?

それならば説明しましょう!システムが行った作業の結果がログ。ウェブ上にログを残すからウェブログ。略してブログ。そして、人生のイベントを記録するのがライフログ。今やこの言葉は、数多くの新語とともにハードディスクの肥やしと成り果てています。

ところが、まだ忘れるのは早い!という訳で、kintoneでライフログです。

このライフログという概念は2000年代の初め頃に産声をあげていたといいます。ITジャーナリストの佐々木俊尚氏は、2010年の秋に発表した『キュレーションの時代』の中でライフログの概念は2010年代の間にはまだ根付かないだろう、と的確に予言されています。なぜなら、日本にはプライバシーの公開に気持ち悪さを持つ人が大多数だから、と。つまり、ライフログとはまだこれからの概念なのです。佐々木氏はその本の中で、ライフログを集めるツールとしてFourSquare_logoをプッシュしています。このFourSquare_logoですが、ある時期に仕様が迷走したこともあって日本ではすっかり影を潜めてしまいました。ですが、海外では位置とモノを関連付けたデータをがっちり握った企業として存在感を保っているようです。FourSquare_logoとは、一言でいうと訪問場所でチェックインすることで自分がいつどこにいたかを後世の自分、またはどこかの物好きのために記録しておくツールです。それはまさにライフログ。なお『キュレーションの時代』はかつて弊社のブログでも取り上げています。

kintoneは言うまでもなく優れたプラットホームサービスですし、仕事の改善にはテキメンに効きます。それはもう間違いない。でも、プライベートな使い方だってできるのですよ。ワークライフバランスを充実させるにはプライベートでもkintoneを使い倒したほうがいいと思いませんかみなさん!?

私にとって幸いなことにkintoneFourSquare_logoの連携事例はウェブにほぼ皆無です。そこは青い大海原、ブルーオーシャン!というわけでささやかですが、連携事例を公開したいと思います。あわせて、これもkintoneとの連携事例が極めて少ないGoogle Chartと絡めて。題して「ライフログのkintone盛り alasql仕込みのGoogle Chart添え」

2.ご用意いただく材料

  目次へ↑

ちなみにこのレシピの内容を再現せんと志す奇特な方は、以下のものをご用意ください。

  • FourSquare_logoのアカウント(チェックイン用のアプリSwarm_logoも忘れずに)
  • kintoneのスタンダードプラン
  • phpが動き、cronが実行できるサーバー(レンタルサーバーでも可)
  • Google Maps Platform上で動くライブラリを呼び出すためのAPI KEYの入手。こちらをご参考に

このレシピは2018年12月時点の「さくらのレンタルサーバー スタンダード」(phpは7.2.10(CGI版))で動作することを確認しております。
また、Google Maps Platformで動くAPIについては、月あたり40000リクエストまでが無料枠内だそうです。このレシピはプライベート利用なので、そこまでいかないことを念頭においています。
ちなみに、みんな大好き「zapier」でも、Swarm_logoでチェックインする度にkintoneに新たなレコードを追加できます。が、それをいっちゃぁおしめえよ、ということでお付き合いいただければ幸いです。
このレシピは一日単位でkintoneにライフログを一括で登録することに意義を持たせています。そして激動の一日を振り返りつつ、自分をねぎらいたい、そして昨日を忘れ去りたい「あ・な・た」の味方になることも!

なお、いうまでもなく、ライフログはみだりに公開するものではありませんぞ。このレシピのせいであなたのデイリーライフに何か問題が起こっても責任は取れませんのであしからず〜

3.献立の出来上がりイメージとレシピ

  目次へ↑

FourSquare_logoはAPIがOAUTH付きで公開されています。なので私の策略はこうです。まず、午前0時を過ぎたあたりでFourSquare_logoの前日分のチェックインデータをkintoneにシュっと放り込んでやろう。そして溜まったデータをウヒャヒャと一覧で眺めてみよう。ついでに今年、どんぐらい国や県を訪れたんやろう、と地図に出して一人ニヤけて悦にいる。うーん、ダークロースト。

まずは着地点をお見せします。上が世界の長井。下が日本の長井です。

世界の中心で長井を晒す

日本の中心で長井を晒す

ともにkintoneのカスタマイズビューではなく通常の一覧を使って長井を晒しています。世界地図は日本と台湾に色がついており、日本地図は東京が最も濃く、次に神奈川。データはFourSquare_logoSwarm_logo)でチェックインしたデータをphpでkintoneに取り込みます。地図を書き込むのは一覧画面のヘッダスペースです。そのため、世界地図版と日本地図版で一覧を分けています。

kintoneのアプリの構造はこんなんです。

フィールド名 フィールドコード
場所 場所 文字列(1行)
文字列(1行)
都道府県 都道府県 文字列(1行)
コメント コメント 文字列(1行)
日時 日時 日時

あと、アプリの設定画面で下図の3つのファイルを登録してください。
https://www.gstatic.com/charts/loader.js
とhttps://cdnjs.cloudflare.com/ajax/libs/alasql/0.4.11/alasql.min.js
はCDNから使います。最後のjsファイルはこの後説明します。
kintone_app

ここであらためて全体の構成図をご覧いただきましょう。一目瞭然とはこのことですね。

構成図

4.FourSquareの下ごしらえ

  目次へ↑

日々のチェックイン履歴をFourSquare_logoに溜める方法は、簡単に書くとこんな感じです。

  • Swarm_logoのアプリを立ち上げ、チェックインしたい場所でチェックインボタンを押す。
  • Swarm_logoの候補の一覧にその場所が登場します。
  • 登場しなくても検索すれば場所が指定できます。
  • 指定した場所でチェックイン(構成図の①)を行えば時間とともに自動的に記録(構成図の②)されます。気が向けばコメントも入れられます。

どうです?モンスターボールを投げなくてもよいし、ポータルを三角形で囲わなくてもよいのです。簡単ですよねFourSquare_logo

さて、今回の記事ではAccessTokenをFourSquare_logoから取得する処理(構成図の③)から説明します。AccessTokenはこのレシピの中で一度取得するだけです。なぜ一度だけでよいかというと、AccessTokenはユーザーを特定するのに使われるからです。つまり、一度AccessTokenを取得しておけば、何度でも使いまわしができるのです。FourSquare_logoには他にもたくさんのAPIが用意されています。処理によってはその都度AccessTokenを取らねばならず、ソースも複雑な下ごしらえが求められることもあります。ですが、このレシピではユーザーのチェックイン情報だけをとるのが目的なので、一度きりでよいのです。

5.FourSquareのAPI KeyとAPI Secretを取り寄せる

  目次へ↑

まず、https://ja.foursquare.com/にアクセスしてください。

FourSquarePage

そして、右上の開発者をクリックします。

すると、https://developer.foursquare.com/にページが遷移します。

FourSquareDeveloperPage

がでたら、右上のCreate Accountからアカウントを作成してください。

多分、FourSquare_logoで作ったアカウントがそのまま開発者アカウントとして引き継がれるはずです。

そしたら下図のようにMy Appsを選びましょう。アプリの作成のリンクがあるはずです。

FourSquareMyApp

こんな風に入れてください。

FourSquareMyAppRegist

この中で重要なのは3点。

  • Client ID、Client Secretは次の作業で使うのでちゃんとメモっておきましょう。
  • Client ID、Client Secretは墓まで持っていきましょう。人に教えちゃダメ!
  • Redirect URI (s)もちゃんと考えておきましょう。これは次の作業でこさえるスクリプトのURIです。

Application Urlという項目もありますが、ここは適当でよいです。他の項目も商用で使わなければ空白でよいはず。

6.FourSquareのアクセストークンの取り寄せとソースの仕込み方

  目次へ↑

続いて、アクセストークンを取得するためのphpを示しましょう。98行。38ステップです。

コメントに内容は記載しているので、まずはソースを味わってみてください。

設定A ⇒ 条件分岐B ⇒ 条件分岐C ⇒ 処理D ⇒ 処理E ⇒ 処理F ⇒ 条件分岐G ⇒ 処理H ⇒ 条件分岐I ⇒ 処理Jの順に進みます。

  // 設定A //
  // マイアプリで表示されたClient ID
  $client_id = 'KOKONIHAHONMAYATTARARANDAMUNAMOJIRETSUGAHAITTORUNEN';
  // マイアプリで表示されたClient Secret
  $client_secret = 'SEYAKEDOKOKONISOREDASHITARAORENOKOUDOUGABARERUKARADASAHENNEN';
  // リダイレクトURL (このスクリプト自身のアドレスです)
  $redirect_uri = 'https://*****.*****.ne.jp/*****/****_*********.php';
  // アクセストークン取得URLのベース
  $access_token_baseurl = 'https://foursquare.com/oauth2/access_token';
  // 認証URLのベース
  $authenticate_baseurl = 'https://foursquare.com/oauth2/authenticate';

  // 結果表示HTML用
  $html = '';
  // 結果表示見出し
  $html .= '<h2>実行結果</h2>';

  // 条件分岐B //
  //初回は$_GET['code']がなく中の処理は実行されない。「許可」され、リダイレクトされた場合に実行
  if( isset( $_GET['code'] ) 
    && !empty( $_GET['code'] ) 
    && is_string( $_GET['code'] ) ) {
    // 処理E //
    // 認証画面でFourSquareの実行が許可されると$_GET['code']付きでこのスクリプトが呼び出される。
    // アクセストークンの取得に利用するコード
    $code = $_GET['code'];
    // 処理F //
    // アクセストークン取得のためのパラメータを設定したUrlを組み立てる。
    // リクエストURL
    $request_url = 
      $access_token_baseurl . 
        '?client_id=' . $client_id . 
        '&client_secret=' . $client_secret . 
        '&grant_type=authorization_code' . 
        '&redirect_uri=' . rawurlencode( $redirect_uri ) . 
        '&code=' . $_GET['code'] . 
        '&state=users/self';
    // curlを初期化する
    $curl = curl_init();
    curl_setopt( $curl , CURLOPT_URL , $request_url );
    curl_setopt( $curl , CURLOPT_HEADER, 1 );
    // 証明書の検証を行わない
    curl_setopt( $curl , CURLOPT_SSL_VERIFYPEER , false );
    // curl_execの結果を文字列で返す
    curl_setopt( $curl , CURLOPT_RETURNTRANSFER , true );
    // タイムアウトの秒数
    curl_setopt( $curl , CURLOPT_TIMEOUT , 5 );
    // 実行し、結果を$jsonに代入
    $res1 = curl_exec( $curl );
    $res2 = curl_getinfo( $curl );
    curl_close( $curl );
    // 取得したJSONデータ(ヘッダーサイズでTrimしないとFourSquareはエラーになる)
    $json = substr( $res1, $res2['header_size'] );

    // JSONをオブジェクト型に変換する
    $obj = json_decode( $json );

    // 条件分岐G //
    if( !isset( $obj->access_token ) ) {
      // アクセストークンを取得できなかった場合
      $error = 'アクセストークンを上手く取得することができませんでした。';
    } else {
      // 処理H //
      // アクセストークンを[$access_token]に代入する
      $access_token = $obj->access_token;
      // アクセストークンをブラウザーに出力する
      $html .= '<p>取得したアクセストークンは <b><mark>' . 
        $access_token . '</mark></b>です。</p>';
    }
  } elseif( isset( $_GET['error'] ) ) {
    // 「拒否」して返された場合怒る。
    $error = 'なんで「許可」してくれへんの!?';
  } else {
    // 条件分岐C //
    // 初回はこの処理が行われるはず。
    header( 'Location: ' . $authenticate_baseurl . 
      '?client_id=' . $client_id . 
      '&response_type=code' . 
      '&redirect_uri=' . rawurlencode( $redirect_uri ) );
    // 処理D //
    // headerの後はexit()
    exit;
  }

  // 条件分岐I //
  // エラー判定
  if( isset( $error ) && !empty( $error ) ) {
    $html .= '<p><mark>' . $error . '</mark>' . 
      'もう一度、認証をするには、' . 
      '<a href="' . explode( '?' , $_SERVER['REQUEST_URI'] )[0] . '">こちら</a>' . 
      'をクリックして下さい。</p>' ;
  } else {
    // 処理J //
    // ブラウザーに[$html]を出力 (結果としてアクセストークンの文字列が表示されます)
    echo $html;
  }

もう一度上の処理をおさらいします。
・最初にこのスクリプトが呼ばれた際、URLにcodeパラメーターはついていません。
・だからBの条件でEFGHの処理は行われず、条件分岐Cの処理が実行されます。
・その中ではLocationヘッダを送信しています。なので、その下の処理Dでただちに認証画面にリダイレクトされます(認証画面は割愛します)。
・そこで認証が終われば、再びこのスクリプトがcodeパラメーター付きで呼ばれるよう、処理DのLocationヘッダの中にredirect_uriパラメーターを指定しています。
・再びこのスクリプトが実行されると、今度は条件分岐Bで処理E、Fが実行されます。
・FではFourSquare_logoのアクセストークンを返すAPIが呼ばれます。
・なので、無事にアクセストークンが返ってくるという流れです。お判りでしょうか?

そしたらこのスクリプトファイルをどこかのウェブサーバーに送ってください。phpファイルの実行権限付きで。先ほどFourSquare_logoのマイアプリの設定でRedirect Url (s)に入力していただきましたが、その内容と合わせておいてください。

たとえばhttps://hanamogera.com/mokeke/foursqaure_tokun_yokose.phpに送るとします。そしたらブラウザーのアドレスバーに直接上のアドレスを指定し、実行します。

すると結果が以下のように表示されるはずです。このマーカー部分をコピーしておきましょう。

アクセストークン

おめでとうございます。アクセストークンはこれであなたのものです。ついに調理人として存分に腕を振るう時が来たのです!!

7.FourSquareからCheckinデータの取り寄せとソースの仕込み方

  目次へ↑

続いては、いよいよAccessTokenを使ってFourSquare_logoにデータくれ!とおねだり (構成図の④) してみましょう!もらったデータをkintoneにシュッと投げ込むまで(構成図の⑤)のphpも説明します。まずはphpを以下に示します。115行。64ステップです。

こちらもコメントに内容は記載しているので、まずはソースを味わってみてください。
先のスクリプトはソースに大分シェフの手を加えましたが、このスクリプトは素材の味を生かすような作りにしています。

設定A ⇒ 設定B ⇒ 設定C ⇒ 設定D ⇒ 設定E ⇒ 条件分岐F ⇒ 条件分岐G ⇒ 処理H ⇒ 処理I ⇒ 処理J ⇒ 処理Kの順に進みます。

  // 設定A //
  // アクセストークン (前の処理で取ってきた文字列です)
  $access_token = 'YATTAYOAKUSESUTOKUNTORETAYOKOREDESIAWASENINARERUNE';

  // 設定項目 (ここはアクセストークン以外はデフォルト)
  $params = array(
    'oauth_token' => $access_token ,// アクセストークン (これでユーザーが認識される)
    'locale' => 'ja' ,              // ローカライズ (jaは日本)
    'm' => 'swarm' ,                // モード (foursquare OR swarm)
    'v' => '20150801' ,             // バージョン (APIのバージョン。今のところ左の年月日)
    'limit' => '250'                // 取得件数 (250が上限)
  ) ;
  // 設定B //
  // GETメソッドで指定した場合 (設定項目のパラメーターを差し替える)
  foreach( array( 'locale' , 'm' , 'limit' , 'sort' , 'afterTimestamp' , 'beforeTimestamp' ) as $val ) {
    if( isset( $_GET[ $val ] ) && $_GET[ $val ] != '' ) {
      $params[ $val ] = $_GET[ $val ] ;
    }
  }

  // 設定C //
  // 設定項目 (日付や並び替えなどの条件を追加します。例えば2018/11/30の00:15時点で実行されたとします。
             するとstrtotime("today")は2018-11-30 00:00:00が返されます。
              strtotime("yesterday")は2018-11-29 00:00:00が返されます。
              それをもとにした$params["afterTimestamp"]は2018-11-29 00:00:00より後のチェックインデータを、
              それをもとにした$params["beforeTimestamp"]は2018-11-30 00:00:00より前のチェックインデータを取得します。
              $params["sort"]はoldestfirstを指定すると上記の日付範囲のチェックインデータのうち古いものを最初に取得します。
              ここ重要です。kintoneライフログ調理師試験に出ますので、要おさらい!!) //
  $today = strtotime("today");
  $yesterday = strtotime("yesterday");
  $params["afterTimestamp"]=$yesterday;
  $params["beforeTimestamp"]=$today;
  $params["sort"]="oldestfirst";

  // リクエストURL (usersの次のselfは固定文字)
  $request_url = 'https://api.foursquare.com/v2/users/self/checkins?' . http_build_query( $params );

  // 設定D //
  // cURLでリクエスト
  $curl = curl_init();
  curl_setopt( $curl , CURLOPT_URL , $request_url );
  curl_setopt( $curl , CURLOPT_HEADER, 1 );
  curl_setopt( $curl , CURLOPT_SSL_VERIFYPEER , false );
  curl_setopt( $curl , CURLOPT_RETURNTRANSFER , true );
  curl_setopt( $curl , CURLOPT_TIMEOUT , 5 );
  $res1 = curl_exec( $curl );
  $res2 = curl_getinfo( $curl );
  curl_close( $curl );

  // 設定E //
  // 取得したJSONデータをオブジェクト形式に変換する (ヘッダーサイズでTrimしないとFourSquareはエラーになる)
  $json = substr( $res1, $res2['header_size'] );
  $obj = json_decode( $json );

  // 条件分岐F //
  // エラー判定 (metaのcodeの値が200だと正常に取得されている)
  if( !$obj || !isset($obj->meta->code) || $obj->meta->code != 200 ) {
    //ログ出力して調査!
  } else {
    // 説明
    $data = array();
    $count = 0;
    // 条件分岐G //
    //取得したデータオブジェクトの -> response -> checkins -> itemsの中をループする //
    foreach( $obj->response->checkins->items as $item ) {
      // 処理H //
      //$itemの中にはチェックインの場所についての栄養が豊富に含まれています。緯度経度やコメントや市長など。詳しくはhttps://developer.foursquare.com/docs/api/users/checkins //
      // チェックインID
      $id = $item->id ;
      // ベニューのID
      $venue_id = $item->venue->id ;
      // ベニューの国 (国名は日本語で取得できる)
      $venue_country = $item->venue->location->country;
      // ベニューの都道府県 (取得した後、Google Chartの都合で末尾の「都」「府」「県」は除去する)
      $venue_prefecture = $item->venue->location->state;
      switch ($venue_prefecture){
        case '北海道':
          break;
        default:
          $venue_prefecture = mb_substr($venue_prefecture,0,-1, "UTF-8") ;
          break;
      }
      // ベニューの名前 (改行は除去しておく)
      $venue_name = str_replace(array("\r\n", "\r", "\n"), '', $item->venue->name);
      // チェックイン日時(オフセットと合わせる。要は日本の時間に合わせる)
      $createdAt = $item->createdAt + 54000 - 86400;
      // 日時の整形 (kintoneにあった日付データ形式に変更する)
      $createdAt = date( 'Y-m-d' , $createdAt )."T".date( 'H:i:s' , $createdAt ) ;
      // コメント (改行は除去しておく)
      $shout = ( isset($item->shout) ) ? str_replace(array("\r\n", "\r", "\n"), '', $item->shout) : '';
      // kintoneに投げるデータです。フィールドコードとデータ型を合わせることを忘れずに。
      // 処理I //
      //kintoneに投げるデータをここで指定します。文字列データは文字列型にキャストしておくとふっくら仕上がります //
      $data[$count] = array(
        "場所" => array("value" => (string)$venue_name),
        "コメント" => array("value" => (string)$shout),
        "国" => array("value" => (string)$venue_country),
        "都道府県" => array("value" => (string)$venue_prefecture),
        "日時" => array("value" => $createdAt)
      );
      $count++;
    }
  }

  // 処理J //
  // kintoneの対象のアプリIDを指定する。(1111というのはダミーです)。FourSquareからのデータもあわせて。
  $postdata = array("app" => 1111,records=>$data);
  // 処理K //
  // kintoneのアクセストークンを指定する。ベーシック認証がある場合はそちらもあわせて指定。
  $headers = array(
    "X-Cybozu-API-Token:" . "kintoneNOAPURISETTEIDESHUTOKUDEKIRUTOKUN",
    "Authorization:" . "Basic " . base64_encode("YUUZAAID:PASUWAADO"),
    "Content-Type:" . "application/json"
  );
  // 以下の******はお使いのkintoneのサブドメインを入れてください。
  $curl = curl_init('https://******.cybozu.com/k/v1/records.json');
  curl_setopt($curl, CURLOPT_POST, true);
  curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
  curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($postdata,JSON_UNESCAPED_UNICODE));
  curl_setopt($curl, CURLOPT_HEADER, true);
  // curlで出たエラーを補足するためのものです。
  $fp = fopen('curl.log', 'a');
  // 詳細な情報を出力する
  curl_setopt($curl, CURLOPT_VERBOSE, true);
  // STDERR の代わりにエラーを出力するファイルポインタ
  curl_setopt($curl, CURLOPT_STDERR, $fp);
  if(curl_exec($curl) === false){
    //ログ出力して調査!
  } else {
    return true;
  }
  curl_close($curl);

このスクリプトもウェブサーバーに送ってください。ファイルの実行権限付きで。あと、ファイルのUrlはきちんとメモしておいてくださいね(^_^)

次にcronの設定を行います。cron実行のための構文はサーバーによって違います。ここに載せているcron設定はさくらインターネットの例ですが、phpファイルの実行パスと、スクリプトのファイル名はどのサーバーでも求められるはずです。このレシピは毎日00:15に自動で実行するように設定しています。

Cron設定1

cron2-1

8.kintone上でデータの盛り付け

  目次へ↑

さて、7までのレシピ(2018/12時点の)に忠実に行うとチェックインデータは毎日順調にkintoneに流れ込むはず。あとはデータを盛り付けるだけ。「ライフログのkintone盛り alasql仕込みのGoogle Chart添え」とうたっている以上、最後の仕上げにGoogle Chartを添えるのを忘れるなかれ。それぞれの一覧ごとに違うマップを表示するJavaScriptを以下に示します。この中でkintoneのデータをalasqlで集計し、その結果をGoogle Chartで地図に表示しています。

世界地図の場合、処理A ⇒ 条件分岐B ⇒ 処理C ⇒ 処理D ⇒ 処理E ⇒ 処理V ⇒ 処理F ⇒ 処理W ⇒ 処理G ⇒ 処理H ⇒ 処理I ⇒ 処理J ⇒ 処理Kの順に進みます。
日本地図の場合、処理A ⇒ 条件分岐L ⇒ 処理M ⇒ 処理N ⇒ 処理O ⇒ 処理V ⇒ 処理P ⇒ 処理W ⇒ 処理Q ⇒ 処理R ⇒ 処理S ⇒ 処理T ⇒ 処理Uの順に進みます。

(function () {
  "use strict";
  // 処理V //
  // 再帰処理による一回のリクエスト制限を超えた全レコードを取得 //
  function fetchRecords(appId, opt_query, opt_fields, opt_offset, opt_limit, opt_records) {
    var query = opt_query || '';
    var offset = opt_offset || 0;
    var limit = opt_limit || 500;
    var allRecords = opt_records || [];
    var params = {app: appId, query: query + ' limit ' + limit + ' offset ' + offset };
    if (opt_fields) params.fields = opt_fields;
    return kintone.api(kintone.api.url('/k/v1/records', true), 'GET', params).then(function(resp) {
      allRecords = allRecords.concat(resp.records);
      if (resp.records.length === limit) {
        return fetchRecords(appId, query, opt_fields, offset + limit, limit, allRecords);
      }
      return allRecords;
    });
  }

  // 処理W //
  // json形式で取得したkintoneのレコードをalasqlで扱えるようレコードセット形式に変換 //
  function convertToRows(records) {
    var rows = records.map(function(record){
      var keys = Object.keys(record);
      var row = {};
      keys.map(function(key){
        row[key] = record[key].type === 'NUMBER' ? Number(record[key].value) : record[key].value;
      });
      return row;
    });
    return rows;
  }

  // 一覧ページ
  kintone.events.on('app.record.index.show', function(event) {
    // 処理A //
    //Google Cloud Platformで発行したAPI KEYです。
    //API KEYがない場合、以下の処理Cでgoogle.charts.load('upcoming', {'packages':['geochart']});としても動きますが、世界地図の色塗りができません。また、コンソールでエラーが表示されます。
    //作成したプロジェクトの認証はリファラーを限定するとよいです。https://subdomain.cybozu.com/* のように指定すると、対象のサブドメインに対して動作します。https://akvabit.cybozu.com/だと動きませんので注意が必要です。
    //あと、許可するAPIですが、「Maps JavaScript API」「Geolocation API」「Geocoding API」の三つを有効にしています。
    const ApiKey = 'k54u6 jkrawyeie-wkjykethiudarwhyeiu_rekyjur';
    // 条件分岐B //
    //世界地図ビューの場合 //
    if (event.viewId === 5351051) {
      // 処理C //
      //google chartを読み込む。種類は地図 //
      google.charts.load('upcoming', {'packages':['geochart'],'mapsApiKey':ApiKey});
      // 処理D //
      //google chartの読み込みが完了したらコールバックでdrawWorldMapを呼び出す //
      google.charts.setOnLoadCallback(drawWorldMap);
      function drawWorldMap() {
        var obj = {};
        // 処理E //
        //このファイルの3-17行のfetchRecords関数を呼び出す。対象アプリは自分で、フィールドは[場所][国] //
        fetchRecords(event.appId, '', ['場所', '国']).then(function(records) {
          // 処理F //
          //取得したレコードをSqlで扱えるようなレコードセットの形式に変換するconvertToRows関数(このファイルの20-30行)をご参照 //
          obj.rs1 = convertToRows(records);
          // 処理G //
          //alasqlでsql文のソースを組み上げ、バインドパラメータに処理Fで得たレコードセットをセットする //
          var result = 
            alasql(
              "SELECT t.[国], COUNT(t.[場所]) as [回数] \
              FROM ? AS t \
              GROUP BY t.[国] \
              ORDER BY t.[国]", [obj.rs1]);
          // 処理H //
          //google chartのデータテーブルのインスタンスを新たに確保する //
          var data = new google.visualization.DataTable();
          // 処理I //
          //google chartのデータテーブルの列と行を追加します。alasqlでグループ集計された国ごとの訪問回数です。 //
          data.addColumn('string', '国');
          data.addColumn('number', '訪問場所数');
          result.forEach(value => {
            data.addRow([value["国"], value["回数"]]);
          });
          // 処理J //
          //google chartの世界地図の色塗りの書式を設定する //
          var options = {
            datalessRegionColor: '#ffffff',
            colorAxis:{
                maxValue:500,
                colors:['#D8F6CE','#21610B']
            }
          };
          // 処理K //
          //google chartの世界地図のデータに処理Iで格納した内容を代入し、kintoneのヘッダスペースに描画する //
          var chart = new google.visualization.GeoChart(kintone.app.getHeaderSpaceElement());
          chart.draw(data, options);
        });
      }
    // 条件分岐L //
    //日本地図ビューの場合 //
    } else if (event.viewId === 5351053) {
      // 処理M //
      //google chartを読み込む。種類は地図 //
      google.charts.load('upcoming', {'packages':['geochart'],'mapsApiKey':ApiKey});
      // 処理N //
      //google chartの読み込みが完了したらコールバックでdrawJapanMapを呼び出す //
      google.charts.setOnLoadCallback(drawJapanMap);
      function drawJapanMap() {
        var obj = {};
        // 処理O //
        //このファイルの3-17行のfetchRecords関数を呼び出す。対象アプリは自分で、フィールドは[場所][都道府県] //
        fetchRecords(event.appId, '', ['場所', '都道府県']).then(function(records) {
          // 処理P //
          //取得したレコードをSqlで扱えるようなレコードセットの形式に変換するconvertToRows関数(このファイルの20-30行)をご参照 //
          obj.rs1 = convertToRows(records);
          // 処理Q //
          //alasqlでsql文のソースを組み上げ、バインドパラメータに処理Pで得たレコードセットをセットする //
          var result = 
            alasql(
              "SELECT a.[都道府県], COUNT(a.[場所]) as [回数] \
              FROM ? AS a \
              GROUP BY a.[都道府県] \
              ORDER BY a.[都道府県]", [obj.rs1]);
          // 処理R //
          //google chartのデータテーブルのインスタンスを新たに確保する //
          var data = new google.visualization.DataTable();
          // 処理S //
          //google chartのデータテーブルの列と行を追加します。alasqlでグループ集計された都道府県ごとの訪問回数です。 //
          data.addColumn('string', '都道府県');
          data.addColumn('number', '訪問場所数');
          result.forEach(value => {
            data.addRow([value["都道府県"], value["回数"]]);
          });
          // 処理T //
          //google chartの地図を日本地図の都道府県とし、色塗りの書式を設定する //
          var options = {
            region: 'JP',
            resolution: 'provinces',
            datalessRegionColor: '#ffffff',
            colorAxis:{
                maxValue:600,
                colors:['#F2FBEF','#21610B']
            }
          };
          // 処理U //
          //google chartの世界地図のデータに処理Sで格納した内容を代入し、kintoneのヘッダスペースに描画する //
          var chart = new google.visualization.GeoChart(kintone.app.getHeaderSpaceElement());
          chart.draw(data, options);
        });
      }
    }
  });
})();

いかがでしょうか? ここはカスタマイズビューでリッチにデータを表現しても良いですし、条件に応じて絞り込めば、さらに面白いこともできます。チェックインデータにはカテゴリーもありますので、例えば訪れた酒場だけを抜き出し都道府県で図示したり、訪れたラーメン屋だけを都道府県で抜き出すことだってできます。例えば私の趣味ですが、今までに訪れた滝を色分けすればこうなります。城とすればこう。駅だとこう。他にも登った山や訪れた蒸留所や酒蔵、ブルワリー。各地の日本酒や世界の酒、料理などで色分けしても面白そうです。
残念なことにGoogle chartはまだ市町村には対応していません。もしそうなればもっと面白いライフログが作れそうです。もちろん、地図で塗り分けるほかにもGoogle chartはかなりのグラフの種類を用意しています。そこは皆様の自由です。
なお、ライフログとは、別に人様に見せて放浪癖を誇るものでも、旅行経験を自慢するものでもありません。あくまでもプライベートな利用がよろしいかと思います。ご自身の今までの人生とこれから残された人生に何を成すかを定める助けになればそれで十分です。そのあたり、ライフログについての私の考えは、このレシピ同時に書いたこちらで世に問うてみました。またご覧頂ければ幸いです。
このレシピをまとめるなら、要するにkintoneをプライベート用途に使おうよ、との主旨です。そういう使い方がもっと増えればkintoneはより身近なものになるのですから!

9.当レシピの参考にさせていただいたブログ

  目次へ↑

最後になりましたが、このレシピを作るのに、以下の5サイトを参考にさせていただきました。ありがとうございました。

 Foursquare(Swarm) APIの使い方まとめ (サンプルコード付き)
 Get Check-Ins for a User
 kintone でSQLを使う
 GoogleのGeochartを使ってみた
Google Maps Platform