# Configuration / Additional Settings

This guide explains how to add new settings to the Additional Settings system, which provides company-specific configuration options.

## Overview

The application uses a **hierarchical settings system**:

| Level | Access | Storage | Use Case |
|-------|--------|---------|----------|
| Global Settings | `gs($key)` | `settings` table | App-wide configuration |
| Company Settings | `cs($key)` | `company_settings` table | Company-specific settings |
| Additional Settings | `csa($key)` | JSON in `additional_settings` column | Granular company options |

## Settings Structure

### Additional Settings JSON Format

Each setting in the `additional_settings` JSON column follows this structure:

```php
"setting_key" => [
    "value" => mixed,           // The actual setting value
    "operation" => string|null, // Comparison operation for validation
    "error_message" => string,  // Error message if validation fails
    "override_key" => string|null, // Permission key to allow override
    "immutable" => bool         // Optional - prevents changes
]
```

**Example:**
```php
"invoices_has_currencies" => [
    "value" => true,
    "operation" => "===",
    "error_message" => "",
    "override_key" => null,
]
```

## Adding a New Setting

### Step 1: Add to Seeder

**Location:** `database/seeders/SettingSeeder.php`

Add the new setting to the `additional_settings` array in the seeder:

```php
public function run(): void
{
    $additionalSettings = [
        // ... existing settings

        // Add your new setting
        "your_new_setting" => [
            "value" => "default_value",  // Default value for new companies
            "operation" => "===",         // Validation operation
            "error_message" => "",        // Error if validation fails
            "override_key" => null,       // Permission to override (optional)
        ],

        // Boolean setting example
        "enable_feature_x" => [
            "value" => false,
            "operation" => "===",
            "error_message" => "",
            "override_key" => null,
        ],

        // Numeric setting example
        "max_items_per_page" => [
            "value" => 50,
            "operation" => ">=",
            "error_message" => "Value must be at least the configured minimum",
            "override_key" => "settings.override_pagination",
        ],
    ];

    // Create or update the setting record
    Setting::updateOrCreate(
        ['id' => 1],
        ['additional_settings' => $additionalSettings]
    );

    // Also create for CompanySetting
    CompanySetting::updateOrCreate(
        ['company_id' => 1],
        ['additional_settings' => $additionalSettings]
    );
}
```

### Step 2: Add to DTO

**Location:** `app/Domains/Core/Setting/DTOs/AdditionalSettingDTO.php`

Add the property to the DTO:

```php
<?php

namespace App\Domains\Core\Setting\DTOs;

class AdditionalSettingDTO
{
    public function __construct(
        // ... existing properties

        // Add your new setting property
        public ?bool $enable_feature_x = null,
        public ?int $max_items_per_page = null,
        public ?string $your_new_setting = null,
    ) {}

    public static function fromRequest(array $array): self
    {
        return new self(
            // ... existing mappings

            // Add your new setting mapping
            enable_feature_x: $array['enable_feature_x'] ?? null,
            max_items_per_page: $array['max_items_per_page'] ?? null,
            your_new_setting: $array['your_new_setting'] ?? null,
        );
    }
}
```

### Step 3: Add Validation Rules

**Location:** `app/Domains/Core/Setting/Http/Requests/V1/AdditionalSettingRequest.php`

Add validation rules for the new setting:

```php
public function rules(): array
{
    return [
        // ... existing rules

        // Boolean setting
        'enable_feature_x' => 'nullable|boolean',

        // Numeric setting with constraints
        'max_items_per_page' => 'nullable|integer|min:10|max:100',

        // String setting with enum values
        'your_new_setting' => 'nullable|string|in:option1,option2,option3',

        // String setting with max length
        'custom_message' => 'nullable|string|max:500',
    ];
}
```

### Step 4: Add to Service Update Method

**Location:** `app/Domains/Core/Setting/Services/AdditionalSettingServices.php`

Add the setting to the update method:

```php
public function update(AdditionalSettingDTO $dto): CompanySetting
{
    return DB::transaction(function () use ($dto) {
        $companySetting = CompanySetting::query()
            ->where('company_id', company()->id)
            ->firstOrFail();

        $additionalSettings = $companySetting->additional_settings;

        // ... existing updates

        // Add your new setting update
        $this->updateSettingIfProvided(
            $additionalSettings,
            'enable_feature_x',
            $dto->enable_feature_x
        );

        $this->updateSettingIfProvided(
            $additionalSettings,
            'max_items_per_page',
            $dto->max_items_per_page
        );

        $this->updateSettingIfProvided(
            $additionalSettings,
            'your_new_setting',
            $dto->your_new_setting
        );

        // Save and clear cache
        $companySetting->additional_settings = $additionalSettings;
        $companySetting->save();

        // Clear settings cache
        Cache::forget('cs.' . company()->id);

        return $companySetting->fresh();
    });
}

/**
 * Helper method to conditionally update a setting
 */
private function updateSettingIfProvided(array &$settings, string $key, $value): void
{
    if ($value !== null) {
        if (isset($settings[$key])) {
            $settings[$key]['value'] = $value;
        } else {
            $settings[$key] = [
                'value' => $value,
                'operation' => null,
                'error_message' => '',
                'override_key' => null,
            ];
        }
    }
}
```

### Step 5: Create Migration (For Existing Data)

If you need to add the setting to existing companies:

```bash
php artisan make:migration add_feature_x_to_additional_settings
```

