/*
* 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.document;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import org.kuali.kfs.gl.GeneralLedgerConstants;
import org.kuali.kfs.gl.batch.CorrectionProcessScrubberStep;
import org.kuali.kfs.gl.businessobject.CorrectionChangeGroup;
import org.kuali.kfs.gl.document.service.CorrectionDocumentService;
import org.kuali.kfs.gl.service.OriginEntryGroupService;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.batch.BatchSpringContext;
import org.kuali.kfs.sys.batch.Step;
import org.kuali.kfs.sys.context.ProxyUtils;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.document.AmountTotaling;
import org.kuali.kfs.sys.document.FinancialSystemTransactionalDocumentBase;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.kew.framework.postprocessor.DocumentRouteLevelChange;
import org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange;
import org.kuali.rice.krad.util.ObjectUtils;
/**
* The General Ledger Correction Document, a document that allows editing and processing of origin entry groups and the origin
* entries within them.
*/
public class GeneralLedgerCorrectionProcessDocument extends FinancialSystemTransactionalDocumentBase implements AmountTotaling {
protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(GeneralLedgerCorrectionProcessDocument.class);
protected String correctionTypeCode; // CorrectionDocumentService.CORRECTION_TYPE_MANUAL or
protected boolean correctionSelection; // false if all input rows should be in the output, true if only selected rows should be
// in the output
protected boolean correctionFileDelete; // false if the file should be processed by scrubber, true if the file should not be
// processed by scrubber
protected Integer correctionRowCount; // Row count in output group
protected KualiDecimal correctionDebitTotalAmount; // Debit amount total in output group
protected KualiDecimal correctionCreditTotalAmount; // Credit amount total in output group
protected KualiDecimal correctionBudgetTotalAmount; // Budget amount total in output group
protected String correctionInputFileName; // input file name
protected String correctionOutputFileName; // output file name
protected String correctionScriptText; // Not used
protected Integer correctionChangeGroupNextLineNumber;
protected List<CorrectionChangeGroup> correctionChangeGroup;
public GeneralLedgerCorrectionProcessDocument() {
super();
correctionChangeGroupNextLineNumber = new Integer(0);
correctionChangeGroup = new ArrayList<CorrectionChangeGroup>();
}
/**
* Returns a Map representation of the primary key of this document
*
* @return a Map that represents the database key of this document
* @see org.kuali.rice.krad.bo.BusinessObjectBase#toStringMapper()
*/
protected LinkedHashMap toStringMapper_RICE20_REFACTORME() {
LinkedHashMap m = new LinkedHashMap();
m.put(KFSPropertyConstants.DOCUMENT_NUMBER, this.documentNumber);
return m;
}
/**
* Returns the editing method to use on the origin entries in the document, either "Manual Edit," "Using Criteria," "Remove
* Group from Processing," or "Not Available"
*
* @return the String representation of the method this document is using
*/
public String getMethod() {
if (CorrectionDocumentService.CORRECTION_TYPE_MANUAL.equals(correctionTypeCode)) {
return "Manual Edit";
}
else if (CorrectionDocumentService.CORRECTION_TYPE_CRITERIA.equals(correctionTypeCode)) {
return "Using Criteria";
}
else if (CorrectionDocumentService.CORRECTION_TYPE_REMOVE_GROUP_FROM_PROCESSING.equals(correctionTypeCode)) {
return "Remove Group from Processing";
}
else {
return KFSConstants.NOT_AVAILABLE_STRING;
}
}
/**
* Returns the source of the origin entries this document uses: either an uploaded file of origin entries or the database
*
* @return a String with the name of the system in use
*/
public String getSystem() {
if (correctionInputFileName != null) {
return "File Upload";
}
else {
return "Database";
}
}
/**
* This method...
*
* @param ccg
*/
public void addCorrectionChangeGroup(CorrectionChangeGroup ccg) {
ccg.setDocumentNumber(documentNumber);
ccg.setCorrectionChangeGroupLineNumber(correctionChangeGroupNextLineNumber++);
correctionChangeGroup.add(ccg);
}
/**
* This method...
*
* @param changeNumber
*/
public void removeCorrectionChangeGroup(int changeNumber) {
for (Iterator iter = correctionChangeGroup.iterator(); iter.hasNext();) {
CorrectionChangeGroup element = (CorrectionChangeGroup) iter.next();
if (changeNumber == element.getCorrectionChangeGroupLineNumber().intValue()) {
iter.remove();
}
}
}
/**
* This method...
*
* @param groupNumber
* @return
*/
public CorrectionChangeGroup getCorrectionChangeGroupItem(int groupNumber) {
for (Iterator iter = correctionChangeGroup.iterator(); iter.hasNext();) {
CorrectionChangeGroup element = (CorrectionChangeGroup) iter.next();
if (groupNumber == element.getCorrectionChangeGroupLineNumber().intValue()) {
return element;
}
}
CorrectionChangeGroup ccg = new CorrectionChangeGroup(documentNumber, groupNumber);
correctionChangeGroup.add(ccg);
return ccg;
}
public void doRouteStatusChange(DocumentRouteStatusChange statusChangeEvent) {
super.doRouteStatusChange(statusChangeEvent);
if (getDocumentHeader().getWorkflowDocument().isProcessed()) {
if (LOG.isDebugEnabled()) {
LOG.debug("GLCP Route status Change: " + statusChangeEvent);
}
CorrectionDocumentService correctionDocumentService = SpringContext.getBean(CorrectionDocumentService.class);
OriginEntryGroupService originEntryGroupService = SpringContext.getBean(OriginEntryGroupService.class);
String docId = getDocumentHeader().getDocumentNumber();
GeneralLedgerCorrectionProcessDocument doc = correctionDocumentService.findByCorrectionDocumentHeaderId(docId);
String correctionType = doc.getCorrectionTypeCode();
if (CorrectionDocumentService.CORRECTION_TYPE_REMOVE_GROUP_FROM_PROCESSING.equals(correctionType)) {
String dataFileName = doc.getCorrectionInputFileName();
String doneFileName = dataFileName.replace(GeneralLedgerConstants.BatchFileSystem.EXTENSION, GeneralLedgerConstants.BatchFileSystem.DONE_FILE_EXTENSION);
originEntryGroupService.deleteFile(doneFileName);
}
else if (CorrectionDocumentService.CORRECTION_TYPE_MANUAL.equals(correctionType)
|| CorrectionDocumentService.CORRECTION_TYPE_CRITERIA.equals(correctionType)) {
// KFSMI-5760 - apparently, this node can be executed more than once, which results in multiple
// files being created. We need to check for the existence of a file with the proper
// name pattern and abort the rest of this if found
synchronized ( CorrectionDocumentService.class ) {
if ( !checkForExistingOutputDocument( doc.getDocumentNumber() ) ) {
// save the output file to originEntry directory when correctionFileDelete is false
DateTimeService dateTimeService = SpringContext.getBean(DateTimeService.class);
Date today = dateTimeService.getCurrentDate();
// generate output file and set file name
String outputFileName = "";
if (!correctionFileDelete) {
outputFileName = correctionDocumentService.createOutputFileForProcessing(doc.getDocumentNumber(), today);
}
doc.setCorrectionOutputFileName(outputFileName);
Step step = BatchSpringContext.getStep(CorrectionProcessScrubberStep.STEP_NAME);
CorrectionProcessScrubberStep correctionStep = (CorrectionProcessScrubberStep) ProxyUtils.getTargetIfProxied(step);
correctionStep.setDocumentId(docId);
try {
step.execute(getClass().getName(), dateTimeService.getCurrentDate());
}
catch (Exception e) {
LOG.error("GLCP scrubber encountered error:", e);
throw new RuntimeException("GLCP scrubber encountered error:", e);
}
correctionStep = (CorrectionProcessScrubberStep) ProxyUtils.getTargetIfProxied(step);
correctionStep.setDocumentId(null);
correctionDocumentService.generateCorrectionReport(this);
correctionDocumentService.aggregateCorrectionDocumentReports(this);
} else {
LOG.warn( "Attempt to re-process final GLCP operations for document: " + doc.getDocumentNumber() + " File with that document number already exists." );
}
}
}
else {
LOG.error("GLCP doc " + doc.getDocumentNumber() + " has an unknown correction type code: " + correctionType);
}
}
}
/**
* Returns true if an existing document like "glcp_output.docNum" is found.
*/
protected boolean checkForExistingOutputDocument( String documentNumber ) {
CorrectionDocumentService correctionDocumentService = SpringContext.getBean(CorrectionDocumentService.class);
String[] filenamesFound = correctionDocumentService.findExistingCorrectionOutputFilesForDocument(documentNumber);
if ( LOG.isInfoEnabled() ) {
LOG.info( "Scanned for output files for document: " + documentNumber );
LOG.info( "Files Found: " + Arrays.toString(filenamesFound));
}
return filenamesFound != null && filenamesFound.length > 0;
}
/**
* Waits for the event of the route level changing to "Approve" and at that point, saving all the entries as origin entries in a
* newly created origin entry group, then scrubbing those entries
*
* @param cahnge a representation of the route level changed that just occurred
* @see org.kuali.rice.krad.document.DocumentBase#handleRouteLevelChange(org.kuali.rice.kew.clientapp.vo.DocumentRouteLevelChangeDTO)
*/
@Override
public void doRouteLevelChange(DocumentRouteLevelChange change) {
super.doRouteLevelChange(change);
}
/**
* Returns the total dollar amount associated with this document
*
* @return if credit total is zero, debit total, otherwise credit total
*/
public KualiDecimal getTotalDollarAmount() {
return getCorrectionCreditTotalAmount().add(getCorrectionDebitTotalAmount());
}
/**
* Sets this document's document number, but also sets the document number on all children objects
*
* @param documentNumber the document number for this document
* @see org.kuali.rice.krad.document.DocumentBase#setDocumentNumber(java.lang.String)
*/
@Override
public void setDocumentNumber(String documentNumber) {
super.setDocumentNumber(documentNumber);
for (Iterator iter = correctionChangeGroup.iterator(); iter.hasNext();) {
CorrectionChangeGroup element = (CorrectionChangeGroup) iter.next();
element.setDocumentNumber(documentNumber);
}
}
public String getCorrectionTypeCode() {
return correctionTypeCode;
}
public void setCorrectionTypeCode(String correctionTypeCode) {
this.correctionTypeCode = correctionTypeCode;
}
public boolean getCorrectionSelection() {
return correctionSelection;
}
public void setCorrectionSelection(boolean correctionSelection) {
this.correctionSelection = correctionSelection;
}
public boolean getCorrectionFileDelete() {
return correctionFileDelete;
}
public void setCorrectionFileDelete(boolean correctionFileDelete) {
this.correctionFileDelete = correctionFileDelete;
}
public Integer getCorrectionRowCount() {
return correctionRowCount;
}
public void setCorrectionRowCount(Integer correctionRowCount) {
this.correctionRowCount = correctionRowCount;
}
public Integer getCorrectionChangeGroupNextLineNumber() {
return correctionChangeGroupNextLineNumber;
}
public void setCorrectionChangeGroupNextLineNumber(Integer correctionChangeGroupNextLineNumber) {
this.correctionChangeGroupNextLineNumber = correctionChangeGroupNextLineNumber;
}
public KualiDecimal getCorrectionDebitTotalAmount() {
if (ObjectUtils.isNull(correctionDebitTotalAmount)) {
return KualiDecimal.ZERO;
}
return correctionDebitTotalAmount;
}
public void setCorrectionDebitTotalAmount(KualiDecimal correctionDebitTotalAmount) {
this.correctionDebitTotalAmount = correctionDebitTotalAmount;
}
public KualiDecimal getCorrectionCreditTotalAmount() {
if (ObjectUtils.isNull(correctionCreditTotalAmount)) {
return KualiDecimal.ZERO;
}
return correctionCreditTotalAmount;
}
public void setCorrectionCreditTotalAmount(KualiDecimal correctionCreditTotalAmount) {
this.correctionCreditTotalAmount = correctionCreditTotalAmount;
}
public KualiDecimal getCorrectionBudgetTotalAmount() {
return correctionBudgetTotalAmount;
}
public void setCorrectionBudgetTotalAmount(KualiDecimal correctionBudgetTotalAmount) {
this.correctionBudgetTotalAmount = correctionBudgetTotalAmount;
}
public String getCorrectionInputFileName() {
return correctionInputFileName;
}
public void setCorrectionInputFileName(String correctionInputFileName) {
this.correctionInputFileName = correctionInputFileName;
}
public String getCorrectionOutputFileName() {
return correctionOutputFileName;
}
public void setCorrectionOutputFileName(String correctionOutputFileName) {
this.correctionOutputFileName = correctionOutputFileName;
}
public List<CorrectionChangeGroup> getCorrectionChangeGroup() {
Collections.sort(correctionChangeGroup);
return correctionChangeGroup;
}
public void setCorrectionChangeGroup(List<CorrectionChangeGroup> correctionChangeGroup) {
this.correctionChangeGroup = correctionChangeGroup;
}
protected String buildFileExtensionWithDate(Date date) {
String dateFormatStr = ".yyyy-MMM-dd.HH-mm-ss";
DateFormat dateFormat = new SimpleDateFormat(dateFormatStr);
return dateFormat.format(date) + GeneralLedgerConstants.BatchFileSystem.EXTENSION;
}
}