# Collection App

## 🎯 Application Overview

This document demonstrates how to implement a comprehensive collection management system using the Flexbase framework, featuring domain models, workflow states, debt tracking, payment processing, installment management, and enterprise-grade role-based access control. This system is designed to be **universally applicable** across different collection scenarios including loans, subscriptions, invoices, utilities, memberships, and rent.

***

## 📋 Business Requirements

### **Core Entities:**

* **Collection** - A debt obligation that needs to be collected (Loans, Invoices, Subscriptions, Dues)
* **Debtor** - Entity responsible for paying the debt (Customer, Borrower, Tenant, Subscriber)
* **Creditor** - Entity owed the debt (Lender, Company, Landlord, Service Provider)
* **Installment** - Scheduled payment due dates and amounts
* **Payment** - Actual payment received against installments
* **CollectionAgent** - Staff assigned to collect debts
* **CollectionCampaign** - Outbound campaigns (calls, emails, letters)
* **PaymentPlan** - Negotiated payment arrangements
* **WriteOff** - Debt cancellation or settlement
* **Reminder** - Automated payment reminders

### **Workflow Requirements:**

* **Debt Origination** - Creating collection records from various sources
* **Installment Generation** - Automatic or manual schedule creation
* **Payment Application** - Recording and matching payments to installments
* **Collection Management** - Agent assignment, campaign tracking
* **Payment Negotiation** - Creating flexible payment plans
* **Delinquency Handling** - Escalation workflows for overdue debts
* **Write-Off Management** - Bad debt cancellation process
* **Reporting & Analytics** - Collection performance metrics

### **Role-Based Access Control:**

* **Debtor** - View own debts and payment history
* **CollectionAgent** - Manage assigned collection accounts
* **CollectionManager** - Oversee all collections and campaigns
* **Finance** - Process payments, manage write-offs
* **Accounting** - Review financial reports
* **Admin** - Full system access
* **Legal** - Manage disputes and settlements

***

## 🎯 Business Scenario Adaptations

This collection system is designed to be universally applicable. Here are example adaptations:

| Business Scenario | Collection Type  | Debtor         | Creditor        | Example                    |
| ----------------- | ---------------- | -------------- | --------------- | -------------------------- |
| **Banking**       | Personal Loan    | Borrower       | Bank            | Auto loans, personal loans |
| **Banking**       | Mortgage         | Homeowner      | Bank            | Home mortgages             |
| **Retail**        | Layaway          | Customer       | Store           | Purchase plans             |
| **Healthcare**    | Medical Bill     | Patient        | Clinic/Hospital | Treatment bills            |
| **Education**     | Tuition          | Student/Parent | School          | Semester fees              |
| **Utilities**     | Utility Bill     | Customer       | Utility Company | Electricity, water         |
| **Property**      | Rent             | Tenant         | Landlord        | Monthly rent               |
| **Subscription**  | Membership       | Member         | Business        | Gym, streaming             |
| **Business**      | Invoice          | Client         | Vendor          | B2B invoices               |
| **Credit Cards**  | Credit Card Bill | Cardholder     | Bank            | Credit card debt           |

***

## 🧱 Core Collection Modules (In Depth)

### 1) Collections Module

#### CollectionsController Features

* CreateCollection (POST) → `Collection.CreateCollection()`
* UpdateCollection (PUT)
* AssignAgent (POST) → `Collection.AssignAgent()`
* PauseCollection (POST) → `Collection.Pause()`
* ResumeCollection (POST) → `Collection.Resume()`
* WriteOffCollection (POST) → `Collection.WriteOff()`
* SettleCollection (POST) → `Collection.Settle()`
* GetCollection(s) (GET)

#### Domain Object: Collection

* Workflow: `CollectionWorkflowState`
* States: Originated → Active → Delinquent → In Collections → Settled/WrittenOff
* Types: Loan, Invoice, Subscription, Rent, Utility
* RBAC:
  * Admin/Agent: Create, Update, Assign, Pause, WriteOff
  * Debtor: View own collections
  * Manager: Oversight and reporting

