# Target PDL Berjalan (Ongoing Target PDL)

## 📋 Deskripsi

**Target PDL Berjalan** adalah akumulasi dari target PDL harian dari awal bulan hingga tanggal yang dipilih, dengan pengecualian:
- Hari Minggu
- Hari Close Operation (dari `operational_close_schedules`)

## ⚡ Performance: Redis Cache

Untuk meningkatkan performa, hasil perhitungan di-cache di **Redis** dengan fitur:

### Cache Strategy

**Cache Key Format:**
```
ongoing_target_pdl:resort_{resortId}:unit_{unitId}:{startDate}_to_{endDate}
```

**Contoh Cache Key:**
```
ongoing_target_pdl:resort_global:unit_global:2026-02-01_to_2026-02-18  (all resorts, all units)
ongoing_target_pdl:resort_RESORT001:unit_global:2026-02-01_to_2026-02-18  (specific resort, all units)
ongoing_target_pdl:resort_RESORT001:unit_UNIT001:2026-02-01_to_2026-02-18  (specific resort & unit)
```

- **Cache TTL**: Sampai akhir hari (expired otomatis pada pukul 00:00)
- **Cache Invalidation**: Otomatis di-reset setiap hari pukul **05:00 pagi**
- **Cache Granularity**: Terpisah per resort dan unit untuk optimasi hit rate

### Requirements
- **Redis** (recommended untuk production)
- Jika Redis tidak tersedia, sistem akan fallback ke **file cache** (database/cache)

### Scheduled Task
Command untuk invalidasi cache:
```bash
php artisan cache:invalidate-ongoing-target-pdl
```

Jadwal otomatis: **Setiap hari pukul 05:00 pagi** (via Laravel Scheduler)

> ⚠️ **Note**: Jika Redis tidak terinstall, command akan tetap berjalan dengan graceful fallback dan mencatat warning di log.

### Manual Cache Invalidation
Jika perlu invalidate cache secara manual:
```bash
php artisan cache:invalidate-ongoing-target-pdl
```

### Install Redis (Optional tapi Recommended)

**Ubuntu/Debian:**
```bash
sudo apt-get install php-redis
sudo systemctl restart php-fpm
sudo systemctl restart nginx
```

**Setup Redis di .env:**
```env
CACHE_DRIVER=redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
```

## 🧮 Cara Perhitungan

### Rumus Target PDL Harian
```php
Target PDL Harian = Total Target PDL * ((100 + interestRate) / maxLimitLoanDays) / 100
```

Dimana:
- `Total Target PDL` = SUM(original_loan_amount) dari semua loan aktif (status=activated, is_npl=false) yang di-disburse sebelum tanggal tersebut
- `interestRate` = Dari product_config
- `maxLimitLoanDays` = Dari product_config

### Rumus Target PDL Berjalan
```
Ongoing Target PDL = Σ (Target PDL Harian) untuk setiap hari kerja dari awal bulan hingga tanggal yang dipilih
```

### Contoh Perhitungan

```
Tanggal 1 Feb 2026 (Senin):
  - Total loan aktif: 10,000,000
  - Target PDL Harian: 10,000,000 * ((100 + 10) / 30) / 100 = 366,667
  - Akumulasi: 366,667

Tanggal 2 Feb 2026 (Selasa):
  - Total loan aktif: 12,000,000 (ada loan baru)
  - Target PDL Harian: 12,000,000 * ((100 + 10) / 30) / 100 = 440,000
  - Akumulasi: 366,667 + 440,000 = 806,667

Tanggal 3 Feb 2026 (Rabu):
  - Total loan aktif: 12,000,000
  - Target PDL Harian: 440,000
  - Akumulasi: 806,667 + 440,000 = 1,246,667

Tanggal 4 Feb 2026 (Minggu):
  - SKIP (hari Minggu)

Tanggal 5 Feb 2026 (Senin):
  - Total loan aktif: 15,000,000 (ada loan baru)
  - Target PDL Harian: 15,000,000 * ((100 + 10) / 30) / 100 = 550,000
  - Akumulasi: 1,246,667 + 550,000 = 1,796,667
```

