ゲストスペース内のアプリのレコードとゲストスペース外のアプリのレコードを同期する

# はじめに
kintoneには、kintoneの利用ユーザー以外の人が、ゲストとして参加できるスペース「ゲストスペース」があり、1ユーザーあたりの料金も安くなっており、運用方法を決めれば非常に有効だと思われます。しかし、ゲストスペースには機能が限られていることも欠点のひとつです。
そのなかのひとつに、kintoneにはアプリ間でレコードを連携できる便利機能「[アプリアクション](https://jp.cybozu.help/k/ja/user/app_settings/appaction/whats_appaction.html)」がありますが、ゲストスペースではゲストスペース内でのアプリでしか使用できず、ゲストスペース外に連携することはできません。
今回は、javascriptとkintoneAPIを用いて、ゲストスペース内のアプリのレコードとゲストスペース外のアプリのレコードを同期するためのjavascriptのコードを作成しましたので共有します。

# 成果物
## 動作内容

* ゲストスペース側のアプリ(以下、登録アプリとする)で登録したレコードが、ゲストスペース外のアプリ(以下、管理アプリとする)と同期する
* 管理アプリからは、登録アプリのレコードにジャンプすることもできる



## 想定される使用シーン

* ゲストスペースが複数存在して同じアプリが使用される
* ゲストスペースで入力した項目を集計などに利用したい
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90313/675d0c3c-6b1d-393b-e44a-452eedfa67aa.png)

# 実装
## ゲストスペース側登録アプリの画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90313/71617f98-a579-0bab-a4e4-ce95006706ec.png)


登録するデータの内容は下記のとおり。

|データ名|項目内容|
|:--|:--|
|レコードNo.|レコードを登録すると自動で登録される   |
|ゲスト入力項目|今回のアプリでは便宜的にレコード(文字列)、レコード(複数行)、レコード(ラジオボタンの3つ)|
|to_record_ID|管理アプリ側のレコードNo.|

## 管理アプリ(ゲストスペース外)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90313/d4d83dc7-92d1-de00-00eb-cb10c8661b96.png)

登録するデータの内容は下記のとおり。

|データ名|項目内容|
|:--|:--|
|レコードNo.|レコードを登録すると自動で登録される。編集・削除の同期に必要|
|ゲスト入力項目|登録アプリで登録された内容が自動で転記される。|
|ゲストスペース側登録アプリURL|登録アプリのレコードを参照するURL|

# 実装
## ソースコード
全ソースコードはgithubに掲載します。
https://github.com/iamakawa/kintone_guestspace_syncsample

## 管理アプリのアプリIDの登録
管理アプリのIDを入力しておく。管理アプリのIDを開いたときにの、「YYY」の数値を入力すること。
https://DOMAINNAME.cybozu.com/k/YYY/

```javascript
//管理用アプリNo.
var TO_APP_ID = xx;
```

## イベントハンドラの登録

* javascript APIの一覧を参考に、下記のようにイベントハンドラを設定。
* 一覧での編集/削除とレコード詳細画面での編集/削除にそれぞれ異なるAPIが使われていることに注意。(本当はモバイルも違うが、今回は対象外)
	* 
APIの一覧については、巻末の参考資料を参考。

