646 lines
23 KiB
PHP
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');
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|