First add a release flag to payroll.

## SQL

Run:

```sql
ALTER TABLE payroll_details
ADD COLUMN payslip_released TINYINT(1) DEFAULT 0;
```

---

# payroll_release_script.php

```php
<?php

error_reporting(E_ALL);
ini_set('display_errors',1);

require 'config.php';
session_start();

if(!isset($_SESSION['user_id'])){
    header("Location: login.php");
    exit;
}

$role = strtolower($_SESSION['role']);

if(
    !in_array(
        $role,
        ['hr','accounts','admin','superadmin']
    )
){
    die("Access Denied");
}

if(isset($_GET['release'])){

    $id = intval($_GET['release']);

    $stmt = $conn->prepare("
        UPDATE payroll_details
        SET payslip_released = 1
        WHERE id = ?
        AND payroll_status='Disbursed'
    ");

    $stmt->bind_param("i",$id);
    $stmt->execute();

    header("Location: payroll_release.php");
    exit;
}

if(isset($_GET['unrelease'])){

    $id = intval($_GET['unrelease']);

    $stmt = $conn->prepare("
        UPDATE payroll_details
        SET payslip_released = 0
        WHERE id = ?
    ");

    $stmt->bind_param("i",$id);
    $stmt->execute();

    header("Location: payroll_release.php");
    exit;
}

$res = $conn->query("
SELECT

p.*,
e.name,
d.name department

FROM payroll_details p

LEFT JOIN employees e
ON e.id=p.employee_id

LEFT JOIN departments d
ON d.id=e.department_id

WHERE p.payroll_status='Disbursed'

ORDER BY p.generated_at DESC
");
```

---

# payroll_release.php

```php
<?php require 'payroll_release_script.php'; ?>

<!DOCTYPE html>
<html>
<head>

<title>Payslip Release Control</title>

<script src="https://cdn.tailwindcss.com"></script>

</head>

<body class="flex bg-gray-100">

<?php include 'sidebar.php'; ?>

<div class="flex-1 p-8">

<h1 class="text-3xl font-bold mb-6">
Payslip Release Control
</h1>

<div class="bg-white rounded-xl shadow overflow-hidden">

<table class="w-full">

<thead class="bg-gray-100">

<tr>

<th class="p-4 text-left">Employee</th>
<th class="p-4 text-left">Department</th>
<th class="p-4 text-right">Net Salary</th>
<th class="p-4 text-center">Released</th>
<th class="p-4 text-center">Action</th>

</tr>

</thead>

<tbody>

<?php while($row=$res->fetch_assoc()): ?>

<tr class="border-b">

<td class="p-4">
<?= htmlspecialchars($row['name']) ?>
</td>

<td class="p-4">
<?= htmlspecialchars($row['department']) ?>
</td>

<td class="p-4 text-right">
₦<?= number_format($row['net_salary'],2) ?>
</td>

<td class="p-4 text-center">

<?php if($row['payslip_released']): ?>

<span class="bg-green-100 text-green-700 px-3 py-1 rounded-full">
Released
</span>

<?php else: ?>

<span class="bg-red-100 text-red-700 px-3 py-1 rounded-full">
Hidden
</span>

<?php endif; ?>

</td>

<td class="p-4 text-center">

<?php if(!$row['payslip_released']): ?>

<a
href="?release=<?= $row['id'] ?>"
class="bg-green-600 text-white px-4 py-2 rounded">

Release

</a>

<?php else: ?>

<a
href="?unrelease=<?= $row['id'] ?>"
class="bg-red-600 text-white px-4 py-2 rounded">

Hide

</a>

<?php endif; ?>

</td>

</tr>

<?php endwhile; ?>

</tbody>

</table>

</div>

</div>

</body>
</html>
```

---

# UPDATE payslip.php

Find:

```php
if($payslip['payroll_status'] !== 'Paid'){
    die("Payslip has not been released yet.");
}
```

Replace with:

```php
if(
    $payslip['payroll_status'] !== 'Disbursed'
    ||
    !$payslip['payslip_released']
){
    die("Payslip has not been released yet.");
}
```

---

# UPDATE payslip_script.php

Ensure your query includes:

```sql
p.payslip_released
```

Example:

```php
SELECT
p.*,
e.name,
d.name department
```

Since `p.*` already contains all payroll fields, you likely don't need to add anything if you're already selecting `p.*`.

---

# UPDATE payslips.php

Add:

```php
AND p.payroll_status='Disbursed'
AND p.payslip_released=1
```

for staff users.

Example:

```php
if($role == 'staff'){

    $sql .= "
    AND p.payroll_status='Disbursed'
    AND p.payslip_released=1
    ";
}
```

---

# SIDEBAR

Add:

```php
<li>
<a href="payroll_release.php">
Payslip Release
</a>
</li>
```

Visible to:

```php
hr
accounts
admin
superadmin
```

---

