# Reports Backend Rework Plan

## Overview
Transform the current hardcoded report system into a flexible, database-driven architecture that allows dynamic report configuration without code changes.

## Current Architecture Analysis

### Existing Structure
- **Report Classes**: Individual PHP classes (`ActiveContractsReport`, `ActivitiesPerPeriodReport`, etc.)
- **Registry Pattern**: `ReportRegistry` stores report instances in memory
- **Service Provider**: `ReportServiceProvider` registers reports at boot time
- **Base Class**: `BaseEloquentReport` provides common pagination logic
- **Contract Interface**: `Report` interface defines required methods (`slug`, `name`, `description`, `inputs`, `columns`, `query`)
- **Controller**: `ReportController` handles index, show, data, export routes

### Current Features
1. **Report Definition**: Each report defines:
   - Slug (unique identifier)
   - Name & Description
   - Input parameters (filters)
   - Column definitions
   - Eloquent query builder
2. **Filter Types**: `date`, `string`, `select:client`, etc.
3. **Export**: PDF and CSV export functionality
4. **Pagination**: Server-side pagination support

## Proposed New Architecture

### 1. Database Schema

#### `reports` Table
```sql
CREATE TABLE reports (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    slug VARCHAR(255) UNIQUE NOT NULL,
    name VARCHAR(255) NOT NULL,
    description TEXT NULL,
    category VARCHAR(100) NULL,  -- e.g., 'contracts', 'activities', 'financial'
    enabled BOOLEAN DEFAULT TRUE,
    order INT DEFAULT 0,
    created_at TIMESTAMP,
    updated_at TIMESTAMP,
    INDEX idx_slug (slug),
    INDEX idx_enabled_order (enabled, order)
);
```

#### `report_entities` Table
Defines which database entities (models) the report queries.

```sql
CREATE TABLE report_entities (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    report_id BIGINT UNSIGNED NOT NULL,
    model_class VARCHAR(255) NOT NULL,  -- e.g., 'App\Models\Contract'
    alias VARCHAR(50) NULL,  -- table alias for joins
    join_type ENUM('base', 'join', 'leftJoin', 'rightJoin') DEFAULT 'base',
    join_first VARCHAR(100) NULL,  -- first column for join
    join_operator VARCHAR(10) NULL,  -- =, !=, etc.
    join_second VARCHAR(100) NULL,  -- second column for join
    order INT DEFAULT 0,
    created_at TIMESTAMP,
    updated_at TIMESTAMP,
    FOREIGN KEY (report_id) REFERENCES reports(id) ON DELETE CASCADE,
    INDEX idx_report_order (report_id, order)
);
```

#### `report_columns` Table
Defines selectable columns and their presentation.

```sql
CREATE TABLE report_columns (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    report_id BIGINT UNSIGNED NOT NULL,
    key VARCHAR(100) NOT NULL,  -- column identifier
    label VARCHAR(255) NOT NULL,  -- display label
    type VARCHAR(50) DEFAULT 'string',  -- string, number, date, boolean, currency
    expression TEXT NOT NULL,  -- SQL expression or column path
    sortable BOOLEAN DEFAULT TRUE,
    visible BOOLEAN DEFAULT TRUE,
    order INT DEFAULT 0,
    format_options JSON NULL,  -- { "decimals": 2, "prefix": "$" }
    created_at TIMESTAMP,
    updated_at TIMESTAMP,
    FOREIGN KEY (report_id) REFERENCES reports(id) ON DELETE CASCADE,
    INDEX idx_report_order (report_id, order)
);
```

#### `report_filters` Table
Defines available filter parameters.

```sql
CREATE TABLE report_filters (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    report_id BIGINT UNSIGNED NOT NULL,
    key VARCHAR(100) NOT NULL,  -- filter identifier
    label VARCHAR(255) NOT NULL,
    type VARCHAR(50) NOT NULL,  -- date, string, select, multiselect, number, boolean
    nullable BOOLEAN DEFAULT TRUE,
    default_value TEXT NULL,
    options JSON NULL,  -- For select/multiselect: [{"label":"...", "value":"..."}]
    data_source VARCHAR(255) NULL,  -- e.g., 'clients', 'segments' for dynamic selects
    order INT DEFAULT 0,
    created_at TIMESTAMP,
    updated_at TIMESTAMP,
    FOREIGN KEY (report_id) REFERENCES reports(id) ON DELETE CASCADE,
    INDEX idx_report_order (report_id, order)
);
```

#### `report_conditions` Table
Defines WHERE clause conditions (rules) for filtering data.

