diff --git a/Modules/ApprovalWorkflow/app/Http/Controllers/ApprovalController.php b/Modules/ApprovalWorkflow/app/Http/Controllers/ApprovalController.php index 59b2050..1f8217b 100644 --- a/Modules/ApprovalWorkflow/app/Http/Controllers/ApprovalController.php +++ b/Modules/ApprovalWorkflow/app/Http/Controllers/ApprovalController.php @@ -23,16 +23,27 @@ class ApprovalController extends Controller $user = $request->user(); $tab = $request->get('tab', 'pending'); + $isAdmin = $user->user_type === 'admin' || $user->hasRole(['Super Admin', 'admin', 'Main Contractor Admin']); + $isApprovingRole = $isAdmin || $user->hasRole(['Project Manager', 'Contractor Admin']); + $query = ApprovalChain::query(); if ($tab === 'history') { - $query->whereHas('steps', function ($q) use ($user) { - $q->where('approver_id', $user->id)->where('status', '!=', 'pending'); - }); + if ($isApprovingRole) { + $query->whereIn('status', ['approved', 'rejected']); + } else { + $query->whereHas('steps', function ($q) use ($user) { + $q->where('approver_id', $user->id)->where('status', '!=', 'pending'); + }); + } } else { - $query->whereHas('steps', function ($q) use ($user) { - $q->where('approver_id', $user->id)->where('status', 'pending'); - })->whereIn('status', ['pending', 'in_review']); + if ($isApprovingRole) { + $query->whereIn('status', ['pending', 'in_review']); + } else { + $query->whereHas('steps', function ($q) use ($user) { + $q->where('approver_id', $user->id)->where('status', 'pending'); + })->whereIn('status', ['pending', 'in_review']); + } } $chains = $query->with(['steps.approver:id,name', 'initiator:id,name']) diff --git a/Modules/ApprovalWorkflow/app/Services/ApprovalService.php b/Modules/ApprovalWorkflow/app/Services/ApprovalService.php index d3e81a0..37d1aa6 100644 --- a/Modules/ApprovalWorkflow/app/Services/ApprovalService.php +++ b/Modules/ApprovalWorkflow/app/Services/ApprovalService.php @@ -63,6 +63,7 @@ class ApprovalService $step = $this->getCurrentStepForApprover($chain, $approver); $step->update([ + 'approver_id' => $approver->id, 'status' => ApprovalStepStatus::Approved, 'notes' => $notes, 'acted_at' => now(), @@ -70,17 +71,12 @@ class ApprovalService StepApproved::dispatch($chain, $step); - // Check if all steps are approved - if ($chain->isFullyApproved()) { - $chain->transitionTo(ApprovalChainStatus::Approved); - ApprovalCompleted::dispatch($chain); - } else { - // Notify next approver - $nextStep = $chain->currentStep(); - if ($nextStep) { - ApprovalRequired::dispatch($chain, $nextStep); - } - } + // One admin/manager approval completes the chain for estimations, requisitions, and single-step requests + $chain->steps()->where('status', ApprovalStepStatus::Pending)->update([ + 'status' => ApprovalStepStatus::Skipped, + ]); + $chain->transitionTo(ApprovalChainStatus::Approved); + ApprovalCompleted::dispatch($chain); return $chain->fresh('steps.approver'); } @@ -126,7 +122,9 @@ class ApprovalService throw new \InvalidArgumentException('No pending step found in this approval chain.'); } - if ($step->approver_id !== $approver->id) { + $isAdmin = $approver->user_type === 'admin' || $approver->hasRole(['Super Admin', 'admin', 'Main Contractor Admin']); + + if ($step->approver_id !== $approver->id && !$isAdmin) { throw new \InvalidArgumentException('You are not the current approver for this step.'); } diff --git a/Modules/ApprovalWorkflow/resources/js/Components/ApprovalTimeline.tsx b/Modules/ApprovalWorkflow/resources/js/Components/ApprovalTimeline.tsx index 15ef6e7..e454078 100644 --- a/Modules/ApprovalWorkflow/resources/js/Components/ApprovalTimeline.tsx +++ b/Modules/ApprovalWorkflow/resources/js/Components/ApprovalTimeline.tsx @@ -22,12 +22,14 @@ const statusConfig: Record s.status !== 'skipped'); + return (
- {steps.map((step, idx) => { + {visibleSteps.map((step, idx) => { const config = statusConfig[step.status] || statusConfig.pending; const Icon = config.icon; - const isLast = idx === steps.length - 1; + const isLast = idx === visibleSteps.length - 1; return (
diff --git a/Modules/ApprovalWorkflow/resources/js/Pages/Approvals/Index.tsx b/Modules/ApprovalWorkflow/resources/js/Pages/Approvals/Index.tsx index 1af8ebf..2e6c386 100644 --- a/Modules/ApprovalWorkflow/resources/js/Pages/Approvals/Index.tsx +++ b/Modules/ApprovalWorkflow/resources/js/Pages/Approvals/Index.tsx @@ -78,7 +78,6 @@ export default function Index({ approvals, tab }: Props) { Type Initiated By Status - Steps Submitted Actions @@ -86,7 +85,7 @@ export default function Index({ approvals, tab }: Props) { {approvals.data.length === 0 ? ( - + {isHistory ? 'No past approvals found.' : "No pending approvals. You're all caught up! 🎉"} @@ -100,9 +99,6 @@ export default function Index({ approvals, tab }: Props) { {typeLabel(chain.status)} - - {chain.steps.filter(s => s.status === 'approved').length}/{chain.steps.length} - {new Date(chain.created_at).toLocaleDateString()} diff --git a/Modules/ApprovalWorkflow/resources/js/Pages/Approvals/Show.tsx b/Modules/ApprovalWorkflow/resources/js/Pages/Approvals/Show.tsx index 3b1f2c6..d098cae 100644 --- a/Modules/ApprovalWorkflow/resources/js/Pages/Approvals/Show.tsx +++ b/Modules/ApprovalWorkflow/resources/js/Pages/Approvals/Show.tsx @@ -40,7 +40,8 @@ export default function Show({ chain, breakdownData }: Props) { const [rejectNotes, setRejectNotes] = useState(''); const currentStep = chain.steps.find(s => s.status === 'pending'); - const isCurrentApprover = currentStep && currentStep.approver.id === auth.user.id; + const isAdmin = auth.user.user_type === 'admin' || auth.user.roles?.some((r: any) => ['Super Admin', 'admin', 'Main Contractor Admin'].includes(r.name)); + const isCurrentApprover = currentStep && (currentStep.approver.id === auth.user.id || isAdmin); const handleApprove = () => { router.patch(route('approvals.approve', chain.ulid), { notes: approveNotes || undefined }); @@ -113,14 +114,6 @@ export default function Show({ chain, breakdownData }: Props) { {/* Approvable Breakdown */} - {/* Timeline */} - - Approval Progress - - - - - {/* Action Buttons */} {isCurrentApprover && chain.status === 'in_review' && ( diff --git a/Modules/ContractorManagement/app/Http/Controllers/ContractorController.php b/Modules/ContractorManagement/app/Http/Controllers/ContractorController.php index fefdc75..97a816f 100644 --- a/Modules/ContractorManagement/app/Http/Controllers/ContractorController.php +++ b/Modules/ContractorManagement/app/Http/Controllers/ContractorController.php @@ -246,6 +246,14 @@ class ContractorController extends Controller public function approveInvoice(ContractorInvoice $invoice) { + $user = auth()->user(); + $isHigherUp = $user->user_type === 'admin' || + $user->roles()->whereIn('name', ['Super Admin', 'admin', 'Main Contractor Admin'])->exists(); + + if (!$isHigherUp) { + return back()->with('error', 'Unauthorized. Only Main Contractor Admin or Super Admin can approve subcontractor invoices.'); + } + try { $invoice->transitionTo(InvoiceStatus::Approved); } catch (\InvalidArgumentException $e) { diff --git a/Modules/ContractorManagement/resources/js/Pages/Contractors/Form.tsx b/Modules/ContractorManagement/resources/js/Pages/Contractors/Form.tsx index c444a43..e4af3f1 100644 --- a/Modules/ContractorManagement/resources/js/Pages/Contractors/Form.tsx +++ b/Modules/ContractorManagement/resources/js/Pages/Contractors/Form.tsx @@ -153,7 +153,7 @@ export default function Form({ contractor }: Props) { form.setData('create_admin', e.target.checked)} + onChange={(e: React.ChangeEvent) => form.setData('create_admin', e.target.checked)} />