# Plan 3: Open Core BS Backend Changes

## Overview

Changes to the customer-facing Open Core Business Suite backend application:
- OTA (Over-The-Air) update system
- Activation screen and flow
- License status page
- Support expiry reminders
- Feature restrictions based on license status

**Location:** `/Volumes/Storage/Projects/CZ/OpenCoreBusinessSuite/open_core_bs_backend/`

---

## PART A: OTA UPDATE SYSTEM

### Complete OtaService Implementation

**File:** `app/Services/OtaService/OtaService.php`

```php
<?php

namespace App\Services\OtaService;

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\File;
use ZipArchive;

class OtaService implements IOtaService
{
    private string $apiBase;
    private string $licenseKey;
    private string $domain;

    public function __construct()
    {
        $this->apiBase = config('variables.apiUrl', 'https://czappstudio.com/api/v1');
        $this->licenseKey = $this->getLicenseKey();
        $this->domain = request()->getHost();
    }

    public function checkForUpdates(): array
    {
        $response = Http::withHeaders($this->getHeaders())
            ->get("{$this->apiBase}/updates/check");

        return $response->json();
    }

    public function checkAddonUpdates(): array
    {
        $response = Http::withHeaders($this->getHeaders())
            ->get("{$this->apiBase}/updates/check-addons");

        return $response->json();
    }

    public function downloadUpdate(string $fromVersion): string
    {
        $response = Http::withHeaders($this->getHeaders())
            ->get("{$this->apiBase}/downloads/1/update/{$fromVersion}");

        $data = $response->json();
        $tempPath = storage_path("app/updates/{$data['file_name']}");

        Http::sink($tempPath)->get($data['download_url']);

        // Verify checksum
        if (hash_file('sha256', $tempPath) !== $data['checksum_sha256']) {
            unlink($tempPath);
            throw new \Exception('Checksum verification failed');
        }

        return $tempPath;
    }

    public function applyUpdate(string $zipPath): array
    {
        $result = ['success' => false, 'messages' => []];

        // 1. Extract ZIP
        $extractPath = storage_path('app/updates/extracted');
        $zip = new ZipArchive();
        $zip->open($zipPath);
        $zip->extractTo($extractPath);
        $zip->close();

        // 2. Read manifest
        $manifest = json_decode(
            file_get_contents("{$extractPath}/UPDATE_MANIFEST.json"),
            true
        );

        // 3. Backup current files
        $backupPath = storage_path("app/backups/pre-update-" . date('Y-m-d-His'));
        $this->backupFiles($manifest['files'], $backupPath);
        $result['messages'][] = "Backup created at: {$backupPath}";

        // 4. Apply file changes
        foreach ($manifest['files']['modified'] as $file) {
            $source = "{$extractPath}/files/{$file}";
            $dest = base_path($file);
            File::ensureDirectoryExists(dirname($dest));
            File::copy($source, $dest);
        }

        foreach ($manifest['files']['added'] as $file) {
            $source = "{$extractPath}/files/{$file}";
            $dest = base_path($file);
            File::ensureDirectoryExists(dirname($dest));
            File::copy($source, $dest);
        }

        foreach ($manifest['files']['deleted'] as $file) {
            $dest = base_path($file);
            if (File::exists($dest)) {
                File::delete($dest);
            }
        }

        $result['messages'][] = "Files updated successfully";

        // 5. Run migrations
        if (!empty($manifest['migrations'])) {
            Artisan::call('migrate', ['--force' => true]);
            $result['messages'][] = "Migrations completed";
        }

        // 6. Run post-update commands
        foreach ($manifest['post_update_commands'] ?? [] as $command) {
            Artisan::call($command);
        }

        // 7. Clear caches
        Artisan::call('optimize:clear');
        $result['messages'][] = "Caches cleared";

        // 8. Cleanup
        File::deleteDirectory($extractPath);
        File::delete($zipPath);

        $result['success'] = true;
        return $result;
    }

    public function rollback(string $backupPath): bool
    {
        // Restore from backup
        // Implementation details...
    }

    private function getHeaders(): array
    {
        return [
            'X-License-Key' => $this->licenseKey,
            'X-Domain' => $this->domain,
            'X-Current-Version' => config('variables.templateVersion'),
        ];
    }
}
```

