com.example.ekanban.service.ProductService.java Source code

Java tutorial

Introduction

Here is the source code for com.example.ekanban.service.ProductService.java

Source

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.example.ekanban.service;

import com.example.ekanban.dto.PConsumption;
import com.example.ekanban.dto.ProductCsv;
import com.example.ekanban.dto.ProductDto;
import com.example.ekanban.dto.ProductError;
import com.example.ekanban.entity.*;
import com.example.ekanban.enums.BinState;
import com.example.ekanban.enums.ClassType;
import com.example.ekanban.enums.KanbanType;
import com.example.ekanban.enums.OrderState;
import com.example.ekanban.exception.ProductDetailsNotValidException;
import com.example.ekanban.page.converter.ProductConverter;
import com.example.ekanban.respository.*;

import java.io.IOException;
import java.io.StringReader;
import java.util.*;
import java.util.stream.Collectors;

import com.example.ekanban.util.Constants;
import com.example.ekanban.util.CsvUtils;
import com.example.ekanban.util.MiscUtil;
import com.opencsv.CSVReader;
import com.opencsv.bean.CsvToBean;
import com.opencsv.bean.HeaderColumnNameMappingStrategy;
import org.apache.commons.collections.map.HashedMap;
import org.dozer.Mapper;
import org.hibernate.Hibernate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

@Service
@Transactional(readOnly = true)
public class ProductService {
    private final Logger logger = LoggerFactory.getLogger(ProductService.class);

    private final ProductRepository productRepository;
    private final SectionRepository sectionRepository;
    private final SupplierRepository supplierRepository;
    @Autowired
    private CategoryRepository categoryRepository;
    @Autowired
    private SubCategoryRepository subCategoryRepository;
    @Autowired
    private ConsumptionRepositoryImpl consumptionRepository;
    @Autowired
    private OrderRepository orderRepository;
    @Autowired
    private UserRepository userRepository;
    private ProductConverter converter;
    private Mapper mapper;

    int i = -1;
    long totalMonthyConsumption;
    double commulativePercentage;

    @Autowired
    public ProductService(ProductRepository productRepository, SectionRepository sectionRepository,
            SupplierRepository supplierRepository) {
        this.productRepository = productRepository;
        this.sectionRepository = sectionRepository;
        this.supplierRepository = supplierRepository;
    }

    @Autowired
    public void setConverter(ProductConverter converter) {
        this.converter = converter;
    }

    @Autowired
    public void setMapper(Mapper mapper) {
        this.mapper = mapper;
    }

    public ProductDto findOne(Long id, boolean initSections, boolean initSuupliers) {
        logger.debug("findOne(): id = {}", id);
        Product product = productRepository.findOne(id);
        if (product != null && initSections) {
            Hibernate.initialize(product.getSectionList());
        }
        if (product != null && initSuupliers) {
            Hibernate.initialize(product.getSupplierList());
        }
        return mapper.map(product, ProductDto.class);
    }

    public List<ProductDto> findAll() {
        logger.debug("findAll()");
        return productRepository.findAll().stream().map(product -> mapper.map(product, ProductDto.class))
                .collect(Collectors.toList());
    }

    public Page<ProductDto> findPageBySubCategory(SubCategory subCategory, Pageable pageable) {
        logger.debug("findPageBySubCategory()");
        Page<Product> page = productRepository.findBySubCategory(subCategory, pageable);
        page.forEach(p -> {
            Hibernate.initialize(p.getSectionList());
            Hibernate.initialize(p.getSupplierList());
        });
        return page.map(converter);
    }

    public ProductDto findByName(String name) {
        logger.debug("findByName(): name = ", name);
        return mapper.map(productRepository.findByName(name), ProductDto.class);
    }

    public Boolean exists(Long id) {
        logger.debug("exists(): id = ", id);
        return productRepository.exists(id);
    }

    public Long count() {
        logger.debug("count()");
        return productRepository.count();
    }

