誰も使わなかった「決裁文書設定」を掘り起こしたら、ノーコードの申請フォームになった
誰も使っていないはずの機能を掘り起こしたら、動的フォームも起案もPDFも既にできていて、止まっていたのはセレクトボックスを囲むHTMLコメント1つだった。復活は1行、手間はその周りに全部あった。
直したのはコメントアウトを1つ外しただけだった。それなのに、そこへ辿り着くまでと、辿り着いたあとが長かった。
NGWLinux の「決裁文書設定」という画面を、ずっと前に見た記憶があった。休暇マスターと同じ @ 区切りのDSL(Domain Specific Language、ある用途だけのために作った独自の小さな記法。ここでは「1行で1項目」を表す)で、決裁書類の項目を自由に定義できる仕組みらしい。ただし記憶の中では、それは誰も使っていない機能だった。
今回、別件で決裁書類まわりを触っていて、この設定画面をもう一度開いた。動くかどうかも分からないまま、コードを掘ってみることにした。
掘ってみたら、全部あった
マスターのCRUD(Create・Read・Update・Delete、登録・参照・更新・削除の4操作。管理画面の基本セット)、DSLから動的フォームを組み立てる処理(fncMakeWfdBody)、起案・決裁・取下げ・PDF出力、業務ごとの分岐が入った通知処理まで、一通り実装済みだった。休暇マスター(jinjKyuka.php)を原型に、同じ項目定義DSLを流用して作られていた。
無かったのはこの5つだけだった。
- テンプレートを選ぶセレクトボックス — HTMLコメント
<!-- -->で丸ごと無効化され、項目IDが-1に固定されていた - 決裁完了時に所管へ通知する処理
- 所管から申請者へ書類を返す経路
- マスターの有効・無効を切り替える仕組み
- 保存先テーブルの
CREATE TABLE— リポジトリのどのSQLにも入っていなかった
つまり、画面としては完成していたのに、入口のセレクトボックスがコメントで塞がれていた。これが冒頭の「復活は1行」の正体である。既存データ27,000件超はすべて自由記述(テンプレート未選択)で決裁されていたので、「テンプレート未選択なら従来通りの自由記述」を残し、選択肢に「自由記述」という項目は並べないことにした。今まで通り自由に書きたい人には、今まで通りの画面を見せる。
復活は1行、地雷は索引にあった
セレクトを開放するだけなら数分の作業だった。腰を据えたのは、その先で見つけた索引のズレのほうだった。
このDSLは NAME@表示名@TYPE@高さ@選択肢 のように @ で区切った1行で1項目を表す。ところが、@ で区切った何番目が何を意味するかが、画面とPDFと原型のマスターとでバラバラだった。
- 決裁申請設定の画面は
SUBJECT/RADIO/CHECKを4番目、REM/TEXT_AREAを3番目から読む - 原型の休暇申請は
RADIOを3番目から読む - マスターのヘルプ文言は3番目の例しか出していない
- PDF側は3番目(高さ)を省略できない数値として扱う
この最後が致命的だった。RADIO@@… のように高さを省いて書くと float * '' で型エラーになり、DATE1@使用日@DATE のように3番目が無い行はエラーも出さず黙って落ちる。決裁画面のPDFプレビューが真っ黒になっていた原因はこれだった。
直したのは、PDF側は高さ未指定なら1行として扱う、4番目はisset()で存在チェックする、ヘルプ文言は「3番目は高さ。省略時は1、4番目まで使うなら3番目は @@ と空にする」で統一する、の3つ。だが直しながら気づいた。この書式を人に手で書かせる限り、この手の食い違いはまた起きる。 @@ の空欄を正しく数えて書ける人は少ない。
ビルダーを被せることにした本当の理由はここにある。保存形式はDSLの1行テキストのまま変えない。ビルダーは、ブロックをドラッグ&ドロップで並べたら、その裏で同じDSLの1行を書き出すだけの編集画面にした。実行側もPDF側も無改造で済む。ライブラリも追加していない。画面には既に読み込まれていた jQuery UI 1.11 の sortable / draggable がそのまま使えた。
既存のDSLをビルダーに読み込んで、保存し直して、もう一度DSLとして書き出す。それを決裁申請・休暇マスターの両方の既存データで試したところ、1文字も変わらなかった。往復して差分が出ないことを確認できて、初めて置き換えていいと判断できた。
実際に組んでみたテンプレートの1つが、戸籍の公用申請だった。DSLで書けばこの6行になる。
TXA1@事業名@TEXT_AREA@2
TXT2@補助事業名@TEXT@1
TXT3@必要な人の本籍@TEXT@1
SEL1@謄本・抄本@RADIO@1@1:謄本,2:抄本
TXT4@筆頭者氏名@TEXT@1
DT1@受領希望日@DATE@1
ビルダーに載せると、こうなる。

申請者が実際に開く決裁・申請画面は、こうなる。

