業務:経費の申請と精算
クラウド型ワークフローで「経費申請フロー」のシステム化を実現した!(第559話参照)
しかも「多角的なグルーピング」(クラスタリング分析)を実現するための仕組みも導入した!(第560話参照)
経理チームは、「48時間の滞留で自動的に部長決裁されたものとみなす」というルールが適用された経費申請(案件ステータス115~155)に対して、目を光らせているらしい。
課題:監査法人とのデータ共有
しかし、、、情報量は「多ければ多いほど良い」というモノではない。ワークフローには、『立替金額』や『計上月』といった基本情報だけでなく『立替申請を行ったヒト・時刻』や『支払を証明する書類』あるいは『プロジェクトの名称』や『取引先企業名』といった関連情報まで、様々なデータが含まれている。たしかに社内の人間であれば、様々なデータから「情報」を抽出することは重要なシゴトと言える。
ただ、例えば、会計監査人にしてみれば「審査過程」や「所要時間」などは無用なデータだ。例えば、社長や会長といった経営トップにしてみれば「誰が多くの経費を使ったか」まで見る時間がない。彼らにとっては「多角的な集計フィルタリング」より、必要なデータが簡潔に一覧されている方が重要なのだ。
う~む、ならば報告用の Spreadsheet を「手作り」するか???(悪夢)
[経費精算フロー-Spreadsheet出力]
解決:Spreadsheet へのリアルタイム追記
この業務プロセス定義では、最下流にある「経理チームの承認工程」の直後に『Append Sheet』という自動工程が配置されています。ここでは、
- A. 立て替えた日
- B. 計上月(YYYY-MM)
- C. 勘定費目分類
- D. 精算金額
- E. 立替人所属組織
- F. 稟議書ID
会計監査人は Spreadsheet を参照するだけで「全ての審査が完了した経費案件」について効率よく確認することが可能となっています。ワークフロー基盤にログインする必要はありません。
考察:外部連携の意義
ワークフローに流れたデータを「外部システムである Google Spreadsheet」に自動出力する仕組みは、単に「データの見やすさ」や「リアルタイム共有」を実現するだけのものではありません。つまり「無人で自動追記される」という特性から、ログデータとしての存在意義があると言えます。
具体的には、業務データの隠蔽削除や改ざんといった不正を抑止し、システム障害時には業務データの代替参照先として活用できます。(業務データ・ミラーリング)
PS:書式や背景色などのカスタマイズ
なお『Google Sheets 行追加』は、各企業文化にあわせたカスタマイズ要求が多い自動工程です。つまり「負の金額は赤色で書く」や「100万円以下は四捨五入する/切り捨てる」や「四半期の区分を追記する」など、業種や事業規模によって基本的なロギングニーズが異なります。より調和のとれた業務データ環境を実現するために、会社全体での共通利用を積極的に進めた方が良いかも知れません。
<自動工程の設定画面>
<自動工程の定義ファイル>
<?xml version="1.0" encoding="UTF-8"?><service-task-definition> <label>Google Sheets Append Cells</label> <label locale="ja">Google Sheets 行追加</label> <summary>Adds new cells after the last row with data in a sheet, inserting new rows into the sheet if necessary.</summary> <summary locale="ja">シート末尾に1行追加し、その各セルにデータを入力します。必要あれば行領域を拡大します。</summary> <help-page-url>https://www.questetra.com/tour/m4/m415/addon-googlesheets-appendcells/</help-page-url> <help-page-url locale="ja">https://www.questetra.com/ja/tour/m4/m415/addon-googlesheets-appendcells/</help-page-url> <configs> <config name="conf_OAuth2" required="true" form-type="TEXTFIELD"> <label>V: Set OAuth2 Config Name (at [OAuth 2.0 Setting])</label> <label locale="ja">V: OAuth2通信許可の設定名 (←[OAuth 2.0 設定]における設定名)</label> </config> <config name="conf_DataIdW" required="true" form-type="SELECT" select-data-type="STRING_TEXTFIELD|SELECT_SINGLE"> <label>W: Select STRING/SELECT DATA for Spreadsheet ID (File ID)</label> <label locale="ja">W: Spreadsheet ID (ファイルのID)が格納されている文字列型or選択肢型データを選択してください</label> </config> <config name="conf_DataIdX" required="true" form-type="SELECT" select-data-type="STRING_TEXTFIELD|SELECT_SINGLE|DECIMAL"> <label>X: Select STRING/SELECT/NUMERIC DATA for SheetGID (Worksheet ID)</label> <label locale="ja">X: Sheet GID (WorksheetのID)が格納されている文字列型or選択肢型or数値型データを選択してください</label> </config> <config name="conf_DataIdY" required="false" form-type="SELECT" select-data-type="STRING_TEXTAREA"> <label>Y: Select STRING DATA for Access Log (update)</label> <label locale="ja">Y: 通信ログが格納される文字列型データを選択してください (更新)</label> </config> <config name="conf_DataIdA" required="false" form-type="SELECT" select-data-type="STRING|DECIMAL|DATE|DATETIME|SELECT_SINGLE|QUSER|QGROUP|FILE"> <label>A: Select DATA for Column-A Value of New Row</label> <label locale="ja">A: 末尾行のA列に追加される値が格納されているデータを選択してください</label> </config> <config name="conf_DataIdB" required="false" form-type="SELECT" select-data-type="STRING|DECIMAL|DATE|DATETIME|SELECT_SINGLE|QUSER|QGROUP|FILE"> <label>B: Select DATA for Column-B Value of New Row</label> <label locale="ja">B: 末尾行のB列に追加される値が格納されているデータを選択してください</label> </config> <config name="conf_DataIdC" required="false" form-type="SELECT" select-data-type="STRING|DECIMAL|DATE|DATETIME|SELECT_SINGLE|QUSER|QGROUP|FILE"> <label>C: Select DATA for Column-C Value of New Row</label> <label locale="ja">C: 末尾行のC列に追加される値が格納されているデータを選択してください</label> </config> <config name="conf_DataIdD" required="false" form-type="SELECT" select-data-type="STRING|DECIMAL|DATE|DATETIME|SELECT_SINGLE|QUSER|QGROUP|FILE"> <label>D: Select DATA for Column-D Value of New Row</label> <label locale="ja">D: 末尾行のD列に追加される値が格納されているデータを選択してください</label> </config> <config name="conf_DataIdE" required="false" form-type="SELECT" select-data-type="STRING|DECIMAL|DATE|DATETIME|SELECT_SINGLE|QUSER|QGROUP|FILE"> <label>E: Select DATA for Column-E Value of New Row</label> <label locale="ja">E: 末尾行のE列に追加される値が格納されているデータを選択してください</label> </config> <config name="conf_DataIdF" required="false" form-type="SELECT" select-data-type="STRING|DECIMAL|DATE|DATETIME|SELECT_SINGLE|QUSER|QGROUP|FILE"> <label>F: Select DATA for Column-F Value of New Row</label> <label locale="ja">F: 末尾行のF列に追加される値が格納されているデータを選択してください</label> </config> <config name="conf_DataIdG" required="false" form-type="SELECT" select-data-type="STRING|DECIMAL|DATE|DATETIME|SELECT_SINGLE|QUSER|QGROUP|FILE"> <label>G: Select DATA for Column-G Value of New Row</label> <label locale="ja">G: 末尾行のG列に追加される値が格納されているデータを選択してください</label> </config> <config name="conf_DataIdH" required="false" form-type="SELECT" select-data-type="STRING|DECIMAL|DATE|DATETIME|SELECT_SINGLE|QUSER|QGROUP|FILE"> <label>H: Select DATA for Column-H Value of New Row</label> <label locale="ja">H: 末尾行のH列に追加される値が格納されているデータを選択してください</label> </config> <config name="conf_DataIdI" required="false" form-type="SELECT" select-data-type="STRING|DECIMAL|DATE|DATETIME|SELECT_SINGLE|QUSER|QGROUP|FILE"> <label>I: Select DATA for Column-I Value of New Row</label> <label locale="ja">I: 末尾行のI列に追加される値が格納されているデータを選択してください</label> </config> <config name="conf_DataIdJ" required="false" form-type="SELECT" select-data-type="STRING|DECIMAL|DATE|DATETIME|SELECT_SINGLE|QUSER|QGROUP|FILE"> <label>J: Select DATA for Column-J Value of New Row</label> <label locale="ja">J: 末尾行のJ列に追加される値が格納されているデータを選択してください</label> </config> </configs> <script><![CDATA[ // Google Sheets Append Cells (ver. 20171031) // (c) 2017, Questetra, Inc. (the MIT License) // by spreadsheets.batchUpdate [AppendCellsRequest] (not by spreadsheets.values.append) // https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#appendcellsrequest // OAuth2 config sample at [OAuth 2.0 Setting] // - Authorization Endpoint URL: https://accounts.google.com/o/oauth2/auth?access_type=offline&approval_prompt=force // - Token Endpoint URL: https://accounts.google.com/o/oauth2/token // - Scope: https://www.googleapis.com/auth/spreadsheets // - Consumer Key: (Get by Google Developers Console) // - Consumer Secret: (Get by Google Developers Console) //// == Config Retrieving / 工程コンフィグの参照 == var oauth2 = configs.get( "conf_OAuth2" ) + ""; var dataIdW = configs.get( "conf_DataIdW" ) + ""; var dataIdX = configs.get( "conf_DataIdX" ) + ""; var dataIdY = configs.get( "conf_DataIdY" ) + ""; var dataIdA = configs.get( "conf_DataIdA" ) + ""; var dataIdB = configs.get( "conf_DataIdB" ) + ""; var dataIdC = configs.get( "conf_DataIdC" ) + ""; var dataIdD = configs.get( "conf_DataIdD" ) + ""; var dataIdE = configs.get( "conf_DataIdE" ) + ""; var dataIdF = configs.get( "conf_DataIdF" ) + ""; var dataIdG = configs.get( "conf_DataIdG" ) + ""; var dataIdH = configs.get( "conf_DataIdH" ) + ""; var dataIdI = configs.get( "conf_DataIdI" ) + ""; var dataIdJ = configs.get( "conf_DataIdJ" ) + ""; // convert 'java.lang.String' to 'javascript string' //// == Data Retrieving / ワークフローデータの参照 == var spreadsheetId = ""; if( engine.findDataDefinitionByNumber( dataIdW ).matchDataType( "SELECT_SINGLE" ) ){ spreadsheetId = engine.findDataByNumber( dataIdW ).get(0).getValue() + ""; }else{ spreadsheetId = engine.findDataByNumber( dataIdW ) + ""; } var sheetId = ""; if( engine.findDataDefinitionByNumber( dataIdX ).matchDataType( "SELECT_SINGLE" ) ){ sheetId = engine.findDataByNumber( dataIdX ).get(0).getValue() + ""; }else if( engine.findDataDefinitionByNumber( dataIdX ).matchDataType( "DECIMAL" ) ){ sheetId = engine.findDataByNumber( dataIdX ) + ""; sheetId = sheetId.replace(/,/g,"").replace(/\./g,""); // Remove dots and commas }else{ sheetId = engine.findDataByNumber( dataIdX ) + ""; } //// == Calculating / 演算 == // preparing for API Request var apiRequest = httpClient.begin(); // HttpRequestWrapper // com.questetra.bpms.core.event.scripttask.HttpClientWrapper // Request HEADER (OAuth2 Token, HTTP Basic Auth, etc) var token = httpClient.getOAuth2Token( oauth2 ); apiRequest = apiRequest.bearer( token ); // Request PATH (https://example.com/abc/def/) var apiUri = "https://sheets.googleapis.com/v4/spreadsheets/"; apiUri += spreadsheetId + ":batchUpdate"; // Request QUERY (?a=b) // (no set) // Request BODY (JSON, Form Parameters, etc) function cellDataObject( dataId ){ var dataObj = {}; dataObj.userEnteredValue = {}; if( dataId === "" ){ dataObj.userEnteredValue.stringValue = ""; }else if( engine.findDataDefinitionByNumber( dataId ).matchDataType( "SELECT_SINGLE" ) ){ dataObj.userEnteredValue.stringValue = engine.findDataByNumber( dataId ).get(0).getDisplay() + ""; }else if( engine.findDataDefinitionByNumber( dataId ).matchDataType( "FILE" ) ){ dataObj.userEnteredValue.stringValue = engine.findDataByNumber( dataId ).get(0).getName() + ""; }else if( engine.findDataDefinitionByNumber( dataId ).matchDataType( "QUSER" ) ){ dataObj.userEnteredValue.stringValue = engine.findDataByNumber( dataId ).getName() + ""; }else if( engine.findDataDefinitionByNumber( dataId ).matchDataType( "QGROUP" ) ){ dataObj.userEnteredValue.stringValue = engine.findDataByNumber( dataId ).getName() + ""; }else if( engine.findDataDefinitionByNumber( dataId ).matchDataType( "DATE" ) ){ var serialDays = ( engine.findDataByNumber( dataId ).getTime() + engine.getTimeZoneOffsetInMinutes() * 60000 ) / 86400000 + 25569; dataObj.userEnteredValue.numberValue = serialDays; dataObj.userEnteredFormat = {}; dataObj.userEnteredFormat.numberFormat = {}; dataObj.userEnteredFormat.numberFormat.type = "DATE"; }else if( engine.findDataDefinitionByNumber( dataId ).matchDataType( "DATETIME" ) ){ var serialDays2 = ( engine.findDataByNumber( dataId ).getTime() + engine.getTimeZoneOffsetInMinutes() * 60000 ) / 86400000 + 25569; dataObj.userEnteredValue.numberValue = serialDays2; dataObj.userEnteredFormat = {}; dataObj.userEnteredFormat.numberFormat = {}; dataObj.userEnteredFormat.numberFormat.type = "DATE_TIME"; }else if( engine.findDataDefinitionByNumber( dataId ).matchDataType( "DECIMAL" ) ){ dataObj.userEnteredValue.numberValue = engine.findDataByNumber( dataId ) - 0; }else{ // for "STRING" dataObj.userEnteredValue.stringValue = engine.findDataByNumber( dataId ) + ""; } return dataObj; } var requestObj = {}; requestObj.requests = []; requestObj.requests[0] = {}; requestObj.requests[0].appendCells = {}; requestObj.requests[0].appendCells.sheetId = sheetId; requestObj.requests[0].appendCells.fields = "*"; requestObj.requests[0].appendCells.rows = []; requestObj.requests[0].appendCells.rows[0] = {}; requestObj.requests[0].appendCells.rows[0].values = []; requestObj.requests[0].appendCells.rows[0].values[0] = cellDataObject( dataIdA ); requestObj.requests[0].appendCells.rows[0].values[1] = cellDataObject( dataIdB ); requestObj.requests[0].appendCells.rows[0].values[2] = cellDataObject( dataIdC ); requestObj.requests[0].appendCells.rows[0].values[3] = cellDataObject( dataIdD ); requestObj.requests[0].appendCells.rows[0].values[4] = cellDataObject( dataIdE ); requestObj.requests[0].appendCells.rows[0].values[5] = cellDataObject( dataIdF ); requestObj.requests[0].appendCells.rows[0].values[6] = cellDataObject( dataIdG ); requestObj.requests[0].appendCells.rows[0].values[7] = cellDataObject( dataIdH ); requestObj.requests[0].appendCells.rows[0].values[8] = cellDataObject( dataIdI ); requestObj.requests[0].appendCells.rows[0].values[9] = cellDataObject( dataIdJ ); apiRequest = apiRequest.body( JSON.stringify( requestObj ), "application/json" ); // Access to the API (POST, GET, PUT, etc) var response = apiRequest.post( apiUri ); // HttpResponseWrapper var httpStatus = response.getStatusCode() + ""; var accessLog = "---POST request--- " + httpStatus + "\n"; accessLog += response.getResponseAsString() + "\n"; //var responseObj = JSON.parse( response.getResponseAsString() ); // Retrieve Properties from Response-JSON // (no action) // Error Handling - https://stripe.com/docs/api#errors // (no action) //// == Data Updating / ワークフローデータへの代入 == if ( dataIdY !== "" ){ engine.setDataByNumber( dataIdY, accessLog ); } ]]></script> <icon> (省略) </icon> </service-task-definition>
<経費精算フロー-Spreadsheet出力:「4.補正/却下」画面>
<データ項目一覧画面>
[雛形ダウンロード (無料)]
- 業務テンプレート:経費精算フロー-Spreadsheet出力
- 第464話:立替金精算依頼を回す(基本業務パック) (2016-01-04)
- 第510話:もう経費精算フローの中で「事後承認」すればイイ (2016-11-21)
- 第466話:出張申請に対する承認は「課長代理」のシゴトだ (2016-01-18)
- M117 データ検索: 全案件データを絞込検索する/絞込条件を保存する (使い方)
- M227 自動工程: 業務データの結合や四則演算が自動実行されるように設定する (使い方)
- M209 引受ルール: 引受候補者を“営業部”などの組織情報や“資格者”などのロール情報で設定する (使い方)
- M304 一般ユーザ: ユーザアカウントをグルーピングする [ロール] を登録する (使い方)
- M401 業務の流れ: 申請と差戻対応の工程を分け“手戻り”をモニタリングしやすいように設定する (使い方)
- M416 自動工程: 業務プロセス定義で利用可能な自動工程を自作する (使い方)
- Google Sheets 行追加 (Addon解説)
[英文記事 (English Entry) ]
0 件のコメント :
コメントを投稿