# Phase 16 & CapRover — Reporting, Analytics & Deployment

---

# PART A: REPORTING & ANALYTICS

## Dashboard Widgets (Academy Admin)

### Financial Dashboard
| Widget | Data Source | Visualization |
|--------|-------------|---------------|
| Total Revenue (this month) | Sum of confirmed inbound payments | Big number + % vs last month |
| Outstanding Balance | Sum of all unpaid invoice amounts | Big number (red if high) |
| Overdue Invoices | Count + total of overdue invoices | Count badge + amount |
| Revenue Trend | Monthly revenue last 6 months | Line chart |
| Revenue by Source | Grouped by invoice item type (program, equipment, rental) | Pie/donut chart |
| Payment Methods | Breakdown by cash/card/wallet/transfer | Horizontal bar |
| Top Programs by Revenue | Programs ranked by total revenue | Ranked list |
| Upcoming Installments | Next 7 days of due installments | Table |
| Cash Session Status | Today's open/closed sessions | Status indicator |

### Operations Dashboard
| Widget | Data Source | Visualization |
|--------|-------------|---------------|
| Active Participants | Count of status=active | Big number + trend |
| New Registrations (this month) | Participants registered this month | Number + comparison |
| Attendance Rate (today) | Present / expected for today's sessions | Percentage + color |
| Sessions Today | Today's scheduled sessions with status | Timeline/list |
| Trainer Utilization | Hours assigned / available hours | Percentage bars per trainer |
| Facility Occupancy | Reserved segments / total segments today | Percentage per facility |
| Low Stock Alerts | Products below min_stock | Badge + list |
| Pending Approvals | Refunds, large discounts awaiting approval | Count + link |
| Upcoming Renewals | Subscriptions expiring in 7 days | Count + list |

### SuperAdmin Dashboard
| Widget | Data Source | Visualization |
|--------|-------------|---------------|
| Total Academies | Active + trial count | Big number |
| Total Users (platform) | All users across tenants | Big number |
| Platform Revenue | Sum across all academies (if platform charges) | Big number |
| Academy Health | Academies by status (active/trial/suspended) | Bar chart |
| Recent Activity | Cross-tenant activity stream | Feed |
| System Notifications | Failed jobs, errors, alerts | Alert list |

## Exportable Reports

| Report | Filters | Format |
|--------|---------|--------|
| Revenue Report | Date range, branch, payment method | PDF, Excel |
| Participant List | Status, program, age, gender, classification | Excel, CSV |
| Attendance Report | Date range, group, participant, trainer | PDF, Excel |
| Trainer Payroll | Date range, trainer, compensation model | PDF, Excel |
| Inventory Valuation | Warehouse, category, date | Excel |
| Invoice Aging | 30/60/90/120+ days | PDF |
| Program Profitability | Program, date range | PDF |
| Daily Closing Report | Branch, date | PDF (printable) |
| Enrollment Report | Date range, program, status | Excel |
| Facility Utilization | Date range, facility | PDF |

### Report Generation Service
```php
ReportService::generate(string $reportType, array $filters, string $format): string|StreamedResponse
```
- PDF: uses DomPDF with report template
- Excel: uses Laravel Excel (Maatwebsite)
- CSV: simple fputcsv
- All reports respect: current academy scope, branch scope, permissions

---

# PART B: CAPROVER DEPLOYMENT

## Container Architecture

Single-container approach (PHP-FPM + Nginx + Supervisor in one container).
Reason: simpler CapRover setup, one `captain-definition`, no Docker Compose needed.

