From 2c940ee7fd2b607acfcbd356f63f12e6a52bfbdb Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 28 Apr 2026 22:08:13 +0800 Subject: [PATCH] feat(roster): add legacy roster sync and propagation seeders --- database/data/employee_patterns.json | 302 ++++++++++++++++++++ database/seeders/LegacyRosterSyncSeeder.php | 59 ++++ database/seeders/PropagateRosterSeeder.php | 85 ++++++ 3 files changed, 446 insertions(+) create mode 100644 database/data/employee_patterns.json create mode 100644 database/seeders/LegacyRosterSyncSeeder.php create mode 100644 database/seeders/PropagateRosterSeeder.php diff --git a/database/data/employee_patterns.json b/database/data/employee_patterns.json new file mode 100644 index 000000000..8972209c1 --- /dev/null +++ b/database/data/employee_patterns.json @@ -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 + } + } +} \ No newline at end of file diff --git a/database/seeders/LegacyRosterSyncSeeder.php b/database/seeders/LegacyRosterSyncSeeder.php new file mode 100644 index 000000000..70ee89882 --- /dev/null +++ b/database/seeders/LegacyRosterSyncSeeder.php @@ -0,0 +1,59 @@ +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."); + } +} diff --git a/database/seeders/PropagateRosterSeeder.php b/database/seeders/PropagateRosterSeeder.php new file mode 100644 index 000000000..2317821ab --- /dev/null +++ b/database/seeders/PropagateRosterSeeder.php @@ -0,0 +1,85 @@ +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."); + } +}