# Deployment Plan: 022-admin-reporting-refresh

**Branch:** `feature/022-admin-reporting-refresh`
**Created:** 2025-12-31
**Author:** Claude Code

---

## Overview

This deployment introduces:
1. **Analytics Dashboard** - New reporting with velocity, momentum, retention, and wait time metrics
2. **Unified Auth System** - New centralized `users` table with OAuth, MFA, and session management
3. **Store Metrics Aggregation** - Cross-store comparison data for peer analytics
4. **TaskEngine Jobs** - New scheduled aggregation jobs

---

## Pre-Deployment Checklist

- [ ] All tests pass locally (`./test.sh`)
- [ ] PHPStan analysis passes (`./test.sh --stan`)
- [ ] CSS bundle built (`php userfrosting/conductor build-css --minify`)
- [ ] Vite build complete (`npm run build` in resources/js)
- [ ] Branch merged to master or ready for deployment

---

## Deployment Steps

### Phase 1: Database Migrations (FIRST - Critical)

Run the migration conductor on **each environment** (staging first, then production):

```bash
cd /path/to/buyerkiosk-web
php userfrosting/conductor run
```

This will apply the following new migrations:

#### Analytics Tables (per-store databases `{{store}}`):
| Migration | Description |
|-----------|-------------|
| `20251230_001_analytics_velocity.json` | Creates `analytics_item_velocity` and `analytics_velocity_daily` tables |
| `20251230_003_analytics_retention.json` | Creates `analytics_seller_cohorts` table |

#### Analytics Tables (central `kiosk_sales`):
| Migration | Description |
|-----------|-------------|
| `20251230_002_analytics_momentum.json` | Creates `analytics_category_momentum` table |

#### Store Metrics (central `kiosk_buykiosk`):
| Migration | Description |
|-----------|-------------|
| `20251231_001_store_metrics_daily.json` | Creates `storeMetricsDaily` table for peer comparison |
| `20251230_004_analytics_jobs.json` | Inserts new job definitions for analytics aggregation |

#### Unified Auth Tables (central `kiosk_users`):
| Migration | Description |
|-----------|-------------|
| `20251220_001_users_table.json` | Creates unified `users` table |
| `20251220_002_user_store_assignments.json` | Creates `userStoreAssignments` junction table |
| `20251220_003_user_groups.json` | Creates user group memberships |
| `20251220_004_user_permissions.json` | Creates user permissions |
| `20251220_005_oauth_refresh_tokens.json` | Creates `oauthRefreshTokens` for JWT refresh |
| `20251220_006_user_sessions.json` | Creates session tracking |
| `20251220_007_auth_audit_log.json` | Creates `authAuditLog` audit trail |
| `20251220_008_user_sync_log.json` | Creates `userSyncLog` for external provider sync |

---

### Phase 2: Unified Auth Data Migration (CRITICAL)

**This is the main user migration!** After schema migrations complete, run the Phase 4 migration suite:

```bash
cd userfrosting/migrations/scripts

# First, do a dry run to preview all changes
php phase4_run_all.php --dry-run

# When ready, run the actual migration
php phase4_run_all.php
```

**The runner executes these steps in order:**

| Step | Script | What it Does |
|------|--------|--------------|
| 1 | `phase4_backup.sh` | Creates database backups (REQUIRED before migration) |
| 2 | `phase4_migrate_users.php` | Migrates `uf_user` → new `users` table |
| 3 | `phase4_migrate_groups.php` | Migrates group memberships |
| 4 | `phase4_migrate_permissions.php` | Migrates user permissions |
| 5 | `phase4_migrate_linked_employees.php` | Creates user-employee links (already linked) |
| 6 | `phase4_migrate_unlinked_employees.php` | Creates users for unlinked employees |
| 7 | `phase4_migrate_sync_logs.php` | Migrates sync history |
| 8 | `phase4_validate_migration.php` | Validates all data migrated correctly |
| 9 | `phase4_create_views.php` | Creates compatibility views for legacy code |

**Options:**
```bash
# Resume from a specific step if one fails
php phase4_run_all.php --step=5

# Skip backup if you've already backed up
php phase4_run_all.php --skip-backup
```

**⚠️ IMPORTANT:**
- Run `--dry-run` first to preview changes
- The script pauses between steps for confirmation
- Takes backups automatically in step 1
- Creates compatibility views so old `uf_user` references still work

---

### Phase 3: One-Time Data Scripts

After Phase 4 migration completes:

#### 3a. Auto-Link Employees (Supplemental)

If you have employees that weren't caught by Phase 4, run the auto-linker:

```bash
# Run for all stores
php userfrosting/scripts/auto-link-employees.php

# Or run for a specific store
php userfrosting/scripts/auto-link-employees.php ou00
```

**What it does:**
- Matches remaining `uf_user` accounts to `employees` by name/login
- Updates `hasUserAccount` flags on employee records
- Safe to re-run (idempotent)

#### 3b. Initial Store Stats Aggregation (Optional - for immediate data)

Populates historical aggregated stats if you want data immediately:

```bash
# Run for all stores
php userfrosting/scripts/aggregate-store-stats.php --verbose

# Or run for a specific store
php userfrosting/scripts/aggregate-store-stats.php --store=ou00 --verbose
```

**Note:** This is optional since the TaskEngine will run this nightly going forward.

---

### Phase 4: TaskEngine Job Registration

The migration `20251230_004_analytics_jobs.json` automatically registers these jobs:

