/*
* The Kuali Financial System, a comprehensive financial management system for higher education.
*
* Copyright 2005-2014 The Kuali Foundation
*
* 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.ar.report.service.impl;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.integration.cg.ContractsAndGrantsAward;
import org.kuali.kfs.integration.cg.ContractsAndGrantsBillingAward;
import org.kuali.kfs.integration.cg.ContractsAndGrantsModuleBillingService;
import org.kuali.kfs.module.ar.ArConstants;
import org.kuali.kfs.module.ar.ArPropertyConstants;
import org.kuali.kfs.module.ar.businessobject.CustomerInvoiceDetail;
import org.kuali.kfs.module.ar.document.ContractsGrantsInvoiceDocument;
import org.kuali.kfs.module.ar.document.service.ContractsGrantsInvoiceDocumentService;
import org.kuali.kfs.module.ar.report.service.ContractsGrantsAgingReportService;
import org.kuali.kfs.module.ar.report.service.ContractsGrantsReportHelperService;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.service.NonTransactional;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.search.SearchOperator;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.kim.api.identity.PersonService;
import org.kuali.rice.krad.service.BusinessObjectService;
import org.kuali.rice.krad.service.LookupService;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.ObjectUtils;
import org.springframework.transaction.annotation.Transactional;
/**
* This class is used to get the services for PDF generation and other services for CG Aging report.
*/
public class ContractsGrantsAgingReportServiceImpl implements ContractsGrantsAgingReportService {
protected ContractsGrantsInvoiceDocumentService contractsGrantsInvoiceDocumentService;
protected BusinessObjectService businessObjectService;
protected PersonService personService;
protected ContractsGrantsReportHelperService contractsGrantsReportHelperService;
protected ContractsAndGrantsModuleBillingService contractsAndGrantsModuleBillingService;
protected DateTimeService dateTimeService;
protected LookupService lookupService;
private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ContractsGrantsAgingReportServiceImpl.class);
/**
* @see org.kuali.kfs.module.ar.report.service.ContractsGrantsAgingReportService#filterContractsGrantsAgingReport(java.util.Map)
*/
@Override
@Transactional
public Map<String, List<ContractsGrantsInvoiceDocument>> filterContractsGrantsAgingReport(Map fieldValues, java.sql.Date begin, java.sql.Date end) throws ParseException {
Collection<ContractsGrantsInvoiceDocument> contractsGrantsInvoiceDocs = retrieveMatchingContractsGrantsInvoiceDocuments(fieldValues, begin, end);
Map<String, List<ContractsGrantsInvoiceDocument>> cgMapByCustomer = generateMapFromContractsGrantsInvoiceDocuments(contractsGrantsInvoiceDocs);
return cgMapByCustomer;
}
/**
* Utility method to lookup matching contracts & grants invoice documents for the given field values
* @param fieldValues the field values with criteria to lookup report on
* @param begin the begin da
* @param end
* @return
*/
protected List<ContractsGrantsInvoiceDocument> retrieveMatchingContractsGrantsInvoiceDocuments(Map fieldValues, java.sql.Date begin, java.sql.Date end) {
String reportOption = (String) fieldValues.get(ArPropertyConstants.REPORT_OPTION);
String orgCode = (String) fieldValues.get(KFSPropertyConstants.ORGANIZATION_CODE);
String chartCode = (String) fieldValues.get(ArPropertyConstants.PROCESSING_OR_BILLING_CHART_CODE);
String customerNumber = (String) fieldValues.get(KFSPropertyConstants.CUSTOMER_NUMBER);
String customerName = (String) fieldValues.get(KFSPropertyConstants.CUSTOMER_NAME);
String accountNumber = (String) fieldValues.get(KFSPropertyConstants.ACCOUNT_NUMBER);
String accountChartOfAccountsCode = (String) fieldValues.get(ArPropertyConstants.ContractsGrantsAgingReportFields.ACCOUNT_CHART_CODE);
String fundManager = (String) fieldValues.get(ArPropertyConstants.ContractsGrantsAgingReportFields.FUND_MANAGER);
String proposalNumber = (String) fieldValues.get(KFSPropertyConstants.PROPOSAL_NUMBER);
String collectorPrincName = (String) fieldValues.get(ArPropertyConstants.COLLECTOR_PRINC_NAME);
String collectorPrincipalId = null;
List<ContractsGrantsInvoiceDocument> contractsGrantsInvoiceDocs = new ArrayList<>();
if (!StringUtils.isBlank(collectorPrincName)) {
Person collUser = personService.getPersonByPrincipalName(collectorPrincName);
if (ObjectUtils.isNull(collUser)) {
return contractsGrantsInvoiceDocs; // if the principal name is not a real user, then return no values
} else {
collectorPrincipalId = collUser.getPrincipalId();
}
}
if (!StringUtils.isBlank(fundManager)) {
final Person fundManagerUser = getPersonService().getPersonByPrincipalName(fundManager);
if (ObjectUtils.isNull(fundManagerUser)) {
return contractsGrantsInvoiceDocs; // the fund manager doesn't exist, so empty results
}
}
String awardDocumentNumber = (String) fieldValues.get(ArPropertyConstants.ContractsGrantsAgingReportFields.AWARD_DOCUMENT_NUMBER);
String markedAsFinal = (String) fieldValues.get(ArPropertyConstants.ContractsGrantsAgingReportFields.MARKED_AS_FINAL);
String endDateString = (String) fieldValues.get(ArPropertyConstants.ContractsGrantsAgingReportFields.AWARD_END_DATE);
String invoiceAmountFrom = (String) fieldValues.get(ArPropertyConstants.ContractsGrantsAgingReportFields.INVOICE_AMT_FROM);
String invoiceAmountTo = (String) fieldValues.get(ArPropertyConstants.ContractsGrantsAgingReportFields.INVOICE_AMT_TO);
String invoiceNumber = (String) fieldValues.get(ArPropertyConstants.INVOICE_NUMBER);
String invoiceDateFromString = (String) fieldValues.get(ArPropertyConstants.ContractsGrantsAgingReportFields.INVOICE_DATE_FROM);
String invoiceDateToString = (String) fieldValues.get(ArPropertyConstants.ContractsGrantsAgingReportFields.INVOICE_DATE_TO);
String invoiceDateCriteria = getContractsGrantsReportHelperService().fixDateCriteria(invoiceDateFromString, invoiceDateToString, true);
String responsibilityId = (String) fieldValues.get(ArPropertyConstants.ContractsGrantsAgingReportFields.CG_ACCT_RESP_ID);
String awardEndFromDate = (String) fieldValues.get(ArPropertyConstants.ContractsGrantsAgingReportFields.AWARD_END_DATE_FROM);
String awardEndToDate = (String) fieldValues.get(ArPropertyConstants.ContractsGrantsAgingReportFields.AWARD_END_DATE_TO);
Map<String,String> fieldValuesForInvoice = new HashMap<String,String>();
fieldValuesForInvoice.put(ArPropertyConstants.OPEN_INVOICE_IND, KFSConstants.Booleans.TRUE);
fieldValues.put(KFSPropertyConstants.DOCUMENT_HEADER+"."+KFSPropertyConstants.WORKFLOW_DOCUMENT_TYPE_NAME, ArConstants.ArDocumentTypeCodes.CONTRACTS_GRANTS_INVOICE);
//Now to involve reportOption and handle chart and org
if(ObjectUtils.isNotNull(reportOption)){
if (reportOption.equalsIgnoreCase(ArConstants.ReportOptionFieldValues.PROCESSING_ORG) && StringUtils.isNotBlank(chartCode) && StringUtils.isNotBlank(orgCode)) {
fieldValuesForInvoice.put(ArPropertyConstants.CustomerInvoiceDocumentFields.PROCESSING_ORGANIZATION_CODE, orgCode);
fieldValuesForInvoice.put(ArPropertyConstants.CustomerInvoiceDocumentFields.PROCESSING_CHART_OF_ACCOUNT_CODE, chartCode);
}
if (reportOption.equalsIgnoreCase(ArConstants.ReportOptionFieldValues.BILLING_ORG) && StringUtils.isNotBlank(chartCode) && StringUtils.isNotBlank(orgCode)) {
fieldValuesForInvoice.put(ArPropertyConstants.CustomerInvoiceDocumentFields.BILLED_BY_ORGANIZATION_CODE, orgCode);
fieldValuesForInvoice.put(ArPropertyConstants.CustomerInvoiceDocumentFields.BILL_BY_CHART_OF_ACCOUNT_CODE, chartCode);
}
}
if (!StringUtils.isBlank(customerNumber)) {
fieldValuesForInvoice.put(ArPropertyConstants.CustomerInvoiceDocumentFields.CUSTOMER_NUMBER, customerNumber);
}
if (!StringUtils.isBlank(customerName)) {
fieldValuesForInvoice.put(ArPropertyConstants.CUSTOMER_NAME, customerName);
}
if (!StringUtils.isBlank(proposalNumber)) {
fieldValuesForInvoice.put(ArPropertyConstants.ContractsGrantsInvoiceDocumentFields.PROPOSAL_NUMBER, proposalNumber);
}
if (!StringUtils.isBlank(markedAsFinal)) {
if (markedAsFinal.equalsIgnoreCase(KFSConstants.ParameterValues.YES)) {
fieldValuesForInvoice.put(ArPropertyConstants.ContractsGrantsInvoiceDocumentFields.FINAL_BILL, KFSConstants.Booleans.TRUE);
}
else if (markedAsFinal.equalsIgnoreCase(KFSConstants.ParameterValues.NO)) {
fieldValuesForInvoice.put(ArPropertyConstants.ContractsGrantsInvoiceDocumentFields.FINAL_BILL, KFSConstants.Booleans.FALSE);
}
}
if (!StringUtils.isBlank(invoiceNumber)) {
fieldValuesForInvoice.put(KFSPropertyConstants.DOCUMENT_NUMBER, invoiceNumber);
}
if (!StringUtils.isBlank(responsibilityId)) {
fieldValuesForInvoice.put(ArPropertyConstants.CustomerInvoiceDocumentFields.CG_ACCT_RESP_ID, responsibilityId);
}
if (!StringUtils.isBlank(accountChartOfAccountsCode)) {
fieldValuesForInvoice.put(KFSPropertyConstants.SOURCE_ACCOUNTING_LINES+"."+KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, accountChartOfAccountsCode);
}
if (!StringUtils.isBlank(accountNumber)) {
fieldValuesForInvoice.put(KFSPropertyConstants.SOURCE_ACCOUNTING_LINES+"."+KFSPropertyConstants.ACCOUNT_NUMBER, accountNumber);
}
if (!StringUtils.isBlank(invoiceDateCriteria)) {
fieldValuesForInvoice.put(KFSPropertyConstants.DOCUMENT_HEADER+"."+KFSPropertyConstants.WORKFLOW_CREATE_DATE, invoiceDateCriteria);
}
final String sourceTotalCriteria = getAmountCriteria(invoiceAmountFrom, invoiceAmountTo);
if (!StringUtils.isBlank(sourceTotalCriteria)) {
fieldValuesForInvoice.put(KFSPropertyConstants.SOURCE_TOTAL, sourceTotalCriteria);
}
String billingBeginDateString = null;
if (begin != null) {
billingBeginDateString = getDateTimeService().toDateString(begin);
}
String billingEndDateString = null;
if (end != null) {
billingEndDateString = getDateTimeService().toDateString(end);
}
final String billingDateCriteria = getContractsGrantsReportHelperService().fixDateCriteria(billingBeginDateString, billingEndDateString, false);
if (!StringUtils.isBlank(billingDateCriteria)) {
fieldValuesForInvoice.put(ArPropertyConstants.CustomerInvoiceDocumentFields.BILLING_DATE, billingDateCriteria);
}
final Set<Long> awardIds = lookupBillingAwards(awardDocumentNumber, awardEndFromDate, awardEndToDate, fundManager);
// here put all criterias and find the docs
contractsGrantsInvoiceDocs.addAll(getLookupService().findCollectionBySearch(ContractsGrantsInvoiceDocument.class, fieldValuesForInvoice));
filterContractsGrantsInvoiceDocumentsByAwardAndCollector(contractsGrantsInvoiceDocs, collectorPrincipalId, awardIds);
return contractsGrantsInvoiceDocs;
}
/**
* Removes contracts & grants invoice documents from the given collection if they do not match the given award ids or if the collector permissions do not allow viewing of the document
* @param contractsGrantsInvoiceDocs a Collection of ContractsGrantsInvoiceDocuments
* @param collectorPrincName the principal name of the collector
* @param awardIds a Set of proposal numbers of awards which match given criteria
*/
protected void filterContractsGrantsInvoiceDocumentsByAwardAndCollector(Collection<ContractsGrantsInvoiceDocument> contractsGrantsInvoiceDocs, String collectorPrincipalId, Set<Long> awardIds) {
// filter by collector and user performing the search
Person user = GlobalVariables.getUserSession().getPerson();
if (!CollectionUtils.isEmpty(contractsGrantsInvoiceDocs)) {
for (Iterator<ContractsGrantsInvoiceDocument> iter = contractsGrantsInvoiceDocs.iterator(); iter.hasNext();) {
ContractsGrantsInvoiceDocument document = iter.next();
if (!ObjectUtils.isNull(document.getInvoiceGeneralDetail()) && !ObjectUtils.isNull(document.getInvoiceGeneralDetail().getAward()) && awardIds != null && !awardIds.contains(document.getInvoiceGeneralDetail().getAward().getProposalNumber())) {
iter.remove();
} else if (StringUtils.isNotEmpty(collectorPrincipalId)) {
if (!contractsGrantsInvoiceDocumentService.canViewInvoice(document, collectorPrincipalId)) {
iter.remove();
}
} else if (!contractsGrantsInvoiceDocumentService.canViewInvoice(document, user.getPrincipalId())) {
iter.remove();
}
}
}
}
/**
* Generates a Map keyed by customer number and name from a Collection of ContractsGrantsInvoiceDocuments
* @param contractsGrantsInvoiceDocs a Collection of ContractsGrantsInvoiceDocuments to convert into a Map
* @return a Map of CINV docs, keyed by customer number and name
*/
protected Map<String, List<ContractsGrantsInvoiceDocument>> generateMapFromContractsGrantsInvoiceDocuments(Collection<ContractsGrantsInvoiceDocument> contractsGrantsInvoiceDocs) {
Map<String, List<ContractsGrantsInvoiceDocument>> cgMapByCustomer = null;
if (!CollectionUtils.isEmpty(contractsGrantsInvoiceDocs)) {
cgMapByCustomer = new HashMap<String, List<ContractsGrantsInvoiceDocument>>();
for (ContractsGrantsInvoiceDocument cgDoc : contractsGrantsInvoiceDocs) {
List<ContractsGrantsInvoiceDocument> cgInvoiceDocs;
String customerNbr = cgDoc.getCustomer().getCustomerNumber();
String customerNm = cgDoc.getCustomer().getCustomerName();
String key = customerNbr + "-" + customerNm;
if (cgMapByCustomer.containsKey(key)) {
cgInvoiceDocs = cgMapByCustomer.get(key);
}
else {
cgInvoiceDocs = new ArrayList<ContractsGrantsInvoiceDocument>();
}
cgInvoiceDocs.add(cgDoc);
cgMapByCustomer.put(key, cgInvoiceDocs);
}
}
return cgMapByCustomer;
}
/**
* Generates a Set of proposal ids for awards which match the given criteria
* @param awardDocumentNumber the document number of the award
* @param awardEndFromDate the award ending date of the award
* @param awardEndToDate the award ending date of the award
* @param fundManager the principal name of the fund manager
* @return a Set of Award ids to filter on, or null if no search was actually completed
*/
protected Set<Long> lookupBillingAwards(String awardDocumentNumber, String awardEndFromDate, String awardEndToDate, String fundManager) {
if (StringUtils.isBlank(awardDocumentNumber) && StringUtils.isBlank(awardEndFromDate) && StringUtils.isBlank(awardEndToDate) && StringUtils.isBlank(fundManager)) {
return null; // nothing to search on? then return null to note that no search was completed
}
final Set<String> fundManagerIds = getContractsGrantsReportHelperService().lookupPrincipalIds(fundManager);
Map<String, String> fieldValues = new HashMap<>();
if (!StringUtils.isBlank(awardDocumentNumber)) {
fieldValues.put(KFSPropertyConstants.AWARD_DOCUMENT_NUMBER, awardDocumentNumber);
}
final String awardEnd = getContractsGrantsReportHelperService().fixDateCriteria(awardEndFromDate, awardEndToDate, false);
if (!StringUtils.isBlank(awardEnd)) {
fieldValues.put(KFSPropertyConstants.AWARD_ENDING_DATE, awardEnd);
}
fieldValues.put(KFSPropertyConstants.ACTIVE, KFSConstants.ACTIVE_INDICATOR);
final List<? extends ContractsAndGrantsAward> awards = getContractsAndGrantsModuleBillingService().lookupAwards(fieldValues, true);
Set<Long> billingAwardIds = new HashSet<>();
for (ContractsAndGrantsAward award : awards) {
if (award instanceof ContractsAndGrantsBillingAward) {
final ContractsAndGrantsBillingAward cgbAward = (ContractsAndGrantsBillingAward)award;
if (ObjectUtils.isNull(cgbAward.getAwardPrimaryFundManager()) || fundManagerIds.isEmpty() || fundManagerIds.contains(cgbAward.getAwardPrimaryFundManager().getPrincipalId())) {
billingAwardIds.add(cgbAward.getProposalNumber());
}
}
}
return billingAwardIds;
}
/**
* Turns a from amount and to amount into a lookupable criteria
* @param fromAmount the lower bound amount
* @param toAmount the upper bound amount
* @return a lookupable criteria
*/
protected String getAmountCriteria(String fromAmount, String toAmount) {
if (!StringUtils.isBlank(fromAmount)) {
if (!StringUtils.isBlank(toAmount)) {
return fromAmount+SearchOperator.BETWEEN.op()+toAmount;
} else {
return SearchOperator.GREATER_THAN_EQUAL.op()+fromAmount;
}
} else if (!StringUtils.isBlank(toAmount)) {
return SearchOperator.LESS_THAN_EQUAL.op()+toAmount;
}
return null;
}
/**
* This method is used to get the Invoice Details by account number and chart code
*
* @param accountChartCode
* @param accountNumber
* @return a List of the CustomerInvoiceDetails associated with a given Account Number
*/
@SuppressWarnings("unchecked")
protected Collection<CustomerInvoiceDetail> getCustomerInvoiceDetailsByAccountNumber(String accountChartCode, String accountNumber) {
Map<String, Object> args = new HashMap<>();
if (!StringUtils.isBlank(accountNumber)) {
args.put(KFSPropertyConstants.ACCOUNT_NUMBER, accountNumber);
}
if (!StringUtils.isBlank(accountChartCode)) {
args.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, accountChartCode);
}
return businessObjectService.findMatching(CustomerInvoiceDetail.class, args);
}
/**
* Figures out the reportRunDate and then uses filterContractsGrantsAgingReport to look up the right documents
* @see org.kuali.kfs.module.ar.report.service.ContractsGrantsAgingReportService#lookupContractsGrantsInvoiceDocumentsForAging(java.util.Map)
*/
@Override
public List<ContractsGrantsInvoiceDocument> lookupContractsGrantsInvoiceDocumentsForAging(Map<String, Object> fieldValues) {
try {
java.util.Date today = getDateTimeService().getCurrentDate();
String reportRunDateStr = (String) fieldValues.get(ArPropertyConstants.CustomerAgingReportFields.REPORT_RUN_DATE);
java.util.Date reportRunDate = (ObjectUtils.isNull(reportRunDateStr) || reportRunDateStr.isEmpty()) ?
today :
getDateTimeService().convertToDate(reportRunDateStr);
// retrieve filtered data according to the lookup
List<ContractsGrantsInvoiceDocument> contractsGrantsInvoiceDocuments = retrieveMatchingContractsGrantsInvoiceDocuments(fieldValues, null, new java.sql.Date(reportRunDate.getTime()));
return contractsGrantsInvoiceDocuments;
}
catch (ParseException ex) {
throw new RuntimeException("Could not parse report run date for lookup",ex);
}
}
@NonTransactional
public PersonService getPersonService() {
return personService;
}
@NonTransactional
public void setPersonService(PersonService personService) {
this.personService = personService;
}
public ContractsGrantsReportHelperService getContractsGrantsReportHelperService() {
return contractsGrantsReportHelperService;
}
public void setContractsGrantsReportHelperService(ContractsGrantsReportHelperService contractsGrantsReportHelperService) {
this.contractsGrantsReportHelperService = contractsGrantsReportHelperService;
}
public DateTimeService getDateTimeService() {
return dateTimeService;
}
public void setDateTimeService(DateTimeService dateTimeService) {
this.dateTimeService = dateTimeService;
}
public ContractsAndGrantsModuleBillingService getContractsAndGrantsModuleBillingService() {
return contractsAndGrantsModuleBillingService;
}
public void setContractsAndGrantsModuleBillingService(ContractsAndGrantsModuleBillingService contractsAndGrantsModuleBillingService) {
this.contractsAndGrantsModuleBillingService = contractsAndGrantsModuleBillingService;
}
public LookupService getLookupService() {
return lookupService;
}
public void setLookupService(LookupService lookupService) {
this.lookupService = lookupService;
}
/**
* Gets the businessObjectService attribute.
*
* @return Returns the businessObjectService.
*/
@NonTransactional
public BusinessObjectService getBusinessObjectService() {
return businessObjectService;
}
/**
* Sets the businessObjectService attribute value.
*
* @param businessObjectService The businessObjectService to set.
*/
@NonTransactional
public void setBusinessObjectService(BusinessObjectService businessObjectService) {
this.businessObjectService = businessObjectService;
}
/**
* Gets the contractsGrantsInvoiceDocumentService attribute.
*
* @return Returns the contractsGrantsInvoiceDocumentService.
*/
@NonTransactional
public ContractsGrantsInvoiceDocumentService getContractsGrantsInvoiceDocumentService() {
return contractsGrantsInvoiceDocumentService;
}
/**
* Sets the collectorHierarchyDao attribute value.
*
* @param collectorHierarchyDao The collectorHierarchyDao to set.
*/
@NonTransactional
public void setContractsGrantsInvoiceDocumentService(ContractsGrantsInvoiceDocumentService contractsGrantsInvoiceDocumentService) {
this.contractsGrantsInvoiceDocumentService = contractsGrantsInvoiceDocumentService;
}
}