歩合給(不動産営業職)の勤怠管理システムの設計ガイド

完全設定ガイド | 不動産営業職の勤怠管理
歩合給(不動産営業職)の
勤怠管理システムの設計ガイド

外回りの多い営業職の労働時間を、LINEで正確に把握する。
歩合給の割増賃金計算まで、エルメ設定から本番運用まで一冊にまとめました

エルメ+GAS使用(有料プラン前提)
設定1〜6
一度設定すれば自動で動く
歩合給の割増賃金に対応
1か月単位変形労働時間制
導入ロードマップ PHASE 1 / 詳細設計図
エルメッセージ設定
詳細設計図

不動産営業職 歩合給対応 勤怠管理システム
LINE公式アカウント・タグ・リッチメニュー・出退勤分岐ロジックの実装仕様

⏱️ 所要:1〜2日
🛠️ LINE公式アカウント・エルメ
📐 実装担当者向け仕様
🗺️
0. PHASE 1 の全体像Overview

目的:LINE公式アカウントとエルメッセージを連携し、スタッフが友だち追加してリッチメニューから出退勤・休憩を打刻できる土台を完成させます。
打刻はエルメの回答フォームで行い、「打刻がフォームの回答として連携シートに残り、タグで状態が切り替わる」状態をゴールとします。台帳への転記と計算はPHASE 4で接続します。

この段階の構成
📱
LINE公式アカウント
スタッフが友だち追加して使う窓口
↔
⚙️
エルメッセージ
タグ・リッチメニュー・分岐を制御
→
🏷️
フォーム+タグ
打刻は連携シートに記録、状態はタグで分岐
📐 本書は実装担当者がエルメ上で設定する際の仕様書です。アカウント設定・タグ・リッチメニュー・分岐ロジックを定義します。
💡 なぜ営業職ほど打刻が要るのか:不動産営業は外回りが中心で、タイムカードのある事務所にいる時間が短くなりがちです。しかし歩合給がある場合、残業代の計算にはその月の総労働時間が必要になります(PHASE 4 §5)。労働時間が分からなければ歩合給の時間単価も出せず、最低賃金を満たしているかも確認できません。スマホ1つで打刻できる仕組みが、歩合給の会社ほど効いてくる理由です。
📱
1. LINE公式アカウントの開設Account

勤怠管理専用のアカウントを用意します。集客用アカウントとは分けて作成し、スタッフ向けと顧客向けのメッセージが混ざらないようにします。

設定項目推奨値備考
アカウント種別勤怠管理専用(新規)既存の集客用とは分離
アカウント名あおば不動産販売 勤怠スタッフが識別しやすい名称
応答設定Webhookオン/チャットオンエルメ連携の必須設定(応答メッセージはオフ)
あいさつメッセージ(LINE公式側)オフ友だち追加時の案内はエルメの「友だち追加時アクション」で送る(§7)
料金プランまず無料(月200通)リマインド通数次第で有料検討
💡 通数の見積り:無料のコミュニケーションプランは月200通まで(超過分の追加購入は不可)。通数は「配信1回×受け取った人数」で消費します。スタッフ10名に1日3通のリマインドを送ると月900通相当となり無料枠を超えるため、リマインド設計(PHASE 3 §6)とあわせて有料プランを検討します。
※ 料金・通数の条件は改定されることがあります。導入時点でLINE for Businessの公式ページをご確認ください。
⚠️ アカウント作成・各種登録は権限のあるご担当者が操作してください。パスワードの代理入力は行いません。
⚙️
2. エルメッセージとの連携Integration
連携の手順
1
エルメ開設
エルメッセージのアカウントを開設
2
API連携
Messaging APIのチャネル情報(チャネルID・シークレット・トークン)をエルメに登録
3
Webhook設定
LINE公式アカウント管理画面で Webhookオン/チャットオン/あいさつ・応答メッセージオフ になっていることを確認
4
疎通テスト
自分のLINEで友だち追加し、エルメの友だち追加時アクションの案内が届くか確認
📌 連携情報(チャネルシークレット等)は社外秘です。共有メモやチャットに貼らず、管理者が安全に保管してください。
🏷️
3. タグ定義と状態遷移Tags

PHASE 1の心臓部です。打刻フォームの回答後アクションで「出勤中」「退勤済」「休憩中」を付け外しし、その有無で二重打刻の案内やリマインドを分岐します。最初にタグを正確に設計します。

作成打刻状態タグ
タグ名付与タイミング解除タイミング用途
出勤中打刻フォーム「出勤」送信時「退勤」送信時/翌朝5時リセット二重打刻の案内・退勤忘れリマインドの判定軸
退勤済打刻フォーム「退勤」送信時翌朝5時リセット当日の勤務終了の判定・日次リセットと日報リマインドの開始条件
休憩中休憩フォーム「休憩開始」送信時「休憩終了」送信時休憩戻り忘れリマインドの判定軸
スタッフ_在籍勤怠用QRの読み込み時(流入経路アクション)退職時に管理者が解除リッチメニューの表示条件・すべての配信の絞り込み
作成属性タグ(手動付与)
タグ名付与方法用途
管理者友だち追加後に手動付与管理者向けメニュー・通知の対象
正社員 / パート・アルバイト手動 or 流入経路で付与雇用形態別の集計・運用
試用期間中手動付与対象者の識別
歩合給あり手動付与(社員マスタH列と一致させる)割増賃金の計算方法の切替(PHASE 4 §5)
直行直帰あり手動付与外回り中心の営業職の識別・打刻ルールの案内
💡 日次リセット:エルメには「時刻を指定してタグを一括解除する」機能がなく、GASからエルメのタグを操作することもできません。そこで「退勤済」の付与を開始条件にしたステップ配信で、翌朝5:00に「出勤中/退勤済/休憩中/日報_提出済」を解除します(PHASE 3 §6)。0時でなく5時にするのは、23時出勤→翌2時退勤のような日付をまたぐ勤務を同じ日として扱うためです。
🔘
4. リッチメニューの設計Rich Menu

スタッフが毎日使う6ボタンを設計します。画像を3列2行で分割し、各領域にアクションを割り当てます。打刻・休憩・日報・有給・修正はすべてエルメの回答フォームを開くボタンにします(フォームの回答だけがスプレッドシートへ自動記録されるため)。

A🕘
出勤・退勤
打刻フォーム
B☕
休憩
開始/終了フォーム
C📝
日報入力
PHASE3で実装
D📊
今月の勤怠確認
PHASE4で実装
E🌴
有給休暇届
PHASE3で実装
F⚠️
修正申請
PHASE3で実装
画像・領域の仕様
項目仕様
画像サイズ2500 × 1686 px(大サイズ)
分割3列 × 2行(6分割)
1ボタンの目安約833 × 843 px
形式JPG / PNG(1MB以下推奨)
表示条件タグ「スタッフ_在籍」あり(管理者には「管理者」タグで別メニューを出し分け可)
各ボタンのアクション定義
位置ボタンアクション種別開くもの/動作
A🕘 出勤・退勤URLを開くフォーム「打刻」(区分:出勤/退勤)
B☕ 休憩URLを開くフォーム「休憩」(区分:休憩開始/休憩終了)
C📝 日報入力テキスト送信「準備中です」仮応答(PHASE3でフォームに差し替え)
D📊 今月の勤怠確認テキスト送信「準備中です」仮応答(PHASE4で閲覧用シートのURLに差し替え)
E🌴 有給休暇届テキスト送信「準備中です」仮応答(PHASE3でフォームに差し替え)
F⚠️ 修正申請テキスト送信「準備中です」仮応答(PHASE3でフォームに差し替え)
⚠️ PHASE 1で動かすのはA(出勤・退勤)とB(休憩)のみ。未実装のC〜Fは必ず「準備中です」の仮応答を設定し、押しても無反応にならないようにします。
💡 「ボタン1回で打刻」にしない理由:エルメでボタンに直接タグを付けることはできますが、タグの付け外しはスプレッドシートに記録されません。打刻時刻を給与計算に使うには「フォームの回答日時」として残す必要があるため、本設計はボタン → フォームを開く → 区分を選んで送信(2タップ)を標準にしています。リッチメニューを「出勤」「退勤」「休憩開始」「休憩終了」の4ボタンに分け、それぞれ区分を固定したフォームを開けば1タップ+送信にもできます。
🕘
5. 打刻フォームのロジック(詳細)Logic A

フォーム「打刻」は設問1つ(区分:出勤/退勤)だけの最小構成です。回答日時がそのまま打刻時刻になり、連携シートに記録されます。回答後アクションを選択肢ごとに設定し、タグで状態を切り替えます。

🕘 フォーム「打刻」の処理フロー
押
トリガー
「🕘 出勤・退勤」タップ → フォーム「打刻」が開く(氏名は友だち情報から自動表示、区分を選んで送信)
記
自動記録
連携シート「打刻」に回答日時/友だち名/お名前/区分が1行追加される(PHASE 4のGASがここを読む)
分
回答後アクション(選択肢ごと)
「出勤」を選んだ場合と「退勤」を選んだ場合で、別のアクションを実行
出
区分=出勤
タグ「出勤中」付与・「退勤済」解除 → 「✅ 出勤打刻が完了しました」を返信。条件:「出勤中」が既にある場合は「すでに出勤打刻済みです」を返信して終了
退
区分=退勤
タグ「出勤中」解除・「休憩中」解除・「退勤済」付与 → 「お疲れ様でした」を返信。条件:「出勤中」がない場合は「出勤打刻がありません。修正申請から申請してください」を返信
操作
自動記録
分岐
タグ操作
💡 二重打刻の扱い:タグの条件で「すでに出勤打刻済み」と案内しても、フォームの回答自体は連携シートに残ります。PHASE 4のGASは1日の最初の「出勤」と最後の「退勤」だけを採用するので、誤って2回送っても集計は崩れません。
☕
6. 休憩フォームのロジック(詳細)Logic B
☕ フォーム「休憩」の処理フロー
押
トリガー
「☕ 休憩」タップ → フォーム「休憩」が開く(区分:休憩開始/休憩終了)
記
自動記録
連携シート「打刻」と同じ形式で記録(打刻フォームと休憩フォームの連携先を同じにするか、GASで両方読む)
開
区分=休憩開始
タグ「休憩中」付与 → 「☕ 休憩開始を記録しました」を返信。条件:「出勤中」がない場合は「先に出勤打刻をしてください」
終
区分=休憩終了
タグ「休憩中」解除 → 「✅ 休憩終了を記録しました」を返信。条件:「休憩中」がない場合は「休憩開始の記録がありません」
📌 休憩は1日に複数回可能。開始→終了の組を順に合計し、PHASE 4の実働計算で実働から差し引きます。休憩時間の分数はエルメでは計算せず、GASが連携シートの回答日時から算出します。
👥
7. 友だち追加導線とスタッフ登録Onboarding
スタッフ登録の流れ
1
QR/URL発行
エルメの「流入経路(QRコードアクション)」で勤怠用のQRを作り、読み込み時アクションにタグ「スタッフ_在籍」付与を設定
2
初回案内
友だち追加時アクションで「お名前を登録してください」とフォーム「スタッフ登録」(お名前)のURLを送る → 回答を友だち情報 staff_name に保存
3
スタッフ操作
各スタッフが友だち追加+氏名を登録(社員マスタの氏名と同じ表記で)
4
属性付与
管理者が「正社員/パート/管理者/歩合給あり」などの属性タグを付与
⚠️ アカウント作成や個人情報の入力はスタッフ本人が行います。管理者が代理でパスワード等を入力しない運用ルールを明記してください。
💡 社員マスタとの対応:ここで登録した氏名が、PHASE 2の社員マスタと打刻データを突き合わせる照合キーになります(エルメ利用時はLINEのユーザーIDをGASから取得できないため、氏名で照合します)。各フォームの「お名前」は友だち情報 staff_name から自動入力されるようにし、毎回手で打たせないのがコツです。
🧪
8. PHASE 1 の動作確認Test
🏠 あおば不動産販売 勤怠●●●
🕘 出勤・退勤 → 区分「出勤」を送信
✅ 出勤打刻が完了しました!
⏰ 出勤:09:00
退勤時も同じボタンから「退勤」を選んでください。
① 出勤(フォーム送信→タグ付与)
🏠 あおば不動産販売 勤怠●●●
☕ 休憩 → 「休憩開始」を送信
☕ 休憩開始:12:00
☕ 休憩 → 「休憩終了」を送信
✅ 休憩終了:13:00
② 休憩(開始/終了)
🏠 あおば不動産販売 勤怠●●●
🕘 出勤・退勤 → 区分「退勤」を送信
お疲れ様でした🌙 ⏰ 退勤:18:30
本日の日報が未提出の方は「📝 日報入力」からお願いします。
③ 退勤(タグ切替)
確認テスト項目
ID確認内容期待結果
P1-01未出勤で「出勤」を送信連携シートに1行追加+「出勤中」タグ付与+完了返信
P1-02出勤中で「退勤」を送信連携シートに1行追加+タグが「退勤済」に切替
P1-03出勤中でもう一度「出勤」「すでに出勤打刻済み」と案内(タグはそのまま)
P1-04休憩開始→休憩終了2行記録され「休憩中」が付いて外れる
P1-05出勤前に休憩開始「先に出勤打刻を」と案内
P1-06未実装ボタン(C〜F)「準備中です」と仮応答
P1-07翌朝5時以降の状態出勤中/退勤済タグがリセットされ、朝の案内が届く
📋
9. PHASE 1 完了チェックリストChecklist
✓
LINE公式アカウントが開設され、Webhookオン/チャットオン/あいさつ・応答メッセージオフになっている
✓
エルメッセージと連携でき、友だち追加で初回案内が届く
✓
打刻状態タグ・属性タグを作成し、付与/解除ルールを設定した
✓
フォーム「打刻」「休憩」を作り、スプレッドシート連携と選択肢ごとの回答後アクションを設定した
✓
6ボタンのリッチメニューが表示され、未実装ボタンは仮応答が返る
✓
出勤・退勤のフォーム送信で連携シートに記録され、タグが正しく切り替わる
✓
友だち追加QR/URLを用意し、テストスタッフが打刻できた
✓
日次リセット(翌朝5時)で出勤中/退勤済が外れ、朝の案内が届く
🔜

