/* * 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.gl.batch; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.io.PrintStream; import java.sql.Date; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.coa.businessobject.Chart; import org.kuali.kfs.coa.businessobject.Organization; import org.kuali.kfs.gl.GeneralLedgerConstants; import org.kuali.kfs.gl.batch.service.RunDateService; import org.kuali.kfs.gl.batch.service.impl.OriginEntryTotals; import org.kuali.kfs.gl.businessobject.CollectorDetail; import org.kuali.kfs.gl.businessobject.CollectorHeader; import org.kuali.kfs.gl.businessobject.OriginEntryFull; import org.kuali.kfs.gl.businessobject.OriginEntryGroup; import org.kuali.kfs.gl.businessobject.OriginEntrySource; import org.kuali.kfs.gl.report.CollectorReportData; import org.kuali.kfs.gl.service.CollectorDetailService; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.KFSKeyConstants; import org.kuali.kfs.sys.KFSPropertyConstants; import org.kuali.kfs.sys.ObjectUtil; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.kfs.sys.exception.ParseException; import org.kuali.rice.core.api.datetime.DateTimeService; import org.kuali.rice.core.api.util.type.KualiDecimal; import org.kuali.rice.kns.service.BusinessObjectDictionaryService; import org.kuali.rice.kns.service.DataDictionaryService; import org.kuali.rice.krad.bo.PersistableBusinessObjectBase; import org.kuali.rice.krad.service.BusinessObjectService; import org.kuali.rice.krad.util.MessageMap; /** * Object representation of collector xml input. */ public class CollectorBatch extends PersistableBusinessObjectBase { // way to distinguish this batch from others private String batchName; // common field for header records and trailer records private String universityFiscalYear; private String chartOfAccountsCode; private String organizationCode; private Date transmissionDate; private String recordType; // header record additional fields private String personUserID; private Integer batchSequenceNumber; private String emailAddress; private String campusCode; private String phoneNumber; private String mailingAddress; private String departmentName; private List<OriginEntryFull> originEntries; private List<CollectorDetail> collectorDetails; // trailer records private String firstEmptyField; //first,second,third Empty Fields are dummy fields to read the spaces in the file using dd private String secondEmptyField; private Integer totalRecords; private KualiDecimal totalAmount; private MessageMap messageMap; private OriginEntryTotals originEntryTotals; private boolean headerlessBatch; private static CollectorBatchHeaderFieldUtil collectorBatchHeaderFieldUtil; private static CollectorBatchTrailerRecordFieldUtil collectorBatchTrailerRecordFieldUtil; /** * Constructs a CollectorBatch */ public CollectorBatch() { originEntries = new ArrayList(); collectorDetails = new ArrayList(); messageMap = new MessageMap(); originEntryTotals = null; totalRecords = 0; headerlessBatch = false; } /** * Gets the universityFiscalYear attribute. */ public String getUniversityFiscalYear() { return universityFiscalYear; } /** * Sets the universityFiscalYear attribute */ public void setUniversityFiscalYear(String universityFiscalYear) { this.universityFiscalYear = universityFiscalYear; } /** * Gets the batchSequenceNumber attribute. */ public Integer getBatchSequenceNumber() { return batchSequenceNumber; } /** * Sets the batchSequenceNumber attribute value. */ public void setBatchSequenceNumber(Integer batchSequenceNumber) { this.batchSequenceNumber = batchSequenceNumber; } /** * Gets the chartOfAccountsCode attribute. */ public String getChartOfAccountsCode() { return chartOfAccountsCode; } /** * Sets the chartOfAccountsCode attribute value. */ public void setChartOfAccountsCode(String chartOfAccountsCode) { this.chartOfAccountsCode = chartOfAccountsCode; } /** * Gets the organizationCode attribute. */ public String getOrganizationCode() { return organizationCode; } /** * Sets the organizationCode attribute value. */ public void setOrganizationCode(String organizationCode) { this.organizationCode = organizationCode; } /** * Gets the totalAmount attribute. */ public KualiDecimal getTotalAmount() { return totalAmount; } /** * Sets the totalAmount attribute value. */ public void setTotalAmount(KualiDecimal totalAmount) { this.totalAmount = totalAmount; } /** * Sets the total amount from the String. */ public void setTotalAmount(String totalAmount) { this.totalAmount = new KualiDecimal(totalAmount); } /** * Sets the total amount field to null. */ public void clearTotalAmount() { this.totalAmount = null; } /** * Gets the secondEmptyField attribute */ public String getSecondEmptyField() { return secondEmptyField; } /** * Sets the secondEmptyField attribute */ public void setSecondEmptyField(String secondEmptyField) { this.secondEmptyField = secondEmptyField; } /** * Gets the firstEmptyField attribute */ public String getFirstEmptyField() { return firstEmptyField; } /** * Sets the firstEmptyField attribute */ public void setFirstEmptyField(String firstEmptyField) { this.firstEmptyField = firstEmptyField; } /** * Gets the totalRecords attribute. */ public Integer getTotalRecords() { return totalRecords; } /** * Sets the totalRecords attribute value. */ public void setTotalRecords(Integer totalRecords) { this.totalRecords = totalRecords; } /** * Gets the transmissionDate attribute. */ public Date getTransmissionDate() { return transmissionDate; } /** * Sets the transmissionDate attribute value. */ public void setTransmissionDate(Date transmissionDate) { this.transmissionDate = transmissionDate; } /** * Gets the recordType attribute. */ public String getRecordType() { return recordType; } /** * Sets the recordType attribute. */ public void setRecordType(String recordType) { this.recordType = recordType; } /** * Gets the emailAddress attribute. */ public String getEmailAddress() { return emailAddress; } /** * Sets the emailAddress attribute value. */ public void setEmailAddress(String emailAddress) { this.emailAddress = emailAddress; } /** * Gets the personUserID attribute. */ public String getPersonUserID() { return personUserID; } /** * Sets the personUserID attribute value. */ public void setPersonUserID(String personUserID) { this.personUserID = personUserID; } /** * Gets the idBillings attribute. */ public List<CollectorDetail> getCollectorDetails() { return collectorDetails; } /** * Sets the idBillings attribute value. */ public void setCollectorDetails(List<CollectorDetail> idDetails) { this.collectorDetails = idDetails; } /** * Gets the originEntries attribute. */ public List<OriginEntryFull> getOriginEntries() { return originEntries; } /** * Sets the originEntries attribute value. */ public void setOriginEntries(List<OriginEntryFull> batchOriginEntry) { this.originEntries = batchOriginEntry; } /** * Adds a processed origin entry to the list. */ public void addOriginEntry(OriginEntryFull orginEntry) { this.originEntries.add(orginEntry); } /** * Adds a processed id billing to the list. */ public void addCollectorDetail(CollectorDetail collectorDetail) { this.collectorDetails.add(collectorDetail); } /** * Attempts to retrieve a collector header already exists with the primary key values given for this object * * @return the CollectorHeader found in the database */ public CollectorHeader retrieveDuplicateHeader() { // checkHeader is used to check whether a record with the same PK values exist already (i.e. only PK values are filled in). CollectorHeader checkHeader = createCollectorHeaderWithPKValuesOnly(); CollectorHeader foundHeader = (CollectorHeader) SpringContext.getBean(BusinessObjectService.class).retrieve(checkHeader); return foundHeader; } /** * Sets defaults for fields not populated from file. Store an origin entry group, all gl entries and id billing entries from the * processed file. Also write the header for the duplicate file check. * * @param originEntryGroup the group into which to store the origin entries * @param collectorReportData report data */ public void setDefaultsAndStore(CollectorReportData collectorReportData, String demergerOutputFileName, PrintStream originEntryOutputPs) { BusinessObjectService businessObjectService = SpringContext.getBean(BusinessObjectService.class); CollectorDetailService collectorDetailService = SpringContext.getBean(CollectorDetailService.class); // persistHeader is used to persist a collector header record into the DB CollectorHeader persistHeader = createCollectorHeaderForStorage(); CollectorHeader foundHeader = retrieveDuplicateHeader(); if (foundHeader != null) { // update the version number to prevent OptimisticLockingExceptions persistHeader.setVersionNumber(foundHeader.getVersionNumber()); } businessObjectService.save(persistHeader); // store origin entries by using the demerger output file BufferedReader inputFileReader = null; try { inputFileReader = new BufferedReader(new FileReader(demergerOutputFileName)); String line = null; while ((line = inputFileReader.readLine()) != null) { originEntryOutputPs.printf("%s\n", line); } } catch (IOException e) { throw new RuntimeException("IO Error encountered trying to persist collector batch.", e); } finally { IOUtils.closeQuietly(inputFileReader); inputFileReader = null; } Date nowDate = new Date(SpringContext.getBean(DateTimeService.class).getCurrentDate().getTime()); RunDateService runDateService = SpringContext.getBean(RunDateService.class); Date createDate = new java.sql.Date((runDateService.calculateRunDate(nowDate).getTime())); Integer sequenceNumber = new Integer(0); Integer nextSequence = collectorDetailService.getNextCreateSequence(createDate); if (nextSequence != null) { sequenceNumber = nextSequence; } int countOfdetails = collectorDetails.size(); for (int numSavedDetails = 0; numSavedDetails < countOfdetails; numSavedDetails++) { CollectorDetail idDetail = this.collectorDetails.get(numSavedDetails); // setDefaultsCollectorDetail(idDetail); idDetail.setTransactionLedgerEntrySequenceNumber( ++sequenceNumber); idDetail.setCreateDate(createDate); CollectorDetail foundIdDetail = (CollectorDetail) businessObjectService.retrieve(idDetail); if (foundIdDetail != null) { idDetail.setVersionNumber(foundIdDetail.getVersionNumber()); } businessObjectService.save(idDetail); } collectorReportData.setNumSavedDetails(this, countOfdetails); } /** * Uppercases the appropriate fields in the batch, if told to do so by the data dictionary */ public void prepareDataForStorage() { BusinessObjectDictionaryService businessObjectDictionaryService = SpringContext.getBean(BusinessObjectDictionaryService.class); DataDictionaryService dataDictionaryService = SpringContext.getBean(DataDictionaryService.class); // uppercase the data used to generate the collector header if (dataDictionaryService.getAttributeForceUppercase(Chart.class, KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE)) { setChartOfAccountsCode(getChartOfAccountsCode().toUpperCase()); } if (dataDictionaryService.getAttributeForceUppercase(Organization.class, KFSPropertyConstants.ORGANIZATION_CODE)) { setOrganizationCode(getOrganizationCode().toUpperCase()); } // now uppercase all of the origin entry data for (OriginEntryFull entry : originEntries) { businessObjectDictionaryService.performForceUppercase(entry); } // uppercase the id billing entries for (CollectorDetail collectorDetail : collectorDetails) { businessObjectDictionaryService.performForceUppercase(collectorDetail); } } /** * Creates origin entry group from header fields. * * @return OriginEntryGroup */ private OriginEntryGroup createOriginEntryGroup() { OriginEntryGroup group = new OriginEntryGroup(); group.setSourceCode(OriginEntrySource.COLLECTOR); group.setDate(new java.sql.Date(SpringContext.getBean(DateTimeService.class).getCurrentDate().getTime())); group.setProcess(new Boolean(true)); group.setScrub(new Boolean(true)); group.setValid(new Boolean(true)); return group; } /** * Creates a CollectorHeader from the batch to be used for storage * * @return CollectorHeader */ public CollectorHeader createCollectorHeaderForStorage() { CollectorHeader header = new CollectorHeader(); header.setChartOfAccountsCode(getChartOfAccountsCode()); header.setOrganizationCode(getOrganizationCode()); header.setProcessTransmissionDate(getTransmissionDate()); header.setProcessBatchSequenceNumber(getBatchSequenceNumber()); header.setProcessTotalRecordCount(getTotalRecords()); header.setProcessTotalAmount(getTotalAmount()); header.setContactCampusCode(getCampusCode()); header.setContactPersonPhoneNumber(getPhoneNumber()); header.setContactMailingAddress(getMailingAddress()); header.setContactDepartmentName(getDepartmentName()); return header; } /** * Creates an origin entry record with the PK values filled in only. This is useful to check for duplicate headers. * * @return CollectorHeader with chart of accounts code, organization code, process transmission date, batch sequence number * total record count, and process total amount from this CollectorBatch */ public CollectorHeader createCollectorHeaderWithPKValuesOnly() { CollectorHeader header = new CollectorHeader(); header.setChartOfAccountsCode(getChartOfAccountsCode()); header.setOrganizationCode(getOrganizationCode()); header.setProcessTransmissionDate(getTransmissionDate()); header.setProcessBatchSequenceNumber(getBatchSequenceNumber()); header.setProcessTotalRecordCount(getTotalRecords()); header.setProcessTotalAmount(getTotalAmount()); return header; } // /** // * Sets defaults for missing id billing fields. // * // * @param idDetail CollectorDetail object which has its create date being set // */ // private void setDefaultsCollectorDetail(CollectorDetail idDetail) { // // TODO: Get current fiscal year and period if blank? // // idBilling.setUniversityFiscalPeriodCode(String.valueOf(RandomUtils.nextInt(2))); // // idBilling.setCreateSequence(String.valueOf(RandomUtils.nextInt(2))); // // idBilling.setInterDepartmentalBillingSequenceNumber(String.valueOf(RandomUtils.nextInt(2))); // idDetail.setCreateDate(new Date(SpringContext.getBean(DateTimeService.class).getCurrentDate().getTime())); // // } /** * Gets the campusCode attribute. * * @return Returns the campusCode. */ public String getCampusCode() { return campusCode; } /** * Sets the campusCode attribute value. * * @param campusCode The campusCode to set. */ public void setCampusCode(String campusCode) { this.campusCode = campusCode; } /** * Gets the departmentName attribute. * * @return Returns the departmentName. */ public String getDepartmentName() { return departmentName; } /** * Sets the departmentName attribute value. * * @param departmentName The departmentName to set. */ public void setDepartmentName(String departmentName) { this.departmentName = departmentName; } /** * Gets the mailingAddress attribute. * * @return Returns the mailingAddress. */ public String getMailingAddress() { return mailingAddress; } /** * Sets the mailingAddress attribute value. * * @param mailingAddress The mailingAddress to set. */ public void setMailingAddress(String mailingAddress) { this.mailingAddress = mailingAddress; } /** * Gets the phoneNumber attribute. * * @return Returns the phoneNumber. */ public String getPhoneNumber() { return phoneNumber; } /** * Sets the phoneNumber attribute value. * * @param phoneNumber The phoneNumber to set. */ public void setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; } /** * Gets the batchName attribute. * * @return Returns the batchName. */ public String getBatchName() { return batchName; } /** * Sets the batchName attribute value. * * @param batchName The batchName to set. */ public void setBatchName(String batchName) { this.batchName = batchName; } public MessageMap getMessageMap() { return messageMap; } public void setMessageMap(MessageMap messageMap) { if (messageMap == null) { throw new NullPointerException("messageMap is null"); } if (this.messageMap.hasMessages()) { throw new RuntimeException("Cannot reset MessageMap unless original instance has no messages."); } this.messageMap = messageMap; } public OriginEntryTotals getOriginEntryTotals() { return originEntryTotals; } public void setOriginEntryTotals(OriginEntryTotals originEntryTotals) { this.originEntryTotals = originEntryTotals; } public boolean isHeaderlessBatch() { return headerlessBatch; } public void setHeaderlessBatch(boolean headerlessBatch) { this.headerlessBatch = headerlessBatch; } protected LinkedHashMap toStringMapper_RICE20_REFACTORME() { LinkedHashMap map = new LinkedHashMap(); map.put("chartOfAccountsCode", getChartOfAccountsCode()); map.put("organizationCode", getOrganizationCode()); map.put("transmissionDate", getTransmissionDate()); map.put("personUserID", getPersonUserID()); map.put("batchSequenceNumber", getBatchSequenceNumber()); map.put("emailAddress", getEmailAddress()); map.put("campusCode", getCampusCode()); map.put("phoneNumber", getPhoneNumber()); map.put("mailingAddress", getMailingAddress()); map.put("departmentName", getDepartmentName()); map.put("firstEmptyField", getFirstEmptyField()); map.put("totalRecords", getTotalRecords()); map.put("secondEmptyField", getSecondEmptyField()); map.put("totalAmount", getTotalAmount()); return map; } protected Date parseSqlDate(String date) throws ParseException { try { return new Date(new SimpleDateFormat("yy-MM-dd").parse(date).getTime()); } catch (java.text.ParseException e) { throw new ParseException(e.getMessage(), e); } } protected String getValue(String headerLine, int s, int e) { return org.springframework.util.StringUtils.trimTrailingWhitespace(StringUtils.substring(headerLine, s, e)); } /** * @return the static instance of the CollectorBatchHeaderFieldUtil */ protected static CollectorBatchHeaderFieldUtil getCollectorBatchHeaderFieldUtil() { if (collectorBatchHeaderFieldUtil == null) { collectorBatchHeaderFieldUtil = new CollectorBatchHeaderFieldUtil(); } return collectorBatchHeaderFieldUtil; } public void setFromTextFileForCollectorBatch(String headerLine) { try{ final Map<String, Integer> pMap = getCollectorBatchHeaderFieldUtil().getFieldBeginningPositionMap(); headerLine = org.apache.commons.lang.StringUtils.rightPad(headerLine, GeneralLedgerConstants.getSpaceAllCollectorBatchHeaderFields().length(), ' '); setChartOfAccountsCode(getValue(headerLine, pMap.get(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE), pMap.get(KFSPropertyConstants.ORGANIZATION_CODE))); setOrganizationCode(getValue(headerLine, pMap.get(KFSPropertyConstants.ORGANIZATION_CODE), pMap.get(KFSPropertyConstants.TRANSMISSION_DATE))); String transmissionDate = org.apache.commons.lang.StringUtils.trim(getValue(headerLine, pMap.get(KFSPropertyConstants.TRANSMISSION_DATE), pMap.get(KFSPropertyConstants.COLLECTOR_BATCH_RECORD_TYPE))); try { setTransmissionDate(parseSqlDate(transmissionDate)); } catch (ParseException e) { getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.Collector.HEADER_BAD_TRANSMISSION_DATE_FORMAT, transmissionDate); setTransmissionDate(null); } String batchNumber = org.apache.commons.lang.StringUtils.trim(getValue(headerLine, pMap.get(KFSPropertyConstants.BATCH_SEQUENCE_NUMBER), pMap.get(KFSPropertyConstants.EMAIL_ADDRESS))); if (ObjectUtil.isInteger(batchNumber)) { setBatchSequenceNumber(new Integer(batchNumber)); } else { setBatchSequenceNumber(0); } setEmailAddress(getValue(headerLine, pMap.get(KFSPropertyConstants.EMAIL_ADDRESS), pMap.get(KFSPropertyConstants.COLLECTOR_BATCH_PERSON_USER_ID))); setPersonUserID(getValue(headerLine, pMap.get(KFSPropertyConstants.COLLECTOR_BATCH_PERSON_USER_ID), pMap.get(KFSPropertyConstants.DEPARTMENT_NAME))); setDepartmentName(getValue(headerLine, pMap.get(KFSPropertyConstants.DEPARTMENT_NAME), pMap.get(KFSPropertyConstants.MAILING_ADDRESS))); setMailingAddress(getValue(headerLine, pMap.get(KFSPropertyConstants.MAILING_ADDRESS), pMap.get(KFSPropertyConstants.CAMPUS_CODE))); setCampusCode(getValue(headerLine, pMap.get(KFSPropertyConstants.CAMPUS_CODE), pMap.get(KFSPropertyConstants.PHONE_NUMBER))); setPhoneNumber(org.apache.commons.lang.StringUtils.trim(getValue(headerLine, pMap.get(KFSPropertyConstants.PHONE_NUMBER), GeneralLedgerConstants.getSpaceAllCollectorBatchHeaderFields().length()))); } catch (Exception e){ throw new RuntimeException(e + " occurred in CollectorBatch.setFromTextFileForCollectorBatch()"); } } /** * @return the static instance of the CollectorBatchTrailerRecordFieldUtil */ protected static CollectorBatchTrailerRecordFieldUtil getCollectorBatchTrailerRecordFieldUtil() { if (collectorBatchTrailerRecordFieldUtil == null) { collectorBatchTrailerRecordFieldUtil = new CollectorBatchTrailerRecordFieldUtil(); } return collectorBatchTrailerRecordFieldUtil; } public void setFromTextFileForCollectorBatchTrailerRecord(String trailerLine, int lineNumber) { final Map<String, Integer> pMap = getCollectorBatchTrailerRecordFieldUtil().getFieldBeginningPositionMap(); trailerLine = org.apache.commons.lang.StringUtils.rightPad(trailerLine, GeneralLedgerConstants.getSpaceAllCollectorBatchTrailerFields().length(), ' '); setTotalRecords(new Integer(org.apache.commons.lang.StringUtils.trim(getValue(trailerLine, pMap.get(KFSPropertyConstants.TOTAL_RECORDS), pMap.get(KFSPropertyConstants.TRAILER_RECORD_SECOND_EMPTY_FIELD))))); String trailerAmount = org.apache.commons.lang.StringUtils.trim(getValue(trailerLine, pMap.get(KFSPropertyConstants.TOTAL_AMOUNT), GeneralLedgerConstants.getSpaceAllCollectorBatchTrailerFields().length())); try { setTotalAmount(trailerAmount); } catch (NumberFormatException e) { setTotalAmount(KualiDecimal.ZERO); getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.ERROR_CUSTOM, "Collector trailer total amount cannot be parsed on line " + lineNumber + " amount string " + trailerAmount); } } }