Files
usher-manage-stack/.planning/codebase/ARCHITECTURE.md
2026-02-13 10:34:18 +08:00

12 KiB

Architecture

Analysis Date: 2026-02-13

Pattern Overview

Overall: MVC (Model-View-Controller) with Service Layer for complex business logic

Key Characteristics:

  • Layered architecture: Controllers → Services → Models → Traits
  • Multi-tier approval workflows: Amount-based approval hierarchy (Secretary → Chair → Board)
  • Double-entry bookkeeping: Automatic accounting entry generation for financial documents
  • Trait-based shared behavior: Cross-cutting concerns (approval workflow, accounting entries)
  • Audit logging: Centralized action tracking via AuditLogger::log()
  • Role-based access control (RBAC): Spatie Laravel Permission with financial roles

Layers

Presentation Layer:

  • Purpose: Render web UI and handle HTTP requests
  • Location: resources/views/, app/Http/Controllers/
  • Contains: Blade templates, controllers, Form Requests, API Resources
  • Depends on: Models, Services, View Components
  • Used by: Web browsers, Next.js frontend (via API)

Controller Layer:

  • Purpose: HTTP request handling, input validation, orchestration
  • Location: app/Http/Controllers/
  • Contains: Controllers split by domain (Admin, Auth, Api)
  • Depends on: Models, Services, Form Requests
  • Pattern: Validation via Form Request classes (e.g., StoreFinanceDocumentRequest), service calls for business logic

Service Layer:

  • Purpose: Encapsulate complex business logic
  • Location: app/Services/
  • Contains: FinanceDocumentApprovalService, MembershipFeeCalculator, SettingsService, PaymentVerificationService, SiteRevalidationService
  • Depends on: Models, Audit Logger, Mail
  • Example: FinanceDocumentApprovalService orchestrates multi-tier approval workflow; returns ['success' => bool, 'message' => string, 'complete' => bool]

Model Layer:

  • Purpose: Data access, business rules, relationships
  • Location: app/Models/
  • Contains: 36 Eloquent models including Member, FinanceDocument, User, Issue, Article, Document
  • Depends on: Database, Traits
  • Key models and relationships documented in Finance Workflow section

Trait Layer:

  • Purpose: Shared behavior across multiple models
  • Location: app/Traits/
  • Contains: HasApprovalWorkflow, HasAccountingEntries
  • Usage: Models use these traits to inherit approval/accounting behavior without duplication

Support Layer:

  • Purpose: Utility functions and helper classes
  • Location: app/Support/
  • Contains: AuditLogger (centralized audit logging), DownloadFile (file download handler)
  • Global helper: settings('key') function in app/helpers.php — returns cached system settings

Data Flow

Member Lifecycle Flow

  1. Registration: Non-member completes form at /register/member → stored in Members table with status = pending
  2. Profile Activation: Member logs in, fills profile at /create-member-profile
  3. Payment Submission: Member submits payment proof at /member/submit-paymentMembershipPayments table
  4. Payment Verification: Admin verifies payment via multi-tier approval workflow
  5. Activation: Admin activates member → status = active, membership_expires_at set
  6. Expiry: Cron job or admin action → status = expired when membership_expires_at passes

Key Methods:

  • Member::hasPaidMembership() — checks if active with future expiry
  • Member::canSubmitPayment() — validates pending status with no pending payments
  • Member::getNextFeeType() — returns entrance_fee or annual_fee

Finance Document Approval Flow (3-Stage Lifecycle)

Stage 1: Approval (Amount-Based Multi-Tier)

  1. Submission: User submits finance document → FinanceDocument::STATUS_PENDING

    • submitted_by_user_id, submitted_at, amount, title set
    • Amount tier auto-determined: determineAmountTier() → small/medium/large
  2. Secretary Approval: Secretary reviews → approveBySecretary()

    • For small amount (<5,000): Approval complete, notifies submitter
    • For medium/large: Escalates to Chair, notifies next approvers
    • Status: STATUS_APPROVED_SECRETARY
  3. Chair Approval: Chair reviews (if amount ≥ 5,000) → approveByChair()

    • For medium amount (5,000-50,000): Approval complete
    • For large amount (>50,000): Escalates to Board, notifies board members
    • Status: STATUS_APPROVED_CHAIR
  4. Board Approval: Board members review (if amount > 50,000) → approveByBoard()

    • Final approval stage
    • Status: STATUS_APPROVED_BOARD