```csharp
[Table("Collections")]
[Index(nameof(DebtorId), Name="IX_Collections_Debtor")]
[Index(nameof(CreditorId), Name="IX_Collections_Creditor")]
[Index(nameof(CollectionType), Name="IX_Collections_Type")]
public partial class Collection : DomainModelBridge
{
    public CollectionWorkflowState CollectionState { get; protected set; } = new CollectionOriginated();
    
    public string CollectionType { get; protected set; } // Loan | Invoice | Subscription | Rent | Utility
    public string CollectionCode { get; protected set; } // Unique identifier
    
    public string DebtorId { get; protected set; } // FK → Debtor.Id
    public string CreditorId { get; protected set; } // FK → Creditor.Id
    
    public string? CollectionAgentId { get; protected set; } // Assigned collection agent
    public string? OriginalCreditorId { get; protected set; } // For sold/purchased debt
    
    public decimal PrincipalAmount { get; protected set; }
    public decimal InterestRate { get; protected set; }
    public decimal PenaltyRate { get; protected set; }
    public decimal TotalDue { get; protected set; }
    public decimal AmountPaid { get; protected set; }
    public decimal AmountOutstanding { get; protected set; }
    
    public DateTime OriginationDate { get; protected set; }
    public DateTime FirstDueDate { get; protected set; }
    public DateTime? FinalDueDate { get; protected set; }
    public DateTime? LastPaymentDate { get; protected set; }
    
    public int? InstallmentCount { get; protected set; } // Number of installments
    public int InstallmentFrequency { get; protected set; } // Days between installments
    
    public string Currency { get; protected set; } = "USD";
    public string TimeZoneId { get; protected set; } = "UTC";
    
    public string? PaymentPlanId { get; protected set; } // If on payment plan
    public PaymentPlan? PaymentPlan { get; protected set; }
    
    public string? Notes { get; protected set; }
    
    // Navigation properties
    public Debtor Debtor { get; protected set; }
    public ICollection<Installment> Installments { get; protected set; } = new List<Installment>();
    public ICollection<Payment> Payments { get; protected set; } = new List<Payment>();
    
    [BusinessMethod]
    public virtual Collection CreateCollection(CreateCollectionCommand cmd)
    {
        var role = cmd.Dto.GetAppContext()?.UserRole ?? "Guest";
        if (!CollectionState.CanTransition(role, "Create")) throw new UnauthorizedAccessException();
        this.Convert(cmd.Dto); 
        this.SetAdded(cmd.Dto.GetGeneratedId()); 
        return this;
    }
    
    [BusinessMethod]
    public virtual Collection AssignAgent(AssignAgentCommand cmd)
    {
        CollectionAgentId = cmd.AgentId;
        CollectionState = CollectionState.AssignToCollections();
        this.SetModified();
        return this;
    }
}
```

***

### 2) Debtors Module

#### DebtorsController Features

* RegisterDebtor (POST) → `Debtor.RegisterDebtor()`
* UpdateProfile (PUT)
* Blacklist (POST) → `Debtor.Blacklist()`
* Whitelist (POST) → `Debtor.Whitelist()`

#### Domain Object: Debtor

* Workflow: `DebtorWorkflowState`
* States: Registered → Active → Blacklisted
* Profiles: Individual, Business

