# Late Payment Fees & Disconnection Notice Implementation Guide

**Project**: Water Billing Management System  
**Date**: May 25, 2026  
**Status**: Implementation Ready

---

## 📋 System Overview

This document describes the complete implementation for:
1. **Automatic Late Payment Fee Calculation** - Charges added monthly based on company settings
2. **Disconnection Notice System** - Track and manage disconnection eligibility
3. **Manual Reconnection Management** - Process with optional reconnection fees
4. **Automated Daily Processing** - Scheduled task to apply fees and create notices

---

## 🏗️ Architecture

### **Database Schema**

**Company Settings (Already Implemented)**
```
companies table:
  - billing_due_days (int): Days until payment is due after billing
  - disconnection_days (int): Days until disconnection after billing
  - late_payment_fee_enabled (boolean): Enable/disable late fees
  - late_payment_fee_rate (decimal): Fee amount or percentage
  - late_payment_fee_type (enum): 'percentage' or 'fixed'
  - reconnection_fee_enabled (boolean): Enable/disable reconnection fees
  - reconnection_fee_amount (decimal): Fixed reconnection fee
```

**Billing Enhancements (New Migration)**
```
billings table:
  - last_late_fee_date (datetime): When late fee was last added
  - disconnection_status (enum): 'none', 'notice_sent', 'disconnected', 'reconnected'
  - days_overdue_limit (int): Company override for disconnection calculation
```

**Payment Tracking (Existing)**
```
payments table:
  - payment_type (enum): 'billing', 'installation', 'reconnection', 'late_fee', 'deposit', 'other'
  - status (enum): 'pending', 'completed', 'failed'
  - Reference numbers track all disconnection-related transactions
```

---

## 🔧 Core Components

### **1. LatePaymentService** (`app/Services/LatePaymentService.php`)

**Main Responsibilities:**
- Calculate late fees based on company settings
- Add late fees to billings
- Create disconnection notices
- Track disconnection history
- Provide reporting data

**Key Methods:**

```php
// Process late payments for all companies
$latePaymentService->processAllLatePayments()
// Returns: ['late_fees_added', 'billings_marked_overdue', 'disconnection_notices_created']

// Process single company
$latePaymentService->processCompanyLatePayments($company)

// Get late fees summary for client
$summary = $latePaymentService->getClientLateFeesSummary($client)
// Returns: ['total_late_fees', 'late_fee_count', 'has_pending_disconnection']

// Get overdue balance for client
$overdueBalance = $latePaymentService->getClientOverdueBalance($client)

// Get days overdue for billing
$daysOverdue = $latePaymentService->getDaysOverdue($billing)
```

**How It Works:**

1. **Late Fee Calculation**
   - For overdue billings (past `due_date` but unpaid):
     - If percentage-based: `amount = outstanding_balance * (rate / 100)`
     - If fixed amount: `amount = flat_rate`
   - Fee is added to `penalties` field
   - A `late_fee` payment record is created for tracking

2. **Disconnection Eligibility**
   - When billing passes `disconnection_date` with unpaid balance
   - Service creates a `disconnection_notice` payment record
   - Sets `disconnection_status` to `notice_sent`
   - Billing status marked as `marked_for_disconnection`

3. **Prevents Duplicate Fees**
   - Checks if late fee already exists for the month
   - Only adds fee once per month per billing

---

### **2. ProcessLatePayments Command** (`app/Console/Commands/ProcessLatePayments.php`)

**Usage:**

```bash
# Process all companies
php artisan payments:process-late

# Process specific company
php artisan payments:process-late --company-id=1
```

**Output:**
```
Starting late payment processing...
Processing late payments for all companies...

=== Processing Results ===
Companies Processed: 5
Late Fees Added: 12
Billings Marked Overdue: 12
Disconnection Notices Created: 3

Late payment processing completed successfully!
```

**Scheduling (Add to `app/Console/Kernel.php`):**

