Files
usher-manage-stack/CLAUDE.md
gbanyan a30af8eaf7 Add headless CMS for official site content management
Integrate article and page management into the Laravel admin dashboard
to serve as a headless CMS for the Next.js frontend (usher-site).

Backend:
- 7 migrations: article_categories, article_tags, articles, pivots, attachments, pages
- 5 models with relationships: Article, ArticleCategory, ArticleTag, ArticleAttachment, Page
- 4 admin controllers: articles (with publish/archive/pin), categories, tags, pages
- Admin views with EasyMDE markdown editor, multi-select categories/tags
- Navigation section "官網管理" in admin sidebar

API (v1):
- GET /api/v1/articles (filtered by type, category, tag, search; paginated)
- GET /api/v1/articles/{slug} (with related articles)
- GET /api/v1/categories
- GET /api/v1/pages/{slug} (with children)
- GET /api/v1/homepage (aggregated homepage data)
- Attachment download endpoint
- CORS configured for usher.org.tw, vercel.app, localhost:3000

Content migration:
- ImportHugoContent command: imports Hugo markdown files as articles/pages
- Successfully imported 27 articles, 17 categories, 11 tags, 9 pages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 11:58:22 +08:00

6.3 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

Taiwan NPO (Non-Profit Organization) management platform built with Laravel 10 (PHP 8.1+). Features member lifecycle management, multi-tier financial approval workflows, issue tracking, and document management. UI is in Traditional Chinese. Currency is TWD (NT$).

Commands

# Development
php artisan serve && npm run dev    # Start both servers

# Testing
php artisan test                              # Run all tests
php artisan test --filter=ClassName           # Run specific test class
php artisan test --filter=test_method_name    # Run specific test method
php artisan dusk                              # Run browser tests

# Database
php artisan migrate:fresh --seed              # Reset with all seeders
php artisan db:seed --class=TestDataSeeder    # Seed test data only
php artisan db:seed --class=FinancialWorkflowTestDataSeeder  # Finance test data

# Code Quality
./vendor/bin/pint                  # Fix code style (PSR-12)
./vendor/bin/phpstan analyse       # Static analysis

Tech Stack

  • Backend: Laravel 10, PHP 8.1+, Spatie Laravel Permission
  • Frontend: Blade templates, Tailwind CSS 3.1 (darkMode: 'class'), Alpine.js 3.4, Vite 5
  • PDF/Excel: barryvdh/laravel-dompdf, maatwebsite/excel
  • QR Codes: simplesoftwareio/simple-qrcode
  • DB: SQLite (dev), MySQL (production)

Architecture

Route Structure

Routes split across routes/web.php and routes/auth.php. Admin routes use group pattern:

Route::middleware(['auth', 'admin'])->prefix('admin')->name('admin.')->group(...)
  • Public: /register/member, /documents
  • Member (auth only): dashboard, payment submission
  • Admin (auth + admin): /admin/* with admin.{module}.{action} named routes

The admin middleware (EnsureUserIsAdmin) allows access if user has admin role OR any permissions at all.

Multi-Tier Approval Workflows

Tiered approval based on amount thresholds (configurable in config/accounting.php):

  • Small (<5,000): Secretary approval only
  • Medium (5,000-50,000): Secretary → Chair
  • Large (>50,000): Secretary → Chair → Board

Finance documents follow a 3-stage lifecycle with separate status fields:

  1. Approval Stage (status): Multi-tier approval based on amount
  2. Disbursement Stage (disbursement_status): Dual confirmation (requester + cashier)
  3. Recording Stage (recording_status): Accountant records to ledger
$doc->isApprovalComplete()      // All required approvals obtained
$doc->isDisbursementComplete()  // Both parties confirmed
$doc->isRecordingComplete()     // Ledger entry created
$doc->isFullyProcessed()        // All 3 stages complete

Shared Traits

  • HasApprovalWorkflow: Multi-tier approval with self-approval prevention (isSelfApproval())
  • HasAccountingEntries: Double-entry bookkeeping, auto-generation, balance validation

Service Layer

Complex business logic in app/Services/:

  • MembershipFeeCalculator: Fees with disability discount support
  • SettingsService: System-wide settings with caching
  • FinanceDocumentApprovalService: Multi-tier approval orchestration
  • PaymentVerificationService: Payment workflow coordination

Global helper: settings('key') returns cached setting values (defined in app/helpers.php, autoloaded).

RBAC Structure

Uses Spatie Laravel Permission. Core financial roles:

  • finance_requester, finance_cashier, finance_accountant, finance_chair, finance_board_member

Permission checks: $user->can('permission-name') or @can('permission-name') in Blade.

Data Security Patterns

  • National ID: AES-256 encrypted (national_id_encrypted), SHA256 hashed for search (national_id_hash), virtual accessor national_id decrypts on read
  • File uploads: Private disk storage, served via authenticated controller
  • Audit logging: AuditLogger::log($action, $auditable, $metadata) — static class, call in controllers

Member Lifecycle

States: pendingactiveexpired / suspended

Identity types: patient, parent, social, other (病友/家長分類). Members can have guardian_member_id for parent-child relationships.

$member->hasPaidMembership()   // Active with future expiry
$member->canSubmitPayment()    // Pending with no pending payment
$member->getNextFeeType()      // entrance_fee or annual_fee

Conventions

  • Status values: Defined as class constants (e.g., FinanceDocument::STATUS_PENDING), not enums or magic strings
  • Controller validation: Uses Form Request classes (StoreMemberRequest, etc.)
  • DB transactions: Used in controllers for multi-step operations
  • UI text: Hardcoded Traditional Chinese strings (no __() translation layer)
  • Dark mode: Supported throughout — use dark: Tailwind prefix for styles

Custom Artisan Commands

  • assign:role — Manual role assignment
  • import:members / import:accounting-data / import:documents — Bulk imports
  • send:membership-expiry-reminders — Automated email notifications
  • archive:expired-documents — Document lifecycle cleanup

Testing Patterns

Tests use RefreshDatabase trait. Setup commonly includes:

protected function setUp(): void
{
    parent::setUp();
    $this->artisan('db:seed', ['--class' => 'RoleSeeder']);
}

Test accounts (password: password):

  • admin@test.com - Full access
  • requester@test.com - Submit documents
  • cashier@test.com - Tier 1 approval
  • accountant@test.com - Tier 2 approval
  • chair@test.com - Tier 3 approval

Key Files

  • routes/web.php — All web routes (admin routes under /admin prefix)
  • config/accounting.php — Account codes, amount tier thresholds, currency settings
  • app/Models/FinanceDocument.php — Core financial workflow logic with 27+ status constants
  • app/Models/Member.php — Member lifecycle with encrypted field handling
  • app/Traits/HasApprovalWorkflow.php — Shared multi-tier approval behavior
  • app/Traits/HasAccountingEntries.php — Double-entry bookkeeping behavior
  • app/helpers.php — Global settings() helper (autoloaded via composer)
  • database/seeders/ — RoleSeeder, ChartOfAccountSeeder, TestDataSeeder
  • docs/SYSTEM_SPECIFICATION.md — Complete system specification