# Foreign Key Relationships Update - Task 4.3

## Overview
This document summarizes the implementation of Task 4.3: Update foreign key relationships for the Grazuri Migration spec.

## Date
2026-05-08

## Changes Made

### 1. New Django Models Created

Five new Django models were added to `loans/models.py` to ensure compatibility with the target Grazuri schema. All models include proper foreign key relationships with appropriate `on_delete` behavior and `related_name` attributes.

#### 1.1 BureauRecord Model
**Purpose**: Track credit bureau records for borrowers  
**Database Table**: `bureau_records`  
**Foreign Keys**:
- `borrower` → `users.CustomUser` (CASCADE)
- `baccount` → `loans.Loan` (CASCADE)

**Fields**:
- `id`: AutoField (primary key)
- `borrower`: ForeignKey to CustomUser
- `baccount`: ForeignKey to Loan
- `bureau_name`: CharField (100)
- `record_date`: DateTimeField
- `credit_score`: IntegerField
- `report_data`: TextField
- `created_at`: DateTimeField (auto_now_add)
- `updated_at`: DateTimeField (auto_now)

#### 1.2 LoanDisbursement Model
**Purpose**: Track loan disbursement transactions  
**Database Table**: `loan_disbursements`  
**Foreign Keys**:
- `loan` → `loans.Loan` (CASCADE)

**Fields**:
- `id`: AutoField (primary key)
- `loan`: ForeignKey to Loan
- `amount`: DecimalField (12, 2)
- `disbursement_date`: DateTimeField
- `disbursement_method`: CharField (50)
- `reference_number`: CharField (100)
- `notes`: TextField
- `created_at`: DateTimeField (auto_now_add)
- `updated_at`: DateTimeField (auto_now)

#### 1.3 LoanFee Model
**Purpose**: Track various loan fees  
**Database Table**: `loan_fees`  
**Foreign Keys**:
- `loan` → `loans.Loan` (CASCADE)

**Fields**:
- `id`: AutoField (primary key)
- `loan`: ForeignKey to Loan
- `fee_type`: CharField (50)
- `fee_name`: CharField (100)
- `amount`: DecimalField (12, 2)
- `is_paid`: BooleanField
- `paid_date`: DateTimeField
- `created_at`: DateTimeField (auto_now_add)
- `updated_at`: DateTimeField (auto_now)

#### 1.4 LoanGuarantor Model
**Purpose**: Store loan guarantor information  
**Database Table**: `loan_guarantors`  
**Foreign Keys**:
- `borrower` → `users.CustomUser` (CASCADE)

**Fields**:
- `id`: AutoField (primary key)
- `borrower`: ForeignKey to CustomUser
- `guarantor_name`: CharField (200)
- `guarantor_phone`: CharField (17)
- `guarantor_email`: EmailField
- `guarantor_id_number`: CharField (50)
- `relationship`: CharField (100)
- `is_active`: BooleanField
- `created_at`: DateTimeField (auto_now_add)
- `updated_at`: DateTimeField (auto_now)

#### 1.5 LoanStatus Model
**Purpose**: Track loan status history  
**Database Table**: `loan_statuses`  
**Foreign Keys**:
- `loan` → `loans.Loan` (CASCADE)
- `changed_by` → `users.CustomUser` (SET_NULL)

**Fields**:
- `id`: AutoField (primary key)
- `loan`: ForeignKey to Loan
- `status`: CharField (50)
- `status_date`: DateTimeField
- `changed_by`: ForeignKey to CustomUser
- `notes`: TextField
- `created_at`: DateTimeField (auto_now_add)

### 2. Django Migration Created

**File**: `loans/migrations/0025_add_grazuri_foreign_key_models.py`

This migration creates all five new models with their foreign key relationships. The migration:
- Creates the database tables with proper constraints
- Establishes foreign key relationships with CASCADE and SET_NULL behaviors
- Sets up proper indexes for foreign key columns
- Includes verbose names and ordering for admin interface

**Dependencies**:
- Depends on migration `0024_add_penalty_date_field`
- Requires `AUTH_USER_MODEL` (users.CustomUser)

### 3. Django Admin Registration

**File**: `loans/admin.py`

All five new models have been registered in the Django admin interface with:
- Custom list displays showing key fields
- Search functionality on relevant fields
- Filtering options for dates and status fields
- Raw ID fields for foreign key relationships (better performance)
- Organized fieldsets for better UX
- Read-only timestamp fields

**Admin Classes**:
- `BureauRecordAdmin`
- `LoanDisbursementAdmin`
- `LoanFeeAdmin`
- `LoanGuarantorAdmin`
- `LoanStatusAdmin`

### 4. Existing Foreign Key Relationships

**Review Result**: All existing foreign key relationships in the Django system are properly configured and require no changes.

**Existing Models with Foreign Keys**:
- `CustomUser`: Has foreign keys to Branch and self-references
- `LoanApplication`: Has foreign keys to CustomUser and LoanProduct
- `Loan`: Has foreign keys to LoanApplication, CustomUser, and self-reference
- `Repayment`: Has foreign key to Loan

All existing foreign keys have:
- ✅ Appropriate `on_delete` behavior (CASCADE, SET_NULL)
- ✅ Proper `related_name` attributes for reverse relationships
- ✅ Correct references to related models

