Frappe HR 最值得抄的是它決定不做的那部分
這篇是 2026 年 9 月 2 日補寫的(排程斷了幾天,回頭把日期補齊)。文中對 Frappe HR 的描述以撰寫當日的官方文件為準。
Frappe HR 最強的一段設計,是它把全系統最重要的那張表交給別人管。
Employee。員工主檔。請假要它、打卡要它、薪資單要它、報帳單也要它。這張表不在 frappe/hrms 這個 repo 裡,它住在 ERPNext。
看到這種結構,直覺反應通常是「欠債」:核心資料不在自己手上,等於把命脈交給上游。但順序其實是反的。正因為 Employee 不歸它管,它才有力氣去長出 160 個自己的 DocType,而不用分神維護一張全公司每個模組都在戳的主檔。
一行相依決定了整個架構
hrms/hooks.py:7 寫著這個:
1 | required_apps = ["frappe/erpnext"] |
裝 hrms 之前,站台上得先有 frappe(框架)跟 erpnext(ERP 業務層)。框架那層給的是 DocType、權限、排程、REST API 跟自動生成的後台介面;ERP 那層給的是會計傳票、Payment Entry、Company、Holiday List,以及 Employee 主檔。hrms 只負責 HR 跟 Payroll,hrms/modules.txt 全文就這兩行。
這段歷史 README 自己交代了:HR 本來就長在 ERPNext 裡面,從 version 14 才拆成獨立產品。repo 的 created_at 是 2022-06-08,時間對得上。
所以它在 Frappe 的世界裡有個更精確的名字:app。
它願意換掉的,總共四個 class
不重造主檔,那要怎麼把別人的表改成自己要的樣子?答案在 hrms/hooks.py:158-163:
1 | override_doctype_class = { |
整個 controller class 換掉,資料表原封不動。這是最粗暴也最乾淨的擴充方式:行為歸我,schema 歸你。
比例值得記一下。自己新增的 DocType 有 160 個(hrms/hr/doctype/ 117 個、hrms/payroll/doctype/ 43 個,其中 54 個只是 child table、53 個是要送審的單據),換掉別人的只有 4 個。409 行的 hooks.py 就是這份「哪些自己來、哪些接上去」的接線總表。
會計整合更省,整段用宣告的:
1 | # hrms/hooks.py:281-295 |
翻成人話:報帳單在 ERPNext 眼裡就是一張 invoice,跑薪資要參與期末結帳,離職金走預付款科目。它沒有實作任何一條會計邏輯,只是把自己的單據登記進上游既有的分類裡。兩套系統對 API 要處理的認證、重試、資料不一致,這裡一件都不用處理,因為根本沒有兩套系統。
那它自己造了什麼
薪資公式那塊,它自己刻了一個 Python sandbox。
Salary Detail 的 condition 與 formula 兩個欄位,fieldtype 是 Code、options 是 PythonExpression。使用者在後台填的 Python 運算式,直接存進資料庫,跑薪的時候逐列 eval。而且它刻意不用 Frappe 內建的 frappe.safe_eval,docstring 把理由寫得很白:有些國家的薪資公式又大又深,內建那套會撞遞迴上限,所以改用 AST 檢查屬性的輕量版沙箱,代價是它是黑名單而不是白名單,「safe only for admin-authored salary-structure formulas, not arbitrary or end-user input」。
1 | # 節錄改寫自 hrms/payroll/utils.py:125-144 |
行尾那個 # nosemgrep 是跟靜態掃描器攤牌:我知道你要叫,我知道我在幹嘛。這種註解在正式產品裡不多見,多數專案會選擇繞路寫一個更笨但更好看的實作。
有一個細節讓整套設計立起來:算完的金額會用該項目的縮寫塞回 eval context(data[struct_row.abbr] = amount),所以下一條公式引用得到上一條的結果。整份薪資結構因此變成一張依序求值的試算表,BS 先算完,SA = BS * 0.5 就有值可用。eval context 的組法也很小心:先把系統裡所有薪資項目的縮寫預設塞成 0,再疊上指派的 base、variable,最後疊上員工欄位。公式引用一個當期沒出現的項目時不會噴 NameError,會拿到 0。
另一個自己造的東西是請假餘額。它不存餘額,存帳本。
Leave Ledger Entry 是 append-only 的分錄表,每一筆額度增減寫一列,餘額用 SQL SUM 算出來。transaction_type 是 Link 到 DocType、transaction_name 是 Dynamic Link,所以同一張帳本收得下配發、請假、換現、調整四種來源。on_cancel 幾乎封死,只有到期分錄能撤,其餘直接 throw。沖銷而非修改,標準會計思路。
最能看出實務磨損的是這裡:程式碼把「帳面餘額」跟「可消耗餘額」分成兩個數。員工帳面上有 10 天,但額度明天就到期,實際只能消耗 1 天。這種區分不會出現在第一版設計裡,是被客訴磨出來的。
分界線在這裡:通用的基礎設施一行都不重寫,領域邏輯裡不講理的那些角落全部自己造。 記帳規則是別人的專業,休假到期怎麼算是它的專業。
這個賭注要付的帳單
把主檔押給上游,代價會在別的地方回來收。
required_apps 是硬相依,想「只要 HR 不要 ERP」做不到,整套會計科目表會跟著進來。三個 app 的大版號綁在一起,寫在 pyproject.toml 的 [tool.bench.frappe-dependencies] 裡:要升就三個一起升,也不能從 v14 直接跳 v16,得照 Frappe 的升級路徑一版一版走。
台灣的部分要講清楚:完全沒有在地化。hrms/regional/ 只有 india 跟 united_arab_emirates 兩個目錄,而且分量差很多。印度那份把 PF、Professional Tax、HRA 免稅額、Gratuity 規則、報表都做了,阿聯那份只有一個 57 行的 setup.py,內容是建三條離職金規則。勞保、健保、勞退 6%、二代健保補充保費、所得稅扣繳,一項都沒有,全部要自己用 Salary Component 加 formula 做,或另開 app 用 regional_overrides 抽換。介面也一樣,hrms/locale/zh_TW.po 的 3,033 條裡只有 702 條有翻譯,簡體那份是 2,252 條中的 2,101 條。用繁中介面會看到大量英文夾雜。
社群反應最多的那件事是沒有官方自架正式環境指南。官方文件只有 Frappe Cloud 與 Docker 開發環境,Nginx、SSL、Supervisor、DB 調校一概沒有;open issue #2818 就在要這份文件,截至 2026-08-26 還開著。repo 裡那份 docker/docker-compose.yml 只適合開發,root 密碼寫死 123、developer_mode 開著,別拿去正式環境。
還有兩個會影響體感的:跑薪資超過 30 人自動丟背景 queue,UI 按下去不會馬上有結果,Redis worker 數量直接決定跑薪要多久;打卡轉出勤靠 hourly_long 排程,最長一小時延遲,不是即時。
新手最常卡的地雷倒不是這些,是 is_submittable。Leave Allocation、Salary Structure、Salary Structure Assignment、Salary Slip、Attendance 全都是要送審的單據,用 API 建完只 insert() 沒 submit(),資料看得到但完全不生效——假期餘額是 0,薪資單算不出來。這條來自 repo 內的測試 helper 與各 DocType 的必填欄位,我沒有在真站台上跑過,欄位名稱請以你安裝的版本為準。
什麼時候這個賭注划算
已經在跑 ERPNext 的話,這題沒什麼好想的。員工、部門、會計科目本來就是同一份資料,整合成本接近零,那 4 個 override 幫你把最後一哩接完。
沒在跑 ERPNext 的話,你買的不是一套 HR 系統,是一整套 ERP 的維運成本,外加一份還沒有人寫的正式環境部署指南。要走這條就得先接受養一個會 bench 的人。
會讓我改口的條件有兩個:官方補上自架正式環境的完整文件,或者 regional_overrides 出現 taiwan 目錄。前者解掉的是維運風險,後者解掉的是「勞健保要自己從零刻」這件事。目前這兩個都不在路線圖上看得到——2026-08-26 實查,open issues 450、open PRs 44,標 feature-request 的 246 筆是標 bug 的 148 筆的 1.66 倍,高反應數的前十名幾乎全是功能許願。這是個活著而且改很兇的專案(8,673 stars、當天還有 push、GPL-3.0),但它還在長,不是功能凍結的成熟品。
順帶一提,hooks.py 裡有一整段 doc_events 掛在 hrms.telemetry.*,正常使用會送使用事件與啟用漏斗。要關得從 Frappe Framework 那端設定,確切設定名稱我沒查到,這點未查證。
下次評估一個 repo,先翻接線檔
一個團隊的資源永遠不夠寫完所有東西,所以真正的設計決策落在另一個問題上:哪些東西你打死都不自己寫。Frappe HR 給的答案很極端:凡是別人已經做對的(記帳、權限、排程、API 生成),一行都不碰;凡是這個領域裡沒有標準答案、每家公司都不一樣的(薪資公式、假期到期規則),做到最深。
下次評估一個開源專案,可以先不看它的功能清單。翻它的 hooks.py,或任何一份等價的接線檔,看它把哪些東西登記成別人的責任。那份清單比 README 誠實得多。
參考來源:frappe/hrms、官方文件 docs.frappe.io/hr。本文事實以 2026-08-26 的 develop 分支原始碼與 gh api 機器可讀元資料為準,版本號、issue 數量、翻譯完成度都會隨時間變動。










