org.openmrs.module.webservices.rest.resource.StockOperationResource.java Source code

Java tutorial

Introduction

Here is the source code for org.openmrs.module.webservices.rest.resource.StockOperationResource.java

Source

/*
 * The contents of this file are subject to the OpenMRS Public License
 * Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://license.openmrs.org
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * Copyright (C) OpenMRS, LLC.  All Rights Reserved.
 */
package org.openmrs.module.webservices.rest.resource;

import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.User;
import org.openmrs.api.context.Context;
import org.openmrs.module.openhmis.commons.api.PagingInfo;
import org.openmrs.module.openhmis.commons.api.Utility;
import org.openmrs.module.openhmis.commons.api.entity.IMetadataDataService;
import org.openmrs.module.openhmis.commons.api.f.Action2;
import org.openmrs.module.openhmis.inventory.api.IItemDataService;
import org.openmrs.module.openhmis.inventory.api.IStockOperationDataService;
import org.openmrs.module.openhmis.inventory.api.IStockOperationService;
import org.openmrs.module.openhmis.inventory.api.IStockOperationTypeDataService;
import org.openmrs.module.openhmis.inventory.api.IStockroomDataService;
import org.openmrs.module.openhmis.inventory.api.model.IStockOperationType;
import org.openmrs.module.openhmis.inventory.api.model.Item;
import org.openmrs.module.openhmis.inventory.api.model.StockOperation;
import org.openmrs.module.openhmis.inventory.api.model.StockOperationAttribute;
import org.openmrs.module.openhmis.inventory.api.model.StockOperationItem;
import org.openmrs.module.openhmis.inventory.api.model.StockOperationStatus;
import org.openmrs.module.openhmis.inventory.api.model.Stockroom;
import org.openmrs.module.openhmis.inventory.api.search.StockOperationSearch;
import org.openmrs.module.openhmis.inventory.api.search.StockOperationTemplate;
import org.openmrs.module.openhmis.inventory.api.util.PrivilegeConstants;
import org.openmrs.module.openhmis.inventory.web.ModuleRestConstants;
import org.openmrs.module.webservices.rest.helper.IdgenHelper;
import org.openmrs.module.webservices.rest.web.RequestContext;
import org.openmrs.module.webservices.rest.web.annotation.PropertySetter;
import org.openmrs.module.webservices.rest.web.annotation.Resource;
import org.openmrs.module.webservices.rest.web.representation.RefRepresentation;
import org.openmrs.module.webservices.rest.web.representation.Representation;
import org.openmrs.module.webservices.rest.web.resource.api.PageableResult;
import org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription;
import org.openmrs.module.webservices.rest.web.resource.impl.EmptySearchResult;
import org.springframework.web.client.RestClientException;

@Resource(name = ModuleRestConstants.OPERATION_RESOURCE, supportedClass = StockOperation.class, supportedOpenmrsVersions = {
        "1.9.*", "1.10.*", "1.11.*", "1.12.*" })
