42 KiB
Taiwan NPO Membership Management System
Complete System Specification
Version: 1.0 Last Updated: 2025-11-20 Technology Stack: Laravel 11, PHP 8.x, MySQL, Spatie Permission
Table of Contents
- Executive Summary
- System Architecture
- Database Schema
- Core Features
- Workflows
- Security & Authorization
- Email Notifications
- File Structure
- Configuration
1. Executive Summary
This system is a comprehensive membership management platform designed specifically for Taiwan NPOs (Non-Profit Organizations). It implements a complete lifecycle for member registration, payment verification, financial management, issue tracking, and budget management.
Key Capabilities
- Member Lifecycle Management: Registration → Payment → 3-Tier Verification → Activation
- Financial Management: Budget planning, transaction tracking, finance document approval
- Issue Tracking: Complete work item management with time logging and collaboration
- Multi-Tier Approval Workflows: 3-tier verification for payments and finance documents
- Audit Logging: Complete audit trail for compliance and accountability
- Role-Based Access Control: Granular permissions using Spatie Permission package
User Roles
- Public Users: Can self-register as members
- Members: Can submit payments, view membership status, request issues
- Cashier: First-tier verification for payments and documents
- Accountant: Second-tier verification
- Chair: Third-tier final approval
- Membership Manager: Activates memberships after approval
- Staff: General administrative access
- Admin: Full system access
2. System Architecture
2.1 Technology Stack
Backend:
- Framework: Laravel 11
- PHP Version: 8.x
- Database: MySQL 8.0+
- Authentication: Laravel Breeze
- Authorization: Spatie Laravel Permission
- Queue System: Database/Redis queue driver
Frontend:
- Template Engine: Blade
- CSS Framework: Tailwind CSS
- JavaScript: Alpine.js (via Breeze)
- Dark Mode: Supported
Infrastructure:
- File Storage: Laravel Storage (private disk)
- Email: Laravel Mail with queue support
- Encryption: AES-256 for sensitive data
2.2 Application Layers
┌─────────────────────────────────────────┐
│ Web Interface (Blade) │
│ - Public Registration │
│ - Member Dashboard │
│ - Admin Panel │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ HTTP Layer (Controllers) │
│ - Request Validation │
│ - Business Logic Coordination │
│ - Response Formatting │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Application Layer (Models) │
│ - Business Logic │
│ - Eloquent Relationships │
│ - Accessors & Mutators │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Data Layer (Database) │
│ - MySQL Database │
│ - Migrations & Schema │
│ - Indexes & Constraints │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Support Services (Cross-cutting) │
│ - AuditLogger │
│ - Email Notifications │
│ - File Storage │
│ - Encryption Services │
└─────────────────────────────────────────┘
3. Database Schema
3.1 Core Tables
users
Primary authentication table.
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | bigint unsigned | PK, AUTO_INCREMENT | Primary key |
| name | varchar(255) | NOT NULL | User's full name |
| varchar(255) | NOT NULL, UNIQUE | Email address | |
| email_verified_at | timestamp | NULL | Email verification time |
| password | varchar(255) | NOT NULL | Bcrypt hashed password |
| is_admin | boolean | DEFAULT false | Legacy admin flag |
| profile_photo_path | varchar(2048) | NULL | Profile photo path |
| remember_token | varchar(100) | NULL | Remember me token |
| created_at | timestamp | NULL | Creation timestamp |
| updated_at | timestamp | NULL | Last update timestamp |
Relationships:
- HasOne: Member
- BelongsToMany: Role (via model_has_roles)
- BelongsToMany: Permission (via model_has_permissions)
members
Member profile information.
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | bigint unsigned | PK, AUTO_INCREMENT | Primary key |
| user_id | bigint unsigned | FK(users), NULL, UNIQUE | Associated user account |
| full_name | varchar(255) | NOT NULL, INDEXED | Full name |
| varchar(255) | NOT NULL, INDEXED | Email (indexed for search) | |
| phone | varchar(20) | NULL | Phone number |
| national_id_encrypted | text | NULL | AES-256 encrypted national ID |
| national_id_hash | varchar(64) | NULL, INDEXED | SHA256 hash for search |
| address_line_1 | varchar(255) | NULL | Address line 1 |
| address_line_2 | varchar(255) | NULL | Address line 2 |
| city | varchar(100) | NULL | City |
| postal_code | varchar(10) | NULL | Postal code |
| emergency_contact_name | varchar(255) | NULL | Emergency contact name |
| emergency_contact_phone | varchar(20) | NULL | Emergency contact phone |
| membership_started_at | date | NULL | Membership start date |
| membership_expires_at | date | NULL | Membership expiry date |
| membership_status | enum | DEFAULT 'pending' | Status: pending, active, expired, suspended |
| membership_type | enum | DEFAULT 'regular' | Type: regular, honorary, lifetime, student |
| created_at | timestamp | NULL | Creation timestamp |
| updated_at | timestamp | NULL | Last update timestamp |
Relationships:
- BelongsTo: User
- HasMany: MembershipPayment
- HasMany: FinanceDocument
- HasMany: Issue (for member requests)
Key Methods:
hasPaidMembership()- Returns true if active with future expirycanSubmitPayment()- Returns true if pending and no pending paymentgetPendingPayment()- Gets payment awaiting verificationgetMembershipStatusBadgeAttribute()- CSS badge classgetMembershipStatusLabelAttribute()- Chinese labelgetMembershipTypeLabelAttribute()- Chinese type label
membership_payments
Payment records with 3-tier verification workflow.
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | bigint unsigned | PK, AUTO_INCREMENT | Primary key |
| member_id | bigint unsigned | FK(members), NOT NULL | Member who paid |
| paid_at | date | NOT NULL | Payment date |
| amount | decimal(10,2) | NOT NULL | Payment amount (TWD) |
| method | varchar(255) | NULL | Legacy payment method |
| reference | varchar(255) | NULL | Legacy reference |
| status | enum | DEFAULT 'pending' | Workflow status |
| payment_method | enum | NULL | Method: bank_transfer, convenience_store, cash, credit_card |
| receipt_path | varchar(255) | NULL | Receipt file path (private storage) |
| submitted_by_user_id | bigint unsigned | FK(users), NULL | User who submitted |
| verified_by_cashier_id | bigint unsigned | FK(users), NULL | Tier 1 verifier |
| cashier_verified_at | timestamp | NULL | Tier 1 timestamp |
| verified_by_accountant_id | bigint unsigned | FK(users), NULL | Tier 2 verifier |
| accountant_verified_at | timestamp | NULL | Tier 2 timestamp |
| verified_by_chair_id | bigint unsigned | FK(users), NULL | Tier 3 verifier |
| chair_verified_at | timestamp | NULL | Tier 3 timestamp |
| rejected_by_user_id | bigint unsigned | FK(users), NULL | Rejector |
| rejected_at | timestamp | NULL | Rejection timestamp |
| rejection_reason | text | NULL | Reason for rejection |
| notes | text | NULL | Admin notes |
| created_at | timestamp | NULL | Creation timestamp |
| updated_at | timestamp | NULL | Last update timestamp |
Workflow States:
pending- Awaiting Tier 1 verificationapproved_cashier- Tier 1 approved, awaiting Tier 2approved_accountant- Tier 2 approved, awaiting Tier 3approved_chair- Fully approved (triggers activation)rejected- Rejected at any tier
Key Methods:
canBeApprovedByCashier()- Validates Tier 1 eligibilitycanBeApprovedByAccountant()- Validates Tier 2 eligibilitycanBeApprovedByChair()- Validates Tier 3 eligibilitygetStatusLabelAttribute()- Chinese status labelgetPaymentMethodLabelAttribute()- Chinese method label
finance_documents
Finance document approval workflow (3-tier).
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | bigint unsigned | PK, AUTO_INCREMENT | Primary key |
| member_id | bigint unsigned | FK(members), NULL | Related member |
| submitted_by_user_id | bigint unsigned | FK(users), NOT NULL | Submitter |
| title | varchar(255) | NOT NULL | Document title |
| amount | decimal(10,2) | NULL | Amount (if applicable) |
| status | varchar(255) | DEFAULT 'pending' | Workflow status |
| description | text | NULL | Description |
| attachment_path | varchar(255) | NULL | Attachment file path |
| approved_by_cashier_id | bigint unsigned | FK(users), NULL | Tier 1 approver |
| cashier_approved_at | timestamp | NULL | Tier 1 timestamp |
| approved_by_accountant_id | bigint unsigned | FK(users), NULL | Tier 2 approver |
| accountant_approved_at | timestamp | NULL | Tier 2 timestamp |
| approved_by_chair_id | bigint unsigned | FK(users), NULL | Tier 3 approver |
| chair_approved_at | timestamp | NULL | Tier 3 timestamp |
| rejected_by_user_id | bigint unsigned | FK(users), NULL | Rejector |
| rejected_at | timestamp | NULL | Rejection timestamp |
| rejection_reason | text | NULL | Reason for rejection |
| submitted_at | timestamp | NULL | Submission timestamp |
| created_at | timestamp | NULL | Creation timestamp |
| updated_at | timestamp | NULL | Last update timestamp |
Same 3-tier workflow as membership_payments
issues
Issue tracking system with comprehensive features.
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | bigint unsigned | PK, AUTO_INCREMENT | Primary key |
| issue_number | varchar(50) | NOT NULL, UNIQUE | Auto: ISS-2025-001 |
| title | varchar(255) | NOT NULL | Issue title |
| description | text | NULL | Issue description |
| issue_type | enum | NOT NULL | Type: work_item, project_task, maintenance, member_request |
| status | enum | DEFAULT 'new', INDEXED | Status: new, assigned, in_progress, review, closed |
| priority | enum | DEFAULT 'medium', INDEXED | Priority: low, medium, high, urgent |
| created_by_user_id | bigint unsigned | FK(users), INDEXED | Creator |
| assigned_to_user_id | bigint unsigned | FK(users), NULL, INDEXED | Assignee |
| reviewer_id | bigint unsigned | FK(users), NULL | Reviewer |
| member_id | bigint unsigned | FK(members), NULL | Related member |
| parent_issue_id | bigint unsigned | FK(issues), NULL | Parent issue (for sub-tasks) |
| due_date | date | NULL, INDEXED | Due date |
| closed_at | timestamp | NULL | Closure timestamp |
| estimated_hours | decimal(8,2) | NULL | Estimated hours |
| actual_hours | decimal(8,2) | DEFAULT 0 | Actual hours (from time logs) |
| created_at | timestamp | NULL | Creation timestamp |
| updated_at | timestamp | NULL | Last update timestamp |
| deleted_at | timestamp | NULL | Soft delete timestamp |
Relationships:
- HasMany: IssueComment, IssueAttachment, IssueTimeLog
- BelongsToMany: IssueLabel, User (watchers)
- BelongsTo: User (creator, assignee, reviewer)
- BelongsTo: Issue (parent)
Key Methods:
- Status checks:
isNew(),isAssigned(),isInProgress(),inReview(),isClosed() - Workflow validation:
canBeAssigned(),canMoveToInProgress(),canMoveToReview(),canBeClosed() - Calculations:
getProgressPercentageAttribute(),getIsOverdueAttribute(),getTotalTimeLoggedAttribute()
budgets
Budget management with lifecycle workflow.
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | bigint unsigned | PK, AUTO_INCREMENT | Primary key |
| fiscal_year | integer | NOT NULL, INDEXED | Fiscal year (2000-2100) |
| name | varchar(255) | NOT NULL | Budget name |
| period_type | enum | NOT NULL | Type: annual, quarterly, monthly |
| period_start | date | NOT NULL | Period start date |
| period_end | date | NOT NULL | Period end date |
| status | enum | DEFAULT 'draft', INDEXED | Status: draft, submitted, approved, active, closed |
| created_by_user_id | bigint unsigned | FK(users), NOT NULL | Creator |
| approved_by_user_id | bigint unsigned | FK(users), NULL | Approver |
| approved_at | timestamp | NULL | Approval timestamp |
| notes | text | NULL | Notes |
| created_at | timestamp | NULL | Creation timestamp |
| updated_at | timestamp | NULL | Last update timestamp |
Workflow: draft → submitted → approved → active → closed
Key Methods:
- Status checks:
isDraft(),isApproved(),isActive(),isClosed() - Validation:
canBeEdited(),canBeApproved() - Calculations:
getTotalBudgetedIncomeAttribute(),getTotalActualExpenseAttribute(), etc.
budget_items
Line items within budgets.
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | bigint unsigned | PK, AUTO_INCREMENT | Primary key |
| budget_id | bigint unsigned | FK(budgets), NOT NULL, INDEXED | Parent budget |
| chart_of_account_id | bigint unsigned | FK(chart_of_accounts), NOT NULL | Account code |
| budgeted_amount | decimal(15,2) | NOT NULL | Planned amount |
| actual_amount | decimal(15,2) | NOT NULL, DEFAULT 0 | Actual amount spent |
| notes | text | NULL | Notes |
| created_at | timestamp | NULL | Creation timestamp |
| updated_at | timestamp | NULL | Last update timestamp |
Composite Index: (budget_id, chart_of_account_id)
Key Methods:
getVarianceAttribute()- actual - budgetedgetVariancePercentageAttribute()- (variance / budgeted) × 100getRemainingBudgetAttribute()- budgeted - actualisOverBudget()- Returns true if actual > budgeted
transactions
Financial transaction records.
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | bigint unsigned | PK, AUTO_INCREMENT | Primary key |
| budget_item_id | bigint unsigned | FK(budget_items), NULL | Linked budget item |
| chart_of_account_id | bigint unsigned | FK(chart_of_accounts), NOT NULL | Account code |
| transaction_date | date | NOT NULL, INDEXED | Transaction date |
| amount | decimal(15,2) | NOT NULL | Amount |
| transaction_type | enum | NOT NULL, INDEXED | Type: income, expense |
| description | varchar(255) | NOT NULL | Description |
| reference_number | varchar(255) | NULL | Reference number |
| finance_document_id | bigint unsigned | FK(finance_documents), NULL | Linked document |
| membership_payment_id | bigint unsigned | FK(membership_payments), NULL | Linked payment |
| created_by_user_id | bigint unsigned | FK(users), NOT NULL | Creator |
| notes | text | NULL | Notes |
| created_at | timestamp | NULL | Creation timestamp |
| updated_at | timestamp | NULL | Last update timestamp |
Indexes: transaction_date, transaction_type, (budget_item_id, transaction_date)
chart_of_accounts
Hierarchical chart of accounts for financial tracking.
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | bigint unsigned | PK, AUTO_INCREMENT | Primary key |
| account_code | varchar(50) | NOT NULL, UNIQUE | Account code (e.g., 4000) |
| account_name_zh | varchar(255) | NOT NULL | Chinese name |
| account_name_en | varchar(255) | NOT NULL | English name |
| account_type | enum | NOT NULL | Type: income, expense, asset, liability, net_asset |
| category | varchar(100) | NULL | Category grouping |
| parent_account_id | bigint unsigned | FK(chart_of_accounts), NULL | Parent account (hierarchy) |
| is_active | boolean | DEFAULT true | Active flag |
| display_order | integer | DEFAULT 0 | Sort order |
| description | text | NULL | Description |
| created_at | timestamp | NULL | Creation timestamp |
| updated_at | timestamp | NULL | Last update timestamp |
Hierarchical Structure: Supports parent-child relationships for account grouping
audit_logs
Complete audit trail for compliance.
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | bigint unsigned | PK, AUTO_INCREMENT | Primary key |
| user_id | bigint unsigned | FK(users), NULL | User who performed action |
| action | varchar(255) | NOT NULL, INDEXED | Action name (e.g., member.created) |
| auditable_type | varchar(255) | NULL | Model class name |
| auditable_id | bigint unsigned | NULL | Model ID |
| metadata | json | NULL | Additional context |
| created_at | timestamp | NULL | Action timestamp |
| updated_at | timestamp | NULL | Last update timestamp |
Common Actions:
- member.self_registered, member.created, member.updated, member.activated
- payment.submitted, payment.approved_by_*, payment.rejected
- finance_document., issue., budget., transaction.
3.2 Supporting Tables
roles (Spatie Permission)
- id, name, guard_name, description, timestamps
permissions (Spatie Permission)
- id, name, guard_name, timestamps
model_has_roles (Spatie Permission)
- role_id, model_type, model_id
model_has_permissions (Spatie Permission)
- permission_id, model_type, model_id
role_has_permissions (Spatie Permission)
- permission_id, role_id
issue_comments
- id, issue_id, user_id, comment_text, is_internal, timestamps
issue_attachments
- id, issue_id, user_id, file_path, file_name, file_size, mime_type, timestamps
issue_labels
- id, name (unique), color, description, timestamps
issue_label_pivot
- issue_id, issue_label_id
issue_time_logs
- id, issue_id, user_id, hours, work_date, description, timestamps
issue_watchers
- id, issue_id, user_id
issue_relationships
- id, issue_id, related_issue_id, relationship_type, timestamps
custom_fields
- id, name, field_type, options (JSON), is_required, timestamps
custom_field_values
- id, custom_field_id, customizable_type, customizable_id, value (JSON), timestamps
financial_reports
- id, budget_id, report_type, report_data (JSON), generated_by_user_id, generated_at, timestamps
4. Core Features
4.1 Member Registration & Lifecycle
Public Self-Registration:
- Route:
GET/POST /register/member - Controller: PublicMemberRegistrationController
- Creates User + Member records
- Sets initial status to 'pending'
- Sends welcome email with payment instructions
- Auto-login after registration
Admin-Created Members:
- Route:
POST /admin/members - Controller: AdminMemberController
- Can create member with or without user account
- Sets initial status to 'pending'
Member States:
- Pending: Registered but not yet paid/verified
- Active: Payment approved and membership activated
- Expired: Membership expiry date has passed
- Suspended: Admin-suspended
Key Features:
- National ID encryption (AES-256)
- National ID hash (SHA256) for searching without decryption
- Emergency contact information
- Address management
- Membership type management (regular, student, honorary, lifetime)
4.2 Payment Verification Workflow (3-Tier)
Member Payment Submission:
- Route:
POST /member/payments - Controller: MemberPaymentController
- Upload receipt (JPG, PNG, PDF, max 10MB)
- Specify payment method, amount, date, reference
- Creates payment with status='pending'
- Stored in private storage
- Emails sent to member (confirmation) and cashiers (notification)
Tier 1: Cashier Verification
- Route:
POST /admin/payment-verifications/{payment}/approve-cashier - Permission:
verify_payments_cashier - Verifies receipt legitimacy
- Updates: status=approved_cashier, cashier_verified_at, verified_by_cashier_id
- Sends email to member and accountants
Tier 2: Accountant Verification
- Route:
POST /admin/payment-verifications/{payment}/approve-accountant - Permission:
verify_payments_accountant - Reviews financial details
- Updates: status=approved_accountant, accountant_verified_at, verified_by_accountant_id
- Sends email to member and chairs
Tier 3: Chair Approval
- Route:
POST /admin/payment-verifications/{payment}/approve-chair - Permission:
verify_payments_chair - Final approval
- Updates: status=approved_chair, chair_verified_at, verified_by_chair_id
- Automatically activates membership:
- member.membership_status = 'active'
- member.membership_started_at = today
- member.membership_expires_at = today + 1 year (or lifetime)
- Sends activation email to member
Rejection:
- Route:
POST /admin/payment-verifications/{payment}/reject - Can be done at any tier
- Requires rejection reason
- Updates: status=rejected, rejected_by_user_id, rejected_at, rejection_reason
- Sends rejection email with reason
- Member can resubmit
Dashboard:
- Route:
GET /admin/payment-verifications - Tabbed interface: All, Cashier Queue, Accountant Queue, Chair Queue, Approved, Rejected
- Shows counts for each queue
- Search by member name, email, reference
- Permission-based filtering
4.3 Finance Document Approval
Document Submission:
- Route:
POST /admin/finance-documents - Controller: FinanceDocumentController
- Title, optional amount, optional attachment
- Status starts as 'pending'
3-Tier Approval: Same workflow structure as payment verification:
- Cashier approval (Tier 1)
- Accountant approval (Tier 2)
- Chair approval (Tier 3)
Features:
- File attachment support
- Amount tracking
- Rejection with reason
- Email notifications at each stage
4.4 Issue Tracking System
Issue Creation:
- Route:
POST /admin/issues - Controller: IssueController
- Auto-generates issue number: ISS-{YYYY}-{incrementing}
- Required: title, type, priority
- Optional: description, assignee, labels, due date, estimated hours
Issue Types:
- work_item: General work tasks
- project_task: Project-related tasks
- maintenance: System maintenance
- member_request: Member support requests
Status Workflow:
new → assigned → in_progress → review → closed
Can reopen: closed → assigned
Priority Levels:
- low (default background color: gray)
- medium (default background color: blue)
- high (default background color: orange)
- urgent (default background color: red)
Collaboration Features:
-
Comments:
- Add comments to issues
- is_internal flag hides comments from members
- Notifies watchers
-
Attachments:
- Upload files to issues
- Download attachments
- Delete attachments
-
Time Logging:
- Log hours worked on issues
- Specify work date
- Automatic summation
-
Watchers:
- Add users to watch issue updates
- Receive email notifications
-
Labels:
- Color-coded labels
- Filterable
- Multiple labels per issue
-
Sub-tasks:
- Create child issues via parent_issue_id
- Hierarchical structure
-
Issue Relationships:
- blocks, is_blocked_by
- relates_to
- duplicates, is_duplicated_by
Automation:
- Auto-calculation of progress percentage (0-100% based on status)
- Overdue detection (due_date < today and not closed)
- Days until due calculation
Reports:
- Route:
GET /admin/issue-reports - Status distribution
- Priority distribution
- Workload analysis
4.5 Budget Management
Budget Creation:
- Route:
POST /admin/budgets - Controller: BudgetController
- Fiscal year, period type, date range
- Status starts as 'draft'
Budget Items:
- Link to chart of accounts
- Set budgeted amounts
- Track actual amounts (updated via transactions)
- Calculate variances
Workflow:
draft → submitted → approved → active → closed
Features:
- Total budgeted income/expense calculation
- Total actual income/expense calculation
- Variance analysis (budgeted vs actual)
- Utilization percentage
- Over-budget detection
Reporting:
- Generate financial reports
- Store report snapshots (JSON)
- Historical tracking
4.6 Transaction Management
Transaction Recording:
- Route:
POST /admin/transactions - Controller: TransactionController
- Type: income or expense
- Link to budget item (optional)
- Link to chart of account (required)
- Link to finance document or membership payment (optional)
- Date, amount, description
Features:
- Search by date range, type, description
- Automatic budget item actual amount update
- Reference number tracking
- Notes support
5. Workflows
5.1 Complete Member Journey
┌─────────────────────────────────────────────────────────────┐
│ STEP 1: REGISTRATION │
│ User fills public registration form │
│ → Creates User account (with password) │
│ → Creates Member record (status='pending') │
│ → Sends welcome email │
│ → Auto-login │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ STEP 2: PAYMENT SUBMISSION │
│ Member uploads receipt + payment details │
│ → Payment created (status='pending') │
│ → Receipt stored in private storage │
│ → Email to member (confirmation) │
│ → Email to cashiers (notification) │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ STEP 3: TIER 1 VERIFICATION (Cashier) │
│ Cashier reviews receipt and basic info │
│ → Approves or rejects │
│ IF APPROVED: │
│ → status='approved_cashier' │
│ → Email to member + accountants │
│ IF REJECTED: │
│ → status='rejected' │
│ → Email to member with reason │
│ → Member must resubmit │
└─────────────────────────────────────────────────────────────┘
↓ (if approved)
┌─────────────────────────────────────────────────────────────┐
│ STEP 4: TIER 2 VERIFICATION (Accountant) │
│ Accountant verifies financial details │
│ → Approves or rejects │
│ IF APPROVED: │
│ → status='approved_accountant' │
│ → Email to member + chairs │
│ IF REJECTED: (same as Tier 1) │
└─────────────────────────────────────────────────────────────┘
↓ (if approved)
┌─────────────────────────────────────────────────────────────┐
│ STEP 5: TIER 3 APPROVAL (Chair) │
│ Chair gives final approval │
│ → Approves or rejects │
│ IF APPROVED: │
│ → status='approved_chair' │
│ → AUTOMATIC ACTIVATION: │
│ ◦ member.membership_status = 'active' │
│ ◦ member.membership_started_at = today │
│ ◦ member.membership_expires_at = today + 1 year │
│ → Email to member (activation confirmation) │
│ → Email to membership managers (FYI) │
│ IF REJECTED: (same as Tier 1 & 2) │
└─────────────────────────────────────────────────────────────┘
↓ (if approved)
┌─────────────────────────────────────────────────────────────┐
│ STEP 6: ACTIVE MEMBERSHIP │
│ Member now has: │
│ → Access to member-only resources │
│ → Active membership badge │
│ → Membership expiry date │
│ → Full dashboard access │
└─────────────────────────────────────────────────────────────┘
5.2 Issue Lifecycle
┌──────────┐
│ NEW │ ← Issue created
└────┬─────┘
│ assign user
↓
┌──────────┐
│ ASSIGNED │ ← User assigned
└────┬─────┘
│ start work
↓
┌──────────────┐
│ IN_PROGRESS │ ← Work started
└────┬─────────┘
│ ready for review
↓
┌──────────┐
│ REVIEW │ ← Reviewing
└────┬─────┘
│ approve
↓
┌──────────┐
│ CLOSED │ ← Done
└──────────┘
↑
│ can reopen
└──────────
5.3 Budget Lifecycle
┌────────┐
│ DRAFT │ ← Create budget, add items
└───┬────┘
│ submit for approval
↓
┌───────────┐
│ SUBMITTED │ ← Pending approval
└─────┬─────┘
│ approve
↓
┌───────────┐
│ APPROVED │ ← Approved but not yet active
└─────┬─────┘
│ activate
↓
┌────────┐
│ ACTIVE │ ← Currently in use, transactions linked
└───┬────┘
│ period ends
↓
┌────────┐
│ CLOSED │ ← Period ended, archived
└────────┘
6. Security & Authorization
6.1 Authentication
Method: Session-based authentication via Laravel Breeze Features:
- Password hashing (Bcrypt)
- Email verification (optional)
- Remember me functionality
- Password reset via email
6.2 Authorization (Spatie Permission)
Roles:
- admin - Full system access
- staff - Internal tools access
- cashier - Payment Tier 1 verification
- accountant - Payment Tier 2 verification
- chair - Payment Tier 3 approval
- payment_cashier - Dedicated cashier role
- payment_accountant - Dedicated accountant role
- payment_chair - Dedicated chair role
- membership_manager - Membership activation
Permissions:
verify_payments_cashier- Tier 1 approvalverify_payments_accountant- Tier 2 approvalverify_payments_chair- Tier 3 approvalactivate_memberships- Activate membershipsview_payment_verifications- View dashboard
Middleware:
EnsureUserIsAdmin- Protects/adminroutesCheckPaidMembership- Verifies active paid membership
6.3 Data Security
National ID Protection:
- Stored encrypted (AES-256)
- Hashed with SHA256 for searching
- Never displayed in plain text
Password Security:
- Bcrypt hashing
- Minimum password requirements
- Password confirmation on sensitive actions
CSRF Protection:
- All POST/PATCH/DELETE requests protected
- Automatic token generation
File Security:
- Payment receipts stored in private disk
- Served only via authenticated controller methods
- File type validation on upload
SQL Injection Prevention:
- Eloquent ORM with parameter binding
- Never use raw queries without bindings
7. Email Notifications
7.1 Membership Emails
| Trigger | Recipients | |
|---|---|---|
| MemberRegistrationWelcomeMail | After self-registration | New member |
| PaymentSubmittedMail | Payment submitted | Member + Cashiers |
| PaymentApprovedByCashierMail | Tier 1 approval | Member + Accountants |
| PaymentApprovedByAccountantMail | Tier 2 approval | Member + Chairs |
| PaymentFullyApprovedMail | Tier 3 approval | Member + Membership Managers |
| PaymentRejectedMail | Payment rejected | Member |
| MembershipActivatedMail | Membership activated | Member |
| MembershipExpiryReminderMail | X days before expiry | Member |
7.2 Finance Emails
| Trigger | Recipients | |
|---|---|---|
| FinanceDocumentSubmitted | Document submitted | Cashiers |
| FinanceDocumentApprovedByCashier | Tier 1 approval | Submitter + Accountants |
| FinanceDocumentApprovedByAccountant | Tier 2 approval | Submitter + Chairs |
| FinanceDocumentFullyApproved | Tier 3 approval | Submitter |
| FinanceDocumentRejected | Document rejected | Submitter |
7.3 Issue Emails
| Trigger | Recipients | |
|---|---|---|
| IssueAssignedMail | Issue assigned | Assignee |
| IssueStatusChangedMail | Status changed | Creator + Assignee + Watchers |
| IssueCommentedMail | New comment | Creator + Assignee + Watchers |
| IssueDueSoonMail | Due within X days | Assignee |
| IssueOverdueMail | Past due date | Assignee |
| IssueClosedMail | Issue closed | Creator + Watchers |
7.4 Queue Configuration
All emails implement ShouldQueue for async delivery:
- Queue driver: database/redis
- Failed jobs table for retry
- Queue workers handle delivery
8. File Structure
usher-manage-stack/
├── app/
│ ├── Http/
│ │ ├── Controllers/
│ │ │ ├── AdminMemberController.php
│ │ │ ├── AdminPaymentController.php
│ │ │ ├── PaymentVerificationController.php
│ │ │ ├── PublicMemberRegistrationController.php
│ │ │ ├── MemberPaymentController.php
│ │ │ ├── MemberDashboardController.php
│ │ │ ├── FinanceDocumentController.php
│ │ │ ├── IssueController.php
│ │ │ ├── IssueLabelController.php
│ │ │ ├── IssueReportsController.php
│ │ │ ├── BudgetController.php
│ │ │ ├── TransactionController.php
│ │ │ ├── AdminRoleController.php
│ │ │ ├── AdminAuditLogController.php
│ │ │ └── AdminDashboardController.php
│ │ └── Middleware/
│ │ ├── EnsureUserIsAdmin.php
│ │ └── CheckPaidMembership.php
│ ├── Mail/
│ │ ├── MemberRegistrationWelcomeMail.php
│ │ ├── PaymentSubmittedMail.php
│ │ ├── PaymentApprovedByCashierMail.php
│ │ ├── PaymentApprovedByAccountantMail.php
│ │ ├── PaymentFullyApprovedMail.php
│ │ ├── PaymentRejectedMail.php
│ │ ├── MembershipActivatedMail.php
│ │ ├── FinanceDocument*.php (5 files)
│ │ └── Issue*.php (6 files)
│ ├── Models/
│ │ ├── Member.php
│ │ ├── MembershipPayment.php
│ │ ├── User.php
│ │ ├── Role.php
│ │ ├── Permission.php
│ │ ├── Issue.php
│ │ ├── IssueComment.php
│ │ ├── IssueAttachment.php
│ │ ├── IssueLabel.php
│ │ ├── IssueTimeLog.php
│ │ ├── Budget.php
│ │ ├── BudgetItem.php
│ │ ├── Transaction.php
│ │ ├── ChartOfAccount.php
│ │ ├── FinanceDocument.php
│ │ └── AuditLog.php
│ └── Support/
│ └── AuditLogger.php
├── database/
│ ├── migrations/
│ │ ├── 2025_11_18_092000_create_audit_logs_table.php
│ │ ├── 2025_11_18_093000_create_finance_documents_table.php
│ │ ├── 2025_11_19_133732_create_budgets_table.php
│ │ ├── 2025_11_19_133802_create_transactions_table.php
│ │ ├── 2025_11_19_144027_create_issues_table.php
│ │ ├── 2025_11_19_155725_enhance_membership_payments_table_for_verification.php
│ │ └── 2025_11_19_155807_add_membership_status_to_members_table.php
│ └── seeders/
│ ├── RoleSeeder.php
│ ├── PaymentVerificationRolesSeeder.php
│ ├── ChartOfAccountSeeder.php
│ └── IssueLabelSeeder.php
├── resources/
│ └── views/
│ ├── admin/
│ │ ├── members/
│ │ │ ├── index.blade.php
│ │ │ ├── show.blade.php
│ │ │ ├── create.blade.php
│ │ │ ├── edit.blade.php
│ │ │ └── activate.blade.php
│ │ ├── payment-verifications/
│ │ │ ├── index.blade.php
│ │ │ └── show.blade.php
│ │ ├── issues/
│ │ ├── budgets/
│ │ └── finance-documents/
│ ├── member/
│ │ ├── dashboard.blade.php
│ │ └── submit-payment.blade.php
│ ├── register/
│ │ └── member.blade.php
│ └── emails/
│ ├── members/
│ ├── payments/
│ ├── finance-documents/
│ └── issues/
└── routes/
└── web.php
9. Configuration
9.1 Environment Variables
APP_NAME="Taiwan NPO Membership System"
APP_ENV=production
APP_KEY=base64:...
APP_DEBUG=false
APP_URL=https://your-domain.com
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=usher_manage
DB_USERNAME=root
DB_PASSWORD=
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=noreply@your-domain.com
MAIL_FROM_NAME="${APP_NAME}"
QUEUE_CONNECTION=database
FILESYSTEM_DISK=local
9.2 Permissions Required
Cashier:
- verify_payments_cashier
- view_payment_verifications
Accountant:
- verify_payments_accountant
- view_payment_verifications
Chair:
- verify_payments_chair
- view_payment_verifications
Membership Manager:
- activate_memberships
- view_payment_verifications
Admin:
- All permissions (automatic grant)
10. Deployment Checklist
- Run migrations:
php artisan migrate - Seed roles & permissions:
php artisan db:seed --class=RoleSeeder - Seed payment roles:
php artisan db:seed --class=PaymentVerificationRolesSeeder - Seed chart of accounts:
php artisan db:seed --class=ChartOfAccountSeeder - Seed issue labels:
php artisan db:seed --class=IssueLabelSeeder - Configure mail settings
- Configure queue worker
- Set up file storage (private disk)
- Create admin user
- Assign admin role
- Test payment workflow end-to-end
- Test email delivery
- Set up SSL certificate
- Configure backup strategy
Appendix A: Glossary
Member: A registered person in the NPO system Payment Verification: 3-tier approval process for membership payments Tier 1/2/3: Sequential approval levels (Cashier/Accountant/Chair) Issue: Work item, task, or support request in the tracking system Budget: Financial plan for a fiscal period Chart of Account: Standardized account codes for financial tracking Audit Log: Record of all significant system actions
End of Specification Document