## Foreign Key Configuration Details

### on_delete Behaviors Used

1. **CASCADE**: Used for dependent records that should be deleted when parent is deleted
   - BureauRecord → Loan
   - BureauRecord → CustomUser
   - LoanDisbursement → Loan
   - LoanFee → Loan
   - LoanGuarantor → CustomUser
   - LoanStatus → Loan

2. **SET_NULL**: Used for optional references that should be preserved even if parent is deleted
   - LoanStatus → CustomUser (changed_by)

### Related Names

All foreign keys include `related_name` attributes for easy reverse lookups:
- `borrower.bureau_records` - Access bureau records for a borrower
- `loan.bureau_records` - Access bureau records for a loan
- `loan.disbursements` - Access disbursements for a loan
- `loan.fees` - Access fees for a loan
- `borrower.guarantor_records` - Access guarantors for a borrower
- `loan.status_history` - Access status history for a loan
- `user.loan_status_changes` - Access status changes made by a user

### Database Column Names

The `db_column` parameter is used where the Grazuri schema uses specific column names:
- `borrower` field uses `db_column='borrower'`
- `baccount` field uses `db_column='baccount'`
- `loan` field uses `db_column='loan'`

## Verification Steps

To verify the foreign key relationships after deployment:

### 1. Check Django Models
```python
from loans.models import BureauRecord, LoanDisbursement, LoanFee, LoanGuarantor, LoanStatus

# Verify models are accessible
print(BureauRecord._meta.get_fields())
print(LoanDisbursement._meta.get_fields())
```

### 2. Apply Migration
```bash
python manage.py migrate loans
```

### 3. Verify in Database
```sql
-- Check that tables were created
SHOW TABLES LIKE '%bureau_records%';
SHOW TABLES LIKE '%loan_disbursements%';
SHOW TABLES LIKE '%loan_fees%';
SHOW TABLES LIKE '%loan_guarantors%';
SHOW TABLES LIKE '%loan_statuses%';

-- Check foreign key constraints
SELECT 
    TABLE_NAME,
    COLUMN_NAME,
    CONSTRAINT_NAME,
    REFERENCED_TABLE_NAME,
    REFERENCED_COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = 'acbptxvs_branch_system'
AND TABLE_NAME IN ('bureau_records', 'loan_disbursements', 'loan_fees', 'loan_guarantors', 'loan_statuses')
AND REFERENCED_TABLE_NAME IS NOT NULL;
```

### 4. Test Relationships in Django Shell
```python
from loans.models import Loan, BureauRecord
from users.models import CustomUser

# Get a test loan and borrower
loan = Loan.objects.first()
borrower = CustomUser.objects.first()

# Create a bureau record
bureau_record = BureauRecord.objects.create(
    borrower=borrower,
    baccount=loan,
    bureau_name="Test Bureau",
    credit_score=700
)

# Test reverse relationships
print(loan.bureau_records.all())
print(borrower.bureau_records.all())
```

## Mapping to Grazuri Schema

### Schema Compatibility

| Grazuri Table | Django Model | Foreign Keys |
|---------------|--------------|--------------|
| `bureau_records` | `BureauRecord` | borrower → borrowers, baccount → loan_info |
| `loan_disbursements` | `LoanDisbursement` | loan → loan_info |
| `loan_fees` | `LoanFee` | loan → loan_info |
| `loan_guarantors` | `LoanGuarantor` | borrower → borrowers |
| `loan_statuses` | `LoanStatus` | loan → loan_info |

### Django to Grazuri Table Mapping

| Django Table | Grazuri Table |
|--------------|---------------|
| `users_customuser` | `borrowers` |
| `loans` | `loan_info` |
| `loan_products` | `loan_products` |

## Requirements Fulfilled

This implementation fulfills the following requirements from the Grazuri Migration spec:

- ✅ **Requirement 4.9**: Add foreign key constraints and ensure relationships are maintained correctly in Django models
- ✅ **Task 4.3**: Update foreign key relationships based on analysis files
- ✅ **Design Goal**: Ensure Django database structure aligns with the target Grazuri schema

## Next Steps

1. **Apply Migration**: Run `python manage.py migrate loans` to create the new tables
2. **Verify Database**: Check that all tables and foreign keys were created correctly
3. **Test Relationships**: Use Django shell to test foreign key relationships
4. **Update Documentation**: Document any additional fields needed based on the full Grazuri schema

## Files Modified

1. `loans/models.py` - Added 5 new models with foreign key relationships
2. `loans/migrations/0025_add_grazuri_foreign_key_models.py` - Created migration file
3. `loans/admin.py` - Registered new models in admin interface
4. `FOREIGN_KEY_RELATIONSHIPS_UPDATE.md` - This documentation file

## Notes

- All new models use `AutoField` for primary keys to match Grazuri schema
- Foreign keys use `CASCADE` delete for dependent records
- All models include `created_at` and `updated_at` timestamps
- Admin interface includes search, filtering, and organized fieldsets
- Models follow Django best practices and naming conventions
- Database table names match Grazuri schema exactly

## Status

✅ **Task 4.3 Complete**: Foreign key relationships have been updated and are ready for migration to the database.

The implementation ensures referential integrity is maintained and all foreign key constraints match the target Grazuri schema requirements.
