All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.brihaspathee.zeus.helper.impl.EnrollmentSpanHelperImpl Maven / Gradle / Ivy

There is a newer version: 1.0.8
Show newest version
package com.brihaspathee.zeus.helper.impl;

import com.brihaspathee.zeus.broker.producer.AccountProcessingValidationProducer;
import com.brihaspathee.zeus.constants.AdditionalMaintenanceReasonCode;
import com.brihaspathee.zeus.constants.EnrollmentSpanStatus;
import com.brihaspathee.zeus.constants.EnrollmentType;
import com.brihaspathee.zeus.constants.PremiumSpanStatus;
import com.brihaspathee.zeus.domain.entity.Account;
import com.brihaspathee.zeus.domain.entity.EnrollmentSpan;
import com.brihaspathee.zeus.domain.entity.PremiumSpan;
import com.brihaspathee.zeus.domain.entity.ProcessingRequest;
import com.brihaspathee.zeus.domain.repository.EnrollmentSpanRepository;
import com.brihaspathee.zeus.dto.account.AccountDto;
import com.brihaspathee.zeus.dto.account.EnrollmentSpanDto;
import com.brihaspathee.zeus.dto.account.MemberPremiumDto;
import com.brihaspathee.zeus.dto.account.PremiumSpanDto;
import com.brihaspathee.zeus.dto.transaction.*;
import com.brihaspathee.zeus.exception.NoMatchingEnrollmentSpanException;
import com.brihaspathee.zeus.helper.interfaces.EnrollmentSpanHelper;
import com.brihaspathee.zeus.helper.interfaces.PremiumSpanHelper;
import com.brihaspathee.zeus.info.ChangeTransactionInfo;
import com.brihaspathee.zeus.mapper.interfaces.EnrollmentSpanMapper;
import com.brihaspathee.zeus.util.AccountProcessorUtil;
import com.brihaspathee.zeus.validator.request.ProcessingValidationRequest;
import com.brihaspathee.zeus.web.model.EnrollmentSpanStatusDto;
import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.common.protocol.types.Field;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

