odoo.
odoo14 min read

Odoo HR và Payroll: Quản Lý Nhân Sự Và Tính Lương Cho Doanh Nghiệp

Odoo HR và Payroll: Quản Lý Nhân Sự Và Tính Lương Cho Doanh Nghiệp

Lần đầu tôi setup Odoo HR cho một công ty 80 người ở Quận 7, tôi tưởng ba ngày là xong việc. Kết quả thực tế: ba tuần, một batch payslip phải recompute bảy lần vì rule BHXH viết sai dấu đợt đầu, và một đêm ngồi gõ lại script import từ máy chấm công ZKTeco lúc 2 giờ sáng. Bài này là phiên bản mình mong có được lúc đó — một tấm bản đồ trước khi cài module, thay vì một bài post-mortem sau khi mất ngủ một tuần vì lương. Tôi không hứa đọc xong là Odoo Payroll sẽ đơn giản — nó không đơn giản. Tôi hứa, khi gặp nút bấm Compute Sheet lần đầu, bạn sẽ biết bảng nào là cốt lõi, rule nào thực sự xây con số cuối tháng, và đoạn nào cộng đồng đã làm sẵn để mình khỏi phải viết lại từ đầu.

Vì sao Odoo HR + Payroll đáng nhìn lại

Tại sao một ERP open source lại dám cạnh tranh trực tiếp với Workday và SAP trong mảnh đất nhân sự — vốn được xem là khó nhai nhất? Phần lớn doanh nghiệp Việt Nam khi nhắc tới phần mềm nhân sự vẫn nghĩ tới Excel cộng vài file Google Sheet, hoặc một module HR tách rời trong ERP mà ít ai dám động vào. Odoo, trong nhiều năm liền, đã đi theo một hướng khác: nhân sự không phải một silo tách biệt mà là một mắt xích trong toàn bộ chuỗi vận hành doanh nghiệp — từ chấm công, nghỉ phép, đánh giá hiệu suất, cho đến tính lương và xuất bảng lương cuối tháng. Khi tôi quay lại đọc module hr_payroll trong Odoo 19, điều khiến tôi ấn tượng nhất không phải là bộ rule engine, mà là cách Odoo phân lớp giữa lớp miễn phí (employees, contracts, attendance, holidays, recruitment) và lớp Payroll — vốn ban đầu nằm ở edition cộng đồng nhưng đã được nâng cấp đáng kể qua các phiên bản gần đây.

Bài viết này là một cái nhìn nhập môn cho lập trình viên hoặc người triển khai đang cân nhắc dùng Odoo cho stack nhân sự payroll. Tôi sẽ không đi sâu vào từng cấu hình màn hình, mà sẽ kể lại bức tranh tổng thể, mô tả các bảng dữ liệu cốt lõi, dẫn một ví dụ code nhỏ cho salary rule, rồi nói thẳng về những gì OCA — cộng đồng Odoo Community Association — đang đóng góp riêng cho thị trường Việt Nam. Mục tiêu cuối cùng là giúp bạn trả lời một câu hỏi quan trọng: với quy mô và đặc thù của doanh nghiệp mình, Odoo HR Payroll có thực sự là lựa chọn đúng, hay chỉ là một bản demo đẹp trên slide?

Bức tranh module: từ Employees đến Payroll

