时间追踪与账单自动化
中级
这是一个Document Extraction领域的自动化工作流,包含 15 个节点。主要使用 Code, Jira, Gmail, Merge, ManualTrigger 等节点。 使用Jira和Gmail自动生成开发者发票和合规提醒
前置要求
- •Google 账号和 Gmail API 凭证
分类
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
"id": "VyFk3RnfdN7deOBG",
"meta": {
"instanceId": "8443f10082278c46aa5cf3acf8ff0f70061a2c58bce76efac814b16290845177",
"templateCredsSetupCompleted": true
},
"name": "时间追踪与账单自动化:",
"tags": [],
"nodes": [
{
"id": "e62e8f45-3b5b-4a1d-a120-b0c07ebcc178",
"name": "当点击\"执行工作流\"时",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-128,
0
],
"parameters": {},
"typeVersion": 1
},
{
"id": "fadb2658-c02d-4819-b6eb-12114cc58127",
"name": "便签",
"type": "n8n-nodes-base.stickyNote",
"position": [
1120,
-352
],
"parameters": {
"width": 256,
"height": 336,
"content": "合并提醒与发票数据流"
},
"typeVersion": 1
},
{
"id": "4b5a9488-f7cd-436b-afaf-f698c756d933",
"name": "便签1",
"type": "n8n-nodes-base.stickyNote",
"position": [
736,
-368
],
"parameters": {
"width": 288,
"height": 336,
"content": "识别缺失时间日志的问题"
},
"typeVersion": 1
},
{
"id": "174d0eec-6cce-4d0c-8b38-045b67f12f76",
"name": "便签2",
"type": "n8n-nodes-base.stickyNote",
"position": [
368,
-368
],
"parameters": {
"width": 288,
"height": 336,
"content": "按团队成员汇总工时"
},
"typeVersion": 1
},
{
"id": "13457e31-0e46-48e0-88a1-91610a40b646",
"name": "便签3",
"type": "n8n-nodes-base.stickyNote",
"position": [
80,
176
],
"parameters": {
"width": 304,
"height": 368,
"content": "获取所有带时间数据的项目问题"
},
"typeVersion": 1
},
{
"id": "edcde860-f8c8-4229-be41-c4c026be9a46",
"name": "便签4",
"type": "n8n-nodes-base.stickyNote",
"position": [
720,
560
],
"parameters": {
"width": 304,
"height": 400,
"content": "生成带文本附件的发票摘要"
},
"typeVersion": 1
},
{
"id": "d2450d54-f3e7-4732-bccd-2bc0e29b8238",
"name": "便签5",
"type": "n8n-nodes-base.stickyNote",
"position": [
1760,
208
],
"parameters": {
"width": 272,
"height": 384,
"content": "向团队发送发票和提醒"
},
"typeVersion": 1
},
{
"id": "559bf371-f7b3-477f-82d9-a022ed54856f",
"name": "便签 6",
"type": "n8n-nodes-base.stickyNote",
"position": [
1424,
-384
],
"parameters": {
"width": 288,
"height": 368,
"content": "协调JSON和二进制附件"
},
"typeVersion": 1
},
{
"id": "0da5f2fa-32f6-4c7e-85a0-474bf3550f4e",
"name": "获取所有带时间数据的项目问题",
"type": "n8n-nodes-base.jira",
"position": [
176,
0
],
"parameters": {
"options": {},
"operation": "getAll",
"returnAll": true
},
"credentials": {
"jiraSoftwareCloudApi": {
"id": "199LdjjU3PhhL8xb",
"name": "saurabh jira"
}
},
"typeVersion": 1
},
{
"id": "76eca7bd-8b7d-4119-a18f-81ae5c142653",
"name": "按团队成员汇总工时",
"type": "n8n-nodes-base.code",
"position": [
448,
0
],
"parameters": {
"jsCode": "// Get all Jira issues from input\nconst items = $input.all().map(i => i.json);\n\n// Initialize grouped output\nconst grouped = {};\n\n// Iterate over each issue\nfor (const issue of items) {\n const f = issue.fields || {};\n\n const assigneeName = f.assignee?.displayName || 'Unassigned';\n const email = f.assignee?.emailAddress || 'unknown@domain.com';\n const projectName = f.project?.name || 'Unknown Project';\n const sprint = f.customfield_10020?.[0]?.name || 'No Sprint';\n const timeSpentSeconds = f.timespent || 0;\n const timeSpentHours = (timeSpentSeconds / 3600).toFixed(2);\n\n if (!grouped[assigneeName]) {\n grouped[assigneeName] = {\n assignee: assigneeName,\n email,\n project_name: projectName,\n total_hours: 0,\n issues: []\n };\n }\n\n grouped[assigneeName].total_hours += parseFloat(timeSpentHours);\n grouped[assigneeName].issues.push({\n issue_key: issue.key,\n summary: f.summary,\n timespent_hours: timeSpentHours,\n status: f.status?.name || 'Unknown',\n priority: f.priority?.name || 'None',\n sprint,\n });\n}\n\n// Convert grouped object into array format\nconst output = Object.values(grouped).map(user => ({\n json: {\n assignee: user.assignee,\n email: user.email,\n project_name: user.project_name,\n total_hours: user.total_hours.toFixed(2),\n issues: user.issues,\n }\n}));\n\nreturn output;\n"
},
"typeVersion": 2,
"alwaysOutputData": false
},
{
"id": "3ef99004-e0bd-4346-8ac0-6e83a88260f0",
"name": "识别缺失时间日志的问题",
"type": "n8n-nodes-base.code",
"position": [
832,
0
],
"parameters": {
"jsCode": "const users = $input.all().map(i => i.json);\n\nconst reminders = [];\n\nfor (const user of users) {\n const missing = user.issues.filter(i => parseFloat(i.timespent_hours) === 0);\n\n if (missing.length > 0) {\n const table = missing.map(i => \n `<tr>\n <td>${i.issue_key}</td>\n <td>${i.summary}</td>\n <td>${i.status}</td>\n <td>${i.priority}</td>\n <td>${i.sprint}</td>\n </tr>`\n ).join('');\n\n reminders.push({\n json: {\n assignee: user.assignee,\n email: user.email,\n missing_count: missing.length,\n project_name: user.project_name,\n html_message: `\n <p>Hi ${user.assignee},</p>\n <p>Our system found ${missing.length} issues where no time is logged:</p>\n <table border=\"1\" cellspacing=\"0\" cellpadding=\"5\">\n <tr><th>Issue Key</th><th>Summary</th><th>Status</th><th>Priority</th><th>Sprint</th></tr>\n ${table}\n </table>\n <p>Please log your hours for these issues before EOD to ensure accurate billing.</p>\n <p>Thank you,<br>Automation Bot 🤖</p>\n `\n }\n });\n }\n}\n\nreturn reminders;\n"
},
"typeVersion": 2
},
{
"id": "db74c4fe-3bf1-45f9-a971-64ffc74695df",
"name": "生成带文本附件的发票摘要",
"type": "n8n-nodes-base.code",
"position": [
832,
384
],
"parameters": {
"jsCode": "// Combined Invoice Generator + Text File Export\n\nconst users = $input.all().map(i => i.json);\nconst RATE = 50; // hourly rate\nconst results = [];\n\nfor (const user of users) {\n const totalHours = parseFloat(user.total_hours || 0);\n if (totalHours === 0) continue;\n\n const totalAmount = totalHours * RATE;\n\n // Build the plain text table\n const issueLines = user.issues.map(i =>\n `${i.issue_key.padEnd(10)} | ${i.summary.padEnd(30)} | ${i.timespent_hours} hrs | ${i.status}`\n ).join('\\n');\n\n // Final plain-text invoice content\n const text = `\n===========================================\nINVOICE SUMMARY — ${user.project_name}\n===========================================\n\nAssignee: ${user.assignee}\nEmail: ${user.email}\nRate: $${RATE}/hour\n\n-------------------------------------------\nIssues\n-------------------------------------------\n${issueLines}\n\n-------------------------------------------\nTotal Hours: ${totalHours.toFixed(2)}\nTotal Amount: $${totalAmount.toFixed(2)}\n-------------------------------------------\n\nGenerated automatically by Techdome Billing Automation Bot.\n`;\n\n // Push JSON + Binary for email attachment\n results.push({\n json: {\n assignee: user.assignee,\n email: user.email,\n project_name: user.project_name,\n total_hours: totalHours.toFixed(2),\n total_amount: totalAmount.toFixed(2)\n },\n binary: {\n data: {\n fileName: `Invoice_${user.assignee}.txt`,\n mimeType: 'text/plain',\n data: Buffer.from(text, 'utf8').toString('base64')\n }\n }\n });\n}\n\nreturn results;\n"
},
"typeVersion": 2
},
{
"id": "064ef92a-f13c-4926-8526-a1eb398fa3db",
"name": "合并提醒与发票数据流",
"type": "n8n-nodes-base.merge",
"position": [
1216,
16
],
"parameters": {},
"typeVersion": 3.2
},
{
"id": "d3aec18e-05de-4ca9-8b19-263695b105b7",
"name": "协调JSON和二进制附件",
"type": "n8n-nodes-base.code",
"position": [
1488,
16
],
"parameters": {
"jsCode": "// Combine JSON + Binary after Merge node\n// Safe for n8n v1.112.6 and later\n\nconst combined = [];\n\n// Get all incoming items\nconst items = $input.all();\n\n// We might have duplicates or partials, so we merge smartly\nfor (const item of items) {\n const json = item.json || {};\n const binary = item.binary || {};\n\n // Case 1: this item already has both\n if (json.email && binary.data) {\n combined.push({ json, binary });\n continue;\n }\n\n // Case 2: only has JSON\n if (json.email && !binary.data) {\n // Try to find its binary partner\n const match = items.find(\n (i) => i.binary && i.binary.data && i.json?.assignee === json.assignee\n );\n\n if (match) {\n combined.push({ json, binary: match.binary });\n } else {\n combined.push({ json }); // fallback\n }\n continue;\n }\n\n // Case 3: only has Binary\n if (binary.data && !json.email) {\n // Try to find its JSON partner\n const match = items.find(\n (i) => i.json && i.json.assignee && i.binary === undefined\n );\n if (match) {\n combined.push({ json: match.json, binary });\n } else {\n combined.push({ binary });\n }\n continue;\n }\n}\n\nreturn combined;\n"
},
"typeVersion": 2
},
{
"id": "94b59a38-4e45-47dc-8c3d-d133ddd07455",
"name": "向团队发送发票和提醒",
"type": "n8n-nodes-base.gmail",
"position": [
1808,
16
],
"webhookId": "0c82c299-6938-42ed-bda6-5007d79af34f",
"parameters": {
"sendTo": "={{ $json.email }}",
"message": "=Hi {{$json[\"assignee\"]}},<br><br>\nPlease find attached your invoice summary for <strong>{{$json[\"project_name\"]}}</strong>.<br><br>\n📊 <b>Total Hours:</b> {{ $('Aggregate Hours by Team Member').item.json.total_hours }}<br>\nRegards,<br>\nTechdome Billing Automation Bot\n",
"options": {
"attachmentsUi": {
"attachmentsBinary": [
{}
]
}
},
"subject": "={{ $json.project_name }}"
},
"credentials": {
"gmailOAuth2": {
"id": "RchiXdmY8WaQhOSJ",
"name": "Gmail account"
}
},
"typeVersion": 2.1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "c7c9ac45-c34e-4b9e-a15d-0aea4da1f6d6",
"connections": {
"Aggregate Hours by Team Member": {
"main": [
[
{
"node": "Identify Issues with Missing Time Logs",
"type": "main",
"index": 0
},
{
"node": "Generate Invoice Summary with Text Attachment",
"type": "main",
"index": 0
}
]
]
},
"Send Invoices & Reminders to Team": {
"main": [
[]
]
},
"Reconcile JSON & Binary Attachments": {
"main": [
[
{
"node": "Send Invoices & Reminders to Team",
"type": "main",
"index": 0
}
]
]
},
"When clicking ‘Execute workflow’": {
"main": [
[
{
"node": " Fetch All Project Issues with Time Data",
"type": "main",
"index": 0
}
]
]
},
"Identify Issues with Missing Time Logs": {
"main": [
[
{
"node": "Combine Reminder & Invoice Data Streams",
"type": "main",
"index": 0
}
]
]
},
"Combine Reminder & Invoice Data Streams": {
"main": [
[
{
"node": "Reconcile JSON & Binary Attachments",
"type": "main",
"index": 0
}
]
]
},
" Fetch All Project Issues with Time Data": {
"main": [
[
{
"node": "Aggregate Hours by Team Member",
"type": "main",
"index": 0
}
]
]
},
"Generate Invoice Summary with Text Attachment": {
"main": [
[
{
"node": "Combine Reminder & Invoice Data Streams",
"type": "main",
"index": 1
}
]
]
}
}
}常见问题
如何使用这个工作流?
复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。
这个工作流适合什么场景?
中级 - 文档提取
需要付费吗?
本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。
相关工作流推荐
API速率限制与认证FAQ测试
使用GPT-4o-mini、Google表格和Slack提醒自动化API常见问题质量测试
If
Set
Code
+7
19 节点Rahul Joshi
文档提取
从Monday.com和Jira到Outlook的AI驱动反馈分类与报告
使用Azure GPT-4、Jira任务和Outlook报告分析来自Monday.com的客户反馈
Set
Code
Jira
+12
27 节点Rahul Joshi
退信与无效检测 (Gmail 触发器)
使用 Gmail、Google Sheets 和 Slack 自动化邮件退信与无效检测
Code
Cron
Gmail
+6
26 节点Rahul Joshi
新开发人员入职自动化
使用 GPT-4o 实现员工入职自动化:Jira、Notion 和 Gmail 集成
If
Set
Code
+9
21 节点Rahul Joshi
人力资源
事件管理工作流
通过Jira、Slack、Google Sheets和Drive自动化事件响应
If
Set
Code
+8
23 节点Rahul Joshi
开发运维
## 仅限自托管N8N用户:
使用GPT-4o-mini、Google Sheets和Gmail自动化Zendesk支持回复
Code
Gmail
Merge
+6
24 节点Rahul Joshi
内容创作
工作流信息
难度等级
中级
节点数量15
分类1
节点类型6
作者
Rahul Joshi
@rahul08Rahul Joshi is a seasoned technology leader specializing in the n8n automation tool and AI-driven workflow automation. With deep expertise in building open-source workflow automation and self-hosted automation platforms, he helps organizations eliminate manual processes through intelligent n8n ai agent automation solutions.
外部链接
在 n8n.io 查看 →
分享此工作流