PHASE 2 への引き継ぎ事項

PHASE 1で「打刻がフォームで記録され、タグで状態が切り替わる土台」が完成しました。次のPHASE 2では、この打刻データを集計するGoogleスプレッドシート(5シート)を作成します。
引き継ぐもの:①打刻状態タグの設計(出勤中/退勤済/休憩中) ②フォーム「打刻」「休憩」の設問文(連携シートの見出しになる) ③友だち登録で集めた氏名(社員マスタの照合キー)。
※ 連携シートから日報データへの転記と、実働時間・変形労働残業の計算はPHASE 4で接続します。

導入ロードマップ PHASE 2 / 詳細設計図
スプレッドシート作成
詳細設計図

不動産営業職 歩合給対応 勤怠管理システム
5シートの列定義・計算式・社員マスタ・変形労働カレンダーの実装仕様

⏱️ 所要:1〜2日
🛠️ Googleスプレッドシート
📐 実装担当者向け仕様
🗺️
0. PHASE 2 の全体像Overview

目的:PHASE 1で打刻した勤怠データを集計するGoogleスプレッドシートの器(台帳)を作成します。フォームの回答はエルメが作る連携シート(4ファイル)に自動で溜まるので、台帳は「連携シートから転記して集計する場所」です。各シートの列定義(見出し・データ型)と計算式の置き場所を確定させることが本フェーズの中心です。
GASによる自動転記・集計(PHASE 4)はまだ行いませんが、列構成を正しく設計しておくことが後工程の前提になります。

構成する5枚のシート
Noシート名役割入力方法
1日報データ毎日の打刻・日報を1行ずつ蓄積PHASE4でGAS自動追記
2月次集計社員ごと月別の集計結果計算式+GAS集計
3変形労働カレンダー月の所定労働時間・法定総枠を設定手動(年度初め等)
4社員マスタスタッフ名簿・LINE紐付け手動
5修正履歴打刻修正の証跡(PHASE3・4連動)申請を転記し、管理者が承認者を入力→GASが反映
📐 本書は実装担当者向けの仕様書です。列名・型・計算式・データ検証(入力規則)を各シートごとに定義します。前回のイラスト版(初心者向け)と合わせてご利用ください。
📁
1. ファイル構成と命名規則File
項目仕様
ファイル名勤怠データ_2025
保存場所会社Googleドライブの勤怠管理フォルダ
シートタブ日報データ/月次集計/変形労働カレンダー/社員マスタ/修正履歴
タイムゾーンファイル>設定で「日本標準時 (GMT+9)」に設定
ロケール日本(日付・数値の書式を統一)
日報データ
月次集計
変形労働カレンダー
社員マスタ
修正履歴
↑ 画面左下の「+」でシートを追加し、各タブを上記の名前にリネームします。
⚠️ タイムゾーンが未設定だと、PHASE 4でGASが記録する打刻時刻がずれます。最初に必ず日本標準時に設定してください。
📋
2. 日報データシート(列定義)Sheet 1

最も重要なシート。毎日の打刻が1行ずつ蓄積されます。列の順番・見出しはPHASE 4のGAS書き込みと一致させます。

列見出しデータ型入力元例
A日付日付GAS(打刻日)2025/06/10
B社員ID文字GAS(マスタ参照)001
C氏名文字GAS山田太郎
D出勤時刻GAS(出勤打刻)9:00
E退勤時刻GAS(退勤打刻)18:30
F休憩(分)数値GAS(休憩集計)60
G実働(分)数値/式GAS計算(§PHASE4)510
H深夜(分)数値GAS計算0
I打ち合わせ文字GAS(日報)山田様 90分 案内
J成約内容文字GAS(日報)白金台101 売買 3200万
K報告事項文字GAS(日報)田中様 再連絡希望
L翌日出勤時刻GAS(日報)9:30
M翌日退出時刻GAS(日報)19:00
N翌日予定文字GAS(日報)午前 内見2件
O成約額数値GAS(日報)32000000
P状態文字GAS自動/修正済
💡 成約額をO列に数値で持つ理由:J列の「成約内容」は物件名や種別を含む文字列なので、そのままでは合計できません。歩合給の算定に使う金額はO列に数値だけを分けて保存し、月次集計のK列(成約額 合計)で合算します。
💡 実働(分)の計算(G列):PHASE 4でGASが 退勤−出勤−休憩 を分単位で算出してG列に書き込みます(P列が「修正済」の日は、修正内容から再計算した値になります)。表計算式で持つ場合は =(E2-D2)*1440-F2(1440=24時間の分換算)でも算出可能です。
⚠️ 列を増減した場合は、PHASE 4の項目マッピング(GASの列番号)を必ず同期させてください。列の挿入・削除は後工程に影響します。
📈
3. 月次集計シート(列定義+計算式)Sheet 2

社員ごと・月別に集計します。日報データを参照する計算式を置き、PHASE 4のGASでも更新します。

列見出し算出方法(イメージ)
A対象月2025-06
B社員ID社員マスタ参照
C氏名社員マスタ参照
D出勤日数=COUNTIFS(日報!日付,当月,日報!氏名,C)
E実働(h)=SUMIFS(日報!実働,…)/60
F法定総枠(h)変形労働カレンダー参照(暦日数で決定)
G残業(h)GAS(日→週→月の三段階判定。§PHASE4 §5)
H深夜(h)=SUMIFS(日報!深夜,…)/60
I打ち合わせ件数当月の打ち合わせ記録を集計
J成約件数当月の成約記録を集計
K成約額 合計=SUMIFS(日報!成約額,…)
L歩合給成約額×歩合率(給与規程に基づき算出・手入力も可)
M固定給社員マスタI列(基本給)を参照
N歩合給の時間単価=L / 総労働時間(E)
O割増賃金固定給分+歩合給分+休日・深夜を合算(§PHASE4 §5)
P法定休日(h)法定休日の労働時間(時間外とは別枠・3割5分)
Q最低賃金判定(固定給+歩合給)÷総労働時間 ≧ 最低賃金 → OK/要確認
残業判定(変形労働時間制)の式の考え方
// G列:残業時間の算出ロジック(1か月単位変形労働時間制)
// ① 日:所定が8hを超える日はその所定、それ以外は8hを超えた時間
// ② 週:所定が40hを超える週はその所定、それ以外は40hを超えた時間(①を除く)
// ③ 月:月の実働合計 − 法定総枠(①②を除く)
残業(h) = ① + ② + ③  ※法定休日の労働は含めず別枠(3割5分)

// 法定総枠は暦日数で決まる(変形労働カレンダーから取得)
// 31日=177.0 / 30日=171.0 / 29日(うるう年)=165.5 / 28日=160.0
📌 法定総枠(F列)は§4の変形労働カレンダーから対象月の暦日数に応じて取得します。「月の総枠を超えた分だけ」を残業にすると、所定8時間超の日の超過分が漏れて未払いになるため、日→週→月の三段階で判定します(計算はPHASE 4のGASが行い、このシートには結果を書き込みます)。
📅
4. 変形労働カレンダーシートSheet 3

1か月単位変形労働時間制の各日の所定労働時間と、月の法定総枠を設定します。年度初めや月初に手動設定します。

日別各日の所定労働時間
列見出し例備考
A日付2025/06/02当月の全日
B曜日月=TEXT(A2,”aaa”)
C区分通常/特定/休日入力規則でプルダウン
D所定開始9:00勤務日のみ
E所定終了18:00勤務日のみ
F所定労働(h)8.0=(E−D)*24−休憩
G法定休日○ / 空欄週1日の法定休日
月別月の法定労働時間の総枠(本設計の設定値)
月の暦日数法定総枠該当月
31日177.0 時間1・3・5・7・8・10・12月
30日171.0 時間4・6・9・11月
28日(平年2月)160.0 時間2月(平年)
29日(うるう年2月)165.5 時間2月(うるう年)
💡 総枠表の置き方:このシートの一角に上記の対照表を持たせ、月次集計の法定総枠(§3 F列)が対象月の暦日数から自動参照できるようにします。=VLOOKUP(暦日数, 総枠表, 2, FALSE) など。
⚠️ 1か月単位変形労働時間制は、各日・各週の所定労働時間を就業規則または労使協定で事前に特定しておくことが要件です(労使協定で導入する場合は労基署への届出が必要)。このカレンダーがその「特定」の実体になるので、月の途中で所定を書き換えて残業を減らす運用はできません。所定が8時間を超える日の超過分は日単位の時間外になります(PHASE 4 §5の三段階判定)。
👤
5. 社員マスタシートSheet 4

スタッフの名簿。LINE表示名と本名・社員IDを紐付け、PHASE 4でGASがこのマスタを参照して日報データへ氏名を補完します。

列見出しデータ型例備考
A社員ID文字001主キー・重複不可
B氏名文字山田太郎正式名称
CLINE表示名文字たろう友だち追加時の名前
D照合キー(お名前)文字山田太郎フォームの「お名前」と完全一致させる(空白は無視して比較)
E雇用形態選択正社員正社員/パート/アルバイト
F入社日日付2023/04/01有給付与計算等に使用
G管理者フラグ選択○ / 空欄管理者通知の対象
H給与形態選択固定給+歩合給固定給のみ / 固定給+歩合給 / 完全歩合
I基本給(月額)数値220000固定給部分。割増単価の分子に使う
J歩合率数値3.0%成約額に対する歩合の率(参考値)
K保障給(時間額)数値1200出来高払制の保障給(労基法27条)。最低賃金以上に設定
L月平均所定労働時間数値171.0固定給の割増単価の分母(年間所定労働時間÷12)
📌 D列の照合キーは、PHASE 4でGASが連携シートの「お名前」と社員を突き合わせるキーです。エルメを使う構成ではLINEのユーザーIDをGASが取得できないため、氏名で照合します。各フォームの「お名前」は友だち情報 staff_name から自動入力し、表記ゆれを防ぎます。
⚠️ H〜K列が本設計の要です。歩合給がある社員は割増賃金の計算方法が固定給のみの社員と異なります(PHASE 4 §5)。給与形態を社員ごとに持たせておかないと、月次集計で正しい割増賃金を出せません。
⚠️ 社員の個人情報を含むため、共有範囲を最小限に(§7)。社外への共有は行いません。
🔧
6. 修正履歴シートSheet 5

