SOUZOHSPECS

業務システム ・ TOKIWA

勤怠・シフト管理

打刻は書き換えない。丸めも残業も、毎回そこから計算する

v1.0・作成 2026-08-24

  1. 要件定義書を保存する

    パソコンに tokiwa-app のようなフォルダを作り、その中に保存します。

    要件定義書.md として保存

  2. ChatGPT で画面イメージを作る デザインにこだわりたい人

    ChatGPT に要件定義書を添付して、下の文を送ります。雰囲気のところは、好きな言葉に書きかえてください。

    ChatGPT に送る
    添付の要件定義書は、これから作るアプリ「勤怠・シフト管理」のものです。
    このアプリのいちばん大事な画面を、デザイン画像にしてください。
    実際のアプリのように、文字やボタンまで入れてください。
    雰囲気:(例:やさしい/高級感/ポップ/シンプル など、好きな言葉で)

    気に入るまで「もっと明るく」「ボタンを大きく」のように直してもらい、できた画像を保存します。ほかの画面も欲しければ、同じように作ってもらえます。

  3. Claude Code に渡して「作って」と頼む

    Claude のデスクトップアプリで「Code」タブを開き、「Select folder」から tokiwa-app を選びます。画面イメージがある人は、入力欄に画像をドラッグして貼りつけてから、下の文を送ります。詳しく

    Claude Code に送る(画像あり)
    @要件定義書.md をもとに、このアプリを作ってください。
    デザインは添付の画像に合わせてください。
    Claude Code に送る(画像なし)
    @要件定義書.md をもとに、このアプリを作ってください。

    作り終わるまで、しばらくかかります。途中で「許可しますか」と聞かれたら、内容を見て許可してください。

  4. 動かして確かめる・直す

    できたら、実際に画面を開いて触ります。直してほしいところは、スクリーンショットを貼りつけて、言葉で伝えれば大丈夫です。

    Claude Code に送る
    アプリを起動して、ブラウザで開けるようにしてください。
    Claude Code に送る
    (画面名)で(操作)をすると(今の動き)になります。
    (こうなってほしい動き)にしてください。
    Claude Code に送る
    このエラーを直してください。
    
    (ここにエラーの文をそのまま貼る)
  5. 途中で止まったら 必要なときだけ

    要件定義書が長いので、一度で作りきれないことがあります。そのときは新しい会話で、こう送ります。

    Claude Code に送る
    @要件定義書.md の続きを作ってください。
    まず、どこまでできているかを確かめてから進めてください。

    それでも進まないときは、30分無料相談でお手伝いします。公式LINEからのご相談もどうぞ。

    1段階ずつ頼みたいとき(全6段階)

    要件定義書には、作る順番が Phase 0〜5 で書かれています。止まりやすいときは、1つずつ頼むと確実です。

    Phase 0基盤と労働時間算出エンジン
    Claude Code に送る
    @要件定義書.md の「実装タスク」にある Phase 0 を作ってください。
    Phase 1打刻と勤務照会
    Claude Code に送る
    @要件定義書.md の「実装タスク」にある Phase 1 を作ってください。
    Phase 2訂正・申請・承認
    Claude Code に送る
    @要件定義書.md の「実装タスク」にある Phase 2 を作ってください。
    Phase 3シフト
    Claude Code に送る
    @要件定義書.md の「実装タスク」にある Phase 3 を作ってください。
    Phase 4集計・法令モニタリング・設定
    Claude Code に送る
    @要件定義書.md の「実装タスク」にある Phase 4 を作ってください。
    Phase 5オフライン・記事統合・仕上げ
    Claude Code に送る
    @要件定義書.md の「実装タスク」にある Phase 5 を作ってください。
  6. 自分用に変える・公開する 必要な人だけ

    Claude Code に送る
    この要件定義書は、記事に埋め込むデモ用に書かれています。
    自分の会社で実際に使えるように変えたいです。外したほうがいいところと、残したほうがいいところを教えてください。
    Claude Code に送る
    このアプリをインターネットに公開したいです。
    無料でできる方法を、1手順ずつ案内してください。

