A zero-cost, fully automated overtime approval workflow built on Google Workspace
Language / 语言:
🇺🇸 English · 🇨🇳 中文
This project implements a fully automated, paperless employee overtime approval workflow using only free Google Workspace tools — Google Forms, Google Sheets, and Google Apps Script. No third-party services or paid subscriptions required.
The entire process — from employee submission to manager approval to HR notification — is handled automatically.
- 📝 Online Application — Employees submit requests via Google Form with built-in email validation
- 📧 One-Click Approval — Managers receive a styled HTML email with
Approve/Rejectbuttons — no login required - 🔗 Pre-filled Forms — Approval links auto-populate the Application ID and result; managers only select a compensation type and submit
- 🧮 Smart Hour Calculation — Integrates with a public Chinese public holiday API to determine whether overtime falls on a workday or rest day, and calculates effective overtime hours accordingly
- 📬 Differentiated Notifications
- Approved: Auto-CC to HR leader + optional additional recipients; applicant notified
- Rejected: Only the applicant is notified — HR is not disturbed
- 🗄️ Auto-Archiving — All data written to Google Sheets in real time for audit and analytics
- 🔐 Duplicate Prevention — Status lock ensures each request is only processed once
Employee Manager HR / Applicant
│ │ │
│ Submit Google Form #1 │ │
├──────────────────────► │ │
│ │ ◄── Approval Email ──────────┤
│ │ [Approve] / [Reject] │
│ │ │
│ │ Submit Google Form #2 │
│ ├──────────────────────► │
│ │ │
│ Apps Script processes the submission │
│ │ │
│ ┌────────────┴──────────────┐ │
│ Approved ✅ Rejected ❌ │
│ │ │ │
│ Notify Applicant Notify Applicant │
│ CC: HR Leader (HR not disturbed) │
│ CC: Optional recipients │
└───────────────────────────────────────────────────────► │
Google Sheets auto-archives all data
overtime-approval-system/
│
├── src/
│ └── Code.gs # Google Apps Script — core automation logic
│
├── docs/
│ ├── demo.html # Interactive process demo (open in browser)
│ ├── design-spec.md # Original system design specification
│ └── screenshots/ # System screenshots
│ ├── 员工加班申请表.png
│ ├── 加班批准表格.png
│ ├── 加班通知邮件截图.png
│ ├── 领导审批跳转图.png
│ ├── 领导审批后台记录.png
│ ├── 后台自动记录数据-1.png
│ └── 流程演示-英文版.png
│
├── README.md
├── LICENSE
└── .gitignore
- A Google account (Gmail is sufficient)
- Everything runs inside Google Drive — no installation needed
Create a Google Form with the following fields:
| # | Field | Type | Note |
|---|---|---|---|
| 1 | Applicant Name | Short answer | Required |
| 2 | Department | Dropdown | Required |
| 3 | Overtime Date | Date | Required |
| 4 | Start Time | Time | Required |
| 5 | End Time | Time | Required |
| 6 | Reason | Paragraph | Required |
| 7 | Compensation Type | Multiple choice (Comp-off / Overtime Pay / Other) | Required |
| 8 | Manager Email | Short answer (enable email validation) | Required |
| — | Applicant Email | Collected automatically by Form | Auto |
| # | Field | Type | Note |
|---|---|---|---|
| 1 | Application ID | Short answer | Auto-filled by link |
| 2 | Reviewer Name | Short answer | Optional |
| 3 | Decision | Multiple choice (Approved / Rejected) | Auto-filled by link |
| 4 | Compensation Type | Multiple choice | Required when approving |
| 5 | Additional CC Emails | Short answer | Optional, comma-separated |
⚙️ Important: Enable Collect email addresses in Form #2 settings — the script uses this to identify the approver.
- Open the Google Sheets linked to Form #1 responses
- Go to Extensions → Apps Script
- Paste the full contents of
src/Code.gs, replacing all existing code - Update the configuration section at the top:
const HR_LEADER_EMAIL = 'hr-leader@yourcompany.com'; // ‼️ Required
const SHEET_NAME = 'Form Responses 1'; // Default, usually unchanged
const APPROVAL_SHEET_NAME = 'Form Responses 2'; // Default, usually unchanged
const APPROVAL_FORM_ID = 'YOUR_FORM_2_ID'; // From Form #2's URL
const ENTRY_ID_APP_ID = 'entry.XXXXXXXXXX'; // Entry ID for "Application ID"
const ENTRY_ID_RESULT = 'entry.XXXXXXXXXX'; // Entry ID for "Decision"
const ENTRY_ID_COMPENSATION = 'entry.XXXXXXXXXX'; // Entry ID for "Compensation Type"
const ENTRY_ID_CC = 'entry.XXXXXXXXXX'; // Entry ID for "Additional CC"📌 How to find Entry IDs: Open Form #2's preview, right-click → Inspect Element, and find the
nameattribute on each input field (e.g.entry.1234567890).
In Apps Script, click ⏰ Triggers and add:
| Function | Event Source | Event Type |
|---|---|---|
onFormSubmit |
From spreadsheet | On form submit |
processApprovalSubmission |
From spreadsheet | On form submit |
⚠️ Both triggers are bound to the same Sheets file. The script automatically distinguishes which form triggered it via the sheet name.
- Submit a test request via Form #1
- Check the manager's inbox for the approval email with
Approve/Rejectbuttons - Click
Approve— verify Form #2 opens with the Application ID and decision pre-filled - Submit Form #2 and confirm the applicant receives a result notification and Sheets is updated
| Employee Form | Approval Email |
|---|---|
![]() |
![]() |
| Manager Review Form | Data Archive |
|---|---|
![]() |
![]() |
| Function | Description |
|---|---|
onFormSubmit(e) |
Trigger 1: Fires when employee submits. Generates Application ID, calculates overtime hours, sends approval email to manager |
processApprovalSubmission(e) |
Trigger 2: Fires when manager submits review. Updates status in Sheets, sends final notifications |
sendFinalNotification(row, sheet) |
Sends result email to applicant; CCs HR and optional recipients if approved |
getDayType(dateObject) |
Calls timor.tech holiday API to classify the overtime date (workday / rest day / public holiday) |
formatNameFromEmail(email) |
Helper: parses and formats a display name from an email address |
Q: Why use Form #2 instead of a custom web app for approval?
A: Google Forms opens directly from any email client with no login friction. Pre-filling the URL parameters makes the experience nearly "one-click" for managers, minimizing adoption barriers.
Q: How is duplicate approval prevented?
A: processApprovalSubmission checks whether the request's status is still Pending approval before doing anything. If it has already been processed, the function returns immediately.
Q: What if the holiday API is unavailable?
A: getDayType includes a full fallback — if the API call fails, it defaults to "is it a weekend?" to ensure the system never breaks.
本项目利用完全免费的 Google Workspace 工具——Google Forms、Google Sheets 和 Google Apps Script——实现了一套员工加班自动化审批闭环。无需购买任何第三方软件,无需搭建服务器。
从员工提交申请,到主管邮件审批,再到结果自动通知,全程零人工干预。
- 📝 在线申请 — 员工通过 Google 表单填写,表单自动验证邮箱格式
- 📧 一键审批 — 主管收到精美 HTML 邮件,点击「批准」或「拒绝」按钮即完成操作,无需登录任何系统
- 🔗 预填表单 — 审批链接自动预填申请 ID 和审批结果,主管只需选择补偿方式并提交
- 🧮 智能计算 — 自动调用国内节假日 API,判断加班日期类型(工作日/调休日/法定假日),精确计算有效加班时长
- 📬 差异化通知
- 批准:自动抄送 HR 领导,支持主管额外抄送他人;同时通知申请人
- 拒绝:仅通知申请人,不打扰 HR
- 🗄️ 数据自动归档 — 所有申请及审批信息实时写入 Google Sheets,支持追溯与统计
- 🔐 防重复处理 — 状态锁机制确保同一申请不会被重复审批
员工 直属主管 HR / 申请人
│ │ │
│ 提交 Google 表单 #1 │ │
├───────────────────────► │ │
│ │ ◄── 收到审批邮件 ─────────── │
│ │ [批准(Approve)] [拒绝(Reject)]
│ │ │
│ │ 提交 Google 表单 #2 │
│ ├───────────────────────► │
│ │ │
│ Apps Script 自动处理审批结果 │
│ │ │
│ ┌─────────────┴────────────┐ │
│ 批准 ✅ 拒绝 ❌ │
│ │ │ │
│ 通知申请人 通知申请人 │
│ 抄送 HR 领导 (HR 不收到通知) │
│ 可选抄送其他人 │
└──────────────────────────────────────────────────────────►│
Google Sheets 自动归档所有数据
overtime-approval-system/
│
├── src/
│ └── Code.gs # Apps Script 核心代码
│
├── docs/
│ ├── demo.html # 交互式流程演示页(可直接在浏览器打开)
│ ├── design-spec.md # 系统设计说明文档
│ └── screenshots/ # 系统截图
│
├── README.md
├── LICENSE
└── .gitignore
- 拥有 Google 账号(Gmail 即可)
- 所有操作在 Google Drive 内完成,无需安装任何软件
| # | 字段名称 | 类型 | 说明 |
|---|---|---|---|
| 1 | 申请人姓名 | 简短回答 | 必填 |
| 2 | 部门 | 下拉菜单 | 必填 |
| 3 | 加班日期 | 日期 | 必填 |
| 4 | 加班开始时间 | 时间 | 必填 |
| 5 | 加班结束时间 | 时间 | 必填 |
| 6 | 加班原因 | 段落 | 必填 |
| 7 | 加班方式 | 单选(调休 / 加班费 / 其他) | 必填 |
| 8 | 直属主管邮箱 | 简短回答(需开启邮箱格式验证) | 必填 |
| — | 申请人邮箱 | 表单自动收集 | 自动 |
| # | 字段名称 | 类型 | 说明 |
|---|---|---|---|
| 1 | 申请 ID | 简短回答 | 由链接自动预填 |
| 2 | 审批人姓名 | 简短回答 | 可选 |
| 3 | 审批结果 | 单选(Approved / Rejected) | 由链接自动预填 |
| 4 | 补偿方式 | 单选(调休 / 加班费 / 其他) | 批准时必选 |
| 5 | 额外抄送邮箱 | 简短回答 | 可选,多个地址用逗号分隔 |
⚙️ 重要:表单 #2 的设置中需开启「收集电子邮件地址」,脚本依此识别审批人身份。
- 打开与表单 #1 关联的 Google Sheets 回复表格
- 点击菜单 扩展程序 → Apps Script
- 将
src/Code.gs的全部内容粘贴进编辑器,替换原有代码 - 修改顶部「用户配置区」:
const HR_LEADER_EMAIL = 'hr领导@公司邮箱.com'; // ‼️ 必须修改
const SHEET_NAME = 'Form Responses 1'; // 默认通常不变
const APPROVAL_SHEET_NAME = 'Form Responses 2'; // 默认通常不变
const APPROVAL_FORM_ID = '表单#2的ID'; // 从表单 #2 的 URL 中获取
const ENTRY_ID_APP_ID = 'entry.XXXXXXXXXX'; // 「申请ID」字段的 Entry ID
const ENTRY_ID_RESULT = 'entry.XXXXXXXXXX'; // 「审批结果」字段的 Entry ID
const ENTRY_ID_COMPENSATION = 'entry.XXXXXXXXXX'; // 「补偿方式」字段的 Entry ID
const ENTRY_ID_CC = 'entry.XXXXXXXXXX'; // 「额外抄送」字段的 Entry ID📌 如何获取 Entry ID? 打开表单 #2 的预览页,右键检查元素,找到各输入框的
name属性,格式如entry.1234567890。
在 Apps Script 编辑器中点击左侧「⏰ 触发器」,添加以下两项:
| 函数名 | 事件来源 | 事件类型 |
|---|---|---|
onFormSubmit |
来自试算表 | 提交表单时 |
processApprovalSubmission |
来自试算表 | 提交表单时 |
⚠️ 两个触发器均绑定在同一张 Sheets 文件上。脚本会通过工作表名称自动区分来源,无需担心冲突。
- 以员工身份打开表单 #1,提交一条测试申请
- 检查主管邮箱是否收到带有「批准/拒绝」按钮的审批邮件
- 点击「批准」,确认表单 #2 已自动预填申请 ID 和审批结果
- 提交表单 #2,确认申请人收到结果邮件,Sheets 中的状态已更新
| 员工申请表 | 主管审批邮件 |
|---|---|
![]() |
![]() |
| 主管审批跳转表单 | 后台数据归档 |
|---|---|
![]() |
![]() |
| 函数名 | 说明 |
|---|---|
onFormSubmit(e) |
触发器 1:员工提交后运行。生成申请 ID,计算加班时长,向主管发送审批邮件 |
processApprovalSubmission(e) |
触发器 2:主管提交审批后运行。更新 Sheets 状态,调用通知函数 |
sendFinalNotification(row, sheet) |
向申请人发送最终结果邮件;批准时自动抄送 HR 和其他指定人员 |
getDayType(dateObject) |
调用 timor.tech 节假日 API 判断日期类型,精确计算有效加班时长 |
formatNameFromEmail(email) |
辅助函数:从邮箱地址自动解析并格式化显示名称 |
Q:为什么不用自定义 Web App,而是用表单 #2 来做审批操作?
A:Google Forms 可以从任何邮件客户端直接打开,无需任何登录操作,兼容性最佳。通过 URL 预填参数,让主管的体验接近"一键审批",大幅降低使用门槛和推广难度。
Q:怎么防止主管重复审批?
A:processApprovalSubmission 在执行前会检查 Sheets 中该申请的状态字段。若状态不为 Pending approval,则立即返回,不做任何操作。
Q:节假日 API 不可用时怎么办?
A:getDayType 函数包含完整的兜底逻辑——API 请求失败时,自动降级为「判断是否周末」,确保系统在任何情况下都不会中断。
Made with ☕ using Google Workspace · MIT License



