/*
* The Kuali Financial System, a comprehensive financial management system for higher education.
*
* Copyright 2005-2014 The Kuali Foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kuali.kfs.module.purap.document.service.impl;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.integration.cab.CapitalAssetBuilderModuleService;
import org.kuali.kfs.integration.purap.CapitalAssetSystem;
import org.kuali.kfs.module.purap.PurapConstants;
import org.kuali.kfs.module.purap.PurapPropertyConstants;
import org.kuali.kfs.module.purap.PurapRuleConstants;
import org.kuali.kfs.module.purap.businessobject.PurApAccountingLine;
import org.kuali.kfs.module.purap.businessobject.PurApItem;
import org.kuali.kfs.module.purap.businessobject.PurchasingCapitalAssetItem;
import org.kuali.kfs.module.purap.businessobject.RequisitionCapitalAssetItem;
import org.kuali.kfs.module.purap.businessobject.RequisitionCapitalAssetSystem;
import org.kuali.kfs.module.purap.businessobject.RequisitionItem;
import org.kuali.kfs.module.purap.document.PurchaseOrderDocument;
import org.kuali.kfs.module.purap.document.PurchasingDocument;
import org.kuali.kfs.module.purap.document.RequisitionDocument;
import org.kuali.kfs.module.purap.document.dataaccess.RequisitionDao;
import org.kuali.kfs.module.purap.document.service.PurapService;
import org.kuali.kfs.module.purap.document.service.RequisitionService;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.service.PostalCodeValidationService;
import org.kuali.kfs.sys.service.UniversityDateService;
import org.kuali.kfs.vnd.businessobject.VendorCommodityCode;
import org.kuali.kfs.vnd.businessobject.VendorContract;
import org.kuali.kfs.vnd.businessobject.VendorDetail;
import org.kuali.kfs.vnd.document.service.VendorService;
import org.kuali.rice.core.api.config.property.ConfigurationService;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.coreservice.framework.parameter.ParameterService;
import org.kuali.rice.kew.api.exception.WorkflowException;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.kim.api.identity.PersonService;
import org.kuali.rice.kim.api.role.RoleMembership;
import org.kuali.rice.kim.api.role.RoleService;
import org.kuali.rice.krad.bo.Note;
import org.kuali.rice.krad.service.BusinessObjectService;
import org.kuali.rice.krad.service.DocumentService;
import org.kuali.rice.krad.service.KualiRuleService;
import org.kuali.rice.krad.util.ObjectUtils;
import org.springframework.transaction.annotation.Transactional;
/**
* Implementation of RequisitionService
*/
@Transactional
public class RequisitionServiceImpl implements RequisitionService {
private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(RequisitionServiceImpl.class);
private BusinessObjectService businessObjectService;
private CapitalAssetBuilderModuleService capitalAssetBuilderModuleService;
private DateTimeService dateTimeService;
private DocumentService documentService;
private KualiRuleService ruleService;
private ConfigurationService kualiConfigurationService;
private ParameterService parameterService;
private PersonService personService;
private PostalCodeValidationService postalCodeValidationService;
private PurapService purapService;
private RequisitionDao requisitionDao;
private UniversityDateService universityDateService;
private VendorService vendorService;
private RoleService roleService;
@Override
public PurchasingCapitalAssetItem createCamsItem(PurchasingDocument purDoc, PurApItem purapItem) {
PurchasingCapitalAssetItem camsItem = new RequisitionCapitalAssetItem();
camsItem.setItemIdentifier(purapItem.getItemIdentifier());
// If the system type is INDIVIDUAL then for each of the capital asset items, we need a system attached to it.
if (purDoc.getCapitalAssetSystemTypeCode().equals(PurapConstants.CapitalAssetTabStrings.INDIVIDUAL_ASSETS)) {
CapitalAssetSystem resultSystem = new RequisitionCapitalAssetSystem();
camsItem.setPurchasingCapitalAssetSystem(resultSystem);
}
camsItem.setPurchasingDocument(purDoc);
return camsItem;
}
@Override
public CapitalAssetSystem createCapitalAssetSystem() {
CapitalAssetSystem resultSystem = new RequisitionCapitalAssetSystem();
return resultSystem;
}
/**
* @see org.kuali.kfs.module.purap.document.service.RequisitionService#getRequisitionById(java.lang.Integer)
*/
@Override
public RequisitionDocument getRequisitionById(Integer id) {
String documentNumber = requisitionDao.getDocumentNumberForRequisitionId(id);
if (ObjectUtils.isNotNull(documentNumber)) {
try {
RequisitionDocument doc = (RequisitionDocument) documentService.getByDocumentHeaderId(documentNumber);
return doc;
}
catch (WorkflowException e) {
String errorMessage = "Error getting requisition document from document service";
LOG.error("getRequisitionById() " + errorMessage, e);
throw new RuntimeException(errorMessage, e);
}
}
return null;
}
/**
* @see org.kuali.kfs.module.purap.document.service.RequisitionService#isAutomaticPurchaseOrderAllowed(org.kuali.kfs.module.purap.document.RequisitionDocument)
*/
@Override
public boolean isAutomaticPurchaseOrderAllowed(RequisitionDocument requisition) {
LOG.debug("isAutomaticPurchaseOrderAllowed() started");
/*
* The private checkAutomaticPurchaseOrderRules method contains rules to check if a requisition can become an APO (Automatic
* Purchase Order). The method returns a string containing the reason why this method should return false. Save the reason
* as a note on the requisition.
*/
String note = checkAutomaticPurchaseOrderRules(requisition);
if (StringUtils.isNotEmpty(note)) {
note = PurapConstants.REQ_REASON_NOT_APO + note;
try {
Note apoNote = documentService.createNoteFromDocument(requisition, note);
requisition.addNote(apoNote);
documentService.saveDocumentNotes(requisition);
}
catch (Exception e) {
throw new RuntimeException(PurapConstants.REQ_UNABLE_TO_CREATE_NOTE + " " + e);
}
if (LOG.isDebugEnabled()) {
LOG.debug("isAutomaticPurchaseOrderAllowed() return false; " + note);
}
return false;
}
LOG.debug("isAutomaticPurchaseOrderAllowed() You made it! Your REQ can become an APO; return true.");
return true;
}
/**
* Checks the rule for Automatic Purchase Order eligibility of the requisition and return a String containing the reason why the
* requisition was not eligible to become an APO if it was not eligible, or return an empty String if the requisition is
* eligible to become an APO
*
* @param requisition the requisition document to be checked for APO eligibility.
* @return String containing the reason why the requisition was not eligible to become an APO if it was not eligible, or an
* empty String if the requisition is eligible to become an APO.
*/
protected String checkAutomaticPurchaseOrderRules(RequisitionDocument requisition) {
String requisitionSource = requisition.getRequisitionSourceCode();
KualiDecimal reqTotal = requisition.getTotalDollarAmount();
KualiDecimal apoLimit = purapService.getApoLimit(requisition.getVendorContractGeneratedIdentifier(), requisition.getChartOfAccountsCode(), requisition.getOrganizationCode());
requisition.setOrganizationAutomaticPurchaseOrderLimit(apoLimit);
if (LOG.isDebugEnabled()) {
LOG.debug("isAPO() reqId = " + requisition.getPurapDocumentIdentifier() + "; apoLimit = " + apoLimit + "; reqTotal = " + reqTotal);
}
if (apoLimit == null) {
return "APO limit is empty.";
}
else {
if (reqTotal.compareTo(apoLimit) == 1) {
return "Requisition total is greater than the APO limit.";
}
}
if (reqTotal.compareTo(KualiDecimal.ZERO) <= 0) {
return "Requisition total is not greater than zero.";
}
if (LOG.isDebugEnabled()) {
LOG.debug("isAPO() vendor #" + requisition.getVendorHeaderGeneratedIdentifier() + "-" + requisition.getVendorDetailAssignedIdentifier());
}
if (requisition.getVendorHeaderGeneratedIdentifier() == null || requisition.getVendorDetailAssignedIdentifier() == null) {
return "Vendor was not selected from the vendor database.";
}
else {
VendorDetail vendorDetail = vendorService.getVendorDetail(requisition.getVendorHeaderGeneratedIdentifier(), requisition.getVendorDetailAssignedIdentifier());
if (vendorDetail == null) {
return "Error retrieving vendor from the database.";
}
if ( StringUtils.isBlank(requisition.getVendorLine1Address()) ||
StringUtils.isBlank(requisition.getVendorCityName()) ||
StringUtils.isBlank(requisition.getVendorCountryCode())) {
return "Requisition does not have all of the vendor address fields that are required for Purchase Order.";
}
requisition.setVendorRestrictedIndicator(vendorDetail.getVendorRestrictedIndicator());
if (requisition.getVendorRestrictedIndicator() != null && requisition.getVendorRestrictedIndicator()) {
return "Selected vendor is marked as restricted.";
}
if (vendorDetail.isVendorDebarred()) {
return "Selected vendor is marked as a debarred vendor";
}
requisition.setVendorDetail(vendorDetail);
if ((!PurapConstants.RequisitionSources.B2B.equals(requisitionSource)) && ObjectUtils.isNull(requisition.getVendorContractGeneratedIdentifier())) {
Person initiator = getPersonService().getPerson(requisition.getDocumentHeader().getWorkflowDocument().getInitiatorPrincipalId());
VendorContract b2bContract = vendorService.getVendorB2BContract(vendorDetail, initiator.getCampusCode());
if (b2bContract != null) {
return "Standard requisition with no contract selected but a B2B contract exists for the selected vendor.";
}
}
//vendor contract expirated date validation....KFSMI-8502
// if the vendor is selected through vendor contract is selected
if (StringUtils.isNotBlank(requisition.getVendorContractName())) {
if (vendorService.isVendorContractExpired(requisition, requisition.getVendorContractGeneratedIdentifier(), vendorDetail)) {
return "Contracted Vendor used where the contract end date is expired.";
}
}
}
//if vendor address isn't complete, no APO
if (StringUtils.isBlank(requisition.getVendorLine1Address()) ||
StringUtils.isBlank(requisition.getVendorCityName()) ||
StringUtils.isBlank(requisition.getVendorCountryCode()) ||
!postalCodeValidationService.validateAddress(requisition.getVendorCountryCode(), requisition.getVendorStateCode(), requisition.getVendorPostalCode(), "", "")) {
return "Requistion does not contain a complete vendor address";
}
// These are needed for commodity codes. They are put in here so that
// we don't have to loop through items too many times.
String purchaseOrderRequiresCommodityCode = parameterService.getParameterValueAsString(PurchaseOrderDocument.class, PurapRuleConstants.ITEMS_REQUIRE_COMMODITY_CODE_IND);
boolean commodityCodeRequired = purchaseOrderRequiresCommodityCode.equals("Y");
for (Iterator iter = requisition.getItems().iterator(); iter.hasNext();) {
RequisitionItem item = (RequisitionItem) iter.next();
if (item.isItemRestrictedIndicator()) {
return "Requisition contains an item that is marked as restricted.";
}
//We only need to check the commodity codes if this is an above the line item.
if (item.getItemType().isLineItemIndicator()) {
String commodityCodesReason = "";
List<VendorCommodityCode> vendorCommodityCodes = commodityCodeRequired ? requisition.getVendorDetail().getVendorCommodities() : null;
commodityCodesReason = checkAPORulesPerItemForCommodityCodes(item, vendorCommodityCodes, commodityCodeRequired);
if (StringUtils.isNotBlank(commodityCodesReason)) {
return commodityCodesReason;
}
}
if (PurapConstants.ItemTypeCodes.ITEM_TYPE_ORDER_DISCOUNT_CODE.equals(item.getItemType().getItemTypeCode()) || PurapConstants.ItemTypeCodes.ITEM_TYPE_TRADE_IN_CODE.equals(item.getItemType().getItemTypeCode())) {
if ((item.getItemUnitPrice() != null) && ((BigDecimal.ZERO.compareTo(item.getItemUnitPrice())) != 0)) {
// discount or trade-in item has unit price that is not empty or zero
return "Requisition contains a " + item.getItemType().getItemTypeDescription() + " item, so it does not qualify as an APO.";
}
}
if (!PurapConstants.RequisitionSources.B2B.equals(requisitionSource)) {
for (PurApAccountingLine accountingLine : item.getSourceAccountingLines()) {
if (capitalAssetBuilderModuleService.doesAccountingLineFailAutomaticPurchaseOrderRules(accountingLine)) {
return "Requisition contains accounting line with capital object level";
}
}
}
}// endfor items
if (capitalAssetBuilderModuleService.doesDocumentFailAutomaticPurchaseOrderRules(requisition)) {
return "Requisition contains capital asset items.";
}
if (StringUtils.isNotEmpty(requisition.getRecurringPaymentTypeCode())) {
return "Payment type is marked as recurring.";
}
if ((requisition.getPurchaseOrderTotalLimit() != null) && (KualiDecimal.ZERO.compareTo(requisition.getPurchaseOrderTotalLimit()) != 0)) {
LOG.debug("isAPO() po total limit is not null and not equal to zero; return false.");
return "The 'PO not to exceed' amount has been entered.";
}
if (StringUtils.isNotEmpty(requisition.getAlternate1VendorName()) || StringUtils.isNotEmpty(requisition.getAlternate2VendorName()) || StringUtils.isNotEmpty(requisition.getAlternate3VendorName()) || StringUtils.isNotEmpty(requisition.getAlternate4VendorName()) || StringUtils.isNotEmpty(requisition.getAlternate5VendorName())) {
LOG.debug("isAPO() alternate vendor name exists; return false.");
return "Requisition contains additional suggested vendor names.";
}
if (requisition.isPostingYearNext() && !purapService.isTodayWithinApoAllowedRange()) {
return "Requisition is set to encumber next fiscal year and approval is not within APO allowed date range.";
}
return "";
}
/**
* Checks the APO rules for Commodity Codes.
* The rules are as follow:
* 1. If an institution does not require a commodity code on a requisition but
* does require a commodity code on a purchase order:
* a. If the requisition qualifies for an APO and the commodity code is blank
* on any line item then the system should use the default commodity code
* for the vendor.
* b. If there is not a default commodity code for the vendor then the
* requisition is not eligible to become an APO.
* 2. The commodity codes where the restricted indicator is Y should disallow
* the requisition from becoming an APO.
* 3. If the commodity code is Inactive when the requisition is finally approved
* do not allow the requisition to become an APO.
*
* @param purItem
* @param vendorCommodityCodes
* @param commodityCodeRequired
* @return
*/
protected String checkAPORulesPerItemForCommodityCodes(RequisitionItem purItem, List<VendorCommodityCode>vendorCommodityCodes, boolean commodityCodeRequired) {
// If the commodity code is blank on any line item and a commodity code is required,
// then the system should use the default commodity code for the vendor
if (purItem.getCommodityCode() == null && commodityCodeRequired) {
for (VendorCommodityCode vcc : vendorCommodityCodes) {
if (vcc.isCommodityDefaultIndicator()) {
purItem.setCommodityCode(vcc.getCommodityCode());
purItem.setPurchasingCommodityCode(vcc.getPurchasingCommodityCode());
}
}
}
if (purItem.getCommodityCode() == null) {
// If there is not a default commodity code for the vendor then the requisition is not eligible to become an APO.
if (commodityCodeRequired) {
return "There are missing commodity code(s).";
}
}
else if (!purItem.getCommodityCode().isActive()) {
return "Requisition contains inactive commodity codes.";
}
else if (purItem.getCommodityCode().isRestrictedItemsIndicator()) {
return "Requisition contains an item with a restricted commodity code.";
}
return "";
}
/**
* @see org.kuali.kfs.module.purap.document.service.RequisitionService#getRequisitionsAwaitingContractManagerAssignment()
*/
@Override
public List<RequisitionDocument> getRequisitionsAwaitingContractManagerAssignment() {
return requisitionDao.getDocumentsAwaitingContractManagerAssignment();
}
/**
* This method was added as part of the move to rice20 as a way to get at application doc status. Since
* this data has been moved back into KFS this function is no longer necessary. The code will be removed
* in the 6.0 release.
*/
@Deprecated
protected List<String> getDocumentsNumbersAwaitingContractManagerAssignment() {
List<String> requisitionDocumentNumbers = requisitionDao.getDocumentNumbersAwaitingContractManagerAssignment();
return requisitionDocumentNumbers;
}
/**
* @see org.kuali.kfs.module.purap.document.service.RequisitionService#getCountOfRequisitionsAwaitingContractManagerAssignment()
*/
@Override
public int getCountOfRequisitionsAwaitingContractManagerAssignment() {
List<RequisitionDocument> unassignedRequisitions = getRequisitionsAwaitingContractManagerAssignment();
if (ObjectUtils.isNotNull(unassignedRequisitions)) {
return unassignedRequisitions.size();
}
else {
return 0;
}
}
public void setBusinessObjectService(BusinessObjectService boService) {
this.businessObjectService = boService;
}
public void setDocumentService(DocumentService documentService) {
this.documentService = documentService;
}
public void setRequisitionDao(RequisitionDao requisitionDao) {
this.requisitionDao = requisitionDao;
}
public void setPurapService(PurapService purapService) {
this.purapService = purapService;
}
public KualiRuleService getRuleService() {
return ruleService;
}
public void setRuleService(KualiRuleService ruleService) {
this.ruleService = ruleService;
}
public void setParameterService(ParameterService parameterService) {
this.parameterService = parameterService;
}
public void setDateTimeService(DateTimeService dateTimeService) {
this.dateTimeService = dateTimeService;
}
public void setUniversityDateService(UniversityDateService universityDateService) {
this.universityDateService = universityDateService;
}
public void setVendorService(VendorService vendorService) {
this.vendorService = vendorService;
}
public void setConfigurationService(ConfigurationService kualiConfigurationService) {
this.kualiConfigurationService = kualiConfigurationService;
}
public void setCapitalAssetBuilderModuleService(CapitalAssetBuilderModuleService capitalAssetBuilderModuleService) {
this.capitalAssetBuilderModuleService = capitalAssetBuilderModuleService;
}
public void setPostalCodeValidationService(PostalCodeValidationService postalCodeValidationService) {
this.postalCodeValidationService = postalCodeValidationService;
}
/**
* Sets the roleService.
* @param serv- the RoleService to set
*/
public void setRoleService(RoleService serv){
this.roleService = serv;
}
/**
* @return Returns the roleService.
*/
public RoleService getRoleService(){
return this.roleService;
}
/**
* @return Returns the personService.
*/
protected PersonService getPersonService() {
if(personService==null) {
personService = SpringContext.getBean(PersonService.class);
}
return personService;
}
@Override
public boolean hasContentReviewer(String org, String chart) {
String roleId = roleService.getRoleIdByNamespaceCodeAndName(PurapConstants.PURAP_NAMESPACE, "Content Reviewer");
Map<String, String> qualification = new HashMap<String, String>(2);
qualification.put(PurapPropertyConstants.ORGANIZATION_CODE, org);
qualification.put(PurapPropertyConstants.CHART_OF_ACCOUNTS_CODE, chart);
List<RoleMembership> members = roleService.getRoleMembers(java.util.Arrays.asList(roleId), qualification);
return members.size() > 0;
}
}