/**
   * If the SECURITY_ID has a class code type A (Alternative Investments), the system must validate
   * that the total END_SEC_T: SEC_CVAL for the SECURITY_ID plus the END_TRAN_LN_T:TRAN_AMT does not
   * exceed the value in END_SEC_T: CMTMNT_AMT for the Security. If it does, the transaction should
   * not be allowed.
   *
   * @return true if valid, false otherwise
   */
  protected boolean validateTaxLotsCostAndTransactionAmountLessOrEqualToSecurityCommitment(
      AssetIncreaseDocument assetIncreaseDocument) {
    boolean isValid = true;

    if (assetIncreaseDocument.getTargetTransactionSecurity() != null
        && assetIncreaseDocument.getTargetTransactionSecurity().getSecurity() != null
        && assetIncreaseDocument.getTargetTransactionSecurity().getSecurity().getClassCode() != null
        && EndowConstants.ClassCodeTypes.ALTERNATIVE_INVESTMENT.equalsIgnoreCase(
            assetIncreaseDocument
                .getTargetTransactionSecurity()
                .getSecurity()
                .getClassCode()
                .getClassCodeType())) {

      BigDecimal totalTransactionLinesAmt = BigDecimal.ZERO;
      BigDecimal securityCommitmentAmt = BigDecimal.ZERO;
      BigDecimal securityCarryValue = BigDecimal.ZERO;

      if (assetIncreaseDocument.getTargetTransactionLines() != null) {
        for (EndowmentTransactionLine transactionLine :
            assetIncreaseDocument.getTargetTransactionLines()) {

          // add to total amount
          totalTransactionLinesAmt =
              totalTransactionLinesAmt.add(
                  transactionLine.getTransactionAmount().bigDecimalValue());
        }
      }

      if (assetIncreaseDocument.getTargetTransactionSecurity() != null
          && assetIncreaseDocument.getTargetTransactionSecurity().getSecurity() != null) {
        if (assetIncreaseDocument.getTargetTransactionSecurity().getSecurity().getCommitmentAmount()
            != null) {
          securityCommitmentAmt =
              assetIncreaseDocument
                  .getTargetTransactionSecurity()
                  .getSecurity()
                  .getCommitmentAmount();
        }
        if (assetIncreaseDocument.getTargetTransactionSecurity().getSecurity().getCarryValue()
            != null) {
          securityCarryValue =
              assetIncreaseDocument.getTargetTransactionSecurity().getSecurity().getCarryValue();
        }
      }

      isValid =
          (securityCarryValue.add(totalTransactionLinesAmt)).compareTo(securityCommitmentAmt) <= 0;

      if (!isValid) {
        putFieldError(
            getErrorPrefix(assetIncreaseDocument.getTargetTransactionLine(0), 0)
                + EndowPropertyConstants.TRANSACTION_LINE_TRANSACTION_AMOUNT,
            EndowKeyConstants.EndowmentTransactionDocumentConstants
                .ERROR_TRANSACTION_SECURITY_COMMITMENT_AMT);
      }
    }

    return isValid;
  }
  /**
   * Validates that validateKemId returns false when the kemid on the transaction line does not
   * exist in the db.
   */
  public void testValidateKemId_False() {
    EndowmentTransactionLine endowmentTransactionLine =
        EndowmentTransactionLineFixture.ENDOWMENT_TRANSACTIONAL_LINE_INCOME
            .createEndowmentTransactionLine(false);
    endowmentTransactionLine.setKemid(EndowTestConstants.INVALID_KEMID);

    assertFalse(
        rule.validateKemId(
            endowmentTransactionLine, rule.getErrorPrefix(endowmentTransactionLine, -1)));
  }
  /**
   * Validates that canKEMIDHaveAPrincipalTransaction returns false when the transaction line IP
   * indicator is P and the principal restriction code is NA.
   */
  public void testKemidPrincRestrNotNAWhenTransLinePrincipal_False() {
    EndowmentTransactionLine endowmentTransactionLine =
        EndowmentTransactionLineFixture.ENDOWMENT_TRANSACTIONAL_LINE_PRINCIPAL
            .createEndowmentTransactionLine(false);
    KEMID kemid = KemIdFixture.NA_PRINC_RESTR_KEMID_RECORD.createKemidRecord();
    endowmentTransactionLine.setKemid(kemid.getKemid());
    endowmentTransactionLine.setKemidObj(kemid);

    assertFalse(
        rule.canKEMIDHaveAPrincipalTransaction(
            endowmentTransactionLine, rule.getErrorPrefix(endowmentTransactionLine, -1)));
  }
  /**
   * Sets the transaction line amount to be the total amount of all tax lot lines times negative 1.
   * This is applied in case the transaction sub type is non-cash.
   *
   * @param transactionLine
   */
  private void setTransactionLineTotal(EndowmentTransactionLine transactionLine) {
    List<EndowmentTransactionTaxLotLine> taxLots = transactionLine.getTaxLotLines();
    BigDecimal totalAmount = BigDecimal.ZERO;
    if (taxLots != null && taxLots.size() > 0) {
      for (EndowmentTransactionTaxLotLine taxLot : taxLots) {
        totalAmount = totalAmount.add(taxLot.getLotHoldingCost());
      }
    }

    totalAmount = totalAmount.negate();

    transactionLine.setTransactionAmount(new KualiDecimal(totalAmount));
  }
  /**
   * Validates that validateEndowmentTransactionTypeCode returns false when the etran code type is
   * not income or expense.
   */
  public void testEtranCodeIncomeOrExpense_False() {

    EndowmentTransactionLine endowmentTargetTransactionLine =
        EndowmentTransactionLineFixture.ENDOWMENT_TRANSACTIONAL_LINE_POSITIVE_AMT
            .createEndowmentTransactionLine(false);
    EndowmentTransactionCode endowmentTransactionCode =
        EndowmentTransactionCodeFixture.ASSET_TRANSACTION_CODE.createEndowmentTransactionCode();
    endowmentTargetTransactionLine.setEtranCode(endowmentTransactionCode.getCode());
    endowmentTargetTransactionLine.setEtranCodeObj(endowmentTransactionCode);

    assertFalse(
        rule.validateEndowmentTransactionTypeCode(
            document,
            endowmentTargetTransactionLine,
            rule.getErrorPrefix(endowmentTargetTransactionLine, -1)));
  }
  /**
   * Adds a tax lot line to a transaction line but first it negates the units and cost.
   *
   * @param transactionLine
   * @param taxLotLine
   */
  private void addTaxLotLine(
      EndowmentTransactionLine transactionLine, EndowmentTransactionTaxLotLine taxLotLine) {
    // negate units and cost
    taxLotLine.setLotUnits(taxLotLine.getLotUnits().negate());
    taxLotLine.setLotHoldingCost(taxLotLine.getLotHoldingCost().negate());

    // add the tax lot line to the transaction line
    transactionLine.getTaxLotLines().add(taxLotLine);
  }
  /**
   * Validates that validateChartMatch returns false when etran code gl chart does not match the
   * chart for KEMID general ledger account.
   */
  public void testKemidEtranCodeMatch_False() {
    EndowmentTransactionLine endowmentTargetTransactionLine =
        EndowmentTransactionLineFixture.ENDOWMENT_TRANSACTIONAL_LINE_INCOME
            .createEndowmentTransactionLine(false);
    EndowmentTransactionCode endowmentTransactionCode =
        EndowmentTransactionCodeFixture.INCOME_TRANSACTION_CODE.createEndowmentTransactionCode();
    KEMID kemid = KemIdFixture.OPEN_KEMID_RECORD.createKemidRecord();
    GLLink glLink = GLLinkFixture.GL_LINK_UA_CHART.createGLLink();
    KemidGeneralLedgerAccount generalLedgerAccount =
        KemidGeneralLedgerAccountFixture.KEMID_GL_ACCOUNT.createKemidGeneralLedgerAccount();

    kemid.getKemidGeneralLedgerAccounts().add(generalLedgerAccount);
    endowmentTransactionCode.getGlLinks().add(glLink);
    endowmentTargetTransactionLine.setKemid(kemid.getKemid());
    endowmentTargetTransactionLine.setKemidObj(kemid);
    endowmentTargetTransactionLine.setEtranCode(endowmentTransactionCode.getCode());
    endowmentTargetTransactionLine.setEtranCodeObj(endowmentTransactionCode);

    assertFalse(
        rule.validateChartMatch(
            endowmentTargetTransactionLine,
            rule.getErrorPrefix(endowmentTargetTransactionLine, -1)));
  }
  /**
   * Adjusts the number of units and the amounts if the total is different from the transaction line
   * units. The correction will be done on the oldest tax lot. This method is specific to the Asset
   * Decrease document and is used in updateTaxLotsForSubTypeNonCashAndTransAmtNonZero().
   *
   * @param transLine
   * @param keepIntegers
   */
  private void adjustUnitsAndAmountsForNonCashAndTransactionAmtNotZero(
      EndowmentTransactionLine transLine) {
    // 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()));

      BigDecimal difAmount =
          transLine.getTransactionAmount().bigDecimalValue().subtract(totalComputedCost);
      oldestTaxLotLine.setLotHoldingCost(
          oldestTaxLotLine.getLotHoldingCost().add(difAmount.negate()));
    }
  }
  /**
   * @see
   *     org.kuali.kfs.module.endow.document.service.UpdateTaxLotsBasedOnAccMethodAndTransSubtypeService#updateTransactionLineTaxLots(boolean,
   *     org.kuali.kfs.module.endow.document.EndowmentTaxLotLinesDocument,
   *     org.kuali.kfs.module.endow.businessobject.EndowmentTransactionLine)
   */
  public void updateTransactionLineTaxLots(
      boolean isUpdate,
      EndowmentTaxLotLinesDocument endowmentTaxLotLinesDocument,
      EndowmentTransactionLine transLine) {

    EndowmentTransactionSecurity endowmentTransactionSecurity =
        endowmentTaxLotLinesDocument.getSourceTransactionSecurity();
    String accountingMethod =
        parameterService.getParameterValueAsString(
            KfsParameterConstants.ENDOWMENT_ALL.class,
            EndowParameterKeyConstants.TAX_LOTS_ACCOUNTING_METHOD);
    Security security =
        securityService.getByPrimaryKey(endowmentTransactionSecurity.getSecurityID());

    if (ObjectUtils.isNotNull(security)) {
      // In the Asset Decrease document, if transaction sub type is non-cash and IF the user inserts
      // an amount in the
      // transaction amount, that amount along with the units sill be used to generate the
      // transaction tax lot lines. The
      // amount will not be calculated by the addition of the transaction line. In the event the
      // user has entered an amount in
      // the transaction amount field, that amount along with the units will be spread
      // proportionately among the tax lots.
      if (endowmentTaxLotLinesDocument instanceof AssetDecreaseDocument
          && EndowConstants.TransactionSubTypeCode.NON_CASH.equalsIgnoreCase(
              endowmentTaxLotLinesDocument.getTransactionSubTypeCode())
          && transLine.getTransactionAmount() != null
          && transLine.getTransactionAmount().bigDecimalValue().compareTo(BigDecimal.ZERO) != 0) {
        updateTaxLotsForSubTypeNonCashAndTransAmtNonZero(
            isUpdate, endowmentTransactionSecurity, transLine);
      } else {

        if (EndowConstants.TaxLotsAccountingMethodOptions.AVERAGE_BALANCE.equalsIgnoreCase(
                accountingMethod)
            || (EndowConstants.TaxLotsAccountingMethodOptions.FIFO.equalsIgnoreCase(
                    accountingMethod)
                && !security.getClassCode().isTaxLotIndicator())) {
          if (EndowConstants.TransactionSubTypeCode.CASH.equalsIgnoreCase(
              endowmentTaxLotLinesDocument.getTransactionSubTypeCode())) {
            updateTaxLotsForAccountingMethodAverageBalance(
                true, isUpdate, endowmentTransactionSecurity, transLine);
          }
          if (EndowConstants.TransactionSubTypeCode.NON_CASH.equalsIgnoreCase(
              endowmentTaxLotLinesDocument.getTransactionSubTypeCode())) {
            updateTaxLotsForAccountingMethodAverageBalance(
                false, isUpdate, endowmentTransactionSecurity, transLine);
            setTransactionLineTotal(transLine);
          }
        }

        if ((EndowConstants.TaxLotsAccountingMethodOptions.FIFO.equalsIgnoreCase(accountingMethod)
                || EndowConstants.TaxLotsAccountingMethodOptions.LIFO.equalsIgnoreCase(
                    accountingMethod))
            && security.getClassCode().isTaxLotIndicator()) {
          boolean isFIFO =
              EndowConstants.TaxLotsAccountingMethodOptions.FIFO.equalsIgnoreCase(accountingMethod);

          if (EndowConstants.TransactionSubTypeCode.CASH.equalsIgnoreCase(
              endowmentTaxLotLinesDocument.getTransactionSubTypeCode())) {
            updateTaxLotsForAccountingMethodFIFOorLIFO(
                true, isUpdate, isFIFO, endowmentTransactionSecurity, transLine);
          }
          if (EndowConstants.TransactionSubTypeCode.NON_CASH.equalsIgnoreCase(
              endowmentTaxLotLinesDocument.getTransactionSubTypeCode())) {
            updateTaxLotsForAccountingMethodFIFOorLIFO(
                false, isUpdate, isFIFO, endowmentTransactionSecurity, transLine);
            setTransactionLineTotal(transLine);
          }
        }
      }
    }
  }
  /**
   * 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());
      }
    }
  }