```
┌─────────────────────────────────────────────────┐
│ CapRover Container                              │
│                                                 │
│  ┌───────────┐  ┌───────────┐  ┌────────────┐  │
│  │  Nginx    │  │  PHP-FPM  │  │  Queue     │  │
│  │  :80      │→ │  :9000    │  │  Worker    │  │
│  └───────────┘  └───────────┘  └────────────┘  │
│                                                 │
│  ┌────────────────────────────────────────────┐ │
│  │  Supervisor (manages all processes)        │ │
│  └────────────────────────────────────────────┘ │
│                                                 │
│  ┌───────────┐  ┌───────────┐                   │
│  │  Cron     │  │  Storage  │                   │
│  │  (schedule)│  │  (volume) │                   │
│  └───────────┘  └───────────┘                   │
└─────────────────────────────────────────────────┘
          │                    │
          ▼                    ▼
┌──────────────────┐  ┌──────────────────┐
│ PostgreSQL 16    │  │ Redis (future)   │
│ (separate app)   │  │ (separate app)   │
└──────────────────┘  └──────────────────┘
```

## Dockerfile

```dockerfile
# --- Stage 1: Build frontend assets ---
FROM node:20-alpine AS frontend
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY vite.config.js tailwind.config.js postcss.config.js ./
COPY resources/ resources/
RUN npm run build

# --- Stage 2: Install PHP dependencies ---
FROM composer:2 AS composer
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist
COPY . .
RUN composer dump-autoload --optimize

# --- Stage 3: Production image ---
FROM php:8.3-fpm-alpine

# Install system dependencies
RUN apk add --no-cache \
    nginx \
    supervisor \
    curl \
    libpq-dev \
    icu-dev \
    libzip-dev \
    freetype-dev \
    libjpeg-turbo-dev \
    libpng-dev \
    oniguruma-dev

# Install PHP extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install \
        pdo_pgsql \
        pgsql \
        intl \
        zip \
        gd \
        mbstring \
        opcache \
        pcntl

# Configure OPcache
RUN echo "opcache.enable=1" >> /usr/local/etc/php/conf.d/opcache.ini \
    && echo "opcache.memory_consumption=128" >> /usr/local/etc/php/conf.d/opcache.ini \
    && echo "opcache.max_accelerated_files=10000" >> /usr/local/etc/php/conf.d/opcache.ini \
    && echo "opcache.validate_timestamps=0" >> /usr/local/etc/php/conf.d/opcache.ini

# Configure PHP
RUN echo "upload_max_filesize=20M" >> /usr/local/etc/php/conf.d/uploads.ini \
    && echo "post_max_size=25M" >> /usr/local/etc/php/conf.d/uploads.ini \
    && echo "memory_limit=256M" >> /usr/local/etc/php/conf.d/uploads.ini

# Copy configs
COPY docker/nginx.conf /etc/nginx/http.d/default.conf
COPY docker/supervisord.conf /etc/supervisord.conf
COPY docker/php-fpm.conf /usr/local/etc/php-fpm.d/www.conf

# Copy application
WORKDIR /var/www/html
COPY --from=composer /app .
COPY --from=frontend /app/public/build public/build

# Set permissions
RUN chown -R www-data:www-data storage bootstrap/cache \
    && chmod -R 775 storage bootstrap/cache

# Copy entrypoint
COPY docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

EXPOSE 80

ENTRYPOINT ["/entrypoint.sh"]
```

## Nginx Configuration

```nginx
server {
    listen 80;
    server_name _;
    root /var/www/html/public;
    index index.php;

    client_max_body_size 25M;

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;

    # Health check
    location /up {
        access_log off;
        try_files $uri $uri/ /index.php?$query_string;
    }

    # Static files
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        expires 30d;
        access_log off;
        add_header Cache-Control "public, immutable";
    }

    # PHP handling
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_buffering off;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}
```

## Supervisor Configuration

```ini
[supervisord]
nodaemon=true
logfile=/var/log/supervisord.log
pidfile=/var/run/supervisord.pid

[program:nginx]
command=nginx -g "daemon off;"
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

[program:php-fpm]
command=php-fpm -F
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

[program:queue-worker]
command=php /var/www/html/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
numprocs=2
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

[program:scheduler]
command=sh -c "while true; do php /var/www/html/artisan schedule:run --no-interaction; sleep 60; done"
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
```