public class StockOperationResource extends
        BaseRestInstanceCustomizableMetadataResource<StockOperation, IStockOperationType, StockOperationAttribute> {
    private static final Log LOG = LogFactory.getLog(StockOperationResource.class);

    private IStockOperationService operationService;
    private IStockOperationTypeDataService stockOperationTypeDataService;
    private IStockroomDataService stockroomDataService;
    private IItemDataService itemDataService;
    private boolean submitRequired = false;
    private boolean rollbackRequired = false;

    public StockOperationResource() {
        this.operationService = Context.getService(IStockOperationService.class);
        this.stockOperationTypeDataService = Context.getService(IStockOperationTypeDataService.class);
        this.stockroomDataService = Context.getService(IStockroomDataService.class);
        this.itemDataService = Context.getService(IItemDataService.class);
    }

    @Override
    public StockOperation newDelegate() {
        return new StockOperation();
    }

    @Override
    public Class<? extends IMetadataDataService<StockOperation>> getServiceClass() {
        return IStockOperationDataService.class;
    }

    @Override
    public DelegatingResourceDescription getRepresentationDescription(Representation rep) {
        DelegatingResourceDescription description = super.getRepresentationDescription(rep);
        description.addProperty("instanceType", Representation.DEFAULT);
        description.addProperty("status", Representation.DEFAULT);
        description.addProperty("operationNumber", Representation.DEFAULT);
        description.addProperty("dateCreated", Representation.DEFAULT);
        description.addProperty("operationDate", Representation.DEFAULT);
        description.addProperty("operationOrder", Representation.DEFAULT);
        description.addProperty("cancelReason", Representation.DEFAULT);

        if (!(rep instanceof RefRepresentation)) {
            description.addProperty("source", Representation.REF);
            description.addProperty("destination", Representation.REF);
            description.addProperty("patient", Representation.REF);
            description.addProperty("institution", Representation.REF);
            description.addProperty("department", Representation.REF);

            description.addProperty("canProcess", findMethod("userCanProcess"));
            description.addProperty("canRollback", findMethod("userCanRollback"));
        }

        return description;
    }

    @Override
    public DelegatingResourceDescription getCreatableProperties() {
        DelegatingResourceDescription description = super.getCreatableProperties();
        description.addProperty("items", Representation.REF);

        return description;
    }

    public Boolean userCanProcess(StockOperation operation) {
        return StockOperationTypeResource.userCanProcess(operation.getInstanceType());
    }

    public Boolean userCanRollback(StockOperation operation) {
        return Context.hasPrivilege(PrivilegeConstants.ROLLBACK_OPERATIONS);
    }

    @Override
    public StockOperation save(StockOperation operation) {
        // Ensure that the current user can process the operation
        if (!userCanProcess(operation)) {
            throw new RestClientException("The current user not authorized to process this operation.");
        }

        StockOperation result;

        // If the status has changed, submit the operation
        try {
            if (submitRequired) {
                result = operationService.submitOperation(operation);
            } else if (rollbackRequired) {
                if (!userCanRollback(operation)) {
                    throw new RestClientException("The current user not authorized to rollback this operation.");
                }

                result = operationService.rollbackOperation(operation);
            } else {
                result = super.save(operation);
            }
        } finally {
            submitRequired = false;
            rollbackRequired = false;
        }

        return result;
    }

    @PropertySetter("operationNumber")
    public void setOperationNumber(StockOperation instance, String operationNumber) {
        if (StringUtils.isEmpty(instance.getOperationNumber())) {
            if (IdgenHelper.isOperationNumberGenerated()) {
                operationNumber = IdgenHelper.generateId();
                instance.setOperationNumber(operationNumber);
            } else if (StringUtils.isEmpty(operationNumber)) {
                LOG.error("Operation Number not defined or generated.");

                throw new IllegalStateException(
                        "The Operation Number was not defined and no generator was configured.");
            } else {
                instance.setOperationNumber(operationNumber);
            }
        } else if (!StringUtils.isEmpty(operationNumber)) {
            instance.setOperationNumber(operationNumber);
        } else {
            LOG.error("Operation Number not defined or generated.");

            throw new IllegalStateException(
                    "The Operation Number was not defined and no generator was configured.");
        }
    }

    @PropertySetter("status")
    public void setStatus(StockOperation operation, StockOperationStatus status) {
        if (operation.getStatus() != status) {
            if (status == StockOperationStatus.ROLLBACK) {
                rollbackRequired = true;
            } else {
                submitRequired = true;

                operation.setStatus(status);
            }
        }
    }

    @PropertySetter(value = "items")
    public void setItems(final StockOperation operation, Set<StockOperationItem> items) {
        if (operation.getItems() == null) {
            operation.setItems(new HashSet<StockOperationItem>(items.size()));
        }

        processItemStock(operation, items);

        BaseRestDataResource.syncCollection(operation.getItems(), items,
                new Action2<Collection<StockOperationItem>, StockOperationItem>() {
                    @Override
                    public void apply(Collection<StockOperationItem> collection, StockOperationItem item) {
                        operation.addItem(item);
                    }
                }, new Action2<Collection<StockOperationItem>, StockOperationItem>() {
                    @Override
                    public void apply(Collection<StockOperationItem> collection, StockOperationItem item) {
                        operation.removeItem(item);
                    }
                });
    }

    @PropertySetter("instanceType")
    public void setInstanceType(StockOperation instance, IStockOperationType instanceType) {
        instance.setInstanceType(instanceType);
    }

    @Override
    @PropertySetter("attributes")
    public void setAttributes(StockOperation instance, List<StockOperationAttribute> attributes) {
        super.setAttributes(instance, attributes);
    }

    @PropertySetter("operationDate")
    public void setOperationDate(StockOperation instance, String dateText) {
        Date date = Utility.parseOpenhmisDateString(dateText);
        if (date == null) {
            throw new IllegalArgumentException("Could not parse '" + dateText + "' as a date.");
        }

        instance.setOperationDate(date);
    }

    @Override
    protected PageableResult doSearch(RequestContext context) {
        PageableResult result;

        // TODO: Research if there is a better (more standard) way to do this.

        // Check to see if this search is for 'my', which we're hardcoding to return the list for the current user
        String query = context.getParameter("q");
        if (query != null && query.equals("my")) {
            result = getUserOperations(context);
        } else {
            String status = context.getParameter("operation_status");
            String operationTypeUuid = context.getParameter("operationType_uuid");
            String stockroomUuid = context.getParameter("stockroom_uuid");
            String operationItemUuid = context.getParameter("operationItem_uuid");

            if (status != null || operationTypeUuid != null || stockroomUuid != null || operationItemUuid != null) {
                result = getOperationsByContextParams(context);
            } else {
                result = super.doSearch(context);
            }
        }

        return result;
    }

    protected PageableResult getUserOperations(RequestContext context) {
        User user = Context.getAuthenticatedUser();
        if (user == null) {
            LOG.warn("Could not retrieve the current user to be able to find the current user operations.");

            return new EmptySearchResult();
        }

        StockOperationStatus status = getStatus(context);
        IStockOperationType stockOperationType = getStockOperationType(context);
        Item item = getItem(context);
        Stockroom stockroom = getStockroom(context);
        PagingInfo pagingInfo = PagingUtil.getPagingInfoFromContext(context);

        List<StockOperation> results;
        if (status == null && stockOperationType == null && item == null && stockroom == null) {
            results = ((IStockOperationDataService) getService()).getUserOperations(user, pagingInfo);
        } else {
            results = ((IStockOperationDataService) getService()).getUserOperations(user, status,
                    stockOperationType, item, stockroom, pagingInfo);
        }

        return new AlreadyPagedWithLength<StockOperation>(context, results, pagingInfo.hasMoreResults(),
                pagingInfo.getTotalRecordCount());
    }

    protected PageableResult getOperationsByContextParams(RequestContext context) {
        PagingInfo pagingInfo = PagingUtil.getPagingInfoFromContext(context);
        StockOperationStatus status = getStatus(context);
        IStockOperationType stockOperationType = getStockOperationType(context);
        Stockroom stockroom = getStockroom(context);
        Item item = getItem(context);

        List<StockOperation> results;
        if (status == null && stockOperationType == null && item == null) {
            results = getService().getAll(context.getIncludeAll(), pagingInfo);
        } else {
            StockOperationSearch search = new StockOperationSearch();
            StockOperationTemplate template = search.getTemplate();
            if (status != null) {
                template.setStatus(status);
            }
            if (stockOperationType != null) {
                template.setInstanceType(stockOperationType);
            }
            if (stockroom != null) {
                template.setStockroom(stockroom);
            }
            if (item != null) {
                template.setItem(item);
            }

            results = ((IStockOperationDataService) getService()).getOperations(search, pagingInfo);
        }

        return new AlreadyPagedWithLength<StockOperation>(context, results, pagingInfo.hasMoreResults(),
                pagingInfo.getTotalRecordCount());
    }

    protected StockOperationStatus getStatus(RequestContext context) {
        StockOperationStatus status = null;
        String statusText = context.getParameter("operation_status");
        if (StringUtils.isNotEmpty(statusText)) {
            status = StockOperationStatus.valueOf(statusText.toUpperCase());

            if (status == null) {
                LOG.warn("Could not parse Stock Operation Status '" + statusText + "'");
                throw new IllegalArgumentException(
                        "The status '" + statusText + "' is not a valid operation status.");
            }
        }

        return status;
    }

    private IStockOperationType getStockOperationType(RequestContext context) {
        IStockOperationType stockOperationType = null;
        String stockOperationTypeUuid = context.getParameter("operationType_uuid");
        if (StringUtils.isNotEmpty(stockOperationTypeUuid)) {
            stockOperationType = stockOperationTypeDataService.getByUuid(stockOperationTypeUuid);
            if (stockOperationType == null) {
                LOG.warn("Could not parse Stock Operation Type '" + stockOperationTypeUuid + "'");
                throw new IllegalArgumentException(
                        "The type '" + stockOperationTypeUuid + "' is not a valid operation type.");
            }
        }

        return stockOperationType;
    }

    private Stockroom getStockroom(RequestContext context) {
        Stockroom stockroom = null;
        String stockroomUuid = context.getParameter("stockroom_uuid");
        if (StringUtils.isNotEmpty(stockroomUuid)) {
            stockroom = stockroomDataService.getByUuid(stockroomUuid);

            if (stockroom == null) {
                LOG.warn("Could not parse Stockroom '" + stockroomUuid + "'");
                throw new IllegalArgumentException(
                        "The stockroom '" + stockroomUuid + "' is not a valid stockroom.");
            }
        }

        return stockroom;
    }

    private Item getItem(RequestContext context) {
        Item item = null;
        String stockOperationItemUuid = context.getParameter("operationItem_uuid");
        if (StringUtils.isNotEmpty(stockOperationItemUuid)) {
            item = itemDataService.getByUuid(stockOperationItemUuid);
            if (item == null) {
                LOG.warn("Could not parse Item '" + stockOperationItemUuid + "'");
                throw new IllegalArgumentException(
                        "The item '" + stockOperationItemUuid + "' is not a valid operation type.");
            }
        }
        return item;
    }

    private void processItemStock(StockOperation operation, Set<StockOperationItem> items) {
        IStockOperationType type = operation.getInstanceType();

        // Process each operation item to set the appropriate fields
        for (StockOperationItem opItem : items) {
            Item sourceItem = opItem.getItem();

            if (type.getHasSource()) {
                // If the operation has a source we will allow an expiration or null, regardless of whether the source
                // item is expirable or not

                // The code that saves the operation sets the appropriate fields based on whether a specific expiration,
                // Auto, or None was selected

                if (opItem.getExpiration() != null) {
                    // A specific expiration was selected
                    opItem.setCalculatedExpiration(false);
                }
            } else if (!type.getHasSource() && sourceItem.getHasExpiration()) {
                // This is new item stock so the expiration must be valid

                if (opItem.getExpiration() == null) {
                    // If the operation has no source then all expirable items must define an expiration
                    throw new IllegalArgumentException(
                            "The expiration for item '" + opItem.getItem().getName() + "' must be defined");
                } else {
                    // An expiration was specified so make sure that the calculated flag is not set
                    opItem.setCalculatedExpiration(false);
                }
            }

            // Set the batch operation or calculated batch operation flag
            if (opItem.getBatchOperation() == null) {
                if (!type.getHasSource()) {
                    // The batch operation is set to the current operation when item stock originally enters in the system
                    opItem.setBatchOperation(operation);
                    opItem.setCalculatedBatch(false);
                } else {
                    // The batch operation was not set so flag it as calculated
                    opItem.setCalculatedBatch(true);
                }
            }
        }
    }
}