属人化・工数ブラックボックス化した業務の再構築
Vertical SaaSでBizOpsをしているniiです。
前職ではHorizontal SaaSのCS(カスタマーサクセス)をしており、現職からBizOpsとして勤めています。
入社以降は主にプロセスの改善・プロセスオーナーの巻き取りを推進しており、振り返りも込めて過程を書いていこうと思います。
対象となった業務について
まず、今回改善・巻き取りをした「入札参加資格取得業務」についてざっくりですが記載します。
私が勤めている会社が提供しているVertical SaaSの主要なお客様は自治体様です。
国や地方自治体と取引するには、一部の場合を除き「入札参加資格者名簿」に弊社が登録されていることが必要になります。これは、事前に取引相手が契約対象者としてふさわしいかどうか審査することにより、公共工事ほか行政との取引が問題なく行われるようにするためです。
入札参加資格を取得するための登録申請は自治体ごとに行われますが、申請期間が限られている場合が多く、自治体によっては3年に1回しか行われないところもあります。
弊社がどんなに良質なプロダクト提供できたとしても、入札参加資格を持たなければ、その自治体と契約することができません。
よって、入札参加資格がないこと(入札参加資格者名簿非搭載)による失注を防ぐため、自治体ごとの入札参加資格申請方法や申請時期の確認をします。
なぜ改善したのか?
入札参加資格取得業務について、以下の課題がありました。
プロセスオーナーが不在
関係者全員が兼務しているため作成・レビュー工数が不明瞭
手動作業が多く取得漏れが発生し始めていた
押印・郵送業務の件数が可視化されていないので逼迫具合に気付けない(気付いた時には手遅れ)
何をしたのか?
①その業務を一通り経験し現場感を把握する
まず自分で業務を経験することは必須です。
「プロセスのどこが難所なのか?」「どの順番で着手すると効果が最大化するのか?」「小さく回してQuick Winを出せるポイントはどこか?」「そもそも改善することが正解なのか?」
などを見極めることがめちゃくちゃ大事だと考えています。ここの把握が不足していると全体最適ではなく局所最適な改善となり、結果的に「やらなくてもよかったね」ということにもなり得ます。
②業務プロセスの現状とこうなったらいいな、を可視化する
頭の整理もありますし、他チームのメンバーや今後業務をお願いする人などの第三者がパッと理解できるように、現状を把握した後に必ずGoogleスライド上に可視化しています。
プロセスに変更が入った際にはアップデートしており、面倒ではあるのですが、属人化を解消するためにBizOpsが巻き取ったのに、BizOps内で属人化するというのは、私の中で筋が通らないのでやることを意識しています。
③プロセスの最適化
ここまで来たらあとはやるのみ。重要度・影響度から優先度を決めてガンガン手を動かします。
細部まで拘りつつ、拘りすぎないように構築を進めます。
④定期で振り返り
構築後、1〜2ヶ月は月次で振り返りを実施して細かな修正を行います。振り返りという場を設定しないと、なかなか要望を言い出せなかったり、そもそもそういう観点(更なる改善はできないか?)を持って新しいオペレーションに着手できないと考えているためです。
どうなったのか?
上記でも述べましたが、課題は以下でした。
※1はBizOpsがプロセスオーナーになったという着地なので割愛します。
2. 「関係者全員が兼務しているため工数が不明瞭」
before:兼務人数が7人、1件あたりの工数が3時間🌀
after:専任が2人(実工数は1.3人)、1件あたりの口数が1〜2時間🎉
3. 「手動作業が多く取得漏れが発生し始めていた」
before:kintone上で1件ずつチケットを発行し、手作業で起票をしていたので、情報が蓄積しづらく漏れが発生😱
after:チケット制を廃止、kintoneでレコードの再利用をし、対応期限の自動入力で対応漏れが発生しない状態に🎊
4. 「押印・郵送業務の件数が可視化されていないので逼迫具合に気付けない(気付いた時には手遅れ)」
before:kintone上で自分が担当者となっているチケット数が分からない、タスク管理でJiraとkintoneの併用は管理しづらい☔️
after:押印・郵送ステータスになると、自動でJiraにチケット起票することで、タスク管理のしやすさ・逼迫具合の事前検知を可能に✨
運用を改善してから約半年経過し200件以上対応をしていますが、今のところ漏れなく進めることができています。
反省点
備忘も兼ねて、次回以降はしないように「こうすればよかった」を記載します。
Quick Winポイントの設定
今回できなかった点。小さくでもいいので効果を出すことができれば、各所を巻き込みやすいので意識すべき。
こだわりすぎない
細かい箇所への修正を気にしすぎた。それをやったところで効果はさほど変わらないので、後回しにしてまずは重要なところから着手すること。
参考までに
kintoneに設定しているscriptです。どなたかの参考になれば🙇♂️
素人が書いているscriptですので書き方がイマイチな箇所があると思いますが、優しい目でご意見いただければと思います。
対応期限の更新
kintone.events.on('app.record.detail.process.proceed', function(event) {
moment.locale('ja');
/*実行後のステータスを格納*/
const nextStatus = event.nextStatus.value;
const rec = event.record;
/*空の場合は、nullが入る*/
/*資格期限の値を格納*/
let expiration = rec.expiration.value;
/*取得後の資格期限(開始日)の値を格納*/
let updateExpirationStart = rec.updateExpirationStart.value;
/*取得後の資格期限(終了日)の値を格納*/
let updateExpirationEnd = rec.updateExpirationEnd.value;
/*受付開始日の値を格納*/
const startDate = rec.startDate.value;
/*受付終了日の値を格納*/
const endDate = rec.endDate.value;
/*対応期限の値を格納*/
let responseDeadline = rec.responseDeadline.value;
/*曜日を格納*/
let dayOfWeek;
/*新規取得のため、資格期限がない場合*/
if(expiration === null){
/*取得後の資格期限(開始日)の1日前を資格期限フィールドにセット*/
expiration = moment(updateExpirationStart).subtract(1, 'days').format('YYYY-MM-DD');
}
/*実行後のステータスによって、対応期限フィールドにセットする値を変える*/
switch(nextStatus) {
case "要綱の取得待ち(新規申請)":
/*ステータスが「要綱の取得待ち(新規申請)」になった際、
現在から2日後の平日を対応期限にセット*/
responseDeadline = moment().add(2, 'days').format('YYYY-MM-DD');
/*対応期限の曜日をセット*/
dayOfWeek = moment(responseDeadline).format('dddd');
if(dayOfWeek == '土曜日' || dayOfWeek == '日曜日'){
/*対応期限を平日にしたいので追加で2日を足す*/
responseDeadline = moment(responseDeadline).add(2, 'days').format('YYYY-MM-DD');
}
/*対応期限フィールドにセット*/
rec['responseDeadline'].value = responseDeadline;
/*新規/更新フラグに「新規」をセット*/
rec['newOrUpdateFlag'].value = '新規';
break;
case "作業開始待ち":
/*受付開始日の5日前の平日を対応期限にセット*/
responseDeadline = moment(startDate).subtract(5, 'days').format('YYYY-MM-DD');
/*対応期限の曜日をセット*/
dayOfWeek = moment(responseDeadline).format('dddd');
if(dayOfWeek == '土曜日' || dayOfWeek == '日曜日'){
/*対応期限を平日にしたいので追加で2日を引く*/
responseDeadline = moment(responseDeadline).subtract(2, 'days').format('YYYY-MM-DD');
}
/*対応期限フィールドにセット*/
rec['responseDeadline'].value = responseDeadline;
break;
case "レビュー待ち":
/*現在から3日後の平日を対応期限にセット*/
responseDeadline = moment().add(3, 'days').format('YYYY-MM-DD');
/*対応期限の曜日をセット*/
dayOfWeek = moment(responseDeadline).format('dddd');
if(dayOfWeek == '土曜日' || dayOfWeek == '日曜日'){
/*対応期限を平日にしたいので追加で2日を足す*/
responseDeadline = moment(responseDeadline).add(2, 'days').format('YYYY-MM-DD');
}
/*対応期限フィールドにセット*/
rec['responseDeadline'].value = responseDeadline;
break;
case "押印・郵送待ち":
/*受付終了日を対応期限フィールドにセット*/
rec['responseDeadline'].value = endDate;
break;
case "押印待ち(電子申請)":
/*受付終了日から7日前の平日を対応期限にセット*/
responseDeadline = moment(endDate).subtract(7, 'days').format('YYYY-MM-DD');
/*対応期限の曜日をセット*/
dayOfWeek = moment(responseDeadline).format('dddd');
if(dayOfWeek == '土曜日' || dayOfWeek == '日曜日'){
/*対応期限を平日にしたいので追加で2日を引く*/
responseDeadline = moment(responseDeadline).subtract(2, 'days').format('YYYY-MM-DD');
}
/*対応期限フィールドにセット*/
rec['responseDeadline'].value = responseDeadline;
break;
case "電子申請待ち":
/*現在から3日後の平日を対応期限にセット*/
responseDeadline = moment().add(3, 'days').format('YYYY-MM-DD');
/*対応期限の曜日をセット*/
dayOfWeek = moment(responseDeadline).format('dddd');
if(dayOfWeek == '土曜日' || dayOfWeek == '日曜日'){
/*対応期限を平日にしたいので追加で2日を足す*/
responseDeadline = moment(responseDeadline).add(2, 'days').format('YYYY-MM-DD');
}
/*対応期限フィールドにセット*/
rec['responseDeadline'].value = responseDeadline;
break;
case "結果通知待ち":
/*資格期限の2週間前の平日を対応期限にセット*/
responseDeadline = moment(expiration).subtract(2, 'weeks').format('YYYY-MM-DD');
/*対応期限の曜日をセット*/
dayOfWeek = moment(responseDeadline).format('dddd');
if(dayOfWeek == '土曜日' || dayOfWeek == '日曜日'){
/*対応期限を平日にしたいので追加で2日を足す*/
responseDeadline = moment(responseDeadline).add(2, 'days').format('YYYY-MM-DD');
}
/*対応期限フィールドにセット*/
rec['responseDeadline'].value = responseDeadline;
break;
case "資格取得完了":
/*現在の資格期限の月を格納*/
const expirationMonth = moment(expiration).format('MM');
/*次回の対応期限(年)を格納*/
let nextResponseDeadlineYear;
/*次回の対応期限(月日)を格納*/
let nextResponseDeadlineMonth;
/*取得後の資格期限(終了日)フィールドの値を資格期限(現在)に更新*/
rec['expiration'].value = rec.updateExpirationEnd.value;
/*取得後の資格期限(終了日)フィールドの値を格納*/
expiration = rec.updateExpirationEnd.value;
/*取得後の資格期限(開始日)フィールドの値を選択解除に更新*/
rec['updateExpirationStart'].value = null;
/*取得後の資格期限(終了日)フィールドの値を選択解除に更新*/
rec['updateExpirationEnd'].value = null;
/*現在の資格期限の月のパターンによって次回の対応期限を変える*/
const monthArray1to3 = ['01', '02', '03'];
const monthArray4to7 = ['04', '05', '06', '07'];
const monthArray8to12 = ['08', '09', '10', '11','12'];
if(monthArray1to3.includes(expirationMonth)){
/*現在の資格期限の月が「1〜3月」の場合*/
/*対応期限(年)に資格期限の前年を格納*/
nextResponseDeadlineYear = moment(expiration).subtract(1, 'years').format('YYYY') ;
/*対応期限(月日)に前回受付開始月の2ヶ月前の第一営業日を格納*/
nextResponseDeadlineMonth = moment(startDate).subtract(2, 'months').format('MM') + '-01';
/*対応期限に「YYYY-MM-01」をセット*/
responseDeadline = nextResponseDeadlineYear + '-' + nextResponseDeadlineMonth;
/*対応期限の曜日をセット*/
dayOfWeek = moment(responseDeadline).format('dddd');
if(dayOfWeek == '土曜日'){
/*対応期限を月曜日にしたいので追加で2日を足す*/
responseDeadline = moment(responseDeadline).add(2, 'days').format('YYYY-MM-DD');
}else if(dayOfWeek == '日曜日'){
/*対応期限を月曜日にしたいので追加で1日を足す*/
responseDeadline = moment(responseDeadline).add(1, 'days').format('YYYY-MM-DD');
}
/*対応期限フィールドに「YYYY-MM-01」以降の最初の営業日をセット*/
rec['responseDeadline'].value = responseDeadline;
}else if(monthArray4to7.includes(expirationMonth)){
/*現在の資格期限の月が「4〜7月」の場合*/
/*対応期限(年)に資格期限と同年を格納*/
nextResponseDeadlineYear = moment(expiration).format('YYYY');
/*対応期限(月日)に1月4日を格納*/
nextResponseDeadlineMonth = '01-04';
/*対応期限に「YYYY-01-04」をセット*/
responseDeadline = nextResponseDeadlineYear + '-' + nextResponseDeadlineMonth;
/*対応期限の曜日をセット*/
dayOfWeek = moment(responseDeadline).format('dddd');
if(dayOfWeek == '土曜日'){
/*対応期限を月曜日にしたいので追加で2日を足す*/
responseDeadline = moment(responseDeadline).add(2, 'days').format('YYYY-MM-DD');
}else if(dayOfWeek == '日曜日'){
/*対応期限を月曜日にしたいので追加で1日を足す*/
responseDeadline = moment(responseDeadline).add(1, 'days').format('YYYY-MM-DD');
}
/*対応期限フィールドに「YYYY-01-04」以降の最初の営業日をセット*/
rec['responseDeadline'].value = responseDeadline;
}else if(monthArray8to12.includes(expirationMonth)){
/*現在の資格期限の月が「8〜12月」の場合*/
/*対応期限(年)に資格期限と同年を格納*/
nextResponseDeadlineYear = moment(expiration).format('YYYY');
/*対応期限(月日)に4月1日を格納*/
nextResponseDeadlineMonth = '04-01';
/*対応期限に「YYYY-04-01」をセット*/
responseDeadline = nextResponseDeadlineYear + '-' + nextResponseDeadlineMonth;
/*対応期限の曜日をセット*/
dayOfWeek = moment(responseDeadline).format('dddd');
if(dayOfWeek == '土曜日'){
/*対応期限を月曜日にしたいので追加で2日を足す*/
responseDeadline = moment(responseDeadline).add(2, 'days').format('YYYY-MM-DD');
}else if(dayOfWeek == '日曜日'){
/*対応期限を月曜日にしたいので追加で1日を足す*/
responseDeadline = moment(responseDeadline).add(1, 'days').format('YYYY-MM-DD');
}
/*対応期限フィールドに「YYYY-04-01」以降の最初の営業日をセット*/
rec['responseDeadline'].value = responseDeadline;
}
break;
}
return event;
});
Jiraにチケットを切る手前の処理
kintone.events.on('app.record.detail.process.proceed', function(event) {
// 実行後のステータスを格納
const nextStatus = event.nextStatus.value;
const rec = event.record;
// 実行後のステータスが「押印・郵送待ち」「押印待ち(電子申請)」のときのみJiraに起票
if(nextStatus === "押印・郵送待ち" || nextStatus === "押印待ち(電子申請)"){
// 対応期限の値を格納
//let responseDeadline = rec.responseDeadline.value;
// 【参照】顧客名を格納
let customerName = rec.customerName.value;
// 共同受付タイトルを格納
let commonExaminationTitle = rec.commonExaminationTitle.value;
// 受付開始日を格納
let startDate = rec.startDate.value;
// 受付終了日を格納
let endDate = rec.endDate.value;
// Jiraチケットの要約に記入する文章を格納
let payloadSummary;
// Jiraチケットの詳細に記入する文章を格納
let payloadDescription;
// テキストデータ(本文)に含めるkintoneのレコードURLを格納
const recordUrl = "https://⚪︎⚪︎⚪︎⚪︎.cybozu.com/k/" + kintone.app.getId() + "/show#record=" + kintone.app.record.getId();
// 使用するWebhookのURLを格納
const webhookUrl = "https://hooks.zapier.com/hooks/catch/⚪︎⚪︎⚪︎⚪︎/";
// チケット担当者のJiraアカウントIDを格納
const assignedAccountId = "⚪︎⚪︎⚪︎⚪︎";
if(nextStatus === "押印・郵送待ち"){
payloadSummary
= "【押印・郵送】入札参加資格:" + customerName + "、受付期間:" + startDate + "〜" + endDate;
payloadDescription
= customerName + "の提出書類を印刷→押印後、郵送いただければと思います。\nkintoneレコード:" + recordUrl;
}
if(nextStatus === "押印待ち(電子申請)"){
payloadSummary
= "【押印のみ】入札参加資格:" + customerName + "、受付期間:" + startDate + "〜" + endDate;
payloadDescription
= customerName + "の提出書類を印刷→押印後、Driveに再アップいただければと思います。\nkintoneレコード:" + recordUrl;
}
// POSTするデータを生成
const postData = {
"summary" : payloadSummary,
"description" : payloadDescription,
"assignee" : assignedAccountId
};
return new kintone.Promise(function(resolve, reject) {
kintone.proxy(webhookUrl, "POST", {}, postData, function(body, status, headers) {
console.log(status, body);
resolve(event);
});
});
}
return event;
});
この記事が気に入ったらサポートをしてみませんか?