```php
protected function schedule(Schedule $schedule)
{
    // Run daily at 8 AM to process late payments
    $schedule->command('payments:process-late')
        ->dailyAt('08:00')
        ->withoutOverlapping()
        ->onSuccess(function () {
            Log::info('Late payments processed successfully');
        })
        ->onFailure(function () {
            Log::error('Late payment processing failed');
        });
}
```

---

### **3. Billing Model Enhancements** (`app/Models/Billing.php`)

**New Methods:**

```php
// Check if billing is overdue (past due date with unpaid balance)
$billing->isOverdue() // bool

// Check if eligible for disconnection
$billing->isEligibleForDisconnection() // bool

// Get days overdue
$billing->getDaysOverdue() // int

// Get days until disconnection
$billing->getDaysUntilDisconnection() // int

// Mark disconnection notice sent
$billing->markDisconnectionNoticeSent()

// Mark service disconnected
$billing->markDisconnected()

// Mark service reconnected
$billing->markReconnected()
```

**New Fields:**
- `last_late_fee_date`: When late fee was last calculated
- `disconnection_status`: Current disconnection state
- `days_overdue_limit`: Company-specific override

---

### **4. DisconnectionController** (`app/Http/Controllers/DisconnectionController.php`)

**Routes to Add (in `routes/web.php`):**

```php
Route::middleware(['auth', 'company-admin'])->group(function () {
    // Disconnection management
    Route::get('/disconnections', [DisconnectionController::class, 'index'])->name('disconnections.index');
    Route::get('/disconnections/client/{client}', [DisconnectionController::class, 'clientDetails'])->name('disconnections.client-details');
    Route::post('/disconnections/{billing}/notice', [DisconnectionController::class, 'sendDisconnectionNotice'])->name('disconnections.send-notice');
    Route::post('/disconnections/{billing}/disconnect', [DisconnectionController::class, 'disconnect'])->name('disconnections.disconnect');
    Route::post('/disconnections/reconnect/{client}', [DisconnectionController::class, 'reconnect'])->name('disconnections.reconnect');
    
    // Reports
    Route::get('/disconnections/report/late-fees', [DisconnectionController::class, 'lateFeeReport'])->name('disconnections.late-fee-report');
    Route::get('/disconnections/history/{client}', [DisconnectionController::class, 'clientHistory'])->name('disconnections.client-history');
});
```

**Key Features:**

1. **Index View** - Shows overdue billings requiring action
2. **Client Details** - Late fee summary and disconnection history
3. **Send Notice** - Manually send disconnection notice
4. **Disconnect** - Record service disconnection
5. **Reconnect** - Process reconnection with optional fees
6. **Reports** - Late fee reports and statistics

---

## 📊 Implementation Workflow

### **Scenario 1: Automatic Late Fee Process**

**Day 16 (after 15-day billing due date):**
1. Scheduled command runs at 8 AM
2. System finds billings past `due_date` with unpaid balance
3. Calculates late fee: e.g., `₱5,000 balance * 1.5% = ₱75 fee`
4. Adds ₱75 to `penalties` field
5. New total due: original + ₱75
6. Records `late_fee` payment for tracking
7. Updates billing status to `overdue`

**Day 31 (after 30-day disconnection date):**
1. Scheduled command runs
2. Finds billings past `disconnection_date` with unpaid balance
3. Creates `disconnection_notice` payment record
4. Sets `disconnection_status` to `notice_sent`
5. Billing marked as `marked_for_disconnection`

### **Scenario 2: Manual Disconnection**

**Admin Action:**
1. Navigate to Disconnections dashboard
2. Review list of overdue clients
3. Click "Send Disconnection Notice" button
4. System records notice in payments table
5. Notice email sent to customer
6. After notice period, click "Disconnect Service"
7. Service disconnected, `disconnection_status` set to `disconnected`
8. Customer account marked as `disconnected`

### **Scenario 3: Reconnection Process**

**Admin Action:**
1. Customer makes payment
2. Click "Process Reconnection" for client
3. Enter payment amount and notes
4. System records payment
5. Reconnection fee automatically added (if enabled)
6. `disconnection_status` set to `reconnected`
7. Customer account marked as `active`

---