Rejection Handling: User can reject at any stage → STATUS_REJECTED, triggers email notification

Helper Methods:

$doc->isApprovalComplete()      // All required approvals obtained
$doc->canBeApprovedBySecretary($user)  // Validates secretary can approve
$doc->canBeApprovedByChair($user)      // Validates chair can approve
$doc->getAmountTierLabel()      // Returns 小額/中額/大額

Stage 2: Disbursement (Dual Confirmation)

  1. Requester Confirmation: Document requester confirms disbursement details

    • DISBURSEMENT_REQUESTER_CONFIRMED status
    • Field: disbursement_requester_confirmed_at
  2. Cashier Confirmation: Cashier verifies funds transferred

    • DISBURSEMENT_CASHIER_CONFIRMED status
    • Field: disbursement_cashier_confirmed_at
    • Creates PaymentOrder record
  3. Completion: Once both confirm → DISBURSEMENT_COMPLETED

Helper: $doc->isDisbursementComplete() — both parties confirmed

Stage 3: Recording (Ledger Entry)

  1. Cashier Ledger Entry: Cashier records transaction to ledger

    • Creates CashierLedgerEntry with debit/credit amounts
    • Status: RECORDING_PENDINGRECORDING_COMPLETED
  2. Accounting Entry: Automatic double-entry bookkeeping via HasAccountingEntries trait

    • Creates AccountingEntry records (debit + credit pair)
    • Account codes from config/accounting.php
    • Field: chart_of_account_id

Helper: $doc->isRecordingComplete() — ledger entry created

Full Completion: $doc->isFullyProcessed() — all three stages complete


Payment Verification Flow

Separate approval workflow for MembershipPayments with three tiers:

  1. Cashier Approval: Verifies payment received
  2. Accountant Approval: Validates accounting entry
  3. Chair Approval: Final sign-off for large payments

Route: /admin/payment-verifications with status-based filtering


CMS Content Flow (Articles/Pages)

  1. Creation: Admin creates article at /admin/articles/create with:

    • Title, slug, body (markdown via EasyMDE), featured image, status
    • Categories and tags (many-to-many relationships)
  2. Publication: Admin publishes → status = published, published_at set

  3. API Exposure: Content available via /api/v1/articles, /api/v1/pages

  4. Site Revalidation: On publish/archive, fires webhook to Next.js for static site rebuild

    • Service: SiteRevalidationService
    • Webhook: POST to services.nextjs.revalidate_url with token

Document Library Flow

  1. Upload: Admin uploads document at /admin/documents/create

    • Creates Document with status = active
    • Multiple versions tracked via DocumentVersion table
  2. Version Control:

    • New version upload creates DocumentVersion record
    • Promote version: sets as current version
    • Archive: soft delete via status = archived
  3. Public Access: Documents visible at /documents if access_level allows

    • Access tracking via DocumentAccessLog
    • QR code generation at /documents/{uuid}/qrcode
  4. Permission Check:

    • Method: $doc->canBeViewedBy($user) validates access
    • Visibility: public, members-only, or admin-only

State Management:

  • Status Fields: Use class constants, not enums (e.g., FinanceDocument::STATUS_PENDING)
  • Timestamps: Created/updated via timestamps; specific approval/rejection times via explicit fields
  • Soft Deletes: Used for documents (status = archived), not traditional soft delete
  • Transactions: DB transactions wrap multi-step operations (approval, payment, recording)

Key Abstractions

HasApprovalWorkflow Trait:

  • Purpose: Provides reusable multi-tier approval methods
  • Files: app/Traits/HasApprovalWorkflow.php
  • Used by: FinanceDocument, MembershipPayment, Budget
  • Methods: isSelfApproval(), canProceedWithApproval(), canBeRejected(), getApprovalHistory()
  • Pattern: Models define STATUS_* constants; trait provides helper methods; services orchestrate workflow