打刻の修正申請(PHASE 3)が承認されたとき、PHASE 4のGASが誰がいつ何を修正したかを自動記録します。労務管理上のトレーサビリティを確保する重要なシートです。

列見出し例
A申請日時2025/06/19 10:05
B対象社員山田太郎
C対象日2025/06/18
D項目出勤 / 退勤 / 休憩
E修正前(空欄)
F修正後9:00
G申請者山田太郎
H承認者管理者(入力するとGASが反映)
I反映日時2025/06/19 10:10(GASが記録)
💡 上書きしない:修正は日報データを上書きしますが、変更の事実はこのシートに必ず追記し、後から経緯を追えるようにします。
🔒
7. 共有設定とシート保護Security
対象設定理由
ファイル共有範囲管理者のみ(リンク共有オフ)個人情報・労働時間の保護
編集権限管理者:編集/一般:付与しない誤編集の防止
日報データシート保護(GASのみ書き込み)手動での誤入力防止
見出し行保護(編集不可)列構成の維持
社員マスタ管理者のみ編集個人情報の管理
⚠️ 共有相手・アクセス権の変更は情報漏えいに直結する重要操作です。必ずご担当者ご本人が確認しながら設定してください(代理設定は行いません)。
📋
8. PHASE 2 完了チェックリストChecklist
✓
スプレッドシートを作成し、タイムゾーンを日本標準時に設定した
✓
5枚のシートを作成し、正しい名前を付けた
✓
日報データの列定義(A〜P)を見出し行に入力した
✓
月次集計の列(A〜Q。残業は日→週→月の三段階でGASが算出)を設定した
✓
変形労働カレンダーに当月の所定と法定総枠(177.0/171.0/160.0/165.5h)を入力した
✓
社員マスタに全スタッフの氏名・照合キー・雇用形態・給与形態・月平均所定労働時間を入力した
✓
修正履歴シートの見出しを用意した
✓
共有・保護設定を本人が行い、管理者のみアクセス可にした
🔜

PHASE 3・4 への引き継ぎ事項

PHASE 2で「データが入る器(5シート)」が完成しました。
PHASE 3ではLINEの日報・有給・修正のフォームを作り込み、PHASE 4で連携シートから本シートへの自動転記と計算を構築します。
引き継ぐもの:①日報データの列構成(A〜P)と列番号 ②月次集計の列(A〜Q) ③社員マスタの照合キー(氏名)と月平均所定労働時間 ④変形労働カレンダーの所定・法定休日・法定総枠。
※ 列の順番・見出しは確定版とし、以降は安易に変更しないでください(GAS連携の前提になります)。

導入ロードマップ PHASE 3 / 詳細設計図
フォーム・ステップ配信設定
詳細設計図

不動産営業職 歩合給対応 勤怠管理システム
エルメッセージの回答フォーム・タグ・ステップ配信の実装仕様

⏱️ 所要:2〜3日
🛠️ エルメッセージ(エルメ)
📐 実装担当者向け仕様
🗺️
0. PHASE 3 の全体像Overview

目的:PHASE 1の「準備中です」ボタン(日報入力・有給休暇届・修正申請)を、エルメッセージの回答フォームで実際に動く機能へ作り替える。あわせて打刻漏れリマインドと日次リセットをステップ配信で構築する。
データの扱い:入力値はすべてフォームの回答として連携シートに自動記録する(友だち情報に保存するのは氏名だけ)。PHASE 4のGASは連携シートを読むだけなので、本フェーズでは「フォームの設問文」を確定させておくことが重要。

構築する4つの仕組み
No仕組み入口使用タグ記録先
①日報入力(打ち合わせ/成約/報告事項)リッチメニューC → フォーム「日報」日報_提出済連携シート「日報」
②有給休暇届リッチメニューE → フォーム「有給休暇届」有給_申請中/有給_承認済/有給_差戻し連携シート「有給」+管理者へアプリ通知
③修正申請(打刻漏れ対応)リッチメニューF → フォーム「修正申請」修正_申請中連携シート「修正申請」+管理者へアプリ通知
④リマインド・日次リセットステップ配信(タグ追加を開始条件)出勤中/休憩中/退勤済/日報_提出済—(配信のみ)
📐 本書は実装担当者がエルメ上で設定する際の仕様書です。フォームの設問・回答後アクション・タグ操作・ステップ配信の条件を定義しています。
⚠️ エルメで「できないこと」を前提に設計しています。①ボタンのタップやタグの付け外しはスプレッドシートに記録されない(記録はフォーム回答のみ) ②時刻を指定してタグを一括解除する機能はない ③GASはエルメのタグを操作できない。この3点を踏まえ、記録はフォーム、状態はタグ、時間経過の処理はステップ配信、集計と通知はGAS、という役割分担にしています。
🏷️
1. タグ定義と状態遷移Tags

PHASE 3で追加するタグと、いつ付与・いつ解除するかを定義します。「付けて切り替える」のが原則で、付与と解除は必ずセットで設計します。

追加日報・修正関連タグ
タグ名付与タイミング解除タイミング用途
日報_提出済フォーム「日報」の回答後アクション翌朝5時の日次リセット(ステップ配信)退勤後の「日報未提出」リマインドの除外条件
修正_申請中フォーム「修正申請」の回答後アクション管理者が反映後に解除申請中の重複防止・未処理の把握
追加有給関連タグ
タグ名付与タイミング解除タイミング用途
有給_申請中フォーム「有給休暇届」の回答後アクション管理者が「有給_承認済」または「有給_差戻し」に付け替え申請中の表示・重複防止
有給_承認済管理者が友だち詳細で付与(タグ追加時アクションで本人へ自動返信)取得日経過後に管理者が解除承認状態の管理
有給_差戻し管理者が付与(タグ追加時アクションで本人へ自動返信)本人が再申請した時差し戻しの通知と記録
💡 PHASE 1のタグとの関係:「出勤中/退勤済/休憩中」はPHASE 1で作成済み。本フェーズのリマインド(§6)はこれらのタグ追加をステップ配信の開始条件に使います。「有給取得当日」のような日付で自動的に付くタグはエルメでは作れないため、本設計では使いません(当日の出勤リマインドの除外はGASが有給の連携シートを見て行います)。
🗂️
2. フォームの設問定義(=連携シートの見出し)Forms

各フォームの設問を定義します。設問文がそのまま連携シートの見出しになり、PHASE 4のGASはこの見出し名でデータを読むため、ここで確定させます。「お名前」は全フォーム共通で、友だち情報 staff_name から自動入力します。

フォーム設問文(見出し)形式選択肢・例
打刻(PHASE1)お名前テキスト(必須・自動入力)山田太郎
区分選択(必須)出勤/退勤(休憩フォームは 休憩開始/休憩終了)
日報お名前テキスト(必須・自動入力)山田太郎
記録の種類選択(必須)打ち合わせ/成約報告/報告事項
相手テキスト山田様(打ち合わせ)
時間(分)数値90(打ち合わせ)
内容テキスト白金台マンション案内(打ち合わせ)
物件名テキスト〇〇マンション101(成約)
種別選択売買/賃貸(成約)
金額(円)数値32000000(成約・数字のみ)
連絡事項テキスト田中様 再連絡希望(報告)
翌日の出勤予定テキスト09:30(報告)
翌日の退出予定テキスト19:00(報告)
翌日の予定テキスト午前 内見2件(報告)
有給休暇届お名前テキスト(必須・自動入力)山田太郎
取得日日付(必須)2025/06/20
種類選択(必須)全日/午前半休/午後半休
備考テキスト(任意)理由は任意(記載を求めない)
修正申請お名前テキスト(必須・自動入力)山田太郎
対象日日付(必須)2025/06/18
項目選択(必須)出勤/退勤/休憩
正しい時刻・内容テキスト(必須)9:00(休憩なら分数:60)
⚠️ 設問文はPHASE 4のGASが見出し名で参照します。後から変えると連携が壊れるため、ここで確定させてください。4本のフォームはそれぞれ「フォーム作成」一覧の「スプレッドシート」でGoogleアカウントと連携し、連携シートを作ります(連携シートは編集しない)。
💡 「記録の種類」で設問を出し分ける:日報フォームは1本にまとめ、「記録の種類」の選択で表示する設問を切り替えます(エルメのフォームの条件表示)。出し分けができない場合は打ち合わせ/成約/報告の3本に分け、連携シートも3枚にしてGASの読み先を増やします。
📝
3. 日報入力(フォーム「日報」の詳細)Form ①
共通:起動から記録まで
押
トリガー
「📝 日報入力」タップ → フォーム「日報」が開く(打ち合わせのたび、成約のたびに何回でも送信可)
分
記録の種類
「打ち合わせ」「成約報告」「報告事項」を選ぶと、その種類の設問だけが表示される
記
自動記録
送信ごとに連携シート「日報」へ1行(回答日時/お名前/記録の種類/各設問)。GASが同じ日の分をまとめて日報データのI〜O列へ転記(PHASE 4)
タ
回答後アクション
「✅ 記録しました」を自動返信 → タグ「日報_提出済」付与。成約報告のときは管理者のアプリ通知で「🏆 成約速報」として届く
🤝 打ち合わせ:相手・時間(分)・内容 🏆 成約報告:物件名・種別・金額(円) 📋 報告事項:連絡事項+翌日の予定
💡 退勤時の案内と連動:退勤打刻の自動返信に「本日の日報が未提出の方は『📝 日報入力』からお願いします」と添え、退勤30分後に「日報_提出済 なし」の人だけへステップ配信でリマインドします(§6)。
📌 金額は「数字のみ」で入力してもらいます(カンマ・円を入れると数値として集計できません)。フォームの形式を「数値」にしておくと入力時点で弾けます。
🌴
4. 有給休暇届(フォーム「有給休暇届」の詳細)Form ②
申請〜承認の完全フロー
押
トリガー
「🌴 有給休暇届」タップ → フォーム「有給休暇届」(取得日・種類・備考)を送信
記
自動記録・通知
連携シート「有給」に記録 → 回答後アクションでタグ「有給_申請中」付与+「受け付けました。承認結果はこのLINEでお知らせします」を返信。管理者のスマホにはアプリ通知(顧客対応 → 通知設定でフォーム回答をリアルタイム)
管
管理者の操作(エルメ管理画面)
友だち詳細を開き、タグを「有給_申請中」→「有給_承認済」(または「有給_差戻し」)に付け替える。差し戻しの理由は1:1チャットで伝える
タ
タグ追加時アクション
「有給_承認済」追加時:「✅ 承認されました」を自動返信/「有給_差戻し」追加時:「内容を確認のうえ再度お手続きください」を自動返信。いずれも「有給_申請中」を解除
記
台帳への記録
承認・差し戻しの別はタグで管理する。GASは連携シート「有給」の取得日を見て、当日の未打刻アラートの対象から外す(§6)。差し戻した申請は管理者が連携シートではなく1:1チャットの記録で把握する
📌 「有給_申請中」が付いている間にもう一度申請があった場合は、回答後アクションの条件で「申請中の届があります」と返して受け付けません。半日単位の有給は法律上の制度ではなく就業規則で定めて運用するものなので、「午前半休/午後半休」の選択肢は就業規則に規定がある場合だけ置きます。
⚠️ 有給休暇は本人が時季を指定して取得する権利です。「承認」は会社が時季変更権を行使しないことの確認であり、理由の記載を求めたり、理由で可否を決めたりしない運用にします(備考欄は任意)。
⚠️
5. 修正申請(打刻漏れ対応・フォーム「修正申請」の詳細)Form ③
打刻漏れ・時刻訂正の申請フロー
押
トリガー
「⚠️ 修正申請」タップ → フォーム「修正申請」(対象日・項目・正しい時刻)を送信
記
自動記録・通知
連携シート「修正申請」に記録 → タグ「修正_申請中」付与+「受け付けました」を返信。管理者へアプリ通知
管
管理者の操作(台帳)
台帳「修正履歴」に申請内容を転記(GASが連携シートから自動転記する設計も可)し、内容を確認してH列「承認者」に氏名を入力する
G
GASが反映(PHASE 4 §7)
承認者が入った行を日報データへ反映し、修正前の値をE列に保存、I列に反映日時を記録。該当日は「修正済」として以後の自動転記で上書きしない
タ
本人へ返信
管理者が「修正_申請中」を解除し、1:1チャットで「反映しました」と伝える(差し戻しも同様)
💡 当日中の打ち直しとの使い分け:当日中であれば、もう一度打刻フォームを送れば済みます(GASは1日の最初の「出勤」と最後の「退勤」を採用)。翌日以降の訂正だけをこの修正申請で受け、管理者の承認を必須にします。
🔔
6. リマインド配信と日次リセット(ステップ配信)Step

