一条工时数据走完 Gauzy 全链路:从打卡到工资单、发票、报表
一条工时数据走完 Gauzy 全链路从打卡到工资单、发票、报表【免费下载链接】ever-gauzyEver® Gauzy™ - Open Business Management Platform (ERP/CRM/HRM/ATS/PM) - https://gauzy.co项目地址: https://gitcode.com/GitHub_Trending/ev/ever-gauzyEver Gauzy 的开源定位很直白用一套 TypeScript 技术栈Angular NestJS TypeORM/MikroORM PostgreSQL把 ERP、CRM、HRM、ATS 和项目管理装进同一个平台。但一体化最容易沦为口号——真正有说服力的是数据能不能在模块之间无断裂地流动。这篇文章不聊部署、不聊界面只追踪一类数据一条工时记录。从员工点击开始计时那一刻起它会经历哪些实体、哪些状态机、哪些金额计算最终如何变成工资单上的一行、发票上的一个条目和报表里的一张图表。所有结论均来自仓库源码路径可直接定位验证。第一站打卡与工时数据如何被切碎又拼回工时的最小单元不是一条记录而是按分钟甚至秒切分的时间片。在 time-tracking 模块 下time_log是业务入口time_slot是 10 分钟级切片的载体timesheet则是按周期聚合的结算单元。以 TimeLog 实体 为例一次计时会话只记录两个时间锚点MultiORMColumn({ nullable: true }) startedAt?: Date; MultiORMColumn({ nullable: true }) stoppedAt?: Date; /** Additional virtual columns */ VirtualMultiOrmColumn() duration: number; AfterLoad() OnLoad() afterEntityLoad?() { const startedAt moment(this.startedAt, YYYY-MM-DD HH:mm:ss); const stoppedAt moment(this.stoppedAt || new Date(), YYYY-MM-DD HH:mm:ss); this.duration stoppedAt.diff(startedAt, seconds); ... }duration不在数据库落盘而是在实体加载后由startedAt/stoppedAt实时算出计时器还在跑stoppedAt为空时用当前时间兜底。这个设计保证了运行中的计时在每次查询时都是准确的。更关键的是 TimeLog 携带了一整套业务坐标employee、project、task、organizationContact客户、organizationTeam团队以及isBillable是否可计费、logTypeTRACKED / MANUAL 等来源类型和sourceWeb 计时器、桌面端、扩展等。这些外键就是后续一切聚合的指针——一条工时到底算在哪个项目、哪个客户、哪个任务头上在写入瞬间就定死了。原始的秒级切片需要向上聚合。在 Timesheet 实体 中同一员工同一周期内的工时被聚合成一张结算单duration?: number; // 总时长 keyboard?: number; // 键盘活跃 mouse?: number; // 鼠标活跃 overall?: number; // 综合活跃度 status: TimesheetStatus; // 默认 PENDING approvedAt?: Date; submittedAt?: Date; lockedAt?: Date; isBilled?: boolean;Timesheet自带一条审批生命周期PENDING → SUBMITTED → APPROVEDapprovedAt、approvedBy记录了谁在什么时候放行isBilled标记该周期是否已被开票lockedAt则用于冻结已结算周期。而 TimeSheetService 在查询层做了两道约束所有统计强制tenantId organizationId双租户范围过滤同时用CHANGE_SELECTED_EMPLOYEE权限控制能否查看他人工时——没有该权限的用户employeeIds会被强制替换成自己的employeeId。这是多租户 SaaS 的底线设计数据可见性在服务层就收死而不是依赖前端隐藏。第二站从工时到工资单PayrollRun 的状态机与整数分哲学工时被审批通过后就具备了进入工资计算的资格。Gauzy 在 payroll-run 模块 里用一对实体承载工资核算PayrollRun一次发薪周期与PayrollItem工资条上的每一行EARNING 或 DEDUCTION。PayrollRunService 的注释非常直白地写明了设计原则值得原样引用Every lookup is scoped bytenantIdANDorganizationId. A payroll run holds what people are paid; a lookup by id alone would let any authenticated user of any tenant read or move another companys payroll. Totals are never accepted from a caller. They are recomputed from the runs items, in integer cents, at the moment the run is processed. Summing0.1 0.2in binary floating point does not give0.3, and payroll is the last place to discover that.两句话分别对应工资模块最敏感的两个点越权与浮点误差。前者靠所有查询强制tenantId organizationId后者靠recalculateTotals方法——把金额先转成整数分Math.round(Number(item.amount) * 100)再累加绝不在二进制浮点上直接求和let grossCents 0; let deductionCents 0; for (const item of items) { const cents Math.round(Number(item.amount) * 100); if (item.category PayrollItemCategoryEnum.EARNING) { grossCents cents; } else { deductionCents cents; } } run.totalGross grossCents / 100; run.totalDeductions deductionCents / 100; run.totalNet (grossCents - deductionCents) / 100;工资单的状态流转是一个严谨的有限状态机DRAFT → PENDING_APPROVAL → APPROVED → PROCESSING → PAID另有CANCELLED分支。源码刻意回避了读-改-写式的普通更新而是在单条 SQL 中用 WHERE 条件声明式抢锁防止并发导致双重支付const claimed await this.typeOrmRepository.update( { id, tenantId, organizationId, status: In([APPROVED, PROCESSING]) }, { status: PayrollRunStatusEnum.PAID, paidAt: new Date() } ); if (!claimed.affected) { throw new BadRequestException(This payroll run has already been processed); }process()把状态翻转和总额重算放进同一个数据库事务manager.transaction所以永远不会出现标记已发工资但总额还是旧的的中间态。提交审批submitForApproval前还会先重算一次总额——让审批人签字时看到的是最新数字。工资条行的增删也只允许在DRAFT态进行一旦进入审批流程就锁定不可改。值得一提的还有按币种分组的统计getStatistics。跨币种直接求和是无意义数字因此 Gauzy 把已支付PAID的工资单按currency分组分别统计 run 数、实发人数、毛薪、扣款与净额——这是多币种记账在聚合层的正确姿势。财务侧发票、报销与多币种如何承接业务数据工时数据流到财务侧核心枢纽是 Invoice 实体 与 InvoiceItem 实体。InvoiceItem是连接业务与财务的关节它可以分别关联task、project、employee、expense报销/费用和product。这意味着同一张发票的行项可以来自完全不同的业务源头一部分是任务工时折算一部分是差旅报销转开票一部分是商品销售。每条InvoiceItem自带price × quantity totalValue的金额结构以及applyTax、applyDiscount标记最终由Invoice按discountType / taxType / tax2Type / taxCalculationType组合出税额与应收总额。发票的钱袋子属性在编号上体现得淋漓尽致。InvoiceNumberUniquePerTenant装饰器把invoiceNumber的唯一约束限定在(tenantId, invoiceNumber)维度——注释中明确点出这是对安全通告 GHSA-57hw-jqpj-ww97 的修复此前全局唯一序列会让租户通过唯一键冲突错误探测他人编号、甚至用一个超大号把全平台的下一个编号顶走。这类细节说明多租户不只是加一个tenantId字段而是要把每一个聚合、每一条约束都拉到租户维度重新设计。财务侧的第二条腿是报销。看 Expense 实体amount currency valueDate是金额骨架employee / vendor / category / project / organizationContact是业务归属status携带审批状态splitExpense则支持一笔费用由全员分摊。分摊计算在 EmployeeStatisticsService 中实现amount / splitAmongsplitAmong取当月在职员工数——即按月份把组织级费用摊到每个员工的成本头上这直接服务于后续的利润与奖金核算。收款侧由 Payment 实体 承接每条收款记录paymentDate amount currency paymentMethod并通过invoice关联回到发票Invoice侧相应维护alreadyPaid与amountDue已付、待付形成开票 → 收款 → 核销闭环。发票还支持生成 PDFinvoice.service.ts 中的generateInvoicePdf与公共链接token字段 generateLink签发签名令牌让外部客户无需登录即可查看账单。经营报表层报表不是查表而是按租户与组织重新聚合数据走到报表层Gauzy 没有把报表做成对业务表的裸查询而是建了一套独立的报表元数据体系。看 report.seed.ts 的默认报表目录四条分类与业务模块一一对应Time TrackingTime Activity、Weekly、Apps URLs、Manual time edits、ExpensePaymentsAmounts owed、PaymentsTime OffWeekly limits、Daily limitsInvoicingProject budgets、Client budgets以 Time Activity 报表为例它的描述是 See team members time worked, activity levels, and amounts earned per project or task——工时、活跃度、项目/任务维度的应得金额正好是本文追踪的那条工时数据在报表层的三种投影。它的配图也沉淀在仓库中报表的启用与可见性同样由组织级配置驱动。在 ReportService 的findAllReports中report_organization表记录每个组织对每份报表的开关状态查询时用isEnabled / isActive / isArchived三重过滤只有开启且未被归档的报表才进入菜单showInMenu。也就是说报表能力是产品预置的但这个公司能看到哪些报表是运营按租户按组织配出来的——与工时查询的租户隔离一脉相承。报表背后的计算也不只是 SUM。在员工统计服务中一个完整的经营公式被逐月还原profit income - expense bonus profit-based ? profit × pct% : revenue-based ? income × pct%收入income与费用expense先按自然月归桶逐月相减得到利润再按组织配置的bonusType / bonusPercentage计算奖金。这条链路的每一环都复用前文追踪的实体工时审批 → 工资核算 → 收入/费用入账 → 利润与奖金——数据在模块间不是复制粘贴而是通过外键与状态机接力流转。一条数据的完整旅程把散落的源码串起来一条工时数据的全链路是这样的录入员工在 Web/桌面计时器点击开始TimeLog 记录startedAt实时切片落入 TimeSlotduration在读取时按秒计算聚合按周期归入 Timesheet汇总时长与键盘/鼠标活跃度走PENDING → SUBMITTED → APPROVED审批approvedBy留痕审批通过后isBilled待开票计薪PayrollRun 以periodStart/periodEnd/payDate/frequency/currency圈定发薪周期PayrollItem 逐行录入 EARNING/DEDUCTION整数分累加、事务化状态机确保 DRAFT→…→PAID 每一步不可重入、不可篡改开票InvoiceItem 携带task/project/employee/expense指针把工时或费用折算成price × quantityInvoice 在租户内唯一编号、多币种计税Payment 回填alreadyPaid/amountDue完成核销报表Time Activity、Weekly、Amounts owed、Project budgets 等报表按租户与组织过滤把工时、应得、实付、预算消耗投影为经营视图员工统计层再叠加 splitExpense 分摊逐月产出利润与奖金。对自托管用户和二次开发者而言这条链路的工程含义比界面更值得关注每一个跨模块的金额字段都遵循服务端重算、不接受调用方传入的原则每一个涉及钱的查询都强制租户与组织双范围每一次状态迁移都经过条件 UPDATE 抢锁与事务。这些模式不依赖特定数据库——Gauzy 的实体用MultiORM装饰器同时适配 TypeORM 与 MikroORM同样一套业务规则换库不换逻辑。理解这条数据流也就理解了一个真正一体化的开源业务平台究竟靠什么把 ERP/CRM/HRM 三个词焊接在一起。【免费下载链接】ever-gauzyEver® Gauzy™ - Open Business Management Platform (ERP/CRM/HRM/ATS/PM) - https://gauzy.co项目地址: https://gitcode.com/GitHub_Trending/ev/ever-gauzy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