After this module, the next file to build should be **payroll_dashboard.php**, which will give HR, Audit, GM, and Accounts a management dashboard showing:

* Total Payroll
* Total PAYE
* Total Pension
* Total Loans
* Total Salary Advances
* Total Overtime
* Total Employees Paid
* Pending Approvals
* Disbursed Payroll

all from your existing payroll tables.






The next module should be:

# payroll_dashboard.php

This becomes the landing page for:

* HR
* Audit
* GM
* Accounts
* Admin

and shows payroll KPIs.

---

## payroll_dashboard.php

```php
<?php

require 'config.php';
session_start();

if(!isset($_SESSION['user_id'])){
    header("Location: login.php");
    exit;
}

$month = intval($_GET['month'] ?? date('m'));
$year  = intval($_GET['year'] ?? date('Y'));

$summary = $conn->query("

SELECT

COUNT(*) employees,

SUM(gross_salary) gross_payroll,

SUM(net_salary) net_payroll,

SUM(tax_amount) total_tax,

SUM(pension_amount) total_pension,

SUM(loan_deduction) total_loans,

SUM(salary_advance_deduction) total_advances,

SUM(overtime_pay) total_overtime

FROM payroll_details

WHERE payroll_month=$month
AND payroll_year=$year

")->fetch_assoc();

$statusSummary = $conn->query("

SELECT

payroll_status,
COUNT(*) total

FROM payroll_details

WHERE payroll_month=$month
AND payroll_year=$year

GROUP BY payroll_status

");

?>

<!DOCTYPE html>
<html>
<head>

<title>Payroll Dashboard</title>

<script src="https://cdn.tailwindcss.com"></script>

</head>

<body class="flex bg-gray-100">

<?php include 'sidebar.php'; ?>

<div class="flex-1 p-8">

<h1 class="text-3xl font-bold mb-6">
Payroll Dashboard
</h1>

<form class="mb-6 flex gap-4">

<select
name="month"
class="border p-3 rounded">

<?php for($m=1;$m<=12;$m++): ?>

<option
value="<?= $m ?>"
<?= $month==$m?'selected':'' ?>>

<?= date('F',mktime(0,0,0,$m,1)) ?>

</option>

<?php endfor; ?>

</select>

<input
type="number"
name="year"
value="<?= $year ?>"
class="border p-3 rounded w-32">

<button class="bg-blue-600 text-white px-5 rounded">
Load
</button>

</form>

<div class="grid md:grid-cols-4 gap-5">

<div class="bg-white p-5 rounded-xl shadow">
<div class="text-gray-500">Employees</div>
<div class="text-3xl font-bold">
<?= number_format($summary['employees']) ?>
</div>
</div>

<div class="bg-white p-5 rounded-xl shadow">
<div class="text-gray-500">Gross Payroll</div>
<div class="text-3xl font-bold text-blue-700">
₦<?= number_format($summary['gross_payroll'],2) ?>
</div>
</div>

<div class="bg-white p-5 rounded-xl shadow">
<div class="text-gray-500">Net Payroll</div>
<div class="text-3xl font-bold text-green-700">
₦<?= number_format($summary['net_payroll'],2) ?>
</div>
</div>

<div class="bg-white p-5 rounded-xl shadow">
<div class="text-gray-500">PAYE</div>
<div class="text-3xl font-bold text-red-700">
₦<?= number_format($summary['total_tax'],2) ?>
</div>
</div>

<div class="bg-white p-5 rounded-xl shadow">
<div class="text-gray-500">Pension</div>
<div class="text-3xl font-bold text-yellow-700">
₦<?= number_format($summary['total_pension'],2) ?>
</div>
</div>

<div class="bg-white p-5 rounded-xl shadow">
<div class="text-gray-500">Loans</div>
<div class="text-3xl font-bold text-red-700">
₦<?= number_format($summary['total_loans'],2) ?>
</div>
</div>

<div class="bg-white p-5 rounded-xl shadow">
<div class="text-gray-500">Salary Advances</div>
<div class="text-3xl font-bold text-red-700">
₦<?= number_format($summary['total_advances'],2) ?>
</div>
</div>

<div class="bg-white p-5 rounded-xl shadow">
<div class="text-gray-500">Overtime</div>
<div class="text-3xl font-bold text-green-700">
₦<?= number_format($summary['total_overtime'],2) ?>
</div>
</div>

</div>

<div class="bg-white mt-8 rounded-xl shadow">

<div class="p-5 border-b">

<h2 class="font-bold text-lg">
Payroll Status Summary
</h2>

</div>

<table class="w-full">

<thead class="bg-gray-100">

<tr>

<th class="p-4 text-left">
Status
</th>

<th class="p-4 text-left">
Employees
</th>

</tr>

</thead>

<tbody>

<?php while($s=$statusSummary->fetch_assoc()): ?>

<tr class="border-b">

<td class="p-4">

<?= htmlspecialchars($s['payroll_status']) ?>

</td>

<td class="p-4">

<?= number_format($s['total']) ?>

</td>

</tr>

<?php endwhile; ?>

</tbody>

</table>

</div>

</div>

</body>
</html>
```

