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.
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:
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.
Not a flat annual grant — it accrues month by month with per-type caps and pro-rata rules.
Several heads (overtime, reimbursements, loans, pension, arrears) are computed at runtime, not typed in.
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.
Technology snapshot
| Aspect | Value |
|---|---|
| Language / framework | PHP ^8.1, Laravel ^10.0 |
| Database | MySQL, 71 migrations |
| UI | Server-rendered Blade — 264 templates, no SPA |
| Tables | yajra/laravel-datatables (server-side AJAX) |
| Excel | maatwebsite/excel — employee & leave import, one TTUM export |
| Front-end build | None usable — no package.json; assets are pre-compiled |
| Tests | Pest — stock Breeze auth/profile tests only |
| Third-party APIs | None. Self-contained — no payment gateway, SMS, SSO or webhooks |
PDF export is broken — barryvdh/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.
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.1 | Sign in (email + password)GET|POST / , GET|POST /login | CoreConfirmed |
| 1.2 | Account-deactivation block at loginis_active check | SupportingConfirmed |
| 1.3 | Forced password change on first loginGET /admin/password-reset | SupportingConfirmed |
| 1.4 | Change own passwordPOST /admin/password-update | UserConfirmed |
| 1.5 | Forgot password — custom 15-minute link/user-forgot-password, /user-reset-password/{key} | SupportingConfirmed |
| 1.6 | View / update own profile & photo/admin/account-profile | UserConfirmed |
| 1.7 | Sign outPOST /logout | UserConfirmed |
| 1.8 | Breeze scaffolding still liveself-registration, email verification — routes/auth.php | SystemFlagged |
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.1 | List rolesGET /admin/roles/ | AdminConfirmed |
| 2.2 | Create roleGET|POST /admin/roles/add | AdminConfirmed |
| 2.3 | Attach permissions to a role/admin/roles/{id}/attach-permission | AdminConfirmed |
| 2.4 | Permission-driven menu & page accessPermissionServiceProvider + Blade @can | SystemConfirmed |
| 2.5 | Direct per-user permissionsusers_permissions table | AdminConfirmed |
Employee managementOne record drives leave, payroll, payslip template, tax treatment and appraisal routing
| # | Feature | |
|---|---|---|
| 3.1 | Employee list (server-side DataTable)GET /admin/employees | CoreConfirmed |
| 3.2 | Add employee — step 1: login & personal details/admin/employee/user-details/{eid?} | CoreConfirmed |
| 3.3 | Add employee — step 2: employment details/admin/employee/employee-details/{eid?} | CoreConfirmed |
| 3.4 | Salary history (basic salary over time)/admin/employee/salary-history/{eid?} | CoreConfirmed |
| 3.5 | Opening leave balances & credit/adjustment/admin/employee/current-leaves/{eid?} | CoreConfirmed |
| 3.6 | Leave activity log per employee/admin/employee/current-leaves-log | SupportingConfirmed |
| 3.7–3.12 | Addresses, Passport/NIDA, Qualifications, Medical insurance, Domicile, Department history/admin/employee/{section} | SupportingConfirmed |
| 3.13 | Loan history (EMI schedule feeding payroll)/admin/employee/loan-history | CoreConfirmed |
| 3.14 | Activate / deactivate employeeGET /admin/employees/status/{id} | AdminConfirmed |
| 3.15 | Bulk employee import from ExcelPOST /admin/employees/import | IntegrationConfirmed |
| 3.16 | Employee transfer between branches/departmentsresource /admin/employee-transfer | SupportingConfirmed |
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.1 | Leave settings (types & rules)/admin/leavesettings/list | AdminConfirmed |
| 4.2 | Apply for leave / modify an applicationresource /admin/leave_apply | CoreConfirmed |
| 4.3 | Approve or reject a leave application/admin/leave_apply/status/{id} | CoreConfirmed |
| 4.4–4.6 | Live balance lookup, leave-type dropdown, approval-authority dropdownsupporting AJAX endpoints | SupportingConfirmed |
| 4.7–4.9 | Leave balance history, request history, rejected requestsreporting screens | ReportingConfirmed |
| 4.10 | Reverse a leave-without-pay deduction/admin/reverse-leave-without-pay/ | SupportingConfirmed |
| 4.11 | Maternity leave request & approvalresource /admin/leave-time-approved | CoreConfirmed |
| 4.12–4.13 | Leave encashment request & approvalresource /admin/leave_encashment | CoreConfirmed |
| 4.14 | Bulk leave import from ExcelPOST /admin/leave_apply/import | IntegrationConfirmed |
| 4.15 | Leave reports listingresource /admin/leave_reports | ReportingFlagged |
| 4.16 | Monthly leave accrualphp artisan task:monthly | AutomationConfirmed · unscheduled |
PayrollTurns a standing salary structure plus a month's events into net pay and GL postings
| # | Feature | |
|---|---|---|
| 5.1 | Payroll heads (salary components)resource /admin/payroll/head | AdminConfirmed |
| 5.2–5.3 | Payscale per employee, with live tax calculationresource /admin/payroll/payscale | CoreConfirmed |
| 5.4–5.5 | Monthly salary generation & status toggleresource /admin/payroll/salary | CoreConfirmed |
| 5.6 | Print payslip (Tanzanian / IBO layouts)/admin/payroll/print-salary-slip/{id} | UserConfirmed |
| 5.7–5.9 | Tax slab settings, salary increment settings & reportingresource endpoints | AdminConfirmed |
| 5.10–5.12 | Reimbursement types, claims & approvalresource /admin/payroll/reimbursement* | CoreConfirmed |
| 5.13 | Salary settings (pension %, Bomaid %, salary date)resource /admin/payroll/salary_setting | AdminConfirmed |
| 5.14–5.16 | IBO tax report/calc, 13th cheque list/admin/payroll/tax-for-ibo/*, /emp-13th-cheque | ReportingConfirmed · menu hidden |
| 5.17–5.18 | TTUM salary report + Excel export/admin/payroll/reports/ttum-view | ReportingFlagged |
| 5.19 | Overtime entriesresource /admin/overtime-settings | SupportingConfirmed |
| 5.20–5.21 | Loan types & employee loan applicationresource /admin/loans, /employees_loans | CoreConfirmed |
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.1 | KRA attributes (scored assessment factors)resource /admin/kra-attributes | AdminConfirmed |
| 6.2–6.3 | My Appraisal & create/edit self-assessment (9 questions)resource /admin/employee-performance | CoreConfirmed |
| 6.4 | Reporting-authority feedback/feedback-of-reporting-authority/{id} | CoreConfirmed |
| 6.5 | Final/review-authority feedback/feedback-of-final-authority/{id} | CoreConfirmed |
| 6.6 | Print appraisal/admin/employee-performance/print/{id} | UserConfirmed |
| 6.7 | Download appraisal as PDF/admin/employee-performance/pdf/{id} | UserFlagged |
| 6.8 | Employee KRA recordsresource /admin/employee-kra | SupportingConfirmed |
Documents
| # | Feature | |
|---|---|---|
| 7.1 | Document typesresource /admin/document-type | AdminConfirmed |
| 7.2 | Upload documents (jpeg/jpg/png/pdf)resource /admin/document | CoreConfirmed |
| 7.3 | Assign a document to selected employees/admin/document/asign | CoreConfirmed |
| 7.4 | View own assigned documents/admin/personal-info/document-details | UserConfirmed |
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 details | Family details | Documents | Contact details |
|---|---|---|---|
| Address details | Passport details | Qualifications | Medical insurance |
| Driving licence | Previous employment | Place of domicile | Training details |
| Award details | Union details | Permanent/contractual status | Payscale details |
Master data
| Master | Table |
|---|---|
| Branches | branches |
| Departments | departments |
| Designations | designations |
| Countries | countries, states |
| Currency settings | currency_settings |
| Holidays | holidays |
| Union memberships | memberships |
| Medical card / Bomaid types | medical_cards |
| Manage tax | taxes |
| GL accounts (used by TTUM) | accounts |
Reporting
| # | Report | |
|---|---|---|
| 10.1 | Report type landing page/admin/reports/reports | Confirmed |
| 10.3 | Annual pay reportApril → March | Confirmed |
| 10.4 | Annual tax deduction1 July → 30 June | Confirmed |
| 10.5 | 13th cheque reportDecember → November | Confirmed · menu hidden |
| 10.6 | Branch-wise employee report | Confirmed |
| 10.8 | Leave reportopening, accrual, adjustment, availed, balance | Confirmed |
| 10.9 | TTUM report + Excel export | Confirmed |
Three different financial-year conventions are in use across these reports — see Business Rules § Financial year.
System & operations
| # | Feature | |
|---|---|---|
| 11.1–11.2 | Database backup (mysqldump) & downloadresource /admin/backups | SystemFlagged |
| 11.3 | In-app notification list/admin/notification/list | SupportingConfirmed |
| 11.4 | User manual download/admin/download/{filename} | SupportingConfirmed · tile hidden |
| 11.5 | Monthly leave accrual commandphp artisan task:monthly | AutomationConfirmed |
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).
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.
| Audience | Purpose | Carried by a seeded role? |
|---|---|---|
| admin | Full system authority — every module, all 184 permissions | ✓ |
| hr_head | Owns the employee master & HR calendar | ✓ |
| employee | Self-service: leave, encashment, own appraisal, own payslip | ✓ |
| chief_manager | Branch/department/account masters, employee lifecycle, tax | no seeded role |
| branch_head | Branch-level leave approval, performance, reimbursement | no seeded role |
| branch_supervisor | View employees/holidays/documents, leave approval | no seeded role |
Row-level visibility
| Screen | Managing Director | Branch head | HR head | Everyone else |
|---|---|---|---|---|
| Employee list | all | own branch | all* | own record only |
| Leave applications | all | own branch + approval role | all* | own only |
| Encashment / leave reports | all | own only | own only | own only |
| Appraisals | all | own + authority role | all | own + authority role |
| Payroll salary / loans | all | model scope | model scope | own only |
*Conditional on the HR head's role slug matching what the scope actually tests — flagged in the risk register.
Default administrator
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.
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
- 1Open Leave → Leave Apply/Modify; the list is scoped to what the user is allowed to see.
- 2Choose a leave type — the dropdown is filtered to the applicant's employment type, maternity leave excluded.
- 3The live balance loads from
emp_current_leaves; selecting full-pay halves the displayed balance. - 4Enter dates, reason, approval authority and an optional supporting PDF; submit.
- 5The system stores one
leave_appliesrow (status = pending) plus oneleave_datesrow per calendar day, each flagged for holidays.
- 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
- 1Open the status modal from the leave list — shows the application, remaining balance and any overlapping approved leave.
- 2Choose approved or reject and enter mandatory remarks.
- 3If rejecting: status and remarks are saved and a rejection notification is written.
- 4If approving: the balance is re-checked,
emp_current_leaves.leave_countis decremented, and an approval notification is written.
- 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
- 1Choose an employee and
pay_for_month_year; only employees with an active payscale are listed. - 2The system derives the pay window: end date = the 20th, walked back over holidays; start date = one month earlier.
- 3It counts unpaid/unapproved/half-pay/quarter-pay leave, holidays and approved encashment inside that window.
- 4It renders the head grid — local or IBO layout — with computed heads (overtime, loans, reimbursements, pension…) pre-filled.
- 5On submit, one
payroll_salariesrow and onepayroll_salary_headsrow per head are written.
- 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
- 1Stage 0. Employee completes self-assessment: appraisal type, period, and all 9 fixed questions.
- 2Stage 1. The employee's reporting authority scores every KRA attribute and general feedback question (403 if not the correct authority).
- 3Stage 2. The review authority enters their own marks, updating (not duplicating) the existing rows.
- 4Print or download shows both totals, their average, and a category band (Excellent → Below Standard) that differs for supervisory vs. non-supervisory designations.
- 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
- 1Request via its own screen: employee, dates, reason and a mandatory supporting document.
- 2An approver reviews the request.
- 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
- 1Upload an
.xls/.xlsxfile (≤ 10 MB). - 2Each row is validated independently inside a transaction; unmatched roles/branches/designations are left null rather than failing the row.
- 3A summary reports imported vs. skipped counts with per-row error messages — a partial success, not all-or-nothing.
Business rules & statuses
Seeded leave entitlement
| Employment | Leave type | Days/yr | Notes |
|---|---|---|---|
| Tanzanian | Annual Leave | 30 | accumulates, max 60 |
| Tanzanian | Maternity Leave | 84 | +16 extended, certificate required |
| Tanzanian | Sick Leave (Full / Half Pay) | 63 | half-pay variant deducts 50% salary |
| Tanzanian | Compassionate / Emergency | 7 / 5 | capped per application |
| Tanzanian | Leave Without Pay | 0 | 100% deduction, counts holidays |
| Expatriate | Sick Leave | 15 | accumulates, certificate required |
| Expatriate | Casual Leave | 12 | max 4 at a time, pro-rata |
| Expatriate | Privileged Leave | 30 | pro-rata, encashable |
Status vocabularies
pending → approved (decrements balance, payslip counts it) or reject (terminal, excluded from balance).
0 self-assessed → 1 reporting authority scored → 2 final authority scored.
pending → approved or any other value on rejection — the code only branches on the literal string "approved".
pending → approved (feeds that month's payroll head) or rejected.
Appraisal category bands
| Category | Supervisory | Non-supervisory |
|---|---|---|
| Excellent | > 80 | > 85 |
| Very Good | 71–80 | 76–85 |
| Good | 61–70 | 61–75 |
| Average | 51–60 | 46–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.
| Report | Window |
|---|---|
| Annual pay report | April → March |
| Annual tax deduction | 1 July → 30 June |
| 13th cheque report | December → November |
| Leave report | 1 January → 31 December |
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 lossBoth 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 exposureDatabase 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 controlOnly 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 correctnessAn 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
AvailabilityThe 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 correctnessThe "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 controlBreeze'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 controlThe 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
UsabilityEmployee 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 correctnessNo uniqueness check or stage guard exists on stage 1 — a duplicate submission inflates the printed total.
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).
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
What this document does not cover
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.