エルメのステップ配信は「タグが付いた時」を開始条件に、N時間後・翌日○時といったタイミングで配信やアクションを実行できます。「時刻を指定してタグを一括解除する」機能はないため、日次リセットもステップ配信で行います。各ステップの配信条件でタグを再判定し、不要な人には送らないようにします。

ステップ配信開始条件タイミング配信条件(送る人)内容・アクション
日次リセット+朝の案内タグ「退勤済」追加翌日 5:00スタッフ_在籍 ありアクション:「出勤中」「退勤済」「休憩中」「日報_提出済」を解除。メッセージ「おはようございます。本日も出勤時に打刻をお願いします」
退勤打刻忘れタグ「出勤中」追加12時間後「出勤中」あり「退勤打刻がまだのようです。退勤済みの場合は正しい時刻で修正申請をお願いします」
休憩戻り忘れタグ「休憩中」追加90分後「休憩中」あり「休憩終了の打刻がまだです。戻っている場合は『休憩終了』を送信してください」
日報未提出タグ「退勤済」追加30分後「日報_提出済」なし「本日の日報が未提出です。『📝 日報入力』からお願いします」
出勤打刻なし(管理者向け)GASの時間トリガー(毎日10:30)在籍者のうち当日の「出勤」回答がなく、有給の取得日でもない人を管理者へメール(PHASE 4 §8)
💡 出勤リマインドを本人へ毎朝送らない理由:「出勤する人だけに朝リマインド」はエルメ単体では組めません(全員に同じ文面しか送れず、休日・休暇者の除外もできない)。本設計では朝の案内を日次リセットのステップ配信に兼ねさせ、未打刻の把握はGASが管理者へメールする形にしています。これで通数は1人1日1通(朝の案内)+必要時のみに抑えられます。
⚠️ 通数の見積り:朝の案内(1通)+退勤忘れ・休憩戻り忘れ・日報未提出(該当時のみ)で、1人1日あたり1〜2通。スタッフ10名・月22日出勤なら月220〜440通となり無料枠(200通)を超えます。本設計はLINE公式アカウントの有料プランを前提にするか、朝の案内のメッセージを外して(アクションのみのステップにできる場合)通数を抑えてください。
📌 日次リセットを5:00にしているのは、23:00出勤→翌2:00退勤のような日付をまたぐ勤務を同じ勤務日として扱うためです。PHASE 4のGASも「0〜5時の打刻は前日の勤務」として集計します。
🛡️
7. エラー処理・例外対応Validation
想定ケースシステムの動作
入力形式が違う(金額に文字など)フォームの形式を「数値」「日付」にしておき、送信時にフォーム側で弾く
出勤前に「休憩開始」を送る回答後アクションの条件(「出勤中」なし)で「先に出勤打刻をしてください」と返す
同じ有給を二重申請「有給_申請中」ありなら「申請中の届があります」と返し、管理者には通知しない
氏名の表記ゆれ(社員マスタと不一致)GASは転記せず、月次集計メールに「マスタにない名前」として列挙。管理者が友だち情報 staff_name を修正する
未実装内容を選択「この機能は準備中です」と仮応答(残機能がある場合)
管理者が長時間未承認GASの朝のメールに「修正_申請中/有給_申請中」の未処理件数を載せる(台帳の承認者欄が空の行を数える)
📌 フォームは閉じれば中断できるので「入力途中の離脱検知」は不要です。すべての自動返信に「困ったときは『⚠️ 修正申請』または担当者へ」の一文を入れ、スタッフが詰まらないようにします。
📋
8. PHASE 3 完了チェックリストChecklist
✓
日報・有給・修正のタグを追加し、付与・解除ルールを設定した
✓
4本のフォームの設問文を確定し、それぞれスプレッドシート連携を有効にした
✓
日報フォームの3種類(打ち合わせ・成約・報告事項+翌日予定)が記録され、「日報_提出済」が付く
✓
有給休暇届が申請→管理者通知→タグ付け替え→本人へ自動返信まで動く
✓
修正申請が記録・通知され、台帳「修正履歴」で承認できる
✓
4本のステップ配信(日次リセット・退勤忘れ・休憩戻り忘れ・日報未提出)が条件どおりに届く
✓
管理者のスマホでフォーム回答のアプリ通知が届く設定にした
✓
PHASE 1の「準備中です」仮応答をフォームのURLに差し替えた
🔜

PHASE 4 への引き継ぎ事項

PHASE 3で「LINE上の操作がフォーム回答として記録される仕組み」が完成しました。次のPHASE 4では以下を構築します。
① 4枚の連携シート(打刻/日報/有給/修正申請)をGASが読み、日報データへ転記する処理(時間トリガー・10分おき)。
② 実働時間の計算と、1か月単位変形労働時間制の時間外判定(日→週→月の三段階)、歩合給を含む割増賃金の計算。
③ 修正履歴の承認分を実データへ反映する処理と、管理者への未打刻・集計メール。

導入ロードマップ PHASE 4 / 詳細設計図
自動集計プログラム
詳細設計図

不動産営業職 歩合給対応 勤怠管理システム
連携シート→台帳の自動転記・実働時間計算・変形労働時間制の時間外判定・歩合給の割増賃金の実装仕様

⏱️ 所要:3〜5日
🛠️ GAS・連携シート
📐 開発担当者向け仕様
🗺️
0. PHASE 4 の全体像Overview

目的:PHASE 3でフォーム回答として記録した打刻・日報・有給・修正データを、GASが連携シートから読み取って日報データへ転記し、実働時間の計算と1か月単位変形労働時間制の時間外判定(日→週→月)、歩合給を含む割増賃金の計算を自動化する。
これが完成すると、スタッフがLINEで操作するだけで月次の勤怠集計が自動で出来上がります。

データの流れ(アーキテクチャ)
📱
LINE / エルメ
フォームで打刻・日報・有給・修正を入力(PHASE1・3)
→
🧾
連携シート(4枚)
エルメが回答をリアルタイムで自動記録
→
⚙️
GAS
10分おきに読む→転記→計算(自動処理)
→
📊
勤怠データ_2025
日報データ→月次集計が自動完成・管理者へメール
📐 本書は開発担当者がGAS(Google Apps Script)を実装する際の仕様書です。連携方式・項目対応・計算ロジック・関数構成・トリガー設定・テスト観点を定義します。
🌉
1. 連携方式Bridge
⚠️ 大前提:LINE公式アカウントのWebhook送信先は1つだけ。エルメを使う時点でその枠はエルメが占有するため、GASにLINEからの受信を担わせることはできません。また、エルメには外部URLへデータを送る(Webhook送信)機能も、GASからエルメのタグを操作するAPIもありません。したがって本設計のGASは「連携シートを読む/台帳に書く/管理者にメールする」だけを担当し、LINEにもエルメにも直接触りません。
採用:連携シート → GAS(時間トリガー)本設計
4本のフォームのスプレッドシート連携で自動記録された連携シートを、GASが10分おきに読んで台帳へ転記する。
◎ メリット:エルメ側の設定はフォーム連携だけ。Webアプリの公開やトークン管理が不要で、権限は台帳側に閉じる。
△ 注意:反映は最大10分遅れ。氏名で社員を照合するため、フォームの「お名前」を友だち情報から自動入力して表記ゆれを防ぐ。
参考:GAS単独構成(エルメを使わない)
勤怠専用の公式アカウントでWebhookをGASが受け、ボタンのpostbackを直接シートに記録する構成。1タップ打刻ができる反面、日報・有給・修正・リマインドをすべて自作することになり、本設計の対象外です(別記事「LINE打刻のスプレッドシート自動記録」参照)。
🔗
2. 項目マッピング(連携シート → 日報データ)Mapping

PHASE 3で定義したフォームの設問(連携シートの見出し)を、PHASE 2の「日報データ」シートの列へ対応させます。GASは見出し名で読み、列番号で書きます。

連携シート/見出し→ 日報データの列型/加工
打刻/回答日時A:日付0〜5時の打刻は前日の勤務日として扱う
(社員マスタ照合)B:社員ID「お名前」でマスタB列を照合(空白を除いて比較)
打刻/お名前C:氏名マスタの正式名称で補完
打刻/区分=出勤の回答日時D:出勤その日の最初の「出勤」
打刻/区分=退勤の回答日時E:退勤その日の最後の「退勤」
打刻/休憩開始〜休憩終了F:休憩(分)開始→終了の組を順に合計
(GAS計算)G:実働(分)§4①で算出
(GAS計算)H:深夜(分)§4②で算出
日報/相手・時間(分)・内容I:打ち合わせ連結して格納(同日複数は「/」区切り)
日報/物件名・種別J:成約内容連結して格納(表示用)
日報/連絡事項K:報告事項そのまま
日報/翌日の出勤予定L:翌日出勤そのまま
日報/翌日の退出予定M:翌日退出そのまま
日報/翌日の予定N:翌日予定そのまま
日報/金額(円)O:成約額同日の合計(歩合給の集計用)
(GAS管理)P:状態「自動」または「修正済」。修正済の日は自動転記で上書きしない
⚠️ 列の順番・見出しはPHASE 2で確定済みのものに合わせます(P列「状態」を追加しています)。列を増減した場合はこのマッピングとGASの列番号(定数 D)を必ず同期させてください。
📥
3. 転記処理(設定と共通関数・打刻・日報)Sync
読み取り〜書き込みの流れ(10分おきの時間トリガー)
S
連携シート
フォーム送信のたびにエルメが1行追記(GASは読むだけ・編集しない)
G
GAS:syncAll
syncPunches(打刻)→ syncReports(日報)→ applyFixes(修正)の順に実行
G
冪等(べきとう)な転記
直近3日分を毎回作り直す方式。何度実行しても同じ結果になり、二重送信があっても崩れない
S
日報データ
(日付, 氏名)で行を特定し、なければ追加。P列が「修正済」の行は触らない
スクリプトプロパティと社員マスタの追加列
設定場所キー/列内容
スクリプト プロパティPUNCH_SHEET_IDフォーム「打刻」の連携シートのファイルID(休憩フォームも同じ連携先にする)
スクリプト プロパティREPORT_SHEET_IDフォーム「日報」の連携シートのファイルID
スクリプト プロパティLEAVE_SHEET_IDフォーム「有給休暇届」の連携シートのファイルID
スクリプト プロパティNOTIFY_EMAIL管理者のメールアドレス(未打刻・最低賃金・集計の通知先)
社員マスタL列:月平均所定労働時間固定給の割増単価の分母(例:171.0)。年間所定労働時間÷12
// ① 設定・共通(台帳「勤怠データ_2025」の拡張機能 → Apps Script に貼り付け)
// ===== ① 設定・共通(勤怠データ_2025 のApps Scriptに貼り付け) =====
// スクリプト プロパティ(プロジェクトの設定 → スクリプト プロパティ)
//   PUNCH_SHEET_ID  : 打刻フォームの連携シート(ファイルID)
//   REPORT_SHEET_ID : 日報フォームの連携シート(ファイルID)
//   LEAVE_SHEET_ID  : 有給休暇届フォームの連携シート(ファイルID)
//   NOTIFY_EMAIL    : 管理者のメールアドレス
const P = PropertiesService.getScriptProperties();
const TZ = 'Asia/Tokyo';
const SHEETS = { daily: '日報データ', monthly: '月次集計', cal: '変形労働カレンダー',
                 master: '社員マスタ', fix: '修正履歴' };