準備や、うまくいかないときの対処は 作り方ガイド にまとめています。分からないときは 30分無料相談 へどうぞ。

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. プロジェクト概要
  2. アーキテクチャ方針
  3. スコープ定義
  4. ロールとデモ切替
  5. 画面一覧とユーザーフロー
  6. 機能要件
  7. データ設計
  8. デザイン要件
  9. 技術要件・ディレクトリ構成
  10. 非機能要件・法令
  11. 費用設計
  12. 実装タスク
  13. 受入基準

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. 打刻と解釈が分かれて見える — 生の打刻と、丸め後・区分後の時間が同じ画面に並んでいる
  2. 設定を変えると計算が変わる — 丸め単位を「1分」から「15分切り捨て」に変えると、残業時間と割増額が変わる
  3. 法令の警告が出る — 法定休憩の不足、36協定の上限接近が具体的な数字で警告される
  4. シフトと実績が並ぶ — 予定と実績の差分が1画面で分かる
  5. ロールで見え方が変わる — 従業員は自分だけ、管理者はチーム全体。給与額は管理者のみ
  6. リセットできる — 誰が触った後でも初期状態に戻せる

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. 従業員で打刻する → 管理者に切替 → 日次の勤務状況にその打刻が反映され、予実比較の実績が動く
  2. 管理部が丸め設定を「1分」→「15分切り捨て」に変える → 従業員に切替 → 同じ打刻なのに勤務時間と残業が減っている(過去分も再計算される)
  3. 従業員が打刻訂正を申請 → 管理者に切替 → 承認待ちに出る。承認するまで勤務時間は変わらない → 承認 → 勤務時間が訂正後で再計算される
  4. 管理者がシフトを組む必要人数の不足が赤で表示され、人件費の見込みが更新される → 公開 → 従業員に切替 → 自分のシフトに反映されている
  5. 従業員が残業を積む → 管理部に切替 → 36協定モニタリングでその人の残り時間が減り、上限接近が警告される
  6. 管理部が月次を確定する → 従業員に切替 → その月の打刻が編集不可(ロック)になっている

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:03 19: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 500tabular-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> を用い、scopecaption を適切に設定すること
A11Y-09 フォームの各入力に label を関連付け、エラーは aria-describedbyrole="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.ts36協定の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=1noindex に、デモ本体の 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/attendance lib/shift lib/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. 受入基準

  1. 6章の優先度 P1 の要件がすべて実装され、テストで合格していること
  2. 5章の主要フローA・B・Cが、エンドツーエンドで完走すること
  3. 4章「ロールを跨ぐ体験」の6パターンがすべて動作すること
  4. 打刻レコードが編集・削除されておらず、訂正が別レコードとして保持されていること
  5. 労働時間が保存されておらず、常に生打刻と就業規則設定から算出されていること
  6. 丸め設定(単位・方向)を変更すると、勤務時間・残業区分・割増額が変わること。かつ 50ms以下で反映されること
  7. 就業規則にバージョンがあり、過去の期間が当時の設定で計算されること
  8. 法定休憩の不足が、具体的な分数で警告されること
  9. 法定内残業と法定外残業が正しく切り分けられること(所定 < 法定 の会社で検証)
  10. 深夜(22:00-5:00)が日跨ぎ勤務でも正しく抽出され、割増の重複が正しく合成されること
  11. 法定休日労働と所定休日労働の割増率が異なること
  12. CalcBreakdown に全ステップの入力・出力・根拠が入っており、画面に表示されていること
  13. 訂正申請中は勤務時間が変わらず、承認時に初めて反映されること。かつ生打刻が変わっていないこと
  14. 36協定の4上限(月45h/年360h/複数月平均80h/単月100h)が判定され、残り時間が明示されること
  15. 有給の年5日取得義務の進捗と未達者、失効予定が表示されること
  16. シフト表で法令違反になる割当が弾かれ、理由が表示されること
  17. シフト自動生成の結果が下書きであり、埋められなかった理由が提示されること
  18. 人件費の見込みがシフト確定前に試算され、予算との差分が表示されること
  19. 予実比較で、シフト(予定)と打刻(実績)が同じ時間軸上で並んで表示されること
  20. 月次確定後、打刻と訂正申請がロックされること
  21. ロールを切り替えると、見える行と見える列(時給・給与額・人件費)の両方が変わること。従業員は他人の打刻を一切取得できないこと(CSSで隠していない)
  22. 打刻が100ms以下、月次集計(42名×3ヶ月)が200ms以下であること
  23. 24時間帯が aria-label を持ち、区分ごとの時間が数値としても表示されていること
  24. 警告が色のみで伝えられておらず、具体的な数字を含んでいること
  25. LAW-01 の注記(専門家確認の推奨)が設定画面と月次集計画面に常設されていること
  26. 切り捨ての丸め・法定を下回る割増率に警告が表示されること
  27. 打刻データ・氏名・給与額が外部に一切送信されていないこと(DevTools と GA4 のイベントで確認)
  28. 全34画面のいずれにも空の状態がなく、リアルなデータが表示されていること
  29. lib/attendance lib/shift lib/time のテストカバレッジが 90%以上であること
  30. axe-core で Critical / Serious の指摘が0件であること
  31. データアクセスがすべて lib/repo/ を経由し、労働時間の算出が lib/attendance/ の外に存在しないこと
  32. RELATE / KURA と並べたときに、明確に別のプロダクトとして見えること(24時間帯と時刻目盛りが効いていること)

