496 lines
17 KiB
PHP
496 lines
17 KiB
PHP
<?php
|
|
|
|
namespace Modules\Dashboard\Repositories;
|
|
|
|
use Modules\Dashboard\Repositories\Contracts\PKJDashboardRepositoryInterface;
|
|
use Modules\PKJAssetEntitlement\Entities\PKJAssetEntitlement;
|
|
use Modules\PKJAssetHolding\Entities\PKJAssetHolding;
|
|
use Modules\PKJRepair\Entities\PKJRepair;
|
|
use Modules\PKJReport\Entities\PKJReport;
|
|
use Modules\PKJVariant\Entities\PKJCategory;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Modules\Unit\Entities\Unit;
|
|
use App\Services\VisibilityService;
|
|
use Carbon\Carbon;
|
|
|
|
class PKJDashboardRepository implements PKJDashboardRepositoryInterface
|
|
{
|
|
public function __construct(
|
|
protected VisibilityService $visibilityService
|
|
) {}
|
|
/**
|
|
* Get total PKJ asset entitlement
|
|
*/
|
|
public function getTotalEntitlement(array $filters = [], $user = null): int
|
|
{
|
|
$query = PKJAssetEntitlement::query();
|
|
|
|
// Apply visibility scoping if user is provided
|
|
if ($user) {
|
|
$query->visibleTo($user);
|
|
}
|
|
|
|
$this->applyFilters($query, $filters, excludeDateFilter: true);
|
|
|
|
return $query->sum('entitlement');
|
|
}
|
|
|
|
public function getTotalHoldings(array $filters = [], $user = null): int
|
|
{
|
|
$query = PKJAssetEntitlement::query();
|
|
|
|
// Apply visibility scoping if user is provided
|
|
if ($user) {
|
|
$query->visibleTo($user);
|
|
}
|
|
|
|
$this->applyFilters($query, $filters, excludeDateFilter: true);
|
|
|
|
return $query->sum('holding') ?? 0;
|
|
}
|
|
|
|
/**
|
|
* Get PKJ asset holdings grouped by status
|
|
*/
|
|
public function getHoldingsByStatus(array $filters = [], $user = null): array
|
|
{
|
|
$query = PKJAssetHolding::query();
|
|
|
|
// Apply visibility scoping if user is provided
|
|
if ($user) {
|
|
$query->visibleTo($user);
|
|
}
|
|
|
|
$this->applyFilters($query, $filters);
|
|
|
|
return $query->selectRaw('status, COUNT(*) as count')
|
|
->groupBy('status')
|
|
->orderBy('status')
|
|
->get()
|
|
->toArray();
|
|
}
|
|
|
|
/**
|
|
* Get PKJ asset holdings by specific status
|
|
*/
|
|
public function getHoldingsBySpecificStatus(string $status, array $filters = [], $user = null): array
|
|
{
|
|
$query = PKJAssetHolding::query();
|
|
|
|
// Apply visibility scoping if user is provided
|
|
if ($user) {
|
|
$query->visibleTo($user);
|
|
}
|
|
|
|
$this->applyFilters($query, $filters);
|
|
|
|
$holdings = $query->where('status', $status)->paginate(10);
|
|
|
|
return [
|
|
'data' => $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 PKJ monthly trends
|
|
*/
|
|
public function getMonthlyTrends(array $filters = [], $user = null): array
|
|
{
|
|
$dateRange = $this->getDateRange($filters);
|
|
|
|
$query = PKJReport::query();
|
|
|
|
// Apply visibility scoping if user is provided
|
|
if ($user) {
|
|
$query->visibleTo($user);
|
|
}
|
|
|
|
$trends = $query->selectRaw('
|
|
TO_CHAR(created_at, \'YYYY-MM\') as month,
|
|
COUNT(*) as total_reports
|
|
')
|
|
->whereBetween('created_at', [$dateRange['from'], $dateRange['to']])
|
|
->groupBy('month')
|
|
->orderBy('month')
|
|
->get()
|
|
->toArray();
|
|
|
|
return $trends;
|
|
}
|
|
|
|
/**
|
|
* Get PKJ holdings by category
|
|
*/
|
|
public function getHoldingsByCategory(array $filters = [], $user = null): array
|
|
{
|
|
// Build a subquery for filtered entitlements
|
|
// This ensures all categories are included, but only matching entitlements are summed
|
|
$entitlementSubquery = PKJAssetEntitlement::query()
|
|
->select('pkj_category_id', DB::raw('SUM(holding) as total_holding'))
|
|
->groupBy('pkj_category_id');
|
|
|
|
// Apply visibility scoping if user is provided
|
|
if ($user) {
|
|
$entitlementSubquery->visibleTo($user);
|
|
}
|
|
|
|
// Apply filters to entitlement subquery
|
|
if (isset($filters['unit_id'])) {
|
|
$entitlementSubquery->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)) {
|
|
$entitlementSubquery->whereIn('unit_id', $unitIds);
|
|
} else {
|
|
// If no units match, set to empty result but don't filter categories
|
|
$entitlementSubquery->whereRaw('1 = 0');
|
|
}
|
|
}
|
|
|
|
// Apply formation_id filter
|
|
if (isset($filters['formation_id']) && $filters['formation_id']) {
|
|
$unitIds = Unit::where('formation_id', $filters['formation_id'])
|
|
->pluck('id')
|
|
->toArray();
|
|
|
|
if (!empty($unitIds)) {
|
|
$entitlementSubquery->whereIn('unit_id', $unitIds);
|
|
} else {
|
|
// If no units match, set to empty result but don't filter categories
|
|
$entitlementSubquery->whereRaw('1 = 0');
|
|
}
|
|
}
|
|
|
|
// Main query: Get ALL categories and LEFT JOIN with filtered entitlements
|
|
// This ensures all categories are returned even if they have no matching entitlements
|
|
$query = PKJCategory::query()
|
|
->leftJoinSub($entitlementSubquery, 'filtered_entitlements', function ($join) {
|
|
$join->on('pkj_categories.id', '=', 'filtered_entitlements.pkj_category_id');
|
|
})
|
|
->selectRaw('
|
|
pkj_categories.id as category_id,
|
|
pkj_categories.name as category_name,
|
|
COALESCE(filtered_entitlements.total_holding, 0) as total_holding
|
|
')
|
|
->orderBy('pkj_categories.name');
|
|
|
|
return $query->get()->toArray();
|
|
}
|
|
|
|
/**
|
|
* Get PKJ category metrics (KEUPAYAAN, KESIAGAAN, SERVISIBILITI)
|
|
*/
|
|
public function getCategoryMetrics(int $categoryId, array $filters = [], $user = null): array
|
|
{
|
|
// Get category name
|
|
$category = PKJCategory::find($categoryId);
|
|
$categoryName = $category ? $category->name : 'Unknown Category';
|
|
|
|
// Get entitlement and holding data for the category
|
|
$entitlementQuery = PKJAssetEntitlement::query()
|
|
->where('pkj_category_id', $categoryId);
|
|
|
|
// Apply visibility scoping if user is provided
|
|
if ($user) {
|
|
$entitlementQuery->visibleTo($user);
|
|
}
|
|
|
|
// Apply filters
|
|
if (isset($filters['unit_id'])) {
|
|
$entitlementQuery->where('unit_id', $filters['unit_id']);
|
|
}
|
|
|
|
$entitlementData = $entitlementQuery->selectRaw('
|
|
SUM(entitlement) as total_entitlement,
|
|
SUM(holding) as total_holding
|
|
')->first();
|
|
|
|
// Get operational/usable assets count (BDG, BT, BP statuses) for the category
|
|
$operationalQuery = PKJAssetHolding::query()
|
|
->where('pkj_category_id', $categoryId)
|
|
->whereIn('status', ['BDG', 'BT', 'BP']);
|
|
|
|
// Apply visibility scoping if user is provided
|
|
if ($user) {
|
|
$operationalQuery->visibleTo($user);
|
|
}
|
|
|
|
// Apply filters
|
|
if (isset($filters['unit_id'])) {
|
|
$operationalQuery->where('unit_id', $filters['unit_id']);
|
|
}
|
|
|
|
$operationalCount = $operationalQuery->count();
|
|
|
|
// Get BDG, BT, BG count specifically for servisibiliti calculation
|
|
$bdgQuery = PKJAssetHolding::query()
|
|
->where('pkj_category_id', $categoryId)
|
|
->whereIn('status', ['BDG', 'BT', 'BG']);
|
|
|
|
// Apply visibility scoping if user is provided
|
|
if ($user) {
|
|
$bdgQuery->visibleTo($user);
|
|
}
|
|
|
|
// Apply filters
|
|
if (isset($filters['unit_id'])) {
|
|
$bdgQuery->where('unit_id', $filters['unit_id']);
|
|
}
|
|
|
|
$bdgCount = $bdgQuery->count();
|
|
|
|
$totalEntitlement = $entitlementData ? $entitlementData->total_entitlement : 0;
|
|
$totalHolding = $entitlementData ? $entitlementData->total_holding : 0;
|
|
|
|
// Calculate metrics
|
|
$keupayaan = $totalEntitlement > 0 ? ($totalHolding / $totalEntitlement) * 100 : 0;
|
|
$kesiagaan = $totalEntitlement > 0 ? ($operationalCount / $totalEntitlement) * 100 : 0;
|
|
$servisibiliti = $totalHolding > 0 ? ($bdgCount / $totalHolding) * 100 : 0;
|
|
|
|
return [
|
|
'category_id' => $categoryId,
|
|
'category_name' => $categoryName,
|
|
'keupayaan' => round($keupayaan, 2),
|
|
'kesiagaan' => round($kesiagaan, 2),
|
|
'servisibiliti' => round($servisibiliti, 2),
|
|
'total_entitlement' => $totalEntitlement,
|
|
'total_holding' => $totalHolding,
|
|
'operational_count' => $operationalCount,
|
|
'bdg_count' => $bdgCount
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get PKJ category status breakdown
|
|
*/
|
|
public function getCategoryStatusBreakdown(int $categoryId, array $filters = [], $user = null): array
|
|
{
|
|
// Get category name
|
|
$category = PKJCategory::find($categoryId);
|
|
$categoryName = $category ? $category->name : 'Unknown Category';
|
|
|
|
$query = PKJAssetHolding::query()
|
|
->where('pkj_category_id', $categoryId)
|
|
->selectRaw('status, COUNT(*) as count')
|
|
->groupBy('status')
|
|
->orderBy('status');
|
|
|
|
// Apply visibility scoping if user is provided
|
|
if ($user) {
|
|
$query->visibleTo($user);
|
|
}
|
|
|
|
// Apply filters
|
|
if (isset($filters['unit_id'])) {
|
|
$query->where('unit_id', $filters['unit_id']);
|
|
}
|
|
|
|
$statusCounts = $query->get()->toArray();
|
|
|
|
return [
|
|
'category_id' => $categoryId,
|
|
'category_name' => $categoryName,
|
|
'status_breakdown' => $statusCounts
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get PKJ asset details by category and status.
|
|
*
|
|
* When the asset has no status yet, the status column in the database is NULL.
|
|
* Callers pass status as '' or the string 'null' to request these "no status" assets.
|
|
*/
|
|
public function getAssetDetailsByCategoryAndStatus(int $categoryId, string $status, array $filters = [], $user = null): array
|
|
{
|
|
// Handle no-status: API passes '' or 'null'; DB column is NULL for assets not yet assigned a status
|
|
$status = trim($status ?? '');
|
|
|
|
$query = PKJAssetHolding::query()
|
|
->with(['unit', 'pkjCategory', 'pkjSubcategory', 'pkjModel'])
|
|
->where('pkj_category_id', $categoryId);
|
|
|
|
if ($status === '' || $status === 'null') {
|
|
// Assets with status column NULL (not yet assigned a status)
|
|
$query->whereNull('status');
|
|
} else {
|
|
// For specific status, must match exactly and NOT be null
|
|
$query->whereNotNull('status')
|
|
->where('status', '=', $status);
|
|
}
|
|
|
|
$query->select([
|
|
'id',
|
|
'uuid',
|
|
'registration_number',
|
|
'kewpa_registration_number',
|
|
'status',
|
|
'unit_id',
|
|
'pkj_category_id',
|
|
'pkj_subcategory_id',
|
|
'pkj_model_id',
|
|
'purchase_date',
|
|
'receipt_date',
|
|
'purchase_price',
|
|
'contract_reference',
|
|
'country_of_manufacture',
|
|
'economic_year',
|
|
'economic_distance',
|
|
'note',
|
|
'engine_number',
|
|
'chassis_number',
|
|
'created_at',
|
|
'updated_at'
|
|
]);
|
|
|
|
// Apply visibility scoping if user is provided
|
|
if ($user) {
|
|
$query->visibleTo($user);
|
|
}
|
|
|
|
// Apply filters
|
|
if (isset($filters['unit_id'])) {
|
|
$query->where('unit_id', $filters['unit_id']);
|
|
}
|
|
|
|
$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()
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Apply common filters to query
|
|
*/
|
|
private function applyFilters($query, array $filters = [], bool $excludeDateFilter = false): void
|
|
{
|
|
// Only apply created_at filter if the model has created_at column
|
|
$model = $query->getModel();
|
|
if ($model && method_exists($model, 'getTable')) {
|
|
$schema = $model->getConnection()->getSchemaBuilder();
|
|
$columns = $schema->getColumnListing($model->getTable());
|
|
|
|
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 ($model && method_exists($model, 'getTable')) {
|
|
$columns = $schema->getColumnListing($model->getTable());
|
|
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']) {
|
|
// Get units under the government
|
|
$unitIds = $this->getUnitsByGovernment($filters['government_id']);
|
|
|
|
if ($model && method_exists($model, 'getTable')) {
|
|
$columns = $schema->getColumnListing($model->getTable());
|
|
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 formation_id filter if provided
|
|
if (isset($filters['formation_id']) && $filters['formation_id']) {
|
|
if ($model && method_exists($model, 'getTable')) {
|
|
$columns = $schema->getColumnListing($model->getTable());
|
|
if (in_array('unit_id', $columns)) {
|
|
// 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');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply camp_id filter if provided
|
|
if (isset($filters['camp_id']) && $filters['camp_id']) {
|
|
if ($model && method_exists($model, 'getTable')) {
|
|
$columns = $schema->getColumnListing($model->getTable());
|
|
if (in_array('camp_id', $columns)) {
|
|
$query->where('camp_id', $filters['camp_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 = \Modules\Formation\Entities\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()
|
|
];
|
|
}
|
|
}
|