従業員用WEBアプリ 要件定義書
AIへの一括プロンプト用 — このドキュメント全文を貼り付けるだけでシステムを構築可能
| 項目 | 内容 |
| バージョン | 1.1(2026-03-14) |
| 実績システム | ランチデリ(クリニック向け弁当注文システム) |
| 構築所要時間 | 約1セッション(AIプロンプト1回) |
1. システム概要・目的
1.1 概要
小〜中規模事業所(クリニック・施設等)の従業員が、社内で提供される弁当を
スマホ/PCから注文し、管理者がスプレッドシートで集計・帳票出力するWebアプリケーション。
1.2 解決する課題
- 紙の注文表 → 集計ミス・締切管理の手間
- メールやLINEでの注文 → 集約・カウントが煩雑
- Excel管理 → 複数端末でのリアルタイム共有が困難
1.3 利用者
| ロール | 人数目安 | 利用デバイス | 主な操作 |
| 従業員 | 5〜50名 | スマホ(メイン) | 注文・履歴確認 |
| 管理者 | 1〜3名 | PC(メイン) | メニュー管理・発注集計・帳票出力 |
| 責任者(例: 院長) | 1名 | - | 全メニュー自動注文(操作不要) |
1.4 システム構成図
[ブラウザ(スマホ/PC)]
|
v
[Firebase Hosting] ── React + TypeScript + Vite(SPA)
|
+──> [Firebase Auth] メール+パスワード認証
+──> [Cloud Firestore] 注文・メニュー・ユーザー・設定
+──> [GAS WebアプリURL] スプレッドシート連携 API
|
v
[Google Apps Script] コンテナバインド・V8ランタイム
|
v
[Googleスプレッドシート]
├── ダッシュボード 注文データ一覧
├── 受取サイン表 印刷用帳票
└── 労務士提出用 月次レポート
2. 技術スタック
| レイヤー | 技術 | バージョン | 備考 |
| フレームワーク | React + TypeScript | 19.x / 5.x | Vite でビルド |
| ビルドツール | Vite | 7.x | |
| ホスティング | Firebase Hosting | - | CDN配信 |
| 認証 | Firebase Auth | - | メール+パスワード |
| データベース | Cloud Firestore | - | リアルタイム購読対応 |
| 帳票エンジン | Google Apps Script(V8) | - | コンテナバインド |
| スプレッドシート | Google Sheets | - | GAS経由で読み書き |
| アイコン | lucide-react | - | |
| ルーティング | react-router-dom | 7.x | |
| E2Eテスト | Playwright | - | オプション |
注意: Vercel Serverless Functionsによるデータ永続化は禁止。
データは全てFirestoreに保存する。Google Sheetsはデータベースではなくレポート出力先として使用する。
3. データモデル(Firestore)
3.1 コレクション一覧
users
| フィールド | 型 | 必須 | 説明 |
| uid | string | * | Firebase Auth UID(ドキュメントID) |
| name | string | * | 氏名 |
| email | string | * | メールアドレス |
| employeeNumber | string | * | 社員番号 |
| isAdmin | boolean | * | 管理者フラグ |
| createdAt | Timestamp | * | 登録日時 |
menu_items
| フィールド | 型 | 必須 | 説明 |
| itemId | string | * | ドキュメントID(自動生成) |
| name | string | * | メニュー名 |
| description | string | | 説明 |
| price | number | * | 価格(税込) |
| storeName | string | | 店舗名 |
| imageUrl | string | | 画像URL |
| minOrderQuantity | number | | 最低注文数(省略時は制限なし) |
| isActive | boolean | * | 有効/無効 |
| createdAt | Timestamp | * | |
deliverySchedules
| フィールド | 型 | 必須 | 説明 |
| scheduleId | string | * | ドキュメントID |
| menuItemId | string | * | 紐づくメニューID |
| orderDeadline | Timestamp | * | 注文締切日時 |
| deliveryDateDay1 | Timestamp | * | 配送日1 |
| deliveryDateDay2 | Timestamp | * | 配送日2 |
| isActive | boolean | * | 有効/無効 |
| createdAt | Timestamp | * | |
orders
v1.1変更: 個人情報最小化のため userName フィールドを廃止。
管理者画面での表示時は userId から users コレクションをlookupして取得する。
| フィールド | 型 | 必須 | 説明 |
| orderId | string | * | ドキュメントID |
| userId | string | * | 注文者UID |
| menuItemId | string | * | メニューID |
| scheduleId | string | * | 配送スケジュールID |
| deliveryChoice | string | * | day1 / day2 / day1_flexible / day2_flexible / both |
| deliveryDate | Timestamp | * | 元の配送日(day1ベース) |
| confirmedDeliveryDate | Timestamp | | 確定配送日(管理者が設定) |
| status | string | * | pending / delivered / cancelled |
| createdAt | Timestamp | * | |
invited_users
| フィールド | 型 | 必須 | 説明 |
| email | string | * | 招待メール(ドキュメントID) |
| name | string | * | 氏名 |
| employeeNumber | string | * | 社員番号 |
| isAdmin | boolean | * | 管理者フラグ |
| invitedAt | Timestamp | * | |
settings
| フィールド | 型 | 必須 | 説明 |
| gasUrl | string | | GAS WebアプリURL |
| spreadsheetUrl | string | | スプレッドシートURL |
audit_logs v1.1追加
| フィールド | 型 | 必須 | 説明 |
| action | string | * | 操作種別(grant_admin / delete_user / add_menu 等) |
| targetId | string | | 操作対象のID |
| performedBy | string | * | 操作者のUID |
| performerEmail | string | | 操作者のメールアドレス |
| timestamp | Timestamp | * | 操作日時 |
| details | string | | 操作の詳細説明 |
監査ログの制約: クライアントからは create のみ可能。update / delete は禁止(改ざん防止)。
読み取りは管理者のみ。performedBy は認証済みユーザーのUID と一致しなければ書き込み拒否。
3.2 DeliveryChoice 型定義
type DeliveryChoice = 'day1' | 'day2' | 'day1_flexible' | 'day2_flexible' | 'both'
// day1: 配送日1を希望
// day2: 配送日2を希望
// day1_flexible: 配送日1希望だが変更可
// day2_flexible: 配送日2希望だが変更可
// both: 両日とも希望(責任者用)
4. ユーザーロールと認証
4.1 認証フロー
- 管理者が
invited_users にメールアドレス・社員番号・氏名を事前登録
- 従業員が招待メールのリンクからアプリにアクセス
- 新規登録画面でメール+パスワードを設定
- 登録時に
invited_users と照合し、一致すれば users コレクションに情報をコピー
- 招待にないメールアドレスは登録拒否
4.2 ロール
| ロール | 判定 | 権限 |
| 従業員 | isAdmin = false | 注文の作成・自分の注文閲覧/キャンセル・プロフィール編集 |
| 管理者 | isAdmin = true | 全注文の閲覧/編集/削除・メニュー管理・スケジュール管理・帳票出力・ユーザー招待・設定変更 |
4.3 AuthContext
React Contextで認証状態を管理。useAuth() フックで以下を提供:
currentUser — Firebase Auth User
userData — Firestoreのusersドキュメント
loading — 認証状態の読み込み中フラグ
login() / register() / logout()
アプリ初期化時にFirestoreの settings コレクションからGAS URLを読み込み、localStorageにキャッシュ。
5. 画面一覧・ルーティング
| パス | 画面名 | アクセス | 概要 |
/login | ログイン | 未認証 | メール+パスワード入力 |
/register | 新規登録 | 未認証 | 招待済みメールのみ登録可 |
/forgot-password | パスワード再設定 | 未認証 | リセットメール送信 |
/auth/action | 認証アクション | 全員 | メール内リンクの処理(パスワードリセット確認) |
/ | ホーム | 認証必須 | 注文可能なメニュー一覧、締切情報 |
/order | 注文 | 認証必須 | メニュー選択→配送日選択→注文確定 |
/history | 注文履歴 | 認証必須 | 自分の注文一覧、キャンセル操作 |
/profile | プロフィール | 認証必須 | 氏名変更、パスワード変更 |
/admin | 管理ダッシュボード | 管理者 | タブ構成(注文/メニュー/スケジュール/ユーザー/設定) |
未認証ユーザーが認証必須ページにアクセスした場合は /login にリダイレクト。
管理者以外が /admin にアクセスした場合は / にリダイレクト。
6. 機能要件(従業員側)
6.1 ホーム画面
- 注文受付中のメニュー一覧を表示(配送スケジュールが有効 + 締切前)
- メニューカード: 名前、店舗名、価格、画像、配送日1/2、注文締切日時
- 締切までのカウントダウン表示
6.2 注文画面
- メニューを選択後、配送日を選択: MUST
- 配送日1を希望 / 配送日2を希望
- 配送日1希望(変更可) / 配送日2希望(変更可)
- 両日とも希望(特定ユーザーのみ)
- 注文確定後、確認画面を表示
- 注文確認メールを送信(GAS経由)SHOULD
- 締切後は注文不可
6.3 注文履歴
- 自分の注文一覧を新しい順に表示
- ステータス表示: pending(注文中)/ delivered(配送済み)/ cancelled(キャンセル)
- pending状態の注文はキャンセル可能(締切前のみ)
6.4 プロフィール
- 氏名の変更
- 社員番号の表示(変更不可)
- パスワード変更(Firebase Auth)
7. 機能要件(管理者側)
7.1 管理ダッシュボード — タブ構成
「注文」タブ
- 発注サマリー(配送日別): MUST
- 配送日ごとにカードを横並び表示(日付順)
- 各カードにメニュー名と注文個数を表示
- 最低注文数に達していない場合は赤字で警告
- カード全体がタップで選択/解除のボタン(チェックボックスはビジュアルインジケーターのみ)
- 右上の x ボタンのみが個別削除
- 全選択チェックボックス + 一括削除ボタン
- 配送済みカードは半透明+「配送済み」表示
- カード内のメニュー名タップで変更依頼モーダルを開く
- 帳票出力ボタン(選択時に表示):
- 「受取サイン表を作成」→ 選択した日付のサイン表をGASで生成
- 「労務士レポートを作成」→ 選択した日付のレポートをGASで生成
- 注文一覧: 全注文のフィルタリング・検索・ステータス変更
「メニュー」タブ
- メニューの追加・編集・コピー・削除・有効/無効切替
- コピー時: 元メニューの内容を複製し、名前に「(コピー)」を付加
minOrderQuantity が未入力の場合は undefined としてFirestoreに保存しない(undefinedをFirestoreに渡さない)
「スケジュール」タブ
- 配送スケジュールの追加・編集・削除・有効/無効切替
- メニューとの紐付け(menuItemId)
- 配送日1・配送日2・注文締切日の設定
「ユーザー」タブ
- 招待ユーザーの追加(メール・氏名・社員番号・管理者フラグ)
- 招待メール送信(GAS経由)
- 登録済みユーザー一覧の表示
「設定」タブ
- GAS WebアプリURL の設定 → Firestoreの
settingsに保存
- スプレッドシートURL の設定 → 同上
- スプレッドシートを開くリンク
- 手動同期ボタン(「スプシに同期」)
7.2 スプレッドシート自動同期
自動同期仕様(デバウンス + ハッシュ比較):
- 注文データ(
orders)に変更検知
- 5秒のデバウンスタイマーを開始(連続変更を集約)
- 注文データのハッシュ値(orderId + status + confirmedDeliveryDate)を生成
- 前回同期時のハッシュと比較し、変更がなければスキップ
- 変更があればGASの
syncOrders エンドポイントにPOST
7.3 配送日の確定フロー
- 注文時の
deliveryChoice に基づき、実際の配送日を決定
day1 / day1_flexible → deliveryDateDay1
day2 / day2_flexible → deliveryDateDay2
both → 両日とも(責任者用: 各日の注文としてレポートに反映)
- 管理者は
confirmedDeliveryDate を手動で設定可能
- 一括確定ボタンで全未確定注文を一括設定
8. Google Apps Script 連携
8.1 GAS設計方針
- スプレッドシートにコンテナバインドされたスクリプト
- V8ランタイム使用(const/let使用可)
executeAs: USER_DEPLOYING / access: ANYONE_ANONYMOUS
- WebアプリとしてデプロイしURLをFirestoreに保存 → 全端末で共有
8.2 エンドポイント一覧
| action | HTTP | 説明 | 優先度 |
| status | GET | ヘルスチェック | MUST |
| syncOrders | POST | 注文データをダッシュボードシートに同期 | MUST |
| generateSignSheet | POST | 受取サイン表を生成(selectedDates配列) | MUST |
| generateLaborReport | POST | 労務士提出用レポートを生成(selectedDates配列) | MUST |
| sendOrderConfirmation | POST | 注文確認メール送信 | SHOULD |
| sendInvitationEmail | POST | 招待メール送信 | SHOULD |
| sendDeadlineReminder | POST | 締切リマインダーメール | NICE |
| sendDeliveryReminder | POST | 配送前リマインダーメール | NICE |
| setupTriggers | POST | 自動リマインダートリガー設定 | NICE |
8.3 受取サイン表の仕様
金銭管理に関わるため、以下の仕様を厳密に実装すること。
- 入力:
selectedDates(ISO形式の日付文字列配列: ["2026-03-16", "2026-03-17", ...])
- 日付フィルタリング: ダッシュボードシートの配送日を
selectedDatesのSetで照合。選択した日付のみ出力(月・期間ではなく完全一致)
- 上部テーブル(社員別注文一覧):
- 行3: メニュー名(セル結合、日付が早いメニューが左)
- 行4: 日付 | サイン | 日付 | サイン ... のペア
- 行5〜: 社員番号順に1行ずつ。注文あり→日付表示、なし→「ー」
- 責任者(社員番号1)は全メニュー・全日に自動追加
- 下部テーブル(集計):
- ヘッダー: メニュー(A-B結合)| 日付1 | 日付2 | ... | 合計
- 各行: メニュー名 | 日付ごとの注文個数 | 合計
- A-B列結合のため、日付列はC列から開始(B列に日付を置くと結合で隠れる)
- 個数は
employeeDataからカウント(責任者の自動追加分を含む)
- 日付ソート: 全ての日付表示は
parseDateLabel() でDateオブジェクトに変換し、実際の日付値でソート(文字列ソート禁止)
- メニュー順: 最も早い配送日を持つメニューが左に来る
8.4 スプレッドシートのシート構成
| シート名 | 用途 | 生成タイミング |
| ダッシュボード | 注文データ一覧(syncOrdersで更新) | 自動同期時 |
| 受取サイン表 | 印刷用サイン表 | generateSignSheet実行時 |
| 労務士提出用 | 月次レポート | generateLaborReport実行時 |
| 設定 | システム設定(systemName, adminEmail等) | initSettings実行時 |
9. ビジネスルール
9.1 注文ルール
- 注文は締切日時(
orderDeadline)前のみ受付可能
- 同一ユーザーが同一スケジュールに重複注文は不可
- キャンセルは締切前のみ可能(管理者は締切後もキャンセル可)
- 配送済み(delivered)ステータスの注文はキャンセル不可
9.2 責任者(院長)ルール
社員番号1のユーザーは特別扱い:
- 全メニューの全配送日に自動で注文が追加される
- 受取サイン表・集計に自動的に含まれる
- 実際にFirestoreに注文レコードがなくても、帳票生成時に
employeeDataに追加
9.3 招待ルール
invited_users に登録されたメールアドレスのみ新規登録可能
- 管理者フラグは招待時に設定(登録時に自分でisAdmin=trueにすることは不可)
- 招待メールにはアプリURLと登録手順を記載
9.4 配送日ルール
- 各メニューに配送日1(day1)と配送日2(day2)の2日間を設定
- 従業員は注文時にどちらの日を希望するか選択
flexible 付きは「この日希望だが変更OK」の意味
- 管理者は注文数に応じて配送日を調整可能
- スプレッドシート出力時は
deliveryChoice に基づいて正しい日付を使用
10. セキュリティ要件(v1.1強化)
v1.1で大幅に強化。 個人情報(氏名・メール・連絡先・社員番号)は管理者(isAdmin=true)のみがアクセス可能。
一般従業員は自分自身のデータのみ閲覧できる。
10.1 アクセス制御方針
| コレクション | get(個別取得) | list(一覧取得) | create | update | delete |
| users | 本人 or 管理者 | 管理者のみ | 本人(isAdmin設定禁止) | 本人(isAdmin変更不可)or 管理者 | 管理者のみ |
| orders | 本人 or 管理者 | 本人 or 管理者 | 本人のuserIdのみ | 本人 or 管理者 | 本人 or 管理者 |
| invited_users | 認証済み(登録確認用) | 管理者のみ | 管理者のみ | 管理者のみ | 管理者のみ |
| menu_items | 認証済み | 認証済み | 管理者のみ | 管理者のみ | 管理者のみ |
| deliverySchedules | 認証済み | 認証済み | 管理者のみ | 管理者のみ | 管理者のみ |
| settings | 管理者のみ | 管理者のみ | 管理者のみ | 管理者のみ | 管理者のみ |
| app_settings | 管理者のみ | 管理者のみ | 管理者のみ | 管理者のみ | 管理者のみ |
| audit_logs | 管理者のみ | 管理者のみ | 認証済み(本人UIDのみ) | 禁止 | 禁止 |
10.2 Firestore Security Rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function isAdmin() {
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.isAdmin == true;
}
function isOwner(docUserId) { return request.auth.uid == docUserId; }
function isAuthenticated() { return request.auth != null; }
match /invited_users/{email} {
allow get: if isAuthenticated();
allow list: if isAuthenticated() && isAdmin();
allow write: if isAuthenticated() && isAdmin();
}
match /users/{userId} {
allow get: if isAuthenticated() && (isOwner(userId) || isAdmin());
allow list: if isAuthenticated() && isAdmin();
allow create: if isAuthenticated() && request.auth.uid == userId
&& !("isAdmin" in request.resource.data);
allow update: if isAuthenticated() && (
(isOwner(userId) && !("isAdmin" in request.resource.data
&& request.resource.data.isAdmin != resource.data.isAdmin))
|| isAdmin());
allow delete: if isAuthenticated() && isAdmin();
}
match /orders/{orderId} {
allow read: if isAuthenticated() &&
(resource.data.userId == request.auth.uid || isAdmin());
allow create: if isAuthenticated() &&
request.resource.data.userId == request.auth.uid;
allow update, delete: if isAuthenticated() &&
(resource.data.userId == request.auth.uid || isAdmin());
}
match /menu_items/{itemId} {
allow read: if isAuthenticated();
allow write: if isAuthenticated() && isAdmin();
}
match /deliverySchedules/{scheduleId} {
allow read: if isAuthenticated();
allow write: if isAuthenticated() && isAdmin();
}
match /app_settings/{settingId} {
allow read: if isAuthenticated() && isAdmin();
allow write: if isAuthenticated() && isAdmin();
}
match /settings/{settingId} {
allow read: if isAuthenticated() && isAdmin();
allow write: if isAuthenticated() && isAdmin();
}
match /audit_logs/{logId} {
allow read: if isAuthenticated() && isAdmin();
allow create: if isAuthenticated() &&
request.resource.data.performedBy == request.auth.uid;
allow update, delete: if false;
}
}
}
10.3 個人情報最小化
- 注文データ(orders)に
userName を保存しない(v1.1で廃止)
- 管理者画面での表示は
userId → users コレクションからlookup
- メール送信時のみ、送信処理内で一時的にユーザー名を使用(永続化しない)
10.4 監査ログ
- 管理者操作(権限変更・ユーザー削除・メニュー変更・設定変更等)を自動記録
- クライアントから
audit_logs コレクションに直接書き込み(create のみ)
- 改ざん防止: update / delete は Firestore ルールで完全に禁止
- なりすまし防止:
performedBy が認証済みUIDと一致しなければ書き込み拒否
10.5 セッション管理
- 30分間操作がない場合、自動的にログアウト
- 自動ログアウト5分前に警告を表示
- マウス操作・キーボード操作・タッチ操作・スクロールを「アクティブ」として検知
10.6 HTTPセキュリティヘッダー(Firebase Hosting)
| ヘッダー | 値 | 目的 |
| Content-Security-Policy | default-src 'self'; script-src 'self' google/gstatic; ... | XSS防止 |
| X-Content-Type-Options | nosniff | MIMEスニッフィング防止 |
| X-Frame-Options | DENY | クリックジャッキング防止 |
| Referrer-Policy | strict-origin-when-cross-origin | リファラー情報の制限 |
| Permissions-Policy | camera=(), microphone=(), geolocation=() | 不要なAPI無効化 |
10.7 App Check(オプション — reCAPTCHA v3)
- Firebase App Check で不正クライアント(curl, Postman等)からのアクセスをブロック
- reCAPTCHA v3(無料)を使用、環境変数
VITE_RECAPTCHA_V3_SITE_KEY で有効化
- エミュレータ利用時はデバッグトークンモードで動作
10.8 その他セキュリティ要件
- 環境変数でFirebase設定を管理(
VITE_FIREBASE_*)
- APIキー・パスワードをコードにハードコードしない
- GAS URLは
ANYONE_ANONYMOUSだが、スプレッドシートへの書込みはGAS実行ユーザー権限で制御
- undefined値をFirestoreに送信しない(書き込み前にフィルタリング)
- 注文作成時に
userId が認証済みUIDと一致することをルールで強制(なりすまし注文の防止)
11. 非機能要件
| 項目 | 要件 |
| レスポンシブ | スマホファースト設計。モバイル(375px〜)〜デスクトップ(1280px)対応 |
| パフォーマンス | 初回ロード3秒以内(Firebase Hosting CDN) |
| リアルタイム性 | 注文データはFirestore onSnapshot でリアルタイム更新 |
| 同時接続 | 50名程度の同時利用を想定 |
| 印刷対応 | サイン表・労務士レポートはA4印刷に最適化 |
| アクセシビリティ | コントラスト比4.5:1以上、色だけで情報を伝えない |
| PWA | インストールプロンプト表示(オプション) |
12. デプロイ構成
12.1 Firebase プロジェクト設定
# Firebase 初期化
firebase init
# → Hosting (dist) + Firestore を選択
# 環境変数(.env)
VITE_FIREBASE_API_KEY=xxx
VITE_FIREBASE_AUTH_DOMAIN=xxx.firebaseapp.com
VITE_FIREBASE_PROJECT_ID=xxx
VITE_FIREBASE_STORAGE_BUCKET=xxx
VITE_FIREBASE_MESSAGING_SENDER_ID=xxx
VITE_FIREBASE_APP_ID=xxx
12.2 デプロイコマンド
# Webアプリのビルド&デプロイ
npx vite build && firebase deploy --only hosting
# Firestore Security Rules のデプロイ
firebase deploy --only firestore:rules
# GAS のデプロイ(clasp使用時)
cd gas && clasp push --force && clasp deploy -i "DEPLOYMENT_ID" -d "説明"
12.3 GAS の手動セットアップ
- Googleスプレッドシートを作成
- 「拡張機能」→「Apps Script」でエディタを開く
gas/Code.gs の内容を貼り付けて保存
- 「デプロイ」→「新しいデプロイ」→ ウェブアプリ(実行: 自分、アクセス: 全員)
- 承認して表示されたURLをアプリの設定画面に登録
13. AIプロンプト(コピペ用)
以下のプロンプトをAIに貼り付けるだけで、本システムの基盤を一括生成できます。
カスタマイズ箇所は 【...】 で示しています。
以下の要件で従業員用WEBアプリを作成してください。
## プロジェクト名
【例: ランチデリ — お弁当注文システム】
## 技術スタック
- React 19 + TypeScript + Vite 7(SPA)
- Firebase Hosting(CDN配信)
- Firebase Auth(メール+パスワード認証)
- Cloud Firestore(データベース)
- Google Apps Script(帳票生成・メール送信)
- lucide-react(アイコン)
- react-router-dom v7(ルーティング)
## ユーザーロール
1. 従業員(isAdmin=false): 注文の作成・自分の注文閲覧/キャンセル
2. 管理者(isAdmin=true): 全機能アクセス
3. 責任者(社員番号1): 全メニュー・全配送日に自動注文
## 認証フロー
- 管理者が invited_users コレクションにメール・氏名・社員番号を事前登録
- 従業員は招待済みメールでのみ新規登録可能
- 登録時 isAdmin=true の自己設定は Firestore Security Rules で禁止
## Firestoreコレクション
### users(ドキュメントID = Firebase Auth UID)
- name: string, email: string, employeeNumber: string
- isAdmin: boolean, createdAt: Timestamp
### menu_items(自動ID)
- name: string, description?: string, price: number
- storeName?: string, imageUrl?: string
- minOrderQuantity?: number(未入力時はフィールド自体を保存しない)
- isActive: boolean, createdAt: Timestamp
### deliverySchedules(自動ID)
- menuItemId: string, orderDeadline: Timestamp
- deliveryDateDay1: Timestamp, deliveryDateDay2: Timestamp
- isActive: boolean, createdAt: Timestamp
### orders(自動ID)— userNameは保存しない(個人情報最小化)
- userId: string, menuItemId: string, scheduleId: string
- deliveryChoice: 'day1'|'day2'|'day1_flexible'|'day2_flexible'|'both'
- deliveryDate: Timestamp, confirmedDeliveryDate?: Timestamp
- status: 'pending'|'delivered'|'cancelled'
- createdAt: Timestamp
- ※ 管理者画面でのユーザー名表示は userId から users コレクションをlookup
### audit_logs(自動ID)— 監査ログ(改ざん不可)
- action: string, targetId?: string
- performedBy: string(操作者UID), performerEmail?: string
- timestamp: Timestamp, details?: string
### invited_users(ドキュメントID = email)
- name: string, employeeNumber: string
- isAdmin: boolean, invitedAt: Timestamp
### settings(ドキュメントID = 'main')
- gasUrl: string, spreadsheetUrl: string
## 画面一覧
| パス | 画面 | アクセス |
|------|------|----------|
| /login | ログイン | 未認証 |
| /register | 新規登録(招待メール照合) | 未認証 |
| /forgot-password | パスワード再設定 | 未認証 |
| /auth/action | メールリンク処理 | 全員 |
| / | ホーム(注文可能メニュー一覧) | 認証必須 |
| /order | 注文(メニュー選択→配送日選択→確定) | 認証必須 |
| /history | 注文履歴(キャンセル操作あり) | 認証必須 |
| /profile | プロフィール編集 | 認証必須 |
| /admin | 管理ダッシュボード(タブ構成) | 管理者のみ |
## 管理ダッシュボードのタブ
1. **注文タブ**: 発注サマリー(配送日別カード)+ 注文一覧
2. **メニュータブ**: CRUD + コピー + 有効/無効切替
3. **スケジュールタブ**: 配送日1/2・締切日の設定
4. **ユーザータブ**: 招待管理 + 登録済みユーザー一覧
5. **設定タブ**: GAS URL・スプレッドシートURL設定
## 発注サマリーUI仕様
- 配送日ごとにカードを横並び(日付順、背景色は日付ごとに色分け)
- カード全体がタップで選択/解除(checkboxはビジュアルのみ)
- 右上 x ボタンのみが個別削除(stopPropagationで選択と分離)
- 全選択チェックボックス + 一括削除ボタン
- 選択状態のカードに青い枠線(#3b82f6)
- 配送済みカードは opacity:0.6 +「配送済み」ラベル
- カード内メニュー名タップで変更依頼モーダル(stopPropagation)
## スプレッドシート自動同期
- orders変更検知 → 5秒デバウンス → ハッシュ比較 → 変更時のみGAS POST
- ハッシュ = orders.map(o => `${o.orderId}:${o.status}:${o.confirmedDeliveryDate}`).join('|')
- 手動同期ボタンも設置
## GAS(Google Apps Script)
- コンテナバインドスクリプト(V8ランタイム)
- executeAs: USER_DEPLOYING / access: ANYONE_ANONYMOUS
- doGet: action=status でヘルスチェック
- doPost: action パラメータでルーティング
### 必須エンドポイント
- syncOrders: 注文データをダッシュボードシートに同期
- generateSignSheet(selectedDates): 受取サイン表を生成
- generateLaborReport(selectedDates): 労務士レポートを生成
### 受取サイン表の仕様(金銭管理に関わる — 厳密に実装)
- 入力: selectedDates(ISO日付配列)でフィルタ(月や期間ではなく完全一致)
- 上部テーブル: 行3=メニュー名(結合)、行4=日付|サイン|...
- メニュー順: 最も早い配送日を持つメニューが左
- 日付ソート: parseDateLabel()でDate変換し実値比較(文字列ソート禁止)
- 責任者(社員番号1)は全メニュー・全日に自動追加
- 下部テーブル(集計):
- ヘッダー: メニュー(A-B列結合)| 日付1 | 日付2 | ... | 合計
- A-B結合のため日付列はC列から開始(B列に日付を置くと結合で隠れる)
- 個数はemployeeData(責任者自動追加済み)からカウント
## undefinedフィルタリング
Firestoreへの書き込み前に undefined 値を除去するヘルパー関数を使用:
```typescript
function removeUndefined(obj: Record<string, unknown>): Record<string, unknown> {
const cleaned: Record<string, unknown> = {};
for (const [key, value] of Object.entries(obj)) {
if (value !== undefined) { cleaned[key] = value; }
}
return cleaned;
}
```
## Firestore Security Rules(v1.1強化)
- users: get=本人or管理者、list=管理者のみ、create=本人(isAdmin禁止)、update=本人(isAdmin変更不可)or管理者
- orders: read=本人or管理者、create=本人のuserIdのみ、update/delete=本人or管理者
- invited_users: get=認証済み、list/write=管理者のみ
- menu_items / deliverySchedules: read=認証済み、write=管理者のみ
- settings / app_settings: read/write=管理者のみ
- audit_logs: read=管理者のみ、create=認証済み(performedBy=本人UID)、update/delete=禁止
## セキュリティ対策
- 個人情報最小化: ordersにuserNameを保存しない(lookup方式)
- 監査ログ: 管理者操作を自動記録(改ざん不可)
- セッション管理: 30分無操作で自動ログアウト
- HTTPヘッダー: CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy
- App Check: reCAPTCHA v3で不正クライアント防止(オプション)
## 環境変数(.env)
VITE_FIREBASE_API_KEY, VITE_FIREBASE_AUTH_DOMAIN,
VITE_FIREBASE_PROJECT_ID, VITE_FIREBASE_STORAGE_BUCKET,
VITE_FIREBASE_MESSAGING_SENDER_ID, VITE_FIREBASE_APP_ID
## 非機能要件
- スマホファースト(375px〜1280px対応)
- リアルタイム更新(Firestore onSnapshot)
- A4印刷最適化(サイン表・レポート)
- アクセシビリティ: コントラスト比4.5:1以上
## 注意事項
- Google SheetsはDBではなくレポート出力先として使用
- Vercel Serverless Functionsでのデータ永続化は禁止
- イミュータブルパターン(オブジェクト変異禁止)
- 日本語UIとコメント