はじめに
「ポートフォリオに業務システムを作りたいけど、何から始めればいいの?」
Todoアプリやブログはひと通り作ったけれど、次はもう少し「仕事っぽい」ものを作ってみたい。
そう思ったときに、いきなりコードを書き始めてしまうと、途中で「あれ、これ何を作るんだっけ?」となりがちですよね。
私はいま、ポートフォリオとして社内業務システム「office-hub」を個人開発しています。
社員がポータル画面にログインして、そこから自分が使えるシステムへ移動する構成で、その第1弾として備品在庫管理システムを作っています。
この連載では、開発の工程を1フェーズ=1記事で記録していきます。記事の番号は開発のフェーズの番号とそろえていて、#0のこの記事は、コードを1行も書く前の「設計」のフェーズです。
- 題材をどう選んだか(4つの候補を比べた理由)
- 要件定義書に何を書いたか
- 「未決事項」を「決定事項」に書き換えて残すやり方
- 設計書の段階で見つかったバグ4つ
を順番に紹介します。
先にお伝えしておくと、設計書はAI(Claude)と対話しながら一緒に作りました。
この記事でも、AIに助けてもらったところはそのまま書いていきます。
office-hubの全体像
まずは、何を作っているのかをざっくり紹介します。
| 領域 | 使っている技術 |
|---|---|
| バックエンド | PHP / Laravel |
| フロントエンド | React / TypeScript / Vite / Tailwind CSS |
| データベース | MySQL |
| 認証 | Laravel Sanctum |
| 開発環境 | Laravel Sail(Docker) |
開発は次のように段階(マイルストーン)を分けて進めています。
この記事を書いている時点では、M0(環境構築・ログイン・ポータル画面)まで完成しています。
| 段階 | 内容 |
|---|---|
| M0 | 環境構築、ログイン、ポータル画面 |
| M1 | 備品マスタ、在庫の入出庫 |
| M2 | 貸出・返却 |
| M3 | 棚卸し |
| M4 | アラート、ダッシュボード、監査ログ |
| M5 | CSV、QRコード、テストの拡充 |
コードは GitHub(riu-414/office-hub) で公開しています。
この記事で紹介する設計書も docs フォルダに入っています。
題材選び:4つの候補を比べた
「実務で使えそうな業務システム」という方針で、最初に4つの候補を出しました。
比べたのは技術的な見せ場と弱点です。
| 候補 | 技術的な見せ場 | 弱点 |
|---|---|---|
| 備品在庫管理+貸出 | 在庫の増減を正しく保つ処理、履歴からの在庫数の計算、定期的な通知 | 題材が定番で埋もれやすい |
| 会議室・設備予約 | 二重予約の防止、カレンダー画面 | データの構造が小さく、見た目勝負になりがち |
| 受発注・請求管理 | 状態の移り変わりの設計、金額計算、PDF出力 | 業務の知識が必要で、要件がふくらみやすい |
| ヘルプデスク | 通知、対応期限切れの自動判定、全文検索 | 登録・一覧・コメントだけで終わると平凡になる |
選んだのは備品在庫管理です。
理由は、「定番だからこそ、作りの丁寧さで差が出る」と考えたからです。
同じ題材の作品が多いということは、見る人にとって比べやすいということでもあります。
そこで、次のような「一段深い」部分を入れることにしました。
- 在庫の数をそのまま保存せず、入出庫の履歴から計算できるようにする
- 2人が同時に在庫を操作しても数がズレないようにする
- 棚卸し(システム上の数と、実際に数えた数の差を調整する機能)
- 操作の記録(監査ログ)と、役割ごとの権限
このあたりは実装する段階(M1以降)で、それぞれ記事にする予定です。
要件定義書に書いたこと
題材が決まったら、次は要件定義書です。
「このシステムで何をできるようにするのか」を文章にまとめたもので、今回はこんな章立てにしました。
- 背景と目的
- スコープ(やること・やらないこと)
- 用語の定義
- 利用者と役割(ロール)
- 機能要件(41件)
- 主な業務の流れ
- データの構造(ER図)
- 画面一覧(17画面)
- 非機能要件(速さ・安全性など)
- 設計方針
- リリース計画
- 決定事項
全部を紹介すると長くなるので、作ってみて「これはやってよかった」と感じたポイントを3つに絞ってお話しします。
ポイント1:目的を「在庫を管理する」にしなかった
要件定義書の目的には、こう書いています。
在庫の「現在の数」と「そうなった経緯」を、関係者全員が同じ画面で確認できる状態にする
「在庫を管理する」だけだと、在庫の数さえ合っていればいいことになってしまいます。でも実際の現場で困るのは、「帳簿と実物の数が合わないけど、いつズレたのか分からない」ことなんですよね。
目的に「そうなった経緯」まで入れたことで、このあとの設計の判断がぶれなくなりました。たとえば「在庫の履歴は消さない」「間違えた記録も、取り消しの記録を追加して残す」といった方針は、すべてこの一文から出てきています。
ポイント2:「やらないこと」を理由つきで書いた
スコープの章では、やることだけでなくやらないことも書きました。たとえばこんな感じです。
| やらないこと | 理由 |
|---|---|
| 発注業務そのもの | 「在庫が減ったら知らせる」までを担当にする。発注は会社の既存の流れに任せる |
| 会計との連携 | 会計の要件は重く、「在庫を見える化する」という目的から外れる |
| スマホアプリ | スマホ・タブレットでも見やすいWeb画面で代わりにする |
個人開発だと「あれもこれも」と機能を足したくなりますが、やらないことを先に決めておくと、途中で迷ったときの判断が楽になります。
機能要件はMoSCoW(モスクワ)法で優先度をつけました。
| 記号 | 意味 | 今回の件数 |
|---|---|---|
| Must | これがないとリリースできない | 28件 |
| Should | 重要だが、なくてもリリースはできる | 8件 |
| Could | あればうれしい | 5件 |
| Won’t | 今回はやらない | (スコープ外の表で管理) |
「重要度:高・中・低」よりも判断がはっきりするのがいいところです。
Mustの機能を全部合わせたものが、そのままリリースの範囲になります。
ポイント3:「未決事項」を「決定事項」に書き換えて残した
最初の版では、決めきれない論点を未決事項として6件書き出し、それぞれに選択肢を並べました。
そのうえで1つずつ結論を出していきました。
| 論点 | 決めたこと |
|---|---|
| 貸し出す備品を1台ずつ管理するか | 数量だけで管理する |
| 貸出に申請・承認の流れを入れるか | 入れない |
| 在庫の履歴を一般の社員にも見せるか | 見せる |
| 保管場所を最初から複数扱うか | 備品ごとに1か所 |
| 在庫が減ったときの通知先 | 管理者と在庫担当者の全員 |
| 画像の保存先 | サーバー内(設定でクラウドにも切り替えられる形) |
大事なのは、決めたあとに結論だけでなく「なぜそう決めたか」も残したことです。
たとえば「履歴を一般の社員にも見せる」は、目的の「関係者全員が同じ画面で確認できる」にそのまま沿っています。見せないことにすると、目的と矛盾してしまうんですね。
理由まで書いておくと、こんないいことがあります。
- 半年後の自分が「なんでこうなってるんだっけ?」と同じ議論をくり返さずに済む
- 前提が変わったときに「この理由が崩れたから見直そう」と判断できる
- ポートフォリオを見た人に、考えた過程まで伝えられる
設計書の段階で見つかったバグ4つ
要件定義書のほかに、DB設計書(データベースのテーブルの設計)とAPI設計書(画面とサーバーのやり取りの設計)も作りました。
そして、この3つの設計書をAIにレビューしてもらったところ、バグが4つ見つかりました。
まだコードを1行も書いていないのに、です。
どれも、設計書を書いている最中には見落としていたものです。
バグ1:在庫が少ないかどうかの判定式
備品には「この数を下回ったら知らせる」という発注点を設定できるようにしています。
発注点を0にした備品は「知らせなくていい備品」という扱いです。
最初は、在庫が少ないかどうかをこう判定していました。
on_hand_quantity <= reorder_point
-- 手元の在庫数 <= 発注点
一見よさそうですが、発注点が0の備品の在庫が0になると、0 <= 0 で「在庫が少ない」と判定されてしまいます。
知らせなくていいはずの備品なのに、です。
修正後はこうなりました。
reorder_point > 0 AND on_hand_quantity <= reorder_point
「0のときどうなる?」という境界の値を考えていなかった、典型的なミスでした。
バグ2:書いてあるのに、テーブルがない
API設計書には「同じ登録が2回届いても1回分として扱うために、受け取った目印(キー)を24時間保存する」と書いていました。
ところが、その保存先のテーブルがDB設計書のどこにもありませんでした。
設計書を3つに分けて書いたせいで、片方にしか存在しない仕様が生まれていたんです。
設計書を分けるなら、「この仕様は別の設計書のここに対応する」と、お互いの参照先を書いておく必要があると学びました。
バグ3:「なければ作る」が同時アクセスに弱い
在庫の数を更新する処理に、「在庫の行がまだなければ作ってから更新する」と書いていました。
これは、2人が同時に初めての操作をすると、2人とも「まだない」と判断して、同じ行を2つ作ろうとしてしまう危険があります。
エラーになるか、最悪の場合は在庫が二重に数えられます。
修正では、備品を登録するときに、在庫の行も同時に作ることにしました。
在庫を更新するときには「必ずある」前提で読むだけなので、取り合いが起きません。
バグ4:UNIQUE制約がNULLのときに効かない
ユーザーを削除するときは、データを本当に消すのではなく「削除した日時」を記録する論理削除にしています。
そして、削除した人と同じメールアドレスで再登録できるように、「メールアドレス」と「削除日時」の組み合わせが重複しないようにする制約(UNIQUE制約)を考えていました。
ところが、MySQLのUNIQUE制約はNULL(値がない状態)同士を「別の値」として扱います。
削除されていないユーザーは削除日時がNULLなので、同じメールアドレスの人を何人でも登録できてしまうんです。
これは実際にデータベースを作る直前に分かったもので、そのまま作っていたら確実にハマっていました。
この話はかなり奥が深いので、データベースを作る回でくわしく書きます。
4つのバグから学んだこと
振り返ると、バグ2・3・4には共通点がありました。
- 「別の場所に書いたつもり」(バグ2)
- 「なければ作る」「重複していなければ登録する」を、確認してから実行する(バグ3・4)
特に後者は「確認してから実行する」の間に、別の人の操作が割り込める、という同じ構図です。
この反省から、重複を防ぐのはプログラムの確認ではなく、データベースの制約に任せるという方針を設計全体で採るようになりました。
そしてもう1つ。設計書もコードと同じで、レビューが必要だということです。
今回は人ではなくAIのレビューでしたが、コードを書く前にこれだけ見つかったのは大きかったです。
まとめ
- 題材は「定番だからこそ作りの丁寧さで差が出る」と考えて、備品在庫管理を選んだ
- 要件定義書の目的は「在庫を管理する」ではなく、「今の数」と「そうなった経緯」を全員で確認できることにした。これがあとの設計判断の軸になった
- 「やらないこと」も理由つきで書き、機能はMoSCoW法で優先度をつけた
- 未決事項は、結論と理由をセットで「決定事項」に残した
- 設計書をAIにレビューしてもらったら、コードを書く前にバグが4つ見つかった
次回は、Laravel Sail を使った開発環境の構築について書く予定です。
私はふだん製造業で働きながら、WordPressを中心にWebサイトの制作をしています。
これまでの制作実績はポートフォリオにまとめています。
office-hubのコードはGitHubで公開しているので、設計書の中身が気になった方はのぞいてみてください。

