Pleasanterで多段階承認を設計して分かったこと|承認ボタンを自作し、差戻しは運用に寄せた

Pleasanterで社内の届出業務をシステム化することになり、複数の承認者を経由するワークフローを設計しました。申請フォームそのものは比較的早く形になります。ところが実際に業務として使おうとすると、「差戻しは誰へ戻すのか」「承認途中の申請は誰まで見られるのか」「完了後も編集できてよいのか」「過去日で申請された場合はどうするのか」と、判断が次々に出てきました。

ここでは、画面を作る前に決めることになった運用ルールと、最終的にスクリプトで作った部分・あえて作らなかった部分を、実際に迷った順に整理します。現在は本番で運用しています。

画面を作る前に、承認の順番を一つずつ言葉にした

対象になった業務は、担当者が登録して終わる申請ではありませんでした。担当者が申請した後、係長・主任、課の安全管理者、部の安全管理者へ順番に回し、すべての確認が終わってから完了になる流れです。

既存業務では関係者が経験で理解している部分もありましたが、Pleasanterへ設定するには曖昧なままでは進みません。「最初の承認者は誰か」「次の承認者は固定か」「差し戻されたらどこへ戻るか」「承認者が不在ならどうするか」を、一つずつ確認していきました。

この作業で感じたのは、ワークフロー構築で時間を食うのはボタンや項目の設定ではなく、現行業務を説明できる形にすることだ、という点です。人が暗黙的に処理していた判断も、システムではルールとして書くことになります。

状況は増やさず、承認日で進捗を表した

承認段階ごとに状況を細かく分ける方法もありますが、今回は採りませんでした。実際に使っている状況は2つだけです。

  • 200:回付中(誰かの承認待ち)
  • 900:完了

そのかわり、承認日を段階ごとに列で持たせています。承認日1・承認日2・承認日3のどれが埋まっているかで、いまどこまで進んでいるかが分かる形です。

状況は200と900の2つだけで、承認日1〜3が順に埋まることで進捗を表す遷移図

こうしたのは、状況を段階の数だけ増やすと、承認者が1人増減するたびに状況の定義から作り直すことになるからです。承認日の列を増減させるほうが、影響範囲が小さく済みます。

承認日を1項目に上書きしていく形にしなかった理由もあります。上書き方式だと一覧に出せるのは「最後に承認された日」だけで、「どの段階で止まっているのか」「どこで時間がかかっているのか」が後から追えません。段階ごとに列を持たせておけば、一覧を見るだけで滞留している場所が分かります。

誰が承認したかも、承認日とは別の列に記録しています。日付だけでは、代理承認や担当交代があったときに履歴として成立しません。

承認ボタンを自作して、押した人と日付を同時に記録した

承認操作は、Pleasanterの標準機能ではなくスクリプトで組みました。承認日の入力を人に任せると、入力漏れと打ち間違いが必ず出るからです。承認したのに承認日が空、あるいは実際とは違う日付が入っている状態になると、後から履歴として使えません。

まず、ボタンを出す条件です。

// システム日付の取得
let Today = new Date();
let YYYY = Today.getFullYear();
let MM = ("0" + (Today.getMonth() + 1)).slice(-2);   // getMonth()の戻り値は0~11
let DD = ("0" + Today.getDate()).slice(-2);

// 承認ボタンは「承認待ち」がログインユーザ かつ 状況:承認待ち の時のみ表示
if ($p.getControl('承認待ち').val() == $p.userId() && $p.getControl('状況').val() == 200) {
    if ($p.getControl('課安全管理者').val() == "") {
        $('#Results_DateA').after($('<button onclick="$p.ex.sign1();">承認</button>').button());
    };
    if ($p.getControl('部安全管理者').val() == "") {
        $('#Results_DateB').after($('<button onclick="$p.ex.sign2();">承認</button>').button());
    };
    $('#Results_DateC').after($('<button onclick="$p.ex.sign3();">承認</button>').button());
};

ポイントは、「承認待ち」が自分で、かつ状況が回付中のときにしかボタンが生成されないことです。権限で操作を禁止するのではなく、そもそもボタンを描画しない形にしています。関係のない人が画面を開いても、承認ボタンは存在しません。

次に、承認ボタンを押したときの処理です。

