332 lines
10 KiB
PHP
332 lines
10 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\User;
|
|
use App\Models\Transaction;
|
|
use App\Models\KpiTarget;
|
|
use App\Models\KpiAchievement;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class KpiService
|
|
{
|
|
/**
|
|
* Calculate KPI achievement for a user
|
|
*
|
|
* @param User $user
|
|
* @param int $year
|
|
* @param int $month
|
|
* @return KpiAchievement|null
|
|
*/
|
|
public function calculateKpiAchievement(User $user, $year = null, $month = null)
|
|
{
|
|
$year = $year ?? now()->year;
|
|
$month = $month ?? now()->month;
|
|
|
|
// Get current KPI target (no longer filtered by year/month)
|
|
$kpiTarget = $user->kpiTargets()
|
|
->where('is_active', true)
|
|
->first();
|
|
|
|
if (!$kpiTarget) {
|
|
Log::info("No KPI target found for user {$user->id}");
|
|
return null;
|
|
}
|
|
|
|
// Calculate actual value based on month
|
|
$actualValue = $this->getActualWorkCount($user, $year, $month);
|
|
|
|
// Calculate percentage
|
|
$achievementPercentage = $kpiTarget->target_value > 0
|
|
? ($actualValue / $kpiTarget->target_value) * 100
|
|
: 0;
|
|
|
|
// Save or update achievement with target value stored directly
|
|
return KpiAchievement::updateOrCreate(
|
|
[
|
|
'user_id' => $user->id,
|
|
'year' => $year,
|
|
'month' => $month
|
|
],
|
|
[
|
|
'kpi_target_id' => $kpiTarget->id,
|
|
'target_value' => $kpiTarget->target_value, // Store target value directly for historical tracking
|
|
'actual_value' => $actualValue,
|
|
'achievement_percentage' => $achievementPercentage
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get actual work count for a user in specific month
|
|
*
|
|
* @param User $user
|
|
* @param int $year
|
|
* @param int $month
|
|
* @return int
|
|
*/
|
|
private function getActualWorkCount(User $user, $year, $month)
|
|
{
|
|
return Transaction::where('user_id', $user->id)
|
|
->where('status', 'completed')
|
|
->whereYear('date', $year)
|
|
->whereMonth('date', $month)
|
|
->sum('qty');
|
|
}
|
|
|
|
/**
|
|
* Generate KPI report for a user
|
|
*
|
|
* @param User $user
|
|
* @param int|null $year
|
|
* @param int|null $month
|
|
* @return array
|
|
*/
|
|
public function generateKpiReport(User $user, $year = null, $month = null)
|
|
{
|
|
$year = $year ?? now()->year;
|
|
$month = $month ?? now()->month;
|
|
|
|
$achievements = $user->kpiAchievements()
|
|
->where('year', $year)
|
|
->where('month', $month)
|
|
->orderBy('month')
|
|
->get();
|
|
|
|
$target = $user->kpiTargets()
|
|
->where('is_active', true)
|
|
->first();
|
|
|
|
return [
|
|
'user' => $user,
|
|
'target' => $target,
|
|
'achievements' => $achievements,
|
|
'summary' => $this->calculateSummary($achievements),
|
|
'period' => [
|
|
'year' => $year,
|
|
'month' => $month,
|
|
'period_name' => $this->getMonthName($month) . ' ' . $year
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Calculate summary statistics for achievements
|
|
*
|
|
* @param \Illuminate\Database\Eloquent\Collection $achievements
|
|
* @return array
|
|
*/
|
|
private function calculateSummary($achievements)
|
|
{
|
|
if ($achievements->isEmpty()) {
|
|
return [
|
|
'total_target' => 0,
|
|
'total_actual' => 0,
|
|
'average_achievement' => 0,
|
|
'best_period' => null,
|
|
'worst_period' => null,
|
|
'total_periods' => 0,
|
|
'achievement_rate' => 0
|
|
];
|
|
}
|
|
|
|
$totalTarget = $achievements->sum('target_value');
|
|
$totalActual = $achievements->sum('actual_value');
|
|
$averageAchievement = $achievements->avg('achievement_percentage');
|
|
$totalPeriods = $achievements->count();
|
|
$achievementRate = $totalPeriods > 0 ? ($achievements->where('achievement_percentage', '>=', 100)->count() / $totalPeriods) * 100 : 0;
|
|
|
|
$bestPeriod = $achievements->sortByDesc('achievement_percentage')->first();
|
|
$worstPeriod = $achievements->sortBy('achievement_percentage')->first();
|
|
|
|
return [
|
|
'total_target' => $totalTarget,
|
|
'total_actual' => $totalActual,
|
|
'average_achievement' => round($averageAchievement, 2),
|
|
'best_period' => $bestPeriod,
|
|
'worst_period' => $worstPeriod,
|
|
'total_periods' => $totalPeriods,
|
|
'achievement_rate' => round($achievementRate, 2)
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get KPI statistics for all mechanics
|
|
*
|
|
* @param int|null $year
|
|
* @param int|null $month
|
|
* @return array
|
|
*/
|
|
public function getMechanicsKpiStats($year = null, $month = null)
|
|
{
|
|
$year = $year ?? now()->year;
|
|
$month = $month ?? now()->month;
|
|
|
|
$mechanics = User::whereHas('role', function($query) {
|
|
$query->where('name', 'mechanic');
|
|
})->get();
|
|
|
|
$stats = [];
|
|
foreach ($mechanics as $mechanic) {
|
|
$report = $this->generateKpiReport($mechanic, $year, $month);
|
|
$stats[] = [
|
|
'user' => $mechanic,
|
|
'summary' => $report['summary'],
|
|
'target' => $report['target']
|
|
];
|
|
}
|
|
|
|
return $stats;
|
|
}
|
|
|
|
/**
|
|
* Auto-calculate KPI achievements for all mechanics
|
|
*
|
|
* @param int|null $year
|
|
* @param int|null $month
|
|
* @return array
|
|
*/
|
|
public function autoCalculateAllMechanics($year = null, $month = null)
|
|
{
|
|
$year = $year ?? now()->year;
|
|
$month = $month ?? now()->month;
|
|
|
|
$mechanics = User::whereHas('role', function($query) {
|
|
$query->where('name', 'mechanic');
|
|
})->get();
|
|
|
|
$results = [];
|
|
foreach ($mechanics as $mechanic) {
|
|
try {
|
|
$achievement = $this->calculateKpiAchievement($mechanic, $year, $month);
|
|
$results[] = [
|
|
'user_id' => $mechanic->id,
|
|
'user_name' => $mechanic->name,
|
|
'success' => true,
|
|
'achievement' => $achievement
|
|
];
|
|
} catch (\Exception $e) {
|
|
Log::error("Failed to calculate KPI for user {$mechanic->id}: " . $e->getMessage());
|
|
$results[] = [
|
|
'user_id' => $mechanic->id,
|
|
'user_name' => $mechanic->name,
|
|
'success' => false,
|
|
'error' => $e->getMessage()
|
|
];
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Get KPI trend data for chart
|
|
*
|
|
* @param User $user
|
|
* @param int $months
|
|
* @return array
|
|
*/
|
|
public function getKpiTrendData(User $user, $months = 12)
|
|
{
|
|
$endDate = now();
|
|
$startDate = $endDate->copy()->subMonths($months);
|
|
|
|
$achievements = $user->kpiAchievements()
|
|
->where(function($query) use ($startDate, $endDate) {
|
|
$query->where(function($q) use ($startDate, $endDate) {
|
|
$q->where('year', '>', $startDate->year)
|
|
->orWhere(function($subQ) use ($startDate, $endDate) {
|
|
$subQ->where('year', $startDate->year)
|
|
->where('month', '>=', $startDate->month);
|
|
});
|
|
})
|
|
->where(function($q) use ($endDate) {
|
|
$q->where('year', '<', $endDate->year)
|
|
->orWhere(function($subQ) use ($endDate) {
|
|
$subQ->where('year', $endDate->year)
|
|
->where('month', '<=', $endDate->month);
|
|
});
|
|
});
|
|
})
|
|
->orderBy('year')
|
|
->orderBy('month')
|
|
->get();
|
|
|
|
$trendData = [];
|
|
foreach ($achievements as $achievement) {
|
|
$trendData[] = [
|
|
'period' => $achievement->getPeriodDisplayName(),
|
|
'target' => $achievement->target_value,
|
|
'actual' => $achievement->actual_value,
|
|
'percentage' => $achievement->achievement_percentage,
|
|
'status' => $achievement->status
|
|
];
|
|
}
|
|
|
|
return $trendData;
|
|
}
|
|
|
|
/**
|
|
* Get month name in Indonesian
|
|
*
|
|
* @param int $month
|
|
* @return string
|
|
*/
|
|
private function getMonthName($month)
|
|
{
|
|
$monthNames = [
|
|
1 => 'Januari', 2 => 'Februari', 3 => 'Maret', 4 => 'April',
|
|
5 => 'Mei', 6 => 'Juni', 7 => 'Juli', 8 => 'Agustus',
|
|
9 => 'September', 10 => 'Oktober', 11 => 'November', 12 => 'Desember'
|
|
];
|
|
|
|
return $monthNames[$month] ?? 'Unknown';
|
|
}
|
|
|
|
/**
|
|
* Get KPI summary for dashboard
|
|
*
|
|
* @param User $user
|
|
* @return array
|
|
*/
|
|
public function getKpiSummary(User $user)
|
|
{
|
|
$currentYear = now()->year;
|
|
$currentMonth = now()->month;
|
|
|
|
// Get current month achievement
|
|
$currentAchievement = $user->kpiAchievements()
|
|
->where('year', $currentYear)
|
|
->where('month', $currentMonth)
|
|
->first();
|
|
|
|
// Get current month target (no longer filtered by year/month)
|
|
$currentTarget = $user->kpiTargets()
|
|
->where('is_active', true)
|
|
->first();
|
|
|
|
// Get last 6 months achievements
|
|
$recentAchievements = $user->kpiAchievements()
|
|
->where(function($query) use ($currentYear, $currentMonth) {
|
|
$query->where('year', '>', $currentYear - 1)
|
|
->orWhere(function($q) use ($currentYear, $currentMonth) {
|
|
$q->where('year', $currentYear)
|
|
->where('month', '>=', max(1, $currentMonth - 5));
|
|
});
|
|
})
|
|
->orderBy('year', 'desc')
|
|
->orderBy('month', 'desc')
|
|
->limit(6)
|
|
->get();
|
|
|
|
return [
|
|
'current_achievement' => $currentAchievement,
|
|
'current_target' => $currentTarget,
|
|
'recent_achievements' => $recentAchievements,
|
|
'current_percentage' => $currentAchievement ? $currentAchievement->achievement_percentage : 0,
|
|
'is_on_track' => $currentAchievement ? $currentAchievement->achievement_percentage >= 100 : false
|
|
];
|
|
}
|
|
}
|