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.module.endow.document;
017    
018    import java.math.BigDecimal;
019    import java.sql.Date;
020    import java.util.Map;
021    
022    import org.kuali.kfs.fp.batch.service.impl.ProcurementCardCreateDocumentServiceImpl;
023    import org.kuali.kfs.module.endow.EndowParameterKeyConstants;
024    import org.kuali.kfs.module.endow.businessobject.PooledFundValue;
025    import org.kuali.kfs.module.endow.businessobject.Security;
026    import org.kuali.kfs.module.endow.document.service.SecurityService;
027    import org.kuali.kfs.sys.context.SpringContext;
028    import org.kuali.rice.kew.exception.WorkflowException;
029    import org.kuali.rice.kns.bo.DocumentHeader;
030    import org.kuali.rice.kns.document.MaintenanceDocument;
031    import org.kuali.rice.kns.maintenance.KualiMaintainableImpl;
032    import org.kuali.rice.kns.service.BusinessObjectService;
033    import org.kuali.rice.kns.service.DocumentService;
034    import org.kuali.rice.kns.service.ParameterService;
035    import org.kuali.rice.kns.util.KNSConstants;
036    import org.kuali.rice.kns.util.ObjectUtils;
037    import org.kuali.rice.kns.workflow.service.KualiWorkflowDocument;
038    
039    public class PooledFundValueMaintainableImpl extends KualiMaintainableImpl {
040        private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ProcurementCardCreateDocumentServiceImpl.class);
041    
042        private PooledFundValue newPooledFundValue = null;
043        private PooledFundValue oldPooledFundValue = null;
044        private Security theSecurity = null;
045    
046        /**
047         * Overrides the parent method to check the status of the Pooled Fund Value Maintenance document. Business Rule 1: Saving a new
048         * or changed unit value will result in changes to the Security unit values and the associated holding market values. Once the
049         * document has been approved, If it is in the edit-maintenance doc mode, compare unit value between old and new pooled fund
050         * value BOs. If it is in the create-new or copy maintenance doc mode, compare unit value in the new pooled fund value BO with
051         * unit value in the referred Security record. When saving a new or changed unit value 1) Copy current Security Unit Value to
052         * Previous Unit Value 2) Copy current Security Price Date to Previous Price dAte 3) Copy PooledFundValue.POOL_FND_UNIT_VAL to
053         * current Security Unit Price 4) Copy PooledFundValue.VAL_EFF_DT to current Security Price Date 5) Update Security.Security
054         * Source to Pooled Fund Value Business Rule 2:When the DSTRB_AMT is updated, the amount of the distribution is multiplied by
055         * the number of times per year that the institution will distribute income to the account holders (this is an institution
056         * specified parameter: DISTRIBUTION_TIMES_PER_YEAR stored in the KFS parameter table) and the result is copied to END_SEC_T:
057         * SEC_RT for the Pooled Fund SEC_ID.
058         * 
059         * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#doRouteStatusChange(org.kuali.rice.kns.bo.DocumentHeader)
060         */
061        @Override
062        public void doRouteStatusChange(DocumentHeader header) {
063            super.doRouteStatusChange(header);
064            KualiWorkflowDocument workflowDoc = header.getWorkflowDocument();
065    
066            DocumentService documentService = SpringContext.getBean(DocumentService.class);
067            try {
068                MaintenanceDocument maintDoc = (MaintenanceDocument) documentService.getByDocumentHeaderId(header.getDocumentNumber());
069    
070                initializeAttributes(maintDoc);
071    
072                // Use the stateIsFinal() method so this code is only executed when the final approval occurs
073                if (workflowDoc.stateIsFinal()) {
074                    BigDecimal oldUnitValue = null;
075                    BigDecimal oldDistributionPerUnit = null;
076                    BigDecimal newUnitValue = newPooledFundValue.getUnitValue();
077                    BigDecimal newDistributionPerUnit = newPooledFundValue.getIncomeDistributionPerUnit();
078                    BigDecimal oldIncomeDistributionPerUnit = oldPooledFundValue.getIncomeDistributionPerUnit();
079                    BigDecimal newIncomeDistributionPerUnit = newPooledFundValue.getIncomeDistributionPerUnit();
080                    Date newValueDate = newPooledFundValue.getValueEffectiveDate();
081                    String newUnitValueSource = EndowParameterKeyConstants.POOLED_FUND_VALUE;
082                    String pooledSecurityID = newPooledFundValue.getPooledSecurityID();
083    
084                    // Why the following method didn't work?
085                    // newPooledFundValue.refreshNonUpdateableReferences();
086                    // Security theSecurity = newPooledFundValue.getPooledFundControl().getSecurity();
087                    SecurityService securityService = SpringContext.getBean(SecurityService.class);
088                    Security theSecurity = securityService.getByPrimaryKey(pooledSecurityID);
089    
090                    ParameterService parameterService = SpringContext.getBean(ParameterService.class);
091                    BigDecimal numOfDistributions = new BigDecimal(new Double(parameterService.getParameterValue(PooledFundValue.class, EndowParameterKeyConstants.DISTRIBUTION_TIMES_PER_YEAR)).doubleValue());
092                    BigDecimal interestRate = newDistributionPerUnit.multiply(numOfDistributions);
093    
094                    /*
095                     * Note: There is an assumption here that the unitValue in the oldPooledFundValue is always equal to the unitValue
096                     * in the referred Security object. A business rule has to be added for Security impl -- when a Security has the
097                     * class code type equals to Pooled Investment, the unit value can't be modify directly through Security maintenance
098                     * doc.
099                     */
100                    if (KNSConstants.MAINTENANCE_EDIT_ACTION.equals(getMaintenanceAction())) {
101                        oldUnitValue = oldPooledFundValue.getUnitValue();
102                        oldDistributionPerUnit = oldPooledFundValue.getIncomeDistributionPerUnit();
103                        if (newUnitValue.compareTo(oldUnitValue) != 0) {
104                            compareAndUpdateUnitValue(oldUnitValue, newUnitValue, theSecurity, newValueDate, newUnitValueSource);
105                        }
106                        if (newDistributionPerUnit.compareTo(oldDistributionPerUnit) != 0) {
107                            updateInterestRate(theSecurity, interestRate);
108                        }                    
109                        if (newIncomeDistributionPerUnit.compareTo(oldIncomeDistributionPerUnit) != 0) {
110                            updateIncomeChangeDate(theSecurity);    
111                        }
112                    }
113                    else if (KNSConstants.MAINTENANCE_COPY_ACTION.equals(getMaintenanceAction()) || KNSConstants.MAINTENANCE_NEW_ACTION.equals(getMaintenanceAction())) {
114                        oldUnitValue = theSecurity.getUnitValue();
115                        compareAndUpdateUnitValue(oldUnitValue, newUnitValue, theSecurity, newValueDate, newUnitValueSource);
116                        updateInterestRate(theSecurity, interestRate);
117                        updateIncomeChangeDate(theSecurity);                    
118                    }
119                }
120            }
121            catch (WorkflowException we) {
122                LOG.error("encountered workflow exception while attempting to retrieve PooledFundValueMaintenanceDocument: " + header.getDocumentNumber() + " " + we);
123                throw new RuntimeException(we.getMessage());
124            }
125    
126        }
127    
128        private void compareAndUpdateUnitValue(BigDecimal oldUnitValue, BigDecimal newUnitValue, Security theSecurity, Date newValueDate, String newUnitValueSource) {
129            SecurityService securityService = SpringContext.getBean(SecurityService.class);
130            if (newUnitValue.compareTo(oldUnitValue) != 0) {
131                Security security = securityService.updateUnitValue(theSecurity, newUnitValue, newValueDate, newUnitValueSource);
132                SpringContext.getBean(BusinessObjectService.class).save(security);
133            }
134        }
135    
136        private void updateInterestRate(Security theSecurity, BigDecimal interestRate) {
137            SecurityService securityService = SpringContext.getBean(SecurityService.class);
138            Security security = securityService.updateInterestRate(theSecurity, interestRate);
139            SpringContext.getBean(BusinessObjectService.class).save(security);
140        }
141        
142        /**
143         * Changes IncomeChangeDate in Security to the current date
144         * 
145         * @param theSecurity
146         */
147        private void updateIncomeChangeDate(Security theSecurity) {
148            SecurityService securityService = SpringContext.getBean(SecurityService.class);
149            Security security = securityService.updateIncomeChangeDate(theSecurity);
150            SpringContext.getBean(BusinessObjectService.class).save(security);
151        }
152    
153        /**
154         * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#processAfterCopy(org.kuali.rice.kns.document.MaintenanceDocument,
155         *      java.util.Map)
156         */
157        @Override
158        public void processAfterCopy(MaintenanceDocument document, Map<String, String[]> parameters) {
159            super.processAfterCopy(document, parameters);
160            initializeAttributes(document);
161    
162            // clear out all fields except unit value and income distribution per unit
163            newPooledFundValue.setDistributeIncomeOnDate(null);
164            newPooledFundValue.setDistributeLongTermGainLossOnDate(null);
165            newPooledFundValue.setDistributeShortTermGainLossOnDate(null);
166            newPooledFundValue.setIncomeDistributionComplete(false);
167            newPooledFundValue.setLongTermGainLossDistributionComplete(false);
168            newPooledFundValue.setLongTermGainLossDistributionPerUnit(BigDecimal.ZERO);
169            newPooledFundValue.setShortTermGainLossDistributionComplete(false);
170            newPooledFundValue.setShortTermGainLossDistributionPerUnit(BigDecimal.ZERO);
171            newPooledFundValue.setValuationDate(null);
172            newPooledFundValue.setValueEffectiveDate(null);
173    
174        }
175    
176    
177        /**
178         * Initializes newSecurity and oldSecurity.
179         * 
180         * @param document
181         */
182        private void initializeAttributes(MaintenanceDocument document) {
183            if (newPooledFundValue == null) {
184                newPooledFundValue = (PooledFundValue) document.getNewMaintainableObject().getBusinessObject();
185            }
186            if (oldPooledFundValue == null) {
187                oldPooledFundValue = (PooledFundValue) document.getOldMaintainableObject().getBusinessObject();
188            }
189        }
190    }