---

# Add Sidebar

```php
<a
href="payroll_dashboard.php"
class="sidebar-link">

Payroll Dashboard

</a>
```

---

### Next module after Dashboard

The next one should be:

```text
payroll_export.php
```

which generates:

```csv
Employee Name
Department
Bank Name
Account Number
Net Salary
```

for upload into:

* Access Bank
* Zenith Bank
* First Bank
* UBA
* GTBank

and becomes the Accounts Department payroll transfer file.

That should be built before Finance Reports because it is used every payroll cycle.




Next is the **Bank Transfer Export Module**.

This allows Accounts to generate a CSV file containing all salaries to upload to the bank.

---

# 1. Verify Employees Table

First check:

```sql
DESCRIBE employees;
```

You should ideally have:

```sql
bank_name
account_number
account_name
```

If not, run:

```sql
ALTER TABLE employees
ADD COLUMN bank_name VARCHAR(100) NULL,
ADD COLUMN account_number VARCHAR(50) NULL,
ADD COLUMN account_name VARCHAR(200) NULL;
```

---

# 2. payroll_export.php

Create:

```php
<?php

require 'config.php';
session_start();

if(!isset($_SESSION['user_id'])){
    header("Location: login.php");
    exit;
}

$role = strtolower($_SESSION['role']);

if(
    !in_array(
        $role,
        ['accounts','admin','superadmin']
    )
){
    die("Access Denied");
}

$month = intval($_GET['month'] ?? date('m'));
$year  = intval($_GET['year'] ?? date('Y'));

if(isset($_POST['export'])){

    header('Content-Type: text/csv');
    header(
        'Content-Disposition: attachment; filename="payroll_export_'.$month.'_'.$year.'.csv"'
    );

    $output = fopen('php://output','w');

    fputcsv($output,[

        'Employee Name',
        'Bank Name',
        'Account Number',
        'Account Name',
        'Net Salary'

    ]);

    $sql = "

    SELECT

    e.name,
    e.bank_name,
    e.account_number,
    e.account_name,

    p.net_salary

    FROM payroll_details p

    INNER JOIN employees e
    ON e.id=p.employee_id

    WHERE p.payroll_month=$month
    AND p.payroll_year=$year

    AND p.payroll_status='GM Approved'

    ORDER BY e.name

    ";

    $res = $conn->query($sql);

    while($row=$res->fetch_assoc()){

        fputcsv($output,[

            $row['name'],
            $row['bank_name'],
            $row['account_number'],
            $row['account_name'],
            $row['net_salary']

        ]);
    }

    fclose($output);
    exit;
}
?>

<!DOCTYPE html>
<html>
<head>

<title>Payroll Bank Export</title>

<script src="https://cdn.tailwindcss.com"></script>

</head>

<body class="flex bg-gray-100">

<?php include 'sidebar.php'; ?>

<div class="flex-1 p-8">

<h1 class="text-3xl font-bold mb-6">

Bank Transfer Export

</h1>

<div class="bg-white p-6 rounded-xl shadow">

<form method="GET" class="flex gap-4 mb-6">

<select
name="month"
class="border p-3 rounded">

<?php for($m=1;$m<=12;$m++): ?>

<option
value="<?= $m ?>"
<?= $month==$m?'selected':'' ?>>

<?= date('F',mktime(0,0,0,$m,1)) ?>

</option>

<?php endfor; ?>

</select>

<input
type="number"
name="year"
value="<?= $year ?>"
class="border p-3 rounded w-32">

<button class="bg-blue-600 text-white px-5 rounded">

Load

</button>

</form>

<form method="POST">

<button
name="export"
class="bg-green-600 text-white px-6 py-3 rounded-lg">

Export CSV

</button>

</form>

</div>

</div>

</body>
</html>
```

---

# 3. Sidebar

Add:

```php
<a
href="payroll_export.php"
class="sidebar-link">

Bank Transfer Export

</a>
```

Visible to:

```php
accounts
admin
superadmin
```

---

# 4. Better Workflow

Current:

```text
HR
↓
Audit
↓
GM
↓
Accounts
↓
Disbursed
```

Recommended:

```text
HR Submit
↓
Audit Approve
↓
GM Approve
↓
Export Bank File
↓
Accounts Upload To Bank
↓
Disbursed
↓
Release Payslips
```

This prevents marking salaries as disbursed before the bank file is actually generated.

---

### Next Module

After this, build:

```text
finance_reports.php
```

This will show:

* Payroll Cost By Department
* Monthly Payroll Trend
* PAYE Liability
* Pension Liability
* Loan Recoveries
* Salary Advance Recoveries
* Overtime Cost

which is usually what the GM and Finance Manager want to see every month.