判断に迷ったときのルール

  1. 計算ロジックを推測で書かない。 割増賃金・休憩・36協定は法令に基づく。曖昧なら必ず質問する。 これがこのプロジェクトで最も重要なルール
  2. 打刻を書き換えない。 訂正は別レコード + 承認。これがこのプロジェクトの存在理由
  3. 労働時間を保存しない。 生打刻と設定から常に算出する
  4. 労働時間の算出を lib/attendance/ の外に書かない。 法令判定をコンポーネントに書かない
  5. lib/attendance/ lib/shift/ lib/time/ を純粋関数に保つ。 現在時刻と設定は必ず引数で受け取る
  6. 時刻は分数で扱う。 日跨ぎは 24:00超。文字列の切り貼りで時刻計算をしない
  7. 計算の根拠(CalcBreakdown)を必ず返す。 「なぜこの時間か」が説明できない実装をしない
  8. 警告に必ず数字を入れる。「不足しています」ではなく「3分不足しています」
  9. 時給・給与額は権限がなければデータに含めない。 CSSで隠すのは実装ミス。従業員が他人の打刻を取得できてはならない
  10. 法令解釈を断定しない。 LAW-01 の注記を削らない。設定は警告するがブロックしない
  11. 自動生成を自動確定にしない。 シフトは必ず人が確認して公開する
  12. スコープ外(3章 Won't have)は実装しない。特にフレックス・1年単位変形労働・みなし残業は要件定義段階で洗い出す
  13. 「デモだから」を理由に品質を落とす判断はしない
  14. RELATE / KURA のトークン・コンポーネントをコピーしない。 TOKIWA の帯は必ず時刻目盛りを伴う

FREE CONSULTATION

分からないときは、お気軽に30分無料相談へ

途中で止まった、エラーが消えない、自社向けに作り変えたい。どんなことでも大丈夫です。

日程を決めて話す

代表の西澤と直接お話しできます。空いている日時を選ぶだけで予約できます。

代表 西澤と話す日程を選ぶ

まずは問い合わせる

要件定義書がほしい方・ご相談は公式LINEから。メールでのお問い合わせはフォームから。