Một điều ít ai nói trước với bạn: bấm Install App Employees không phải bấm một nút, mà là ký một hợp đồng với bảy module phụ thuộc cùng lúc kéo theo. Một đồng nghiệp cũ từng nói với tôi rằng Odoo Employees là một trong những App "lừa đảo" tinh vi nhất trong ERP open source — không phải vì nó xấu, mà vì bấm Install xong bạn đã âm thầm kéo về sau bảy module phụ thuộc mà mình chưa hề đọc tên. Khi bạn cài Odoo lần đầu và bật App Employees, thực ra bạn đang kích hoạt một chuỗi module phụ thuộc lẫn nhau:

  • hr — danh sách nhân viên, phòng ban, chức danh
  • hr_contract — hợp đồng lao động, mức lương cơ bản, salary structure tham chiếu
  • hr_attendance — chấm công vào ra qua web, qua kiosk, qua badge
  • hr_holidays — đăng ký và phê duyệt nghỉ phép, allocation theo năm
  • hr_recruitment — tuyển dụng, ứng viên, vòng phỏng vấn, ký candidate
  • hr_skills — kỹ năng, chứng chỉ, đánh giá năng lực theo level
  • hr_payroll — payslip, salary structure, salary rule, payroll period, payslip run batch

Module hr_payroll không tự sống được — nó cần hr_contract để biết mức lương cơ bản, cần hr_holidays để biết số ngày nghỉ có lương, và cần hr_attendance hoặc một module work entry tùy chỉnh để biết số giờ làm thực tế. Đây là kiến trúc đặc trưng của Odoo: mỗi nghiệp vụ được tách thành một addon, và payroll chỉ là khách tiêu thụ dữ liệu từ các addon khác qua các trường M2O và One2many.

Trang tài liệu chính thức của Odoo về module HR là điểm vào tốt nhất nếu bạn muốn xem ảnh chụp màn hình và quy trình end-to-end. Còn nếu bạn muốn đào source code, kho OCA/hr trên GitHub gồm hàng trăm module mở rộng do cộng đồng đóng góp — đa số là patch những điểm yếu mà bản Enterprise không buồn fix.

Cấu trúc dữ liệu cốt lõi

Bạn không cần nhớ hết hàng chục bảng trong Odoo HR — chỉ cần bốn bảng đầu tiên, và bạn sẽ đọc được 80% logic payroll mà không cần mở source code. Nếu bạn chỉ có thời gian thuộc lòng bốn bảng trong toàn bộ Odoo HR, thì đây chính là bốn bảng đó — bỏ qua chúng và mọi rule bạn viết sau này sẽ chạm vào một vùng đất trống. Trước khi viết bất kỳ rule nào, bạn cần thuộc lòng bốn bảng:

hr.employee — một bản ghi cho mỗi nhân viên. Chứa department_id, job_id, company_id, parent_id (manager), và một loạt trường HR cá nhân: CCCD, ngày sinh, địa chỉ thường trú, người liên hệ khẩn cấp, tài khoản ngân hàng nhận lương.

hr.contract — hợp đồng lao động. Quan trọng nhất là wage (lương cơ bản tháng), struct_id (salary structure tham chiếu), date_start, date_end, state (draft / open / close / cancel). Một nhân viên có thể có nhiều contract theo thời gian, nhưng tại một thời điểm chỉ một contract ở state open.

hr.payslip — phiếu lương cho một nhân viên trong một kỳ. Mỗi payslip có nhiều hr.payslip.line — mỗi dòng là một thành phần lương (basic, allowance, bonus, deduction, tax). Payslip có worked_days_line_ids chứa thông tin ngày công, và input_line_ids chứa các input thủ công từ HR (ví dụ thưởng KPI tháng này).

hr.salary.rule — quy tắc tính lương. Đây là nơi bạn viết Python expression để cộng, trừ, nhân, chia ra từng dòng payslip line. Rule được gom thành hr.salary.structure, structure được gắn vào contract.

Khi bạn bấm Compute Sheet trên một payslip ở giao diện, Odoo sẽ duyệt qua từng rule trong structure theo sequence, đánh giá condition_python (rule có áp dụng không), rồi đánh giá amount_python_compute (số tiền là bao nhiêu). Kết quả lưu vào hr.payslip.line và tổng lại thành net payslip.

Quy trình tính lương ở Odoo 19

Một chu trình payroll điển hình ở Odoo trải qua sáu bước:

Khóa hợp đồng và mức lương — đảm bảo mọi nhân viên trong kỳ đều có contract open với wage chính xác. Đây là bước mà HR phải chốt trước ngày cut-off; nếu không, mọi sửa đổi sau sẽ kéo theo recompute hàng loạt.

Tạo work entries — Odoo dùng các hr.work.entry từ attendance, holidays, public holidays. Bước này quan trọng vì work entry là input cho payroll, không phải attendance thô. Conflict giữa work entry (ví dụ nhân viên vừa có attendance vừa có nghỉ phép cùng ngày) phải được giải quyết trước khi compute.

Tạo batch payslip — một hr.payslip.run gồm tất cả payslip trong cùng kỳ (ví dụ tháng 5/2026). Tạo batch giúp duyệt và xác nhận hàng loạt thay vì từng phiếu một.

Compute payslip — bấm nút compute để Odoo chạy salary rule. Đây là lúc bug thường xảy ra: rule sai cú pháp Python, biến inputs chưa được khai báo, hoặc structure chưa được gắn đúng contract.

Verify và xác nhận — kế toán / HR review từng payslip, xác nhận hoặc gửi lại bộ phận để chỉnh. Có thể in PDF preview ở bước này để gửi nhân viên xác nhận trước.

Xuất bảng lương và đẩy sang Accounting — payslip xác nhận có thể đẩy sang module account thành journal entry, từ đó in bảng lương PDF và sinh lệnh chi trả qua ngân hàng.

Mỗi bước trong số này đều có thể tự động hóa bằng cron, server action, hoặc trigger từ module khác. Đây là điểm mạnh lớn nhất của Odoo so với phần mềm HR đóng gói: bạn không bị khóa vào quy trình cứng, và có thể chèn logic compliance riêng (ví dụ chặn duyệt nếu payslip có net lương âm).

Code minh họa: tạo một salary rule đơn giản

Giả sử bạn muốn thêm một rule Phụ cấp xăng xe cố định 500.000 đồng cho mọi nhân viên có contract structure Vietnam Standard. Trong một module addon tự viết, bạn sẽ khai báo rule này dưới dạng XML data record:

<odoo>
    <data noupdate="1">
        <record id="hr_rule_fuel_allowance" model="hr.salary.rule">
            <field name="name">Phu cap xang xe</field>
            <field name="code">FUEL</field>
            <field name="category_id" ref="hr_payroll.ALW"/>
            <field name="sequence">15</field>
            <field name="condition_select">none</field>
            <field name="amount_select">fix</field>
            <field name="amount_fix">500000</field>
            <field name="struct_id" ref="l10n_vn_hr_payroll.salary_structure_vn_standard"/>
        </record>
    </data>
</odoo>

Nếu phụ cấp này chỉ áp dụng cho nhân viên có vị trí Sales, bạn đổi condition_select sang python và viết:

result = employee.job_id and employee.job_id.name == 'Sales'

Còn nếu muốn số tiền phụ thuộc số ngày công thực tế, bạn đổi amount_select sang python và viết:

worked = sum(wd.number_of_days for wd in worked_days if wd.code == 'WORK100')
result = (500000 / 22) * worked

Trong các expression này, biến môi trường có sẵn gồm employee, contract, payslip, categories, rules, worked_days, inputs. Tài liệu chính thức liệt kê đầy đủ scope, nhưng kinh nghiệm là chạy thử rồi đọc traceback — Odoo log rất rõ rule nào throw exception ở payslip nào. Khi viết rule mới, tôi luôn test bằng một payslip cá lẻ trước, sau đó mới mở rộng ra batch.

Tích hợp với chấm công và nghỉ phép

Module hr_attendance mặc định ghi cặp check_in / check_out. Để dữ liệu này biến thành ngày công cho payroll, Odoo có một bước trung gian: hr.work.entry. Mỗi work entry có work_entry_type_id (ví dụ WORK100 — ngày công bình thường, LEAVE100 — nghỉ phép có lương, LEAVE0 — nghỉ không lương), date_start, date_stop, và state (draft / validated / conflict).

Khi bạn tạo một payslip, Odoo gọi _get_worked_day_lines() để gom work entries của nhân viên trong kỳ, nhóm theo work entry type, rồi convert ra giờ hoặc ngày. Số liệu này được lưu vào hr.payslip.worked_days và là input chính cho salary rule loại based on worked days.

Một vấn đề thực tế: máy chấm công vân tay ở Việt Nam thường xuất CSV theo định dạng riêng của hãng (ZKTeco, Suprema, Hikvision). Bạn cần một adapter để parse file đó và create hr.attendance đúng format. OCA có vài addon mã nguồn mở cho ZKTeco; với hãng khác bạn sẽ phải tự viết. Đây là vùng kỹ thuật mà mọi triển khai Odoo HR ở Việt Nam đều phải đụng tới, sớm hay muộn.

OCA và những gì cộng đồng đã làm sẵn cho thị trường Việt Nam

Kho OCA/payroll chứa các addon mở rộng payroll mà bản gốc Odoo không có hoặc không hoàn thiện: payroll_account_anglo_saxon, payroll_loan, payroll_period, hr_payroll_input_max. Riêng cho Việt Nam, có một vài addon lẻ tẻ về bảo hiểm xã hội (BHXH), thuế thu nhập cá nhân (TNCN) theo biểu lũy tiến, và nghỉ lễ theo lịch âm — nhưng không có một bộ Vietnam localization hoàn chỉnh và được bảo trì như cách Odoo đã làm cho Pháp, Mỹ, hoặc Bỉ.

Điều này có nghĩa là nếu bạn triển khai Odoo HR cho doanh nghiệp Việt Nam, gần như chắc chắn bạn sẽ phải tự viết:

  • Một module l10n_vn_hr_payroll chứa salary structure cho Vietnam Standard
  • Các salary rule cho lương cơ bản, phụ cấp, lương net, BHXH 8% / BHYT 1,5% / BHTN 1%, thuế TNCN lũy tiến 7 bậc, giảm trừ gia cảnh
  • Báo cáo bảng lương xuất theo format Bộ Lao động yêu cầu
  • Báo cáo BHXH theo mẫu D02-TS, C70a-HD
  • Báo cáo TNCN theo mẫu 05/KK-TNCN

Lượng việc này khoảng 2 đến 4 tuần dev cho một team quen Odoo, hoặc 6 đến 8 tuần cho team mới. Đây là chi phí ẩn lớn nhất khi chọn Odoo cho HR ở Việt Nam — không phải license, mà là localization. Nếu bạn đang lập budget, đừng quên cộng thêm chi phí maintenance hàng năm vì quy định BHXH và TNCN thay đổi thường xuyên.

Những cạm bẫy thường gặp khi triển khai

Sau nhiều dự án Odoo HR ở các quy mô khác nhau, có ba sai lầm tôi thấy lặp đi lặp lại:

Thứ nhất, cố nhồi nhét mọi business rule vào salary rule. Salary rule là Python expression chạy trong sandbox — debug rất khó, version control gần như không tồn tại (bạn phải so sánh XML record giữa các bản dump). Quy tắc tốt: salary rule chỉ làm số học đơn giản; logic phức tạp như "nếu nhân viên có dưới 12 tháng làm việc thì áp dụng mức BHXH thấp hơn" nên đẩy vào helper method trên hr.contract hoặc hr.employee, salary rule chỉ gọi helper đó. Helper là code Python bình thường, có thể unit test, có thể diff git rõ ràng.

Thứ hai, không tách rời wage (lương trên hợp đồng) với taxable_wage (lương chịu thuế). Khi có phụ cấp ăn trưa không chịu thuế, phụ cấp điện thoại, thưởng KPI có chịu thuế — bạn sẽ cần ba trường khác nhau và logic tính TNCN khác nhau. Đừng nhét tất cả vào wage rồi tính ngược ra; bạn sẽ mất nhiều thời gian khi có kiểm tra thuế và không giải trình được.