```csharp
[Table("Debtors")]
[Index(nameof(Email), Name="IX_Debtors_Email")]
[Index(nameof(Phone), Name="IX_Debtors_Phone")]
public partial class Debtor : DomainModelBridge
{
    public DebtorWorkflowState DebtorState { get; protected set; } = new DebtorRegistered();
    
    [Required] public string FirstName { get; protected set; }
    [Required] public string LastName { get; protected set; }
    [Required] public string Email { get; protected set; }
    public string Phone { get; protected set; }
    
    public string Address { get; protected set; }
    public string City { get; protected set; }
    public string State { get; protected set; }
    public string PostalCode { get; protected set; }
    public string Country { get; protected set; }
    
    public string? EmployerName { get; protected set; }
    public string? EmployerPhone { get; protected set; }
    
    public bool IsBlacklisted { get; protected set; }
    public string? BlacklistReason { get; protected set; }
    
    public string TimeZoneId { get; protected set; } = "UTC";
    
    // Navigation properties
    public ICollection<Collection> Collections { get; protected set; } = new List<Collection>();
}

public class DebtorWorkflowState : FlexState
{
    public virtual DebtorWorkflowState Activate() => this;
    public virtual DebtorWorkflowState Blacklist() => this;
    public virtual DebtorWorkflowState Whitelist() => this;
    public virtual bool CanTransition(string role, string action) => GetAllowedRoles(action).Contains(role);
    protected virtual string[] GetAllowedRoles(string action) => Array.Empty<string>();
}

public class DebtorRegistered : DebtorWorkflowState
{
    protected override string[] GetAllowedRoles(string action) => action switch
    {
        "Activate" => new[] { "CollectionAgent", "Admin" },
        _ => Array.Empty<string>()
    };
    public override DebtorWorkflowState Activate() => new DebtorActive();
}

public class DebtorActive : DebtorWorkflowState
{
    protected override string[] GetAllowedRoles(string action) => action switch
    {
        "Blacklist" => new[] { "CollectionAgent", "CollectionManager", "Admin" },
        _ => Array.Empty<string>()
    };
    public override DebtorWorkflowState Blacklist() => new DebtorBlacklisted();
}

public class DebtorBlacklisted : DebtorWorkflowState
{
    protected override string[] GetAllowedRoles(string action) => action switch
    {
        "Whitelist" => new[] { "CollectionManager", "Admin" },
        _ => Array.Empty<string>()
    };
    public override DebtorWorkflowState Whitelist() => new DebtorActive();
}
```

***

### 3) Installments Module

#### InstallmentsController Features

* GenerateInstallments (POST) → `Collection.GenerateInstallments()`
* UpdateInstallment (PUT)
* MarkDue (POST) → `Installment.MarkDue()`
* MarkOverdue (POST) → `Installment.MarkOverdue()`

#### Domain Object: Installment

* Workflow: `InstallmentWorkflowState` (Scheduled → Due → Paid → Overdue/Partial)
* Represents one payment due in a collection schedule

```csharp
[Table("Installments")]
[Index(nameof(CollectionId), Name="IX_Installments_Collection")]
[Index(nameof(DueDate), Name="IX_Installments_DueDate")]
public partial class Installment : DomainModelBridge
{
    public InstallmentWorkflowState InstallmentState { get; protected set; } = new InstallmentScheduled();
    
    public string CollectionId { get; protected set; }
    public Collection Collection { get; protected set; }
    
    public int InstallmentNumber { get; protected set; } // 1, 2, 3...
    
    public decimal PrincipalAmount { get; protected set; }
    public decimal InterestAmount { get; protected set; }
    public decimal PenaltyAmount { get; protected set; }
    public decimal TotalDue { get; protected set; }
    
    public DateTime DueDate { get; protected set; }
    public DateTime? PaidDate { get; protected set; }
    
    public decimal AmountPaid { get; protected set; }
    public decimal AmountOutstanding { get; protected set; }
    
    public string? PaymentMethod { get; protected set; }
    public string? TransactionReference { get; protected set; }
    
    public string TimeZoneId { get; protected set; }
    
    // Navigation properties
    public ICollection<Payment> Payments { get; protected set; } = new List<Payment>();
}

public class InstallmentWorkflowState : FlexState
{
    public virtual InstallmentWorkflowState MarkDue() => this;
    public virtual InstallmentWorkflowState MarkPaid() => this;
    public virtual InstallmentWorkflowState MarkOverdue() => this;
    public virtual InstallmentWorkflowState MarkPartial() => this;
}

public class InstallmentScheduled : InstallmentWorkflowState
{
    public override InstallmentWorkflowState MarkDue() => new InstallmentDue();
}

public class InstallmentDue : InstallmentWorkflowState
{
    public override InstallmentWorkflowState MarkPaid() => new InstallmentPaid();
    public override InstallmentWorkflowState MarkOverdue() => new InstallmentOverdue();
    public override InstallmentWorkflowState MarkPartial() => new InstallmentPartial();
}

public class InstallmentPaid : InstallmentWorkflowState { }
public class InstallmentPartial : InstallmentWorkflowState { }
public class InstallmentOverdue : InstallmentWorkflowState 
{
    public override InstallmentWorkflowState MarkPaid() => new InstallmentPaid();
    public override InstallmentWorkflowState MarkPartial() => new InstallmentPartial();
}
```

