/**
   * calculates the remainder of fiscal year estimated income for pooled funds
   *
   * @param security
   * @param holdingTaxLot
   * @return amount
   */
  protected BigDecimal getRemainderOfFiscalYearEstimatedIncomeForPooledFunds(
      Security security, HoldingTaxLot holdingTaxLot) {
    BigDecimal amount = BigDecimal.ZERO;

    if (ObjectUtils.isNull(security.getIncomeNextPayDate())
        || ObjectUtils.isNull(security.getFrequencyCode())) {
      return amount;
    }

    Date nextIncomeDueDate = security.getIncomeNextPayDate();
    if (ObjectUtils.isNull(nextIncomeDueDate)) {
      return amount;
    }

    Date fiscalYearEndDate = getFiscalYearEndDate();

    // BONDS - rule 4.a
    if (nextIncomeDueDate.after(fiscalYearEndDate)) {
      return BigDecimal.ZERO;
    }

    // rule 4.b
    if (nextIncomeDueDate.before(fiscalYearEndDate)) {
      String incomePayFrequency = security.getIncomePayFrequency();
      if (ObjectUtils.isNull(incomePayFrequency)) {
        return amount;
      }

      Date lastPaymentDate = getLastPaymentDate(incomePayFrequency, fiscalYearEndDate);

      long paymentsRemaining =
          getTotalPaymentsRemaining(
              lastPaymentDate, fiscalYearEndDate, incomePayFrequency, nextIncomeDueDate);

      long totalNumberOfPayments = kEMService.getTotalNumberOfPaymentsForFiscalYear();

      amount =
          KEMCalculationRoundingHelper.multiply(
              holdingTaxLot.getUnits(),
              security.getIncomeRate(),
              EndowConstants.Scale.SECURITY_MARKET_VALUE);
      amount = amount.multiply(BigDecimal.valueOf(paymentsRemaining));
      amount =
          KEMCalculationRoundingHelper.divide(
              amount,
              BigDecimal.valueOf(totalNumberOfPayments),
              EndowConstants.Scale.SECURITY_MARKET_VALUE);
      amount = amount.add(holdingTaxLot.getCurrentAccrual());
    }

    return amount;
  }
  /**
   * Calculates Gain or Loss for the tax lot line and determines if it's long term or short term
   *
   * @param holdingTaxLot
   * @param taxLotLine
   * @param valueReceived
   * @param originalCost
   */
  private void calculateGainLoss(
      HoldingTaxLot holdingTaxLot,
      EndowmentTransactionTaxLotLine taxLotLine,
      BigDecimal valueReceived,
      BigDecimal originalCost) {
    BigDecimal gainOrLoss = valueReceived.subtract(originalCost);
    gainOrLoss = gainOrLoss.setScale(2, BigDecimal.ROUND_HALF_UP);

    // Determine if short or long term gain/loss
    Date currentDate = kemService.getCurrentDate();
    Date acquiredDate = holdingTaxLot.getAcquiredDate();

    Calendar calendarAcquiredDate = Calendar.getInstance();
    calendarAcquiredDate.setTime(acquiredDate);
    calendarAcquiredDate.add(Calendar.MONTH, EndowConstants.SHORT_VS_LONG_TERM_PERIOD);

    if (calendarAcquiredDate.getTime().before(currentDate)) {
      // long term gain/loss
      taxLotLine.setLotLongTermGainLoss(gainOrLoss);
    }
    // short term gain/loss
    else {
      taxLotLine.setLotShortTermGainLoss(gainOrLoss);
    }
  }
  /**
   * calculates the remainder of fiscal year estimated income for bonds
   *
   * @param security
   * @param holdingTaxLot
   * @return amount
   */
  protected BigDecimal getRemainderOfFiscalYearEstimatedIncomeForBonds(
      Security security, HoldingTaxLot holdingTaxLot) {
    BigDecimal amount = BigDecimal.ZERO;

    if (ObjectUtils.isNull(security.getIncomeRate())
        || security.getIncomeRate().compareTo(BigDecimal.ZERO) == 0) {
      return amount;
    }

    Date nextIncomeDueDate = security.getIncomeNextPayDate();
    if (ObjectUtils.isNull(nextIncomeDueDate)) {
      return amount;
    }

    Date fiscalYearEndDate = getFiscalYearEndDate();

    // BONDS - rule 2.a
    if (nextIncomeDueDate.after(fiscalYearEndDate)) {
      return BigDecimal.ZERO;
    }

    int numberOfMonthsRemaining = getNumberOfMonthsRemaining(fiscalYearEndDate, nextIncomeDueDate);
    // rule 2.b
    if (nextIncomeDueDate.before(fiscalYearEndDate)
        && numberOfMonthsRemaining < EndowConstants.NUMBER_OF_MONTHS_REMAINING) {
      amount =
          KEMCalculationRoundingHelper.multiply(
              holdingTaxLot.getUnits(),
              security.getIncomeRate(),
              EndowConstants.Scale.SECURITY_MARKET_VALUE);
      amount =
          KEMCalculationRoundingHelper.divide(
              amount, BigDecimal.valueOf(2), EndowConstants.Scale.SECURITY_MARKET_VALUE);
    } else {
      amount =
          KEMCalculationRoundingHelper.multiply(
              holdingTaxLot.getUnits(),
              security.getIncomeRate(),
              EndowConstants.Scale.SECURITY_MARKET_VALUE);
    }

    return amount;
  }
  /**
   * calculates the remainder of fiscal year estimated income for cash
   *
   * @param security
   * @param holdingTaxLot
   * @return amount
   */
  protected BigDecimal getRemainderOfFiscalYearEstimatedIncomeForCash(
      Security security, HoldingTaxLot holdingTaxLot) {
    BigDecimal amount = BigDecimal.ZERO;

    if (ObjectUtils.isNull(security.getIncomeRate())
        || security.getIncomeRate().compareTo(BigDecimal.ZERO) == 0) {
      return amount;
    }

    Date nextIncomeDueDate = security.getIncomeNextPayDate();
    Date fiscalYearEndDate = getFiscalYearEndDate();
    String incomePayFrequency = security.getIncomePayFrequency();

    if (ObjectUtils.isNull(nextIncomeDueDate) || ObjectUtils.isNull(incomePayFrequency)) {
      return amount;
    }

    // BONDS - rule 3.a
    if (nextIncomeDueDate.after(fiscalYearEndDate)) {
      return BigDecimal.ZERO;
    }

    // rule 3.b
    if (nextIncomeDueDate.before(fiscalYearEndDate)) {
      Date lastPaymentDate = getLastPaymentDate(incomePayFrequency, fiscalYearEndDate);
      long daysToLastPayment = getTotalDaysToLastPayment(lastPaymentDate, nextIncomeDueDate);

      amount =
          KEMCalculationRoundingHelper.multiply(
              holdingTaxLot.getUnits(),
              security.getIncomeRate(),
              EndowConstants.Scale.SECURITY_MARKET_VALUE);
      amount = amount.multiply(BigDecimal.valueOf(daysToLastPayment));
      amount =
          KEMCalculationRoundingHelper.divide(
              amount,
              BigDecimal.valueOf(EndowConstants.NUMBER_OF_DAYS_IN_YEAR),
              EndowConstants.Scale.SECURITY_MARKET_VALUE);
      amount = amount.add(holdingTaxLot.getCurrentAccrual());
    }

    return amount;
  }
  /**
   * @see
   *     org.kuali.kfs.module.endow.document.service.CurrentTaxLotService#getNextTwelveMonthsEstimatedValue(String)
   *     Method to calculate Next Twelve Months Estimated value
   * @param securityId
   * @return nextTwelveMonthsEstimatedValue
   */
  public BigDecimal getNextTwelveMonthsEstimatedValue(
      HoldingTaxLot holdingTaxLot, String securityId) {
    BigDecimal nextTweleveMonthsEstimatedValue = BigDecimal.ZERO;

    Security security = securityService.getByPrimaryKey(securityId);

    return KEMCalculationRoundingHelper.multiply(
        holdingTaxLot.getUnits(),
        security.getIncomeRate(),
        EndowConstants.Scale.SECURITY_MARKET_VALUE);
  }
  /**
   * @see
   *     org.kuali.kfs.module.endow.document.service.CurrentTaxLotService#getHoldingMarketValue(HoldingTaxLot,
   *     String)
   */
  public BigDecimal getHoldingMarketValue(HoldingTaxLot holdingTaxLot, String securityId) {
    BigDecimal holdingMarketValue = BigDecimal.ZERO;

    Security security = securityService.getByPrimaryKey(securityId);

    String classCodeType = security.getClassCode().getClassCodeType();

    if (EndowConstants.ClassCodeTypes.ALTERNATIVE_INVESTMENT.equalsIgnoreCase(classCodeType)) {
      String kemid = holdingTaxLot.getKemid();
      if (dataDictionaryService.getAttributeForceUppercase(
          TransactionArchive.class, EndowPropertyConstants.TRANSACTION_ARCHIVE_KEM_ID)) {
        kemid = kemid.toUpperCase();
      }
      BigDecimal totalCashActivity =
          transactionArchiveDao.getTransactionArchivesTotalCashActivity(kemid, securityId);
      return (security.getSecurityValueByMarket().subtract(totalCashActivity));
    }
    // calculations for BONDS
    if (EndowConstants.ClassCodeTypes.BOND.equalsIgnoreCase(classCodeType)) {
      holdingMarketValue =
          KEMCalculationRoundingHelper.multiply(
              holdingTaxLot.getUnits(),
              security.getUnitValue(),
              EndowConstants.Scale.SECURITY_MARKET_VALUE);
      holdingMarketValue =
          KEMCalculationRoundingHelper.divide(
              holdingMarketValue,
              BigDecimal.valueOf(100),
              EndowConstants.Scale.SECURITY_MARKET_VALUE);
      return holdingMarketValue;
    }

    // other cases...
    holdingMarketValue =
        KEMCalculationRoundingHelper.multiply(
            holdingTaxLot.getUnits(),
            security.getUnitValue(),
            EndowConstants.Scale.SECURITY_MARKET_VALUE);

    return holdingMarketValue;
  }
  /**
   * calculates the remainder of fiscal year estimated income for stocks
   *
   * @param security
   * @param holdingTaxLot
   * @return amount
   */
  protected BigDecimal getRemainderOfFiscalYearEstimatedIncomeForStocks(
      Security security, HoldingTaxLot holdingTaxLot) {
    BigDecimal amount = BigDecimal.ZERO;

    if (ObjectUtils.isNull(security.getIncomeRate())
        || security.getIncomeRate().compareTo(BigDecimal.ZERO) == 0) {
      return amount;
    }

    String incomePayFrequency = security.getIncomePayFrequency();
    Date nextIncomeDueDate = security.getIncomeNextPayDate();

    if (ObjectUtils.isNull(nextIncomeDueDate)) {
      return amount;
    }

    Date fiscalYearEndDate = getFiscalYearEndDate();

    // BONDS - rule 4.a
    if (nextIncomeDueDate.after(fiscalYearEndDate)) {
      return BigDecimal.ZERO;
    }

    int numberOfMonthsRemaing = getNumberOfMonthsRemaining(fiscalYearEndDate, nextIncomeDueDate);

    if (nextIncomeDueDate.before(fiscalYearEndDate) && numberOfMonthsRemaing < 4) {
      return BigDecimal.ZERO;
    }

    long quartersLeftToFiscalYear = getQuartersLeftToFiscalYear(fiscalYearEndDate);

    // calculate holding units times security rate....
    amount =
        KEMCalculationRoundingHelper.multiply(
            holdingTaxLot.getUnits(),
            security.getIncomeRate(),
            EndowConstants.Scale.SECURITY_MARKET_VALUE);

    // now multiply the above amount by 4 to get amount for 4 quarters or for the year...
    amount =
        KEMCalculationRoundingHelper.divide(
            amount, BigDecimal.valueOf(4), EndowConstants.Scale.SECURITY_MARKET_VALUE);

    // now compute the amount for the quarters remaining in the fiscal year....
    amount =
        KEMCalculationRoundingHelper.multiply(
            amount,
            BigDecimal.valueOf(quartersLeftToFiscalYear),
            EndowConstants.Scale.SECURITY_MARKET_VALUE);

    return amount;
  }
  /**
   * @see
   *     org.kuali.kfs.module.endow.document.service.CurrentTaxLotService#getNextFiscalYearInvestmentIncome(HoldingTaxLot,
   *     String) Method to calculate next fiscal year investment income
   * @param securityId
   * @return nextFiscalyearInvestmentIncome
   */
  public BigDecimal getNextFiscalYearInvestmentIncome(
      HoldingTaxLot holdingTaxLot, String securityId) {
    BigDecimal nextFiscalyearInvestmentIncome = BigDecimal.ZERO;

    Security security = securityService.getByPrimaryKey(securityId);
    nextFiscalyearInvestmentIncome =
        KEMCalculationRoundingHelper.multiply(
            security.getNextFiscalYearDistributionAmount(),
            holdingTaxLot.getUnits(),
            EndowConstants.Scale.SECURITY_MARKET_VALUE);

    return nextFiscalyearInvestmentIncome;
  }
  /**
   * Updates the tax lots for the transaction line in the case the transaction sub type is non-cash
   * and the user entered the transaction amount. This method is specific to the Asset Decrease
   * document.
   *
   * @param isSubTypeCash
   * @param endowmentTransactionSecurity
   * @param transLine
   */
  private void updateTaxLotsForSubTypeNonCashAndTransAmtNonZero(
      boolean isUpdate,
      EndowmentTransactionSecurity endowmentTransactionSecurity,
      EndowmentTransactionLine transLine) {

    BigDecimal transactionUnits = transLine.getTransactionUnits().bigDecimalValue();
    BigDecimal totalTaxLotsUnits = BigDecimal.ZERO;
    BigDecimal transactionAmount = transLine.getTransactionAmount().bigDecimalValue();
    BigDecimal perUnitValue = BigDecimal.ZERO;

    List<HoldingTaxLot> holdingTaxLots = new ArrayList<HoldingTaxLot>();
    Map<Integer, HoldingTaxLot> lotsMap = new HashMap<Integer, HoldingTaxLot>();

    if (!isUpdate) {
      transLine.getTaxLotLines().clear();
      holdingTaxLots =
          taxLotService.getAllTaxLots(
              transLine.getKemid(),
              endowmentTransactionSecurity.getSecurityID(),
              endowmentTransactionSecurity.getRegistrationCode(),
              transLine.getTransactionIPIndicatorCode());
    } else {
      List<EndowmentTransactionTaxLotLine> existingTransactionLines = transLine.getTaxLotLines();
      for (EndowmentTransactionTaxLotLine endowmentTransactionTaxLotLine :
          existingTransactionLines) {
        HoldingTaxLot holdingTaxLot =
            taxLotService.getByPrimaryKey(
                transLine.getKemid(),
                endowmentTransactionSecurity.getSecurityID(),
                endowmentTransactionSecurity.getRegistrationCode(),
                endowmentTransactionTaxLotLine.getTransactionHoldingLotNumber(),
                transLine.getTransactionIPIndicatorCode());

        if (ObjectUtils.isNotNull(holdingTaxLot)) {
          holdingTaxLots.add(holdingTaxLot);
        }
      }

      transLine.getTaxLotLines().clear();
    }

    Map<KualiInteger, EndowmentTransactionTaxLotLine> decreaseHoldingTaxLots =
        new HashMap<KualiInteger, EndowmentTransactionTaxLotLine>();

    if (holdingTaxLots != null && holdingTaxLots.size() > 0) {
      boolean keepIntegers = true;
      // compute the total number of units for tax lots
      for (HoldingTaxLot holdingTaxLot : holdingTaxLots) {
        totalTaxLotsUnits = totalTaxLotsUnits.add(holdingTaxLot.getUnits());

        // 3. Calculate the number of units to be transacted in each lot
        // check if percentage and tax lot units are integers
        BigDecimal lotUnits = BigDecimal.ZERO;
        try {
          int lotUnitsInt = holdingTaxLot.getUnits().intValueExact();
        } catch (ArithmeticException ex) {
          keepIntegers = false;
        }
      }

      for (HoldingTaxLot holdingTaxLot : holdingTaxLots) {
        EndowmentTransactionTaxLotLine taxLotLine = new EndowmentTransactionTaxLotLine();
        taxLotLine.setDocumentLineNumber(transLine.getTransactionLineNumber());

        BigDecimal lotUnits = BigDecimal.ZERO;
        // 2. Calculate percentage each lot holds out of the total units
        BigDecimal percentage =
            KEMCalculationRoundingHelper.divide(holdingTaxLot.getUnits(), totalTaxLotsUnits, 5);

        lotUnits =
            KEMCalculationRoundingHelper.multiply(
                percentage, transLine.getTransactionUnits().bigDecimalValue(), 5);

        // IF all original units per lot are integers (no decimal values), the result is rounded to
        // the nearest
        // integer and stored with the five decimals as zero. If the original units are not all
        // integers, then the
        // value is rounded to five decimals and stored as the five decimal values.
        if (keepIntegers) {
          lotUnits = lotUnits.setScale(0, BigDecimal.ROUND_HALF_UP);
          lotUnits = lotUnits.setScale(5);
        }
        taxLotLine.setLotUnits(lotUnits);

        // 6. Calculate holding cost
        BigDecimal holdingCost =
            KEMCalculationRoundingHelper.multiply(percentage, transactionAmount, 2);
        taxLotLine.setLotHoldingCost(holdingCost);

        // set tax lot line lot number and acquired date
        taxLotLine.setTransactionHoldingLotNumber(holdingTaxLot.getLotNumber().intValue());
        taxLotLine.setKemid(transLine.getKemid());
        taxLotLine.setSecurityID(holdingTaxLot.getSecurityId());
        taxLotLine.setRegistrationCode(holdingTaxLot.getRegistrationCode());
        taxLotLine.setIpIndicator(holdingTaxLot.getIncomePrincipalIndicator());
        taxLotLine.setLotAcquiredDate(holdingTaxLot.getAcquiredDate());

        // set the new lot indicator
        taxLotLine.setNewLotIndicator(false);

        // add the new tax lot line to the transaction line tax lots
        addTaxLotLine(transLine, taxLotLine);
        lotsMap.put(taxLotLine.getTransactionHoldingLotNumber(), holdingTaxLot);
      }
      adjustUnitsAndAmountsForNonCashAndTransactionAmtNotZero(transLine);
    }
  }
  /**
   * Updates the tax lots for the transaction line in the case the accounting method is FIFO or
   * LIFO.
   *
   * @param isSubTypeCash
   * @param isFIFO
   * @param endowmentTransactionSecurity
   * @param transLine
   */
  private void updateTaxLotsForAccountingMethodFIFOorLIFO(
      boolean isSubTypeCash,
      boolean isUpdate,
      boolean isFIFO,
      EndowmentTransactionSecurity endowmentTransactionSecurity,
      EndowmentTransactionLine transLine) {
    BigDecimal transactionUnits = transLine.getTransactionUnits().bigDecimalValue();
    BigDecimal transactionAmount = BigDecimal.ZERO;
    BigDecimal perUnitVal = BigDecimal.ZERO;
    List<HoldingTaxLot> holdingTaxLots = new ArrayList<HoldingTaxLot>();

    if (!isUpdate) {
      transLine.getTaxLotLines().clear();
      holdingTaxLots =
          taxLotService.getAllTaxLotsOrderByAcquiredDate(
              transLine.getKemid(),
              endowmentTransactionSecurity.getSecurityID(),
              endowmentTransactionSecurity.getRegistrationCode(),
              transLine.getTransactionIPIndicatorCode(),
              isFIFO);
    } else {
      List<EndowmentTransactionTaxLotLine> existingTransactionLines = transLine.getTaxLotLines();
      for (EndowmentTransactionTaxLotLine endowmentTransactionTaxLotLine :
          existingTransactionLines) {
        HoldingTaxLot holdingTaxLot =
            taxLotService.getByPrimaryKey(
                transLine.getKemid(),
                endowmentTransactionSecurity.getSecurityID(),
                endowmentTransactionSecurity.getRegistrationCode(),
                endowmentTransactionTaxLotLine.getTransactionHoldingLotNumber(),
                transLine.getTransactionIPIndicatorCode());

        if (ObjectUtils.isNotNull(holdingTaxLot)) {
          holdingTaxLots.add(holdingTaxLot);
        }
      }
      transLine.getTaxLotLines().clear();
    }

    Map<KualiInteger, EndowmentTransactionTaxLotLine> decreaseHoldingTaxLots =
        new HashMap<KualiInteger, EndowmentTransactionTaxLotLine>();

    if (isSubTypeCash) {
      transactionAmount = transLine.getTransactionAmount().bigDecimalValue();
      // 1. Compute per unit value
      perUnitVal = KEMCalculationRoundingHelper.divide(transactionAmount, transactionUnits, 5);
    }

    BigDecimal remainingUnits = transactionUnits;

    if (holdingTaxLots != null && holdingTaxLots.size() > 0) {
      // compute the total number of units for tax lots
      for (HoldingTaxLot holdingTaxLot : holdingTaxLots) {
        EndowmentTransactionTaxLotLine taxLotLine = new EndowmentTransactionTaxLotLine();
        taxLotLine.setDocumentLineNumber(transLine.getTransactionLineNumber());

        // 2. Set the lot units

        // if transaction units is greater than the holding tax lot units then take all the holding
        // tax lot units
        if (remainingUnits.compareTo(holdingTaxLot.getUnits()) == 1) {
          taxLotLine.setLotUnits(holdingTaxLot.getUnits());
          remainingUnits = remainingUnits.subtract(holdingTaxLot.getUnits());
        }
        // if transaction units is less than the holding tax lot units then take just the remaining
        // number of units
        else {
          taxLotLine.setLotUnits(remainingUnits);
          remainingUnits = BigDecimal.ZERO;
        }

        // 4. Calculate the original unit value: cost/units
        BigDecimal originalUnitVal =
            KEMCalculationRoundingHelper.divide(
                holdingTaxLot.getCost(), holdingTaxLot.getUnits(), 5);

        // 5. Calculate original cost
        BigDecimal originalCost =
            KEMCalculationRoundingHelper.multiply(originalUnitVal, taxLotLine.getLotUnits(), 2);

        // set the tax lot holding cost
        taxLotLine.setLotHoldingCost(originalCost);

        // if sub type cash then the
        if (isSubTypeCash) {
          // 3. Calculate the value received for the units sold and round to 2 decimals
          BigDecimal receivedValue =
              KEMCalculationRoundingHelper.multiply(perUnitVal, taxLotLine.getLotUnits(), 2);
          // 6. Calculate the gain or loss
          calculateGainLoss(holdingTaxLot, taxLotLine, receivedValue, originalCost);
        }

        // set the lot number
        taxLotLine.setTransactionHoldingLotNumber(holdingTaxLot.getLotNumber().intValue());
        taxLotLine.setKemid(transLine.getKemid());
        taxLotLine.setSecurityID(holdingTaxLot.getSecurityId());
        taxLotLine.setRegistrationCode(holdingTaxLot.getRegistrationCode());
        taxLotLine.setIpIndicator(holdingTaxLot.getIncomePrincipalIndicator());
        // set the lot acquired date
        taxLotLine.setLotAcquiredDate(holdingTaxLot.getAcquiredDate());

        addTaxLotLine(transLine, taxLotLine);

        if (remainingUnits.compareTo(BigDecimal.ZERO) == 0) {
          break;
        }
      }
    }
  }
  /**
   * Adjusts the number of units if the total is different from the transaction line units.
   *
   * @param lotsMap
   * @param transLine
   * @param keepIntegers
   * @param isSubTypeCash
   * @param perUnitValue
   */
  private void adjustUnitsNumberAndAmountsForAverageBalance(
      Map<Integer, HoldingTaxLot> lotsMap,
      EndowmentTransactionLine transLine,
      boolean keepIntegers,
      boolean isSubTypeCash,
      BigDecimal perUnitValue) {
    // Adjust the number of units if the total is different from the transaction line units
    BigDecimal totalComputedTaxLotUnits = BigDecimal.ZERO;
    BigDecimal totalComputedCost = BigDecimal.ZERO;
    EndowmentTransactionTaxLotLine oldestTaxLotLine = null;

    if (transLine.getTaxLotLines() != null && transLine.getTaxLotLines().size() > 0) {
      for (EndowmentTransactionTaxLotLine taxLotLine : transLine.getTaxLotLines()) {
        BigDecimal lotUnits = taxLotLine.getLotUnits().negate();

        // calculate the total number of units to be decreased
        totalComputedTaxLotUnits = totalComputedTaxLotUnits.add(lotUnits);
        totalComputedCost = totalComputedCost.add(taxLotLine.getLotHoldingCost().negate());

        if (taxLotLine.getLotShortTermGainLoss() != null) {
          totalComputedCost = totalComputedCost.add(taxLotLine.getLotShortTermGainLoss());
        }

        if (taxLotLine.getLotLongTermGainLoss() != null) {
          totalComputedCost = totalComputedCost.add(taxLotLine.getLotLongTermGainLoss());
        }

        // keep the tax lot with the oldest acquired date so that we can adjust the units for that
        // one in case the
        // number of units needs and adjustment
        if (oldestTaxLotLine != null) {
          if (oldestTaxLotLine.getLotAcquiredDate().after(taxLotLine.getLotAcquiredDate())) {
            oldestTaxLotLine = taxLotLine;
          }
        } else {
          oldestTaxLotLine = taxLotLine;
        }
      }
    }

    // compare with the negated number of units on the transaction line because the units on the tax
    // lots have been negated
    if (totalComputedTaxLotUnits.compareTo(
            transLine.getTransactionUnits().bigDecimalValue().negate())
        != 0) {
      BigDecimal difUnits =
          transLine.getTransactionUnits().bigDecimalValue().subtract(totalComputedTaxLotUnits);
      oldestTaxLotLine.setLotUnits(oldestTaxLotLine.getLotUnits().add(difUnits.negate()));
      oldestTaxLotLine.setLotUnits(oldestTaxLotLine.getLotUnits().negate());

      if (isSubTypeCash) {
        // update totalComputedCost
        totalComputedCost =
            totalComputedCost.subtract(oldestTaxLotLine.getLotHoldingCost().negate());
        if (oldestTaxLotLine.getLotShortTermGainLoss() != null) {
          totalComputedCost =
              totalComputedCost.subtract(oldestTaxLotLine.getLotShortTermGainLoss());
        }

        if (oldestTaxLotLine.getLotLongTermGainLoss() != null) {
          totalComputedCost = totalComputedCost.subtract(oldestTaxLotLine.getLotLongTermGainLoss());
        }
      }

      HoldingTaxLot holdingTaxLot = lotsMap.get(oldestTaxLotLine.getTransactionHoldingLotNumber());
      BigDecimal originalUnitValue =
          KEMCalculationRoundingHelper.divide(holdingTaxLot.getCost(), holdingTaxLot.getUnits(), 5);
      BigDecimal originalCost =
          KEMCalculationRoundingHelper.multiply(
              oldestTaxLotLine.getLotUnits(), originalUnitValue, 2);
      oldestTaxLotLine.setLotHoldingCost(originalCost);

      if (isSubTypeCash) {
        // 4. Calculate the value received for units sold in each tax lot
        BigDecimal valueReceived =
            KEMCalculationRoundingHelper.multiply(oldestTaxLotLine.getLotUnits(), perUnitValue, 2);

        // 7. Calculate Gain or loss
        calculateGainLoss(holdingTaxLot, oldestTaxLotLine, valueReceived, originalCost);
        // update totalComputedCost
        totalComputedCost = totalComputedCost.add(oldestTaxLotLine.getLotHoldingCost());
        if (oldestTaxLotLine.getLotShortTermGainLoss() != null) {
          totalComputedCost = totalComputedCost.add(oldestTaxLotLine.getLotShortTermGainLoss());
        }

        if (oldestTaxLotLine.getLotLongTermGainLoss() != null) {
          totalComputedCost = totalComputedCost.add(oldestTaxLotLine.getLotLongTermGainLoss());
        }
      }
      oldestTaxLotLine.setLotHoldingCost(oldestTaxLotLine.getLotHoldingCost().negate());
      oldestTaxLotLine.setLotUnits(oldestTaxLotLine.getLotUnits().negate());
    }

    if (isSubTypeCash) {
      // compare total computed cost with the transaction line amount
      if (totalComputedCost.compareTo(transLine.getTransactionUnits().bigDecimalValue()) != 0) {
        BigDecimal difAmount =
            transLine.getTransactionAmount().bigDecimalValue().subtract(totalComputedCost);
        oldestTaxLotLine.setLotHoldingCost(
            oldestTaxLotLine.getLotHoldingCost().negate().add(difAmount));

        oldestTaxLotLine.setLotHoldingCost(oldestTaxLotLine.getLotHoldingCost().negate());
      }
    }
  }
  /**
   * Service Method to create a new current tax lot balance record and copy HoldingTaxLot record to
   * it
   *
   * @param holdingTaxLot
   * @return currentTaxLotBalance
   */
  public CurrentTaxLotBalance copyHoldingTaxLotToCurrentTaxLotBalance(HoldingTaxLot holdingTaxLot) {
    CurrentTaxLotBalance currentTaxLotBalance = new CurrentTaxLotBalance();

    currentTaxLotBalance.setKemid(holdingTaxLot.getKemid());
    currentTaxLotBalance.setSecurityId(holdingTaxLot.getSecurityId());
    currentTaxLotBalance.setRegistrationCode(holdingTaxLot.getRegistrationCode());
    currentTaxLotBalance.setLotNumber(holdingTaxLot.getLotNumber());
    currentTaxLotBalance.setIncomePrincipalIndicator(holdingTaxLot.getIncomePrincipalIndicator());

    currentTaxLotBalance.setUnits(holdingTaxLot.getUnits());
    currentTaxLotBalance.setCost(holdingTaxLot.getCost());
    currentTaxLotBalance.setAcquiredDate(holdingTaxLot.getAcquiredDate());
    currentTaxLotBalance.setPriorAccrual(holdingTaxLot.getPriorAccrual());
    currentTaxLotBalance.setCurrentAccrual(holdingTaxLot.getCurrentAccrual());
    currentTaxLotBalance.setLastTransactionDate(holdingTaxLot.getLastTransactionDate());

    return currentTaxLotBalance;
  }