Thứ ba, bỏ qua bước backup database trước khi compute batch payslip. Compute payslip là thao tác write hàng nghìn dòng hr.payslip.line. Nếu rule sai và bạn phải xóa batch rồi compute lại, mỗi lần lặp lại tốn rất nhiều thời gian. Backup snapshot Postgres trước mỗi compute lớn là thói quen tốt, và ở môi trường production nên có script tự động snapshot trước mỗi payslip run của batch lớn hơn 100 nhân viên.

Khi nào nên dùng Odoo Payroll, khi nào không

Odoo Payroll phù hợp khi:

  • Doanh nghiệp đã dùng Odoo cho các module khác (Sales, Inventory, Accounting) và muốn HR đồng bộ trên cùng database
  • Quy mô 50 đến 500 nhân viên — đủ lớn để Excel không kham nổi, đủ nhỏ để chưa cần Workday hoặc SAP SuccessFactors
  • Team IT có ít nhất một dev Python / Odoo để bảo trì localization và patch khi có quy định thuế mới
  • Cấu trúc lương tương đối ổn định, không thay đổi rule hàng tháng

Odoo Payroll KHÔNG phù hợp khi:

  • Bạn cần payroll cho 5.000+ nhân viên với nhiều quốc gia khác nhau, mỗi quốc gia có quy định khác — lúc này một SaaS chuyên payroll (Deel, Remote, Papaya) hoặc enterprise (ADP, Workday) tiết kiệm chi phí dev hơn
  • Bạn không có dev nội bộ và không muốn phụ thuộc đối tác triển khai dài hạn
  • Quy định lương quá đặc thù (ngành xây dựng, hàng hải, doanh nghiệp có vốn đầu tư nước ngoài với cơ chế expat) và Odoo không có sẵn

Bước tiếp theo cho người mới bắt đầu

Nếu bạn vừa đọc xong và muốn thử tay, đây là roadmap 1 tuần:

Ngày 1 và 2: Dựng Odoo 19 bằng Docker Compose local. Cài modules hr, hr_contract, hr_attendance, hr_holidays, hr_payroll. Tạo 5 nhân viên giả lập, mỗi người một contract với wage khác nhau.

Ngày 3: Đọc kỹ tài liệu salary rule và work entry. Tạo thử một payslip cho một nhân viên, compute, xem kết quả. Bấm vào từng payslip line để xem rule nào sinh ra số đó.

Ngày 4: Viết một addon my_vn_payroll chứa một salary structure Việt Nam đơn giản (lương cơ bản + BHXH 8% + thuế TNCN flat 10%). Khai báo bằng XML data record như ví dụ ở trên.

Ngày 5: Test với batch payslip 5 người. So sánh kết quả với tính tay trên Excel. Sửa rule cho khớp, recompute, sửa tiếp cho đến khi khớp hoàn toàn.

Ngày 6 và 7: Đọc OCA/payroll, chọn ra 2 đến 3 addon hữu ích, hiểu cách họ extend hr.payslip. Ghi chú lại pattern để tái sử dụng sau.

Sau một tuần, bạn sẽ có đủ context để quyết định Odoo có phải lựa chọn đúng cho doanh nghiệp của mình hay không. Và quan trọng hơn, bạn sẽ hiểu chiều sâu của một hệ thống ERP open source: không phải mọi thứ đều có sẵn, nhưng mọi thứ đều có thể được build từ những viên gạch hiện có. Đây cũng là triết lý khác biệt lớn nhất giữa một ERP open và một SaaS đóng gói: bạn trả giá bằng thời gian setup và bảo trì, đổi lại quyền kiểm soát hoàn toàn logic kinh doanh của chính mình.