    @Transactional
    public ProductDto save(Product product, List<Long> sections, List<Long> suppliers) {
        logger.debug("save()");
        Set<Section> sectionList = new HashSet<>();
        Set<Supplier> supplierList = new HashSet<>();

        if (sections != null) {
            sections.forEach(secId -> {
                sectionList.add(sectionRepository.findOne(secId));
            });
        }
        if (suppliers != null) {
            suppliers.forEach(secId -> {
                supplierList.add(supplierRepository.findOne(secId));
            });
        }
        product.setSectionList(sectionList);
        product.setSupplierList(supplierList);
        product.setStkOnFloor(0L);
        product.setOrderedQty(0L);
        product = productRepository.save(product);
        return mapper.map(product, ProductDto.class);
    }

    @Transactional
    public ProductDto update(Product product, List<Long> sections, List<Long> suppliers) {
        logger.debug("update()");
        Product product2 = productRepository.findOne(product.getId());

        map(product, product2);

        Set<Section> sectionList = product2.getSectionList();
        sectionList.clear();
        sections.forEach(secId -> {
            sectionList.add(sectionRepository.findOne(secId));
        });
        Set<Supplier> supplierList = product2.getSupplierList();
        supplierList.clear();
        suppliers.forEach(secId -> {
            supplierList.add(supplierRepository.findOne(secId));
        });
        return mapper.map(product2, ProductDto.class);
    }

    @Transactional
    public void delete(Long id) {
        logger.debug("delete(): id = {}", id);
        productRepository.delete(id);
    }

    @Transactional
    public void addProductsBatch(MultipartFile file) {
        logger.debug("addProductsBatch");
        List<ProductCsv> list = null;
        //convert to csv string
        String output = null;
        try {
            if (file.getOriginalFilename().contains("xlsx")) {
                output = CsvUtils.fromXlsx(file.getInputStream());
            } else if (file.getOriginalFilename().contains("xls")) {
                output = CsvUtils.fromXls(file.getInputStream());
            }
            //logger.debug(output);
        } catch (IOException e) {
            e.printStackTrace();
        }

        //Read as Bean from csv String
        CSVReader reader = new CSVReader(new StringReader(output), ';');
        HeaderColumnNameMappingStrategy<ProductCsv> strategy = new HeaderColumnNameMappingStrategy<>();
        strategy.setType(ProductCsv.class);
        CsvToBean<ProductCsv> csvToBean = new CsvToBean<>();
        list = csvToBean.parse(strategy, reader);
        //Validate Product Data
        validate(list);

        //convert from DTO to ENTITY
        List<Product> products = map(list);
        productRepository.save(products);
    }

