Files
usher-manage-stack/app/Traits/HasApprovalWorkflow.php
gbanyan 42099759e8 Add phone login support and member import functionality
Features:
- Support login via phone number or email (LoginRequest)
- Add members:import-roster command for Excel roster import
- Merge survey emails with roster data

Code Quality (Phase 1-4):
- Add database locking for balance calculation
- Add self-approval checks for finance workflow
- Create service layer (FinanceDocumentApprovalService, PaymentVerificationService)
- Add HasAccountingEntries and HasApprovalWorkflow traits
- Create FormRequest classes for validation
- Add status-badge component
- Define authorization gates in AuthServiceProvider
- Add accounting config file

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 03:08:06 +08:00

136 lines
3.4 KiB
PHP

<?php
namespace App\Traits;
use App\Models\User;
/**
* Trait for models with multi-tier approval workflows.
*
* Usage:
* 1. Add `use HasApprovalWorkflow;` to your model
* 2. Define STATUS_* constants for each approval stage
* 3. Define STATUS_REJECTED constant
* 4. Override methods as needed for custom approval logic
*/
trait HasApprovalWorkflow
{
/**
* Check if document/payment is rejected
*/
public function isRejected(): bool
{
return $this->status === static::STATUS_REJECTED;
}
/**
* Check if document/payment is pending (initial state)
*/
public function isPending(): bool
{
return $this->status === static::STATUS_PENDING;
}
/**
* Check if self-approval is being attempted
* Prevents users from approving their own submissions
*
* @param User|null $user The user attempting to approve
* @param string $submitterField The field containing the submitter's user ID
*/
protected function isSelfApproval(?User $user, string $submitterField = 'submitted_by_user_id'): bool
{
if (! $user) {
return false;
}
$submitterId = $this->{$submitterField};
return $submitterId && $submitterId === $user->id;
}
/**
* Get the human-readable status label
* Override in model for custom labels
*/
public function getStatusLabelAttribute(): string
{
return ucfirst(str_replace('_', ' ', $this->status));
}
/**
* Check if approval can proceed based on current status
*
* @param string $requiredStatus The status required before this approval
* @param User|null $user The user attempting to approve
* @param bool $checkSelfApproval Whether to check for self-approval
*/
protected function canProceedWithApproval(
string $requiredStatus,
?User $user = null,
bool $checkSelfApproval = true
): bool {
if ($this->status !== $requiredStatus) {
return false;
}
if ($checkSelfApproval && $this->isSelfApproval($user)) {
return false;
}
return true;
}
/**
* Get the rejection details
*/
public function getRejectionDetails(): ?array
{
if (! $this->isRejected()) {
return null;
}
return [
'reason' => $this->rejection_reason ?? null,
'rejected_by' => $this->rejectedBy ?? null,
'rejected_at' => $this->rejected_at ?? null,
];
}
/**
* Check if model can be rejected
* Default: can reject if not already rejected
*/
public function canBeRejected(): bool
{
return ! $this->isRejected();
}
/**
* Get the next approver role required
* Override in model to implement specific logic
*/
public function getNextApproverRole(): ?string
{
return null;
}
/**
* Get approval history
* Returns array of approval stages that have been completed
*/
public function getApprovalHistory(): array
{
$history = [];
// This should be overridden in each model to provide specific fields
// Example structure:
// [
// ['stage' => 'cashier', 'user' => User, 'at' => Carbon],
// ['stage' => 'accountant', 'user' => User, 'at' => Carbon],
// ]
return $history;
}
}