View Javadoc
1   /*
2    * The Kuali Financial System, a comprehensive financial management system for higher education.
3    *
4    * Copyright 2005-2014 The Kuali Foundation
5    *
6    * This program is free software: you can redistribute it and/or modify
7    * it under the terms of the GNU Affero General Public License as
8    * published by the Free Software Foundation, either version 3 of the
9    * License, or (at your option) any later version.
10   *
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   * GNU Affero General Public License for more details.
15   *
16   * You should have received a copy of the GNU Affero General Public License
17   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18   */
19  package org.kuali.kfs.module.ar.document.service.impl;
20  
21  import java.io.ByteArrayInputStream;
22  import java.io.File;
23  import java.io.IOException;
24  import java.math.BigDecimal;
25  import java.math.RoundingMode;
26  import java.text.MessageFormat;
27  import java.util.ArrayList;
28  import java.util.Calendar;
29  import java.util.Collection;
30  import java.util.Date;
31  import java.util.HashMap;
32  import java.util.Iterator;
33  import java.util.List;
34  import java.util.Map;
35  import java.util.concurrent.Callable;
36  
37  import org.apache.commons.collections.CollectionUtils;
38  import org.apache.commons.lang.StringUtils;
39  import org.kuali.kfs.coa.businessobject.Account;
40  import org.kuali.kfs.coa.businessobject.ObjectCodeCurrent;
41  import org.kuali.kfs.coa.service.AccountService;
42  import org.kuali.kfs.coa.service.ObjectCodeService;
43  import org.kuali.kfs.gl.businessobject.Balance;
44  import org.kuali.kfs.integration.cg.ContractsAndGrantsBillingAward;
45  import org.kuali.kfs.integration.cg.ContractsAndGrantsBillingAwardAccount;
46  import org.kuali.kfs.integration.cg.ContractsAndGrantsModuleBillingService;
47  import org.kuali.kfs.module.ar.ArAuthorizationConstants;
48  import org.kuali.kfs.module.ar.ArConstants;
49  import org.kuali.kfs.module.ar.ArKeyConstants;
50  import org.kuali.kfs.module.ar.ArPropertyConstants;
51  import org.kuali.kfs.module.ar.batch.ContractsGrantsInvoiceDocumentBatchStep;
52  import org.kuali.kfs.module.ar.businessobject.AwardAccountObjectCodeTotalBilled;
53  import org.kuali.kfs.module.ar.businessobject.Bill;
54  import org.kuali.kfs.module.ar.businessobject.ContractsGrantsInvoiceDetail;
55  import org.kuali.kfs.module.ar.businessobject.CostCategory;
56  import org.kuali.kfs.module.ar.businessobject.CostCategoryObjectCode;
57  import org.kuali.kfs.module.ar.businessobject.CostCategoryObjectConsolidation;
58  import org.kuali.kfs.module.ar.businessobject.CostCategoryObjectLevel;
59  import org.kuali.kfs.module.ar.businessobject.CustomerInvoiceDetail;
60  import org.kuali.kfs.module.ar.businessobject.InvoiceAccountDetail;
61  import org.kuali.kfs.module.ar.businessobject.InvoiceAddressDetail;
62  import org.kuali.kfs.module.ar.businessobject.InvoiceBill;
63  import org.kuali.kfs.module.ar.businessobject.InvoiceDetailAccountObjectCode;
64  import org.kuali.kfs.module.ar.businessobject.InvoiceMilestone;
65  import org.kuali.kfs.module.ar.businessobject.InvoiceSuspensionCategory;
66  import org.kuali.kfs.module.ar.businessobject.InvoiceTemplate;
67  import org.kuali.kfs.module.ar.businessobject.Milestone;
68  import org.kuali.kfs.module.ar.businessobject.OrganizationAccountingDefault;
69  import org.kuali.kfs.module.ar.businessobject.OrganizationOptions;
70  import org.kuali.kfs.module.ar.businessobject.SystemInformation;
71  import org.kuali.kfs.module.ar.document.ContractsGrantsInvoiceDocument;
72  import org.kuali.kfs.module.ar.document.dataaccess.ContractsGrantsInvoiceDocumentDao;
73  import org.kuali.kfs.module.ar.document.service.AccountsReceivablePendingEntryService;
74  import org.kuali.kfs.module.ar.document.service.ContractsGrantsInvoiceDocumentService;
75  import org.kuali.kfs.module.ar.document.service.CustomerInvoiceDocumentService;
76  import org.kuali.kfs.module.ar.document.validation.SuspensionCategory;
77  import org.kuali.kfs.module.ar.identity.ArKimAttributes;
78  import org.kuali.kfs.module.ar.report.PdfFormattingMap;
79  import org.kuali.kfs.module.ar.service.ContractsGrantsBillingUtilityService;
80  import org.kuali.kfs.module.ar.service.CostCategoryService;
81  import org.kuali.kfs.sys.FinancialSystemModuleConfiguration;
82  import org.kuali.kfs.sys.KFSConstants;
83  import org.kuali.kfs.sys.KFSPropertyConstants;
84  import org.kuali.kfs.sys.PdfFormFillerUtil;
85  import org.kuali.kfs.sys.batch.Job;
86  import org.kuali.kfs.sys.businessobject.FinancialSystemDocumentHeader;
87  import org.kuali.kfs.sys.businessobject.SystemOptions;
88  import org.kuali.kfs.sys.document.service.FinancialSystemDocumentService;
89  import org.kuali.kfs.sys.document.validation.event.AttributedRouteDocumentEvent;
90  import org.kuali.kfs.sys.service.OptionsService;
91  import org.kuali.kfs.sys.service.UniversityDateService;
92  import org.kuali.kfs.sys.util.FallbackMap;
93  import org.kuali.kfs.sys.util.ReflectionMap;
94  import org.kuali.rice.core.api.config.property.ConfigurationService;
95  import org.kuali.rice.core.api.datetime.DateTimeService;
96  import org.kuali.rice.core.api.search.SearchOperator;
97  import org.kuali.rice.core.api.util.type.KualiDecimal;
98  import org.kuali.rice.coreservice.framework.parameter.ParameterService;
99  import org.kuali.rice.kew.api.document.DocumentStatus;
100 import org.kuali.rice.kew.api.exception.WorkflowException;
101 import org.kuali.rice.kim.api.identity.IdentityService;
102 import org.kuali.rice.kim.api.identity.Person;
103 import org.kuali.rice.kim.api.identity.PersonService;
104 import org.kuali.rice.kim.api.identity.principal.Principal;
105 import org.kuali.rice.kim.api.permission.PermissionService;
106 import org.kuali.rice.krad.UserSession;
107 import org.kuali.rice.krad.bo.Attachment;
108 import org.kuali.rice.krad.bo.DocumentHeader;
109 import org.kuali.rice.krad.bo.ModuleConfiguration;
110 import org.kuali.rice.krad.bo.Note;
111 import org.kuali.rice.krad.service.AttachmentService;
112 import org.kuali.rice.krad.service.BusinessObjectService;
113 import org.kuali.rice.krad.service.DocumentService;
114 import org.kuali.rice.krad.service.KualiModuleService;
115 import org.kuali.rice.krad.service.KualiRuleService;
116 import org.kuali.rice.krad.service.NoteService;
117 import org.kuali.rice.krad.util.GlobalVariables;
118 import org.kuali.rice.krad.util.ObjectUtils;
119 import org.springframework.transaction.annotation.Transactional;
120 
121 import com.lowagie.text.DocumentException;
122 
123 /**
124  * This class implements the services required for Contracts & Grants Invoice Document.
125  */
126 @Transactional
127 public class ContractsGrantsInvoiceDocumentServiceImpl implements ContractsGrantsInvoiceDocumentService {
128     protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ContractsGrantsInvoiceDocumentServiceImpl.class);
129 
130     protected AccountsReceivablePendingEntryService accountsReceivablePendingEntryService;
131     protected AccountService accountService;
132     protected AttachmentService attachmentService;
133     protected BusinessObjectService businessObjectService;
134     protected ConfigurationService configurationService;
135     protected ContractsGrantsBillingUtilityService contractsGrantsBillingUtilityService;
136     protected ContractsGrantsInvoiceDocumentDao contractsGrantsInvoiceDocumentDao;
137     protected ContractsAndGrantsModuleBillingService contractsAndGrantsModuleBillingService;
138     protected CostCategoryService costCategoryService;
139     protected CustomerInvoiceDocumentService customerInvoiceDocumentService;
140     protected DateTimeService dateTimeService;
141     protected DocumentService documentService;
142     protected FinancialSystemDocumentService financialSystemDocumentService;
143     protected IdentityService identityService;
144     protected KualiModuleService kualiModuleService;
145     protected KualiRuleService kualiRuleService;
146     protected NoteService noteService;
147     protected ObjectCodeService objectCodeService;
148     protected ParameterService parameterService;
149     protected PermissionService permissionService;
150     protected PersonService personService;
151     protected UniversityDateService universityDateService;
152     protected OptionsService optionsService;
153 
154     private List<SuspensionCategory> suspensionCategories;
155 
156     public static final String REPORT_LINE_DIVIDER = "--------------------------------------------------------------------------------------------------------------";
157 
158     /**
159      * @see org.kuali.kfs.module.ar.document.service.ContractsGrantsInvoiceDocumentService#createSourceAccountingLines(org.kuali.kfs.module.ar.document.ContractsGrantsInvoiceDocument)
160      */
161     @Override
162     public void createSourceAccountingLines(ContractsGrantsInvoiceDocument contractsGrantsInvoiceDocument, List<ContractsAndGrantsBillingAwardAccount> awardAccounts) {
163         // To check if the Source accounting lines are existing. If they are do nothing
164         if (CollectionUtils.isEmpty(contractsGrantsInvoiceDocument.getSourceAccountingLines())) {
165             ContractsAndGrantsBillingAward award = contractsGrantsInvoiceDocument.getInvoiceGeneralDetail().getAward();
166 
167             // To retrieve the financial object code from the Organization Accounting Default.
168             final OrganizationAccountingDefault organizationAccountingDefault = retrieveBillingOrganizationAccountingDefault(contractsGrantsInvoiceDocument.getBillByChartOfAccountCode(), contractsGrantsInvoiceDocument.getBilledByOrganizationCode());
169             if (ObjectUtils.isNotNull(award) && ObjectUtils.isNotNull(organizationAccountingDefault)) {
170                 if (StringUtils.equalsIgnoreCase(award.getInvoicingOptionCode(), ArConstants.INV_ACCOUNT)) {
171                     // If its bill by Account , irrespective of it is by contract control account, there would be a single source accounting line with award account specified by the user.
172                     CustomerInvoiceDetail cide = createSourceAccountingLine(contractsGrantsInvoiceDocument.getDocumentNumber(), awardAccounts.get(0).getChartOfAccountsCode(), awardAccounts.get(0).getAccountNumber(), organizationAccountingDefault.getDefaultInvoiceFinancialObjectCode(), getAccountingLineAmountForDocument(contractsGrantsInvoiceDocument), new Integer(1));
173                     contractsGrantsInvoiceDocument.getSourceAccountingLines().add(cide);
174                 }
175                 else if (StringUtils.equalsIgnoreCase(award.getInvoicingOptionCode(), ArConstants.INV_CONTRACT_CONTROL_ACCOUNT)) {
176                     // by control account
177                     // If its bill by Contract Control Account there would be a single source accounting line.
178                     CustomerInvoiceDetail cide = createSourceAccountingLinesByContractControlAccount(contractsGrantsInvoiceDocument, organizationAccountingDefault);
179                     contractsGrantsInvoiceDocument.getSourceAccountingLines().add(cide);
180                 }
181                 else {
182                     // by award
183                     List<CustomerInvoiceDetail> awardAccountingLines = createSourceAccountingLinesByAward(contractsGrantsInvoiceDocument, organizationAccountingDefault);
184                     contractsGrantsInvoiceDocument.getSourceAccountingLines().addAll(awardAccountingLines);
185                 }
186             }
187         }
188     }
189 
190     /**
191      * Determines the total amount for the given document, based on billing frequency of the award
192      * @param invoice the contracts & grants invoice to find a total amount for
193      * @return the total amount of the document
194      */
195     protected KualiDecimal getAccountingLineAmountForDocument(ContractsGrantsInvoiceDocument invoice) {
196         if (StringUtils.equals(invoice.getInvoiceGeneralDetail().getBillingFrequencyCode(), ArConstants.PREDETERMINED_BILLING_SCHEDULE_CODE)) {
197             return getBillAmountTotal(invoice);
198         } else if (StringUtils.equals(invoice.getInvoiceGeneralDetail().getBillingFrequencyCode(), ArConstants.MILESTONE_BILLING_SCHEDULE_CODE)) {
199             return getInvoiceMilestoneTotal(invoice);
200         } else {
201             return invoice.getTotalCostInvoiceDetail().getInvoiceAmount();
202         }
203     }
204 
205     /**
206      * Generates the source accounting lines for a Contracts & Grants Invoice from the award accounts associated with an award (in the form of the pre-generated invoice account details)
207      * @param contractsGrantsInvoiceDocument the Contracts & Grants Invoice to create invoice details for
208      * @param organizationAccountingDefault the
209      * @return a List of generated accounting lines
210      */
211     protected List<CustomerInvoiceDetail> createSourceAccountingLinesByAward(ContractsGrantsInvoiceDocument contractsGrantsInvoiceDocument, final OrganizationAccountingDefault organizationAccountingDefault) {
212         List<CustomerInvoiceDetail> awardAccountingLines = new ArrayList<>();
213         if (!CollectionUtils.isEmpty(contractsGrantsInvoiceDocument.getAccountDetails())) {
214             final Map<String, KualiDecimal> accountExpenditureAmounts = getCategoryExpenditureAmountsForInvoiceAccountDetail(contractsGrantsInvoiceDocument);
215             for (InvoiceAccountDetail invAcctD : contractsGrantsInvoiceDocument.getAccountDetails()) {
216                 String accountNumber = invAcctD.getAccountNumber();
217                 String coaCode = invAcctD.getChartOfAccountsCode();
218                 String objectCode = organizationAccountingDefault.getDefaultInvoiceFinancialObjectCode();
219                 Integer sequenceNumber = contractsGrantsInvoiceDocument.getAccountDetails().indexOf(invAcctD) + 1;// To set a sequence number for the Accounting Lines
220                 // To calculate totalAmount based on the billing Frequency. Assuming that there would be only one
221                 // account if its Milestone/Predetermined Schedule.
222                 KualiDecimal totalAmount = KualiDecimal.ZERO;
223                 if (StringUtils.equalsIgnoreCase(contractsGrantsInvoiceDocument.getInvoiceGeneralDetail().getBillingFrequencyCode(), ArConstants.MILESTONE_BILLING_SCHEDULE_CODE)) {
224                     final KualiDecimal totalMilestoneAmount = getInvoiceMilestoneTotal(contractsGrantsInvoiceDocument);
225                     if (totalMilestoneAmount != KualiDecimal.ZERO) {
226                         totalAmount = totalMilestoneAmount;
227                     }
228                 }
229                 else if (StringUtils.equalsIgnoreCase(contractsGrantsInvoiceDocument.getInvoiceGeneralDetail().getBillingFrequencyCode(), ArConstants.PREDETERMINED_BILLING_SCHEDULE_CODE)) {
230                     final KualiDecimal totalBillAmount = getBillAmountTotal(contractsGrantsInvoiceDocument);
231                     if (totalBillAmount != KualiDecimal.ZERO) {
232                         totalAmount = totalBillAmount;
233                     }
234                 }
235                 else {
236                     final String accountKey = StringUtils.join(new String[] { invAcctD.getChartOfAccountsCode(), invAcctD.getAccountNumber() }, "-");
237                     totalAmount = accountExpenditureAmounts.containsKey(accountKey)
238                             ? accountExpenditureAmounts.get(accountKey)
239                             : KualiDecimal.ZERO;
240                 }
241 
242                 CustomerInvoiceDetail cide = createSourceAccountingLine(contractsGrantsInvoiceDocument.getDocumentNumber(), coaCode, accountNumber, objectCode, totalAmount, sequenceNumber);
243                 awardAccountingLines.add(cide);
244             }
245         }
246         return awardAccountingLines;
247     }
248 
249     /**
250      * Totals the current expenditure amounts of any invoice detail account object codes on the document which have set categories by account numbers
251      * @param contractsGrantsInvoiceDocument the document holding invoice detail account object codes
252      * @return a Map where the key is the concatenation of chartOfAccountsCode-accountNumber and the value is the expenditure amount on that account
253      */
254     protected Map<String, KualiDecimal> getCategoryExpenditureAmountsForInvoiceAccountDetail(ContractsGrantsInvoiceDocument contractsGrantsInvoiceDocument) {
255         Map<String, KualiDecimal> expenditureAmounts = new HashMap<>();
256         for (InvoiceDetailAccountObjectCode invoiceDetailAccountObjectCode : contractsGrantsInvoiceDocument.getInvoiceDetailAccountObjectCodes()) {
257             final String accountKey = StringUtils.join(new String[] { invoiceDetailAccountObjectCode.getChartOfAccountsCode(), invoiceDetailAccountObjectCode.getAccountNumber() }, "-");
258             if (!StringUtils.isBlank(invoiceDetailAccountObjectCode.getCategoryCode())) {
259                 KualiDecimal total = expenditureAmounts.get(accountKey);
260                 if (ObjectUtils.isNull(total)) {
261                     total = KualiDecimal.ZERO;
262                 }
263                 expenditureAmounts.put(accountKey, total.add(invoiceDetailAccountObjectCode.getCurrentExpenditures()));
264             }
265         }
266         return expenditureAmounts;
267     }
268 
269     /**
270      * Creates source accounting lines using the contract control account to populate
271      * @param contractsGrantsInvoiceDocument the Contracts & Grants Invoice to add lines to
272      * @param organizationAccountingDefault the organization accounting default associated with the invoice
273      */
274     protected CustomerInvoiceDetail createSourceAccountingLinesByContractControlAccount(ContractsGrantsInvoiceDocument contractsGrantsInvoiceDocument, final OrganizationAccountingDefault organizationAccountingDefault) {
275         // To get the account number and coa code for contract control account.
276         String accountNumber = null;
277         // Use the first account to get the contract control account number, as every account would have the same contract control account number.
278         List<InvoiceAccountDetail> accountDetails = contractsGrantsInvoiceDocument.getAccountDetails();
279         if (CollectionUtils.isNotEmpty(accountDetails) && StringUtils.isNotEmpty(accountDetails.get(0).getContractControlAccountNumber())) {
280             accountNumber = accountDetails.get(0).getContractControlAccountNumber();
281         }
282 
283         String coaCode = contractsGrantsInvoiceDocument.getBillByChartOfAccountCode();
284         String objectCode = organizationAccountingDefault.getDefaultInvoiceFinancialObjectCode();
285 
286         CustomerInvoiceDetail cide = createSourceAccountingLine(contractsGrantsInvoiceDocument.getDocumentNumber(), coaCode, accountNumber, objectCode, getAccountingLineAmountForDocument(contractsGrantsInvoiceDocument), new Integer(1));
287         return cide;
288     }
289 
290     /**
291      * Calculates the total of bill amounts if the given CINV doc is a pre-determined billing kind of CINV doc
292      * @param contractsGrantsInvoiceDocument the document to total bills on
293      * @return the total from the bills, or 0 if no bills exist or the CINV is not a pre-determined billing kind of CINV
294      */
295     protected KualiDecimal getBillAmountTotal(ContractsGrantsInvoiceDocument contractsGrantsInvoiceDocument) {
296         KualiDecimal totalBillAmount = KualiDecimal.ZERO;
297         // To calculate the total bill amount.
298         if (contractsGrantsInvoiceDocument.getInvoiceGeneralDetail().getBillingFrequencyCode().equalsIgnoreCase(ArConstants.PREDETERMINED_BILLING_SCHEDULE_CODE) && !CollectionUtils.isEmpty(contractsGrantsInvoiceDocument.getInvoiceBills())) {
299             for (InvoiceBill bill : contractsGrantsInvoiceDocument.getInvoiceBills()) {
300                 if (bill.getEstimatedAmount() != null) {
301                     totalBillAmount = totalBillAmount.add(bill.getEstimatedAmount());
302                 }
303             }
304         }
305         return totalBillAmount;
306     }
307 
308     /**
309      * Calculates the total of milestones on the given CINV, assuming the CINV is a milestone kind of CINV
310      * @param contractsGrantsInvoiceDocument the document to find total milestones on
311      * @return the total of the milestones, or 0 if no milestones exist or if the CINV is not a milestone schedule CINV
312      */
313     protected KualiDecimal getInvoiceMilestoneTotal(ContractsGrantsInvoiceDocument contractsGrantsInvoiceDocument) {
314         KualiDecimal totalMilestoneAmount = KualiDecimal.ZERO;
315         // To calculate the total milestone amount.
316         if (contractsGrantsInvoiceDocument.getInvoiceGeneralDetail().getBillingFrequencyCode().equalsIgnoreCase(ArConstants.MILESTONE_BILLING_SCHEDULE_CODE) && !CollectionUtils.isEmpty(contractsGrantsInvoiceDocument.getInvoiceMilestones())) {
317             for (InvoiceMilestone milestone : contractsGrantsInvoiceDocument.getInvoiceMilestones()) {
318                 if (milestone.getMilestoneAmount() != null) {
319                     totalMilestoneAmount = totalMilestoneAmount.add(milestone.getMilestoneAmount());
320                 }
321             }
322         }
323         return totalMilestoneAmount;
324     }
325 
326     /**
327      * Retrieves the OrganizationAccountingDefault record for the given billing organization
328      * @param billByChartOfAccountsCode the chart of the billing organization
329      * @param billByOrganizationCode the organization code of the billing organization
330      * @return the OrganizationAccountingDefault for the given chart and organization code
331      */
332     protected OrganizationAccountingDefault retrieveBillingOrganizationAccountingDefault(final String billByChartOfAccountsCode, final String billByOrganizationCode) {
333         Map<String, Object> criteria = new HashMap<String, Object>();
334         Integer currentYear = universityDateService.getCurrentFiscalYear();
335         criteria.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, currentYear);
336         criteria.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, billByChartOfAccountsCode);
337         criteria.put(KFSPropertyConstants.ORGANIZATION_CODE, billByOrganizationCode);
338         // Need to avoid hitting database in the loop. option would be to set the financial object code when the form loads and save it somewhere.
339         OrganizationAccountingDefault organizationAccountingDefault = businessObjectService.findByPrimaryKey(OrganizationAccountingDefault.class, criteria);
340         return organizationAccountingDefault;
341     }
342 
343     /**
344      * @param docNum
345      * @param coaCode
346      * @param acctNum
347      * @param obCode
348      * @param totalAmount
349      * @param seqNum
350      * @return
351      */
352     protected CustomerInvoiceDetail createSourceAccountingLine(String docNum, String coaCode, String acctNum, String obCode, KualiDecimal totalAmount, Integer seqNum) {
353         CustomerInvoiceDetail cid = new CustomerInvoiceDetail();
354         cid.setDocumentNumber(docNum);
355 
356         cid.setAccountNumber(acctNum);
357         cid.setChartOfAccountsCode(coaCode);
358         cid.setFinancialObjectCode(obCode);
359 
360         cid.setSequenceNumber(seqNum);
361         cid.setInvoiceItemQuantity(BigDecimal.ONE);
362         cid.setInvoiceItemUnitOfMeasureCode(ArConstants.CUSTOMER_INVOICE_DETAIL_UOM_DEFAULT);
363 
364         cid.setInvoiceItemUnitPrice(totalAmount);
365         cid.setAmount(totalAmount);
366         if (totalAmount.isNegative()) {
367             cid.setInvoiceItemDiscountLineNumber(seqNum);
368         }
369         // To get AR Object codes for the GLPEs .... as it is not being called implicitly..
370 
371         cid.setAccountsReceivableObjectCode(getAccountsReceivablePendingEntryService().getAccountsReceivableObjectCode(cid));
372         return cid;
373     }
374 
375     /**
376      * @see org.kuali.kfs.module.ar.document.service.ContractsGrantsInvoiceDocumentService#recalculateNewTotalBilled(org.kuali.kfs.module.ar.document.ContractsGrantsInvoiceDocument)
377      */
378     @Override
379     public void recalculateTotalAmountBilledToDate(ContractsGrantsInvoiceDocument contractsGrantsInvoiceDocument) {
380         ContractsGrantsInvoiceDetail totalCostInvoiceDetail = contractsGrantsInvoiceDocument.getTotalCostInvoiceDetail();
381 
382         // To verify the expenditure amounts have been changed and
383         // update the invoiceDetailObjectCode
384         boolean expenditureValueChanged = adjustObjectCodeAmountsIfChanged(contractsGrantsInvoiceDocument);
385 
386         if (expenditureValueChanged) {
387             // update Total Direct Cost in the Invoice Detail Tab
388             KualiDecimal totalDirectCostExpenditures = getInvoiceDetailExpenditureSum(contractsGrantsInvoiceDocument.getDirectCostInvoiceDetails());
389 
390             // Set expenditures to Direct Cost invoice Details
391             ContractsGrantsInvoiceDetail totalDirectCostInvoiceDetail = contractsGrantsInvoiceDocument.getTotalDirectCostInvoiceDetail();
392             if (ObjectUtils.isNotNull(totalDirectCostInvoiceDetail)){
393                 totalDirectCostInvoiceDetail.setInvoiceAmount(totalDirectCostExpenditures);
394             }
395 
396             // update Total Indirect Cost in the Invoice Detail Tab
397             KualiDecimal totalInDirectCostExpenditures = getInvoiceDetailExpenditureSum(contractsGrantsInvoiceDocument.getIndirectCostInvoiceDetails());
398 
399             // Set expenditures to Indirect Cost invoice Details
400             ContractsGrantsInvoiceDetail totalInDirectCostInvoiceDetail = contractsGrantsInvoiceDocument.getTotalIndirectCostInvoiceDetail();
401             if (ObjectUtils.isNotNull(totalInDirectCostInvoiceDetail)){
402                 totalInDirectCostInvoiceDetail.setInvoiceAmount(totalInDirectCostExpenditures);
403             }
404 
405             // Set the total for Total Cost Invoice Details section.
406             if(ObjectUtils.isNotNull(totalCostInvoiceDetail)) {
407                 totalCostInvoiceDetail.setInvoiceAmount(totalDirectCostInvoiceDetail.getInvoiceAmount().add(totalInDirectCostExpenditures));
408             }
409             recalculateAccountDetails(contractsGrantsInvoiceDocument.getAccountDetails(), contractsGrantsInvoiceDocument.getInvoiceDetailAccountObjectCodes());
410 
411             // update source accounting lines
412             updateInvoiceSourceAccountingLines(contractsGrantsInvoiceDocument.getAccountDetails(), contractsGrantsInvoiceDocument.getSourceAccountingLines());
413 
414 
415         }
416 
417         contractsGrantsInvoiceDocument.getInvoiceGeneralDetail().setTotalPreviouslyBilled(getAwardBilledToDateAmountByProposalNumber(contractsGrantsInvoiceDocument.getInvoiceGeneralDetail().getProposalNumber()));
418 
419         KualiDecimal newTotalBilled = totalCostInvoiceDetail.getInvoiceAmount().add(contractsGrantsInvoiceDocument.getInvoiceGeneralDetail().getTotalPreviouslyBilled());
420         newTotalBilled = newTotalBilled.add(getOtherTotalBilledForAwardPeriod(contractsGrantsInvoiceDocument));
421 
422         // set the General Detail Total to be billed - there would be only one value for Total Cost invoice Details.
423         contractsGrantsInvoiceDocument.getInvoiceGeneralDetail().setTotalAmountBilledToDate(newTotalBilled);
424     }
425 
426     /**
427      * @see org.kuali.kfs.module.ar.document.service.ContractsGrantsInvoiceDocumentService#getOtherNewTotalBilledForAwardPeriod(org.kuali.kfs.module.ar.document.ContractsGrantsInvoiceDocument)
428      */
429     @Override
430     public KualiDecimal getOtherTotalBilledForAwardPeriod(ContractsGrantsInvoiceDocument contractsGrantsInvoiceDocument) {
431         KualiDecimal newTotalBilled = KualiDecimal.ZERO;
432 
433         Map<String, String> fieldValuesForInvoice = new HashMap<>();
434         fieldValuesForInvoice.put(ArPropertyConstants.ContractsGrantsInvoiceDocumentFields.PROPOSAL_NUMBER, contractsGrantsInvoiceDocument.getInvoiceGeneralDetail().getProposalNumber().toString());
435         fieldValuesForInvoice.put(ArPropertyConstants.INVOICE_GENERAL_DETAIL+"."+ArPropertyConstants.BILLING_PERIOD, contractsGrantsInvoiceDocument.getInvoiceGeneralDetail().getBillingPeriod());
436         String docNumberCriteriaString = SearchOperator.NOT + contractsGrantsInvoiceDocument.getDocumentNumber();
437         if (ObjectUtils.isNotNull(contractsGrantsInvoiceDocument.getFinancialSystemDocumentHeader()) && StringUtils.isNotBlank(contractsGrantsInvoiceDocument.getFinancialSystemDocumentHeader().getFinancialDocumentInErrorNumber())) {
438             docNumberCriteriaString += SearchOperator.NOT + contractsGrantsInvoiceDocument.getFinancialSystemDocumentHeader().getFinancialDocumentInErrorNumber();
439         }
440         fieldValuesForInvoice.put(KFSPropertyConstants.DOCUMENT_NUMBER, docNumberCriteriaString);
441         fieldValuesForInvoice.put(ArPropertyConstants.DOCUMENT_STATUS_CODE, SearchOperator.NOT + KFSConstants.DocumentStatusCodes.PROCESSED + SearchOperator.NOT + KFSConstants.DocumentStatusCodes.APPROVED);
442 
443         Collection<ContractsGrantsInvoiceDocument> cgInvoiceDocuments = retrieveAllCGInvoicesByCriteria(fieldValuesForInvoice);
444         for (ContractsGrantsInvoiceDocument cgInvoiceDocument: cgInvoiceDocuments) {
445             for (InvoiceAccountDetail invAcctD : cgInvoiceDocument.getAccountDetails()) {
446                 newTotalBilled = newTotalBilled.add(invAcctD.getInvoiceAmount());
447             }
448         }
449 
450         return newTotalBilled;
451     }
452 
453     /**
454      * @param invoiceDetails
455      * @return
456      */
457     public KualiDecimal getInvoiceDetailExpenditureSum(List<ContractsGrantsInvoiceDetail> invoiceDetails) {
458         KualiDecimal totalExpenditures = KualiDecimal.ZERO;
459         for (ContractsGrantsInvoiceDetail invoiceDetail : invoiceDetails) {
460             totalExpenditures = totalExpenditures.add(invoiceDetail.getInvoiceAmount());
461         }
462         return totalExpenditures;
463     }
464 
465     /**
466      * @param invoiceAccountDetails
467      * @param sourceAccountingLines
468      */
469     protected void updateInvoiceSourceAccountingLines(List<InvoiceAccountDetail> invoiceAccountDetails, List sourceAccountingLines) {
470 
471         if (sourceAccountingLines.size() > 1) {// Invoice By Award
472             for (CustomerInvoiceDetail cide : (List<CustomerInvoiceDetail>) sourceAccountingLines) {
473                 for (InvoiceAccountDetail invoiceAccountDetail : invoiceAccountDetails) {
474                     if (cide.getAccountNumber().equals(invoiceAccountDetail.getAccountNumber())) {
475                         cide.setInvoiceItemUnitPrice(invoiceAccountDetail.getInvoiceAmount());
476                         cide.setAmount(invoiceAccountDetail.getInvoiceAmount());
477                     }
478                 }
479             }
480         }
481         // This would be a case where the invoice is generated by Contract Control Account or Invoice By Account.
482         else if (sourceAccountingLines.size() == 1) {
483             KualiDecimal totalExpenditureAmount = KualiDecimal.ZERO;
484             if (invoiceAccountDetails.size() == 1) {// Invoice By Account
485                 // update source accounting lines
486                 CustomerInvoiceDetail cide = (CustomerInvoiceDetail) sourceAccountingLines.get(0);
487                 cide.setInvoiceItemUnitPrice(invoiceAccountDetails.get(0).getInvoiceAmount());
488                 cide.setAmount(invoiceAccountDetails.get(0).getInvoiceAmount());
489             }
490             else {// Invoice By Contract Control Account
491                 for (InvoiceAccountDetail invoiceAccountDetail : invoiceAccountDetails) {
492                     totalExpenditureAmount = totalExpenditureAmount.add(invoiceAccountDetail.getInvoiceAmount());
493                 }
494                 // update source accounting lines
495                 CustomerInvoiceDetail cide = (CustomerInvoiceDetail) sourceAccountingLines.get(0);
496                 cide.setInvoiceItemUnitPrice(totalExpenditureAmount);
497                 cide.setAmount(totalExpenditureAmount);
498             }
499         }
500 
501     }
502 
503     /**
504      * @see org.kuali.kfs.module.ar.document.service.ContractsGrantsInvoiceDocumentService#prorateBill(org.kuali.kfs.module.ar.document.ContractsGrantsInvoiceDocument)
505      */
506     @Override
507     public void prorateBill(ContractsGrantsInvoiceDocument contractsGrantsInvoiceDocument) throws WorkflowException {
508         KualiDecimal totalCost = new KualiDecimal(0); // Amount to be billed on this invoice
509         // must iterate through the invoice details because the user might have manually changed the value
510         for (ContractsGrantsInvoiceDetail invD : contractsGrantsInvoiceDocument.getInvoiceDetails()) {
511             totalCost = totalCost.add(invD.getInvoiceAmount());
512         }
513         KualiDecimal billedTotalCost = contractsGrantsInvoiceDocument.getInvoiceGeneralDetail().getTotalPreviouslyBilled(); // Total Billed so far
514         KualiDecimal accountAwardTotal = contractsGrantsInvoiceDocument.getInvoiceGeneralDetail().getAwardTotal(); // AwardTotal
515 
516         if (accountAwardTotal.subtract(billedTotalCost).isGreaterEqual(new KualiDecimal(0))) {
517             KualiDecimal amountEligibleForBilling = accountAwardTotal.subtract(billedTotalCost);
518             // only recalculate if the current invoice is over what's billable.
519 
520             if (totalCost.isGreaterThan(amountEligibleForBilling)) {
521                 // use BigDecimal because percentage should not have only a scale of 2, we need more for accuracy
522                 BigDecimal percentage = amountEligibleForBilling.bigDecimalValue().divide(totalCost.bigDecimalValue(), 10, BigDecimal.ROUND_HALF_DOWN);
523                 KualiDecimal amountToBill = new KualiDecimal(0); // use to check if rounding has left a few cents off
524 
525                 ContractsGrantsInvoiceDetail largestCostCategory = null;
526                 BigDecimal largestAmount = BigDecimal.ZERO;
527                 for (ContractsGrantsInvoiceDetail invD : contractsGrantsInvoiceDocument.getInvoiceDetails()) {
528                     BigDecimal newValue = invD.getInvoiceAmount().bigDecimalValue().multiply(percentage);
529                     KualiDecimal newKualiDecimalValue = new KualiDecimal(newValue.setScale(2, BigDecimal.ROUND_DOWN));
530                     invD.setInvoiceAmount(newKualiDecimalValue);
531                     amountToBill = amountToBill.add(newKualiDecimalValue);
532                     if (newValue.compareTo(largestAmount) > 0) {
533                         largestAmount = newKualiDecimalValue.bigDecimalValue();
534                         largestCostCategory = invD;
535                     }
536                 }
537                 if (!amountToBill.equals(amountEligibleForBilling)) {
538                     KualiDecimal remaining = amountEligibleForBilling.subtract(amountToBill);
539                     if (ObjectUtils.isNull(largestCostCategory) && CollectionUtils.isNotEmpty(contractsGrantsInvoiceDocument.getInvoiceDetails())) {
540                         largestCostCategory = contractsGrantsInvoiceDocument.getInvoiceDetails().get(0);
541                     }
542                     if (ObjectUtils.isNotNull(largestCostCategory)) {
543                         largestCostCategory.setInvoiceAmount(largestCostCategory.getInvoiceAmount().add(remaining));
544                     }
545                 }
546                 recalculateTotalAmountBilledToDate(contractsGrantsInvoiceDocument);
547             }
548         }
549     }
550 
551     /**
552      * @see org.kuali.kfs.module.ar.document.service.ContractsGrantsInvoiceDocumentService#addToAccountObjectCodeBilledTotal(java.util.List)
553      */
554     @Override
555     public void addToAccountObjectCodeBilledTotal(List<InvoiceDetailAccountObjectCode> invoiceDetailAccountObjectCodes) {
556         for (InvoiceDetailAccountObjectCode invoiceDetailAccountObjectCode : invoiceDetailAccountObjectCodes) {
557             Map<String, Object> totalBilledKeys = new HashMap<String, Object>();
558             totalBilledKeys.put(KFSPropertyConstants.PROPOSAL_NUMBER, invoiceDetailAccountObjectCode.getProposalNumber());
559             totalBilledKeys.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, invoiceDetailAccountObjectCode.getChartOfAccountsCode());
560             totalBilledKeys.put(KFSPropertyConstants.ACCOUNT_NUMBER, invoiceDetailAccountObjectCode.getAccountNumber());
561             totalBilledKeys.put(KFSPropertyConstants.FINANCIAL_OBJECT_CODE, invoiceDetailAccountObjectCode.getFinancialObjectCode());
562 
563             List<AwardAccountObjectCodeTotalBilled> awardAccountObjectCodeTotalBilledList = (List<AwardAccountObjectCodeTotalBilled>) businessObjectService.findMatching(AwardAccountObjectCodeTotalBilled.class, totalBilledKeys);
564             AwardAccountObjectCodeTotalBilled awardAccountObjectCodeTotalBilled = new AwardAccountObjectCodeTotalBilled();
565             if (awardAccountObjectCodeTotalBilledList != null && !awardAccountObjectCodeTotalBilledList.isEmpty()) {
566                 awardAccountObjectCodeTotalBilled = awardAccountObjectCodeTotalBilledList.get(0);
567                 awardAccountObjectCodeTotalBilled.setTotalBilled(awardAccountObjectCodeTotalBilled.getTotalBilled().add(invoiceDetailAccountObjectCode.getCurrentExpenditures()));
568             }
569             else {
570                 awardAccountObjectCodeTotalBilled.setProposalNumber(invoiceDetailAccountObjectCode.getProposalNumber());
571                 awardAccountObjectCodeTotalBilled.setChartOfAccountsCode(invoiceDetailAccountObjectCode.getChartOfAccountsCode());
572                 awardAccountObjectCodeTotalBilled.setAccountNumber(invoiceDetailAccountObjectCode.getAccountNumber());
573                 awardAccountObjectCodeTotalBilled.setFinancialObjectCode(invoiceDetailAccountObjectCode.getFinancialObjectCode());
574                 awardAccountObjectCodeTotalBilled.setTotalBilled(invoiceDetailAccountObjectCode.getCurrentExpenditures());
575             }
576             getBusinessObjectService().save(awardAccountObjectCodeTotalBilled);
577         }
578     }
579 
580     /**
581      * If any of the current expenditures for the cost categories on the Contracts & Grants Invoice Document have changed,
582      * recalculate the Object Code amounts.
583      *
584      * @param contractsGrantsInvoiceDocument document containing cost categories to review
585      * @return true if expenditure value changed, false otherwise
586      */
587     protected boolean adjustObjectCodeAmountsIfChanged(ContractsGrantsInvoiceDocument contractsGrantsInvoiceDocument) {
588         boolean isExpenditureValueChanged = false;
589 
590         // put the invoiceDetailAccountObjectCode into a map based on category
591         List<InvoiceDetailAccountObjectCode> invoiceDetailAccountObjectCodes = contractsGrantsInvoiceDocument.getInvoiceDetailAccountObjectCodes();
592         Map<String, List<InvoiceDetailAccountObjectCode>> invoiceDetailAccountObjectCodeMap = new HashMap<>();
593         for (InvoiceDetailAccountObjectCode invoiceDetailAccountObjectCode : invoiceDetailAccountObjectCodes) {
594             String categoryCode = invoiceDetailAccountObjectCode.getCategoryCode();
595             List<InvoiceDetailAccountObjectCode> invoiceDetailAccountObjectCodeList = invoiceDetailAccountObjectCodeMap.get(categoryCode);
596             // if new category, create new list to put into map
597             if (invoiceDetailAccountObjectCodeList == null) {
598                 List<InvoiceDetailAccountObjectCode> newInvoiceDetailAccountObjectCodeList = new ArrayList<InvoiceDetailAccountObjectCode>();
599                 newInvoiceDetailAccountObjectCodeList.add(invoiceDetailAccountObjectCode);
600                 invoiceDetailAccountObjectCodeMap.put(categoryCode, newInvoiceDetailAccountObjectCodeList);
601             }
602             // else, if list is found, add it to existing list
603             else {
604                 invoiceDetailAccountObjectCodeMap.get(categoryCode).add(invoiceDetailAccountObjectCode);
605             }
606         }
607 
608         // figure out if any of the current expenditures for the category has been changed. If yes, then update the invoiceDetailObjectCode and update account details
609         for (ContractsGrantsInvoiceDetail invoiceDetail : contractsGrantsInvoiceDocument.getInvoiceDetails()) {
610             KualiDecimal total = getSumOfExpendituresOfCategory(invoiceDetailAccountObjectCodeMap.get(invoiceDetail.getCategoryCode()));
611             // To set expenditures to zero if its blank - to avoid exceptions.
612             if (ObjectUtils.isNull(invoiceDetail.getInvoiceAmount())) {
613                 invoiceDetail.setInvoiceAmount(KualiDecimal.ZERO);
614             }
615 
616             if (invoiceDetail.getInvoiceAmount().compareTo(total) != 0) {
617                 recalculateObjectCodeByCategory(contractsGrantsInvoiceDocument, invoiceDetail, total, invoiceDetailAccountObjectCodeMap.get(invoiceDetail.getCategoryCode()));
618                 isExpenditureValueChanged = true;
619             }
620         }
621         return isExpenditureValueChanged;
622     }
623 
624     /**
625      * @param invoiceDetailAccountObjectCodes
626      * @return
627      */
628     protected KualiDecimal getSumOfExpendituresOfCategory(List<InvoiceDetailAccountObjectCode> invoiceDetailAccountObjectCodes) {
629         KualiDecimal total = KualiDecimal.ZERO;
630         // null can occur if this category has no invoice detail objectcode amounts
631         if (!ObjectUtils.isNull(invoiceDetailAccountObjectCodes)) {
632             for (InvoiceDetailAccountObjectCode invoiceDetailAccountObjectCode : invoiceDetailAccountObjectCodes) {
633                 total = total.add(invoiceDetailAccountObjectCode.getCurrentExpenditures());
634             }
635         }
636         return total;
637     }
638 
639     /**
640      * This method recalculates the invoiceDetailAccountObjectCode in one category that sits behind the scenes of the invoice document.
641      * @param contractsGrantsInvoiceDocument
642      * @param invoiceDetail
643      * @param total is the sum of the current expenditures from all the object codes in that category
644      * @param invoiceDetailAccountObjectCodes
645      */
646     protected void recalculateObjectCodeByCategory(ContractsGrantsInvoiceDocument contractsGrantsInvoiceDocument, ContractsGrantsInvoiceDetail invoiceDetail, KualiDecimal total, List<InvoiceDetailAccountObjectCode> invoiceDetailAccountObjectCodes) {
647         KualiDecimal currentExpenditure = invoiceDetail.getInvoiceAmount();
648         KualiDecimal newTotalAmount = KualiDecimal.ZERO;
649 
650         // if the sum of the object codes is 0, then distribute the expenditure change evenly to all object codes in the category
651         if (total.compareTo(KualiDecimal.ZERO) == 0) {
652             if (invoiceDetailAccountObjectCodes != null) {
653                 int numberOfObjectCodes = invoiceDetailAccountObjectCodes.size();
654                 if (numberOfObjectCodes != 0) {
655                     KualiDecimal newAmount = new KualiDecimal(currentExpenditure.bigDecimalValue().divide(new BigDecimal(numberOfObjectCodes), 10, BigDecimal.ROUND_HALF_DOWN));
656                     for (InvoiceDetailAccountObjectCode invoiceDetailAccountObjectCode : invoiceDetailAccountObjectCodes) {
657                         invoiceDetailAccountObjectCode.setCurrentExpenditures(newAmount);
658                         newTotalAmount = newTotalAmount.add(newAmount);
659                     }
660                 }
661             }
662             else { // if the list is null, then there are no account/object code in the gl_balance_t. So assign the amount to the first object code in the category
663                 assignCurrentExpenditureToNonExistingAccountObjectCode(contractsGrantsInvoiceDocument, invoiceDetail);
664             }
665         }
666         else {
667 
668             for (InvoiceDetailAccountObjectCode invoiceDetailAccountObjectCode : invoiceDetailAccountObjectCodes) {
669                 // this may rarely happen
670                 // if the initial total is 0, that means none of the object codes in this category is set to bill. If this amount is changed, then just divide evenly among all object codes.
671                 KualiDecimal newAmount = (new KualiDecimal(invoiceDetailAccountObjectCode.getCurrentExpenditures().bigDecimalValue().divide(total.bigDecimalValue(), 10, BigDecimal.ROUND_HALF_DOWN).multiply(currentExpenditure.bigDecimalValue())));
672                 invoiceDetailAccountObjectCode.setCurrentExpenditures(newAmount);
673                 newTotalAmount = newTotalAmount.add(newAmount);
674             }
675 
676             int remainderFromRounding = currentExpenditure.subtract(newTotalAmount).multiply(new KualiDecimal(100)).intValue();
677 
678             // add remainder from rounding
679             KualiDecimal addAmount = new KualiDecimal(0.01);
680             if (remainderFromRounding < 0) {
681                 addAmount = new KualiDecimal(-0.01);
682                 remainderFromRounding = Math.abs(remainderFromRounding);
683             }
684 
685             for (int i = 0, j = 0; i < remainderFromRounding; i++, j++) {
686                 // reset j if its more than size of list
687                 if (j >= invoiceDetailAccountObjectCodes.size()) {
688                     j = 0;
689                 }
690                 invoiceDetailAccountObjectCodes.get(j).setCurrentExpenditures(invoiceDetailAccountObjectCodes.get(j).getCurrentExpenditures().add(addAmount));
691             }
692         }
693     }
694 
695     /**
696      * @param contractsGrantsInvoiceDocument
697      * @param invoiceDetail
698      */
699     protected void assignCurrentExpenditureToNonExistingAccountObjectCode(ContractsGrantsInvoiceDocument contractsGrantsInvoiceDocument, ContractsGrantsInvoiceDetail invoiceDetail) {
700         String categoryCode = invoiceDetail.getCategoryCode();
701         if (StringUtils.isBlank(categoryCode)) {
702             throw new IllegalArgumentException("Category Code can not be null during recalculation of account object code for Contracts & Grants Invoice Document.");
703         }
704         // get the category that matches this category code.
705         final CostCategory category = businessObjectService.findBySinglePrimaryKey(CostCategory.class, categoryCode);
706 
707         // got the category now.
708         if (!ObjectUtils.isNull(category)) {
709             final KualiDecimal oneCent = new KualiDecimal(0.01);
710 
711             int size = contractsGrantsInvoiceDocument.getAccountDetails().size();
712             KualiDecimal amount = new KualiDecimal(invoiceDetail.getInvoiceAmount().bigDecimalValue().divide(new BigDecimal(size), 2, RoundingMode.HALF_UP));
713             KualiDecimal remainder = invoiceDetail.getInvoiceAmount().subtract(amount.multiply(new KualiDecimal(size)));
714 
715             for (InvoiceAccountDetail invoiceAccountDetail : contractsGrantsInvoiceDocument.getAccountDetails()) {
716                 InvoiceDetailAccountObjectCode invoiceDetailAccountObjectCode = new InvoiceDetailAccountObjectCode();
717                 invoiceDetailAccountObjectCode.setDocumentNumber(contractsGrantsInvoiceDocument.getDocumentNumber());
718                 invoiceDetailAccountObjectCode.setProposalNumber(contractsGrantsInvoiceDocument.getInvoiceGeneralDetail().getProposalNumber());
719                 invoiceDetailAccountObjectCode.setCategoryCode(categoryCode);
720                 invoiceDetailAccountObjectCode.setAccountNumber(invoiceAccountDetail.getAccountNumber());
721                 invoiceDetailAccountObjectCode.setChartOfAccountsCode(invoiceAccountDetail.getChartOfAccountsCode());
722                 invoiceDetailAccountObjectCode.setCumulativeExpenditures(KualiDecimal.ZERO); // it's 0.00 that's why we are in this section to begin with.
723                 invoiceDetailAccountObjectCode.setTotalBilled(KualiDecimal.ZERO); // this is also 0.00 because it has never been billed before
724                 final ObjectCodeCurrent objectCode = getCostCategoryService().findObjectCodeForChartAndCategory(invoiceAccountDetail.getChartOfAccountsCode(), categoryCode);
725                 if (!ObjectUtils.isNull(objectCode)) {
726                     invoiceDetailAccountObjectCode.setFinancialObjectCode(objectCode.getFinancialObjectCode());
727                 }
728 
729                 // tack on or remove one penny until the remainder is 0 - take a penny, leave a penny!
730                 if (remainder.isGreaterThan(KualiDecimal.ZERO)) {
731                     amount = amount.add(oneCent);
732                     remainder = remainder.subtract(oneCent);
733                 }
734                 else if (remainder.isLessThan(KualiDecimal.ZERO)) {
735                     amount = amount.subtract(oneCent);
736                     remainder = remainder.add(oneCent);
737                 }
738                 invoiceDetailAccountObjectCode.setCurrentExpenditures(amount);
739 
740                 List<InvoiceDetailAccountObjectCode> invoiceDetailAccountObjectCodes = contractsGrantsInvoiceDocument.getInvoiceDetailAccountObjectCodes();
741                 if (invoiceDetailAccountObjectCodes.contains(invoiceDetailAccountObjectCode)) {
742                     // update existing code
743                     InvoiceDetailAccountObjectCode original = invoiceDetailAccountObjectCodes.get(invoiceDetailAccountObjectCodes.indexOf(invoiceDetailAccountObjectCode));
744                     original.setCurrentExpenditures(amount);
745                     original.setCategoryCode(categoryCode);
746                 } else {
747                     // add this single account object code item to the list in the Map
748                     contractsGrantsInvoiceDocument.getInvoiceDetailAccountObjectCodes().add(invoiceDetailAccountObjectCode);
749                 }
750             }
751         }
752         else {
753             LOG.error("Category Code cannot be found from the category list during recalculation of account object code for Contracts & Grants Invoice Document.");
754         }
755     }
756 
757     /**
758      * @param invoiceAccountDetails
759      * @param invoiceDetailAccountObjectCodes
760      */
761     public void recalculateAccountDetails(List<InvoiceAccountDetail> invoiceAccountDetails, List<InvoiceDetailAccountObjectCode> invoiceDetailAccountObjectCodes) {
762         Map<String, KualiDecimal> currentExpenditureByAccountNumberMap = new HashMap<String, KualiDecimal>();
763         for (InvoiceDetailAccountObjectCode invoiceDetailAccountObjectCode : invoiceDetailAccountObjectCodes) {
764             String accountNumber = invoiceDetailAccountObjectCode.getAccountNumber();
765             KualiDecimal expenditureSum = currentExpenditureByAccountNumberMap.get(accountNumber);
766             // if account number not found in map, then create new total, 0
767             if (expenditureSum == null) {
768                 expenditureSum = KualiDecimal.ZERO;
769             }
770             expenditureSum = expenditureSum.add(invoiceDetailAccountObjectCode.getCurrentExpenditures());
771             currentExpenditureByAccountNumberMap.put(accountNumber, expenditureSum);
772         }
773 
774         for (InvoiceAccountDetail invoiceAccountDetail : invoiceAccountDetails) {
775             final KualiDecimal expenditureAmount = ObjectUtils.isNull(currentExpenditureByAccountNumberMap.get(invoiceAccountDetail.getAccountNumber()))
776                     ? KualiDecimal.ZERO
777                     : currentExpenditureByAccountNumberMap.get(invoiceAccountDetail.getAccountNumber());
778             invoiceAccountDetail.setInvoiceAmount(expenditureAmount);
779         }
780     }
781 
782     /**
783      * @see org.kuali.kfs.module.ar.document.service.ContractsGrantsInvoiceDocumentService#getAwardBilledToDateAmountByProposalNumber(java.lang.Long)
784      */
785     @Override
786     public KualiDecimal getAwardBilledToDateAmountByProposalNumber(Long proposalNumber) {
787         Map<String, Object> keys = new HashMap<String, Object>();
788         keys.put(KFSPropertyConstants.PROPOSAL_NUMBER, proposalNumber);
789 
790         List<AwardAccountObjectCodeTotalBilled> accountObjectCodeTotalBilledList = (List<AwardAccountObjectCodeTotalBilled>) businessObjectService.findMatching(AwardAccountObjectCodeTotalBilled.class, keys);
791         KualiDecimal billedToDateAmount = KualiDecimal.ZERO;
792         for (AwardAccountObjectCodeTotalBilled awardAccountObjectCodeTotalBilled : accountObjectCodeTotalBilledList) {
793             billedToDateAmount = billedToDateAmount.add(awardAccountObjectCodeTotalBilled.getTotalBilled());
794         }
795         return billedToDateAmount;
796     }
797 
798     /**
799      * This method retrieves all CG invoice document that match the given field values
800      *
801      * @param fieldValues
802      * @return
803      */
804     @Override
805     public Collection<ContractsGrantsInvoiceDocument> retrieveAllCGInvoicesByCriteria(Map fieldValues) {
806         return contractsGrantsInvoiceDocumentDao.getMatchingInvoicesByCollection(fieldValues);
807     }
808 
809     /**
810      * This method calculates the Budget and cumulative amount for Award Account
811      *
812      * @param awardAccount
813      * @return
814      */
815     @Override
816     public KualiDecimal getBudgetAndActualsForAwardAccount(ContractsAndGrantsBillingAwardAccount awardAccount, String balanceTypeCode, Date awardBeginningDate) {
817         List<Balance> glBalances = new ArrayList<Balance>();
818         KualiDecimal balanceAmount = KualiDecimal.ZERO;
819         KualiDecimal balAmt = KualiDecimal.ZERO;
820         Integer currentYear = universityDateService.getCurrentFiscalYear();
821         final SystemOptions systemOption = optionsService.getCurrentYearOptions();
822         List<Integer> fiscalYears = new ArrayList<Integer>();
823         Calendar c = Calendar.getInstance();
824 
825         if (ObjectUtils.isNotNull(awardBeginningDate)) {
826             Integer fiscalYear = universityDateService.getFiscalYear(awardBeginningDate);
827 
828             if (ObjectUtils.isNotNull(fiscalYear)) {
829                 for (Integer i = fiscalYear; i <= currentYear; i++) {
830                     fiscalYears.add(i);
831                 }
832                 for (Integer eachFiscalYr : fiscalYears) {
833 
834                     Map<String, Object> balanceKeys = new HashMap<String, Object>();
835                     balanceKeys.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, awardAccount.getChartOfAccountsCode());
836                     balanceKeys.put(KFSPropertyConstants.ACCOUNT_NUMBER, awardAccount.getAccountNumber());
837                     balanceKeys.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, eachFiscalYr);
838                     balanceKeys.put(KFSPropertyConstants.BALANCE_TYPE_CODE, balanceTypeCode);
839                     balanceKeys.put(KFSPropertyConstants.OBJECT_TYPE_CODE, systemOption.getFinObjTypeExpenditureexp().getCode());
840                     glBalances.addAll(businessObjectService.findMatching(Balance.class, balanceKeys));
841                 }
842                 for (Balance bal : glBalances) {
843                     if (ObjectUtils.isNull(bal.getSubAccount()) || ObjectUtils.isNull(bal.getSubAccount().getA21SubAccount()) || !StringUtils.equalsIgnoreCase(bal.getSubAccount().getA21SubAccount().getSubAccountTypeCode(), KFSConstants.SubAccountType.COST_SHARE)) {
844                         balAmt = bal.getContractsGrantsBeginningBalanceAmount().add(bal.getAccountLineAnnualBalanceAmount());
845                         balanceAmount = balanceAmount.add(balAmt);
846                     }
847                 }
848             }
849         }
850         return balanceAmount;
851     }
852 
853     /**
854      * This method serves as a create and update. When it is first called, the List<InvoiceSuspensionCategory> is empty. This list
855      * then gets populated with invoiceSuspensionCategories where the test fails. Each time the document goes through validation,
856      * and this method gets called, it will update the list by adding or remvoing the suspension categories
857      *
858      * @see org.kuali.kfs.module.ar.document.service.ContractsGrantsInvoiceDocumentService#updateSuspensionCategoriesOnDocument(org.kuali.kfs.module.ar.document.ContractsGrantsInvoiceDocument)
859      */
860     @Override
861     public void updateSuspensionCategoriesOnDocument(ContractsGrantsInvoiceDocument contractsGrantsInvoiceDocument) {
862         if (!contractsGrantsInvoiceDocument.isCorrectionDocument()) {
863             ContractsAndGrantsBillingAward award = contractsGrantsInvoiceDocument.getInvoiceGeneralDetail().getAward();
864             String documentNumber = contractsGrantsInvoiceDocument.getDocumentNumber();
865 
866             if (ObjectUtils.isNotNull(suspensionCategories)) {
867                 for (SuspensionCategory suspensionCategory : suspensionCategories) {
868                     InvoiceSuspensionCategory invoiceSuspensionCategory = new InvoiceSuspensionCategory(documentNumber, suspensionCategory.getCode());
869                     if (suspensionCategory.shouldSuspend(contractsGrantsInvoiceDocument)) {
870                         if (!contractsGrantsInvoiceDocument.getInvoiceSuspensionCategories().contains(invoiceSuspensionCategory)) {
871                             contractsGrantsInvoiceDocument.getInvoiceSuspensionCategories().add(invoiceSuspensionCategory);
872                         }
873                     } else if (contractsGrantsInvoiceDocument.getInvoiceSuspensionCategories().contains(invoiceSuspensionCategory)) {
874                         contractsGrantsInvoiceDocument.getInvoiceSuspensionCategories().remove(invoiceSuspensionCategory);
875                     }
876                 }
877             }
878         }
879     }
880 
881     /**
882      * @see org.kuali.kfs.module.ar.document.service.ContractsGrantsInvoiceDocumentService#calculateTotalPaymentsToDateByAward(org.kuali.kfs.integration.cg.ContractsAndGrantsBillingAward)
883      */
884     @Override
885     public KualiDecimal calculateTotalPaymentsToDateByAward(ContractsAndGrantsBillingAward award) {
886         KualiDecimal totalPayments = KualiDecimal.ZERO;
887 
888         Map<String, Object> criteria = new HashMap<String, Object>();
889         criteria.put(ArPropertyConstants.ContractsGrantsInvoiceDocumentFields.PROPOSAL_NUMBER, award.getProposalNumber());
890         Collection<ContractsGrantsInvoiceDocument> cgInvoiceDocs = businessObjectService.findMatching(ContractsGrantsInvoiceDocument.class, criteria);
891 
892         for (ContractsGrantsInvoiceDocument cgInvoiceDoc : cgInvoiceDocs) {
893             totalPayments = totalPayments.add(getCustomerInvoiceDocumentService().calculateAppliedPaymentAmount(cgInvoiceDoc));
894         }
895         return totalPayments;
896     }
897 
898     /**
899      * This method calculates the Cumulative Disbursement amount for an awardAccount
900      *
901      * @param awardAccount
902      * @return
903      */
904     protected KualiDecimal getCumulativeCashDisbursement(ContractsAndGrantsBillingAwardAccount awardAccount, java.sql.Date awardBeginningDate) {
905         Integer currentYear = universityDateService.getCurrentFiscalYear();
906         final SystemOptions systemOption = optionsService.getCurrentYearOptions();
907         KualiDecimal cumAmt = KualiDecimal.ZERO;
908         KualiDecimal balAmt = KualiDecimal.ZERO;
909         List<Balance> glBalances = new ArrayList<Balance>();
910 
911         List<Integer> fiscalYears = new ArrayList<Integer>();
912         Calendar c = Calendar.getInstance();
913 
914 
915         Integer fiscalYear = universityDateService.getFiscalYear(awardBeginningDate);
916 
917         for (Integer i = fiscalYear; i <= currentYear; i++) {
918             fiscalYears.add(i);
919         }
920         for (Integer eachFiscalYr : fiscalYears) {
921             Map<String, Object> balanceKeys = new HashMap<String, Object>();
922             balanceKeys.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, awardAccount.getChartOfAccountsCode());
923             balanceKeys.put(KFSPropertyConstants.ACCOUNT_NUMBER, awardAccount.getAccountNumber());
924             balanceKeys.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, eachFiscalYr);
925             balanceKeys.put(KFSPropertyConstants.BALANCE_TYPE_CODE, systemOption.getActualFinancialBalanceTypeCd());
926             balanceKeys.put(KFSPropertyConstants.OBJECT_TYPE_CODE, systemOption.getFinObjTypeExpenditureexp().getCode());
927             glBalances.addAll(businessObjectService.findMatching(Balance.class, balanceKeys));
928         }
929         for (Balance bal : glBalances) {
930             if (ObjectUtils.isNull(bal.getSubAccount()) || ObjectUtils.isNull(bal.getSubAccount().getA21SubAccount()) || !StringUtils.equalsIgnoreCase(bal.getSubAccount().getA21SubAccount().getSubAccountTypeCode(), KFSConstants.SubAccountType.COST_SHARE)) {
931                 balAmt = bal.getContractsGrantsBeginningBalanceAmount().add(bal.getAccountLineAnnualBalanceAmount());
932                 cumAmt = cumAmt.add(balAmt);
933             }
934         }
935         return cumAmt;
936     }
937 
938     /**
939      * @see org.kuali.kfs.module.ar.document.service.ContractsGrantsInvoiceDocumentService#getMilestonesBilledToDateAmount(java.lang.Long)
940      */
941     @Override
942     public KualiDecimal getMilestonesBilledToDateAmount(Long proposalNumber) {
943         Map<String, Object> totalBilledKeys = new HashMap<String, Object>();
944         totalBilledKeys.put(KFSPropertyConstants.PROPOSAL_NUMBER, proposalNumber);
945         KualiDecimal billedToDateAmount = KualiDecimal.ZERO;
946 
947         List<Milestone> milestones = (List<Milestone>) businessObjectService.findMatching(Milestone.class, totalBilledKeys);
948         if (CollectionUtils.isNotEmpty(milestones)) {
949             Iterator<Milestone> iterator = milestones.iterator();
950             while (iterator.hasNext()) {
951                 Milestone milestone = iterator.next();
952                 if (milestone.isBilled()) {
953                     billedToDateAmount = billedToDateAmount.add(milestone.getMilestoneAmount());
954                 }
955             }
956         }
957         return billedToDateAmount;
958     }
959 
960     /**
961      * @see org.kuali.kfs.module.ar.document.service.ContractsGrantsInvoiceDocumentService#getPredeterminedBillingBilledToDateAmount(java.lang.Long)
962      */
963     @Override
964     public KualiDecimal getPredeterminedBillingBilledToDateAmount(Long proposalNumber) {
965         Map<String, Object> totalBilledKeys = new HashMap<String, Object>();
966         totalBilledKeys.put(KFSPropertyConstants.PROPOSAL_NUMBER, proposalNumber);
967         KualiDecimal billedToDateAmount = KualiDecimal.ZERO;
968 
969         List<Bill> bills = (List<Bill>) businessObjectService.findMatching(Bill.class, totalBilledKeys);
970         if (CollectionUtils.isNotEmpty(bills)) {
971             Iterator<Bill> iterator = bills.iterator();
972             while (iterator.hasNext()) {
973                 Bill bill = iterator.next();
974                 if (bill.isBilled()) {
975                     billedToDateAmount = billedToDateAmount.add(bill.getEstimatedAmount());
976                 }
977             }
978         }
979         return billedToDateAmount;
980     }
981 
982     /**
983      * @see org.kuali.kfs.module.ar.document.service.ContractsGrantsInvoiceDocumentService#getExpiredAccountsOfAward(org.kuali.kfs.integration.cg.ContractsAndGrantsBillingAward)
984      *      Retrive all the expired accounts of an award
985      */
986     @Override
987     public Collection<Account> getExpiredAccountsOfAward(ContractsAndGrantsBillingAward award) {
988 
989         Collection<ContractsAndGrantsBillingAwardAccount> awardAccounts = award.getActiveAwardAccounts();
990         Collection<Account> expiredAwardAccounts = new ArrayList<Account>();
991 
992         if (awardAccounts != null && !awardAccounts.isEmpty()) {
993 
994             Date today = dateTimeService.getCurrentSqlDateMidnight();
995 
996             for (ContractsAndGrantsBillingAwardAccount awardAccount : awardAccounts) {
997                 Account account = awardAccount.getAccount();
998 
999                 if (account != null) {
1000                     Date expDt = account.getAccountExpirationDate();
1001 
1002                     if (expDt != null && expDt.before(today)) {
1003                         expiredAwardAccounts.add(account);
1004                     }
1005                 }
1006 
1007             }
1008 
1009             return expiredAwardAccounts;
1010         }
1011 
1012         return null;
1013     }
1014 
1015 
1016     /**
1017      * @see org.kuali.kfs.module.ar.document.service.ContractsGrantsInvoiceDocumentService#getContractControlAccounts(org.kuali.kfs.integration.cg.ContractsAndGrantsBillingAward)
1018      */
1019     @Override
1020     public List<Account> getContractControlAccounts(ContractsAndGrantsBillingAward award) {
1021 
1022         if (!CollectionUtils.isEmpty(award.getActiveAwardAccounts())) {
1023             List<Account> controlAccounts = new ArrayList<Account>();
1024             for (ContractsAndGrantsBillingAwardAccount awardAccount : award.getActiveAwardAccounts()) {
1025                 if (ObjectUtils.isNotNull(awardAccount.getAccount().getContractControlAccount())) {
1026                     controlAccounts.add(awardAccount.getAccount().getContractControlAccount());
1027                 }
1028             }
1029             if (CollectionUtils.isNotEmpty(controlAccounts)) {
1030                 return controlAccounts;
1031             }
1032         }
1033         return null;
1034     }
1035 
1036     /**
1037      * To retrieve processing chart code and org code from the billing chart code and org code
1038      *
1039      * @param cgInvoiceDocument
1040      * @param billingChartOfAccountsCode
1041      * @param billingOrganizationCode
1042      * @return
1043      */
1044     @Override
1045     public List<String> getProcessingFromBillingCodes(String billingChartCode, String billingOrgCode) {
1046 
1047         List<String> procCodes = new ArrayList<String>();
1048         // To access Organization Options to find the billing values based on procesing codes
1049         Map<String, String> criteria = new HashMap<String, String>();
1050         criteria.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, billingChartCode);
1051         criteria.put(KFSPropertyConstants.ORGANIZATION_CODE, billingOrgCode);
1052         OrganizationOptions organizationOptions = businessObjectService.findByPrimaryKey(OrganizationOptions.class, criteria);
1053 
1054         if (ObjectUtils.isNotNull(organizationOptions)) {
1055             procCodes.add(0, organizationOptions.getProcessingChartOfAccountCode());
1056             procCodes.add(1, organizationOptions.getProcessingOrganizationCode());
1057         }
1058 
1059         return procCodes;
1060     }
1061 
1062     /**
1063      * @see org.kuali.kfs.module.ar.document.service.ContractsGrantsInvoiceDocumentService#canViewInvoice(org.kuali.kfs.module.ar.document.ContractsGrantsInvoiceDocument, java.lang.String)
1064      */
1065     @Override
1066     public boolean canViewInvoice(ContractsGrantsInvoiceDocument invoice, String collectorPrincipalId) {
1067         Map<String, String> qualification = new HashMap<String, String>(3);
1068         qualification.put(ArKimAttributes.BILLING_CHART_OF_ACCOUNTS_CODE, invoice.getBillByChartOfAccountCode());
1069         qualification.put(ArKimAttributes.BILLING_ORGANIZATION_CODE, invoice.getBilledByOrganizationCode());
1070         qualification.put(ArKimAttributes.PROCESSING_CHART_OF_ACCOUNTS_CODE, invoice.getAccountsReceivableDocumentHeader().getProcessingChartOfAccountCode());
1071         qualification.put(ArKimAttributes.PROCESSING_ORGANIZATION_CODE, invoice.getAccountsReceivableDocumentHeader().getProcessingOrganizationCode());
1072 
1073         String customerName = invoice.getCustomerName();
1074         if (!StringUtils.isBlank(customerName)) {
1075             qualification.put(ArKimAttributes.CUSTOMER_NAME, customerName);
1076         }
1077 
1078         return getPermissionService().isAuthorized(collectorPrincipalId, ArConstants.AR_NAMESPACE_CODE, ArAuthorizationConstants.VIEW_CONTRACTS_GRANTS_INVOICE_IN_BILLING_REPORTS_PERMISSION, qualification);
1079     }
1080 
1081     /**
1082      * This method generates the attached invoices for the agency addresses in the Contracts & Grants Invoice Document.
1083      */
1084     @Override
1085     public void generateInvoicesForInvoiceAddresses(ContractsGrantsInvoiceDocument document) {
1086         InvoiceTemplate invoiceTemplate = null;
1087         Iterator<InvoiceAddressDetail> iterator = document.getInvoiceAddressDetails().iterator();
1088         while (iterator.hasNext()) {
1089             InvoiceAddressDetail invoiceAddressDetail = iterator.next();
1090             byte[] reportStream;
1091             byte[] copyReportStream;
1092             // validating the invoice template
1093             if (ObjectUtils.isNotNull(invoiceAddressDetail.getCustomerInvoiceTemplateCode())) {
1094                 invoiceTemplate = businessObjectService.findBySinglePrimaryKey(InvoiceTemplate.class, invoiceAddressDetail.getCustomerInvoiceTemplateCode());
1095 
1096                 // generate invoices from templates.
1097                 if (ObjectUtils.isNotNull(invoiceTemplate) && invoiceTemplate.isActive() && StringUtils.isNotBlank(invoiceTemplate.getFilename())) {
1098                     ModuleConfiguration systemConfiguration = kualiModuleService.getModuleServiceByNamespaceCode(KFSConstants.OptionalModuleNamespaces.ACCOUNTS_RECEIVABLE).getModuleConfiguration();
1099                     String templateFolderPath = ((FinancialSystemModuleConfiguration) systemConfiguration).getTemplateFileDirectories().get(KFSConstants.TEMPLATES_DIRECTORY_KEY);
1100                     String templateFilePath = templateFolderPath + File.separator + invoiceTemplate.getFilename();
1101                     File templateFile = new File(templateFilePath);
1102                     File outputDirectory = null;
1103                     String outputFileName;
1104                     try {
1105                         // generating original invoice
1106                         outputFileName = document.getDocumentNumber() + "_" + invoiceAddressDetail.getCustomerAddressName() + getDateTimeService().toDateStringForFilename(getDateTimeService().getCurrentDate()) + ArConstants.TemplateUploadSystem.EXTENSION;
1107                         Map<String, String> replacementList = getTemplateParameterList(document);
1108                         replacementList.put(ArPropertyConstants.CustomerInvoiceDocumentFields.CUSTOMER+"."+ArPropertyConstants.FULL_ADDRESS, contractsGrantsBillingUtilityService.buildFullAddress(invoiceAddressDetail.getCustomerAddress()));
1109                         reportStream = PdfFormFillerUtil.populateTemplate(templateFile, replacementList);
1110                         // creating and saving the original note with an attachment
1111                         if (ObjectUtils.isNotNull(document.getInvoiceGeneralDetail()) && document.getInvoiceGeneralDetail().isFinalBillIndicator()) {
1112                             reportStream = PdfFormFillerUtil.createFinalmarkOnFile(reportStream, getConfigurationService().getPropertyValueAsString(ArKeyConstants.INVOICE_ADDRESS_PDF_WATERMARK_FINAL));
1113                         }
1114                         Note note = new Note();
1115                         note.setNotePostedTimestampToCurrent();
1116                         final String finalNotePattern = getConfigurationService().getPropertyValueAsString(ArKeyConstants.INVOICE_ADDRESS_PDF_FINAL_NOTE);
1117                         note.setNoteText(MessageFormat.format(finalNotePattern, document.getDocumentNumber(), invoiceAddressDetail.getCustomerAddressName()));
1118                         note.setNoteTypeCode(KFSConstants.NoteTypeEnum.BUSINESS_OBJECT_NOTE_TYPE.getCode());
1119                         Person systemUser = personService.getPersonByPrincipalName(KFSConstants.SYSTEM_USER);
1120                         note = noteService.createNote(note, document.getNoteTarget(), systemUser.getPrincipalId());
1121                         Attachment attachment = attachmentService.createAttachment(note, outputFileName, ArConstants.TemplateUploadSystem.TEMPLATE_MIME_TYPE, reportStream.length, new ByteArrayInputStream(reportStream), KFSConstants.EMPTY_STRING);
1122                         // adding attachment to the note
1123                         note.setAttachment(attachment);
1124                         noteService.save(note);
1125                         attachment.setNoteIdentifier(note.getNoteIdentifier());
1126                         businessObjectService.save(attachment);
1127                         document.addNote(note);
1128 
1129                         // generating Copy invoice
1130                         outputFileName = document.getDocumentNumber() + "_" + invoiceAddressDetail.getCustomerAddressName() + getDateTimeService().toDateStringForFilename(getDateTimeService().getCurrentDate()) + getConfigurationService().getPropertyValueAsString(ArKeyConstants.INVOICE_ADDRESS_PDF_COPY_FILENAME_SUFFIX) + ArConstants.TemplateUploadSystem.EXTENSION;
1131                         copyReportStream = PdfFormFillerUtil.createWatermarkOnFile(reportStream, getConfigurationService().getPropertyValueAsString(ArKeyConstants.INVOICE_ADDRESS_PDF_WATERMARK_COPY));
1132                         // creating and saving the copy note with an attachment
1133                         Note copyNote = new Note();
1134                         copyNote.setNotePostedTimestampToCurrent();
1135                         final String copyNotePattern = getConfigurationService().getPropertyValueAsString(ArKeyConstants.INVOICE_ADDRESS_PDF_COPY_NOTE);
1136                         copyNote.setNoteText(MessageFormat.format(copyNotePattern, document.getDocumentNumber(), invoiceAddressDetail.getCustomerAddressName()));
1137                         copyNote.setNoteTypeCode(KFSConstants.NoteTypeEnum.BUSINESS_OBJECT_NOTE_TYPE.getCode());
1138                         copyNote = noteService.createNote(copyNote, document.getNoteTarget(), systemUser.getPrincipalId());
1139                         Attachment copyAttachment = attachmentService.createAttachment(copyNote, outputFileName, ArConstants.TemplateUploadSystem.TEMPLATE_MIME_TYPE, copyReportStream.length, new ByteArrayInputStream(copyReportStream), KFSConstants.EMPTY_STRING);
1140                         // adding attachment to the note
1141                         copyNote.setAttachment(copyAttachment);
1142                         noteService.save(copyNote);
1143                         copyAttachment.setNoteIdentifier(copyNote.getNoteIdentifier());
1144                         businessObjectService.save(copyAttachment);
1145                         document.addNote(copyNote);
1146                         invoiceAddressDetail.setNoteId(note.getNoteIdentifier());
1147                         // saving the note to the document header
1148                         documentService.updateDocument(document);
1149                     } catch (IOException | DocumentException ex) {
1150                         addNoteForInvoiceReportFail(document);
1151                     }
1152                 } else {
1153                     addNoteForInvoiceReportFail(document);
1154                 }
1155             } else {
1156                 addNoteForInvoiceReportFail(document);
1157             }
1158 
1159         }
1160     }
1161 
1162     /**
1163      * This method generated the template parameter list to populate the pdf invoices that are attached to the Document.
1164      * The evident goal of this method was to return practically any possible property from the given document into a Map which could be stamped on any invoice PDF.  Given that, we've done some strange
1165      * tricks with Map implementations.  First, we wrap the document in a ReflectionMap, which means that all nested properties from the document can be read via Map notation.  We still have a number of properties
1166      * we want to add, though, for instance for the payee and the award.  So we wrap ReflectionMap in a FallbackMap, which will treat a regular HashMap and the ReflectionMap as if they were one Map for the sake of
1167      * getting at least.  Finally, we wrap the map of all properties into a PdfFormattingMap, which formats any values to be returned through get() into properly formatted Strings.
1168      * @param document the ContractsGrantsInvoiceDocument to convert into a Map form
1169      * @return a Map.  With everything.
1170      */
1171     protected Map<String, String> getTemplateParameterList(ContractsGrantsInvoiceDocument document) {
1172         ContractsAndGrantsBillingAward award = document.getInvoiceGeneralDetail().getAward();
1173 
1174         Map cinvDocMap = new ReflectionMap(document);
1175         Map<String, Object> parameterMap = new FallbackMap<String, Object>(cinvDocMap);
1176 
1177         Map<String, Object> primaryKeys = new HashMap<>();
1178         primaryKeys.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, document.getAccountingPeriod().getUniversityFiscalYear());
1179         primaryKeys.put(KFSPropertyConstants.PROCESSING_CHART_OF_ACCT_CD, document.getAccountsReceivableDocumentHeader().getProcessingChartOfAccountCode());
1180         primaryKeys.put(KFSPropertyConstants.PROCESSING_ORGANIZATION_CODE, document.getAccountsReceivableDocumentHeader().getProcessingOrganizationCode());
1181         SystemInformation sysInfo = businessObjectService.findByPrimaryKey(SystemInformation.class, primaryKeys);
1182         parameterMap.put(KFSPropertyConstants.DOCUMENT_NUMBER, document.getDocumentNumber());
1183         if (ObjectUtils.isNotNull(document.getDocumentHeader().getWorkflowDocument().getDateCreated())) {
1184             parameterMap.put(KFSPropertyConstants.DATE, getDateTimeService().toDateString(document.getDocumentHeader().getWorkflowDocument().getDateCreated().toDate()));
1185         }
1186         if (ObjectUtils.isNotNull(document.getDocumentHeader().getWorkflowDocument().getDateFinalized())) {
1187             parameterMap.put(ArPropertyConstants.FINAL_STATUS_DATE, getDateTimeService().toDateString(document.getDocumentHeader().getWorkflowDocument().getDateFinalized().toDate()));
1188         }
1189         parameterMap.put(KFSPropertyConstants.PROPOSAL_NUMBER, document.getInvoiceGeneralDetail().getProposalNumber());
1190         parameterMap.put(KFSPropertyConstants.PAYEE+"."+KFSPropertyConstants.NAME, document.getBillingAddressName());
1191         parameterMap.put(KFSPropertyConstants.PAYEE+"."+KFSPropertyConstants.ADDRESS_LINE1, document.getBillingLine1StreetAddress());
1192         parameterMap.put(KFSPropertyConstants.PAYEE+"."+KFSPropertyConstants.ADDRESS_LINE2, document.getBillingLine2StreetAddress());
1193         parameterMap.put(KFSPropertyConstants.PAYEE+"."+KFSPropertyConstants.CITY, document.getBillingCityName());
1194         parameterMap.put(KFSPropertyConstants.PAYEE+"."+KFSPropertyConstants.STATE, document.getBillingStateCode());
1195         parameterMap.put(KFSPropertyConstants.PAYEE+"."+KFSPropertyConstants.ZIPCODE, document.getBillingZipCode());
1196         parameterMap.put(ArPropertyConstants.ADVANCE_FLAG, ArConstants.PREDETERMINED_BILLING_SCHEDULE_CODE.equals(document.getInvoiceGeneralDetail().getBillingFrequencyCode()));
1197         parameterMap.put(ArPropertyConstants.REIMBURSEMENT_FLAG, !ArConstants.PREDETERMINED_BILLING_SCHEDULE_CODE.equals(document.getInvoiceGeneralDetail().getBillingFrequencyCode()));
1198         parameterMap.put(ArPropertyConstants.ACCOUNT_DETAILS+"."+KFSPropertyConstants.CONTRACT_CONTROL_ACCOUNT_NUMBER, getRecipientAccountNumber(document.getAccountDetails()));
1199         if (ObjectUtils.isNotNull(sysInfo)) {
1200             parameterMap.put(ArPropertyConstants.SYSTEM_INFORMATION+"."+ArPropertyConstants.SystemInformationFields.FEIN_NUMBER, sysInfo.getUniversityFederalEmployerIdentificationNumber());
1201             parameterMap.put(ArPropertyConstants.SYSTEM_INFORMATION+"."+KFSPropertyConstants.NAME, sysInfo.getOrganizationRemitToAddressName());
1202             parameterMap.put(ArPropertyConstants.SYSTEM_INFORMATION+"."+KFSPropertyConstants.ADDRESS_LINE1, sysInfo.getOrganizationRemitToLine1StreetAddress());
1203             parameterMap.put(ArPropertyConstants.SYSTEM_INFORMATION+"."+KFSPropertyConstants.ADDRESS_LINE2, sysInfo.getOrganizationRemitToLine2StreetAddress());
1204             parameterMap.put(ArPropertyConstants.SYSTEM_INFORMATION+"."+KFSPropertyConstants.CITY, sysInfo.getOrganizationRemitToCityName());
1205             parameterMap.put(ArPropertyConstants.SYSTEM_INFORMATION+"."+KFSPropertyConstants.STATE, sysInfo.getOrganizationRemitToStateCode());
1206             parameterMap.put(ArPropertyConstants.SYSTEM_INFORMATION+"."+KFSPropertyConstants.ZIPCODE, sysInfo.getOrganizationRemitToZipCode());
1207         }
1208         if (CollectionUtils.isNotEmpty(document.getDirectCostInvoiceDetails())) {
1209             ContractsGrantsInvoiceDetail firstInvoiceDetail = document.getDirectCostInvoiceDetails().get(0);
1210 
1211             for (int i = 0; i < document.getDirectCostInvoiceDetails().size(); i++) {
1212                 parameterMap.put(ArPropertyConstants.INVOICE_DETAIL+"[" + i + "]."+ArPropertyConstants.INVOICE_DETAIL_IDENTIFIER, document.getDirectCostInvoiceDetails().get(i).getInvoiceDetailIdentifier());
1213                 parameterMap.put(ArPropertyConstants.INVOICE_DETAIL+"[" + i + "]."+KFSPropertyConstants.DOCUMENT_NUMBER, document.getDirectCostInvoiceDetails().get(i).getDocumentNumber());
1214                 parameterMap.put(ArPropertyConstants.INVOICE_DETAIL+"[" + i + "]."+ArPropertyConstants.CATEGORY, document.getDirectCostInvoiceDetails().get(i).getCostCategory().getCategoryName());
1215                 parameterMap.put(ArPropertyConstants.INVOICE_DETAIL+"[" + i + "]."+ArPropertyConstants.TOTAL_BUDGET, document.getDirectCostInvoiceDetails().get(i).getTotalBudget());
1216                 parameterMap.put(ArPropertyConstants.INVOICE_DETAIL+"[" + i + "]."+ArPropertyConstants.INVOICE_AMOUNT, document.getDirectCostInvoiceDetails().get(i).getInvoiceAmount());
1217                 parameterMap.put(ArPropertyConstants.INVOICE_DETAIL+"[" + i + "]."+ArPropertyConstants.CUMULATIVE_EXPENDITURES, document.getDirectCostInvoiceDetails().get(i).getCumulativeExpenditures());
1218                 parameterMap.put(ArPropertyConstants.INVOICE_DETAIL+"[" + i + "]."+ArPropertyConstants.BUDGET_REMAINING, document.getDirectCostInvoiceDetails().get(i).getBudgetRemaining());
1219                 parameterMap.put(ArPropertyConstants.INVOICE_DETAIL+"[" + i + "]."+ArPropertyConstants.TOTAL_PREVIOUSLY_BILLED, document.getDirectCostInvoiceDetails().get(i).getTotalPreviouslyBilled());
1220                 parameterMap.put(ArPropertyConstants.INVOICE_DETAIL+"[" + i + "]."+ArPropertyConstants.TOTAL_AMOUNT_BILLED_TO_DATE, document.getDirectCostInvoiceDetails().get(i).getTotalAmountBilledToDate());
1221                 parameterMap.put(ArPropertyConstants.INVOICE_DETAIL+"[" + i + "]."+ArPropertyConstants.AMOUNT_REMAINING_TO_BILL, firstInvoiceDetail.getAmountRemainingToBill());
1222             }
1223         }
1224         ContractsGrantsInvoiceDetail totalDirectCostInvoiceDetail = document.getTotalDirectCostInvoiceDetail();
1225         if (ObjectUtils.isNotNull(totalDirectCostInvoiceDetail)) {
1226             parameterMap.put(ArPropertyConstants.DIRECT_COST_INVOICE_DETAIL+"."+ArPropertyConstants.INVOICE_DETAIL_IDENTIFIER, totalDirectCostInvoiceDetail.getInvoiceDetailIdentifier());
1227             parameterMap.put(ArPropertyConstants.DIRECT_COST_INVOICE_DETAIL+"."+KFSPropertyConstants.DOCUMENT_NUMBER, totalDirectCostInvoiceDetail.getDocumentNumber());
1228             parameterMap.put(ArPropertyConstants.DIRECT_COST_INVOICE_DETAIL+"."+ArPropertyConstants.CATEGORY, getConfigurationService().getPropertyValueAsString(ArKeyConstants.CONTRACTS_GRANTS_INVOICE_DETAILS_DIRECT_SUBTOTAL_LABEL));
1229             parameterMap.put(ArPropertyConstants.DIRECT_COST_INVOICE_DETAIL+"."+ArPropertyConstants.TOTAL_BUDGET, totalDirectCostInvoiceDetail.getTotalBudget());
1230             parameterMap.put(ArPropertyConstants.DIRECT_COST_INVOICE_DETAIL+"."+ArPropertyConstants.INVOICE_AMOUNT, totalDirectCostInvoiceDetail.getInvoiceAmount());
1231             parameterMap.put(ArPropertyConstants.DIRECT_COST_INVOICE_DETAIL+"."+ArPropertyConstants.CUMULATIVE_EXPENDITURES, totalDirectCostInvoiceDetail.getCumulativeExpenditures());
1232             parameterMap.put(ArPropertyConstants.DIRECT_COST_INVOICE_DETAIL+"."+ArPropertyConstants.BUDGET_REMAINING, totalDirectCostInvoiceDetail.getBudgetRemaining());
1233             parameterMap.put(ArPropertyConstants.DIRECT_COST_INVOICE_DETAIL+"."+ArPropertyConstants.TOTAL_PREVIOUSLY_BILLED, totalDirectCostInvoiceDetail.getTotalPreviouslyBilled());
1234             parameterMap.put(ArPropertyConstants.DIRECT_COST_INVOICE_DETAIL+"."+ArPropertyConstants.TOTAL_AMOUNT_BILLED_TO_DATE, totalDirectCostInvoiceDetail.getTotalAmountBilledToDate());
1235             parameterMap.put(ArPropertyConstants.DIRECT_COST_INVOICE_DETAIL+"."+ArPropertyConstants.AMOUNT_REMAINING_TO_BILL, totalDirectCostInvoiceDetail.getAmountRemainingToBill());
1236         }
1237         ContractsGrantsInvoiceDetail totalInDirectCostInvoiceDetail = document.getTotalIndirectCostInvoiceDetail();
1238         if (ObjectUtils.isNotNull(totalInDirectCostInvoiceDetail)) {
1239             parameterMap.put(ArPropertyConstants.IN_DIRECT_COST_INVOICE_DETAIL+"."+ArPropertyConstants.INVOICE_DETAIL_IDENTIFIER, totalInDirectCostInvoiceDetail.getInvoiceDetailIdentifier());
1240             parameterMap.put(ArPropertyConstants.IN_DIRECT_COST_INVOICE_DETAIL+"."+KFSPropertyConstants.DOCUMENT_NUMBER, totalInDirectCostInvoiceDetail.getDocumentNumber());
1241             parameterMap.put(ArPropertyConstants.IN_DIRECT_COST_INVOICE_DETAIL+"."+ArPropertyConstants.CATEGORIES, getConfigurationService().getPropertyValueAsString(ArKeyConstants.CONTRACTS_GRANTS_INVOICE_DETAILS_INDIRECT_SUBTOTAL_LABEL));
1242             parameterMap.put(ArPropertyConstants.IN_DIRECT_COST_INVOICE_DETAIL+"."+ArPropertyConstants.TOTAL_BUDGET, totalInDirectCostInvoiceDetail.getTotalBudget());
1243             parameterMap.put(ArPropertyConstants.IN_DIRECT_COST_INVOICE_DETAIL+"."+ArPropertyConstants.INVOICE_AMOUNT, totalInDirectCostInvoiceDetail.getInvoiceAmount());
1244             parameterMap.put(ArPropertyConstants.IN_DIRECT_COST_INVOICE_DETAIL+"."+ArPropertyConstants.CUMULATIVE_EXPENDITURES, totalInDirectCostInvoiceDetail.getCumulativeExpenditures());
1245             parameterMap.put(ArPropertyConstants.IN_DIRECT_COST_INVOICE_DETAIL+"."+ArPropertyConstants.BUDGET_REMAINING, totalInDirectCostInvoiceDetail.getBudgetRemaining());
1246             parameterMap.put(ArPropertyConstants.IN_DIRECT_COST_INVOICE_DETAIL+"."+ArPropertyConstants.TOTAL_PREVIOUSLY_BILLED, totalInDirectCostInvoiceDetail.getTotalPreviouslyBilled());
1247             parameterMap.put(ArPropertyConstants.IN_DIRECT_COST_INVOICE_DETAIL+"."+ArPropertyConstants.TOTAL_AMOUNT_BILLED_TO_DATE, totalInDirectCostInvoiceDetail.getTotalAmountBilledToDate());
1248             parameterMap.put(ArPropertyConstants.IN_DIRECT_COST_INVOICE_DETAIL+"."+ArPropertyConstants.AMOUNT_REMAINING_TO_BILL, totalInDirectCostInvoiceDetail.getAmountRemainingToBill());
1249         }
1250         ContractsGrantsInvoiceDetail totalCostInvoiceDetail = document.getTotalCostInvoiceDetail();
1251         if (ObjectUtils.isNotNull(totalCostInvoiceDetail)) {
1252             parameterMap.put(ArPropertyConstants.TOTAL_INVOICE_DETAIL+"."+ArPropertyConstants.INVOICE_DETAIL_IDENTIFIER, totalCostInvoiceDetail.getInvoiceDetailIdentifier());
1253             parameterMap.put(ArPropertyConstants.TOTAL_INVOICE_DETAIL+"."+KFSPropertyConstants.DOCUMENT_NUMBER, totalCostInvoiceDetail.getDocumentNumber());
1254             parameterMap.put(ArPropertyConstants.TOTAL_INVOICE_DETAIL+"."+ArPropertyConstants.CATEGORIES, getConfigurationService().getPropertyValueAsString(ArKeyConstants.CONTRACTS_GRANTS_INVOICE_DETAILS_TOTAL_LABEL));
1255             parameterMap.put(ArPropertyConstants.TOTAL_INVOICE_DETAIL+"."+ArPropertyConstants.TOTAL_BUDGET, totalCostInvoiceDetail.getTotalBudget());
1256             parameterMap.put(ArPropertyConstants.TOTAL_INVOICE_DETAIL+"."+ArPropertyConstants.INVOICE_AMOUNT, totalCostInvoiceDetail.getInvoiceAmount());
1257             parameterMap.put(ArPropertyConstants.TOTAL_INVOICE_DETAIL+"."+ArPropertyConstants.CUMULATIVE_EXPENDITURES, totalCostInvoiceDetail.getCumulativeExpenditures());
1258             parameterMap.put(ArPropertyConstants.TOTAL_INVOICE_DETAIL+"."+ArPropertyConstants.BUDGET_REMAINING, totalCostInvoiceDetail.getBudgetRemaining());
1259             parameterMap.put(ArPropertyConstants.TOTAL_INVOICE_DETAIL+"."+ArPropertyConstants.TOTAL_PREVIOUSLY_BILLED, totalCostInvoiceDetail.getTotalPreviouslyBilled());
1260             parameterMap.put(ArPropertyConstants.TOTAL_INVOICE_DETAIL+"."+ArPropertyConstants.ESTIMATED_COST, totalCostInvoiceDetail.getTotalPreviouslyBilled().add(totalCostInvoiceDetail.getInvoiceAmount()));
1261             parameterMap.put(ArPropertyConstants.TOTAL_INVOICE_DETAIL+"."+ArPropertyConstants.TOTAL_AMOUNT_BILLED_TO_DATE, totalCostInvoiceDetail.getTotalAmountBilledToDate());
1262             parameterMap.put(ArPropertyConstants.TOTAL_INVOICE_DETAIL+"."+ArPropertyConstants.AMOUNT_REMAINING_TO_BILL, totalCostInvoiceDetail.getAmountRemainingToBill());
1263         }
1264 
1265         if (ObjectUtils.isNotNull(award)) {
1266             KualiDecimal billing = getAwardBilledToDateAmountByProposalNumber(award.getProposalNumber());
1267             KualiDecimal payments = calculateTotalPaymentsToDateByAward(award);
1268             KualiDecimal receivable = billing.subtract(payments);
1269             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.BILLINGS, billing);
1270             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.PAYMENTS, payments);
1271             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.RECEIVABLES, receivable);
1272             parameterMap.put(KFSPropertyConstants.AWARD+"."+KFSPropertyConstants.PROPOSAL_NUMBER, award.getProposalNumber());
1273             if (ObjectUtils.isNotNull(award.getAwardBeginningDate())) {
1274                 parameterMap.put(KFSPropertyConstants.AWARD+"."+KFSPropertyConstants.AWARD_BEGINNING_DATE, getDateTimeService().toDateString(award.getAwardBeginningDate()));
1275             }
1276             if (ObjectUtils.isNotNull(award.getAwardEndingDate())) {
1277                 parameterMap.put(KFSPropertyConstants.AWARD+"."+KFSPropertyConstants.AWARD_ENDING_DATE, getDateTimeService().toDateString(award.getAwardEndingDate()));
1278             }
1279             parameterMap.put(KFSPropertyConstants.AWARD+"."+KFSPropertyConstants.AWARD_TOTAL_AMOUNT, award.getAwardTotalAmount());
1280             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.AWARD_ADDENDUM_NUMBER, award.getAwardAddendumNumber());
1281             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.AWARD_ALLOCATED_UNIVERSITY_COMPUTING_SERVICES_AMOUNT, award.getAwardAllocatedUniversityComputingServicesAmount());
1282             parameterMap.put(KFSPropertyConstants.AWARD+"."+KFSPropertyConstants.FEDERAL_PASS_THROUGH_FUNDED_AMOUNT, award.getFederalPassThroughFundedAmount());
1283             if (ObjectUtils.isNotNull(award.getAwardEntryDate())) {
1284                 parameterMap.put(KFSPropertyConstants.AWARD+"."+KFSPropertyConstants.AWARD_ENTRY_DATE, getDateTimeService().toDateString(award.getAwardEntryDate()));
1285             }
1286             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.AGENCY_FUTURE_1_AMOUNT, award.getAgencyFuture1Amount());
1287             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.AGENCY_FUTURE_2_AMOUNT, award.getAgencyFuture2Amount());
1288             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.AGENCY_FUTURE_3_AMOUNT, award.getAgencyFuture3Amount());
1289             parameterMap.put(KFSPropertyConstants.AWARD+"."+KFSPropertyConstants.AWARD_DOCUMENT_NUMBER, award.getAwardDocumentNumber());
1290             if (ObjectUtils.isNotNull(award.getAwardLastUpdateDate())) {
1291                 parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.AWARD_LAST_UPDATE_DATE, getDateTimeService().toDateString(award.getAwardLastUpdateDate()));
1292             }
1293             parameterMap.put(KFSPropertyConstants.AWARD+"."+KFSPropertyConstants.FEDERAL_PASS_THROUGH_INDICATOR, award.getFederalPassThroughIndicator());
1294             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.OLD_PROPOSAL_NUMBER, award.getOldProposalNumber());
1295             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.AWARD_DIRECT_COST_AMOUNT, award.getAwardDirectCostAmount());
1296             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.AWARD_INDIRECT_COST_AMOUNT, award.getAwardIndirectCostAmount());
1297             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.FEDERAL_FUNDED_AMOUNT, award.getFederalFundedAmount());
1298             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.AWARD_CREATE_TIMESTAMP, award.getAwardCreateTimestamp());
1299             if (ObjectUtils.isNotNull(award.getAwardClosingDate())) {
1300                 parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.AWARD_CLOSING_DATE, getDateTimeService().toDateString(award.getAwardClosingDate()));
1301             }
1302             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.PROPOSAL_AWARD_TYPE_CODE, award.getProposalAwardTypeCode());
1303             parameterMap.put(KFSPropertyConstants.AWARD+"."+KFSPropertyConstants.AWARD_STATUS_CODE, award.getAwardStatusCode());
1304             if (ObjectUtils.isNotNull(award.getLetterOfCreditFund())) {
1305                 parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.LETTER_OF_CREDIT_FUND_GROUP_CODE, award.getLetterOfCreditFund().getLetterOfCreditFundGroupCode());
1306             }
1307             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.LETTER_OF_CREDIT_FUND_CODE, award.getLetterOfCreditFundCode());
1308             parameterMap.put(KFSPropertyConstants.AWARD+"."+KFSPropertyConstants.GRANT_DESCRIPTION_CODE, award.getGrantDescriptionCode());
1309             if (ObjectUtils.isNotNull(award.getProposal())) {
1310                 parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.GRANT_NUMBER, award.getProposal().getGrantNumber());
1311             }
1312             parameterMap.put(KFSPropertyConstants.AGENCY_NUMBER, award.getAgencyNumber());
1313             parameterMap.put(KFSPropertyConstants.AGENCY+"."+KFSPropertyConstants.FULL_NAME, award.getAgency().getFullName());
1314             parameterMap.put(KFSPropertyConstants.AWARD+"."+KFSPropertyConstants.FEDERAL_PASS_THROUGH_AGENCY_NUMBER, award.getFederalPassThroughAgencyNumber());
1315             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.AGENCY_ANALYST_NAME, award.getAgencyAnalystName());
1316             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.ANALYST_TELEPHONE_NUMBER, award.getAnalystTelephoneNumber());
1317             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.BILLING_FREQUENCY_CODE, award.getBillingFrequencyCode());
1318             parameterMap.put(KFSPropertyConstants.AWARD+"."+KFSPropertyConstants.AWARD_PROJECT_TITLE, award.getAwardProjectTitle());
1319             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.AWARD_PURPOSE_CODE, award.getAwardPurposeCode());
1320             parameterMap.put(KFSPropertyConstants.AWARD+"."+KFSPropertyConstants.ACTIVE, award.isActive());
1321             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.KIM_GROUP_NAMES, award.getKimGroupNames());
1322             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.ROUTING_ORG, award.getRoutingOrg());
1323             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.ROUTING_CHART, award.getRoutingChart());
1324             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.EXCLUDED_FROM_INVOICING, award.isExcludedFromInvoicing());
1325             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.ADDITIONAL_FORMS_REQUIRED, award.isAdditionalFormsRequiredIndicator());
1326             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.ADDITIONAL_FORMS_DESCRIPTION, award.getAdditionalFormsDescription());
1327             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.INSTRUMENT_TYPE_CODE, award.getInstrumentTypeCode());
1328             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.MIN_INVOICE_AMOUNT, award.getMinInvoiceAmount());
1329             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.AUTO_APPROVE, award.getAutoApproveIndicator());
1330             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.LOOKUP_PERSON_UNIVERSAL_IDENTIFIER, award.getLookupPersonUniversalIdentifier());
1331             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.LOOKUP_PERSON, award.getLookupPerson().getPrincipalName());
1332             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.USER_LOOKUP_ROLE_NAMESPACE_CODE, award.getUserLookupRoleNamespaceCode());
1333             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.USER_LOOKUP_ROLE_NAME, award.getUserLookupRoleName());
1334             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.FUNDING_EXPIRATION_DATE, award.getFundingExpirationDate());
1335             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.STOP_WORK_INDICATOR, award.isStopWorkIndicator());
1336             parameterMap.put(KFSPropertyConstants.AWARD+"."+KFSPropertyConstants.STOP_WORK_REASON, award.getStopWorkReason());
1337             if (ObjectUtils.isNotNull(award.getAwardPrimaryProjectDirector())) {
1338                 parameterMap.put(KFSPropertyConstants.AWARD+"."+ArConstants.AWARD_PROJECT_DIRECTOR+"."+KFSPropertyConstants.NAME, award.getAwardPrimaryProjectDirector().getProjectDirector().getName());
1339             }
1340             parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.LETTER_OF_CREDIT_FUND_CODE, award.getLetterOfCreditFundCode());
1341             if (ObjectUtils.isNotNull(award.getAwardPrimaryFundManager())) {
1342                 parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.PRIMARY_FUND_MANAGER+"."+KFSPropertyConstants.NAME, award.getAwardPrimaryFundManager().getFundManager().getName());
1343                 parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.PRIMARY_FUND_MANAGER+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.EMAIL, award.getAwardPrimaryFundManager().getFundManager().getEmailAddress());
1344                 parameterMap.put(KFSPropertyConstants.AWARD+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.PRIMARY_FUND_MANAGER+"."+ArPropertyConstants.ContractsAndGrantsBillingAwardFields.PHONE, award.getAwardPrimaryFundManager().getFundManager().getPhoneNumber());
1345             }
1346             if (ObjectUtils.isNotNull(document.getInvoiceGeneralDetail())) {
1347                 parameterMap.put(ArPropertyConstants.TOTAL_AMOUNT_DUE, receivable.add(document.getInvoiceGeneralDetail().getTotalAmountBilledToDate()));
1348             }
1349         }
1350         return new PdfFormattingMap(parameterMap);
1351     }
1352 
1353     /**
1354      * returns proper contract control Account Number.
1355      *
1356      * @return
1357      */
1358     protected String getRecipientAccountNumber(List<InvoiceAccountDetail> accountDetails) {
1359         if (CollectionUtils.isNotEmpty(accountDetails)) {
1360             if (ObjectUtils.isNull(accountDetails.get(0).getContractControlAccountNumber())) {
1361                 return accountDetails.get(0).getAccountNumber();
1362             }
1363             return accountDetails.get(0).getContractControlAccountNumber();
1364         }
1365         return null;
1366     }
1367 
1368     /**
1369      * This method sets the last billed date to Award and Award Account objects based on the status of the invoice. Final or
1370      * Corrected.
1371      *
1372      * @param document
1373      */
1374     @Override
1375     public void updateLastBilledDate(ContractsGrantsInvoiceDocument document) {
1376         boolean isFinalBill = document.getInvoiceGeneralDetail().isFinalBillIndicator();
1377 
1378         // To calculate and update Last Billed Date based on the status of the invoice. Final or Corrected.
1379         // 1. Set last Billed Date to Award Accounts
1380 
1381         Iterator<InvoiceAccountDetail> iterator = document.getAccountDetails().iterator();
1382         while (iterator.hasNext()) {
1383             InvoiceAccountDetail id = iterator.next();
1384             if (isFinalBill) {
1385                 setAwardAccountFinalBilledValueAndLastBilledDate(id, true, document.getInvoiceGeneralDetail().getProposalNumber(), document.isInvoiceReversal(), document.getInvoiceGeneralDetail().getLastBilledDate());
1386             } else {
1387                 calculateAwardAccountLastBilledDate(id, document.isInvoiceReversal(), document.getInvoiceGeneralDetail().getLastBilledDate(), document.getInvoiceGeneralDetail().getProposalNumber());
1388             }
1389         }
1390 
1391         // 2. Set last Billed to Award = least of last billed date of award account.
1392         Long proposalNumber = document.getInvoiceGeneralDetail().getProposalNumber();
1393         Map<String, Object> map = new HashMap<String, Object>();
1394         map.put(KFSPropertyConstants.PROPOSAL_NUMBER, proposalNumber);
1395         ContractsAndGrantsBillingAward award = kualiModuleService.getResponsibleModuleService(ContractsAndGrantsBillingAward.class).getExternalizableBusinessObject(ContractsAndGrantsBillingAward.class, map);
1396 
1397         if (CollectionUtils.isNotEmpty(award.getActiveAwardAccounts())) {
1398             // To set last billed Date to award.
1399             contractsAndGrantsModuleBillingService.setLastBilledDateToAward(proposalNumber, getLastBilledDate(award));
1400         }
1401 
1402     }
1403 
1404     /**
1405      * @see org.kuali.kfs.module.ar.document.service.ContractsGrantsInvoiceDocumentService#getLastBilledDate(java.lang.Long)
1406      */
1407     @Override
1408     public java.sql.Date getLastBilledDate(ContractsAndGrantsBillingAward award) {
1409         java.sql.Date awdLastBilledDate = null;
1410 
1411         if (ObjectUtils.isNotNull(award)) {
1412             // last Billed of Award = least of last billed date of award account.
1413             ContractsAndGrantsBillingAwardAccount awardAccount;
1414 
1415             if (CollectionUtils.isNotEmpty(award.getActiveAwardAccounts())) {
1416                 ContractsAndGrantsBillingAwardAccount firstActiveawardAccount = award.getActiveAwardAccounts().get(0);
1417 
1418                 awardAccount = firstActiveawardAccount;
1419                 awdLastBilledDate = firstActiveawardAccount.getCurrentLastBilledDate();
1420 
1421                 for (int i = 0; i < award.getActiveAwardAccounts().size(); i++) {
1422                     if (ObjectUtils.isNull(awdLastBilledDate) || ObjectUtils.isNull(award.getActiveAwardAccounts().get(i).getCurrentLastBilledDate())) {
1423                         // The dates would be null if the user is correcting the first invoice created for the award.
1424                         // Then the award last billed date should also be null.
1425                         awdLastBilledDate = null;
1426                     }
1427                     else if (ObjectUtils.isNotNull(awdLastBilledDate) && ObjectUtils.isNotNull(award.getActiveAwardAccounts().get(i).getCurrentLastBilledDate())) {
1428                         if (awdLastBilledDate.after(award.getActiveAwardAccounts().get(i).getCurrentLastBilledDate())) {
1429                             awdLastBilledDate = award.getActiveAwardAccounts().get(i).getCurrentLastBilledDate();
1430                         }
1431                     }
1432                 }
1433             }
1434         }
1435 
1436         return awdLastBilledDate;
1437     }
1438 
1439     /**
1440      * This method updates the AwardAccount object's last billed Variable with the value provided
1441      *
1442      * @param id
1443      * @param invoiceStatus
1444      * @param lastBilledDate
1445      * @param proposalNumber
1446      */
1447     protected void calculateAwardAccountLastBilledDate(InvoiceAccountDetail id, boolean invoiceReversal, java.sql.Date lastBilledDate, Long proposalNumber) {
1448         Map<String, Object> mapKey = new HashMap<String, Object>();
1449         mapKey.put(KFSPropertyConstants.ACCOUNT_NUMBER, id.getAccountNumber());
1450         mapKey.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, id.getChartOfAccountsCode());
1451         mapKey.put(KFSPropertyConstants.PROPOSAL_NUMBER, proposalNumber);
1452         // To set previous and current last Billed Date for award account .
1453         contractsAndGrantsModuleBillingService.setLastBilledDateToAwardAccount(mapKey, invoiceReversal, lastBilledDate);
1454 
1455     }
1456 
1457     /**
1458      * This method updates the Bills and Milestone objects billed Field.
1459      *
1460      * @param billed
1461      */
1462     @Override
1463     public void updateBillsAndMilestones(boolean billed, List<InvoiceMilestone> invoiceMilestones, List<InvoiceBill> invoiceBills) {
1464         updateMilestonesBilledIndicator(billed, invoiceMilestones);
1465         updateBillsBilledIndicator(billed, invoiceBills);
1466     }
1467 
1468     /**
1469      * Update Milestone objects billed value.
1470      * @param billed
1471      * @param invoiceMilestones
1472      */
1473     @Override
1474     public void updateMilestonesBilledIndicator(boolean billed, List<InvoiceMilestone> invoiceMilestones) {
1475         if (CollectionUtils.isNotEmpty(invoiceMilestones)) {
1476             List<Long> milestoneIds = new ArrayList<Long>();
1477             for (InvoiceMilestone invoiceMilestone : invoiceMilestones) {
1478                 milestoneIds.add(invoiceMilestone.getMilestoneIdentifier());
1479             }
1480 
1481             if (CollectionUtils.isNotEmpty(milestoneIds)) {
1482                 setMilestonesBilled(milestoneIds, billed);
1483             }
1484         }
1485     }
1486 
1487     /**
1488      * This method updates value of billed in Milestone BO to the value of the billed parameter
1489      *
1490      * @param proposalNumber
1491      * @param milestoneIds
1492      * @param billed
1493      */
1494     protected void setMilestonesBilled(List<Long> milestoneIds, boolean billed) {
1495         List<Milestone> milestones = null;
1496         Map<String, Object> fieldValues = new HashMap<String, Object>();
1497         fieldValues.put(ArPropertyConstants.MilestoneFields.MILESTONE_IDENTIFIER, milestoneIds);
1498         milestones = (List<Milestone>)getBusinessObjectService().findMatching(Milestone.class, fieldValues);
1499 
1500         if (ObjectUtils.isNotNull(milestones)) {
1501             for (Milestone milestone : milestones) {
1502                 milestone.setBilled(billed);
1503             }
1504             getBusinessObjectService().save(milestones);
1505         }
1506     }
1507 
1508     /**
1509      * Update Bill objects billed value.
1510      * @param billed
1511      * @param invoiceBills
1512      */
1513     @Override
1514     public void updateBillsBilledIndicator(boolean billed, List<InvoiceBill> invoiceBills) {
1515         if (CollectionUtils.isNotEmpty(invoiceBills)) {
1516             List<Long> billIds = new ArrayList<Long>();
1517             for (InvoiceBill invoiceBill : invoiceBills) {
1518                 billIds.add(invoiceBill.getBillIdentifier());
1519             }
1520 
1521             if (CollectionUtils.isNotEmpty(invoiceBills)) {
1522                 setBillsBilled(billIds, billed);
1523             }
1524         }
1525     }
1526 
1527     /**
1528      * This method updates value of billed in Bill BO to billed
1529      *
1530      * @param fieldValuesList
1531      * @param billed
1532      */
1533     protected void setBillsBilled(List<Long> billIds, boolean billed) {
1534         List<Bill> bills = null;
1535         Map<String, Object> fieldValues = new HashMap<String, Object>();
1536         fieldValues.put(ArPropertyConstants.BillFields.BILL_IDENTIFIER, billIds);
1537         bills = (List<Bill>)getBusinessObjectService().findMatching(Bill.class, fieldValues);
1538 
1539         if (ObjectUtils.isNotNull(bills)) {
1540             for (Bill bill : bills) {
1541                 bill.setBilled(billed);
1542             }
1543             getBusinessObjectService().save(bills);
1544         }
1545     }
1546 
1547 
1548     /**
1549      * This method updates the ContractsAndGrantsBillingAwardAccount object's FinalBilled Variable with the value provided
1550      *
1551      * @param id
1552      * @param value
1553      * @param proposalNumber
1554      */
1555     protected void setAwardAccountFinalBilledValue(InvoiceAccountDetail id, boolean value, Long proposalNumber) {
1556         Map<String, Object> mapKey = new HashMap<String, Object>();
1557         mapKey.put(KFSPropertyConstants.ACCOUNT_NUMBER, id.getAccountNumber());
1558         mapKey.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, id.getChartOfAccountsCode());
1559         mapKey.put(KFSPropertyConstants.PROPOSAL_NUMBER, proposalNumber);
1560 
1561         // To set final Billed to award Account
1562         contractsAndGrantsModuleBillingService.setFinalBilledToAwardAccount(mapKey, value);
1563     }
1564 
1565     /**
1566      * This method updates the ContractsAndGrantsBillingAwardAccount object's FinalBilled Variable with the value provided
1567      * and also sets the last billed date and invoice status.
1568      *
1569      * @param id
1570      * @param finalBilled
1571      * @param proposalNumber
1572      * @param invoiceStatus
1573      * @param lastBilledDate
1574      */
1575     protected void setAwardAccountFinalBilledValueAndLastBilledDate(InvoiceAccountDetail id, boolean finalBilled, Long proposalNumber, boolean invoiceReversal, java.sql.Date lastBilledDate) {
1576         Map<String, Object> mapKey = new HashMap<String, Object>();
1577         mapKey.put(KFSPropertyConstants.ACCOUNT_NUMBER, id.getAccountNumber());
1578         mapKey.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, id.getChartOfAccountsCode());
1579         mapKey.put(KFSPropertyConstants.PROPOSAL_NUMBER, proposalNumber);
1580 
1581         // To set previous and current last Billed Date for award account .
1582         contractsAndGrantsModuleBillingService.setFinalBilledAndLastBilledDateToAwardAccount(mapKey, finalBilled, invoiceReversal, lastBilledDate);
1583     }
1584 
1585     /**
1586      * This method updates AwardAccounts
1587      */
1588     @Override
1589     public void updateUnfinalizationToAwardAccount(List<InvoiceAccountDetail> accountDetails, Long proposalNumber) {
1590         Iterator iterator = accountDetails.iterator();
1591         while (iterator.hasNext()) {
1592             InvoiceAccountDetail id = (InvoiceAccountDetail) iterator.next();
1593             setAwardAccountFinalBilledValue(id, false, proposalNumber);
1594         }
1595     }
1596 
1597     /**
1598      * Corrects the Contracts & Grants Invoice Document.
1599      *
1600      * @throws WorkflowException
1601      */
1602     @Override
1603     public void correctContractsGrantsInvoiceDocument(ContractsGrantsInvoiceDocument document) throws WorkflowException {
1604         // correct Direct Cost Invoice Details.
1605         for (ContractsGrantsInvoiceDetail id : document.getDirectCostInvoiceDetails()) {
1606             correctInvoiceDetail(id);
1607         }
1608 
1609         // correct Indirect Cost Invoice Details.
1610         for (ContractsGrantsInvoiceDetail id : document.getIndirectCostInvoiceDetails()) {
1611             correctInvoiceDetail(id);
1612         }
1613 
1614         // update correction to the InvoiceAccountDetail objects
1615         for (InvoiceAccountDetail id : document.getAccountDetails()) {
1616             correctInvoiceAccountDetail(id);
1617         }
1618 
1619         // correct invoiceDetailAccountObjectCode.
1620         for (InvoiceDetailAccountObjectCode invoiceDetailAccountObjectCode : document.getInvoiceDetailAccountObjectCodes()) {
1621             invoiceDetailAccountObjectCode.correctInvoiceDetailAccountObjectCodeExpenditureAmount();
1622         }
1623 
1624         // correct Bills
1625         KualiDecimal totalBillingAmount = KualiDecimal.ZERO;
1626         for (InvoiceBill bill : document.getInvoiceBills()) {
1627             bill.setEstimatedAmount(bill.getEstimatedAmount().negated());
1628             totalBillingAmount = totalBillingAmount.add(bill.getEstimatedAmount());
1629         }
1630 
1631         // correct Milestones
1632         KualiDecimal totalMilestonesAmount = KualiDecimal.ZERO;
1633         for (InvoiceMilestone milestone : document.getInvoiceMilestones()) {
1634             milestone.setMilestoneAmount(milestone.getMilestoneAmount().negated());
1635             totalMilestonesAmount = totalMilestonesAmount.add(milestone.getMilestoneAmount());
1636         }
1637 
1638         // set the billed to Date Field
1639         if (document.getInvoiceGeneralDetail().getBillingFrequencyCode().equalsIgnoreCase(ArConstants.MILESTONE_BILLING_SCHEDULE_CODE) && CollectionUtils.isNotEmpty(document.getInvoiceMilestones())) {
1640             // check if award has milestones
1641             document.getInvoiceGeneralDetail().setTotalPreviouslyBilled(getMilestonesBilledToDateAmount(document.getInvoiceGeneralDetail().getProposalNumber()));
1642             // update the new total billed for the invoice.
1643             document.getInvoiceGeneralDetail().setTotalAmountBilledToDate(document.getInvoiceGeneralDetail().getTotalAmountBilledToDate().add(totalMilestonesAmount));
1644         }
1645         else if (document.getInvoiceGeneralDetail().getBillingFrequencyCode().equalsIgnoreCase(ArConstants.PREDETERMINED_BILLING_SCHEDULE_CODE) && CollectionUtils.isNotEmpty(document.getInvoiceBills())) {
1646             // check if award has bills
1647             document.getInvoiceGeneralDetail().setTotalPreviouslyBilled(getPredeterminedBillingBilledToDateAmount(document.getInvoiceGeneralDetail().getProposalNumber()));
1648             // update the new total billed for the invoice.
1649             document.getInvoiceGeneralDetail().setTotalAmountBilledToDate(document.getInvoiceGeneralDetail().getTotalAmountBilledToDate().add(totalBillingAmount));
1650         }
1651         else {
1652             document.getInvoiceGeneralDetail().setTotalPreviouslyBilled(getAwardBilledToDateAmountByProposalNumber(document.getInvoiceGeneralDetail().getProposalNumber()));
1653             KualiDecimal newTotalBilled = document.getTotalCostInvoiceDetail().getInvoiceAmount().add(document.getInvoiceGeneralDetail().getTotalPreviouslyBilled());
1654             newTotalBilled = newTotalBilled.add(getOtherTotalBilledForAwardPeriod(document));
1655             document.getInvoiceGeneralDetail().setTotalAmountBilledToDate(newTotalBilled);
1656         }
1657 
1658         for (InvoiceAddressDetail invoiceAddressDetail: document.getInvoiceAddressDetails()) {
1659             invoiceAddressDetail.setInitialTransmissionDate(null);
1660         }
1661     }
1662 
1663     /**
1664      * Error corrects an invoice detail
1665      * @param invoiceDetail the invoice detail to error correct
1666      */
1667     protected void correctInvoiceDetail(ContractsGrantsInvoiceDetail invoiceDetail) {
1668         invoiceDetail.setTotalPreviouslyBilled(invoiceDetail.getTotalPreviouslyBilled().add(invoiceDetail.getInvoiceAmount()));
1669         invoiceDetail.setCumulativeExpenditures(invoiceDetail.getCumulativeExpenditures().subtract(invoiceDetail.getInvoiceAmount()));
1670         invoiceDetail.setInvoiceAmount(invoiceDetail.getInvoiceAmount().negated());
1671         invoiceDetail.setInvoiceDocument(null);
1672     }
1673 
1674     /**
1675      * Error corrects an invoice account detail
1676      * @param invoiceAccountDetail the invoice account detail to error correct
1677      */
1678     protected void correctInvoiceAccountDetail(InvoiceAccountDetail invoiceAccountDetail) {
1679         invoiceAccountDetail.setTotalPreviouslyBilled(invoiceAccountDetail.getTotalPreviouslyBilled().add(invoiceAccountDetail.getInvoiceAmount()));
1680         invoiceAccountDetail.setCumulativeExpenditures(invoiceAccountDetail.getCumulativeExpenditures().subtract(invoiceAccountDetail.getInvoiceAmount()));
1681         invoiceAccountDetail.setInvoiceAmount(invoiceAccountDetail.getInvoiceAmount().negated());
1682         invoiceAccountDetail.setInvoiceDocument(null);
1683     }
1684 
1685     /**
1686      * This method returns a list of character strings that represent base 36 integers from start(non-inclusive) to limit
1687      * (inclusive).
1688      *
1689      * @param start the starting point of the list. This value is not included in the list.
1690      * @param limit the ending point of the list. This value is included in the list
1691      * @return the list of strings
1692      * @throws IllegalArgumentException if start is not less than limit
1693      */
1694     protected List<String> incrementAlphaNumericString(String stringToIncrement, String stringLimit) throws IllegalArgumentException {
1695         int startInt = Integer.parseInt(stringToIncrement, 36);
1696         int limitInt = Integer.parseInt(stringLimit, 36);
1697         if (startInt >= limitInt) {
1698             throw new IllegalArgumentException("Starting code must be less than limit code.");
1699         }
1700         List<String> retval = new ArrayList<String>();
1701         for (int i = startInt + 1; i <= limitInt; i++) {
1702             // format below forces the string back to 4 characters and replace makes the extra
1703             // characters '0'
1704             retval.add(String.format("%4s", Integer.toString(i, 36)).replace('\u0020', '0'));
1705         }
1706         return retval;
1707     }
1708 
1709     protected void addNoteForInvoiceReportFail(ContractsGrantsInvoiceDocument document) {
1710         Note note = new Note();
1711         note.setNotePostedTimestampToCurrent();
1712         note.setNoteText(configurationService.getPropertyValueAsString(ArKeyConstants.ERROR_FILE_UPLOAD_NO_PDF_FILE_SELECTED_FOR_SAVE));
1713         note.setNoteTypeCode(KFSConstants.NoteTypeEnum.BUSINESS_OBJECT_NOTE_TYPE.getCode());
1714         Person systemUser = personService.getPersonByPrincipalName(KFSConstants.SYSTEM_USER);
1715         note = noteService.createNote(note, document.getNoteTarget(), systemUser.getPrincipalId());
1716         noteService.save(note);
1717         document.addNote(note);
1718     }
1719 
1720     @Override
1721     public List<String> checkAwardContractControlAccounts(ContractsAndGrantsBillingAward award) {
1722         List<String> errorString = new ArrayList<String>();
1723         boolean isValid = true;
1724         int accountNum = award.getActiveAwardAccounts().size();
1725         // To check if invoicing options exist on the award
1726         if (ObjectUtils.isNotNull(award.getInvoicingOptionCode())) {
1727 
1728             // To check if the award account is associated with a contract control account.
1729             for (ContractsAndGrantsBillingAwardAccount awardAccount : award.getActiveAwardAccounts()) {
1730                 if (ObjectUtils.isNull(awardAccount.getAccount().getContractControlAccount())) {
1731                     isValid = false;
1732                     break;
1733                 }
1734             }
1735 
1736             // if the Invoicing option is "By Contract Control Account" and there are no contract control accounts for one / all
1737             // award accounts, then throw error.
1738             if (award.getInvoicingOptionCode().equalsIgnoreCase(ArConstants.INV_CONTRACT_CONTROL_ACCOUNT)) {
1739                 if (!isValid) {
1740                     errorString.add(ArKeyConstants.AwardConstants.ERROR_NO_CTRL_ACCT);
1741                     errorString.add(award.getInvoicingOptionDescription());
1742                 }
1743             }
1744 
1745             // if the Invoicing option is "By Award" and there are no contract control accounts for one / all award accounts, then
1746             // throw error.
1747             else if (award.getInvoicingOptionCode().equalsIgnoreCase(ArConstants.INV_AWARD)) {
1748                 if (!isValid) {
1749                     errorString.add(ArKeyConstants.AwardConstants.ERROR_NO_CTRL_ACCT);
1750                     errorString.add(award.getInvoicingOptionDescription());
1751                 }
1752                 else {
1753                     if (accountNum != 1) {
1754                         Account tmpAcct1, tmpAcct2;
1755 
1756                         Object[] awardAccounts = award.getActiveAwardAccounts().toArray();
1757                         for (int i = 0; i < awardAccounts.length - 1; i++) {
1758                             tmpAcct1 = ((ContractsAndGrantsBillingAwardAccount) awardAccounts[i]).getAccount().getContractControlAccount();
1759                             tmpAcct2 = ((ContractsAndGrantsBillingAwardAccount) awardAccounts[i + 1]).getAccount().getContractControlAccount();
1760                             // if the Invoicing option is "By Award" and there are more than one contract control account assigned
1761                             // for the award, then throw error.
1762                             if (ObjectUtils.isNull(tmpAcct1) || !tmpAcct1.equals(tmpAcct2)) {
1763                                 errorString.add(ArKeyConstants.AwardConstants.ERROR_MULTIPLE_CTRL_ACCT);
1764                                 errorString.add(award.getInvoicingOptionDescription());
1765                             }
1766                         }
1767                     }
1768                 }
1769             }
1770         }
1771         return errorString;
1772     }
1773 
1774     /**
1775      * Determines whether the given ContractsGrantsInvoiceDocument is "effective" or not: if it is disapproved, cancelled, or error corrected then it is NOT effective,
1776      * and in all other cases, it is effective
1777      * @param invoiceDocument the invoice document to check
1778      * @return true if the document is "effective" given the rules above, false otherwise
1779      */
1780     @Override
1781     public boolean isInvoiceDocumentEffective(String documentNumber) {
1782         final FinancialSystemDocumentHeader invoiceDocHeader = getBusinessObjectService().findBySinglePrimaryKey(FinancialSystemDocumentHeader.class, documentNumber);
1783         final String documentStatus = invoiceDocHeader.getWorkflowDocumentStatusCode();
1784         if (StringUtils.isBlank(invoiceDocHeader.getFinancialDocumentInErrorNumber()) && !StringUtils.equals(documentStatus, DocumentStatus.CANCELED.getCode()) && !StringUtils.equals(documentStatus, DocumentStatus.DISAPPROVED.getCode())) { // skip error correcting CINVs, as they should be taken care of by the error correcting code
1785             final DocumentHeader correctingDocumentHeader = getFinancialSystemDocumentService().getCorrectingDocumentHeader(documentNumber);
1786             if (ObjectUtils.isNull(correctingDocumentHeader) || isCorrectedInvoiceDocumentEffective(correctingDocumentHeader.getDocumentNumber())) {
1787                 return true;
1788             }
1789         }
1790         return false;
1791     }
1792 
1793     /**
1794      * Determines if an error correction is "effective" - ie, currently locking resources like milestones and pre-determined billing from the original CINV
1795      * @param errorCorrectionDocumentNumber the document number to check for the effectiveness of
1796      * @return true if the document is effectively locking resources, false otherwise
1797      */
1798     protected boolean isCorrectedInvoiceDocumentEffective(String errorCorrectionDocumentNumber) {
1799         final FinancialSystemDocumentHeader invoiceDocHeader = getBusinessObjectService().findBySinglePrimaryKey(FinancialSystemDocumentHeader.class, errorCorrectionDocumentNumber);
1800         final String documentStatus = invoiceDocHeader.getWorkflowDocumentStatusCode();
1801         if (getFinancialSystemDocumentService().getPendingDocumentStatuses().contains(documentStatus)) {
1802             return true; // the error correction document is currently pending, then it has not yet freed the milestones and pre-billings on the original CINV, so it's effective
1803         }
1804         final DocumentHeader correctingDocumentHeader = getFinancialSystemDocumentService().getCorrectingDocumentHeader(errorCorrectionDocumentNumber);
1805         if (!ObjectUtils.isNull(correctingDocumentHeader) && isCorrectedInvoiceDocumentEffective(correctingDocumentHeader.getDocumentNumber())) {
1806             return true; // is the error correction currently undergoing error correction itself?  Then recheck the rules on the newer error corrector to see if this document is effective or not
1807         }
1808         return false; // the error correction document is not effective and has freed resources
1809     }
1810 
1811     /**
1812      * @see org.kuali.kfs.module.ar.document.service.ContractsGrantsInvoiceDocumentService#isTemplateValidForContractsGrantsInvoiceDocument(org.kuali.kfs.module.ar.businessobject.InvoiceTemplate, org.kuali.kfs.module.ar.document.ContractsGrantsInvoiceDocument)
1813      */
1814     @Override
1815     public boolean isTemplateValidForContractsGrantsInvoiceDocument(InvoiceTemplate invoiceTemplate, ContractsGrantsInvoiceDocument contractsGrantsInvoiceDocument) {
1816         if (ObjectUtils.isNotNull(contractsGrantsInvoiceDocument)) {
1817             return StringUtils.equals(invoiceTemplate.getBillByChartOfAccountCode(), contractsGrantsInvoiceDocument.getBillByChartOfAccountCode()) && StringUtils.equals(invoiceTemplate.getBilledByOrganizationCode(),contractsGrantsInvoiceDocument.getBilledByOrganizationCode());
1818         }
1819         return true;
1820     }
1821 
1822     /**
1823      * @see org.kuali.kfs.module.ar.document.service.ContractsGrantsInvoiceDocumentService#doesCostCategoryContainObjectCode(org.kuali.kfs.module.ar.businessobject.CostCategory, java.lang.String, java.lang.String)
1824      */
1825     @Override
1826     public boolean doesCostCategoryContainObjectCode(CostCategory category, String chartOfAccountsCode, String objectCode) {
1827         if (!CollectionUtils.isEmpty(category.getObjectCodes())) {
1828             for (CostCategoryObjectCode categoryObjectCode : category.getObjectCodes()) {
1829                 if (StringUtils.equals(categoryObjectCode.getChartOfAccountsCode(), chartOfAccountsCode) && StringUtils.equals(categoryObjectCode.getFinancialObjectCode(), objectCode)) {
1830                     return true;
1831                 }
1832             }
1833         }
1834 
1835         if (!CollectionUtils.isEmpty(category.getObjectLevels())) {
1836             for (CostCategoryObjectLevel categoryObjectLevel : category.getObjectLevels()) {
1837                 if (getObjectCodeService().doesObjectLevelContainObjectCode(categoryObjectLevel.getChartOfAccountsCode(), categoryObjectLevel.getFinancialObjectLevelCode(), chartOfAccountsCode, objectCode)) {
1838                     return true;
1839                 }
1840             }
1841         }
1842 
1843         if (!CollectionUtils.isEmpty(category.getObjectConsolidations())) {
1844             for (CostCategoryObjectConsolidation categoryObjectConsolidation : category.getObjectConsolidations()) {
1845                 if (getObjectCodeService().doesObjectConsolidationContainObjectCode(categoryObjectConsolidation.getChartOfAccountsCode(), categoryObjectConsolidation.getFinConsolidationObjectCode(), chartOfAccountsCode, objectCode)) {
1846                     return true;
1847                 }
1848             }
1849         }
1850 
1851         return false;
1852     }
1853 
1854     /**
1855      * @see org.kuali.kfs.module.ar.document.service.ContractsGrantsInvoiceDocumentService#getInvoicesByAward(java.util.Collection)
1856      */
1857     @Override
1858     public Map<Long, List<ContractsGrantsInvoiceDocument>> getInvoicesByAward(Collection<ContractsGrantsInvoiceDocument> invoices) {
1859         Map<Long, List<ContractsGrantsInvoiceDocument>> invoicesByAward = new HashMap<>();
1860         for (ContractsGrantsInvoiceDocument invoice : invoices) {
1861             Long proposalNumber = invoice.getInvoiceGeneralDetail().getProposalNumber();
1862             if (invoicesByAward.containsKey(proposalNumber)) {
1863                 invoicesByAward.get(proposalNumber).add(invoice);
1864             }
1865             else {
1866                 List<ContractsGrantsInvoiceDocument> invoicesByProposalNumber = new ArrayList<ContractsGrantsInvoiceDocument>();
1867                 invoicesByProposalNumber.add(invoice);
1868                 invoicesByAward.put(proposalNumber, invoicesByProposalNumber);
1869             }
1870         }
1871         return invoicesByAward;
1872     }
1873 
1874     /**
1875      * If the document has only one CustomerInvoiceDetail, update the amount with the total expenditures from the cost categories; otherwise,
1876      * split the amounts by account
1877      * @see org.kuali.kfs.module.ar.document.service.ContractsGrantsInvoiceDocumentService#recalculateSourceAccountingLineTotals(org.kuali.kfs.module.ar.document.ContractsGrantsInvoiceDocument)
1878      */
1879     @Override
1880     public void recalculateSourceAccountingLineTotals(ContractsGrantsInvoiceDocument contractsGrantsInvoiceDocument) {
1881         if (!CollectionUtils.isEmpty(contractsGrantsInvoiceDocument.getSourceAccountingLines())) {
1882             if (contractsGrantsInvoiceDocument.getSourceAccountingLines().size() == 1) {
1883                 final CustomerInvoiceDetail customerInvoiceDetail = (CustomerInvoiceDetail)contractsGrantsInvoiceDocument.getSourceAccountingLine(0);
1884                 customerInvoiceDetail.setAmount(getAccountingLineAmountForDocument(contractsGrantsInvoiceDocument));
1885             } else {
1886                 final Map<String, KualiDecimal> accountExpenditureAmounts = getCategoryExpenditureAmountsForInvoiceAccountDetail(contractsGrantsInvoiceDocument);
1887                 for (Object al : contractsGrantsInvoiceDocument.getSourceAccountingLines()) {
1888                     final CustomerInvoiceDetail customerInvoiceDetail = (CustomerInvoiceDetail)al;
1889                     final String accountKey = StringUtils.join(new String[] { customerInvoiceDetail.getChartOfAccountsCode(), customerInvoiceDetail.getAccountNumber() }, "-");
1890                     customerInvoiceDetail.setAmount(accountExpenditureAmounts.get(accountKey));
1891                 }
1892             }
1893         }
1894     }
1895 
1896     /**
1897      * Checks the KFS-AR / ContractsGrantsInvoiceDocumentBatchStep / User parameter, or, if no value is found for that parameter, the KFS system user to see if they are the
1898      * initiator of the document.  If the KFS-AR / ContractsGrantsInvoiceDocumentBatchStep / User parameter is set to a value of a principal who creates CINVs outside of batch
1899      * - well, okay, that's an interesting business process, that also means that the results of this method may be incorrect.  But you can always override, right?
1900      * @see org.kuali.kfs.module.ar.document.service.ContractsGrantsInvoiceDocumentService#isDocumentBatchCreated(org.kuali.kfs.module.ar.document.ContractsGrantsInvoiceDocument)
1901      */
1902     @Override
1903     public boolean isDocumentBatchCreated(ContractsGrantsInvoiceDocument document) {
1904         if (document.getInvoiceGeneralDetail().getAward().getAutoApproveIndicator()) {
1905             final Principal batchJobInitiatorPrincipal = getContractsGrantsInvoiceBatchCreationUserPrincipal();
1906             return StringUtils.equalsIgnoreCase(document.getFinancialSystemDocumentHeader().getInitiatorPrincipalId(), batchJobInitiatorPrincipal.getPrincipalId());
1907         }
1908         return false;
1909     }
1910 
1911     /**
1912      * @see org.kuali.kfs.module.ar.document.service.ContractsGrantsInvoiceDocumentService#doesInvoicePassValidation(org.kuali.kfs.module.ar.document.ContractsGrantsInvoiceDocument)
1913      */
1914     @Override
1915     public boolean doesInvoicePassValidation(final ContractsGrantsInvoiceDocument document) {
1916         try {
1917             final Boolean result = GlobalVariables.doInNewGlobalVariables(new UserSession(getContractsGrantsInvoiceBatchCreationUserPrincipal().getPrincipalName()), new Callable<Boolean>() {
1918                 /**
1919                  * Checks if the given document passes rule validation with no errors
1920                  * @see java.util.concurrent.Callable#call()
1921                  */
1922                 @Override
1923                 public Boolean call() throws Exception {
1924                     final AttributedRouteDocumentEvent routeEvent = new AttributedRouteDocumentEvent(document);
1925                     getKualiRuleService().applyRules(routeEvent);
1926                     return Boolean.valueOf(!GlobalVariables.getMessageMap().hasErrors());
1927                 }
1928 
1929             });
1930             return result.booleanValue();
1931         } catch (Exception e) {
1932             // validation actually caused an exception?  Um, let's log that, and maybe we'll say we didn't pass validation here, okay?
1933             LOG.error("Running validation on Contracts & Grants Invoice "+document.getDocumentNumber()+" caused an exception", e);
1934             return false;
1935         }
1936     }
1937 
1938     /**
1939      * Determines the user who creates CINV documents via the batch job
1940      * @return the principal for the user who creates CINV documents via the batch job
1941      */
1942     protected Principal getContractsGrantsInvoiceBatchCreationUserPrincipal() {
1943         final String batchJobInitiatorPrincipalName = getParameterService().getParameterValueAsString(ContractsGrantsInvoiceDocumentBatchStep.class, Job.STEP_USER_PARM_NM, KFSConstants.SYSTEM_USER);
1944         final Principal batchJobInitiatorPrincipal = getIdentityService().getPrincipalByPrincipalName(batchJobInitiatorPrincipalName);
1945         return ObjectUtils.isNull(batchJobInitiatorPrincipal)
1946                 ? getIdentityService().getPrincipalByPrincipalName(KFSConstants.SYSTEM_USER)
1947                 : batchJobInitiatorPrincipal;
1948     }
1949 
1950     public ContractsAndGrantsModuleBillingService getContractsAndGrantsModuleBillingService() {
1951         return contractsAndGrantsModuleBillingService;
1952     }
1953 
1954     public void setContractsAndGrantsModuleBillingService(ContractsAndGrantsModuleBillingService contractsAndGrantsModuleBillingService) {
1955         this.contractsAndGrantsModuleBillingService = contractsAndGrantsModuleBillingService;
1956     }
1957 
1958     public AccountService getAccountService() {
1959         return accountService;
1960     }
1961 
1962     /**
1963      * Sets the accountService attribute value.
1964      *
1965      * @param accountService The accountService to set.
1966      */
1967     public void setAccountService(AccountService accountService) {
1968         this.accountService = accountService;
1969     }
1970 
1971     /**
1972      * Gets the objectCodeService attribute.
1973      * @return Returns the objectCodeService.
1974      */
1975     public ObjectCodeService getObjectCodeService() {
1976         return objectCodeService;
1977     }
1978 
1979     /**
1980      * Sets the objectCodeService attribute value.
1981      * @param objectCodeService The objectCodeService to set.
1982      */
1983     public void setObjectCodeService(ObjectCodeService objectCodeService) {
1984         this.objectCodeService = objectCodeService;
1985     }
1986 
1987     /**
1988      * Gets the attachmentService attribute.
1989      *
1990      * @return Returns the attachmentService.
1991      */
1992     public AttachmentService getAttachmentService() {
1993         return attachmentService;
1994     }
1995 
1996     /**
1997      * Sets the attachmentService attribute value.
1998      *
1999      * @param attachmentService The attachmentService to set.
2000      */
2001     public void setAttachmentService(AttachmentService attachmentService) {
2002         this.attachmentService = attachmentService;
2003     }
2004 
2005     public NoteService getNoteService() {
2006         return noteService;
2007     }
2008 
2009     public void setNoteService(NoteService noteService) {
2010         this.noteService = noteService;
2011     }
2012 
2013     public KualiModuleService getKualiModuleService() {
2014         return kualiModuleService;
2015     }
2016 
2017     /**
2018      * Sets the kualiModuleService attribute value.
2019      *
2020      * @param kualiModuleService The kualiModuleService to set.
2021      */
2022     public void setKualiModuleService(KualiModuleService kualiModuleService) {
2023         this.kualiModuleService = kualiModuleService;
2024     }
2025 
2026     /**
2027      * @return
2028      */
2029     public ContractsGrantsInvoiceDocumentDao getContractsGrantsInvoiceDocumentDao() {
2030         return contractsGrantsInvoiceDocumentDao;
2031     }
2032 
2033     /**
2034      * @param contractsGrantsInvoiceDocumentDao
2035      */
2036     public void setContractsGrantsInvoiceDocumentDao(ContractsGrantsInvoiceDocumentDao contractsGrantsInvoiceDocumentDao) {
2037         this.contractsGrantsInvoiceDocumentDao = contractsGrantsInvoiceDocumentDao;
2038     }
2039 
2040     public ContractsGrantsBillingUtilityService getContractsGrantsBillingUtilityService() {
2041         return contractsGrantsBillingUtilityService;
2042     }
2043 
2044     public void setContractsGrantsBillingUtilityService(ContractsGrantsBillingUtilityService contractsGrantsBillingUtilityService) {
2045         this.contractsGrantsBillingUtilityService = contractsGrantsBillingUtilityService;
2046     }
2047 
2048     public FinancialSystemDocumentService getFinancialSystemDocumentService() {
2049         return financialSystemDocumentService;
2050     }
2051 
2052     public void setFinancialSystemDocumentService(FinancialSystemDocumentService financialSystemDocumentService) {
2053         this.financialSystemDocumentService = financialSystemDocumentService;
2054     }
2055 
2056     public List<SuspensionCategory> getSuspensionCategories() {
2057         return suspensionCategories;
2058     }
2059 
2060     public void setSuspensionCategories(List<SuspensionCategory> suspensionCategories) {
2061         this.suspensionCategories = suspensionCategories;
2062     }
2063 
2064     public BusinessObjectService getBusinessObjectService() {
2065         return businessObjectService;
2066     }
2067 
2068     public void setBusinessObjectService(BusinessObjectService businessObjectService) {
2069         this.businessObjectService = businessObjectService;
2070     }
2071 
2072     public ParameterService getParameterService() {
2073         return parameterService;
2074     }
2075 
2076     public void setParameterService(ParameterService parameterService) {
2077         this.parameterService = parameterService;
2078     }
2079 
2080     public UniversityDateService getUniversityDateService() {
2081         return universityDateService;
2082     }
2083 
2084     public void setUniversityDateService(UniversityDateService universityDateService) {
2085         this.universityDateService = universityDateService;
2086     }
2087 
2088     public ConfigurationService getConfigurationService() {
2089         return configurationService;
2090     }
2091 
2092     public void setConfigurationService(ConfigurationService configurationService) {
2093         this.configurationService = configurationService;
2094     }
2095 
2096     public DateTimeService getDateTimeService() {
2097         return dateTimeService;
2098     }
2099 
2100     public void setDateTimeService(DateTimeService dateTimeService) {
2101         this.dateTimeService = dateTimeService;
2102     }
2103 
2104     public PersonService getPersonService() {
2105         return personService;
2106     }
2107 
2108     public void setPersonService(PersonService personService) {
2109         this.personService = personService;
2110     }
2111 
2112     public DocumentService getDocumentService() {
2113         return documentService;
2114     }
2115 
2116     public void setDocumentService(DocumentService documentService) {
2117         this.documentService = documentService;
2118     }
2119 
2120     public AccountsReceivablePendingEntryService getAccountsReceivablePendingEntryService() {
2121         return accountsReceivablePendingEntryService;
2122     }
2123 
2124     public void setAccountsReceivablePendingEntryService(AccountsReceivablePendingEntryService accountsReceivablePendingEntryService) {
2125         this.accountsReceivablePendingEntryService = accountsReceivablePendingEntryService;
2126     }
2127 
2128     public OptionsService getOptionsService() {
2129         return optionsService;
2130     }
2131 
2132     public void setOptionsService(OptionsService optionsService) {
2133         this.optionsService = optionsService;
2134     }
2135 
2136     public KualiRuleService getKualiRuleService() {
2137         return kualiRuleService;
2138     }
2139 
2140     public void setKualiRuleService(KualiRuleService kualiRuleService) {
2141         this.kualiRuleService = kualiRuleService;
2142     }
2143 
2144     public IdentityService getIdentityService() {
2145         return identityService;
2146     }
2147 
2148     public void setIdentityService(IdentityService identityService) {
2149         this.identityService = identityService;
2150     }
2151 
2152     public CostCategoryService getCostCategoryService() {
2153         return costCategoryService;
2154     }
2155 
2156     public void setCostCategoryService(CostCategoryService costCategoryService) {
2157         this.costCategoryService = costCategoryService;
2158     }
2159 
2160     public PermissionService getPermissionService() {
2161         return permissionService;
2162     }
2163 
2164     public void setPermissionService(PermissionService permissionService) {
2165         this.permissionService = permissionService;
2166     }
2167 
2168     public CustomerInvoiceDocumentService getCustomerInvoiceDocumentService() {
2169         return customerInvoiceDocumentService;
2170     }
2171 
2172     public void setCustomerInvoiceDocumentService(CustomerInvoiceDocumentService customerInvoiceDocumentService) {
2173         this.customerInvoiceDocumentService = customerInvoiceDocumentService;
2174     }
2175 }