***

### 4) Payments Module

#### PaymentsController Features

* RecordPayment (POST) → `Payment.RecordPayment()`
* AllocatePayment (POST) → `Payment.AllocateToInstallment()`
* RefundPayment (POST) → `Payment.Refund()`
* GetPaymentHistory (GET)

#### Domain Object: Payment

* Workflow: `PaymentWorkflowState` (Recorded → Allocated → Confirmed/Refunded)
* Links payments to specific installments

```csharp
[Table("Payments")]
[Index(nameof(CollectionId), Name="IX_Payments_Collection")]
[Index(nameof(DebtorId), Name="IX_Payments_Debtor")]
public partial class Payment : DomainModelBridge
{
    public PaymentWorkflowState PaymentState { get; protected set; } = new PaymentRecorded();
    
    public string CollectionId { get; protected set; }
    public string DebtorId { get; protected set; }
    public string InstallmentId { get; protected set; } // Which installment this pays
    
    public decimal Amount { get; protected set; }
    public string PaymentMethod { get; protected set; } // Cash | Check | CreditCard | ACH | Wire
    public DateTime PaymentDate { get; protected set; }
    
    public string TransactionReference { get; protected set; }
    public string? CheckNumber { get; protected set; }
    public string? BankAccountLast4 { get; protected set; }
    
    public bool IsConfirmed { get; protected set; }
    public DateTime? ConfirmedDate { get; protected set; }
    
    public string? RefundReason { get; protected set; }
    public decimal? RefundAmount { get; protected set; }
    
    public string Notes { get; protected set; }
    
    // Navigation properties
    public Collection Collection { get; protected set; }
    public Debtor Debtor { get; protected set; }
    public Installment Installment { get; protected set; }
    
    [BusinessMethod]
    public virtual Payment RecordPayment(RecordPaymentCommand cmd)
    {
        this.Convert(cmd.Dto);
        this.SetAdded(cmd.Dto.GetGeneratedId());
        
        // Update installment
        var installment = _repo.FindAll<Installment>().FirstOrDefault(i => i.Id == cmd.Dto.InstallmentId);
        if (installment != null)
        {
            installment.AmountPaid += cmd.Dto.Amount;
            installment.AmountOutstanding = installment.TotalDue - installment.AmountPaid;
            
            if (installment.AmountOutstanding <= 0)
            {
                installment.PaymentState = new InstallmentPaid();
            }
            else
            {
                installment.PaymentState = new InstallmentPartial();
            }
        }
        
        return this;
    }
}

public class PaymentWorkflowState : FlexState
{
    public virtual PaymentWorkflowState Allocate() => this;
    public virtual PaymentWorkflowState Confirm() => this;
    public virtual PaymentWorkflowState Refund() => this;
}

public class PaymentRecorded : PaymentWorkflowState
{
    public override PaymentWorkflowState Allocate() => new PaymentAllocated();
    public override PaymentWorkflowState Refund() => new PaymentRefunded();
}

public class PaymentAllocated : PaymentWorkflowState
{
    public override PaymentWorkflowState Confirm() => new PaymentConfirmed();
    public override PaymentWorkflowState Refund() => new PaymentRefunded();
}

public class PaymentConfirmed : PaymentWorkflowState { }
public class PaymentRefunded : PaymentWorkflowState { }
```

