gt:         if (nonpositiveTotalError != null) {
context: /*
   * The Kuali Financial System, a comprehensive financial management system for higher education.
   *
   * Copyright 2005-2017 Kuali, Inc.
   *
   * This program is free software: you can redistribute it and/or modify
   * it under the terms of the GNU Affero General Public License as
   * published by the Free Software Foundation, either version 3 of the
   * License, or (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU Affero General Public License for more details.
   *
   * You should have received a copy of the GNU Affero General Public License
   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
   */
  package org.kuali.kfs.module.ec.batch.service.impl;
  
  import org.apache.commons.lang.StringUtils;
  import org.kuali.kfs.integration.ld.LaborLedgerBalance;
  import org.kuali.kfs.integration.ld.LaborLedgerBalanceForEffortCertification;
  import org.kuali.kfs.integration.ld.LaborModuleService;
  import org.kuali.kfs.krad.service.BusinessObjectService;
  import org.kuali.kfs.krad.service.KualiModuleService;
  import org.kuali.kfs.module.ec.EffortConstants;
  import org.kuali.kfs.module.ec.EffortConstants.ExtractProcess;
  import org.kuali.kfs.module.ec.EffortConstants.SystemParameters;
  import org.kuali.kfs.module.ec.EffortKeyConstants;
  import org.kuali.kfs.module.ec.EffortPropertyConstants;
  import org.kuali.kfs.module.ec.batch.service.EffortCertificationExtractService;
  import org.kuali.kfs.module.ec.businessobject.EffortCertificationDocumentBuild;
  import org.kuali.kfs.module.ec.businessobject.EffortCertificationReportDefinition;
  import org.kuali.kfs.module.ec.document.EffortCertificationDocument;
  import org.kuali.kfs.module.ec.document.validation.impl.LedgerBalanceFieldValidator;
  import org.kuali.kfs.module.ec.service.EffortCertificationDocumentBuildService;
  import org.kuali.kfs.module.ec.service.EffortCertificationReportDefinitionService;
  import org.kuali.kfs.module.ec.service.EffortCertificationReportService;
  import org.kuali.kfs.module.ec.util.EffortCertificationParameterFinder;
  import org.kuali.kfs.module.ec.util.ExtractProcessReportDataHolder;
  import org.kuali.kfs.module.ec.util.LedgerBalanceConsolidationHelper;
  import org.kuali.kfs.module.ec.util.LedgerBalanceWithMessage;
  import org.kuali.kfs.sys.KFSPropertyConstants;
  import org.kuali.kfs.sys.Message;
  import org.kuali.kfs.sys.MessageBuilder;
  import org.kuali.kfs.sys.service.OptionsService;
  import org.kuali.kfs.sys.service.UniversityDateService;
  import org.kuali.rice.core.api.datetime.DateTimeService;
  import org.kuali.rice.core.api.util.type.KualiDecimal;
  import org.springframework.transaction.annotation.Transactional;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  
  /**
   * This is an implementation of Effort Certification Extract process, which extracts Labor Ledger records of the employees who were
   * paid on a grant or cost shared during the selected reporting period, and generates effort certification build/temporary document.
   * Its major tasks include:
   * <p>
   * <li>Identify employees who were paid on a grant or cost shared;</li>
   * <li>Select qualified Labor Ledger records for each identified employee;</li>
   * <li>Generate effort certification build document from the selected Labor Ledger records for each employee.</li>
   */
  @Transactional
  public class EffortCertificationExtractServiceImpl implements EffortCertificationExtractService {
      protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(EffortCertificationExtractServiceImpl.class);
  
      protected BusinessObjectService businessObjectService;
      protected OptionsService optionsService;
      protected DateTimeService dateTimeService;
      protected UniversityDateService universityDateService;
  
      protected LaborModuleService laborModuleService;
      protected KualiModuleService kualiModuleService;
  
      protected EffortCertificationDocumentBuildService effortCertificationDocumentBuildService;
      protected EffortCertificationReportService effortCertificationReportService;
      protected EffortCertificationReportDefinitionService effortCertificationReportDefinitionService;
  
      /**
       * @see org.kuali.kfs.module.ec.batch.service.EffortCertificationExtractService#extract()
       */
  
      @Override
      public void extract() {
          Integer fiscalYear = EffortCertificationParameterFinder.getExtractReportFiscalYear();
          String reportNumber = EffortCertificationParameterFinder.getExtractReportNumber();
  
          this.extract(fiscalYear, reportNumber);
      }
  
      /**
       * @see org.kuali.kfs.module.ec.batch.service.EffortCertificationExtractService#extract(java.lang.Integer, java.lang.String)
       */
  
      @Override
      public void extract(Integer fiscalYear, String reportNumber) {
          Map<String, String> fieldValues = EffortCertificationReportDefinition.buildKeyMap(fiscalYear, reportNumber);
  
          // check if a report has been defined and its documents have not been generated.
          LOG.info("Validating and retrieving report definition ...");
          String errorMessage = this.validateReportDefintion(fiscalYear, reportNumber);
          errorMessage = StringUtils.isNotEmpty(errorMessage) ? errorMessage : this.existEffortCertificationDocument(fieldValues);
          if (StringUtils.isNotEmpty(errorMessage)) {
              LOG.fatal(errorMessage);
              throw new IllegalArgumentException(errorMessage);
          }
  
          Map<String, Collection<String>> parameters = this.getSystemParameters();
          parameters.put(ExtractProcess.EXPENSE_OBJECT_TYPE, getExpenseObjectTypeCodes(fiscalYear));
  
          EffortCertificationReportDefinition reportDefinition = effortCertificationReportDefinitionService.findReportDefinitionByPrimaryKey(fieldValues);
          ExtractProcessReportDataHolder reportDataHolder = this.initializeReportData(reportDefinition);
  
          LOG.info("Finding employees eligible for effort certification ...");
          List<String> employees = this.findEmployeesEligibleForEffortCertification(reportDefinition);
  
          LOG.info("Generating document build for the eligible employees ...");
          effortCertificationDocumentBuildService.removeExistingDocumentBuild(fieldValues);
          this.generateDocumentBuild(reportDefinition, employees, reportDataHolder, parameters);
  
          LOG.info("Generating report for extract process ...");
          Date runDate = dateTimeService.getCurrentSqlDate();
          effortCertificationReportService.generateReportForExtractProcess(reportDataHolder, runDate);
      }
  
      /**
       * @see org.kuali.kfs.module.ec.batch.service.EffortCertificationExtractService#extract(java.lang.String,
       * org.kuali.kfs.module.ec.businessobject.EffortCertificationReportDefinition)
       */
  
      @Override
      public EffortCertificationDocumentBuild extract(String emplid, EffortCertificationReportDefinition reportDefinition) {
          Map<String, Collection<String>> parameters = this.getSystemParameters();
          parameters.put(ExtractProcess.EXPENSE_OBJECT_TYPE, getExpenseObjectTypeCodes(reportDefinition.getUniversityFiscalYear()));
  
          List<String> positionGroupCodes = effortCertificationReportDefinitionService.findPositionObjectGroupCodes(reportDefinition);
          Integer postingYear = universityDateService.getCurrentFiscalYear();
          ExtractProcessReportDataHolder reportDataHolder = this.initializeReportData(reportDefinition);
  
          return this.generateDocumentBuildByEmployee(postingYear, emplid, positionGroupCodes, reportDefinition, reportDataHolder, parameters);
      }
  
      /**
       * @see org.kuali.kfs.module.ec.batch.service.EffortCertificationExtractService#isEmployeesEligibleForEffortCertification(java.lang.String,
       * org.kuali.kfs.module.ec.businessobject.EffortCertificationReportDefinition)
       */
  
      @Override
      public boolean isEmployeeEligibleForEffortCertification(String emplid, EffortCertificationReportDefinition reportDefinition) {
          Map<String, Set<String>> earnCodePayGroups = effortCertificationReportDefinitionService.findReportEarnCodePayGroups(reportDefinition);
          List<String> balanceTypeList = EffortConstants.ELIGIBLE_BALANCE_TYPES_FOR_EFFORT_REPORT;
          Map<Integer, Set<String>> reportPeriods = reportDefinition.getReportPeriods();
  
          return laborModuleService.isEmployeeWithPayType(emplid, reportPeriods, balanceTypeList, earnCodePayGroups);
      }
  
      /**
       * @see org.kuali.kfs.module.ec.batch.service.EffortCertificationExtractService#findEmployeesEligibleForEffortCertification(org.kuali.kfs.module.ec.businessobject.EffortCertificationReportDefinition)
       */
  
      @Override
      public List<String> findEmployeesEligibleForEffortCertification(EffortCertificationReportDefinition reportDefinition) {
          Map<String, Set<String>> earnCodePayGroups = effortCertificationReportDefinitionService.findReportEarnCodePayGroups(reportDefinition);
          List<String> balanceTypeList = EffortConstants.ELIGIBLE_BALANCE_TYPES_FOR_EFFORT_REPORT;
          Map<Integer, Set<String>> reportPeriods = reportDefinition.getReportPeriods();
  
          return laborModuleService.findEmployeesWithPayType(reportPeriods, balanceTypeList, earnCodePayGroups);
      }
  
      /**
       * check if a report has been defined. The combination of fiscal year and report number can determine a report definition.
       *
       * @param fiscalYear   the the given fiscalYear
       * @param reportNumber the the given report number
       * @return a message if a report has not been defined or its documents have been generated; otherwise, return null
       */
      protected String validateReportDefintion(Integer fiscalYear, String reportNumber) {
          EffortCertificationReportDefinition reportDefinition = new EffortCertificationReportDefinition();
          reportDefinition.setUniversityFiscalYear(fiscalYear);
          reportDefinition.setEffortCertificationReportNumber(reportNumber);
  
          String errorMessage = effortCertificationReportDefinitionService.validateEffortCertificationReportDefinition(reportDefinition);
          return StringUtils.isNotEmpty(errorMessage) ? errorMessage : null;
      }
  
      /**
       * check if the documents for the given report definition have not been generated. The combination of fiscal year and report
       * number can determine a report definition.
       *
       * @param fieldValues the map containing fiscalYear and report number
       * @return a message if the documents have been generated; otherwise, return null
       */
      protected String existEffortCertificationDocument(Map<String, String> fieldValues) {
          String fiscalYear = fieldValues.get(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR);
          String reportNumber = fieldValues.get(EffortPropertyConstants.EFFORT_CERTIFICATION_REPORT_NUMBER);
  
          // check if any document has been generated for the selected report definition
          int countOfDocuments = businessObjectService.countMatching(EffortCertificationDocument.class, fieldValues);
          if (countOfDocuments > 0) {
              return MessageBuilder.buildMessageWithPlaceHolder(EffortKeyConstants.ERROR_REPORT_DOCUMENT_EXIST, reportNumber, fiscalYear).getMessage();
          }
  
          return null;
      }
  
      /**
       * generate a document (build) as well as their detail lines for the given employees
       *
       * @param reportDefinition the given report definition
       * @param employees        the given employees
       * @param reportDataHolder the holder of report data
       * @param parameters       the given system parameters
       */
      protected void generateDocumentBuild(EffortCertificationReportDefinition reportDefinition, List<String> employees, ExtractProcessReportDataHolder reportDataHolder, Map<String, Collection<String>> parameters) {
          List<String> positionGroupCodes = effortCertificationReportDefinitionService.findPositionObjectGroupCodes(reportDefinition);
          Integer postingYear = universityDateService.getCurrentFiscalYear();
  
          for (String emplid : employees) {
              EffortCertificationDocumentBuild document = this.generateDocumentBuildByEmployee(postingYear, emplid, positionGroupCodes, reportDefinition, reportDataHolder, parameters);
  
              if (document != null) {
                  List<EffortCertificationDocumentBuild> documents = new ArrayList<EffortCertificationDocumentBuild>();
                  documents.add(document);
  
                  businessObjectService.save(documents);
  
                  reportDataHolder.updateBasicStatistics(ExtractProcess.NUM_DETAIL_LINES_WRITTEN, this.getCountOfDetailLines(documents));
                  reportDataHolder.updateBasicStatistics(ExtractProcess.NUM_CERTIFICATIONS_WRITTEN, documents.size());
              }
          }
          reportDataHolder.updateBasicStatistics(ExtractProcess.NUM_EMPLOYEES_SELECTED, employees.size());
          reportDataHolder.updateBasicStatistics(ExtractProcess.NUM_ERRORS_FOUND, reportDataHolder.getLedgerBalancesWithMessage().size());
      }
  
      /**
       * extract the qualified labor ledger balance records of the given employee with the given report periods.
       *
       * @param emplid             the given employee id
       * @param positionGroupCodes the specified position group codes
       * @param reportDefinition   the specified report definition
       * @param parameters         the system parameters setup in front
       * @param reportDataHolder   the given holder that contains any information to be written into the working report
       * @return the qualified labor ledger balance records of the given employee
       */
      protected List<LaborLedgerBalance> getQualifiedLedgerBalances(String emplid, List<String> positionGroupCodes, EffortCertificationReportDefinition reportDefinition, ExtractProcessReportDataHolder reportDataHolder, Map<String, Collection<String>> parameters) {
          List<LedgerBalanceWithMessage> ledgerBalancesWithMessage = reportDataHolder.getLedgerBalancesWithMessage();
          Map<Integer, Set<String>> reportPeriods = reportDefinition.getReportPeriods();
  
          // select ledger balances by the given employee and other criteria
          Collection<LaborLedgerBalance> ledgerBalances = this.selectLedgerBalanceForEmployee(emplid, positionGroupCodes, reportDefinition, parameters);
          reportDataHolder.updateBasicStatistics(ExtractProcess.NUM_BALANCES_READ, ledgerBalances.size());
  
          // clear up the ledger balance collection
          List<LaborLedgerBalance> validLedgerBalances = this.removeUnqualifiedLedgerBalances(ledgerBalances, reportDefinition, reportDataHolder);
          reportDataHolder.updateBasicStatistics(ExtractProcess.NUM_BALANCES_SELECTED, validLedgerBalances.size());
  
          // consolidate the pre-qualified ledger balances
          List<LaborLedgerBalance> consolidatedLedgerBalances = this.cosolidateLedgerBalances(validLedgerBalances, reportDefinition);
  
          // check the employee according to the pre-qualified ledger balances
          boolean isQualifiedEmployee = this.checkEmployeeBasedOnLedgerBalances(emplid, consolidatedLedgerBalances, reportDefinition, reportDataHolder, parameters);
  
          // abort all ledger balances if the employee is not qualified; otherwise, adopt the consolidated balances
          List<LaborLedgerBalance> qualifiedLedgerBalances = isQualifiedEmployee ? consolidatedLedgerBalances : null;
  
          return qualifiedLedgerBalances;
      }
  
      /**
       * remove the ledger balances without valid account, and nonzero total amount
       *
       * @param ledgerBalances   the given ledger balances
       * @param reportDefinition the given report definition
       * @param reportDataHolder the given holder that contains any information to be written into the working report
       */
      protected List<LaborLedgerBalance> removeUnqualifiedLedgerBalances(Collection<LaborLedgerBalance> ledgerBalances, EffortCertificationReportDefinition reportDefinition, ExtractProcessReportDataHolder reportDataHolder) {
          Map<Integer, Set<String>> reportPeriods = reportDefinition.getReportPeriods();
          List<LedgerBalanceWithMessage> ledgerBalancesWithMessage = reportDataHolder.getLedgerBalancesWithMessage();
  
          List<LaborLedgerBalance> validLedgerBalances = new ArrayList<LaborLedgerBalance>();
  
          for (LaborLedgerBalance balance : ledgerBalances) {
              // within the given periods, the total amount of a single balance cannot be ZERO
              Message errorAmountMessage = LedgerBalanceFieldValidator.isNonZeroAmountBalanceWithinReportPeriod(balance, reportPeriods);
  
              // every balance record must be associated with a valid account
              Message invalidAccountMessage = LedgerBalanceFieldValidator.hasValidAccount(balance);
              if (invalidAccountMessage != null) {
                  this.reportInvalidLedgerBalance(ledgerBalancesWithMessage, balance, invalidAccountMessage);
              }
  
              if (errorAmountMessage == null && invalidAccountMessage == null) {
                  validLedgerBalances.add(balance);
              }
          }
  
          ledgerBalances = null;
          return validLedgerBalances;
      }
  
      /**
       * check all ledger balances of the given employee and see if they can meet certain requirements. If not, the employee would be
       * unqualified for effort reporting
       *
       * @param emplid           the given employee id
       * @param ledgerBalances   the all pre-qualified ledger balances of the employee
       * @param reportDefinition the specified report definition
       * @param reportDataHolder the given holder that contains any information to be written into the working report
       * @param parameters       the system parameters setup in front
       * @return true if all ledger balances as whole meet requirements; otherwise, return false
       */
      protected boolean checkEmployeeBasedOnLedgerBalances(String emplid, List<LaborLedgerBalance> ledgerBalances, EffortCertificationReportDefinition reportDefinition, ExtractProcessReportDataHolder reportDataHolder, Map<String, Collection<String>> parameters) {
          if (ledgerBalances == null || ledgerBalances.isEmpty()) {
              return false;
          }
  
          Map<Integer, Set<String>> reportPeriods = reportDefinition.getReportPeriods();
          List<LedgerBalanceWithMessage> ledgerBalancesWithMessage = reportDataHolder.getLedgerBalancesWithMessage();
  
          // the total amount of all balances must be positive; otherwise, not to generate effort report for the employee
          Message nonpositiveTotalError = LedgerBalanceFieldValidator.isTotalAmountPositive(ledgerBalances, reportPeriods);
  