Add the late-fee rule with boundary tests
Java · Spring Boot · beginner · greenfield
Adds the late-fee rule finance asked for: 14 days of grace, then a flat 2,500. The calculator is a small pure function, and the tests cover both sides of the grace boundary (day 14 free, day 15 charged), the flat amount months later, invoices not yet due and paid invoices. `./gradlew test` is green.
Billing service on Spring Boot 3.3 (Java 21, Gradle), tests with JUnit 5 and AssertJ 3.25 from `spring-boot-starter-test`. Existing `com.acme.billing.invoice.Invoice` has `isPaid()`, `getDueDate()` (a non-null `LocalDate`) and a test-friendly `Invoice.builder().dueDate(LocalDate).paid(boolean).build()`. The `billingDate` is supplied by the caller (the dunning job passes the business date); the calculator does not read the clock.
Requirements
- Add `LateFeeCalculator` (a Spring `@Component`) with `long feeFor(Invoice invoice, LocalDate billingDate)`. An unpaid invoice whose `billingDate` is more than 14 days after its due date (day 15 or later) owes a flat late fee of 2,500 minor units; on day 14 or earlier, before the due date, or for a paid invoice, the fee is 0. The fee does not grow with further days.
- Finance signs this rule off on the tests, so the PR ships unit tests that pin each part of it: no fee before the due date, no fee on day 14, exactly 2,500 on day 15 (the first fee day), the flat amount long after, and no fee once paid. A change to the grace period or the amount must turn a test red.
- Wiring the calculator into the nightly dunning job is the next PR; nothing calls it yet.
Files touched
- src/main/java/com/acme/billing/fees/LateFeeCalculator.java
- src/test/java/com/acme/billing/fees/LateFeeCalculatorTest.java
--- src/main/java/com/acme/billing/fees/LateFeeCalculator.java
+package com.acme.billing.fees;
+
+import java.time.LocalDate;
+import java.time.temporal.ChronoUnit;
+
+import org.springframework.stereotype.Component;
+
+import com.acme.billing.invoice.Invoice;
+
+@Component
+public class LateFeeCalculator {
+
+ static final int GRACE_DAYS = 14;
+ static final long LATE_FEE_MINOR = 2_500;
+
+ public long feeFor(Invoice invoice, LocalDate billingDate) {
+ if (invoice.isPaid()) {
+ return 0;
+ }
+ long daysOverdue = ChronoUnit.DAYS.between(invoice.getDueDate(), billingDate);
+ return daysOverdue > GRACE_DAYS ? LATE_FEE_MINOR : 0;
+ }
+}
+
--- src/test/java/com/acme/billing/fees/LateFeeCalculatorTest.java
+package com.acme.billing.fees;
+
+import static org.assertj.core.api.Assertions.assertThat;
+