## 🔌 API Endpoint

### Request
```http
POST /api/statistic/ongoing-pdl-target
Content-Type: application/json
Accept: application/json

{
  "endDate": "2026-02-18",
  "unitId": "all",
  "resortId": "all"
}
```

### Parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `endDate` | string | No | Tanggal akhir (format: YYYY-MM-DD). Default: hari ini |
| `unitId` | string | No | Filter berdasarkan unit. Default: "all" |
| `resortId` | string | No | Filter berdasarkan resort. Default: "all" |

### Response
```json
{
  "success": true,
  "data": 1796667
}
```

## 💡 Cara Menggunakan di Client

### Menghitung Persentase Target PDL Berjalan
```javascript
// Fetch ongoing target PDL
const ongoingTargetPdlResponse = await fetch('/api/statistic/ongoing-pdl-target', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Accept': 'application/json'
  },
  body: JSON.stringify({
    endDate: '2026-02-18',
    unitId: 'all',
    resortId: 'all'
  })
});
const ongoingTargetPdl = await ongoingTargetPdlResponse.json();

// Fetch ongoing storting
const ongoingStortingResponse = await fetch('/api/statistic/ongoing-storting', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Accept': 'application/json'
  },
  body: JSON.stringify({
    endDate: '2026-02-18',
    unitId: 'all',
    resortId: 'all'
  })
});
const ongoingStorting = await ongoingStortingResponse.json();

// Hitung persentase
const percentage = (ongoingStorting.data / ongoingTargetPdl.data) * 100;
console.log(`Persentase Target PDL: ${percentage.toFixed(2)}%`);
```

## 🧪 Testing dengan cURL

```bash
# Test dengan semua filter default
./curl/ongoing-pdl-target.sh

# Atau manual
curl -X POST "http://localhost:8000/api/statistic/ongoing-pdl-target" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "endDate": "2026-02-18",
    "unitId": "all",
    "resortId": "all"
  }'
```

## 📝 Catatan Implementasi

### Pendekatan: Dynamic Calculation
Implementasi ini menggunakan **dynamic calculation** tanpa menyimpan data target PDL harian ke database.

**Keuntungan:**
- ✅ Tidak perlu perubahan database
- ✅ Tidak perlu backfill data historis
- ✅ Selalu menggunakan data terbaru
- ✅ Simpel dan straightforward

**Kekurangan:**
- ⚠️ Performance: Loop per hari dari awal bulan (max 31 iterasi)
- ⚠️ Setiap iterasi melakukan query ke database

**Optimasi yang bisa dilakukan di masa depan:**
- Cache hasil per hari
- Materialized view
- Simpan target harian di transaction_reports

## 🏗️ Arsitektur

```
Controller: StatisticController@getOngoingTargetPdlByFilter
    ↓
Service: StatisticServiceImpl@getOngoingTargetPdlByFilter
    ↓
Repository: StatisticRepositoryImpl@calculateOngoingTargetPdlByFilter
    ↓
Redis Cache (Cache::remember)
    ↓
Private Method: calculateDailyPdlTarget()
```

### Cache Flow
```
Request → Check Cache → Hit: Return Cached Value
                     → Miss: Calculate → Store in Cache → Return Value

Daily at 05:00 → Scheduler runs → InvalidateOngoingTargetPdlCache → Clear all ongoing_target_pdl:* keys
```

## 📚 File yang Diubah

1. `app/Repositories/StatisticRepository.php` - Interface method
2. `app/Repositories/StatisticRepositoryImpl.php` - Implementation with Redis cache
3. `app/Services/StatisticService.php` - Interface method
4. `app/Services/StatisticServiceImpl.php` - Service method
5. `app/Http/Controllers/StatisticController.php` - Controller method
6. `routes/api.php` - Route definition
7. `app/Console/Commands/InvalidateOngoingTargetPdlCache.php` - Console command for cache invalidation
8. `bootstrap/app.php` - Scheduler configuration