// 承認ボタン1
$p.ex.sign1 = function() {
    if ($p.getControl('ClassA').val() == "") {
        $p.set($('#Results_ClassA'), $p.userId());
        $p.set($('#Results_DateA'), YYYY + "/" + MM + "/" + DD);
        $p.set($('#Results_Status'), 200);
        // 承認待ちユーザと承認者1が同じだったら、承認待ちユーザをクリア
        // ※違うユーザを指定してもらうため
        if ($p.getControl('承認待ち').val() == $p.getControl('係長・主任').val()) {
            $p.set($('#Results_Manager'), "");
        }
    } else if ($p.getControl('ClassA').val() != $p.userId()) {
        // 自分以外が承認している場合はエラーメッセージ表示
        alert('他のユーザが承認しています');
    } else {
        $p.set($('#Results_ClassA'), "");
        $p.set($('#Results_DateA'), "");
        $p.set($('#Results_Status'), 200);
        $p.set($('#Results_Manager'), $p.userId());
    }
};

承認ボタン2・3も同じ構造です。違いは、書き込む列(ClassB/DateB、ClassC/DateC)と、最終承認である3だけが状況を900へ進め、承認待ちを空にする点です。

このコードには、運用する中で必要になった判断が3つ入っています。

1つ目は、承認者と承認日を必ずセットで書くこと。誰が押したかを ClassA に、押した日を DateA に、同じ処理の中で入れています。片方だけ入っている状態を作らないためです。

2つ目は、同じボタンで取り消せること。すでに自分が承認している状態でもう一度押すと、承認者と承認日をクリアし、承認待ちを自分に戻します。承認ボタンと取消ボタンを分けると画面が増えるうえ、「どちらを押すのか」を説明する手間が発生します。押すたびに状態が切り替わる形にしておけば、説明は一行で済みます。

他人が承認済みのレコードは触れません。ClassA が自分以外の場合はアラートを出して処理を止めています。取り消せるのは自分が押した承認だけ、というルールです。

3つ目は、自分から自分へ回さないための処理です。承認した人と、次の「承認待ち」に入っている人が同一だった場合、承認待ちを空にしています。ここを空にしておくと、承認者は次に回す相手を必ず指定することになります。放っておくと自分宛てのまま止まり、誰も気づかないまま滞留する申請ができてしまいます。

この3つ目は、設計の段階では思いつきませんでした。運用テストで実際に回してみて、初めて必要だと分かった処理です。

クライアント側で組んだことの限界も、承知のうえで選んだ

この実装は、テーブルの管理の「スクリプト」機能、つまりブラウザ側で動くJavaScriptです。画面を開いたときにボタンを描画し、押されたら値をセットする、という作りになっています。

公式マニュアルにあるとおり、サーバスクリプトはインポートによるレコード追加・更新やAPIによる操作のタイミングでも実行されますが、クライアント側のスクリプト機能はそうではありません。この承認処理は、画面から操作されたときにしか動きません。

今回の業務では、承認は必ず人が画面で行うものなので、これで問題ないと判断しました。ただし、後からAPI連携や一括インポートで状況を書き換える要件が出てくると、承認日が入らないまま状況だけ進む、という状態が起こり得ます。そこを触ることになったら、サーバ側へ移す前提で考えています。

同じ理由で、alert による排他も厳密なものではありません。画面を開いた時点の値を見ているだけなので、二人が同時に開いていれば、後から押したほうが上書きする可能性は残ります。実際の運用では「承認待ち」が1人に絞られているため衝突は起きていませんが、仕組みとして完全ではないことは把握しています。

差戻しは専用の仕組みを作らず、既存の部品を流用した

差戻しは、最初から自動化しないと決めていました。検討の末にたどり着いた結論ではありません。

社内の他の申請業務で使っている状況遷移の共通部品が、すでに手元にあったからです。新しく専用の仕組みを組むより、動作実績のある部品を流用するほうが、作る手間も、後から保守する手間も、利用者への説明も少なく済みます。ワークフローを1本作るたびに専用の作りにしていくと、数が増えたときに面倒を見きれません。

そのかわり、差戻し時にコメントを残すことを運用ルールにしました。状態を一つ前へ戻すだけでは、申請者は何を修正すべきか判断できません。「日付を修正してください」「添付資料を追加してください」といった理由を残して、差し戻された側が次の行動を取れるようにしています。

それから、差し戻したときには承認日をクリアしています。承認日を見て動くリマインダなどの仕組みがあるので、日付が残ったままだと、実際には承認が取り消されているのに、その段階は済んだものとして扱われてしまいます。

差戻しは状況を戻すだけでは足りず、その段階に紐づく記録も一緒に戻す必要がありました。前述の承認ボタンが「もう一度押すと承認日をクリアする」作りになっているのも、同じ考え方です。

回付中と完了後で、見せ方と編集権限を変えた

設計に時間がかかったもう一つの項目が権限です。申請途中の情報をPleasanterの利用者全員へ見せる必要はありません。回付中は申請者、現在の承認者、管理上必要な利用者など、業務上必要な範囲だけが閲覧できるようにしました。