***

### 5) Collection Campaigns Module

#### CampaignsController Features

* CreateCampaign (POST) → `CollectionCampaign.Create()`
* AssignContact (POST) → `CollectionCampaign.AssignContact()`
* RecordContact (POST) → `ContactRecord.Create()`
* CloseCampaign (POST) → `CollectionCampaign.Close()`

#### Domain Object: CollectionCampaign

* Tracks collection efforts and agent activities
* Maintains history of all contacts with debtor

```csharp
[Table("CollectionCampaigns")]
[Index(nameof(CollectionId), Name="IX_Campaigns_Collection")]
public partial class CollectionCampaign : DomainModelBridge
{
    public CampaignWorkflowState CampaignState { get; protected set; } = new CampaignActive();
    
    public string CollectionId { get; protected set; }
    public string? AgentId { get; protected set; } // Assigned agent
    
    public string CampaignType { get; protected set; } // Call | Email | Letter | Visit
    
    public DateTime StartDate { get; protected set; }
    public DateTime? EndDate { get; protected set; }
    
    public int ContactAttempts { get; protected set; }
    public int SuccessfulContacts { get; protected set; }
    public DateTime? LastContactDate { get; protected set; }
    
    public string? Notes { get; protected set; }
    
    // Navigation properties
    public Collection Collection { get; protected set; }
    public ICollection<ContactRecord> Contacts { get; protected set; } = new List<ContactRecord>();
}

[Table("ContactRecords")]
public partial class ContactRecord : DomainModelBridge
{
    public string CampaignId { get; protected set; }
    public string? AgentId { get; protected set; }
    
    public string ContactMethod { get; protected set; } // Call | Email | Letter | Visit
    public DateTime ContactDate { get; protected set; }
    
    public string ContactOutcome { get; protected set; } // NoAnswer | LeftMessage | Spoke | PromisedToPay | Refused
    public string ContactNotes { get; protected set; }
    
    public DateTime? NextFollowUpDate { get; protected set; }
    
    // Navigation properties
    public CollectionCampaign Campaign { get; protected set; }
}
```

***

### 6) Payment Plans Module

#### PaymentPlansController Features

* CreatePlan (POST) → `PaymentPlan.Create()`
* ProposePlan (POST) → `PaymentPlan.Propose()`
* AcceptPlan (POST) → `PaymentPlan.Accept()`
* RejectPlan (POST) → `PaymentPlan.Reject()`

#### Domain Object: PaymentPlan

* Negotiated arrangements to pay debt over time
* Different from original payment schedule

```csharp
[Table("PaymentPlans")]
[Index(nameof(CollectionId), Name="IX_PaymentPlans_Collection")]
public partial class PaymentPlan : DomainModelBridge
{
    public PaymentPlanWorkflowState PlanState { get; protected set; } = new PlanProposed();
    
    public string CollectionId { get; protected set; }
    public string? ProposedByAgentId { get; protected set; }
    
    public decimal RevisedTotalAmount { get; protected set; }
    public decimal? RevisedInterestRate { get; protected set; }
    public decimal? RevisedPenaltyRate { get; protected set; }
    
    public int RevisedInstallmentCount { get; protected set; }
    public int RevisedFrequency { get; protected set; }
    
    public DateTime? ProposedDate { get; protected set; }
    public DateTime? AcceptedDate { get; protected set; }
    public DateTime? RejectedDate { get; protected set; }
    
    public string? RejectionReason { get; protected set; }
    public string? PlanNotes { get; protected set; }
    
    // Navigation properties
    public Collection Collection { get; protected set; }
    public ICollection<PaymentPlanInstallment> Installments { get; protected set; } = new List<PaymentPlanInstallment>();
}

public class PaymentPlanWorkflowState : FlexState
{
    public virtual PaymentPlanWorkflowState Propose() => this;
    public virtual PaymentPlanWorkflowState Accept() => this;
    public virtual PaymentPlanWorkflowState Reject() => this;
}

public class PlanProposed : PaymentPlanWorkflowState
{
    public override PaymentPlanWorkflowState Accept() => new PlanAccepted();
    public override PaymentPlanWorkflowState Reject() => new PlanRejected();
}

public class PlanAccepted : PaymentPlanWorkflowState { }
public class PlanRejected : PaymentPlanWorkflowState 
{
    public override PaymentPlanWorkflowState Propose() => new PlanProposed();
}
```

