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.bc.document.service.impl; 017 018 import java.io.BufferedReader; 019 import java.io.ByteArrayOutputStream; 020 import java.io.InputStream; 021 import java.io.InputStreamReader; 022 import java.math.BigDecimal; 023 import java.text.MessageFormat; 024 import java.util.ArrayList; 025 import java.util.Arrays; 026 import java.util.HashMap; 027 import java.util.List; 028 import java.util.Map; 029 030 import org.kuali.kfs.module.bc.BCConstants; 031 import org.kuali.kfs.module.bc.BCKeyConstants; 032 import org.kuali.kfs.module.bc.BCPropertyConstants; 033 import org.kuali.kfs.module.bc.businessobject.BudgetConstructionHeader; 034 import org.kuali.kfs.module.bc.businessobject.BudgetConstructionLockStatus; 035 import org.kuali.kfs.module.bc.businessobject.BudgetConstructionPayRateHolding; 036 import org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding; 037 import org.kuali.kfs.module.bc.document.dataaccess.PayrateImportDao; 038 import org.kuali.kfs.module.bc.document.service.BudgetDocumentService; 039 import org.kuali.kfs.module.bc.document.service.LockService; 040 import org.kuali.kfs.module.bc.document.service.PayrateImportService; 041 import org.kuali.kfs.module.bc.exception.BudgetConstructionLockUnavailableException; 042 import org.kuali.kfs.module.bc.util.BudgetParameterFinder; 043 import org.kuali.kfs.module.bc.util.ExternalizedMessageWrapper; 044 import org.kuali.kfs.sys.KFSConstants; 045 import org.kuali.kfs.sys.KFSPropertyConstants; 046 import org.kuali.kfs.sys.ObjectUtil; 047 import org.kuali.kfs.sys.context.SpringContext; 048 import org.kuali.kfs.sys.service.NonTransactional; 049 import org.kuali.kfs.sys.service.OptionsService; 050 import org.kuali.rice.kim.bo.Person; 051 import org.kuali.rice.kns.service.BusinessObjectService; 052 import org.kuali.rice.kns.service.KualiConfigurationService; 053 import org.kuali.rice.kns.util.KualiInteger; 054 import org.springframework.transaction.PlatformTransactionManager; 055 import org.springframework.transaction.TransactionStatus; 056 import org.springframework.transaction.annotation.Transactional; 057 import org.springframework.transaction.support.DefaultTransactionDefinition; 058 059 import com.lowagie.text.Document; 060 import com.lowagie.text.DocumentException; 061 import com.lowagie.text.Paragraph; 062 import com.lowagie.text.pdf.PdfWriter; 063 064 public class PayrateImportServiceImpl implements PayrateImportService { 065 066 private BusinessObjectService businessObjectService; 067 private LockService lockService; 068 private int importCount; 069 private int updateCount; 070 private OptionsService optionsService; 071 private PayrateImportDao payrateImportDao; 072 private BudgetDocumentService budgetDocumentService; 073 074 /** 075 * 076 * @see org.kuali.kfs.module.bc.service.PayrateImportService#importFile(java.io.InputStream) 077 */ 078 @Transactional 079 public boolean importFile(InputStream fileImportStream, List<ExternalizedMessageWrapper> messageList, String principalId) { 080 Map payRateHoldingPersonUniversalIdentifierKey = new HashMap(); 081 payRateHoldingPersonUniversalIdentifierKey.put(KFSPropertyConstants.PERSON_UNIVERSAL_IDENTIFIER, principalId); 082 083 this.businessObjectService.deleteMatching(BudgetConstructionPayRateHolding.class, payRateHoldingPersonUniversalIdentifierKey); 084 085 BufferedReader fileReader = new BufferedReader(new InputStreamReader(fileImportStream)); 086 this.importCount = 0; 087 088 try { 089 while(fileReader.ready()) { 090 BudgetConstructionPayRateHolding budgetConstructionPayRateHolding = new BudgetConstructionPayRateHolding(); 091 String line = fileReader.readLine(); 092 ObjectUtil.convertLineToBusinessObject(budgetConstructionPayRateHolding, line, DefaultImportFileFormat.fieldLengths, Arrays.asList(DefaultImportFileFormat.fieldNames)); 093 budgetConstructionPayRateHolding.setPrincipalId(principalId); 094 budgetConstructionPayRateHolding.setAppointmentRequestedPayRate(budgetConstructionPayRateHolding.getAppointmentRequestedPayRate().movePointLeft(2)); 095 businessObjectService.save(budgetConstructionPayRateHolding); 096 this.importCount++; 097 } 098 } 099 catch (Exception e) { 100 this.businessObjectService.deleteMatching(BudgetConstructionPayRateHolding.class, payRateHoldingPersonUniversalIdentifierKey); 101 messageList.add(new ExternalizedMessageWrapper(BCKeyConstants.ERROR_PAYRATE_IMPORT_ABORTED)); 102 103 return false; 104 } 105 106 if (importCount == 0 ) messageList.add(new ExternalizedMessageWrapper(BCKeyConstants.MSG_PAYRATE_IMPORT_NO_IMPORT_RECORDS)); 107 else messageList.add(new ExternalizedMessageWrapper(BCKeyConstants.MSG_PAYRATE_IMPORT_COUNT, String.valueOf(importCount))); 108 109 return true; 110 } 111 112 /** 113 * 114 * @see org.kuali.kfs.module.bc.service.PayrateImportService#update() 115 */ 116 @Transactional 117 public void update(Integer budgetYear, Person user, List<ExternalizedMessageWrapper> messageList, String principalId) { 118 List<PendingBudgetConstructionAppointmentFunding> lockedFundingRecords = new ArrayList<PendingBudgetConstructionAppointmentFunding>(); 119 boolean updateContainsErrors = false; 120 this.updateCount = 0; 121 122 Map payRateHoldingPersonUniversalIdentifierKey = new HashMap(); 123 payRateHoldingPersonUniversalIdentifierKey.put(KFSPropertyConstants.PERSON_UNIVERSAL_IDENTIFIER, principalId); 124 List<BudgetConstructionPayRateHolding> records = (List<BudgetConstructionPayRateHolding>) this.businessObjectService.findMatching(BudgetConstructionPayRateHolding.class, payRateHoldingPersonUniversalIdentifierKey); 125 126 if ( !getPayrateLock(lockedFundingRecords, messageList, budgetYear, user, records) ) { 127 messageList.add(new ExternalizedMessageWrapper(BCKeyConstants.ERROR_PAYRATE_UPDATE_ABORTED, String.valueOf(this.updateCount))); 128 doRollback(); 129 return; 130 } 131 132 List<String> biweeklyPayObjectCodes = BudgetParameterFinder.getBiweeklyPayObjectCodes(); 133 for (BudgetConstructionPayRateHolding holdingRecord : records) { 134 if (holdingRecord.getAppointmentRequestedPayRate().equals( -1.0)) { 135 messageList.add(new ExternalizedMessageWrapper(BCKeyConstants.ERROR_PAYRATE_IMPORT_NO_PAYROLL_MATCH, holdingRecord.getEmplid(), holdingRecord.getPositionNumber())); 136 updateContainsErrors = true; 137 continue; 138 } 139 140 List<PendingBudgetConstructionAppointmentFunding> fundingRecords = this.payrateImportDao.getFundingRecords(holdingRecord, budgetYear, biweeklyPayObjectCodes); 141 if (fundingRecords.isEmpty()) { 142 messageList.add(new ExternalizedMessageWrapper(BCKeyConstants.ERROR_PAYRATE_NO_ACTIVE_FUNDING_RECORDS, holdingRecord.getEmplid(), holdingRecord.getPositionNumber())); 143 updateContainsErrors = true; 144 continue; 145 } 146 147 for (PendingBudgetConstructionAppointmentFunding fundingRecord : fundingRecords) { 148 149 if ( !fundingRecord.getAppointmentRequestedPayRate().equals(holdingRecord.getAppointmentRequestedPayRate()) ) { 150 if (fundingRecord.getAppointmentRequestedFteQuantity().equals(0) || fundingRecord.getAppointmentRequestedFteQuantity() == null) { 151 messageList.add(new ExternalizedMessageWrapper(BCKeyConstants.ERROR_PAYRATE_NO_UPDATE_FTE_ZERO_OR_BLANK, holdingRecord.getEmplid(), holdingRecord.getPositionNumber(), fundingRecord.getChartOfAccountsCode(), fundingRecord.getAccountNumber(), fundingRecord.getSubAccountNumber())); 152 updateContainsErrors = true; 153 continue; 154 } 155 156 BigDecimal temp1 = fundingRecord.getAppointmentRequestedTimePercent().divide(new BigDecimal(100)); 157 BigDecimal temp2 = new BigDecimal((fundingRecord.getAppointmentFundingMonth()/fundingRecord.getBudgetConstructionPosition().getIuPayMonths()) * BudgetParameterFinder.getAnnualWorkingHours()); 158 159 KualiInteger annualAmount = new KualiInteger(holdingRecord.getAppointmentRequestedPayRate().multiply(temp1.multiply(temp2))); 160 KualiInteger updateAmount = annualAmount.subtract(fundingRecord.getAppointmentRequestedAmount()); 161 162 BudgetConstructionHeader header = budgetDocumentService.getBudgetConstructionHeader(fundingRecord); 163 if (header == null ) { 164 messageList.add(new ExternalizedMessageWrapper(BCKeyConstants.ERROR_PAYRATE_NO_BUDGET_DOCUMENT, budgetYear.toString(), fundingRecord.getChartOfAccountsCode(), fundingRecord.getAccountNumber(), fundingRecord.getSubAccountNumber())); 165 messageList.add(new ExternalizedMessageWrapper(BCKeyConstants.ERROR_PAYRATE_OBJECT_LEVEL_ERROR, fundingRecord.getEmplid(), fundingRecord.getPositionNumber(), fundingRecord.getChartOfAccountsCode(), fundingRecord.getAccountNumber(), fundingRecord.getSubAccountNumber())); 166 updateContainsErrors = true; 167 continue; 168 } 169 170 // update or create pending budget GL record and plug line 171 budgetDocumentService.updatePendingBudgetGeneralLedger(fundingRecord, updateAmount); 172 if (updateAmount.isNonZero()) { 173 budgetDocumentService.updatePendingBudgetGeneralLedgerPlug(fundingRecord, updateAmount.negated()); 174 } 175 176 fundingRecord.setAppointmentRequestedPayRate(holdingRecord.getAppointmentRequestedPayRate()); 177 fundingRecord.setAppointmentRequestedAmount(annualAmount); 178 this.businessObjectService.save(fundingRecord); 179 } 180 } 181 this.updateCount ++; 182 } 183 184 messageList.add(new ExternalizedMessageWrapper(BCKeyConstants.MSG_PAYRATE_IMPORT_UPDATE_COMPLETE, String.valueOf(updateCount))); 185 186 for (PendingBudgetConstructionAppointmentFunding recordToUnlock : lockedFundingRecords) { 187 this.lockService.unlockAccount(budgetDocumentService.getBudgetConstructionHeader(recordToUnlock)); 188 } 189 } 190 191 /** 192 * 193 * @see org.kuali.kfs.module.bc.service.PayrateImportService#generatePdf(java.lang.StringBuilder, java.io.ByteArrayOutputStream) 194 */ 195 @NonTransactional 196 public void generatePdf(List<ExternalizedMessageWrapper> logMessages, ByteArrayOutputStream baos) throws DocumentException { 197 Document document = new Document(); 198 PdfWriter.getInstance(document, baos); 199 document.open(); 200 for (ExternalizedMessageWrapper messageWrapper : logMessages) { 201 String message; 202 if (messageWrapper.getParams().length == 0 ) message = SpringContext.getBean(KualiConfigurationService.class).getPropertyString(messageWrapper.getMessageKey()); 203 else { 204 String temp = SpringContext.getBean(KualiConfigurationService.class).getPropertyString(messageWrapper.getMessageKey()); 205 message = MessageFormat.format(temp, (Object[])messageWrapper.getParams()); 206 } 207 document.add(new Paragraph(message)); 208 } 209 210 document.close(); 211 } 212 213 /** 214 * Sets the business object service 215 * 216 * @param businessObjectService 217 */ 218 @NonTransactional 219 public void setBusinessObjectService(BusinessObjectService businessObjectService) { 220 this.businessObjectService = businessObjectService; 221 } 222 223 /** 224 * sets lock service 225 * 226 * @param lockService 227 */ 228 @NonTransactional 229 public void setLockService(LockService lockService) { 230 this.lockService = lockService; 231 } 232 233 /** 234 * sets option service 235 * 236 * @param optionsService 237 */ 238 @NonTransactional 239 public void setOptionsService(OptionsService optionsService) { 240 this.optionsService = optionsService; 241 } 242 243 /** 244 * sets payrate import dao 245 * 246 * @param payrateImportDao 247 */ 248 @NonTransactional 249 public void setPayrateImportDao(PayrateImportDao payrateImportDao) { 250 this.payrateImportDao = payrateImportDao; 251 } 252 253 /** 254 * Sets the budgetDocumentService attribute value. 255 * @param budgetDocumentService The budgetDocumentService to set. 256 */ 257 @NonTransactional 258 public void setBudgetDocumentService(BudgetDocumentService budgetDocumentService) { 259 this.budgetDocumentService = budgetDocumentService; 260 } 261 262 /** 263 * Creates the locking key to use in retrieving account locks 264 * 265 * @param record 266 * @return 267 */ 268 protected String getLockingKeyString(PendingBudgetConstructionAppointmentFunding record) { 269 return record.getUniversityFiscalYear() + "-" + record.getChartOfAccountsCode() + "-" + record.getAccountNumber() + "-" + record.getSubAccountNumber(); 270 } 271 272 /** 273 * Retrieves Account locks for payrate import records 274 * 275 * @param lockMap 276 * @param messageList 277 * @param budgetYear 278 * @param user 279 * @param records 280 * @return 281 */ 282 @Transactional 283 protected boolean getPayrateLock(List<PendingBudgetConstructionAppointmentFunding> lockedRecords, List<ExternalizedMessageWrapper> messageList, Integer budgetYear, Person user, List<BudgetConstructionPayRateHolding> records) { 284 List<String> biweeklyPayObjectCodes = BudgetParameterFinder.getBiweeklyPayObjectCodes(); 285 286 for (BudgetConstructionPayRateHolding record: records) { 287 List<PendingBudgetConstructionAppointmentFunding> fundingRecords = this.payrateImportDao.getFundingRecords(record, budgetYear, biweeklyPayObjectCodes); 288 try { 289 lockedRecords.addAll(this.lockService.lockPendingBudgetConstructionAppointmentFundingRecords(fundingRecords, user)); 290 } catch(BudgetConstructionLockUnavailableException e) { 291 BudgetConstructionLockStatus lockStatus = e.getLockStatus(); 292 if ( lockStatus.getLockStatus().equals(BCConstants.LockStatus.BY_OTHER) ) { 293 messageList.add(new ExternalizedMessageWrapper(BCKeyConstants.ERROR_PAYRATE_ACCOUNT_LOCK_EXISTS)); 294 295 return false; 296 } else if ( lockStatus.getLockStatus().equals(BCConstants.LockStatus.FLOCK_FOUND) ) { 297 messageList.add(new ExternalizedMessageWrapper(BCKeyConstants.ERROR_PAYRATE_FUNDING_LOCK_EXISTS)); 298 299 return false; 300 } else if ( !lockStatus.getLockStatus().equals(BCConstants.LockStatus.SUCCESS) ) { 301 messageList.add(new ExternalizedMessageWrapper(BCKeyConstants.ERROR_PAYRATE_BATCH_ACCOUNT_LOCK_FAILED)); 302 return false; 303 } 304 } 305 306 } 307 308 return true; 309 } 310 311 /** 312 * File format for payrate import file 313 * 314 */ 315 protected static class DefaultImportFileFormat { 316 private static final int[] fieldLengths = new int[] {11, 8, 50, 5, 4, 3, 3, 10, 8}; 317 private static final String[] fieldNames = new String[] {KFSPropertyConstants.EMPLID, KFSPropertyConstants.POSITION_NUMBER, KFSPropertyConstants.PERSON_NAME, BCPropertyConstants.SET_SALARY_ID, BCPropertyConstants.SALARY_ADMINISTRATION_PLAN, BCPropertyConstants.GRADE, "unionCode", BCPropertyConstants.APPOINTMENT_REQUESTED_PAY_RATE, BCPropertyConstants.CSF_FREEZE_DATE}; 318 } 319 320 /** 321 * If retrieving budget locks fails, this method rolls back previous changes 322 * 323 */ 324 protected void doRollback() { 325 PlatformTransactionManager transactionManager = SpringContext.getBean(PlatformTransactionManager.class); 326 DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition(); 327 TransactionStatus transactionStatus = transactionManager.getTransaction(defaultTransactionDefinition); 328 transactionManager.rollback( transactionStatus ); 329 330 } 331 332 } 333