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.ar.batch;
017    
018    import java.math.BigDecimal;
019    import java.text.SimpleDateFormat;
020    import java.util.ArrayList;
021    import java.util.Arrays;
022    import java.util.Calendar;
023    import java.util.Collection;
024    import java.util.Date;
025    import java.util.HashMap;
026    import java.util.List;
027    import java.util.Map;
028    
029    import org.apache.commons.lang.StringUtils;
030    import org.apache.commons.lang.time.DateUtils;
031    import org.kuali.kfs.module.ar.ArConstants;
032    import org.kuali.kfs.module.ar.batch.service.LockboxService;
033    import org.kuali.kfs.module.ar.businessobject.CustomerAddress;
034    import org.kuali.kfs.module.ar.businessobject.CustomerInvoiceDetail;
035    import org.kuali.kfs.module.ar.businessobject.Lockbox;
036    import org.kuali.kfs.module.ar.document.CustomerInvoiceDocument;
037    import org.kuali.kfs.module.ar.document.CustomerInvoiceWriteoffDocument;
038    import org.kuali.kfs.module.ar.document.service.CustomerAddressService;
039    import org.kuali.kfs.module.ar.document.service.CustomerInvoiceDocumentService;
040    import org.kuali.kfs.module.ar.document.service.CustomerInvoiceWriteoffDocumentService;
041    import org.kuali.kfs.sys.KFSConstants;
042    import org.kuali.kfs.sys.batch.AbstractStep;
043    import org.kuali.kfs.sys.batch.Job;
044    import org.kuali.kfs.sys.batch.TestingStep;
045    import org.kuali.kfs.sys.context.SpringContext;
046    import org.kuali.rice.kew.exception.WorkflowException;
047    import org.kuali.rice.kim.bo.Person;
048    import org.kuali.rice.kim.service.PersonService;
049    import org.kuali.rice.kns.UserSession;
050    import org.kuali.rice.kns.bo.Parameter;
051    import org.kuali.rice.kns.document.Document;
052    import org.kuali.rice.kns.service.BusinessObjectService;
053    import org.kuali.rice.kns.service.DateTimeService;
054    import org.kuali.rice.kns.service.DocumentService;
055    import org.kuali.rice.kns.service.PersistenceStructureService;
056    import org.kuali.rice.kns.util.GlobalVariables;
057    import org.kuali.rice.kns.util.KualiDecimal;
058    import org.kuali.rice.kns.util.ObjectUtils;
059    import org.kuali.rice.kns.workflow.service.KualiWorkflowDocument;
060    
061    public class CustomerInvoiceDocumentBatchStep extends AbstractStep implements TestingStep {
062        
063        private static final long MAX_SEQ_NBR_OFFSET = 1000;
064        
065        CustomerInvoiceDocumentService customerInvoiceDocumentService; 
066        BusinessObjectService businessObjectService;
067        DocumentService documentService;
068        DateTimeService dateTimeService;
069        Collection<String> createdInvoices = new ArrayList<String>();
070    
071        
072        private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CustomerInvoiceDocumentBatchStep.class);
073    
074        // parameter constants and logging
075        private static final int NUMBER_OF_INVOICES_TO_CREATE = 5;
076        private static final String RUN_INDICATOR_PARAMETER_NAMESPACE_CODE = ArConstants.AR_NAMESPACE_CODE;
077        private static final String RUN_INDICATOR_PARAMETER_APPLICATION_NAMESPACE_CODE = KFSConstants.APPLICATION_NAMESPACE_CODE;
078        private static final String RUN_INDICATOR_PARAMETER_NAMESPACE_STEP = "CustomerInvoiceDocumentBatchStep";
079        private static final String RUN_INDICATOR_PARAMETER_VALUE = "N"; // Tells the job framework whether to run this job or not; set to NO because the CustomerInvoiceDocumentBatchStep needs to only be run once after database initialization.
080        private static final String RUN_INDICATOR_PARAMETER_ALLOWED = "A";
081        private final String RUN_INDICATOR_PARAMETER_DESCRIPTION = "Tells the job framework whether to run this job or not; because the CustomerInvoiceDocumentBatchStep needs to only be run once after database initialization.";
082        private static final String RUN_INDICATOR_PARAMETER_TYPE = "CONFG";
083        private static final String INITIATOR_PRINCIPAL_NAME = "khuntley";
084        
085        private final int currentYear = Calendar.getInstance().get(Calendar.YEAR);
086    
087        public boolean execute(String jobName, Date jobRunDate) throws InterruptedException {
088            
089            Parameter runIndicatorParameter = (Parameter) businessObjectService.findByPrimaryKey(Parameter.class, this.buildSearchKeyMap());
090            if (ObjectUtils.isNull(runIndicatorParameter) || "Y".equals(runIndicatorParameter.getParameterValue())) {
091    
092                GlobalVariables.clear();
093                GlobalVariables.setUserSession(new UserSession(INITIATOR_PRINCIPAL_NAME));
094                setDateTimeService(SpringContext.getBean(DateTimeService.class));
095            
096                Date billingDate = getDateTimeService().getCurrentDate();
097                List<String> customernames;
098            
099                if ((jobName.length() <=8 ) && (jobName.length() >= 4)) {
100                    setCustomerInvoiceDocumentService(SpringContext.getBean(CustomerInvoiceDocumentService.class));
101                    setBusinessObjectService(SpringContext.getBean(BusinessObjectService.class));
102                    setDocumentService(SpringContext.getBean(DocumentService.class));
103    
104                    customernames = Arrays.asList(jobName);
105                } else {
106                    customernames = Arrays.asList("ABB2", "3MC17500","ACE21725","ANT7297","CAR23612", "CON19567", "DEL14448", "EAT17609", "GAP17272");
107                }
108    
109                // create non-random data
110                if (customernames.size() > 1) {
111                    for (int i = 0; i < NUMBER_OF_INVOICES_TO_CREATE; i++) {
112        
113                        billingDate = DateUtils.addDays(billingDate, -30);
114        
115                        createCustomerInvoiceDocumentForFunctionalTesting("HIL22195", billingDate, 1, new KualiDecimal(10), new BigDecimal(1), "2336320", "BL", "BUSCF");  // $10 entries
116                        createCustomerInvoiceDocumentForFunctionalTesting("IBM2655", billingDate, 2, new KualiDecimal(10), new BigDecimal(1), "2336320", "BL", "IBCE");  // $20 entries
117                        createCustomerInvoiceDocumentForFunctionalTesting("JAS19572", billingDate, 3, new KualiDecimal(10), new BigDecimal(1), "2336320", "BL", "WRB");  // $30 entries
118        
119                        Thread.sleep(500);
120                    }
121                }
122    
123                // easy dynamic data creation
124                if (customernames.size() == 1) {
125                    billingDate = jobRunDate;
126                    createCustomerInvoiceDocumentForFunctionalTesting(customernames.get(0), billingDate, 1, new KualiDecimal(10), new BigDecimal(1), "1111111", "BA", "MATT");  // $10 entries
127                    Thread.sleep(500);
128                }
129    
130                // create lockboxes for the non-random invoices
131                Long seqNbr = findAvailableLockboxBaseSeqNbr();
132                int scenarioNbr =1;
133                for (String createdInvoice : createdInvoices){
134                    createLockboxesForFunctionalTesting(createdInvoice, seqNbr, scenarioNbr);
135                    Thread.sleep(500);
136                    seqNbr++;
137                    if (scenarioNbr<=6) {
138                        scenarioNbr++;
139                    }
140                    else {
141                        scenarioNbr = 1;
142                    }
143                }
144    
145                // create random data
146    //            if (customernames.size() > 1) {
147    //                for (String customername : customernames) {
148    //
149    //                    billingDate = getDateTimeService().getCurrentDate();
150    //
151    //                    for( int i = 0; i < NUMBER_OF_INVOICES_TO_CREATE; i++ ){
152    //
153    //                        billingDate = DateUtils.addDays(billingDate, -30);
154    //
155    //                        createCustomerInvoiceDocumentForFunctionalTesting(customername,billingDate, 0, null, null, "1031400", "BL");
156    //                        Thread.sleep(500);
157    //
158    //                    }
159    //                }
160    //            }
161    
162    
163    
164                // save runParameter as "N" so that the job won't run until DB has been cleared
165                setInitiatedParameter();
166            }    
167            return true;
168        }
169       
170        private long findAvailableLockboxBaseSeqNbr() {
171            LockboxService lockboxService = SpringContext.getBean(LockboxService.class);
172            return lockboxService.getMaxLockboxSequenceNumber() + MAX_SEQ_NBR_OFFSET;
173        }
174        
175        private boolean dupLockboxRecordExists(Long seqNbr) {
176            Map<String,Long> pks = new HashMap<String,Long>();
177            pks.put("invoiceSequenceNumber", seqNbr);
178            Lockbox dupLockBox = (Lockbox) businessObjectService.findByPrimaryKey(Lockbox.class, pks);
179            return (dupLockBox != null);
180        }
181        
182        /**
183         * This method sets a parameter that tells the step that it has already run and it does not need to run again.
184         */
185        private void setInitiatedParameter() {
186            // first see if we can find an existing Parameter object with this key
187            Parameter runIndicatorParameter = (Parameter) businessObjectService.findByPrimaryKey(Parameter.class, this.buildSearchKeyMap());
188            if (runIndicatorParameter == null)
189            {
190               runIndicatorParameter = new Parameter();
191               runIndicatorParameter.setVersionNumber(new Long(1));
192               runIndicatorParameter.setParameterNamespaceCode(CustomerInvoiceDocumentBatchStep.RUN_INDICATOR_PARAMETER_NAMESPACE_CODE);
193               runIndicatorParameter.setParameterDetailTypeCode(CustomerInvoiceDocumentBatchStep.RUN_INDICATOR_PARAMETER_NAMESPACE_STEP);
194               runIndicatorParameter.setParameterName(Job.STEP_RUN_PARM_NM);
195               runIndicatorParameter.setParameterDescription(RUN_INDICATOR_PARAMETER_DESCRIPTION);
196               runIndicatorParameter.setParameterConstraintCode(CustomerInvoiceDocumentBatchStep.RUN_INDICATOR_PARAMETER_ALLOWED);
197               runIndicatorParameter.setParameterTypeCode(CustomerInvoiceDocumentBatchStep.RUN_INDICATOR_PARAMETER_TYPE);
198               runIndicatorParameter.setParameterApplicationNamespaceCode(CustomerInvoiceDocumentBatchStep.RUN_INDICATOR_PARAMETER_APPLICATION_NAMESPACE_CODE);
199            }
200            runIndicatorParameter.setParameterValue(CustomerInvoiceDocumentBatchStep.RUN_INDICATOR_PARAMETER_VALUE);
201            businessObjectService.save(runIndicatorParameter);
202        }
203        
204        private Map<String,Object> buildSearchKeyMap()
205        {
206           Map<String,Object> pkMapForParameter = new HashMap<String,Object>();
207           PersistenceStructureService psService = SpringContext.getBean(PersistenceStructureService.class);
208    
209           // set up a list of all the  field names and values of the fields in the Parameter object.
210           // the OJB names are nowhere in Kuali properties, apparently.
211           // but, since we use set routines above, we know what the names must be.  if they change at some point, we will have to change the set routines anyway.
212           // we can change the code here also when we do that.
213           Map<String,Object> fieldNamesValuesForParameter = new HashMap<String,Object>();
214           fieldNamesValuesForParameter.put("parameterNamespaceCode",CustomerInvoiceDocumentBatchStep.RUN_INDICATOR_PARAMETER_NAMESPACE_CODE);
215           fieldNamesValuesForParameter.put("parameterDetailTypeCode",CustomerInvoiceDocumentBatchStep.RUN_INDICATOR_PARAMETER_NAMESPACE_STEP);
216           fieldNamesValuesForParameter.put("parameterName",Job.STEP_RUN_PARM_NM);
217           fieldNamesValuesForParameter.put("parameterConstraintCode",CustomerInvoiceDocumentBatchStep.RUN_INDICATOR_PARAMETER_ALLOWED);
218           fieldNamesValuesForParameter.put("parameterTypeCode",CustomerInvoiceDocumentBatchStep.RUN_INDICATOR_PARAMETER_TYPE);
219    
220           // get the primary keys and assign them to values
221           List<String> parameterPKFields = psService.getPrimaryKeys(Parameter.class);
222           for (String pkFieldName: parameterPKFields)
223           {
224               pkMapForParameter.put(pkFieldName,fieldNamesValuesForParameter.get(pkFieldName));
225           }
226           return (pkMapForParameter);
227        }
228    
229        private Lockbox populateLockbox(String invoiceNumber, Long seqNbr) {
230            
231            CustomerInvoiceDocument customerInvoiceDocument = customerInvoiceDocumentService.getInvoiceByInvoiceDocumentNumber(invoiceNumber);
232            
233            Lockbox newLockbox = new Lockbox();
234            newLockbox.setFinancialDocumentReferenceInvoiceNumber(invoiceNumber);
235            newLockbox.setCustomerNumber(customerInvoiceDocument.getCustomer().getCustomerNumber());
236            newLockbox.setInvoiceTotalAmount(customerInvoiceDocument.getTotalDollarAmount());
237            newLockbox.setInvoicePaidOrAppliedAmount(customerInvoiceDocument.getOpenAmount());
238            newLockbox.setBillingDate(customerInvoiceDocument.getBillingDate());
239            newLockbox.setCustomerPaymentMediumCode("CK");
240            newLockbox.setBankCode("1003");
241            newLockbox.setBatchSequenceNumber(8004);
242            newLockbox.setInvoiceSequenceNumber(seqNbr);
243            newLockbox.setLockboxNumber("66249");
244    
245            return newLockbox;
246        }
247        
248        private void createLockboxesForFunctionalTesting(String invoiceNumber, Long seqNbr, int testtype) throws InterruptedException {
249            
250            Lockbox newLockbox = populateLockbox(invoiceNumber, seqNbr);
251    
252            // 1) Payment matches customer (CUST_NBR), invoice number (FDOC_REF_INV_NBR), and amount (AR_INV_PD_APLD_AMT). These should auto-approve, the remaining scenarios should not.
253            if (testtype == 1) {
254                // dont need to do anything, its auto-set to pay the OpenAmount on the invoice
255            }
256    
257            // 2) Payment matches customer and invoice, but the invoice has no outstanding balance (due to a previous payment, a credit memo, or a write-off)
258            if (testtype == 2) {
259                newLockbox.setInvoicePaidOrAppliedAmount(newLockbox.getInvoiceTotalAmount());
260                writeoffInvoice(invoiceNumber);
261            }
262    
263            // 3) Payment matches customer and invoice, but the amount of the payment exceeds the outstanding balance on the invoice.
264            if (testtype == 3) {
265                newLockbox.setInvoicePaidOrAppliedAmount(newLockbox.getInvoicePaidOrAppliedAmount().add(new KualiDecimal("100.00")));
266            }
267    
268            // 4) The payment matches customer and invoice, but the amount is short-paid (less than the invoice outstanding balance)
269            if (testtype == 4) {
270                newLockbox.setInvoicePaidOrAppliedAmount(newLockbox.getInvoicePaidOrAppliedAmount().subtract(new KualiDecimal("1.00")));
271            }
272    
273            // 5) The payment matches a customer number, but the invoice number is missing
274            if (testtype == 5) {
275                newLockbox.setFinancialDocumentReferenceInvoiceNumber(null);
276            }
277    
278            // 6) The payment matches a customer number, but the invoice number is invalid
279            if (testtype == 6) {
280                newLockbox.setFinancialDocumentReferenceInvoiceNumber("999999");
281            }
282    
283            // 7) The payment matches nothing (not even the customer number)
284            if (testtype == 7) {
285                newLockbox.setFinancialDocumentReferenceInvoiceNumber("999999");
286                newLockbox.setCustomerNumber("KEY17536");
287            }
288    
289            LOG.info("Creating customer LOCKBOX [" + seqNbr.toString() + "] for invoice " + invoiceNumber);
290            if (dupLockboxRecordExists(seqNbr)) {
291                throw new RuntimeException("Trying to enter duplicate Lockbox.invoiceSequenceNumber, which will fail, and should never happen.");
292            }
293            businessObjectService.save(newLockbox);
294        }
295    
296        public void writeoffInvoice(String invoiceNumberToWriteOff) {
297            CustomerInvoiceWriteoffDocumentService writeoffService = SpringContext.getBean(CustomerInvoiceWriteoffDocumentService.class);
298            Person initiator = SpringContext.getBean(PersonService.class).getPersonByPrincipalName(INITIATOR_PRINCIPAL_NAME);
299            
300            //  have the service create us a new writeoff doc
301            String writeoffDocNumber;
302            try {
303                writeoffDocNumber = writeoffService.createCustomerInvoiceWriteoffDocument(initiator, invoiceNumberToWriteOff, 
304                        "Created by CustomerInvoiceDocumentBatch process.");
305            }
306            catch (WorkflowException e) {
307                throw new RuntimeException("A WorkflowException was thrown when trying to create a new Invoice Writeoff document.", e);
308            }
309            
310            //  load the newly created writeoff doc from the db
311            CustomerInvoiceWriteoffDocument writeoff;
312            try {
313                writeoff = (CustomerInvoiceWriteoffDocument) documentService.getByDocumentHeaderId(writeoffDocNumber);
314            }
315            catch (WorkflowException e) {
316                throw new RuntimeException("A WorkflowException was thrown when trying to load Invoice Writeoff doc #" + writeoffDocNumber + ".", e);
317            }
318            
319            boolean wentToFinal = false;
320            try {
321                wentToFinal = waitForStatusChange(60, writeoff.getDocumentHeader().getWorkflowDocument(), new String[] {"F", "P"});
322            }
323            catch (Exception e) {
324                throw new RuntimeException("An Exception was thrown when trying to monitor writeoff doc #" + writeoffDocNumber +" going to FINAL.", e);
325            }
326    
327            //  if the doc didnt go to final, then blanket approve the doc, bypassing all rules
328            if (!wentToFinal) {
329                try {
330                    if (writeoff.getDocumentHeader().getWorkflowDocument().stateIsFinal()) {
331                        
332                    }
333                    writeoff.getDocumentHeader().getWorkflowDocument().blanketApprove("BlanketApproved by CustomerInvoiceDocumentBatch process.");
334                }
335                catch (WorkflowException e) {
336                    throw new RuntimeException("A WorkflowException was thrown when trying to blanketApprove Invoice Writeoff doc #" + writeoffDocNumber + ".", e);
337                }
338    
339                //  wait for it to go to final
340                wentToFinal = false;
341                try {
342                    wentToFinal = waitForStatusChange(60, writeoff.getDocumentHeader().getWorkflowDocument(), new String[] {"F", "P"});
343                }
344                catch (Exception e) {
345                    throw new RuntimeException("An Exception was thrown when trying to monitor writeoff doc #" + writeoffDocNumber +" going to FINAL.", e);
346                }
347            }
348            
349            if (!wentToFinal) {
350                throw new RuntimeException("InvoiceWriteoff document #" + writeoffDocNumber + " failed to route to FINAL.");
351            }
352        }
353    
354        public boolean waitForStatusChange(int numSeconds, KualiWorkflowDocument document, String[] desiredStatus) throws Exception {
355            DocWorkflowStatusMonitor monitor = new DocWorkflowStatusMonitor(SpringContext.getBean(DocumentService.class), "" + document.getRouteHeaderId(), desiredStatus);
356            return waitUntilChange(monitor, numSeconds, 5);
357        }
358    
359        /**
360         * Iterates, with pauseSeconds seconds between iterations, until either the given ChangeMonitor's valueChanged method returns
361         * true, or at least maxWaitSeconds seconds have passed.
362         * 
363         * @param monitor ChangeMonitor instance which watches for whatever change your test is waiting for
364         * @param maxWaitSeconds
365         * @param pauseSeconds
366         * @return true if the the ChangeMonitor's valueChanged method returned true before time ran out
367         */
368        public boolean waitUntilChange(DocWorkflowStatusMonitor monitor, int maxWaitSeconds, int pauseSeconds) throws Exception {
369            long maxWaitMs = maxWaitSeconds * 1000;
370            long pauseMs = pauseSeconds * 1000;
371    
372            boolean valueChanged = false;
373            boolean interrupted = false;
374            long startTimeMs = System.currentTimeMillis();
375            long endTimeMs = startTimeMs + maxWaitMs;
376    
377            Thread.sleep(pauseMs / 10); // the first time through, sleep a fraction of the specified time
378            valueChanged = monitor.valueChanged();
379            while (!interrupted && !valueChanged && (System.currentTimeMillis() < endTimeMs)) {
380                try {
381                    Thread.sleep(pauseMs);
382                }
383                catch (InterruptedException e) {
384                    interrupted = true;
385                }
386                valueChanged = monitor.valueChanged();
387            }
388            return valueChanged;
389        }
390        
391    //    public void writeoffInvoice(String invoiceToWriteOff) {
392    //        CustomerCreditMemoDetailService customerCreditMemoDetailService = SpringContext.getBean(CustomerCreditMemoDetailService.class);
393    //        CustomerCreditMemoDocument customerCreditMemoDocument;
394    //        CustomerCreditMemoDetail customerCreditMemoDetail = new CustomerCreditMemoDetail();
395    //        CustomerInvoiceDocument customerInvoiceDocument;
396    //
397    //        try {
398    //            customerInvoiceDocument = (CustomerInvoiceDocument) documentService.getNewDocument(CustomerInvoiceDocument.class);
399    //            LOG.info("\nCreated customer invoice document " + customerInvoiceDocument.getDocumentNumber());
400    //        } catch (WorkflowException e) {
401    //            throw new RuntimeException("Customer Invoice Document creation failed.");
402    //        }
403    //
404    //        customerInvoiceDocumentService.setupDefaultValuesForNewCustomerInvoiceDocument(customerInvoiceDocument);
405    //        customerInvoiceDocument.getDocumentHeader().setDocumentDescription("TEST paid off CUSTOMER INVOICE DOCUMENT");
406    //        customerInvoiceDocument.getAccountsReceivableDocumentHeader().setCustomerNumber("KAT17282");
407    //        customerInvoiceDocument.setBillingDate(getDateTimeService().getCurrentSqlDate());
408    //
409    //        CustomerAddress customerBillToAddress = SpringContext.getBean(CustomerAddressService.class).getPrimaryAddress("KAT17282");
410    //
411    //        customerInvoiceDocument.setCustomerBillToAddress(customerBillToAddress);
412    //        customerInvoiceDocument.setCustomerBillToAddressIdentifier(1);
413    //        customerInvoiceDocument.setBillingAddressTypeCode("P");
414    //        customerInvoiceDocument.setBillingAddressName(customerBillToAddress.getCustomerAddressName());
415    //        customerInvoiceDocument.setBillingLine1StreetAddress(customerBillToAddress.getCustomerLine1StreetAddress());
416    //        customerInvoiceDocument.setBillingLine2StreetAddress(customerBillToAddress.getCustomerLine2StreetAddress());
417    //        customerInvoiceDocument.setBillingCityName(customerBillToAddress.getCustomerCityName());
418    //        customerInvoiceDocument.setBillingStateCode(customerBillToAddress.getCustomerStateCode());
419    //        customerInvoiceDocument.setBillingZipCode(customerBillToAddress.getCustomerZipCode());
420    //        customerInvoiceDocument.setBillingCountryCode(customerBillToAddress.getCustomerCountryCode());
421    //        customerInvoiceDocument.setBillingAddressInternationalProvinceName(customerBillToAddress.getCustomerAddressInternationalProvinceName());
422    //        customerInvoiceDocument.setBillingInternationalMailCode(customerBillToAddress.getCustomerInternationalMailCode());
423    //        customerInvoiceDocument.setBillingEmailAddress(customerBillToAddress.getCustomerEmailAddress());
424    //        customerInvoiceDocument.addSourceAccountingLine(createCustomerInvoiceDetailForFunctionalTesting(customerInvoiceDocument, new KualiDecimal(1), new BigDecimal(100), "2336320", "BL", "ISRT" ));
425    //        
426    //        customerCreditMemoDetail.setCreditMemoItemQuantity(new BigDecimal(100));
427    //        customerCreditMemoDetail.setDuplicateCreditMemoItemTotalAmount();
428    //        try {
429    //            // the document header is created and set here
430    //            
431    //            customerCreditMemoDocument = (CustomerCreditMemoDocument) DocumentTestUtils.createDocument(SpringContext.getBean(DocumentService.class), CustomerCreditMemoDocument.class);
432    //        }
433    //        catch (WorkflowException e) {
434    //            throw new RuntimeException("Document customerCreditMemoDocument creation failed.");
435    //        }
436    //        customerCreditMemoDocument.setFinancialDocumentReferenceInvoiceNumber(customerInvoiceDocument.getDocumentNumber());
437    //        customerCreditMemoDocument.setInvoice(customerInvoiceDocument);
438    //        customerCreditMemoDocument.populateCustomerCreditMemoDetails();
439    //
440    //        List<CustomerCreditMemoDetail> customerCreditMemoDetails = customerCreditMemoDocument.getCreditMemoDetails();
441    //        for (CustomerCreditMemoDetail customerCreditMemoDetail : customerCreditMemoDetails) {
442    //            customerCreditMemoDetail.setCreditMemoItemQuantity(new BigDecimal(1));
443    //            customerCreditMemoDetail.setCreditMemoItemTotalAmount(new KualiDecimal(100));
444    //            customerCreditMemoDetail.setCreditMemoLineTotalAmount(new KualiDecimal(100));
445    //        }
446    //
447    //        try {
448    //            documentService.blanketApproveDocument(customerInvoiceDocument, null, null);
449    //            Thread.sleep(5000);
450    //            documentService.blanketApproveDocument(customerCreditMemoDocument, null, null);
451    //            LOG.info("Created customer credit memo " + customerCreditMemoDocument.getDocumentNumber());
452    //        } catch (WorkflowException e) {
453    //            throw new RuntimeException("Customer Invoice Document routing failed.");
454    //        }
455    //        LOG.info("Created customer credit memo " + customerCreditMemoDocument.getDocumentNumber());
456    //
457    //    }
458    
459    
460        public void createCustomerInvoiceDocumentForFunctionalTesting(String customerNumber, Date billingDate, int numinvoicedetails,  KualiDecimal nonrandomquantity, BigDecimal nonrandomunitprice, String accountnumber, String chartcode, String invoiceitemcode) {
461            SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy");
462            
463            CustomerInvoiceDocument customerInvoiceDocument;
464            try {
465                customerInvoiceDocument = (CustomerInvoiceDocument)documentService.getNewDocument(CustomerInvoiceDocument.class);
466                LOG.info("Created customer invoice document " + customerInvoiceDocument.getDocumentNumber());
467            } catch (WorkflowException e) {
468                throw new RuntimeException("Customer Invoice Document creation failed.");
469            }
470            
471            customerInvoiceDocumentService.setupDefaultValuesForNewCustomerInvoiceDocument(customerInvoiceDocument);
472            //customerInvoiceDocument.getDocumentHeader().setDocumentDescription(customerNumber+" - TEST CUSTOMER INVOICE DOCUMENT");// - BILLING DATE - "+sdf.format(billingDate));
473            customerInvoiceDocument.getDocumentHeader().setDocumentDescription("TEST CUSTOMER INVOICE DOCUMENT");
474            customerInvoiceDocument.getAccountsReceivableDocumentHeader().setCustomerNumber(customerNumber);
475            customerInvoiceDocument.setBillingDate(new java.sql.Date(billingDate.getTime()));
476    
477            CustomerAddress customerBillToAddress = SpringContext.getBean(CustomerAddressService.class).getPrimaryAddress(customerNumber);
478    //        CustomerAddress customerShipToAddress = SpringContext.getBean(CustomerAddressService.class).getPrimaryAddress(customerNumber);
479    
480            customerInvoiceDocument.setCustomerBillToAddress(customerBillToAddress);
481            customerInvoiceDocument.setCustomerBillToAddressIdentifier(1);
482            customerInvoiceDocument.setBillingAddressTypeCode("P");
483            customerInvoiceDocument.setBillingAddressName(customerBillToAddress.getCustomerAddressName());
484            customerInvoiceDocument.setBillingLine1StreetAddress(customerBillToAddress.getCustomerLine1StreetAddress());
485            customerInvoiceDocument.setBillingLine2StreetAddress(customerBillToAddress.getCustomerLine2StreetAddress());
486            customerInvoiceDocument.setBillingCityName(customerBillToAddress.getCustomerCityName());
487            customerInvoiceDocument.setBillingStateCode(customerBillToAddress.getCustomerStateCode());
488            customerInvoiceDocument.setBillingZipCode(customerBillToAddress.getCustomerZipCode());
489            customerInvoiceDocument.setBillingCountryCode(customerBillToAddress.getCustomerCountryCode());
490            customerInvoiceDocument.setBillingAddressInternationalProvinceName(customerBillToAddress.getCustomerAddressInternationalProvinceName());
491            customerInvoiceDocument.setBillingInternationalMailCode(customerBillToAddress.getCustomerInternationalMailCode());
492            customerInvoiceDocument.setBillingEmailAddress(customerBillToAddress.getCustomerEmailAddress());
493    
494    //        customerInvoiceDocument.setCustomerShipToAddress(customerShipToAddress);
495    //        customerInvoiceDocument.setShippingAddressTypeCode("P");
496    //        customerInvoiceDocument.setShippingAddressName(customerShipToAddress.getCustomerAddressName());
497    //        customerInvoiceDocument.setShippingLine1StreetAddress(customerShipToAddress.getCustomerLine1StreetAddress());
498    //        customerInvoiceDocument.setShippingLine2StreetAddress(customerShipToAddress.getCustomerLine2StreetAddress());
499    //        customerInvoiceDocument.setShippingCityName(customerShipToAddress.getCustomerCityName());
500    //        customerInvoiceDocument.setShippingStateCode(customerShipToAddress.getCustomerStateCode());
501    //        customerInvoiceDocument.setShippingZipCode(customerShipToAddress.getCustomerZipCode());
502    //        customerInvoiceDocument.setShippingCountryCode(customerShipToAddress.getCustomerCountryCode());
503    //        customerInvoiceDocument.setShippingAddressInternationalProvinceName(customerShipToAddress.getCustomerAddressInternationalProvinceName());
504    //        customerInvoiceDocument.setShippingInternationalMailCode(customerShipToAddress.getCustomerInternationalMailCode());
505    //        customerInvoiceDocument.setShippingEmailAddress(customerShipToAddress.getCustomerEmailAddress());
506    
507    
508            if (ObjectUtils.isNotNull(nonrandomquantity)&&ObjectUtils.isNotNull(nonrandomunitprice)&&numinvoicedetails>=1) {
509                for (int i = 0; i < numinvoicedetails; i++) { 
510                    customerInvoiceDocument.addSourceAccountingLine(createCustomerInvoiceDetailForFunctionalTesting(customerInvoiceDocument, nonrandomquantity, nonrandomunitprice, accountnumber, chartcode, invoiceitemcode));
511                }  
512            } else {       
513                int randomnuminvoicedetails = (int) (Math.random()*9); // add up to 9
514                if (randomnuminvoicedetails==0) randomnuminvoicedetails=1; // add at least one
515                for (int i = 0; i < randomnuminvoicedetails; i++) { 
516                    customerInvoiceDocument.addSourceAccountingLine(createCustomerInvoiceDetailForFunctionalTesting(customerInvoiceDocument, null, null, accountnumber, chartcode, invoiceitemcode));
517                }              
518            }
519            try {
520                documentService.blanketApproveDocument(customerInvoiceDocument, null, null);
521                createdInvoices.add(customerInvoiceDocument.getDocumentNumber());
522                LOG.info("Submitted customer invoice document " + customerInvoiceDocument.getDocumentNumber()+" for "+customerNumber+" - "+sdf.format(billingDate)+"\n\n");
523            } catch (WorkflowException e){
524                throw new RuntimeException("Customer Invoice Document routing failed.");
525            }
526        }
527        
528        public CustomerInvoiceDetail createCustomerInvoiceDetailForFunctionalTesting(CustomerInvoiceDocument customerInvoiceDocument, KualiDecimal nonrandomquantity, BigDecimal nonrandomunitprice, String accountnumber, String chartcode, String invoiceitemcode){
529            
530            KualiDecimal quantity;
531            BigDecimal unitprice;
532            
533            if (ObjectUtils.isNull(nonrandomquantity)) {
534                quantity = new KualiDecimal(100*Math.random()); // random number 0 to 100 total items      // TODO FIXME  <-- InvoiceItemQuantities of more than 2 decimal places cause rule errors; BigDecimal values such as 5.3333333333 should be valid InvoiceItemQuantities
535            } else {
536                quantity = nonrandomquantity;
537            }
538            if (ObjectUtils.isNull(nonrandomunitprice)) {    
539            unitprice = new BigDecimal(1); // 0.00 to 100.00 dollars per item
540            } else {
541                unitprice = nonrandomunitprice;
542            }                
543            
544            KualiDecimal amount = quantity.multiply(new KualiDecimal(unitprice)); // setAmount has to be set explicitly below; so we calculate it here
545            //LOG.info("\n\n\n\n\t\t\t\t quantity="+quantity.toString()+"\t\t\t\tprice="+unitprice.toString()+"\t\t\t\tamount="+amount.toString()+"\t\t\t\t"+customerInvoiceDocument.getCustomerName());
546            
547            CustomerInvoiceDetail customerInvoiceDetail = new CustomerInvoiceDetail();
548            customerInvoiceDetail.setDocumentNumber(customerInvoiceDocument.getDocumentNumber());
549            customerInvoiceDetail.setChartOfAccountsCode(chartcode);
550            customerInvoiceDetail.setAccountNumber(accountnumber);    //   other BL account numbers:   2231401   2324601
551            customerInvoiceDetail.setFinancialObjectCode("1800");
552            customerInvoiceDetail.setAccountsReceivableObjectCode("8118");
553            customerInvoiceDetail.setInvoiceItemCode(invoiceitemcode);
554            customerInvoiceDetail.setInvoiceItemServiceDate(dateTimeService.getCurrentSqlDate());
555            customerInvoiceDetail.setInvoiceItemUnitPrice(unitprice);
556            customerInvoiceDetail.setInvoiceItemQuantity(quantity.bigDecimalValue());
557            customerInvoiceDetail.setInvoiceItemTaxAmount(new KualiDecimal(100));
558            customerInvoiceDetail.setTaxableIndicator(true);
559            customerInvoiceDetail.setAmount(amount);
560            customerInvoiceDetail.setPostingYear(currentYear);
561    
562    
563            return customerInvoiceDetail;
564        }  
565        
566        
567        public DateTimeService getDateTimeService() {
568            return dateTimeService;
569        }
570    
571    
572        public void setDateTimeService(DateTimeService dateTimeService) {
573            this.dateTimeService = dateTimeService;
574        }
575    
576    
577        public DocumentService getDocumentService() {
578            return documentService;
579        }
580    
581    
582        public void setDocumentService(DocumentService documentService) {
583            this.documentService = documentService;
584        }    
585        
586        public CustomerInvoiceDocumentService getCustomerInvoiceDocumentService() {
587            return customerInvoiceDocumentService;
588        }
589    
590    
591        public void setCustomerInvoiceDocumentService(CustomerInvoiceDocumentService customerInvoiceDocumentService) {
592            this.customerInvoiceDocumentService = customerInvoiceDocumentService;
593        }
594    
595    
596        public BusinessObjectService getBusinessObjectService() {
597            return businessObjectService;
598        }
599    
600    
601        public void setBusinessObjectService(BusinessObjectService businessObjectService) {
602            this.businessObjectService = businessObjectService;
603        }    
604    
605        private class DocWorkflowStatusMonitor {
606            final DocumentService documentService;
607            final private String docHeaderId;
608            final private String[] desiredWorkflowStates;
609    
610            public DocWorkflowStatusMonitor(DocumentService documentService, String docHeaderId, String desiredWorkflowStatus) {
611                this.documentService = documentService;
612                this.docHeaderId = docHeaderId;
613                this.desiredWorkflowStates = new String[] { desiredWorkflowStatus };
614            }
615    
616            public DocWorkflowStatusMonitor(DocumentService documentService, String docHeaderId, String[] desiredWorkflowStates) {
617                this.documentService = documentService;
618                this.docHeaderId = docHeaderId;
619                this.desiredWorkflowStates = desiredWorkflowStates;
620            }
621    
622            public boolean valueChanged() throws Exception {
623                Document d = documentService.getByDocumentHeaderId(docHeaderId.toString());
624    
625                String currentStatus = d.getDocumentHeader().getWorkflowDocument().getRouteHeader().getDocRouteStatus();
626    
627                for (int i = 0; i < desiredWorkflowStates.length; i++) {
628                    if (StringUtils.equals(desiredWorkflowStates[i], currentStatus)) {
629                        return true;
630                    }
631                }
632                return false;
633            }
634        }
635    }
636