# Quickstart: Feature 005 - Bulk Operations

**Feature**: Bulk Operations for Resource Management  
**Date**: 2025-12-22

---

## Overview

This quickstart guide helps developers get up and running with Feature 005 (Bulk Operations) for local development, testing, and debugging.

---

## Prerequisites

- Laravel Sail installed and running
- Composer dependencies installed
- NPM dependencies installed
- Database migrated
- At least one Tenant configured
- Sample Policies, PolicyVersions, BackupSets, RestoreRuns seeded

---

## Local Development Setup

### 1. Start Sail

```bash
cd /path/to/TenantAtlas
./vendor/bin/sail up -d
```

### 2. Run Migrations

```bash
./vendor/bin/sail artisan migrate
```

This creates:
- `bulk_operation_runs` table
- `ignored_at` column on `policies` table

### 3. Seed Test Data (Optional)

```bash
# NOTE: Removed by Feature 056 (OperationRun migration).
# There is no BulkOperationsTestSeeder anymore.
```

Creates:
- 100 test policies
- 50 policy versions (some old, some referenced)
- 10 backup sets
- 20 restore runs (mixed statuses)

### 4. Start Queue Worker

Bulk operations require queue processing:

```bash
./vendor/bin/sail artisan queue:work --tries=3 --timeout=300
```

Or run in background with Supervisor (production):

```bash
./vendor/bin/sail artisan queue:restart
```

### 5. Access Filament Panel

```bash
open http://localhost/admin
```

Navigate to:
- **Policies** → Select multiple → Bulk Actions dropdown
- **Policy Versions** → Bulk Prune
- **Backup Sets** → Bulk Delete
- **Restore Runs** → Bulk Delete

---

## Running Tests

### Unit Tests

Test individual components (jobs, scopes, helpers):

```bash
./vendor/bin/sail artisan test tests/Unit/BulkPolicyDeleteJobTest.php
./vendor/bin/sail artisan test tests/Unit/BulkActionPermissionTest.php
./vendor/bin/sail artisan test tests/Unit/BulkEligibilityCheckTest.php
```

### Feature Tests

Test E2E flows (UI → job → database):

```bash
./vendor/bin/sail artisan test tests/Feature/BulkDeletePoliciesTest.php
./vendor/bin/sail artisan test tests/Feature/BulkExportToBackupTest.php
./vendor/bin/sail artisan test tests/Feature/BulkProgressNotificationTest.php
./vendor/bin/sail artisan test tests/Feature/BulkTypeToConfirmTest.php
```

### All Tests

```bash
./vendor/bin/sail artisan test --filter=Bulk
```

### Browser Tests (Pest v4)

Test UI interactions:

```bash
./vendor/bin/sail artisan test tests/Browser/BulkOperationsTest.php
```

---

## Configuration

These defaults are safe for staging/production, but can be tuned per environment.

- **Chunk size** (job refresh/progress cadence):
   - `TENANTPILOT_BULK_CHUNK_SIZE` (default `10`)
- **Progress polling interval** (UI updates):
   - `TENANTPILOT_BULK_POLL_INTERVAL_SECONDS` (default `3`, clamped to 1–10 seconds)
- **Policy version prune retention window**:
   - Default `90` days (editable in the prune modal as “Retention Days”)

## Manual Testing Workflow

### Scenario 1: Bulk Delete Policies (< 20 items)

1. **Navigate**: Admin → Policies
2. **Select**: Check 10 policies
3. **Action**: Click "Delete" in bulk actions dropdown
4. **Confirm**: Modal appears: "Delete 10 policies?"
5. **Submit**: Click "Confirm"
6. **Verify**:
   - Success notification: "Deleted 10 policies"
   - Policies have `ignored_at` timestamp set
   - Policies still exist in Intune (no Graph DELETE call)
   - Audit log entry created

### Scenario 2: Bulk Delete Policies (≥ 20 items, queued)

