Product Feature Document · Generated from the repository

What Tanzania HRMS actually does.

A single-tenant, back-office HR & payroll system built for a bank operating in Tanzania — traced feature by feature from routes/, controllers, models and seed data, not from assumptions.

~120
Features traced
11
Modules
21
Seeded roles
184
Permissions
18
Workflows mapped
~90%
Confirmed in code
01

Overview

Every authenticated screen lives under /admin, behind one shared login. There is no public area and no mobile app — this is purely an internal system of record.

What it manages

The full employee lifecycle and the money that flows from it: employee master records, branch/department/designation structure, leave entitlement and approval, payroll from payscale through payslip, KRA-based performance appraisal, statutory and management reporting, a per-employee document library, and database backup.

Why it exists

Five rules are specific enough to this organisation that generic HR software couldn't have met them:

1. Two employee populations.

Local (Tanzanian) and expatriate (IBO) staff get different leave types, payroll heads, payslip layouts, tax handling and currencies (TSHS vs USD) — every calculation branches on employment_type.

2. Leave accrues monthly.

Not a flat annual grant — it accrues month by month with per-type caps and pro-rata rules.

3. Salary heads are configurable data.

Several heads (overtime, reimbursements, loans, pension, arrears) are computed at runtime, not typed in.

4. Bank-specific GL output.

The TTUM report maps each salary component to a specific general-ledger account.

5. A formal, bank-style appraisal instrument — fixed self-assessment questions, KRA attributes scored out of 10, and separate reporting/review-authority scoring.

Who uses it

Employees

Maintain their own profile, apply for leave and encashment, submit self-appraisal, view payslip and documents.

Line managers / branch heads

Approve branch leave, give reporting-authority appraisal feedback, view branch data.

HR (Head of HR & team)

Own the employee master, leave settings and balances, imports, HR reporting.

Payroll / Finance

Maintain heads, payscales, tax slabs; generate salary; approve reimbursements; produce TTUM and tax reports.

Managing Director

Final-authority appraisal feedback, top-level approvals, full visibility across the system.

System administrator

Roles and permissions, master data, database backups.

02

Technology snapshot

AspectValue
Language / frameworkPHP ^8.1, Laravel ^10.0
DatabaseMySQL, 71 migrations
UIServer-rendered Blade — 264 templates, no SPA
Tablesyajra/laravel-datatables (server-side AJAX)
Excelmaatwebsite/excel — employee & leave import, one TTUM export
Front-end buildNone usable — no package.json; assets are pre-compiled
TestsPest — stock Breeze auth/profile tests only
Third-party APIsNone. Self-contained — no payment gateway, SMS, SSO or webhooks
Notable gaps

PDF export is brokenbarryvdh/laravel-dompdf is referenced by the appraisal PDF download but has been removed from composer.json. No queue workers — everything runs synchronously in the request. Nothing is scheduled — monthly leave accrual (task:monthly) must be triggered manually or by an external cron.

03

Feature inventory

Every feature below was traced from a route through its controller to its models and views. Class labels: Core, Supporting, Admin, User, Automation, Integration, Reporting, System.

Authentication & accountRestrict the system to active employees and force a password change off any seeded credential

#Feature
1.1Sign in (email + password)GET|POST / , GET|POST /loginCoreConfirmed
1.2Account-deactivation block at loginis_active checkSupportingConfirmed
1.3Forced password change on first loginGET /admin/password-resetSupportingConfirmed
1.4Change own passwordPOST /admin/password-updateUserConfirmed
1.5Forgot password — custom 15-minute link/user-forgot-password, /user-reset-password/{key}SupportingConfirmed
1.6View / update own profile & photo/admin/account-profileUserConfirmed
1.7Sign outPOST /logoutUserConfirmed
1.8Breeze scaffolding still liveself-registration, email verification — routes/auth.phpSystemFlagged

Errors are verbatim from the code, typos included: “You Account is deactived”, “The provided credentials do not match our records.”

Roles & permissionsA custom implementation — Spatie's package is not used

#Feature
2.1List rolesGET /admin/roles/AdminConfirmed
2.2Create roleGET|POST /admin/roles/addAdminConfirmed
2.3Attach permissions to a role/admin/roles/{id}/attach-permissionAdminConfirmed
2.4Permission-driven menu & page accessPermissionServiceProvider + Blade @canSystemConfirmed
2.5Direct per-user permissionsusers_permissions tableAdminConfirmed

