Files
My-KOPKB/be/Modules/Dashboard/Repositories/KJCDashboardRepository.php
T
ISMAIL MASSERAN 94ecbe5887 first init
2026-06-08 11:37:14 +08:00

646 lines
23 KiB
PHP

<?php
namespace Modules\Dashboard\Repositories;
use Modules\Dashboard\Repositories\Contracts\KJCDashboardRepositoryInterface;
use Modules\KJCAssetEntitlement\Entities\KJCAssetEntitlement;
use Modules\KJCAssetHolding\Entities\KJCAssetHolding;
use App\Models\KJCHistoricalEntitlement;
use App\Models\KJCHistoricalHoldingStatus;
use Modules\Unit\Entities\Unit;
use Modules\Formation\Entities\Formation;
use App\Services\VisibilityService;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;
class KJCDashboardRepository implements KJCDashboardRepositoryInterface
{
public function __construct(
protected VisibilityService $visibilityService
) {}
/**
* Get total KJC asset entitlement
*/
public function getTotalEntitlement(array $filters = [], $user = null): int
{
$query = KJCAssetEntitlement::query();
// Apply visibility scoping if user is provided
if ($user) {
$query->visibleTo($user);
}
$this->applyFilters($query, $filters, excludeDateFilter: true);
return $query->sum('entitlement');
}
/**
* Get total KJC asset holdings
*/
public function getTotalHoldings(array $filters = [], $user = null): int
{
$query = KJCAssetEntitlement::query();
// Apply visibility scoping if user is provided
if ($user) {
$query->visibleTo($user);
}
$this->applyFilters($query, $filters, excludeDateFilter: true);
return $query->sum('holding');
}
/**
* Get KJC asset holdings by status.
* Does not filter by date so totals match full KJCAssetHolding counts (unit/government/formation filters still apply).
*/
public function getHoldingsByStatus(array $filters = [], $user = null): array
{
$query = KJCAssetHolding::query();
// Apply visibility scoping if user is provided
if ($user) {
$query->visibleTo($user);
}
$this->applyFilters($query, $filters, excludeDateFilter: true);
return $query->selectRaw('status, COUNT(*) as count')
->groupBy('status')
->get()
->toArray();
}
/**
* Get KJC asset holdings with pagination
*/
public function getHoldingsPaginated(array $filters = [], int $perPage = 20, $user = null): array
{
$query = KJCAssetHolding::with(['unit', 'kjcCategory', 'kjcSubcategory', 'kjcModel']);
// Apply visibility scoping if user is provided
if ($user) {
$query->visibleTo($user);
}
$this->applyFilters($query, $filters);
$holdings = $query->paginate($perPage);
return [
'items' => $holdings->items(),
'pagination' => [
'current_page' => $holdings->currentPage(),
'per_page' => $holdings->perPage(),
'total' => $holdings->total(),
'last_page' => $holdings->lastPage(),
'from' => $holdings->firstItem(),
'to' => $holdings->lastItem()
]
];
}
/**
* Get KJC asset entitlements with pagination
*/
public function getEntitlementsPaginated(array $filters = [], int $perPage = 20, $user = null): array
{
$query = KJCAssetEntitlement::with(['kjcSubcategory']);
// Apply visibility scoping if user is provided
if ($user) {
$query->visibleTo($user);
}
$this->applyFilters($query, $filters);
$entitlements = $query->paginate($perPage);
return [
'items' => $entitlements->items(),
'pagination' => [
'current_page' => $entitlements->currentPage(),
'per_page' => $entitlements->perPage(),
'total' => $entitlements->total(),
'last_page' => $entitlements->lastPage(),
'from' => $entitlements->firstItem(),
'to' => $entitlements->lastItem()
]
];
}
/**
* Get KJC asset holdings by specific status
*/
public function getHoldingsBySpecificStatus(string $status, array $filters = [], $user = null): array
{
$query = KJCAssetHolding::with(['unit', 'kjcCategory', 'kjcSubcategory', 'kjcModel'])
->where('status', $status);
// Apply visibility scoping if user is provided
if ($user) {
$query->visibleTo($user);
}
$this->applyFilters($query, $filters);
$holdings = $query->paginate(20);
return [
'items' => $holdings->items(),
'pagination' => [
'current_page' => $holdings->currentPage(),
'per_page' => $holdings->perPage(),
'total' => $holdings->total(),
'last_page' => $holdings->lastPage(),
'from' => $holdings->firstItem(),
'to' => $holdings->lastItem()
]
];
}
/**
* Get KJC asset details by status (paginated).
* When status is '' or 'null', returns assets where status column is NULL (not yet assigned).
*/
public function getAssetDetailsByStatus(string $status, array $filters = [], $user = null): array
{
$status = trim($status ?? '');
$query = KJCAssetHolding::query()
->with(['kjcCategory', 'kjcSubcategory', 'kjcModel', 'unit'])
->select([
'id',
'uuid',
'kjc_category_id',
'kjc_subcategory_id',
'kjc_model_id',
'registration_number',
'kewpa_registration_number',
'status',
'unit_id',
'purchase_date',
'receipt_date',
'purchase_price',
'contract_reference',
'economic_year',
'economic_distance',
'note',
'engine_number',
'chassis_number',
'created_at',
'updated_at'
]);
if ($status === '' || $status === 'null') {
$query->whereNull('status');
} else {
$query->whereNotNull('status')
->where('status', '=', $status);
}
if ($user) {
$query->visibleTo($user);
}
$this->applyFilters($query, $filters, excludeDateFilter: true);
$perPage = isset($filters['per_page']) ? (int) $filters['per_page'] : 10;
$page = isset($filters['page']) ? (int) $filters['page'] : null;
$assets = $query->paginate($perPage, ['*'], 'page', $page);
return [
'data' => $assets->items(),
'pagination' => [
'current_page' => $assets->currentPage(),
'per_page' => $assets->perPage(),
'total' => $assets->total(),
'last_page' => $assets->lastPage(),
'from' => $assets->firstItem(),
'to' => $assets->lastItem(),
'has_more_pages' => $assets->hasMorePages()
]
];
}
/**
* Get KJC asset holdings grouped by unit
*/
public function getHoldingsByUnit(array $filters = [], $user = null): array
{
$query = KJCAssetHolding::query();
// Apply visibility scoping if user is provided
if ($user) {
$query->visibleTo($user);
}
$this->applyFilters($query, $filters);
return $query->join('units', 'kjc_asset_holdings.unit_id', '=', 'units.id')
->selectRaw('units.name as unit_name, COUNT(*) as count')
->groupBy('units.name')
->get()
->toArray();
}
/**
* Get KJC asset holdings monthly trends
*/
public function getMonthlyTrends(array $filters = [], $user = null): array
{
$dateRange = $this->getDateRange($filters);
$query = KJCAssetHolding::query();
// Apply visibility scoping if user is provided
if ($user) {
$query->visibleTo($user);
}
$this->applyFilters($query, $filters);
return $query->selectRaw('
DATE_FORMAT(created_at, "%Y-%m") as month,
status,
COUNT(*) as count
')
->whereBetween('created_at', [$dateRange['from'], $dateRange['to']])
->groupBy('month', 'status')
->orderBy('month')
->get()
->toArray();
}
/**
* Get KJC historical metrics (KEUPAYAAN, KESIAGAAN, SERVISIBILITI) by month
* 1. KEUPAYAAN (%) = (PEGANGAN/PERJAWATAN) * 100
* 2. SIAPSIAGA or KESIAGAAN (%) = (BDG/perjawatan) * 100
* 3. SERVISIBILITI (%) = (BDG/pegangan) * 100
*/
public function getHistoricalMetricsByMonth(array $filters = [], $user = null): array
{
$dateRange = $this->getDateRange($filters);
$currentMonth = Carbon::now()->format('Y-m');
// Get entitlement and holding data by month from historical tables using Eloquent
$entitlementQuery = KJCHistoricalEntitlement::query()
->selectRaw('
TO_CHAR(kjc_historical_entitlement.date, \'YYYY-MM\') as month,
SUM(kjc_historical_entitlement.entitlement) as total_entitlement,
SUM(kjc_historical_entitlement.holding) as total_holding
')
->whereBetween('kjc_historical_entitlement.date', [$dateRange['from'], $dateRange['to']]);
// Apply filters to historical entitlement query
$this->applyHistoricalFiltersToEloquent($entitlementQuery, $filters);
// Apply visibility scoping if user is provided (uses HasVisibility trait)
if ($user) {
$entitlementQuery->visibleTo($user);
}
$entitlementData = $entitlementQuery->groupBy('month')
->orderBy('month')
->get()
->keyBy('month');
// Get BDG status count by month from historical tables using Eloquent
$bdgQuery = KJCHistoricalHoldingStatus::query()
->selectRaw('
TO_CHAR(kjc_historical_holding_status.date, \'YYYY-MM\') as month,
COUNT(*) as bdg_count
')
->where('kjc_historical_holding_status.status', 'BDG')
->whereBetween('kjc_historical_holding_status.date', [$dateRange['from'], $dateRange['to']]);
// Apply filters to historical holding status query
$this->applyHistoricalFiltersToEloquent($bdgQuery, $filters);
// Apply visibility scoping if user is provided (uses HasVisibility trait)
if ($user) {
$bdgQuery->visibleTo($user);
}
$bdgData = $bdgQuery->groupBy('month')
->orderBy('month')
->get()
->keyBy('month');
// Get current month data from real-time tables if current month is in range
$currentMonthEntitlement = 0;
$currentMonthHolding = 0;
$currentMonthBdgCount = 0;
if ($this->isCurrentMonthInRange($currentMonth, $dateRange)) {
// Get current month entitlement data (all entitlements, not filtered by updated_at)
// This ensures we get real-time entitlement values regardless of when they were last updated
$currentEntitlementQuery = KJCAssetEntitlement::query();
if ($user) {
$currentEntitlementQuery->visibleTo($user);
}
$this->applyCurrentMonthFilters($currentEntitlementQuery, $filters);
$currentMonthEntitlement = $currentEntitlementQuery->sum('entitlement');
// Calculate current month holdings from actual KJCAssetHolding records
// This ensures we get real-time holding counts even if entitlement.holding is stale
$currentHoldingQuery = KJCAssetHolding::query();
if ($user) {
$currentHoldingQuery->visibleTo($user);
}
$this->applyCurrentMonthFilters($currentHoldingQuery, $filters);
$currentMonthHolding = $currentHoldingQuery->count();
// Get current month BDG count (only holdings updated in current month)
// This shows BDG holdings that were updated/changed status in the current month
$currentBdgQuery = KJCAssetHolding::query()
->where('status', 'BDG')
->whereMonth('updated_at', Carbon::now()->month)
->whereYear('updated_at', Carbon::now()->year);
if ($user) {
$currentBdgQuery->visibleTo($user);
}
$this->applyCurrentMonthFilters($currentBdgQuery, $filters);
$currentMonthBdgCount = $currentBdgQuery->count();
}
// Generate all months in the range
$months = [];
$current = $dateRange['from']->copy()->startOfMonth();
$end = $dateRange['to']->copy()->endOfMonth();
while ($current->lte($end)) {
$monthKey = $current->format('Y-m');
$months[] = $monthKey;
$current->addMonth();
}
// Calculate metrics for each month
$result = [];
foreach ($months as $month) {
$entitlement = $entitlementData->get($month);
$bdg = $bdgData->get($month);
// Use current month real-time data if this is the current month
if ($month === $currentMonth && $this->isCurrentMonthInRange($currentMonth, $dateRange)) {
$totalEntitlement = $currentMonthEntitlement;
$totalHolding = $currentMonthHolding;
$bdgCount = $currentMonthBdgCount;
} else {
$totalEntitlement = $entitlement ? $entitlement->total_entitlement : 0;
$totalHolding = $entitlement ? $entitlement->total_holding : 0;
$bdgCount = $bdg ? $bdg->bdg_count : 0;
}
// Calculate metrics
$keupayaan = $totalEntitlement > 0 ? ($totalHolding / $totalEntitlement) * 100 : 0;
$kesiagaan = $totalEntitlement > 0 ? ($bdgCount / $totalEntitlement) * 100 : 0;
$servisibiliti = $totalHolding > 0 ? ($bdgCount / $totalHolding) * 100 : 0;
$result[] = [
'month' => $month,
'keupayaan' => round($keupayaan, 2),
'kesiagaan' => round($kesiagaan, 2),
'servisibiliti' => round($servisibiliti, 2),
'total_entitlement' => $totalEntitlement,
'total_holding' => $totalHolding,
'bdg_count' => $bdgCount
];
}
return $result;
}
/**
* Apply common filters to query
*/
private function applyFilters($query, array $filters = [], bool $excludeDateFilter = false): void
{
$model = $query->getModel();
if ($model && method_exists($model, 'getTable')) {
$schema = $model->getConnection()->getSchemaBuilder();
$columns = $schema->getColumnListing($model->getTable());
// Only apply created_at filter if the model has created_at column (skip when excludeDateFilter)
if (!$excludeDateFilter && in_array('created_at', $columns)) {
$dateRange = $this->getDateRange($filters);
$query->whereBetween('created_at', [$dateRange['from'], $dateRange['to']]);
}
// Apply unit_id filter if model has unit_id column
if (isset($filters['unit_id']) && $filters['unit_id']) {
if (in_array('unit_id', $columns)) {
$query->where('unit_id', $filters['unit_id']);
}
}
// Apply government_id filter if provided
if (isset($filters['government_id']) && $filters['government_id']) {
$unitIds = $this->getUnitsByGovernment($filters['government_id']);
if (in_array('unit_id', $columns)) {
if (!empty($unitIds)) {
$query->whereIn('unit_id', $unitIds);
} else {
// No units found for this government - return empty result
$query->whereNull('id');
}
}
}
// Apply camp_id filter
if (isset($filters['camp_id']) && $filters['camp_id']) {
$query->whereHas('unit', function ($q) use ($filters) {
$q->where('camp_id', $filters['camp_id']);
});
}
// Apply formation_id filter
if (isset($filters['formation_id']) && $filters['formation_id']) {
// Get units under this formation
$unitIds = Unit::where('formation_id', $filters['formation_id'])
->pluck('id')
->toArray();
if (!empty($unitIds)) {
if (in_array('unit_id', $columns)) {
$query->whereIn('unit_id', $unitIds);
} else {
// Fallback to whereHas if no unit_id column
$query->whereHas('unit', function ($q) use ($filters) {
$q->where('formation_id', $filters['formation_id']);
});
}
} else {
// No units found for this formation - return empty result
$query->whereNull('id');
}
}
}
}
/**
* Get units by government ID
* Uses VisibilityService for consistent logic
*/
private function getUnitsByGovernment(int $governmentId): array
{
$governmentUnit = Unit::where('government_id', $governmentId)->first();
if (!$governmentUnit) {
// If no direct unit found, try to get from formation
$formation = Formation::where('government_id', $governmentId)->first();
if ($formation) {
$governmentUnit = Unit::where('formation_id', $formation->id)->first();
}
}
if (!$governmentUnit) {
return [];
}
return $this->visibilityService->getUnitsUnderGovernment($governmentUnit);
}
/**
* Get date range for filtering
*/
private function getDateRange(array $filters = []): array
{
$dateFrom = $filters['date_from'] ?? Carbon::now()->startOfYear();
$dateTo = $filters['date_to'] ?? Carbon::now()->endOfYear();
return [
'from' => Carbon::parse($dateFrom)->startOfDay(),
'to' => Carbon::parse($dateTo)->endOfDay()
];
}
/**
* Apply filters to historical Eloquent queries
* Handles government_id, formation_id, unit_id, and camp_id filters
*/
private function applyHistoricalFiltersToEloquent($query, array $filters = []): void
{
$model = $query->getModel();
$isHoldingStatus = $model instanceof KJCHistoricalHoldingStatus;
// Apply unit_id filter
if (isset($filters['unit_id']) && $filters['unit_id']) {
if ($isHoldingStatus) {
// For holding status, filter through the relationship
$query->whereHas('kjcAssetHolding', function ($q) use ($filters) {
$q->where('unit_id', $filters['unit_id']);
});
} else {
$query->where('unit_id', $filters['unit_id']);
}
}
// Apply government_id filter
if (isset($filters['government_id']) && $filters['government_id']) {
$unitIds = $this->getUnitsByGovernment($filters['government_id']);
if (!empty($unitIds)) {
if ($isHoldingStatus) {
$query->whereHas('kjcAssetHolding', function ($q) use ($unitIds) {
$q->whereIn('unit_id', $unitIds);
});
} else {
$query->whereIn('unit_id', $unitIds);
}
} else {
// No units found for this government - return empty result
$query->whereRaw('1 = 0');
}
}
// Apply formation_id filter
if (isset($filters['formation_id']) && $filters['formation_id']) {
// Get units under this formation
$unitIds = Unit::where('formation_id', $filters['formation_id'])
->pluck('id')
->toArray();
if (!empty($unitIds)) {
if ($isHoldingStatus) {
$query->whereHas('kjcAssetHolding', function ($q) use ($unitIds) {
$q->whereIn('unit_id', $unitIds);
});
} else {
$query->whereIn('unit_id', $unitIds);
}
} else {
// No units found for this formation - return empty result
$query->whereRaw('1 = 0');
}
}
// Apply camp_id filter
if (isset($filters['camp_id']) && $filters['camp_id']) {
if ($isHoldingStatus) {
$query->whereHas('kjcAssetHolding.unit', function ($q) use ($filters) {
$q->where('camp_id', $filters['camp_id']);
});
} else {
$query->whereHas('unit', function ($q) use ($filters) {
$q->where('camp_id', $filters['camp_id']);
});
}
}
}
/**
* Check if current month is within the date range
*/
private function isCurrentMonthInRange(string $currentMonth, array $dateRange): bool
{
$currentMonthDate = Carbon::createFromFormat('Y-m', $currentMonth)->startOfMonth();
return $currentMonthDate->between($dateRange['from'], $dateRange['to']);
}
/**
* Apply filters for current month data queries (without date range filtering)
*/
private function applyCurrentMonthFilters($query, array $filters = []): void
{
if (isset($filters['unit_id']) && $filters['unit_id']) {
$query->where('unit_id', $filters['unit_id']);
}
// Apply government_id filter
if (isset($filters['government_id']) && $filters['government_id']) {
$unitIds = $this->getUnitsByGovernment($filters['government_id']);
if (!empty($unitIds)) {
$query->whereIn('unit_id', $unitIds);
} else {
// No units found for this government - return empty result
$query->whereNull('id');
}
}
if (isset($filters['camp_id']) && $filters['camp_id']) {
$query->whereHas('unit', function ($q) use ($filters) {
$q->where('camp_id', $filters['camp_id']);
});
}
if (isset($filters['formation_id']) && $filters['formation_id']) {
// Get units under this formation
$unitIds = Unit::where('formation_id', $filters['formation_id'])
->pluck('id')
->toArray();
if (!empty($unitIds)) {
$query->whereIn('unit_id', $unitIds);
} else {
// No units found for this formation - return empty result
$query->whereNull('id');
}
}
}
}