## 💡 Company Settings Configuration

**Example Configuration:**

```
Company: ABC Water Corporation
- Billing Due Days: 15
- Disconnection Days: 30
- Late Payment Fee Enabled: Yes
- Late Payment Fee Rate: 1.5
- Late Payment Fee Type: Percentage
- Reconnection Fee Enabled: Yes
- Reconnection Fee Amount: ₱500
```

**Result:**
- Bills due 15 days after issue
- Service can be disconnected after 30 days past due
- Late fee: 1.5% of outstanding balance per month
- ₱500 charge to reconnect service

---

## 📈 Reporting & Analytics

### **Available Reports:**

1. **Late Fee Report**
   - Total late fees collected
   - Breakdown by client
   - Breakdown by period
   - Late fee payment rate

2. **Disconnection Report**
   - Total disconnections (by period)
   - Reconnections within X days
   - Current disconnected clients
   - Cost impact of disconnections

3. **Client Risk Report**
   - Clients approaching due date
   - Clients approaching disconnection date
   - High-risk delinquent clients
   - Revenue at risk

### **Dashboard Metrics:**

```
Disconnections Dashboard:
- Total Overdue Clients: 5
- Pending Disconnection Notices: 3
- Currently Disconnected: 2
- Total Late Fees This Month: ₱850
- Revenue at Risk: ₱15,000
```

---

## 🚀 Installation Steps

### **Step 1: Run Migration**

```bash
php artisan migrate
```

**This creates:**
- `last_late_fee_date` column on billings
- `disconnection_status` column on billings
- `days_overdue_limit` column on billings

### **Step 2: Register Service**

In `app/Providers/AppServiceProvider.php`:

```php
public function register(): void
{
    $this->app->singleton(LatePaymentService::class, function () {
        return new LatePaymentService();
    });
}
```

### **Step 3: Register Command**

The command is auto-discovered. Verify with:

```bash
php artisan list | grep process-late
```

### **Step 4: Add Routes**

In `routes/web.php`:

```php
Route::middleware(['auth', 'company-admin'])->group(function () {
    Route::get('/disconnections', [DisconnectionController::class, 'index'])->name('disconnections.index');
    Route::get('/disconnections/client/{client}', [DisconnectionController::class, 'clientDetails'])->name('disconnections.client-details');
    Route::post('/disconnections/{billing}/notice', [DisconnectionController::class, 'sendDisconnectionNotice'])->name('disconnections.send-notice');
    Route::post('/disconnections/{billing}/disconnect', [DisconnectionController::class, 'disconnect'])->name('disconnections.disconnect');
    Route::post('/disconnections/reconnect/{client}', [DisconnectionController::class, 'reconnect'])->name('disconnections.reconnect');
    Route::get('/disconnections/report/late-fees', [DisconnectionController::class, 'lateFeeReport'])->name('disconnections.late-fee-report');
    Route::get('/disconnections/history/{client}', [DisconnectionController::class, 'clientHistory'])->name('disconnections.client-history');
});
```

### **Step 5: Schedule Command** (Optional)

In `app/Console/Kernel.php`:

```php
protected function schedule(Schedule $schedule)
{
    $schedule->command('payments:process-late')
        ->dailyAt('08:00')
        ->withoutOverlapping();
}
```

### **Step 6: Create Views**

Create Blade templates:
- `resources/views/disconnections/index.blade.php`
- `resources/views/disconnections/client-details.blade.php`
- `resources/views/disconnections/late-fee-report.blade.php`
- `resources/views/disconnections/client-history.blade.php`

---

## 🧪 Testing

### **Manual Testing:**

```bash
# Test late payment processing for single company
php artisan payments:process-late --company-id=1

# Test command scheduling
php artisan schedule:work
```

### **Artisan Commands for Testing:**

```bash
# Check late fees for client
php artisan tinker
> $client = App\Models\Client::find(1);
> app(App\Services\LatePaymentService::class)->getClientLateFeesSummary($client);

# Check overdue balance
> app(App\Services\LatePaymentService::class)->getClientOverdueBalance($client);

# Check days overdue
> $billing = $client->billings()->first();
> $billing->getDaysOverdue();
```