***

## 📊 Collection Tracking (Audit Trail)

Purpose: Track all changes to collection status, payments, and agent activities with full audit history.

### Tracking Events

```csharp
[Table("CollectionTracking")]
[Index(nameof(CollectionId), nameof(EventDate), Name="IX_Tracking_Collection_Date")]
public class CollectionTracking : DomainModelBridge
{
    public string CollectionId { get; set; }
    public string EventType { get; set; } // Created | Paid | Contacted | Overdue | WrittenOff | Settled
    public DateTime EventDate { get; set; }
    public string? AgentId { get; set; }
    public string? EventDescription { get; set; }
    public string? PreviousState { get; set; }
    public string? NewState { get; set; }
    public decimal? AmountBefore { get; set; }
    public decimal? AmountAfter { get; set; }
}
```

***

## 📚 Collection Calendar (Due Date Tracking)

Purpose: Track all payment due dates and overdue accounts with fast queries.

Data sources:

* `Installment` (Scheduled → Due → Paid/Overdue)
* Payment deadlines by collection/agent
* Holiday/cutoff date tracking

Recommended schema:

```csharp
[Table("CollectionCalendar")]
[Index(nameof(CollectionId), nameof(DueDate), Name="IX_Calendar_Collection_Due")]
[Index(nameof(DebtorId), Name="IX_Calendar_Debtor")]
public class CollectionCalendar : DomainModelBridge
{
    public string CollectionId { get; set; }
    public string DebtorId { get; set; }
    public string InstallmentId { get; set; }
    
    public DateTime DueDate { get; set; }
    public decimal AmountDue { get; set; }
    
    public string CalendarType { get; set; } // Installment | PaymentPlan | Reminder
    public string CalendarStatus { get; set; } // Scheduled | Due | Overdue | Paid
    
    public string? AgentId { get; set; }
    public string? CampaignId { get; set; }
    
    // Navigation
    public Collection Collection { get; set; }
    public Installment Installment { get; set; }
}
```

***

## 📈 Collection Analytics (Performance Metrics)

### Summary Dashboard

Purpose: Executive/operations view with daily/weekly metrics across agents, collection types, and payment performance.

New projection table:

```csharp
[Table("CollectionSummary")]
[Index(nameof(AgentId), nameof(Date), Name="IX_Summary_Agent_Date")]
public class CollectionSummary : DomainModelBridge
{
    public string? AgentId { get; set; }
    public string CollectionType { get; set; }
    public DateTime Date { get; set; }
    
    public int TotalCollections { get; set; }
    public decimal TotalAmountDue { get; set; }
    public decimal TotalAmountCollected { get; set; }
    public decimal CollectionRate { get; set; } // Percentage
    
    public int OverdueCount { get; set; }
    public decimal OverdueAmount { get; set; }
    public int WriteOffCount { get; set; }
    public decimal WriteOffAmount { get; set; }
    
    public int ContactsMade { get; set; }
    public int SuccessfulContacts { get; set; }
}
```

***

