# CalculatePurchaseBillDueSchedule

## Overview

CalculatePurchaseBillDueSchedule converts a purchase payment term plus bill inputs into one or more payable due-schedule lines, including early-payment discount windows when configured.

## Business Rules

- Accepts a payment-term snapshot or a payment-term id plus bill date and bill amount inputs
- Installment schedules can mix percentage-based and fixed-amount lines as documented by the term
- End-of-month logic is calculated from calendar month boundaries, not from a simple day offset
- Any rounding residual is assigned to the final due-schedule line so the total equals the bill amount in transaction currency
- Discount calculations explicitly honor the term's `GROSS_AMOUNT` or `NET_AMOUNT` discount base
- Exchange-rate conversion is out of scope; all results stay in the bill transaction currency

## Process Flow

```mermaid
flowchart TD
    A[Receive bill date, amount, and payment term] --> B{Payment term available?}
    B -->|No| C[Return PAYMENT_TERM_NOT_FOUND]
    B -->|Yes| D[Expand term schedule lines]
    D --> E[Calculate due dates using net-day, fixed-day, or end-of-month rules]
    E --> F[Calculate installment amounts and assign residual to final line]
    F --> G[Attach discount metadata where configured]
    G --> H[Return due-schedule lines]
```

## External Dependencies

- None

## Error Scenarios

- **PAYMENT_TERM_NOT_FOUND**: Referenced purchase payment term does not exist
- **INVALID_PAYMENT_TERM_CONFIGURATION**: Payment term schedule is incomplete or internally inconsistent
- **INVALID_BILL_AMOUNT**: Bill amount input is negative or incompatible with the schedule type

## Test Cases

- calculates a single due date for net-30 terms
- calculates discount window and final due date for 2/10 net 30
- calculates multiple due-schedule lines for installment terms
- applies end-of-month logic correctly
- assigns rounding residual to the final schedule line
- returns error when payment term does not exist
- returns error when payment term configuration is invalid
