PaymentCycleEntitlementCalculator.java

package uk.gov.dhsc.htbhf.claimant.entitlement;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import uk.gov.dhsc.htbhf.dwp.model.QualifyingReason;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import static java.util.Collections.emptyList;

/**
 * Calculates the entitlement for a claimant over a given payment cycle. Entitlement is calculated for a number of
 * calculation periods and the sum of those entitlements is returned. E.g. a cycle period of 28 days with 4 calculation
 * periods would result in entitlement being calculated four times, each one week apart.
 */
@Component
@Slf4j
public class PaymentCycleEntitlementCalculator {

    private final Integer entitlementCalculationDuration;
    private final Integer numberOfCalculationPeriods;
    private final Integer weeksBeforeDueDate;
    private final Integer weeksAfterDueDate;
    private final EntitlementCalculator entitlementCalculator;
    private final BackDatedPaymentCycleEntitlementCalculator backDatedPaymentCycleEntitlementCalculator;

    /**
     * Constructor for {@link PaymentCycleEntitlementCalculator}.
     *
     * @param paymentCycleConfig                  configuration for the payment cycle
     * @param entitlementCalculator               calculates entitlement for a single calculation period
     * @param backDatedPaymentCycleEntitlementCalculator calculates back dated vouchers for previous cycles
     */
    public PaymentCycleEntitlementCalculator(PaymentCycleConfig paymentCycleConfig,
                                             EntitlementCalculator entitlementCalculator,
                                             BackDatedPaymentCycleEntitlementCalculator backDatedPaymentCycleEntitlementCalculator) {
        this.weeksBeforeDueDate = paymentCycleConfig.getWeeksBeforeDueDate();
        this.weeksAfterDueDate = paymentCycleConfig.getWeeksAfterDueDate();
        this.entitlementCalculationDuration = paymentCycleConfig.getEntitlementCalculationDurationInDays();
        this.numberOfCalculationPeriods = paymentCycleConfig.getNumberOfCalculationPeriods();
        this.entitlementCalculator = entitlementCalculator;
        this.backDatedPaymentCycleEntitlementCalculator = backDatedPaymentCycleEntitlementCalculator;
    }

    /**
     * Calculates the total voucher entitlement for a payment cycle when there is no previous voucher entitlement.
     *
     * @param expectedDueDate       expected due date
     * @param dateOfBirthOfChildren the date of birth of the claimant's children
     * @param cycleStartDate        the start date of the payment cycle
     * @param qualifyingReason      overrides the reason that this applicant qualifies for Healthy Start
     * @return the payment cycle voucher entitlement calculated for the claimant
     */
    public PaymentCycleVoucherEntitlement calculateEntitlement(Optional<LocalDate> expectedDueDate,
                                                               List<LocalDate> dateOfBirthOfChildren,
                                                               LocalDate cycleStartDate,
                                                               QualifyingReason qualifyingReason) {
        log.debug("Calculating entitlement");
        List<VoucherEntitlement> voucherEntitlements = calculateCycleEntitlements(expectedDueDate, dateOfBirthOfChildren, cycleStartDate, qualifyingReason);
        return new PaymentCycleVoucherEntitlement(voucherEntitlements);
    }

    /**
     * Calculates the total voucher entitlement for a payment cycle given the previous entitlement.
     *
     * @param expectedDueDate            expected due date
     * @param dateOfBirthOfChildren      the date of birth of the claimant's children
     * @param cycleStartDate             the start date of the payment cycle
     * @param previousVoucherEntitlement voucher entitlement from last payment cycle
     * @param qualifyingReason           overrides the reason that this applicant qualifies for Healthy Start
     *
     * @return the payment cycle voucher entitlement calculated for the claimant
     */
    public PaymentCycleVoucherEntitlement calculateEntitlement(Optional<LocalDate> expectedDueDate,
                                                               List<LocalDate> dateOfBirthOfChildren,
                                                               LocalDate cycleStartDate,
                                                               PaymentCycleVoucherEntitlement previousVoucherEntitlement,
                                                               QualifyingReason qualifyingReason) {
        log.debug("Calculating entitlement using the previous voucher entitlement");
        List<LocalDate> newChildren = newChildrenMatchedToExpectedDeliveryDate(expectedDueDate, dateOfBirthOfChildren, previousVoucherEntitlement);
        if (newChildren.isEmpty()) {
            return calculateEntitlement(expectedDueDate, dateOfBirthOfChildren, cycleStartDate, qualifyingReason);
        }

        // ignore expected due date as we've determined that the pregnancy has happened
        List<VoucherEntitlement> voucherEntitlements = calculateCycleEntitlements(Optional.empty(), dateOfBirthOfChildren, cycleStartDate, qualifyingReason);
        int backdateVouchers
                = backDatedPaymentCycleEntitlementCalculator.calculateBackDatedVouchers(expectedDueDate, newChildren, cycleStartDate, qualifyingReason);
        return new PaymentCycleVoucherEntitlement(voucherEntitlements, backdateVouchers);
    }

    private List<VoucherEntitlement> calculateCycleEntitlements(Optional<LocalDate> expectedDueDate,
                                                                List<LocalDate> dateOfBirthOfChildren,
                                                                LocalDate cycleStartDate,
                                                                QualifyingReason qualifyingReason) {
        List<LocalDate> entitlementDates = getVoucherEntitlementDatesFromStartDate(cycleStartDate);
        return entitlementDates.stream()
                .map(date -> entitlementCalculator.calculateVoucherEntitlement(expectedDueDate, dateOfBirthOfChildren, date, qualifyingReason))
                .collect(Collectors.toList());
    }

    // A child is considered a result of expected due date if their date of birth falls within a range of that date
    private List<LocalDate> newChildrenMatchedToExpectedDeliveryDate(Optional<LocalDate> expectedDueDate,
                                                                     List<LocalDate> dateOfBirthOfChildren,
                                                                     PaymentCycleVoucherEntitlement previousVoucherEntitlement) {
        if (noPregnancyVouchers(previousVoucherEntitlement) || notPregnant(expectedDueDate)) {
            return emptyList();
        }

        return dateOfBirthOfChildren.stream()
                .filter(date -> isWithinPregnancyMatchPeriod(expectedDueDate.get(), date))
                .collect(Collectors.toList());
    }

    private boolean notPregnant(Optional<LocalDate> expectedDueDate) {
        return expectedDueDate.isEmpty();
    }

    private boolean noPregnancyVouchers(PaymentCycleVoucherEntitlement entitlement) {
        return entitlement == null || entitlement.getVouchersForPregnancy() == 0;
    }

    private boolean isWithinPregnancyMatchPeriod(LocalDate expectedDueDate, LocalDate dateOfBirth) {
        LocalDate startDate = expectedDueDate.minusWeeks(weeksBeforeDueDate);
        LocalDate endDate = expectedDueDate.plusWeeks(weeksAfterDueDate);

        return !dateOfBirth.isBefore(startDate) && !dateOfBirth.isAfter(endDate);
    }

    /**
     * Get the voucher entitlement dates from the given PaymentCycle start date.
     *
     * @param cycleStartDate The start date of the PaymentCycle
     * @return The List of dates for the payment cycle.
     */
    public List<LocalDate> getVoucherEntitlementDatesFromStartDate(LocalDate cycleStartDate) {
        List<LocalDate> entitlementDates = new ArrayList<>(numberOfCalculationPeriods);
        for (long i = 0; i < numberOfCalculationPeriods; i++) {
            entitlementDates.add(cycleStartDate.plusDays(i * entitlementCalculationDuration));
        }
        return entitlementDates;
    }
}