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        relatedRequisitionViews = updateRelatedView(RequisitionView.class, relatedRequisitionViews, true);
196        return relatedRequisitionViews;
197    }
198
199    public List<ElectronicInvoiceRejectView> getRelatedRejectViews() {
200        relatedRejectViews = updateRelatedView(ElectronicInvoiceRejectView.class, relatedRejectViews, true);
201        return relatedRejectViews;
202    }
203
204    /**
205     * Obtains a list of related PurchaseOrderViews, first ordered by POIDs descending, then by document numbers descending;
206     * thus POs with newer POIDs will be in the front, and within the same POID, the current PO will be in the front.
207     *
208     * @return A list of <PurchaseOrderView> with newer POs in the front.
209     */
210    public List<PurchaseOrderView> getRelatedPurchaseOrderViews() {
211        if (relatedPurchaseOrderViews != null) {
212            return relatedPurchaseOrderViews;
213        }
214
215        // Obtain a list which is sorted by workflow document ID descending.
216        relatedPurchaseOrderViews = updateRelatedView(PurchaseOrderView.class, relatedPurchaseOrderViews, true);
217
218        // Sort the list.
219        Collections.sort(relatedPurchaseOrderViews,
220                new Comparator<PurchaseOrderView>() {
221                    @Override
222                    public int compare(PurchaseOrderView v1, PurchaseOrderView v2) {
223                        if ((v1 != null) && (v2 != null) &&
224                                (v1.getPurapDocumentIdentifier() != null) &&
225                                (v2.getPurapDocumentIdentifier() != null)) {
226                            // sort by POID descending
227                            int compare = -v1.getPurapDocumentIdentifier().compareTo(v2.getPurapDocumentIdentifier());
228                            // if POIDs are the same, sort by document number descending; usually current PO has biggest documentNumber
229                            if (compare == 0) {
230                                compare = v1.getPurchaseOrderCurrentIndicator() ? -1 :
231                                        v2.getPurchaseOrderCurrentIndicator() ? 1 :
232                                                -v1.getCreateDate().compareTo(v2.getCreateDate());
233                            }
234                            return compare;
235                        }
236                        return 0;
237                    }
238                }
239        );
240
241        return relatedPurchaseOrderViews;
242    }
243
244    /**
245     * Groups related PurchaseOrderViews by POIDs descending, and within each group order POs by document numbers descending;
246     * thus groups of newer POIDs will be in the front, and within each group, more current POs will be in the front.
247     *
248     * @return A list of <PurchaseOrderViewGroup> with newer POs in the front.
249     * @see org.kuali.ole.module.purap.util.PurApRelatedViews.getRelatedPurchaseOrderViews
250     * @see org.kuali.ole.module.purap.businessobject.PurchaseOrderView
251     */
252    public List<PurchaseOrderViewGroup> getGroupedRelatedPurchaseOrderViews() {
253        if (groupedRelatedPurchaseOrderViews != null) {
254            return groupedRelatedPurchaseOrderViews;
255        }
256
257        /*
258         * This extra layer of grouping is necessary in order to display the notes for a group of
259         * related POChange documents (which should have identical POID) after that group,
260         * and before any other related groups which may result from PO splitting (with different POIDs).
261         * With direct use of relatedPurchaseOrderViews, location of the end of the group is problematic.
262         */
263        groupedRelatedPurchaseOrderViews = new ArrayList<PurchaseOrderViewGroup>();
264        PurchaseOrderViewGroup group = new PurchaseOrderViewGroup();
265        int previousPOID = 0;
266        relatedPurchaseOrderViews = getRelatedPurchaseOrderViews();
267        for (PurchaseOrderView view : relatedPurchaseOrderViews) {
268            if (previousPOID == 0) {
269                previousPOID = view.getPurapDocumentIdentifier();
270
271            }
272            if (view.getPurapDocumentIdentifier() == previousPOID) {
273                group.getViews().add(view);
274            } else {
275                groupedRelatedPurchaseOrderViews.add(group);
276                group = new PurchaseOrderViewGroup();
277                group.getViews().add(view);
278                previousPOID = view.getPurapDocumentIdentifier();
279            }
280            if (relatedPurchaseOrderViews.size() == relatedPurchaseOrderViews.indexOf(view) + 1) {
281                groupedRelatedPurchaseOrderViews.add(group);
282            }
283        }
284
285        return groupedRelatedPurchaseOrderViews;
286    }
287
288    /**
289     * @see org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument#getRelatedPaymentRequestViews()
290     */
291    public List<PaymentRequestView> getRelatedPaymentRequestViews() {
292        relatedPaymentRequestViews = updateRelatedView(PaymentRequestView.class, relatedPaymentRequestViews, true);
293        return relatedPaymentRequestViews;
294    }
295
296    /**
297     * @see org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument#getRelatedCreditMemoViews()
298     */
299    public List<CreditMemoView> getRelatedCreditMemoViews() {
300        relatedCreditMemoViews = updateRelatedView(CreditMemoView.class, relatedCreditMemoViews, true);
301        return relatedCreditMemoViews;
302    }
303
304
305    /**
306     * @see org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument#getRelatedPaymentRequestViews()
307     */
308    public List<InvoiceView> getRelatedInvoiceViews() {
309        relatedInvoiceViews = updateRelatedView(InvoiceView.class, relatedInvoiceViews, true);
310        return relatedInvoiceViews;
311    }
312
313    /**
314     * Gets the Payment History Payment Request Views for this document.
315     *
316     * @return the list of Payment History Payment Request Views.
317     */
318    public List<PaymentRequestView> getPaymentHistoryPaymentRequestViews() {
319        paymentHistoryPaymentRequestViews = updateRelatedView(PaymentRequestView.class, paymentHistoryPaymentRequestViews, false);
320        return paymentHistoryPaymentRequestViews;
321    }
322
323    /**
324     * Gets the Payment History Credit Memo Views for this document.
325     *
326     * @return the list of Payment History Credit Memo Views.
327     */
328    public List<CreditMemoView> getPaymentHistoryCreditMemoViews() {
329        paymentHistoryCreditMemoViews = updateRelatedView(CreditMemoView.class, paymentHistoryCreditMemoViews, false);
330        return paymentHistoryCreditMemoViews;
331    }
332
333    /**
334     * Gets the Payment History Invoice Views for this document.
335     *
336     * @return the list of Payment History Invoice Views.
337     */
338    public List<InvoiceView> getPaymentHistoryInvoiceViews() {
339        paymentHistoryInvoiceViews = updateRelatedView(InvoiceView.class, paymentHistoryInvoiceViews, false);
340        return paymentHistoryInvoiceViews;
341    }
342
343    /**
344     * @see org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument#getRelatedRequisitionViews()
345     */
346    public List<LineItemReceivingView> getRelatedLineItemReceivingViews() {
347        relatedLineItemReceivingViews = updateRelatedView(LineItemReceivingView.class, relatedLineItemReceivingViews, true);
348        return relatedLineItemReceivingViews;
349    }
350
351    /**
352     * @see org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument#getRelatedRequisitionViews()
353     */
354    public List<CorrectionReceivingView> getRelatedCorrectionReceivingViews() {
355        relatedCorrectionReceivingViews = updateRelatedView(CorrectionReceivingView.class, relatedCorrectionReceivingViews, true);
356        return relatedCorrectionReceivingViews;
357    }
358
359    public List<BulkReceivingView> getRelatedBulkReceivingViews() {
360        relatedBulkReceivingViews = updateRelatedView(BulkReceivingView.class, relatedBulkReceivingViews, true);
361        return relatedBulkReceivingViews;
362    }
363
364    /**
365     * Groups related LineItemReceivingView and its CorrectionReceivingViews, with more recent receiving groups in the front;
366     * and within each group, with more recent corrections in the front.
367     *
368     * @return A list of ReceivingCorrectionViewGroups.
369     */
370    public List<ReceivingViewGroup> getGroupedRelatedReceivingViews() {
371        if (groupedRelatedReceivingViews != null) {
372            return groupedRelatedReceivingViews;
373        }
374
375        groupedRelatedReceivingViews = new ArrayList<ReceivingViewGroup>();
376        PurapService purapService = SpringContext.getBean(PurapService.class);
377        List<LineItemReceivingView> liviews = purapService.getRelatedViews(LineItemReceivingView.class, accountsPayablePurchasingDocumentLinkIdentifier);
378        List<CorrectionReceivingView> crviews = purapService.getRelatedViews(CorrectionReceivingView.class, accountsPayablePurchasingDocumentLinkIdentifier);
379
380        // both LineItemReceivingViews and CorrectionReceivingViews are already in order with most recent first, so no need to sort
381        for (LineItemReceivingView liview : liviews) {
382            ReceivingViewGroup group = new ReceivingViewGroup();
383            group.lineItemView = liview; // could be current document
384            for (CorrectionReceivingView crview : crviews) {
385                if (StringUtils.equals(crview.getLineItemReceivingDocumentNumber(), liview.getDocumentNumber()) &&
386                        !documentNumber.equals(crview.getDocumentNumber())) {// exclude current document
387                    group.addCorrectionView(crview);
388                }
389            }
390            groupedRelatedReceivingViews.add(group);
391        }
392
393        return groupedRelatedReceivingViews;
394    }
395
396    /**
397     * A container for a List<PurchaseOrderView>, to be used by a nested c:forEach tag
398     * in relatedPurchaseOrderDocumentsDetail.tag.
399     */
400    public class PurchaseOrderViewGroup {
401        protected List<PurchaseOrderView> views = new ArrayList<PurchaseOrderView>();
402
403        protected PurchaseOrderViewGroup() {
404        }
405
406        public List<PurchaseOrderView> getViews() {
407            return views;
408        }
409    }
410
411    /**
412     * A container for a LineItemReceivingView and a list of its associated CorrectionReceivingViews.
413     */
414    public class ReceivingViewGroup {
415        protected LineItemReceivingView lineItemView;
416        protected List<CorrectionReceivingView> correctionViews = new ArrayList<CorrectionReceivingView>();
417
418        protected ReceivingViewGroup() {
419        }
420
421        public LineItemReceivingView getLineItemView() {
422            return lineItemView;
423        }
424
425        public List<CorrectionReceivingView> getCorrectionViews() {
426            return correctionViews;
427        }
428
429        public void addCorrectionView(CorrectionReceivingView correctionView) {
430            correctionViews.add(correctionView);
431        }
432
433        public boolean getIsLineItemViewCurrentDocument() {
434            return (lineItemView != null && documentNumber.equals(lineItemView.getDocumentNumber()));
435        }
436    }
437
438}