'date', 'check_in_at' => 'datetime', 'check_in_latitude' => 'decimal:7', 'check_in_longitude' => 'decimal:7', 'check_out_at' => 'datetime', 'check_out_latitude' => 'decimal:7', 'check_out_longitude' => 'decimal:7', 'work_duration_minutes' => 'integer', ]; } // 3. Attribute public function attendanceDateFormatted(): Attribute { return Attribute::make( get: fn () => $this->attendance_date?->translatedFormat('l, d F Y'), ); } public function checkInAtFormatted(): Attribute { return Attribute::make( get: fn () => $this->check_in_at?->translatedFormat('H:i'), ); } public function checkInPhotoUrl(): Attribute { return Attribute::make( get: function () { $photo = MediaPresenter::first($this, 'checkin'); return $photo['url'] ?? null; }, ); } public function checkOutAtFormatted(): Attribute { return Attribute::make( get: fn () => $this->check_out_at?->translatedFormat('H:i'), ); } public function checkOutPhotoUrl(): Attribute { return Attribute::make( get: function () { $photo = MediaPresenter::first($this, 'checkout'); return $photo['url'] ?? null; }, ); } public function employeeName(): Attribute { return Attribute::make( get: fn () => $this->employee?->user?->profile?->full_name, ); } public function workDurationFormatted(): Attribute { return Attribute::make( get: function () { if ($this->work_duration_minutes === null) { return null; } $hours = intdiv($this->work_duration_minutes, 60); $minutes = $this->work_duration_minutes % 60; if ($hours > 0) { return "{$hours} jam {$minutes} menit"; } return "{$minutes} menit"; }, ); } // 4. Other Methods public static function mediaModuleName(): string { return 'attendance'; } public function registerMediaCollections(): void { $this->addMediaCollection('checkin')->singleFile(); $this->addMediaCollection('checkout')->singleFile(); } // 5. Relation public function employee(): BelongsTo { return $this->belongsTo(Employee::class); } public function payrollAdjustments(): HasMany { return $this->hasMany(PayrollAdjustment::class); } }