feat: enhance module RBAC permissions, financial billing workflows, task evidence uploading, and role analytics
This commit is contained in:
@@ -407,15 +407,38 @@ class DashboardController extends Controller
|
||||
->count();
|
||||
|
||||
// 2. PM Milestone & Progress Analytics
|
||||
$milestonesQuery = \Modules\TimelineScheduling\Models\Milestone::query();
|
||||
$milestonesQuery = \Modules\ProjectManagement\Models\ProjectMilestone::with('tasks');
|
||||
if ($project) {
|
||||
$milestonesQuery->where('project_id', $project->id);
|
||||
} else {
|
||||
$milestonesQuery->whereIn('project_id', $visibleProjectIds);
|
||||
}
|
||||
$totalMilestones = $milestonesQuery->count();
|
||||
$completedMilestones = (clone $milestonesQuery)->where('status', 'completed')->count();
|
||||
$inProgressMilestones = (clone $milestonesQuery)->where('status', 'in_progress')->count();
|
||||
$allMilestones = $milestonesQuery->get();
|
||||
$totalMilestones = $allMilestones->count();
|
||||
$completedMilestones = 0;
|
||||
$inProgressMilestones = 0;
|
||||
|
||||
foreach ($allMilestones as $m) {
|
||||
$taskCount = $m->tasks->count();
|
||||
$completedTaskCount = $m->tasks->filter(function ($t) {
|
||||
$st = is_object($t->status) ? $t->status->value : $t->status;
|
||||
return in_array(strtolower($st ?? ''), ['completed', 'closed', 'done']);
|
||||
})->count();
|
||||
|
||||
$isCompleted = ($m->actual_date !== null) || ($taskCount > 0 && $completedTaskCount === $taskCount);
|
||||
|
||||
if ($isCompleted) {
|
||||
$completedMilestones++;
|
||||
} else {
|
||||
$hasActivity = $m->planned_date !== null || $m->tasks->contains(function ($t) {
|
||||
$st = is_object($t->status) ? $t->status->value : $t->status;
|
||||
return in_array(strtolower($st ?? ''), ['in_progress', 'completed', 'closed', 'done']);
|
||||
});
|
||||
if ($hasActivity) {
|
||||
$inProgressMilestones++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Subcontractor / Bidding Analytics
|
||||
$bidsQuery = \Modules\BiddingManagement\Models\BidPackage::query();
|
||||
@@ -425,11 +448,33 @@ class DashboardController extends Controller
|
||||
$bidsQuery->whereIn('project_id', $visibleProjectIds);
|
||||
}
|
||||
$activeBids = $bidsQuery->count();
|
||||
$openInvitations = \Modules\BiddingManagement\Models\BidInvitation::where('status', 'sent')
|
||||
->whereHas('package', function ($query) use ($visibleProjectIds, $project) {
|
||||
$query->whereIn('project_id', $project ? [$project->id] : $visibleProjectIds);
|
||||
})
|
||||
->count();
|
||||
|
||||
$openInvitationsQuery = \Modules\BiddingManagement\Models\BidInvitation::query()
|
||||
->whereIn('status', ['invited', 'accepted'])
|
||||
->whereDoesntHave('submission');
|
||||
|
||||
if ($user?->contractor_id) {
|
||||
$openInvitationsQuery->where('contractor_id', $user->contractor_id);
|
||||
$openInvitations = $openInvitationsQuery
|
||||
->whereHas('package', function ($query) use ($project) {
|
||||
$query->where('status', 'open');
|
||||
if ($project) {
|
||||
$query->where('project_id', $project->id);
|
||||
}
|
||||
})
|
||||
->count();
|
||||
} else {
|
||||
$openInvitations = $openInvitationsQuery
|
||||
->whereHas('package', function ($query) use ($visibleProjectIds, $project) {
|
||||
$query->where('status', 'open');
|
||||
if ($project) {
|
||||
$query->where('project_id', $project->id);
|
||||
} elseif (!empty($visibleProjectIds)) {
|
||||
$query->whereIn('project_id', $visibleProjectIds);
|
||||
}
|
||||
})
|
||||
->count();
|
||||
}
|
||||
|
||||
// 4. Warehouse & Technical Inventory
|
||||
$totalItems = \Modules\MasterData\Models\Material::count();
|
||||
@@ -437,6 +482,41 @@ class DashboardController extends Controller
|
||||
$totalDocuments = \Modules\DocumentManagement\Models\Document::whereIn('project_id', $project ? [$project->id] : $visibleProjectIds)->count();
|
||||
$pendingCashAdvances = \Modules\FinancialManagement\Models\CashAdvance::where('status', 'pending')->count();
|
||||
|
||||
// 5. Unconfirmed Payments
|
||||
$pendingInvoicesList = \Modules\FinancialManagement\Models\FinancialInvoice::with('project:id,name,code')
|
||||
->whereIn('project_id', $visibleProjectIds)
|
||||
->where('status', 'payment_sent')
|
||||
->get(['id', 'ulid', 'invoice_number', 'project_id', 'total_amount', 'status'])
|
||||
->map(fn ($inv) => [
|
||||
'id' => 'inv_' . $inv->id,
|
||||
'ulid' => $inv->ulid,
|
||||
'number' => $inv->invoice_number,
|
||||
'project_name' => $inv->project?->name ?? 'Project',
|
||||
'amount' => (float) $inv->total_amount,
|
||||
'type' => 'invoice',
|
||||
'confirm_url' => route('finance.confirm-payment', $inv->ulid),
|
||||
'view_url' => route('finance.show', $inv->ulid),
|
||||
]);
|
||||
|
||||
$pendingRetentionsList = \Modules\FinancialManagement\Models\RetentionEntry::with('project:id,name,code')
|
||||
->whereIn('project_id', $visibleProjectIds)
|
||||
->where('type', 'credit')
|
||||
->whereIn('status', ['payment_sent', 'submitted'])
|
||||
->get(['id', 'ulid', 'project_id', 'amount', 'status', 'description'])
|
||||
->map(fn ($ret) => [
|
||||
'id' => 'ret_' . $ret->id,
|
||||
'ulid' => $ret->ulid,
|
||||
'number' => 'Retention Release',
|
||||
'project_name' => $ret->project?->name ?? 'Project',
|
||||
'amount' => (float) $ret->amount,
|
||||
'type' => 'retention',
|
||||
'status' => $ret->status,
|
||||
'confirm_url' => route('retention.confirm', $ret->ulid),
|
||||
'view_url' => route('retention.index'),
|
||||
]);
|
||||
|
||||
$mergedPayments = $pendingInvoicesList->concat($pendingRetentionsList);
|
||||
|
||||
return [
|
||||
'role' => $roleName,
|
||||
'roles' => $user?->getRoleNames()->values()->all() ?? [],
|
||||
@@ -470,6 +550,11 @@ class DashboardController extends Controller
|
||||
'documents' => $totalDocuments,
|
||||
'pending_cash_advances' => $pendingCashAdvances,
|
||||
],
|
||||
'unconfirmed_payments' => [
|
||||
'count' => $mergedPayments->count(),
|
||||
'total_amount' => (float) $mergedPayments->sum('amount'),
|
||||
'items' => $mergedPayments->values()->all(),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user