001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.kfs.sys.document.validation.impl;
017    
018    import org.kuali.kfs.sys.KFSConstants;
019    import org.kuali.kfs.sys.KFSKeyConstants;
020    import org.kuali.kfs.sys.context.SpringContext;
021    import org.kuali.kfs.sys.document.AccountingDocument;
022    import org.kuali.kfs.sys.document.validation.GenericValidation;
023    import org.kuali.kfs.sys.document.validation.event.AttributedDocumentEvent;
024    import org.kuali.kfs.sys.document.validation.event.AttributedSaveDocumentEvent;
025    import org.kuali.rice.kew.exception.WorkflowException;
026    import org.kuali.rice.kns.service.DocumentService;
027    import org.kuali.rice.kns.util.GlobalVariables;
028    import org.kuali.rice.kns.util.KualiDecimal;
029    import org.kuali.rice.kns.web.format.CurrencyFormatter;
030    
031    /**
032     * A validation, used on accounting document approval, that accounting line totals are unchanged
033     */
034    public class AccountingLineGroupTotalsUnchangedValidation extends GenericValidation {
035        private AccountingDocument accountingDocumentForValidation;
036        
037        /**
038         * Checks that the source and total amounts on the current version of the accounting document
039         * are equal to the persisted source and total totals.
040         * <strong>Expects a document to be sent in as the first parameter</strong>
041         * @see org.kuali.kfs.sys.document.validation.GenericValidation#validate(java.lang.Object[])
042         */
043        public boolean validate(AttributedDocumentEvent event) {
044            AccountingDocument persistedDocument = null;
045            
046            if (event instanceof AttributedSaveDocumentEvent && !accountingDocumentForValidation.getDocumentHeader().getWorkflowDocument().stateIsEnroute()) {
047                return true; // only check save document events if the document is enroute
048            }
049    
050            persistedDocument = retrievePersistedDocument(accountingDocumentForValidation);
051    
052            boolean isUnchanged = true;
053            if (persistedDocument == null) {
054                handleNonExistentDocumentWhenApproving(accountingDocumentForValidation);
055            }
056            else {
057                // retrieve the persisted totals
058                KualiDecimal persistedSourceLineTotal = persistedDocument.getSourceTotal();
059                KualiDecimal persistedTargetLineTotal = persistedDocument.getTargetTotal();
060    
061                // retrieve the updated totals
062                KualiDecimal currentSourceLineTotal = accountingDocumentForValidation.getSourceTotal();
063                KualiDecimal currentTargetLineTotal = accountingDocumentForValidation.getTargetTotal();
064    
065                // make sure that totals have remained unchanged, if not, recognize that, and
066                // generate appropriate error messages
067                if (currentSourceLineTotal.compareTo(persistedSourceLineTotal) != 0) {
068                    isUnchanged = false;
069    
070                    // build out error message
071                    buildTotalChangeErrorMessage(KFSConstants.SOURCE_ACCOUNTING_LINE_ERRORS, persistedSourceLineTotal, currentSourceLineTotal);
072                }
073    
074                if (currentTargetLineTotal.compareTo(persistedTargetLineTotal) != 0) {
075                    isUnchanged = false;
076    
077                    // build out error message
078                    buildTotalChangeErrorMessage(KFSConstants.TARGET_ACCOUNTING_LINE_ERRORS, persistedTargetLineTotal, currentTargetLineTotal);
079                }
080            }
081    
082            return isUnchanged;
083        }
084    
085        /**
086         * attempt to retrieve the document from the DB for comparison
087         * 
088         * @param accountingDocument
089         * @return AccountingDocument
090         */
091        protected AccountingDocument retrievePersistedDocument(AccountingDocument accountingDocument) {
092            AccountingDocument persistedDocument = null;
093    
094            try {
095                persistedDocument = (AccountingDocument) SpringContext.getBean(DocumentService.class).getByDocumentHeaderId(accountingDocument.getDocumentNumber());
096            }
097            catch (WorkflowException we) {
098                handleNonExistentDocumentWhenApproving(accountingDocument);
099            }
100    
101            return persistedDocument;
102        }
103        
104        /**
105         * This method builds out the error message for when totals have changed.
106         * 
107         * @param propertyName
108         * @param persistedSourceLineTotal
109         * @param currentSourceLineTotal
110         */
111        protected void buildTotalChangeErrorMessage(String propertyName, KualiDecimal persistedSourceLineTotal, KualiDecimal currentSourceLineTotal) {
112            String persistedTotal = (String) new CurrencyFormatter().format(persistedSourceLineTotal);
113            String currentTotal = (String) new CurrencyFormatter().format(currentSourceLineTotal);
114            GlobalVariables.getMessageMap().putError(propertyName, KFSKeyConstants.ERROR_DOCUMENT_SINGLE_ACCOUNTING_LINE_SECTION_TOTAL_CHANGED, new String[] { persistedTotal, currentTotal });
115        }
116    
117        /**
118         * Handles the case when a non existent document is attempted to be retrieve and that if it's in an initiated state, it's ok.
119         * 
120         * @param accountingDocument
121         */
122        protected final void handleNonExistentDocumentWhenApproving(AccountingDocument accountingDocument) {
123            // check to make sure this isn't an initiated document being blanket approved
124            if (!accountingDocument.getDocumentHeader().getWorkflowDocument().stateIsInitiated()) {
125                throw new IllegalStateException("Document " + accountingDocument.getDocumentNumber() + " is not a valid document that currently exists in the system.");
126            }
127        }
128    
129        /**
130         * Gets the accountingDocumentForValidation attribute. 
131         * @return Returns the accountingDocumentForValidation.
132         */
133        public AccountingDocument getAccountingDocumentForValidation() {
134            return accountingDocumentForValidation;
135        }
136    
137        /**
138         * Sets the accountingDocumentForValidation attribute value.
139         * @param accountingDocumentForValidation The accountingDocumentForValidation to set.
140         */
141        public void setAccountingDocumentForValidation(AccountingDocument accountingDocumentForValidation) {
142            this.accountingDocumentForValidation = accountingDocumentForValidation;
143        }
144    }