    @Transactional
    public void sync(Boolean firstSync) {
        totalMonthyConsumption = 0L;
        commulativePercentage = 0.0;
        List<Product> products = productRepository.findAll();
        Map<Long, PConsumption> consumptionMap = new HashedMap();

        /*/////////Processing monthly consumption/////////*/
        products.forEach(product -> {
            Long avgValue = consumptionRepository.findAvgConsumptionOfLastYear(product.getId());
            consumptionMap.put(product.getId(), new PConsumption(product.getPrice().longValue() * avgValue));
            totalMonthyConsumption += product.getPrice().longValue() * avgValue;
        });
        //Sort on basis of consumption value descending
        Map<Long, PConsumption> sortedConsumptionMap = MiscUtil.sortByValueDesc(consumptionMap);
        //Calculate percentage contribution and commulative contribution
        sortedConsumptionMap.forEach((id, pConsumption) -> {
            double percent = (double) pConsumption.getMonthlyConsumption() / (double) totalMonthyConsumption;
            percent *= 100;
            pConsumption.setPercentage(percent);
            pConsumption.setCommulativePercentage(commulativePercentage + percent);
            commulativePercentage += percent;
        });
        //Update product dynamically calculated values
        products.forEach(product -> {
            double cp = sortedConsumptionMap.get(product.getId()).getCommulativePercentage();
            ClassType classType = cp <= 60 ? ClassType.CLASS_A : cp <= 80 ? ClassType.CLASS_B : ClassType.CLASS_C;
            product.setClassType(classType);
            /////////////////
            KanbanType kanbanType = classType == ClassType.CLASS_A || classType == ClassType.CLASS_B
                    ? KanbanType.N_BIN
                    : KanbanType.TWO_BIN;
            product.setKanbanType(kanbanType);
            ///////////////
            Long maxValue = consumptionRepository.findMaxConsumptionOfLastYear(product.getId());
            product.setDemand(maxValue / Constants.NO_OF_DAYS_IN_MONTH);
            ///////////////////
            long binQty = product.getDemand() > product.getMinOrderQty() ? product.getDemand()
                    : product.getMinOrderQty();
            if (product.getPacketSize().doubleValue() < 1.0) {
                binQty = (long) ((binQty / product.getPacketSize().doubleValue() + 1)
                        * product.getPacketSize().doubleValue());
            } else {
                binQty = (binQty / product.getPacketSize().longValue() + 1) * product.getPacketSize().longValue();
            }

            product.setBinQty(binQty);
            int noOfBins = 2;
            if (classType != ClassType.CLASS_C) {
                int tat = product.getTimeOrdering() + product.getTimeProcurement() + product.getTimeTransporation()
                        + product.getTimeBuffer();
                noOfBins = (int) (product.getDemand() * tat / binQty + 2);
            }
            product.setNoOfBins(noOfBins);
        });
        //Update Inventory
        if (firstSync) {
            User application = userRepository.findOne(1L);
            products.forEach(product -> {
                int noOfBins = product.getNoOfBins();
                int binInStock = getNoOfBins(product.getStkOnFloor(), product.getBinQty());
                Inventory inv = null;

                if (binInStock <= noOfBins) { //binInStock is less than NoOfBins, So add that number of bins in Stock
                    for (int i = 0; i < binInStock; i++) {
                        product.addInventory(new Inventory(i + 1, BinState.STORE));
                    }
                    int binInOrder = getNoOfBins(product.getOrderedQty(), product.getBinQty());
                    if (binInStock + binInOrder <= noOfBins) { //binInStock+binInOrder is less than NoOfBins, So add binInOrder Bins in Ordered state
                        StringBuilder builder = new StringBuilder();
                        for (int i = binInStock; i < binInStock + binInOrder; i++) {
                            inv = new Inventory(i + 1, BinState.ORDERED);
                            product.addInventory(inv);
                            builder.append((i + 1)).append(",");
                        }
                        if (builder.length() >= 2) {
                            builder.setLength(builder.length() - 1);
                            orderRepository.save(new Order(product, builder.toString(), new Date(), application,
                                    OrderState.ORDERED.getValue()));
                        }
                        if (noOfBins - (binInStock + binInOrder) > 0) { //noOfBins - (binInStock + binInOrder) > 0, means this diff Bins are pending orders
                            for (int i = binInStock + binInOrder; i < noOfBins; i++) {
                                product.addInventory(new Inventory(i + 1, BinState.PURCHASE));
                            }
                        }
                    } else { //binInStock+binInOrder is greater than NoOfBins, So add (noOfBins - binInStock) Bins in Ordered state, and stop furhter processing
                        StringBuilder builder = new StringBuilder();
                        for (int i = binInStock; i < noOfBins; i++) {
                            product.addInventory(new Inventory(i + 1, BinState.ORDERED));
                            builder.append((i + 1)).append(",");
                        }
                        if (builder.length() >= 2) {
                            builder.setLength(builder.length() - 1);
                            orderRepository.save(new Order(product, builder.toString(), new Date(), application,
                                    OrderState.ORDERED.getValue()));
                        }
                    }
                } else { // //binInStock is greater than NoOfBins, So add noOfBins Bin in Stock and stop any further processing Since Stock is full
                    for (int i = 0; i < noOfBins; i++) {
                        product.addInventory(new Inventory(i + 1, BinState.STORE));
                    }
                }
            });

        } else {
            products.forEach(product -> {
                int noOfInventory = product.getInventorySet().size();
                //if inventory size for a product is less than no of bins, then no of bins got increased after sync.
                if (noOfInventory < product.getNoOfBins()) {
                    for (int i = noOfInventory + 1; i <= product.getNoOfBins(); i++) {
                        product.addInventory(new Inventory(i, BinState.PURCHASE));
                    }
                }
            });
        }

    }