```sql
CREATE TABLE report_conditions (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    report_id BIGINT UNSIGNED NOT NULL,
    column VARCHAR(255) NOT NULL,  -- e.g., 'contracts.start_date'
    operator VARCHAR(50) NOT NULL,  -- =, !=, >, <, >=, <=, LIKE, IN, BETWEEN, IS NULL, etc.
    value_type VARCHAR(50) NOT NULL,  -- static, filter, expression
    value TEXT NULL,  -- static value or expression
    filter_key VARCHAR(100) NULL,  -- references report_filters.key
    logical_operator ENUM('AND', 'OR') DEFAULT 'AND',
    group_id INT NULL,  -- for grouping conditions (AND within group, OR between groups)
    order INT DEFAULT 0,
    enabled BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP,
    updated_at TIMESTAMP,
    FOREIGN KEY (report_id) REFERENCES reports(id) ON DELETE CASCADE,
    INDEX idx_report_group (report_id, group_id, order)
);
```

#### `report_orders` Table
Defines default ORDER BY clauses.

```sql
CREATE TABLE report_orders (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    report_id BIGINT UNSIGNED NOT NULL,
    column VARCHAR(255) NOT NULL,
    direction ENUM('ASC', 'DESC') DEFAULT 'ASC',
    order INT DEFAULT 0,
    created_at TIMESTAMP,
    updated_at TIMESTAMP,
    FOREIGN KEY (report_id) REFERENCES reports(id) ON DELETE CASCADE,
    INDEX idx_report_order (report_id, order)
);
```

### 2. Model Structure

#### Report Model
```php
class Report extends Model
{
    protected $fillable = ['slug', 'name', 'description', 'category', 'enabled', 'order'];
    protected $casts = ['enabled' => 'boolean'];
    
    public function entities(): HasMany { return $this->hasMany(ReportEntity::class); }
    public function columns(): HasMany { return $this->hasMany(ReportColumn::class); }
    public function filters(): HasMany { return $this->hasMany(ReportFilter::class); }
    public function conditions(): HasMany { return $this->hasMany(ReportCondition::class); }
    public function orders(): HasMany { return $this->hasMany(ReportOrder::class); }
}
```

### 3. Query Builder Service

Create `ReportQueryBuilder` service to dynamically construct queries:

```php
class ReportQueryBuilder
{
    public function build(Report $report, array $filters = []): Builder
    {
        // 1. Start with base model query
        // 2. Apply joins from report_entities
        // 3. Select columns from report_columns
        // 4. Apply conditions from report_conditions
        // 5. Apply filter values to parameterized conditions
        // 6. Apply ORDER BY from report_orders
        // 7. Return Builder instance
    }
}
```

### 4. Backward Compatibility Layer

Keep existing Report classes but load from database:

```php
class DatabaseReport extends BaseEloquentReport implements Report
{
    public function __construct(protected Report $dbReport) {}
    
    public function slug(): string { return $this->dbReport->slug; }
    public function name(): string { return $this->dbReport->name; }
    public function description(): ?string { return $this->dbReport->description; }
    
    public function inputs(): array {
        return $this->dbReport->filters()
            ->orderBy('order')
            ->get()
            ->map(fn($f) => [
                'key' => $f->key,
                'type' => $f->type,
                'label' => $f->label,
                'nullable' => $f->nullable,
                'default' => $f->default_value,
                'options' => $f->options,
            ])
            ->toArray();
    }
    
    public function columns(): array {
        return $this->dbReport->columns()
            ->where('visible', true)
            ->orderBy('order')
            ->get()
            ->map(fn($c) => ['key' => $c->key, 'label' => $c->label])
            ->toArray();
    }
    
    public function query(array $filters): Builder {
        return app(ReportQueryBuilder::class)->build($this->dbReport, $filters);
    }
}
```

### 5. Migration Strategy

#### Phase 1: Database Setup
1. Create migrations for all new tables
2. Create models with relationships
3. Create seeders to migrate existing hardcoded reports

#### Phase 2: Service Layer
1. Build `ReportQueryBuilder` service
2. Build `DatabaseReport` adapter class
3. Update `ReportRegistry` to load from database
4. Create report management CRUD (admin UI)

#### Phase 3: Testing & Validation
1. Unit tests for query builder
2. Integration tests comparing old vs new results
3. Performance benchmarks
4. Export functionality validation

