/*
 * The Kuali Financial System, a comprehensive financial management system for higher education.
 *
 * Copyright 2005-2016 The Kuali Foundation
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kuali.kfs.module.purap.document;

import org.kuali.kfs.kns.service.TransactionalDocumentDictionaryService;
import org.kuali.kfs.krad.service.DocumentService;
import org.kuali.kfs.krad.service.SequenceAccessorService;
import org.kuali.kfs.krad.util.GlobalVariables;
import org.kuali.kfs.krad.util.ObjectUtils;
import org.kuali.kfs.module.purap.PurapConstants.PurchaseOrderStatuses;
import org.kuali.kfs.module.purap.businessobject.AccountsPayableItem;
import org.kuali.kfs.module.purap.businessobject.PaymentRequestItem;
import org.kuali.kfs.module.purap.businessobject.PurApAccountingLine;
import org.kuali.kfs.module.purap.businessobject.PurchaseOrderAccount;
import org.kuali.kfs.module.purap.businessobject.PurchaseOrderItem;
import org.kuali.kfs.module.purap.businessobject.PurchaseOrderItemUseTax;
import org.kuali.kfs.module.purap.document.service.PaymentRequestService;
import org.kuali.kfs.module.purap.document.service.PurapService;
import org.kuali.kfs.module.purap.document.service.PurchaseOrderService;
import org.kuali.kfs.module.purap.fixture.PaymentRequestDocumentFixture;
import org.kuali.kfs.module.purap.fixture.PaymentRequestItemFixture;
import org.kuali.kfs.module.purap.fixture.PurchaseOrderDocumentFixture;
import org.kuali.kfs.sys.ConfigureContext;
import org.kuali.kfs.sys.DocumentTestUtils;
import org.kuali.kfs.sys.context.KualiTestBase;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.document.AccountingDocumentTestUtils;
import org.kuali.kfs.sys.document.workflow.WorkflowTestUtils;
import org.kuali.kfs.vnd.VendorConstants;
import org.kuali.kfs.vnd.businessobject.PaymentTermType;
import org.kuali.kfs.vnd.businessobject.VendorAddress;
import org.kuali.kfs.vnd.document.service.VendorService;
import org.kuali.rice.core.api.util.type.KualiDecimal;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import static org.kuali.kfs.sys.document.AccountingDocumentTestUtils.testGetNewDocument_byDocumentClass;
import static org.kuali.kfs.sys.fixture.UserNameFixture.appleton;
import static org.kuali.kfs.sys.fixture.UserNameFixture.parke;
import static org.kuali.kfs.sys.fixture.UserNameFixture.rorenfro;

/**
 * This class is used to create and test populated Payment Request Documents of various kinds.
 */