// 日報データの列番号(PHASE 2 §2 と一致させる)
const D = { date: 1, id: 2, name: 3, inT: 4, outT: 5, brk: 6, work: 7, night: 8,
            mtg: 9, deal: 10, note: 11, nIn: 12, nOut: 13, nPlan: 14, amount: 15, state: 16 };

// 連携シートの見出し(フォームの設問文に合わせて変更)
const H = {
  time: '回答日時', name: 'お名前', kind: '区分',
  // 日報フォーム
  rType: '記録の種類', mtgWho: '相手', mtgMin: '時間(分)', mtgWhat: '内容',
  prop: '物件名', dealType: '種別', amount: '金額(円)', note: '連絡事項',
  nIn: '翌日の出勤予定', nOut: '翌日の退出予定', nPlan: '翌日の予定',
  // 有給休暇届フォーム
  leaveDate: '取得日'
};

// 月の法定総枠(本設計の設定値。厳密な上限は 177.1 / 171.4 / 165.7 / 160.0)
const LEGAL_CAP = { 31: 177.0, 30: 171.0, 29: 165.5, 28: 160.0 };
const WEEK_START = 0;      // 週の起算日:0=日曜(就業規則に合わせる)
const MIN_WAGE = 1200;     // 事業場の最低賃金(円/時)。改定時に更新

function ss_() { return SpreadsheetApp.getActiveSpreadsheet(); }
function sh_(name) { return ss_().getSheetByName(name); }
function fmt_(d, f) { return Utilities.formatDate(new Date(d), TZ, f); }
function norm_(s) { return String(s || '').replace(/[\s ]/g, ''); }
function mail_(subject, body) {
  const to = P.getProperty('NOTIFY_EMAIL');
  if (to) MailApp.sendEmail(to, subject, body);
}

// 連携シートを見出し名で読み、1行=1オブジェクトにする
function readSheet_(id) {
  if (!id) return [];
  const v = SpreadsheetApp.openById(id).getSheets()[0].getDataRange().getValues();
  if (v.length < 2) return [];
  const head = v[0].map(String);
  return v.slice(1).map(function (row) {
    const o = {};
    head.forEach(function (k, i) { o[k] = row[i]; });
    return o;
  });
}

// 社員マスタ:氏名 → { id, pay, fixedPay, stdHours, commissionRate }
function master_() {
  const m = {};
  sh_(SHEETS.master).getDataRange().getValues().slice(1).forEach(function (r) {
    if (!r[1]) return;
    m[norm_(r[1])] = { id: r[0], name: r[1], payType: r[7], fixedPay: Number(r[8]) || 0,
                       rate: Number(r[9]) || 0, guarantee: Number(r[10]) || 0,
                       stdHours: Number(r[11]) || 0 };
  });
  return m;
}

// 【トリガー】10分おき:打刻・日報・修正をまとめて反映
function syncAll() {
  syncPunches();
  syncReports();
  applyFixes();
}
// ② 打刻の転記:連携シート「打刻」→ 日報データ A〜H列
// ===== ② 打刻の転記(連携シート「打刻」→ 日報データ) =====
// 直近3日分を毎回作り直す(何度動かしても同じ結果になる)。修正済の日は触らない
function syncPunches() {
  const daily = sh_(SHEETS.daily);
  const master = master_();
  const since = new Date(); since.setDate(since.getDate() - 3);
  const byKey = {};
  readSheet_(P.getProperty('PUNCH_SHEET_ID')).forEach(function (r) {
    const t = new Date(r[H.time]);
    if (isNaN(t.getTime()) || t < since) return;
    // 深夜0〜5時の打刻は前日の勤務として扱う
    const workDate = new Date(t); if (t.getHours() < 5) workDate.setDate(workDate.getDate() - 1);
    const key = norm_(r[H.name]) + '|' + fmt_(workDate, 'yyyy/MM/dd');
    (byKey[key] = byKey[key] || []).push({ t: t, kind: String(r[H.kind]) });
  });
  Object.keys(byKey).forEach(function (key) {
    const parts = key.split('|');
    const m = master[parts[0]];
    if (!m) return;                                   // マスタにない名前は無視(表記ゆれは管理者が確認)
    const ps = byKey[key].sort(function (a, b) { return a.t - b.t; });
    let inT = null, outT = null, brkStart = null, brkMin = 0;
    ps.forEach(function (p) {
      if (p.kind === '出勤' && !inT) inT = p.t;          // 最初の出勤
      if (p.kind === '退勤') outT = p.t;                 // 最後の退勤
      if (p.kind === '休憩開始') brkStart = p.t;
      if (p.kind === '休憩終了' && brkStart) { brkMin += (p.t - brkStart) / 60000; brkStart = null; }
    });
    const row = findDailyRow_(daily, parts[1], m.name);
    if (daily.getRange(row, D.state).getValue() === '修正済') return;
    daily.getRange(row, D.date).setValue(parts[1]);
    daily.getRange(row, D.id).setValue(m.id);
    daily.getRange(row, D.name).setValue(m.name);
    if (inT) daily.getRange(row, D.inT).setValue(fmt_(inT, 'HH:mm'));
    if (outT) daily.getRange(row, D.outT).setValue(fmt_(outT, 'HH:mm'));
    daily.getRange(row, D.brk).setValue(Math.round(brkMin));
    if (inT && outT) {
      const w = calcWorkMinutes_(inT, outT, Math.round(brkMin));
      daily.getRange(row, D.work).setValue(w.work);
      daily.getRange(row, D.night).setValue(w.night);
    }
    daily.getRange(row, D.state).setValue('自動');
  });
}

// 日報データで(日付, 氏名)の行を探す。なければ末尾に追加して行番号を返す
function findDailyRow_(daily, dateStr, name) {
  const v = daily.getDataRange().getValues();
  for (let i = 1; i < v.length; i++) {
    if (fmt_(v[i][0], 'yyyy/MM/dd') === dateStr && norm_(v[i][2]) === norm_(name)) return i + 1;
  }
  return daily.getLastRow() + 1;
}

// 実働(分)と深夜(22:00〜翌5:00)の分数
function calcWorkMinutes_(inT, outT, breakMin) {
  const gross = (outT - inT) / 60000;
  let night = 0;
  for (let t = new Date(inT); t < outT; t.setMinutes(t.getMinutes() + 1)) {
    const h = t.getHours();
    if (h >= 22 || h < 5) night++;
  }
  return { work: Math.max(0, Math.round(gross - breakMin)), night: night };
}
// ③ 日報の転記:連携シート「日報」→ 日報データ I〜O列
// ===== ③ 日報の転記(連携シート「日報」→ 日報データ I〜O 列) =====
function syncReports() {
  const daily = sh_(SHEETS.daily);
  const master = master_();
  const since = new Date(); since.setDate(since.getDate() - 3);
  const byKey = {};
  readSheet_(P.getProperty('REPORT_SHEET_ID')).forEach(function (r) {
    const t = new Date(r[H.time]);
    if (isNaN(t.getTime()) || t < since) return;
    const key = norm_(r[H.name]) + '|' + fmt_(t, 'yyyy/MM/dd');
    (byKey[key] = byKey[key] || []).push(r);
  });
  Object.keys(byKey).forEach(function (key) {
    const parts = key.split('|');
    const m = master[parts[0]];
    if (!m) return;
    const mtg = [], deal = [], note = [];
    let amount = 0, nIn = '', nOut = '', nPlan = '';
    byKey[key].forEach(function (r) {
      const type = String(r[H.rType] || '');
      if (type === '打ち合わせ') mtg.push(r[H.mtgWho] + ' ' + r[H.mtgMin] + '分 ' + r[H.mtgWhat]);
      if (type === '成約報告') { deal.push(r[H.prop] + ' ' + r[H.dealType]); amount += Number(r[H.amount]) || 0; }
      if (type === '報告事項') { note.push(String(r[H.note] || '')); nIn = r[H.nIn] || nIn; nOut = r[H.nOut] || nOut; nPlan = r[H.nPlan] || nPlan; }
    });
    const row = findDailyRow_(daily, parts[1], m.name);
    daily.getRange(row, D.date).setValue(parts[1]);
    daily.getRange(row, D.id).setValue(m.id);
    daily.getRange(row, D.name).setValue(m.name);
    daily.getRange(row, D.mtg).setValue(mtg.join('/'));
    daily.getRange(row, D.deal).setValue(deal.join('/'));
    daily.getRange(row, D.note).setValue(note.join('/'));
    daily.getRange(row, D.nIn).setValue(nIn);
    daily.getRange(row, D.nOut).setValue(nOut);
    daily.getRange(row, D.nPlan).setValue(nPlan);
    daily.getRange(row, D.amount).setValue(amount);
  });
}
📌 ファイルIDやメールアドレスはコードに直書きせず、スクリプトプロパティに保存します。連携シートの見出し名は定数 H にまとめてあるので、フォームの設問文を変えたらここだけ直します。
⏱️
4. 実働時間の計算Work Hours

打刻の転記時(syncPunches)に当日の実働時間を計算し、日報データのG列(実働)とH列(深夜)へ書き込みます。

① 当日の実働時間
実働時間 = 退勤時刻 − 出勤時刻 − 休憩合計時間
② 深夜労働(割増対象・別集計)
深夜時間 = 22:00〜翌5:00 に含まれる実働分
💡 端数処理:分単位で記録し、表示は時:分へ変換。労働時間は1分単位で把握するのが原則で、日々の端数を切り捨てることはできません(1か月の合計で30分未満を切り捨て・30分以上を切り上げる丸めのみ可)。
📌 深夜時間の算出は、出勤から退勤までを1分ずつ走査して22時〜翌5時に入る分を数える素直な実装にしています(休憩が深夜帯にかかる場合は保守的に深夜に含めます。厳密に除外したい場合は休憩の開始・終了も走査します)。
📐
5. 時間外労働の判定と歩合給の割増賃金Overtime

本システムの核心です。1か月単位変形労働時間制では、時間外労働を日 → 週 → 月の三段階で判定します。「月の総枠を超えた分だけ」を残業とする方式は、所定が8時間を超える日の超過分を取りこぼし、未払いになるので採用できません。

③-1 日単位
所定が8hを超える日 → その所定を超えた時間
それ以外の日 → 8hを超えた時間
③-2 週単位
所定が40hを超える週 → その所定を超えた時間
それ以外の週 → 40hを超えた時間(③-1で数えた分を除く)
③-3 月単位
月の実働累計 − 法定総枠(③-1・③-2で数えた分を除く)
③-4 法定休日労働
法定休日(カレンダーG列「○」)の労働は時間外に入れず別枠(3割5分)
設定値:月の法定労働時間の総枠
月の暦日数本設計の設定値厳密な上限(40h×暦日数÷7)該当月の例
31日177.0 時間177.1 時間1・3・5・7・8・10・12月
30日171.0 時間171.4 時間4・6・9・11月
29日(うるう年2月)165.5 時間165.7 時間2月(うるう年)
28日(平年2月)160.0 時間160.0 時間2月(平年)
💡 設定値を端数切り捨てにしている理由:法定の上限は小数第1位まであり、本設計は労働者に不利にならない安全側に丸めています。この総枠は「変形期間の所定労働時間をここまでに収める」上限であり、就業規則または労使協定で各日・各週の所定労働時間を事前に特定しておくことが制度の要件です(労使協定で導入する場合は労基署への届出が必要)。
// ⑤ 三段階の時間外判定と割増賃金(設定値は①の LEGAL_CAP / WEEK_START)
// ===== ⑤ 1か月単位変形労働時間制の残業判定(日→週→月)と割増賃金 =====
// cal:{ 'yyyy/MM/dd': { std: 所定(h), legalHoliday: true/false } }
function calcOvertime_(rows, cal, daysInMonth) {
  let dayOt = 0, weekOt = 0, holiday = 0, total = 0;
  const weeks = {};
  rows.forEach(function (r) {
    const dateStr = fmt_(r.date, 'yyyy/MM/dd');
    const c = cal[dateStr] || { std: 0, legalHoliday: false };
    const h = r.work / 60;
    if (c.legalHoliday) { holiday += h; return; }     // 法定休日労働は別枠(3割5分)
    total += h;
    const dayLimit = Math.max(8, c.std);               // 所定が8h超の日はその所定、それ以外は8h
    const ot = Math.max(0, h - dayLimit);
    dayOt += ot;
    const wk = weekKey_(new Date(r.date));
    weeks[wk] = weeks[wk] || { work: 0, std: 0, dayOt: 0 };
    weeks[wk].work += h; weeks[wk].std += c.std; weeks[wk].dayOt += ot;
  });
  Object.keys(weeks).forEach(function (k) {
    const w = weeks[k];
    const weekLimit = Math.max(40, w.std);             // 所定が40h超の週はその所定、それ以外は40h
    weekOt += Math.max(0, w.work - weekLimit - w.dayOt);
  });
  const cap = LEGAL_CAP[daysInMonth];
  const monthOt = Math.max(0, total - cap - dayOt - weekOt);
  return { total: total, holiday: holiday, dayOt: dayOt, weekOt: weekOt, monthOt: monthOt,
           overtime: dayOt + weekOt + monthOt };
}