    private int getNoOfBins(Long value, Long binQty) {
        int result = (int) (value / binQty);
        int surplus = (int) (value % binQty);
        int roundValue = (int) (Constants.STK_ROUND_FRACTION * binQty.doubleValue());
        result = surplus > roundValue ? result + 1 : result;
        return result;
    }

    private void validate(List<ProductCsv> productCsvList) {
        logger.debug("validating {} products ...", productCsvList.size());
        List<ProductCsv> removeList = new ArrayList<>();
        List<ProductError> errors = new ArrayList<>();
        i = 2;
        productCsvList.forEach(p -> {
            if (p.getName() == null && p.getCategory() == null && p.getSubCategory() == null) {
                removeList.add(p);
            } else {
                if (p.getName() == null) {
                    errors.add(new ProductError("PRODUCT_NAME", i, "Product Name cannot be balnk."));
                }
                if (p.getCategory() == null) {
                    errors.add(new ProductError("CATEGORY", i, "Product Category cannot be blank."));
                }
                if (p.getSubCategory() == null) {
                    errors.add(new ProductError("SUB_CATEGORY", i, "Product SubCategory cannot be blank."));
                }
                if (p.getPrice() == null) {
                    errors.add(new ProductError("PRICE", i, "Product Price cannot be blank."));
                }
                if (p.getTimeOrdering() == null) {
                    errors.add(new ProductError("ORDERING_TIME", i, "Product Ordering time cannot be blank."));
                }
                if (p.getTimeProcurement() == null) {
                    errors.add(new ProductError("PRODUCTION_TIME", i, "Product Production time cannot be blank."));
                }
                if (p.getTimeTransporation() == null) {
                    errors.add(new ProductError("TRANSPORTATION_TIME", i,
                            "Product Transportation time cannot be blank."));
                }
                if (p.getTimeBuffer() == null) {
                    errors.add(new ProductError("BUFFER_TIME", i, "Product Buffer time cannot be blank."));
                }
                if (p.getUomPurchase() == null) {
                    errors.add(new ProductError("UOM_PURCHASE", i,
                            "Product Unit of Measurment (Purchase) cannot be blank" + "."));
                }
                if (p.getUomConsumption() == null) {
                    errors.add(new ProductError("UOM_CONSUMPTION", i,
                            "Product Unit of Measurment (Consumption) cannot be" + " blank."));
                }
                if (p.getConversionFactor() == null) {
                    errors.add(
                            new ProductError("CONVERSION_FACTOR", i, "Product Conversion Factor cannot be blank."));
                }
                if (p.getMinOrderQty() == null) {
                    errors.add(new ProductError("MIN_ORDER_QTY", i,
                            "Product minimum order quantity cannot be blank."));
                }
                if (p.getPacketSize() == null) {
                    errors.add(new ProductError("PACKET_SIZE", i, "Product packet size cannot be blank."));
                }
                if (p.getItemCode() == null) {
                    errors.add(new ProductError("ITEM_CODE", i, "Product Item Code cannot be blank."));
                }

                if (p.getSupplierType() != null
                        && !(p.getSupplierType().equals("LOCAL") || p.getSupplierType().equals("NON_LOCAL"))) {
                    errors.add(new ProductError("SUPPLIER_TYPE", i,
                            "Supplier type can have [LOCAL,NON_LOCAL] values"));
                }
            }
            i++;
        });
        for (ProductCsv p : removeList) {
            productCsvList.remove(p);
        }
        if (errors.size() > 0) {
            throw new ProductDetailsNotValidException("Product details has errors.", errors);
        }
    }

