Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
package com.smartdukaan.cron.migrations;import com.spice.profitmandi.common.exception.ProfitMandiBusinessException;import com.spice.profitmandi.dao.entity.auth.AuthUser;import com.spice.profitmandi.dao.entity.catalog.Item;import com.spice.profitmandi.dao.entity.warehouse.Supplier;import com.spice.profitmandi.dao.entity.warehouse.WarehousePurchaseOrder;import com.spice.profitmandi.dao.model.CreateWarehousePurchaseOrderModel;import com.spice.profitmandi.dao.model.InternalMovementAllocationModel;import com.spice.profitmandi.dao.model.InternalMovementAvailabilityModel;import com.spice.profitmandi.dao.model.ItemWisePurchaseModel;import com.spice.profitmandi.dao.repository.auth.AuthRepository;import com.spice.profitmandi.dao.repository.catalog.ItemRepository;import com.spice.profitmandi.dao.repository.warehouse.SupplierRepository;import com.spice.profitmandi.dao.repository.warehouse.WarehousePurchaseOrderRepository;import com.spice.profitmandi.service.warehouse.InternalMovementPricingService;import com.spice.profitmandi.service.warehouse.PurchaseOrderService;import org.apache.poi.ss.usermodel.CellType;import org.apache.poi.ss.usermodel.Row;import org.apache.poi.xssf.usermodel.XSSFRow;import org.apache.poi.xssf.usermodel.XSSFSheet;import org.apache.poi.xssf.usermodel.XSSFWorkbook;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import org.springframework.transaction.annotation.Transactional;import java.io.FileInputStream;import java.io.InputStream;import java.time.LocalDate;import java.util.ArrayList;import java.util.Comparator;import java.util.LinkedHashMap;import java.util.List;import java.util.Map;import java.util.function.Function;import java.util.stream.Collectors;import java.util.stream.IntStream;/*** Raises an internal movement PO from a PO-screen upload file (itemId, quantity on the first sheet) the way the PO* screen does - createPurchaseOrder, recorded as the operator, dated today - which also creates and pays the internal* order that MovementBiller then bills. {@link #preview} shows the POs it would raise without writing anything.*/@Componentpublic class MovementPoCreator {// createPurchaseOrder returns the first PO; its cost-split and margin-scheme siblings are raised right after it.private static final int SPLIT_ID_WINDOW = 50;@Autowiredprivate PurchaseOrderService purchaseOrderService;@Autowiredprivate InternalMovementPricingService internalMovementPricingService;@Autowiredprivate SupplierRepository supplierRepository;@Autowiredprivate ItemRepository itemRepository;@Autowiredprivate WarehousePurchaseOrderRepository warehousePurchaseOrderRepository;@Autowiredprivate AuthRepository authRepository;public static class Preview {public String supplierName;public final List<String> problems = new ArrayList<>();/** One entry per PO that would be raised, each its priced lines. */public List<List<InternalMovementAllocationModel>> purchaseOrders = new ArrayList<>();public Map<Integer, Item> items = new LinkedHashMap<>();}/** itemId -> quantity, in file order. Same layout the PO screen's bulk upload reads. */public static Map<Integer, Integer> readFile(String path) throws Exception {Map<Integer, Integer> quantities = new LinkedHashMap<>();try (InputStream in = new FileInputStream(path); XSSFWorkbook workbook = new XSSFWorkbook(in)) {workbook.setMissingCellPolicy(Row.MissingCellPolicy.RETURN_BLANK_AS_NULL);XSSFSheet sheet = workbook.getSheetAt(0);for (int rowNumber = 1; rowNumber <= sheet.getLastRowNum(); rowNumber++) {XSSFRow row = sheet.getRow(rowNumber);if (row == null || row.getCell(0) == null) {break;}if (row.getCell(0).getCellTypeEnum() != CellType.NUMERIC || row.getCell(1) == null|| row.getCell(1).getCellTypeEnum() != CellType.NUMERIC) {throw new ProfitMandiBusinessException("row", rowNumber + 1, "itemId and quantity must be numbers");}int itemId = (int) row.getCell(0).getNumericCellValue();int quantity = (int) row.getCell(1).getNumericCellValue();if (quantity <= 0) {throw new ProfitMandiBusinessException("row", rowNumber + 1, "quantity must be positive");}if (quantities.put(itemId, quantity) != null) {throw new ProfitMandiBusinessException("itemId", itemId, "Item " + itemId + " appears twice");}}}return quantities;}/** Everything that would stop the PO, and the POs it would be raised as. Writes nothing. */@Transactional(readOnly = true, rollbackFor = Throwable.class)public Preview preview(int warehouseId, int supplierId, Map<Integer, Integer> quantities) throws ProfitMandiBusinessException {Preview preview = new Preview();Supplier supplier = supplierRepository.selectById(supplierId);if (supplier == null || !supplier.isInternal()) {preview.problems.add("supplier " + supplierId + " is not an internal supplier");return preview;}preview.supplierName = supplier.getName();if (supplier.getInternalWarehouseId() != null && supplier.getInternalWarehouseId() == warehouseId) {preview.problems.add(supplier.getName() + " cannot move stock to its own warehouse");}try {purchaseOrderService.checkSupplierMappedToWarehouse(supplier, warehouseId);} catch (ProfitMandiBusinessException e) {preview.problems.add(e.getCode());}List<Item> items = itemRepository.selectByIds(new ArrayList<>(quantities.keySet()));preview.items = items.stream().collect(Collectors.toMap(Item::getId, Function.identity(), (a, b) -> a, LinkedHashMap::new));List<Integer> unknown = quantities.keySet().stream().filter(x -> !preview.items.containsKey(x)).collect(Collectors.toList());if (!unknown.isEmpty()) {preview.problems.add("unknown item ids " + unknown);}Map<Integer, InternalMovementAvailabilityModel> availability = internalMovementPricingService.describeAvailability(supplier, items);for (Map.Entry<Integer, Integer> line : quantities.entrySet()) {InternalMovementAvailabilityModel movable = availability.get(line.getKey());int canMove = movable == null ? 0 : movable.getMovableAltogether();if (line.getValue() > canMove) {preview.problems.add("item " + line.getKey() + " asks " + line.getValue() + ", only " + canMove+ " can move (in stock " + (movable == null ? 0 : movable.getInStock()) + ")");}}if (preview.problems.isEmpty()) {preview.purchaseOrders = purchaseOrderService.previewMovementPurchaseOrders(supplier, toPurchaseItems(quantities));}return preview;}/** Raises the PO and its siblings, all or nothing. Returns their PO numbers in the order raised. */@Transactional(rollbackFor = Throwable.class)public List<String> create(int warehouseId, int supplierId, Map<Integer, Integer> quantities, String operatorEmail)throws Exception {Preview preview = this.preview(warehouseId, supplierId, quantities);if (!preview.problems.isEmpty()) {throw new ProfitMandiBusinessException("file", supplierId, String.join("; ", preview.problems));}AuthUser operator = authRepository.selectByEmailOrMobile(operatorEmail);if (operator == null) {throw new ProfitMandiBusinessException("email", operatorEmail, "No auth user for " + operatorEmail);}CreateWarehousePurchaseOrderModel model = new CreateWarehousePurchaseOrderModel();model.setWarehouseId(warehouseId);model.setVendorId(supplierId);model.setPoDate(LocalDate.now());model.setCreatedBy(operator.getFullName());model.setRequesterAuthId(operator.getId());model.setItems(toPurchaseItems(quantities));int firstPoId = purchaseOrderService.createPurchaseOrder(model);List<Integer> window = IntStream.range(firstPoId, firstPoId + SPLIT_ID_WINDOW).boxed().collect(Collectors.toList());List<WarehousePurchaseOrder> raised = warehousePurchaseOrderRepository.selectAll(window).stream().filter(x -> x.getSupplierId() == supplierId && x.getWarehouseId() == warehouseId&& operator.getFullName().equals(x.getCreatedBy())&& x.getCreatedAt() != null && LocalDate.now().equals(x.getCreatedAt().toLocalDate())).sorted(Comparator.comparingInt(WarehousePurchaseOrder::getId)).collect(Collectors.toList());if (raised.size() != preview.purchaseOrders.size()) {throw new ProfitMandiBusinessException("po", firstPoId, "Expected " + preview.purchaseOrders.size()+ " PO(s) but found " + raised.size() + " near PO id " + firstPoId + " - rolled back, check by hand");}return raised.stream().map(WarehousePurchaseOrder::getPoNumber).collect(Collectors.toList());}private static List<ItemWisePurchaseModel> toPurchaseItems(Map<Integer, Integer> quantities) {List<ItemWisePurchaseModel> items = new ArrayList<>();for (Map.Entry<Integer, Integer> line : quantities.entrySet()) {ItemWisePurchaseModel item = new ItemWisePurchaseModel();item.setItemId(line.getKey());item.setAmendedQty(line.getValue());items.add(item);}return items;}}