システムの目的
グループ企業全体の管理業務を一括で行う方法をシェアードオフィスと言います。 法人格を分けることは、独立採算の責任を明確にし、将来のマネジメント層を育成し、モチベーションを引き出すのに有効な方法です。 一方、間接部門のオーバーヘッドがデメリットですが、IT化されたシェアードオフィスを設けることでデメリットをなくします。 このようなシェアードオフィス用ERPを短時間で構築します。
※このページの最後に今回のプロトタイプのテンプレートを添付しています。 実際に動かしてみたいという方はぜひご活用ください。
システム化の方針
今回は、ワークフローマネジメントシステムにコラボスタイル社の「コラボフロー」を使います。
発注申請以降の購買プロセスを例にシステム構築します。
購買のワークフローを各社毎に構築するのでは管理コストが増えますので、グループで共有します。
そして、申請者の組織情報をもとに、下図のようにデータを渡すツバイソの法人アカウントを自動的に切り替えるという事例です。
いつもと同様、専用業務フローを作ることで、入力項目を最小限にします。
シナリオ
・発注申請〜承認(コラボフロー)
幡ヶ谷システム株式会社 営業1課の里見 駿さんは消耗品の発注申請をしました。
*新規文書から「消耗品・書籍購入申請書」を選択します。
↓ ↓
*必要事項を記入します。
↓ ↓
*内容を確認して申請します。
同様に野多目コンサル株式会社 営業3課の二乃浦 凛さんは書籍の発注申請をしました。
金額基準で、課長、部長の承認が必要となる場合がありますが、承認されれば、調達部門の管理部へワークフローが回ります。
*里見駿さんが申請した棚の購入は課長の承認が必要だったので、承認作業を行っています
・発注(コラボフロー)
調達部門は承認された発注申請書の内容を発注します。発注するとkintoneにデータが連携します。
発注部門はシェアードオフィスですから、グループ企業全ての処理を行います。
*管理部管理太郎さんが発注先などを選択して発注を行います
*同様に里見 駿さんが提出した申請書も 発注先や品番など記入して発注します。
↓ ↓ ↓
・発注/検収管理(kintone)
発注済リストを元に納品の有無を管理します。納品された場合は納品物をシェアードオフィスの検収担当の総務チームが検収します。検収日をもってツバイソにデータが連携します。
*明細の詳細画面を表示します。
↓ ↓
*編集画面に入り、検収日に日付を入力して保存ボタンを押します。
↓ ↓
↓ ↓ ↓
*ツバイソに登録されます。
{仕訳}
*同様に里見駿さんが申請した棚の購入も検収されました。
↓ ↓ ↓
{仕訳}
・支払管理、債務管理、財務/管理会計(ツバイソ)
野多目コンサル株式会社 営業3課の検収データは、野多目コンサル株式会社のツバイソの購買モジュールに、営業3課の費用として計上されます。
同様に、幡ヶ谷システム株式会社 営業1課の検収データは、幡ヶ谷システム株式会社のツバイソの購買モジュールに、営業1課の費用として計上されます。
以降は、他のワークフローから登録された購買情報をまとめて、各社ごとの銀行口座から総合振込のFBデータを作成し、支払い、消し込み、会計処理を自動的に行います。
《野多目コンサル株式会社フロー(ツバイソ)》
*仕入・経費明細に計上された明細のデータから自動的にFBデータが作成されて、支払予定表のページからダウンロードできます。
↓ ↓
*FBデータを利用し支払を行ったら、銀行明細に計上します。
{仕訳}
↓ ↓
*その後支払・消込明細から一括消込を利用し消込を行います
↓ ↓
{仕訳}
以上で、管理会計含めた購買プロセスが一巡します。
ツバイソ連携用javascript
ツバイソへの連携は、kintoneの「検収日」のデータが存在し、「保存ボタン」を押されたことをトリガーに行っています。
javascriptコードのポイントを説明します。
・会社コードとアクセストークンを設定する
まずは、各会社ごとの会社コードとアクセストークンを記入します。kintoneからツバイソにデータを同期する際個々の会社コードと申請書の会社コードを比較してどの会社のアクセストークンが必要なのか判断します。なのでここの記入が間違っていると正しい登録ができなくなります。
"会社コード" : "アクセストークン"
という形式で、カンマ区切りで複数の会社を並べていきます。
今回の場合会社コードは、幡ヶ谷システム株式会社はHS、野多目コンサル株式会社はNSになっています。
var ACCESS_TOKENS = {
"HS" : "ここにアクセストークンを入れてください", ←幡ヶ谷システム株式会社
"NS" : "ここにアクセストークンを入れてください" ←野多目コンサル株式会社
};
・会社コードと部門コード
会社コード・・・kintoneからツバイソにデータを登録する時に申請書がどの会社から送られてきた申請書なのかkintone上で判断するために使います、kintone上でしか使わないので任意のコードを決めて使えます
部門コード・・・ツバイソ上で明細を登録するときにどの部門のデータなのかを判断するために使います、ツバイソ内の部門の部門コードと一致させることで明細の部門を自動登録するのでツバイソ上の部門コードと一致させてください
この二つのコードはコラボフロー上ではクループコードの値として「会社コード-(ハイフン)部門コード」という形で登録されています。
ex. 幡ヶ谷システム営業1課のグループコード → HS-SalesK
kintone上では申請部門の値として自動登録され、ツバイソに登録しようとするときにJavascriptでデータが変換されます。
変換方法は、ハイフンで分割して1つ目は会社コード、二つ目は部門コードとみなされます。
ex. HS-SalesK → HS, SakesK
変換したデータは以下のコードで利用されています。
// 共通の設定
commonApJson['accrual_timestamp'] = kintoneRecord['Accept_date']['value'];
commonApJson['customer_master_code'] = kintoneRecord['OrderDestination']['value'];
commonApJson['reason_master_code'] = kintoneRecord['ReasonCode']['value'];
if (kintoneRecord['Division']['value']) {
commonApJson['dept_code'] = kintoneRecord['Division']['value'].split('-')[1];
}
// ---------------------------------------------------
// アクセストークン選択
// ---------------------------------------------------
function selectAccessToken(division)
{
if (division) {
var corp = division.split('-')[0];
if (corp in ACCESS_TOKENS) {
return kintone.Promise.resolve(ACCESS_TOKENS[corp]);
}
}
return kintone.Promise.reject('選択中の法人に該当するアクセストークンがありません');
}
・ツバイソ形式のデータへの変換
kintoneのデータはツバイソに登録できるようにデータの形式を変える必要があります。
// 固定の設定
commonApJson['dc'] = 'c'; //増加
commonApJson['tax_code'] = 1003; //共通売上分一般仕入(8%)
commonApJson['port_type'] = 1; //国内
commonApJson['need_tax_deduction'] = 0;
上記のコードではkintoneアプリから明細を登録するときすべての明細に適用される条件を設定しています。各設定の値はツバイソ上で使われている値を入力します。
// 共通の設定
commonApJson['accrual_timestamp'] = kintoneRecord['Accept_date']['value'];
commonApJson['customer_master_code'] = kintoneRecord['OrderDestination']['value'];
commonApJson['reason_master_code'] = kintoneRecord['ReasonCode']['value'];
if (kintoneRecord['Division']['value']) {
commonApJson['dept_code'] = kintoneRecord['Division']['value'].split('-')[1];
}
for (var i = 1; i <= 5; i++) {
if (kintoneRecord['ItemName' + i]['value']) {
var apJson = clone(commonApJson);
apJson['price_including_tax'] = kintoneRecord['ItemPrice' + i]['value'];
apJson['memo'] = kintoneRecord['requestUsername']['value'] + '/'
+ kintoneRecord['ItemName' + i]['value'] + '/'
+ kintoneRecord['ItemQuantity' + i]['value'] + '/'
+ kintoneRecord['ItemUnitPrice' + i]['value'] + '/'
+ kintoneRecord['Purpose']['value'] + '/'
+ kintoneRecord['Remarks']['value'];
上記のコードはkintoneアプリからツバイソに登録する明細の個々の情報をツバイソの形式に変換しています。
それぞれのコードが何をしているのかもっと詳しく知りたい方は下記を参照しながら確認してみてください。
・GitHubのTsubaiso API
・cybozu.com developper network のkintone API
・collaboflow(コラボフロー)サポートサイト
最後に、ソースコード全文を載せておきます。
ソースコード全文
var ACCESS_TOKENS = {
"HS" : "ここにアクセストークンを入れてください",
"NS" : "ここにアクセストークンを入れてください"
};
var API_AP_POST_URL = "https://tsubaiso.net/ap_payments/create";
// ---------------------------------------------------
// kintoneのイベントハンドラを登録する
// ---------------------------------------------------
(function () {
"use strict";
kintone.events.on(['app.record.create.submit', 'app.record.index.edit.submit', 'app.record.edit.submit'], function(event) {
if ( (event.record['Accept_date']['value'] != null)
&& (event.record['Accept_date']['value'] != '' ) ) {
var apPostRecords;
var accessToken;
event.record['Division']['value'] = event.record['Division']['value'].replace(/^\s+|\s+$/g, "");
return selectAccessToken(event.record['Division']['value'])
.then(function(retAccessToken) {
accessToken = retAccessToken;
apPostRecords = convertToApJson(event.record);
return postTsubaisoRecordsWrapper(API_AP_POST_URL, apPostRecords, accessToken);
}).then(function(resp) {
return event;
}).catch(function(error) {
console.log(error);
event.error = error;
return event;
});
}
});
})();
// ---------------------------------------------------
// アクセストークン選択
// ---------------------------------------------------
function selectAccessToken(division)
{
if (division) {
var corp = division.split('-')[0];
if (corp in ACCESS_TOKENS) {
return kintone.Promise.resolve(ACCESS_TOKENS[corp]);
}
}
return kintone.Promise.reject('選択中の法人に該当するアクセストークンがありません');
}
// ---------------------------------------------------
// ツバイソ形式のJsonに変換
// kitone形式のレコードをツバイソAPI形式に変換する
// 最大5つ分のレコードを作ります。
// 最初にそれぞれのレコードの共通部をcommonApJsonに作り、
// それを必要な数だけコピーしてレコードの配列を作ります。
// ---------------------------------------------------
function convertToApJson(kintoneRecord)
{
var commonApJson = {};
var apJsons = [];
// 固定の設定
commonApJson['dc'] = 'c'; //増加
commonApJson['tax_code'] = 1003; //共通売上分一般仕入(8%)
commonApJson['port_type'] = 1; //国内
commonApJson['need_tax_deduction'] = 0;
// 共通の設定
commonApJson['accrual_timestamp'] = kintoneRecord['Accept_date']['value'];
commonApJson['customer_master_code'] = kintoneRecord['OrderDestination']['value'];
commonApJson['reason_master_code'] = kintoneRecord['ReasonCode']['value'];
if (kintoneRecord['Division']['value']) {
commonApJson['dept_code'] = kintoneRecord['Division']['value'].split('-')[1];
}
for (var i = 1; i <= 5; i++) {
if (kintoneRecord['ItemName' + i]['value']) {
var apJson = clone(commonApJson);
apJson['price_including_tax'] = kintoneRecord['ItemPrice' + i]['value'];
apJson['memo'] = kintoneRecord['requestUsername']['value'] + '/'
+ kintoneRecord['ItemName' + i]['value'] + '/'
+ kintoneRecord['ItemQuantity' + i]['value'] + '/'
+ kintoneRecord['ItemUnitPrice' + i]['value'] + '/'
+ kintoneRecord['Purpose']['value'] + '/'
+ kintoneRecord['Remarks']['value'];
console.log('add apJson:' + i);
console.log(apJson);
apJsons.push(apJson);
}
}
console.log(apJsons);
return apJsons;
}
// ---------------------------------------------------
// ツバイソ連続POSTラッパー
// ---------------------------------------------------
function postTsubaisoRecordsWrapper(url, restRecords, accessToken)
{
if (restRecords.length == 0) {
return kintone.Promise.resolve();
}
var record = restRecords.shift();
return postTsubaisoRecord(url, record, accessToken)
.then(function() {
return postTsubaisoRecordsWrapper(url, restRecords, accessToken);
}).catch(function(error) {
reject(error);
});
}
// ---------------------------------------------------
// ツバイソPOST API
// ---------------------------------------------------
function postTsubaisoRecord(url, record, accessToken) {
var headers = {'Access-Token':accessToken, 'Accept':'application/json', 'Content-Type':'application/json'};
return kintone.proxy(url, 'POST', headers, record)
.then(checkResponse);
};
// ---------------------------------------------------
// 共通Responseチェック処理
// ---------------------------------------------------
function checkResponse(resp) {
var body = resp[0];
var status = resp[1];
if (200 <= status && status <= 299) {
// HTTPステータスコードが2xx(Success)
console.log('status Success');
var params = JSON.parse( body.replace(/^\s+|\s+$/g,'') );
return kintone.Promise.resolve(params);
}
else {
console.log('status error');
return kintone.Promise.reject(tsubaisoErrorHandle(status, body));
}
};
// ---------------------------------------------------
// ツバイソエラーメッセージのパターンを吸収する
// ---------------------------------------------------
function tsubaisoError(errorMessage)
{
var error;
if (errorMessage.error) {
error = errorMessage.error;
}
else if(errorMessage.errors) {
console.log(errorMessage);
error = errorMessage.errors;
}
else {
console.log(errorMessage);
error = errorMessage;
}
return error;
}
// ---------------------------------------------------
// ツバイソエラーハンドリング
// ツバイソで発生したエラーをstatusで振り分けて解析する
// ---------------------------------------------------
function tsubaisoErrorHandle(status, body)
{
var errorMessage;
switch (status) {
case 401 : errorMessage = "アクセストークンが正しくありません。" ; break;
case 403 : errorMessage = "権限がありません。"; break;
case 404 : errorMessage = "リソースが見つかりません。"; break;
case 422 : errorMessage = tsubaisoError(JSON.parse(body.replace(/^\s+|\s+$/g,''))); break;
case 500 : errorMessage = "サーバーで何らかのエラーが発生しました。"; break;
case 503 : errorMessage = "しばらく時間をおいてからリトライしてください"; break;
default : errorMessage = "未知のエラーが発生しました。"; console.log(status); console.log(body); break;
}
return errorMessage;
}
function clone(src)
{
var dst = {};
for (var key in src) {
dst[key] = src[key];
}
return dst;
}