# 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 1. [Executive Summary](#1-executive-summary) 2. [System Architecture](#2-system-architecture) 3. [Database Schema](#3-database-schema) 4. [Core Features](#4-core-features) 5. [Workflows](#5-workflows) 6. [Security & Authorization](#6-security--authorization) 7. [Email Notifications](#7-email-notifications) 8. [File Structure](#8-file-structure) 9. [Configuration](#9-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 1. **Public Users:** Can self-register as members 2. **Members:** Can submit payments, view membership status, request issues 3. **Cashier:** First-tier verification for payments and documents 4. **Accountant:** Second-tier verification 5. **Chair:** Third-tier final approval 6. **Membership Manager:** Activates memberships after approval 7. **Staff:** General administrative access 8. **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 | | email | 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 | | email | 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 expiry - `canSubmitPayment()` - Returns true if pending and no pending payment - `getPendingPayment()` - Gets payment awaiting verification - `getMembershipStatusBadgeAttribute()` - CSS badge class - `getMembershipStatusLabelAttribute()` - Chinese label - `getMembershipTypeLabelAttribute()` - 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:** 1. `pending` - Awaiting Tier 1 verification 2. `approved_cashier` - Tier 1 approved, awaiting Tier 2 3. `approved_accountant` - Tier 2 approved, awaiting Tier 3 4. `approved_chair` - Fully approved (triggers activation) 5. `rejected` - Rejected at any tier **Key Methods:** - `canBeApprovedByCashier()` - Validates Tier 1 eligibility - `canBeApprovedByAccountant()` - Validates Tier 2 eligibility - `canBeApprovedByChair()` - Validates Tier 3 eligibility - `getStatusLabelAttribute()` - Chinese status label - `getPaymentMethodLabelAttribute()` - 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 - budgeted - `getVariancePercentageAttribute()` - (variance / budgeted) × 100 - `getRemainingBudgetAttribute()` - budgeted - actual - `isOverBudget()` - 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:** 1. **Pending:** Registered but not yet paid/verified 2. **Active:** Payment approved and membership activated 3. **Expired:** Membership expiry date has passed 4. **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: 1. Cashier approval (Tier 1) 2. Accountant approval (Tier 2) 3. 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:** 1. **Comments:** - Add comments to issues - is_internal flag hides comments from members - Notifies watchers 2. **Attachments:** - Upload files to issues - Download attachments - Delete attachments 3. **Time Logging:** - Log hours worked on issues - Specify work date - Automatic summation 4. **Watchers:** - Add users to watch issue updates - Receive email notifications 5. **Labels:** - Color-coded labels - Filterable - Multiple labels per issue 6. **Sub-tasks:** - Create child issues via parent_issue_id - Hierarchical structure 7. **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:** 1. **admin** - Full system access 2. **staff** - Internal tools access 3. **cashier** - Payment Tier 1 verification 4. **accountant** - Payment Tier 2 verification 5. **chair** - Payment Tier 3 approval 6. **payment_cashier** - Dedicated cashier role 7. **payment_accountant** - Dedicated accountant role 8. **payment_chair** - Dedicated chair role 9. **membership_manager** - Membership activation **Permissions:** - `verify_payments_cashier` - Tier 1 approval - `verify_payments_accountant` - Tier 2 approval - `verify_payments_chair` - Tier 3 approval - `activate_memberships` - Activate memberships - `view_payment_verifications` - View dashboard **Middleware:** - `EnsureUserIsAdmin` - Protects `/admin` routes - `CheckPaidMembership` - 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 | Email | 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 | Email | 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 | Email | 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 ```env 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**