kintone のアプリのデータの集計値を、別アプリに保管する（JavaScript処理）

## 処理内容
前回の [kintone のアプリのデータの集計値を、別アプリに保管する（外部API連携）](https://qiita.com/yukataoka/items/e8ce5ae93857eda51f90 "外部API連携")と同等の処理を、JavaScript で実装します。

・kintone の明細アプリにレコードが追加された直後、集計アプリの該当するコードと年月のデータを更新します。
・kintone の集計アプリに該当するコードと年月が存在しない場合は、レコードを追加します。
・集計処理を複雑にしないため、kintone の明細を更新する時には、日付とコードの入力を抑制します。

kintone は RDB のように**参照制約を設定できない**し、**トランザクション処理がない**ので、データの同期などは要注意ですね。
本来は追加したレコードだけでなく、該当年月の集計を全てやり直すべきでしょうが、、、面倒なので今回は省略します。

kitone の JavaScript だけで実装してみたい方は、[kintone のアプリのデータの集計値を、別アプリに保管する（JavaScript処理）](https://qiita.com/yukataoka/items/3a9a6e6ff6dacb6a7ccb "JavaScript処理") を参照ください。（データの整合性を守るのが大変ですが、、、。）
## kitone の設定
前回の [kintone のアプリのデータの集計値を、別アプリに保管する（外部API連携）](https://qiita.com/yukataoka/items/e8ce5ae93857eda51f90#kintone-%E3%81%AE%E8%A8%AD%E5%AE%9A "kitone の設定") を参照ください。
## kintone の JavaScriptコード
一覧表示前と、追加後、変更表示前、変更後、削除前のそれぞれイベントで処理を行います。
一覧表示は、編集、削除のボタンを非表示にします。
追加後は、集計レコードの追加か、更新を行います。
変更表示前に、日付とコードを表示項目に変えます。
変更後は、集計レコードを更新します。（集計レコードが削除される恐れは考慮せず。）
削除前に、集計レコードを更新します。

``` totalUpdate.js
(function() {
	"use strict";
	
	var TotalAppId   = "KINTONE_TOTAL_APP_ID";
	var ErrorMessage = "エラー発生。一覧画面に戻ってから再度処理を実行してください！";
	
	// 一覧表示のイベント
	var eventsIndexShow = [
		'app.record.index.show',
		'mobile.app.record.index.show'
	];
	kintone.events.on(eventsIndexShow, function(event) {
		// 明細でコードや日付を変えると処理が大変なので、変更、削除を非表示にする
		for (var i = 0; i < event.records.length; i++) {
			document.getElementsByClassName('recordlist-remove-gaia')[i].parentNode.style.display = 'none';
		}
		return event;
	});
	
	// レコード編集表示イベント
	var eventsCreateEditShow = [
		'app.record.edit.show',
	];
	kintone.events.on(eventsCreateEditShow, function(event) {
		var record = event.record;
		// 日付とコードは変更させない
		record['date']['disabled'] = true;
		record['code']['disabled'] = true;
		return event;
	});
	
	// 追加のSubmitイベント
	var eventsCreateSubmit = [
		'app.record.create.submit.success',
		'mobile.app.record.create.submit.success',
	];
	kintone.events.on(eventsCreateSubmit, function(event) 
	{
		var record = event.record;
		
		// 集計アプリのレコード参照
		var respons = GetTotalRecord(TotalAppId, record['date']['value'], record['code']['value']);
		
		// 集計アプリ追加
		if(respons == null){
			var ym = parseInt(record['date']['value'].substring(0, 4), 10) * 100 + parseInt(record['date']['value'].substring(5, 7), 10);
			var value = {
				"ym"    : { "value": ym.toString() },
				"code"  : { "value": record['code']['value'] },
				"amount": { "value": record['amount']['value'] },
			}
			if(!PostTotalRecord(TotalAppId, value)){
				event.error = ErrorMessage;
			}
			
		// 集計アプリ更新
		}else{
			var amount = parseInt(respons['amount']['value'], 10) + parseInt(record['amount']['value'], 10);
			var value = {
				"amount": { "value": amount },
			}
			if(!PutTotalRecord(TotalAppId, respons['レコード番号']['value'], value)){
				event.error = ErrorMessage;
			}
		}
		return event;
	});
	
	// 編集のSubmitイベント
	var eventsEditSubmit = [
		'app.record.index.edit.submit.success',
		'app.record.edit.submit.success',
		'mobile.app.record.edit.submit.success',
	];
	kintone.events.on(eventsEditSubmit, function(event) 
	{
		var record = event.record;
		
		// 集計アプリのレコード参照
		var respons = GetTotalRecord(TotalAppId, record['date']['value'], record['code']['value']);
		
		// 集計アプリ更新
		if(respons == null){
			event.error = ErrorMessage;
		}else{
			var amount = parseInt(respons['amount']['value'], 10) + parseInt(record['amount']['value'], 10);
			var value = {
				"amount": { "value": amount },
			}
			if(!PutTotalRecord(TotalAppId, respons['レコード番号']['value'], value)){
				event.error = ErrorMessage;
			}
		}
		return event;
	});
	
	// レコード削除イベント
	var eventsDeleteShow = [
		'app.record.detail.delete.submit',
		'mobile.app.record.detail.delete.submit',
	];
	kintone.events.on(eventsDeleteShow, function(event) {
		
		var record = event.record;
		// 集計アプリのレコード参照
		var respons = GetTotalRecord(TotalAppId, record['date']['value'], record['code']['value']);
		
		// 集計アプリ更新
		if(respons == null){
			//event.error = ErrorMessage;
		}else{
			var amount = parseInt(respons['amount']['value'], 10) - parseInt(record['amount']['value'], 10);
			var value = {
				"amount": { "value": amount },
			}
			if(!PutTotalRecord(TotalAppId, respons['レコード番号']['value'], value)){
				event.error = ErrorMessage;
			}
		}
		return event;
	});
	
	// 集計データ取得
	function GetTotalRecord(appId, date, code) 
	{
		var ym = parseInt(date.substring(0, 4), 10) * 100 + parseInt(date.substring(5, 7), 10);
		var query = ' ym = "' + ym.toString() + '"';
			query += ' and code = "' + code + '"';
		
		var appUrl = kintone.api.url('/k/v1/records', true) 
			+ '?app=' + appId
			+ '&query=' + encodeURI(query);
		
		var xmlHttp = new XMLHttpRequest();
		xmlHttp.open("GET", appUrl, false);
		xmlHttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
		xmlHttp.send(null);
		if (xmlHttp.status == 200 && window.JSON) {
			var respons = JSON.parse(xmlHttp.responseText);
			if(respons.records != null){
				return respons.records[0];
			}else{
				return null;
			}
		}else{
			console.log(xmlHttp.status);
			console.log(xmlHttp.responseText);
			return null;
		}
	}
	
	// 集計データの追加
	function PostTotalRecord(appId, value){
		var appUrl = kintone.api.url('/k/v1/record', true);
		var body = {
			"app": appId,
			"record": value,
			"__REQUEST_TOKEN__": kintone.getRequestToken()
		};
		var xmlHttp = new XMLHttpRequest();
		xmlHttp.open("POST", appUrl, false);
		xmlHttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
		xmlHttp.setRequestHeader('Content-Type', 'application/json');
		xmlHttp.send(JSON.stringify(body));
		if(xmlHttp.status == 200){
			return true;
		}else{
			console.log(xmlHttp.responseText);
			return false;
		}
	}
	
	// 集計データの更新
	function PutTotalRecord(appId, key, value){
		var appUrl = kintone.api.url('/k/v1/record', true);
		var body = {
			"app": appId,
			"id": key,
			"record": value,
			"__REQUEST_TOKEN__": kintone.getRequestToken()
		};
		var xmlHttp = new XMLHttpRequest();
		xmlHttp.open("PUT", appUrl, false);
		xmlHttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
		xmlHttp.setRequestHeader('Content-Type', 'application/json');
		xmlHttp.send(JSON.stringify(body));
		if(xmlHttp.status == 200){
			return true;
		}else{
			console.log(xmlHttp.responseText);
			return false;
		}
	}
	
})();

```
## 処理結果
・処理前の集計の一覧です。
![kintoneTtl01.png](https://qiita-image-store.s3.amazonaws.com/0/75892/39fd5ada-83c5-994e-f461-66750c269b41.png)

・集計のない明細を追加すると、集計のレコードが追加されています。
![kintoneTtl02.png](https://qiita-image-store.s3.amazonaws.com/0/75892/1d3f225f-dd91-d389-eddc-1b13607dd2e5.png)
![kintoneTtl04.png](https://qiita-image-store.s3.amazonaws.com/0/75892/7b714548-5f0d-4bae-702a-468c03d7b5e6.png)

・明細を追加すると、集計金額が更新されます。
![kintoneTtl05.png](https://qiita-image-store.s3.amazonaws.com/0/75892/48b9d7e9-96b3-e4ef-c1d9-6f2e596d6a7e.png)
![kintoneTtl06.png](https://qiita-image-store.s3.amazonaws.com/0/75892/5eaf0d7c-c6a1-acc2-e9fd-d06136f8f1a1.png)

やはり kintone は RDB のように**参照制約を設定できない**し、**トランザクション処理がない**ので、データの同期などは誰も操作しない夜間に、AWS の Lamda とか利用して更新するのが安全ですね。

AWS の Lamda で更新する方法については、以下参照ください。
[kintone のアプリのデータの集計値を、別アプリに保管する（外部API連携）](https://qiita.com/yukataoka/items/e8ce5ae93857eda51f90 "外部API連携")