SpringBoot+SSM办公管理系统开发实践:从权限审批到调试全解析

SpringBoot+SSM办公管理系统开发实践:从权限审批到调试全解析

我几年前接过一个办公管理系统项目,技术栈是现在很多人在用的 Java SpringBoot SSM。当时甲方的要求很简单:把人、事、流程管起来,把请假、报销、用章这些日常审批线上化。结果一聊需求才发现,办公管理系统这东西,看…

2026/10/10 19:38:09 阅读更多 →
AI Agent 出问题时,不要只看最终回答:用 TaoToken 做一次请求级调试

AI Agent 出问题时,不要只看最终回答:用 TaoToken 做一次请求级调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 19:38:09 阅读更多 →
雷达孔径模糊可视化:斑马图生成与参数敏感性分析

雷达孔径模糊可视化:斑马图生成与参数敏感性分析

简介:本资源是一份面向雷达系统工程师与信号处理方向研究生的MATLAB实战工具包,聚焦PRF(脉冲重复频率)设计中的距离模糊与方位模糊分析问题。斑马图作为雷达参数权衡的关键可视化手段,能直观揭示不同PRF设置下模糊区域…

2026/10/10 19:37:09 阅读更多 →

最新新闻

黄仁勋最担心的事还是来了:DeepSeek V4 适配昇腾意味着什么

