TOKIWA — 要件定義書 兼 Claude Code 実装指示書
版数 1.0(DBレス構成 / 記事埋め込み型デモ)/ 作成日 2026-08-24 / 発行 ソウゾウ合同会社 用途 SEO記事「【デモで試せる】Claude Codeで勤怠・シフト管理システムを自作する方法と費用|動画つき資料【2026年】」に組み込む実演デモ
このドキュメントの使い方(Claude Code へ)
これ1枚が、規約・仕様・タスクのすべて。リポジトリ直下に CLAUDE.md として置き、毎回読み込むこと。
- 実装は「12. 実装タスク」の順に進める。フェーズを飛ばさない。
- 各タスクの括弧内は要件ID。着手前に該当章を読むこと。
- 仕様がここに書かれていない場合は、推測で実装せず質問する。
- スコープ外(3章 Won't have)は、思いついても実装しない。
- 「デモだから」を理由に品質を落とす判断はしない。
- この領域だけの特別ルール:計算ロジックを推測で書かない。 割増賃金・休憩・36協定の判定は法令に基づく。仕様が曖昧なまま実装すると、動くが違法な計算が生まれる。曖昧なら必ず質問する(1章)。
姉妹プロジェクトとの関係 HIREBASE(求人)/ RELATE(顧客管理)/ CASTA(動画配信)/ FIELDPIN(位置情報)/ KANADE(音楽)/ MIWAKE(画像認識)/ KAMIWAZA(美容室予約)/ KURA(在庫管理)に続くシリーズ。並べて「同じテンプレ」と思われた時点で失敗。 **特に RELATE / KURA との差別化に注意。**3本とも「白背景・高密度・業務システム」だが、 RELATE=罫線と等幅数字で表を読ませる(藍)/ KURA=各行に在庫の水位バーを描く(ティール+アンバー・縦長数字)/ TOKIWA=1日を24時間の帯として横に描く。時間の重なりが図で見える(インディゴ+コーラル・等幅数字+大きな時刻表示)。 8章のデザイン要件は意図的に別方向に振っている。トークンを流用しないこと。
目次
- プロジェクト概要
- アーキテクチャ方針
- スコープ定義
- ロールとデモ切替
- 画面一覧とユーザーフロー
- 機能要件
- データ設計
- デザイン要件
- 技術要件・ディレクトリ構成
- 非機能要件・法令
- 費用設計
- 実装タスク
- 受入基準
1. プロジェクト概要
背景と目的
「勤怠・シフト管理を作りたい」という相談の背景は、ほぼ次のいずれか。
- タイムカードとExcelで運用しており、月末の集計に何日もかかる
- シフトをLINEで調整していて、希望の取りまとめと確定の連絡が毎月地獄
- 残業時間の把握が月末までできず、気づいたら上限を超えていた
そして、この領域には他のどのテーマにもない特殊性が3つある。
特殊性①:間違えると法令違反になる
在庫が1個ズレても事故だが、労働時間の計算がズレると未払賃金・36協定違反になる。しかも従業員全員に影響し、遡って修正が必要になる。
勤怠システムは「動けばいい」が最も通用しない領域。 割増賃金の計算式、休憩の付与義務、時間外の上限——これらは設計者の裁量ではなく法令で決まっている。
そして厄介なのは、画面上は正しく動いているように見えること。だから計算ロジックは純粋関数として切り出し、テストで固める。それがこのプロジェクトの中核(2章)。
特殊性②:「打刻」は生データ。「勤務時間」は解釈
打刻を集計すれば勤務時間になる——ならない。間に大量の解釈が挟まる。
打刻(生データ) 9:03 出勤 / 12:01 休憩開始 / 12:58 休憩終了 / 19:47 退勤
↓ ①丸め(15分単位?切り捨て?切り上げ?→ 切り捨ては違法になりうる)
↓ ②所定労働時間との突き合わせ(9:00-18:00 / 休憩60分)
↓ ③休憩の実績と法定休憩の充足チェック
↓ ④遅刻・早退の判定
↓ ⑤法定内残業と法定外残業の切り分け
↓ ⑥深夜(22:00-5:00)の抽出
↓ ⑦休日労働(法定休日/所定休日)の判定
↓ ⑧割増率の適用と重複(深夜×休日 等)
勤務時間(解釈済み) 所定8h / 法定外残業1.75h / 深夜0h / 休憩57分(法定60分に3分不足)
この①〜⑧のどれか1つを間違えると、給与が変わる。 そして各社で①②⑦のルールが違う。だから設定で切り替えられる構造にしなければならない。
特殊性③:シフトと勤怠は別物だが、繋がっている
| シフト | 勤怠 | |
|---|---|---|
| 何を扱うか | 予定(これから働く予定) | 実績(実際に働いた記録) |
| 誰が作るか | 管理者(希望を集めて組む) | 従業員(打刻する) |
| 目的 | 人員配置・人件費の予算管理 | 給与計算・法令遵守 |
多くの失敗は「シフト=勤怠」と考えて作ること。 シフト通りに働くことは稀で、予定と実績の差分こそが管理対象。TOKIWA はこの2つを別のデータとして持ち、常に並べて見せる。
TOKIWA はこの3つの特殊性を前提に設計する。
| 目的 | 内容 |
|---|---|
| 実装力の証明 | 打刻・丸め・残業区分・深夜・休日・36協定・シフト希望収集・自動シフト生成・有給まで、実運用に耐える勤怠エンジンを見せる |
| 期待値の正常化 | **「打刻は生データ、勤務時間は解釈」**を実物で示す。設定を変えると計算結果が変わることを体験させる |
| 法令リスクの可視化 | 36協定の残り時間、法定休憩の不足、割増の内訳を画面上で示す |
| 費用判断の材料 | 人数課金の既製サービスと自作の損益分岐、打刻ハードウェアの選択肢を示す |
プロダクト定義
| 項目 | 内容 |
|---|---|
| プロダクト名 | TOKIWA(時輪)※仮称 |
| 一言定義 | 打刻の生データから勤務時間を組み立て、シフトの予定と実績を並べて管理するシステム |
| 想定業態 | 飲食・小売(シフト制)、介護・医療(夜勤あり)、製造(交替勤務)、士業・IT(固定時間+残業) |
| 提供形態 | レスポンシブWebアプリ(PWA)。フロントエンド完結(サーバー側の永続化なし) |
| 主戦場 | 従業員はスマホ(打刻・シフト希望提出)。管理者はPC(シフト作成・集計・承認) |
| 想定利用者 | 記事の読者。登録なしで、従業員側と管理者側の両方を体験できる |
プロダクトコンセプト
「打刻を書き換えない。解釈を見せる。」
TOKIWA は打刻の生データを一切変更しない。丸めも、残業区分も、割増も、すべて生データからの算出結果として提示する。だから「なぜこの時間になったか」が常に遡れ、設定を変えれば即座に再計算される。
デモとしての成功条件
- 打刻と解釈が分かれて見える — 生の打刻と、丸め後・区分後の時間が同じ画面に並んでいる
- 設定を変えると計算が変わる — 丸め単位を「1分」から「15分切り捨て」に変えると、残業時間と割増額が変わる
- 法令の警告が出る — 法定休憩の不足、36協定の上限接近が具体的な数字で警告される
- シフトと実績が並ぶ — 予定と実績の差分が1画面で分かる
- ロールで見え方が変わる — 従業員は自分だけ、管理者はチーム全体。給与額は管理者のみ
- リセットできる — 誰が触った後でも初期状態に戻せる
2. アーキテクチャ方針
基本方針
バックエンドとデータベースを持たない。すべてブラウザ内で完結させる。
| 一般的な構成 | 本プロジェクト |
|---|---|
| PostgreSQL | シードデータ + ブラウザ内ストア |
| 認証・SSO | デモ用ロール切替(ワンクリック) |
| REST / GraphQL API | ストア上の同期的な操作 |
| ICカード/生体認証の打刻機 | Web打刻(ボタン)+ 位置情報の任意付与 |
| 給与計算システム連携 | CSVエクスポートで代替 |
| メール・LINE通知 | 送信内容のプレビュー表示で再現 |
| 労働時間の計算 | これは本物。 生打刻からの算出をすべて実装する |
なぜこの構成にするか
- LPデモとして最適 — 訪問者は登録もログインもせずに、全機能を即座に触れる
- 打刻データが外に出ない — 労働時間は従業員の個人情報。データを預からないことが訴求になる
- 開発速度 — 認証・APIの実装が不要になり、計算ロジックとUIの作り込みに全時間を投下できる
- 運用コストゼロ — Vercel の静的配信のみ
計算エンジンの原則(最重要の設計判断)
打刻を書き換えない。すべて生データからの算出結果として持つ。
[ 生打刻 ] ← 絶対に変更しない(訂正は「訂正申請+承認」の別レコード)
↓
[ 丸め ] 丸め単位・方向を設定で切替
↓
[ 休憩の突き合わせ ] 実績休憩 vs 法定休憩の充足判定
↓
[ 所定との比較 ] 遅刻・早退・所定内労働
↓
[ 残業の区分 ] 法定内残業 / 法定外残業
↓
[ 時間帯の抽出 ] 深夜(22:00-5:00)/休日(法定/所定)
↓
[ 割増の適用 ] 率の重複を正しく合成
↓
[ 日次サマリ ] → [ 月次集計 ] → [ 36協定チェック ]
| ID | 規約 |
|---|---|
| CAL-01 | 打刻レコード(Punch)を編集・削除しないこと。 訂正は PunchCorrection(申請+承認)として別レコードで持ち、算出時に適用する |
| CAL-02 | 労働時間を保存しないこと。 lib/attendance/ で常に生打刻と設定から算出する |
| CAL-03 | lib/attendance/ は純粋関数のみ。 ストア・DOM に依存させず、現在時刻・就業規則設定を引数で受け取ること |
| CAL-04 | 算出結果はメモ化してよいが、メモ化のキーに「打刻の世代番号」と「就業規則設定のバージョン」の両方を含めること。設定変更で必ず無効化されること |
| CAL-05 | 時刻は「その日の0時からの分数」で内部処理すること。 日跨ぎ勤務は 24:00 超(1500分 = 翌1:00)で表現し、文字列の切り貼りをしないこと |
| CAL-06 | 「なぜこの時間になったか」の内訳を必ず返すこと(丸め前後、区分ごとの時間、適用した割増率、根拠となった設定) |
| CAL-07 | 法令に関わる判定(法定休憩・36協定・割増率)を、UIコンポーネント側に書かないこと。 必ず lib/attendance/ から返す |
この設計が、記事の技術パートの中核になる。 「打刻を丸めて保存する」設計との違いを説明できることが専門性になる。
差し替え可能性の担保
データアクセスは必ず lib/repo/ のリポジトリ層を経由すること。 コンポーネントからストアを直接触らない。
[ コンポーネント ]
↓ 呼ぶのはこの層だけ
[ lib/repo/*.ts ] ← 全メソッドを async にしておく
↓
[ lib/attendance/ ] ← 労働時間の算出(純粋関数)
[ lib/shift/ ] ← シフト生成・充足判定(純粋関数)
[ lib/store/*.ts ] ← Zustand + persist(localStorage)
↓
[ lib/seed/*.ts ] ← 初期データ
リポジトリの各メソッドは、中身が同期処理でも async で定義し、await で呼ぶ。実案件へ転用する際、リポジトリの実装だけをAPI呼び出しに差し替えれば、UI層は一行も変更せずに済む。
記事への組み込み
| 段 | 配置 | 中身 |
|---|---|---|
| ① 記事内インライン | 記事の冒頭〜中盤、<iframe> |
1日の打刻タイムライン + 丸め設定トグル。 設定を変えると勤務時間と割増額が変わる |
| ② 全画面デモ | 「全画面で試す」カード → 別タブ | アプリ全体(シフト作成・36協定・有給・月次集計・承認) |
記事内で見せるのは「設定を変えると計算結果が変わること」だけに絞る。 これが1章の特殊性②そのものであり、スクロール中に一目で伝わる唯一の絵になる。
| ID | 要件 |
|---|---|
| EMB-01 | 記事内 iframe は loading="lazy" とし、ビューポート接近まで読み込まないこと |
| EMB-02 | 埋め込みモードのJS初期バンドルは 60KB以下(gzip後)とすること |
| EMB-03 | 埋め込み iframe が記事の LCP 要素にならないこと |
| EMB-04 | 埋め込みモードで位置情報(Geolocation)を呼ばないこと。 打刻ボタンも押させないこと(表示のみ) |
| EMB-05 | 埋め込みモードでは、ヘッダー・ナビ・ロール切替バーを非表示とし、アプリ内遷移を禁止すること。「全画面で試す」は target="_blank" |
| EMB-06 | 設定トグル(丸め単位・丸め方向)の切替による再計算は 50ms以下で完了すること |
| EMB-07 | GA4 で計測すること:demo_open / rounding_changed(丸め設定を変えた)/punch_posted / shift_generated / overtime_alert_viewed / role_switch / doc_download / form_click / line_click |
| EMB-08 | rounding_changed は最重要指標。 「読者が設定を変えて計算差を自分で確かめたか」が、この記事の主張が届いたかを表す |
| EMB-09 | 氏名・打刻データ・給与額を GA4 に送らないこと |
| EMB-10 | ?embed=1 のURLは noindex とすること。デモ本体は固有の title / description を持つこと |
永続化の範囲
| 対象 | 挙動 |
|---|---|
| シードデータ(従業員・部署・就業規則・シフト・過去3ヶ月の打刻) | 初期投入。マスタは編集可能 |
| 打刻・訂正申請・シフト希望・シフト・休暇申請・承認 | localStorage に保存。リロードしても残る |
| 就業規則の設定変更 | localStorage に保存。変更すると過去分も再計算される(バージョン管理付き) |
| リセット | 「デモをリセット」で localStorage をクリア |
擬似ディレイ は 150〜400ms。ただし打刻・労働時間の再算出・一覧の絞り込みは 100ms以下とし、ディレイを入れない(打刻でもたつくのは致命的な体験)。
3. スコープ定義
打刻・勤怠(従業員)
| ID | 機能 | 概要 | 優先度 |
|---|---|---|---|
| F-P01 | 打刻 | 出勤/退勤/休憩開始/休憩終了。現在の勤務状態に応じてボタンが変わる | Must |
| F-P02 | 位置情報の任意付与 | 打刻時に位置を記録(設定でON/OFF、拒否しても打刻可) | Should |
| F-P03 | 本日の勤務状況 | 現在の勤務時間、休憩残り、退勤予定時刻、残業見込み | Must |
| F-P04 | 月次勤務一覧 | 日別の打刻・勤務時間・残業・区分。丸め前後の両方を表示 | Must |
| F-P05 | 勤務日詳細 | 打刻タイムライン、丸めの適用、区分ごとの内訳、計算の根拠 | Must |
| F-P06 | 打刻訂正申請 | 打刻漏れ・誤打刻の訂正を申請。生打刻は変更しない | Must |
| F-P07 | 残業時間の見える化 | 当月の累計残業、36協定の上限までの残り時間 | Must |
| F-P08 | 休暇申請 | 有給・特別休暇・欠勤。半休・時間単位有給に対応 | Must |
| F-P09 | 有給残日数の確認 | 付与日、残日数、失効予定日、取得義務(年5日)の進捗 | Must |
| F-P10 | オフライン打刻 | 圏外でも打刻でき、復帰時に同期 | Should |
シフト(従業員)
| ID | 機能 | 概要 | 優先度 |
|---|---|---|---|
| F-S01 | シフト希望の提出 | 期間内の希望を入力(勤務可/不可/時間指定)。締切管理 | Must |
| F-S02 | 確定シフトの確認 | 自分のシフト(月/週表示)、変更通知 | Must |
| F-S03 | シフト交代の申請 | 他メンバーへの交代依頼、相手の承諾、管理者承認 | Should |
| F-S04 | シフトのics出力 | 自分のシフトをカレンダーに取り込む | Could |
シフト作成・勤怠管理(管理者)
| ID | 機能 | 概要 | 優先度 |
|---|---|---|---|
| F-M01 | シフト表(作成・編集) | 従業員×日付のグリッド。ドラッグで割当、必要人数の充足を色で表示 | Must |
| F-M02 | 必要人数の設定 | 曜日別・時間帯別の必要人数。過不足をリアルタイム表示 | Must |
| F-M03 | シフト自動生成 | 希望・スキル・必要人数・法令制約から自動割当。手修正前提の下書き | Must |
| F-M04 | 人件費の見込み | シフト確定前に人件費を試算。予算との差分 | Must |
| F-M05 | シフトの公開 | 確定して従業員に通知。公開後の変更履歴 | Must |
| F-M06 | 日次の勤務状況 | 本日の出勤状況、未打刻者、遅刻、退勤忘れの検出 | Must |
| F-M07 | 予実比較 | シフト(予定)と打刻(実績)の差分。人件費の予実 | Must |
| F-M08 | 打刻訂正の承認 | 申請一覧、訂正内容の差分、承認/差戻し | Must |
| F-M09 | 休暇申請の承認 | 申請一覧、残日数の確認、承認/差戻し | Must |
| F-M10 | 月次集計・確定 | 従業員別の労働時間集計、月次締め、確定後のロック | Must |
| F-M11 | 給与計算用CSV出力 | 基本・法定内残業・法定外残業・深夜・休日・遅刻早退・有給の集計 | Must |
| F-M12 | 36協定モニタリング | 月45h/年360h、複数月平均80h、単月100h の各上限に対する進捗 | Must |
| F-M13 | 有給の取得義務管理 | 年5日取得義務の進捗、未達者の抽出、失効予定の警告 | Must |
| F-M14 | 従業員マスタ | 雇用形態、所定労働時間、時給/月給、スキル、部署 | Must |
| F-M15 | 就業規則の設定 | 丸め、所定時間、休憩ルール、割増率、法定休日、変形労働 | Must |
| F-M16 | 部署・拠点管理 | 部署、拠点、必要人数のテンプレート | Should |
| F-M17 | 操作ログ | 誰がいつ何を承認・訂正・確定したか | Should |
共通・基盤
| ID | 機能 | 概要 | 優先度 |
|---|---|---|---|
| F-C01 | ロール切替 | 従業員/管理者(店長)/管理部 をワンクリック切替 | Must |
| F-C02 | 埋め込みモード | ?embed=1 で打刻タイムライン + 丸め設定(2章) |
Must |
| F-C03 | 通知 | シフト公開、承認結果、打刻忘れ、上限接近(プレビュー表示で再現) | Must |
| F-C04 | 保存ビュー | 絞り込み + 表示列 + 並び順を保存 | Should |
| F-C05 | 記事への導線 | 元記事へのリンク、資料ダウンロード、問い合わせ | Must |
| F-C06 | デモリセット | localStorage をクリア | Must |
| F-C07 | ガイドツアー | 初回訪問時に「何を試せるか」を3ステップで案内 | Should |
対象外(Won't have)
| 項目 | 理由 |
|---|---|
| データベース・バックエンドAPI | 2章の方針に基づく |
| 本物の認証・SSO | ロール切替で代替 |
| 給与計算そのもの(所得税・社会保険・年末調整) | 労働時間の集計とCSV出力までが対象。 給与計算は専門領域であり別システム |
| ICカード・生体認証・打刻機との連携 | Web打刻で代替。ハード比較は11章で扱う |
| 給与計算ソフト・人事システムとのAPI連携 | CSV出力で代替。個別案件のオプション |
| フレックスタイム制の清算期間管理 | 対象外。要件定義段階で洗い出す論点として扱う |
| 1年単位の変形労働時間制 | 1ヶ月単位のみ実装。1年単位は年間カレンダーの設計が別物 |
| 管理監督者・裁量労働制の特殊な取り扱い | 従業員区分としては持つが、計算の特例は対象外 |
| 実際のメール・LINE・プッシュ送信 | プレビュー表示で再現 |
| 労務相談・法令アドバイス | 本デモは計算の実装例であり、法令解釈の保証はしない(10章) |
| 多言語UI | 日本語のみ |
スコープリスク: 勤怠は「うちの就業規則がこうで」が最も多い領域。変形労働時間制・フレックス・みなし残業(固定残業代)・深夜割増の重複・法定休日の特定方法・週40時間の起算曜日は会社ごとに違い、計算の根幹に影響する。後付けは全期間の再計算になるため、要件定義の段階で必ず洗い出す。資料PDFの「就業規則ヒアリングシート」はこの洗い出しのために作る。
4. ロールとデモ切替
ロール設計は、そのまま実案件での権限制御の設計根拠になる。勤怠は個人情報と給与情報を扱うため、権限設計を誤ると事故になる。
ロール定義
| ロールID | 名称 | 見えるデータ | 特徴的な権限 |
|---|---|---|---|
employee |
従業員 | 自分の打刻・勤務時間・シフト・有給のみ | 打刻、訂正申請、休暇申請、シフト希望提出 |
manager |
管理者(店長) | 自部署のメンバー全員 | シフト作成・公開、訂正/休暇の承認、日次確認、予実比較 |
hr |
管理部 | 全社 | 上記 + 就業規則設定・月次確定・給与CSV出力・36協定監視・操作ログ |
データスコープと項目スコープ(デモの見せ場)
勤怠では「見える行」だけでなく「見える列」が権限で厳格に分かれる。 他人の給与額が見えるのは重大な事故。
- 従業員で勤務一覧 → 自分の1ヶ月分のみ。時給・給与額は自分の分だけ
- 管理者に切替 → 自部署8名。時給と人件費が出現する(他部署は見えない)
- 管理部に切替 → 全社42名。就業規則設定と給与CSV出力が出現する
| ID | 要件 |
|---|---|
| SCP-01 | データスコープ(見える行)と項目スコープ(見える列)を、ともに lib/repo/_scope.ts に集約すること |
| SCP-02 | コンポーネント側で role === 'employee' のような分岐を書かないこと |
| SCP-03 | 時給・給与額・人件費は、権限がない場合そもそもデータに含めずに返すこと(CSSで隠さない) |
| SCP-04 | 従業員は、他人の打刻・勤務時間を一切取得できないこと。 リポジトリが空配列を返すこと |
ロールを跨ぐ体験(必ず動くようにする)
- 従業員で打刻する → 管理者に切替 → 日次の勤務状況にその打刻が反映され、予実比較の実績が動く
- 管理部が丸め設定を「1分」→「15分切り捨て」に変える → 従業員に切替 → 同じ打刻なのに勤務時間と残業が減っている(過去分も再計算される)
- 従業員が打刻訂正を申請 → 管理者に切替 → 承認待ちに出る。承認するまで勤務時間は変わらない → 承認 → 勤務時間が訂正後で再計算される
- 管理者がシフトを組む → 必要人数の不足が赤で表示され、人件費の見込みが更新される → 公開 → 従業員に切替 → 自分のシフトに反映されている
- 従業員が残業を積む → 管理部に切替 → 36協定モニタリングでその人の残り時間が減り、上限接近が警告される
- 管理部が月次を確定する → 従業員に切替 → その月の打刻が編集不可(ロック)になっている
2番と3番が、このデモの核心。 1章の特殊性②が体験として成立する瞬間。
デモ切替バー(F-C01)
画面上部に常時表示する固定バー。埋め込みモードでは非表示。
- 現在のロールと氏名・部署の表示、3ロールの切替
- 従業員ロール時:3名から操作主体を選択(残業が多い人/有給未取得の人/新人 を用意)
- デモ内の現在日時(シードが相対日付のため基準を示す)
- 「デモをリセット」ボタン(確認ダイアログ付き)
- 元記事へ戻るリンク
- 「これはデモです。打刻データは送信されません」の明示
デザイン上の扱い: プロダクト本体のUIとは意図的に別扱い(ダークな帯 + 小さめのタイポ)にし、「アプリの外側にある操作パネル」であることを視覚的に区別する。
5. 画面一覧とユーザーフロー
全34画面。画面IDはディレクトリ構成と 1:1 で対応させる。
従業員側
| 画面ID | 画面名 | パス | 主要要素 |
|---|---|---|---|
| SC-001 | 打刻・ホーム | / |
大きな打刻ボタン、本日の勤務時間、休憩残り、退勤予定、今月の残業累計 |
| SC-010 | 月次勤務一覧 | /attendance |
日別の打刻・勤務時間・残業区分。丸め前後の両方、合計行 |
| SC-011 | 勤務日詳細 | /attendance/[date] |
打刻タイムライン(24h帯)、丸めの適用、区分内訳、計算の根拠 |
| SC-012 | 打刻訂正申請 | /attendance/[date]/correct |
訂正内容、理由、差分プレビュー |
| SC-013 | 申請状況 | /requests |
訂正・休暇の申請一覧、承認状況 |
| SC-020 | 残業サマリ | /overtime |
当月累計、36協定の上限までの残り、月別推移 |
| SC-030 | 休暇申請 | /leave/new |
種別、日付、半休/時間単位、残日数の確認 |
| SC-031 | 有給残日数 | /leave |
付与履歴、残日数、失効予定、年5日取得義務の進捗 |
| SC-040 | シフト希望の提出 | /shift/request |
期間内の希望入力(可/不可/時間指定)、締切カウントダウン |
| SC-041 | 確定シフト | /shift |
月/週表示、自分のシフト、変更通知、ics出力 |
| SC-042 | シフト交代の申請 | /shift/swap |
相手選択、依頼、承諾状況 |
| SC-050 | 通知センター | /notifications |
シフト公開、承認結果、打刻忘れ、上限接近 |
| SC-900 | 埋め込みモード | /?embed=1 |
打刻タイムライン + 丸め設定トグル(2章) |
管理者側
| 画面ID | 画面名 | パス | 主要要素 |
|---|---|---|---|
| SC-100 | 日次ダッシュボード | /admin |
本日の出勤状況、未打刻者、遅刻、退勤忘れ、承認待ち件数 |
| SC-110 | シフト表 | /admin/shift |
従業員×日付グリッド、ドラッグ割当、必要人数の過不足を色表示 |
| SC-111 | 時間帯別の充足 | /admin/shift/coverage |
横軸=時間、縦軸=日付。必要人数と配置人数のヒートマップ |
| SC-112 | 必要人数の設定 | /admin/shift/demand |
曜日別・時間帯別の必要人数、テンプレート |
| SC-113 | シフト自動生成 | /admin/shift/generate |
制約の確認、生成、採用しなかった理由の提示 |
| SC-114 | 人件費の見込み | /admin/shift/cost |
シフト確定前の試算、予算との差分、従業員別内訳 |
| SC-115 | シフトの公開 | モーダル | 対象期間、通知プレビュー、公開履歴 |
| SC-116 | シフト希望の集計 | /admin/shift/requests |
提出状況、未提出者、希望の可視化 |
| SC-120 | 勤怠一覧(部署) | /admin/attendance |
メンバー×日付、勤務時間、残業、異常の強調 |
| SC-121 | 予実比較 | /admin/attendance/variance |
シフト(予定)と打刻(実績)の差分、人件費の予実 |
| SC-130 | 打刻訂正の承認 | /admin/approvals/corrections |
申請一覧、訂正前後の差分と再計算結果、承認/差戻し |
| SC-131 | 休暇申請の承認 | /admin/approvals/leave |
申請一覧、残日数、シフトへの影響、承認/差戻し |
| SC-132 | シフト交代の承認 | /admin/approvals/swap |
交代内容、法令制約のチェック |
| SC-140 | 従業員一覧(部署) | /admin/members |
勤務状況、当月残業、有給残、スキル |
管理部(HR)
| 画面ID | 画面名 | パス | 主要要素 |
|---|---|---|---|
| SC-200 | 月次集計 | /hr/monthly |
従業員別の労働時間集計、未確定の異常、確定ボタン |
| SC-201 | 給与CSV出力 | /hr/export |
出力項目の確認、期間、プレビュー、ダウンロード |
| SC-202 | 36協定モニタリング | /hr/compliance |
月45h/年360h/複数月平均80h/単月100h の進捗、上限接近者の抽出 |
| SC-203 | 有給の取得義務管理 | /hr/leave-compliance |
年5日義務の進捗、未達者、失効予定 |
| SC-204 | 就業規則の設定 | /hr/settings/rules |
丸め、所定時間、休憩、割増率、法定休日、変形労働。設定バージョン履歴 |
| SC-205 | 従業員マスタ | /hr/employees |
雇用形態、所定労働時間、時給/月給、スキル、部署、入社日 |
| SC-206 | 部署・拠点 | /hr/orgs |
部署、拠点、必要人数テンプレート |
| SC-207 | 休日カレンダー | /hr/settings/calendar |
法定休日、所定休日、祝日、年間カレンダー |
| SC-208 | 通知文面の設定 | /hr/settings/notifications |
シフト公開・承認・打刻忘れ・上限接近の文面 |
| SC-209 | 操作ログ | /hr/logs |
操作者・日時・対象・内容の検索 |
| SC-210 | 計算ルールの検証 | /hr/settings/verify |
サンプル打刻に対する計算結果の一覧。設定変更の影響を確認 |
主要フロー
フローA:記事の読者が「打刻と解釈の違い」を理解する(最重要)
SEO記事を読んでいる
↓
記事内の埋め込み(SC-900)
┌──────────────────────────────────────────────────────────┐
│ 2026-08-20(木) 田中さんの1日 │
│ │
│ 0 6 9 12 15 18 21 24 │
│ ├────┼──────┼────────┼───────┼───────┼────────┼─────┤ │
│ ●9:03━━━━━━━━━━▓▓━━━━━━━━━━━━━━━━━━━━●19:47 │
│ 休憩57分 │
│ │
│ 丸め設定 [ 1分単位 ▼ ] │
│ │
│ ┌── 生打刻 ──────────┐ ┌── 解釈後 ──────────────┐ │
│ │ 出勤 9:03 │ │ 所定内 8:00 │ │
│ │ 休憩 12:01-12:58 │ │ 法定外残業 1:47 │ │
│ │ 退勤 19:47 │ │ 深夜 0:00 │ │
│ │ 実働 9:47 │ │ 休憩不足 3分 ⚠ │ │
│ └────────────────────┘ └───────────────────────┘ │
│ │
│ 当日の割増賃金相当 ¥2,900 │
└──────────────────────────────────────────────────────────┘
↓
丸め設定を「1分単位」→「15分単位・切り捨て」に変える
↓ ★ 同じ打刻なのに、法定外残業が 1:47 → 1:30 に減る
★ 割増賃金相当が ¥2,900 → ¥2,450 に減る
★ 同時に注意が出る:「切り捨ての丸めは、実労働時間の一部を
賃金計算から除外することになり、適法性に注意が必要です」
↓
「休憩不足 3分 ⚠」にホバー
↓ ★「8時間超の労働には60分以上の休憩が必要です(実績57分)」
↓
「全画面で試す」→ 別タブ
↓
SC-011 勤務日詳細 ── ★ 計算の根拠が全ステップ表示される
↓
ロール切替「管理部」→ SC-204 就業規則の設定
↓ ★ 丸め単位を変えると、SC-210 で全サンプルの計算結果が変わる
SC-202 36協定モニタリング ── ★ 上限接近者が具体的な残り時間で表示される
↓
資料ダウンロード or 元記事に戻る
フローB:打刻訂正の申請と承認(1章の特殊性②の実装を見せる)
【従業員】SC-010 月次勤務一覧
8月18日:退勤打刻がない(打刻忘れ)→ 行が警告色
↓
SC-012 打刻訂正申請
退勤時刻を「19:30」で申請、理由:「打刻忘れ」
↓ ★ 差分プレビューが出る
現在:退勤なし → 勤務時間 算出不能
訂正後:退勤 19:30 → 所定内 8:00 / 法定外残業 1:30
↓
申請
↓ ★ 生打刻は一切変更されていない(PunchCorrection として別レコード)
【管理者】SC-130 打刻訂正の承認
申請一覧に出る
★ 訂正前後の差分と、再計算後の勤務時間が並んで表示される
★ 承認するまで勤務時間は変わらない
↓
承認
↓
【従業員】SC-011 勤務日詳細
★ 勤務時間が訂正後で再計算されている
★ 「打刻タイムライン」に、生打刻(点線)と訂正後(実線)の両方が描かれる
★ 「訂正あり(8/19 管理者が承認)」のバッジが付く
↓
【管理部】SC-209 操作ログ
★ 誰がいつ何を訂正・承認したかが記録されている
↓
★ ここで伝わること:
「打刻は消さない。訂正は履歴として残す。だから後から検証できる」
フローC:シフトを組んで、実績と比べる
【管理者】SC-112 必要人数の設定
金曜 17:00-22:00 は 4名必要、平日昼は 2名
↓
SC-116 シフト希望の集計 ── 提出8名/未提出2名(催促の通知プレビュー)
↓
SC-113 シフト自動生成
制約:希望・必要人数・スキル(レジ可)・法令(週40h、連続勤務日数)
↓ ★ 生成される。ただし「下書き」
★ 採用しなかった理由が出る:
「8/22(金) 17-22時:1名不足。希望提出者の中に配置可能な人がいません」
↓
SC-110 シフト表 ── ドラッグで手修正
★ 必要人数を満たすと緑、不足すると赤で表示される
★ 法令違反になる割当は弾かれる(週40h超、連続7日勤務)
↓
SC-111 時間帯別の充足 ── ★ 横軸=時間のヒートマップで穴が一目で分かる
↓
SC-114 人件費の見込み
★ このシフトだと ¥842,000(予算 ¥800,000 に対し +5.3%)
★ 深夜割増が効いている日が強調される
↓
SC-115 公開 ── 通知プレビューを確認して公開
↓
【従業員】SC-041 確定シフト ── ★ 自分のシフトに反映されている
↓
(数日後)実際に働いて打刻が積まれる
↓
【管理者】SC-121 予実比較
★ シフト(予定)と打刻(実績)が並ぶ
8/22(金) 予定 17:00-22:00(5.0h) / 実績 16:52-22:34(5.5h)
→ 予定より +0.5h、人件費 予実差 +¥1,200
★ 月間:予定 ¥842,000 → 実績 ¥871,300(+3.5%)
↓
★ ここで伝わること:
「シフトは予定、打刻は実績。差分こそが管理対象」
6. 機能要件
各要件はテスト仕様書の項目と1:1で対応する。「〜できること」の粒度で、判定可能な形で記述している。
6.1 労働時間の算出(このデモの心臓部)
| ID | 要件 | 優先 |
|---|---|---|
| FR-101 | 打刻レコードを編集・削除しないこと。 訂正は別レコード(PunchCorrection)として保持し、算出時に適用すること(CAL-01) |
P1 |
| FR-102 | 労働時間を保存せず、生打刻と就業規則設定から常に算出すること(CAL-02) | P1 |
| FR-103 | 丸めの単位(1分/5分/10分/15分/30分)と方向(丸めなし/切り上げ/切り捨て/四捨五入)を設定でき、算出に反映されること | P1 |
| FR-104 | 丸めの方向を「切り捨て」に設定した場合、適法性に注意が必要である旨の警告を設定画面に表示すること(10章) | P1 |
| FR-105 | 出勤・退勤で丸め方向を別に設定できること(出勤は切り上げ、退勤は切り捨て 等の設定を再現できること) | P2 |
| FR-106 | 法定休憩の充足を判定すること(労働6時間超で45分以上、8時間超で60分以上)。不足時は不足分を明示して警告すること | P1 |
| FR-107 | 実績休憩が未打刻の場合、所定休憩を自動控除するか、警告するかを設定で選べること | P1 |
| FR-108 | 所定労働時間との比較で、遅刻・早退・所定内労働時間を算出すること | P1 |
| FR-109 | 法定内残業と法定外残業を切り分けること(所定労働時間 < 法定労働時間 の場合、その差分は法定内残業) | P1 |
| FR-110 | 深夜労働(22:00〜翌5:00)の時間を抽出すること。 日跨ぎ勤務でも正しく計算されること | P1 |
| FR-111 | 休日労働を、法定休日と所定休日で区別して判定すること。 割増率が異なること | P1 |
| FR-112 | 割増率の重複を正しく合成すること(法定外残業×深夜、休日×深夜 等)。適用した率と根拠を内訳として返すこと | P1 |
| FR-113 | 割増率は設定で変更できること(法定を下回る値を設定した場合は警告すること) | P1 |
| FR-114 | 週40時間超の労働を判定すること。 週の起算曜日を設定できること | P1 |
| FR-115 | 1ヶ月単位の変形労働時間制に対応すること(対象期間の総枠との比較で時間外を算出) | P2 |
| FR-116 | 日跨ぎ勤務(夜勤)を正しく扱うこと。 労働日の帰属ルール(始業日に帰属)を設定できること | P1 |
| FR-117 | 「なぜこの時間になったか」の内訳を必ず返すこと(丸め前後、区分ごとの時間、適用率、根拠設定)(CAL-06) | P1 |
| FR-118 | lib/attendance/ を純粋関数とし、現在時刻と就業規則設定を引数で受け取ること(CAL-03) |
P1 |
| FR-119 | 時刻を分数で内部処理し、日跨ぎは 24:00 超で表現すること(CAL-05) | P1 |
| FR-120 | 算出をメモ化し、打刻の追加と就業規則設定の変更の両方で無効化すること(CAL-04) | P1 |
| FR-121 | 42名 × 3ヶ月分(約2,700勤務日)の月次集計が 200ms以下であること | P1 |
6.2 打刻
| ID | 要件 | 優先 |
|---|---|---|
| FR-201 | 現在の勤務状態に応じて打刻ボタンが変わること(未出勤→出勤/勤務中→休憩開始・退勤/休憩中→休憩終了) | P1 |
| FR-202 | 打刻は1タップで完了し、確認ダイアログを出さないこと(出勤時の摩擦を作らない) | P1 |
| FR-203 | 打刻直後に、打刻した時刻と現在の勤務時間をその場で表示すること | P1 |
| FR-204 | 誤打刻を直後(既定3分以内)なら自分で取り消せること。それ以降は訂正申請とすること | P1 |
| FR-205 | 二重打刻(勤務中に再度出勤等)を防止し、現在の状態を明示して案内すること | P1 |
| FR-206 | 位置情報の付与を設定で ON/OFF でき、ON でも権限拒否時は打刻できること(打刻をブロックしない) | P2 |
| FR-207 | 位置情報が指定範囲外の場合、打刻は成功させ、記録に「範囲外」フラグを付けること | P2 |
| FR-208 | 退勤打刻がないまま日付が変わった場合、「退勤忘れ」として検出し、翌日の打刻画面で通知すること | P1 |
| FR-209 | オフラインでも打刻でき、復帰時に自動同期すること。未同期件数をバッジ表示すること | P2 |
| FR-210 | 月次確定後の期間は打刻・訂正申請ができないこと(ロック)。理由を表示すること | P1 |
| FR-211 | すべての打刻に、端末種別・位置(設定時)・記録日時を残すこと | P1 |
6.3 訂正・申請・承認
| ID | 要件 | 優先 |
|---|---|---|
| FR-301 | 打刻訂正を申請でき、訂正前後の差分と再計算後の勤務時間をプレビュー表示すること | P1 |
| FR-302 | 訂正には理由の入力を必須とすること(打刻忘れ/誤打刻/システム不具合/その他) | P1 |
| FR-303 | 申請中は勤務時間を変えないこと。 承認時に初めて算出に反映されること | P1 |
| FR-304 | 管理者は訂正申請を承認/差戻しでき、差戻しには理由を必須とすること | P1 |
| FR-305 | 承認後、勤務日詳細に**「訂正あり」のバッジと、誰がいつ承認したかを表示すること** | P1 |
| FR-306 | 勤務日詳細のタイムラインに、生打刻と訂正後の両方を描画すること(生打刻を点線、訂正後を実線) | P1 |
| FR-307 | 休暇申請(有給/特別休暇/欠勤、全日/半休/時間単位)ができること | P1 |
| FR-308 | 休暇申請時に残日数を表示し、不足する場合は警告すること(ブロックはしない) | P1 |
| FR-309 | 休暇の承認時、その日のシフトへの影響(人員不足になるか)を表示すること | P2 |
| FR-310 | 申請一覧で、種別・状態・期間・申請者で絞り込めること | P1 |
| FR-311 | 承認・差戻し・訂正のすべてを操作ログに記録すること | P1 |
6.4 シフト作成
| ID | 要件 | 優先 |
|---|---|---|
| FR-401 | シフト表を従業員×日付のグリッドで表示・編集できること。セルにシフトパターン(早番/遅番/夜勤 等)を割り当てられること | P1 |
| FR-402 | ドラッグで割当・コピー・移動ができること。キーボードでも操作できること | P1 |
| FR-403 | 必要人数(曜日別・時間帯別)を設定でき、シフト表上で過不足を色表示すること | P1 |
| FR-404 | 時間帯別の充足状況を、横軸=時間・縦軸=日付のヒートマップで表示すること | P1 |
| FR-405 | 法令違反になる割当を弾くこと(週40時間超、連続勤務日数の上限、勤務間インターバルの下限)。理由を表示すること | P1 |
| FR-406 | シフト自動生成:希望・必要人数・スキル・法令制約から下書きを生成すること | P1 |
| FR-407 | 自動生成で埋められなかった枠について、理由を提示すること(「希望提出者の中に配置可能な人がいません」等) | P1 |
| FR-408 | 自動生成の結果は必ず下書きとし、公開前に手修正できること(自動で確定させない) | P1 |
| FR-409 | 人件費の見込みを、シフト確定前に試算すること(時給×時間+割増見込み)。予算との差分を表示すること | P1 |
| FR-410 | 深夜・休日の割増が発生する割当を、人件費内訳で強調表示すること | P2 |
| FR-411 | シフトを公開でき、公開時に通知内容をプレビュー表示すること(実際には送信しない) | P1 |
| FR-412 | 公開後の変更は履歴として残し、変更通知をプレビュー表示すること | P1 |
| FR-413 | シフトパターンのマスタ(名称、開始・終了、休憩、色)を管理できること | P1 |
| FR-414 | 前月のシフトを複製して作成できること | P2 |
6.5 シフト希望・交代
| ID | 要件 | 優先 |
|---|---|---|
| FR-501 | 従業員がシフト希望を提出できること(勤務可/不可/時間指定)。提出締切を設定でき、締切後は提出不可とすること | P1 |
| FR-502 | 締切までの残り時間を従業員側に表示し、未提出者に催促通知(プレビュー)を送れること | P2 |
| FR-503 | 管理者は希望の提出状況(提出済/未提出)と、希望内容を一覧・カレンダーで確認できること | P1 |
| FR-504 | 希望に「上限勤務日数」「週の希望勤務時間」を含められること | P2 |
| FR-505 | シフト交代を申請でき、相手の承諾 → 管理者承認の2段階とすること | P2 |
| FR-506 | 交代によって法令違反や人員不足が生じる場合、警告すること | P2 |
| FR-507 | 従業員は確定シフトを月/週表示で確認でき、ics をダウンロードできること | P2 |
6.6 集計・法令モニタリング
| ID | 要件 | 優先 |
|---|---|---|
| FR-601 | 従業員別の月次集計を表示すること(所定内/法定内残業/法定外残業/深夜/法定休日/所定休日/遅刻早退/有給/欠勤) | P1 |
| FR-602 | 異常(未打刻・退勤忘れ・休憩不足・上限超過・承認待ち)を月次集計上で強調し、確定前に一覧できること | P1 |
| FR-603 | 月次確定ができ、確定後は打刻・訂正申請がロックされること。 確定解除は管理部のみ可能とし、操作ログに記録すること | P1 |
| FR-604 | 給与計算用CSVを出力できること。 出力項目を確認できるプレビューを表示すること | P1 |
| FR-605 | 36協定モニタリング:月45時間/年360時間/複数月平均80時間/単月100時間 の各上限に対する進捗を表示すること | P1 |
| FR-606 | 上限に接近している従業員(既定80%超)を抽出し、残り時間を明示すること | P1 |
| FR-607 | 特別条項の適用回数(年6回まで)をカウントし、表示すること | P2 |
| FR-608 | 有給の年5日取得義務の進捗を表示し、未達者を抽出すること | P1 |
| FR-609 | 有給の付与(入社日基準/一斉付与)を管理し、失効予定日を警告すること | P1 |
| FR-610 | 時間単位有給の上限(年5日相当)を管理すること | P2 |
| FR-611 | 予実比較:シフト(予定)と打刻(実績)の差分を、時間と人件費の両方で表示すること | P1 |
| FR-612 | 部署別・月別の残業時間推移をグラフ表示すること | P2 |
| FR-613 | 日次ダッシュボードで、未打刻者・遅刻・退勤忘れ・承認待ちを一覧できること | P1 |
6.7 設定・マスタ
| ID | 要件 | 優先 |
|---|---|---|
| FR-701 | 就業規則を設定できること:丸め(単位・方向)、所定労働時間、休憩ルール、割増率、法定休日の特定方法、週の起算曜日、変形労働の適用 | P1 |
| FR-702 | 就業規則の設定にバージョンを持たせ、適用開始日を指定できること。 過去の期間は当時の設定で計算されること | P1 |
| FR-703 | 設定変更時に、影響を受ける従業員数と期間を事前に表示すること | P1 |
| FR-704 | SC-210 計算ルールの検証画面で、サンプル打刻に対する計算結果を一覧表示すること。 設定を変えると即座に結果が変わること | P1 |
| FR-705 | 法定を下回る割増率、切り捨ての丸めを設定した場合、警告を表示すること(設定自体はブロックしない) | P1 |
| FR-706 | 従業員マスタで、雇用形態・所定労働時間・時給/月給・スキル・部署・入社日を管理できること | P1 |
| FR-707 | 休日カレンダーで、法定休日・所定休日・祝日を年間で設定できること | P1 |
| FR-708 | 部署・拠点と、必要人数のテンプレートを管理できること | P2 |
| FR-709 | 通知文面(シフト公開・承認結果・打刻忘れ・上限接近)を編集できること | P2 |
| FR-710 | 在庫を動かす操作に相当するもの——打刻訂正の承認・月次確定・就業規則の変更——を操作ログに記録し、検索できること | P1 |
6.8 一覧・デモ基盤
| ID | 要件 | 優先 |
|---|---|---|
| FR-801 | 一覧の表示列をユーザーが選択・並び替えできること。設定は保持されること | P2 |
| FR-802 | 絞り込み条件をURLクエリに反映し、リロード・URL共有で再現できること | P1 |
| FR-803 | 絞り込み条件 + 表示列 + 並び順を「ビュー」として保存できること | P2 |
| FR-804 | 3ロールをワンクリックで切り替えられること | P1 |
| FR-805 | データスコープと項目スコープを lib/repo/_scope.ts に集約すること(SCP-01, SCP-02) |
P1 |
| FR-806 | 時給・給与額・人件費は、権限がない場合データに含めずに返すこと(SCP-03) | P1 |
| FR-807 | 従業員ロールでは、他人の打刻・勤務時間を一切取得できないこと(SCP-04) | P1 |
| FR-808 | 「デモをリセット」で localStorage をクリアすること | P1 |
| FR-809 | 操作結果がリロード後も保持されること | P1 |
| FR-810 | デモ内の現在日時を切替バーに表示すること | P2 |
| FR-811 | ロール権限外の画面では案内画面から切り替えられること。素の404を出さないこと | P1 |
| FR-812 | 権限で不可の操作はボタンを非活性にし、理由をツールチップで示すこと | P1 |
| FR-813 | 元記事へ戻るリンクと資料ダウンロードを常設すること | P1 |
| FR-814 | 初回訪問時に3ステップのガイドツアーを表示し、「今後表示しない」を選べること | P2 |
| FR-815 | 埋め込みモード:打刻タイムライン + 丸め設定トグルのみを表示し、位置情報を呼ばず、打刻もさせず、アプリ内遷移をしないこと(EMB-04, EMB-05) | P1 |
7. データ設計
型定義(lib/types/)
// ---- 組織・従業員 ----
type Org = { id: string; name: string; kind: 'department' | 'site'; parentId?: string }
type EmploymentType = 'full_time' | 'part_time' | 'contract' | 'arbeit'
type WageType = 'monthly' | 'hourly'
type Employee = {
id: string
code: string
name: string // ★ 架空
orgId: string
role: 'employee' | 'manager' | 'hr'
employmentType: EmploymentType
joinedAt: string
// --- 所定労働(★ 法定内/法定外の切り分けに使う)---
scheduledStartMin: number // 540 = 9:00
scheduledEndMin: number // 1080 = 18:00
scheduledBreakMin: number // 60
scheduledWorkDays: number[] // [1,2,3,4,5]
// --- 賃金(★ 権限で出し入れする項目。SCP-03)---
wageType: WageType
hourlyWage?: number
monthlySalary?: number
// --- シフト ---
skills: string[] // 'レジ' 'キッチン' '夜勤可'
maxWorkDaysPerMonth?: number
color: string // シフト表の識別色
isActive: boolean
}
// ---- ★★★ 打刻(生データ。絶対に書き換えない)★★★ ----
type PunchKind = 'clock_in' | 'break_start' | 'break_end' | 'clock_out'
type Punch = {
id: string
seq: number // ★ メモ化の世代管理(CAL-04)
employeeId: string
workDate: string // ★ 労働日の帰属(日跨ぎは始業日に帰属。FR-116)
kind: PunchKind
atMin: number // ★ workDate の 0:00 からの分数。24:00超で日跨ぎ表現(CAL-05)
// --- 記録の状況 ---
device: 'web' | 'mobile' | 'offline_sync'
coord?: { lat: number; lng: number; accuracyM: number }
isOutOfRange?: boolean // 位置が範囲外(FR-207)
recordedAt: string // 実際に記録された時刻(ISO)
cancelledAt?: string // 直後の自己取消(FR-204)
syncState: 'synced' | 'pending'
}
// ---- ★ 訂正(打刻を書き換えず、別レコードで持つ。CAL-01)----
type PunchCorrection = {
id: string
employeeId: string
workDate: string
// --- 訂正内容 ---
targetPunchId?: string // 既存打刻の訂正。未指定なら新規追加
kind: PunchKind
fromAtMin?: number // 訂正前(新規追加なら undefined)
toAtMin: number // 訂正後
isDeletion: boolean // 打刻の取消
// --- 申請・承認 ---
reasonCode: 'forgot' | 'mistake' | 'system' | 'other'
reason: string
status: 'pending' | 'approved' | 'rejected'
requestedBy: string
requestedAt: string
reviewedBy?: string
reviewedAt?: string
rejectReason?: string
}
// ---- 就業規則(★ バージョン管理。FR-702)----
type WorkRule = {
id: string
version: number
effectiveFrom: string // ★ 適用開始日
name: string
// --- 丸め(FR-103)---
rounding: {
unitMin: 1 | 5 | 10 | 15 | 30
inDirection: 'none' | 'up' | 'down' | 'nearest'
outDirection: 'none' | 'up' | 'down' | 'nearest'
}
// --- 法定労働時間 ---
legalDailyMin: number // 480 = 8h
legalWeeklyMin: number // 2400 = 40h
weekStartsOn: number // 週の起算曜日(0=日)
// --- 休憩(FR-106, FR-107)---
breakRules: { overWorkMin: number; requiredBreakMin: number }[] // [{360,45},{480,60}]
autoDeductScheduledBreak: boolean // 休憩未打刻時に所定休憩を控除するか
// --- 深夜 ---
nightStartMin: number // 1320 = 22:00
nightEndMin: number // 300 = 5:00(翌日)
// --- 割増率(FR-113)---
premiums: {
overtime: number // 法定外残業(法定下限 0.25)
overtimeOver60h: number // 月60時間超(法定下限 0.50)
night: number // 深夜(法定下限 0.25)
legalHoliday: number // 法定休日(法定下限 0.35)
scheduledHoliday: number // 所定休日(法定外。時間外として扱う)
}
// --- 変形労働(FR-115)---
flexibleMonthly: { enabled: boolean; periodStartDay: number; totalFrameMin?: number }
// --- 日跨ぎ ---
overnightAttribution: 'start_date' | 'end_date'
}
type HolidayCalendar = {
year: number
legalHolidayWeekday?: number // 法定休日を曜日で特定する場合
dates: { date: string; kind: 'legal_holiday' | 'scheduled_holiday' | 'national_holiday'; name: string }[]
}
// ---- ★ 日次サマリ(保存しない。常に算出)----
type DailySummary = {
employeeId: string
workDate: string
dayType: 'workday' | 'legal_holiday' | 'scheduled_holiday'
// --- 生打刻(丸め前)---
rawPunches: { kind: PunchKind; atMin: number }[]
rawWorkMin: number
rawBreakMin: number
// --- 丸め後 ---
roundedInMin?: number
roundedOutMin?: number
// --- 区分ごとの時間(★ ここが給与に直結)---
scheduledWorkMin: number // 所定内労働
legalInsideOtMin: number // 法定内残業(所定超〜法定内)
legalOutsideOtMin: number // 法定外残業(法定超)
over60hOtMin: number // 月60時間超の部分
nightMin: number // 深夜
legalHolidayMin: number // 法定休日労働
scheduledHolidayMin: number // 所定休日労働
lateMin: number // 遅刻
earlyLeaveMin: number // 早退
breakActualMin: number
breakRequiredMin: number
breakShortageMin: number // ★ 法定休憩の不足(FR-106)
// --- 休暇 ---
leaveKind?: LeaveKind
leaveUnit?: 'full' | 'half' | 'hourly'
leaveHours?: number
// --- 金額(★ 権限がない場合は含めない。SCP-03)---
basePay?: number
premiumPay?: number
totalPay?: number
// --- ★ 計算の根拠(CAL-06, FR-117)---
breakdown: CalcBreakdown
// --- 状態 ---
hasCorrection: boolean
anomalies: Anomaly[]
isLocked: boolean // 月次確定済み
}
type CalcBreakdown = {
appliedRuleVersion: number
steps: {
step: 'rounding' | 'break_check' | 'scheduled_compare' | 'ot_split'
| 'night_extract' | 'holiday_judge' | 'premium_apply' | 'weekly_40h'
input: string // 人が読める入力の説明
output: string // 人が読める結果
note?: string // 適用した設定・根拠
}[]
appliedPremiums: { kind: string; rate: number; minutes: number; amount?: number }[]
}
type Anomaly = {
kind: 'missing_punch' | 'no_clock_out' | 'break_shortage' | 'over_limit'
| 'out_of_range' | 'pending_correction' | 'late' | 'early_leave'
severity: 'info' | 'warn' | 'error'
message: string
}
// ---- 月次集計(算出値)----
type MonthlySummary = {
employeeId: string
yearMonth: string // '2026-08'
workDays: number
totalWorkMin: number
scheduledWorkMin: number
legalInsideOtMin: number
legalOutsideOtMin: number
nightMin: number
legalHolidayMin: number
scheduledHolidayMin: number
lateMin: number
earlyLeaveMin: number
paidLeaveDays: number
absenceDays: number
// --- 金額(権限依存)---
basePay?: number
premiumPay?: number
totalPay?: number
// --- 状態 ---
anomalyCount: number
pendingApprovals: number
status: 'open' | 'confirmed'
confirmedBy?: string
confirmedAt?: string
}
// ---- 36協定(算出値。FR-605)----
type ComplianceStatus = {
employeeId: string
yearMonth: string
monthlyOtMin: number // 当月の時間外(法定外+休日)
monthlyLimitMin: number // 45h = 2700
yearlyOtMin: number // 年度累計
yearlyLimitMin: number // 360h = 21600
multiMonthAvgMin: number // 直近2〜6ヶ月の平均
multiMonthLimitMin: number // 80h = 4800
singleMonthLimitMin: number // 100h = 6000
specialClauseUsedCount: number // 特別条項の適用回数(年6回)
alerts: { kind: string; usageRatio: number; remainingMin: number; severity: 'warn' | 'error' }[]
}
// ---- 休暇 ----
type LeaveKind = 'paid' | 'special' | 'absence' | 'compensatory'
type LeaveRequest = {
id: string
employeeId: string
kind: LeaveKind
unit: 'full' | 'half_am' | 'half_pm' | 'hourly'
dates: string[]
hours?: number
reason: string
status: 'pending' | 'approved' | 'rejected'
requestedAt: string
reviewedBy?: string
reviewedAt?: string
}
type PaidLeaveGrant = {
id: string
employeeId: string
grantedAt: string
days: number
expiresAt: string // ★ 失効予定(FR-609)
usedDays: number
usedHours: number // 時間単位有給
}
// ---- シフト ----
type ShiftPattern = {
id: string
name: string // 早番 / 遅番 / 夜勤 / 休
startMin: number
endMin: number // 24:00超で日跨ぎ
breakMin: number
color: string
requiredSkills: string[]
isDayOff: boolean
}
type ShiftAssignment = {
id: string
employeeId: string
date: string
patternId: string
startMin?: number // パターンから変更する場合
endMin?: number
status: 'draft' | 'published'
createdVia: 'manual' | 'auto_generated' | 'swap'
note: string
}
type ShiftRequest = { // 従業員の希望(FR-501)
id: string
employeeId: string
periodId: string
entries: { date: string; availability: 'available' | 'unavailable' | 'partial'; fromMin?: number; toMin?: number }[]
maxWorkDays?: number
submittedAt?: string
}
type ShiftPeriod = {
id: string
yearMonth: string
requestDeadline: string // ★ 希望提出の締切
status: 'collecting' | 'drafting' | 'published'
publishedAt?: string
}
type StaffingDemand = { // 必要人数(FR-403)
id: string
orgId: string
weekday: number
fromMin: number
toMin: number
requiredCount: number
requiredSkills: string[]
}
type SwapRequest = {
id: string
fromEmployeeId: string
toEmployeeId: string
assignmentId: string
status: 'pending_peer' | 'pending_manager' | 'approved' | 'rejected'
requestedAt: string
}
// ---- 監査・通知 ----
type AuditLog = {
id: string
actorId: string
action: 'approve_correction' | 'reject_correction' | 'confirm_month' | 'unconfirm_month'
| 'update_work_rule' | 'publish_shift' | 'approve_leave' | 'export_csv'
targetType: string
targetId: string
changes: { field: string; before: unknown; after: unknown }[]
createdAt: string
}
type Notification = {
id: string
targetEmployeeId: string
kind: 'shift_published' | 'shift_changed' | 'approval_result' | 'punch_forgotten'
| 'limit_approaching' | 'request_deadline'
title: string
body: string
emailPreview?: { subject: string; body: string } // ★ 送信しない。プレビューのみ
readAt?: string
createdAt: string
}
労働時間算出の実装(7章の中核)
// lib/attendance/daily.ts
export function calcDaily(input: DailyCalcInput): DailySummary {
const { employee, workDate, punches, corrections, rule, calendar } = input
const steps: CalcBreakdown['steps'] = []
// ① 訂正を適用した「有効な打刻列」を作る(★ 生打刻は変更しない。CAL-01)
const effective = applyCorrections(punches, corrections.filter(c => c.status === 'approved'))
// ② 丸め(FR-103)
const { inMin, outMin } = applyRounding(effective, rule.rounding)
steps.push({
step: 'rounding',
input: `出勤 ${fmt(rawIn)} / 退勤 ${fmt(rawOut)}`,
output: `出勤 ${fmt(inMin)} / 退勤 ${fmt(outMin)}`,
note: `${rule.rounding.unitMin}分単位・出勤${dirLabel(rule.rounding.inDirection)}・退勤${dirLabel(rule.rounding.outDirection)}`,
})
// ③ 休憩の充足判定(FR-106)
const grossMin = outMin - inMin
const breakActual = sumBreaks(effective)
const breakRequired = requiredBreak(grossMin - breakActual, rule.breakRules)
const breakShortage = Math.max(0, breakRequired - breakActual)
steps.push({
step: 'break_check',
input: `実働見込 ${fmtMin(grossMin - breakActual)} / 休憩実績 ${breakActual}分`,
output: breakShortage > 0 ? `不足 ${breakShortage}分` : '充足',
note: `労働${breakRequired === 60 ? '8時間超' : '6時間超'}のため${breakRequired}分以上が必要`,
})
// ④ 休日判定(FR-111)→ ⑤ 所定との比較 → ⑥ 残業区分(FR-109)
// → ⑦ 深夜抽出(FR-110)→ ⑧ 週40h(FR-114)→ ⑨ 割増合成(FR-112)
// ※ 各ステップで steps.push() し、根拠を必ず残す(CAL-06)
return { /* ... */ breakdown: { appliedRuleVersion: rule.version, steps, appliedPremiums } }
}
シードデータ(lib/seed/)
| ファイル | 内容 | 件数 |
|---|---|---|
orgs.ts |
部署4(店舗2・本部2) | 4件 |
employees.ts |
従業員(正社員/パート/アルバイトを混在。夜勤可の人を数名) | 42名 |
workRules.ts |
就業規則(バージョン2つ。3ヶ月前に丸め設定が変わった履歴を持つ) | 2件 |
holidayCalendar.ts |
休日カレンダー(法定休日・所定休日・祝日) | 1年分 |
shiftPatterns.ts |
シフトパターン(早番/中番/遅番/夜勤/休) | 5件 |
staffingDemand.ts |
必要人数(曜日別・時間帯別。金土夕方が厚い) | 約40件 |
shiftPeriods.ts shiftRequests.ts |
期間3ヶ月分。希望未提出者を2名残す | — |
shiftAssignments.ts |
シフト(過去2ヶ月は確定、当月は公開済、翌月は下書き) | 約2,600件 |
punches.ts |
打刻(過去3ヶ月・42名分) | 約10,000件 |
punchCorrections.ts |
訂正(承認済み/申請中を混在。申請中を3件残す) | 28件 |
leaveRequests.ts paidLeaveGrants.ts |
休暇申請・有給付与(年5日未達者を3名作る) | — |
シード作成のルール(品質を左右する)
- 実在企業名・実在人名を使わない。 架空の従業員名・店舗名で構成する
- 氏名を「従業員A」のようなダミーにしない。 実在しそうな架空名にする。ここが手抜きだとデモ全体が安っぽくなる
- 打刻を「9:00 きっかり」にしない。 9:03、8:57、19:47 のように丸めが意味を持つ端数を持たせる。これが最重要
- 休憩不足の日を作る(実働8時間超で休憩57分など)。法定休憩の警告が動いて見えること
- 退勤打刻がない日を1〜2件作る(打刻忘れの検出とフローBの実演用)
- 深夜勤務・日跨ぎ勤務を含める(夜勤者を数名、22:00超の退勤を複数)
- 法定休日・所定休日に出勤している日を作る(休日割増が計算に出ること)
- 36協定の上限に接近している人を2名、超えそうな人を1名作る。 全員余裕だとモニタリングが動いて見えない
- 有給の年5日未達者を3名、失効間近の人を1名作る
- シフトの予定と実績にズレを持たせる(予定17:00-22:00 → 実績16:52-22:34 等)。予実比較が意味を持つこと
- 必要人数が不足している日を数日作る(シフト表の赤表示とフローCの実演用)
- 就業規則のバージョンを2つ持ち、3ヶ月前に丸め設定が変わった状態にする(FR-702 の実演用)
- 日付は現在日時からの相対で生成する。固定日付を埋め込まない
- 打刻列は時系列に矛盾がないこと(休憩終了が休憩開始より前にならない、等)
ストアとリポジトリ
lib/
├── types/ seed/
├── attendance/ # ★ 労働時間の算出。純粋関数のみ
│ ├── daily.ts # ★ 日次サマリ(7章のコード参照)
│ ├── rounding.ts # 丸め(FR-103, FR-105)
│ ├── breaks.ts # 法定休憩の充足(FR-106, FR-107)
│ ├── overtime.ts # 法定内/法定外の切り分け、週40h(FR-109, FR-114)
│ ├── night.ts # 深夜の抽出(FR-110)
│ ├── holiday.ts # 休日判定(FR-111)
│ ├── premium.ts # 割増率の合成(FR-112)
│ ├── flexible.ts # 1ヶ月単位変形労働(FR-115)
│ ├── monthly.ts # 月次集計(FR-601)
│ ├── compliance.ts # ★ 36協定(FR-605〜607)
│ ├── paidLeave.ts # 有給の付与・残・年5日義務(FR-608〜610)
│ └── corrections.ts # 訂正の適用(CAL-01)
├── shift/ # ★ シフト。純粋関数のみ
│ ├── coverage.ts # 必要人数の充足判定(FR-403, FR-404)
│ ├── constraints.ts # ★ 法令制約の検証(FR-405)
│ ├── generate.ts # 自動生成(FR-406, FR-407)
│ ├── cost.ts # 人件費の見込み(FR-409)
│ └── variance.ts # 予実比較(FR-611)
├── time/ # 分数⇔時刻、24:00超の扱い(CAL-05)
├── csv/ # 給与CSV出力
├── query/ # 絞り込み・ソート・ビュー
├── store/ { session, data, rules, sync, ui }
└── repo/ # ★ コンポーネントが触るのはここだけ
├── _delay.ts
├── _scope.ts # ★ データスコープ + 項目スコープ(SCP-01)
├── punches.ts attendance.ts corrections.ts leave.ts
├── shift.ts shiftRequests.ts demand.ts
├── monthly.ts compliance.ts employees.ts rules.ts logs.ts
リポジトリ層の規約(厳守)
- 全メソッドを
asyncで定義する。中身が同期でも例外なく - 戻り値は
{ ok: true; data: T } | { ok: false; error: string }に統一する - コンポーネントから Zustand ストアを直接参照しない。 読み取りも書き込みもリポジトリ経由
- 労働時間の算出を
lib/attendance/の外に書かない(CAL-02) - 法令に関わる判定をコンポーネントに書かない(CAL-07)
- 打刻レコードを更新・削除するコードを書かない(CAL-01)
- スコープ適用(
_scope.ts)を全メソッドが必ず通す。 ここが実案件で権限制御に置き換わる箇所 - 時給・給与額・人件費は、権限がない場合オブジェクトから除外して返す(SCP-03)
- 派生値(労働時間・月次集計・36協定・人件費)はストアに保存せず算出する
- 打刻と労働時間の再算出に擬似ディレイを入れない
8. デザイン要件
アートディレクション
「1日を、24時間の帯として横に描く」
勤怠システムが扱うのは「時刻」と「長さ」である。9:03 という点と、8時間という幅。数字の羅列では、どこが所定でどこが残業か、どこが深夜に食い込んでいるかが分からない。
TOKIWA は1日を横1本の24時間帯として描き、その上に打刻の点、労働の帯、休憩の抜け、深夜のゾーン、残業の色分けを重ねる。時間の重なりを図として読ませる。
- 横軸は常に時間。 日次も、シフト表も、充足ヒートマップも、横軸を時間で統一する
- 色は区分のためだけにある。 所定内・法定内残業・法定外残業・深夜・休日。それ以外に色を使わない
- 時刻は大きく、等幅で。
9:0319:47は読み間違えると事故になる - 警告は数字で。「休憩不足」ではなく「休憩不足 3分」
RELATE / KURA との差別化(最重要) 3本とも「白背景・高密度・業務システム」だが、方向を分ける。 RELATE:罫線と等幅数字で表を読ませる。藍。 KURA:各行に在庫の水位バーを描く。ティール+アンバー。縦長数字。 TOKIWA:各行に1日の24時間帯を描く。インディゴ+コーラル。等幅数字+大きな時刻表示。 KURA の水位バーは「量」、TOKIWA の時間帯は「位置と長さ」。 見た目が似ないよう、TOKIWA の帯は必ず時刻目盛りを伴うこと。
カラートークン
:root {
/* ニュートラル(わずかに青みのある事務的な白) */
--bg: #F4F5F8; /* ページ背景 */
--panel: #FFFFFF; /* テーブル・カード */
--panel-alt: #EDEFF4; /* 縞・ヘッダー */
--line: #DCDFE7; /* 罫線 */
--line-hi: #B7BCC9; /* テーブルヘッダー下、時刻目盛り */
--ink-900: #131722; /* 見出し・本文・時刻 */
--ink-600: #4C5464; /* 補助テキスト */
--ink-400: #8A92A3; /* ラベル・非活性 */
--primary: #3A4CAD; /* 主要CTA・選択状態(インディゴ) */
--primary-d: #2C3A87;
--primary-50:#E8EAF7;
/* ★ 労働時間の区分。これ以外の色を帯に使わない */
--seg-scheduled: #3A4CAD; /* 所定内労働(インディゴ) */
--seg-ot-inside: #6F7ECB; /* 法定内残業(薄いインディゴ) */
--seg-ot-outside: #E4633F; /* 法定外残業(コーラル) */
--seg-night: repeating-linear-gradient( /* ★ 深夜はゾーンとして重ねる */
45deg, transparent 0 5px, rgba(58,76,173,.28) 5px 10px);
--seg-holiday: #B0459A; /* 休日労働(マゼンタ寄り) */
--seg-break: #DCDFE7; /* 休憩(抜き) */
--seg-leave: #4C9A7A; /* 有給・休暇 */
/* 時間帯の地 */
--band-track: #EDEFF4;
--band-night-bg: #E4E7F2; /* 22:00-5:00 の背景 */
--band-scheduled: #F7F8FB; /* 所定時間帯の背景 */
/* ★ 異常・法令警告 */
--alert-error: #C2382C; /* 上限超過・休憩不足 */
--alert-warn: #C08320; /* 上限接近・未打刻 */
--alert-info: #3A6FA8; /* 承認待ち */
/* シフトの充足 */
--cov-short: #C2382C; /* 不足 */
--cov-exact: #4C9A7A; /* ちょうど */
--cov-over: #6F7ECB; /* 過剰 */
}
配色ルール
- 通常の勤務日に色を持たせない。 色がついている行は「対応が必要な行」だけ
- 深夜は塗りではなく斜線ゾーンで重ねる。 「別の区分」ではなく「別の軸の属性」だから、塗りを奪わない
- 法定外残業だけがコーラル(暖色)。 ここが給与と法令に最も効く区分だから、唯一の暖色を割り当てる
- 上限超過を赤、上限接近を琥珀にする。 逆にしない
- 部署にも雇用形態にも固有色を割り当てない。バッジが色だらけの画面にしない
タイポグラフィ
| 役割 | 書体 | 用途 |
|---|---|---|
| UI全般 | Noto Sans JP 400 / 700 | 見出しも本文もこれ1つ |
| 時刻・時間 | Roboto Mono 500(tabular-nums) |
打刻時刻、勤務時間、残り時間、割増額 |
| トークン | サイズ | 用途 |
|---|---|---|
| page-title | 22px / 700 | 画面タイトル |
| section | 15px / 700 | セクション見出し |
| body | 13px / 400 | テーブルセル、本文 |
| label | 11px / 700(letter-spacing:.05em) |
項目ラベル |
| punch-lg | 36px / 500 Mono | 打刻画面の現在時刻・本日の勤務時間 |
| time-md | 20px / 500 Mono | 勤務日詳細の主要時間、36協定の残り時間 |
| time | 13px / 500 Mono | テーブル内の時刻・時間 |
RELATE も Roboto Mono を使うが、TOKIWA は punch-lg(36px)という極端に大きい時刻表示を持つ点が違う。 打刻画面では時刻が主役になる。
レイアウトとスペーシング
| 項目 | 定義 |
|---|---|
| スペーシング | 4pxベース:4 / 8 / 12 / 16 / 24 / 32 / 48 |
| アプリシェル | 従業員側=ボトムナビ(モバイル)/管理側=左サイドバー232px + ヘッダー52px |
| コンテンツ幅 | 従業員側 640px(読み進める導線)/管理側は画面幅いっぱい |
| テーブル行高 | 標準40px / コンパクト32px(切替可能) |
| 24時間帯(行内) | 高さ10px × 幅いっぱい。目盛りは 0/6/12/18/24 の5本 |
| 24時間帯(詳細) | 高さ32px。目盛りは1時間ごと、ラベルは3時間ごと |
| 打刻ボタン | 高さ72px、幅いっぱい。画面下1/3に配置(親指の届く範囲) |
| シフト表セル | 幅56px × 高さ40px(従業員×日付グリッド) |
| 角丸 | 3px(カード・入力・ボタン)/2px(バッジ)/0(時間帯・シフトセル) |
| 影 | ポップオーバー・モーダルのみ。テーブルやカードに影を使わない |
| セーフエリア | env(safe-area-inset-bottom) を考慮(従業員側の固定打刻ボタン) |
主要コンポーネント仕様
| コンポーネント | 仕様 |
|---|---|
| 24時間帯(最重要) | 地の上に、所定時間帯の薄い背景/深夜ゾーンの背景/労働の帯(区分別の色)/休憩の抜き/打刻の点を重ねる。必ず時刻目盛りを伴う。 ホバーで区分ごとの時間をツールチップ表示 |
| 打刻の点 | 生打刻=小さな中空の円(点線の縦線)/訂正後=塗りの円(実線の縦線)。両方を同時に描く(FR-306) |
| 打刻ボタン | 状態別に1つだけ表示(出勤/休憩開始/休憩終了/退勤)。確認ダイアログを出さない(FR-202)。押下直後に時刻を punch-lg で表示 |
| 区分内訳 | 所定内/法定内残業/法定外残業/深夜/休日 を横棒の積み上げ + 時間(Mono)。深夜は斜線を重ねる |
| 計算の根拠パネル | CalcBreakdown.steps を順に表示。各ステップに「入力 → 出力(根拠)」。畳めるが既定で開いている |
| 丸め設定トグル | 単位セレクト + 方向セレクト。変更すると隣の計算結果が即座に変わる。切り捨て選択時は警告文が出現 |
| 異常バッジ | 種別 + 具体的な数字(「休憩不足 3分」「上限まで 4h20m」)。色は severity に従う |
| 36協定ゲージ | 4本の横バー(月45h/年360h/複数月平均80h/単月100h)。上限線と現在値、残り時間(Mono)。80%超で琥珀、超過で赤 |
| シフト表セル | パターン名(略称)+ 時間(Mono・小)。従業員色を左端2pxの帯で。空セルはドロップ可能であることを示す |
| 充足ヒートマップ | 横軸=時間(1時間ごと)、縦軸=日付。セルの色は --cov-short / --cov-exact / --cov-over。数字(配置/必要)を併記 |
| 人件費カード | 見込み額(Mono・大)/予算との差分(符号付き・色付き)/割増の内訳 |
| 予実比較行 | 日付/予定の24時間帯(薄い)/実績の24時間帯(濃い)を上下2段で重ねて描く/差分時間(Mono・符号付き)/人件費差分 |
| 訂正差分カード | 訂正前 → 訂正後 を左右に並べ、再計算後の勤務時間の変化を強調 |
| 空状態 | 全一覧に専用の空状態。「データ0件」と「絞り込み0件」を別の文言にする |
| スケルトン | テーブルは行形状。24時間帯の位置も確保してレイアウトシフトを防ぐ |
| デモ切替バー | プロダクトのトークンとは別扱い(ダークな帯 + 小さめタイポ) |
インタラクション要件(業務システムの肝)
| ID | 要件 |
|---|---|
| IX-01 | 打刻は1タップで完了し、100ms以下で結果が表示されること |
| IX-02 | 丸め設定の変更による再計算が 50ms以下で画面に反映されること(EMB-06) |
| IX-03 | 一覧の絞り込み・並び替えは体感で即座(100ms以下) |
| IX-04 | テーブル上で ↑↓ 行移動、Enter 詳細を開く |
| IX-05 | シフト表はキーボードで操作できること(矢印でセル移動、数字キーでパターン割当、Delete で解除) |
| IX-06 | 承認画面は連続処理できること(A 承認/R 差戻し/→ 次) |
| IX-07 | 破壊的操作(月次確定・確定解除・シフト公開)は確認ダイアログ + 対象件数の明示 |
| IX-08 | 承認・差戻しは5秒間「元に戻す」を表示すること |
| IX-09 | 保存は楽観的更新とし、失敗時のみロールバックしてエラーを表示すること |
| IX-10 | モーダルを開いても背後の一覧の状態が保持されること |
モーション
| 対象 | duration | 内容 |
|---|---|---|
| ホバー・フォーカス | 80ms | 色のみ |
| 打刻の完了 | 240ms | 打刻の点が帯に着地する。成功が身体で分かる程度に、しかし待たせない |
| 丸め設定の変更 | 300ms | 帯の端が伸縮し、数値がカウント変化する。 ここは意図的に見せる |
| モーダル・ポップオーバー | 160ms | フェード + 4px |
| シフトセルのドラッグ | 150ms | 掴む/離すのスケール変化 |
| 36協定ゲージ | 400ms | バーの伸縮。上限超過時のみ、一度だけ脈動 |
| トースト | 160ms | 右下から |
prefers-reduced-motion: reduce 時は全アニメーションを無効化する。
アクセシビリティ(WCAG 2.1 AA)
| ID | 要件 |
|---|---|
| A11Y-01 | コントラスト比は通常4.5:1以上。13px本文でも必ず満たすこと |
| A11Y-02 | 全ての操作をキーボードのみで完遂できること |
| A11Y-03 | シフト表のドラッグ操作にキーボード代替を用意すること(IX-05) |
| A11Y-04 | フォーカスリングは2px・オフセット2pxで常時可視。outline:none の単独使用禁止 |
| A11Y-05 | 24時間帯は装飾ではなく情報である。 区分ごとの時間を必ず数値としても表示し、帯に aria-label(「所定内8時間、法定外残業1時間47分、深夜なし」)を持たせること |
| A11Y-06 | 深夜ゾーンを斜線テクスチャで示し、色の違いだけに依存しないこと |
| A11Y-07 | 異常・警告を色のみで伝えないこと。 アイコン + 具体的な数字を併記すること |
| A11Y-08 | テーブルは <table> を用い、scope と caption を適切に設定すること |
| A11Y-09 | フォームの各入力に label を関連付け、エラーは aria-describedby と role="alert" で読み上げること |
| A11Y-10 | 打刻の成功・失敗を aria-live="polite" で通知すること(打刻した時刻を読み上げる) |
| A11Y-11 | 上限超過の警告を aria-live="assertive" で通知すること |
| A11Y-12 | モーダルはフォーカストラップ + Esc で閉じる + 起動元へフォーカス復帰 |
| A11Y-13 | 打刻ボタンのタップ領域は最低56×56px(現場で急いで押されるため) |
| A11Y-14 | フォントサイズ200%指定でも内容が読め、テーブルは横スクロールで対応すること |
ライティング規約
| 原則 | 例 |
|---|---|
| 警告は必ず数字で | ○「休憩が3分不足しています(8時間超の労働には60分以上が必要)」/×「休憩時間が不足しています」 |
| 残りを示す | ○「今月の時間外は32時間15分。上限(45時間)まで残り12時間45分」/×「残業時間が多くなっています」 |
| 計算の根拠を書く | ○「15分単位・切り捨てで丸めた結果、退勤19:47 → 19:45 として計算しています」 |
| 法令の注意は事実として | ○「切り捨ての丸めは、実労働時間の一部を賃金計算から除外することになり、適法性に注意が必要です」/× 断定的な違法宣言も、無言の許容も避ける |
| 打刻は結果を返す | ○「9:03 に出勤を記録しました」/×「打刻しました」 |
| 申請と反映を区別する | ○「訂正を申請しました。承認されると勤務時間に反映されます」/×「訂正しました」 |
| 空状態を区別する | データ0件:「まだ打刻がありません」/絞り込み0件:「条件に一致する日はありません」+ 条件解除 |
| システム語を使わない | ○「この月を確定する」/×「statusをconfirmedに更新」 |
9. 技術要件・ディレクトリ構成
技術スタック(固定・勝手に変更しない)
| レイヤ | 技術 | 備考 |
|---|---|---|
| フレームワーク | Next.js 15(App Router)/ TypeScript strict | |
| スタイリング | Tailwind CSS + CSS Variables | トークンは CSS 変数で定義 |
| UIコンポーネント | shadcn/ui(Radix UI基盤) | a11y要件を自前実装せずに満たす |
| 状態管理 | Zustand + persist | localStorage に永続化 |
| テーブル | TanStack Table v8 | 列制御・ソート・選択。UIは自前 |
| 仮想スクロール | TanStack Virtual | 大量行の表示 |
| 労働時間の算出 | 自前実装(lib/attendance/) |
これが本プロジェクトの価値。ライブラリに任せない |
| シフト自動生成 | 自前実装(貪欲法 + 制約チェック) | 最適化ソルバを入れない。 限界を明示する(FR-408) |
| 24時間帯・ヒートマップ | 自前実装(div または SVG) | チャートライブラリを使わない |
| 日時処理 | date-fns(ja locale)+ 自前の分数ユーティリティ | タイムゾーンは Asia/Tokyo 固定。 dayjs / moment を入れない |
| D&D | dnd-kit | シフト表。キーボード対応必須 |
| フォーム | React Hook Form + Zod | |
| CSV | Papa Parse | 給与CSV出力 |
| グラフ | Recharts(動的import) | 残業推移、部署別集計 |
| PWA | next-pwa | オフライン打刻(FR-209) |
| アイコン | lucide-react | |
| ホスティング | Vercel | |
| テスト | Vitest / Playwright / axe-core |
上記以外のライブラリを入れる前に必ず提案し、承認を得ること。特に給与計算・労務計算系のライブラリ、最適化ソルバ、重量級チャートライブラリの導入は禁止。
ディレクトリ構成
/
├── CLAUDE.md
├── app/
│ ├── layout.tsx # アプリシェル(ロール別ナビ・デモ切替バー)
│ ├── page.tsx # SC-001 打刻 / SC-900(?embed=1)
│ ├── attendance/ # SC-010〜013
│ ├── overtime/ leave/ # SC-020, 030, 031
│ ├── shift/ # SC-040〜042
│ ├── notifications/ # SC-050
│ ├── admin/ # SC-100〜140
│ ├── hr/ # SC-200〜210
│ └── dev/components/
├── components/
│ ├── timeband/ # ★ 最重要
│ │ ├── DayBand.tsx # 24時間帯(行内・詳細の2サイズ)
│ │ ├── BandRuler.tsx # 時刻目盛り
│ │ ├── PunchMarker.tsx # 生打刻/訂正後の点
│ │ ├── SegmentLegend.tsx # 区分の凡例
│ │ └── BandTooltip.tsx
│ ├── punch/ # PunchButton, PunchClock(punch-lg), PunchResult
│ ├── shift/ # ShiftGrid, ShiftCell, CoverageHeatmap, CostCard, useShiftDnd
│ ├── compliance/ # ComplianceGauge, LimitBadge
│ ├── domain/ # CalcBreakdownPanel, RoundingToggle, AnomalyBadge, CorrectionDiff, VarianceRow
│ ├── table/ # DataTable, FilterBar, ViewSidebar
│ ├── demo/ # RoleSwitcher, ResetButton, GuideTour, BackToArticle
│ └── layout/
├── lib/
│ ├── types/ seed/ store/ repo/
│ ├── attendance/ # ★ 労働時間の算出。純粋関数のみ
│ ├── shift/ # ★ シフト。純粋関数のみ
│ ├── time/ csv/ query/ validation/ utils/
├── docs/
│ └── work-rule-checklist.md # 資料PDFの元原稿(就業規則ヒアリングシート)
├── e2e/
└── public/
コーディング規約
- TypeScript strict。
any禁止。やむを得ない場合はunknown+ 型ガード - ファイル名は kebab-case、コンポーネントは PascalCase
- 1ファイル300行を超えたら分割を検討する
- コンポーネントから Zustand ストアを直接参照しない。必ず
lib/repo/経由 - 労働時間の算出を
lib/attendance/の外に書かない(CAL-02) - 法令に関わる判定をコンポーネントに書かない(CAL-07)
- 打刻レコードを更新・削除するコードを書かない(CAL-01)
lib/attendance/lib/shift/lib/time/は純粋関数のみ。 ストア・DOM に依存させず、new Date()を内部で呼ばない(CAL-03)- 時刻は分数で扱い、日跨ぎは 24:00 超で表現する。 文字列の切り貼りで時刻計算をしない(CAL-05)
- タイムゾーンは Asia/Tokyo 固定(実案件では要検討である旨をコメントに残す)
- 派生値(労働時間・月次集計・36協定・人件費)をストアに保存しない
- 賃金計算に浮動小数点の累積誤差を持ち込まない(円単位の整数演算で扱い、端数処理の方針をコメントに明記する)
- コミットは1タスクごと。メッセージに要件IDを含める
例:
feat(attendance): 深夜と法定外残業の割増重複を合成 (FR-112)
10. 非機能要件・法令
性能要件
| ID | 要件 | 目標値 |
|---|---|---|
| NFR-01 | 打刻から結果表示まで | 100ms以下 |
| NFR-02 | 丸め設定変更による再計算と画面反映 | 50ms以下 |
| NFR-03 | 42名 × 3ヶ月分(約2,700勤務日)の月次集計 | 200ms以下 |
| NFR-04 | 36協定モニタリング(42名分)の算出 | 150ms以下 |
| NFR-05 | シフト自動生成(42名 × 1ヶ月) | 2秒以下(進捗表示を出すこと) |
| NFR-06 | 一覧の絞り込み・並び替えの反映 | 100ms以下 |
| NFR-07 | LCP(デスクトップ) | 1.8秒以下 |
| NFR-08 | INP | 200ms以下 |
| NFR-09 | CLS | 0.1以下。24時間帯の領域を最初から確保すること |
| NFR-10 | シフト表のドラッグ操作 | 60fps維持 |
| NFR-11 | 埋め込みモードの初期JSバンドル(gzip後) | 60KB以下 |
| NFR-12 | アプリ本体の初期JSバンドル(gzip後) | 250KB以下(グラフは動的import) |
| NFR-13 | Lighthouse | Performance 90 / Accessibility 95 以上 |
| NFR-14 | 対応環境 | Chrome / Safari / Edge 最新2バージョン、iOS Safari 16以降、Android Chrome 最新 |
| NFR-15 | localStorage の容量上限に達した場合、警告を表示し、古い月を月次集計に要約して圧縮すること(データを破損させない) | — |
セキュリティ・プライバシー
| ID | 要件 |
|---|---|
| SEC-01 | 打刻データ・氏名・給与額を一切外部に送信しないこと。 localStorage にのみ保存すること |
| SEC-02 | この事実を、打刻画面・設定画面・デモ切替バーで明示すること |
| SEC-03 | GA4 に氏名・打刻データ・給与額を送らないこと(EMB-09) |
| SEC-04 | ユーザー入力(訂正理由・シフトメモ)をサニタイズすること |
| SEC-05 | 「デモをリセット」ですべてのデータが消えることを明示し、実際に消えること |
| SEC-06 | 位置情報を取得する場合、取得目的を事前に説明すること。拒否しても打刻できること(FR-206) |
法令に関する取り扱い(この領域の最重要事項)
本デモは労働時間計算の実装例であり、法令解釈の保証をするものではない。 この立場を明確にする。
| ID | 要件 |
|---|---|
| LAW-01 | デモ内に「本デモの計算は実装例です。実運用の際は社会保険労務士等の専門家による確認を推奨します」の注記を、設定画面と月次集計画面に常設すること |
| LAW-02 | 記事内でも同じ注記を明示すること。 法令解釈を断定しないこと |
| LAW-03 | 設定が法定を下回る場合、警告を表示すること(割増率が法定下限未満、休憩ルールが法定未満)。ただし設定自体はブロックしない(就業規則が法定を上回る運用もあるため、判定は「下回るか」で行う) |
| LAW-04 | 切り捨ての丸めを設定した場合、適法性に注意が必要である旨を表示すること(FR-104) |
| LAW-05 | 36協定の上限値は設定で変更できるが、既定値は法定の上限(月45h/年360h/複数月平均80h/単月100h)とすること |
| LAW-06 | 法定休憩の判定(6時間超45分/8時間超60分)を実装し、不足を明示すること |
| LAW-07 | 有給の年5日取得義務の進捗を表示し、未達者を抽出すること |
| LAW-08 | 割増率の既定値は法定下限(時間外0.25/深夜0.25/法定休日0.35/月60h超0.50)とすること |
実案件への転用時の注意(デモ本体の要件ではない。商談で聞かれた際に説明できるよう記録)
① 勤怠システムは「作って終わり」にならない。 法改正・就業規則変更のたびに計算ロジックの見直しが必要。保守を前提とした契約設計が必須
② 労働時間の記録は法定の保存義務がある。 保存期間(賃金台帳等は原則5年、当面3年)と、改ざんできない記録の保持が求められる。打刻を書き換えない設計(CAL-01)はこの要求に直結する
③ 客観的な記録による把握が求められる。 自己申告のみの運用にはリスクがあり、打刻・PCログ等の客観記録が望ましい
④ 打刻データは個人情報。 位置情報を取得する場合は特に、利用目的の明示と同意、取得時間帯の制限が論点になる(従業員監視との線引き)
⑤ 給与計算との接続点で端数処理が問題になる。 時間の端数、賃金額の端数の処理方法は就業規則で定め、システムと一致させる必要がある
⑥ 未払賃金の遡及リスク。 計算誤りが見つかった場合、過去に遡って再計算・追加支払が発生する。だから計算ロジックのテストが他システム以上に重要
本デモはデータをサーバーに送信しないため、②〜④の義務は発生しない。「デモである」旨と LAW-01 の注記を画面上に常時明示する
11. 費用設計
記事の主題の半分は「費用」。この章は実装要件であると同時に、記事本文の原稿素材でもある。
勤怠システムだけの特殊事情:人数課金 × 保守が続く
| 層 | 内容 | 特徴 |
|---|---|---|
| ① 初期開発費 | 設計・実装・テスト | 就業規則の複雑さで大きく変わる(変形労働・フレックス・みなし残業) |
| ② 就業規則の整理・ヒアリング工数 | 実際の運用ルールの言語化 | 見積書に「お客様側作業」として明記しないと必ず揉める |
| ③ ハードウェア費 | 打刻端末(タブレット・ICカードリーダー) | 拠点数×台数。Web打刻なら不要 |
| ④ 固定運用費 | ホスティング、ドメイン、監視、データベース | 小さい |
| ⑤ 保守費(法改正対応) | 他システムより重い。 法改正・就業規則変更で計算ロジックが変わる | 勤怠固有。長期で最大の項目になりうる |
⑤を見積もらずに始めると、「法改正のたびに追加費用で揉める」ことになる。 記事ではここを明示する。
①の分岐:既製の勤怠サービスがあるのに、なぜ自作するのか
この問いに答えられない案件は、受けるべきではない。 勤怠は既製サービスが極めて充実しており、法改正対応も含まれる。正直にそう書くことが信頼を生む。
| 自作が正当化されるケース | 内容 |
|---|---|
| ① 就業規則が既製サービスの設定範囲を超える | 独自の交替勤務、複数事業所を跨ぐ勤務、業種特有の手当計算 |
| ② シフト作成のロジックが独特 | スキル・資格・組み合わせ制約(「有資格者を各シフトに1名以上」等)が複雑 |
| ③ 既存システムと繋ぐ必要がある | 基幹システム・給与ソフト・POS の勤務データと一体化したい |
| ④ 人数が多く、人数課金が重い | 数百名規模で、月額×人数が積み上がっている |
| ⑤ 勤怠データを他業務に使いたい | 原価計算、案件別工数、生産性分析に転用したい |
逆に、①〜⑤のどれにも当てはまらないなら、既製サービスを使うべき。 特に法改正対応が標準で付いてくることは、自作にはない大きな価値。
損益分岐の計算式
既製サービスの年間コスト
= 月額単価 × 従業員数 × 12
+ 初期設定費
( + オプション:シフト機能・有給管理・API連携 )
回収年数 = (開発費 + ハード費)÷(既製サービス年間コスト − 自作の年間運用費 − 年間保守費)
ここで重要なのは、分母から「年間保守費」を引くこと。 法改正対応を自社で持つなら、その費用を計算に入れないと損益分岐が実態と合わない。
| 規模 | 従業員数 | 判断 |
|---|---|---|
| 小 | 〜30名 | 既製サービスで十分。 人数課金が軽く、法改正対応の価値が相対的に大きい |
| 中 | 50〜200名 | ①②③⑤に該当するなら検討価値あり |
| 大 | 300名以上 | 人数課金が効いてくる。 ただし要件も複雑化するため、開発費も上がる |
③の分岐:打刻の手段(現場を左右する判断)
| Web打刻(スマホ) | タブレット共有打刻 | ICカード/生体認証 | |
|---|---|---|---|
| 初期費用 | ゼロ | 拠点あたりタブレット1台 | 拠点あたり端末 + カード |
| なりすまし耐性 | 弱い(本人以外でも押せる) | 中(その場にいる必要) | 強い |
| 出勤時の混雑 | なし(各自のスマホ) | 行列ができる | 行列ができる |
| 私物端末の利用 | 必要(BYOD の論点が発生) | 不要 | 不要 |
| 客観性 | 位置情報で補強可 | その場にいた証拠になる | 最も客観的 |
判断の目安:
- オフィスワーク中心/直行直帰がある → Web打刻(スマホ)+位置情報
- 店舗・工場で同時刻に多人数が出勤 → タブレット共有だと行列になる。ICカードが有利
- なりすまし防止が重要(労務トラブルの実績がある)→ ICカード/生体認証
TOKIWA が Web打刻を基本にしているのは、デモとして最速で体験できるため。 実案件では上表で選定する。
②の分岐:見落とされやすい「就業規則の整理」工数
勤怠システムの導入で最も工数を食うのは、実装ではなく就業規則の言語化。
- 丸めのルール(実は明文化されていない/部署でバラバラ)
- 法定休日の特定方法(就業規則に書いていない)
- 休憩の扱い(実績を取っていない、所定控除で運用している)
- 残業の申請フロー(事前申請なのか事後承認なのか)
- 「運用と就業規則が一致していない」ことが判明する
この整理が終わらないと、計算ロジックを実装できない。 資料PDFの「就業規則ヒアリングシート」は、この工数を前倒しするために作る。資料請求が有効なリードになる設計。
Webで足りるか、ネイティブが必要か
勤怠は、Webで足りる領域。
| やりたいこと | Web(PWA) | ネイティブ |
|---|---|---|
| 打刻・シフト確認・申請 | できる | できる |
| ホーム画面から1タップ起動 | できる(PWA) | できる |
| 位置情報の付与 | できる(画面を開いている間) | できる |
| 打刻忘れのプッシュ通知 | 限定的 | できる |
| バックグラウンドでの位置記録 | できない | できる(ただし監視の論点) |
| ICカードリーダー(NFC)の利用 | 限定的 | できる |
判断の目安: 打刻忘れのプッシュ通知を重視するなら、LINE公式アカウントでの通知が現実的な代替になる(アプリ開発より安い)。NFC を使うなら専用端末かネイティブ。
デモ自体の運用コストを実測して記事に載せる
| ID | 要件 |
|---|---|
| COST-01 | デモ公開後、月次で「デモ起動数・打刻数・ホスティング費用」を記録すること |
| COST-02 | rounding_changed の発生率を記録すること。「読者の何%が丸め設定を変えて計算差を確かめたか」は、記事の主張が届いた指標になる(EMB-08) |
| COST-03 | 記録した実測値を記事に掲載し、確認日を明記すること |
| COST-04 | 既製勤怠サービス・ICカード端末の価格は変動するため、この文書に金額を書き込まない。 記事執筆時点で公式ページを確認し、確認日を明記すること |
12. 実装タスク
この順に進める。フェーズを飛ばさない。 完了時は [x] に更新する。
Phase 0 — 基盤と労働時間算出エンジン(5日)
0-1. 初期化
- Next.js 15 / TypeScript strict / App Router / Tailwind で初期化
- ESLint / Prettier / husky(pre-commit で lint + typecheck)
- 9章のディレクトリ構成を作成
0-2. lib/time/(純粋関数。最初に固める)
- 分数⇔時刻文字列の相互変換、24:00超の表現(CAL-05)
- 時間帯の重なり・差分・分割(深夜抽出の土台)
- 週の起算曜日を考慮した週の生成
- 単体テストを書く。特に日跨ぎと時間帯の分割は境界条件を網羅
0-3. lib/attendance/(このプロジェクトの心臓部。ここに最も時間をかける)
-
rounding.ts:丸め(単位 × 方向、出勤/退勤別)(FR-103, FR-105) -
breaks.ts:法定休憩の充足判定、所定休憩の自動控除(FR-106, FR-107) -
holiday.ts:法定休日/所定休日の判定(FR-111) -
overtime.ts:法定内/法定外の切り分け、週40時間(FR-109, FR-114) -
night.ts:深夜の抽出。日跨ぎ対応(FR-110, FR-116) -
premium.ts:割増率の合成(重複の正しい処理)(FR-112) -
flexible.ts:1ヶ月単位変形労働(FR-115) -
corrections.ts:訂正の適用。生打刻は変更しない(CAL-01) -
daily.ts:日次サマリとCalcBreakdownの生成(7章のコード参照)(FR-117) -
monthly.ts:月次集計(FR-601) -
compliance.ts:36協定の4上限(FR-605〜607) -
paidLeave.ts:有給の付与・残・年5日義務・失効(FR-608〜610) -
new Date()を内部で呼ばない。現在時刻と就業規則を引数で受け取る(CAL-03) - メモ化と、打刻の追加・設定変更の両方での無効化(CAL-04, FR-120)
- 単体テストを網羅的に書く。以下を必ず含めること:
- 丸め単位・方向を変えると勤務時間と残業時間が変わること(最重要)
- 切り捨ての丸めで実労働時間より短くなること(そしてそれが警告対象であること)
- 法定休憩の判定(6時間超45分/8時間超60分/ちょうど6時間・8時間の境界)
- 所定7時間の会社で7〜8時間が法定内残業、8時間超が法定外残業になること
- 深夜(22:00-5:00)の抽出。日跨ぎ勤務で正しく計算されること
- 法定休日労働と所定休日労働の割増率が異なること
- 深夜×法定外残業、深夜×法定休日 の割増重複が正しく合成されること
- 週40時間超の判定(起算曜日を変えると結果が変わること)
- 遅刻・早退の算出
- 訂正を承認すると勤務時間が変わり、生打刻は変わっていないこと
- 就業規則のバージョンにより、過去の期間は当時の設定で計算されること(FR-702)
- 36協定の4上限(月45/年360/複数月平均80/単月100)が正しく判定されること
- 有給の年5日義務と失効予定の算出
-
CalcBreakdownに全ステップの根拠が入っていること(FR-117)
- 42名 × 3ヶ月分で月次集計が 200ms以下をベンチマークで確認(NFR-03)
0-4. lib/shift/(純粋関数)
-
coverage.ts:必要人数の充足判定(FR-403, FR-404) -
constraints.ts:法令制約の検証(週40h、連続勤務日数、勤務間インターバル)(FR-405) -
generate.ts:貪欲法での自動生成 + 埋められない理由の記録(FR-406, FR-407) -
cost.ts:人件費の見込み(割増込み)(FR-409) -
variance.ts:予実比較(FR-611) - 単体テストを書く。特に制約違反の割当が弾かれること
0-5. 型とシード
-
lib/types/に7章の型定義をすべて実装 -
lib/seed/の全ファイル - 打刻を「9:00きっかり」にしない。9:03・8:57・19:47 の端数を持たせる(最重要)
- 休憩不足の日、退勤打刻がない日、深夜・日跨ぎ勤務、休日出勤を含める
- 36協定の上限に接近2名・超えそう1名、有給年5日未達3名、失効間近1名を作る
- シフトの予定と実績にズレを持たせる。必要人数が不足している日を数日作る
- 就業規則のバージョンを2つ持ち、3ヶ月前に丸め設定が変わった状態にする
- 打刻列の時系列に矛盾がないこと
- 日付は現在日時からの相対で生成する
0-6. ストアとリポジトリ
-
lib/store/{session,data,rules,sync,ui}.tsを Zustand + persist で実装 -
lib/repo/_scope.tsを最初に実装する(データスコープ + 項目スコープ)(SCP-01〜04, FR-805〜807) -
lib/repo/の全モジュール。全メソッド async、Result型、スコープ適用 - 打刻レコードを更新・削除するコードを書かない(CAL-01)
- スコープの単体テストをこの時点で書く(従業員が他人の打刻を取得できないこと、時給が含まれないこと)
- 打刻と労働時間の再算出に擬似ディレイを入れない
0-7. デザインシステム
-
app/globals.cssにカラートークンを CSS 変数で定義(区分色 + 深夜の斜線 + 充足色) -
tailwind.config.tsから CSS 変数を参照するよう theme を拡張 - フォント(Noto Sans JP / Roboto Mono)を
next/fontで最適化。時刻にtabular-nums - shadcn/ui を導入し、トークンに合わせて上書き
-
DayBand.tsx:24時間帯(行内10px / 詳細32pxの2サイズ)+BandRuler.tsx時刻目盛り -
PunchMarker.tsx:生打刻(中空・点線)/訂正後(塗り・実線)の描き分け(FR-306) - 帯の
aria-labelと、区分ごとの数値併記(A11Y-05, A11Y-06) - 共通コンポーネント:
AnomalyBadge(数字を必ず含む)/SegmentLegend/EmptyState(2種の出し分け)/Skeleton(帯の位置も確保) -
prefers-reduced-motionの対応 -
/dev/componentsにコンポーネントカタログを作成
0-8. デモ基盤
-
RoleSwitcher(3ロール + 従業員の3ペルソナ選択)(FR-804) - デモ切替バー(ダークな帯)+ 「これはデモです。打刻データは送信されません」(SEC-02)
- LAW-01 の注記コンポーネント(設定画面・月次集計画面に常設)
- 「デモをリセット」(FR-808)/ロール権限外アクセス時の案内画面(FR-811)
- 元記事リンク・資料ダウンロード(FR-813)
Phase 0 完了チェック
-
lib/attendance/のテストが全パターン通る - 「丸め設定を変えると残業時間が変わる」がテストで検証済み
- 「深夜×法定外残業の割増重複」がテストで検証済み
- 「訂正の承認で勤務時間が変わり、生打刻は変わらない」がテストで検証済み
- 従業員ロールで他人の打刻が取得できず、時給が含まれないことがテストで検証済み
- 42名 × 3ヶ月の月次集計が200ms以下
-
/dev/componentsで24時間帯が全パターン正しく描画される
Phase 1 — 打刻と勤務照会(4日)
1-1. 打刻
- SC-001 打刻画面:状態別に1つのボタン(FR-201)
- 1タップ完了、確認ダイアログなし、100ms以下(FR-202, IX-01, NFR-01)
- 打刻直後に時刻を
punch-lgで表示、aria-liveで通知(FR-203, A11Y-10) - 直後の自己取消(3分以内)(FR-204)/二重打刻の防止(FR-205)
- 位置情報の任意付与(拒否しても打刻できる)(FR-206, FR-207, SEC-06)
- 退勤忘れの検出と翌日の通知(FR-208)
- 本日の勤務時間・休憩残り・退勤予定・今月の残業累計(F-P03)
- 月次確定後はロックされ、理由を表示すること(FR-210)
- 全打刻に端末種別・位置・記録日時を残す(FR-211)
1-2. 勤務照会
- SC-010 月次勤務一覧:日別の24時間帯 + 丸め前後の両方 + 合計行(FR-104 相当の可視化)
- 異常の強調(未打刻・休憩不足・承認待ち。数字を含むバッジ)(A11Y-07)
- SC-011 勤務日詳細:24時間帯(詳細サイズ)+ 区分内訳 + 計算の根拠パネル(FR-117)
-
CalcBreakdownPanel:全ステップを「入力 → 出力(根拠)」で表示。既定で開く - SC-020 残業サマリ:当月累計、36協定の上限までの残り時間、月別推移(F-P07)
-
ComplianceGauge:4本のバー(月45h/年360h/複数月平均80h/単月100h)
1-3. 埋め込みモード
- SC-900 埋め込みモード:24時間帯 + 丸め設定トグル + 生打刻/解釈後の並列表示(FR-815, 2章)
- 丸め設定を変えると 50ms以下で再計算され、帯と数値が変わること(IX-02, EMB-06)
- 切り捨て選択時に適法性の注意が出現すること(FR-104, LAW-04)
- 位置情報を呼ばない・打刻させない(EMB-04)/アプリ内遷移をしない(EMB-05)
- 初期バンドル 60KB以下を実測(NFR-11)
Phase 1 完了チェック
- フローAが完走する(埋め込みで丸めを変えると計算が変わる → 全画面 → 根拠が全ステップ見える)
- 4章「ロールを跨ぐ体験」の1と2が動作する
- 打刻が100ms以下、丸め変更が50ms以下
- 従業員ロールで他人のデータが一切見えない
Phase 2 — 訂正・申請・承認(3日)
- SC-012 打刻訂正申請:訂正前後の差分と再計算結果のプレビュー(FR-301)
- 理由コードの必須化(FR-302)
- 申請中は勤務時間を変えないこと(FR-303)
- SC-130 打刻訂正の承認:差分と再計算結果を並べて表示、承認/差戻し(FR-304)
- 連続処理(
A承認/R差戻し/→次)(IX-06) - 取り消し可能なトースト(IX-08)
- 承認後の「訂正あり」バッジと承認者の表示(FR-305)
- 勤務日詳細のタイムラインに、生打刻と訂正後の両方を描画(FR-306)
- SC-030 休暇申請(有給/特別/欠勤、全日/半休/時間単位)(FR-307)
- 残日数の表示と不足時の警告(FR-308)
- SC-131 休暇申請の承認:シフトへの影響表示(FR-309)
- SC-031 有給残日数:付与履歴、残、失効予定、年5日義務の進捗(FR-609, FR-608)
- SC-013 申請状況/SC-050 通知センター
- 承認・差戻し・訂正を操作ログに記録(FR-311)
Phase 2 完了チェック
- フローBが完走する(打刻忘れ → 訂正申請 → 差分確認 → 承認 → 再計算 → 生打刻は残る)
- 4章「ロールを跨ぐ体験」の3が動作する
- 承認前に勤務時間が変わっていないことを確認
Phase 3 — シフト(5日)
3-1. シフト希望
- SC-040 シフト希望の提出:期間内の希望入力、締切カウントダウン(FR-501)
- 締切後の提出不可、未提出者への催促プレビュー(FR-502)
- SC-116 希望の集計:提出状況、未提出者、希望の可視化(FR-503)
- 上限勤務日数・週の希望時間(FR-504)
3-2. シフト表
- SC-112 必要人数の設定(曜日別・時間帯別、テンプレート)(FR-403)
- SC-110 シフト表:従業員×日付グリッド、パターン割当(FR-401)
- dnd-kit でのドラッグ割当・コピー・移動(FR-402)
- キーボード操作(矢印移動、数字キーで割当、Delete で解除)(IX-05, A11Y-03)
- 必要人数の過不足を色表示(FR-403)
- 法令違反の割当を弾き、理由を表示(FR-405)
- SC-111 時間帯別の充足ヒートマップ(横軸=時間、縦軸=日付)(FR-404)
- SC-113 シフト自動生成 + 埋められなかった理由の提示(FR-406, FR-407)
- 生成結果は必ず下書き。自動確定しない(FR-408)
- SC-114 人件費の見込み(割増込み、予算差分、深夜/休日の強調)(FR-409, FR-410)
- SC-115 公開:通知プレビュー、公開履歴(FR-411, FR-412)
- シフトパターンのマスタ(FR-413)/前月複製(FR-414)
- SC-041 確定シフト(従業員側、月/週表示、ics)(FR-507)
- SC-042 / SC-132 シフト交代(相手の承諾 → 管理者承認の2段階)(FR-505, FR-506)
Phase 3 完了チェック
- フローCの前半が完走する(必要人数 → 希望集計 → 自動生成 → 手修正 → 人件費 → 公開 → 従業員に反映)
- 4章「ロールを跨ぐ体験」の4が動作する
- シフト表がキーボードのみで操作完遂できる
- 法令違反の割当が弾かれ、理由が表示される
Phase 4 — 集計・法令モニタリング・設定(4日)
4-1. 管理者の日次・予実
- SC-100 日次ダッシュボード(未打刻者・遅刻・退勤忘れ・承認待ち)(FR-613)
- SC-120 勤怠一覧(部署):メンバー×日付、異常の強調
- SC-121 予実比較:予定と実績の24時間帯を上下2段で重ねて描画、時間差分・人件費差分(FR-611)
- SC-140 従業員一覧(当月残業、有給残、スキル)
4-2. 月次と法令
- SC-200 月次集計:従業員別の全区分、異常の一覧(FR-601, FR-602)
- 月次確定とロック。確定解除は管理部のみ + 操作ログ(FR-603)
- SC-201 給与CSV出力 + 出力項目のプレビュー(FR-604)
- SC-202 36協定モニタリング:4上限の進捗、上限接近者の抽出と残り時間(FR-605, FR-606)
- 特別条項の適用回数(FR-607)
- SC-203 有給の取得義務管理:年5日の進捗、未達者、失効予定(FR-608, FR-609)
- 時間単位有給の上限管理(FR-610)
- 部署別・月別の残業推移グラフ(FR-612、Recharts 動的import)
4-3. 設定
- SC-204 就業規則の設定(丸め・所定・休憩・割増率・法定休日・週起算・変形労働)(FR-701)
- 設定のバージョン管理と適用開始日。過去は当時の設定で計算(FR-702)
- 設定変更時に影響を受ける従業員数と期間を事前表示(FR-703)
- SC-210 計算ルールの検証画面:サンプル打刻への計算結果一覧。設定変更で即座に変わる(FR-704)
- 法定を下回る設定・切り捨ての丸めに警告(FR-705, LAW-03, LAW-04)
- SC-205 従業員マスタ(FR-706)/SC-207 休日カレンダー(FR-707)
- SC-206 部署・拠点(FR-708)/SC-208 通知文面(FR-709)
- SC-209 操作ログ(FR-710)
- LAW-01 の注記が設定画面と月次集計画面に常設されていること
Phase 4 完了チェック
- 4章「ロールを跨ぐ体験」の5と6が動作する
- 就業規則の丸め設定を変えると、過去分も含めて再計算される
- 36協定モニタリングに意味のある警告が出ている(シードの品質確認)
- 月次確定後に打刻・訂正申請ができない
Phase 5 — オフライン・記事統合・仕上げ(3日)
5-1. オフライン
- Service Worker / PWA マニフェスト/オフライン検知とバナー
- オフライン打刻と未同期バッジ(FR-209)
- オンライン復帰時の自動同期と進捗表示
- localStorage 上限時の圧縮処理(NFR-15)
5-2. 記事統合
- 記事側の埋め込みコードを作成し、実際の記事ページで動作確認(EMB-01〜06)
- 埋め込みで「丸め設定を変えると計算が変わる」が伝わることを確認
- 記事の Core Web Vitals を埋め込み前後で比較検証
- 「全画面で試す」カードの設置
- 資料PDF(就業規則ヒアリングシート・損益分岐計算シート)の作成とダウンロード導線
- 元記事リンク・問い合わせ導線
- GA4 イベント設定。
rounding_changedを必ず含める(EMB-07, EMB-08) - GA4 に氏名・打刻データ・給与額を送っていないことを確認(EMB-09, SEC-03)
-
?embed=1をnoindexに、デモ本体の title / description / OGP(EMB-10) - 記事内に LAW-02 の注記を明示
5-3. 体験の総点検
- 4章「ロールを跨ぐ体験」の6パターンをすべて手動で確認
- 全34画面を開き、空の画面が1つもないことを確認
- 空状態(データ0件/絞り込み0件)の出し分けを確認
- ガイドツアーを実装(FR-814)
5-4. アクセシビリティ監査
- axe-core を全画面に実行し、Critical / Serious を0件に
- 13px本文のコントラスト比を実測(A11Y-01)
- 24時間帯の
aria-labelと区分の数値併記を確認(A11Y-05, A11Y-06) - 警告が色のみで伝わっていないことを確認(数字とアイコンの併記)(A11Y-07)
- キーボードのみで全画面・打刻・シフト表・承認を完遂
- 打刻ボタンのタップ領域56px以上を確認(A11Y-13)
- フォントサイズ200%での確認(A11Y-14)
5-5. パフォーマンス
- グラフを動的import に切り出し、初期バンドルを250KB以下に(NFR-12)
- 打刻100ms以下、丸め変更50ms以下、月次集計200ms以下を実測(NFR-01〜03)
- シフト自動生成2秒以下、ドラッグ60fpsを実測(NFR-05, NFR-10)
- Lighthouse で Performance 90 / Accessibility 95(NFR-13)
5-6. 公開と記録
- Playwright で フローA・B・C の E2E
- Vitest:
lib/attendancelib/shiftlib/time_scopeのカバレッジ 90%以上(他テーマより高く設定する。計算誤りが未払賃金に直結するため) -
/dev/componentsを本番で非公開に - Vercel へデプロイ
- 打刻数・
rounding_changed発生率の記録を開始(COST-01, COST-02) - 解説動画(3分)の収録:打刻 → 丸め設定を変えると残業が変わる → 計算の根拠 → 打刻訂正の承認 → シフト作成と人件費 → 36協定の警告
- 各フェーズのキャプチャを整理し、記事の「方法」パートの素材にまとめる
合計 24営業日(約5週間)/1名専任 + レビュー体制。
工数配分の意図: Phase 0 に5日、うち
lib/attendance/のテストに3日を充てる。労働時間の算出はこのプロジェクトの価値そのものであり、かつ間違っていても画面上は正しく動いているように見える。そして間違いは未払賃金の遡及という形で表面化する。他テーマよりテストカバレッジの目標を高く(90%)設定しているのはこの理由。 Phase 3(シフト)に5日を割いているのは、シフト表のグリッドとD&D、充足ヒートマップ、自動生成、人件費試算がそれぞれ独立した実装量を持つため。
13. 受入基準
- 6章の優先度 P1 の要件がすべて実装され、テストで合格していること
- 5章の主要フローA・B・Cが、エンドツーエンドで完走すること
- 4章「ロールを跨ぐ体験」の6パターンがすべて動作すること
- 打刻レコードが編集・削除されておらず、訂正が別レコードとして保持されていること
- 労働時間が保存されておらず、常に生打刻と就業規則設定から算出されていること
- 丸め設定(単位・方向)を変更すると、勤務時間・残業区分・割増額が変わること。かつ 50ms以下で反映されること
- 就業規則にバージョンがあり、過去の期間が当時の設定で計算されること
- 法定休憩の不足が、具体的な分数で警告されること
- 法定内残業と法定外残業が正しく切り分けられること(所定 < 法定 の会社で検証)
- 深夜(22:00-5:00)が日跨ぎ勤務でも正しく抽出され、割増の重複が正しく合成されること
- 法定休日労働と所定休日労働の割増率が異なること
CalcBreakdownに全ステップの入力・出力・根拠が入っており、画面に表示されていること- 訂正申請中は勤務時間が変わらず、承認時に初めて反映されること。かつ生打刻が変わっていないこと
- 36協定の4上限(月45h/年360h/複数月平均80h/単月100h)が判定され、残り時間が明示されること
- 有給の年5日取得義務の進捗と未達者、失効予定が表示されること
- シフト表で法令違反になる割当が弾かれ、理由が表示されること
- シフト自動生成の結果が下書きであり、埋められなかった理由が提示されること
- 人件費の見込みがシフト確定前に試算され、予算との差分が表示されること
- 予実比較で、シフト(予定)と打刻(実績)が同じ時間軸上で並んで表示されること
- 月次確定後、打刻と訂正申請がロックされること
- ロールを切り替えると、見える行と見える列(時給・給与額・人件費)の両方が変わること。従業員は他人の打刻を一切取得できないこと(CSSで隠していない)
- 打刻が100ms以下、月次集計(42名×3ヶ月)が200ms以下であること
- 24時間帯が
aria-labelを持ち、区分ごとの時間が数値としても表示されていること - 警告が色のみで伝えられておらず、具体的な数字を含んでいること
LAW-01の注記(専門家確認の推奨)が設定画面と月次集計画面に常設されていること- 切り捨ての丸め・法定を下回る割増率に警告が表示されること
- 打刻データ・氏名・給与額が外部に一切送信されていないこと(DevTools と GA4 のイベントで確認)
- 全34画面のいずれにも空の状態がなく、リアルなデータが表示されていること
lib/attendancelib/shiftlib/timeのテストカバレッジが 90%以上であること- axe-core で Critical / Serious の指摘が0件であること
- データアクセスがすべて
lib/repo/を経由し、労働時間の算出がlib/attendance/の外に存在しないこと - RELATE / KURA と並べたときに、明確に別のプロダクトとして見えること(24時間帯と時刻目盛りが効いていること)
判断に迷ったときのルール
- 計算ロジックを推測で書かない。 割増賃金・休憩・36協定は法令に基づく。曖昧なら必ず質問する。 これがこのプロジェクトで最も重要なルール
- 打刻を書き換えない。 訂正は別レコード + 承認。これがこのプロジェクトの存在理由
- 労働時間を保存しない。 生打刻と設定から常に算出する
- 労働時間の算出を
lib/attendance/の外に書かない。 法令判定をコンポーネントに書かない lib/attendance/lib/shift/lib/time/を純粋関数に保つ。 現在時刻と設定は必ず引数で受け取る- 時刻は分数で扱う。 日跨ぎは 24:00超。文字列の切り貼りで時刻計算をしない
- 計算の根拠(
CalcBreakdown)を必ず返す。 「なぜこの時間か」が説明できない実装をしない - 警告に必ず数字を入れる。「不足しています」ではなく「3分不足しています」
- 時給・給与額は権限がなければデータに含めない。 CSSで隠すのは実装ミス。従業員が他人の打刻を取得できてはならない
- 法令解釈を断定しない。 LAW-01 の注記を削らない。設定は警告するがブロックしない
- 自動生成を自動確定にしない。 シフトは必ず人が確認して公開する
- スコープ外(3章 Won't have)は実装しない。特にフレックス・1年単位変形労働・みなし残業は要件定義段階で洗い出す
- 「デモだから」を理由に品質を落とす判断はしない
- RELATE / KURA のトークン・コンポーネントをコピーしない。 TOKIWA の帯は必ず時刻目盛りを伴う

