001/*
002 * Copyright 2008 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 */
016package org.kuali.ole.module.purap.util;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.ole.module.purap.PurapConstants;
020import org.kuali.ole.module.purap.PurapPropertyConstants;
021import org.kuali.ole.module.purap.businessobject.*;
022import org.kuali.ole.module.purap.document.PurchaseOrderDocument;
023import org.kuali.ole.module.purap.document.service.PurapService;
024import org.kuali.ole.sys.OLEConstants;
025import org.kuali.ole.sys.context.SpringContext;
026import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
027import org.kuali.rice.kew.api.document.DocumentStatus;
028import org.kuali.rice.kew.api.exception.WorkflowException;
029import org.kuali.rice.kim.api.KimConstants;
030import org.kuali.rice.kim.api.services.IdentityManagementService;
031import org.kuali.rice.kns.service.DataDictionaryService;
032import org.kuali.rice.krad.datadictionary.exception.UnknownDocumentTypeException;
033import org.kuali.rice.krad.document.Document;
034import org.kuali.rice.krad.service.DocumentService;
035import org.kuali.rice.krad.service.KRADServiceLocatorInternal;
036import org.kuali.rice.krad.util.GlobalVariables;
037import org.kuali.rice.krad.util.ObjectUtils;
038import org.springframework.transaction.PlatformTransactionManager;
039import org.springframework.transaction.TransactionStatus;
040import org.springframework.transaction.support.TransactionCallback;
041import org.springframework.transaction.support.TransactionTemplate;
042
043import java.util.*;
044
045public class PurApRelatedViews {
046    private String documentNumber;
047    private Integer accountsPayablePurchasingDocumentLinkIdentifier;
048
049    private transient List<RequisitionView> relatedRequisitionViews;
050    private transient List<PurchaseOrderView> relatedPurchaseOrderViews;
051    private transient List<PaymentRequestView> relatedPaymentRequestViews;
052    private transient List<PaymentRequestView> paymentHistoryPaymentRequestViews;
053    private transient List<CreditMemoView> relatedCreditMemoViews;
054    private transient List<CreditMemoView> paymentHistoryCreditMemoViews;
055    private transient List<InvoiceView> relatedInvoiceViews;
056    private transient List<InvoiceView> paymentHistoryInvoiceViews;
057    private transient List<LineItemReceivingView> relatedLineItemReceivingViews;
058    private transient List<CorrectionReceivingView> relatedCorrectionReceivingViews;
059    private transient List<BulkReceivingView> relatedBulkReceivingViews;
060    private transient List<PurchaseOrderViewGroup> groupedRelatedPurchaseOrderViews;
061    private transient List<ReceivingViewGroup> groupedRelatedReceivingViews;
062    private transient List<ElectronicInvoiceRejectView> relatedRejectViews;
063
064    public PurApRelatedViews(String documentNumber, Integer accountsPayablePurchasingDocumentLinkIdentifier) {
065        super();
066        this.documentNumber = documentNumber;
067        this.accountsPayablePurchasingDocumentLinkIdentifier = accountsPayablePurchasingDocumentLinkIdentifier;
068    }
069
070    /**
071     * Reset all related view lists to null.
072     */
073    public void resetRelatedViews() {
074        relatedRequisitionViews = null;
075        relatedPurchaseOrderViews = null;
076        relatedPaymentRequestViews = null;
077        paymentHistoryPaymentRequestViews = null;
078        relatedInvoiceViews = null;
079        paymentHistoryInvoiceViews = null;
080        relatedCreditMemoViews = null;
081        paymentHistoryCreditMemoViews = null;
082        relatedLineItemReceivingViews = null;
083        relatedCorrectionReceivingViews = null;
084        relatedBulkReceivingViews = null;
085        groupedRelatedPurchaseOrderViews = null;
086        groupedRelatedReceivingViews = null;
087        relatedRejectViews = null;
088    }
089
090    private static PlatformTransactionManager transactionManager;
091
092    public static PlatformTransactionManager getTransactionManager() {
093        if (transactionManager == null) {
094            transactionManager = GlobalResourceLoader.getService("transactionManager");
095        }
096        return transactionManager;
097    }
098
099    public List updateRelatedView(final Class<?> clazz, List<? extends AbstractRelatedView> relatedList, final boolean removeCurrentDocument) {
100        if (relatedList == null) {
101            TransactionTemplate template = new TransactionTemplate(getTransactionManager());
102            relatedList = template.execute(new TransactionCallback<List<? extends AbstractRelatedView>>() {
103                @Override
104                public List<? extends AbstractRelatedView> doInTransaction(TransactionStatus status) {
105                    List<? extends AbstractRelatedView> relatedList = SpringContext.getBean(PurapService.class).getRelatedViews(clazz, accountsPayablePurchasingDocumentLinkIdentifier);
106                    if (removeCurrentDocument) {
107                        for (AbstractRelatedView view : relatedList) {
108                            //KFSMI-4576 Mask/Unmask purapDocumentIdentifier field value
109                            maskPONumberIfUnapproved(view);
110                            if (documentNumber.equals(view.getDocumentNumber())) {
111                                relatedList.remove(view);
112                                break;
113                            }
114                        }
115                    }
116                    return relatedList;
117                }
118            });
119        }
120
121        return relatedList;
122    }
123
124    /**
125     * masks the po number if the po is unappoved yet.  If the document status is not FINAL then
126     * check for permission for purapDocumentIdentifier field.  If NOT permitted to view the value
127     * then mask the value with * and setting this value in poNumberMasked property.
128     *
129     * @param view
130     */
131    protected void maskPONumberIfUnapproved(AbstractRelatedView view) {
132        Document document = findDocument(view.getDocumentNumber());
133
134        String poIDstr = "";
135
136        if (ObjectUtils.isNotNull(view.getPurapDocumentIdentifier())) {
137            poIDstr = view.getPurapDocumentIdentifier().toString();
138        }
139
140        if (PurapConstants.PurapDocTypeCodes.PO_DOCUMENT.equals(document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName())) {
141            if ((document != null) && (document.getDocumentHeader().getWorkflowDocument() != null)) {
142                if (!document.getDocumentHeader().getWorkflowDocument().getStatus().equals(DocumentStatus.FINAL)) {
143
144                    String principalId = GlobalVariables.getUserSession().getPrincipalId();
145
146                    String namespaceCode = OLEConstants.ParameterNamespaces.KNS;
147                    String permissionTemplateName = KimConstants.PermissionTemplateNames.FULL_UNMASK_FIELD;
148
149                    Map<String, String> roleQualifiers = new HashMap<String, String>();
150
151                    Map<String, String> permissionDetails = new HashMap<String, String>();
152                    permissionDetails.put(KimConstants.AttributeConstants.COMPONENT_NAME, PurchaseOrderDocument.class.getSimpleName());
153                    permissionDetails.put(KimConstants.AttributeConstants.PROPERTY_NAME, PurapPropertyConstants.PURAP_DOC_ID);
154
155                    IdentityManagementService identityManagementService = SpringContext.getBean(IdentityManagementService.class);
156                    Boolean isAuthorized = identityManagementService.isAuthorizedByTemplateName(principalId, namespaceCode, permissionTemplateName, permissionDetails, roleQualifiers);
157                    if (!isAuthorized) {
158                        //not authorized to see... so mask the po number string
159                        poIDstr = "";
160                        int strLength = SpringContext.getBean(DataDictionaryService.class).getAttributeMaxLength(PurApGenericAttributes.class.getName(), PurapPropertyConstants.PURAP_DOC_ID);
161                        for (int i = 0; i < strLength; i++) {
162                            poIDstr = poIDstr.concat("*");
163                        }
164                    }
165                }
166            }
167        }
168
169        view.setPoNumberMasked(poIDstr);
170    }
171
172    /**
173     * This method finds the document for the given document header id
174     *
175     * @param documentHeaderId
176     * @return document The document in the workflow that matches the document header id.
177     */
178    protected Document findDocument(String documentHeaderId) {
179        Document document = null;
180
181        try {
182            document = SpringContext.getBean(DocumentService.class).getByDocumentHeaderId(documentHeaderId);
183        } catch (WorkflowException ex) {
184        } catch (UnknownDocumentTypeException ex) {
185            // don't blow up just because a document type is not installed (but don't return it either)
186        }
187
188        return document;
189    }
190
191    /**
192     * @see org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument#getRelatedRequisitionViews()
193     */
194    public List<RequisitionView> getRelatedRequisitionViews() {
195        if(relatedRequisitionViews==null){
196            relatedRequisitionViews = updateRelatedView(RequisitionView.class, relatedRequisitionViews, true);
197        }
198        return relatedRequisitionViews;
199    }
200
201    public List<ElectronicInvoiceRejectView> getRelatedRejectViews() {
202        relatedRejectViews = updateRelatedView(ElectronicInvoiceRejectView.class, relatedRejectViews, true);
203        return relatedRejectViews;
204    }
205
206    /**
207     * Obtains a list of related PurchaseOrderViews, first ordered by POIDs descending, then by document numbers descending;
208     * thus POs with newer POIDs will be in the front, and within the same POID, the current PO will be in the front.
209     *
210     * @return A list of <PurchaseOrderView> with newer POs in the front.
211     */
212    public List<PurchaseOrderView> getRelatedPurchaseOrderViews() {
213        if (relatedPurchaseOrderViews != null) {
214            return relatedPurchaseOrderViews;
215        }
216
217        // Obtain a list which is sorted by workflow document ID descending.
218        relatedPurchaseOrderViews = updateRelatedView(PurchaseOrderView.class, relatedPurchaseOrderViews, true);
219
220        // Sort the list.
221        Collections.sort(relatedPurchaseOrderViews,
222                new Comparator<PurchaseOrderView>() {
223                    @Override
224                    public int compare(PurchaseOrderView v1, PurchaseOrderView v2) {
225                        if ((v1 != null) && (v2 != null) &&
226                                (v1.getPurapDocumentIdentifier() != null) &&
227                                (v2.getPurapDocumentIdentifier() != null)) {
228                            // sort by POID descending
229                            int compare = -v1.getPurapDocumentIdentifier().compareTo(v2.getPurapDocumentIdentifier());
230                            // if POIDs are the same, sort by document number descending; usually current PO has biggest documentNumber
231                            if (compare == 0) {
232                                compare = v1.getPurchaseOrderCurrentIndicator() ? -1 :
233                                        v2.getPurchaseOrderCurrentIndicator() ? 1 :
234                                                -v1.getCreateDate().compareTo(v2.getCreateDate());
235                            }
236                            return compare;
237                        }
238                        return 0;
239                    }
240                }
241        );
242
243        return relatedPurchaseOrderViews;
244    }
245
246    /**
247     * Groups related PurchaseOrderViews by POIDs descending, and within each group order POs by document numbers descending;
248     * thus groups of newer POIDs will be in the front, and within each group, more current POs will be in the front.
249     *
250     * @return A list of <PurchaseOrderViewGroup> with newer POs in the front.
251     * @see org.kuali.ole.module.purap.util.PurApRelatedViews.getRelatedPurchaseOrderViews
252     * @see org.kuali.ole.module.purap.businessobject.PurchaseOrderView
253     */
254    public List<PurchaseOrderViewGroup> getGroupedRelatedPurchaseOrderViews() {
255        if (groupedRelatedPurchaseOrderViews != null) {
256            return groupedRelatedPurchaseOrderViews;
257        }
258
259        /*
260         * This extra layer of grouping is necessary in order to display the notes for a group of
261         * related POChange documents (which should have identical POID) after that group,
262         * and before any other related groups which may result from PO splitting (with different POIDs).
263         * With direct use of relatedPurchaseOrderViews, location of the end of the group is problematic.
264         */
265        groupedRelatedPurchaseOrderViews = new ArrayList<PurchaseOrderViewGroup>();
266        PurchaseOrderViewGroup group = new PurchaseOrderViewGroup();
267        int previousPOID = 0;
268        relatedPurchaseOrderViews = getRelatedPurchaseOrderViews();
269        for (PurchaseOrderView view : relatedPurchaseOrderViews) {
270            if (previousPOID == 0) {
271                previousPOID = view.getPurapDocumentIdentifier();
272
273            }
274            if (view.getPurapDocumentIdentifier() == previousPOID) {
275                group.getViews().add(view);
276            } else {
277                groupedRelatedPurchaseOrderViews.add(group);
278                group = new PurchaseOrderViewGroup();
279                group.getViews().add(view);
280                previousPOID = view.getPurapDocumentIdentifier();
281            }
282            if (relatedPurchaseOrderViews.size() == relatedPurchaseOrderViews.indexOf(view) + 1) {
283                groupedRelatedPurchaseOrderViews.add(group);
284            }
285        }
286
287        return groupedRelatedPurchaseOrderViews;
288    }
289
290    /**
291     * @see org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument#getRelatedPaymentRequestViews()
292     */
293    public List<PaymentRequestView> getRelatedPaymentRequestViews() {
294        if(relatedPaymentRequestViews == null){
295            relatedPaymentRequestViews = updateRelatedView(PaymentRequestView.class, relatedPaymentRequestViews, true);
296        }
297        return relatedPaymentRequestViews;
298    }
299
300    /**
301     * @see org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument#getRelatedCreditMemoViews()
302     */
303    public List<CreditMemoView> getRelatedCreditMemoViews() {
304        if(relatedCreditMemoViews==null){
305            relatedCreditMemoViews = updateRelatedView(CreditMemoView.class, relatedCreditMemoViews, true);
306        }
307        return relatedCreditMemoViews;
308    }
309
310
311    /**
312     * @see org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument#getRelatedPaymentRequestViews()
313     */
314    public List<InvoiceView> getRelatedInvoiceViews() {
315        relatedInvoiceViews = updateRelatedView(InvoiceView.class, relatedInvoiceViews, true);
316        return relatedInvoiceViews;
317    }
318
319    /**
320     * Gets the Payment History Payment Request Views for this document.
321     *
322     * @return the list of Payment History Payment Request Views.
323     */
324    public List<PaymentRequestView> getPaymentHistoryPaymentRequestViews() {
325        if(paymentHistoryPaymentRequestViews==null){
326            paymentHistoryPaymentRequestViews = updateRelatedView(PaymentRequestView.class, paymentHistoryPaymentRequestViews, false);
327        }
328        return paymentHistoryPaymentRequestViews;
329    }
330
331    /**
332     * Gets the Payment History Credit Memo Views for this document.
333     *
334     * @return the list of Payment History Credit Memo Views.
335     */
336    public List<CreditMemoView> getPaymentHistoryCreditMemoViews() {
337        if(paymentHistoryCreditMemoViews==null){
338            paymentHistoryCreditMemoViews = updateRelatedView(CreditMemoView.class, paymentHistoryCreditMemoViews, false);
339        }
340        return paymentHistoryCreditMemoViews;
341    }
342
343    /**
344     * Gets the Payment History Invoice Views for this document.
345     *
346     * @return the list of Payment History Invoice Views.
347     */
348    public List<InvoiceView> getPaymentHistoryInvoiceViews() {
349        paymentHistoryInvoiceViews = updateRelatedView(InvoiceView.class, paymentHistoryInvoiceViews, false);
350        return paymentHistoryInvoiceViews;
351    }
352
353    /**
354     * @see org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument#getRelatedRequisitionViews()
355     */
356    public List<LineItemReceivingView> getRelatedLineItemReceivingViews() {
357        if(relatedLineItemReceivingViews==null){
358            relatedLineItemReceivingViews = updateRelatedView(LineItemReceivingView.class, relatedLineItemReceivingViews, true);
359        }
360        return relatedLineItemReceivingViews;
361    }
362
363    /**
364     * @see org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument#getRelatedRequisitionViews()
365     */
366    public List<CorrectionReceivingView> getRelatedCorrectionReceivingViews() {
367        if(relatedCorrectionReceivingViews == null){
368            relatedCorrectionReceivingViews = updateRelatedView(CorrectionReceivingView.class, relatedCorrectionReceivingViews, true);
369        }
370        return relatedCorrectionReceivingViews;
371    }
372
373    public List<BulkReceivingView> getRelatedBulkReceivingViews() {
374        relatedBulkReceivingViews = updateRelatedView(BulkReceivingView.class, relatedBulkReceivingViews, true);
375        return relatedBulkReceivingViews;
376    }
377
378    /**
379     * Groups related LineItemReceivingView and its CorrectionReceivingViews, with more recent receiving groups in the front;
380     * and within each group, with more recent corrections in the front.
381     *
382     * @return A list of ReceivingCorrectionViewGroups.
383     */
384    public List<ReceivingViewGroup> getGroupedRelatedReceivingViews() {
385        if (groupedRelatedReceivingViews != null) {
386            return groupedRelatedReceivingViews;
387        }
388
389        groupedRelatedReceivingViews = new ArrayList<ReceivingViewGroup>();
390        PurapService purapService = SpringContext.getBean(PurapService.class);
391        List<LineItemReceivingView> liviews = purapService.getRelatedViews(LineItemReceivingView.class, accountsPayablePurchasingDocumentLinkIdentifier);
392        List<CorrectionReceivingView> crviews = purapService.getRelatedViews(CorrectionReceivingView.class, accountsPayablePurchasingDocumentLinkIdentifier);
393
394        // both LineItemReceivingViews and CorrectionReceivingViews are already in order with most recent first, so no need to sort
395        for (LineItemReceivingView liview : liviews) {
396            ReceivingViewGroup group = new ReceivingViewGroup();
397            group.lineItemView = liview; // could be current document
398            for (CorrectionReceivingView crview : crviews) {
399                if (StringUtils.equals(crview.getLineItemReceivingDocumentNumber(), liview.getDocumentNumber()) &&
400                        !documentNumber.equals(crview.getDocumentNumber())) {// exclude current document
401                    group.addCorrectionView(crview);
402                }
403            }
404            groupedRelatedReceivingViews.add(group);
405        }
406
407        return groupedRelatedReceivingViews;
408    }
409
410    /**
411     * A container for a List<PurchaseOrderView>, to be used by a nested c:forEach tag
412     * in relatedPurchaseOrderDocumentsDetail.tag.
413     */
414    public class PurchaseOrderViewGroup {
415        protected List<PurchaseOrderView> views = new ArrayList<PurchaseOrderView>();
416
417        protected PurchaseOrderViewGroup() {
418        }
419
420        public List<PurchaseOrderView> getViews() {
421            return views;
422        }
423    }
424
425    /**
426     * A container for a LineItemReceivingView and a list of its associated CorrectionReceivingViews.
427     */
428    public class ReceivingViewGroup {
429        protected LineItemReceivingView lineItemView;
430        protected List<CorrectionReceivingView> correctionViews = new ArrayList<CorrectionReceivingView>();
431
432        protected ReceivingViewGroup() {
433        }
434
435        public LineItemReceivingView getLineItemView() {
436            return lineItemView;
437        }
438
439        public List<CorrectionReceivingView> getCorrectionViews() {
440            return correctionViews;
441        }
442
443        public void addCorrectionView(CorrectionReceivingView correctionView) {
444            correctionViews.add(correctionView);
445        }
446
447        public boolean getIsLineItemViewCurrentDocument() {
448            return (lineItemView != null && documentNumber.equals(lineItemView.getDocumentNumber()));
449        }
450    }
451
452}