黄仁勋最担心的事还是来了:DeepSeek V4 适配昇腾意味着什么

黄仁勋最担心的事还是来了:DeepSeek V4 适配昇腾意味着什么 【免费下载链接】DeepSeek-R1 探索新一代推理模型,DeepSeek-R1系列以大规模强化学习为基础,实现自主推理,表现卓越,推理行为强大且独特。开源共享&#xff0…

2026/10/10 23:33:06 阅读更多 →
YOLO汽车头尾方向检测数据集与实操指南

YOLO汽车头尾方向检测数据集与实操指南

简介:本资源是面向计算机视觉初学者与YOLO目标检测实践者的汽车头部尾部专用数据集,解决真实场景下细粒度车辆部件检测的数据匮乏与多格式适配难题。压缩包共2000个文件,含1000张高质量实景标注图片及配套标签:1000个VOC格式XML文…

2026/10/10 23:33:06 阅读更多 →
SpringBoot+Vue前后端分离疫情防控管理系统:毕设项目源码全解析

SpringBoot+Vue前后端分离疫情防控管理系统:毕设项目源码全解析

直接说结论:这套 SpringBoot Vue 的疫情防控管理系统,是我最近整理出来的一套能直接跑的前后端分离项目,Java 做后端、Vue 做前端、MySQL 存数据,覆盖了用户信息上报、管理员审核、健康数据统计、异常状态提醒这一整条链路。如果…

