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.ar.document.validation.impl;
017    
018    import java.util.HashMap;
019    import java.util.Map;
020    
021    import org.apache.commons.lang.StringUtils;
022    import org.kuali.kfs.module.ar.ArConstants;
023    import org.kuali.kfs.module.ar.ArKeyConstants;
024    import org.kuali.kfs.module.ar.ArPropertyConstants;
025    import org.kuali.kfs.module.ar.businessobject.CustomerInvoiceDetail;
026    import org.kuali.kfs.module.ar.businessobject.OrganizationAccountingDefault;
027    import org.kuali.kfs.module.ar.document.CustomerInvoiceDocument;
028    import org.kuali.kfs.module.ar.document.CustomerInvoiceWriteoffDocument;
029    import org.kuali.kfs.module.ar.document.service.CustomerInvoiceDocumentService;
030    import org.kuali.kfs.module.ar.document.service.CustomerInvoiceWriteoffDocumentService;
031    import org.kuali.kfs.module.ar.document.validation.ContinueCustomerInvoiceWriteoffDocumentRule;
032    import org.kuali.kfs.sys.KFSConstants;
033    import org.kuali.kfs.sys.context.SpringContext;
034    import org.kuali.kfs.sys.service.UniversityDateService;
035    import org.kuali.rice.kns.document.Document;
036    import org.kuali.rice.kns.document.TransactionalDocument;
037    import org.kuali.rice.kns.rules.TransactionalDocumentRuleBase;
038    import org.kuali.rice.kns.service.BusinessObjectService;
039    import org.kuali.rice.kns.service.DocumentService;
040    import org.kuali.rice.kns.service.ParameterService;
041    import org.kuali.rice.kns.util.GlobalVariables;
042    import org.kuali.rice.kns.util.KNSConstants;
043    import org.kuali.rice.kns.util.KualiDecimal;
044    import org.kuali.rice.kns.util.ObjectUtils;
045    
046    public class CustomerInvoiceWriteoffDocumentRule extends TransactionalDocumentRuleBase implements ContinueCustomerInvoiceWriteoffDocumentRule<TransactionalDocument> {
047    
048        /**
049         * @see org.kuali.rice.kns.rules.DocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.rice.kns.document.Document)
050         */
051        protected boolean processCustomSaveDocumentBusinessRules(Document document) {
052            boolean success = super.processCustomSaveDocumentBusinessRules(document);
053    
054            GlobalVariables.getMessageMap().addToErrorPath(KNSConstants.DOCUMENT_PROPERTY_NAME);
055    
056            CustomerInvoiceWriteoffDocument customerInvoiceWriteoffDocument = (CustomerInvoiceWriteoffDocument) document;
057            success &= validateCustomerNote(customerInvoiceWriteoffDocument.getCustomerNote());
058            success &= validateWriteoffGLPEGenerationInformation(customerInvoiceWriteoffDocument);
059            GlobalVariables.getMessageMap().removeFromErrorPath(KNSConstants.DOCUMENT_PROPERTY_NAME);
060    
061            return success;
062        }
063    
064        @Override
065        protected boolean processCustomRouteDocumentBusinessRules(Document document) {
066            boolean success = super.processCustomSaveDocumentBusinessRules(document);
067    
068            GlobalVariables.getMessageMap().addToErrorPath(KNSConstants.DOCUMENT_PROPERTY_NAME);
069    
070            CustomerInvoiceWriteoffDocument customerInvoiceWriteoffDocument = (CustomerInvoiceWriteoffDocument) document;
071            success &= validateCustomerNote(customerInvoiceWriteoffDocument.getCustomerNote());
072            success &= validateWriteoffGLPEGenerationInformation(customerInvoiceWriteoffDocument);
073            success &= doesCustomerInvoiceDocumentHaveValidBalance(customerInvoiceWriteoffDocument);
074    
075            GlobalVariables.getMessageMap().removeFromErrorPath(KNSConstants.DOCUMENT_PROPERTY_NAME);
076    
077            return success;
078        }
079    
080        protected boolean validateCustomerNote(String customerNote) {
081            boolean success = true;
082            if (StringUtils.isNotEmpty(customerNote) && (customerNote.trim().length() < 5)) {
083                GlobalVariables.getMessageMap().putError(ArPropertyConstants.CustomerInvoiceWriteoffLookupResultFields.CUSTOMER_NOTE, ArKeyConstants.ERROR_CUSTOMER_INVOICE_WRITEOFF_CUSTOMER_NOTE_INVALID);
084                success = false;
085            }
086    
087            return success;
088        }
089    
090        /**
091         * This method validates any writeoff GLPE required information
092         * 
093         * @param customerInvoiceWriteoffDocument
094         * @return
095         */
096        protected boolean validateWriteoffGLPEGenerationInformation(CustomerInvoiceWriteoffDocument customerInvoiceWriteoffDocument) {
097            boolean success = true;
098    
099            String writeoffGenerationOption = SpringContext.getBean(ParameterService.class).getParameterValue(CustomerInvoiceWriteoffDocument.class, ArConstants.GLPE_WRITEOFF_GENERATION_METHOD);
100    
101            if (ArConstants.GLPE_WRITEOFF_GENERATION_METHOD_CHART.equals(writeoffGenerationOption)) {
102                for (CustomerInvoiceDetail customerInvoiceDetail : customerInvoiceWriteoffDocument.getCustomerInvoiceDocument().getCustomerInvoiceDetailsWithoutDiscounts()) {
103                    success &= doesChartCodeHaveCorrespondingWriteoffObjectCode(customerInvoiceDetail);
104                }
105            }
106            else if (ArConstants.GLPE_WRITEOFF_GENERATION_METHOD_ORG_ACCT_DEFAULT.equals(writeoffGenerationOption)) {
107                success &= doesOrganizationAccountingDefaultHaveWriteoffInformation(customerInvoiceWriteoffDocument);
108            }
109    
110            String writeoffTaxGenerationOption = SpringContext.getBean(ParameterService.class).getParameterValue(CustomerInvoiceWriteoffDocument.class, ArConstants.ALLOW_SALES_TAX_LIABILITY_ADJUSTMENT_IND);
111            if (ArConstants.ALLOW_SALES_TAX_LIABILITY_ADJUSTMENT_IND_NO.equals(writeoffTaxGenerationOption)) {
112                success &= doesOrganizationAccountingDefaultHaveWriteoffInformation(customerInvoiceWriteoffDocument);
113            }
114    
115            return success;
116        }
117    
118        /**
119         * This method checks if the chart object code using on the invoice detail has a corresponding
120         * 
121         * @param customerInvoiceDetail
122         * @return TODO
123         */
124        protected boolean doesChartCodeHaveCorrespondingWriteoffObjectCode(CustomerInvoiceDetail customerInvoiceDetail) {
125            boolean success = true;
126    
127            String writeoffObjectCode = SpringContext.getBean(ParameterService.class).getParameterValue(CustomerInvoiceWriteoffDocument.class, ArConstants.GLPE_WRITEOFF_OBJECT_CODE_BY_CHART, customerInvoiceDetail.getChartOfAccountsCode());
128            if (StringUtils.isBlank(writeoffObjectCode)) {
129                GlobalVariables.getMessageMap().putError(ArPropertyConstants.CustomerInvoiceWriteoffDocumentFields.CUSTOMER_INVOICE_DETAILS_FOR_WRITEOFF, ArKeyConstants.ERROR_CUSTOMER_INVOICE_WRITEOFF_CHART_WRITEOFF_OBJECT_DOESNT_EXIST, customerInvoiceDetail.getChartOfAccountsCode());
130                success = false;
131            }
132    
133            return success;
134        }
135    
136        protected boolean doesOrganizationAccountingDefaultHaveWriteoffInformation(CustomerInvoiceWriteoffDocument customerInvoiceWriteoffDocument) {
137            boolean success = true;
138            Integer currentFiscalYear = SpringContext.getBean(UniversityDateService.class).getCurrentFiscalYear();
139            String billByChartOfAccountCode = customerInvoiceWriteoffDocument.getCustomerInvoiceDocument().getBillByChartOfAccountCode();
140            String billedByOrganizationCode = customerInvoiceWriteoffDocument.getCustomerInvoiceDocument().getBilledByOrganizationCode();
141    
142            Map<String, Object> criteria = new HashMap<String, Object>();
143            criteria.put("universityFiscalYear", currentFiscalYear);
144            criteria.put("chartOfAccountsCode", billByChartOfAccountCode);
145            criteria.put("organizationCode", billedByOrganizationCode);
146    
147            OrganizationAccountingDefault organizationAccountingDefault = (OrganizationAccountingDefault) SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(OrganizationAccountingDefault.class, criteria);
148    
149            if (ObjectUtils.isNotNull(organizationAccountingDefault)) {
150                success &= doesWriteoffAccountNumberExist(organizationAccountingDefault);
151                success &= doesWriteoffChartOfAccountsCodeExist(organizationAccountingDefault);
152                success &= doesWriteoffFinancialObjectCodeExist(organizationAccountingDefault);
153            }
154            else {
155                GlobalVariables.getMessageMap().putError(ArPropertyConstants.CustomerInvoiceWriteoffDocumentFields.CUSTOMER_INVOICE_DETAILS_FOR_WRITEOFF, ArKeyConstants.ERROR_CUSTOMER_INVOICE_WRITEOFF_FAU_MUST_EXIST, new String[] { currentFiscalYear.toString(), billByChartOfAccountCode, billedByOrganizationCode });
156                success = false;
157            }
158    
159            return success;
160    
161        }
162    
163        /**
164         * This method returns true if payment account number is provided and is valid.
165         * 
166         * @param doc
167         * @return
168         */
169        protected boolean doesWriteoffAccountNumberExist(OrganizationAccountingDefault organizationAccountingDefault) {
170    
171            if (StringUtils.isBlank(organizationAccountingDefault.getWriteoffAccountNumber())) {
172                GlobalVariables.getMessageMap().putError(ArPropertyConstants.CustomerInvoiceWriteoffDocumentFields.CUSTOMER_INVOICE_DETAILS_FOR_WRITEOFF, ArKeyConstants.ERROR_CUSTOMER_INVOICE_WRITEOFF_FAU_ACCOUNT_MUST_EXIST, new String[] { organizationAccountingDefault.getUniversityFiscalYear().toString(), organizationAccountingDefault.getChartOfAccountsCode(), organizationAccountingDefault.getOrganizationCode() });
173                return false;
174            }
175    
176            return true;
177        }
178    
179        /**
180         * This method returns true if payment chart of accounts code is provided and is valid
181         * 
182         * @param doc
183         * @return
184         */
185        protected boolean doesWriteoffChartOfAccountsCodeExist(OrganizationAccountingDefault organizationAccountingDefault) {
186    
187            if (StringUtils.isBlank(organizationAccountingDefault.getWriteoffChartOfAccountsCode())) {
188                GlobalVariables.getMessageMap().putError(ArPropertyConstants.CustomerInvoiceWriteoffDocumentFields.CUSTOMER_INVOICE_DETAILS_FOR_WRITEOFF, ArKeyConstants.ERROR_CUSTOMER_INVOICE_WRITEOFF_FAU_CHART_MUST_EXIST, new String[] { organizationAccountingDefault.getUniversityFiscalYear().toString(), organizationAccountingDefault.getChartOfAccountsCode(), organizationAccountingDefault.getOrganizationCode() });
189                return false;
190            }
191    
192            return true;
193        }
194    
195        /**
196         * This method returns true if payment financial object code is provided and is valid
197         * 
198         * @param doc
199         * @return
200         */
201        protected boolean doesWriteoffFinancialObjectCodeExist(OrganizationAccountingDefault organizationAccountingDefault) {
202            if (StringUtils.isBlank(organizationAccountingDefault.getWriteoffFinancialObjectCode())) {
203                GlobalVariables.getMessageMap().putError(ArPropertyConstants.CustomerInvoiceWriteoffDocumentFields.CUSTOMER_INVOICE_DETAILS_FOR_WRITEOFF, ArKeyConstants.ERROR_CUSTOMER_INVOICE_WRITEOFF_FAU_OBJECT_CODE_MUST_EXIST, new String[] { organizationAccountingDefault.getUniversityFiscalYear().toString(), organizationAccountingDefault.getChartOfAccountsCode(), organizationAccountingDefault.getOrganizationCode() });
204                return false;
205            }
206    
207            return true;
208        }
209    
210        /**
211         * This method returns true if customer invoice document for writeoff does not have a credit balance (i.e. a open amount less
212         * than 0).
213         * 
214         * @param customerInvoiceWriteoffDocument
215         * @return
216         */
217        protected boolean doesCustomerInvoiceDocumentHaveValidBalance(CustomerInvoiceWriteoffDocument customerInvoiceWriteoffDocument) {
218            if (KualiDecimal.ZERO.isGreaterEqual(customerInvoiceWriteoffDocument.getCustomerInvoiceDocument().getOpenAmount())) {
219                GlobalVariables.getMessageMap().putError(ArPropertyConstants.CustomerInvoiceWriteoffDocumentFields.CUSTOMER_INVOICE_DETAILS_FOR_WRITEOFF, ArKeyConstants.ERROR_CUSTOMER_INVOICE_WRITEOFF_INVOICE_HAS_CREDIT_BALANCE);
220                return false;
221            }
222            return true;
223        }
224    
225        /**
226         * This method checks if there is no another CRM in route for the invoice not in route if CRM status is one of the following:
227         * processed, cancelled, or disapproved
228         * 
229         * @param invoice
230         * @return
231         */
232        public boolean checkIfThereIsNoAnotherCRMInRouteForTheInvoice(String invoiceDocumentNumber) {
233            CustomerInvoiceWriteoffDocumentService service = SpringContext.getBean(CustomerInvoiceWriteoffDocumentService.class);
234            boolean success = service.checkIfThereIsNoAnotherCRMInRouteForTheInvoice(invoiceDocumentNumber);
235            if (!success)
236                GlobalVariables.getMessageMap().putError(ArPropertyConstants.CustomerCreditMemoDocumentFields.CREDIT_MEMO_DOCUMENT_REF_INVOICE_NUMBER, ArKeyConstants.ERROR_CUSTOMER_CREDIT_MEMO_DOCUMENT_ONE_CRM_IN_ROUTE_PER_INVOICE);
237    
238            return success;
239        }
240    
241        /**
242         * This method checks if there is no another writeoff in route for the invoice not in route if CRM status is one of the
243         * following: processed, cancelled, or disapproved
244         * 
245         * @param invoice
246         * @return
247         */
248        public boolean checkIfThereIsNoAnotherWriteoffInRouteForTheInvoice(String invoiceDocumentNumber) {
249            CustomerInvoiceWriteoffDocumentService service = SpringContext.getBean(CustomerInvoiceWriteoffDocumentService.class);
250            boolean success = service.checkIfThereIsNoAnotherWriteoffInRouteForTheInvoice(invoiceDocumentNumber);
251            if (!success)
252                GlobalVariables.getMessageMap().putError(ArPropertyConstants.CustomerCreditMemoDocumentFields.CREDIT_MEMO_DOCUMENT_REF_INVOICE_NUMBER, ArKeyConstants.ERROR_CUSTOMER_INVOICE_WRITEOFF_ONE_WRITEOFF_IN_ROUTE_PER_INVOICE);
253    
254            return success;
255        }
256    
257        /*
258         * @see org.kuali.kfs.module.ar.document.validation.ContinueCustomerInvoiceWriteoffDocumentRule#processContinueCustomerInvoiceWriteoffDocumentRules(org.kuali.kfs.sys.document.AccountingDocument)
259         */
260    
261        public boolean processContinueCustomerInvoiceWriteoffDocumentRules(TransactionalDocument document) {
262            boolean success;
263            CustomerInvoiceWriteoffDocument customerInvoiceWriteoffDocument = (CustomerInvoiceWriteoffDocument) document;
264    
265            success = checkIfInvoiceNumberIsValid(customerInvoiceWriteoffDocument.getFinancialDocumentReferenceInvoiceNumber());
266            if (success)
267                success = doesCustomerInvoiceDocumentHaveValidBalance(customerInvoiceWriteoffDocument);
268            if (success)
269                success = checkIfThereIsNoAnotherCRMInRouteForTheInvoice(customerInvoiceWriteoffDocument.getFinancialDocumentReferenceInvoiceNumber());
270            if (success)
271                success = checkIfThereIsNoAnotherWriteoffInRouteForTheInvoice(customerInvoiceWriteoffDocument.getFinancialDocumentReferenceInvoiceNumber());
272    
273            return success;
274        }
275    
276        public boolean checkIfInvoiceNumberIsValid(String invDocumentNumber) {
277            boolean success = true;
278    
279            if (ObjectUtils.isNull(invDocumentNumber) || StringUtils.isBlank(invDocumentNumber)) {
280                success = false;
281                GlobalVariables.getMessageMap().putError(ArPropertyConstants.CustomerCreditMemoDocumentFields.CREDIT_MEMO_DOCUMENT_REF_INVOICE_NUMBER, ArKeyConstants.ERROR_CUSTOMER_CREDIT_MEMO_DOCUMENT__INVOICE_DOCUMENT_NUMBER_IS_REQUIRED);
282            }
283            else {
284                CustomerInvoiceDocumentService service = SpringContext.getBean(CustomerInvoiceDocumentService.class);
285                CustomerInvoiceDocument customerInvoiceDocument = service.getInvoiceByInvoiceDocumentNumber(invDocumentNumber);
286    
287                if (ObjectUtils.isNull(customerInvoiceDocument)) {
288                    success = false;
289                    GlobalVariables.getMessageMap().putError(ArPropertyConstants.CustomerCreditMemoDocumentFields.CREDIT_MEMO_DOCUMENT_REF_INVOICE_NUMBER, ArKeyConstants.ERROR_CUSTOMER_CREDIT_MEMO_DOCUMENT_INVALID_INVOICE_DOCUMENT_NUMBER);
290                }
291                else if (!SpringContext.getBean(CustomerInvoiceDocumentService.class).checkIfInvoiceNumberIsFinal(invDocumentNumber)) {
292                    success = false;
293                    GlobalVariables.getMessageMap().putError(ArPropertyConstants.CustomerCreditMemoDocumentFields.CREDIT_MEMO_DOCUMENT_REF_INVOICE_NUMBER, ArKeyConstants.ERROR_CUSTOMER_INVOICE_DOCUMENT_NOT_FINAL);
294                }
295    
296            }
297            return success;
298        }
299    }