さらに、「閲覧できること」と「編集できること」を分けています。承認途中に関係のない利用者が内容を書き換えられると、承認した内容そのものが変わってしまいます。

承認完了後は、情報共有の目的から閲覧範囲を広げました。ただし承認済みデータが後から自由に変更されるのは避けたいので、完了後は原則として読取専用です。

ワークフローでは「誰が操作できるか」だけでなく、「いつから誰に見せるか」「完了後に誰が変更できるか」まで決めておかないと、運用開始後に権限変更の依頼が増えます。

開始日は過去日を許可し、終了日だけ開始日+1年で制限した

今回の届出には開始日と終了日を入力する項目があり、終了日は開始日から1年以内にする必要がありました。このチェックもスクリプトで実装しています。

設計時に迷ったのが開始日です。システムだけを考えれば、「開始日は今日以降」と制限したほうがデータはきれいに見えます。ただ実際の業務では、届出を忘れていて開始日を過ぎた後に申請することもあります。

そこで過去日を入力できないようにすると、実際の開始日とは違う日付を利用者に入力させることになります。届出の実態と台帳の内容がずれるほうが、業務上は問題が大きい。だから開始日については過去日も許容し、終了日だけを「開始日+1年以内」に制限する仕様にしました。

入力制限を厳しくすれば正しいデータになるとは限りません。現実に起こるケースを受け止められるルールにしたほうが、結果としてデータの正確性を保ちやすくなります。

この日付チェックの実装は、条件の整理からサーバースクリプトまで終了日を開始日から1年以内に制限する方法に切り出して書いています。

承認後もすべてPleasanterに閉じ込める設計にはしなかった

今回の業務では、承認完了後に担当者がPDFを出力し、既存の掲示や後続業務へ使う想定がありました。申請から最後の業務までをすべてPleasanter上で完結させる設計にはしていません。

システム化というと、紙をゼロにすることや既存業務を全面的に置き換えることが目標になりがちです。そこまで範囲を広げると関係者が増え、導入までの負担も大きくなります。

今回は、申請、承認、進捗確認、履歴管理をPleasanterへ移して、その後はPDFで既存業務につなげる形にしました。少なくとも申請書を人が持ち回る必要はなくなり、「現在誰の確認待ちなのか」をシステム上で管理できます。

すべてをデジタル化すること自体を目的にせず、どこを変えると業務上の効果が大きいかで範囲を決める。今回はその考え方で線を引きました。

逆に、承認完了を起点にPleasanter内で処理を続けた例もあります。安全講習の承認完了後に運転者台帳の受講日を自動更新した実装です。

本番前に、少人数で1週間の運用テストを行った

本番利用前には、管理部門内の数名で約1週間の運用テストを行いました。確認したのは、承認順、差戻し後の再申請、通知、権限切替、日付チェック、PDF出力です。

作成者が自分で操作すると、どこに何があるか分かっているので、無意識に正しい手順を踏んでしまいます。実際に申請する人に触ってもらわないと、どこで迷うかは見つかりません。いきなり全社へ公開するのではなく、少人数で一通り流してから本番運用に入りました。

前述した「承認者と次の承認待ちが同じ人だったら承認待ちを空にする」処理は、このテストで見つかったものです。正常ルートだけを試していたら、本番で申請が静かに滞留するまで気づかなかったと思います。

まとめ:作る部分と、作らない部分を分けて決める

今回Pleasanterで承認ワークフローを設計して分かったのは、全部を機能で作ろうとしないほうが早い、ということでした。

スクリプトで作ったのは、承認ボタンと、押したときの記録処理、それと終了日の入力チェックです。どれも「人が毎回やると必ず漏れる」「記録として正確でないと後から困る」という共通点があります。

差戻しは専用の仕組みを作らず、既存の共通部品を流用した手動運用にしました。ワークフローごとに専用の作りを増やしていくと、本数が増えたときに保守しきれなくなります。

状況も、段階の数だけ増やしていません。回付中と完了の2つに絞り、進捗は承認日の列で表しています。承認者が1人増減したときに、状況の定義から作り直したくなかったからです。

誰が承認するのか。差し戻すときはどこへ戻すのか。申請途中は誰が閲覧できるのか。完了後は編集可能なのか。開始日の過去入力を許すのか。こうした業務上の判断が決まった後で、初めて「どこを自動化して、どこを人と既存の部品に任せるか」が決まります。

Pleasanterの柔軟さを活かすなら、最初から機能を盛り込むより、現在の業務を一つずつ整理して、必要な部分だけを設定へ落とし込むほうが進めやすいと感じました。

コメント

タイトルとURLをコピーしました