    /**
     * Called by addProductsBatch() method to map from ProductCsv List to Product List
     *
     * @param list List of ProductCsv
     * @return List of Product
     */
    private List<Product> map(List<ProductCsv> list) {
        List<Product> products = new ArrayList<>();
        List<ProductError> errors = new ArrayList<>();

        Set<String> itemCodeList = new HashSet<>();

        Map<String, Integer> mapMonth = MiscUtil.getMapMonth();
        Set<String> months = mapMonth.keySet();
        int currYear = MiscUtil.getCurrentYear();
        int currMonth = MiscUtil.getCurrentMonth();

        i = 2;
        list.forEach(productCsv -> {
            /*check packet value if it is zero*/
            if (productCsv.getPacketSize().doubleValue() == 0) {
                errors.add(new ProductError("PACKET_SIZE", i, "Packet Size cannot be zero."));
            }
            /*check price value if it is zero*/
            if (productCsv.getPrice().doubleValue() == 0) {
                errors.add(new ProductError("PRICE", i, "Packet Size cannot be zero."));
            }
            if (itemCodeList.contains(productCsv.getItemCode())) {
                errors.add(new ProductError("ITEM_CODE", i, productCsv.getItemCode() + " is duplicate Item Code."));
            }
            itemCodeList.add(productCsv.getItemCode());
            Product product = mapper.map(productCsv, Product.class);
            /*////////Mapping SubCategory////////////*/
            SubCategory subCategory = subCategoryRepository.findByName(productCsv.getSubCategory().trim());

            //If category of existing subcategory is not equal to category of coming product, report error
            if (subCategory != null && !subCategory.getCategory().getName().trim()
                    .equalsIgnoreCase(productCsv.getCategory().trim())) {
                errors.add(new ProductError("SUB_CATEGORY", i,
                        "Sub Category '" + subCategory.getName() + "' belongs to Category '"
                                + productCsv.getCategory() + "' which  can't belong to Category '"
                                + subCategory.getCategory().getName() + "'."));
            } else {
                //If SubCategory is null or category of existing subcategory is not equal to category of coming product,add new SubCategory
                if (subCategory == null) {
                    Category category = categoryRepository.findByName(productCsv.getCategory().trim());
                    if (category == null) {
                        category = categoryRepository.save(new Category(productCsv.getCategory().trim()));
                    }
                    subCategory = new SubCategory(productCsv.getSubCategory().trim());
                    subCategory.setCategory(category);
                    subCategory = subCategoryRepository.save(subCategory);
                }

                product.setSubCategory(subCategory);

                /*////////Mapping Suuplier////////////*/
                if (productCsv.getSupplier() != null) {
                    Supplier supplier = supplierRepository.findByName(productCsv.getSupplier().trim());
                    if (supplier == null) {
                        supplier = supplierRepository.save(new Supplier(productCsv.getSupplier().trim(),
                                productCsv.getContactPerson(), productCsv.getSupplierType()));
                    }
                    Set<Supplier> suppliers = new HashSet<>();
                    suppliers.add(supplier);
                    product.setSupplierList(suppliers);
                }
                /*Set Total Lead Time*/
                product.setTotalLeadTime(product.getTimeOrdering() + product.getTimeProcurement()
                        + product.getTimeTransporation() + product.getTimeBuffer());

                /*/////Adding Consumptions//////////*/
                Consumption consumption;
                int mnth;
                Long value;
                for (String m : months) {
                    mnth = mapMonth.get(m);
                    value = getConsumptionForMonth(productCsv, m);
                    if (value != null) {
                        if (mnth <= currMonth) {
                            consumption = new Consumption(currYear, mnth, value);
                        } else {
                            consumption = new Consumption(currYear - 1, mnth, value);
                        }
                        product.addConsumption(consumption);
                    }
                }
                /*//////Setting stkOnFloor and orderedQty to zero if value is null*/
                if (product.getStkOnFloor() == null)
                    product.setStkOnFloor(0L);
                if (product.getOrderedQty() == null)
                    product.setOrderedQty(0L);
                products.add(product);
            }
            i++;
        });
        if (errors.size() > 0) {
            throw new ProductDetailsNotValidException("Product Details has errors", errors);
        }
        return products;
    }

