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.fp.batch.service.impl;
017    
018    import static org.kuali.kfs.fp.document.validation.impl.ProcurementCardDocumentRuleConstants.AUTO_APPROVE_DOCUMENTS_IND;
019    import static org.kuali.kfs.fp.document.validation.impl.ProcurementCardDocumentRuleConstants.AUTO_APPROVE_NUMBER_OF_DAYS;
020    import static org.kuali.kfs.fp.document.validation.impl.ProcurementCardDocumentRuleConstants.DEFAULT_TRANS_ACCOUNT_PARM_NM;
021    import static org.kuali.kfs.fp.document.validation.impl.ProcurementCardDocumentRuleConstants.DEFAULT_TRANS_CHART_CODE_PARM_NM;
022    import static org.kuali.kfs.fp.document.validation.impl.ProcurementCardDocumentRuleConstants.DEFAULT_TRANS_OBJECT_CODE_PARM_NM;
023    import static org.kuali.kfs.fp.document.validation.impl.ProcurementCardDocumentRuleConstants.ERROR_TRANS_ACCOUNT_PARM_NM;
024    import static org.kuali.kfs.fp.document.validation.impl.ProcurementCardDocumentRuleConstants.SINGLE_TRANSACTION_IND_PARM_NM;
025    import static org.kuali.kfs.sys.KFSConstants.GL_CREDIT_CODE;
026    import static org.kuali.kfs.sys.KFSConstants.FinancialDocumentTypeCodes.PROCUREMENT_CARD;
027    
028    import java.rmi.RemoteException;
029    import java.sql.Timestamp;
030    import java.util.ArrayList;
031    import java.util.HashMap;
032    import java.util.Iterator;
033    import java.util.List;
034    import java.util.Map;
035    
036    import org.apache.commons.lang.StringUtils;
037    import org.kuali.kfs.fp.batch.ProcurementCardAutoApproveDocumentsStep;
038    import org.kuali.kfs.fp.batch.ProcurementCardCreateDocumentsStep;
039    import org.kuali.kfs.fp.batch.ProcurementCardLoadStep;
040    import org.kuali.kfs.fp.batch.service.ProcurementCardCreateDocumentService;
041    import org.kuali.kfs.fp.businessobject.ProcurementCardHolder;
042    import org.kuali.kfs.fp.businessobject.ProcurementCardSourceAccountingLine;
043    import org.kuali.kfs.fp.businessobject.ProcurementCardTargetAccountingLine;
044    import org.kuali.kfs.fp.businessobject.ProcurementCardTransaction;
045    import org.kuali.kfs.fp.businessobject.ProcurementCardTransactionDetail;
046    import org.kuali.kfs.fp.businessobject.ProcurementCardVendor;
047    import org.kuali.kfs.fp.document.ProcurementCardDocument;
048    import org.kuali.kfs.fp.document.validation.impl.ProcurementCardDocumentRuleConstants;
049    import org.kuali.kfs.integration.cab.CapitalAssetBuilderModuleService;
050    import org.kuali.kfs.sys.KFSConstants;
051    import org.kuali.kfs.sys.KFSPropertyConstants;
052    import org.kuali.kfs.sys.context.SpringContext;
053    import org.kuali.kfs.sys.document.service.AccountingLineRuleHelperService;
054    import org.kuali.kfs.sys.document.service.FinancialSystemDocumentService;
055    import org.kuali.kfs.sys.document.validation.event.DocumentSystemSaveEvent;
056    import org.kuali.rice.kew.dto.DocumentSearchCriteriaDTO;
057    import org.kuali.rice.kew.dto.DocumentSearchResultDTO;
058    import org.kuali.rice.kew.dto.DocumentSearchResultRowDTO;
059    import org.kuali.rice.kew.dto.KeyValueDTO;
060    import org.kuali.rice.kew.exception.WorkflowException;
061    import org.kuali.rice.kew.util.KEWConstants;
062    import org.kuali.rice.kew.util.KEWPropertyConstants;
063    import org.kuali.rice.kns.bo.DocumentHeader;
064    import org.kuali.rice.kns.service.BusinessObjectService;
065    import org.kuali.rice.kns.service.DataDictionaryService;
066    import org.kuali.rice.kns.service.DateTimeService;
067    import org.kuali.rice.kns.service.DocumentService;
068    import org.kuali.rice.kns.service.ParameterService;
069    import org.kuali.rice.kns.util.DateUtils;
070    import org.kuali.rice.kns.util.GlobalVariables;
071    import org.kuali.rice.kns.util.KualiDecimal;
072    import org.kuali.rice.kns.util.MessageMap;
073    import org.kuali.rice.kns.util.ObjectUtils;
074    import org.kuali.rice.kns.workflow.service.KualiWorkflowInfo;
075    import org.kuali.rice.kns.workflow.service.WorkflowDocumentService;
076    import org.springframework.transaction.annotation.Transactional;
077    
078    
079    /**
080     * This is the default implementation of the ProcurementCardCreateDocumentService interface.
081     * 
082     * @see org.kuali.kfs.fp.batch.service.ProcurementCardCreateDocumentService
083     */
084    @Transactional
085    public class ProcurementCardCreateDocumentServiceImpl implements ProcurementCardCreateDocumentService {
086        private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ProcurementCardCreateDocumentServiceImpl.class);
087        
088        protected static final String WORKFLOW_SEARCH_RESULT_KEY = KEWPropertyConstants.DOC_SEARCH_RESULT_PROPERTY_NAME_ROUTE_HEADER_ID;
089    
090        protected ParameterService parameterService;
091        protected BusinessObjectService businessObjectService;
092        protected DocumentService documentService;
093        protected DataDictionaryService dataDictionaryService;
094        protected DateTimeService dateTimeService;
095        protected WorkflowDocumentService workflowDocumentService;
096        protected AccountingLineRuleHelperService accountingLineRuleUtil;
097        protected CapitalAssetBuilderModuleService capitalAssetBuilderModuleService;
098    
099    
100        /**
101         * This method retrieves a collection of credit card transactions and traverses through this list, creating 
102         * ProcurementCardDocuments for each card.
103         * 
104         * @return True if the procurement card documents were created successfully.  If any problem occur while creating the 
105         * documents, a runtime exception will be thrown.
106         * 
107         * @see org.kuali.kfs.fp.batch.service.ProcurementCardCreateDocumentService#createProcurementCardDocuments()
108         */
109        @SuppressWarnings("rawtypes")
110        public boolean createProcurementCardDocuments() {
111            List documents = new ArrayList();
112            List cardTransactions = retrieveTransactions();
113    
114            // iterate through card transaction list and create documents
115            for (Iterator iter = cardTransactions.iterator(); iter.hasNext();) {
116                documents.add(createProcurementCardDocument((List) iter.next()));
117            }
118    
119            // now store all the documents
120            for (Iterator iter = documents.iterator(); iter.hasNext();) {
121                ProcurementCardDocument pcardDocument = (ProcurementCardDocument) iter.next();
122                try {
123                    documentService.saveDocument(pcardDocument, DocumentSystemSaveEvent.class);
124                    if ( LOG.isInfoEnabled() ) {
125                        LOG.info("Saved Procurement Card document: "+pcardDocument.getDocumentNumber());
126                    }
127                }
128                catch (Exception e) {
129                    LOG.error("Error persisting document # " + pcardDocument.getDocumentHeader().getDocumentNumber() + " " + e.getMessage(), e);
130                    throw new RuntimeException("Error persisting document # " + pcardDocument.getDocumentHeader().getDocumentNumber() + " " + e.getMessage(), e);
131                }
132            }
133    
134            return true;
135        }
136    
137        /**
138         * This method retrieves all the procurement card documents with a status of 'I' and routes them to the next step in the
139         * routing path.
140         * 
141         * @return True if the routing was performed successfully.  A runtime exception will be thrown if any errors occur while routing.
142         * 
143         * @see org.kuali.kfs.fp.batch.service.ProcurementCardCreateDocumentService#routeProcurementCardDocuments(java.util.List)
144         */
145        public boolean routeProcurementCardDocuments() {
146            List<String> documentIdList = null;
147            try {
148                documentIdList = retrieveProcurementCardDocumentsToRoute(KEWConstants.ROUTE_HEADER_SAVED_CD);
149            } catch (WorkflowException e1) {
150                LOG.error("Error retrieving pcdo documents for routing: " + e1.getMessage(),e1);
151                throw new RuntimeException(e1.getMessage(),e1);
152            } catch (RemoteException re) {
153                LOG.error("Error retrieving pcdo documents for routing: " + re.getMessage(),re);
154                throw new RuntimeException(re.getMessage(),re);
155            }
156            
157            //Collections.reverse(documentIdList);
158            if ( LOG.isInfoEnabled() ) {
159                LOG.info("PCards to Route: "+documentIdList);
160            }
161            
162            for (String pcardDocumentId: documentIdList) {
163                try {
164                    ProcurementCardDocument pcardDocument = (ProcurementCardDocument)documentService.getByDocumentHeaderId(pcardDocumentId);
165                    if ( LOG.isInfoEnabled() ) {
166                        LOG.info("Routing PCDO document # " + pcardDocumentId + ".");
167                    }
168                    documentService.prepareWorkflowDocument(pcardDocument);
169    
170                    // calling workflow service to bypass business rule checks
171                    workflowDocumentService.route(pcardDocument.getDocumentHeader().getWorkflowDocument(), "", null);
172                }
173                catch (WorkflowException e) {
174                    LOG.error("Error routing document # " + pcardDocumentId + " " + e.getMessage());
175                    throw new RuntimeException(e.getMessage(),e);
176                }
177            }
178    
179            return true;
180        }
181        
182        /**
183         * Returns a list of all initiated but not yet routed procurement card documents, using the KualiWorkflowInfo service.
184         * @return a list of procurement card documents to route
185         */
186        protected List<String> retrieveProcurementCardDocumentsToRoute(String statusCode) throws WorkflowException, RemoteException {
187            List<String> documentIds = new ArrayList<String>();
188            
189            DocumentSearchCriteriaDTO criteria = new DocumentSearchCriteriaDTO();
190            criteria.setDocTypeFullName(KFSConstants.FinancialDocumentTypeCodes.PROCUREMENT_CARD);
191            criteria.setDocRouteStatus(statusCode);
192            DocumentSearchResultDTO results = SpringContext.getBean(KualiWorkflowInfo.class).performDocumentSearch(GlobalVariables.getUserSession().getPerson().getPrincipalId(), criteria);
193            
194            for (DocumentSearchResultRowDTO resultRow: results.getSearchResults()) {
195                for (KeyValueDTO field : resultRow.getFieldValues()) {
196                    if (field.getKey().equals(WORKFLOW_SEARCH_RESULT_KEY)) {
197                        documentIds.add(parseDocumentIdFromRouteDocHeader(field.getValue()));
198                    }
199                }
200            }
201            
202            return documentIds;
203        }
204        
205        /**
206         * Retrieves the document id out of the route document header
207         * @param routeDocHeader the String representing an HTML link to the document
208         * @return the document id
209         */
210        protected String parseDocumentIdFromRouteDocHeader(String routeDocHeader) {
211            int rightBound = routeDocHeader.indexOf('>') + 1;
212            int leftBound = routeDocHeader.indexOf('<', rightBound);
213            return routeDocHeader.substring(rightBound, leftBound);
214        }
215    
216        /**
217         * This method determines if procurement card documents can be auto approved.  A document can be auto approved if 
218         * the grace period for allowing auto approval of a procurement card document has passed.  The grace period is defined
219         * by a parameter in the parameters table.  The create date of the document is then compared against the current date and 
220         * if the difference is larger than the grace period defined, then the document is auto approved.
221         * 
222         * @return This method always returns true.
223         * 
224         * @see org.kuali.kfs.fp.batch.service.ProcurementCardCreateDocumentService#autoApproveProcurementCardDocuments()
225         */
226        public boolean autoApproveProcurementCardDocuments() {
227            // check if auto approve is turned on
228            boolean autoApproveOn = parameterService.getIndicatorParameter(ProcurementCardAutoApproveDocumentsStep.class, AUTO_APPROVE_DOCUMENTS_IND);
229    
230            if (!autoApproveOn) { // no auto approve?  then skip out of here...
231                return true;
232            }
233    
234            List<String> documentIdList = null;
235            try {
236                documentIdList = retrieveProcurementCardDocumentsToRoute(KEWConstants.ROUTE_HEADER_ENROUTE_CD);
237            }
238            catch (WorkflowException e1) {
239                throw new RuntimeException(e1.getMessage(),e1);
240            }
241            catch (RemoteException re) {
242                throw new RuntimeException(re.getMessage(),re);
243            }
244    
245            // get number of days and type for auto approve
246            int autoApproveNumberDays = Integer.parseInt(parameterService.getParameterValue(ProcurementCardAutoApproveDocumentsStep.class, AUTO_APPROVE_NUMBER_OF_DAYS));
247    
248            Timestamp currentDate = dateTimeService.getCurrentTimestamp();
249            for (String pcardDocumentId: documentIdList) {
250                try {
251                    ProcurementCardDocument pcardDocument = (ProcurementCardDocument)documentService.getByDocumentHeaderId(pcardDocumentId);
252                    
253                    // prevent PCard documents from auto approving if they have capital asset info to collect
254                    if(capitalAssetBuilderModuleService.hasCapitalAssetObjectSubType(pcardDocument)) {
255                        continue;
256                    }
257    
258                    // if number of days in route is passed the allowed number, call doc service for super user approve
259                    Timestamp docCreateDate = pcardDocument.getDocumentHeader().getWorkflowDocument().getCreateDate();
260                    if (DateUtils.getDifferenceInDays(docCreateDate, currentDate) > autoApproveNumberDays) {
261                        // update document description to reflect the auto approval
262                        pcardDocument.getDocumentHeader().setDocumentDescription("Auto Approved On " + dateTimeService.toDateTimeString(currentDate) + ".");
263                    
264                        if ( LOG.isInfoEnabled() ) {
265                            LOG.info("Auto approving document # " + pcardDocument.getDocumentHeader().getDocumentNumber());
266                        }
267                        documentService.superUserApproveDocument(pcardDocument, "");
268                    }
269                } catch (WorkflowException e) {
270                    LOG.error("Error auto approving document # " + pcardDocumentId + " " + e.getMessage(),e);
271                    throw new RuntimeException(e.getMessage(),e);
272                }
273            }
274    
275            return true;
276        }
277    
278    
279        /**
280         * This method retrieves a list of transactions from a temporary table, and groups them into document lists, based on 
281         * single transaction indicator or a grouping by card.
282         * 
283         * @return List containing transactions for document.
284         */
285        @SuppressWarnings("rawtypes")
286        protected List retrieveTransactions() {
287            List groupedTransactions = new ArrayList();
288    
289            // retrieve records from transaction table order by card number
290            List transactions = (List) businessObjectService.findMatchingOrderBy(ProcurementCardTransaction.class, new HashMap(), KFSPropertyConstants.TRANSACTION_CREDIT_CARD_NUMBER, true);
291    
292            // check apc for single transaction documents or multiple by card
293            boolean singleTransaction = parameterService.getIndicatorParameter(ProcurementCardCreateDocumentsStep.class, SINGLE_TRANSACTION_IND_PARM_NM);
294    
295            List documentTransactions = new ArrayList();
296            if (singleTransaction) {
297                for (Iterator iter = transactions.iterator(); iter.hasNext();) {
298                    documentTransactions.add(iter.next());
299                    groupedTransactions.add(documentTransactions);
300                    documentTransactions = new ArrayList();
301                }
302            }
303            else {
304                Map cardTransactionsMap = new HashMap();
305                for (Iterator iter = transactions.iterator(); iter.hasNext();) {
306                    ProcurementCardTransaction transaction = (ProcurementCardTransaction) iter.next();
307                    if (!cardTransactionsMap.containsKey(transaction.getTransactionCreditCardNumber())) {
308                        cardTransactionsMap.put(transaction.getTransactionCreditCardNumber(), new ArrayList());
309                    }
310                    ((List) cardTransactionsMap.get(transaction.getTransactionCreditCardNumber())).add(transaction);
311                }
312    
313                for (Iterator iter = cardTransactionsMap.values().iterator(); iter.hasNext();) {
314                    groupedTransactions.add(iter.next());
315    
316                }
317            }
318    
319            return groupedTransactions;
320        }
321    
322    
323        /**
324         * Creates a ProcurementCardDocument from the List of transactions given.
325         * 
326         * @param transactions List of ProcurementCardTransaction objects to be used for creating the document.
327         * @return A ProcurementCardDocument populated with the transactions provided.
328         */
329        protected ProcurementCardDocument createProcurementCardDocument(List transactions) {
330            ProcurementCardDocument pcardDocument = null;
331    
332            try {
333                // get new document from doc service
334                pcardDocument = (ProcurementCardDocument) SpringContext.getBean(DocumentService.class).getNewDocument(PROCUREMENT_CARD);
335                if (ObjectUtils.isNotNull(pcardDocument.getCapitalAssetInformation())) {
336                    pcardDocument.getCapitalAssetInformation().setDocumentNumber(pcardDocument.getDocumentNumber());
337                }
338    
339                // set the card holder record on the document from the first transaction
340                createCardHolderRecord(pcardDocument, (ProcurementCardTransaction) transactions.get(0));
341    
342                // for each transaction, create transaction detail object and then acct lines for the detail
343                int transactionLineNumber = 1;
344                KualiDecimal documentTotalAmount = KualiDecimal.ZERO;
345                String errorText = "";
346                for (Iterator iter = transactions.iterator(); iter.hasNext();) {
347                    ProcurementCardTransaction transaction = (ProcurementCardTransaction) iter.next();
348    
349                    // create transaction detail record with accounting lines
350                    errorText += createTransactionDetailRecord(pcardDocument, transaction, transactionLineNumber);
351    
352                    // update document total
353                    documentTotalAmount = documentTotalAmount.add(transaction.getFinancialDocumentTotalAmount());
354    
355                    transactionLineNumber++;
356                }
357                
358                pcardDocument.getDocumentHeader().setFinancialDocumentTotalAmount(documentTotalAmount);
359                pcardDocument.getDocumentHeader().setDocumentDescription("SYSTEM Generated");
360    
361                // Remove duplicate messages from errorText
362                String messages[] = StringUtils.split(errorText, ".");
363                for (int i = 0; i < messages.length; i++) {
364                    int countMatches = StringUtils.countMatches(errorText, messages[i]) - 1;
365                    errorText = StringUtils.replace(errorText, messages[i] + ".", "", countMatches);
366                }
367                // In case errorText is still too long, truncate it and indicate so.
368                Integer documentExplanationMaxLength = dataDictionaryService.getAttributeMaxLength(DocumentHeader.class.getName(), KFSPropertyConstants.EXPLANATION);
369                if (documentExplanationMaxLength != null && errorText.length() > documentExplanationMaxLength.intValue()) {
370                    String truncatedMessage = " ... TRUNCATED.";
371                    errorText = errorText.substring(0, documentExplanationMaxLength - truncatedMessage.length()) + truncatedMessage;
372                }
373                pcardDocument.getDocumentHeader().setExplanation(errorText);
374            }
375            catch (WorkflowException e) {
376                LOG.error("Error creating pcdo documents: " + e.getMessage(),e);
377                throw new RuntimeException("Error creating pcdo documents: " + e.getMessage(),e);
378            }
379    
380            return pcardDocument;
381        }
382    
383        /**
384         * Creates card holder record and sets that record to the document given.
385         * 
386         * @param pcardDocument Procurement card document to place the record in.
387         * @param transaction The transaction to set the card holder record fields from.
388         */
389        protected void createCardHolderRecord(ProcurementCardDocument pcardDocument, ProcurementCardTransaction transaction) {
390            ProcurementCardHolder cardHolder = new ProcurementCardHolder();
391    
392            cardHolder.setDocumentNumber(pcardDocument.getDocumentNumber());
393            cardHolder.setAccountNumber(transaction.getAccountNumber());
394            cardHolder.setCardCycleAmountLimit(transaction.getCardCycleAmountLimit());
395            cardHolder.setCardCycleVolumeLimit(transaction.getCardCycleVolumeLimit());
396            cardHolder.setCardHolderAlternateName(transaction.getCardHolderAlternateName());
397            cardHolder.setCardHolderCityName(transaction.getCardHolderCityName());
398            cardHolder.setCardHolderLine1Address(transaction.getCardHolderLine1Address());
399            cardHolder.setCardHolderLine2Address(transaction.getCardHolderLine2Address());
400            cardHolder.setCardHolderName(transaction.getCardHolderName());
401            cardHolder.setCardHolderStateCode(transaction.getCardHolderStateCode());
402            cardHolder.setCardHolderWorkPhoneNumber(transaction.getCardHolderWorkPhoneNumber());
403            cardHolder.setCardHolderZipCode(transaction.getCardHolderZipCode());
404            cardHolder.setCardLimit(transaction.getCardLimit());
405            cardHolder.setCardNoteText(transaction.getCardNoteText());
406            cardHolder.setCardStatusCode(transaction.getCardStatusCode());
407            cardHolder.setChartOfAccountsCode(transaction.getChartOfAccountsCode());
408            cardHolder.setSubAccountNumber(transaction.getSubAccountNumber());
409            cardHolder.setTransactionCreditCardNumber(transaction.getTransactionCreditCardNumber());
410    
411            pcardDocument.setProcurementCardHolder(cardHolder);
412        }
413    
414        /**
415         * Creates a transaction detail record and adds that record to the document provided.
416         * 
417         * @param pcardDocument Document to place record in.
418         * @param transaction Transaction to set fields from.
419         * @param transactionLineNumber Line number of the new transaction detail record within the procurement card document.
420         * @return The error text that was generated from the creation of the detail records.  If the text is empty, no errors were encountered.
421         */
422        protected String createTransactionDetailRecord(ProcurementCardDocument pcardDocument, ProcurementCardTransaction transaction, Integer transactionLineNumber) {
423            ProcurementCardTransactionDetail transactionDetail = new ProcurementCardTransactionDetail();
424    
425            // set the document transaction detail fields from the loaded transaction record
426            transactionDetail.setDocumentNumber(pcardDocument.getDocumentNumber());
427            transactionDetail.setFinancialDocumentTransactionLineNumber(transactionLineNumber);
428            transactionDetail.setTransactionDate(transaction.getTransactionDate());
429            transactionDetail.setTransactionReferenceNumber(transaction.getTransactionReferenceNumber());
430            transactionDetail.setTransactionBillingCurrencyCode(transaction.getTransactionBillingCurrencyCode());
431            transactionDetail.setTransactionCurrencyExchangeRate(transaction.getTransactionCurrencyExchangeRate());
432            transactionDetail.setTransactionDate(transaction.getTransactionDate());
433            transactionDetail.setTransactionOriginalCurrencyAmount(transaction.getTransactionOriginalCurrencyAmount());
434            transactionDetail.setTransactionOriginalCurrencyCode(transaction.getTransactionOriginalCurrencyCode());
435            transactionDetail.setTransactionPointOfSaleCode(transaction.getTransactionPointOfSaleCode());
436            transactionDetail.setTransactionPostingDate(transaction.getTransactionPostingDate());
437            transactionDetail.setTransactionPurchaseIdentifierDescription(transaction.getTransactionPurchaseIdentifierDescription());
438            transactionDetail.setTransactionPurchaseIdentifierIndicator(transaction.getTransactionPurchaseIdentifierIndicator());
439            transactionDetail.setTransactionSalesTaxAmount(transaction.getTransactionSalesTaxAmount());
440            transactionDetail.setTransactionSettlementAmount(transaction.getTransactionSettlementAmount());
441            transactionDetail.setTransactionTaxExemptIndicator(transaction.getTransactionTaxExemptIndicator());
442            transactionDetail.setTransactionTravelAuthorizationCode(transaction.getTransactionTravelAuthorizationCode());
443            transactionDetail.setTransactionUnitContactName(transaction.getTransactionUnitContactName());
444    
445            if (GL_CREDIT_CODE.equals(transaction.getTransactionDebitCreditCode())) {
446                transactionDetail.setTransactionTotalAmount(transaction.getFinancialDocumentTotalAmount().negated());
447            }
448            else {
449                transactionDetail.setTransactionTotalAmount(transaction.getFinancialDocumentTotalAmount());
450            }
451    
452            // create transaction vendor record
453            createTransactionVendorRecord(pcardDocument, transaction, transactionDetail);
454    
455            // add transaction detail to document
456            pcardDocument.getTransactionEntries().add(transactionDetail);
457    
458            // now create the initial source and target lines for this transaction
459            return createAndValidateAccountingLines(pcardDocument, transaction, transactionDetail);
460        }
461    
462    
463        /**
464         * Creates a transaction vendor detail record and adds it to the transaction detail.
465         * 
466         * @param pcardDocument The procurement card document to retrieve values from.
467         * @param transaction Transaction to set fields from.
468         * @param transactionDetail The transaction detail to set the vendor record on.
469         */
470        protected void createTransactionVendorRecord(ProcurementCardDocument pcardDocument, ProcurementCardTransaction transaction, ProcurementCardTransactionDetail transactionDetail) {
471            ProcurementCardVendor transactionVendor = new ProcurementCardVendor();
472    
473            transactionVendor.setDocumentNumber(pcardDocument.getDocumentNumber());
474            transactionVendor.setFinancialDocumentTransactionLineNumber(transactionDetail.getFinancialDocumentTransactionLineNumber());
475            transactionVendor.setTransactionMerchantCategoryCode(transaction.getTransactionMerchantCategoryCode());
476            transactionVendor.setVendorCityName(transaction.getVendorCityName());
477            transactionVendor.setVendorLine1Address(transaction.getVendorLine1Address());
478            transactionVendor.setVendorLine2Address(transaction.getVendorLine2Address());
479            transactionVendor.setVendorName(transaction.getVendorName());
480            transactionVendor.setVendorOrderNumber(transaction.getVendorOrderNumber());
481            transactionVendor.setVendorStateCode(transaction.getVendorStateCode());
482            transactionVendor.setVendorZipCode(transaction.getVendorZipCode());
483            transactionVendor.setVisaVendorIdentifier(transaction.getVisaVendorIdentifier());
484    
485            transactionDetail.setProcurementCardVendor(transactionVendor);
486        }
487    
488        /**
489         * From the transaction accounting attributes, creates source and target accounting lines. Attributes are validated first, and
490         * replaced with default and error values if needed. There will be 1 source and 1 target line generated.
491         * 
492         * @param pcardDocument The procurement card document to add the new accounting lines to.
493         * @param transaction The transaction to process into account lines.
494         * @param docTransactionDetail The transaction detail to create source and target accounting lines from.
495         * @return String containing any error messages.
496         */
497        protected String createAndValidateAccountingLines(ProcurementCardDocument pcardDocument, ProcurementCardTransaction transaction, ProcurementCardTransactionDetail docTransactionDetail) {
498            // build source lines
499            ProcurementCardSourceAccountingLine sourceLine = createSourceAccountingLine(transaction, docTransactionDetail);
500            sourceLine.setPostingYear(pcardDocument.getPostingYear());
501    
502            // add line to transaction through document since document contains the next sequence number fields
503            pcardDocument.addSourceAccountingLine(sourceLine);
504    
505            // build target lines
506            ProcurementCardTargetAccountingLine targetLine = createTargetAccountingLine(transaction, docTransactionDetail);
507            targetLine.setPostingYear(pcardDocument.getPostingYear());
508            
509            // add line to transaction through document since document contains the next sequence number fields
510            pcardDocument.addTargetAccountingLine(targetLine);
511    
512            return validateTargetAccountingLine(targetLine);
513        }
514    
515        /**
516         * Creates the to record for the transaction. The chart of account attributes from the transaction are used to create 
517         * the accounting line.
518         * 
519         * @param transaction The transaction to pull information from to create the accounting line.
520         * @param docTransactionDetail The transaction detail to pull information from to populate the accounting line.
521         * @return The target accounting line fully populated with values from the parameters passed in. 
522         */
523        protected ProcurementCardTargetAccountingLine createTargetAccountingLine(ProcurementCardTransaction transaction, ProcurementCardTransactionDetail docTransactionDetail) {
524            ProcurementCardTargetAccountingLine targetLine = new ProcurementCardTargetAccountingLine();
525    
526            targetLine.setDocumentNumber(docTransactionDetail.getDocumentNumber());
527            targetLine.setFinancialDocumentTransactionLineNumber(docTransactionDetail.getFinancialDocumentTransactionLineNumber());
528            targetLine.setChartOfAccountsCode(transaction.getChartOfAccountsCode());
529            targetLine.setAccountNumber(transaction.getAccountNumber());
530            targetLine.setFinancialObjectCode(transaction.getFinancialObjectCode());
531            targetLine.setSubAccountNumber(transaction.getSubAccountNumber());
532            targetLine.setFinancialSubObjectCode(transaction.getFinancialSubObjectCode());
533            targetLine.setProjectCode(transaction.getProjectCode());
534    
535            if (GL_CREDIT_CODE.equals(transaction.getTransactionDebitCreditCode())) {
536                targetLine.setAmount(transaction.getFinancialDocumentTotalAmount().negated());
537            }
538            else {
539                targetLine.setAmount(transaction.getFinancialDocumentTotalAmount());
540            }
541    
542            return targetLine;
543        }
544    
545        /**
546         * Creates the from record for the transaction. The clearing chart, account, and object code is used for creating the line.
547         * 
548         * @param transaction The transaction to pull information from to create the accounting line.
549         * @param docTransactionDetail The transaction detail to pull information from to populate the accounting line.
550         * @return The source accounting line fully populated with values from the parameters passed in.
551         */
552        protected ProcurementCardSourceAccountingLine createSourceAccountingLine(ProcurementCardTransaction transaction, ProcurementCardTransactionDetail docTransactionDetail) {
553            ProcurementCardSourceAccountingLine sourceLine = new ProcurementCardSourceAccountingLine();
554    
555            sourceLine.setDocumentNumber(docTransactionDetail.getDocumentNumber());
556            sourceLine.setFinancialDocumentTransactionLineNumber(docTransactionDetail.getFinancialDocumentTransactionLineNumber());
557            sourceLine.setChartOfAccountsCode(getDefaultChartCode());
558            sourceLine.setAccountNumber(getDefaultAccountNumber());
559            sourceLine.setFinancialObjectCode(getDefaultObjectCode());
560    
561            if (GL_CREDIT_CODE.equals(transaction.getTransactionDebitCreditCode())) {
562                sourceLine.setAmount(transaction.getFinancialDocumentTotalAmount().negated());
563            }
564            else {
565                sourceLine.setAmount(transaction.getFinancialDocumentTotalAmount());
566            }
567    
568            return sourceLine;
569        }
570    
571        /**
572         * Validates the chart of account attributes for existence and active indicator. Will substitute for defined 
573         * default parameters or set fields to empty that if they have errors.
574         * 
575         * @param targetLine The target accounting line to be validated.
576         * @return String with error messages discovered during validation.  An empty string indicates no validation errors were found.
577         */
578        protected String validateTargetAccountingLine(ProcurementCardTargetAccountingLine targetLine) {
579            String errorText = "";
580    
581            targetLine.refresh();
582            
583            if (!accountingLineRuleUtil.isValidChart(targetLine.getChart(), dataDictionaryService.getDataDictionary())) {
584                String tempErrorText = "Chart " + targetLine.getChartOfAccountsCode() + " is invalid; using error Chart Code.";
585                if ( LOG.isInfoEnabled() ) {
586                    LOG.info(tempErrorText);
587                }
588                errorText += " " + tempErrorText;
589    
590                targetLine.setChartOfAccountsCode(getErrorChartCode());
591                targetLine.refresh();
592            }   
593            
594            if (!accountingLineRuleUtil.isValidAccount(targetLine.getAccount(), dataDictionaryService.getDataDictionary()) || targetLine.getAccount().isExpired()) {
595                String tempErrorText = "Chart " + targetLine.getChartOfAccountsCode() + " Account " + targetLine.getAccountNumber() + " is invalid; using error account.";
596                if ( LOG.isInfoEnabled() ) {
597                    LOG.info(tempErrorText);
598                }
599                errorText += " " + tempErrorText;
600    
601                targetLine.setChartOfAccountsCode(getErrorChartCode());
602                targetLine.setAccountNumber(getErrorAccountNumber());
603                targetLine.refresh();
604            }
605    
606            if (!accountingLineRuleUtil.isValidObjectCode(targetLine.getObjectCode(), dataDictionaryService.getDataDictionary())) {
607                String tempErrorText = "Chart " + targetLine.getChartOfAccountsCode() + " Object Code " + targetLine.getFinancialObjectCode() + " is invalid; using default Object Code.";
608                if ( LOG.isInfoEnabled() ) {
609                    LOG.info(tempErrorText);
610                }
611                errorText += " " + tempErrorText;
612    
613                targetLine.setFinancialObjectCode(getDefaultObjectCode());
614                targetLine.refresh();
615            }
616    
617            if (StringUtils.isNotBlank(targetLine.getSubAccountNumber()) && !accountingLineRuleUtil.isValidSubAccount(targetLine.getSubAccount(), dataDictionaryService.getDataDictionary())) {
618                String tempErrorText = "Chart " + targetLine.getChartOfAccountsCode() + " Account " + targetLine.getAccountNumber() + " Sub Account " + targetLine.getSubAccountNumber() + " is invalid; Setting Sub Account to blank.";
619                if ( LOG.isInfoEnabled() ) {
620                    LOG.info(tempErrorText);
621                }
622                errorText += " " + tempErrorText;
623    
624                targetLine.setSubAccountNumber("");
625            }
626    
627            if (StringUtils.isNotBlank(targetLine.getFinancialSubObjectCode()) && !accountingLineRuleUtil.isValidSubObjectCode(targetLine.getSubObjectCode(), dataDictionaryService.getDataDictionary())) {
628                String tempErrorText = "Chart " + targetLine.getChartOfAccountsCode() + " Account " + targetLine.getAccountNumber() + " Object Code " + targetLine.getFinancialObjectCode() + " Sub Object Code " + targetLine.getFinancialSubObjectCode() + " is invalid; setting Sub Object to blank.";
629                if ( LOG.isInfoEnabled() ) {
630                    LOG.info(tempErrorText);
631                }
632                errorText += " " + tempErrorText;
633    
634                targetLine.setFinancialSubObjectCode("");
635            }
636    
637            if (StringUtils.isNotBlank(targetLine.getProjectCode()) && !accountingLineRuleUtil.isValidProjectCode(targetLine.getProject(), dataDictionaryService.getDataDictionary())) {
638                if ( LOG.isInfoEnabled() ) {
639                    LOG.info("Project Code " + targetLine.getProjectCode() + " is invalid; setting to blank.");
640                }
641                errorText += " Project Code " + targetLine.getProjectCode() + " is invalid; setting to blank.";
642    
643                targetLine.setProjectCode("");
644            }
645    
646            // clear out GlobalVariable message map, since we have taken care of the errors
647            GlobalVariables.setMessageMap(new MessageMap());
648    
649            return errorText;
650        }
651    
652        /**
653         * Retrieves the error chart code from the parameter table.
654         * @return The error chart code defined in the parameter table.
655         */
656        protected String getErrorChartCode() {
657            return parameterService.getParameterValue(ProcurementCardCreateDocumentsStep.class, ProcurementCardDocumentRuleConstants.ERROR_TRANS_CHART_CODE_PARM_NM);
658        }
659    
660        /**
661         * Retrieves the error account number from the parameter table.
662         * @return The error account number defined in the parameter table.
663         */
664        protected String getErrorAccountNumber() {
665            return parameterService.getParameterValue(ProcurementCardCreateDocumentsStep.class, ERROR_TRANS_ACCOUNT_PARM_NM);
666        }
667    
668        /**
669         * Retrieves the default chard code from the parameter table.
670         * @return The default chart code defined in the parameter table.
671         */
672        protected String getDefaultChartCode() {
673            return parameterService.getParameterValue(ProcurementCardLoadStep.class, DEFAULT_TRANS_CHART_CODE_PARM_NM);
674        }
675    
676        /**
677         * Retrieves the default account number from the parameter table.
678         * @return The default account number defined in the parameter table.
679         */
680        protected String getDefaultAccountNumber() {
681            return parameterService.getParameterValue(ProcurementCardLoadStep.class, DEFAULT_TRANS_ACCOUNT_PARM_NM);
682        }
683    
684        /**
685         * Retrieves the default object code from the parameter table.
686         * @return The default object code defined in the parameter table.
687         */
688        protected String getDefaultObjectCode() {
689            return parameterService.getParameterValue(ProcurementCardLoadStep.class, DEFAULT_TRANS_OBJECT_CODE_PARM_NM);
690        }
691    
692        /**
693         * Calls businessObjectService to remove all the procurement card transaction rows from the transaction load table.
694         */
695        protected void cleanTransactionsTable() {
696            businessObjectService.deleteMatching(ProcurementCardTransaction.class, new HashMap());
697        }
698    
699        /**
700         * Loads all the parsed XML transactions into the temp transaction table.
701         * 
702         * @param transactions List of ProcurementCardTransactions to load.
703         */
704        protected void loadTransactions(List transactions) {
705            businessObjectService.save(transactions);
706        }
707    
708        /**
709         * Sets the parameterService attribute.
710         * @param parameterService
711         */
712        public void setParameterService(ParameterService parameterService) {
713            this.parameterService = parameterService;
714        }
715    
716        /**
717         * Gets the businessObjectService attribute.
718         * @return Returns the businessObjectService.
719         */
720        public BusinessObjectService getBusinessObjectService() {
721            return businessObjectService;
722        }
723    
724        /**
725         * Sets the businessObjectService attribute.
726         * @param businessObjectService The businessObjectService to set.
727         */
728        public void setBusinessObjectService(BusinessObjectService businessObjectService) {
729            this.businessObjectService = businessObjectService;
730        }
731    
732        /**
733         * Gets the documentService attribute.
734         * @return Returns the documentService.
735         */
736        public DocumentService getDocumentService() {
737            return documentService;
738        }
739    
740        /**
741         * Sets the documentService attribute.
742         * @param documentService The documentService to set.
743         */
744        public void setDocumentService(DocumentService documentService) {
745            this.documentService = documentService;
746        }
747    
748    
749        /**
750         * Gets the dataDictionaryService attribute.
751         * @return Returns the dataDictionaryService.
752         */
753        public DataDictionaryService getDataDictionaryService() {
754            return dataDictionaryService;
755        }
756    
757        /**
758         * Sets the dataDictionaryService attribute.
759         * @param dataDictionaryService dataDictionaryService to set.
760         */
761        public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
762            this.dataDictionaryService = dataDictionaryService;
763        }
764    
765    
766        /**
767         * Gets the dateTimeService attribute.
768         * @return Returns the dateTimeService.
769         */
770        public DateTimeService getDateTimeService() {
771            return dateTimeService;
772        }
773    
774        /**
775         * Sets the dateTimeService attribute.
776         * @param dateTimeService The dateTimeService to set.
777         */
778        public void setDateTimeService(DateTimeService dateTimeService) {
779            this.dateTimeService = dateTimeService;
780        }
781    
782        /**
783         * Gets the workflowDocumentService attribute.
784         * @return Returns the workflowDocumentService.
785         */
786        public WorkflowDocumentService getWorkflowDocumentService() {
787            return workflowDocumentService;
788        }
789    
790        /**
791         * Sets the workflowDocumentService attribute value.
792         * @param workflowDocumentService The workflowDocumentService to set.
793         */
794        public void setWorkflowDocumentService(WorkflowDocumentService workflowDocumentService) {
795            this.workflowDocumentService = workflowDocumentService;
796        }
797    
798        /**
799         * Sets the accountingLineRuleUtil attribute value.
800         * @param accountingLineRuleUtil The accountingLineRuleUtil to set.
801         */
802        public void setAccountingLineRuleUtil(AccountingLineRuleHelperService accountingLineRuleUtil) {
803            this.accountingLineRuleUtil = accountingLineRuleUtil;
804        }
805    
806        /**
807         * Sets the capitalAssetBuilderModuleService attribute value.
808         * @param capitalAssetBuilderModuleService The capitalAssetBuilderModuleService to set.
809         */
810        public void setCapitalAssetBuilderModuleService(CapitalAssetBuilderModuleService capitalAssetBuilderModuleService) {
811            this.capitalAssetBuilderModuleService = capitalAssetBuilderModuleService;
812        }
813    
814    }