@ConfigureContext(session = appleton)
public class PaymentRequestDocumentTest extends KualiTestBase {
    private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PaymentRequestItem.class);

    public static final Class<PaymentRequestDocument> DOCUMENT_CLASS = PaymentRequestDocument.class;
    private static final String ACCOUNT_REVIEW = "Account";

    protected static DocumentService documentService = null;
    protected static PurchaseOrderService purchaseOrderService = null;
    private PaymentRequestDocument paymentRequestDocument = null;
    protected static PurchaseOrderDocument purchaseOrderDocument = null;

    @Override
    protected void setUp() throws Exception {
        documentService = SpringContext.getBean(DocumentService.class);
        purchaseOrderService = SpringContext.getBean(PurchaseOrderService.class);
        super.setUp();
    }

    @Override
    protected void tearDown() throws Exception {
        paymentRequestDocument = null;
        super.tearDown();
    }

    private int getExpectedPrePeCount() {
        return 4;
    }

    public final void testAddItem() throws Exception {
        List<AccountsPayableItem> items = new ArrayList<AccountsPayableItem>();
        items.add(PaymentRequestItemFixture.PREQ_QTY_UNRESTRICTED_ITEM_1.createPaymentRequestItem());

        int expectedItemTotal = items.size();
        AccountsPayableDocumentTestUtils.testAddItem(DocumentTestUtils.createDocument(documentService, DOCUMENT_CLASS), items, expectedItemTotal);
    }

    public final void testNothing() throws Exception {
    }

    public final void testGetNewDocument() throws Exception {
        testGetNewDocument_byDocumentClass(DOCUMENT_CLASS, documentService);
    }

    @ConfigureContext(session = appleton, shouldCommitTransactions = false)
    public final void testConvertIntoErrorCorrection() throws Exception {
        purchaseOrderDocument = createPurchaseOrderDocument(PurchaseOrderDocumentFixture.PO_APPROVAL_REQUIRED, false);
        paymentRequestDocument = createPaymentRequestDocument(PaymentRequestDocumentFixture.PREQ_APPROVAL_REQUIRED, purchaseOrderDocument, true, new KualiDecimal[]{new KualiDecimal(100)});
        AccountingDocumentTestUtils.testConvertIntoErrorCorrection(paymentRequestDocument, getExpectedPrePeCount(), documentService, SpringContext.getBean(TransactionalDocumentDictionaryService.class));
    }

    @ConfigureContext(session = appleton, shouldCommitTransactions = false)
    public final void testSaveDocument() throws Exception {
        purchaseOrderDocument = createPurchaseOrderDocument(PurchaseOrderDocumentFixture.PO_APPROVAL_REQUIRED, false);
        paymentRequestDocument = createPaymentRequestDocument(PaymentRequestDocumentFixture.PREQ_APPROVAL_REQUIRED, purchaseOrderDocument, true, new KualiDecimal[]{new KualiDecimal(100)});
    }

    @ConfigureContext(session = appleton, shouldCommitTransactions = false)
    public final void testRouteDocument() throws Exception {
        purchaseOrderDocument = createPurchaseOrderDocument(PurchaseOrderDocumentFixture.PO_APPROVAL_REQUIRED, true);
        purchaseOrderDocument.setAccountDistributionMethod("S");
        paymentRequestDocument = createPaymentRequestDocument(PaymentRequestDocumentFixture.PREQ_APPROVAL_REQUIRED, purchaseOrderDocument, true, new KualiDecimal[]{new KualiDecimal(100)});
        paymentRequestDocument.setAccountDistributionMethod("S");
        AccountingDocumentTestUtils.testRouteDocument(paymentRequestDocument, documentService);
    }

    @ConfigureContext(session = appleton, shouldCommitTransactions = false)
    public final void testRouteDocumentToFinal() throws Exception {
        purchaseOrderDocument = createPurchaseOrderDocument(PurchaseOrderDocumentFixture.PO_APPROVAL_REQUIRED, true);
        paymentRequestDocument = createPaymentRequestDocument(PaymentRequestDocumentFixture.PREQ_APPROVAL_REQUIRED, purchaseOrderDocument, true, new KualiDecimal[]{new KualiDecimal(100)});

        final String docId = paymentRequestDocument.getDocumentNumber();
        AccountingDocumentTestUtils.routeDocument(paymentRequestDocument, documentService);
        WorkflowTestUtils.waitForNodeChange(paymentRequestDocument.getDocumentHeader().getWorkflowDocument(), ACCOUNT_REVIEW);

        // the document should now be routed to vputman as Fiscal Officer
        changeCurrentUser(rorenfro);
        paymentRequestDocument = (PaymentRequestDocument) documentService.getByDocumentHeaderId(docId);
        assertTrue("At incorrect node.", WorkflowTestUtils.isAtNode(paymentRequestDocument, ACCOUNT_REVIEW));
        assertTrue("Document should be enroute.", paymentRequestDocument.getDocumentHeader().getWorkflowDocument().isEnroute());
        assertTrue("rorenfro should have an approve request.", paymentRequestDocument.getDocumentHeader().getWorkflowDocument().isApprovalRequested());
        documentService.approveDocument(paymentRequestDocument, "Test approving as rorenfro", null);

        WorkflowTestUtils.waitForDocumentApproval(paymentRequestDocument.getDocumentNumber());

        paymentRequestDocument = (PaymentRequestDocument) documentService.getByDocumentHeaderId(docId);
        assertTrue("Document should now be final.", paymentRequestDocument.getDocumentHeader().getWorkflowDocument().isFinal());
    }

    // Commented due to Jira issue preventing documents created by PREQ from going to final
    // the underlying issue with this test is that the way it is written causes an exception we are unable to solve. The result is:
    //
    // Caused by: org.apache.ojb.broker.OptimisticLockException: Object has been modified by someone else
    //
    // This test needs to be re-written to alleviate the OptimisticLockException.
    //
    @ConfigureContext(session = appleton, shouldCommitTransactions = false)
    public final void testClosePo() throws Exception {
        // route a preq and mark the close po indicator
        /*
                purchaseOrderDocument = createPurchaseOrderDocument(PurchaseOrderDocumentFixture.CLOSE_PO_WITH_PREQ, true);

                paymentRequestDocument = createPaymentRequestDocument(PaymentRequestDocumentFixture.CLOSE_PO_WITH_PREQ,
                        purchaseOrderDocument, true, new KualiDecimal[] {new KualiDecimal(100)});

                final String docId = paymentRequestDocument.getDocumentNumber();
                final String poDocId = paymentRequestDocument.getPurchaseOrderDocument().getDocumentNumber();
                Thread.sleep(5000);  // attempt to make sure any other async processing on this document is done

                // another attempt to solve the problem of OptimisticLockException in the call to routeDocument... still no good.
        //        for (int i = 0; i < 10; i++) {
        //            try {
        //                AccountingDocumentTestUtils.routeDocument(paymentRequestDocument, documentService);
        //                break;
        //            } catch (Exception e) {
        //                LOG.warn("attempt #" + (i + 1) + " exception trying to routDocument: [" + e + "]");
        //                Thread.sleep(6000);
        //            }
        //        }
                WorkflowTestUtils.waitForNodeChange(paymentRequestDocument.getDocumentHeader().getWorkflowDocument(), ACCOUNT_REVIEW);

                // now route
                changeCurrentUser(rorenfro);
                paymentRequestDocument = (PaymentRequestDocument) documentService.getByDocumentHeaderId(docId);
                assertTrue("At incorrect node.", WorkflowTestUtils.isAtNode(paymentRequestDocument, ACCOUNT_REVIEW));
                assertTrue("Document should be enroute.", paymentRequestDocument.getDocumentHeader().getWorkflowDocument().isEnroute());
                assertTrue("rorenfro should have an approve request.", paymentRequestDocument.getDocumentHeader().getWorkflowDocument().isApprovalRequested());
                documentService.approveDocument(paymentRequestDocument, "Test approving as rorenfro", null);

                WorkflowTestUtils.waitForStatusChange(paymentRequestDocument.getDocumentHeader().getWorkflowDocument(), KewApiConstants.ROUTE_HEADER_FINAL_CD);

                // check if the purchase order document is closed
                PurchaseOrderDocument purchaseOrderDocument =(PurchaseOrderDocument) purchaseOrderService.getCurrentPurchaseOrder(paymentRequestDocument.getPurchaseOrderDocument().getPurapDocumentIdentifier()); //documentService.getByDocumentHeaderId(poDocId);
                assertTrue( "Purchase order should be closed.", PurchaseOrderStatuses.CLOSED.equals( purchaseOrderDocument.getStatusCode() ) );
                */
    }

    // Commented due to Jira issue preventing documents created by PREQ from going to final
    @ConfigureContext(session = appleton, shouldCommitTransactions = false)
    public final void testReopenPo() throws Exception {
        // route a preq and mark the close po indicator
        /*
        purchaseOrderDocument = createPurchaseOrderDocument(PurchaseOrderDocumentFixture.REOPEN_PO_WITH_PREQ, true);
        paymentRequestDocument = createPaymentRequestDocument(PaymentRequestDocumentFixture.REOPEN_PO_WITH_PREQ,
                purchaseOrderDocument, true, new KualiDecimal[] {new KualiDecimal(100)});

        final String docId = paymentRequestDocument.getDocumentNumber();
        final String poDocId = paymentRequestDocument.getPurchaseOrderDocument().getDocumentNumber();
        AccountingDocumentTestUtils.routeDocument(paymentRequestDocument, documentService);
        WorkflowTestUtils.waitForNodeChange(paymentRequestDocument.getDocumentHeader().getWorkflowDocument(), ACCOUNT_REVIEW);

        //close po
        changeCurrentUser(parke);
        purchaseOrderDocument.setStatusCode(PurchaseOrderStatuses.CLOSED);
        AccountingDocumentTestUtils.testSaveDocument(purchaseOrderDocument, documentService);
        purchaseOrderDocument =(PurchaseOrderDocument) documentService.getByDocumentHeaderId(poDocId);

        // now route
        changeCurrentUser(rorenfro);
        paymentRequestDocument = (PaymentRequestDocument) documentService.getByDocumentHeaderId(docId);
        assertTrue("At incorrect node.", WorkflowTestUtils.isAtNode(paymentRequestDocument, ACCOUNT_REVIEW));
        assertTrue("Document should be enroute.", paymentRequestDocument.getDocumentHeader().getWorkflowDocument().isEnroute());
        assertTrue("rorenfro should have an approve request.", paymentRequestDocument.getDocumentHeader().getWorkflowDocument().isApprovalRequested());
        documentService.approveDocument(paymentRequestDocument, "Test approving as rorenfro", null);

        WorkflowTestUtils.waitForStatusChange(paymentRequestDocument.getDocumentHeader().getWorkflowDocument(), KewApiConstants.ROUTE_HEADER_FINAL_CD);

        // check if the purchase order document is open
        PurchaseOrderDocument purchaseOrderDocument =(PurchaseOrderDocument)purchaseOrderService.getCurrentPurchaseOrder(paymentRequestDocument.getPurchaseOrderDocument().getPurapDocumentIdentifier()); //documentService.getByDocumentHeaderId(poDocId);
        assertTrue( "Purchase order should be opened.", PurchaseOrderStatuses.OPEN.equals( purchaseOrderDocument.getStatusCode() ) );
        */
    }

    @ConfigureContext(session = appleton, shouldCommitTransactions = false)
    public final void testRequestCancel() throws Exception {
        // route a preq and mark the preq as requested cancel
        /*purchaseOrderDocument = createPurchaseOrderDocument(PurchaseOrderDocumentFixture.PO_APPROVAL_REQUIRED, false);
        paymentRequestDocument = createPaymentRequestDocument(PaymentRequestDocumentFixture.PREQ_APPROVAL_REQUIRED,
                purchaseOrderDocument, true, new KualiDecimal[] {new KualiDecimal(100)});

        final String docId = paymentRequestDocument.getDocumentNumber();
        final String poDocId = paymentRequestDocument.getPurchaseOrderDocument().getDocumentNumber();
        AccountingDocumentTestUtils.testRouteDocument(paymentRequestDocument, documentService);
        WorkflowTestUtils.waitForNodeChange(paymentRequestDocument.getDocumentHeader().getWorkflowDocument(), ACCOUNT_REVIEW);

        paymentRequestDocument = (PaymentRequestDocument) documentService.getByDocumentHeaderId(docId);
        PaymentRequestDocumentActionAuthorizer preqAA = new PaymentRequestDocumentActionAuthorizer(paymentRequestDocument);
        assertTrue( "Payment Request should allow remove request cancel.", preqAA.canRemoveRequestCancel() );*/
    }

    @ConfigureContext(session = appleton, shouldCommitTransactions = false)
    public final void testRequestHold() throws Exception {
        // route a preq and mark the preq as requested hold
        /*purchaseOrderDocument = createPurchaseOrderDocument(PurchaseOrderDocumentFixture.REQUEST_HOLD_PREQ, false);
        paymentRequestDocument = createPaymentRequestDocument(PaymentRequestDocumentFixture.REQUEST_HOLD_PREQ,
                purchaseOrderDocument, true, new KualiDecimal[] {new KualiDecimal(100)});

        final String docId = paymentRequestDocument.getDocumentNumber();
        final String poDocId = paymentRequestDocument.getPurchaseOrderDocument().getDocumentNumber();
        AccountingDocumentTestUtils.routeDocument(paymentRequestDocument, documentService);
        WorkflowTestUtils.waitForNodeChange(paymentRequestDocument.getDocumentHeader().getWorkflowDocument(), ACCOUNT_REVIEW);

        paymentRequestDocument = (PaymentRequestDocument) documentService.getByDocumentHeaderId(docId);
        PaymentRequestDocumentActionAuthorizer preqAA = new PaymentRequestDocumentActionAuthorizer(paymentRequestDocument);
        assertTrue( "Payment Request should allow remove hold.", preqAA.canRemoveHold());*/
    }

    @ConfigureContext(session = appleton, shouldCommitTransactions = false)
    public final void testCalculate() throws Exception {
    }

    /**
     * Test Usetax and split accounting
     *
     * @throws Exception
     */
    @ConfigureContext(session = appleton, shouldCommitTransactions = false)
    public final void testUseTax() throws Exception {
        purchaseOrderDocument = createPurchaseOrderDocument(PurchaseOrderDocumentFixture.PO_APPROVAL_REQUIRED, true);
        purchaseOrderDocument.setDeliveryBuildingCode("BL");
        purchaseOrderDocument.setDeliveryBuildingLine1Address("2332 Correa Rd");
        purchaseOrderDocument.setDeliveryBuildingRoomNumber("124");
        purchaseOrderDocument.setDeliveryCityName("Tuscon");
        purchaseOrderDocument.setDeliveryStateCode("AZ");
        purchaseOrderDocument.setDeliveryPostalCode("85034");
        purchaseOrderDocument.setDeliveryCountryCode("US");

        purchaseOrderDocument.setBillingLine1Address("2332 Correa Rd");
        purchaseOrderDocument.setBillingCityName("Tuscon");
        purchaseOrderDocument.setBillingStateCode("AZ");
        purchaseOrderDocument.setBillingPostalCode("85034");
        purchaseOrderDocument.setBillingCountryCode("US");

        purchaseOrderDocument.setVendorName("Prepotech");
        purchaseOrderDocument.setVendorCityName("Rocky Hill");
        purchaseOrderDocument.setVendorNumber("4105-0");
        purchaseOrderDocument.setVendorStateCode("NJ");
        purchaseOrderDocument.setVendorPostalCode("08553");
        purchaseOrderDocument.setVendorCountryCode("US");
        purchaseOrderDocument.setUseTaxIndicator(true);
        List<PurchaseOrderItem> poiList = generateItems();
        purchaseOrderDocument.setItems(poiList); // add items
        purchaseOrderDocument.fixItemReferences();
        purchaseOrderDocument.setTotalDollarAmount(new KualiDecimal(1000));
        paymentRequestDocument = createPaymentRequestDocument(PaymentRequestDocumentFixture.PREQ_APPROVAL_REQUIRED, purchaseOrderDocument, true, new KualiDecimal[]{new KualiDecimal(1)});
        // setup payment request items

        for (PurchaseOrderItem poi : poiList) {
            if (poi.isItemActiveIndicator() && (poi.getItemQuantity().isPositive()) && (poi.getItemUnitPrice().intValue() > 0)) {
                PaymentRequestItem pri = new PaymentRequestItem(poi, paymentRequestDocument);
                // pri.setUseTaxItems(poi.getUseTaxItems());
                paymentRequestDocument.addItem(pri);
            }
        }
        updateQuantityAndPrice(paymentRequestDocument, new KualiDecimal[]{new KualiDecimal(1)});
        paymentRequestDocument.setUseTaxIndicator(true);
        AccountingDocumentTestUtils.testSaveDocument(paymentRequestDocument, documentService);

        // retrieve saved payment request
        final String preqDocId = paymentRequestDocument.getDocumentNumber();
        paymentRequestDocument = (PaymentRequestDocument) documentService.getByDocumentHeaderId(preqDocId);

        final String docId = paymentRequestDocument.getDocumentNumber();

        AccountingDocumentTestUtils.routeDocument(paymentRequestDocument, documentService);
        WorkflowTestUtils.waitForNodeChange(paymentRequestDocument.getDocumentHeader().getWorkflowDocument(), ACCOUNT_REVIEW);

        // the document should now be routed to vputman as Fiscal Officer
        changeCurrentUser(rorenfro);
        paymentRequestDocument = (PaymentRequestDocument) documentService.getByDocumentHeaderId(docId);
        assertTrue("At incorrect node.", WorkflowTestUtils.isAtNode(paymentRequestDocument, ACCOUNT_REVIEW));
        assertTrue("Document should be enroute.", paymentRequestDocument.getDocumentHeader().getWorkflowDocument().isEnroute());
        assertTrue("rorenfro should have an approve request.", paymentRequestDocument.getDocumentHeader().getWorkflowDocument().isApprovalRequested());

        // TODO: This assert needs to be based on non-DB data. Until this is re-written, it will have to be manually changed every time the DB changes.
        // assertTrue("total values should be 183", paymentRequestDocument.getTotalDollarAmount().equals(new KualiDecimal(183)));
        documentService.approveDocument(paymentRequestDocument, "Test approving as rorenfro", null);

        WorkflowTestUtils.waitForDocumentApproval(paymentRequestDocument.getDocumentNumber());

        paymentRequestDocument = (PaymentRequestDocument) documentService.getByDocumentHeaderId(docId);
        assertTrue("Document should now be final.", paymentRequestDocument.getDocumentHeader().getWorkflowDocument().isFinal());

        // paymentRequestDocument.getItems();

    }

    /**
     * Creates a purchase order document with a provided purchase order document.
     * At a minimum saves the document, but can additionally route the document (stipulation: coded to work only for budget review).
     *
     * @param poFixture - purchase order document fixture to source test data
     * @param routePO   - An option to route the purchase order if set to true
     * @return
     * @throws Exception
     * @ConfigureContext(shouldCommitTransactions=false)
     */
    public final PurchaseOrderDocument createPurchaseOrderDocument(PurchaseOrderDocumentFixture poFixture, boolean routePO) throws Exception {
        return createPurchaseOrderDocument(poFixture, routePO, null);
    }

    /**
     * Creates a purchase order document with a provided purchase order document.
     * At a minimum saves the document, but can additionally route the document (stipulation: coded to work only for budget review).
     *
     * @param poFixture                                       - purchase order document fixture to source test data
     * @param routePO                                         - An option to route the purchase order if set to true
     * @param accountsPayablePurchasingDocumentLinkIdentifier - Integer accountsPayablePurchasingDocumentLinkIdentifier to set on the PO if not null
     * @return
     * @throws Exception
     * @ConfigureContext(shouldCommitTransactions=false)
     */
    public final PurchaseOrderDocument createPurchaseOrderDocument(PurchaseOrderDocumentFixture poFixture, boolean routePO, Integer accountsPayablePurchasingDocumentLinkIdentifier) throws Exception {

        // check if test has been setup
        if (documentService == null) {
            documentService = SpringContext.getBean(DocumentService.class);
        }

        // setup purchase order and save
        changeCurrentUser(parke);
        PurchaseOrderDocument po = poFixture.createPurchaseOrderDocument();
        po.setApplicationDocumentStatus(PurchaseOrderStatuses.APPDOC_OPEN);
        po.refreshNonUpdateableReferences();
        if (ObjectUtils.isNotNull(accountsPayablePurchasingDocumentLinkIdentifier)) {
            po.setAccountsPayablePurchasingDocumentLinkIdentifier(accountsPayablePurchasingDocumentLinkIdentifier);
        }
        AccountingDocumentTestUtils.testSaveDocument(po, documentService);

        // retrieve saved purchase order
        final String poDocId = po.getDocumentNumber();
        po = (PurchaseOrderDocument) documentService.getByDocumentHeaderId(poDocId);

        // route if requested
        if (routePO) {
            AccountingDocumentTestUtils.testRouteDocument(po, documentService);
            WorkflowTestUtils.waitForDocumentApproval(po.getDocumentNumber());
            po = (PurchaseOrderDocument) documentService.getByDocumentHeaderId(poDocId);
        }
        po.setAccountDistributionMethod("S");
        return po;
    }

    /* Populates the Purchase Order Item using the info contained in this fixture with split accounting 30/70
     *
     * @return the populated Purchase Order Item.
     */
    private PurchaseOrderItem createPoItem() {
        String chart_code = "BL";
        BigDecimal percentage = BigDecimal.valueOf(100);
        String item_desc = "Iphones";
        String item_um = "CT";
        String item_catalog_number = "1F742";

        PurchaseOrderItem poi = new PurchaseOrderItem();
        SequenceAccessorService sas = SpringContext.getBean(SequenceAccessorService.class);
        Integer itemIdentifier = sas.getNextAvailableSequenceNumber("PO_ITM_ID", PurchaseOrderDocument.class).intValue();
        poi.setItemIdentifier(itemIdentifier);
        poi.setItemDescription(item_desc);
        poi.setItemUnitOfMeasureCode(item_um);
        poi.setItemUnitPrice(BigDecimal.valueOf(100));
        poi.setItemTypeCode("ITEM");
        poi.setItemQuantity(new KualiDecimal(2));
        poi.setItemLineNumber(new Integer(1));
        poi.setItemUnitPrice(BigDecimal.valueOf(100));

        poi.setTotalAmount(new KualiDecimal(5000));
        poi.setItemCatalogNumber(item_catalog_number);

        itemIdentifier = sas.getNextAvailableSequenceNumber("PO_ITM_ID", PurchaseOrderDocument.class).intValue();
        PurchaseOrderItemUseTax newItemUseTax = new PurchaseOrderItemUseTax();
//        newItemUseTax.setAutoIncrementSet(true);
        newItemUseTax.setChartOfAccountsCode(chart_code);
        newItemUseTax.setAccountNumber("0102395");
        newItemUseTax.setFinancialObjectCode("9016");
        newItemUseTax.setTaxAmount(new KualiDecimal(56));
        newItemUseTax.setRateCode("5");
        newItemUseTax.setItemIdentifier(itemIdentifier);
        poi.getUseTaxItems().add(newItemUseTax);

        itemIdentifier = sas.getNextAvailableSequenceNumber("PO_ITM_ID", PurchaseOrderDocument.class).intValue();
        newItemUseTax = new PurchaseOrderItemUseTax();
//        newItemUseTax.setAutoIncrementSet(true);
        newItemUseTax.setChartOfAccountsCode(chart_code);
        newItemUseTax.setAccountNumber("0102395");
        newItemUseTax.setFinancialObjectCode("9015");
        newItemUseTax.setTaxAmount(new KualiDecimal(20));
        newItemUseTax.setRateCode("7");
        newItemUseTax.setItemIdentifier(itemIdentifier);
        poi.getUseTaxItems().add(newItemUseTax);

        itemIdentifier = sas.getNextAvailableSequenceNumber("PO_ITM_ID", PurchaseOrderDocument.class).intValue();
        newItemUseTax = new PurchaseOrderItemUseTax();
//        newItemUseTax.setAutoIncrementSet(true);
        newItemUseTax.setChartOfAccountsCode(chart_code);
        newItemUseTax.setAccountNumber("0102395");
        newItemUseTax.setFinancialObjectCode("9016");
        newItemUseTax.setTaxAmount(new KualiDecimal(7));
        newItemUseTax.setRateCode("12");
        newItemUseTax.setItemIdentifier(itemIdentifier);
        poi.getUseTaxItems().add(newItemUseTax);

        // poi.refreshNonUpdateableReferences();
        List<PurApAccountingLine> lines = new ArrayList<PurApAccountingLine>();
        PurchaseOrderAccount poAccount = new PurchaseOrderAccount();
        poAccount.setAccountNumber("1031400");
        poAccount.setAccountLinePercent(BigDecimal.valueOf(70));
        poAccount.setAmount(new KualiDecimal(70));
        poAccount.setChartOfAccountsCode(chart_code);
        poAccount.setFinancialObjectCode("5000");
        lines.add(poAccount);

        PurchaseOrderAccount poAccount1 = new PurchaseOrderAccount();
        poAccount1.setAccountNumber("1031420");
        poAccount1.setAccountLinePercent(BigDecimal.valueOf(30));
        poAccount1.setAmount(new KualiDecimal(30));
        poAccount1.setChartOfAccountsCode(chart_code);
        poAccount1.setFinancialObjectCode("5000");
        lines.add(poAccount1);

        poi.setSourceAccountingLines(lines);
        poi.setItemActiveIndicator(true);
        poi.refreshNonUpdateableReferences();
        return poi;

    }

    /**
     * generate list of usetax items to test with
     *
     * @return
     * @throws Exception
     */
    private List<PurchaseOrderItem> generateItems() throws Exception {
        List<PurchaseOrderItem> items = new ArrayList<PurchaseOrderItem>();
        // set items to document
        items.add(createPoItem());
        return items;
    }

    /**
     * Creates a new payment request document and additionally saves it.
     *
     * @param preqFixture      - The payment request document fixture to source test data from
     * @param po               - The purchase order to copy select values from and to link to the payment request document
     * @param copyPoItems      - An option to copy the items from the purchase order if set to true
     * @param itemQuantityList - A list of quantity values for each item in your payment request document
     * @return
     * @throws Exception
     * @ConfigureContext(shouldCommitTransactions=false)
     */
    public final PaymentRequestDocument createPaymentRequestDocument(PaymentRequestDocumentFixture preqFixture, PurchaseOrderDocument po, boolean copyPoItems, KualiDecimal[] itemQuantityList) throws Exception {

        // check if test has been setup
        if (documentService == null) {
            documentService = SpringContext.getBean(DocumentService.class);
        }

        // setup purchase order and save
        changeCurrentUser(appleton);
        PaymentRequestDocument preq = preqFixture.createPaymentRequestDocument();
        preq.initiateDocument();

        // set payment request from po, and optionally copy the items
        if (copyPoItems) {
            preq.populatePaymentRequestFromPurchaseOrder(po, new HashMap());
        } else {
            populatePaymentRequestFromPurchaseOrderWithoutItems(preq, po);
        }

        // set quantity and calculate price
        updateQuantityAndPrice(preq, itemQuantityList);

        AccountingDocumentTestUtils.testSaveDocument(preq, documentService);

        // retrieve saved payment request
        final String preqDocId = preq.getDocumentNumber();
        preq = (PaymentRequestDocument) documentService.getByDocumentHeaderId(preqDocId);
        preq.setAccountDistributionMethod("S");
        return preq;
    }

    /**
     * Populates fields on a payment request with data from a purchase order, but
     * does not copy the purchase order items.
     *
     * @param preq
     * @param po
     */
    private void populatePaymentRequestFromPurchaseOrderWithoutItems(PaymentRequestDocument preq, PurchaseOrderDocument po) {

        preq.setPurchaseOrderIdentifier(po.getPurapDocumentIdentifier());
        preq.getDocumentHeader().setOrganizationDocumentNumber(po.getDocumentHeader().getOrganizationDocumentNumber());
        preq.setPostingYear(po.getPostingYear());
        preq.setVendorCustomerNumber(po.getVendorCustomerNumber());

        if (po.getPurchaseOrderCostSource() != null) {
            preq.setPaymentRequestCostSource(po.getPurchaseOrderCostSource());
            preq.setPaymentRequestCostSourceCode(po.getPurchaseOrderCostSourceCode());
        }

        if (po.getVendorShippingPaymentTerms() != null) {
            preq.setVendorShippingPaymentTerms(po.getVendorShippingPaymentTerms());
            preq.setVendorShippingPaymentTermsCode(po.getVendorShippingPaymentTermsCode());
        }

        if (po.getRecurringPaymentType() != null) {
            preq.setRecurringPaymentType(po.getRecurringPaymentType());
            preq.setRecurringPaymentTypeCode(po.getRecurringPaymentTypeCode());
        }

        preq.setVendorHeaderGeneratedIdentifier(po.getVendorHeaderGeneratedIdentifier());
        preq.setVendorDetailAssignedIdentifier(po.getVendorDetailAssignedIdentifier());
        preq.setVendorCustomerNumber(po.getVendorCustomerNumber());
        preq.setVendorName(po.getVendorName());

        // populate preq vendor address with the default remit address type for the vendor if found
        String userCampus = GlobalVariables.getUserSession().getPerson().getCampusCode();
        VendorAddress vendorAddress = SpringContext.getBean(VendorService.class).getVendorDefaultAddress(po.getVendorHeaderGeneratedIdentifier(), po.getVendorDetailAssignedIdentifier(), VendorConstants.AddressTypes.REMIT, userCampus);
        if (vendorAddress != null) {
            preq.templateVendorAddress(vendorAddress);
            preq.setVendorAddressGeneratedIdentifier(vendorAddress.getVendorAddressGeneratedIdentifier());
        } else {
            // set address from PO
            preq.setVendorAddressGeneratedIdentifier(po.getVendorAddressGeneratedIdentifier());
            preq.setVendorLine1Address(po.getVendorLine1Address());
            preq.setVendorLine2Address(po.getVendorLine2Address());
            preq.setVendorCityName(po.getVendorCityName());
            preq.setVendorStateCode(po.getVendorStateCode());
            preq.setVendorPostalCode(po.getVendorPostalCode());
            preq.setVendorCountryCode(po.getVendorCountryCode());
        }

        if ((po.getVendorPaymentTerms() == null) || ("".equals(po.getVendorPaymentTerms().getVendorPaymentTermsCode()))) {
            preq.setVendorPaymentTerms(new PaymentTermType());
            preq.setVendorPaymentTermsCode("");
        } else {
            preq.setVendorPaymentTermsCode(po.getVendorPaymentTermsCode());
            preq.setVendorPaymentTerms(po.getVendorPaymentTerms());
        }

        preq.setPaymentRequestPayDate(SpringContext.getBean(PaymentRequestService.class).calculatePayDate(preq.getInvoiceDate(), preq.getVendorPaymentTerms()));

        // add missing below the line
        SpringContext.getBean(PurapService.class).addBelowLineItems(preq);
        preq.setAccountsPayablePurchasingDocumentLinkIdentifier(po.getAccountsPayablePurchasingDocumentLinkIdentifier());

        preq.refreshNonUpdateableReferences();
    }

    /**
     * Updates the quanities for each payment request item in order.
     * Also updates the extended price for above and below the line items,
     * and finally updates the amounts for each accounting line.
     *
     * @param preq
     * @param quantityList
     */
    public void updateQuantityAndPrice(PaymentRequestDocument preq, KualiDecimal[] quantityList) {

        int quantityIndex = 0;
        KualiDecimal quantity = null;

        for (PaymentRequestItem pri : (List<PaymentRequestItem>) preq.getItems()) {
            if (pri.getItemType().isLineItemIndicator()) {
                if (pri.getItemType().isQuantityBasedGeneralLedgerIndicator()) {

                    // if within range of passed in quantities
                    if (quantityIndex <= (quantityList.length - 1)) {
                        quantity = quantityList[quantityIndex];
                    } else {
                        quantity = new KualiDecimal(0);

                    }
                    pri.setItemQuantity(quantity);
                    pri.setExtendedPrice(pri.calculateExtendedPrice());
                    BigDecimal calcExtendedPrice = pri.getItemUnitPrice().multiply(pri.getItemQuantity().bigDecimalValue());
                } else {
                    pri.setExtendedPrice(pri.calculateExtendedPrice());
                }
            } else {
                pri.setExtendedPrice(pri.calculateExtendedPrice());
                // LOG.info("updateQuantityandPrice() no gl"+ pri.getItemQuantity() + " : "+ pri.getItemDescription() +
                // " : up "+ pri.getItemUnitPrice() + " calc " + pri.calculateExtendedPrice());
            }

            for (PurApAccountingLine accountingLine : pri.getSourceAccountingLines()) {
                accountingLine.setAmount(pri.getExtendedPrice().multiply(new KualiDecimal(accountingLine.getAccountLinePercent())).divide(new KualiDecimal(100)));
            }
        }

    }

}