// 週の起算日(WEEK_START)でそろえた週キー
function weekKey_(d) {
  const x = new Date(d);
  x.setDate(x.getDate() - ((x.getDay() - WEEK_START + 7) % 7));
  return fmt_(x, 'yyyy/MM/dd');
}

// 固定給分(月平均所定労働時間で割る)+歩合給分(その月の総労働時間で割る)
function calcPremium_(m, ot, holidayH, nightH, commission, totalH) {
  const fixedUnit = m.stdHours ? m.fixedPay / m.stdHours : 0;
  const commUnit = totalH ? commission / totalH : 0;
  return {
    fixedUnit: fixedUnit, commUnit: commUnit,
    overtime: fixedUnit * ot * 1.25 + commUnit * ot * 0.25,
    holiday: fixedUnit * holidayH * 1.35 + commUnit * holidayH * 0.35,
    night: (fixedUnit + commUnit) * nightH * 0.25,
    total: fixedUnit * ot * 1.25 + commUnit * ot * 0.25 +
           fixedUnit * holidayH * 1.35 + commUnit * holidayH * 0.35 +
           (fixedUnit + commUnit) * nightH * 0.25
  };
}
⚠️ 月またぎの週:上のコードは暦月の中だけで週を区切るため、月初・月末の週は前月分と通算されません。週の起算日(WEEK_START)と変形期間の起算日を合わせ、変形期間の初日を週の初日と一致させると、ズレが起きません。合わせられない場合は前月末の数日を含めて週判定するロジックを追加します。
本設計の核心歩合給がある場合の割増賃金の計算

不動産営業職のように固定給+歩合給で支払う場合、時間外労働の割増賃金は固定給部分と歩合給部分を分けて計算します(労働基準法施行規則19条1項6号)。ここを1本の式で済ませてしまうのが、歩合給のある会社で最も多い設計ミスです。

④ 固定給部分(通常の計算)
固定給の時間単価 = 月の固定給 ÷ 月平均所定労働時間
固定給分の割増 = 時間単価 × 残業時間 × 1.25
⑤ 歩合給部分(出来高払制の計算)
歩合給の時間単価 = その月の歩合給 ÷ その月の総労働時間
歩合給分の割増 = 時間単価 × 残業時間 × 0.25
⑥ 法定休日・深夜
法定休日:固定給分 × 1.35 + 歩合給分 × 0.35
深夜:(固定給単価 + 歩合給単価)× 深夜時間 × 0.25
⑦ その月に支払う割増賃金
④ + ⑤ + ⑥ の合計(月次集計 O列)
⚠️ 掛率が違う理由:歩合給は「働いた時間に対して既に支払われている」ため、割増部分の0.25倍だけを追加で支払います。固定給部分は1.25倍。両方1.25で計算すると過払い、両方0.25で計算すると未払いになります。分母も異なり、固定給は「月平均所定労働時間」、歩合給は「その月の総労働時間(時間外・休日労働を含む)」で割ります。固定給のうち家族手当・通勤手当・住宅手当など除外できる手当は分子から外します(名称でなく実質で判断)。
💡 保障給と最低賃金:出来高払制で使用する労働者には、労働時間に応じた一定額の保障給を定める義務があります(労働基準法27条。社員マスタK列)。さらに、固定給+歩合給を総労働時間で割った時間額が最低賃金を下回らないかを月次集計で毎月検算します(歩合給も最低賃金の対象に含めて計算)。労働時間を正確に把握できていて初めてこの検算ができる、というのが本システムを入れる最大の理由です。
📊
6. 月次集計の自動生成Aggregation

日報データを集計し、月次集計シートの当月分を毎晩23時の時間トリガーで作り直します(当月の行を削除して書き直すので、何度実行しても同じ結果になります)。

集計項目算出方法出力先(月次集計シート)
出勤日数当月の実働がある日数D:出勤日数
総労働時間当月の実働(分)を合算し60で除算(法定休日労働を含む)E:実働(h)
法定総枠暦日数から総枠を取得(§5)F:法定総枠(h)
時間外労働§5の三段階判定(日+週+月)G:残業(h)
深夜労働§4②を合算H:深夜(h)
打ち合わせ/成約当月の記録がある日数I・J:件数
成約額 合計当月の成約額を合算K:成約額
歩合給成約額 × 歩合率(給与規程で確定した額に手で差し替え可)L:歩合給
固定給社員マスタI列M:固定給
歩合給の時間単価歩合給 ÷ 総労働時間(§5⑤)N:時間単価
割増賃金固定給分1.25+歩合給分0.25+休日・深夜(§5⑦)O:割増賃金
法定休日労働カレンダーG列「○」の日の実働P:休日(h)
最低賃金の検算(固定給+歩合給)÷ 総労働時間 ≧ 最低賃金Q:最低賃金判定(下回れば管理者へメール)
// ⑥ 月次集計(トリガー:毎日 23:00〜24:00)
// ===== ⑥ 月次集計(毎晩23時のトリガーで当月分を更新) =====
function aggregateMonthly() {
  const now = new Date();
  const ym = fmt_(now, 'yyyy-MM');
  const daysInMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();
  const master = master_();
  const cal = {};
  sh_(SHEETS.cal).getDataRange().getValues().slice(1).forEach(function (r) {
    if (r[0]) cal[fmt_(r[0], 'yyyy/MM/dd')] = { std: Number(r[5]) || 0, legalHoliday: String(r[6]).trim() === '○' };
  });
  const byStaff = {};
  sh_(SHEETS.daily).getDataRange().getValues().slice(1).forEach(function (r) {
    if (!r[0] || fmt_(r[0], 'yyyy-MM') !== ym || !r[D.work - 1]) return;
    const k = norm_(r[D.name - 1]);
    (byStaff[k] = byStaff[k] || []).push({ date: r[0], work: Number(r[D.work - 1]) || 0,
      night: Number(r[D.night - 1]) || 0, mtg: r[D.mtg - 1], deal: r[D.deal - 1], amount: Number(r[D.amount - 1]) || 0 });
  });
  const out = [];
  const alerts = [];
  Object.keys(byStaff).forEach(function (k) {
    const m = master[k]; if (!m) return;
    const rows = byStaff[k];
    const ot = calcOvertime_(rows, cal, daysInMonth);
    const nightH = rows.reduce(function (s, r) { return s + r.night; }, 0) / 60;
    const mtgCnt = rows.filter(function (r) { return r.mtg; }).length;
    const dealCnt = rows.filter(function (r) { return r.deal; }).length;
    const amount = rows.reduce(function (s, r) { return s + r.amount; }, 0);
    const commission = Math.round(amount * m.rate);   // 歩合給(給与規程で確定した額に手で差し替え可)
    const totalH = ot.total + ot.holiday;
    const prem = calcPremium_(m, ot.overtime, ot.holiday, nightH, commission, totalH);
    const hourly = totalH ? (m.fixedPay + commission) / totalH : 0;
    const minOk = hourly >= MIN_WAGE ? 'OK' : '要確認';
    if (minOk !== 'OK') alerts.push(m.name + ':時間額 ' + Math.round(hourly) + '円');
    out.push([ym, m.id, m.name, rows.length, round1_(totalH), LEGAL_CAP[daysInMonth],
      round1_(ot.overtime), round1_(nightH), mtgCnt, dealCnt, amount, commission, m.fixedPay,
      Math.round(prem.commUnit), Math.round(prem.total), round1_(ot.holiday), minOk]);
  });
  const sheet = sh_(SHEETS.monthly);
  const v = sheet.getDataRange().getValues();
  for (let i = v.length - 1; i >= 1; i--) { if (v[i][0] === ym) sheet.deleteRow(i + 1); }
  if (out.length) sheet.getRange(sheet.getLastRow() + 1, 1, out.length, out[0].length).setValues(out);
  if (alerts.length) mail_('【勤怠】最低賃金の要確認 ' + ym, alerts.join('\n'));
}
function round1_(x) { return Math.round(x * 10) / 10; }
💡 歩合給の確定額:L列はGASが「成約額×歩合率」で仮計算しますが、歩合の計算期間(契約日か決済日か)や控除の扱いは給与規程で決まります。給与計算の際は確定した歩合給をL列に上書きし、aggregateMonthlyを再実行せずに(再実行すると仮計算に戻ります)割増賃金を再計算する運用にするか、確定額を社員マスタ側に持たせてGASが優先して読むように拡張します。
🔧
7. 修正申請の反映処理Fix
承認された修正をデータへ反映
管
管理者
台帳「修正履歴」の行を確認し、H列「承認者」に氏名を入力(差し戻すなら入力しない)
G
GAS:applyFixes
承認者ありかつ反映日時なしの行を対象に、対象日・対象社員の行を特定し、修正前の値をE列に保存してから該当セルを上書き
C
再計算
該当日の実働・深夜を再計算し、P列を「修正済」に(以後の自動転記で上書きしない)。月次は23時の集計で反映
履
履歴
I列に反映日時を記録。「いつ・誰が・どの打刻を・何から何に」変更したかが残る
// ④ 修正申請の反映:修正履歴(承認済み)→ 日報データ
// ===== ④ 修正申請の反映(修正履歴シートで管理者が承認した行を日報データへ) =====
// 修正履歴:A申請日時 B対象社員 C対象日 D項目 E修正前 F修正後 G申請者 H承認者 I反映日時
function applyFixes() {
  const fix = sh_(SHEETS.fix);
  const daily = sh_(SHEETS.daily);
  const v = fix.getDataRange().getValues();
  for (let i = 1; i < v.length; i++) {
    if (!v[i][7] || v[i][8]) continue;                 // 未承認、または反映済みは飛ばす
    const dateStr = fmt_(v[i][2], 'yyyy/MM/dd');
    const row = findDailyRow_(daily, dateStr, v[i][1]);
    const col = { '出勤': D.inT, '退勤': D.outT, '休憩': D.brk }[String(v[i][3])];
    if (!col) continue;
    fix.getRange(i + 1, 5).setValue(daily.getRange(row, col).getValue()); // 修正前を保存
    daily.getRange(row, col).setValue(v[i][5]);                            // 修正後を反映
    daily.getRange(row, D.date).setValue(dateStr);
    daily.getRange(row, D.name).setValue(v[i][1]);
    daily.getRange(row, D.state).setValue('修正済');
    recalcDailyRow_(daily, row);
    fix.getRange(i + 1, 9).setValue(new Date());
  }
}

