/* * Copyright (c) 2005-2011 Grameen Foundation USA * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing * permissions and limitations under the License. * * See also http://www.apache.org/licenses/LICENSE-2.0.html for an * explanation of the license and how it is applied. */ package org.mifos.application.importexport.servicefacade; import java.io.FileInputStream; import java.io.InputStream; import java.math.BigInteger; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeSet; import org.hibernate.Query; import org.apache.commons.lang.StringUtils; import org.joda.time.DateTime; import org.mifos.accounts.api.TransactionImport; import org.mifos.accounts.business.AccountTrxnEntity; import org.mifos.accounts.servicefacade.AccountServiceFacade; import org.mifos.accounts.servicefacade.UserContextFactory; import org.mifos.application.admin.servicefacade.MonthClosingServiceFacade; import org.mifos.application.importexport.business.ImportedFilesEntity; import org.mifos.application.importexport.business.service.ImportedFilesService; import org.mifos.application.master.MessageLookup; import org.mifos.application.servicefacade.ApplicationContextProvider; import org.mifos.application.servicefacade.ListItem; import org.mifos.core.MifosRuntimeException; import org.mifos.customers.personnel.business.PersonnelBO; import org.mifos.customers.personnel.persistence.PersonnelDao; import org.mifos.dto.domain.AccountTrxDto; import org.mifos.dto.domain.ParseResultDto; import org.mifos.dto.domain.UserReferenceDto; import org.mifos.dto.screen.ImportedFileDto; import org.mifos.framework.hibernate.helper.StaticHibernateUtil; import org.mifos.framework.plugin.PluginManager; import org.mifos.framework.util.helpers.Money; import org.mifos.security.MifosUser; import org.mifos.security.util.UserContext; import org.mifos.service.BusinessRuleException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.context.SecurityContextHolder; public class ImportTransactionsServiceFacadeWebTier implements ImportTransactionsServiceFacade { private static final Logger logger = LoggerFactory.getLogger(ImportTransactionsServiceFacadeWebTier.class); private static final String IMPORT_UNDONE = "Import file has been phased out"; private static final String INVALID_TRXN = "INVALID_TRXN"; private static final String VALID_TRXN = "VALID_TRXN"; private ImportedFilesService importedFilesService; private PersonnelDao personnelDao; private AccountServiceFacade accountServiceFacade; private MonthClosingServiceFacade monthClosingServiceFacade; @Autowired public ImportTransactionsServiceFacadeWebTier(ImportedFilesService importedFilesService, PersonnelDao personnelDao, AccountServiceFacade accountServiceFacade, MonthClosingServiceFacade monthClosingServiceFacade) { this.importedFilesService = importedFilesService; this.personnelDao = personnelDao; this.accountServiceFacade = accountServiceFacade; this.monthClosingServiceFacade = monthClosingServiceFacade; } @Override public boolean isAlreadyImported(String importTransactionsFileName) { ImportedFilesEntity importedFile = importedFilesService.getImportedFileByName(importTransactionsFileName); if (importedFile != null) { logger.debug(importTransactionsFileName + " has already been submitted"); logger.debug("Submitted by" + importedFile.getSubmittedBy()); logger.debug("Submitted on" + importedFile.getSubmittedOn()); return true; } return false; } @Override public void saveImportedFileName(String importTransactionsFileName, String importPluginClassname, List<AccountTrxDto> idsToUndoImport) { MifosUser mifosUser = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); UserContext userContext = new UserContextFactory().create(mifosUser); PersonnelBO submittedBy = this.personnelDao.findPersonnelById(userContext.getId()); Boolean undoable = Boolean.FALSE; if (importPluginClassname.equalsIgnoreCase("org.almajmoua.AudiBankXlsImporter")){ undoable = Boolean.TRUE; } importedFilesService.saveImportedFileName(importTransactionsFileName, submittedBy, idsToUndoImport, Boolean.FALSE, undoable); } @Override public List<ListItem<String>> retrieveImportPlugins() { List<ListItem<String>> importPlugins = new ArrayList<ListItem<String>>(); for (TransactionImport ti : new PluginManager().loadImportPlugins()) { importPlugins.add(new ListItem<String>(ti.getClass().getName(), ti.getDisplayName())); } return importPlugins; } @Override public ParseResultDto parseImportTransactions(String importPluginClassname, InputStream inputStream) { MifosUser mifosUser = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); UserContext userContext = new UserContextFactory().create(mifosUser); final TransactionImport ti = getInitializedImportPlugin(importPluginClassname, userContext.getId()); final ParseResultDto importResult = ti.parse(inputStream); int numberRowSuccessfullyParsed = ti.getSuccessfullyParsedRows(); importResult.setNumberRowSuccessfullyParsed(numberRowSuccessfullyParsed); String statusLogFile = generateStatusLogfile(importResult, ti); importResult.setStatusLogFile(statusLogFile); return importResult; } private TransactionImport getInitializedImportPlugin(String importPluginClassname, Short userId) { final TransactionImport ti = new PluginManager().getImportPlugin(importPluginClassname); final UserReferenceDto userReferenceDTO = new UserReferenceDto(userId); ti.setUserReferenceDto(userReferenceDTO); return ti; } private static final String LOG_TEMPLATE = "%d rows were read.\n" + "\n" + "%d rows contained no errors and will be imported\n" + "%d rows will be ignored\n" + "%d rows contained errors and were not imported\n" + "\n" + "Total amount of transactions imported: %s\n" + "Total amount of disbursements imported: %s\n" + "Total amount of transactions with error: %s\n" + "\n" + "%s"; private String generateStatusLogfile(ParseResultDto result, TransactionImport transactionImport) { String rowErrors = ""; if (!result.getParseErrors().isEmpty()) { rowErrors = StringUtils.join(result.getParseErrors(), System.getProperty("line.separator")); } return String.format(LOG_TEMPLATE, result.getNumberOfReadRows(), transactionImport.getSuccessfullyParsedRows(), result.getNumberOfIgnoredRows(), result.getNumberOfErrorRows(), new Money(Money.getDefaultCurrency(), result.getTotalAmountOfTransactionsImported()).toString(), new Money(Money.getDefaultCurrency(), result.getTotalAmountOfDisbursementsImported().toString()), new Money(Money.getDefaultCurrency(), result.getTotalAmountOfTransactionsWithError()).toString(), rowErrors); } @Override public ParseResultDto confirmImport(String importPluginClassname, String tempFileName) { MifosUser mifosUser = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); UserContext userContext = new UserContextFactory().create(mifosUser); FileInputStream fileInput = null; try { final TransactionImport transactionImport = getInitializedImportPlugin(importPluginClassname, userContext.getId()); fileInput = new FileInputStream(tempFileName); final ParseResultDto importResult = transactionImport.parse(fileInput); fileInput.close(); fileInput = new FileInputStream(tempFileName); if (importPluginClassname.equalsIgnoreCase("org.almajmoua.AudiBankXlsImporter")) { importResult.setTrxIdsToUndo(transactionImport.storeForUndoImport(fileInput)); } else { transactionImport.store(fileInput); } return importResult; } catch (Exception e) { throw new MifosRuntimeException(e); } finally { if (fileInput != null) { try { fileInput.close(); } catch (Exception e2) { throw new MifosRuntimeException(e2); } } } } @Override public void undoFullImport(String importTransactionsFileName) { MifosUser mifosUser = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); UserContext userContext = new UserContextFactory().create(mifosUser); TreeSet<String> accountsWithAdjustedPayments = new TreeSet<String>(); try { ImportedFilesEntity filesEntity = this.importedFilesService.getImportedFileByName(importTransactionsFileName); List<AccountTrxnEntity> trxUndo = new ArrayList<AccountTrxnEntity>(filesEntity.getImportedTrxn()); List<ImportedAccPaymentDto> accPaymentList = new ArrayList<ImportedAccPaymentDto>(); for (AccountTrxnEntity trxn : trxUndo) { try { validateForAdjustedPayments(trxn, accountsWithAdjustedPayments); accPaymentList.add(new ImportedAccPaymentDto(trxn.getAccount().getGlobalAccountNum(), trxn.getAccountPayment().getPaymentId())); } catch (BusinessRuleException e) { //nothing to do } } for (ImportedAccPaymentDto accDto : accPaymentList) { try { this.accountServiceFacade.applyHistoricalAdjustment(accDto.getGlobalNum(), accDto.getPaymentId(), IMPORT_UNDONE, userContext.getId(), null); } catch (MifosRuntimeException e) { // TODO: validation will be added with MIFOS-5779 } } this.importedFilesService.saveImportedFileName(filesEntity.getFileName(), filesEntity.getSubmittedBy(), null, Boolean.TRUE, filesEntity.getUndoable()); } catch (Exception e) { throw new MifosRuntimeException(e); } } @Override public List<ImportedFileDto> getImportedFiles() { List<ImportedFileDto> importedFilesDto = new ArrayList<ImportedFileDto>(); List<ImportedFilesEntity> importedFiles = this.importedFilesService.getImportedFiles(); DateTime date; for (ImportedFilesEntity fileEntity : importedFiles) { date = new DateTime(fileEntity.getSubmittedOn().getTime()); importedFilesDto.add(new ImportedFileDto(fileEntity.getFileName(), date, fileEntity.getPhaseOut(), fileEntity.getUndoable())); } return importedFilesDto; } private class ImportedAccPaymentDto { private String globalNum; private Integer paymentId; public String getGlobalNum() { return globalNum; } public Integer getPaymentId() { return paymentId; } public ImportedAccPaymentDto(String globalNum, Integer paymentId) { super(); this.globalNum = globalNum; this.paymentId = paymentId; } } /** * Method currently returns last problem found for each account. */ @Override public Map<String, Map<String, String>> getUndoImportDateToValidate(String importTransactionsFileName) { Map<String, Map<String, String>> validationResults = new HashMap<String, Map<String, String>>(); Map<String, String> accountStatusValidationResults = new HashMap<String, String>();; Map<String, String> trxnToUndo = new HashMap<String, String>();; List<AccountTrxnEntity> trxnData = new ArrayList<AccountTrxnEntity>(this.importedFilesService.getImportedFileByName(importTransactionsFileName).getImportedTrxn()); TreeSet<String> accountsWithAdjustedPayments = new TreeSet<String>(); Integer iterator = 0; Boolean error_flag = Boolean.FALSE; for (AccountTrxnEntity trxn : trxnData) { error_flag = Boolean.FALSE; if(trxn.getAccount().getAccountState().isLoanCanceled() || trxn.getAccount().getAccountState().isLoanClosedWrittenOff()) { accountStatusValidationResults.put(trxn.getAccount().getGlobalAccountNum(), ApplicationContextProvider.getBean(MessageLookup.class).lookup("ftlDefinedLabels.undoImport.invalidAccountState")+trxn.getAccount().getAccountState().getName()); error_flag = Boolean.TRUE; } else { try { validateForAdjustedPayments(trxn, accountsWithAdjustedPayments); monthClosingServiceFacade.validateTransactionDate(trxn.getAccountPayment().getPaymentDate()); } catch (BusinessRuleException e) { accountStatusValidationResults.put(trxn.getAccount().getGlobalAccountNum(), ApplicationContextProvider.getBean(MessageLookup.class).lookup(e.getMessageKey())); error_flag = Boolean.TRUE; } } if(error_flag) { iterator +=1; } } Integer valid_trxn = trxnData.size() - iterator; validationResults.put(INVALID_TRXN, accountStatusValidationResults); trxnToUndo.put(valid_trxn.toString(), ((Integer)trxnData.size()).toString()); validationResults.put(VALID_TRXN, trxnToUndo); return validationResults; } private void validateForAdjustedPayments(AccountTrxnEntity trxn, TreeSet<String> accountsWithAdjustedPayments) { if(accountsWithAdjustedPayments.contains(trxn.getAccount().getGlobalAccountNum())) { throw new BusinessRuleException("errors.paymentsWereAdjusted"); } Query query = StaticHibernateUtil.getSessionTL().getNamedQuery("countRelatedTransactions"); query.setParameter("trxn_id", trxn.getAccountTrxnId().intValue()); if(((BigInteger)query.uniqueResult()).intValue()>0) { accountsWithAdjustedPayments.add(trxn.getAccount().getGlobalAccountNum()); throw new BusinessRuleException("errors.paymentsWereAdjusted"); } } }