### Update Controller

**File:** `app/Http/Controllers/UpdateController.php`

```php
<?php

namespace App\Http\Controllers;

use App\Services\OtaService\OtaService;
use Illuminate\Http\Request;

class UpdateController extends Controller
{
    public function __construct(private OtaService $otaService) {}

    public function check()
    {
        $updates = $this->otaService->checkForUpdates();
        $addonUpdates = $this->otaService->checkAddonUpdates();

        return view('system.updates', compact('updates', 'addonUpdates'));
    }

    public function apply(Request $request)
    {
        $currentVersion = config('variables.templateVersion');

        try {
            // Download update package
            $zipPath = $this->otaService->downloadUpdate($currentVersion);

            // Apply update
            $result = $this->otaService->applyUpdate($zipPath);

            return response()->json($result);
        } catch (\Exception $e) {
            return response()->json([
                'success' => false,
                'error' => $e->getMessage()
            ], 500);
        }
    }
}
```

### Admin Update UI

**File:** `resources/views/system/updates.blade.php`

Dashboard widget showing:

- Current version
- Available updates
- One-click update button
- Update history
- Addon updates list

---

## PART B: ACTIVATION & LICENSE UI

### Existing Infrastructure (Already in Backend)

| Component         | File Path                                                  | Purpose                              |
| ----------------- | ---------------------------------------------------------- | ------------------------------------ |
| ActivationService | `app/Services/Activation/ActivationService.php`            | License validation with external API |
| LicenseChecker    | `app/Http/Middleware/LicenseChecker.php`                   | Blocks access if not activated       |
| Activation View   | `resources/views/activation/index.blade.php`               | License activation form              |
| License Modal     | `resources/views/_partials/_modals/core/license.blade.php` | In-app license display               |
| BaseController    | `app/Http/Controllers/BaseController.php`                  | Handles activation routes            |
| Config Toggle     | `config/custom.php` → `activationService`                  | Enable/disable activation system     |

### Activation Flow

#### 1. First-Time Activation

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                         FIRST-TIME ACTIVATION FLOW                          │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  1. Fresh Install                                                           │
│     └──▶ User visits any page                                               │
│          └──▶ LicenseChecker middleware runs                               │
│               └──▶ No valid license found                                  │
│                    └──▶ Redirect to /activation                            │
│                                                                              │
│  2. Activation Screen                                                       │
│     ┌─────────────────────────────────────────┐                            │
│     │  Product Activation                     │                            │
│     │                                         │                            │
│     │  Purchase Code: [________________]      │                            │
│     │  Email:         [________________]      │                            │
│     │                                         │                            │
│     │  Domain Type:   ○ Production            │                            │
│     │                 ○ Development           │                            │
│     │                                         │                            │
│     │  [Activate License]                     │                            │
│     │                                         │                            │
│     │  ── OR ──                               │                            │
│     │                                         │                            │
│     │  [Activate via Envato]                  │                            │
│     └─────────────────────────────────────────┘                            │
│                                                                              │
│  3. Activation Request → czappstudio.com API                               │
│     └──▶ POST /api/v1/activate                                             │
│          └──▶ Validate license key                                         │
│          └──▶ Check domain limits (1 prod + 1 dev)                         │
│          └──▶ Return activation token + license info                       │
│                                                                              │
│  4. Success                                                                 │
│     └──▶ Store activation code in storage/app/activation_code.txt         │
│     └──▶ Cache license validity for 1 hour                                 │
│     └──▶ Redirect to login page                                            │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘
```

#### 2. Periodic Validation (Every Hour)

```php
// LicenseChecker middleware behavior
1. Check cache for `license_validity_{APP_URL}`
2. If cache miss or expired:
   └──▶ Call ActivationService::checkValidActivation()
        └──▶ POST /api/v1/validate to czappstudio.com
        └──▶ Returns: valid, support_active, support_expires_at, latest_version
