feat(roster): add legacy roster sync and propagation seeders
This commit is contained in:
302
database/data/employee_patterns.json
Normal file
302
database/data/employee_patterns.json
Normal file
@@ -0,0 +1,302 @@
|
||||
{
|
||||
"SEB2018-2008T": {
|
||||
"1": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"2": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"4": {
|
||||
"shift": "OPS 05:30PM-02:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"5": {
|
||||
"shift": "OPS 05:30PM-02:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"3": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"6": {
|
||||
"shift": null,
|
||||
"is_rd": true
|
||||
},
|
||||
"0": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
}
|
||||
},
|
||||
"SEB2023-2068H": {
|
||||
"2": {
|
||||
"shift": "OPS 08:00AM-05:00PM",
|
||||
"is_rd": false
|
||||
},
|
||||
"4": {
|
||||
"shift": "OPS 08:00AM-05:00PM",
|
||||
"is_rd": false
|
||||
},
|
||||
"6": {
|
||||
"shift": null,
|
||||
"is_rd": true
|
||||
},
|
||||
"0": {
|
||||
"shift": "OPS 08:00AM-05:00PM",
|
||||
"is_rd": false
|
||||
},
|
||||
"1": {
|
||||
"shift": "OPS 08:00AM-05:00PM",
|
||||
"is_rd": false
|
||||
},
|
||||
"3": {
|
||||
"shift": "OPS 08:00AM-05:00PM",
|
||||
"is_rd": false
|
||||
},
|
||||
"5": {
|
||||
"shift": null,
|
||||
"is_rd": true
|
||||
}
|
||||
},
|
||||
"EMP-001": {
|
||||
"1": {
|
||||
"shift": "OPS 08:00AM-05:00PM",
|
||||
"is_rd": false
|
||||
},
|
||||
"6": {
|
||||
"shift": null,
|
||||
"is_rd": true
|
||||
},
|
||||
"5": {
|
||||
"shift": null,
|
||||
"is_rd": true
|
||||
},
|
||||
"0": {
|
||||
"shift": "OPS 08:00AM-05:00PM",
|
||||
"is_rd": false
|
||||
},
|
||||
"2": {
|
||||
"shift": "OPS 08:00AM-05:00PM",
|
||||
"is_rd": false
|
||||
},
|
||||
"3": {
|
||||
"shift": "OPS 08:00AM-05:00PM",
|
||||
"is_rd": false
|
||||
},
|
||||
"4": {
|
||||
"shift": "OPS 08:00AM-05:00PM",
|
||||
"is_rd": false
|
||||
}
|
||||
},
|
||||
"SEB2024-2109T": {
|
||||
"1": {
|
||||
"shift": null,
|
||||
"is_rd": true
|
||||
},
|
||||
"6": {
|
||||
"shift": null,
|
||||
"is_rd": true
|
||||
},
|
||||
"0": {
|
||||
"shift": null,
|
||||
"is_rd": true
|
||||
},
|
||||
"2": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"3": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"4": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"5": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
}
|
||||
},
|
||||
"SEB2018-2024T": {
|
||||
"6": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"0": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"1": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"2": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"3": {
|
||||
"shift": null,
|
||||
"is_rd": true
|
||||
},
|
||||
"4": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"5": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
}
|
||||
},
|
||||
"SEB2024-2101T": {
|
||||
"6": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"0": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"1": {
|
||||
"shift": null,
|
||||
"is_rd": true
|
||||
},
|
||||
"2": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"3": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"4": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"5": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
}
|
||||
},
|
||||
"SEB2018-2023T": {
|
||||
"6": {
|
||||
"shift": null,
|
||||
"is_rd": true
|
||||
},
|
||||
"0": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"1": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"2": {
|
||||
"shift": "OPS 05:30PM-02:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"3": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"4": {
|
||||
"shift": "OPS 05:30PM-02:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"5": {
|
||||
"shift": "OPS 05:30PM-02:30AM",
|
||||
"is_rd": false
|
||||
}
|
||||
},
|
||||
"SEB2018-2013T": {
|
||||
"6": {
|
||||
"shift": null,
|
||||
"is_rd": true
|
||||
},
|
||||
"0": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"1": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"2": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"3": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"4": {
|
||||
"shift": "OPS 05:30PM-02:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"5": {
|
||||
"shift": null,
|
||||
"is_rd": true
|
||||
}
|
||||
},
|
||||
"SEB2018-2033T": {
|
||||
"6": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"0": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"1": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"2": {
|
||||
"shift": null,
|
||||
"is_rd": true
|
||||
},
|
||||
"3": {
|
||||
"shift": null,
|
||||
"is_rd": true
|
||||
},
|
||||
"4": {
|
||||
"shift": "OPS 05:30PM-02:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"5": {
|
||||
"shift": "OPS 05:30PM-02:30AM",
|
||||
"is_rd": false
|
||||
}
|
||||
},
|
||||
"SEB2024-2111T": {
|
||||
"6": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"0": {
|
||||
"shift": null,
|
||||
"is_rd": true
|
||||
},
|
||||
"1": {
|
||||
"shift": null,
|
||||
"is_rd": true
|
||||
},
|
||||
"2": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"3": {
|
||||
"shift": "OPS 03:30PM-12:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"4": {
|
||||
"shift": "OPS 05:30PM-02:30AM",
|
||||
"is_rd": false
|
||||
},
|
||||
"5": {
|
||||
"shift": "OPS 05:30PM-02:30AM",
|
||||
"is_rd": false
|
||||
}
|
||||
}
|
||||
}
|
||||
59
database/seeders/LegacyRosterSyncSeeder.php
Normal file
59
database/seeders/LegacyRosterSyncSeeder.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\User;
|
||||
use App\Models\Employee;
|
||||
use App\Models\Shift;
|
||||
use App\Models\AttendanceRecord;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
class LegacyRosterSyncSeeder extends Seeder
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$jsonPath = base_path('scratch/legacy_roster.json');
|
||||
|
||||
if (!File::exists($jsonPath)) {
|
||||
$this->command->error("JSON file not found at $jsonPath");
|
||||
return;
|
||||
}
|
||||
|
||||
$roster = json_decode(File::get($jsonPath), true);
|
||||
$total = count($roster);
|
||||
$count = 0;
|
||||
|
||||
$this->command->getOutput()->progressStart($total);
|
||||
|
||||
foreach ($roster as $item) {
|
||||
$employee = Employee::where('employee_id', $item['code'])->first();
|
||||
|
||||
if ($employee) {
|
||||
$shift = null;
|
||||
if ($item['shift_name']) {
|
||||
$shift = Shift::where('name', $item['shift_name'])->first();
|
||||
}
|
||||
|
||||
// Create or update the attendance record (Roster)
|
||||
AttendanceRecord::updateOrCreate(
|
||||
[
|
||||
'employee_id' => $employee->user_id, // Attendance uses user_id as employee_id relationship
|
||||
'date' => $item['date']
|
||||
],
|
||||
[
|
||||
'shift_id' => $shift ? $shift->id : null,
|
||||
'is_rest_day' => $item['is_rest_day'],
|
||||
'status' => 'absent', // Roster placeholder
|
||||
'created_by' => 1
|
||||
]
|
||||
);
|
||||
}
|
||||
$this->command->getOutput()->progressAdvance();
|
||||
$count++;
|
||||
}
|
||||
|
||||
$this->command->getOutput()->progressFinish();
|
||||
$this->command->info("Synced $count roster records successfully.");
|
||||
}
|
||||
}
|
||||
85
database/seeders/PropagateRosterSeeder.php
Normal file
85
database/seeders/PropagateRosterSeeder.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\User;
|
||||
use App\Models\Employee;
|
||||
use App\Models\Shift;
|
||||
use App\Models\AttendanceRecord;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class PropagateRosterSeeder extends Seeder
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$jsonPath = database_path('data/employee_patterns.json');
|
||||
|
||||
if (!File::exists($jsonPath)) {
|
||||
$this->command->error("Patterns file not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
$patterns = json_decode(File::get($jsonPath), true);
|
||||
|
||||
// Months to seed: April and May 2026
|
||||
$months = [
|
||||
['year' => 2026, 'month' => 4],
|
||||
['year' => 2026, 'month' => 5],
|
||||
];
|
||||
|
||||
$employees = Employee::all();
|
||||
$total = $employees->count();
|
||||
|
||||
foreach ($months as $m) {
|
||||
$startDate = Carbon::create($m['year'], $m['month'], 1);
|
||||
$endDate = $startDate->copy()->endOfMonth();
|
||||
|
||||
$this->command->info("Propagating patterns into " . $startDate->format('F Y') . "...");
|
||||
$this->command->getOutput()->progressStart($total);
|
||||
|
||||
foreach ($employees as $emp) {
|
||||
$pattern = $patterns[$emp->employee_id] ?? null;
|
||||
|
||||
if (!$pattern) {
|
||||
$this->command->getOutput()->progressAdvance();
|
||||
continue;
|
||||
}
|
||||
|
||||
$current = $startDate->copy();
|
||||
while ($current->lte($endDate)) {
|
||||
$pyDow = $current->dayOfWeekIso - 1; // 0=Mon, 6=Sun
|
||||
$assignment = $pattern[$pyDow] ?? null;
|
||||
|
||||
if ($assignment) {
|
||||
$shift = null;
|
||||
if ($assignment['shift']) {
|
||||
$shift = Shift::where('name', $assignment['shift'])->first();
|
||||
}
|
||||
|
||||
AttendanceRecord::updateOrCreate(
|
||||
[
|
||||
'employee_id' => $emp->user_id,
|
||||
'date' => $current->toDateString()
|
||||
],
|
||||
[
|
||||
'shift_id' => $shift ? $shift->id : null,
|
||||
'is_rest_day' => $assignment['is_rd'],
|
||||
'status' => 'absent',
|
||||
'created_by' => 1
|
||||
]
|
||||
);
|
||||
}
|
||||
$current->addDay();
|
||||
}
|
||||
|
||||
$this->command->getOutput()->progressAdvance();
|
||||
}
|
||||
|
||||
$this->command->getOutput()->progressFinish();
|
||||
}
|
||||
|
||||
$this->command->info("Propagated roster for April and May 2026 successfully.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user