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>
136 lines
3.4 KiB
PHP
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;
|
|
}
|
|
}
|