3. Cache result for 1 hour
4. If invalid → Redirect to /activation
5. If valid but support expired → Allow access, show warning notices
```

### License Status Page

#### New Route & Controller

**Route:** `/system/license-status`
**Permission:** Admin only

**File:** `app/Http/Controllers/LicenseStatusController.php`

```php
<?php

namespace App\Http\Controllers;

use App\Services\Activation\IActivationService;

class LicenseStatusController extends Controller
{
    public function __construct(private IActivationService $activationService) {}

    public function index()
    {
        $licenseInfo = $this->activationService->getActivationInfo();

        return view('system.license-status', [
            'license' => $licenseInfo,
            'title' => __('License Status'),
            'breadcrumbs' => [
                ['label' => __('System'), 'active' => true],
                ['label' => __('License Status'), 'active' => true],
            ],
        ]);
    }

    public function deactivate()
    {
        $result = $this->activationService->deactivate($this->getLicenseKey());
        return redirect()->route('activation.index');
    }
}
```

#### License Status View

**File:** `resources/views/system/license-status.blade.php`

```
┌─────────────────────────────────────────────────────────────────────────────┐
│  License Status                                                             │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  ┌─────────────────────────┐  ┌─────────────────────────┐                  │
│  │ License Information     │  │ Support Status          │                  │
│  ├─────────────────────────┤  ├─────────────────────────┤                  │
│  │ Product: Open Core BS   │  │ Status: Active          │                  │
│  │ License: CZ-XXXX-XXXX   │  │ Type: Lifetime          │                  │
│  │ Domain: myapp.com       │  │ Expires: Never          │                  │
│  │ Type: Production        │  │                         │                  │
│  │ Activated: Jan 5, 2025  │  │ [Renew Support]         │                  │
│  └─────────────────────────┘  └─────────────────────────┘                  │
│                                                                              │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ Activated Domains                                                   │   │
│  ├──────────────────────────────┬──────────────┬──────────────────────┤   │
│  │ Domain                       │ Type         │ Activated            │   │
│  ├──────────────────────────────┼──────────────┼──────────────────────┤   │
│  │ myapp.com                    │ Production   │ Jan 5, 2025          │   │
│  │ localhost                    │ Development  │ Jan 5, 2025          │   │
│  └──────────────────────────────┴──────────────┴──────────────────────┘   │
│                                                                              │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ Licensed Addons                                                     │   │
│  ├──────────────────────────────┬──────────────┬──────────────────────┤   │
│  │ Addon                        │ Version      │ Status               │   │
│  ├──────────────────────────────┼──────────────┼──────────────────────┤   │
│  │ Payroll                      │ 1.0.3        │ Licensed             │   │
│  │ Attendance                   │ 2.0.0        │ Licensed             │   │
│  │ Recruitment                  │ 1.5.0        │ Update Available     │   │
│  └──────────────────────────────┴──────────────┴──────────────────────┘   │
│                                                                              │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ Need to transfer license to a new domain?                           │   │
│  │                                                                     │   │
│  │ You can deactivate this domain and activate on a new one.          │   │
│  │ This will log you out and require reactivation.                    │   │
│  │                                                                     │   │
│  │ [Deactivate This Domain]                                           │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘
```

### Support Expiry Reminders

#### 1. Global Warning Banner

**File:** `resources/views/layouts/sections/navbar/license-warning.blade.php`

Display a persistent warning banner when support is expiring/expired:

```blade
@php
    $licenseStatus = app(\App\Services\Activation\IActivationService::class)->getCachedStatus();
@endphp