試験で自分がデータを壊した
機能が動くところまで組んだあと、試験用のテンプレートをコマンドラインから流し込んで更新していた。そこで自分のデータを自分で壊した。
mysql -N で項目定義を取り出すと、改行は実際の改行ではなく \n という2文字の文字列になって出てくる。それに気づかないまま curl --data-urlencode でそのままサーバへ投げてテンプレートを上書きしたら、項目の区切りが文字列の \n に化けて保存された。エラーは何も出ない。申請画面を開くと、項目がごっそり消えていた。
REPLACE(定義, '\\n', '<br>') で復元して事なきを得たが、原因を見ながら「見る」と「書く」を同じコマンドの並びでやったのがまずかったと分かった。mysql -N で見た結果をそのまま書く側の入力に使い回したのが間違いで、試験は見る経路と書く経路を分けるべきだった。
アップローダは「フォルダの中のindex.php」に投げている
所管から申請者へ書類を返す機能も、同じ理由でもう一段つまずいた。添付のアップローダ(blueimp)は、アップロード先URLとして保存先フォルダそのものを指定する作りになっている。決裁者用のフォルダは、決裁画面を開いたときに WorkflowAttcDocs/TEMP/ の中身(index.php とファイル置き場)を再帰コピーして初めて出来上がる。
所管用の返信タブを先に画面へ出しただけで、フォルダを作る処理を呼び忘れた。結果、アップロードしたファイルはブラウザ側のキューに並んだまま、何も起きない。ここもエラーが出ない。タブを表示する前に、決裁者用フォルダを作っているのと同じ関数(fncRecursiveCopyDir())を呼ぶようにして直した。「フォルダの雛形はディレクトリごとに用意する」という、このアプリでは前にも踏んだやり方を、今回も踏んでいた。
決裁で終わらせず、返信まで乗せる
ここまでは申請と決裁の話だったが、実際に動かしてみて、もうひとつ気づいた。NGWの決裁ワークフローにそのまま乗せると、紙を1枚も挟まない申請と返信が組める。
流れはこうなる。決裁が完了すると所管へ通知が届く。所管は、その通知への返信として書類を添付して送り返す。申請者は、届いた返信を確認してダウンロードする。ここまで、ずっとペーパーレスのままで済む。




結局、新しく作ったのは「所管から返信」タブと、決裁完了時に飛ばす通知の宛先設定くらいだった。既存のワークフロー(通知・回覧・添付アップローダ)の仕組みに少し手を入れただけで、申請から返信までのやり取りが紙を挟まずに一周した。
休暇申請も、同じビルダーに乗せた
決裁申請設定に手を入れたついでに、原型だった休暇マスターの画面も同じビルダーに乗せ替えた。休暇と決裁申請ではDSLの流儀が少し違う(休暇には高さの枠が無い、選択肢の番号が別の位置にある)ので、ビルダーに「プロファイル」という概念を持たせ、休暇用と決裁申請用の違いを吸収した。休暇の既存DSLも、ビルダーに通して書き出し直しても1文字も変わらないことを確認してから切り替えた。
もともと1つの画面のために作ったビルダーが、原型だった側にも被せ直せたのは、保存形式のDSLを最後まで変えなかったからだった。
数字で見る
| 項目 | 値 |
|---|---|
| 復活に要した本質的な変更 | セレクトのコメントアウト解除(1箇所) |
| 追加したライブラリ | 0(既存の jQuery UI 1.11 sortable/draggable を流用) |
| DSLの種別 | 11種類(注記・1行・複数行・日付・日時・期間・時刻・時間帯・選択1つ・選択複数・その他) |
| 既存DSLをビルダーに往復させた差分 | 0(決裁申請・休暇マスターとも) |
| 自由記述のまま残した既存データ | 27,117件 |
AIとの協働作業での学び
「なぜ使われなかったか」より「今どこまで出来ているか」を先に聞いた。 誰も使っていない機能だと思い込んでいたので、最初から作り直すつもりで掘り始めた。実際にはマスターCRUDからPDF出力まで大半が実装済みで、要らなかったのは「もう一度全部作る」ではなく「止まっている1箇所を探す」だった。作り直す前に現状を洗い出す聞き方をしたから、無駄な実装をせずに済んだ。
索引のズレは、人に何番目かを数えさせている限り再発する。 @ 区切りの何番目が高さで何番目が選択肢か、画面・PDF・ヘルプがそれぞれ違う番号を信じていた。1箇所直しても、また誰かが違う番号で書けば同じ不具合が起きる。ビルダーを被せたのは機能の見た目を良くするためではなく、人に数えさせる場面そのものを無くすための選択だった。
「見る」コマンドの出力を、そのまま「書く」コマンドに使い回さない。 mysql -N の出力にある \n が文字列だと知らずに curl へ流し、自分の試験データを自分で壊した。AIに試験手順を組んでもらうときも、読み取った値をそのまま書き込みに使う経路は、間に1段変換や確認を挟むよう頼むべきだった。楽をしようとした場所で壊した。