## 💰 Loan Collection Example (Detailed)

### Scenario: Personal Auto Loan

**Initial Setup:**

```csharp
var loanCollection = new Collection()
{
    CollectionType = "Loan",
    CollectionCode = "AUTO-2024-001234",
    DebtorId = "John.Doe",
    CreditorId = "ABC_BANK",
    PrincipalAmount = 25000.00m,
    InterestRate = 5.50m, // Annual rate
    PenaltyRate = 2.00m, // Monthly penalty for late payment
    OriginationDate = new DateTime(2024, 1, 15),
    FirstDueDate = new DateTime(2024, 2, 15),
    FinalDueDate = new DateTime(2027, 1, 15),
    InstallmentCount = 60, // 5 years monthly
    InstallmentFrequency = 30, // Monthly
    Currency = "USD",
    TimeZoneId = "America/New_York",
    CollectionState = new CollectionActive()
};
```

**Installment Generation:**

```csharp
public List<Installment> GenerateLoanInstallments(Collection loan, ILogger logger)
{
    var installments = new List<Installment>();
    var principalPerMonth = loan.PrincipalAmount / loan.InstallmentCount;
    var monthlyRate = loan.InterestRate / 100 / 12; // Convert annual to monthly
    
    for (int i = 1; i <= loan.InstallmentCount; i++)
    {
        var dueDate = loan.FirstDueDate.AddMonths(i - 1);
        var remainingPrincipal = loan.PrincipalAmount - (principalPerMonth * (i - 1));
        var interestAmount = remainingPrincipal * monthlyRate;
        
        installments.Add(new Installment()
        {
            CollectionId = loan.Id,
            InstallmentNumber = i,
            PrincipalAmount = principalPerMonth,
            InterestAmount = interestAmount,
            TotalDue = principalPerMonth + interestAmount,
            DueDate = dueDate,
            InstallmentState = new InstallmentScheduled()
        });
    }
    
    return installments;
}
```

**Payment Application:**

```csharp
public void RecordPaymentForLoan(string collectionId, string installmentId, decimal amount, string paymentMethod)
{
    var payment = new Payment()
    {
        CollectionId = collectionId,
        InstallmentId = installmentId,
        Amount = amount,
        PaymentMethod = paymentMethod,
        PaymentDate = DateTime.UtcNow,
        PaymentState = new PaymentRecorded()
    };
    
    // Update installment
    var installment = FindInstallment(installmentId);
    installment.AmountPaid += amount;
    
    if (installment.AmountOutstanding <= 0)
    {
        installment.InstallmentState = new InstallmentPaid();
        installment.PaidDate = DateTime.UtcNow;
    }
    else
    {
        installment.InstallmentState = new InstallmentPartial();
    }
    
    // Update collection
    var collection = FindCollection(collectionId);
    collection.AmountPaid += amount;
    collection.AmountOutstanding = collection.TotalDue - collection.AmountPaid;
}
```

**Overdue Handling:**

```csharp
public void MarkOverdueInstallments()
{
    var overdueInstallments = _repo.FindAll<Installment>()
        .Where(i => i.DueDate < DateTime.UtcNow && i.InstallmentState is InstallmentDue)
        .ToList();
    
    foreach (var installment in overdueInstallments)
    {
        // Calculate penalty
        var daysOverdue = (DateTime.UtcNow - installment.DueDate).Days;
        var penalty = installment.TotalDue * (installment.Collection.PenaltyRate / 100 / 30) * daysOverdue;
        
        installment.PenaltyAmount = penalty;
        installment.TotalDue += penalty;
        installment.InstallmentState = new InstallmentOverdue();
        
        // Update collection state
        if (installment.Collection.CollectionState is CollectionActive)
        {
            installment.Collection.CollectionState = new CollectionDelinquent();
        }
    }
}
```

**Collection Campaign:**