1. **Navigate**: Admin → Policies
2. **Select**: Check 25 policies
3. **Action**: Click "Delete"
4. **Confirm**: Modal requires typing "DELETE"
5. **Type**: Enter "DELETE" (case-sensitive)
6. **Submit**: Click "Confirm"
7. **Verify**:
   - Job dispatched to queue
   - Progress notification: "Deleting policies... 0/25"
   - Notification updates every 5s: "Deleting... 10/25", "20/25"
   - Final notification: "Deleted 25 policies"
   - `BulkOperationRun` record created with status `completed`
   - Audit log entry

### Scenario 3: Bulk Export to Backup

1. **Navigate**: Admin → Policies
2. **Select**: Check 30 policies
3. **Action**: Click "Export to Backup"
4. **Form**:
   - Backup Set Name: "Production Snapshot"
   - Include Assignments: ☑ (if Feature 004 implemented)
5. **Submit**: Click "Confirm"
6. **Verify**:
   - Job dispatched
   - Progress: "Backing up... 10/30"
   - Final: "Backup Set 'Production Snapshot' created (30 items)"
   - New `BackupSet` record
   - 30 `BackupItem` records
   - Audit log entry

### Scenario 4: Bulk Prune Policy Versions

1. **Navigate**: Admin → Policy Versions
2. **Filter**: Show only non-current versions older than 90 days
3. **Select**: Check 15 versions
4. **Action**: Click "Delete"
5. **Confirm**: Type "DELETE"
6. **Submit**: Click "Confirm"
7. **Verify**:
   - Eligibility check runs
   - Eligible versions deleted (hard delete)
   - Ineligible versions skipped
   - Notification: "Deleted 12 policy versions (3 skipped)"
   - Failures array shows skip reasons:
     - "Referenced in Backup Set #5"
     - "Current version"
     - "Too recent (< 90 days)"

### Scenario 5: Circuit Breaker (abort on >50% fail)

1. **Setup**: Mock Graph API to fail for 60% of items
2. **Navigate**: Admin → Policies
3. **Select**: Check 100 policies
4. **Action**: Bulk Delete
5. **Verify**:
   - Job processes ~50 items
   - Detects >50% failure rate
   - Aborts remaining items
   - Notification: "Bulk operation aborted: 55/100 failures exceeded threshold"
   - `BulkOperationRun.status` = `aborted`

---

## Debugging

### View Queue Jobs

```bash
# List failed jobs
./vendor/bin/sail artisan queue:failed

# Retry failed job
./vendor/bin/sail artisan queue:retry <job-id>

# Flush failed jobs
./vendor/bin/sail artisan queue:flush
```

### Inspect BulkOperationRun Records

```bash
./vendor/bin/sail tinker
```

```php
// Get recent runs
$runs = \App\Models\BulkOperationRun::recent()->limit(10)->get();

// View failures
$run = \App\Models\BulkOperationRun::find(1);
dd($run->failures);

// Check progress
echo "{$run->processed_items}/{$run->total_items} ({$run->progressPercentage()}%)";
```

### View Audit Logs

```bash
# Via Filament UI
# Navigate to: Admin → Audit Logs → Filter by event: "policies.bulk_deleted"

# Via Tinker
$logs = \App\Models\AuditLog::where('event', 'policies.bulk_deleted')->get();
```

### Database Queries

```bash
./vendor/bin/sail artisan tinker
```

```php
// Policies marked as ignored
$ignored = \App\Models\Policy::ignored()->count();

// Policy versions eligible for pruning
$eligible = \App\Models\PolicyVersion::pruneEligible(90)->count();

// Deletable restore runs
$deletable = \App\Models\RestoreRun::deletable()->count();
```

### Test Queue Job Manually

```bash
./vendor/bin/sail artisan tinker
```