@if($licenseStatus && !$licenseStatus['support_active'])
    {{-- Support Expired --}}
    <div class="alert alert-danger alert-dismissible mb-0 rounded-0" role="alert">
        <div class="d-flex align-items-center">
            <i class="bx bx-error-circle me-2 fs-4"></i>
            <div>
                <strong>{{ __('Support Expired') }}</strong> -
                {{ __('Your support period has ended. You cannot download updates until you renew.') }}
                <a href="{{ config('variables.supportUrl') }}" target="_blank" class="alert-link">
                    {{ __('Renew Now') }}
                </a>
            </div>
        </div>
    </div>
@elseif($licenseStatus && $licenseStatus['support_expires_soon'])
    {{-- Support Expiring Soon (within 30 days) --}}
    <div class="alert alert-warning alert-dismissible mb-0 rounded-0" role="alert">
        <div class="d-flex align-items-center">
            <i class="bx bx-time me-2 fs-4"></i>
            <div>
                <strong>{{ __('Support Expiring Soon') }}</strong> -
                {{ __('Your support expires on :date. Renew now to continue receiving updates.', ['date' => $licenseStatus['support_expires_at']->format('M d, Y')]) }}
                <a href="{{ config('variables.supportUrl') }}" target="_blank" class="alert-link">
                    {{ __('Renew Now') }}
                </a>
            </div>
            <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
        </div>
    </div>
@endif
```

**Include in layout:** `resources/views/layouts/contentNavbarLayout.blade.php`

```blade
{{-- After opening body tag, before main content --}}
@include('layouts.sections.navbar.license-warning')
```

#### 2. Dashboard Widget

**File:** `resources/views/components/dashboard/license-status-card.blade.php`

```blade
<div class="card">
    <div class="card-header d-flex justify-content-between">
        <h5 class="card-title mb-0">{{ __('License Status') }}</h5>
        <a href="{{ route('system.license-status') }}" class="btn btn-sm btn-outline-primary">
            {{ __('View Details') }}
        </a>
    </div>
    <div class="card-body">
        <div class="d-flex align-items-center mb-3">
            @if($licenseStatus['support_active'])
                <span class="badge bg-success me-2">{{ __('Active') }}</span>
            @else
                <span class="badge bg-danger me-2">{{ __('Expired') }}</span>
            @endif
            <span class="text-muted">{{ $licenseStatus['license_type'] }}</span>
        </div>

        @if($licenseStatus['support_expires_at'])
            <p class="mb-2">
                <strong>{{ __('Support Expires:') }}</strong>
                {{ $licenseStatus['support_expires_at']->format('M d, Y') }}
            </p>
        @else
            <p class="mb-2">
                <strong>{{ __('Support:') }}</strong>
                {{ __('Lifetime') }}
            </p>
        @endif

        @if($licenseStatus['update_available'])
            <div class="alert alert-info mb-0">
                <i class="bx bx-download me-1"></i>
                {{ __('Update available: v:version', ['version' => $licenseStatus['latest_version']]) }}
                <a href="{{ route('system.updates') }}">{{ __('View') }}</a>
            </div>
        @endif
    </div>
</div>
```

#### 3. Email Reminders (Scheduled)

**File:** `app/Console/Commands/SendSupportExpiryReminders.php`

```php
// Schedule in bootstrap/app.php or routes/console.php
// Runs daily, checks support_expires_at and sends emails

// 30 days before expiry → First reminder
// 14 days before expiry → Second reminder
// 7 days before expiry → Urgent reminder
// 1 day before expiry → Final reminder
// On expiry day → Expired notification
```

### Feature Restrictions

#### 1. Update Download Restriction

When support is expired, users can still use the app but cannot download updates:

**File:** `app/Http/Controllers/UpdateController.php`

```php
public function download(Request $request)
{
    $licenseStatus = $this->activationService->getCachedStatus();

    if (!$licenseStatus['support_active']) {
        return response()->json([
            'success' => false,
            'error' => 'support_expired',
            'message' => __('Your support period has expired. Please renew to download updates.'),
            'renew_url' => config('variables.supportUrl'),
        ], 403);
    }

    // Proceed with download...
}
```

#### 2. License Invalid - Full Block

When license is invalid (not activated or deactivated):

```php
// LicenseChecker middleware (existing behavior)
// Redirects ALL requests to /activation page
// Only /activation routes are allowed through
```

#### 3. Support Expired - Limited Access

When support is expired but license is valid:

```
ALLOWED:
- Full application usage
- All existing features work
- View license status
- View available updates (but not download)