```php
<?php

use Illuminate\Database\Migrations\Migration;
use App\Models\CompanySetting;

return new class extends Migration
{
    public function up(): void
    {
        CompanySetting::query()->each(function ($companySetting) {
            $additionalSettings = $companySetting->additional_settings ?? [];

            // Add new setting if it doesn't exist
            if (!isset($additionalSettings['enable_feature_x'])) {
                $additionalSettings['enable_feature_x'] = [
                    'value' => false,  // Default value
                    'operation' => '===',
                    'error_message' => '',
                    'override_key' => null,
                ];

                $companySetting->additional_settings = $additionalSettings;
                $companySetting->save();
            }
        });
    }

    public function down(): void
    {
        CompanySetting::query()->each(function ($companySetting) {
            $additionalSettings = $companySetting->additional_settings ?? [];

            unset($additionalSettings['enable_feature_x']);

            $companySetting->additional_settings = $additionalSettings;
            $companySetting->save();
        });
    }
};
```

## Accessing Settings

### Using Helper Functions

```php
// Get company setting
$value = cs('setting_column');

// Get additional setting (from JSON)
$featureEnabled = csa('enable_feature_x');
$maxItems = csa('max_items_per_page');

// Get global setting
$appTitle = gs('title');
```

### In Services/Controllers

```php
class YourService
{
    public function process(): void
    {
        // Check setting before feature
        if (csa('enable_feature_x')) {
            // Feature is enabled
            $this->doFeatureX();
        }

        // Use numeric setting
        $limit = csa('max_items_per_page') ?? 50;
        $items = Model::query()->limit($limit)->get();
    }
}
```

### In Blade Templates

```php
@if(csa('show_banner'))
    <div class="banner">{{ csa('banner_message') }}</div>
@endif
```

## Setting Categories

Settings are organized by category. Common categories include:

### Currency Settings
```php
"purchase_currency" => ["value" => 1, ...],
"sales_currency" => ["value" => 1, ...],
"expense_currency" => ["value" => 1, ...],
```

### Inventory Settings
```php
"inventory_system" => ["value" => "perpetual", ...],  // perpetual or periodic
"inventory_valuation_method" => ["value" => "FIFO", ...],  // FIFO or LIFO
```

### Notification Settings
```php
"invoice_reminder_before" => ["value" => 7, ...],
"contract_expiry_reminder" => ["value" => 30, ...],
"send_email_notifications" => ["value" => true, ...],
```

### Feature Toggles
```php
"enable_multi_currency" => ["value" => true, ...],
"require_approval" => ["value" => true, ...],
"allow_negative_stock" => ["value" => false, ...],
```

## Domain-Specific Settings

For domain-specific settings, use separate tables:

### Attendance Settings

**Location:** `app/Domains/Management/Attendance/Models/AttendanceSettings.php`

```php
class AttendanceSettings extends BaseModel
{
    protected $fillable = [
        'company_id',
        'late_threshold_minutes',
        'early_leave_threshold_minutes',
        'overtime_calculation_method',
        // ...
    ];
}
```

### Numbering Settings

**Location:** `app/Domains/Core/Settings/Models/NumberingSetting.php`

```php
class NumberingSetting extends BaseModel
{
    protected $fillable = [
        'company_id',
        'section_key',
        'prefix',
        'suffix',
        'next_number',
        'padding',
        // ...
    ];
}
```

## Caching Strategy

Settings are cached per company for performance:

```php
// In AppServiceProvider
$this->app->singleton('company.settings', function ($app) {
    return new class {
        protected $settings = null;

        public function get($key) {
            if ($this->settings === null) {
                $this->loadSettings();
            }
            return $this->settings->$key ?? null;
        }

        protected function loadSettings() {
            $this->settings = Cache::rememberForever("cs." . company()->id, function () {
                return CompanySetting::query()->company()->first();
            });
        }
    };
});
```

### Cache Invalidation

Always clear cache after updating settings:

```php
// In service update method
Cache::forget('cs.' . company()->id);
```

## API Endpoints

### Get Settings
```
GET /api/v1/app-settings/additional
```

### Update Settings
```
PUT /api/v1/app-settings/additional
Content-Type: application/json

{
    "enable_feature_x": true,
    "max_items_per_page": 25
}
```

## Best Practices

### DO:

1. **Always add to seeder** for new company defaults
2. **Add validation rules** to prevent invalid values
3. **Clear cache** after updates
4. **Use DTO** for type safety
5. **Document new settings** with comments in seeder
6. **Group related settings** by naming convention (e.g., `invoice_*`, `notification_*`)

### DON'T:

1. **Don't access settings directly** from database - use helpers
2. **Don't forget migration** for existing companies
3. **Don't store sensitive data** in settings (use encrypted config instead)
4. **Don't create circular dependencies** (settings depending on other settings)

## Troubleshooting

### Setting not updating

1. Clear the cache:
   ```php
   Cache::forget('cs.' . company()->id);
   ```

2. Verify the key exists in additional_settings JSON

3. Check DTO mapping is correct

### New setting not available

1. Run the seeder:
   ```bash
   php artisan db:seed --class=SettingSeeder
   ```

2. Run migration for existing companies:
   ```bash
   php artisan migrate
   ```

### Cache issues in development

```php
// Clear all settings cache
Artisan::call('cache:clear');

// Or specifically
Cache::flush();
```
