Get a quote

بناء نظام موارد بشرية وكشوف رواتب بـ Go للشركات في لبنان والمنطقة

معظم الشركات الصغيرة والمتوسطة في لبنان تدير كشوف الرواتب على 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/

Free PDF Download

Enjoying this article?

Enter your email and get a clean, formatted PDF of this article - free, no spam.

Free. No spam. Unsubscribe any time.

Back to blog
Chat on WhatsApp