BLOCKED:
- Download new updates
- Access OTA update feature
- Download new addon versions
```

### Activation Data Storage

#### Current Storage (File-based)

**File:** `storage/app/activation_code.txt`

Contains activation token returned from API.

#### Enhanced Storage (Database + File)

Add new columns to `settings` table:

```php
// Migration: add_license_columns_to_settings_table.php
Schema::table('settings', function (Blueprint $table) {
    $table->string('license_key')->nullable();
    $table->string('license_type')->nullable(); // 'lifetime' or 'annual'
    $table->string('license_domain')->nullable();
    $table->string('license_domain_type')->nullable(); // 'production' or 'development'
    $table->timestamp('license_activated_at')->nullable();
    $table->timestamp('license_support_expires_at')->nullable();
    $table->timestamp('license_last_validated_at')->nullable();
    $table->json('license_addons')->nullable(); // Licensed addon UIDs
});
```

#### Cached Status Structure

```php
// Cache key: license_status_{APP_URL}
// TTL: 1 hour

[
    'valid' => true,
    'license_key' => 'CZ-XXXX-XXXX-XXXX-XXXX',
    'license_type' => 'lifetime',
    'product' => 'Open Core Business Suite',
    'domain' => 'myapp.com',
    'domain_type' => 'production',
    'activated_at' => Carbon::parse('2025-01-05'),
    'support_active' => true,
    'support_expires_at' => null, // or Carbon date for annual
    'support_expires_soon' => false, // true if within 30 days
    'days_until_expiry' => null, // or integer
    'latest_version' => '5.0.2',
    'current_version' => '5.0.1',
    'update_available' => true,
    'addons' => [
        ['uid' => 'payroll', 'name' => 'Payroll', 'version' => '1.0.3'],
        ['uid' => 'attendance', 'name' => 'Attendance', 'version' => '2.0.0'],
    ],
]
```

### Menu Integration

Add to System menu (admin only):

```php
// resources/menu/adminMenu.json
{
    "menuHeader": "System",
    "items": [
        // ... existing items ...
        {
            "name": "License Status",
            "icon": "bx bx-key",
            "route": "system.license-status",
            "permission": "system.license.view"
        },
        {
            "name": "System Updates",
            "icon": "bx bx-download",
            "route": "system.updates",
            "permission": "system.updates.view"
        }
    ]
}
```

---

## Files to Create/Modify

### New Files

```
app/Http/Controllers/LicenseStatusController.php
app/Http/Controllers/UpdateController.php
app/Services/OtaService/OtaService.php (complete implementation)
resources/views/system/license-status.blade.php
resources/views/system/updates.blade.php
resources/views/layouts/sections/navbar/license-warning.blade.php
resources/views/components/dashboard/license-status-card.blade.php
app/Console/Commands/SendSupportExpiryReminders.php
database/migrations/xxxx_add_license_columns_to_settings_table.php
```

### Modify Files

```
app/Services/Activation/ActivationService.php   # Add getCachedStatus(), enhance storage
app/Services/Activation/IActivationService.php  # Add new method signatures
app/Http/Middleware/LicenseChecker.php          # Cache enhanced status
resources/views/layouts/contentNavbarLayout.blade.php  # Include warning banner
resources/views/activation/index.blade.php      # Add domain type selector
Modules/SystemCore/resources/views/dashboard.blade.php  # Add license widget
routes/web.php                                  # Add license-status and updates routes
config/variables.php                            # Add apiUrl, supportUrl, renewUrl
resources/menu/adminMenu.json                   # Add menu items
```

---

## Implementation Phases

### Phase 1: OTA Service

1. Complete OtaService implementation
2. Create IOtaService interface
3. Register in service provider
4. Create UpdateController

### Phase 2: Update UI

1. Create updates.blade.php view
2. Add update checking functionality
3. Add one-click update with progress
4. Add update history display
5. Add addon updates section

### Phase 3: Enhanced Activation

1. Add license columns migration to settings table
2. Enhance ActivationService with getCachedStatus()
3. Update LicenseChecker middleware to cache enhanced status
4. Update activation view with domain type selector

### Phase 4: License Status Page

1. Create LicenseStatusController
2. Create license-status.blade.php view
3. Add route and menu item
4. Implement deactivation functionality

### Phase 5: Expiry Reminders

1. Create license warning banner partial
2. Include in layout
3. Create dashboard license status widget
4. Create email reminder command
5. Schedule command to run daily

### Phase 6: Feature Restrictions

1. Add support check to update download
2. Block download when support expired
3. Show appropriate error messages
4. Add renew links to error responses

---

## Verification

### Test Commands

```bash
# OTA
php artisan update:check
php artisan update:apply