Employee managementOne record drives leave, payroll, payslip template, tax treatment and appraisal routing

#Feature
3.1Employee list (server-side DataTable)GET /admin/employeesCoreConfirmed
3.2Add employee — step 1: login & personal details/admin/employee/user-details/{eid?}CoreConfirmed
3.3Add employee — step 2: employment details/admin/employee/employee-details/{eid?}CoreConfirmed
3.4Salary history (basic salary over time)/admin/employee/salary-history/{eid?}CoreConfirmed
3.5Opening leave balances & credit/adjustment/admin/employee/current-leaves/{eid?}CoreConfirmed
3.6Leave activity log per employee/admin/employee/current-leaves-logSupportingConfirmed
3.7–3.12Addresses, Passport/NIDA, Qualifications, Medical insurance, Domicile, Department history/admin/employee/{section}SupportingConfirmed
3.13Loan history (EMI schedule feeding payroll)/admin/employee/loan-historyCoreConfirmed
3.14Activate / deactivate employeeGET /admin/employees/status/{id}AdminConfirmed
3.15Bulk employee import from ExcelPOST /admin/employees/importIntegrationConfirmed
3.16Employee transfer between branches/departmentsresource /admin/employee-transferSupportingConfirmed

Onboarding is a multi-step wizard: each step saves its slice and hands back the next URL. The employee id (emp-<year>-<count+1>) is generated on step 1 and used to address every later step.

LeaveBalances feed directly into net pay via loss-of-pay days

#Feature
4.1Leave settings (types & rules)/admin/leavesettings/listAdminConfirmed
4.2Apply for leave / modify an applicationresource /admin/leave_applyCoreConfirmed
4.3Approve or reject a leave application/admin/leave_apply/status/{id}CoreConfirmed
4.4–4.6Live balance lookup, leave-type dropdown, approval-authority dropdownsupporting AJAX endpointsSupportingConfirmed
4.7–4.9Leave balance history, request history, rejected requestsreporting screensReportingConfirmed
4.10Reverse a leave-without-pay deduction/admin/reverse-leave-without-pay/SupportingConfirmed
4.11Maternity leave request & approvalresource /admin/leave-time-approvedCoreConfirmed
4.12–4.13Leave encashment request & approvalresource /admin/leave_encashmentCoreConfirmed
4.14Bulk leave import from ExcelPOST /admin/leave_apply/importIntegrationConfirmed
4.15Leave reports listingresource /admin/leave_reportsReportingFlagged
4.16Monthly leave accrualphp artisan task:monthlyAutomationConfirmed · unscheduled

PayrollTurns a standing salary structure plus a month's events into net pay and GL postings

