Implement 13th Month Pay module with DOLE compliance, back-year filters, and bank CSV export
This commit is contained in:
176
app/Http/Controllers/ThirteenthMonthController.php
Normal file
176
app/Http/Controllers/ThirteenthMonthController.php
Normal file
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Branch;
|
||||
use App\Models\ThirteenthMonthRun;
|
||||
use App\Models\ThirteenthMonthEntry;
|
||||
use App\Services\ThirteenthMonthService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class ThirteenthMonthController extends Controller
|
||||
{
|
||||
protected $service;
|
||||
|
||||
public function __construct(ThirteenthMonthService $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the 13th Month Pay management index page.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$selectedYear = (int) $request->input('year', date('Y'));
|
||||
$selectedBranchId = $request->input('branch_id') ? (int) $request->input('branch_id') : null;
|
||||
$startMonth = (int) $request->input('start_month', 1);
|
||||
$endMonth = (int) $request->input('end_month', 12);
|
||||
|
||||
// Fetch available years for back-year filtering (last 5 years up to next year)
|
||||
$currentYear = (int) date('Y');
|
||||
$availableYears = range($currentYear - 4, $currentYear + 1);
|
||||
|
||||
// Fetch all branches
|
||||
$branches = Branch::all(['id', 'name']);
|
||||
|
||||
// Check if there is an existing run saved for this year and branch
|
||||
$existingRun = ThirteenthMonthRun::where('year', $selectedYear)
|
||||
->when($selectedBranchId, function ($q) use ($selectedBranchId) {
|
||||
$q->where('branch_id', $selectedBranchId);
|
||||
}, function ($q) {
|
||||
$q->whereNull('branch_id');
|
||||
})
|
||||
->with(['entries.employee.user', 'entries.employee.designation', 'entries.employee.department', 'branch', 'creator'])
|
||||
->first();
|
||||
|
||||
if (!$existingRun) {
|
||||
// Generate dynamic preview run on the fly (not saved to DB yet)
|
||||
$existingRun = $this->service->generateRun($selectedYear, $selectedBranchId, $startMonth, $endMonth, null, Auth::id());
|
||||
}
|
||||
|
||||
// Fetch historical saved runs list
|
||||
$pastRuns = ThirteenthMonthRun::with(['branch', 'creator'])
|
||||
->orderBy('year', 'desc')
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
|
||||
return Inertia::render('hr/thirteenth-month/index', [
|
||||
'activeRun' => $existingRun,
|
||||
'pastRuns' => $pastRuns,
|
||||
'branches' => $branches,
|
||||
'availableYears' => array_values($availableYears),
|
||||
'filters' => [
|
||||
'year' => $selectedYear,
|
||||
'branch_id' => $selectedBranchId,
|
||||
'start_month' => $startMonth,
|
||||
'end_month' => $endMonth,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate or recalculate a 13th month batch run.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'year' => 'required|integer|min:2020|max:2035',
|
||||
'branch_id' => 'nullable|exists:branches,id',
|
||||
'start_month' => 'required|integer|min:1|max:12',
|
||||
'end_month' => 'required|integer|min:1|max:12',
|
||||
]);
|
||||
|
||||
$year = (int) $request->year;
|
||||
$branchId = $request->branch_id ? (int) $request->branch_id : null;
|
||||
$startMonth = (int) $request->start_month;
|
||||
$endMonth = (int) $request->end_month;
|
||||
|
||||
$run = $this->service->generateRun($year, $branchId, $startMonth, $endMonth, null, Auth::id());
|
||||
|
||||
return redirect()->back()->with('success', "13th Month Pay for {$year} generated successfully!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an individual employee's adjustment or note.
|
||||
*/
|
||||
public function updateEntry(Request $request, $id)
|
||||
{
|
||||
$request->validate([
|
||||
'adjustment_amount' => 'required|numeric',
|
||||
'notes' => 'nullable|string|max:255',
|
||||
]);
|
||||
|
||||
$entry = ThirteenthMonthEntry::findOrFail($id);
|
||||
$adjustment = (float) $request->adjustment_amount;
|
||||
|
||||
$entry->adjustment_amount = $adjustment;
|
||||
$entry->final_payout = round($entry->computed_amount + $adjustment, 2);
|
||||
if ($request->has('notes')) {
|
||||
$entry->notes = $request->notes;
|
||||
}
|
||||
$entry->save();
|
||||
|
||||
// Recalculate parent run totals
|
||||
$run = $entry->run;
|
||||
if ($run) {
|
||||
$run->total_payout = round($run->entries()->sum('final_payout'), 2);
|
||||
$run->save();
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', '13th Month entry updated successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Approve and lock the 13th Month Pay run.
|
||||
*/
|
||||
public function approveRun(Request $request, $id)
|
||||
{
|
||||
$run = ThirteenthMonthRun::findOrFail($id);
|
||||
$run->status = 'approved';
|
||||
$run->save();
|
||||
|
||||
return redirect()->back()->with('success', "13th Month Pay run for {$run->year} has been approved!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Export 13th Month Payout Schedule as Bank CSV.
|
||||
*/
|
||||
public function exportCsv($id)
|
||||
{
|
||||
$run = ThirteenthMonthRun::with(['entries.employee.user', 'entries.employee.department', 'branch'])->findOrFail($id);
|
||||
|
||||
$filename = "13th_Month_Pay_{$run->year}_Export.csv";
|
||||
$headers = [
|
||||
'Content-Type' => 'text/csv',
|
||||
'Content-Disposition' => "attachment; filename=\"{$filename}\"",
|
||||
];
|
||||
|
||||
$callback = function () use ($run) {
|
||||
$file = fopen('php://output', 'w');
|
||||
fputcsv($file, ['Employee Code', 'Employee Name', 'Department', 'Total Base Earned', 'Computed 13th Month', 'Adjustment', 'Final Net Payout']);
|
||||
|
||||
foreach ($run->entries as $entry) {
|
||||
$empCode = $entry->employee->employee_code ?? "EMP-{$entry->employee_id}";
|
||||
$empName = $entry->employee->user->name ?? 'Staff';
|
||||
$dept = $entry->employee->department->name ?? 'General';
|
||||
|
||||
fputcsv($file, [
|
||||
$empCode,
|
||||
$empName,
|
||||
$dept,
|
||||
number_format($entry->total_base_earned, 2, '.', ''),
|
||||
number_format($entry->computed_amount, 2, '.', ''),
|
||||
number_format($entry->adjustment_amount, 2, '.', ''),
|
||||
number_format($entry->final_payout, 2, '.', ''),
|
||||
]);
|
||||
}
|
||||
|
||||
fclose($file);
|
||||
};
|
||||
|
||||
return response()->stream($callback, 200, $headers);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user