#### Phase 4: Migration Seeder
1. Create seeder that converts each hardcoded report into database records
2. Example for `ActiveContractsReport`:
   ```php
   $report = Report::create([
       'slug' => 'active-contracts',
       'name' => 'Aktivne pogodbe',
       'description' => 'Pogodbe, ki so aktivne...',
       'enabled' => true,
   ]);
   
   // Add entities (joins)
   $report->entities()->create([
       'model_class' => 'App\Models\Contract',
       'join_type' => 'base',
       'order' => 0,
   ]);
   
   $report->entities()->create([
       'model_class' => 'App\Models\ClientCase',
       'join_type' => 'join',
       'join_first' => 'contracts.client_case_id',
       'join_operator' => '=',
       'join_second' => 'client_cases.id',
       'order' => 1,
   ]);
   
   // Add columns
   $report->columns()->create([
       'key' => 'contract_reference',
       'label' => 'Pogodba',
       'expression' => 'contracts.reference',
       'order' => 0,
   ]);
   
   // Add filters
   $report->filters()->create([
       'key' => 'client_uuid',
       'label' => 'Stranka',
       'type' => 'select',
       'data_source' => 'clients',
       'nullable' => true,
       'order' => 0,
   ]);
   
   // Add conditions
   $report->conditions()->create([
       'column' => 'contracts.start_date',
       'operator' => '<=',
       'value_type' => 'expression',
       'value' => 'CURRENT_DATE',
       'group_id' => 1,
       'order' => 0,
   ]);
   ```

#### Phase 5: Remove Old Report System
Once the new database-driven system is validated and working:

1. **Delete Hardcoded Report Classes**:
   - Remove `app/Reports/ActiveContractsReport.php`
   - Remove `app/Reports/ActivitiesPerPeriodReport.php`
   - Remove `app/Reports/ActionsDecisionsCountReport.php`
   - Remove `app/Reports/DecisionsCountReport.php`
   - Remove `app/Reports/FieldJobsCompletedReport.php`
   - Remove `app/Reports/SegmentActivityCountsReport.php`

2. **Remove Base Classes/Interfaces** (if no longer needed):
   - Remove `app/Reports/BaseEloquentReport.php`
   - Remove `app/Reports/Contracts/Report.php` interface

3. **Remove/Update Service Provider**:
   - Remove `app/Providers/ReportServiceProvider.php`
   - Or update it to only load reports from database

4. **Update ReportRegistry**:
   - Modify to load from database instead of manual registration
   - Remove all hardcoded `register()` calls

5. **Clean Up Config**:
   - Remove any report-specific configuration files if they exist
   - Update `bootstrap/providers.php` to remove ReportServiceProvider

6. **Documentation Cleanup**:
   - Update any documentation referencing old report classes
   - Add migration guide for future report creation

### 6. Admin UI for Report Management

Create CRUD interface at `Settings/Reports/*`:
- **Index**: List all reports with enable/disable toggle
- **Create**: Wizard-style form for building new reports
- **Edit**: Visual query builder interface
- **Test**: Preview report results
- **Clone**: Duplicate existing report as starting point

### 7. Advanced Features (Future)

1. **Calculated Fields**: Allow expressions like `(column_a + column_b) / 2`
2. **Aggregations**: Support SUM, AVG, COUNT, MIN, MAX
3. **Subqueries**: Define subquery relationships
4. **Report Templates**: Predefined report structures
5. **Scheduled Reports**: Email reports on schedule
6. **Report Sharing**: Share reports with specific users/roles
7. **Version History**: Track report definition changes
8. **Report Permissions**: Control who can view/edit reports

## Benefits

1. **No Code Changes**: Add/modify reports through UI
2. **Flexibility**: Non-developers can create reports
3. **Consistency**: All reports follow same structure
4. **Maintainability**: Centralized report logic
5. **Reusability**: Share entities, filters, conditions
6. **Version Control**: Track changes to report definitions
7. **Performance**: Optimize query builder once
8. **Export**: Works with any report automatically

## Risks & Considerations

1. **Complexity**: Query builder must handle diverse SQL patterns
2. **Performance**: Dynamic query building overhead
3. **Security**: SQL injection risks with user input
4. **Learning Curve**: Team needs to understand new system
5. **Testing**: Comprehensive test suite required
6. **Migration**: Convert all existing reports correctly
7. **Edge Cases**: Complex queries may be difficult to represent

## Timeline Estimate

- **Phase 1 (Database)**: 2-3 days
- **Phase 2 (Services)**: 4-5 days  
- **Phase 3 (Testing)**: 2-3 days
- **Phase 4 (Migration)**: 1-2 days
- **Phase 5 (Cleanup)**: 1 day
- **Admin UI**: 3-4 days
- **Total**: 13-18 days

## Success Criteria

1. ✅ All existing reports work identically
2. ✅ New reports can be created via UI
3. ✅ Export functionality preserved
4. ✅ Performance within 10% of current
5. ✅ Zero SQL injection vulnerabilities
6. ✅ Comprehensive test coverage (>80%)
7. ✅ Documentation complete
