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