001 /*
002 * Copyright 2011 The Kuali Foundation.
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.kfs.module.purap.document;
017
018 import java.sql.Timestamp;
019 import java.util.List;
020
021 import org.apache.commons.lang.StringUtils;
022 import org.kuali.kfs.module.purap.PurapPropertyConstants;
023 import org.kuali.kfs.module.purap.PurapWorkflowConstants.NodeDetails;
024 import org.kuali.kfs.module.purap.businessobject.AccountsPayableItem;
025 import org.kuali.kfs.module.purap.businessobject.PurApItemUseTax;
026 import org.kuali.kfs.module.purap.businessobject.PurchaseOrderItem;
027 import org.kuali.kfs.module.purap.document.service.AccountsPayableDocumentSpecificService;
028 import org.kuali.kfs.module.purap.document.service.PurapService;
029 import org.kuali.kfs.module.purap.document.service.PurchaseOrderService;
030 import org.kuali.kfs.sys.KFSPropertyConstants;
031 import org.kuali.kfs.sys.businessobject.Bank;
032 import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry;
033 import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper;
034 import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySourceDetail;
035 import org.kuali.kfs.sys.context.SpringContext;
036 import org.kuali.kfs.vnd.businessobject.CampusParameter;
037 import org.kuali.rice.kew.dto.DocumentRouteLevelChangeDTO;
038 import org.kuali.rice.kim.bo.Person;
039 import org.kuali.rice.kns.rule.event.KualiDocumentEvent;
040 import org.kuali.rice.kns.service.DataDictionaryService;
041 import org.kuali.rice.kns.util.KualiDecimal;
042 import org.kuali.rice.kns.util.ObjectUtils;
043
044 /**
045 * Accounts Payable Document Base
046 */
047 public abstract class AccountsPayableDocumentBase extends PurchasingAccountsPayableDocumentBase implements AccountsPayableDocument {
048 protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AccountsPayableDocumentBase.class);
049
050 // SHARED FIELDS BETWEEN PAYMENT REQUEST AND CREDIT MEMO
051 protected Timestamp accountsPayableApprovalTimestamp;
052 protected String lastActionPerformedByPersonId;
053 protected String accountsPayableProcessorIdentifier;
054 protected boolean holdIndicator;
055 protected Timestamp extractedTimestamp;
056 protected Integer purchaseOrderIdentifier;
057 protected String processingCampusCode;
058 protected String noteLine1Text;
059 protected String noteLine2Text;
060 protected String noteLine3Text;
061 protected boolean continuationAccountIndicator;
062 protected boolean closePurchaseOrderIndicator;
063 protected boolean reopenPurchaseOrderIndicator;
064 protected String bankCode;
065
066 protected boolean unmatchedOverride; // not persisted
067
068 // NOT PERSISTED IN DB
069 // BELOW USED BY ROUTING
070 protected String chartOfAccountsCode;
071 protected String organizationCode;
072
073 // NOT PERSISTED IN DB
074 // BELOW USED BY GL ENTRY CREATION
075 protected boolean generateEncumbranceEntries;
076 protected String debitCreditCodeForGLEntries;
077 protected PurApItemUseTax offsetUseTax;
078
079 // REFERENCE OBJECTS
080 protected CampusParameter processingCampus;
081 protected transient PurchaseOrderDocument purchaseOrderDocument;
082 protected Bank bank;
083
084 /**
085 * Constructs a AccountsPayableDocumentBase
086 */
087 public AccountsPayableDocumentBase() {
088 super();
089 setUnmatchedOverride(false);
090 }
091
092 public void setLineItemTotal(KualiDecimal total) {
093 // do nothing, this is so that the jsp won't complain about lineItemTotal have no setter method.
094 }
095
096 public void setGrandTotal(KualiDecimal total) {
097 // do nothing, this is so that the jsp won't complain about grandTotal have no setter method.
098 }
099
100 /**
101 * Overriding to stop the deleting of general ledger entries.
102 *
103 * @see org.kuali.kfs.sys.document.GeneralLedgerPostingDocumentBase#removeGeneralLedgerPendingEntries()
104 */
105 @Override
106 protected void removeGeneralLedgerPendingEntries() {
107 // do not delete entries for PREQ or CM (hjs)
108 }
109
110 /**
111 * @see org.kuali.kfs.module.purap.document.AccountsPayableDocument#requiresAccountsPayableReviewRouting()
112 */
113 public boolean requiresAccountsPayableReviewRouting() {
114 return !approvalAtAccountsPayableReviewAllowed();
115 }
116
117 /**
118 * @see org.kuali.kfs.module.purap.document.AccountsPayableDocument#approvalAtAccountsPayableReviewAllowed()
119 */
120 public boolean approvalAtAccountsPayableReviewAllowed() {
121 return !(isAttachmentRequired() && documentHasNoImagesAttached());
122 }
123
124 /**
125 * Checks whether an attachment is required
126 *
127 * @return - true if attachment is required, otherwise false
128 */
129 protected abstract boolean isAttachmentRequired();
130
131 /**
132 * Checks all documents notes for attachments and to be overriden by sub class
133 *
134 * @return - true if document does not have an image attached, false otherwise
135 */
136 public abstract boolean documentHasNoImagesAttached();
137
138 /**
139 * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocumentBase#populateDocumentForRouting()
140 */
141 @Override
142 public void populateDocumentForRouting() {
143 if (ObjectUtils.isNotNull(getPurchaseOrderDocument())) {
144 this.setChartOfAccountsCode(getPurchaseOrderDocument().getChartOfAccountsCode());
145 this.setOrganizationCode(getPurchaseOrderDocument().getOrganizationCode());
146 if (ObjectUtils.isNull(this.getPurchaseOrderDocument().getDocumentHeader().getDocumentNumber())) {
147 this.getPurchaseOrderDocument().refreshReferenceObject(KFSPropertyConstants.DOCUMENT_HEADER);
148 }
149 }
150 super.populateDocumentForRouting();
151 }
152
153 /**
154 * Calls a custom prepare for save method, as the super class does GL entry creation that causes problems with AP documents.
155 *
156 * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocumentBase#prepareForSave(org.kuali.rice.kns.rule.event.KualiDocumentEvent)
157 */
158 @Override
159 public void prepareForSave(KualiDocumentEvent event) {
160
161 // copied from super because we can't call super for AP docs
162 customPrepareForSave(event);
163
164 // DO NOT CALL SUPER HERE!! Cannot call super because it will mess up the GL entry creation process (hjs)
165 // super.prepareForSave(event);
166 }
167
168 /**
169 * Helper method to be called from custom prepare for save and to be overriden by sub class.
170 *
171 * @return - Po Document Type
172 */
173 public abstract String getPoDocumentTypeForAccountsPayableDocumentCancel();
174
175 /**
176 * @see org.kuali.rice.kns.document.DocumentBase#handleRouteLevelChange(org.kuali.rice.kew.clientapp.vo.DocumentRouteLevelChangeDTO)
177 */
178 @Override
179 public void doRouteLevelChange(DocumentRouteLevelChangeDTO levelChangeEvent) {
180 LOG.debug("handleRouteLevelChange() started");
181 super.doRouteLevelChange(levelChangeEvent);
182 String newNodeName = levelChangeEvent.getNewNodeName();
183 if (processNodeChange(newNodeName, levelChangeEvent.getOldNodeName())) {
184 if (StringUtils.isNotBlank(newNodeName)) {
185 NodeDetails nodeDetailEnum = getNodeDetailEnum(newNodeName);
186 if (ObjectUtils.isNotNull(nodeDetailEnum)) {
187 String statusCode = nodeDetailEnum.getAwaitingStatusCode();
188 if (StringUtils.isNotBlank(statusCode)) {
189 SpringContext.getBean(PurapService.class).updateStatus(this, statusCode);
190 saveDocumentFromPostProcessing();
191 }
192 else {
193 LOG.debug("Document with id " + getDocumentNumber() + " will stop in route node '" + newNodeName + "' but no awaiting status found to set");
194 }
195 }
196 }
197 }
198 }
199
200 /**
201 * Hook to allow processing after a route level is passed.
202 *
203 * @param newNodeName - current route level
204 * @param oldNodeName - previous route level
205 * @return - true if process completes to valid state
206 */
207 public abstract boolean processNodeChange(String newNodeName, String oldNodeName);
208
209 /**
210 * Retrieves node details object based on name.
211 *
212 * @param nodeName - route level
213 * @return - Information about the supplied route level
214 */
215 public abstract NodeDetails getNodeDetailEnum(String nodeName);
216
217 /**
218 * Hook point to allow processing after a save.
219 */
220 public abstract void saveDocumentFromPostProcessing();
221
222 // GETTERS AND SETTERS
223 public Integer getPurchaseOrderIdentifier() {
224 return purchaseOrderIdentifier;
225 }
226
227 public void setPurchaseOrderIdentifier(Integer purchaseOrderIdentifier) {
228 this.purchaseOrderIdentifier = purchaseOrderIdentifier;
229 }
230
231 public String getAccountsPayableProcessorIdentifier() {
232 return accountsPayableProcessorIdentifier;
233 }
234
235 public void setAccountsPayableProcessorIdentifier(String accountsPayableProcessorIdentifier) {
236 this.accountsPayableProcessorIdentifier = accountsPayableProcessorIdentifier;
237 }
238
239 public String getLastActionPerformedByPersonId() {
240 return lastActionPerformedByPersonId;
241 }
242
243 public void setLastActionPerformedByPersonId(String lastActionPerformedByPersonId) {
244 this.lastActionPerformedByPersonId = lastActionPerformedByPersonId;
245 }
246
247 public String getProcessingCampusCode() {
248 return processingCampusCode;
249 }
250
251 public void setProcessingCampusCode(String processingCampusCode) {
252 this.processingCampusCode = processingCampusCode;
253 }
254
255 public Timestamp getAccountsPayableApprovalTimestamp() {
256 return accountsPayableApprovalTimestamp;
257 }
258
259 public void setAccountsPayableApprovalTimestamp(Timestamp accountsPayableApprovalTimestamp) {
260 this.accountsPayableApprovalTimestamp = accountsPayableApprovalTimestamp;
261 }
262
263 public Timestamp getExtractedTimestamp() {
264 return extractedTimestamp;
265 }
266
267 public void setExtractedTimestamp(Timestamp extractedTimestamp) {
268 this.extractedTimestamp = extractedTimestamp;
269 }
270
271 public boolean isHoldIndicator() {
272 return holdIndicator;
273 }
274
275 public void setHoldIndicator(boolean holdIndicator) {
276 this.holdIndicator = holdIndicator;
277 }
278
279 public String getNoteLine1Text() {
280 return noteLine1Text;
281 }
282
283 public void setNoteLine1Text(String noteLine1Text) {
284 this.noteLine1Text = noteLine1Text;
285 }
286
287 public String getNoteLine2Text() {
288 return noteLine2Text;
289 }
290
291 public void setNoteLine2Text(String noteLine2Text) {
292 this.noteLine2Text = noteLine2Text;
293 }
294
295 public String getNoteLine3Text() {
296 return noteLine3Text;
297 }
298
299 public void setNoteLine3Text(String noteLine3Text) {
300 this.noteLine3Text = noteLine3Text;
301 }
302
303 public CampusParameter getProcessingCampus() {
304 return processingCampus;
305 }
306
307 public String getChartOfAccountsCode() {
308 return chartOfAccountsCode;
309 }
310
311 public void setChartOfAccountsCode(String chartOfAccountsCode) {
312 this.chartOfAccountsCode = chartOfAccountsCode;
313 }
314
315 public String getOrganizationCode() {
316 return organizationCode;
317 }
318
319 public void setOrganizationCode(String organizationCode) {
320 this.organizationCode = organizationCode;
321 }
322
323 public boolean isGenerateEncumbranceEntries() {
324 return generateEncumbranceEntries;
325 }
326
327 public void setGenerateEncumbranceEntries(boolean generateEncumbranceEntries) {
328 this.generateEncumbranceEntries = generateEncumbranceEntries;
329 }
330
331 /**
332 * @see org.kuali.kfs.module.purap.document.AccountsPayableDocument#getPurchaseOrderDocument()
333 */
334 public PurchaseOrderDocument getPurchaseOrderDocument() {
335 if ((ObjectUtils.isNull(purchaseOrderDocument) || ObjectUtils.isNull(purchaseOrderDocument.getPurapDocumentIdentifier())) && (ObjectUtils.isNotNull(getPurchaseOrderIdentifier()))) {
336 setPurchaseOrderDocument(SpringContext.getBean(PurchaseOrderService.class).getCurrentPurchaseOrder(this.getPurchaseOrderIdentifier()));
337 }
338 return purchaseOrderDocument;
339 }
340
341 /**
342 * @see org.kuali.kfs.module.purap.document.AccountsPayableDocument#setPurchaseOrderDocument(org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
343 */
344 public void setPurchaseOrderDocument(PurchaseOrderDocument purchaseOrderDocument) {
345 if (ObjectUtils.isNull(purchaseOrderDocument)) {
346 // KUALI-PURAP 1185 PO Id not being set to null, instead throwing error on main screen that value is invalid.
347 // setPurchaseOrderIdentifier(null);
348 this.purchaseOrderDocument = null;
349 }
350 else {
351 if (ObjectUtils.isNotNull(purchaseOrderDocument.getPurapDocumentIdentifier())) {
352 setPurchaseOrderIdentifier(purchaseOrderDocument.getPurapDocumentIdentifier());
353 }
354 this.purchaseOrderDocument = purchaseOrderDocument;
355 }
356 }
357
358 public boolean isClosePurchaseOrderIndicator() {
359 return closePurchaseOrderIndicator;
360 }
361
362 public void setClosePurchaseOrderIndicator(boolean closePurchaseOrderIndicator) {
363 this.closePurchaseOrderIndicator = closePurchaseOrderIndicator;
364 }
365
366 public boolean isReopenPurchaseOrderIndicator() {
367 return reopenPurchaseOrderIndicator;
368 }
369
370 public void setReopenPurchaseOrderIndicator(boolean reopenPurchaseOrderIndicator) {
371 this.reopenPurchaseOrderIndicator = reopenPurchaseOrderIndicator;
372 }
373
374 public String getBankCode() {
375 return bankCode;
376 }
377
378 public void setBankCode(String bankCode) {
379 this.bankCode = bankCode;
380 }
381
382 public Bank getBank() {
383 return bank;
384 }
385
386 public void setBank(Bank bank) {
387 this.bank = bank;
388 }
389
390 /**
391 * Sets the processing campus.
392 * @deprecated
393 * @param processingCampus
394 */
395 public void setProcessingCampus(CampusParameter processingCampus) {
396 this.processingCampus = processingCampus;
397 }
398
399 // Helper methods
400 /**
401 * Retrieves the universal user object for the last person to perform an action on the document.
402 */
403 public Person getLastActionPerformedByUser() {
404 return SpringContext.getBean(org.kuali.rice.kim.service.PersonService.class).getPerson(getLastActionPerformedByPersonId());
405 }
406
407 /**
408 * Retrieves the person name for the last person to perform an action on the document.
409 *
410 * @return - the person's name who last performed an action on the document.
411 */
412 public String getLastActionPerformedByPersonName() {
413 Person user = getLastActionPerformedByUser();
414 if (ObjectUtils.isNull(user)) {
415 return "";
416 }
417 else {
418 return user.getName();
419 }
420 }
421
422 public String getDebitCreditCodeForGLEntries() {
423 return debitCreditCodeForGLEntries;
424 }
425
426 public void setDebitCreditCodeForGLEntries(String debitCreditCodeForGLEntries) {
427 this.debitCreditCodeForGLEntries = debitCreditCodeForGLEntries;
428 }
429
430 public boolean isUnmatchedOverride() {
431 return unmatchedOverride;
432 }
433
434 public void setUnmatchedOverride(boolean unmatchedOverride) {
435 this.unmatchedOverride = unmatchedOverride;
436 }
437
438 public boolean getExtractedIndicatorForSearching() {
439 return extractedTimestamp != null;
440 }
441
442 public boolean isHoldIndicatorForSearching() {
443 return holdIndicator;
444 }
445
446 /**
447 * @see org.kuali.kfs.module.purap.document.AccountsPayableDocument#getGrandTotal()
448 */
449 public abstract KualiDecimal getGrandTotal();
450
451 /**
452 * @see org.kuali.kfs.module.purap.document.AccountsPayableDocument#getInitialAmount()
453 */
454 public abstract KualiDecimal getInitialAmount();
455
456 public boolean isContinuationAccountIndicator() {
457 return continuationAccountIndicator;
458 }
459
460 public void setContinuationAccountIndicator(boolean continuationAccountIndicator) {
461 this.continuationAccountIndicator = continuationAccountIndicator;
462 }
463
464 public boolean isExtracted() {
465 return (ObjectUtils.isNotNull(getExtractedTimestamp()));
466 }
467
468 public abstract AccountsPayableDocumentSpecificService getDocumentSpecificService();
469
470 public AccountsPayableItem getAPItemFromPOItem(PurchaseOrderItem poi) {
471 for (AccountsPayableItem preqItem : (List<AccountsPayableItem>) this.getItems()) {
472 if (preqItem.getItemType().isLineItemIndicator()) {
473 if (preqItem.getItemLineNumber().compareTo(poi.getItemLineNumber()) == 0) {
474 return preqItem;
475 }
476 }
477 else {
478 return (AccountsPayableItem) SpringContext.getBean(PurapService.class).getBelowTheLineByType(this, poi.getItemType());
479 }
480 }
481 return null;
482 }
483
484 /**
485 * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocumentBase#getItemClass()
486 */
487 public Class getItemClass() {
488 return null;
489 }
490
491 /**
492 * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocumentBase#getPurApSourceDocumentIfPossible()
493 */
494 public PurchasingAccountsPayableDocument getPurApSourceDocumentIfPossible() {
495 return null;
496 }
497
498 /**
499 * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocumentBase#getPurApSourceDocumentLabelIfPossible()
500 */
501 public String getPurApSourceDocumentLabelIfPossible() {
502 return null;
503 }
504
505 public void updateExtendedPriceOnItems() {
506 for (AccountsPayableItem item : (List<AccountsPayableItem>) getItems()) {
507 item.refreshReferenceObject(PurapPropertyConstants.ITEM_TYPE);
508
509 final KualiDecimal itemExtendedPrice = (item.getExtendedPrice()==null)?KualiDecimal.ZERO:item.getExtendedPrice();;
510 if (item.getItemType().isQuantityBasedGeneralLedgerIndicator()) {
511 KualiDecimal newExtendedPrice = item.calculateExtendedPrice();
512 item.setExtendedPrice(newExtendedPrice);
513 }
514 }
515 }
516
517 /**
518 *
519 * @see org.kuali.kfs.module.purap.document.AccountsPayableDocument#getTotalRemitAmount()
520 */
521 public KualiDecimal getTotalRemitTax() {
522 if(!this.isUseTaxIndicator()) {
523 return (KualiDecimal.ZERO.equals(this.getTotalTaxAmount()))?null:this.getTotalTaxAmount();
524 }
525 return null;
526 }
527
528 @Override
529 public boolean customizeOffsetGeneralLedgerPendingEntry(GeneralLedgerPendingEntrySourceDetail accountingLine, GeneralLedgerPendingEntry explicitEntry, GeneralLedgerPendingEntry offsetEntry) {
530 boolean value = super.customizeOffsetGeneralLedgerPendingEntry(accountingLine, explicitEntry, offsetEntry);
531 if(offsetEntry != null && this.offsetUseTax != null) {
532 offsetEntry.setChartOfAccountsCode(this.offsetUseTax.getChartOfAccountsCode());
533 offsetEntry.refreshReferenceObject(KFSPropertyConstants.CHART);
534 offsetEntry.setAccountNumber(this.offsetUseTax.getAccountNumber());
535 offsetEntry.refreshReferenceObject(KFSPropertyConstants.ACCOUNT);
536 offsetEntry.setFinancialObjectCode(this.offsetUseTax.getFinancialObjectCode());
537 offsetEntry.refreshReferenceObject(KFSPropertyConstants.FINANCIAL_OBJECT);
538 } else {
539 value=false;
540 }
541 return value;
542 }
543
544 public boolean generateGeneralLedgerPendingEntries(GeneralLedgerPendingEntrySourceDetail glpeSourceDetail, GeneralLedgerPendingEntrySequenceHelper sequenceHelper, PurApItemUseTax offsetUseTax) {
545 this.offsetUseTax = offsetUseTax;
546 boolean value = this.generateGeneralLedgerPendingEntries(glpeSourceDetail, sequenceHelper);
547 this.offsetUseTax = null;
548 return value;
549 }
550
551 public String getHoldIndicatorForResult(){
552 return isHoldIndicator() ? "Yes" : "No";
553 }
554
555 public String getProcessingCampusCodeForSearch(){
556 return getProcessingCampusCode();
557 }
558
559 public String getDocumentChartOfAccountsCodeForSearching(){
560 return getPurchaseOrderDocument().getChartOfAccountsCode();
561 }
562
563 public String getDocumentOrganizationCodeForSearching(){
564 return getPurchaseOrderDocument().getOrganizationCode();
565 }
566
567 /**
568 * @return workflow document type for the purap document
569 */
570 public String getDocumentType() {
571 return SpringContext.getBean(DataDictionaryService.class).getDocumentTypeNameByClass(this.getClass());
572 }
573
574 public boolean shouldGiveErrorForEmptyAccountsProration() {
575 return true;
576 }
577 }
578