从时间追踪到发票应用:从记录到品牌化 PDF
构建一个时间追踪到发票的应用:捕捉项目工时、将其转为发票并为客户生成带品牌信息的 PDF 文件。

你将构建的内容以及为什么重要
一个时间追踪到发票的应用能解决一个常见的混乱:工时散落在日历、聊天和笔记里。到了开票日,必须有人手工把整个月份重建出来。错误就会在这时候出现:漏计可收费时间、费率错误、重复行或总额不一致。
这个应用适用于按小时计费并希望有可重复流程的任何人:在多个客户之间奔波的自由职业者、多人向同一项目记工时的代理机构,以及需要向客户或部门回拨时间的内部团队。
目标很实用:按项目采集时间条目,将它们汇总为发票记录,并生成客户能理解的品牌 PDF。当这个工作流可靠后,开票就不再是每月的混乱。
坚持“简单优先”通常意味着:
- 一种记录时间的方式(日期、项目、小时、备注)
- 一条费率规则(按项目或按人)
- 每个客户每个期间一张发票
- 一个带你徽标与公司信息的 PDF 布局
- 清晰的状态(草稿、已发送、已付款)
一个小场景:一个两人工作室为“客户 A - 网站更新”跟踪时间。每个人在一周内记录条目。周五,你为该项目和日期范围创建发票,应用把条目变成发票行,PDF 可直接发送,无需重新输入任何内容。
如果你使用像 AppMaster 这样的无代码平台,先把数据和工作流做对,然后再添加收据、多币种、折扣或审批等附加功能。一旦核心流程既快速又稳健,这些扩展更容易实现。
核心功能(以及先不要做的部分)
一个小而实用的首版能更快达到“可发送发票”。把注意力放在三件事:采集时间、把时间变成清晰的发票行、并生成客户能一眼看懂的 PDF。
从几个核心记录开始(以后可以重命名,但结构很重要):客户(Client)、项目(Project)、时间条目(Time Entry)、发票(Invoice) 和 发票行(Invoice Line)。
用单一状态字段保持发票工作流简单。对于大多数团队,草稿、就绪和已付款能长期满足需求。
你的必备操作应匹配每周实际发生的工作:
- 记录时间(手动录入通常是最快且最容易纠正的)
- 批准时间(即便只是一个“已批准”状态)
- 从已批准的时间创建发票
- 导出 PDF
“品牌化”并不意味着花哨,而是要一致且让人信任:徽标、公司信息、发票号码与日期、清晰的合计以及付款说明。
首次发布时可以先不做的内容:税费、折扣、多币种和附件。它们有用,但会引入边缘情况(四舍五入、司法辖区规则、汇率、文件存储),会拖慢首发速度。
数据模型:所需记录与关键字段
时间追踪到发票的应用成败取决于数据模型。保持精简且可预测,这样总额才总是与承诺一致。
一个最小表集合通常如下:
- 客户(Client):名称、账单邮箱、账单地址、默认货币、付款条款(如 Net 14)
- 项目(Project):client_id、项目名称、默认小时费率(可选)、启用标识
- 时间条目(Time entry):project_id、人员(姓名或 user_id)、日期、时长(小时)、描述、rate_at_time、是否计费(是/否)、invoiced_invoice_id(开票前为空)
- 发票(Invoice):client_id、project_id(可选)、发票号、开具日期、到期日、状态、小计、税额、总额
费率是容易出问题的地方。选一种方法并坚持:按项目费率、按人员费率或按任务/服务固定费率。
即便你在项目或人员上保存了默认费率,也要在创建(或批准)时间条目时把实际费率复制到每条时间记录的 rate_at_time。这能防止费率日后变更带来意外。发票应反映工作发生时的事实。
对于时间条目,你通常可以跳过单独的状态字段,依赖于 invoiced_invoice_id 是否为空来判断是否已开票。发票状态则保持紧凑:Draft(草稿)、Ready(就绪)、Sent(已发送)、Paid(已付款)(如果需要干净的取消状态,可加 Void)。
在 AppMaster 中,数据设计器(Data Designer)能很好地映射到 PostgreSQL,并且更容易保持关系清晰而不在各处重复字段。
按项目采集时间条目(简单的用户体验)
时间采集决定了应用是顺手好用还是被忽略。把首个版本做得平淡而快速:一屏、一个主要动作、尽量少的选择。
选择一种采集方式开始。手动录入通常早期胜出,因为它适用于所有人且易于审核。计时器可以稍后添加,但即使加了计时器,也应允许手动编辑以弥补漏停情况。
把能保障计费质量的字段设为必填:
- 项目(或 客户 + 项目)
- 日期
- 时长(小时和分钟)
- 简短描述(客户能识别的内容)
- 人员(如果多人记录时间)
尽早决定舍入规则,因为它影响信任和总额。常见方法是以 6 分钟为增量(0.1 小时)舍入。明确是对每条记录舍入还是对每日合计舍入。逐条舍入更易解释与审计。
如果多人参与计费,添加一个轻量级的审批步骤。实用规则:一旦批准,条目默认锁定不可编辑。如果必须更改,要求经理角色重新打开并记录是谁为什么修改的。
把时间转换为发票行(汇总规则)
汇总是把原始日志变为客户能理解的发票行的过程。保持规则简单且可重复,这样你就能信任每一次生成的发票。
先做一个动作:选择客户和日期范围,然后只拉取未开票的时间条目。这个过滤器是防止重复开票的重要护栏。如果某条记录缺少客户或项目,把它视为“未准备好开票”,并在修正前把它排除在汇总之外。
如何把条目分组为发票行
分组决定了你会生成多少行以及客户查看时是否容易理解。选一个默认,并在需要时提供一个可选开关以增加灵活性。
常见分组选项:
- 按项目分组
- 按人员分组(当费率不同有用)
- 按天或按周分组
- 按任务/分类分组(设计 vs 开发)
无论选择哪种方式,每行应显示:清晰的标签、总小时、费率与行金额。如果费率会变化,请使用保存在每条条目上的 rate_at_time(或带有效期的费率表),而非单一的“当前费率”。
标记为已开票(同时避免把自己困住)
当你把条目加入发票时,请在每条时间条目上保存发票 ID。这会产生审计痕迹并防止同一条目被再次拉取。
修正是会发生的。如果你从发票中移除某一行,不要删除历史。解除与发票的关联(清除 invoice ID),重新计算总额,并保存一条简短说明,例如“移除 2.0 小时,项目错误”。
在 AppMaster 中,这可以作为一个单一业务流程实现:查询未开票条目、分组、创建发票行,然后更新每条条目的发票引用。
发票记录:总额、编号与状态
发票记录是你之后可以发送、跟踪和审计的容器。它应保持稳定,即便有人编辑了项目名称或更改了默认费率也不会影响已保存的发票。
一个实用的发票头部包含:
- 发票号(唯一、便于识别)
- 开具日期与到期日
- 收款方信息(客户名称、账单地址、税号如需)
- 备注(付款说明、简短的感谢语)
- 货币(如果跨国结算,可选保存兑换率)
保持总额可预测。小计是发票行之和,然后应用折扣(固定金额或百分比)、计算税额(通常在折后小计上计算),并保存最终总额。务必保存使用的确切税率与折扣数值,以便以后能复现该发票。
发票编号不必复杂。选一个格式并坚持:顺序号(000123)、按年(2026-00123)或客户前缀加序列(ACME-014)。一致性比完美更重要。
状态应侧重于沟通与内部控制:
- Draft(草稿,可编辑,未发送)
- Ready(就绪,总额锁定)
- Sent(已发送,与客户共享)
- Paid(已确认付款)
- Overdue(逾期)
- Void(作废,保留历史)
生成客户可读的品牌 PDF
一个好的发票 PDF 要迅速回答两个问题:计费的内容是什么,以及如何付款。请从发票记录生成 PDF(而不是从原始时间条目生成),这样文档总是与发票号、总额和状态一致。
大多数客户期望每次都看到相同的模块:
- 页眉:公司名、发票号与开具日期
- 客户信息(公司、联系人、账单地址、税号如需)
- 行项目(描述、数量或小时、费率、行总额)
- 总额(小计、税额、折扣、总计)
- 付款条款(到期日、可接受的付款方式、逾期费说明如有)
品牌化重要,但可读性更重要。保持一种强调色、使用干净字体,并让总额易于扫读。
布局问题会在真实数据下显现。用长描述和 30+ 行条目测试。确保列标题在分页时重复,总额区块不要被拆开。
如果你在 AppMaster 中生成 PDF,把该文件(或存储引用)保存到发票记录上,并带上生成时间戳与版本号。这样便于重新发送客户看到的确切文档。
分步构建计划(无代码工作流)
决定什么是“事实来源”。时间条目是原始事实。发票是一个可以发送和审计的快照。
1)先建数据模型
创建表与关系,然后在基础稳定后再添加一些质量字段:
- Clients
- Projects
- Time Entries
- Invoices
- Invoice Lines
2)构建两个简单界面
保持 UI 最小化:
- 时间条目表单:项目、日期、时长、备注、保存
- 发票审阅:客户、期间、行项、总额、状态
管理与审阅通常一个 Web UI 就足够。若有人需要外出记录时间,再补充移动端界面。
3)自动化汇总逻辑
构建一个流程:选择客户 + 日期范围,获取未开票条目,分组它们,创建发票行。只有在发票处于 Draft 时才把条目标记为已开票,或在移动到 Ready 后统一标记。
4)生成并保存 PDF
添加“生成 PDF”动作,从发票头部、客户信息和行项拉取数据填入模板,然后把输出保存到发票记录上。
示例:从每周工时日志到客户就绪的发票
一个 3 人的代理机构有一个客户 Northstar Co,为两个项目结算两周的工时:Website Refresh 和 Monthly Support。团队成员是 Alex(设计)、Priya(开发)和 Sam(项目经理)。大家每天记录时间,选择客户、项目、日期和简短备注。
每天,这些条目先保存为草稿。周五下午,Sam 打开一个筛选为“本周,Northstar Co”的审阅界面。他修正了两个备注(把“Homepage hero”改为“Hero”),确认了可计费与非计费项,并锁定了这一周的数据。
下面是那周的条目示例:
| 日期 | 人员 | 项目 | 小时 | 备注 |
|---|---|---|---|---|
| 周一 | Priya | Website Refresh | 2.5 | Header layout fixes |
| 周二 | Alex | Website Refresh | 3.0 | New homepage mock |
| 周二 | Sam | Monthly Support | 1.0 | Client call |
| 周三 | Priya | Website Refresh | 4.0 | Contact form logic |
| 周四 | Alex | Monthly Support | 1.5 | Banner update |
| 周四 | Priya | Monthly Support | 2.0 | Email template tweak |
| 周五 | Sam | Website Refresh | 1.0 | QA and handoff |
当 Sam 点击“创建发票”时,应用按简单规则把条目汇总成发票行:按项目和计费费率分组、汇总工时并带上简短描述。发票最终包含 3 行:
| 行号 | 描述 | 数量 | 费率 | 金额 |
|---|---|---|---|---|
| 1 | Website Refresh (Design) | 3.0 hrs | $120 | $360 |
| 2 | Website Refresh (Development/PM) | 7.5 hrs | $140 | $1,050 |
| 3 | Monthly Support | 4.5 hrs | $110 | $495 |
系统分配了发票号(如 NS-2026-014),计算小计与税额,并将状态设为就绪(Ready)。再点击一次即可生成带徽标、客户地址、行明细、总额与付款说明的品牌 PDF。发送后,状态更新为已发送(Sent),底层时间条目被标记为已开票,从而防止重复计费。
常见错误以及如何避免
大多数问题不是算错,而是工作流出问题。
未锁定已开票时间条目。如果人们还能编辑或重新选择同一条目进行新发票,就会出现重复计费。用发票引用字段把发票ID保存在时间条目上,并在“准备开票”视图中隐藏已开票条目。
在费率变更时改写历史。如果只用“当前”项目或用户费率来计算,变更后会影响旧发票。把当时生效的费率保存到每条条目的 rate_at_time,以避免此类问题。
在批准后编辑时间却无审核记录。为批准后编辑添加“批准人”、“批准时间”和编辑说明字段。
PDF 在真实数据下出问题。长描述、许多行以及大数值会考验模板。
快速修复以防止大多数布局问题:
- 设置最大描述长度,把溢出的内容放到备注区
- 允许文本换行并用 30+ 行进行测试
- 保持页眉紧凑以给表格留出空间
- 使用一致的数字格式(货币、小数位)
模糊的状态流会导致发票被重复发送或根本不被发送。
一个简单且安全的流是:Draft -> Ready -> Sent -> Paid。只允许在 Draft 时进行汇总,仅在总额锁定后才允许生成 PDF。
简短检查清单与实用的下一步
在发送发票前做一个快速审查,可以防止最常见的问题:总额错误、漏填细节以及在打印时看起来正常但格式错乱的 PDF。
发送前检查表:
- 客户信息完整(公司法定名称、账单地址、正确联系人)
- 发票期间正确(起止日期与实际工作匹配)
- 总额一致(小计、税额、总额与条目、费率和舍入一致)
- 没有时间被遗漏或重复(没有漏开票、也没有重复纳入)
- 操作字段清晰(唯一发票号、正确状态、PDF 已保存于发票上)
然后用“打印机视角”预览 PDF。检查徽标位置、长地址、表格换行和分页。测试短发票(1–2 行)和长发票(20+ 行)。
当基础稳定后可以考虑:
- 用一致的邮件模板发送发票
- 接入 Stripe 支付并自动把发票标为已付款
- 添加权限控制,仅允许合适角色编辑费率、批准时间或更改状态
如果想快速构建并迭代而不是从头编码,AppMaster(appmaster.io)是一个实际可行的选项,能在无代码环境下创建带真实数据库、业务逻辑和 PDF 生成功能的开票应用,并在需求变化时重新生成干净的源代码。
如果本周只能改进一件事,就让“未开票时间”不可能被忽视。仅此一项即可节省数小时并保护收入。
常见问题
首先确保每条时间记录都有项目、日期、时长和简短描述。然后通过选择客户和日期范围来创建发票,拉取仅未开票的条目,将它们分组为发票行,并从发票快照生成 PDF。
使用五个记录:客户(Client)、项目(Project)、时间条目(Time Entry)、发票(Invoice)和发票行(Invoice Line)。字段保持精简,但每个时间条目应包含 rate_at_time,并有 invoiced_invoice_id 引用,这样账单历史一致且能防止重复开票。
在每条时间条目上保存当时使用的费率(例如 rate_at_time)。默认费率可以放在项目或人员记录上,但计算发票时应始终基于保存的费率,这样当费率日后更改时,旧发票不会被改写。
选择一种舍入规则并坚持执行,然后在流程中显式告知。常见做法是将每条记录按 6 分钟增量(0.1 小时)舍入,因为这种方式易于审计且能保持发票总额可预测。
在发票上使用一个状态字段并保持精简:草稿(Draft)、就绪(Ready)、已发送(Sent)、已付款(Paid)(只有需要时再添加作废 Void)。制定明确规则,例如“仅在草稿状态下允许汇总”以及“就绪后锁定总额”,以免意外修改已发送内容。
在创建发票时只拉取 invoiced_invoice_id 为空的时间条目,并在将条目附加到发票时立即设置该字段。这会生成审计痕迹并防止同一条目再次被选择。还应在“准备开票”视图中隐藏已开票条目,避免重复选择。
从发票记录生成 PDF(而不是直接用原始时间条目),这样文档总是与发票号、总额和状态一致。包含清晰的页眉、客户信息、行项目、总额和付款说明,并用长描述和 30+ 行数据进行测试以发现布局问题。
不要删除历史记录。取消关联受影响的时间条目(清除发票引用),重新生成发票行和总额,并保存一条简短的更正说明,这样以后就能解释更改,同时保留审计轨迹。
先从手动时间录入开始,因为它构建快速且易于修正。计时器会引入额外边缘情况(漏停、编辑、设备问题),因此应在核心流程可靠后再添加。
先建立核心流程:时间录入、审批/锁定、从未开票时间创建发票以及生成 PDF。初期跳过税费、多币种、折扣和附件,因为它们会引入复杂的边缘情况,拖慢进度并增加计算复杂性。