# License
php artisan license:status
php artisan license:validate

# Email Reminders (manual trigger for testing)
php artisan support:send-expiry-reminders --dry-run
```

### Test Scenarios

#### Activation Flow
1. **Fresh Activation**: Fresh install → Visit any page → Redirect to /activation → Enter license → Success
2. **Domain Type**: Activate as production → Check domain_type stored correctly
3. **Domain Limits**: Try activating 2nd production domain → Should fail with clear error
4. **Deactivation**: Deactivate domain → License freed → Can activate new domain
5. **Invalid License**: Enter wrong license key → Shows error message

#### License Status UI
6. **License Status Page**: Navigate to /system/license-status → Shows all license info
7. **Dashboard Widget**: Check dashboard shows license status card
8. **Addon Display**: Licensed addons displayed with version info

#### Support Expiry Reminders
9. **Warning Banner**: When support expires in 30 days → Yellow warning banner appears
10. **Expired Banner**: When support expired → Red banner appears, persists
11. **Email Reminders**: Scheduled command sends emails at 30/14/7/1 days before expiry

#### Feature Restrictions
12. **Expired Support**: Annual license expired → Can use app, cannot download updates
13. **Update Block**: Try to download update with expired support → 403 with renew link
14. **Invalid License**: Not activated → All pages redirect to /activation

#### OTA Updates
15. **Update Check**: Check for updates → Shows available version
16. **Update Download**: Download update package → Verify checksum
17. **Update Apply**: Apply update → Files replaced → Migrations run → Version updated
18. **Rollback**: Apply bad update → Rollback from backup → App works

### UI Test Checklist

#### Activation Screen (/activation)
- [ ] Shows product name and version
- [ ] Purchase Code input field
- [ ] Email input field
- [ ] Domain Type radio buttons (Production/Development)
- [ ] Activate License button
- [ ] Envato activation option
- [ ] Loading indicator on submit
- [ ] Error messages display properly
- [ ] Success redirects to login

#### License Status Page (/system/license-status)
- [ ] License Information card (product, key, domain, type, activated date)
- [ ] Support Status card (status badge, type, expiry date, renew button)
- [ ] Activated Domains table
- [ ] Licensed Addons table with version and status
- [ ] Deactivate button with confirmation
- [ ] Admin-only access (permission check)

#### Warning Banners
- [ ] Yellow banner appears 30 days before expiry
- [ ] Red banner appears when expired
- [ ] Banners include renew link
- [ ] Dismissible for warning (not for expired)
- [ ] Persists across page navigation

#### Dashboard Widget
- [ ] Shows license status (Active/Expired badge)
- [ ] Shows license type (Lifetime/Annual)
- [ ] Shows support expiry date
- [ ] Shows update available notice
- [ ] Links to license status page

#### Updates Page (/system/updates)
- [ ] Shows current version
- [ ] Shows available updates with changelog
- [ ] Update button with confirmation
- [ ] Progress indicator during update
- [ ] Success/error messages
- [ ] Addon updates list
- [ ] Update history
