first init
This commit is contained in:
@@ -0,0 +1,495 @@
|
||||
<?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()
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user