reconstruction of the project
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Api\Attendance;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AdminAttendanceApiControllerTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_daily_data_returns_success(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->getJson('/api/attendance/admin/daily');
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Attendance;
|
||||
|
||||
use App\Services\Attendance\AttendancePolicyService;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class AttendancePolicyServiceTest extends TestCase
|
||||
{
|
||||
public function test_teacher_can_edit_draft_day(): void
|
||||
{
|
||||
$service = new AttendancePolicyService();
|
||||
|
||||
$day = ['status' => 'draft'];
|
||||
$user = [
|
||||
'roles' => ['teacher'],
|
||||
'permissions' => [],
|
||||
];
|
||||
|
||||
$this->assertTrue($service->canEditDay($day, $user));
|
||||
}
|
||||
|
||||
public function test_teacher_cannot_edit_finalized_day(): void
|
||||
{
|
||||
$service = new AttendancePolicyService();
|
||||
|
||||
$day = ['status' => 'finalized'];
|
||||
$user = [
|
||||
'roles' => ['teacher'],
|
||||
'permissions' => [],
|
||||
];
|
||||
|
||||
$this->assertFalse($service->canEditDay($day, $user));
|
||||
}
|
||||
|
||||
public function test_admin_can_edit_finalized_day(): void
|
||||
{
|
||||
$service = new AttendancePolicyService();
|
||||
|
||||
$day = ['status' => 'finalized'];
|
||||
$user = [
|
||||
'roles' => ['admin'],
|
||||
'permissions' => [],
|
||||
];
|
||||
|
||||
$this->assertTrue($service->canEditDay($day, $user));
|
||||
}
|
||||
|
||||
public function test_is_admin_like_excludes_teacher_roles(): void
|
||||
{
|
||||
$service = new AttendancePolicyService();
|
||||
|
||||
$this->assertFalse($service->isAdminLike(['teacher']));
|
||||
$this->assertFalse($service->isAdminLike(['teacher_assistant']));
|
||||
$this->assertTrue($service->isAdminLike(['principal']));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Attendance;
|
||||
|
||||
use App\Models\AttendanceRecord;
|
||||
use App\Services\Attendance\AttendanceRecordSyncService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AttendanceRecordSyncServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected AttendanceRecordSyncService $service;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->service = app(AttendanceRecordSyncService::class);
|
||||
}
|
||||
|
||||
public function test_update_attendance_record_increments_and_decrements_counters(): void
|
||||
{
|
||||
$record = AttendanceRecord::query()->create([
|
||||
'class_section_id' => 10,
|
||||
'student_id' => 200,
|
||||
'school_id' => 'SCH-200',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'total_presence' => 5,
|
||||
'total_absence' => 2,
|
||||
'total_late' => 1,
|
||||
'total_attendance' => 8,
|
||||
'modified_by' => 1,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$this->service->updateAttendanceRecord(
|
||||
studentId: 200,
|
||||
schoolId: 'SCH-200',
|
||||
newStatus: 'absent',
|
||||
semester: 'Fall',
|
||||
schoolYear: '2025-2026',
|
||||
oldStatus: 'present',
|
||||
modifiedBy: 99
|
||||
);
|
||||
|
||||
$record->refresh();
|
||||
|
||||
$this->assertSame(4, (int)$record->total_presence);
|
||||
$this->assertSame(3, (int)$record->total_absence);
|
||||
$this->assertSame(1, (int)$record->total_late);
|
||||
$this->assertSame(99, (int)$record->modified_by);
|
||||
}
|
||||
|
||||
public function test_update_attendance_record_does_nothing_when_record_missing(): void
|
||||
{
|
||||
$this->service->updateAttendanceRecord(
|
||||
studentId: 999,
|
||||
schoolId: 'SCH-999',
|
||||
newStatus: 'late',
|
||||
semester: 'Fall',
|
||||
schoolYear: '2025-2026',
|
||||
oldStatus: 'present',
|
||||
modifiedBy: 10
|
||||
);
|
||||
|
||||
$this->assertDatabaseMissing('attendance_record', [
|
||||
'student_id' => 999,
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_update_attendance_record_never_goes_below_zero(): void
|
||||
{
|
||||
$record = AttendanceRecord::query()->create([
|
||||
'class_section_id' => 11,
|
||||
'student_id' => 201,
|
||||
'school_id' => 'SCH-201',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'total_presence' => 0,
|
||||
'total_absence' => 0,
|
||||
'total_late' => 0,
|
||||
'total_attendance' => 0,
|
||||
'modified_by' => 1,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$this->service->updateAttendanceRecord(
|
||||
studentId: 201,
|
||||
schoolId: 'SCH-201',
|
||||
newStatus: 'present',
|
||||
semester: 'Fall',
|
||||
schoolYear: '2025-2026',
|
||||
oldStatus: 'absent',
|
||||
modifiedBy: 5
|
||||
);
|
||||
|
||||
$record->refresh();
|
||||
|
||||
$this->assertSame(1, (int)$record->total_presence);
|
||||
$this->assertSame(0, (int)$record->total_absence);
|
||||
$this->assertSame(0, (int)$record->total_late);
|
||||
}
|
||||
|
||||
public function test_add_new_attendance_record_creates_record_when_missing(): void
|
||||
{
|
||||
$this->service->addNewAttendanceRecord(
|
||||
classSectionId: 20,
|
||||
studentId: 300,
|
||||
schoolId: 'SCH-300',
|
||||
status: 'late',
|
||||
semester: 'Spring',
|
||||
schoolYear: '2025-2026',
|
||||
modifiedBy: 88
|
||||
);
|
||||
|
||||
$this->assertDatabaseHas('attendance_record', [
|
||||
'class_section_id' => 20,
|
||||
'student_id' => 300,
|
||||
'school_id' => 'SCH-300',
|
||||
'semester' => 'Spring',
|
||||
'school_year' => '2025-2026',
|
||||
'total_presence' => 0,
|
||||
'total_absence' => 0,
|
||||
'total_late' => 1,
|
||||
'total_attendance' => 1,
|
||||
'modified_by' => 88,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_add_new_attendance_record_updates_existing_record(): void
|
||||
{
|
||||
$record = AttendanceRecord::query()->create([
|
||||
'class_section_id' => 21,
|
||||
'student_id' => 301,
|
||||
'school_id' => 'SCH-301',
|
||||
'semester' => 'Spring',
|
||||
'school_year' => '2025-2026',
|
||||
'total_presence' => 1,
|
||||
'total_absence' => 2,
|
||||
'total_late' => 3,
|
||||
'total_attendance' => 6,
|
||||
'modified_by' => 1,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$this->service->addNewAttendanceRecord(
|
||||
classSectionId: 21,
|
||||
studentId: 301,
|
||||
schoolId: 'SCH-301',
|
||||
status: 'present',
|
||||
semester: 'Spring',
|
||||
schoolYear: '2025-2026',
|
||||
modifiedBy: 77
|
||||
);
|
||||
|
||||
$record->refresh();
|
||||
|
||||
$this->assertSame(2, (int)$record->total_presence);
|
||||
$this->assertSame(2, (int)$record->total_absence);
|
||||
$this->assertSame(3, (int)$record->total_late);
|
||||
$this->assertSame(7, (int)$record->total_attendance);
|
||||
$this->assertSame(77, (int)$record->modified_by);
|
||||
}
|
||||
|
||||
public function test_upsert_attendance_record_totals_updates_existing_record(): void
|
||||
{
|
||||
$record = AttendanceRecord::query()->create([
|
||||
'class_section_id' => 30,
|
||||
'student_id' => 400,
|
||||
'school_id' => 'SCH-400',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'total_presence' => 0,
|
||||
'total_absence' => 0,
|
||||
'total_late' => 0,
|
||||
'total_attendance' => 0,
|
||||
'modified_by' => 1,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$result = $this->service->upsertAttendanceRecordTotals(
|
||||
studentId: 400,
|
||||
classSectionId: '30',
|
||||
schoolId: 'SCH-400',
|
||||
semester: 'Fall',
|
||||
schoolYear: '2025-2026',
|
||||
totalPresence: 7,
|
||||
totalLate: 2,
|
||||
totalAbsence: 1,
|
||||
totalAttendance: 10,
|
||||
modifiedBy: 44
|
||||
);
|
||||
|
||||
$this->assertTrue($result);
|
||||
|
||||
$record->refresh();
|
||||
|
||||
$this->assertSame(7, (int)$record->total_presence);
|
||||
$this->assertSame(2, (int)$record->total_late);
|
||||
$this->assertSame(1, (int)$record->total_absence);
|
||||
$this->assertSame(10, (int)$record->total_attendance);
|
||||
$this->assertSame(44, (int)$record->modified_by);
|
||||
}
|
||||
|
||||
public function test_upsert_attendance_record_totals_creates_new_record(): void
|
||||
{
|
||||
$result = $this->service->upsertAttendanceRecordTotals(
|
||||
studentId: 401,
|
||||
classSectionId: '31',
|
||||
schoolId: 'SCH-401',
|
||||
semester: 'Fall',
|
||||
schoolYear: '2025-2026',
|
||||
totalPresence: 3,
|
||||
totalLate: 1,
|
||||
totalAbsence: 2,
|
||||
totalAttendance: 6,
|
||||
modifiedBy: 55
|
||||
);
|
||||
|
||||
$this->assertTrue($result);
|
||||
|
||||
$this->assertDatabaseHas('attendance_record', [
|
||||
'student_id' => 401,
|
||||
'class_section_id' => 31,
|
||||
'school_id' => 'SCH-401',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'total_presence' => 3,
|
||||
'total_late' => 1,
|
||||
'total_absence' => 2,
|
||||
'total_attendance' => 6,
|
||||
'modified_by' => 55,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_upsert_attendance_record_totals_returns_false_when_semester_or_school_year_missing(): void
|
||||
{
|
||||
$result = $this->service->upsertAttendanceRecordTotals(
|
||||
studentId: 402,
|
||||
classSectionId: '32',
|
||||
schoolId: 'SCH-402',
|
||||
semester: '',
|
||||
schoolYear: '',
|
||||
totalPresence: 1,
|
||||
totalLate: 1,
|
||||
totalAbsence: 1,
|
||||
totalAttendance: 3,
|
||||
modifiedBy: 1
|
||||
);
|
||||
|
||||
$this->assertFalse($result);
|
||||
|
||||
$this->assertDatabaseMissing('attendance_record', [
|
||||
'student_id' => 402,
|
||||
'school_id' => 'SCH-402',
|
||||
]);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Attendance;
|
||||
|
||||
use App\Services\Attendance\SemesterRangeService;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class SemesterRangeServiceTest extends TestCase
|
||||
{
|
||||
public function test_normalize_semester(): void
|
||||
{
|
||||
$service = new SemesterRangeService();
|
||||
|
||||
$this->assertSame('Fall', $service->normalizeSemester('fall'));
|
||||
$this->assertSame('Spring', $service->normalizeSemester('SPRING'));
|
||||
$this->assertSame('', $service->normalizeSemester('summer'));
|
||||
}
|
||||
|
||||
public function test_get_school_year_range(): void
|
||||
{
|
||||
$service = new SemesterRangeService();
|
||||
|
||||
[$start, $end] = $service->getSchoolYearRange('2025-2026');
|
||||
|
||||
$this->assertSame('2025-09-01', $start);
|
||||
$this->assertSame('2026-05-31', $end);
|
||||
}
|
||||
|
||||
public function test_build_sunday_list(): void
|
||||
{
|
||||
$service = new SemesterRangeService();
|
||||
|
||||
$sundays = $service->buildSundayList('2025-09-01', '2025-09-30');
|
||||
|
||||
$this->assertContains('2025-09-07', $sundays);
|
||||
$this->assertContains('2025-09-14', $sundays);
|
||||
$this->assertContains('2025-09-21', $sundays);
|
||||
$this->assertContains('2025-09-28', $sundays);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Api\Attendance;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
use Tests\TestCase;
|
||||
|
||||
class StaffAttendanceApiControllerTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_month_data_returns_success(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->getJson('/api/attendance/staff/month');
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
public function test_admins_data_returns_success(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->getJson('/api/attendance/staff/admins');
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Attendance;
|
||||
|
||||
use App\Models\AttendanceData;
|
||||
use App\Models\AttendanceRecord;
|
||||
use App\Models\Student;
|
||||
use App\Services\Attendance\AttendanceRecordSyncService;
|
||||
use App\Services\Attendance\StudentAttendanceWriterService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use RuntimeException;
|
||||
use Tests\TestCase;
|
||||
|
||||
class StudentAttendanceWriterServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected StudentAttendanceWriterService $service;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->service = app(StudentAttendanceWriterService::class);
|
||||
}
|
||||
|
||||
public function test_resolve_student_school_id_returns_passed_value_when_present(): void
|
||||
{
|
||||
$result = $this->service->resolveStudentSchoolId(100, 'SCH-100');
|
||||
|
||||
$this->assertSame('SCH-100', $result);
|
||||
}
|
||||
|
||||
public function test_resolve_student_school_id_fetches_from_database_when_missing(): void
|
||||
{
|
||||
Student::query()->create([
|
||||
'id' => 101,
|
||||
'firstname' => 'John',
|
||||
'lastname' => 'Doe',
|
||||
'school_id' => 'SCH-101',
|
||||
'is_active' => 1,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$result = $this->service->resolveStudentSchoolId(101, null);
|
||||
|
||||
$this->assertSame('SCH-101', $result);
|
||||
}
|
||||
|
||||
public function test_resolve_student_school_id_throws_when_student_missing(): void
|
||||
{
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('Student school ID not found.');
|
||||
|
||||
$this->service->resolveStudentSchoolId(9999, null);
|
||||
}
|
||||
|
||||
public function test_insert_attendance_data_creates_new_row(): void
|
||||
{
|
||||
$this->service->insertAttendanceData(
|
||||
classSectionId: 10,
|
||||
studentId: 200,
|
||||
studentSchoolId: 'SCH-200',
|
||||
status: 'Absent',
|
||||
reason: 'Sick',
|
||||
reported: 'yes',
|
||||
semester: 'Fall',
|
||||
schoolYear: '2025-2026',
|
||||
date: '2025-10-05',
|
||||
classId: 5,
|
||||
userId: 22
|
||||
);
|
||||
|
||||
$this->assertDatabaseHas('attendance_data', [
|
||||
'class_id' => 5,
|
||||
'class_section_id' => 10,
|
||||
'student_id' => 200,
|
||||
'school_id' => 'SCH-200',
|
||||
'status' => 'absent',
|
||||
'reason' => 'Sick',
|
||||
'is_reported' => 'yes',
|
||||
'is_notified' => 'no',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'date' => '2025-10-05',
|
||||
'modified_by' => 22,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_update_attendance_data_updates_existing_row(): void
|
||||
{
|
||||
$row = AttendanceData::query()->create([
|
||||
'class_id' => 6,
|
||||
'class_section_id' => 12,
|
||||
'student_id' => 201,
|
||||
'school_id' => 'SCH-201',
|
||||
'status' => 'present',
|
||||
'reason' => null,
|
||||
'is_reported' => 'no',
|
||||
'is_notified' => 'no',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'date' => '2025-10-12',
|
||||
'modified_by' => 1,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$this->service->updateAttendanceData(
|
||||
attendanceId: $row->id,
|
||||
newStatus: 'late',
|
||||
newReason: 'Traffic',
|
||||
reported: 'yes',
|
||||
userId: 99
|
||||
);
|
||||
|
||||
$row->refresh();
|
||||
|
||||
$this->assertSame('late', $row->status);
|
||||
$this->assertSame('Traffic', $row->reason);
|
||||
$this->assertSame('yes', $row->is_reported);
|
||||
$this->assertSame(99, (int)$row->modified_by);
|
||||
}
|
||||
|
||||
public function test_save_or_update_student_attendance_creates_new_attendance_and_record(): void
|
||||
{
|
||||
Student::query()->create([
|
||||
'id' => 300,
|
||||
'firstname' => 'Sara',
|
||||
'lastname' => 'Smith',
|
||||
'school_id' => 'SCH-300',
|
||||
'is_active' => 1,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$result = $this->service->saveOrUpdateStudentAttendance([
|
||||
'class_section_id' => 15,
|
||||
'student_id' => 300,
|
||||
'school_id' => null,
|
||||
'status' => 'present',
|
||||
'reason' => 'On time',
|
||||
'is_reported' => 'no',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'date' => '2025-10-19',
|
||||
'class_id' => 9,
|
||||
'user_id' => 88,
|
||||
]);
|
||||
|
||||
$this->assertSame('created', $result['mode']);
|
||||
$this->assertSame('present', $result['new_status']);
|
||||
$this->assertSame('SCH-300', $result['school_id']);
|
||||
|
||||
$this->assertDatabaseHas('attendance_data', [
|
||||
'student_id' => 300,
|
||||
'school_id' => 'SCH-300',
|
||||
'status' => 'present',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'date' => '2025-10-19',
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('attendance_record', [
|
||||
'student_id' => 300,
|
||||
'school_id' => 'SCH-300',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'total_presence' => 1,
|
||||
'total_absence' => 0,
|
||||
'total_late' => 0,
|
||||
'total_attendance' => 1,
|
||||
'modified_by' => 88,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_save_or_update_student_attendance_updates_existing_attendance_and_record(): void
|
||||
{
|
||||
Student::query()->create([
|
||||
'id' => 301,
|
||||
'firstname' => 'Ali',
|
||||
'lastname' => 'Khan',
|
||||
'school_id' => 'SCH-301',
|
||||
'is_active' => 1,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
AttendanceRecord::query()->create([
|
||||
'class_section_id' => 16,
|
||||
'student_id' => 301,
|
||||
'school_id' => 'SCH-301',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'total_presence' => 2,
|
||||
'total_absence' => 0,
|
||||
'total_late' => 0,
|
||||
'total_attendance' => 2,
|
||||
'modified_by' => 1,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$attendance = AttendanceData::query()->create([
|
||||
'class_id' => 10,
|
||||
'class_section_id' => 16,
|
||||
'student_id' => 301,
|
||||
'school_id' => 'SCH-301',
|
||||
'status' => 'present',
|
||||
'reason' => null,
|
||||
'is_reported' => 'no',
|
||||
'is_notified' => 'no',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'date' => '2025-10-26',
|
||||
'modified_by' => 1,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$result = $this->service->saveOrUpdateStudentAttendance([
|
||||
'class_section_id' => 16,
|
||||
'student_id' => 301,
|
||||
'school_id' => 'SCH-301',
|
||||
'status' => 'absent',
|
||||
'reason' => 'Family trip',
|
||||
'is_reported' => 'yes',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'date' => '2025-10-26',
|
||||
'class_id' => 10,
|
||||
'user_id' => 77,
|
||||
]);
|
||||
|
||||
$this->assertSame('updated', $result['mode']);
|
||||
$this->assertSame('present', $result['old_status']);
|
||||
$this->assertSame('absent', $result['new_status']);
|
||||
|
||||
$attendance->refresh();
|
||||
|
||||
$this->assertSame('absent', $attendance->status);
|
||||
$this->assertSame('Family trip', $attendance->reason);
|
||||
$this->assertSame('yes', $attendance->is_reported);
|
||||
$this->assertSame(77, (int)$attendance->modified_by);
|
||||
|
||||
$this->assertDatabaseHas('attendance_record', [
|
||||
'student_id' => 301,
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'total_presence' => 1,
|
||||
'total_absence' => 1,
|
||||
'total_late' => 0,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_save_or_update_student_attendance_updates_existing_without_counter_change_when_status_same(): void
|
||||
{
|
||||
Student::query()->create([
|
||||
'id' => 302,
|
||||
'firstname' => 'Mona',
|
||||
'lastname' => 'Lee',
|
||||
'school_id' => 'SCH-302',
|
||||
'is_active' => 1,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$record = AttendanceRecord::query()->create([
|
||||
'class_section_id' => 17,
|
||||
'student_id' => 302,
|
||||
'school_id' => 'SCH-302',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'total_presence' => 4,
|
||||
'total_absence' => 1,
|
||||
'total_late' => 0,
|
||||
'total_attendance' => 5,
|
||||
'modified_by' => 1,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
AttendanceData::query()->create([
|
||||
'class_id' => 11,
|
||||
'class_section_id' => 17,
|
||||
'student_id' => 302,
|
||||
'school_id' => 'SCH-302',
|
||||
'status' => 'present',
|
||||
'reason' => null,
|
||||
'is_reported' => 'no',
|
||||
'is_notified' => 'no',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'date' => '2025-11-02',
|
||||
'modified_by' => 1,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$this->service->saveOrUpdateStudentAttendance([
|
||||
'class_section_id' => 17,
|
||||
'student_id' => 302,
|
||||
'school_id' => 'SCH-302',
|
||||
'status' => 'present',
|
||||
'reason' => 'Still present',
|
||||
'is_reported' => 'no',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'date' => '2025-11-02',
|
||||
'class_id' => 11,
|
||||
'user_id' => 66,
|
||||
]);
|
||||
|
||||
$record->refresh();
|
||||
|
||||
$this->assertSame(4, (int)$record->total_presence);
|
||||
$this->assertSame(1, (int)$record->total_absence);
|
||||
$this->assertSame(0, (int)$record->total_late);
|
||||
$this->assertSame(5, (int)$record->total_attendance);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Api\Attendance;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
use Tests\TestCase;
|
||||
|
||||
class TeacherAttendanceApiControllerTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_teacher_grid_endpoint_returns_success(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->getJson('/api/attendance/teacher/grid');
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
public function test_teacher_form_endpoint_returns_success_or_validation_message(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->getJson('/api/attendance/teacher/form');
|
||||
|
||||
$this->assertContains($response->status(), [200, 422]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,545 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Attendance;
|
||||
|
||||
use App\Models\AttendanceData;
|
||||
use App\Models\AttendanceDay;
|
||||
use App\Models\Student;
|
||||
use App\Models\TeacherClass;
|
||||
use App\Models\User;
|
||||
use App\Services\Attendance\AttendancePolicyService;
|
||||
use App\Services\Attendance\StudentAttendanceWriterService;
|
||||
use App\Services\Attendance\TeacherAttendanceSubmissionService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Mockery;
|
||||
use RuntimeException;
|
||||
use Tests\TestCase;
|
||||
|
||||
class TeacherAttendanceSubmissionServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
Mockery::close();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
protected function makeService(
|
||||
?AttendanceDay $attendanceDay = null,
|
||||
?AttendanceData $attendanceData = null,
|
||||
mixed $teacherClass = null,
|
||||
?Student $student = null,
|
||||
mixed $attendancePolicyService = null,
|
||||
mixed $studentAttendanceWriterService = null
|
||||
): TeacherAttendanceSubmissionService {
|
||||
return new TeacherAttendanceSubmissionService(
|
||||
$attendanceDay ?? app(AttendanceDay::class),
|
||||
$attendanceData ?? app(AttendanceData::class),
|
||||
$teacherClass ?? app(TeacherClass::class),
|
||||
$student ?? app(Student::class),
|
||||
$attendancePolicyService ?? app(AttendancePolicyService::class),
|
||||
$studentAttendanceWriterService ?? app(StudentAttendanceWriterService::class),
|
||||
);
|
||||
}
|
||||
|
||||
public function test_submit_throws_when_class_section_missing(): void
|
||||
{
|
||||
$service = $this->makeService();
|
||||
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('Missing or invalid class section.');
|
||||
|
||||
$service->submit(
|
||||
user: $user,
|
||||
data: [
|
||||
'class_section_id' => '',
|
||||
'attendance' => [],
|
||||
'teachers' => [],
|
||||
],
|
||||
detectExternalSubmission: fn (?array $row) => ['locked' => false, 'source' => null],
|
||||
currentSemester: fn () => 'Fall',
|
||||
currentSchoolYear: fn () => '2025-2026'
|
||||
);
|
||||
}
|
||||
|
||||
public function test_submit_throws_when_section_not_found(): void
|
||||
{
|
||||
$service = $this->makeService();
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('Invalid class section.');
|
||||
|
||||
$service->submit(
|
||||
user: $user,
|
||||
data: [
|
||||
'class_section_id' => '999999',
|
||||
'attendance' => [['student_id' => 1, 'status' => 'present']],
|
||||
'teachers' => [1 => ['status' => 'present']],
|
||||
],
|
||||
detectExternalSubmission: fn (?array $row) => ['locked' => false, 'source' => null],
|
||||
currentSemester: fn () => 'Fall',
|
||||
currentSchoolYear: fn () => '2025-2026'
|
||||
);
|
||||
}
|
||||
|
||||
public function test_submit_throws_when_student_attendance_missing(): void
|
||||
{
|
||||
DB::table('classSection')->insert([
|
||||
'id' => 1,
|
||||
'class_id' => 5,
|
||||
'class_section_id' => 100,
|
||||
]);
|
||||
|
||||
$teacherClassMock = Mockery::mock(TeacherClass::class);
|
||||
$teacherClassMock->shouldReceive('assignedForSectionTerm')
|
||||
->once()
|
||||
->andReturn([
|
||||
[
|
||||
'teacher_id' => 55,
|
||||
'position' => 'main',
|
||||
'firstname' => 'Main',
|
||||
'lastname' => 'Teacher',
|
||||
],
|
||||
]);
|
||||
|
||||
$policyMock = Mockery::mock(AttendancePolicyService::class);
|
||||
$policyMock->shouldReceive('canEditDay')->once()->andReturnTrue();
|
||||
|
||||
$writerMock = Mockery::mock(StudentAttendanceWriterService::class);
|
||||
|
||||
$service = $this->makeService(
|
||||
teacherClass: $teacherClassMock,
|
||||
attendancePolicyService: $policyMock,
|
||||
studentAttendanceWriterService: $writerMock
|
||||
);
|
||||
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('No student attendance data provided.');
|
||||
|
||||
$service->submit(
|
||||
user: $user,
|
||||
data: [
|
||||
'class_section_id' => '100',
|
||||
'attendance' => [],
|
||||
'teachers' => [55 => ['status' => 'present']],
|
||||
],
|
||||
detectExternalSubmission: fn (?array $row) => ['locked' => false, 'source' => null],
|
||||
currentSemester: fn () => 'Fall',
|
||||
currentSchoolYear: fn () => '2025-2026'
|
||||
);
|
||||
}
|
||||
|
||||
public function test_submit_throws_when_teacher_attendance_missing(): void
|
||||
{
|
||||
DB::table('classSection')->insert([
|
||||
'id' => 2,
|
||||
'class_id' => 6,
|
||||
'class_section_id' => 101,
|
||||
]);
|
||||
|
||||
$service = $this->makeService();
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('No teacher attendance data provided.');
|
||||
|
||||
$service->submit(
|
||||
user: $user,
|
||||
data: [
|
||||
'class_section_id' => '101',
|
||||
'attendance' => [
|
||||
['student_id' => 1, 'status' => 'present'],
|
||||
],
|
||||
'teachers' => [],
|
||||
],
|
||||
detectExternalSubmission: fn (?array $row) => ['locked' => false, 'source' => null],
|
||||
currentSemester: fn () => 'Fall',
|
||||
currentSchoolYear: fn () => '2025-2026'
|
||||
);
|
||||
}
|
||||
|
||||
public function test_submit_throws_when_policy_denies_edit(): void
|
||||
{
|
||||
DB::table('classSection')->insert([
|
||||
'id' => 3,
|
||||
'class_id' => 7,
|
||||
'class_section_id' => 102,
|
||||
]);
|
||||
|
||||
$teacherClassMock = Mockery::mock(TeacherClass::class);
|
||||
$teacherClassMock->shouldReceive('assignedForSectionTerm')->never();
|
||||
|
||||
$policyMock = Mockery::mock(AttendancePolicyService::class);
|
||||
$policyMock->shouldReceive('canEditDay')->once()->andReturnFalse();
|
||||
|
||||
$writerMock = Mockery::mock(StudentAttendanceWriterService::class);
|
||||
|
||||
$service = $this->makeService(
|
||||
teacherClass: $teacherClassMock,
|
||||
attendancePolicyService: $policyMock,
|
||||
studentAttendanceWriterService: $writerMock
|
||||
);
|
||||
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('Attendance is locked for your role.');
|
||||
|
||||
$service->submit(
|
||||
user: $user,
|
||||
data: [
|
||||
'class_section_id' => '102',
|
||||
'attendance' => [
|
||||
['student_id' => 1, 'status' => 'present'],
|
||||
],
|
||||
'teachers' => [
|
||||
55 => ['status' => 'present'],
|
||||
],
|
||||
],
|
||||
detectExternalSubmission: fn (?array $row) => ['locked' => false, 'source' => null],
|
||||
currentSemester: fn () => 'Fall',
|
||||
currentSchoolYear: fn () => '2025-2026'
|
||||
);
|
||||
}
|
||||
|
||||
public function test_submit_throws_when_assigned_teacher_status_missing_or_invalid(): void
|
||||
{
|
||||
DB::table('classSection')->insert([
|
||||
'id' => 4,
|
||||
'class_id' => 8,
|
||||
'class_section_id' => 103,
|
||||
]);
|
||||
|
||||
$teacherClassMock = Mockery::mock(TeacherClass::class);
|
||||
$teacherClassMock->shouldReceive('assignedForSectionTerm')
|
||||
->once()
|
||||
->andReturn([
|
||||
[
|
||||
'teacher_id' => 77,
|
||||
'position' => 'main',
|
||||
'firstname' => 'Main',
|
||||
'lastname' => 'Teacher',
|
||||
],
|
||||
]);
|
||||
|
||||
$policyMock = Mockery::mock(AttendancePolicyService::class);
|
||||
$policyMock->shouldReceive('canEditDay')->once()->andReturnTrue();
|
||||
|
||||
$writerMock = Mockery::mock(StudentAttendanceWriterService::class);
|
||||
|
||||
$service = $this->makeService(
|
||||
teacherClass: $teacherClassMock,
|
||||
attendancePolicyService: $policyMock,
|
||||
studentAttendanceWriterService: $writerMock
|
||||
);
|
||||
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('Please fill teacher attendance for all assigned teachers.');
|
||||
|
||||
$service->submit(
|
||||
user: $user,
|
||||
data: [
|
||||
'class_section_id' => '103',
|
||||
'attendance' => [
|
||||
['student_id' => 1, 'status' => 'present'],
|
||||
],
|
||||
'teachers' => [
|
||||
77 => ['status' => ''],
|
||||
],
|
||||
],
|
||||
detectExternalSubmission: fn (?array $row) => ['locked' => false, 'source' => null],
|
||||
currentSemester: fn () => 'Fall',
|
||||
currentSchoolYear: fn () => '2025-2026'
|
||||
);
|
||||
}
|
||||
|
||||
public function test_submit_saves_teacher_staff_attendance_and_student_rows(): void
|
||||
{
|
||||
DB::table('classSection')->insert([
|
||||
'id' => 5,
|
||||
'class_id' => 9,
|
||||
'class_section_id' => 104,
|
||||
]);
|
||||
|
||||
$teacherClassMock = Mockery::mock(TeacherClass::class);
|
||||
$teacherClassMock->shouldReceive('assignedForSectionTerm')
|
||||
->once()
|
||||
->andReturn([
|
||||
[
|
||||
'teacher_id' => 88,
|
||||
'position' => 'main',
|
||||
'firstname' => 'Lead',
|
||||
'lastname' => 'Teacher',
|
||||
],
|
||||
[
|
||||
'teacher_id' => 89,
|
||||
'position' => 'ta',
|
||||
'firstname' => 'Assist',
|
||||
'lastname' => 'Teacher',
|
||||
],
|
||||
]);
|
||||
|
||||
$policyMock = Mockery::mock(AttendancePolicyService::class);
|
||||
$policyMock->shouldReceive('canEditDay')->once()->andReturnTrue();
|
||||
$policyMock->shouldReceive('isTeacher')->once()->andReturnTrue();
|
||||
|
||||
$writerMock = Mockery::mock(StudentAttendanceWriterService::class);
|
||||
$writerMock->shouldReceive('saveOrUpdateStudentAttendance')
|
||||
->twice()
|
||||
->withArgs(function (array $payload) {
|
||||
return in_array($payload['student_id'], [501, 502], true)
|
||||
&& $payload['class_section_id'] === 104
|
||||
&& $payload['class_id'] === 9
|
||||
&& $payload['semester'] === 'Fall'
|
||||
&& $payload['school_year'] === '2025-2026';
|
||||
})
|
||||
->andReturn(['mode' => 'created']);
|
||||
|
||||
$service = $this->makeService(
|
||||
teacherClass: $teacherClassMock,
|
||||
attendancePolicyService: $policyMock,
|
||||
studentAttendanceWriterService: $writerMock
|
||||
);
|
||||
|
||||
$user = User::factory()->create();
|
||||
|
||||
$result = $service->submit(
|
||||
user: $user,
|
||||
data: [
|
||||
'class_section_id' => '104',
|
||||
'date' => '2025-10-05',
|
||||
'attendance' => [
|
||||
[
|
||||
'student_id' => 501,
|
||||
'school_id' => 'SCH-501',
|
||||
'status' => 'present',
|
||||
'reason' => '',
|
||||
],
|
||||
[
|
||||
'student_id' => 502,
|
||||
'school_id' => 'SCH-502',
|
||||
'status' => 'late',
|
||||
'reason' => 'Bus delay',
|
||||
],
|
||||
],
|
||||
'teachers' => [
|
||||
88 => ['status' => 'present', 'reason' => null],
|
||||
89 => ['status' => 'late', 'reason' => 'Traffic'],
|
||||
],
|
||||
],
|
||||
detectExternalSubmission: fn (?array $row) => ['locked' => false, 'source' => null],
|
||||
currentSemester: fn () => 'Fall',
|
||||
currentSchoolYear: fn () => '2025-2026'
|
||||
);
|
||||
|
||||
$this->assertSame(['ok' => true, 'message' => 'Attendance submitted successfully.'], $result);
|
||||
|
||||
$this->assertDatabaseHas('staff_attendance', [
|
||||
'user_id' => 88,
|
||||
'date' => '2025-10-05',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'class_section_id' => 104,
|
||||
'position' => 'main',
|
||||
'status' => 'present',
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('staff_attendance', [
|
||||
'user_id' => 89,
|
||||
'date' => '2025-10-05',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'class_section_id' => 104,
|
||||
'position' => 'ta',
|
||||
'status' => 'late',
|
||||
'reason' => 'Traffic',
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('attendance_day', [
|
||||
'class_section_id' => 104,
|
||||
'date' => '2025-10-05',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'status' => 'submitted',
|
||||
'submitted_by' => $user->id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_submit_skips_locked_existing_student_rows_for_teacher(): void
|
||||
{
|
||||
DB::table('classSection')->insert([
|
||||
'id' => 6,
|
||||
'class_id' => 10,
|
||||
'class_section_id' => 105,
|
||||
]);
|
||||
|
||||
AttendanceData::query()->create([
|
||||
'class_id' => 10,
|
||||
'class_section_id' => 105,
|
||||
'student_id' => 601,
|
||||
'school_id' => 'SCH-601',
|
||||
'status' => 'absent',
|
||||
'reason' => 'Parent reported',
|
||||
'is_reported' => 'yes',
|
||||
'is_notified' => 'no',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'date' => '2025-10-12',
|
||||
'modified_by' => 2,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$teacherClassMock = Mockery::mock(TeacherClass::class);
|
||||
$teacherClassMock->shouldReceive('assignedForSectionTerm')
|
||||
->once()
|
||||
->andReturn([
|
||||
[
|
||||
'teacher_id' => 90,
|
||||
'position' => 'main',
|
||||
'firstname' => 'Lead',
|
||||
'lastname' => 'Teacher',
|
||||
],
|
||||
]);
|
||||
|
||||
$policyMock = Mockery::mock(AttendancePolicyService::class);
|
||||
$policyMock->shouldReceive('canEditDay')->once()->andReturnTrue();
|
||||
$policyMock->shouldReceive('isTeacher')->once()->andReturnTrue();
|
||||
|
||||
$writerMock = Mockery::mock(StudentAttendanceWriterService::class);
|
||||
$writerMock->shouldNotReceive('saveOrUpdateStudentAttendance');
|
||||
|
||||
$service = $this->makeService(
|
||||
teacherClass: $teacherClassMock,
|
||||
attendancePolicyService: $policyMock,
|
||||
studentAttendanceWriterService: $writerMock
|
||||
);
|
||||
|
||||
$user = User::factory()->create();
|
||||
|
||||
$result = $service->submit(
|
||||
user: $user,
|
||||
data: [
|
||||
'class_section_id' => '105',
|
||||
'date' => '2025-10-12',
|
||||
'attendance' => [
|
||||
[
|
||||
'student_id' => 601,
|
||||
'school_id' => 'SCH-601',
|
||||
'status' => 'present',
|
||||
'reason' => '',
|
||||
],
|
||||
],
|
||||
'teachers' => [
|
||||
90 => ['status' => 'present', 'reason' => null],
|
||||
],
|
||||
],
|
||||
detectExternalSubmission: fn (?array $row) => ['locked' => true, 'source' => 'parent'],
|
||||
currentSemester: fn () => 'Fall',
|
||||
currentSchoolYear: fn () => '2025-2026'
|
||||
);
|
||||
|
||||
$this->assertTrue($result['ok']);
|
||||
|
||||
$this->assertDatabaseHas('attendance_day', [
|
||||
'class_section_id' => 105,
|
||||
'date' => '2025-10-12',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'status' => 'submitted',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_submit_allows_non_teacher_context_to_write_existing_rows(): void
|
||||
{
|
||||
DB::table('classSection')->insert([
|
||||
'id' => 7,
|
||||
'class_id' => 11,
|
||||
'class_section_id' => 106,
|
||||
]);
|
||||
|
||||
AttendanceData::query()->create([
|
||||
'class_id' => 11,
|
||||
'class_section_id' => 106,
|
||||
'student_id' => 701,
|
||||
'school_id' => 'SCH-701',
|
||||
'status' => 'absent',
|
||||
'reason' => 'Parent reported',
|
||||
'is_reported' => 'yes',
|
||||
'is_notified' => 'no',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'date' => '2025-10-19',
|
||||
'modified_by' => 2,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$teacherClassMock = Mockery::mock(TeacherClass::class);
|
||||
$teacherClassMock->shouldReceive('assignedForSectionTerm')
|
||||
->once()
|
||||
->andReturn([
|
||||
[
|
||||
'teacher_id' => 91,
|
||||
'position' => 'main',
|
||||
'firstname' => 'Lead',
|
||||
'lastname' => 'Teacher',
|
||||
],
|
||||
]);
|
||||
|
||||
$policyMock = Mockery::mock(AttendancePolicyService::class);
|
||||
$policyMock->shouldReceive('canEditDay')->once()->andReturnTrue();
|
||||
$policyMock->shouldReceive('isTeacher')->once()->andReturnFalse();
|
||||
|
||||
$writerMock = Mockery::mock(StudentAttendanceWriterService::class);
|
||||
$writerMock->shouldReceive('saveOrUpdateStudentAttendance')
|
||||
->once()
|
||||
->withArgs(function (array $payload) {
|
||||
return $payload['student_id'] === 701
|
||||
&& $payload['class_section_id'] === 106
|
||||
&& $payload['class_id'] === 11;
|
||||
})
|
||||
->andReturn(['mode' => 'updated']);
|
||||
|
||||
$service = $this->makeService(
|
||||
teacherClass: $teacherClassMock,
|
||||
attendancePolicyService: $policyMock,
|
||||
studentAttendanceWriterService: $writerMock
|
||||
);
|
||||
|
||||
$user = User::factory()->create();
|
||||
|
||||
$result = $service->submit(
|
||||
user: $user,
|
||||
data: [
|
||||
'class_section_id' => '106',
|
||||
'date' => '2025-10-19',
|
||||
'attendance' => [
|
||||
[
|
||||
'student_id' => 701,
|
||||
'school_id' => 'SCH-701',
|
||||
'status' => 'present',
|
||||
'reason' => '',
|
||||
],
|
||||
],
|
||||
'teachers' => [
|
||||
91 => ['status' => 'present', 'reason' => null],
|
||||
],
|
||||
],
|
||||
detectExternalSubmission: fn (?array $row) => ['locked' => true, 'source' => 'parent'],
|
||||
currentSemester: fn () => 'Fall',
|
||||
currentSchoolYear: fn () => '2025-2026'
|
||||
);
|
||||
|
||||
$this->assertTrue($result['ok']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user