391 lines
10 KiB
Markdown
391 lines
10 KiB
Markdown
# Tenant Settings Templates Implementation
|
|
|
|
## Overview
|
|
|
|
This document describes the implementation of the tenant settings templates system, which allows administrators to configure tenant-specific settings through a structured, validated interface.
|
|
|
|
## Architecture
|
|
|
|
### Models
|
|
|
|
#### 1. SettingTemplate
|
|
Defines the structure and validation rules for available settings.
|
|
|
|
**Key Fields:**
|
|
- `key`: Unique identifier (e.g., 'basic_clinic_name_en')
|
|
- `category`: Organizes settings into logical groups
|
|
- `data_type`: Defines the type of value (STRING, INTEGER, BOOLEAN, CHOICE, FILE, ENCRYPTED, etc.)
|
|
- `is_required`: Whether the setting must be provided
|
|
- `validation_regex`: Optional regex pattern for validation
|
|
- `choices`: For CHOICE type, defines available options
|
|
- `order`: Display order within category
|
|
|
|
**Categories:**
|
|
- BASIC: Basic clinic information (name, logo, colors)
|
|
- VAT: VAT registration details
|
|
- ADDRESS: Physical address information
|
|
- ZATCA: ZATCA e-invoicing configuration
|
|
- NPHIES: NPHIES integration settings
|
|
- SMS: SMS/WhatsApp integration
|
|
- LAB: Laboratory integration
|
|
- RADIOLOGY: Radiology integration
|
|
|
|
#### 2. TenantSetting
|
|
Stores actual setting values for each tenant.
|
|
|
|
**Key Fields:**
|
|
- `tenant`: Foreign key to Tenant
|
|
- `template`: Foreign key to SettingTemplate
|
|
- `value`: Text field for most data types
|
|
- `encrypted_value`: Binary field for sensitive data
|
|
- `file_value`: File field for uploads
|
|
- `updated_by`: Tracks who made the change
|
|
|
|
### Service Layer
|
|
|
|
#### TenantSettingsService
|
|
|
|
Located in `core/settings_service.py`, provides:
|
|
|
|
**Key Methods:**
|
|
- `get_setting(key, default)`: Retrieve a setting with type conversion
|
|
- `set_setting(key, value, user)`: Save a setting with validation
|
|
- `get_category_settings(category)`: Get all settings in a category
|
|
- `validate_required_settings()`: Check if all required settings are set
|
|
- `get_missing_required_settings()`: List missing required settings
|
|
- `export_settings()`: Export settings as JSON
|
|
- `import_settings(data, user)`: Import settings from JSON
|
|
|
|
**Features:**
|
|
- Type-safe value retrieval
|
|
- Automatic type conversion
|
|
- Validation against template rules
|
|
- Encryption/decryption for sensitive data
|
|
- Caching for performance (5-minute TTL)
|
|
- Audit trail via updated_by field
|
|
|
|
## Admin Interface
|
|
|
|
### Enhanced TenantAdmin
|
|
|
|
The Tenant admin interface now includes:
|
|
|
|
1. **Settings Status Indicator**: Shows completion status in list view
|
|
2. **Inline Settings Editor**: Edit settings directly on tenant page
|
|
3. **Visual Indicators**: Color-coded required vs optional fields
|
|
4. **Help Text**: Inline guidance for each setting
|
|
5. **Settings Status Detail**: Detailed view of missing required settings
|
|
|
|
### SettingTemplateAdmin
|
|
|
|
Manage setting templates:
|
|
- Create/edit setting definitions
|
|
- Set validation rules
|
|
- Define choices for dropdown fields
|
|
- Control display order
|
|
|
|
### TenantSettingAdmin
|
|
|
|
View and manage individual setting values:
|
|
- Filter by category and tenant
|
|
- Search by setting name or value
|
|
- View audit history
|
|
- Masked display for encrypted values
|
|
|
|
## Usage
|
|
|
|
### For Administrators
|
|
|
|
#### Configuring Tenant Settings
|
|
|
|
1. Navigate to Django Admin → Core → Tenants
|
|
2. Select a tenant to edit
|
|
3. Scroll to the "Tenant Settings" inline section
|
|
4. Add/edit settings as needed
|
|
5. Required settings are marked with visual indicators
|
|
6. Save the tenant
|
|
|
|
#### Checking Settings Status
|
|
|
|
The tenant list view shows a "Settings Status" column:
|
|
- ✓ Complete: All required settings configured
|
|
- ✗ X Missing: Number of missing required settings
|
|
|
|
### For Developers
|
|
|
|
#### Retrieving Settings
|
|
|
|
```python
|
|
from core.settings_service import get_tenant_settings_service
|
|
|
|
# Get service for a tenant
|
|
service = get_tenant_settings_service(tenant)
|
|
|
|
# Get a single setting
|
|
clinic_name = service.get_setting('basic_clinic_name_en')
|
|
vat_number = service.get_setting('vat_registration_number')
|
|
|
|
# Get with default value
|
|
logo = service.get_setting('basic_logo', default=None)
|
|
|
|
# Get all settings in a category
|
|
basic_settings = service.get_category_settings('BASIC')
|
|
zatca_settings = service.get_category_settings('ZATCA')
|
|
|
|
# Get all settings
|
|
all_settings = service.get_all_settings()
|
|
```
|
|
|
|
#### Setting Values
|
|
|
|
```python
|
|
from core.settings_service import get_tenant_settings_service
|
|
|
|
service = get_tenant_settings_service(tenant)
|
|
|
|
# Set a setting
|
|
service.set_setting('basic_clinic_name_en', 'Agdar Centre', user=request.user)
|
|
|
|
# Set encrypted value
|
|
service.set_setting('zatca_otp', 'secret123', user=request.user)
|
|
|
|
# Set boolean
|
|
service.set_setting('sms_enable_notifications', True, user=request.user)
|
|
```
|
|
|
|
#### Validation
|
|
|
|
```python
|
|
# Check if all required settings are configured
|
|
is_valid = service.validate_required_settings()
|
|
|
|
# Get list of missing required settings
|
|
missing = service.get_missing_required_settings()
|
|
for template in missing:
|
|
print(f"Missing: {template.label_en} ({template.get_category_display()})")
|
|
```
|
|
|
|
#### Export/Import
|
|
|
|
```python
|
|
# Export settings
|
|
settings_dict = service.export_settings()
|
|
|
|
# Import settings
|
|
count = service.import_settings(settings_dict, user=request.user)
|
|
print(f"Imported {count} settings")
|
|
```
|
|
|
|
## Setting Templates
|
|
|
|
### Current Templates (40 total)
|
|
|
|
#### Basic Information (6)
|
|
- Clinic Name (English) - Required
|
|
- Clinic Name (Arabic)
|
|
- Clinic Code - Required
|
|
- Clinic Logo
|
|
- Primary Brand Color
|
|
- Secondary Brand Color
|
|
|
|
#### VAT Registration (3)
|
|
- VAT Registration Number - Required
|
|
- Tax ID
|
|
- Commercial Registration Number
|
|
|
|
#### Address Information (6)
|
|
- Street Address - Required
|
|
- Building Number
|
|
- City - Required
|
|
- Postal Code - Required
|
|
- Country Code - Required
|
|
- Additional Number
|
|
|
|
#### ZATCA E-Invoicing (7)
|
|
- ZATCA Environment - Required
|
|
- ZATCA OTP - Required
|
|
- CSID (auto-generated)
|
|
- Certificate (auto-generated)
|
|
- Private Key (auto-generated)
|
|
- Device Name
|
|
- Solution Name
|
|
|
|
#### NPHIES Integration (5)
|
|
- NPHIES Environment - Required
|
|
- Client ID - Required
|
|
- Client Secret - Required (encrypted)
|
|
- Organization License Number - Required
|
|
- Provider License Number
|
|
|
|
#### SMS/WhatsApp Integration (7)
|
|
- SMS Provider - Required
|
|
- Account SID - Required
|
|
- Auth Token - Required (encrypted)
|
|
- SMS Phone Number - Required
|
|
- WhatsApp Number
|
|
- Enable SMS Notifications
|
|
- Enable WhatsApp Notifications
|
|
|
|
#### Lab Integration (3)
|
|
- Lab API URL
|
|
- Lab API Key (encrypted)
|
|
- Enable Lab Integration
|
|
|
|
#### Radiology Integration (3)
|
|
- Radiology API URL
|
|
- Radiology API Key (encrypted)
|
|
- Enable Radiology Integration
|
|
|
|
## Management Commands
|
|
|
|
### populate_setting_templates
|
|
|
|
Populates or updates setting templates with default definitions.
|
|
|
|
```bash
|
|
python manage.py populate_setting_templates
|
|
```
|
|
|
|
This command:
|
|
- Creates new templates if they don't exist
|
|
- Updates existing templates with new definitions
|
|
- Is idempotent (safe to run multiple times)
|
|
|
|
## Security
|
|
|
|
### Encrypted Fields
|
|
|
|
Sensitive data (API keys, tokens, passwords) are stored encrypted:
|
|
- Uses Fernet symmetric encryption
|
|
- Encryption key derived from Django SECRET_KEY
|
|
- Encrypted values stored in `encrypted_value` binary field
|
|
- Displayed as "***ENCRYPTED***" in admin interface
|
|
|
|
### Audit Trail
|
|
|
|
All setting changes are tracked:
|
|
- `updated_by`: User who made the change
|
|
- `updated_at`: Timestamp of change
|
|
- Historical records via django-simple-history
|
|
|
|
## Performance
|
|
|
|
### Caching
|
|
|
|
Settings are cached for 5 minutes to reduce database queries:
|
|
- Cache key format: `tenant_setting:{tenant_id}:{setting_key}`
|
|
- Automatic cache invalidation on update
|
|
- Manual cache clearing: `service.clear_cache()`
|
|
|
|
### Database Indexes
|
|
|
|
Optimized queries with indexes on:
|
|
- `tenant` + `template` (unique together)
|
|
- `template.category` + `template.order`
|
|
- `template.key`
|
|
|
|
## Migration Path
|
|
|
|
### From Legacy JSON Settings
|
|
|
|
The old `Tenant.settings` JSONField is preserved for backward compatibility:
|
|
- Marked as "Legacy Settings (Deprecated)" in admin
|
|
- Collapsed by default
|
|
- Can be migrated to new system using import/export
|
|
|
|
### Migration Steps
|
|
|
|
1. Export existing settings from JSON field
|
|
2. Map to new setting keys
|
|
3. Import using `service.import_settings()`
|
|
4. Verify all settings migrated correctly
|
|
5. Remove old JSON field in future migration
|
|
|
|
## Extending the System
|
|
|
|
### Adding New Settings
|
|
|
|
1. Add template definition to `populate_setting_templates.py`
|
|
2. Run `python manage.py populate_setting_templates`
|
|
3. New setting appears in admin interface
|
|
|
|
### Adding New Categories
|
|
|
|
1. Add category to `SettingTemplate.Category` choices in `core/models.py`
|
|
2. Create migration: `python manage.py makemigrations`
|
|
3. Run migration: `python manage.py migrate`
|
|
4. Add templates for new category
|
|
|
|
### Custom Validation
|
|
|
|
Add custom validation in `TenantSettingsService._validate_value()`:
|
|
|
|
```python
|
|
elif template.data_type == SettingTemplate.DataType.CUSTOM:
|
|
# Add custom validation logic
|
|
if not custom_validator(value):
|
|
raise ValidationError("Custom validation failed")
|
|
```
|
|
|
|
## Testing
|
|
|
|
### Unit Tests
|
|
|
|
Test the service layer:
|
|
|
|
```python
|
|
from django.test import TestCase
|
|
from core.models import Tenant, SettingTemplate
|
|
from core.settings_service import get_tenant_settings_service
|
|
|
|
class TenantSettingsServiceTest(TestCase):
|
|
def setUp(self):
|
|
self.tenant = Tenant.objects.create(name="Test Clinic", code="TEST")
|
|
self.service = get_tenant_settings_service(self.tenant)
|
|
|
|
def test_get_setting(self):
|
|
value = self.service.get_setting('basic_clinic_name_en', 'Default')
|
|
self.assertEqual(value, 'Default')
|
|
|
|
def test_set_setting(self):
|
|
self.service.set_setting('basic_clinic_name_en', 'Test Clinic')
|
|
value = self.service.get_setting('basic_clinic_name_en')
|
|
self.assertEqual(value, 'Test Clinic')
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Common Issues
|
|
|
|
**Issue**: Settings not appearing in admin
|
|
- **Solution**: Run `python manage.py populate_setting_templates`
|
|
|
|
**Issue**: Validation errors when saving
|
|
- **Solution**: Check validation_regex in template definition
|
|
|
|
**Issue**: Encrypted values not decrypting
|
|
- **Solution**: Ensure SECRET_KEY hasn't changed
|
|
|
|
**Issue**: Cache not updating
|
|
- **Solution**: Call `service.clear_cache()` or wait 5 minutes
|
|
|
|
## Future Enhancements
|
|
|
|
Potential improvements:
|
|
1. Settings versioning and rollback
|
|
2. Settings templates per tenant type
|
|
3. Conditional settings (show/hide based on other settings)
|
|
4. Settings import/export via admin UI
|
|
5. Settings validation dashboard
|
|
6. Multi-language support for all help text
|
|
7. Settings change notifications
|
|
8. Bulk settings update across tenants
|
|
|
|
## Support
|
|
|
|
For issues or questions:
|
|
1. Check this documentation
|
|
2. Review code comments in `core/models.py` and `core/settings_service.py`
|
|
3. Check Django admin for validation errors
|
|
4. Review audit logs for setting changes
|