50,000): Secretary → Chair → Board */ class FinanceDocumentApprovalService { /** * Approve by Secretary (first stage) */ public function approveBySecretary(FinanceDocument $document, User $user): array { if (! $document->canBeApprovedBySecretary($user)) { return ['success' => false, 'message' => '無法在此階段進行秘書長審核。']; } $document->update([ 'approved_by_secretary_id' => $user->id, 'secretary_approved_at' => now(), 'status' => FinanceDocument::STATUS_APPROVED_SECRETARY, ]); AuditLogger::log('finance_document.approved_by_secretary', $document, [ 'approved_by' => $user->name, 'amount_tier' => $document->amount_tier, ]); // Small amount: approval complete if ($document->amount_tier === FinanceDocument::AMOUNT_TIER_SMALL) { $this->notifySubmitter($document); return [ 'success' => true, 'message' => '秘書長已核准。小額申請審核完成,申請人可向出納領款。', 'complete' => true, ]; } // Medium/Large: notify chairs $this->notifyNextApprovers($document, 'finance_chair'); return [ 'success' => true, 'message' => '秘書長已核准。已送交理事長審核。', 'complete' => false, ]; } /** * Approve by Chair (second stage) */ public function approveByChair(FinanceDocument $document, User $user): array { if (! $document->canBeApprovedByChair($user)) { return ['success' => false, 'message' => '無法在此階段進行理事長審核。']; } $document->update([ 'approved_by_chair_id' => $user->id, 'chair_approved_at' => now(), 'status' => FinanceDocument::STATUS_APPROVED_CHAIR, ]); AuditLogger::log('finance_document.approved_by_chair', $document, [ 'approved_by' => $user->name, 'amount_tier' => $document->amount_tier, ]); // Medium amount: approval complete if ($document->amount_tier === FinanceDocument::AMOUNT_TIER_MEDIUM) { $this->notifySubmitter($document); return [ 'success' => true, 'message' => '理事長已核准。中額申請審核完成,申請人可向出納領款。', 'complete' => true, ]; } // Large: notify board members $this->notifyNextApprovers($document, 'finance_board_member'); return [ 'success' => true, 'message' => '理事長已核准。大額申請需送交董理事會審核。', 'complete' => false, ]; } /** * Approve by Board (third stage) */ public function approveByBoard(FinanceDocument $document, User $user): array { if (! $document->canBeApprovedByBoard($user)) { return ['success' => false, 'message' => '無法在此階段進行董理事會審核。']; } $document->update([ 'board_meeting_approved_by_id' => $user->id, 'board_meeting_approved_at' => now(), 'status' => FinanceDocument::STATUS_APPROVED_BOARD, ]); AuditLogger::log('finance_document.approved_by_board', $document, [ 'approved_by' => $user->name, 'amount_tier' => $document->amount_tier, ]); $this->notifySubmitter($document); return [ 'success' => true, 'message' => '董理事會已核准。審核流程完成,申請人可向出納領款。', 'complete' => true, ]; } /** * Reject document at any stage */ public function reject(FinanceDocument $document, User $user, string $reason): array { if ($document->isRejected()) { return ['success' => false, 'message' => '此申請已被駁回。']; } $document->update([ 'status' => FinanceDocument::STATUS_REJECTED, 'rejected_by_user_id' => $user->id, 'rejected_at' => now(), 'rejection_reason' => $reason, ]); AuditLogger::log('finance_document.rejected', $document, [ 'rejected_by' => $user->name, 'reason' => $reason, ]); // Notify submitter if ($document->submittedBy) { Mail::to($document->submittedBy->email)->queue(new FinanceDocumentRejected($document)); } return [ 'success' => true, 'message' => '申請已駁回。', ]; } /** * Process approval based on user role */ public function processApproval(FinanceDocument $document, User $user): array { $isSecretary = $user->hasRole('secretary_general'); $isChair = $user->hasRole('finance_chair'); $isBoardMember = $user->hasRole('finance_board_member'); $isAdmin = $user->hasRole('admin'); // Secretary approval if ($document->canBeApprovedBySecretary($user) && ($isSecretary || $isAdmin)) { return $this->approveBySecretary($document, $user); } // Chair approval if ($document->canBeApprovedByChair($user) && ($isChair || $isAdmin)) { return $this->approveByChair($document, $user); } // Board approval if ($document->canBeApprovedByBoard($user) && ($isBoardMember || $isAdmin)) { return $this->approveByBoard($document, $user); } return ['success' => false, 'message' => '您無權在此階段審核此文件。']; } /** * Notify submitter that approval is complete */ protected function notifySubmitter(FinanceDocument $document): void { if ($document->submittedBy) { Mail::to($document->submittedBy->email)->queue(new FinanceDocumentFullyApproved($document)); } } /** * Notify next approvers in workflow */ protected function notifyNextApprovers(FinanceDocument $document, string $roleName): void { $approvers = User::role($roleName)->get(); foreach ($approvers as $approver) { Mail::to($approver->email)->queue(new FinanceDocumentApprovedByAccountant($document)); } } }