| Job Name | Schedule | Description |
|----------|----------|-------------|
| `aggregate-velocity-stats` | 3:00 AM daily | Aggregates item velocity buckets per store |
| `aggregate-momentum-stats` | 4:00 AM daily | Computes category momentum (MACD signals) |
| `aggregate-retention-stats` | 5:00 AM daily | Calculates seller cohort retention |

**Verify jobs are registered:**
```bash
php userfrosting/bin/task job:list
```

**Manually trigger for initial backfill (optional):**
```bash
# Dispatch velocity aggregation for all stores
php userfrosting/bin/task job:dispatch aggregate-velocity-stats

# Or for a specific store
php userfrosting/bin/task job:dispatch aggregate-velocity-stats --store=ou00
```

---

### Phase 5: Verify TaskEngine Workers

Ensure workers are running to process the new jobs:

```bash
# Check worker status
php userfrosting/bin/task worker:manager --status

# Check queue depths
php userfrosting/bin/task queue:status --detailed
```

On **production servers**, verify launchd workers are active:
```bash
launchctl list | grep buyerkiosk
```

If workers aren't running:
```bash
launchctl load ~/Library/LaunchAgents/com.buyerkiosk.taskengine-worker*.plist
```

---

## Post-Deployment Verification

### 1. Check Migration Status
```bash
# View conductor logs
tail -f logs/conductor.log
```

### 2. Verify Analytics Tables Exist
```sql
-- Per-store database
SHOW TABLES LIKE 'analytics_%';

-- Central kiosk_buykiosk
SELECT * FROM storeMetricsDaily LIMIT 5;

-- Central kiosk_users
SHOW TABLES LIKE 'user%';
SHOW TABLES LIKE 'auth%';
SHOW TABLES LIKE 'oauth%';
```

### 3. Verify Job Definitions
```sql
SELECT name, displayName, schedule, isEnabled
FROM kiosk_buykiosk.task_job_definitions
WHERE name LIKE '%aggregate%' OR name LIKE '%velocity%' OR name LIKE '%momentum%' OR name LIKE '%retention%';
```

### 4. Test Analytics Dashboard
Navigate to:
- `/admin/{typeNum}/analytics/inventory` - Velocity metrics
- `/admin/{typeNum}/analytics/retention` - Seller retention
- `/admin/{typeNum}/analytics/waittime` - Wait time analytics

---

## Rollback Plan

If issues occur:

### 1. Disable New Jobs
```sql
UPDATE kiosk_buykiosk.task_job_definitions
SET isEnabled = 0
WHERE name IN ('aggregate-velocity-stats', 'aggregate-momentum-stats', 'aggregate-retention-stats');
```

### 2. Tables Are Additive
The new tables don't modify existing data. They can be left in place or dropped:

```sql
-- Per-store (run on each store DB)
DROP TABLE IF EXISTS analytics_item_velocity;
DROP TABLE IF EXISTS analytics_velocity_daily;
DROP TABLE IF EXISTS analytics_seller_cohorts;

-- Central kiosk_sales
DROP TABLE IF EXISTS analytics_category_momentum;

-- Central kiosk_buykiosk
DROP TABLE IF EXISTS storeMetricsDaily;

-- Central kiosk_users (CAUTION: only if no data migrated yet)
DROP TABLE IF EXISTS userSyncLog;
DROP TABLE IF EXISTS authAuditLog;
DROP TABLE IF EXISTS userSessions;
DROP TABLE IF EXISTS oauthRefreshTokens;
DROP TABLE IF EXISTS userPermissions;
DROP TABLE IF EXISTS userGroups;
DROP TABLE IF EXISTS userStoreAssignments;
DROP TABLE IF EXISTS users;
```

---

## Cron Jobs (if not using TaskEngine scheduler)

If the TaskEngine scheduler isn't running via cron, add these manually:

```cron
# Store stats aggregation (legacy script) - 5 AM daily
0 5 * * * php /path/to/userfrosting/scripts/aggregate-store-stats.php >> /var/log/aggregate-stats.log 2>&1

# TaskEngine scheduler (runs every minute, dispatches due jobs)
* * * * * php /path/to/userfrosting/bin/task scheduler:run >> /var/log/task-scheduler.log 2>&1
```

---

## Environment-Specific Notes

### Staging
- Run all migrations first
- Test auto-link script on a few stores before running globally
- Manually trigger aggregation jobs to verify they work
- Test analytics dashboard with real store data

### Production
- Schedule deployment during off-peak hours (early morning recommended)
- Migrations are non-blocking (CREATE TABLE, INSERT IGNORE)
- Auto-link script is safe to run during business hours
- Monitor worker logs for first 24 hours after deployment

---

## Files Changed Summary

### New Directories
- `userfrosting/src/BuyerKiosk/Analytics/` - Full analytics module
- `userfrosting/routes/analytics/` - API and page routes
- `userfrosting/templates/themes/default/analytics/` - Dashboard templates
- `userfrosting/tests/Unit/Analytics/` - Unit tests

### New Jobs
- `userfrosting/src/BuyerKiosk/TaskEngine/Jobs/StoreMetricsAggregatorJob.php`
- Analytics jobs in `userfrosting/src/BuyerKiosk/Analytics/Jobs/`

### Modified Files
- `userfrosting/templates/themes/default/menus/sidebar.html` - Analytics menu items
- `userfrosting/src/BuyerKiosk/TaskEngine/Commands/TaskCommandFactory.php` - Job registration

---

## Support Contacts

If issues arise during deployment:
- Check logs: `logs/conductor.log`, `logs/task-worker.log`
- Review migration history in database: `SELECT * FROM migrations ORDER BY applied_at DESC LIMIT 20;`
