# Open Core Business Suite - Complete Release & Licensing Ecosystem

## Overview

A comprehensive system covering:

1. **Release Tool** (Node.js) - Versioning, changelogs, packaging
2. **Website API** (Laravel) - Activation, licensing, downloads, updates
3. **Backend OTA** (Laravel) - Customer-facing update system
4. **Mobile Apps** - Version checking and updates

---

## System Architecture

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                           CZ App Studio Ecosystem                            │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  ┌──────────────────┐     ┌──────────────────┐     ┌──────────────────┐    │
│  │   Release Tool   │────▶│  Website (API)   │◀────│ Customer Backend │    │
│  │    (Node.js)     │     │    (Laravel)     │     │    (Laravel)     │    │
│  └──────────────────┘     └──────────────────┘     └──────────────────┘    │
│          │                        │                        │               │
│          │                        │                        │               │
│          ▼                        ▼                        ▼               │
│  ┌──────────────────┐     ┌──────────────────┐     ┌──────────────────┐    │
│  │  GitHub Releases │     │   File Storage   │     │   Mobile Apps    │    │
│  │   (Backup/CI)    │     │ (Downloads/ZIPs) │     │    (Flutter)     │    │
│  └──────────────────┘     └──────────────────┘     └──────────────────┘    │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘
```

---

# PART 1: RELEASE TOOL (Node.js)

**Location:** `/Volumes/Storage/Projects/CZ/OpenCoreBusinessSuite/release-tool/`

## Components Managed

| Component         | Repository                     | Versioning Files               |
| ----------------- | ------------------------------ | ------------------------------ |
| Backend (Laravel) | open_core_bs_backend           | `config/variables.php`         |
| 44 Modules        | (within backend)               | `Modules/*/module.json`        |
| Employee App      | apps/open_core_employee_app    | `pubspec.yaml` + build configs |
| Connect App       | apps/open_core_connect         | `pubspec.yaml` + build configs |
| Field Sales App   | apps/open_core_field_sales_app | `pubspec.yaml` + build configs |
| Attendance Device | apps/OpenCoreAttendance        | `app/build.gradle.kts`         |

## Directory Structure

```
release-tool/
├── package.json
├── bin/
│   └── release-cli.js              # CLI entry point
├── src/
│   ├── index.js
│   ├── commands/
│   │   ├── status.js               # Show current versions
│   │   ├── version.js              # Version bumping
│   │   ├── changelog.js            # Changelog generation
│   │   ├── package.js              # ZIP packaging (full + update)
│   │   ├── release.js              # Full release workflow
│   │   └── upload.js               # Upload to website API
│   ├── parsers/
│   │   ├── git-log.js              # Parse git commits
│   │   ├── module-json.js          # Parse module.json
│   │   ├── pubspec.js              # Parse Flutter pubspec.yaml
│   │   └── gradle.js               # Parse Android build.gradle
│   ├── generators/
│   │   ├── changelog.js            # Generate CHANGELOG.md
│   │   ├── release-notes.js        # GitHub release notes
│   │   └── update-manifest.js      # Generate update manifest JSON
│   ├── packagers/
│   │   ├── backend-full.js         # Full installation ZIP
│   │   ├── backend-update.js       # Update-only ZIP (changed files)
│   │   ├── addon.js                # Addon module ZIPs
│   │   └── mobile.js               # Mobile app builds
│   ├── uploaders/
│   │   └── website-api.js          # Upload to czappstudio.com API
│   └── utils/
│       ├── git.js
│       ├── file.js
│       └── config.js
├── templates/
│   ├── changelog.md.hbs
│   └── release-notes.md.hbs
└── release.config.js
```

## CLI Commands

### Status

```bash
npx release-cli status                    # All components
npx release-cli status --component=backend
```

### Version Bumping

```bash
npx release-cli version bump --type=patch
npx release-cli version bump-module Payroll --type=minor
npx release-cli version bump-changed --type=patch    # Auto-detect changed modules
npx release-cli version bump-app employee --type=minor
```

### Changelog

```bash
npx release-cli changelog generate --since=v5.0.1
npx release-cli changelog generate --module=Payroll
```

### Packaging

```bash
# Full installation packages
npx release-cli package --type=core          # Main app + *Core modules (non-SaaS)
npx release-cli package --type=saas          # Main app + *Core modules + MultiTenancyCore
npx release-cli package --addon=Payroll      # Single addon
npx release-cli package --addon=MultiTenancyCore  # SaaS upgrade addon ($50)

# UPDATE packages (differential - changed files only)
npx release-cli package:update --from=v5.0.0 --to=v5.0.1
npx release-cli package:update --addon=Payroll --from=1.0.2 --to=1.0.3

# Mobile builds
npx release-cli package --app=employee --platform=android
```

### Upload to Website

```bash
npx release-cli upload --file=releases/opencorebs-v5.0.1.zip --product=1
npx release-cli upload --file=releases/payroll-addon-v1.0.3.zip --product=15
```

### Full Release Workflow

```bash
npx release-cli release --dry-run           # Preview
npx release-cli release                     # Interactive
```

## Update Package Structure

### CORE APP Update (Main Product)

Contains only core framework + core modules. No addon modules included.

```
opencorebs-update-v5.0.0-to-v5.0.1.zip
├── UPDATE_MANIFEST.json
├── files/
│   ├── app/                      # Core Laravel app changes
│   │   └── Services/
│   │       └── SomeService.php
│   ├── Modules/                  # ONLY *Core MODULES (suffix = Core)
│   │   ├── SystemCore/
│   │   ├── AccountingCore/
│   │   ├── PMCore/
│   │   ├── CRMCore/
│   │   └── WMSInventoryCore/
│   │   # NOTE: MultiTenancyCore included only in SaaS package
│   ├── config/
│   │   └── variables.php
│   └── resources/
├── migrations/
│   └── 2025_01_08_xxx.php
├── CHANGELOG.md
└── UPDATE.md
```

### ADDON Update (Each addon separately)

Each addon has its own version and update package.

```
payroll-addon-update-v1.0.2-to-v1.0.3.zip
├── UPDATE_MANIFEST.json
├── files/
│   └── Modules/
│       └── Payroll/              # ONLY this addon's files
│           ├── App/
│           ├── Database/
│           ├── resources/
│           └── module.json       # Updated version
├── migrations/
│   └── 2025_01_08_payroll_xxx.php
├── CHANGELOG.md
└── UPDATE.md
```

### UPDATE_MANIFEST.json (Core App Example)

```json
{
  "product_type": "core",
  "from_version": "5.0.0",
  "to_version": "5.0.1",
  "release_date": "2025-01-08",
  "files": {
    "added": ["app/Services/NewService.php"],
    "modified": ["config/variables.php", "Modules/HRCore/App/..."],
    "deleted": []
  },
  "migrations": ["2025_01_08_123456_add_column.php"],
  "requires_composer_update": false,
  "requires_npm_install": false,
  "post_update_commands": ["php artisan migrate", "php artisan optimize:clear"]
}
```

### UPDATE_MANIFEST.json (Addon Example)

```json
{
  "product_type": "addon",
  "addon_uid": "payroll",
  "addon_name": "Payroll",
  "from_version": "1.0.2",
  "to_version": "1.0.3",
  "release_date": "2025-01-08",
  "requires_core_version": ">=5.0.0",
  "files": {
    "added": [],
    "modified": ["Modules/Payroll/App/Services/PayrollService.php"],
    "deleted": []
  },
  "migrations": ["2025_01_08_payroll_fix.php"],
  "post_update_commands": ["php artisan module:migrate Payroll"]
}
```

---

# PART 2: WEBSITE API (czappstudio.com)

## Existing Database Tables (Keep/Modify)

### `product_versions` (✅ EXISTS - REFACTOR)

Remove file storage fields, keep only version metadata:

- `version`, `changelog`, `release_notes`, `released_at`
- `min_laravel_version`, `min_php_version`
- `is_latest`, `is_beta`

**REMOVE these columns** (files will be in `product_version_files`):

- ~~`file_path`~~
- ~~`file_size`~~
- ~~`file_hash`~~

### `license_keys` (✅ EXISTS - Core licensing)

Already has:

- `license_key` - Unique key (CZ-XXXX-XXXX-XXXX-XXXX)
- `license_type` - 'lifetime' or 'annual'
- `support_expires_at` - NULL for lifetime, date for annual
- `renewal_count`, `last_renewed_at`
- `user_id`, `product_id`, `order_id`

**Usage:** Use existing `canDownloadUpdates()` method in LicenseKey model.

---

## New Database Tables (To Create)

### `license_activations` (NEW - Domain Tracking)

Track which domains are activated per license (1 production + 1 development).

```php
Schema::create('license_activations', function (Blueprint $table) {
    $table->id();
    $table->foreignId('license_key_id')->constrained()->cascadeOnDelete();
    $table->string('domain');
    $table->string('domain_type');      // 'production' or 'development'
    $table->string('ip_address')->nullable();
    $table->string('server_hostname')->nullable();
    $table->string('app_version')->nullable();
    $table->string('php_version')->nullable();
    $table->string('laravel_version')->nullable();
    $table->timestamp('activated_at');
    $table->timestamp('last_validated_at')->nullable();
    $table->boolean('is_active')->default(true);
    $table->timestamps();

    $table->unique(['license_key_id', 'domain']);
    $table->index(['license_key_id', 'is_active']);
});
```

### `product_version_files` (NEW - All files for versions)

Single source of truth for all downloadable files.

```php
Schema::create('product_version_files', function (Blueprint $table) {
    $table->id();
    $table->foreignId('product_version_id')->constrained()->cascadeOnDelete();
    $table->string('file_type');        // 'full', 'update', 'source' (for mobile)
    $table->string('platform')->nullable(); // 'backend', 'android', 'ios', null
    $table->string('file_name');
    $table->string('file_path');
    $table->unsignedBigInteger('file_size');
    $table->string('file_hash', 64);    // SHA256
    $table->string('from_version')->nullable(); // For update packages (e.g., "5.0.0")
    $table->json('manifest')->nullable(); // UPDATE_MANIFEST.json content
    $table->unsignedInteger('download_count')->default(0);
    $table->timestamps();

    $table->index(['product_version_id', 'file_type']);
    $table->unique(['product_version_id', 'file_type', 'from_version', 'platform']);
});
```

### `download_logs` (NEW - Download tracking)

Audit trail for downloads.

```php
Schema::create('download_logs', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
    $table->foreignId('license_key_id')->nullable()->constrained()->nullOnDelete();
    $table->foreignId('product_version_file_id')->constrained()->cascadeOnDelete();
    $table->string('ip_address')->nullable();
    $table->string('user_agent')->nullable();
    $table->timestamp('downloaded_at');

    $table->index(['license_key_id', 'downloaded_at']);
});
```

---

## Modifications to Existing Tables

### Refactor `product_versions` migration:

```php
// Remove these columns from the original migration file:
// $table->string('file_path')->nullable();
// $table->unsignedBigInteger('file_size')->nullable();
// $table->string('file_hash', 64)->nullable();
```

### Add to `license_keys` table:

```php
// Add to existing migration:
$table->unsignedTinyInteger('max_production_activations')->default(1);
$table->unsignedTinyInteger('max_development_activations')->default(1);
```

### Add to `products` table:

```php
// Add to existing migration:
$table->string('uid')->nullable()->unique()->after('alias');
// For addons: 'payroll', 'attendance', etc. - matches module.json alias
```

## API Endpoints

**Base URL:** `https://czappstudio.com/api/v1`

### Authentication

All API requests require:

- `X-License-Key: CZ-XXXX-XXXX-XXXX-XXXX`
- `X-Domain: customer-domain.com`

### Activation Endpoints

#### `POST /activate`

Activate a license for a domain.

**Request:**

```json
{
  "license_key": "CZ-XXXX-XXXX-XXXX-XXXX",
  "domain": "customer-app.com",
  "domain_type": "production",
  "server_info": {
    "ip": "192.168.1.1",
    "hostname": "server1",
    "php_version": "8.2.0",
    "app_version": "5.0.1"
  }
}
```

**Response (Success):**

```json
{
  "success": true,
  "activation": {
    "license_key": "CZ-XXXX-XXXX-XXXX-XXXX",
    "product": "Open Core Business Suite",
    "license_type": "lifetime",
    "support_expires_at": null,
    "activated_domains": [
      { "domain": "customer-app.com", "type": "production" },
      { "domain": "localhost", "type": "development" }
    ],
    "addons": [
      { "uid": "payroll", "name": "Payroll", "version": "1.0.3" },
      { "uid": "attendance", "name": "Attendance", "version": "2.0.0" }
    ]
  }
}
```

**Response (Error - Domain Limit):**

```json
{
  "success": false,
  "error": "domain_limit_exceeded",
  "message": "This license already has 1 production domain activated. Please deactivate the existing domain first.",
  "current_domains": [{ "domain": "old-domain.com", "type": "production" }]
}
```

#### `POST /deactivate`

Deactivate a domain from license.

**Request:**

```json
{
  "license_key": "CZ-XXXX-XXXX-XXXX-XXXX",
  "domain": "old-domain.com"
}
```

#### `POST /validate`

Check if activation is still valid (periodic check).

**Request:**

```json
{
  "license_key": "CZ-XXXX-XXXX-XXXX-XXXX",
  "domain": "customer-app.com",
  "app_version": "5.0.1"
}
```

**Response:**

```json
{
  "valid": true,
  "license_type": "lifetime",
  "support_active": true,
  "support_expires_at": null,
  "latest_version": "5.0.2",
  "update_available": true,
  "addons": [...]
}
```

### Update Check Endpoints

#### `GET /updates/check`

Check for available updates.

**Request Headers:**

```
X-License-Key: CZ-XXXX-XXXX-XXXX-XXXX
X-Domain: customer-app.com
X-Current-Version: 5.0.1
```

**Response:**

```json
{
  "update_available": true,
  "current_version": "5.0.1",
  "latest_version": "5.0.2",
  "can_update": true,
  "update_type": "patch",
  "changelog": "### Bug Fixes\n- Fixed payroll calculation...",
  "update_package": {
    "type": "differential",
    "from_version": "5.0.1",
    "to_version": "5.0.2",
    "file_size": 2457600,
    "checksum": "sha256:abc123..."
  },
  "modules_with_updates": [{ "uid": "payroll", "current": "1.0.2", "latest": "1.0.3" }]
}
```

#### `GET /updates/check-addons`

Check addon updates.

**Response:**

```json
{
  "addons": [
    {
      "uid": "payroll",
      "name": "Payroll",
      "current_version": "1.0.2",
      "latest_version": "1.0.3",
      "update_available": true,
      "can_update": true,
      "changelog": "..."
    }
  ]
}
```

### Download Endpoints

#### `GET /downloads/{product_id}/latest`

Get download URL for latest version.

#### `GET /downloads/{product_id}/version/{version}`

Get specific version.

#### `GET /downloads/{product_id}/update/{from_version}`

Get update package from specific version to latest.

**Response:**

```json
{
  "download_url": "https://czappstudio.com/downloads/temp/abc123.zip",
  "expires_at": "2025-01-08T12:00:00Z",
  "file_name": "opencorebs-update-5.0.1-to-5.0.2.zip",
  "file_size": 2457600,
  "checksum_sha256": "abc123..."
}
```

### Release Management Endpoints (Admin)

#### `POST /admin/releases`

Create new release (called by release-tool).

**Request:**

```json
{
  "api_key": "admin-secret-key",
  "product_id": 1,
  "version": "5.0.2",
  "changelog": "...",
  "files": [
    { "type": "full", "path": "/tmp/opencorebs-v5.0.2.zip" },
    {
      "type": "update",
      "from_version": "5.0.1",
      "path": "/tmp/update-5.0.1-to-5.0.2.zip"
    }
  ]
}
```

## Website Files to Create/Modify

### New Files

```
app/
├── Http/
│   └── Controllers/
│       └── Api/
│           └── V1/
│               ├── ActivationController.php
│               ├── UpdateController.php
│               ├── DownloadController.php
│               └── ReleaseController.php
├── Services/
│   └── Activation/
│       ├── ActivationService.php
│       ├── DomainValidator.php
│       └── UpdateService.php
└── Models/
    ├── ProductFile.php
    ├── LicenseActivation.php
    └── AddonLicense.php

routes/
└── api.php                         # Add API routes

database/migrations/
├── xxxx_create_product_files_table.php
├── xxxx_create_license_activations_table.php
└── xxxx_create_addon_licenses_table.php
```

### Filament Admin Resources

```
app/Filament/Admin/Resources/
├── ProductFileResource.php         # Manage downloadable files
├── LicenseActivationResource.php   # View activations
└── ProductResource.php             # Add file management relation
```

---

# PART 3: BACKEND OTA UPDATE SYSTEM

## Files to Modify/Create in Customer Backend

### 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 4: MOBILE APP RELEASES

## Overview

Mobile apps are distributed as **source code only**. Customers receive:

- Full source code ZIP when they purchase
- Email notification when new versions are released
- Download link to updated source code

## Release Tool Commands for Mobile Apps

```bash
# Bump mobile app version
npx release-cli version bump-app employee --type=minor

# Generate changelog for mobile app
npx release-cli changelog generate --app=employee

# Create source code package
npx release-cli package --app=employee
# Output: releases/open-core-employee-app-v5.1.0-source.zip

# Upload to website
npx release-cli upload --app=employee --version=5.1.0
```

## Mobile App Package Contents

```
open-core-employee-app-v5.1.0-source.zip
├── lib/                    # Dart source code
├── android/                # Android project
├── ios/                    # iOS project
├── pubspec.yaml           # Dependencies (version updated)
├── CHANGELOG.md           # Version changelog
├── README.md              # Setup instructions
└── .env.example           # Environment template
```

## Email Notification (Manual or Automated)

When a mobile app release is created:

1. Release tool generates changelog
2. Website stores new version info
3. Admin can trigger email to customers with active licenses:
   - Subject: "Open Core Employee App v5.1.0 Released"
   - Body: Changelog + Download link (requires valid license)

## Website Integration

The website already has:

- `product_versions` table - Stores version info
- Customer portal - Shows available downloads
- License validation - Controls download access

New for mobile apps:

- Store source code ZIPs in `product_files` table
- Same download validation as backend (license + support check)

---

# PART 5: BACKEND ACTIVATION & LICENSE UI

## Overview

The customer backend application needs a complete activation system with:

- Activation screen and flow (enhanced from existing)
- License status display page
- Support expiry reminders (dashboard notices, global alerts)
- Feature restrictions when not activated or support expired
- Admin-visible license information

## 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'],
    ],
]
```

## Files to Create/Modify (Backend)

### New Files

```
app/Http/Controllers/LicenseStatusController.php
resources/views/system/license-status.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 route
config/variables.php                            # Add supportUrl, renewUrl
```

## 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"
        }
    ]
}
```

---

# IMPLEMENTATION PHASES

## Phase 1: Release Tool Core

1. Create release-tool directory structure
2. Implement version parsers (module.json, pubspec.yaml, gradle)
3. Implement git log parser
4. Implement changelog generator
5. Implement status command

## Phase 2: Packaging

1. Implement full package creation
2. Implement differential update package creation
3. Implement addon package creation
4. Generate UPDATE_MANIFEST.json

## Phase 3: Website API

1. Create database migrations
2. Create API controllers
3. Implement activation service
4. Implement domain validation (1 prod + 1 dev)
5. Implement download service

## Phase 4: Website Admin

1. Create Filament resources for file management
2. Add file upload to product versions
3. Create activation monitoring dashboard

## Phase 5: Backend OTA

1. Complete OtaService implementation
2. Create update controller and routes
3. Create admin update UI
4. Implement backup/rollback

## Phase 6: Backend Activation UI

1. Enhance ActivationService with getCachedStatus() method
2. Add license columns to settings table migration
3. Create LicenseStatusController and view
4. Create license warning banner partial
5. Create dashboard license status widget
6. Update activation view with domain type selector
7. Add license-status route and menu item
8. Create email reminder command (scheduled)

## Phase 7: Integration & Upload

1. Implement release-tool upload command
2. Connect release-tool to website API
3. Test end-to-end release flow

## Phase 8: Mobile App Releases

1. Add mobile app packaging to release tool
2. Implement source code ZIP generation (excludes build artifacts)
3. Add email notification feature in website admin (optional)

---

# FILES TO CREATE/MODIFY

## Release Tool (New)

```
/Volumes/Storage/Projects/CZ/OpenCoreBusinessSuite/release-tool/
├── package.json
├── bin/release-cli.js
├── src/commands/*.js
├── src/parsers/*.js
├── src/generators/*.js
├── src/packagers/*.js
├── src/uploaders/website-api.js
└── release.config.js
```

## Website (Modify)

```
/Volumes/Storage/Projects/CZ/website/
├── app/Http/Controllers/Api/V1/
│   ├── ActivationController.php (new)
│   ├── UpdateController.php (new)
│   ├── DownloadController.php (new)
│   └── ReleaseController.php (new)
├── app/Services/
│   ├── Activation/
│   │   ├── ActivationService.php (new)
│   │   └── DomainValidator.php (new)
│   └── License/
│       └── LicenseKeyService.php (modify - add activation methods)
├── app/Models/
│   ├── LicenseActivation.php (new)
│   ├── ProductVersionFile.php (new)
│   ├── DownloadLog.php (new)
│   ├── LicenseKey.php (modify - add activations relationship)
│   └── ProductVersion.php (modify - add files relationship)
├── app/Filament/Admin/Resources/
│   ├── LicenseActivationResource.php (new)
│   └── ProductVersionResource.php (modify - add file management)
├── database/migrations/
│   ├── *_create_product_versions_table.php (modify - remove file columns)
│   ├── *_create_license_keys_table.php (modify - add activation limits)
│   ├── *_create_products_table.php (modify - add uid column)
│   ├── xxxx_create_license_activations_table.php (new)
│   ├── xxxx_create_product_version_files_table.php (new)
│   └── xxxx_create_download_logs_table.php (new)
├── routes/api.php (modify - add API routes)
└── config/services.php (modify - add API config)
```

## Backend (Modify)

```
/Volumes/Storage/Projects/CZ/OpenCoreBusinessSuite/open_core_bs_backend/
├── app/Services/OtaService/OtaService.php (complete)
├── app/Http/Controllers/UpdateController.php (new)
├── resources/views/system/updates.blade.php (new)
├── routes/web.php (add update routes)
└── config/variables.php (add apiUrl)
```

## Backend Modules (Modify)

```
Modules/*/module.json - Add releaseCategory field to all 44 modules
```

---

# VERIFICATION

## Test Scenarios

### Release & Packaging

1. **Release Flow**: Create release → Upload to website → Customer downloads
2. **Update Package**: Generate differential update → Verify manifest contains only changed files

### Activation Flow

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

### License Status UI

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

### Support Expiry Reminders

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

### Feature Restrictions

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

### OTA Updates

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

## Commands to Verify

```bash
# Release tool
npx release-cli status
npx release-cli release --dry-run

# Website API (curl)
curl -X POST https://czappstudio.com/api/v1/activate \
  -H "Content-Type: application/json" \
  -d '{"license_key": "CZ-TEST-1234", "domain": "test.com", "domain_type": "production"}'

curl -X POST https://czappstudio.com/api/v1/validate \
  -H "X-License-Key: CZ-TEST-1234" \
  -H "X-Domain: test.com"

curl -X GET https://czappstudio.com/api/v1/updates/check \
  -H "X-License-Key: CZ-TEST-1234" \
  -H "X-Domain: test.com" \
  -H "X-Current-Version: 5.0.1"

# Backend - OTA
php artisan update:check
php artisan update:apply

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

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

## 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