#Feature
5.1Payroll heads (salary components)resource /admin/payroll/headAdminConfirmed
5.2–5.3Payscale per employee, with live tax calculationresource /admin/payroll/payscaleCoreConfirmed
5.4–5.5Monthly salary generation & status toggleresource /admin/payroll/salaryCoreConfirmed
5.6Print payslip (Tanzanian / IBO layouts)/admin/payroll/print-salary-slip/{id}UserConfirmed
5.7–5.9Tax slab settings, salary increment settings & reportingresource endpointsAdminConfirmed
5.10–5.12Reimbursement types, claims & approvalresource /admin/payroll/reimbursement*CoreConfirmed
5.13Salary settings (pension %, Bomaid %, salary date)resource /admin/payroll/salary_settingAdminConfirmed
5.14–5.16IBO tax report/calc, 13th cheque list/admin/payroll/tax-for-ibo/*, /emp-13th-chequeReportingConfirmed · menu hidden
5.17–5.18TTUM salary report + Excel export/admin/payroll/reports/ttum-viewReportingFlagged
5.19Overtime entriesresource /admin/overtime-settingsSupportingConfirmed
5.20–5.21Loan types & employee loan applicationresource /admin/loans, /employees_loansCoreConfirmed
Disabled in code

createTTum() — which writes the GL posting rows — is commented out in PayrollSalaryController::store(). Salary generation currently does not populate the TTUM report.

Performance appraisalA three-stage KRA-based cycle: self-assessment → reporting authority → final authority

#Feature
6.1KRA attributes (scored assessment factors)resource /admin/kra-attributesAdminConfirmed
6.2–6.3My Appraisal & create/edit self-assessment (9 questions)resource /admin/employee-performanceCoreConfirmed
6.4Reporting-authority feedback/feedback-of-reporting-authority/{id}CoreConfirmed
6.5Final/review-authority feedback/feedback-of-final-authority/{id}CoreConfirmed
6.6Print appraisal/admin/employee-performance/print/{id}UserConfirmed
6.7Download appraisal as PDF/admin/employee-performance/pdf/{id}UserFlagged
6.8Employee KRA recordsresource /admin/employee-kraSupportingConfirmed

Documents

#Feature
7.1Document typesresource /admin/document-typeAdminConfirmed
7.2Upload documents (jpeg/jpg/png/pdf)resource /admin/documentCoreConfirmed
7.3Assign a document to selected employees/admin/document/asignCoreConfirmed
7.4View own assigned documents/admin/personal-info/document-detailsUserConfirmed

Files move to public/assets/document/ under their original filename — a later upload with the same name silently overwrites the earlier one.

Self-service profile16 screens under Personal Information & Personal Profile — visible to everyone except the Managing Director role

Employee detailsFamily detailsDocumentsContact details
Address detailsPassport detailsQualificationsMedical insurance
Driving licencePrevious employmentPlace of domicileTraining details
Award detailsUnion detailsPermanent/contractual statusPayscale details

Master data

MasterTable
Branchesbranches
Departmentsdepartments
Designationsdesignations
Countriescountries, states
Currency settingscurrency_settings
Holidaysholidays
Union membershipsmemberships
Medical card / Bomaid typesmedical_cards
Manage taxtaxes
GL accounts (used by TTUM)accounts

Reporting

#Report
10.1Report type landing page/admin/reports/reportsConfirmed
10.3Annual pay reportApril → MarchConfirmed
10.4Annual tax deduction1 July → 30 JuneConfirmed
10.513th cheque reportDecember → NovemberConfirmed · menu hidden
10.6Branch-wise employee reportConfirmed
10.8Leave reportopening, accrual, adjustment, availed, balanceConfirmed
10.9TTUM report + Excel exportConfirmed

Three different financial-year conventions are in use across these reports — see Business Rules § Financial year.

System & operations

#Feature
11.1–11.2Database backup (mysqldump) & downloadresource /admin/backupsSystemFlagged
11.3In-app notification list/admin/notification/listSupportingConfirmed
11.4User manual download/admin/download/{filename}SupportingConfirmed · tile hidden
11.5Monthly leave accrual commandphp artisan task:monthlyAutomationConfirmed
04

Roles & access model

Access is decided by three independent mechanisms that must be read together: permission gates (what the menu shows), hard-coded role-slug checks (what data scopes actually return), and the isemplooye() helper (self-service vs. everyone's-records views).

Permissions gate the menu, not the route

No route carries a can: middleware, and no controller calls authorize() except PerformanceController. A signed-in user who types a URL directly reaches the controller regardless of permissions — what actually limits them is row-level scoping, where it exists.

The six permission audiences

The permission catalogue targets six audiences. Only three of them (admin, hr_head, employee) are actually carried by a seeded role — see the risk register.

AudiencePurposeCarried by a seeded role?
adminFull system authority — every module, all 184 permissions
hr_headOwns the employee master & HR calendar
employeeSelf-service: leave, encashment, own appraisal, own payslip
chief_managerBranch/department/account masters, employee lifecycle, taxno seeded role
branch_headBranch-level leave approval, performance, reimbursementno seeded role
branch_supervisorView employees/holidays/documents, leave approvalno seeded role

Row-level visibility

ScreenManaging DirectorBranch headHR headEveryone else
Employee listallown branchall*own record only
Leave applicationsallown branch + approval roleall*own only
Encashment / leave reportsallown onlyown onlyown only
Appraisalsallown + authority roleallown + authority role
Payroll salary / loansallmodel scopemodel scopeown only

*Conditional on the HR head's role slug matching what the scope actually tests — flagged in the risk register.

Default administrator

Seeded credentials

admin@hrmstanzania.com / User@123 — created by UserTableSeeder, which also deletes every other user. Imported employees also receive User@123 as their default password. Both must be rotated before any real deployment.

05

Key workflows

Six representative journeys, each traced route → controller → model → database/UI. The full documentation set covers 18; these carry the most operational weight.

Apply for leave

Employee · Leave · happy path
  1. 1Open Leave → Leave Apply/Modify; the list is scoped to what the user is allowed to see.
  2. 2Choose a leave type — the dropdown is filtered to the applicant's employment type, maternity leave excluded.
  3. 3The live balance loads from emp_current_leaves; selecting full-pay halves the displayed balance.
  4. 4Enter dates, reason, approval authority and an optional supporting PDF; submit.
  5. 5The system stores one leave_applies row (status = pending) plus one leave_dates row per calendar day, each flagged for holidays.
Can fail when
  • Overlap — dates collide with an existing non-rejected application.
  • Before joining — start date precedes the employee's start date.
  • Balance — requested days exceed the remaining balance.
  • Missing document — a leave type that requires a certificate has none attached.

Approve or reject a leave application

Approver · Leave
  1. 1Open the status modal from the leave list — shows the application, remaining balance and any overlapping approved leave.
  2. 2Choose approved or reject and enter mandatory remarks.
  3. 3If rejecting: status and remarks are saved and a rejection notification is written.
  4. 4If approving: the balance is re-checked, emp_current_leaves.leave_count is decremented, and an approval notification is written.
Can fail when
  • Insufficient balance at approval time — the request is blocked with the remaining count shown.
  • No current-leave row exists — the decrement is silently skipped and the leave is approved without consuming balance.

Generate monthly salary

Payroll / Finance · Core
  1. 1Choose an employee and pay_for_month_year; only employees with an active payscale are listed.
  2. 2The system derives the pay window: end date = the 20th, walked back over holidays; start date = one month earlier.
  3. 3It counts unpaid/unapproved/half-pay/quarter-pay leave, holidays and approved encashment inside that window.
  4. 4It renders the head grid — local or IBO layout — with computed heads (overtime, loans, reimbursements, pension…) pre-filled.
  5. 5On submit, one payroll_salaries row and one payroll_salary_heads row per head are written.
Can fail when
  • No payscale on or before the window end — returns “Pay Scale not defined”.
  • Tax-slab gap — a taxable amount that falls in the uncovered slab range triggers a null-reference error.

Performance appraisal cycle

Employee → Reporting authority → Final authority · Core
  1. 1Stage 0. Employee completes self-assessment: appraisal type, period, and all 9 fixed questions.
  2. 2Stage 1. The employee's reporting authority scores every KRA attribute and general feedback question (403 if not the correct authority).
  3. 3Stage 2. The review authority enters their own marks, updating (not duplicating) the existing rows.
  4. 4Print or download shows both totals, their average, and a category band (Excellent → Below Standard) that differs for supervisory vs. non-supervisory designations.
Can fail when
  • Not the designated authority — 403 with an explicit message.
  • Reporting feedback submitted twice — there is no duplicate guard on stage 1, so totals can be inflated.

Maternity leave request

Employee → Approver · Core
  1. 1Request via its own screen: employee, dates, reason and a mandatory supporting document.
  2. 2An approver reviews the request.
  3. 3If approved the dates are merged into a synthetic request and fed straight into the normal leave-application flow — quarter-pay if a prior maternity claim exists, half-pay otherwise.

Bulk employee & leave import

HR · Integration
  1. 1Upload an .xls/.xlsx file (≤ 10 MB).
  2. 2Each row is validated independently inside a transaction; unmatched roles/branches/designations are left null rather than failing the row.
  3. 3A summary reports imported vs. skipped counts with per-row error messages — a partial success, not all-or-nothing.
06

Business rules & statuses

Seeded leave entitlement

EmploymentLeave typeDays/yrNotes
TanzanianAnnual Leave30accumulates, max 60
TanzanianMaternity Leave84+16 extended, certificate required
TanzanianSick Leave (Full / Half Pay)63half-pay variant deducts 50% salary
TanzanianCompassionate / Emergency7 / 5capped per application
TanzanianLeave Without Pay0100% deduction, counts holidays
ExpatriateSick Leave15accumulates, certificate required
ExpatriateCasual Leave12max 4 at a time, pro-rata
ExpatriatePrivileged Leave30pro-rata, encashable

Status vocabularies

Leave application

pendingapproved (decrements balance, payslip counts it) or reject (terminal, excluded from balance).

Appraisal stage

0 self-assessed → 1 reporting authority scored → 2 final authority scored.

Leave encashment

pendingapproved or any other value on rejection — the code only branches on the literal string "approved".

Reimbursement

pendingapproved (feeds that month's payroll head) or rejected.

Appraisal category bands

CategorySupervisoryNon-supervisory
Excellent> 80> 85
Very Good71–8076–85
Good61–7061–75
Average51–6046–60
Below Standard< 51< 46

Financial-year conventions

The system uses three different financial-year windows depending on the report — there is no single canonical year.

ReportWindow
Annual pay reportApril → March
Annual tax deduction1 July → 30 June
13th cheque reportDecember → November
Leave report1 January → 31 December
07

Risk register

Twenty-five items in the underlying documentation could not be settled from the repository alone, or show the code contradicting its apparent intent. The ten highest-priority items, ranked by the coverage report:

1. Deleting a leave encashment or a loan deletes the employee's login

Data loss

Both destroy actions call User::destroy() immediately after deleting the record — almost certainly a copy-paste error. Removing a routine record wipes the employee's account.

2. Backups and uploads are served publicly

Data exposure

Database dumps and documents (some potentially medical) live under public/ with no authentication check — reachable by anyone who guesses the filename.

3. Role types and permission audiences barely overlap

Access control

Only 3 of 13 role types match a permission audience. After a clean seed, roles like Manager, IT Officer and Head of Finance receive zero permissions.

4. Inconsistent employment-type strings

Data correctness

An employee stored as "tanzanian" is classified as expatriate by the leave-type selector and receives the IBO payslip layout, while being treated as local for the 24-day month rule.

5. A tax-slab gap causes a fatal error

Availability

The seeded slab table has an inverted bound between 760,001 and 1,000,000 — any salary in that range null-references inside getTaxAmount().

6. Monthly leave accrual is not idempotent and not scheduled

Data correctness

The "once per month" guard is commented out and Laravel's scheduler is empty — running the command twice double-credits leave, and nothing runs it automatically.

7. Public self-registration is enabled

Access control

Breeze's /register route is still live; anyone reaching the app can create an authenticated session with no route-level authorisation stopping them.

8. No login throttling

Access control

The custom login controller bypasses Breeze's rate-limited request class — unlimited password guessing against any known email.

9. Three menu items are permanently hidden by wrong permission slugs

Usability

Employee Transfer, Leave Reports and the Annual Tax report guards reference slugs that don't exist — the routes work, but no one can reach them from the menu, including the administrator.

10. Reporting-authority appraisal feedback can be submitted twice

Data correctness

No uniqueness check or stage guard exists on stage 1 — a duplicate submission inflates the printed total.

Also disabled or missing

Appraisal PDF export (dependency removed) · TTUM GL-posting generation (commented out) · the leave_types table queried by Leave Reports (no migration) · the general-feedback question seeder (not registered in DatabaseSeeder).

08

Coverage & confidence

Generated from a full pass over routes/, app/, database/ and resources/views/ — 68 controllers, 74 models, 71 migrations, 23 seeders, 7 traits and 264 Blade views.

Confidence distribution

Confirmed in code
~90%
Inferred from implementation
~7%
Needs confirmation
~3%

What this document does not cover

Gaps

The live database was not inspected — roles, permissions, leave types, tax slabs and employment-type values are derived from seeders and migrations, and the running system may differ. Screen-level detail comes from controllers, not screenshots. No business brief, ticket history or changelog exists in the repository, so statements of why a rule exists are inferences. Nothing was executed against a live database.

Before publishing to end users

Query the live roles, permissions and employees tables to resolve the role-slug mismatch and the employment-type inconsistency — these determine whether the role matrix and payroll behaviour described here match production. Confirm whether the DomPDF removal and disabled TTUM generation are intentional before documenting either as working.