どうも。つじけ(tsujikenzo)です。このシリーズでは、「会計freeeAPIクラスを作ろう」について、全3回でお送りいたします。今日は3回目です。
前回のおさらい
前回は、「クラスで楽をしよう」をお届けしました。
今回は、「スプレッドシートで楽をしよう」をお届けします。
今日のアジェンダ
- パラメータ用スプレッドシート
- リクエストボディ用スプレッドシート
- 出力用スプレッドシート
パラメータ用スプレッドシート
今回は、コンテナバインドスクリプトでコードを書いています。
せっかくスプレッドシートが使えますので、freeeAPIクラスの手助けをしてくれるシートとして活用したいところです。
よく使うもので、コードに書いたり管理がめんどくさくて、人間の目にやさしいインターフェイスが欲しいのが、「パラメータ」です。
コンテナバインドスクリプトを、パラメータ作成用に活用しましょう。
データ種別ごとのシート
クラスと同じように、シート構造もクラスと一致させるとわかりやすいでしょう。
シート名の先頭には**parameters_**というキーワードをつけておきます。
クラスParameterを作成します。インスタンス生成時にシート名の後半を受け取るように待ち構えておきます。
/**
* パラメータを作成するクラス
*/
class Parameters {
/** シート名を作るコンストラクタ */
constructor(sheetName) {
this.sheetName = `parameters_${sheetName}`;
}
/**
* シートを掴むメソッド
* @return {Object} sheetObject
*/
getSheet() {
return SpreadsheetApp.getActiveSpreadsheet().getSheetByName(this.sheetName);
}
}
インスタンス生成時に、シート名の後半を渡すというのは、こういうことです。
const p = new Parameters('account_items');
もちろん、クラスAccount_items内に記述されます。
/**
* 勘定科目に関するクラス
* @extends {ApiRequests}
*/
class AccountItems extends ApiRequests {
/** urlを作るコンストラクタ */
constructor() {
super();
this.url = `${this.baseUrl}account_items?`;
}
/**
* すべての勘定科目を返すメソッド
* @return {Array} items
*/
getAccountItems() {
const p = new Parameters('account_items');
//処理
}
オブジェクト指向っぽいですね。
パラメータ入力用セル
スプレッドシートには、このように、パラメータと入力用の枠を用意しておきます。
公式リファレンスから、項目について解説を貼り付けてあげると親切ですね。
ここに入力された値を、GASで取得します。
クラスParameterに、getQueries()メソッドを定義してみました。
/**
* シートから連結したクエリ文字列を返すメソッド
* @return {string} queries - e.g 事業所ID&件数&offset
*/
getQueries() {
const sheet = this.getSheet();
const values = sheet.getDataRange().getValues();
const noEmptyValues = values.filter(value => value[1] !== ''); //[1]はシートのB列
const strValues = noEmptyValues.map(value => `&${value[0]}=${value[1]}`)
const queries = strValues.join('').slice(1); //先頭の&を削除
return queries;
}
このようにリクエストURLを生成できます。(実際にはリクエストURLはfetchされるので、内部に組み込まれています)
/**
* TEST用関数
*/
function testAccountItems() {
const a = new AccountItems();
const p = new Parameters('account_items');
const queries = p.getQueries();
const url = a.url + queries;
console.log(url); //https://api.freee.co.jp/api/1/account_items?company_id=3293428
}
リクエストボディ用スプレッドシート
コンテナのスプレッドシートは、パラメータだけでなく、リクエストボディの作成にも応用できるでしょう。
リクエストボディシートを掴むために、クラスParameterにインスタンス変数とメソッドを追記します。
/**
* パラメータを作成するクラス
*/
class Parameters {
/** シート名を作るコンストラクタ */
constructor(sheetName) {
this.sheetName = `parameters_${sheetName}`;
this.requestbodySheetName = `requestbody_${sheetName}`
}
/**
* シートを掴むメソッド
* @return {Object} sheetObject
*/
getSheet() {
return SpreadsheetApp.getActiveSpreadsheet().getSheetByName(this.sheetName);
}
/**
* リクエストボディシートを掴むメソッド
* @return {Object} sheetObject
*/
getRequestBodySheet() {
return SpreadsheetApp.getActiveSpreadsheet().getSheetByName(this.requestbodySheetName);
}
/**
* シートから連結したクエリ文字列を返すメソッド
* @return {string} queries - e.g 事業所ID&件数&offset
*/
getQueries() {
const sheet = this.getSheet();
const values = sheet.getDataRange().getValues();
const noEmptyValues = values.filter(value => value[1] !== ''); //[1]はシートのB列
const strValues = noEmptyValues.map(value => `&${value[0]}=${value[1]}`)
const queries = strValues.join('').slice(1); //先頭の&を削除
return queries;
}
/**
* リクエストボディシートからvaluesを返すメソッド
* @return {Array} values
*/
getRequestBodyValues() {
const sheet = this.getRequestBodySheet();
return sheet.getDataRange().getValues();
}
}
パラメータを作成する処理と、リクエストボディを作成する処理は、無関係です。
このように、処理が関わり合っていないことを、疎結合と呼びます。
ソースコードをできるだけ疎結合にすることで、「ここを変更したら、どこに影響でるか分からないから怖いなぁ」ということがなくなります。
疎結合にするために、メソッドもちいさく、(1行でもメソッドに分けたりするそうです。)、1つのメソッドでやることは1つなどのお作法がありますが、いつかまとめたいと思います。
出力用スプレッドシート
最後に、出力用のスプレッドシートです。
出力用のスプレッドシートは、「取引先別」や「収支/支出別」や「支払期限別」など、いくつあってもいいと思います。
取引先がそこまで多くないのなら、あらかじめシートを用意してもいいと思います。
よく、「取引先が変更になったときでも耐えられるようなソースコードを書きたいな」と思ってしまうところですが、これはわたしの悪いクセです。
取引先の名前が変更になったら、手で変更すればいいのです。
追加されたらシートを増やせばいいのです。
それを、Partnersクラスから、Partners一覧を取得して、一覧からシートを作成・管理しよう、と意気込んでしまうので、本来の目的がなかなか達成できなくなるのです。
プロパティとフィールド名
おなじことが、フィールド名に使うJSONプロパティにも言えると思います。
会計freeeAPIで返されるJSONは、無限ではありませんし、そんなしょっちゅうプロパティが変更されるものではありません。
特に抽出したいプロパティが決まっているなら、フィールド名としてベタ書きしてもかまわないと思います。
たいせつなのは、配列のインデックスで取らずに、連想配列で処理することです。
dealsクラス
と、ながくなってしまいましたが、実はdealsクラスをきれいに書けていません。。。
とりあえずdealsをスプレッドシートに出力するものは、書きましたのでUPしておきます。
Parameterシートのリクエストボディを利用した、dealsへのPOSTも書けています。
/**
* 取引(収入/収支)に関するクラス
* @extends {ApiRequests}
*/
class Deals extends ApiRequests {
/** urlを作るコンストラクタ */
constructor() {
super();
this.url = `${this.baseUrl}deals?`;
this.postParams.payload = this.getRequestBody();
}
/**
* 取引(収入/収支)JSONを取得するメソッド
* @return {Object} json
*/
getDeals() {
const p = new Parameters('deals');
const queries = p.getQueries();
const url = this.url + queries;
const json = this.fetchRequest(url, this.params); //fetchRequest()とparamsはsuperclassから
const deals = json.deals;
return deals;
}
/**
* 取引(収入/収支)を登録するメソッド
* renews, receiptsには非対応
* @return {Object} json
*/
getRequestBody() {
const p = new Parameters('deals');
const values = p.getRequestBodyValues();
//最終的なリクエストボディオブジェクトを作るための初期化
const requestBody = {};
//details、paymentsの中身オブジェクトを作るための初期化
const detailsObj = {};
const paymentsObj = {};
//requestBodyを作る処理
values.map(value => {
//空白セルの排除
if (value[1] === '') return;
//value[0]にキーワードが入ってるかどうか
const isDetails_ = value[0].includes('details_');
const isPayments_ = value[0].includes('payments_');
/** detailsを整形 */
if (isDetails_) {
const property = value[0].replace('details_', '');
detailsObj[property] = value[1];
requestBody['details'] = [detailsObj];
}
/** paymentsを整形 */
if (isPayments_) {
const property = value[0].replace('payments_', '');
paymentsObj[property] = value[1];
requestBody['payments'] = [paymentsObj];
}
/** それ以外を整形 */
if (!(isDetails_) && !(isPayments_)) requestBody[value[0]] = value[1];
});
const json = JSON.stringify(requestBody);
return json;
}
/**
* 取引(収入/収支)を登録するメソッド
* @return {Object} json
*/
postDeal() {
const json = this.fetchRequest(this.url, this.postParams); //fetchRequest()とpostParamsはsuperclassから
return json;
}
/**
* 取引(収入/収支)Valuesをすべて取得するメソッド
* @return {Object} values
*/
getDealValues() {
const deals = this.getDeals();
const values = deals.map(deal => this.getFullArray_(deal)).flat();
return values;
}
/**
* 単体dealを、2次元配列に変換するサブメソッド
* @param {Object} 単体deal
* @return {Array} dealの2次元配列化
*/
getFullArray_(deal) {
//最上層のプロパティを格納
const upperProperties = Object.values(deal);
//detailsプロパティを格納
const details = deal['details'];
const detailsArray = details.map(detail => Object.values(detail));
//paymentプロパティを格納
const payments = deal['payments'];
//deal['payments']プロパティが無い場合、paymentsArrayを作る
let paymentsArray; //let宣言が必要
try {
paymentsArray = payments.map(payment => Object.values(payment)).flat();
} catch{
upperProperties.push(''); //配列の要素数を合わせる為
paymentsArray = ["id", "date", "from_walletable_type", "from_walletable_id", "amount"];//freeeでプロパティの増減仕様変更があると動かなくなる
}
//detailsプロパティを基点に、fullArrayを整形
const fullArray = detailsArray.map(detail => {
return [...upperProperties, ...detail, ...paymentsArray];
}
);
return fullArray;
}
}
クラス作成に終わりはありませんので、今後もアップデートしていきたいと思います。
まとめ
以上で、「スプレッドシートで楽をしよう」をお届けしました。
ノンプロ研freee講座をこれから受講されるみなさまを応援いたします!
ソースコードは、Githubで公開しています。