|イベント発生タイミング|API名|実行する処理|
|:--|:--|:--|
|レコード追加完了|app.record.create.submit.success|[レコード新規作成完了時の処理](https://qiita.com/iori_ama/items/b100e0172d141cdf2efd#%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89%E6%96%B0%E8%A6%8F%E4%BD%9C%E6%88%90%E5%AE%8C%E4%BA%86%E6%99%82%E3%81%AE%E5%87%A6%E7%90%86)|
|レコード編集完了|app.record.edit.submit.success|[レコード編集完了時の処理](https://qiita.com/iori_ama/items/b100e0172d141cdf2efd#%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89%E7%B7%A8%E9%9B%86%E5%AE%8C%E4%BA%86%E6%99%82%E3%81%AE%E5%87%A6%E7%90%86)|
|レコード削除完了|app.record.detail.delete.submit|[レコード削除完了時の処理](https://qiita.com/iori_ama/items/b100e0172d141cdf2efd#%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89%E5%89%8A%E9%99%A4%E5%AE%8C%E4%BA%86%E6%99%82%E3%81%AE%E5%87%A6%E7%90%86)|
|一覧画面でのレコード編集完了|app.record.index.edit.submit.success|[レコード編集完了時の処理](https://qiita.com/iori_ama/items/b100e0172d141cdf2efd#%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89%E7%B7%A8%E9%9B%86%E5%AE%8C%E4%BA%86%E6%99%82%E3%81%AE%E5%87%A6%E7%90%86)|
|一覧画面でのレコード削除完了|app.record.index.delete.submit|[レコード削除完了時の処理](https://qiita.com/iori_ama/items/b100e0172d141cdf2efd#%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89%E5%89%8A%E9%99%A4%E5%AE%8C%E4%BA%86%E6%99%82%E3%81%AE%E5%87%A6%E7%90%86)|

```javascript
//イベントハンドラ
kintone.events.on('app.record.create.submit.success', function(event) {
  connect_new_record(event);
});
kintone.events.on('app.record.index.edit.submit.success', function(event) {
  connect_edit_record(event);
});
kintone.events.on('app.record.edit.submit.success', function(event) {
  connect_edit_record(event);
});
kintone.events.on('app.record.index.delete.submit', function(event) {
  connect_delete_record(event);
});
kintone.events.on('app.record.detail.delete.submit', function(event) {
  connect_delete_record(event);
});
```

## レコード新規作成完了時の処理

```javascript
// レコード新規作成時の動作
function connect_new_record (event) {
  var record = event["record"];
  ["レコード番号", "更新者", "作成者", "更新日時", "作成日時", "$revision", "$id", "to_record_ID"].forEach(e => {delete record[e]});
  
  //登録側ゲストスペースアプリの参照URLを生成
  var url = kintone.api.url('/k/v1/record',true)
  url = url.substr(0,url.indexOf("/v1/"))
  record["from_URL"] = {"value":url + "/" + event["appId"] +"/show#record=" + event["recordId"]}

  var post_body = {
      'app': TO_APP_ID,
      'record': record
  };
  var put_body = {}

  return kintone.api(kintone.api.url('/k/v1/record'), 'POST', post_body).then(function(resp) {
      // 登録側ゲストスペースアプリのレコードに管理側アプリ側のIDの値を登録
      put_body = {
        'app' : event["appId"],
        'id' : event["recordId"],
        "record": {
          "to_record_ID" : {"value" : resp["id"]}
        },
      };
    },
    function(error) {
      alert("post-error:" + JSON.stringify(error));
    }).then(function() {
    return kintone.api(kintone.api.url('/k/v1/record', true), 'PUT', put_body).then(function(resp_2){
      alert("レコードの新規作成-接続完了");
    },
    function(error_2) {
      alert("put-error:" + JSON.stringify(error_2));
    });
  });
};
```

実施する処理は下記のとおり。

1. レコード新規作成時のイベントハンドラ返り値から、登録した登録アプリのレコード情報を取得
2. 不要なフィールドを削除
3. 管理アプリにレコード情報を転記、Promise処理でレコードのIDを取得
4. レコードIDを登録アプリのレコードにも記録する(to_record_ID)

## レコード編集完了時の処理

```javascript
// レコード編集時の動作
// 登録側ゲストスペースアプリに登録された管理側アプリのレコードIDを使って、管理側アプリのレコードを更新する
function connect_edit_record (event) {
  var get_body = {
    "app" :event["appId"],
    "id"  :event["recordId"]
  };
  var put_body = {};
  return kintone.api(kintone.api.url('/k/v1/record', true), 'GET', get_body).then(function(resp){
    var record = resp["record"];
    var to_record_id = record["to_record_ID"]["value"];
    ["レコード番号", "更新者", "作成者", "更新日時", "作成日時", "$revision", "$id", "to_record_ID"].forEach(e => {delete record[e]});

    put_body = {
      'app' : TO_APP_ID,
      'id'  : to_record_id,
      'record': record
    };
    }).then(function() {
      return kintone.api(kintone.api.url('/k/v1/record'), 'PUT', put_body).then(function() {
        alert("レコードの更新-接続完了");
      },
      function(error_2) {
        alert("put-error:" + JSON.stringify(error_2));
      });
  });
};

```

実施する処理は下記のとおり。

1. レコード編集完了時のイベントハンドラ返り値から、編集した登録アプリのレコード情報を取得
2. 不要なフィールドを削除
3. 管理アプリにレコード情報を転記。この際、to_record_IDに登録されたレコードIDを参照している。



## レコード削除完了時の処理

```javascript
// レコード削除時の動作
// 登録側ゲストスペースアプリに登録された管理側アプリのレコードIDを使って、管理側アプリのレコードを削除する
function connect_delete_record (event) {
  var delete_body = {
    "app" : TO_APP_ID,
    "ids" : [event["record"]["to_record_ID"]["value"]]
  };

  return kintone.api(kintone.api.url('/k/v1/records'), 'DELETE', delete_body).then(function(resp) {
    alert("レコードの削除-接続完了");
  },
  function(error) {
    alert("削除エラー" + JSON.stringify(error));
  });
}
```

実施する処理は下記のとおり。

1. レコード削除完了時のイベントハンドラ返り値から、削除した登録アプリのto_record_IDを取得
2. 管理アプリの同じレコードを削除。この際、to_record_IDに登録されたレコードIDを参照している。


# おわりに

まずは、ゲストスペース側の登録アプリに登録されたレコードを同期することができました。今後、より使いやすくするために、様々な課題はありますが、それは適宜対応します。

## 今後の課題

* モバイルアプリのAPI対応
* パラメータ対応などで、「転記しないフィールド」を作る

## 参考サイト

* [ゲストスペース内アプリのレコードを、スペース内アプリに転記](https://developer.cybozu.io/hc/ja/articles/115003525206-%E3%82%B2%E3%82%B9%E3%83%88%E3%82%B9%E3%83%9A%E3%83%BC%E3%82%B9%E5%86%85%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AE%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92-%E3%82%B9%E3%83%9A%E3%83%BC%E3%82%B9%E5%86%85%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AB%E8%BB%A2%E8%A8%98)
* [kintoneにおけるPromiseの書き方の基本](https://developer.cybozu.io/hc/ja/articles/360023047852-kintone%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8BPromise%E3%81%AE%E6%9B%B8%E3%81%8D%E6%96%B9%E3%81%AE%E5%9F%BA%E6%9C%AC)
* [kintone javascriptAPIイベント一覧](https://developer.cybozu.io/hc/ja/articles/360000361686-kintone-JavaScript-API-%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88-%E4%B8%80%E8%A6%A7)