##伝票情報のようなポータルを含むレコード内容を、JSON形式で取得し、別ファイルに渡したい
FileMakerの解説書とかでもよくある見積伝票から請求伝票へのレコード転記で、今まではExecuteSQLで行っていた伝票の転記処理を、JSON関数を使ってシンプルに分かりやすく処理する方法をまとめてみました。
一番下にサンプルファイル置いています。
ポータルを含むレイアウトの丸ごと転記処理は、そこそこFileMakerをいじっている人にとってもハードルが高く、スクリプトも長くなって見ただけでやる気が失せます。でもJSONだと見通しもよく、かなりシンプルなスクリプトになります。
もちろんJSONの表記法を学習する必要がありますが、ここで使うJSON形式ってハードルがとても低い。
今後転記処理はこの方法を使ったほうがいいのかなと思い書いてみました。JSON形式に整形しておけばいろんな利用方法がありますし。
###伝票構成は以下のテーブル構成とします。鉄板構成です。
- 商品(マスター)
- 得意先(マスター)
- 伝票
- 明細(伝票レイアウトにポータルで配置)
###リレーションは
- 伝票IDで伝票と明細を
- 得意先IDで伝票と得意先を
- 商品IDで明細と商品を
という基本的な構成
で、いきなりですがここから最大のミソになります。というかこれでほぼ終わり。
伝票本体にJSON取得用のフィールド(計算値)を作成します。
伝票テーブルの一つのテキストフィールドに、ポータルに配置した伝票明細の内容まで全てをJSON形式で持たせます。荒技ですが。
その計算式を以下に。
#レコード内の情報を全部JSONで取得する計算式
/*伝票明細行をJSON形式で取得する再帰計算式
JSONの型は見やすいので数字で指定しています。一応参考までに対応リスト
1=JSONString テキスト
2=JSONNumber 数字
3=JSONObject オブジェクト
4=JSONArray 配列
*/
Let
(
[
$max=ValueCount (List(明細::kf_商品ID)); //ポータル行数取得
$cnt=0; //$json用のカウンター
$n=1; //ポータル行用のカウンター(JSON用の0始まりと区別するため)
$json=JSONSetElement ( "{}";"";"";3 ); //JSONデータの初期状態
/*まず伝票本体の主要素をルートオブジェクトに入れていく
4つ目、最後にポータル行の配列構造を明細として作成しておく
*/
$json=JSONSetElement ( $json;
["伝票_ID";伝票::伝票_ID;2];
["得意先_ID";伝票::kf_得意先ID;2];
["日付";伝票::日付;1];
["明細";"";4]
);
/*以下の$funcで明細行の取得用再帰処理*/
$func=
"
Case(
/*最初に終了条件 $json用のカウンターがポータル行数と同じになったら終了*/
$cnt = $max;$json;
/*終了条件に合致しない間は以下を繰り返す*/
/*ポータルの内容を$lineitemに入れていく。この中ではクオーテーションマークの前にバックスラッシュを付けないとダメ。*/
Let(
[
/*毎回$lineitemを初期化*/
$lineitem=\"\";
$lineitem=JSONSetElement ($lineitem;
[\"商品_ID\";GetNthRecord ( 明細::kf_商品ID ;$n);2];
[\"納品数\";GetNthRecord ( 明細::納品数 ;$n);2];
[\"販売区分\";GetNthRecord ( 明細::販売区分 ;$n);1]
);
/*$json(親)に$lineitemを入れる*/
$json=JSONSetElement($json;\"明細[\"&$cnt&\"]\";$lineitem;3);
$cnt=$cnt+1; /*json用のカウントアップ*/
$n=$n+1 /*ポータル用のカウントアップ*/
];
Evaluate($func) /*再帰処理*/
)/*子letの終わり*/
) /*caseの終わり*/
"
];
If(Left ( Evaluate($func) ; 1 ) <> "?"; /*ちゃんとjson形式で取れているかの確認*/
Evaluate($func);""
)/*ifの終わり*/
)/*親letの終わり*/
自分はこの再帰計算式作るのが最大の難関でした。再帰計算式の作成はなじみが薄いし、デバッグができない。そもそもJSON初心者なので思い通りの型で取り出すのに手間取った。数日戦った。おかげでJSONにだいぶ馴染んで来ました。
しかしこのEvaluateを使った再帰計算を発案した人ほんと凄い。
再帰上限が250程度なので大量のレコード処理は不向きですが、今回のようなケースには最適です。素晴らしい。
この公式?を使うたびに、なんでこの計算式で再帰するのか狐につままれたような感覚になります。そうなんです、あらかた公式として作法を丸暗記して使わせて頂いています。手品に近い。
JSON形式で持たせておけば8割方完成したようなもの。そのフィールドたった一つを別テーブルの同じくJSON用のテキストフィールドに渡すだけです。リレーションも関係なし。これは本当に楽だ。
例として見積書から請求書(ここでのファイル名はJSON_Receiver)への転記をあげていますが、自分はFileMakerGoからホストされたファイルへのアップロード時間を短縮するのが目的で作成しました。移動中にiPhoneのFileMakerGoから販売データをアップロードする場面で、たまにコンビニのWiFiに絡まれたりして送信エラー起こすことがあるのです。そういった場合もエラー処理はしていますが、とにかくスクリプトが長くなり、メンテナンス性が非常に悪かったのです。その解決策として極力データ送信の時間を短縮する方法を模索していました。
複数のレコードをまとめて転記する時は、給前悟郎氏が公開されているGetFieldValuesというカスタム関数を使えば、たった一行でそのJSONテキストを改行区切りでリスト化して引数として渡せますが、Advanced版以外も想定して愚直にLoopでリスト化しています。
#決して正しい処理ではないが、転送時間短縮が目的です。
各レコードにJSONデータを持たせない場合は、このデータ転送スクリプト発動時に各レコードの値を一件一件LOOPでJSONに束ねて行きます。本当はそれが正解かつ王道です。普通です。
でも伝票毎のフィールドにシャドーテキストとしてJSONデータを持たせておくと、そのLOOP処理の時間をカットできるし、スクリプトがとてもシンプルになるのでいいかなと。伝票1件が持つ明細行なんて知れたもんですから、非保存の計算式フィールドでテキストを持ったとしてもそんなに負荷がかかるとは思えません。
参考までに上のLoopで伝票を1レコード毎にJSONデータとして束ねていく処理をスクリプトでやるとこうなります。
まあこちらもたいした手間ではないですね(笑)。
通信が安定してるなら普通はこっちです。
##受け取ったJSONデータの展開
受け取った側(ここではファイル名JSON_Receiver)は、今度は逆に受け取ったそのJSONテキストを各フィールドに割り当てていけばいいだけ。非常に分かりやすく、スクリプトもパッと見で内容が分かりますね。
受け取ったJSONデータを各フィールドに配置していくスクリプトは非常に簡素なものになります。
##転送されてきたデータを新規レコードで追加していくスクリプト
(ここでは受け取った伝票の主要素(伝票ID・得意先ID・日付)フィールドは、計算式でjsonフィールドから自動的に抜き出す設定にしているのでそのステップがないですが、それぞれフィールド設定で埋めていくのが普通ですね。)
JSONにはまだ慣れてないので本当にこのJSON形がベストなのかはわかりません。自分が再利用しやすいようにやってみただけです。
APIとかバンバン利用したいのでかじり出したJSON関数ですが、案外このような利用の方がFileMaker的には使いどこがあったりして。
次のバージョンではレコードのエスクポートでCSVやExel形式に加えてJSONってのも当然来そうですね。
##最後にここで使ったサンプルファイルを以下に置いておきます。
要FMP16
突っ込みどこ満載ですがご容赦下さい。
フォルダでアップするの忘れたので2つのファイルを同じフォルダに入れて使って下さい。
サンプルファイル