2026/10/10 23:33:06 阅读更多 →
TypeScript泛型尖括号<T>详解:从语法原理到工程实践

TypeScript泛型尖括号<T>详解:从语法原理到工程实践

在开始写之前&#xff0c;我先确认一下我收到的输入&#xff1a;这篇文章的标题是关于TypeScript泛型中尖括号<T>的解释。这是前端/TypeScript开发中最基础但也最容易被问懵的知识点。我从实际操作和面试经历出发&#xff0c;打算把它讲透。从"看着眼熟"到&quo…

2026/10/10 23:33:06 阅读更多 →
ReviewBench:AI代码审查不能只比评论数量,还要验证它找对了什么

ReviewBench:AI代码审查不能只比评论数量,还要验证它找对了什么

一个 pull request&#xff08;提交给团队审查的代码变更&#xff09;交给两个 AI reviewer&#xff0c;一个留下十几条评论&#xff0c;另一个只指出两处问题&#xff0c;应该选谁&#xff1f;评论多&#xff0c;可能是覆盖更全面&#xff0c;也可能让开发者忙着处理无关紧要的…

2026/10/10 23:33:06 阅读更多 →
Ubuntu 上部署 OpenClaw 完整指南:Node.js 与 systemd 实战

Ubuntu 上部署 OpenClaw 完整指南:Node.js 与 systemd 实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 23:32:06 阅读更多 →

日新闻

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

1. 从“卫星轨道分类”这个标题说起&#xff1a;为什么值得花时间搞懂第一次接触“卫星轨道分类”这个概念&#xff0c;很多人会觉得它离自己很远——不就是天上的星星怎么转吗&#xff1f;但如果你正在做航天任务规划、遥感数据接收、星座设计&#xff0c;甚至只是准备一场航天…

2026/10/10 0:00:39 阅读更多 →
Spring AOP 核心原理与实战:从概念到日志切面落地

Spring AOP 核心原理与实战:从概念到日志切面落地

1. 从一个真实痛点说起&#xff1a;为什么你的代码里到处都是重复逻辑刚入行那会儿&#xff0c;我写过一个用户管理模块&#xff0c;注册、登录、改密码、注销四个接口。每个接口里都塞了几乎一样的日志打印、参数校验、事务开启和提交。当时觉得没什么&#xff0c;能跑就行。直…

2026/10/10 0:00:40 阅读更多 →
Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

简介&#xff1a;这是一套面向计算机相关专业学生与项目实战学习者的Python数据采集与分析可视化完整项目&#xff0c;以Boss直聘岗位数据为对象&#xff0c;适合用作毕业设计、课程设计或期末大作业。资源包共38个文件&#xff0c;约246KB&#xff0c;以13个py源码文件为核心&…

2026/10/10 0:00:40 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 11:14:25 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 1:36:08 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 11:14:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 5:23:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 10:38:42 阅读更多 →