معظم الشركات الصغيرة والمتوسطة في لبنان تدير كشوف الرواتب على Excel. عندما يصل عدد الموظفين إلى 15 شخصًا، تصبح هذه العملية عبئًا شهريًا يستهلك يومين. هذا المقال يشرح كيف نبني نظام HR وكشوف رواتب بـ Go يتعامل مع احتساب NSSF ومتطلبات ضريبة الدخل اللبنانية وحسابات صافي الراتب بدقة.
معظم الشركات الصغيرة والمتوسطة في لبنان تدير كشوف الرواتب على Excel. عندما يصل عدد الموظفين إلى 15 شخصًا أو أكثر، تصبح هذه العملية عبئًا شهريًا يستهلك يومين من وقت الإدارة، مع احتمال وجود أخطاء في الحسابات، ولا يوجد مسار تدقيق موثوق. عندما يبدأ عميل يشغّل 40 موظفًا في المطالبة بنظام آلي، هذا ما نبنيه.
ما هي متطلبات كشوف الرواتب اللبنانية التي يجب على النظام تغطيتها؟
لبنان لديه هيكل رواتب محدد يجب على أي نظام HR أن يتعامل معه بدقة:
NSSF (الصندوق الوطني للضمان الاجتماعي): يقتطع 3 بالمئة من راتب الموظف كحصة صحية، والصاحب يدفع 6 بالمئة. مكافأة نهاية الخدمة عادةً 8.5 بالمئة من الراتب الأساسي تُدفع من قبل الصاحب. هذه الأرقام قابلة للتغيير مع التشريعات، لذا يجب أن تكون قابلة للتهيئة في النظام.
ضريبة الدخل: تطبق شرائح تصاعدية على الراتب السنوي الصافي بعد الخصومات. معظم الموظفين في الشركات الصغيرة يقعون ضمن الشرائح الأولى.
البدلات والمكافآت: بدل النقل، بدل السكن، مكافآت الأداء. هذه قابلة للضريبة بناءً على نوعها.
العملات: مع الأوضاع الاقتصادية في لبنان، كثير من الشركات تدفع جزءًا من الرواتب بالدولار وجزءًا بالليرة. النظام يجب أن يتعامل مع عملات متعددة في نفس السجل.
كيف تصمم قاعدة البيانات لكشوف الرواتب؟
CREATE TABLE employees (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
full_name TEXT NOT NULL,
job_title TEXT NOT NULL,
department TEXT,
hire_date DATE NOT NULL,
base_salary_usd NUMERIC(12,2) NOT NULL,
base_salary_lbp NUMERIC(18,0) NOT NULL DEFAULT 0,
status TEXT NOT NULL DEFAULT 'active',
nssf_number TEXT,
tax_id TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE allowances (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
employee_id UUID NOT NULL REFERENCES employees(id),
type TEXT NOT NULL, -- 'transport', 'housing', 'phone', 'bonus'
amount NUMERIC(12,2) NOT NULL,
currency TEXT NOT NULL DEFAULT 'USD',
taxable BOOLEAN NOT NULL DEFAULT false,
active BOOLEAN NOT NULL DEFAULT true
);
CREATE TABLE pay_runs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
period_start DATE NOT NULL,
period_end DATE NOT NULL,
status TEXT NOT NULL DEFAULT 'draft',
total_gross NUMERIC(12,2),
total_net NUMERIC(12,2),
processed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE payslips (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
pay_run_id UUID NOT NULL REFERENCES pay_runs(id),
employee_id UUID NOT NULL REFERENCES employees(id),
gross_salary NUMERIC(12,2) NOT NULL,
total_allowances NUMERIC(12,2) NOT NULL DEFAULT 0,
nssf_employee NUMERIC(12,2) NOT NULL DEFAULT 0,
nssf_employer NUMERIC(12,2) NOT NULL DEFAULT 0,
income_tax NUMERIC(12,2) NOT NULL DEFAULT 0,
other_deductions NUMERIC(12,2) NOT NULL DEFAULT 0,
net_salary NUMERIC(12,2) NOT NULL,
currency TEXT NOT NULL DEFAULT 'USD'
);
كيف يعمل حساب الراتب في Go؟
محرك الحساب هو منطق خالص، بدون اعتمادات على قاعدة البيانات مباشرةً. هذا يجعل الاختبار سهلًا:
type PayrollConfig struct {
NSSFEmployeeRate float64 // 0.03
NSSFEmployerRate float64 // 0.06
EOSBRate float64 // 0.085 (نهاية الخدمة)
TaxBrackets []TaxBracket
}
type TaxBracket struct {
UpTo float64
Rate float64
}
type PayslipCalculation struct {
EmployeeID uuid.UUID
GrossSalary decimal.Decimal
TotalAllowances decimal.Decimal
NSSFEmployee decimal.Decimal
NSSFEmployer decimal.Decimal
EOSBContribution decimal.Decimal
IncomeTax decimal.Decimal
NetSalary decimal.Decimal
}
func CalculatePayslip(emp Employee, allowances []Allowance, cfg PayrollConfig) PayslipCalculation {
gross := emp.BaseSalaryUSD
taxableAllowances := decimal.Zero
totalAllowances := decimal.Zero
for _, a := range allowances {
if !a.Active {
continue
}
totalAllowances = totalAllowances.Add(a.Amount)
if a.Taxable {
taxableAllowances = taxableAllowances.Add(a.Amount)
}
}
nssfEmployee := gross.Mul(decimal.NewFromFloat(cfg.NSSFEmployeeRate))
nssfEmployer := gross.Mul(decimal.NewFromFloat(cfg.NSSFEmployerRate))
eosb := gross.Mul(decimal.NewFromFloat(cfg.EOSBRate))
taxableIncome := gross.Add(taxableAllowances).Sub(nssfEmployee)
incomeTax := calculateProgressiveTax(taxableIncome, cfg.TaxBrackets)
net := gross.Add(totalAllowances).Sub(nssfEmployee).Sub(incomeTax)
return PayslipCalculation{
EmployeeID: emp.ID,
GrossSalary: gross,
TotalAllowances: totalAllowances,
NSSFEmployee: nssfEmployee,
NSSFEmployer: nssfEmployer,
EOSBContribution: eosb,
IncomeTax: incomeTax,
NetSalary: net,
}
}
func calculateProgressiveTax(income decimal.Decimal, brackets []TaxBracket) decimal.Decimal {
tax := decimal.Zero
prev := decimal.Zero
for _, b := range brackets {
ceiling := decimal.NewFromFloat(b.UpTo)
if income.LessThanOrEqual(prev) {
break
}
taxable := income.Sub(prev)
if taxable.GreaterThan(ceiling.Sub(prev)) {
taxable = ceiling.Sub(prev)
}
tax = tax.Add(taxable.Mul(decimal.NewFromFloat(b.Rate)))
prev = ceiling
}
return tax
}
استخدام decimal.Decimal بدلًا من float64 ضروري للحسابات المالية. أخطاء التقريب في float64 تتراكم عبر 40 موظفًا وتنتج كشوف رواتب غير متطابقة بجنيهات ليرة.
كيف تدير دورة عمل كشوف الرواتب؟
كشوف الرواتب لها دورة عمل: draft (مسودة) ثم review (مراجعة) ثم approved (معتمد) ثم processed (منفذ). يجب أن تكون قابلة للتعديل في مرحلة draft وغير قابلة للتعديل بعد الاعتماد.
type PayRunService struct {
db *sqlx.DB
calc *PayrollCalculator
}
func (s *PayRunService) GenerateDraft(
ctx context.Context,
tenantID uuid.UUID,
periodStart, periodEnd time.Time,
) (*PayRun, error) {
employees, err := s.listActiveEmployees(ctx, tenantID)
if err != nil {
return nil, err
}
tx, err := s.db.BeginTxx(ctx, nil)
if err != nil {
return nil, err
}
defer tx.Rollback()
run := &PayRun{
ID: uuid.New(),
TenantID: tenantID,
PeriodStart: periodStart,
PeriodEnd: periodEnd,
Status: "draft",
}
if err := insertPayRun(ctx, tx, run); err != nil {
return nil, err
}
cfg := s.getPayrollConfig(tenantID)
for _, emp := range employees {
allowances, _ := s.getAllowances(ctx, emp.ID)
calc := CalculatePayslip(emp, allowances, cfg)
if err := insertPayslip(ctx, tx, run.ID, calc); err != nil {
return nil, err
}
}
run.Status = "ready"
return run, tx.Commit()
}
كيف تنشئ ملفات كشوف الرواتب؟
الكشف في PDF هو المخرج المتوقع. نستخدم مكتبة gofpdf أو unipdf لإنتاج PDF بتنسيق احترافي يتضمن شعار الشركة، بيانات الموظف، تفاصيل الراتب، والمبالغ المقتطعة.
يجب أن يُنشأ ملف PDF لكل موظف يحتوي على:
- الراتب الأساسي والبدلات
- مقتطعات NSSF بحصة الموظف والصاحب
- ضريبة الدخل المحسوبة
- صافي الراتب بعد جميع الاقتطاعات
- الفترة الزمنية للراتب
- رقم المرجع للسجل التدقيق
الدروس الرئيسية من الإنتاج
نظام كشوف الرواتب هو أكثر مكونات النظام حساسية لأن الأخطاء تؤثر مباشرةً على الموظفين. محرك الحساب يجب أن يكون منطقًا خالصًا مغطى بالاختبارات بالكامل. معدلات NSSF وشرائح الضريبة يجب أن تكون قابلة للتهيئة دون تعديل الكود لأن التشريعات تتغير. أرقام decimal.Decimal دائمًا لحسابات الرواتب، وليس أبدًا float64.
هل تحتاج إلى مساعدة في بناء نظام HR؟
فوكسير تبني أنظمة إدارة الأعمال المخصصة للشركات في لبنان ومنطقة الشرق الأوسط وشمال إفريقيا. إذا كنت تشغّل فريقًا متناميًا وتحتاج إلى نظام HR يتعامل مع واقع الأعمال في المنطقة، تواصل معنا.
https://voxire.com/get-a-quote/
Enjoying this article?
Enter your email and get a clean, formatted PDF of this article - free, no spam.