    private Long getConsumptionForMonth(ProductCsv productCsv, String month) {
        switch (month) {
        case "JAN":
            return productCsv.getJan() != null ? productCsv.getJan().longValue() : null;
        case "FEB":
            return productCsv.getFeb() != null ? productCsv.getFeb().longValue() : null;
        case "MAR":
            return productCsv.getMar() != null ? productCsv.getMar().longValue() : null;
        case "APR":
            return productCsv.getApr() != null ? productCsv.getApr().longValue() : null;
        case "MAY":
            return productCsv.getMay() != null ? productCsv.getMay().longValue() : null;
        case "JUN":
            return productCsv.getJun() != null ? productCsv.getJun().longValue() : null;
        case "JUL":
            return productCsv.getJul() != null ? productCsv.getJul().longValue() : null;
        case "AUG":
            return productCsv.getAug() != null ? productCsv.getAug().longValue() : null;
        case "SEP":
            return productCsv.getSep() != null ? productCsv.getSep().longValue() : null;
        case "OCT":
            return productCsv.getOct() != null ? productCsv.getOct().longValue() : null;
        case "NOV":
            return productCsv.getNov() != null ? productCsv.getNov().longValue() : null;
        case "DEC":
            return productCsv.getDec() != null ? productCsv.getDec().longValue() : null;
        }
        return null;
    }

    /**
     * Called by Update method
     *
     * @param src  New value that needs to be updated
     * @param dest Old Value from Repository
     */
    private void map(Product src, Product dest) {
        if (src.getName() != null)
            dest.setName(src.getName());
        if (src.getDescription() != null)
            dest.setDescription(src.getDescription());
        if (src.getPrice() != null)
            dest.setPrice(src.getPrice());
        if (src.getItemCode() != null)
            dest.setItemCode(src.getItemCode());
        if (src.getTimeOrdering() != null)
            dest.setTimeOrdering(src.getTimeOrdering());
        if (src.getTimeProcurement() != null)
            dest.setTimeProcurement(src.getTimeProcurement());
        if (src.getTimeTransporation() != null)
            dest.setTimeTransporation(src.getTimeTransporation());
        if (src.getTimeBuffer() != null)
            dest.setTimeBuffer(src.getTimeBuffer());
        if (src.getUomPurchase() != null)
            dest.setUomPurchase(src.getUomPurchase());
        if (src.getUomConsumption() != null)
            dest.setUomConsumption(src.getUomConsumption());
        if (src.getConversionFactor() != null)
            dest.setConversionFactor(src.getConversionFactor());
        if (src.getMinOrderQty() != null)
            dest.setMinOrderQty(src.getMinOrderQty());
        if (src.getPacketSize() != null)
            dest.setPacketSize(src.getPacketSize());
        if (src.getClassType() != null)
            dest.setClassType(src.getClassType());
        if (src.getKanbanType() != null)
            dest.setKanbanType(src.getKanbanType());
        if (src.getBinQty() != null)
            dest.setBinQty(src.getBinQty());
        if (src.getNoOfBins() != null)
            dest.setNoOfBins(src.getNoOfBins());
        if (src.getDemand() != null)
            dest.setDemand(src.getDemand());
    }

    public Resource printBarcode(Long id, String bins) {
        Product product = productRepository.findOne(id);
        return MiscUtil.generateBarcodePdf(product, bins);
    }

}