// 1行分の実働・深夜を出勤・退勤・休憩から再計算する
function recalcDailyRow_(daily, row) {
  const r = daily.getRange(row, 1, 1, D.state).getValues()[0];
  if (!r[D.inT - 1] || !r[D.outT - 1]) return;
  const base = fmt_(r[0], 'yyyy/MM/dd');
  const inT = new Date(base + ' ' + fmt_(r[D.inT - 1], 'HH:mm'));
  const outT = new Date(base + ' ' + fmt_(r[D.outT - 1], 'HH:mm'));
  if (outT <= inT) outT.setDate(outT.getDate() + 1);  // 日付をまたぐ勤務
  const w = calcWorkMinutes_(inT, outT, Number(r[D.brk - 1]) || 0);
  daily.getRange(row, D.work).setValue(w.work);
  daily.getRange(row, D.night).setValue(w.night);
}
💡 トレーサビリティ:労務管理上、修正は上書きだけでなく履歴を必ず残します。元の値・修正後の値・申請者・承認者・反映日時を記録し、出勤簿として5年(当分の間3年)保存します(労働基準法109条)。
🔔
8. 管理者へのメール通知とトリガー設定Notify
// ⑦ 未打刻アラート(トリガー:毎日 10:00〜11:00)
// ===== ⑦ 未打刻アラート(毎日10:30のトリガー:出勤打刻がない在籍者を管理者へメール) =====
function alertUnpunched() {
  const today = fmt_(new Date(), 'yyyy/MM/dd');
  const punched = new Set(
    readSheet_(P.getProperty('PUNCH_SHEET_ID'))
      .filter(function (r) { return fmt_(r[H.time], 'yyyy/MM/dd') === today && String(r[H.kind]) === '出勤'; })
      .map(function (r) { return norm_(r[H.name]); })
  );
  const onLeave = new Set(
    readSheet_(P.getProperty('LEAVE_SHEET_ID'))
      .filter(function (r) { return fmt_(r[H.leaveDate], 'yyyy/MM/dd') === today; })
      .map(function (r) { return norm_(r[H.name]); })
  );
  const cal = sh_(SHEETS.cal).getDataRange().getValues().slice(1)
    .filter(function (r) { return r[0] && fmt_(r[0], 'yyyy/MM/dd') === today; })[0];
  if (cal && String(cal[2]) === '休日') return;        // 会社の休日は送らない
  const missing = Object.keys(master_()).filter(function (k) { return !punched.has(k) && !onLeave.has(k); });
  if (missing.length) mail_('【勤怠】出勤打刻なし ' + today + '(' + missing.length + '名)', missing.join('\n'));
}
時間主導型トリガーの設定(GASエディタ → トリガー)
関数イベントタイミング役割
syncAll分ベースのタイマー10分おき打刻・日報・修正の転記
alertUnpunched日付ベースのタイマー午前10時〜11時出勤打刻がない在籍者を管理者へメール(有給取得日・会社休日は除外)
aggregateMonthly日付ベースのタイマー午後11時〜12時当月の月次集計を作り直し、最低賃金の要確認者をメール
📌 時刻はトリガーの時間帯(1時間の幅)で指定します。初回実行時に認証画面が出たら「許可」します。GASはスタッフのLINEには何も送りません(スタッフへの配信はすべてエルメ側)。
テスト観点(PHASE 5へ橋渡し)
テスト項目確認内容
打刻の転記出勤→退勤のフォーム送信で日報データに正しい時刻・実働が入る
休憩除外休憩開始→終了の分が実働から正しく差し引かれる
変形労働の時間外日8h超・週40h超・月総枠超のそれぞれが取りこぼしなく計上される
法定休日G列「○」の日の労働が時間外でなく休日労働として別計上される
月またぎ月末→月初で集計が正しくリセットされる
うるう年2月29日の月で総枠165.5hが適用される
深夜労働22時以降の労働が深夜列に別集計される
修正申請承認後にデータが変わり、再計算され、履歴に反映日時が残る。以後の自動転記で戻らない
二重送信同じ区分を2回送っても最初の出勤・最後の退勤だけが採用される
表記ゆれマスタにない氏名が転記されず、管理者が気づける
📋
9. PHASE 4 完了チェックリストChecklist
✓
スクリプトプロパティに4つの連携シートIDと通知先メールを登録した
✓
見出し名(H)と列番号(D)がフォームの設問・日報データの列と一致している
✓
打刻・日報の転記が10分おきに動き、修正済の日は上書きされない
✓
実働時間の計算(退勤−出勤−休憩)と深夜の別集計が正しい
✓
変形労働の時間外判定が日→週→月の三段階で動き、法定休日は別枠になる
✓
法定総枠(177.0/171.0/165.5/160.0h)が暦日数で正しく切り替わる
✓
割増賃金が固定給分1.25・歩合給分0.25・休日・深夜で計算され、最低賃金の検算が出る
✓
月次集計が毎晩更新され、修正申請の反映+履歴記録が動く
✓
未打刻アラートが管理者へメールで届く
🔜

PHASE 5 への引き継ぎ事項

PHASE 4で「LINEの操作が自動で集計されるエンジン」が完成しました。次のPHASE 5(テスト・修正)では、上記§8のテスト観点に沿って実データでの動作確認を行い、計算のズレ・月またぎ・うるう年・深夜労働などのエッジケースを検証・修正します。
その後、PHASE 6でスタッフへの説明と本番運用に進みます。
引き継ぐもの:①GASのコードとトリガー設定 ②連携シートのファイルIDと台帳の共有設定 ③テスト結果の記録。

導入ロードマップ PHASE 5 / 詳細設計図
テスト・修正
詳細設計図

不動産営業職 歩合給対応 勤怠管理システム
本番前の動作確認・エッジケース検証・不具合修正の進め方

⏱️ 所要:3〜5日
🧪 テスト計画・ケース・不具合管理
📐 検証担当者向け仕様
🗺️
0. PHASE 5 の全体像Overview

目的:PHASE 1〜4で構築したシステムを本番運用前に総点検します。LINEの操作・データの書き込み・実働時間と変形労働残業の計算・各種配信が、実際のデータですべて正しく動くかを検証し、見つかった不具合を直します。
特に計算ロジック(変形労働時間制の残業判定)と月またぎ・うるう年などのエッジケースを重点的に確認します。

テストの4ラウンド
ROUND 1
機能テスト
各ボタン・各シナリオが想定どおり動くか
1日
ROUND 2
計算テスト
実働時間・残業・深夜が正確か
1〜2日
ROUND 3
エッジケース
月またぎ・うるう年・打刻漏れ等
1日
ROUND 4
運用テスト
実スタッフ数名で1〜2日試運用
1〜2日
📐 本書は検証担当者向けのテスト仕様書です。テストケースには通し番号(T-xx)を付け、§6の不具合管理表と紐付けて進捗を管理します。
📅
1. テスト計画と準備Plan
事前に用意するもの
準備物内容
テスト用LINEアカウントスタッフ役・管理者役の2つ以上(本人のLINEで可)
テスト用データ過去の勤務実績をモデルにした打刻パターン(残業あり/なし両方)
テストシート本番とは別の検証用スプレッドシート(誤記録防止)
期待値の早見表各テストの「正解の数字」を事前に手計算で用意
不具合管理表§6のフォーマット(発見→修正→再テストを記録)
⚠️ テストは必ず本番と分けた検証環境で行います。本番シートに誤った打刻が混ざると給与計算に影響するため、テスト完了までは本番接続しません。
🧩
2. ROUND 1:機能テストFunctional

各ボタン・各シナリオが想定どおり動くかを1つずつ確認します。

IDテスト項目操作期待結果
T-01出勤打刻出退勤ボタンをタップ出勤時刻が記録され「出勤中」タグが付く
T-02退勤打刻出勤中に再タップ→確認退勤時刻記録・実働表示・タグが退勤済に
T-03休憩トグル休憩ボタンを2回開始・終了が記録され休憩分が算出
T-04日報・打ち合わせ日報入力→打ち合わせ相手/時間/内容が保存される
T-05日報・成約報告日報入力→成約報告物件/種別/金額が保存される
T-06日報・報告事項+翌日日報入力→報告事項連絡と翌日予定(出勤/退出/予定)が保存
T-07有給休暇届申請→管理者承認通知→承認→本人返信、タグ切替
T-08修正申請打刻種別選択→申請管理者承認でデータ反映・履歴記録
T-09スプレッドシート転記各操作後に連携シート→10分後に台帳を確認連携シートに1行入り、項目マッピングどおり日報データに転記される
🧮
3. ROUND 2:計算テスト(最重要)Calculation

実働時間と変形労働時間制の残業判定が正確かを、手計算した期待値と照合します。本システムの心臓部です。

IDテスト項目テストデータ例期待値
T-10基本の実働計算9:00出勤・18:30退勤・休憩60分実働 8時間30分
T-11複数休憩の除外休憩を午前午後で計90分90分が実働から除外
T-12残業なし(総枠未満)30日の月・各日所定内・月実働168.5h残業 0h(日・週の超過なし、171.0h未満)
T-12b日単位の時間外所定8hの日に9h勤務×21日(189h)残業 21.0h(日単位1h×21。月総枠超過としては数えない)
T-12c法定休日労働カレンダーG列「○」の日に6h勤務時間外に含めず、法定休日(h)に6.0h(3割5分)
T-13残業あり(総枠超過)30日の月・各日所定内で出勤日数が多く月実働180.0h残業 9.0h(180.0−171.0)
T-1431日の月の総枠31日の月・月実働185.0h残業 8.0h(185.0−177.0)
T-15平年2月の総枠28日・月実働165.0h残業 5.0h(165.0−160.0)
T-16うるう年2月の総枠29日・月実働170.0h残業 4.5h(170.0−165.5)
T-17深夜労働の別集計22:00以降に2時間勤務深夜2hが深夜列に別計上
T-18歩合給の割増賃金歩合給30万円・総労働時間180h・残業9h時間単価1,667円→割増 約3,750円(×0.25)
T-19固定給+歩合給の合算固定給22万円(所定171h)+上記の歩合固定給分(×1.25)と歩合分(×0.25)が合算される
💡 照合のコツ:テスト前に各ケースの「正解の数字」を手計算で用意し、システムの出力と1件ずつ突き合わせます。ズレがあれば§5のロジック(法定総枠の値・暦日数の判定)を確認します。
⚡
4. ROUND 3:エッジケース検証Edge Cases

通常運用では起きにくいが、起きると影響が大きいケースを重点的に確認します。

IDエッジケース確認内容
T-20月またぎ月末→月初で集計がリセットされ、繰り越されない
T-21うるう年の切替2月29日のある年で総枠165.5hが適用される
T-22日付をまたぐ勤務23:00出勤→翌2:00退勤で実働・深夜が正しい
T-23出勤打刻のし忘れ10:30に管理者へメールが届く+修正申請で後から訂正できる
T-24退勤打刻のし忘れ退勤リマインド+翌日に修正申請で訂正
T-25二重送信「出勤」を2回送っても案内が返り、台帳には最初の出勤だけが採用される
T-26入力形式エラー金額に文字を入れるとフォーム側で送信できない
T-27有給取得当日有給の取得日は未打刻アラートの対象から外れる
T-28同時打刻複数人が同時刻に打刻しても取りこぼさない
T-29無料プラン上限月の配信通数が上限に近づいたら把握できる
⚠️ T-21(うるう年)は実機で再現しにくいため、システム日付を一時的に変更するか、暦日数を引数で指定してテストします。
👥
5. ROUND 4:運用テスト(実スタッフ試運用)Pilot

実際のスタッフ数名に1〜2日使ってもらい、現場で無理なく使えるかを確認します。机上では気づけない使い勝手の問題を洗い出します。

ID観点確認内容
T-30分かりやすさ説明なしでも打刻・日報入力ができるか
T-31入力の手間外回り中でも負担なく入力できるか
T-32文面の自然さ自動メッセージが分かりにくくないか
T-33リマインドの頻度多すぎ・少なすぎないか
T-34管理者の確認管理者が日次・月次を無理なく確認できるか
💡 フィードバック収集:試運用後にスタッフから「迷った所・面倒だった所」をヒアリングし、文面やボタン配置の改善(§7の修正)に反映します。
🐛
6. 不具合の管理(記録フォーマット)Bug Tracking

見つかった不具合は、深刻度を付けて一覧で管理します。記録はスプレッドシートやメモで構いません。