/**
 * Created in Intellij IDEA
 * User: Balaji Varadharajan
 * Date: 24, November 2022
 * Time: 6:24 AM
 * Project: Zeus
 * Package Name: com.brihaspathee.zeus.helper.impl
 * To change this template use File | Settings | File and Code Template
 * Nuclino:Nuclino
 * Confluence: Confluence
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class EnrollmentSpanHelperImpl implements EnrollmentSpanHelper {

    /**
     * Enrollment span mapper instance
     */
    private final EnrollmentSpanMapper enrollmentSpanMapper;

    /**
     * Enrollment span repository instance to perform CRUD operations
     */
    private final EnrollmentSpanRepository enrollmentSpanRepository;

    /**
     * Premium span helper instance
     */
    private final PremiumSpanHelper premiumSpanHelper;

    /**
     * The utility class for account processor service
     */
    private final AccountProcessorUtil accountProcessorUtil;

    /**
     * Processing validation producer to send the transaction for validation
     */
    private final AccountProcessingValidationProducer accountProcessingValidationProducer;

    /**
     * The spring environment instance
     */
    private final Environment environment;


    /**
     * Create an enrollment span from the transaction data
     * @param transactionDto The transaction detail
     * @param account the account to which the enrollment span should be associated
     * @param priorEnrollmentSpans enrollment spans that is immediately prior to the effective date in the transaction
     * @return return the created enrollment span
     */
    @Override
    public EnrollmentSpan createEnrollmentSpan(TransactionDto transactionDto,
                                               Account account,
                                               List priorEnrollmentSpans) {
        // Get the primary member from the transaction dto, need to get the
        // exchange subscriber id from the primary subscriber
        TransactionMemberDto primarySubscriber = getPrimaryMember(transactionDto);
        // Create the enrollment span entity
        String enrollmentSpanCode = accountProcessorUtil.generateUniqueCode(transactionDto.getEntityCodes(),
                "enrollmentSpanCode");
        EnrollmentSpan enrollmentSpan = EnrollmentSpan.builder()
                .acctEnrollmentSpanSK(null)
                .enrollmentSpanCode(enrollmentSpanCode)
                .account(account)
                .ztcn(transactionDto.getZtcn())
                .source(transactionDto.getSource())
                .stateTypeCode(transactionDto.getTradingPartnerDto().getStateTypeCode())
                .marketplaceTypeCode(transactionDto.getTradingPartnerDto().getMarketplaceTypeCode())
                .businessUnitTypeCode(transactionDto.getTradingPartnerDto().getBusinessTypeCode())
                .coverageTypeCode(transactionDto.getTransactionDetail().getCoverageTypeCode())
                .startDate(transactionDto.getTransactionDetail().getEffectiveDate())
                // get the exchange subscriber id and set it
                .exchangeSubscriberId(getExchangeSubscriberId(primarySubscriber))
                // determine the effectuation date
                .effectuationDate(determineEffectuationDate(transactionDto, priorEnrollmentSpans))
                // determine the enrollment type
                .enrollmentType(determineEnrollmentType(transactionDto.getTransactionAttributes()))
                .planId(transactionDto.getTransactionDetail().getPlanId())
                .productTypeCode("HMO")
                .groupPolicyId(transactionDto.getTransactionDetail().getGroupPolicyId())
                .effectiveReason(transactionDto.getTransactionDetail().getMaintenanceReasonCode())
                .changed(true)
                .build();
        // Determine the end date of the enrollment span
        LocalDate enrollmentSpanEndDate = determineEndDate(
                transactionDto.getTransactionDetail().getEffectiveDate(),
                transactionDto.getTransactionDetail().getEndDate());
        enrollmentSpan.setEndDate(enrollmentSpanEndDate);
        // If the end date of the enrollment span being added is less than 12/31
        // then set the term reason
        if (enrollmentSpanEndDate.isBefore(LocalDate.of(transactionDto.getTransactionDetail().getEffectiveDate().getYear(),
                12, 31))){
            enrollmentSpan.setTermReason("VOLWITH");
        }
        // Determine the enrollment span status
        String spanStatus = determineEnrollmentSpanStatus(enrollmentSpan, priorEnrollmentSpans);
        enrollmentSpan.setStatusTypeCode(spanStatus);
        enrollmentSpan = enrollmentSpanRepository.save(enrollmentSpan);
        // Create the premium span
        premiumSpanHelper.createPremiumSpans(transactionDto,
                enrollmentSpan,
                account);
        return enrollmentSpan;
    }

    /**
     * Set the enrollment span to send to MMS
     * @param accountDto
     * @param account
     * @param ztcn
     */
    @Override
    public void setEnrollmentSpan(AccountDto accountDto,
                                  Account account,
                                  String ztcn) {
        if(account.getEnrollmentSpan() != null && account.getEnrollmentSpan().size() > 0){
            List enrollmentSpanDtos = new ArrayList<>();
            account.getEnrollmentSpan().forEach(enrollmentSpan -> {
                EnrollmentSpanDto enrollmentSpanDto = enrollmentSpanMapper.enrollmentSpanToEnrollmentSpanDto(enrollmentSpan);
//                enrollmentSpanDto.setZtcn(ztcn);
                premiumSpanHelper.setPremiumSpan(enrollmentSpanDto, enrollmentSpan, ztcn);
                enrollmentSpanDtos.add(enrollmentSpanDto);
            });
            accountDto.setEnrollmentSpans(enrollmentSpanDtos.stream().collect(Collectors.toSet()));
        }

    }

    /**
     * Determine the status of an enrollment span
     * @param enrollmentSpanStatusDto
     * @return
     */
    @Override
    public String determineStatus(EnrollmentSpanStatusDto enrollmentSpanStatusDto) {
        return determineEnrollmentSpanStatus(enrollmentSpanMapper.enrollmentSpanDtoToEnrollmentSpan(enrollmentSpanStatusDto.getCurrentEnrollmentSpan()),
                enrollmentSpanStatusDto.getPriorEnrollmentSpans());
    }

    /**
     * Get enrollment spans that are overlapping
     * @param accountDto The account from which the overlapping enrollment spans are to be identfied
     * @param transactionDto the transaction that is being processed
     * @return return the enrollment spans that are overlapping with the dates that are passed
     */
    @Override
    public List getOverlappingEnrollmentSpans(AccountDto accountDto,
                                                                 TransactionDto transactionDto) {
        if(accountDto.getEnrollmentSpans() == null || accountDto.getEnrollmentSpans().isEmpty()){
            return null;
        }
        // the start date that is to be used
        LocalDate effectiveStartDate = transactionDto.getTransactionDetail().getEffectiveDate();
        // the end date that is to be used
        LocalDate effectiveEndDate = transactionDto.getTransactionDetail().getEndDate();
        if(effectiveEndDate == null){
            effectiveEndDate = LocalDate.of(effectiveStartDate.getYear(), 12, 31);
        }
        //  identifies the type of coverage "FAM" or "DEP"
        String coverageTypeCode = transactionDto.getTransactionDetail().getCoverageTypeCode();
        // The list of members present in the transaction
        List transactionMemberDtos = transactionDto.getMembers();

        Set overlappingEnrollmentSpans = new HashSet<>();
        Set enrollmentSpanDtos = accountDto.getEnrollmentSpans();
        int effectiveYear = effectiveStartDate.getYear();
        // Get all the enrollment spans that are present for the year for which the effective date is received and
        // has the same coverage type code that is passed in the input
        // i.e. if the effective date is 2/1/2023 get all the enrollment spans that belong to the year 2023 and also
        // has the same coverage type code "FAM" or "DEP"
        overlappingEnrollmentSpans = enrollmentSpanDtos.stream()
                .filter(enrollmentSpanDto -> enrollmentSpanDto.getStartDate().getYear() == effectiveYear &&
                                             enrollmentSpanDto.getCoverageTypeCode().equals(coverageTypeCode))
                .collect(Collectors.toSet());
        // Get all the enrollment spans that have the end date that is greater than the effective start date
        // This eliminates all the enrollment spans that are termed prior to the effective start date of the transaction
        overlappingEnrollmentSpans = overlappingEnrollmentSpans.stream()
                .filter(enrollmentSpanDto -> enrollmentSpanDto.getEndDate().isAfter(effectiveStartDate))
                .collect(Collectors.toSet());
        // Get all enrollment spans that have the start date that is before the effective end date received in the
        // transaction
        LocalDate finalEffectiveEndDate = effectiveEndDate;
        overlappingEnrollmentSpans = overlappingEnrollmentSpans.stream()
                .filter(enrollmentSpanDto -> {
                    if(enrollmentSpanDto.getStatusTypeCode().equals("CANCELED")){
                        return false;
                    }
                    LocalDate enrollmentSpanStartDate = enrollmentSpanDto.getStartDate();
                    return enrollmentSpanStartDate.isBefore(finalEffectiveEndDate);
                })
                .collect(Collectors.toSet());
        if(coverageTypeCode.equals("DEP") && !overlappingEnrollmentSpans.isEmpty()){
            // If the coverage type code is "DEP"
            // There can be overlapping enrollment spans in the same account with different set
            // of members than those that is being added in the transaction
            // Such enrollment spans should not be termed or canceled because of the
            // addition of the new enrollment span
            // So fileOverlappingSpans will check for such enrollment spans and filter them out
            // keeping only enrollment spans that have one or more members that exists in the transaction
            overlappingEnrollmentSpans = filterOverlappingSpans(overlappingEnrollmentSpans, transactionMemberDtos, effectiveStartDate);
        }
        // Return null if there are no overlapping enrollment spans
        if(overlappingEnrollmentSpans == null || overlappingEnrollmentSpans.isEmpty()){
            return null;
        }else {

            return overlappingEnrollmentSpans.stream().toList();


        }
    }

    /**
     * This method will filter out the enrollment spans that does not belong to the members
     * in the transaction so that such enrollment spans don't get termed or canceled.
     * @param overlappingSpans
     * @param dependents
     * @param effectiveDate
     * @return
     */
    private Set filterOverlappingSpans(Set overlappingSpans,
                                                        List dependents,
                                                        LocalDate effectiveDate){
        // return null if overlapping spans passed in the input is null or is empty
        if(overlappingSpans == null || overlappingSpans.isEmpty()){
            return null;
        }
//        log.info("Overlapping Spans before filtering:{}", overlappingSpans);
        // Get all the account member codes that were matched with the members
        // in the transaction. If the member in the transaction was not matched with
        // any member in the account, then they will obviously be not present in the enrollment span
        Set transactionMembers = dependents.stream()
                        .map(TransactionMemberDto::getMmsMemberCode)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
//        log.info("Transaction Effective Date:{}", effectiveDate);
//        log.info("Transaction Members:{}", transactionMembers);
        // Filter out only the enrollment span that contains members who are in the transaction
        // because only those enrollment spans are the ones that are truly overlapping
        // and needs to be termed or canceled
        overlappingSpans = overlappingSpans.stream().filter(enrollmentSpanDto -> {
            // Retrieve the premium spans associated with the enrollment span
            // that are active and have start date that is equal or greater than the
            // effective date of the transaction
            List premiumSpanDtos = enrollmentSpanDto.getPremiumSpans().stream()
                    .filter(premiumSpanDto -> (premiumSpanDto.getStartDate().equals(effectiveDate) ||
                            premiumSpanDto.getStartDate().isAfter(effectiveDate)) &&
                            premiumSpanDto.getStatusTypeCode().equals(PremiumSpanStatus.ACTIVE.toString()))
                    .toList();
//            log.info("Premium Span Dtos:{}", premiumSpanDtos);
            // Add all the member in these premium spans to the below set.
            // Set is used instead of List to avoid duplicating the member codes
            Set enrollmentSpanMembers = new HashSet<>();
            premiumSpanDtos.forEach(premiumSpanDto ->
                    enrollmentSpanMembers.addAll(
                            premiumSpanDto
                                    .getMemberPremiumSpans()
                                    .stream()
                                    .map(MemberPremiumDto::getMemberCode)
                                    .collect(Collectors.toSet())));
//            log.info("Enrollment Span Members: {}", enrollmentSpanMembers);
            // Return true if any of the member in the enrollment span are present in the transaction
            return enrollmentSpanMembers.stream().anyMatch(transactionMembers::contains);
        }).collect(Collectors.toSet());
        // Return the overlapping enrollment spans that contain one or more member who are present in the
        // transaction
        return overlappingSpans;
    }

    /**
     * Identify the enrollment spans that overlap the dates and term or cancel them appropriately
     * @param overlappingEnrollmentSpans List of enrollment spans that overlap
     * @param effectiveStartDate the dates when the enrollment spans are overlapping
     * @param effectiveEndDate the dates when the enrollment spans are overlapping
     * @return return the enrollment spans the need to be termed or canceled to avoid overlapping issues
     */
    private List updateOverlappingEnrollmentSpans(List overlappingEnrollmentSpans,
                                                                    LocalDate effectiveStartDate,
                                                                    LocalDate effectiveEndDate) {
        // Check if there are any enrollment spans that need to be termed or canceled
        if(overlappingEnrollmentSpans != null && !overlappingEnrollmentSpans.isEmpty()){
            // Get the enrollment span in which the effective date is falling in between its start date and end date
            Set termCancelEnrollmentSpans = overlappingEnrollmentSpans.stream().filter(enrollmentSpanDto ->
                    enrollmentSpanDto.getStartDate().isEqual(effectiveStartDate) ||
                            (enrollmentSpanDto.getStartDate().isBefore(effectiveStartDate) &&
                                    enrollmentSpanDto.getEndDate().isAfter(effectiveStartDate))).collect(Collectors.toSet());
            if(termCancelEnrollmentSpans.size() > 1){
                log.info("More than one enrollment spans to be termed");
                // todo generate an exception when this is true
            }else if (termCancelEnrollmentSpans.size() == 1) {
                // Get the first element from the set to term
                final EnrollmentSpanDto termCancelEnrollmentSpan = termCancelEnrollmentSpans.stream().findFirst().get();
                // Identify the enrollment span that needs to be termed/canceled from the overlapping enrollment span list
                // The rest of the enrollment spans should be canceled
                overlappingEnrollmentSpans.forEach(enrollmentSpanDto -> {
                    if (enrollmentSpanDto.getEnrollmentSpanCode().equals(termCancelEnrollmentSpan.getEnrollmentSpanCode())) {
                        if (enrollmentSpanDto.getStartDate().isBefore(effectiveStartDate)) {
                            LocalDate termDate = effectiveStartDate.minusDays(1);
                            enrollmentSpanDto.setEndDate(termDate);
                            enrollmentSpanDto.setTermReason("VOLWITH");
                            enrollmentSpanDto.setChanged(new AtomicBoolean(true));
                            // Identify the premium spans that need to be termed or canceled
                            enrollmentSpanDto.getPremiumSpans().forEach(premiumSpanDto -> {
                                // Premiums spans where the start date is before the term date but the end date is
                                // after the term date, then the premium span has to be termed
                                if (premiumSpanDto.getStartDate().isBefore(termDate) &&
                                        premiumSpanDto.getEndDate().isAfter(termDate)) {
                                    premiumSpanDto.setEndDate(termDate);
                                    premiumSpanDto.setChanged(new AtomicBoolean(true));
                                }// If the premium span start date is after the term date then it has to be canceled
                                else if (premiumSpanDto.getStartDate().isAfter(termDate)) {
                                    cancelPremiumSpan(premiumSpanDto);
                                }
                            });
                        } else {
                            enrollmentSpanDto.setEndDate(enrollmentSpanDto.getStartDate());
                            enrollmentSpanDto.setStatusTypeCode("CANCELED");
                            enrollmentSpanDto.setTermReason("VOLWITH");
                            enrollmentSpanDto.setChanged(new AtomicBoolean(true));
                            cancelPremiumSpans(enrollmentSpanDto.getPremiumSpans());
                        }
                    } else { // The rest of the enrollment spans should be canceled
                        enrollmentSpanDto.setEndDate(enrollmentSpanDto.getStartDate());
                        enrollmentSpanDto.setStatusTypeCode("CANCELED");
                        enrollmentSpanDto.setTermReason("VOLWITH");
                        enrollmentSpanDto.setChanged(new AtomicBoolean(true));
                        cancelPremiumSpans(enrollmentSpanDto.getPremiumSpans());
                    }
                });
            }
            // This contains the list of enrollment span dtos that are updated depending on if they need to be
            // canceled or termed
            return overlappingEnrollmentSpans;
        }
        return null;
    }

    /**
     * Save the updated enrollment spans
     * @param enrollmentSpanDtos enrollment spans that need to be saved
     * @param account the account to which the enrollment spans belong
     * @return saved enrollment spans
     */
    private List saveUpdatedEnrollmentSpans(List enrollmentSpanDtos, Account account) {
        if(enrollmentSpanDtos != null && !enrollmentSpanDtos.isEmpty()){
            List savedEnrollmentSpans = new ArrayList<>();
            enrollmentSpanDtos.forEach(enrollmentSpanDto -> {

                EnrollmentSpan enrollmentSpan =
                        enrollmentSpanMapper.enrollmentSpanDtoToEnrollmentSpan(enrollmentSpanDto);
                enrollmentSpan.setAcctEnrollmentSpanSK(enrollmentSpanDto.getEnrollmentSpanSK());
                enrollmentSpan.setChanged(true);
                enrollmentSpan.setAccount(account);
                enrollmentSpan = enrollmentSpanRepository.save(enrollmentSpan);
                List updatedPremiumSpans = premiumSpanHelper
                        .saveUpdatedPremiumSpans(enrollmentSpanDto.getPremiumSpans().stream().toList(),
                                enrollmentSpan);
                enrollmentSpan.setPremiumSpans(updatedPremiumSpans);
                savedEnrollmentSpans.add(enrollmentSpan);

            });
            return savedEnrollmentSpans;
        }

        return null;
    }

    /**
     * Get the enrollment spans that are immediately before the start date provided in the input
     * @param accountDto the account dto that contains the enrollment spans
     * @param startDate the start date before which the enrollment spans are requested
     * @param matchCancelSpans boolean to indicate of cancel spans should be considered a match
     * @return return the list of matched enrollment spans
     */
    private List getPriorEnrollmentSpans(AccountDto accountDto, LocalDate startDate, boolean matchCancelSpans) {
        // get all the enrollment spans from the account
        List enrollmentSpanDtos = accountDto.getEnrollmentSpans().stream().toList();
        // Sort the enrollment spans by the ascending order of the date
        enrollmentSpanDtos =
                enrollmentSpanDtos.stream()
                        .sorted(Comparator.comparing(EnrollmentSpanDto::getStartDate))
                        .collect(Collectors.toList());
        // Get all the enrollment spans that is prior to the start date provided in the input
        enrollmentSpanDtos =
                enrollmentSpanDtos.stream()
                        .takeWhile(
                                enrollmentSpanDto ->
                                        enrollmentSpanDto.getStartDate().isBefore(startDate))
                        .collect(Collectors.toList());
        if(!matchCancelSpans){
            // Remove canceled spans if match cancel spans is "FALSE"
            enrollmentSpanDtos = removeCanceledSpans(enrollmentSpanDtos);
        }
        if(enrollmentSpanDtos != null && !enrollmentSpanDtos.isEmpty()){
            // Retrieve the maximum end date of all the enrollment spans
            LocalDate maxEndDate =
                    enrollmentSpanDtos.stream()
                            .map(EnrollmentSpanDto::getEndDate)
                            .max(Comparator.naturalOrder())
                            .get();
            // Get all the enrollment spans with the end date that matches the max end date
            List priorYearEnrollmentSpans =
                    enrollmentSpanDtos.stream()
                            .takeWhile(
                                    enrollmentSpanDto -> enrollmentSpanDto.getEndDate().isEqual(maxEndDate))
                            .toList();
            return priorYearEnrollmentSpans;
        }
        return null;
    }

    /**
     * Update the impacted enrollment spans and create ones as needed
     * @param accountDto
     * @param transactionDto
     * @param account
     * @param overlappingEnrollmentSpans
     */
    @Override
    public void updateEnrollmentSpans(AccountDto accountDto,
                                      TransactionDto transactionDto,
                                      Account account,
                                      List overlappingEnrollmentSpans) throws JsonProcessingException {
        LocalDate effectiveStartDate = transactionDto.getTransactionDetail().getEffectiveDate();
        LocalDate effectiveEndDate = transactionDto.getTransactionDetail().getEndDate();
        if(effectiveEndDate == null){
            effectiveEndDate = LocalDate.of(effectiveStartDate.getYear(), 12, 31);
        }
        log.info("Effective start date of the new enrollment span:{}", effectiveStartDate);
        log.info("Effective end date of the new enrollment span:{}", effectiveEndDate);
        log.info("Existing overlapping spans are:{}", overlappingEnrollmentSpans);
        // Get the overlapping enrollment spans updated appropriately.
        // Note this will just update within the DTO and not in the DB
        overlappingEnrollmentSpans = updateOverlappingEnrollmentSpans(
                overlappingEnrollmentSpans,
                effectiveStartDate,
                effectiveEndDate);
        log.info("Overlapping spans once the updates are made:{}", overlappingEnrollmentSpans);
        updateAccountDtoWithOverlappingSpans(accountDto, overlappingEnrollmentSpans);
        List updatedEnrollmentSpans = saveUpdatedEnrollmentSpans(overlappingEnrollmentSpans,
                account);
        if(updatedEnrollmentSpans == null){
            updatedEnrollmentSpans = new ArrayList<>();
        }
        updatedEnrollmentSpans.forEach(enrollmentSpan -> {
//            log.info("Saved Enrollment span code before :{}", enrollmentSpan.getEnrollmentSpanCode());
//            log.info("Saved Enrollment span ztcn before:{}", enrollmentSpan.getZtcn());
        });
        EnrollmentSpan newEnrollmentSpan = createEnrollmentSpan(transactionDto,
                account,
                getPriorEnrollmentSpans(accountDto, effectiveStartDate, false));
        updatedEnrollmentSpans.add(newEnrollmentSpan);
        updatedEnrollmentSpans.forEach(enrollmentSpan -> {
//            log.info("Saved Enrollment span code after :{}", enrollmentSpan.getEnrollmentSpanCode());
//            log.info("Saved Enrollment span ztcn after:{}", enrollmentSpan.getZtcn());
        });
        account.setEnrollmentSpan(updatedEnrollmentSpans);
    }

    /**
     * Process the financial change for the enrollment span
     * @param changeTransactionInfo - Details associated with the change transaction
     * @param transactionDto - Change transaction data
     * @param account - The account entity
     * @param accountDto - the account for which the transaction was received
     * @param matchedEnrollmentSpanDto - The matched enrollment span
     */
    @Override
    public void processFinancialChange(ChangeTransactionInfo changeTransactionInfo,
                                       TransactionDto transactionDto,
                                       Account account,
                                       AccountDto accountDto,
                                       EnrollmentSpanDto matchedEnrollmentSpanDto) {
        EnrollmentSpan enrollmentSpan = enrollmentSpanMapper.enrollmentSpanDtoToEnrollmentSpan(matchedEnrollmentSpanDto);
        enrollmentSpan.setAccount(account);
        enrollmentSpan.setChanged(false);
        enrollmentSpan = enrollmentSpanRepository.save(enrollmentSpan);
        account.setEnrollmentSpan(List.of(enrollmentSpan));
        premiumSpanHelper.processFinancialChange(changeTransactionInfo, transactionDto,
                account, accountDto, enrollmentSpan, matchedEnrollmentSpanDto);
    }

    /**
     * Cancel or term the requested enrollment span
     * @param matchedEnrollmentSpanDto
     * @param transactionDto
     * @param account
     */
    @Override
    public void cancelTermEnrollmentSpan(EnrollmentSpanDto matchedEnrollmentSpanDto, TransactionDto transactionDto, Account account) {
        LocalDate effectiveEndDate = transactionDto.getTransactionDetail().getEffectiveDate();
        boolean isTermRequested = isTermRequested(transactionDto);
        if(effectiveEndDate.isBefore(matchedEnrollmentSpanDto.getStartDate()) ||
                (effectiveEndDate.equals(matchedEnrollmentSpanDto.getStartDate()) && !isTermRequested)
            ){
            // The request is to cancel the span if
            // end date in the transaction is before the start date of the matched enrollment span
            // OR
            // end date in the transaction is equal to start date of the matched enrollment span and
            // either term is not requested in the transaction

            // Note: If the end date in the transaction is equal to the start date and a termination of the span
            // is requested in the transaction, then it means tha the span has to be active for One Day
            EnrollmentSpan enrollmentSpan = termCancelEnrollmentSpan(account, matchedEnrollmentSpanDto,
                    transactionDto.getTransactionDetail().getEffectiveDate(),
                    transactionDto.getTransactionDetail().getMaintenanceReasonCode(),
                    EnrollmentSpanStatus.CANCELED.toString());
            premiumSpanHelper.cancelPremiumSpans(matchedEnrollmentSpanDto, enrollmentSpan);
        }else{
            // Term the enrollment span if the request is to term the span
            EnrollmentSpan enrollmentSpan = termCancelEnrollmentSpan(account, matchedEnrollmentSpanDto,
                    transactionDto.getTransactionDetail().getEffectiveDate(),
                    transactionDto.getTransactionDetail().getMaintenanceReasonCode(),
                    null);
            premiumSpanHelper.termPremiumSpans(matchedEnrollmentSpanDto, enrollmentSpan);
        }
    }

    /**
     * Check if termination is requested in the transaction
     * @param transactionDto
     * @return
     */
    private boolean isTermRequested(TransactionDto transactionDto){
        String maintenanceReasonCode = transactionDto.getTransactionDetail().getMaintenanceReasonCode();
        String amrcValue = getAMRCValue(transactionDto.getTransactionAttributes());
        // If maintenance reason code is "Termination of Benefits" or
        // if the AMRC Value in the transaction is "TERM" then
        // the transaction is requesting the enrollment span to be termed
        return maintenanceReasonCode.equals("TERMOFBEN") ||
                (amrcValue != null && amrcValue.equals("TERM"));
    }

    /**
     * Get the amrc value if received in the transaction
     * @param transactionAttributeDtos
     * @return
     */
    private String getAMRCValue(List transactionAttributeDtos){
        if(transactionAttributeDtos == null || transactionAttributeDtos.isEmpty()){
            return null;
        }
        Optional optionalAttributeDto = transactionAttributeDtos.stream()
                .filter(transactionAttributeDto ->
                        transactionAttributeDto.getTransactionAttributeTypeCode()
                                .equals("AMRC")).findFirst();
        return optionalAttributeDto.map(TransactionAttributeDto::getTransactionAttributeValue)
                .orElse(null);
    }

    /**
     * Reinstate enrollment span received in the transaction
     * @param accountDto
     * @param transactionDto
     * @param account
     */
    @Override
    public void reinstateEnrollmentSpan(AccountDto accountDto, TransactionDto transactionDto, Account account) {
        // Identify the enrollment span that needs to be reinstated
        EnrollmentSpanDto matchedEnrollmentSpanDto = getMatchedEnrollmentSpan(accountDto.getEnrollmentSpans(),
                transactionDto.getTransactionDetail().getGroupPolicyId());
        LocalDate effectiveDate = transactionDto.getTransactionDetail().getEffectiveDate();
        int year = effectiveDate.getYear();
        matchedEnrollmentSpanDto.setEndDate(LocalDate.of(year, 12, 31));
        List priorEnrollmentSpans = getPriorEnrollmentSpans(accountDto,
                effectiveDate, false);
        EnrollmentSpanStatusDto enrollmentSpanStatusDto = EnrollmentSpanStatusDto.builder()
                .currentEnrollmentSpan(matchedEnrollmentSpanDto)
                .priorEnrollmentSpans(priorEnrollmentSpans)
                .build();
        String status = determineStatus(enrollmentSpanStatusDto);
        matchedEnrollmentSpanDto.setStatusTypeCode(status);
        matchedEnrollmentSpanDto.setChanged(new AtomicBoolean(true));
        matchedEnrollmentSpanDto.setTermReason(null);
        EnrollmentSpan enrollmentSpan = enrollmentSpanMapper.enrollmentSpanDtoToEnrollmentSpan(matchedEnrollmentSpanDto);
        enrollmentSpan.setAccount(account);
        enrollmentSpan = enrollmentSpanRepository.save(enrollmentSpan);
        account.setEnrollmentSpan(List.of(enrollmentSpan));
        premiumSpanHelper.reinstatePremiumSpans(matchedEnrollmentSpanDto, enrollmentSpan);
    }

    /**
     * Term or cancel the enrollment span
     * @param account
     * @param matchedEnrollmentSpanDto
     * @param endDate
     * @param termReasonCode
     * @param spanStatus
     * @return
     */
    private EnrollmentSpan termCancelEnrollmentSpan(Account account,
                                                    EnrollmentSpanDto matchedEnrollmentSpanDto,
                                                    LocalDate endDate,
                                                    String termReasonCode,
                                                    String spanStatus){
        matchedEnrollmentSpanDto.setChanged(new AtomicBoolean(true));
        matchedEnrollmentSpanDto.setEndDate(endDate);
        matchedEnrollmentSpanDto.setTermReason(termReasonCode);
        if(spanStatus != null){
            matchedEnrollmentSpanDto.setStatusTypeCode(spanStatus);
        }
        EnrollmentSpan enrollmentSpan = enrollmentSpanMapper.enrollmentSpanDtoToEnrollmentSpan(matchedEnrollmentSpanDto);
        enrollmentSpan.setAccount(account);
        enrollmentSpan = enrollmentSpanRepository.save(enrollmentSpan);
        account.setEnrollmentSpan(List.of(enrollmentSpan));
        return enrollmentSpan;
    }

    /**
     * Identify if there is an enrollment span that matches the group policy id received as input
     * @param enrollmentSpanDtos
     * @param groupPolicyId
     * @return
     */
    @Override
    public EnrollmentSpanDto getMatchedEnrollmentSpan(Set enrollmentSpanDtos, String groupPolicyId){
        return enrollmentSpanDtos.stream().filter(
                enrollmentSpanDto1 -> enrollmentSpanDto1.getGroupPolicyId()
                        .equals
                                (groupPolicyId)
        ).findFirst().orElseThrow(() ->
                new NoMatchingEnrollmentSpanException("No enrollment span matched group policy id " +
                        groupPolicyId));
    }


    /**
     * Determine what should be end date
     * @param startDate
     * @param endDate
     * @return
     */
    private LocalDate determineEndDate(LocalDate startDate, LocalDate endDate){
        // if the end date is present in the transaction use that end date
        if(endDate != null){
            return endDate;
        }else{
            // if it is not present then the 12/31 of the same year as the start date should be
            int year = startDate.getYear();
            return LocalDate.of(year, 12, 31);
        }
    }

    /**
     * Get the primay subscriber in the transaction
     * @param transactionDto
     * @return
     */
    private TransactionMemberDto    getPrimaryMember(TransactionDto transactionDto){
        TransactionMemberDto primaryMember = transactionDto.getMembers().stream().filter(memberDto -> {
            return memberDto.getRelationshipTypeCode().equals("HOH");
        }).findFirst().get();
        return primaryMember;
    }

    /**
     * Get the exchange subscriber id
     * @param primarySubscriber
     * @return
     */
    private String getExchangeSubscriberId(TransactionMemberDto primarySubscriber){
        Optional optionalIdentifier = primarySubscriber.getIdentifiers().stream().filter(memberIdentifierDto -> {
            return memberIdentifierDto.getIdentifierTypeCode().equals("EXCHSUBID");
        }).findFirst();
        if(optionalIdentifier.isPresent()){
            return optionalIdentifier.get().getIdentifierValue();
        }else {
            return null;
        }
    }

    /**
     * Determine the effectuation date of the enrollment span
     * @param transactionDto
     * @param priorEnrollmentSpans
     * @return
     */
    private LocalDate determineEffectuationDate(TransactionDto transactionDto,
                                                List priorEnrollmentSpans){
//        log.info("Prior Enrollment Spans - effectuation date determination:{}", priorEnrollmentSpans);
//        Optional optionalTotResAmt = transactionDto.getTransactionRates().stream().filter(rateDto -> {
//            return rateDto.getRateTypeCode().equals("TOTRESAMT");
//        }).findFirst();

        // Determine the member total responsibility amount
        BigDecimal totalResponsibilityAmount = determineMemberResponsibility(transactionDto.getTransactionRates());
        if(totalResponsibilityAmount.compareTo(BigDecimal.valueOf(0)) == 0){
//                return LocalDate.now();
            return transactionDto.getTransactionReceivedDate().toLocalDate();
        }
//        if(optionalTotResAmt.isPresent()){
//            TransactionRateDto rateDto = optionalTotResAmt.get();
//            BigDecimal totResAmt = rateDto.getTransactionRate();
//            if(totResAmt.compareTo(BigDecimal.valueOf(0)) == 0){
////                return LocalDate.now();
//                return transactionDto.getTransactionReceivedDate().toLocalDate();
//            }
//        }
        if(priorEnrollmentSpans!=null){
            Optional optionalPriorEnrollmentSpan = priorEnrollmentSpans.stream()
                    .filter(
                            enrollmentSpanDto -> enrollmentSpanDto.getStatusTypeCode().equals("ENROLLED")).findFirst();
            if(optionalPriorEnrollmentSpan.isPresent()){
                EnrollmentSpanDto priorEnrollmentSpan = optionalPriorEnrollmentSpan.get();
                boolean samePlan = isSamePlan(transactionDto.getTransactionDetail().getPlanId(), priorEnrollmentSpan.getPlanId());
                boolean gapInCoverage = isThereGapInCoverage(transactionDto.getTransactionDetail().getEffectiveDate(), priorEnrollmentSpan);
//                log.info("Same Plan:{}", samePlan);
//                log.info("Gap In Coverage:{}", gapInCoverage);
                if(samePlan && !gapInCoverage){
//                    return LocalDate.now();
                    return transactionDto.getTransactionReceivedDate().toLocalDate();
                }
            }
        }
        return null;
    }

    /**
     * Determine the members total responsibility amount
     * @param rateDtos
     * @return
     */
    private BigDecimal determineMemberResponsibility(List rateDtos){
        List resAmtRates = rateDtos.stream().filter(rateDto -> {
            return rateDto.getRateTypeCode().equals("TOTRESAMT");
        }).toList();
        Optional optionalResAmt = resAmtRates.stream()
                .min(Comparator.comparing(TransactionRateDto::getRateStartDate));
        if(optionalResAmt.isPresent()){
            return optionalResAmt.get().getTransactionRate();
        }else{
            return BigDecimal.ZERO;
        }
    }

    /**
     * Determine the enrollment span status
     * @param currentEnrollmentSpan
     * @param priorEnrollmentSpans
     * @return
     */
    @Override
    public String determineEnrollmentSpanStatus(EnrollmentSpan currentEnrollmentSpan,
                                                 List priorEnrollmentSpans){
        boolean isEndDateGreaterThanStartDate = currentEnrollmentSpan.getEndDate().isAfter(currentEnrollmentSpan.getStartDate());
        boolean isEndDateLessThanStartDate = currentEnrollmentSpan.getEndDate().isBefore(currentEnrollmentSpan.getStartDate());
        boolean isEndDateEqualToStartDate =currentEnrollmentSpan.getEndDate().isEqual(currentEnrollmentSpan.getStartDate());
        if(isDelinquent(currentEnrollmentSpan, priorEnrollmentSpans) == 1){
            return EnrollmentSpanStatus.DELINQUENT.name();
        }else if(isDelinquent(currentEnrollmentSpan, priorEnrollmentSpans) == -1){
            return EnrollmentSpanStatus.SUSPENDED.name();
        }
        if (isEndDateLessThanStartDate) {
            return EnrollmentSpanStatus.CANCELED.name();
        }
        if(currentEnrollmentSpan.getEffectuationDate() != null &&
                (isEndDateGreaterThanStartDate || isEndDateEqualToStartDate) &&
                !currentEnrollmentSpan.isDelinqInd()){
            return EnrollmentSpanStatus.ENROLLED.name();
        }
        if(currentEnrollmentSpan.getEffectuationDate() == null &&
                (isEndDateGreaterThanStartDate || isEndDateEqualToStartDate) &&
                !currentEnrollmentSpan.isDelinqInd()){
            return EnrollmentSpanStatus.PRE_MEMBER.name();
        }
        return EnrollmentSpanStatus.NO_VALID_STATUS.name();
    }

    /**
     * Determine if the current enrollment span is delinquent or suspended or neither
     * This method will return
     * 1 - Delinquent
     * -1 - Suspended
     * 0 - Neither Suspended nor Delinquent
     * @param currentEnrollmentSpan
     * @param priorEnrollmentSpans
     * @return
     */
    private int isDelinquent(EnrollmentSpan currentEnrollmentSpan,
                                 List priorEnrollmentSpans){
        // Check if the effectuation date is not NULL and the delinquency flag is set to true and
        // the end date is not before the start date of the enrollment span

        if(currentEnrollmentSpan.getEffectuationDate() != null &&
            currentEnrollmentSpan.isDelinqInd() &&
                !currentEnrollmentSpan.getStartDate()
                        .isAfter(currentEnrollmentSpan.getEndDate())){
            // if the above conditions are satisfied check if the claim paid through date is not null
            if(currentEnrollmentSpan.getClaimPaidThroughDate() != null){
                // If the claim paid through date is not null then
                // Check if the claim paid through date is greater than the current system date
                // and if the plan of the current and the prior enrollment spans are same
                // and check if there is any gap in coverage between the prior and current enrollment spans
                // and if the prior enrollment span is delinquent status
                boolean localDateIsBefore = LocalDate.now().isBefore(currentEnrollmentSpan.getClaimPaidThroughDate());
                boolean localDateIsAfter = LocalDate.now().isAfter(currentEnrollmentSpan.getClaimPaidThroughDate());
                boolean localDateIsEqual = LocalDate.now().isEqual(currentEnrollmentSpan.getClaimPaidThroughDate());
                AtomicBoolean samePlan = new AtomicBoolean(false);
                AtomicBoolean gapInCoverage = new AtomicBoolean(false);
                AtomicBoolean priorSpanStatus = new AtomicBoolean(false);
                boolean delinquent = false;
                if(priorEnrollmentSpans != null && !priorEnrollmentSpans.isEmpty()){
                    delinquent = priorEnrollmentSpans.stream().anyMatch(priorEnrollmentSpan -> {
                        samePlan.set(priorEnrollmentSpan != null &&
                                isSamePlan(currentEnrollmentSpan.getPlanId(), priorEnrollmentSpan.getPlanId()));
                        gapInCoverage.set(priorEnrollmentSpan != null &&
                                isThereGapInCoverage(currentEnrollmentSpan.getStartDate(), priorEnrollmentSpan));
                        priorSpanStatus.set(priorEnrollmentSpan != null &&
                                priorEnrollmentSpan.getStatusTypeCode().equals(EnrollmentSpanStatus.DELINQUENT.name()));
                        return localDateIsBefore || localDateIsEqual ||
                                (samePlan.get() &&
                                        !gapInCoverage.get() &&
                                        priorSpanStatus.get());
                    });
                }

//                boolean delinquent = localDateIsBefore || localDateIsEqual ||
//                        (samePlan.get() &&
//                                !gapInCoverage.get() &&
//                                priorSpanStatus.get());

                log.info("Delinquent:{}", delinquent);
                if(delinquent){
                    return 1;
                }else{
                    boolean suspended = localDateIsAfter || localDateIsEqual;
                    return -1;
                }
            }else{
                // If the claim paid through date is null then
                // set the enrollment span status should be SUSPENDED
                // At this point the delinquency flag is yes and the effectuation date is not null
                return -1;
            }
        }else {
            // if effectuation date is not present or delinquency flag is not set to true or if the start date of the
            // enrollment span is greater than the end date of the enrollment span, then the enrollment span is not
            // delinquent
            return 0;
        }
    }

    /**
     * Determine if the planIds are same
     * @param currentPlanId
     * @param priorPlanId
     * @return
     */
    private boolean isSamePlan(String currentPlanId,
                               String priorPlanId){
        return currentPlanId.equals(priorPlanId);
    }

    /**
     * Determine if there is a gap between the current and the prior enrollment span
     * @param effectiveStartDate of the enrollment span to be created
     * @param priorEnrollmentSpan the prior year enrollment span
     * @return return true if there is a gap in coverage
     */
    private boolean isThereGapInCoverage(LocalDate effectiveStartDate,
                                         EnrollmentSpanDto priorEnrollmentSpan){
        if(priorEnrollmentSpan.getStatusTypeCode().equals(EnrollmentSpanStatus.CANCELED)){
            return true;
        }else{
            long numOfDays = ChronoUnit.DAYS.between(priorEnrollmentSpan.getEndDate(),
                    effectiveStartDate);
            // numOfDays will be 1 if the end date of the prior enrollment span is the day prior to the
            // start of the current enrollment span

            // num of days will be greater than one if there is a gap between the end of the prior enrollment span
            // and start of the current enrollment span

            // num of days will be 0 if the end date of the prior enrollment span is same as that of the start date of the
            // current enrollment span

            // number of days will be -ve if the end date of the prior enrollment span is greater than the start date
            // of the current enrollment span
            if(numOfDays > 1){
                // This means that there is a gap between the end of the prior enrollment span
                // and start of the current enrollment span hence return tru
                return true;
            }else{
                // in all other cases there is no gap, hence return false
                return false;
            }
        }
    }

    /**
     * Cancel the premium span and set the status
     * @param premiumSpanDto cancel the premium span
     */
    private void cancelPremiumSpan(PremiumSpanDto premiumSpanDto){
        // Check if the premium span is not already canceled before setting it to cancel
        if(!premiumSpanDto.getStartDate().isEqual(premiumSpanDto.getEndDate()) ||
                (premiumSpanDto.getStartDate().isEqual(premiumSpanDto.getEndDate()) &&
                        premiumSpanDto.getStatusTypeCode().equals("ACTIVE"))){
            premiumSpanDto.setEndDate(premiumSpanDto.getStartDate());
            premiumSpanDto.setStatusTypeCode("CANCELED");
            premiumSpanDto.setChanged(new AtomicBoolean(true));
        }

    }

    /**
     * Cancel all the premium spans
     * @param premiumSpanDtos premium spans to be canceled
     */
    private void cancelPremiumSpans(Set premiumSpanDtos){
        premiumSpanDtos.forEach(this::cancelPremiumSpan);
    }

    /**
     * Remove canceled enrollment span from the list
     * @param enrollmentSpanDtos
     * @return
     */
    private List removeCanceledSpans(List enrollmentSpanDtos){
        List nonCanceledEnrollmentSpans = enrollmentSpanDtos.stream()
                .filter(
                        enrollmentSpanDto ->
                                !enrollmentSpanDto.getStatusTypeCode()
                                        .equals(EnrollmentSpanStatus.CANCELED))
                .collect(Collectors.toList());
        return nonCanceledEnrollmentSpans;
    }

    /**
     * The account dto object will be updated with the overlapping enrollment spans
     * @param accountDto The account dto that is to be updated
     * @param overlappingEnrollmentSpans the overlapping enrollment spans that needs to be added back to the account
     */
    private void updateAccountDtoWithOverlappingSpans(AccountDto accountDto,
                                                      List overlappingEnrollmentSpans) {
        if(overlappingEnrollmentSpans == null || overlappingEnrollmentSpans.isEmpty()){
            return;
        }else{
            // todo add the overlapping enrollment spans
            Set accountEnrollmentSpans = accountDto.getEnrollmentSpans();
            overlappingEnrollmentSpans.forEach(enrollmentSpanDto -> {
                Optional optionalEnrollmentSpan = accountEnrollmentSpans.stream()
                        .filter(
                                accountEnrollmentSpan ->
                                        accountEnrollmentSpan.getEnrollmentSpanCode().equals(
                                                enrollmentSpanDto.getEnrollmentSpanCode()))
                        .findFirst();
                optionalEnrollmentSpan.ifPresent(accountEnrollmentSpans::remove);

            });
            accountEnrollmentSpans.addAll(overlappingEnrollmentSpans);
        }
    }

    /**
     * Determine the enrollment type of the enrollment span
     * @param transactionAttributeDtos
     * @return
     */
    private String determineEnrollmentType(List transactionAttributeDtos){
        Optional optionalAMRC = transactionAttributeDtos.stream().filter(
                transactionAttribute -> transactionAttribute.getTransactionAttributeTypeCode().equals("AMRC")
        ).findFirst();
        if(optionalAMRC.isPresent()){
            TransactionAttributeDto transactionAttributeDto = optionalAMRC.get();
            String attributeValue = transactionAttributeDto.getTransactionAttributeValue();
            if(attributeValue.equals(AdditionalMaintenanceReasonCode.PASSIVE_ENROLLMENT.toString()) ||
                    attributeValue.equals(AdditionalMaintenanceReasonCode.PASSIVE.toString())){
                return EnrollmentType.PASSIVE.toString();
            }
        }
        return EnrollmentType.ACTIVE.toString();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy