140 lines
5.6 KiB
PHP
140 lines
5.6 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use Illuminate\Console\Command;
|
|
use App\Models\User;
|
|
use App\Models\PayrollRun;
|
|
use App\Models\PayrollEntry;
|
|
use App\Models\Payslip;
|
|
use App\Services\PayrollService;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class SimulatePayslips extends Command
|
|
{
|
|
protected $signature = 'payroll:simulate {start_date?} {end_date?}';
|
|
protected $description = 'Simulate payslip generation based on legacy attendance data';
|
|
|
|
public function handle(PayrollService $payrollService)
|
|
{
|
|
// Authenticate system user so DOMPDF/settings generators don't crash on Auth::user()
|
|
\Illuminate\Support\Facades\Auth::loginUsingId(1);
|
|
|
|
$startDate = $this->argument('start_date') ?: '2026-01-11';
|
|
$endDate = $this->argument('end_date') ?: '2026-01-25';
|
|
|
|
$this->info("Simulating payroll for period: $startDate to $endDate");
|
|
|
|
// 1. Patch Target Users in Legacy table
|
|
$this->info("Patching legacy salaries for target users...");
|
|
// Find target users dynamically
|
|
$anaUser = User::where('name', 'like', '%Ana%')->first();
|
|
if ($anaUser) {
|
|
$anaId = $anaUser->id;
|
|
|
|
// Update her local system ID so PDF reflects the exact legacy formatting
|
|
\App\Models\Employee::where('user_id', $anaId)->update(['employee_id' => 'SEB2018-2008T']);
|
|
|
|
DB::connection('legacy')->table('employee_salary_structures')
|
|
->where('user_id', $anaId)
|
|
->update([
|
|
'gross_salary' => 26000.00,
|
|
'pay_frequency' => 'semi-monthly',
|
|
'daily_rate' => 1000.00
|
|
]);
|
|
|
|
// Scrub false positive records
|
|
\App\Models\AttendanceRecord::where('employee_id', $anaId)
|
|
->whereIn('date', ['2026-01-11', '2026-01-15'])
|
|
->delete();
|
|
}
|
|
|
|
$paulUser = User::where('name', 'like', '%Paul%')->first();
|
|
if ($paulUser) {
|
|
$paulId = $paulUser->id;
|
|
DB::connection('legacy')->table('employee_salary_structures')
|
|
->where('user_id', $paulId)
|
|
->update([
|
|
'gross_salary' => 22000.00,
|
|
'pay_frequency' => 'semi-monthly',
|
|
'daily_rate' => 846.15
|
|
]);
|
|
}
|
|
|
|
// 2. Clear previous simulation runs to prevent duplicates
|
|
PayrollRun::where('title', 'like', 'Simulation%')->delete();
|
|
|
|
// 3. Create a Payroll Run
|
|
$payrollRun = PayrollRun::create([
|
|
'title' => 'Simulation Run ' . Carbon::now()->format('Y-m-d H:i:s'),
|
|
'pay_period_start' => $startDate,
|
|
'pay_period_end' => $endDate,
|
|
'pay_date' => Carbon::parse($endDate)->addDays(1)->toDateString(),
|
|
'status' => 'completed',
|
|
'created_by' => 1,
|
|
]);
|
|
|
|
$employees = User::where('type', 'employee')->whereHas('employee')->get();
|
|
$bar = $this->output->createProgressBar(count($employees));
|
|
|
|
$generatedFiles = [];
|
|
|
|
foreach ($employees as $user) {
|
|
$employee = $user->employee;
|
|
if (!$employee) continue;
|
|
|
|
$computation = $payrollService->calculateForPeriod($employee, $startDate, $endDate);
|
|
|
|
// Create Payroll Entry
|
|
$payrollEntry = PayrollEntry::create([
|
|
'payroll_run_id' => $payrollRun->id,
|
|
'employee_id' => $user->id,
|
|
'basic_salary' => $computation['basic_salary'],
|
|
'gross_pay' => $computation['total_earnings'],
|
|
'deductions' => $computation['total_deductions'],
|
|
'net_pay' => $computation['net_pay'],
|
|
'earnings_breakdown' => $computation['earnings'],
|
|
'deductions_breakdown' => $computation['deductions'],
|
|
'created_by' => 1,
|
|
]);
|
|
|
|
// Save summary explicitly to the employee model temporarily, or we inject it into the payslip view later.
|
|
// Since we use strict JSON in real life, let's attach the summary to earnings_breakdown as meta to easily read it.
|
|
// Actually, we can add it to the payslip or just rewrite template to read custom fields.
|
|
// For now, I'll sneak it into earnings_breakdown['summary']
|
|
$earningsBreakdown = $computation['earnings'];
|
|
$earningsBreakdown['summary'] = $computation['summary'];
|
|
$earningsBreakdown['meta'] = [
|
|
'gross_monthly' => $computation['gross_monthly'],
|
|
'daily_rate' => $computation['daily_rate'],
|
|
'pay_type' => $computation['pay_type'],
|
|
];
|
|
$payrollEntry->update(['earnings_breakdown' => $earningsBreakdown]);
|
|
|
|
// Create Payslip
|
|
$payslip = Payslip::create([
|
|
'payroll_entry_id' => $payrollEntry->id,
|
|
'employee_id' => $user->id,
|
|
'payslip_number' => Payslip::generatePayslipNumber($employee->employee_id, $payrollRun->pay_date),
|
|
'pay_period_start' => $startDate,
|
|
'pay_period_end' => $endDate,
|
|
'pay_date' => $payrollRun->pay_date,
|
|
'status' => 'generated',
|
|
'created_by' => 1,
|
|
]);
|
|
|
|
$path = $payslip->generatePDF();
|
|
$generatedFiles[] = $path;
|
|
|
|
$bar->advance();
|
|
}
|
|
|
|
$bar->finish();
|
|
$this->info("\nSimulation Complete! Generated " . count($generatedFiles) . " payslips:");
|
|
foreach ($generatedFiles as $file) {
|
|
$this->line(" - public/storage/$file");
|
|
}
|
|
}
|
|
}
|