深刻度の区分
深刻度意味対応
A 重大計算ミス・データ消失など給与に影響本番前に必ず修正
B 中動くが分かりにくい・手間がかかる本番前に修正推奨
C 軽微表記ゆれ・見た目の小さな問題運用しながら改善可
不具合管理表のフォーマット
No関連ID内容深刻度状態
B-01T-16うるう年2月の残業が誤って160hで計算されるA修正済→再テストOK
B-02T-32打ち合わせ入力の例文が分かりにくいB修正中
B-03T-26時刻エラー時の文面が冷たい印象C未対応
📌 各不具合は「発見時のテストID」と紐付けます。修正後は必ず同じテストを再実行(§7)して、直ったことを確認します。
🔧
7. 修正〜再テストのフローFix Cycle
不具合の修正サイクル
発
発見
テスト中に不具合を発見し、関連IDとともに管理表へ記録
原
原因特定
どのPHASE由来か切り分け(シナリオ/タグ/GAS計算/連携)
修
修正
該当箇所を修正。計算系はGAS、文面系はエルメで対応
再
再テスト
同じテストIDを再実行+周辺機能への影響も確認(デグレ防止)
完
クローズ
再テストOKで管理表を「修正済」に。Aランクが0になれば本番GO
⚠️ デグレード(後戻り)に注意:1か所直すと別の所が壊れることがあります。修正後は直した箇所だけでなく、関連する機能(特に計算と集計)もまとめて再確認します。
📋
8. PHASE 5 完了チェックリスト(本番GO判定)Checklist
✓
機能テスト(T-01〜09)がすべてOK
✓
計算テスト(T-10〜19)が手計算の期待値と一致
✓
エッジケース(T-20〜29)が想定どおり処理される
✓
運用テスト(T-30〜34)で現場の使い勝手を確認した
✓
深刻度Aの不具合が0件になった
✓
修正後の再テストがすべて完了し、デグレがない
✓
本番用スプレッドシートへの接続切替の準備ができた
🔜

PHASE 6 への引き継ぎ事項

PHASE 5で「正しく動くことを確認したシステム」が完成しました。深刻度Aの不具合が0件になり、本番GOの判定が出たら、いよいよPHASE 6(スタッフ説明・本番運用)へ進みます。
引き継ぐもの:①テスト結果の記録と残課題(C軽微の改善リスト) ②本番接続の手順 ③運用テストで集めたスタッフの声(説明資料づくりに活用)。
本番では検証シートから本番シートへ切り替え、最初の1か月は計算結果を給与計算と二重チェックすると安心です。

導入ロードマップ PHASE 6 / 詳細設計図
スタッフ説明・本番運用
詳細設計図

不動産営業職 歩合給対応 勤怠管理システム
本番への切り替え・スタッフ説明会・安定運用までの最終フェーズ

⏱️ 所要:1日(+運用開始)
👥 スタッフ説明・運用ルール
🚀 本番稼働
🗺️
0. PHASE 6 の全体像Overview

目的:PHASE 5でテスト合格したシステムを本番に切り替え、スタッフへ使い方を説明して、毎日無理なく使える状態にします。これが導入の最終フェーズです。
ゴールは「全スタッフがLINEで打刻・日報入力を習慣化し、管理者が月次集計を確認できている」状態です。

本番稼働までの流れ
🔀
STEP 1
本番環境へ切り替え
検証シートから本番スプレッドシートへ接続を切り替え、最終動作を確認
📣
STEP 2
スタッフ説明会
全スタッフに友だち追加と基本操作を説明(30分程度)
🚀
STEP 3
本番運用スタート
運用ルールに沿って全員で打刻開始
🤝
STEP 4
定着フォロー
最初の1か月は二重チェックしながら定着を支援
🔀
1. 本番環境への移行手順Go-Live
順作業内容・注意点
1本番シートの用意テスト用と別の本番スプレッドシートを用意し、見出し・社員マスタ・変形労働カレンダーを正式版で入力
2GAS接続先の切替本番の台帳にGASを置き、スクリプトプロパティの連携シートID・通知先メールを本番用に確定
3テストデータの除去検証中に入った仮データが本番シートに混ざっていないか確認・削除
4最終疎通テスト本番環境で1人分の打刻→記録→集計を1サイクル確認
5権限・共有の最終確認本番シートの共有範囲を管理者のみに(共有設定はご本人が操作)
⚠️ 切替後は本番シートに直接テスト打刻をしないこと。確認は1サイクルのみとし、終わったらその行を削除します。給与計算に使うデータが汚れるのを防ぎます。
📣
2. スタッフ説明会の進め方(約30分)Briefing

説明会は「その場で全員が実際に打刻してみる」のが定着の近道です。資料を配るだけでなく、手を動かしてもらいます。

0-5分
① 導入の目的を伝える
「タイムカード打刻と日報がLINEで完結し、紙や口頭報告が不要になる」メリットを共有
5-10分
② その場で友だち追加
QRコードを提示し、全員にその場で友だち追加+名前入力をしてもらう
10-20分
③ 基本操作を実演&体験
出退勤・休憩・日報入力を画面で実演し、全員に1回ずつ操作してもらう
20-27分
④ 運用ルールの説明
§4の打刻タイミング・打刻漏れ時の対応(修正申請)を説明
27-30分
⑤ 質疑応答
§5のよくある質問を先回りで案内し、その場の疑問を解消
💡 当日が難しいスタッフへ:説明会に出られない人向けに、§3の「スタッフ向け操作手順」を1枚にまとめて配布・LINE送信しておくと安心です。
📱
3. スタッフ向け操作手順(配布用)Manual
🏠 あおば不動産販売 勤怠●●●
🔄 出退勤
✅ 出勤打刻が完了しました! ⏰ 出勤:09:00
朝:出退勤を1タップ
🏠 あおば不動産販売 勤怠●●●
📝 日報入力
内容を選んでください 🤝打ち合わせ / 🏆成約 / 📋報告
🤝 打ち合わせ
✅ 記録しました!
日中:その都度 日報入力
🏠 あおば不動産販売 勤怠●●●
🔄 出退勤
退勤しますか? ⏰18:30
✅ 退勤する
お疲れ様でした🌙 本日の記録をまとめました
夕方:出退勤で退勤
1日の使い方(かんたん3ステップ)
タイミングやること
出社したら「🔄 出退勤」をタップ(出勤打刻)
休憩のとき「☕ 休憩」をタップ(戻ったらもう一度)
打ち合わせ・成約のあと「📝 日報入力」から種別を選んで入力
帰るとき「🔄 出退勤」をタップ→「退勤する」
打刻を忘れたとき「⚠️ 修正申請」から正しい時刻を申請
📌 「文字入力が苦手」という人も、出退勤・休憩はボタンを押すだけです。まずはそこだけ覚えてもらえば大丈夫です。
📜
4. 運用ルールの取り決めRules

運用前に、会社として以下のルールを決めて全員に共有します。曖昧だと打刻のばらつきや労務トラブルの原因になります。

項目決めること(例)
打刻のタイミング出勤=業務開始時/退勤=業務終了時。移動中の打刻可否も明確に
日報の提出期限当日の退勤打刻まで。打ち合わせ等はその都度入力を推奨
打刻漏れの対応当日中は本人修正可、翌日以降は修正申請+管理者承認
修正申請の承認者誰が承認するか(管理者・店長など)を決める
有給申請の締切取得希望日の何日前までに申請するか
直行直帰の扱い現地で出勤打刻してよいか等を明確に
歩合給の計算期間成約をどの月の歩合に計上するか(契約日/決済日など)を1つに決める
保障給の水準成約がない月の保障給を定め、最低賃金を下回らないか毎月検算する
データの確認頻度管理者が日次・月次でいつ確認するか
記録の保存期間出勤簿・賃金台帳などは5年(当分の間3年)の保存義務があります(労働基準法109条)。連携シートと台帳を年度ごとに保管し、途中で削除しない運用に
退職者のデータ退職後もタグ・友だちを即削除せず、保存期間を満たすまでシート側の記録を残す
⚠️ 労働時間の記録は労務管理に直結します。打刻ルール・修正ルールは就業規則や労使の取り決めと整合させ、必要に応じて社労士へ確認してください。
💡 「営業職だから事業場外みなし」に注意:外回りの営業職に事業場外みなし労働時間制を適用しているケースがありますが、これは労働時間を算定し難いときに限られます。本システムのようにスマホで随時、出退勤・休憩・訪問先を把握できる状態では「算定し難い」とは言いにくくなります。導入を機に、みなし制の適用可否と就業規則の記載を社労士と点検しておくと安全です。
❓
5. スタッフからのよくある質問(FAQ)FAQ
Q出勤打刻を忘れたらどうすればいい?
A「⚠️ 修正申請」から正しい時刻を申請してください。当日中なら本人修正、翌日以降は管理者の承認で反映されます。
Q圏外・電波が悪いところでも打刻できる?
A電波が戻ってから打刻し、実際の時刻とズレた場合は修正申請で訂正できます。
Q間違えて退勤を押してしまった
Aそのまま仕事を続け、本当に帰るときにもう一度「退勤」を送ってください。集計はその日の最後の退勤を採用するので、先に送った分は無視されます。翌日以降に気づいた場合は修正申請で訂正できます。
Q日報の打ち合わせは毎回入力しないとダメ?
Aその都度の入力を推奨しますが、まとめて退勤前に入力してもOKです。当日中であれば追記できます。
Q残業は自分で計算しないといけない?
Aいいえ。1か月単位変形労働時間制に基づき、月の実働が法定総枠を超えた分をシステムが自動計算します。
Q歩合給があっても残業代は出るの?
Aはい。歩合給があっても、時間外労働をすれば割増賃金は別途支払われます。歩合給の部分は「その月の歩合給÷その月の総労働時間」で時間単価を出し、残業時間に対して0.25倍を上乗せして計算します。打刻が正確でないと、この計算自体ができません。
Q直行直帰の日はどこで打刻する?
A会社の取り決めによりますが、本設計では現地に到着して業務を開始した時点で「🔄 出退勤」をタップする運用を想定しています。移動時間の扱いは§4の運用ルールで会社として決めてください。
Qスマホを変えたら登録し直し?
A同じLINEアカウントを引き継げば、友だち登録もそのまま使えます。新しいLINEにした場合は再度友だち追加してください。
🛟
6. トラブル時の対応(管理者向け)Support
症状確認・対応
打刻しても台帳に記録されない連携シートに行が入っているか(入っていなければフォームのスプレッドシート連携)、入っていればスクリプトプロパティのIDと見出し名(H)、社員マスタの照合キーを確認
残業時間がおかしい変形労働カレンダーの所定・法定休日「○」と、月の暦日数の総枠(177.0/171.0/165.5/160.0h)、週の起算日(WEEK_START)を確認
リマインドが来ない/多すぎる配信時刻とタグ絞り込み条件を確認。無料プランの通数上限も確認
特定スタッフだけ動かない友だち追加・社員マスタの紐付け・属性タグを確認
月次集計が更新されないGASの時間トリガーが有効か、エラーログを確認
📌 原因切り分けの順番:①スタッフ個別の問題か全体か → ②LINE/エルメ側かGAS/シート側か → ③設定値(タグ・総枠・トリガー)の確認。PHASE 1〜4の該当設計図に戻って点検します。
🤝
7. 運用開始後の定着フォローAdoption
時期フォロー内容
最初の1週間毎日、打刻漏れがないか管理者が確認。漏れた人には声かけ+修正申請を案内
最初の1か月システムの集計結果を従来の方法と二重チェックし、計算が合うか確認
1か月後スタッフの声を集め、文面・リマインド頻度・ボタン配置を微調整(PHASE 5のC軽微課題も対応)
3か月後運用が安定。二重チェックを終了し、システムへ完全移行
💡 二重チェックの目的:最初の1か月だけは「システムの数字」と「これまでの方法」を並べて確認することで、安心して給与計算に使える状態にします。問題がなければ二重チェックは卒業です。
📋
8. PHASE 6 完了チェックリスト(本番稼働完了)Checklist
✓
本番シートへの切替が完了し、最終疎通テストOK
✓
テストデータが本番シートから除去されている
✓
全スタッフが友だち追加し、社員マスタに紐付いた
✓
説明会で全員が一度打刻を体験した
✓
運用ルール(打刻・修正・有給)を決めて共有した
✓
スタッフ向け手順・FAQを配布した
✓
定着フォロー計画(1週間・1か月の二重チェック)を立てた
🎉

導入完了!不動産営業職 歩合給対応 勤怠管理システム 本番稼働

PHASE 1〜6を経て、LINEで打刻・日報・有給・修正が完結し、
1か月単位変形労働時間制の残業計算まで自動化されたシステムが本番稼働しました。
あとは日々の運用の中で少しずつ磨き込み、現場にフィットさせていきましょう。