```php
use App\Jobs\BulkPolicyDeleteJob;
use App\Models\BulkOperationRun;

$policyIds = [1, 2, 3];
$tenantId = 1;
$userId = 1;

$run = BulkOperationRun::create([
    'tenant_id' => $tenantId,
    'user_id' => $userId,
    'resource' => 'policies',
    'action' => 'delete',
    'status' => 'pending',
    'total_items' => count($policyIds),
    'item_ids' => $policyIds,
]);

// Dispatch synchronously for debugging
BulkPolicyDeleteJob::dispatchSync($policyIds, $tenantId, $userId, $run->id);

// Check result
$run->refresh();
echo $run->summaryText();
```

---

## Common Issues & Solutions

### Issue 1: Type-to-confirm not working

**Symptom**: Confirm button remains enabled without typing "DELETE"

**Solution**: Check Filament form validation rule:
```php
->rule('in:DELETE') // Case-sensitive
```

### Issue 2: Progress notifications don't update

**Symptom**: Progress stuck at "0/100"

**Solution**: 
- Ensure queue worker is running: `./vendor/bin/sail artisan queue:work`
- Check Livewire polling: `wire:poll.5s="refresh"`
- Verify BulkOperationRun is updated in job

### Issue 3: Policies reappear after deletion

**Symptom**: Deleted policies show up again after sync

**Solution**:
- Check `ignored_at` is set: `Policy::find($id)->ignored_at`
- Verify SyncPoliciesJob filters: `->whereNull('ignored_at')`

### Issue 4: Circuit breaker not aborting

**Symptom**: Job continues despite >50% failures

**Solution**: Check circuit breaker logic in job:
```php
if ($results['failed'] > count($this->policyIds) * 0.5) {
    $run->update(['status' => 'aborted']);
    throw new \Exception('Bulk operation aborted: >50% failure rate');
}
```

### Issue 5: Policy versions deleted despite references

**Symptom**: Referenced versions are deleted

**Solution**: Verify eligibility scope includes:
```php
->whereDoesntHave('backupItems')
->whereNotIn('id', function ($subquery) {
    $subquery->select(DB::raw("CAST(metadata->>'policy_version_id' AS INTEGER)"))
        ->from('restore_runs')
        ->whereNotNull(DB::raw("metadata->>'policy_version_id'"));
});
```

---

## Performance Benchmarks

Expected performance (local Sail environment):

| Operation | Item Count | Duration | Notes |
|-----------|------------|----------|-------|
| Bulk Delete (sync) | 10 | <1s | Immediate feedback |
| Bulk Delete (queued) | 100 | <2min | Chunked, progress updates |
| Bulk Export | 50 | <3min | Includes Graph API calls |
| Bulk Prune | 30 | <30s | Eligibility checks |
| Progress Update | - | 5s | Polling interval |

---

## Code Formatting

Before committing:

```bash
./vendor/bin/sail composer pint
```

Formats all PHP files per PSR-12.

---

## Next Steps

1. ✅ Complete Phase 2.1 (Foundation) tasks
2. ✅ Run all tests: `./vendor/bin/sail artisan test --filter=Bulk`
3. ✅ Manual QA: Follow scenarios above
4. ✅ Code review: Check PSR-12, permissions, audit logs
5. ✅ Load testing: Bulk delete 500 items
6. → Deploy to staging
7. → Manual QA on staging
8. → Deploy to production

---

## Useful Commands

```bash
# Watch queue jobs in real-time
./vendor/bin/sail artisan queue:work --verbose

# Monitor bulk operations
./vendor/bin/sail artisan tinker
>>> \App\Models\OperationRun::query()->where('status', 'running')->get()

# Seed more test data
# NOTE: Removed by Feature 056 (OperationRun migration).

# Clear cache
./vendor/bin/sail artisan optimize:clear

# Restart queue workers (after code changes)
./vendor/bin/sail artisan queue:restart
```

---

## Resources

- [Laravel Queue Documentation](https://laravel.com/docs/12.x/queues)
- [Filament Bulk Actions](https://filamentphp.com/docs/4.x/tables/actions#bulk-actions)
- [Livewire Polling](https://livewire.laravel.com/docs/polling)
- [Pest Testing](https://pestphp.com/docs)

---

**Status**: Quickstart Complete  
**Next Step**: Update agent context with new learnings
