毎月の発注書を自動発行にした。ついでに、自分で付けた安全装置が効いていないのを見つけた話


今回はこのブログの話ではなく、本業のほう。Hiyoku合同会社で毎月発行している発注書の作成を、Claude Codeと一緒に自動化した。7月から本番で回っている。

前回の最後に「収益導線の前提が初日から崩れた話は別の記事で書く」と予告したけど、それは次回に回す。

仕組み自体は動いているんだけど、この記事で一番書きたいのはそこではなくて、本番投入の当日に「自分で付けた安全装置が一度も動いていない」と気づいた話のほう。順番に書く。

毎月1回、同じ書類を作る仕事

うちは案件によって、元請から受けた仕事を協力会社に再委託する形が入る。その協力会社に対して、毎月「個別契約書(兼 発注書・発注請書)」という書類を発行する。テンプレートを開いて、発注番号と発注日と納期を書き換えて、PDFにして、電子契約サービスにアップロードして署名を依頼する。1社あたり数分の作業で、月に1回。

正直、今の時点では協力会社は1社しかない。数分の作業を月1回やるだけなので、費用対効果だけで判断したら自動化する理由はない。それでも作ったのは、今後この形が増える見込みがあるのと、この手の「少額で定期的で、忘れると相手に迷惑がかかる作業」は、増えてから慌てて仕組み化するのが一番しんどいと思ったから。

自動化しようとして、対象を勘違いしていたことに気づく

作り始める前に、Claude Codeに既存のテンプレートと過去の発行済み書類を全部読ませて、毎月何が変わっているのかを洗い出させた。

俺の頭の中では「毎月の金額計算を自動化する」つもりだったんだけど、出てきた答えは違った。発注書に書く合計金額は、人月単価と基準時間から決まる固定値で、毎月変わらない。月ごとに変わるのは発注番号・発注日・納期くらい。実際の変動精算——基準時間を超えたら超過単価、下回ったら控除単価——は、協力会社側が別途上げてくる請求書のほうで起きていた。

つまり自動化で効くのは計算ではなく、「毎月忘れずに出すこと」と「毎回まったく同じ体裁で出ること」のほうだった。ここを勘違いしたまま作り始めていたら、要らない計算ロジックを組んで、肝心の部分は手作業のまま残っていたと思う。

事故を防ぐための安全装置を入れた

構成そのものは素直で、取引先マスタ(スプレッドシート)とテンプレートを用意して、Cloudflare Workers上のプログラムが毎月1日にテンプレートを複製し、値を差し込んでPDFにしてDriveに保存、完了通知をメールの下書きに残す、という流れ。

ただ、この手の月次自動化には明確な事故のパターンがある。案件がもう終わっているのに、機械が何も知らずに発注書を出してしまうやつだ。

そこで着手前に、SES業界で案件の終了や継続の意思確認が普通いつ行われるのかを調べさせた。契約満了の1〜3ヶ月前が目安で、自動更新条項があると「満了3ヶ月前」が意思表示の締切になりやすい、という慣行が出てきた。二次商流だと、正式な通知より先にエンジニア本人や現場から口頭で話が伝わってきて、社内への正式反映が遅れやすいという構造的な弱点も指摘されていた。

それを踏まえて、マスタに契約満了日・自動更新の有無・継続確認ステータスの3列を足して、発注書を作る前に判定を挟むようにした。

  • 継続確認ステータスが「終了予定」なら作らない
  • 契約満了日が対象月より前なら作らない
  • 満了日が対象月の中にあって、自動更新なし・継続確認も未完了なら作らない
  • 満了まで残り60日を切ったら、作った上で警告を添える

自分でもよくできたと思っていた。

本番で動かしたら、毎回「未入力です」と言われた

実際に翌月分を生成させたら、書類自体はちゃんと出てきた。ただ通知に、こういう一文が付いていた。

契約満了日が未入力です。マスタへの入力をお願いします。

マスタには入力済みだった。カレンダーから選んで、ちゃんと日付が入っている。

原因を追わせたら、日付の読み方だった。スプレッドシートのカレンダーピッカーで入れた日付を、表示のままの文字列としてAPIから読むと 2027/03/31 のようにスラッシュ区切りで返ってくる。ところが俺たちが書いた日付の解析処理は、2027-03-31 のハイフン区切りしか受け付けない書き方になっていた。読めないので null が返り、プログラムから見れば「満了日は入っていない」ことになる。

そうなると、さっき並べた判定のうち契約満了日を見ている3つが、まるごと素通りする。実質的に生きていたのは「終了予定」のステータスを人間が手で入れたときだけ。事故を防ぐために付けた安全装置が、本番一発目から無効になっていた。

一番怖かったのは、壊れ方が「正常に見えた」こと

修正自体は一瞬で、ハイフンとスラッシュのどちらも受け付けるようにするだけだった。それより後を引いたのは、この壊れ方の性質のほう。

もしプログラムが落ちていたら、その場で気づいた。実際に出ていたのは「契約満了日が未入力です」という、それ単体では正しい文面の警告だった。マスタの入力漏れを親切に教えてくれているようにしか見えない。俺がその日たまたま「いや入れたはずだけど」と引っかからなければ、そのまま運用に乗っていた可能性が高い。

しかも設計の思想として、判定材料が無いときは「止める」ではなく「作った上で警告を添える」を選んでいた。全件止めると運用が回らなくなるからで、判断としては今も間違っていないと思う。ただその結果、読めない日付は静かに「素通り」側に倒れた。安全装置を付けたつもりで、実際には動かないときに黙って通す装置を作っていたことになる。

自動化で一番怖いのは、止まることではなく、それらしい顔で通り抜けることなんだと、身をもって理解した。

最後の1ステップはどうやっても自動化できなかった

もう一つ想定外だったのが、ゴール地点。書類を電子契約サービスにアップロードして署名依頼を送るところまでやりたかったんだけど、うちが使っているサービスには一般の開発者向けの公開APIが無かった(有料の連携サービスを噛ませるルートしか無い)。

なので、この仕組みは「PDFを作ってDriveに置いて、完了をメールの下書きで知らせる」で意図的に止めてある。最後に人間が中身を見てからアップロードする。

作っている最中は妥協した気分だったけど、上の安全装置バグが出た後だと評価が変わった。あそこで自動送信まで繋いでいたら、終わった案件の発注書が誰の目にも触れずに相手へ飛ぶ経路が完成していた。APIが無くて助かった、というのも変な話だけど、結果的にはそうだった。

かかった費用は月5ドル

余談ひとつ。デプロイしようとしたら、定期実行の枠が上限に達しているというエラーで弾かれた。Cloudflareの無料プランは、アカウント全体で定期実行の設定が5個まで。数えたら、別プロジェクトのものも含めてちょうど5個使い切っていた。

月5ドルの有料プランに上げて解決した。協力会社1社のための自動化に、月5ドル。今の時点では明らかに割に合っていない。ただ、この5ドルの枠は他の自動化とも共用なので、次に何か作るときの追加コストはゼロになる。そういう意味では、割に合う日はそのうち来ると思っている。

次回

今回は普通に作って、普通に動いて、途中で自分のミスを1個見つけた、という話だった。

次は予告どおり、このブログの収益導線に手を付けたら初日で前提が崩れた話を書く。そっちはまた、やめる方向の判断になる。