---
description: >-
  Panduan lengkap cara menggunakan semua komponen dalam aplikasi Laravel
  Koperasi - Dokumentasi teknis untuk developer.
---

# Cara Penggunaan

Dokumen ini adalah panduan lengkap untuk developer yang ingin memahami dan menggunakan komponen-komponen dalam aplikasi Laravel Koperasi MSJ. Dokumentasi ini mencakup penggunaan validation rules, helper classes, generators, dan business logic helpers.

## Daftar Isi

* [1. Validation Rules](how.md#1-validation-rules)
  * [1.1 MemberEligibilityRule](how.md#11-membereligibilityrule)
  * [1.2 NoPendingApplicationRule](how.md#12-nopendingapplicationrule)
  * [1.3 TopUpEligibilityRule](how.md#13-topupeligibilityrule)
  * [1.4 Integration dengan ValidationHelper](how.md#14-integration-dengan-validationhelper)
  * [1.5 Usage dalam Controller](how.md#15-usage-dalam-controller)
  * [1.6 Usage dalam Helper Classes](how.md#16-usage-dalam-helper-classes)
  * [1.7 Frontend Integration](how.md#17-frontend-integration)
  * [1.8 Error Handling Best Practices](how.md#18-error-handling-best-practices)
* [2. Helper Classes](how.md#2-helper-classes)
  * [2.1 IdGenerator - Generate ID Otomatis](how.md#21-idgenerator---generate-id-otomatis)
  * [2.2 ValidationHelper - Validasi Data](how.md#22-validationhelper---validasi-data)
  * [2.3 PaketHelper - Manajemen Stock Paket](how.md#23-pakethelper---manajemen-stock-paket)
  * [2.4 ResponseHelper - Standardized Response](how.md#24-responsehelper---standardized-response)
  * [2.5 AnggotaHelper - Manajemen Anggota](how.md#25-anggotahelper---manajemen-anggota)
* [3. Generator Classes](how.md#3-generator-classes)
  * [3.1 CicilanGenerator - Generate Jadwal Cicilan](how.md#31-cicilangenerator---generate-jadwal-cicilan)
  * [3.2 PotonganGenerator - Generate Potongan Gaji](how.md#32-potongangenerator---generate-potongan-gaji)
* [4. Business Logic Helpers](how.md#4-business-logic-helpers)
  * [4.1 PengajuanPinjamanHelper - Pengajuan Pinjaman](how.md#41-pengajuanpinjamanhelper---pengajuan-pinjaman)
  * [4.2 PengajuanPinjamanApprovalHelper - Approval Workflow](how.md#42-pengajuanpinjamanaprovalhelper---approval-workflow)
* [5. Report Helpers](how.md#5-report-helpers)
  * [5.1 Generate Laporan](how.md#51-generate-laporan)
* [6. Controller Usage Examples](how.md#6-controller-usage-examples)
  * [6.1 Dalam Controller](how.md#61-dalam-controller)
* [7. Frontend Integration](how.md#7-frontend-integration)
  * [7.1 AJAX Calls](how.md#71-ajax-calls)
* [8. Constants Usage](how.md#8-constants-usage)
* [9. Model Relationships](how.md#9-model-relationships)
* [10. Testing](how.md#10-testing)
  * [10.1 Menjalankan Tests](how.md#101-menjalankan-tests)
  * [10.2 Test Structure](how.md#102-test-structure)
  * [10.3 Writing Tests](how.md#103-writing-tests)
* [11. Best Practices](how.md#11-best-practices)
  * [11.1 Code Organization](how.md#111-code-organization)
  * [11.2 Error Handling](how.md#112-error-handling)
  * [11.3 Validation](how.md#113-validation)
  * [11.4 Database Queries](how.md#114-database-queries)
  * [11.5 Constants Usage](how.md#115-constants-usage)
* [12. Troubleshooting](how.md#12-troubleshooting)
  * [12.1 Common Issues](how.md#121-common-issues)
  * [12.2 Debugging Tips](how.md#122-debugging-tips)
* [13. Deployment](how.md#13-deployment)
  * [13.1 Pre-deployment Checklist](how.md#131-pre-deployment-checklist)
  * [13.2 Environment Configuration](how.md#132-environment-configuration)
  * [13.3 Performance Optimization](how.md#133-performance-optimization)
* [14. API Documentation](how.md#14-api-documentation)
  * [14.1 Authentication](how.md#141-authentication)
  * [14.2 Response Format](how.md#142-response-format)
  * [14.3 Error Responses](how.md#143-error-responses)
* [15. Additional Resources](how.md#15-additional-resources)
  * [15.1 Related Documentation](how.md#151-related-documentation)
  * [15.2 External References](how.md#152-external-references)
  * [15.3 Support](how.md#153-support)

***

## 1. Validation Rules

Aplikasi Laravel Koperasi menggunakan custom validation rules untuk menangani business logic yang kompleks terkait kelayakan anggota dan pengajuan pinjaman. Semua validation rules mengimplementasikan `ValidationRule` interface dari Laravel 11 untuk integrasi yang mulus dengan sistem validasi Laravel.

### 1.1 MemberEligibilityRule

**Path**: `app/Rules/MemberEligibilityRule.php`

**Fungsi**: Memvalidasi kelayakan anggota untuk mengajukan pinjaman berdasarkan tanggal bergabung koperasi.

**Business Logic**:

* Anggota harus sudah terdaftar sebelum atau pada periode yang dicek
* **Bulan bergabung (bulan pertama)**: Anggota hanya menerima Simpanan Pokok, TIDAK BISA mengajukan pinjaman
* **Bulan berikutnya**: Anggota baru dapat mengajukan pinjaman (minimal 1 bulan setelah bergabung)
* Support format periode MMYYYY (012025) dan YYYYMM (202501)
* Terintegrasi otomatis dengan ValidationHelper untuk validasi pengajuan pinjaman

**Constructor**:

```php
public function __construct(private ?string $periode = null)
```

**Usage dalam Validation**:

```php
use App\Rules\MemberEligibilityRule;

// Validasi dengan periode spesifik
$rules = [
    'nik' => ['required', new MemberEligibilityRule('012025')]
];

// Validasi dengan periode current
$rules = [
    'nik' => ['required', new MemberEligibilityRule()]
];
```

**Static Methods**:

```php
// Check eligibility programmatically
$result = MemberEligibilityRule::validateMemberEligibility($tanggalBergabung, '012025');
if (!$result['eligible']) {
    echo $result['reason']; // "Bulan bergabung - akan mendapat Simpanan Pokok..."
}

// Parse periode to Carbon date
$date = MemberEligibilityRule::parsePeriodeToDate('012025'); // January 2025
```

**Return Types**:

```php
[
    'eligible' => bool,
    'reason' => string,
    'type' => 'not_joined_yet' | 'join_month' | 'too_early' | 'eligible' | 'invalid_date'
]
```

### 1.2 NoPendingApplicationRule

**Path**: `app/Rules/NoPendingApplicationRule.php`

**Fungsi**: Memastikan anggota tidak memiliki pengajuan pinjaman yang masih dalam proses (pending atau menunggu transfer).

**Business Logic**:

* Cek pengajuan dengan status 'pending' (masih dalam proses approval)
* Cek pengajuan yang sudah disetujui tetapi belum ditransfer (level_approval = 3, tanggal_transfer = null)
* Mencegah duplikasi pengajuan - satu anggota hanya boleh memiliki satu pengajuan aktif
* Menampilkan nomor pinjaman yang sedang diproses untuk informasi user

**Constructor**:

```php
public function __construct($nik = null)
```

**Usage**:

```php
use App\Rules\NoPendingApplicationRule;

// Validasi NIK field
$rules = [
    'nik' => ['required', new NoPendingApplicationRule()]
];

// Validasi dengan NIK dari constructor
$rules = [
    'other_field' => ['required', new NoPendingApplicationRule($nik)]
];
```

**Error Messages**:

* `"Anggota dengan NIK ini masih memiliki pengajuan pinjaman yang sedang diproses (Nomor: PIN250001)..."`
* `"Anggota dengan NIK ini memiliki pengajuan pinjaman yang sudah disetujui dan menunggu transfer dana..."`

### 1.3 TopUpEligibilityRule

**Path**: `app/Rules/TopUpEligibilityRule.php`

**Fungsi**: Memvalidasi kelayakan anggota untuk mengajukan pinjaman top-up berdasarkan sisa cicilan pinjaman aktif.

**Business Logic**:

* Hanya melakukan validasi jika jenis_pengajuan = 'top_up'
* Anggota harus memiliki pinjaman aktif dengan status 'transferred' (status = 4)
* Sisa cicilan yang belum dibayar tidak boleh melebihi max_topup_cicilan (konfigurasi dari sys_enum, default: 5 cicilan)
* Jika sisa cicilan = 0, maka harus menggunakan jenis 'Pinjaman Baru', bukan top-up
* Nominal pinjaman baru harus lebih besar dari total sisa cicilan yang belum dibayar
* Auto-detect jenis pengajuan (baru/top-up) berdasarkan kondisi pinjaman anggota

**Constructor**:

```php
public function __construct($nik = null, $jenisPengajuan = null)
```

**Usage**:

```php
use App\Rules\TopUpEligibilityRule;

// Validasi dalam form request
$rules = [
    'nik' => [
        'required',
        new TopUpEligibilityRule(
            request('nik'),
            request('jenis_pengajuan')
        )
    ]
];
```

**Static Methods**:

```php
// Check eligibility dan get details
$result = TopUpEligibilityRule::checkEligibility('1234567890');

// Return format:
[
    'eligible' => bool,
    'jenis_pengajuan' => 'baru' | 'top_up',
    'message' => string,
    'sisa_cicilan' => int,
    'max_allowed' => int,
    'pinjaman_aktif' => TrsPinjaman|null
]
```

**Error Messages**:

* `"Anggota tidak memiliki pinjaman aktif untuk top-up"`
* `"Masih ada 8 cicilan yang belum dibayar. Maksimal 5 cicilan untuk top-up."`
* `"Semua cicilan sudah lunas. Jenis pengajuan harus 'Pinjaman Baru'."`

### 1.4 Integration dengan ValidationHelper

Rules ini terintegrasi dengan ValidationHelper untuk validasi otomatis:

```php
// Dalam ValidationHelper::validatePengajuanPinjamanStore()
$rules = $validation['rules'];

// Add custom rule ke existing nik rules
if (isset($rules['nik'])) {
    $existingRules = explode('|', $rules['nik']);
    $existingRules[] = new NoPendingApplicationRule;
    $rules['nik'] = $existingRules;
}
```

### 1.5 Usage dalam Controller

```php
class PengajuanPinjamanController extends MSJBaseController
{
    public function store($data)
    {
        // ValidationHelper sudah include NoPendingApplicationRule
        $validation = ValidationHelper::validatePengajuanPinjamanStore();

        if (!$validation['success']) {
            return redirect()->back()
                ->withErrors($validation['errors'])
                ->withInput();
        }

        // Process validated data...
    }
}
```

### 1.6 Usage dalam Helper Classes

```php
class PengajuanPinjamanHelper
{
    public static function determineApplicationType(string $nik): array
    {
        // Gunakan TopUpEligibilityRule untuk determine jenis
        $eligibility = TopUpEligibilityRule::checkEligibility($nik);

        return [
            'jenis_pengajuan' => $eligibility['jenis_pengajuan'],
            'eligible' => $eligibility['eligible'],
            'sisa_cicilan' => $eligibility['sisa_cicilan'],
            'message' => $eligibility['message']
        ];
    }
}
```

### 1.7 Frontend Integration

```javascript
// Check jenis pengajuan via AJAX
$.post(
    "/pengajuan-pinjaman/checkJenisPengajuan",
    {
        nik: $("#nik").val(),
    },
    function (response) {
        if (response.success) {
            $("#jenis_pengajuan").val(response.data.jenis_pengajuan);

            if (response.data.jenis_pengajuan === "top_up") {
                $("#info_sisa_cicilan").show();
                $("#sisa_cicilan_text").text(
                    response.data.sisa_cicilan + " cicilan",
                );
            }
        }
    },
);
```

### 1.8 Error Handling Best Practices

```php
// Dalam Form Request
public function rules()
{
    return [
        'nik' => [
            'required',
            'exists:mst_anggota,nik',
            new MemberEligibilityRule($this->periode),
            new NoPendingApplicationRule(),
            new TopUpEligibilityRule($this->nik, $this->jenis_pengajuan)
        ]
    ];
}

// Custom error messages
public function messages()
{
    return [
        'nik.required' => 'NIK anggota harus diisi',
        'nik.exists' => 'NIK tidak ditemukan dalam database anggota'
    ];
}
```

## 2. Helper Classes

Helper classes adalah komponen inti yang menangani business logic, validasi, dan operasi data dalam aplikasi koperasi. Semua helper dirancang dengan prinsip single responsibility dan reusability.

### 2.1 IdGenerator - Generate ID Otomatis

**Path**: `app/Helpers/Koperasi/Generator/IdGenerator.php`

**Fungsi**: Generator ID otomatis dengan format yang dapat dikonfigurasi melalui sys_id dan auto-increment counter dari sys_counter.

**Fitur**:
* Polymorphic ID generation berdasarkan tipe entitas
* Auto-increment counter dengan locking mechanism
* Support berbagai format: prefix + tahun + counter, format custom periode, dll
* Thread-safe untuk concurrent requests

**Usage**:

```php
use App\Helpers\Koperasi\Generator\IdGenerator;

// Generate ID Pinjaman (Format: PIN + YY + Counter)
$nomorPinjaman = IdGenerator::generate('pinjaman');
// Output: PIN250001, PIN250002, ...

// Generate ID Debit/Kredit dengan prefix dinamis
$idDebit = IdGenerator::generate('debitKredit', type: 'debit');
$idKredit = IdGenerator::generate('debitKredit', type: 'kredit');
// Output: DEB250001, KRE250001

// Generate ID Periode (Format: MMYYYY)
$idPeriode = IdGenerator::generate('periode', bulan: 1, tahun: 2025);
// Output: 012025

// Generate ID untuk transaksi lainnya
$idCicilan = IdGenerator::generate('cicilan');      // CIC250001
$idPelunasan = IdGenerator::generate('pelunasan');  // LNS250001
$idPotongan = IdGenerator::generate('potongan');    // POT012025
$idShu = IdGenerator::generate('shu');              // SHU250001
```

**Konfigurasi**: ID format dikonfigurasi di tabel `sys_id` dengan pattern dan lookup counter di `sys_counter`.

### 2.2 ValidationHelper - Validasi Data

**Path**: `app/Helpers/Koperasi/ValidationHelper.php`

**Fungsi**: Centralized validation system yang mengambil rules dari database (sys_table) dan menambahkan custom validation rules sesuai business logic.

**Fitur**:
* Dynamic validation rules dari sys_table configuration
* Auto-inject custom rules (MemberEligibilityRule, NoPendingApplicationRule, TopUpEligibilityRule)
* Support unique validation dengan exclude untuk update operations
* Consistent return format dengan success flag, errors, dan validated data

**Usage**:

```php
use App\Helpers\Koperasi\ValidationHelper;

// Validasi Anggota (Create)
$validation = ValidationHelper::validateAnggotaStore();
if (!$validation['success']) {
    return redirect()->back()
        ->withErrors($validation['errors'])
        ->withInput();
}
$validatedData = $validation['data'];

// Validasi Anggota (Update) - dengan exclude untuk unique fields
$validation = ValidationHelper::validateAnggotaUpdate($currentNik);

// Validasi Pengajuan Pinjaman - dengan custom rules otomatis
$validation = ValidationHelper::validatePengajuanPinjamanStore();
// Otomatis include: MemberEligibilityRule, NoPendingApplicationRule, TopUpEligibilityRule

// Validasi Generic dari Database berdasarkan kode tabel
$validation = ValidationHelper::validateFromDatabase('KOP201');
```

**Return Format**:
```php
[
    'success' => true|false,
    'data' => [...],      // Validated data
    'errors' => [...],    // Validation errors
    'rules' => [...],     // Applied rules
    'messages' => [...]   // Custom messages
]
```

### 2.3 PaketHelper - Manajemen Stock Paket

**Path**: `app/Helpers/Koperasi/PaketHelper.php`

**Fungsi**: Single gate untuk manajemen stock paket pinjaman. Paket adalah unit pinjaman dengan nominal tetap yang dikonfigurasi dalam sys_enum.

**Fitur**:
* Real-time stock tracking dengan auto-update dari transaksi debit/kredit
* Validasi ketersediaan paket sebelum approval pengajuan
* Perhitungan balance keuangan dan konversi ke unit paket
* Statistics dan reporting untuk dashboard
* Thread-safe operations untuk concurrent updates

**Usage**:

```php
use App\Helpers\Koperasi\PaketHelper;
use App\Helpers\Koperasi\Constants\KoperasiConstants;

// Get current stock paket
$currentStock = PaketHelper::get();
echo "Stock saat ini: {$currentStock} paket";

// Update stock (SET) - untuk inisialisasi atau koreksi manual
PaketHelper::update(100, $username, KoperasiConstants::PACKAGE_OP_SET);

// Reduce stock - saat pengajuan pinjaman disetujui
PaketHelper::update(10, $username, KoperasiConstants::PACKAGE_OP_REDUCE);

// Increase stock - saat ada pemasukan dari debit/kredit
PaketHelper::update(5, $username, KoperasiConstants::PACKAGE_OP_INCREASE);

// Validate availability sebelum approve pengajuan
$validation = PaketHelper::validate(50); // Request 50 paket
if (!$validation['valid']) {
    throw new Exception($validation['message']); // "Stock paket tidak mencukupi"
}

// Get statistics untuk dashboard dengan periode
$stats = PaketHelper::getStatistics('2025-01');
echo "Paket tersedia: {$stats['paket_tersedia']}";
echo "Paket digunakan: {$stats['paket_digunakan']}";
echo "Saldo kumulatif: {$stats['formatted']['saldo_kumulatif']}";
echo "Total pinjaman: {$stats['formatted']['total_pinjaman']}";
```

**Business Logic**: 
* 1 Paket = Nominal tetap (konfigurasi di sys_enum, contoh: Rp 1.000.000)
* Stock paket = Total balance keuangan / Nominal per paket
* Auto-update saat transaksi debit (tambah) atau kredit (kurang)

### 2.4 ResponseHelper - Standardized Response

**Path**: `app/Helpers/Koperasi/ResponseHelper.php`

**Fungsi**: Menyediakan format response yang konsisten untuk AJAX endpoints dan internal helper methods.

**Fitur**:
* Standardized JSON response format untuk AJAX calls
* Consistent array response untuk internal helper communications
* Support untuk success/error states dengan optional data payload
* HTTP status code handling untuk API responses

**Usage**:

```php
use App\Helpers\Koperasi\ResponseHelper;

// Untuk AJAX endpoints - return JSON response
public function calculateLoan()
{
    try {
        $result = $this->performCalculation();
        return ResponseHelper::json(true, 'Perhitungan berhasil', $result);
        // Response: {"success": true, "message": "Perhitungan berhasil", "data": {...}}
    } catch (Exception $e) {
        return ResponseHelper::json(false, 'Error: ' . $e->getMessage());
        // Response: {"success": false, "message": "Error: ...", "data": null}
    }
}

// Untuk internal helper methods - return array
public function processData()
{
    if ($error) {
        return ResponseHelper::response(false, 'Processing failed', $errorData);
    }
    return ResponseHelper::response(true, 'Success', $processedData);
}

// Response structure
// Array: ['success' => bool, 'message' => string, 'data' => mixed]
```

### 2.5 AnggotaHelper - Manajemen Anggota

**Path**: `app/Helpers/Koperasi/Anggota/AnggotaHelper.php`

**Fungsi**: Centralized business logic untuk manajemen data anggota koperasi dan integrasi dengan sistem user management.

**Fitur**:
* CRUD operations untuk master anggota
* Auto-generate user account untuk login anggota
* Manajemen status login (activate/deactivate/reactivate)
* Integration dengan master karyawan untuk auto-fill data
* Form options untuk dropdown (departemen, jabatan, bank)
* Audit trail dengan user_create dan user_update

**Usage**:

```php
use App\Helpers\Koperasi\Anggota\AnggotaHelper;

// Create anggota baru dengan auto-generate NIK jika diperlukan
$anggotaData = [
    'nik' => '1234567890',
    'nama_lengkap' => 'John Doe',
    'email' => 'john@example.com',
    'departemen' => 'IT',
    'jabatan' => 'Developer',
    'tanggal_bergabung' => '2025-01-15',
    'no_rekening' => '1234567890',
    'nama_bank' => 'BCA',
    // ... data lainnya
];
$anggota = AnggotaHelper::create($anggotaData, $username);

// Get anggota by NIK dengan relationships
$anggota = AnggotaHelper::get('1234567890');

// Update anggota
$updateData = ['nama_lengkap' => 'John Doe Updated', 'no_telp' => '08123456789'];
AnggotaHelper::update('1234567890', $updateData, $username);

// Toggle status aktif/non-aktif (soft delete)
AnggotaHelper::delete('1234567890', $username);

// Manajemen akses login anggota
AnggotaHelper::createLogin('1234567890');      // Create user account untuk login
AnggotaHelper::removeLogin('1234567890');      // Deactivate login access
AnggotaHelper::reactivateLogin('1234567890'); // Reactivate login access

// Check login status anggota
$hasActiveLogin = AnggotaHelper::hasLogin('1234567890', true);    // Check active login
$hasInactiveLogin = AnggotaHelper::hasLogin('1234567890', false); // Check inactive login

// Get options untuk form dropdown
$departemen = AnggotaHelper::getDepartemenOptions();  // Dari sys_enum
$jabatan = AnggotaHelper::getJabatanOptions();        // Dari sys_enum
$banks = AnggotaHelper::getBankOptions();              // Dari sys_enum
```

## 3. Generator Classes

Generator classes bertanggung jawab untuk auto-generate data transaksional seperti jadwal cicilan dan potongan gaji berdasarkan business rules yang telah ditentukan.

### 3.1 CicilanGenerator - Generate Jadwal Cicilan

**Path**: `app/Helpers/Koperasi/Generator/CicilanGenerator.php`

**Fungsi**: Auto-generate jadwal cicilan bulanan berdasarkan data pinjaman dengan perhitungan bunga dan periode jatuh tempo.

**Fitur**:
* Perhitungan cicilan dengan metode flat rate (bunga tetap per bulan)
* Generate periode jatuh tempo otomatis dari bulan berikutnya
* Preview calculation tanpa save untuk form pengajuan
* Batch insert ke database untuk performa optimal
* Support berbagai tenor: 3, 6, 12, 18, 24 bulan (konfigurasi di sys_enum)

**Business Rules**:
* Bunga dihitung per bulan dari nominal pinjaman (bukan sisa pokok)
* Cicilan ke-1 jatuh tempo bulan berikutnya setelah transfer
* Total angsuran = Cicilan pokok + Bunga per bulan
* Semua cicilan memiliki nominal yang sama (flat rate)

**Usage**:

```php
use App\Helpers\Koperasi\Generator\CicilanGenerator;

// Generate dan save ke database saat pinjaman ditransfer
$result = CicilanGenerator::generate('PIN250001', '1234567890', $username);
if ($result['success']) {
    echo "Berhasil generate {$result['data']['total_cicilan']} cicilan";
    echo "Total pinjaman: Rp {$result['data']['grand_total']}";
} else {
    echo "Error: {$result['message']}";
}

// Calculate preview untuk form pengajuan (tanpa save ke database)
$preview = CicilanGenerator::calculate(null, 10000000, '12 Bulan', true);
echo "Nominal pinjaman: Rp " . number_format($preview['nominal_pinjaman']);
echo "Bunga total: Rp " . number_format($preview['bunga_pinjaman']);
echo "Grand total: Rp " . number_format($preview['grand_total']);
echo "Angsuran per bulan: Rp " . number_format($preview['total_angsuran']);

// Access jadwal detail untuk preview
foreach ($preview['jadwal_cicilan'] as $index => $cicilan) {
    echo "Cicilan ke-{$cicilan['angsuran_ke']} ({$cicilan['periode']}): ";
    echo "Pokok: Rp {$cicilan['nominal_pokok']}, ";
    echo "Bunga: Rp {$cicilan['bunga_rp']}, ";
    echo "Total: Rp {$cicilan['total_angsuran']}";
}
```

**Rumus Perhitungan**:
```
Bunga per bulan = Nominal pinjaman × % bunga per bulan
Cicilan pokok = Nominal pinjaman / Tenor (bulan)
Total angsuran = Cicilan pokok + Bunga per bulan
Grand total = Nominal pinjaman + (Bunga per bulan × Tenor)
```

### 3.2 PotonganGenerator - Generate Potongan Gaji

**Path**: `app/Helpers/Koperasi/Generator/PotonganGenerator.php`

**Fungsi**: Auto-generate data potongan gaji bulanan untuk semua anggota berdasarkan cicilan yang jatuh tempo pada periode tersebut.

**Fitur**:
* Generate potongan gaji untuk semua anggota dengan cicilan jatuh tempo
* Include simpanan wajib untuk semua anggota aktif
* Skip cicilan yang sudah lunas melalui pelunasan manual
* Support regenerate untuk include pinjaman baru setelah generate awal
* Auto-close master periode setelah generate
* Validasi periode untuk mencegah duplikasi

**Business Rules**:
* Potongan = Simpanan wajib + Total cicilan jatuh tempo bulan ini
* Hanya generate untuk anggota aktif (isactive = 1)
* Skip cicilan dengan status isbayar = 1 (sudah lunas)
* Satu anggota = satu record potongan per periode
* Multiple cicilan dari beberapa pinjaman dijumlahkan dalam satu potongan

**Usage**:

```php
use App\Helpers\Koperasi\Generator\PotonganGenerator;

// Generate potongan dari master periode
$result = PotonganGenerator::generate('012025', $username);
echo "Berhasil generate: {$result['generated']} anggota";
echo "Dilewati: {$result['skipped']} anggota (tidak ada cicilan atau sudah ada)";
echo "Total potongan: Rp " . number_format($result['total_amount']);

// Generate dengan validasi periode terlebih dahulu
$result = PotonganGenerator::generateWithPeriodeValidation('012025', $username);
if (!$result['success']) {
    echo "Error: {$result['message']}"; // Periode tidak valid atau sudah closed
}

// Regenerate existing periode untuk include pinjaman baru
// Gunakan ini jika ada pinjaman baru yang ditransfer setelah generate awal
$result = PotonganGenerator::regenerate('012025', $username);
echo "Diupdate: {$result['updated']} anggota";
echo "Ditambah baru: {$result['added']} anggota";
```

**Return Format**:
```php
[
    'success' => true,
    'message' => 'Potongan berhasil digenerate',
    'generated' => 150,    // Jumlah anggota
    'skipped' => 5,        // Anggota tanpa cicilan
    'total_amount' => 125000000,  // Total potongan semua anggota
    'periode' => '012025'
]
```

## 4. Business Logic Helpers

Business logic helpers menangani proses bisnis kompleks seperti pengajuan pinjaman, approval workflow, dan transfer dana. Helper ini mengenkapsulasi logika bisnis agar mudah di-maintain dan di-test.

### 4.1 PengajuanPinjamanHelper - Pengajuan Pinjaman

**Path**: `app/Helpers/Koperasi/Pengajuan/PinjamanRepositories.php`

**Fungsi**: Centralized business logic untuk proses pengajuan pinjaman termasuk validasi, pembuatan pengajuan, dan pengecekan eligibilitas.

**Fitur**:
* Role-based data filtering (anggota hanya lihat pengajuan sendiri)
* Auto-detect jenis pengajuan (baru atau top-up)
* Validasi paket availability untuk admin
* Calculate remaining amount untuk top-up
* Integration dengan validation rules dan generators
* Support pagination dan filtering

**Usage**:

```php
use App\Helpers\Koperasi\Pengajuan\PinjamanRepositories;

// Get data pengajuan dengan role-based filtering
$pengajuan = PinjamanRepositories::get([
    'user_role' => 'akredt',      // Role user yang request
    'username' => $username,       // Username untuk filter anggota
    'query_type' => 'list',        // 'list' atau 'detail'
    'paginate' => 15,              // Items per page
    'status' => 'pending',         // Filter by status (optional)
    'periode' => '012025'          // Filter by periode (optional)
]);

// Create pengajuan pinjaman baru
$data = [
    'nik' => '1234567890',
    'nominal_pinjaman' => 10000000,
    'tenor_pinjaman' => '12 Bulan',
    'jumlah_paket_dipilih' => 10,
    'tujuan_pinjaman' => 'Modal usaha',
    'jenis_pengajuan' => 'baru', // atau 'top_up'
    // ... data lainnya
];
$result = PinjamanRepositories::create($data, $username, $userRole);
if ($result['success']) {
    echo "Pengajuan berhasil dibuat: {$result['data']['nomor_pinjaman']}";
}

// Determine application type (baru/top-up) secara otomatis
$eligibility = TopUpEligibilityRule::checkEligibility('1234567890');
if ($eligibility['jenis_pengajuan'] === 'top_up') {
    echo "Eligible untuk top-up";
    echo "Sisa cicilan: {$eligibility['sisa_cicilan']} bulan";
    echo "Nominal sisa: Rp " . number_format($eligibility['sisa_cicilan_amount']);
}

// Get pinjaman aktif anggota
$activeLoan = TrsPinjaman::where('nik', '1234567890')
    ->where('status_approval', KoperasiConstants::STATUS_TRANSFERRED)
    ->where('isactive', '1')
    ->first();

// Get remaining amount untuk top-up calculation
$remaining = PinjamanRepositories::getRemainingAmount('PIN250001', '1234567890');
echo "Sisa yang harus dibayar: Rp " . number_format($remaining);
```

### 4.2 PengajuanPinjamanApprovalHelper - Approval Workflow

**Path**: `app/Helpers/Koperasi/Pengajuan/ApprovalHelper.php`

**Fungsi**: Menangani multi-level approval workflow untuk pengajuan pinjaman dengan role-based authorization.

**Approval Levels**:
1. **Level 0**: Pengajuan dibuat (status pending)
2. **Level 1**: Admin Kredit review (akredt)
3. **Level 2**: Ketua Umum approve (ketuum)
4. **Level 3**: Admin Transfer cairkan dana (atrans)
5. **Level 4**: Transferred - Auto-generate cicilan

**Fitur**:
* Role-based approval authorization
* Sequential approval workflow (tidak bisa skip level)
* Approval history logging ke sys_log
* Auto-progression ke level berikutnya
* Reject di level manapun langsung set status rejected
* Final approval trigger cicilan generation

**Usage**:

```php
use App\Helpers\Koperasi\Pengajuan\ApprovalHelper;

// Approve pengajuan berdasarkan role
$result = ApprovalHelper::approve(
    $pengajuan,                    // Object TrsPinjaman
    $userRole,                     // 'akredt', 'ketuum', atau 'atrans'
    $username,                     // Username approver
    'Pengajuan disetujui'          // Catatan approval
);

if ($result['success']) {
    echo "Approval berhasil";
    if ($result['is_final']) {
        echo "Final approval - cicilan sudah digenerate";
        echo "Level saat ini: {$result['level']}";
    } else {
        echo "Lanjut ke level {$result['next_level']}";
    }
}

// Reject pengajuan dengan alasan
$result = ApprovalHelper::reject(
    $pengajuan,
    $userRole,
    $username,
    'Dokumen tidak lengkap'         // Alasan reject
);

if ($result['success']) {
    echo "Pengajuan ditolak";
    // Status langsung jadi 'rejected', tidak bisa diproses lagi
}

// Check authorization sebelum approve/reject
$canApprove = ApprovalHelper::canApprove($pengajuan, $userRole);
if (!$canApprove) {
    echo "User tidak memiliki authorization untuk approve di level ini";
}
```

**Workflow Logic**:
```
Anggota (create) → pending → Level 1 (akredt approve) 
  → Level 2 (ketuum approve) → Level 3 (atrans transfer)
  → Status 'transferred' → Auto-generate cicilan
  
Reject di level manapun → status 'rejected' (final)
```

## 5. Report Helpers

Report helpers menyediakan fungsi untuk generate berbagai laporan keuangan dan operasional dengan filtering, summary calculation, dan format export-ready.

### 5.1 Generate Laporan

**Available Reports**:
* **ShuReport**: Laporan distribusi SHU per anggota
* **PinjamanReport**: Laporan pengajuan dan pinjaman aktif
* **PotonganReport**: Laporan potongan gaji per periode
* **DebitKreditReport**: Laporan transaksi keuangan koperasi

**Fitur**:
* Dynamic filtering berdasarkan parameter request
* Summary calculations (total, rata-rata, count)
* Formatted data untuk Excel/PDF export
* Pagination support untuk large datasets
* Session-based data storage untuk export

**Usage**:

```php
use App\Helpers\Koperasi\Report\ShuReport;
use App\Helpers\Koperasi\Report\PinjamanReport;
use App\Helpers\Koperasi\Report\PotonganReport;
use App\Helpers\Koperasi\Report\DebitKreditReport;
use Illuminate\Http\Request;

// Generate SHU Report dengan filter
$request = new Request([
    'tahun' => '2025',
    'min_shu' => 100000,          // Filter minimum SHU
    'max_shu' => 1000000,         // Filter maksimum SHU
    'departemen' => 'IT'          // Filter by departemen (optional)
]);

$reportData = ShuReport::generate($request);
echo "Total records: {$reportData['summary']['total_records']}";
echo "Total SHU: Rp " . number_format($reportData['summary']['total_shu']);
echo "Rata-rata SHU: Rp " . number_format($reportData['summary']['avg_shu']);

// Access data untuk display
foreach ($reportData['data'] as $shu) {
    echo "{$shu['nik']} - {$shu['nama_lengkap']}: Rp {$shu['total_shu']}";
    echo "Simpanan: Rp {$shu['simpanan_total']}, Bunga: Rp {$shu['bunga_total']}";
}

// Generate Pinjaman Report
$request = new Request([
    'status' => 'pending',         // Filter by status
    'periode' => '012025',         // Filter by periode
    'jenis_pengajuan' => 'baru'   // Filter by jenis
]);
$reportData = PinjamanReport::generate($request);

// Generate Potongan Report
$request = new Request([
    'periode' => '012025',
    'min_potongan' => 500000,
    'max_potongan' => 2000000
]);
$reportData = PotonganReport::generate($request);

// Generate Debit Kredit Report
$request = new Request([
    'bulan' => '01',
    'tahun' => '2025',
    'type' => 'debit'             // Filter by type
]);
$reportData = DebitKreditReport::generate($request);
```

**Report Data Structure**:
```php
[
    'data' => [...],              // Array of records
    'summary' => [
        'total_records' => 150,
        'total_amount' => 125000000,
        'avg_amount' => 833333,
        // ... summary fields lainnya
    ],
    'filters' => [...],           // Applied filters
    'generated_at' => '2025-01-15 10:30:00'
]
```

## 6. Controller Usage Examples

Contoh implementasi controller yang menggunakan helper classes, validation, dan best practices untuk maintainable code.

### 6.1 Dalam Controller

**MSJBaseController** menyediakan helper methods dan utilities untuk semua controller:

**Available Methods**:
* `getUsername()` - Get current logged in username
* `getUserRole()` - Get current user role
* `isAdmin()` - Check if user is admin
* `transaction()` - Database transaction wrapper dengan auto rollback
* `paginate()` - Custom pagination helper
* `exportData()` - Export data ke Excel/PDF

**Usage**:

```php
use App\Http\Controllers\MSJBaseController;
use App\Helpers\Koperasi\ValidationHelper;
use App\Helpers\Koperasi\Generator\IdGenerator;
use App\Helpers\Koperasi\PaketHelper;
use App\Helpers\Koperasi\ResponseHelper;

class CustomController extends MSJBaseController
{
    /**
     * Store data dengan validation dan transaction
     */
    public function store($data)
    {
        // Gunakan validation helper dari database configuration
        $validation = ValidationHelper::validateFromDatabase('KOP999');
        if (!$validation['success']) {
            return redirect()->back()
                ->withErrors($validation['errors'])
                ->withInput();
        }

        // Gunakan transaction wrapper untuk data consistency
        return $this->transaction(function () use ($validation) {
            // Generate ID otomatis
            $id = IdGenerator::generate('custom');

            // Create record dengan audit trail
            $record = CustomModel::create([
                'id' => $id,
                ...$validation['data'],
                'user_create' => $this->getUsername(),
                'isactive' => '1'
            ]);

            // Update paket stock jika diperlukan
            PaketHelper::update(10, $this->getUsername(), KoperasiConstants::PACKAGE_OP_REDUCE);

            // Log ke sys_log
            $this->logActivity('create', 'custom', $id, 'Data berhasil disimpan');

            return "Data berhasil disimpan dengan ID: {$id}";
        }, $data, 'Data berhasil disimpan');
    }

    /**
     * AJAX endpoint dengan standardized response
     */
    public function ajaxEndpoint()
    {
        try {
            // Validate request
            $validated = request()->validate([
                'field1' => 'required',
                'field2' => 'numeric'
            ]);

            // Process business logic
            $result = $this->processData($validated);
            
            return ResponseHelper::json(true, 'Operasi berhasil', $result);
        } catch (\Illuminate\Validation\ValidationException $e) {
            return ResponseHelper::json(false, 'Validasi gagal', $e->errors());
        } catch (Exception $e) {
            Log::error('AJAX Error: ' . $e->getMessage());
            return ResponseHelper::json(false, 'Terjadi kesalahan: ' . $e->getMessage());
        }
    }

    /**
     * List data dengan role-based filtering
     */
    public function index()
    {
        $query = CustomModel::query();

        // Role-based filtering
        if ($this->getUserRole() === KoperasiConstants::ROLE_ANGGOTA) {
            // Anggota hanya lihat data sendiri
            $query->where('nik', $this->getUsername());
        }

        // Apply filters
        if (request('search')) {
            $query->where('nama', 'like', '%' . request('search') . '%');
        }

        // Paginate results
        $data = $query->paginate(15);

        return view('custom.index', compact('data'));
    }
}
```

## 7. Frontend Integration

Frontend menggunakan jQuery, DataTables, dan SweetAlert2 untuk interaksi yang smooth dengan backend melalui AJAX.

### 7.1 AJAX Calls

**Setup CSRF Token** (diperlukan untuk semua POST requests):

```javascript
// Set CSRF token untuk semua AJAX requests
$.ajaxSetup({
    headers: {
        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
    }
});
```

**Example: Calculate Loan Preview**

```javascript
// Calculate dan preview cicilan sebelum submit
$("#calculate-btn").click(function() {
    $.post(
        "/pengajuanPinjaman/calculate",
        {
            nik: $("#nik").val(),
            nominal_pinjaman: $("#nominal_pinjaman").val(),
            tenor_pinjaman: $("#tenor_pinjaman").val(),
            jenis_pengajuan: $("#jenis_pengajuan").val()
        },
        function (response) {
            if (response.success) {
                // Update summary
                $("#nominal_pinjaman_display").text(
                    formatRupiah(response.data.nominal_pinjaman)
                );
                $("#bunga_pinjaman_display").text(
                    formatRupiah(response.data.bunga_pinjaman)
                );
                $("#total_pinjaman_display").text(
                    formatRupiah(response.data.grand_total)
                );
                $("#total_angsuran_display").text(
                    formatRupiah(response.data.total_angsuran)
                );

                // Populate jadwal cicilan table
                let jadwalHtml = "";
                response.data.jadwal_cicilan.forEach(function (cicilan) {
                    jadwalHtml += `<tr>
                        <td>${cicilan.angsuran_ke}</td>
                        <td>${cicilan.periode}</td>
                        <td class="text-end">${formatRupiah(cicilan.nominal_pokok)}</td>
                        <td class="text-end">${formatRupiah(cicilan.bunga_rp)}</td>
                        <td class="text-end fw-bold">${formatRupiah(cicilan.total_angsuran)}</td>
                    </tr>`;
                });
                $("#jadwal-cicilan-tbody").html(jadwalHtml);
                
                // Show preview section
                $("#preview-section").slideDown();
            } else {
                Swal.fire('Error', response.message, 'error');
            }
        }
    ).fail(function() {
        Swal.fire('Error', 'Gagal menghubungi server', 'error');
    });
});

// Helper function untuk format rupiah
function formatRupiah(angka) {
    return 'Rp ' + parseInt(angka).toLocaleString('id-ID');
}
```

**Example: Generate Report**

```javascript
// Generate laporan dengan loading indicator
$("#generate-report-btn").click(function() {
    let btn = $(this);
    btn.prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> Generating...');
    
    $.post(
        "/reportrekap/generate/shu",
        {
            tahun: $("#tahun").val(),
            min_shu: $("#min_shu").val(),
            max_shu: $("#max_shu").val(),
            departemen: $("#departemen").val()
        },
        function (response) {
            if (response.success) {
                Swal.fire({
                    title: 'Berhasil!',
                    text: response.message,
                    icon: 'success',
                    showCancelButton: true,
                    confirmButtonText: 'Lihat Laporan',
                    cancelButtonText: 'Tutup'
                }).then((result) => {
                    if (result.isConfirmed) {
                        window.location.href = response.redirect_url;
                    }
                });
            } else {
                Swal.fire('Error', response.message, 'error');
            }
        }
    ).fail(function() {
        Swal.fire('Error', 'Gagal generate laporan', 'error');
    }).always(function() {
        btn.prop('disabled', false).html('Generate Laporan');
    });
});
```

**Example: Check Jenis Pengajuan**

```javascript
// Auto-detect jenis pengajuan saat NIK dipilih
$("#nik").change(function() {
    let nik = $(this).val();
    if (nik) {
        $.post("/pengajuanPinjaman/checkJenisPengajuan", { nik: nik })
            .done(function(response) {
                if (response.success) {
                    $("#jenis_pengajuan").val(response.data.jenis_pengajuan);
                    
                    if (response.data.jenis_pengajuan === 'top_up') {
                        $("#info-topup").show();
                        $("#sisa-cicilan").text(response.data.sisa_cicilan);
                        $("#sisa-nominal").text(formatRupiah(response.data.sisa_cicilan_amount));
                    } else {
                        $("#info-topup").hide();
                    }
                }
            });
    }
});
```

## 8. Constants Usage

Constants menyediakan nilai-nilai tetap yang digunakan di seluruh aplikasi untuk konsistensi dan kemudahan maintenance.

**Path**: `app/Helpers/Koperasi/Constants/KoperasiConstants.php`

**Categories**:
* **Role Constants**: User roles dan permissions
* **Status Constants**: Status approval dan transaksi
* **Enum IDs**: Reference ke sys_enum untuk dynamic values
* **Upload Directories**: Path untuk file uploads
* **Value Constants**: Active/Inactive, Paid/Unpaid status

**Usage**:

```php
use App\Helpers\Koperasi\Constants\KoperasiConstants;

// ============ ROLE CONSTANTS ============

// Check if user is admin
if (in_array($userRole, array_keys(KoperasiConstants::ADMIN_ROLES))) {
    // Admin-only functionality
}

// Get admin role info
$roleInfo = KoperasiConstants::ADMIN_ROLES[KoperasiConstants::ROLE_ADMIN_KREDIT];
echo "Level: {$roleInfo['level']}, Description: {$roleInfo['description']}";

// Get workflow steps untuk approval
$steps = KoperasiConstants::getWorkflowSteps();
foreach ($steps as $step) {
    echo "Level {$step['level']}: {$step['description']} ({$step['role']})";
}

// ============ STATUS CONSTANTS ============

// Set status pengajuan
$pengajuan->status_approval = KoperasiConstants::STATUS_PENDING;          // '0'
$pengajuan->status_approval = KoperasiConstants::STATUS_APPROVED_ADMIN_1; // '1'
$pengajuan->status_approval = KoperasiConstants::STATUS_APPROVED_ADMIN_2; // '2'
$pengajuan->status_approval = KoperasiConstants::STATUS_APPROVED_KETUA_UMUM; // '3'
$pengajuan->status_approval = KoperasiConstants::STATUS_TRANSFERRED;      // '4'
$pengajuan->status_approval = KoperasiConstants::STATUS_REJECTED;         // '-1'

// Check if pinjaman sudah ditransfer
if (KoperasiConstants::isTransferred($pinjaman)) {
    echo "Pinjaman sudah ditransfer dan cicilan sudah digenerate";
}

// ============ JENIS PENGAJUAN ============

$jenisPengajuan = KoperasiConstants::JENIS_PENGAJUAN_BARU;    // 'baru'
$jenisPengajuan = KoperasiConstants::JENIS_PENGAJUAN_TOP_UP;  // 'top_up'

// ============ TRANSACTION TYPE ============

$type = KoperasiConstants::TRANSACTION_TYPE_DEBIT;   // 'debit'
$type = KoperasiConstants::TRANSACTION_TYPE_KREDIT;  // 'kredit'

// ============ PAKET OPERATIONS ============

PaketHelper::update(100, $username, KoperasiConstants::PACKAGE_OP_SET);      // 'set'
PaketHelper::update(10, $username, KoperasiConstants::PACKAGE_OP_REDUCE);    // 'reduce'
PaketHelper::update(5, $username, KoperasiConstants::PACKAGE_OP_INCREASE);   // 'increase'

// ============ UPLOAD DIRECTORIES ============

$uploadPath = KoperasiConstants::UPLOAD_DIR_ANGGOTA;           // 'uploads/anggota'
$uploadPath = KoperasiConstants::UPLOAD_DIR_TRANSFER;          // 'uploads/transfer'
$uploadPath = KoperasiConstants::UPLOAD_DIR_PELUNASAN;         // 'uploads/pelunasan'
$uploadPath = KoperasiConstants::UPLOAD_DIR_DEBITKREDIT;       // 'uploads/debitkredit'

// ============ VALUE CONSTANTS ============

// Active/Inactive status
$model->isactive = KoperasiConstants::VALUE_ACTIVE;    // '1'
$model->isactive = KoperasiConstants::VALUE_INACTIVE;  // '0'

// Paid/Unpaid status untuk cicilan
$cicilan->isbayar = KoperasiConstants::VALUE_PAID;     // '1'
$cicilan->isbayar = KoperasiConstants::VALUE_UNPAID;   // '0'

// ============ ENUM IDs untuk EnumValues ============

use App\Helpers\Koperasi\Constants\EnumValues;

// Get values dari sys_enum
$paketTersedia = EnumValues::get(KoperasiConstants::ENUM_PAKET);
$nominalPerPaket = EnumValues::get(KoperasiConstants::ENUM_NOMINAL_PER_PAKET);
$bungaPerBulan = EnumValues::get(KoperasiConstants::ENUM_BUNGA_PER_BULAN);
$tenorOptions = EnumValues::getOptions(KoperasiConstants::ENUM_TENOR);
$maxTopUpCicilan = EnumValues::get(KoperasiConstants::ENUM_MAX_TOPUP_CICILAN);
```

**Benefits**:
* Centralized constants untuk easy maintenance
* Type-safe values (tidak ada typo)
* Self-documenting code
* Easy refactoring jika ada perubahan value

## 9. Model Relationships

Aplikasi menggunakan Eloquent relationships untuk mengelola relasi antar tabel dengan efisien. Semua model menggunakan eager loading untuk menghindari N+1 query problem.

**Model Structure**:

```
MstAnggota (Master)
├── hasMany → TrsPinjaman
├── hasMany → TrsCicilan
├── hasMany → TrsPotongan
├── hasMany → TrsShu
└── belongsTo → User

TrsPinjaman (Transaction)
├── belongsTo → MstAnggota
├── hasMany → TrsCicilan
└── hasMany → TrsPelunasan

TrsCicilan (Transaction)
├── belongsTo → MstAnggota
└── belongsTo → TrsPinjaman
```

**Usage Examples**:

```php
use App\Models\MstAnggota;
use App\Models\TrsPinjaman;
use App\Models\TrsCicilan;
use App\Helpers\Koperasi\Constants\KoperasiConstants;

// ============ EAGER LOADING ============

// Get anggota dengan semua relasi (avoid N+1 queries)
$anggota = MstAnggota::with([
    'trs_pinjamans',           // Semua pinjaman
    'trs_cicilans',            // Semua cicilan
    'trs_potongans',           // Semua potongan
    'trs_shus',                // SHU
    'user'                      // User account
])->find($nik);

// Access relationships
echo "Nama: {$anggota->nama_lengkap}";
echo "Total pinjaman: " . $anggota->trs_pinjamans->count();
echo "Username: {$anggota->user->username}";

// Get pinjaman dengan cicilan dan anggota
$pinjaman = TrsPinjaman::with([
    'trs_cicilans' => function($query) {
        $query->orderBy('periode');
    },
    'anggota',
    'trs_pelunasans'
])->find($nomorPinjaman);

// ============ FILTERED RELATIONSHIPS ============

// Get hanya cicilan belum bayar
$anggota = MstAnggota::with([
    'trs_cicilans' => function($query) {
        $query->where('isbayar', KoperasiConstants::VALUE_UNPAID)
              ->orderBy('periode');
    }
])->find($nik);

$totalBelumBayar = $anggota->trs_cicilans->sum('total_angsuran');

// Get pinjaman aktif saja
$anggota = MstAnggota::with([
    'trs_pinjamans' => function($query) {
        $query->where('status_approval', KoperasiConstants::STATUS_TRANSFERRED)
              ->where('isactive', KoperasiConstants::VALUE_ACTIVE);
    }
])->find($nik);

// ============ QUERY RELATIONSHIPS ============

// Check if anggota has pinjaman aktif
$hasPinjamanAktif = $anggota->trs_pinjamans()
    ->where('status_approval', KoperasiConstants::STATUS_TRANSFERRED)
    ->exists();

// Count cicilan belum bayar
$sisaCicilan = $anggota->trs_cicilans()
    ->where('isbayar', KoperasiConstants::VALUE_UNPAID)
    ->count();

// Get potongan untuk periode tertentu
$potongan = $anggota->trs_potongans()
    ->where('periode', '012025')
    ->first();

// ============ AGGREGATE FUNCTIONS ============

// Sum total pinjaman
$totalPinjaman = $anggota->trs_pinjamans()
    ->where('status_approval', KoperasiConstants::STATUS_TRANSFERRED)
    ->sum('total_pinjaman');

// Sum total SHU
$totalShu = $anggota->trs_shus()
    ->where('isactive', KoperasiConstants::VALUE_ACTIVE)
    ->sum('total_shu');

// ============ INVERSE RELATIONSHIPS ============

// Dari cicilan ke pinjaman dan anggota
$cicilan = TrsCicilan::with(['trs_pinjaman', 'mst_anggota'])
    ->where('periode', '012025')
    ->where('isbayar', KoperasiConstants::VALUE_UNPAID)
    ->get();

foreach ($cicilan as $c) {
    echo "Anggota: {$c->mst_anggota->nama_lengkap}";
    echo "Pinjaman: {$c->trs_pinjaman->nomor_pinjaman}";
    echo "Angsuran: Rp " . number_format($c->total_angsuran);
}

// ============ COMPOSITE KEY RELATIONSHIPS ============

// TrsCicilan menggunakan composite key
// Relasi tetap work dengan composite key setup

// Get cicilan untuk specific pinjaman
$cicilanList = TrsCicilan::where('nomor_pinjaman', $nomorPinjaman)
    ->where('nik', $nik)
    ->where('isbayar', KoperasiConstants::VALUE_UNPAID)
    ->orderBy('angsuran_ke')
    ->get();

// ============ RELATIONSHIP METHODS ============

// Method di Model untuk business logic
class MstAnggota extends Model {
    // Get pinjaman aktif
    public function getPinjamanAktifAttribute() {
        return $this->trs_pinjamans()
            ->where('status_approval', KoperasiConstants::STATUS_TRANSFERRED)
            ->where('isactive', KoperasiConstants::VALUE_ACTIVE)
            ->first();
    }
    
    // Get total cicilan belum bayar
    public function getTotalCicilanBelumBayarAttribute() {
        return $this->trs_cicilans()
            ->where('isbayar', KoperasiConstants::VALUE_UNPAID)
            ->sum('total_angsuran');
    }
}

// Usage
$anggota = MstAnggota::find($nik);
$pinjamanAktif = $anggota->pinjaman_aktif;  // Auto-load via accessor
$totalBelumBayar = $anggota->total_cicilan_belum_bayar;
```

**Best Practices**:

1. **Always use eager loading** untuk avoid N+1 queries
2. **Filter di relationship** untuk performance optimization
3. **Use accessor methods** untuk complex business logic
4. **Index foreign keys** untuk faster queries
5. **Soft delete aware** - check isactive field

***

## 10. Testing

Aplikasi ini dilengkapi dengan comprehensive test suite yang mencakup unit tests dan feature tests.

### 10.1 Menjalankan Tests

```bash
# Run all tests
php artisan test

# Run specific test file
php artisan test --filter=AnggotaControllerTest

# Run tests with coverage
php artisan test --coverage

# Run specific test method
php artisan test --filter=test_can_create_anggota
```

### 10.2 Test Structure

```
tests/
├── Feature/
│   ├── Controllers/
│   │   ├── AnggotaControllerTest.php
│   │   ├── PengajuanPinjamanControllerTest.php
│   │   ├── PeriodeControllerTest.php
│   │   ├── PotonganControllerTest.php
│   │   ├── PelunasanControllerTest.php
│   │   ├── ReportrekapControllerTest.php
│   │   └── ShuControllerTest.php
├── Unit/
│   ├── Helpers/Koperasi/
│   ├── Models/
│   │   ├── MstAnggotaTest.php
│   │   ├── TrsPinjamanTest.php
│   │   └── TrsCicilanTest.php
│   ├── Rules/
│   │   ├── MemberEligibilityRuleTest.php
│   │   ├── TopUpEligibilityRuleTest.php
│   │   └── NoPendingApplicationRuleTest.php
│   └── Services/
```

### 10.3 Writing Tests

```php
use Tests\TestCase;
use App\Models\MstAnggota;
use Illuminate\Foundation\Testing\RefreshDatabase;

class CustomTest extends TestCase
{
    use RefreshDatabase;

    public function test_example()
    {
        // Arrange
        $anggota = MstAnggota::factory()->create();

        // Act
        $response = $this->actingAs($anggota->user)
            ->get('/anggota');

        // Assert
        $response->assertStatus(200);
    }
}
```

***

## 11. Best Practices

### 11.1 Code Organization

```php
// ✅ Good: Use helper classes untuk business logic
$result = PengajuanPinjamanHelper::create($data, $username, $userRole);

// ❌ Bad: Business logic di controller
public function store() {
    // 100 lines of business logic...
}
```

### 11.2 Error Handling

```php
// ✅ Good: Use try-catch dengan ResponseHelper
try {
    $result = $this->processData();
    return ResponseHelper::json(true, 'Success', $result);
} catch (Exception $e) {
    Log::error('Process failed: ' . $e->getMessage());
    return ResponseHelper::json(false, 'Error: ' . $e->getMessage());
}

// ❌ Bad: Silent failure
$result = $this->processData();
return $result;
```

### 11.3 Validation

```php
// ✅ Good: Use ValidationHelper dengan custom rules
$validation = ValidationHelper::validatePengajuanPinjamanStore();
if (!$validation['success']) {
    return redirect()->back()
        ->withErrors($validation['errors'])
        ->withInput();
}

// ❌ Bad: Manual validation
if (empty($request->nik)) {
    return back()->with('error', 'NIK required');
}
```

### 11.4 Database Queries

```php
// ✅ Good: Use eager loading
$anggota = MstAnggota::with(['trs_pinjamans', 'trs_cicilans'])->get();

// ❌ Bad: N+1 query problem
$anggota = MstAnggota::all();
foreach ($anggota as $a) {
    $pinjaman = $a->trs_pinjamans; // Query untuk setiap anggota
}
```

### 11.5 Constants Usage

```php
// ✅ Good: Use constants dari KoperasiConstants
if ($pinjaman->status_approval === KoperasiConstants::STATUS_APPROVED_KETUA_UMUM) {
    // Process...
}

// ❌ Bad: Magic strings/numbers
if ($pinjaman->status_approval === '3') {
    // Process...
}
```

***

## 12. Troubleshooting

### 12.1 Common Issues

**Issue**: Error "Class 'IdGenerator' not found"

**Solution**:
```bash
composer dump-autoload
php artisan config:clear
php artisan cache:clear
```

**Issue**: Validation rules tidak berfungsi

**Solution**:
- Pastikan sys_table sudah diisi dengan benar
- Cek ValidationHelper::validateFromDatabase() menggunakan kode yang benar
- Jalankan seeder: `php artisan db:seed --class=KoperasiTableSeeder`

**Issue**: ID tidak auto-generate

**Solution**:
- Cek sys_id configuration di database
- Pastikan sys_counter memiliki record untuk pattern yang digunakan
- Jalankan seeder: `php artisan db:seed --class=KoperasiSysIdSeeder`

**Issue**: Paket stock tidak update

**Solution**:
- Cek sys_enum untuk KOP_paket
- Gunakan PaketHelper::update() dengan operation type yang benar
- Verify sys_counter untuk paket lookup

### 12.2 Debugging Tips

```php
// Enable query logging
DB::enableQueryLog();
// Your queries here
dd(DB::getQueryLog());

// Debug helper responses
$result = PengajuanPinjamanHelper::create($data, $username, $userRole);
dd($result); // Check response structure

// Check validation errors
$validation = ValidationHelper::validatePengajuanPinjamanStore();
if (!$validation['success']) {
    dd($validation['errors']); // See all validation errors
}
```

***

## 13. Deployment

### 13.1 Pre-deployment Checklist

```bash
# 1. Update dependencies
composer install --optimize-autoloader --no-dev
npm install && npm run build

# 2. Clear dan optimize caches
php artisan config:cache
php artisan route:cache
php artisan view:cache

# 3. Run migrations
php artisan migrate --force

# 4. Seed essential data
php artisan db:seed --class=KoperasiMenuSeeder
php artisan db:seed --class=KoperasiEnumSeeder
php artisan db:seed --class=KoperasiTableSeeder

# 5. Set proper permissions
chmod -R 755 storage bootstrap/cache
```

### 13.2 Environment Configuration

```env
# Production settings
APP_ENV=production
APP_DEBUG=false
APP_URL=https://yourdomain.com

# Database
DB_CONNECTION=mysql
DB_HOST=your-db-host
DB_DATABASE=your-db-name
DB_USERNAME=your-db-user
DB_PASSWORD=your-db-password

# Cache & Session
CACHE_DRIVER=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis
```

### 13.3 Performance Optimization

```bash
# Use OPcache for PHP
# Enable in php.ini:
opcache.enable=1
opcache.memory_consumption=128
opcache.max_accelerated_files=10000

# Use Laravel Octane (optional)
php artisan octane:start --server=roadrunner

# Monitor with Laravel Telescope (development only)
php artisan telescope:install
```

***

## 14. API Documentation

### 14.1 Authentication

Semua API endpoint memerlukan authentication melalui session atau Sanctum token.

```javascript
// AJAX dengan CSRF token
$.ajaxSetup({
    headers: {
        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
    }
});
```

### 14.2 Response Format

Semua API response menggunakan format standar dari ResponseHelper:

```json
{
    "success": true,
    "message": "Operation successful",
    "data": {
        // Response data here
    }
}
```

### 14.3 Error Responses

```json
{
    "success": false,
    "message": "Error message here",
    "errors": {
        "field_name": ["Error detail"]
    }
}
```

***

## 15. Additional Resources

### 15.1 Related Documentation

- **[DOCUMENTATION.md](documentation.md)** - Arsitektur sistem dan database schema
- **[README.md](README.md)** - Quick start dan instalasi
- **MSJ Framework Docs** - Framework documentation (jika tersedia)

### 15.2 External References

- [Laravel 12 Documentation](https://laravel.com/docs/12.x)
- [Laravel Validation Rules](https://laravel.com/docs/12.x/validation)
- [Eloquent Relationships](https://laravel.com/docs/12.x/eloquent-relationships)
- [Laravel Testing](https://laravel.com/docs/12.x/testing)

### 15.3 Support

Untuk pertanyaan dan issue:
- Hubungi development team
- Lihat issue tracker (jika ada)
- Review test cases untuk contoh penggunaan

***

**Catatan**: Dokumentasi ini mencakup semua cara penggunaan komponen dalam aplikasi Laravel Koperasi. Untuk informasi arsitektur dan struktur aplikasi, lihat file `DOCUMENTATION.md`.

**Last Updated**: 2025  
**Version**: 1.0  
**Maintained by**: MSJ Development Team