---

## 📝 Database Queries

### **Find Overdue Billings**

```sql
SELECT * FROM billings
WHERE company_id = 1
  AND status != 'paid'
  AND due_date < NOW()
  AND balance > 0
ORDER BY due_date;
```

### **Find Disconnection-Eligible Billings**

```sql
SELECT * FROM billings
WHERE company_id = 1
  AND status != 'paid'
  AND disconnection_date < NOW()
  AND balance > 0
ORDER BY disconnection_date;
```

### **Late Fee Summary**

```sql
SELECT 
  client_id,
  COUNT(*) as late_fee_count,
  SUM(ABS(amount)) as total_late_fees,
  MAX(created_at) as last_late_fee_date
FROM payments
WHERE company_id = 1
  AND payment_type = 'late_fee'
GROUP BY client_id;
```

---

## ⚠️ Important Considerations

1. **Timezone** - All dates use application timezone. Ensure configured correctly.

2. **Performance** - For large customer bases, process late payments during off-peak hours.

3. **Communication** - Always notify customers before disconnection:
   - First: Late payment notice (automated email)
   - Second: Disconnection warning (7-10 days before)
   - Third: Disconnection notice (final notice)

4. **Legal Compliance** - Check local regulations for:
   - Notice periods required
   - Maximum late fee amounts
   - Customer communication requirements
   - Disconnection procedures

5. **Manual Override** - Admins can manually adjust:
   - Late fees (reduce/waive)
   - Disconnection dates (extend grace period)
   - Reconnection fees (waive/reduce)

---

## 📊 Status Codes Reference

**Billing Status:**
- `draft` - New billing, not yet sent
- `sent` - Billing issued to customer
- `partially_paid` - Payment received but balance remains
- `paid` - Fully paid
- `overdue` - Past due date, unpaid
- `marked_for_disconnection` - Eligible for service disconnection

**Disconnection Status:**
- `none` - No disconnection action
- `notice_sent` - Disconnection notice issued
- `disconnected` - Service is disconnected
- `reconnected` - Service reconnected

**Payment Type:**
- `billing` - Regular billing payment
- `late_fee` - Late payment fee charge
- `disconnection_notice` - Notice record
- `disconnection` - Disconnection execution
- `reconnection` - Service reconnection payment

---

## 🔗 Integration Points

### **With Payment Processing:**
- Late fee automatically added to next month's balance
- Late fee considered in balance calculations
- Reconnection fee added on reconnection payment

### **With Customer Portal:**
- Customers can view pending disconnection status
- Customers see late fee amounts in bill details
- Customers can make reconnection payments

### **With Reporting:**
- Late fees included in collection reports
- Disconnection metrics in dashboard KPIs
- Risk analysis by disconnection status

---

## 📞 Support & Troubleshooting

**Issue: Late fees not being applied**
- Check if late fees enabled in company settings
- Verify command is running: `php artisan schedule:list`
- Check if billings have past due_date

**Issue: Disconnection notices not created**
- Verify disconnection_days setting is correct
- Check if billings have past disconnection_date
- Verify balance > 0

**Issue: Duplicate late fees**
- Check last_late_fee_date field
- Service has built-in duplicate prevention
- Review payment records for late_fee type

---

## ✅ Checklist for Implementation

- [ ] Run migration for billing changes
- [ ] Register LatePaymentService in AppServiceProvider
- [ ] Create DisconnectionController routes
- [ ] Create view templates for disconnection management
- [ ] Configure company settings (due days, disconnection days, late fees)
- [ ] Test late payment processing command manually
- [ ] Schedule command in Kernel.php
- [ ] Set up email notifications for disconnection notices
- [ ] Train admin users on disconnection management
- [ ] Set up reports and dashboards
- [ ] Review local legal requirements for disconnections
- [ ] Document company-specific disconnection policies

---

**Implementation Status**: ✅ Ready  
**Last Updated**: May 25, 2026  
**Version**: 1.0