HasAccountingEntries Trait:

  • Purpose: Automatic double-entry bookkeeping
  • Files: app/Traits/HasAccountingEntries.php
  • Used by: FinanceDocument, Income, Transaction
  • Methods: debitEntries(), creditEntries(), validateBalance(), autoGenerateAccountingEntries()
  • Pattern: Account IDs from config/accounting.php; amount tier determines debit/credit mapping

Audit Logging:

  • Purpose: Track all business-critical actions
  • Class: app/Support/AuditLogger
  • Usage: AuditLogger::log('finance_document.approved_by_secretary', $document, ['approved_by' => $user->name])
  • Storage: AuditLog model with JSON metadata
  • Pattern: Static method callable from anywhere; filters secrets/uploads

Settings Service:

  • Purpose: Centralized system configuration
  • File: app/Services/SettingsService
  • Usage: settings('membership.entrance_fee') or settings('key', 'default')
  • Cache: Redis/cache layer to avoid repeated DB queries
  • Locations: SystemSetting model, config/ files

Entry Points

Web Routes:

  • Location: routes/web.php (393 lines covering all web endpoints)
  • Public: /, /register/member (if enabled), /documents
  • Authenticated: /dashboard, /my-membership, /member/submit-payment
  • Admin: /admin/* with admin middleware and named route prefix admin.{module}.{action}

API Routes:

  • Location: routes/api.php
  • Version: v1 prefix
  • Endpoints: /api/v1/articles, /api/v1/pages, /api/v1/homepage, /api/v1/public-documents
  • Authentication: Sanctum (optional for public endpoints)

Artisan Commands:

  • Location: app/Console/Commands/
  • Custom commands: assign:role, import:members, import:accounting-data, import:documents, send:membership-expiry-reminders, archive:expired-documents

Auth Routes:

  • Location: routes/auth.php
  • Includes: Login, registration (if enabled), password reset, email verification
  • Middleware: auth, verified

Error Handling

Strategy: Exception-based with Blade error views

Patterns:

  • Form validation errors: Automatically displayed in Blade templates via @error directive
  • Authorization errors: Thrown by @can directive or authorize() in controllers
  • Business logic errors: Services return ['success' => false, 'message' => 'reason'] or throw exceptions
  • Audit logging errors: Wrapped in try-catch to prevent breaking main flow
  • Exception handling: app/Exceptions/Handler.php defines response rendering

File uploads:

  • Stored in storage/app/ (private by default)
  • Finance documents: storage/app/finance-documents/
  • Article uploads: storage/app/articles/
  • Disability certificates: storage/app/disability-certificates/

Cross-Cutting Concerns

Logging:

  • Framework: Laravel's default logging (PSR-3 via Monolog)
  • Audit trail: AuditLogger class for business actions
  • Config: config/logging.php

Validation:

  • Location: app/Http/Requests/ — Form Request classes enforce rules
  • Pattern: Controller calls $request->validated() after implicit validation
  • Examples: StoreMemberRequest, StoreFinanceDocumentRequest, UpdateMemberRequest

Authentication:

  • Framework: Laravel Breeze (session-based)
  • Models: User model with HasFactory and relationships to roles/permissions
  • Guards: web (default for sessions), sanctum (for API)

Authorization:

  • Framework: Spatie Laravel Permission
  • Roles: admin, finance_requester, finance_cashier, finance_accountant, finance_chair, finance_board_member, membership_manager
  • Permissions: view_*, create_*, edit_*, delete_*, publish_*, custom permissions for finance tiers
  • Checking: $user->can('permission-name') in controllers, @can in Blade

Encryption:

  • National IDs: AES-256 encrypted in national_id_encrypted column
  • Hash for searching: SHA256 in national_id_hash column
  • Virtual accessor national_id: Auto-decrypts on read
  • Method: Member::findByNationalId($id) uses hash for query

Architecture analysis: 2026-02-13