```csharp
public void CreateCollectionCampaign(string collectionId, string agentId)
{
    var campaign = new CollectionCampaign()
    {
        CollectionId = collectionId,
        AgentId = agentId,
        CampaignType = "Call",
        StartDate = DateTime.UtcNow,
        CampaignState = new CampaignActive()
    };
    
    _repo.Insert(campaign);
}

public void RecordContactAttempt(string campaignId, string agentId, string outcome, string notes)
{
    var contact = new ContactRecord()
    {
        CampaignId = campaignId,
        AgentId = agentId,
        ContactMethod = "PhoneCall",
        ContactDate = DateTime.UtcNow,
        ContactOutcome = outcome,
        ContactNotes = notes
    };
    
    _repo.Insert(contact);
    
    // Update campaign
    var campaign = FindCampaign(campaignId);
    campaign.ContactAttempts++;
    if (outcome == "Spoke")
    {
        campaign.SuccessfulContacts++;
    }
    campaign.LastContactDate = DateTime.UtcNow;
}
```

**Payment Plan Negotiation:**

```csharp
public PaymentPlan ProposePaymentPlan(string collectionId, decimal newMonthlyPayment, int newInstallmentCount)
{
    var collection = FindCollection(collectionId);
    var revisedTotal = newMonthlyPayment * newInstallmentCount;
    
    var plan = new PaymentPlan()
    {
        CollectionId = collectionId,
        ProposedByAgentId = _currentAgentId,
        RevisedTotalAmount = revisedTotal,
        RevisedInstallmentCount = newInstallmentCount,
        RevisedFrequency = 30,
        ProposedDate = DateTime.UtcNow,
        PlanState = new PlanProposed()
    };
    
    _repo.Insert(plan);
    return plan;
}

public void AcceptPaymentPlan(string planId)
{
    var plan = FindPaymentPlan(planId);
    plan.PlanState = new PlanAccepted();
    plan.AcceptedDate = DateTime.UtcNow;
    
    // Update collection with new terms
    var collection = FindCollection(plan.CollectionId);
    collection.TotalDue = plan.RevisedTotalAmount;
    collection.InstallmentCount = plan.RevisedInstallmentCount;
    collection.PaymentPlanId = planId;
    
    // Regenerate installments with new terms
    RegenerateInstallments(collection);
}
```

***

## 🎯 Key Implementation Notes

### Timezone Handling

* All due dates stored in UTC ticks for fast queries
* Local dates maintained via `TimeZoneId` for display
* Holiday calendars configured per creditor timezone

### RBAC Examples

```csharp
// Check if user can manage collections
bool CanManageCollections(string userId, string collectionId)
{
    var collection = FindCollection(collectionId);
    var isOwnCollection = collection.AgentId == userId;
    var userRole = GetUserRole(userId);
    
    return userRole == "CollectionManager" || userRole == "Admin" || 
           (userRole == "CollectionAgent" && isOwnCollection);
}

// Check if debtor can view collection
bool CanDebtorViewCollection(string userId, string collectionId)
{
    var collection = FindCollection(collectionId);
    return collection.DebtorId == userId;
}
```

### Reporting Endpoints

```csharp
[HttpGet("/collections/agent/{agentId}/performance")]
public CollectionPerformanceDto GetAgentPerformance(string agentId, DateTime startDate, DateTime endDate)
{
    // Aggregate payments, contacts, settlement rates
}

[HttpGet("/collections/delinquent")]
public List<CollectionDto> GetDelinquentCollections(int daysOverdue)
{
    // Find collections with overdue installments
}

[HttpGet("/collections/forecast")]
public CollectionForecastDto GetForecastByDateRange(DateTime startDate, DateTime endDate)
{
    // Projected collections based on installments and historical rates
}
```

***

This design provides a flexible, enterprise-grade collection management system applicable to any debt collection scenario while maintaining clear workflows, role-based access, and comprehensive tracking.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.flexbase.in/sample-references-domain/collection-app.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