## Entrypoint Script

```bash
#!/bin/sh
set -e

echo "Running migrations..."
php artisan migrate --force

echo "Caching configuration..."
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache

echo "Creating storage link..."
php artisan storage:link 2>/dev/null || true

echo "Publishing Livewire assets..."
php artisan livewire:publish --assets 2>/dev/null || true

echo "Starting services..."
exec /usr/bin/supervisord -c /etc/supervisord.conf
```

## Captain Definition

```json
{
    "schemaVersion": 2,
    "dockerfilePath": "./Dockerfile"
}
```

## CapRover App Configuration

**App name:** `el-captain`
**URL:** `https://el-captain.caprover.al-arcade.com`

**Environment Variables (set in CapRover dashboard):**
```
APP_NAME=El Captain
APP_ENV=production
APP_DEBUG=false
APP_KEY=<generate with: php artisan key:generate --show>
APP_URL=https://el-captain.caprover.al-arcade.com

DB_CONNECTION=pgsql
DB_HOST=srv-captain--elcaptainsportsonlydb
DB_PORT=5432
DB_DATABASE=elcaptainsportsonly
DB_USERNAME=elcaptain
DB_PASSWORD=ElCaptain2024!

CACHE_STORE=file
SESSION_DRIVER=file
QUEUE_CONNECTION=database

MAIL_MAILER=smtp
MAIL_HOST=srv-captain--posteio
MAIL_PORT=587
MAIL_ENCRYPTION=tls
MAIL_VERIFY_PEER=false
MAIL_USERNAME=noreply@al-arcade.com
MAIL_PASSWORD=<configured>
MAIL_FROM_ADDRESS=noreply@al-arcade.com
MAIL_FROM_NAME="El Captain"

LOG_CHANNEL=stderr
LOG_LEVEL=warning
```

**Persistent Storage:**
- Map volume for: `/var/www/html/storage/app` (uploads, generated docs)

**Health Check:**
- Path: `/up`
- Expected: HTTP 200

**Port mapping:** Container exposes 80, CapRover handles SSL termination.

## Deployment Process

```bash
# From local machine:
# 1. Ensure captain-definition is in repo root
# 2. Push to CapRover (via CLI or tar upload)

# Option A: Git-based deploy (if repo is on GitLab/GitHub)
# Configure webhook in CapRover → auto-deploy on push to main

# Option B: CLI deploy
tar -czf deploy.tar.gz --exclude=node_modules --exclude=vendor --exclude=.git .
# Upload to CapRover dashboard → Deploy → Upload tar

# Option C: caprover CLI
npm install -g caprover
caprover deploy -a el-captain
```

## Zero-Downtime Updates
- CapRover uses Docker rolling updates
- Entrypoint runs migrations BEFORE starting services
- If migration fails → container fails health check → CapRover rolls back
- OPcache validate_timestamps=0 → need container restart for PHP changes (which happens on every deploy)

## Backup Strategy

```bash
# Database backup (run on server via cron)
sudo docker exec $(sudo docker ps -q --filter "name=srv-captain--elcaptainsportsonlydb") \
    pg_dump -U elcaptain elcaptainsportsonly > /backups/db_$(date +%Y%m%d_%H%M%S).sql

# Storage backup
sudo docker cp $(sudo docker ps -q --filter "name=srv-captain--el-captain"):/var/www/html/storage/app \
    /backups/storage_$(date +%Y%m%d)/

# Recommended: daily DB backup, weekly storage backup, 30-day retention
```

## Monitoring
- Health endpoint: `/up` (Laravel built-in)
- Container logs: `sudo docker logs srv-captain--el-captain --tail 100`
- Queue monitoring: future Horizon dashboard at `/horizon` (protected by SuperAdmin middleware)
- Database size monitoring: `SELECT pg_size_pretty(pg_database_size('elcaptainsportsonly'));`
