Subversion Repositories SmartDukaan

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
37827 amit 1
package com.smartdukaan.cron.migrations;
2
 
3
import com.spice.profitmandi.common.exception.ProfitMandiBusinessException;
4
import com.spice.profitmandi.dao.entity.auth.AuthUser;
5
import com.spice.profitmandi.dao.entity.catalog.Item;
6
import com.spice.profitmandi.dao.entity.warehouse.Supplier;
7
import com.spice.profitmandi.dao.entity.warehouse.WarehousePurchaseOrder;
8
import com.spice.profitmandi.dao.model.CreateWarehousePurchaseOrderModel;
9
import com.spice.profitmandi.dao.model.InternalMovementAllocationModel;
10
import com.spice.profitmandi.dao.model.InternalMovementAvailabilityModel;
11
import com.spice.profitmandi.dao.model.ItemWisePurchaseModel;
12
import com.spice.profitmandi.dao.repository.auth.AuthRepository;
13
import com.spice.profitmandi.dao.repository.catalog.ItemRepository;
14
import com.spice.profitmandi.dao.repository.warehouse.SupplierRepository;
15
import com.spice.profitmandi.dao.repository.warehouse.WarehousePurchaseOrderRepository;
16
import com.spice.profitmandi.service.warehouse.InternalMovementPricingService;
17
import com.spice.profitmandi.service.warehouse.PurchaseOrderService;
18
import org.apache.poi.ss.usermodel.CellType;
19
import org.apache.poi.ss.usermodel.Row;
20
import org.apache.poi.xssf.usermodel.XSSFRow;
21
import org.apache.poi.xssf.usermodel.XSSFSheet;
22
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
23
import org.springframework.beans.factory.annotation.Autowired;
24
import org.springframework.stereotype.Component;
25
import org.springframework.transaction.annotation.Transactional;
26
 
27
import java.io.FileInputStream;
28
import java.io.InputStream;
29
import java.time.LocalDate;
30
import java.util.ArrayList;
31
import java.util.Comparator;
32
import java.util.LinkedHashMap;
33
import java.util.List;
34
import java.util.Map;
35
import java.util.function.Function;
36
import java.util.stream.Collectors;
37
import java.util.stream.IntStream;
38
 
39
/**
40
 * Raises an internal movement PO from a PO-screen upload file (itemId, quantity on the first sheet) the way the PO
41
 * screen does - createPurchaseOrder, recorded as the operator, dated today - which also creates and pays the internal
42
 * order that MovementBiller then bills. {@link #preview} shows the POs it would raise without writing anything.
43
 */
44
@Component
45
public class MovementPoCreator {
46
 
47
    // createPurchaseOrder returns the first PO; its cost-split and margin-scheme siblings are raised right after it.
48
    private static final int SPLIT_ID_WINDOW = 50;
49
 
50
    @Autowired
51
    private PurchaseOrderService purchaseOrderService;
52
    @Autowired
53
    private InternalMovementPricingService internalMovementPricingService;
54
    @Autowired
55
    private SupplierRepository supplierRepository;
56
    @Autowired
57
    private ItemRepository itemRepository;
58
    @Autowired
59
    private WarehousePurchaseOrderRepository warehousePurchaseOrderRepository;
60
    @Autowired
61
    private AuthRepository authRepository;
62
 
63
    public static class Preview {
64
        public String supplierName;
65
        public final List<String> problems = new ArrayList<>();
66
        /** One entry per PO that would be raised, each its priced lines. */
67
        public List<List<InternalMovementAllocationModel>> purchaseOrders = new ArrayList<>();
68
        public Map<Integer, Item> items = new LinkedHashMap<>();
69
    }
70
 
71
    /** itemId -> quantity, in file order. Same layout the PO screen's bulk upload reads. */
72
    public static Map<Integer, Integer> readFile(String path) throws Exception {
73
        Map<Integer, Integer> quantities = new LinkedHashMap<>();
74
        try (InputStream in = new FileInputStream(path); XSSFWorkbook workbook = new XSSFWorkbook(in)) {
75
            workbook.setMissingCellPolicy(Row.MissingCellPolicy.RETURN_BLANK_AS_NULL);
76
            XSSFSheet sheet = workbook.getSheetAt(0);
77
            for (int rowNumber = 1; rowNumber <= sheet.getLastRowNum(); rowNumber++) {
78
                XSSFRow row = sheet.getRow(rowNumber);
79
                if (row == null || row.getCell(0) == null) {
80
                    break;
81
                }
82
                if (row.getCell(0).getCellTypeEnum() != CellType.NUMERIC || row.getCell(1) == null
83
                        || row.getCell(1).getCellTypeEnum() != CellType.NUMERIC) {
84
                    throw new ProfitMandiBusinessException("row", rowNumber + 1, "itemId and quantity must be numbers");
85
                }
86
                int itemId = (int) row.getCell(0).getNumericCellValue();
87
                int quantity = (int) row.getCell(1).getNumericCellValue();
88
                if (quantity <= 0) {
89
                    throw new ProfitMandiBusinessException("row", rowNumber + 1, "quantity must be positive");
90
                }
91
                if (quantities.put(itemId, quantity) != null) {
92
                    throw new ProfitMandiBusinessException("itemId", itemId, "Item " + itemId + " appears twice");
93
                }
94
            }
95
        }
96
        return quantities;
97
    }
98
 
99
    /** Everything that would stop the PO, and the POs it would be raised as. Writes nothing. */
100
    @Transactional(readOnly = true, rollbackFor = Throwable.class)
101
    public Preview preview(int warehouseId, int supplierId, Map<Integer, Integer> quantities) throws ProfitMandiBusinessException {
102
        Preview preview = new Preview();
103
        Supplier supplier = supplierRepository.selectById(supplierId);
104
        if (supplier == null || !supplier.isInternal()) {
105
            preview.problems.add("supplier " + supplierId + " is not an internal supplier");
106
            return preview;
107
        }
108
        preview.supplierName = supplier.getName();
109
        if (supplier.getInternalWarehouseId() != null && supplier.getInternalWarehouseId() == warehouseId) {
110
            preview.problems.add(supplier.getName() + " cannot move stock to its own warehouse");
111
        }
112
        try {
113
            purchaseOrderService.checkSupplierMappedToWarehouse(supplier, warehouseId);
114
        } catch (ProfitMandiBusinessException e) {
115
            preview.problems.add(e.getCode());
116
        }
117
 
118
        List<Item> items = itemRepository.selectByIds(new ArrayList<>(quantities.keySet()));
119
        preview.items = items.stream().collect(Collectors.toMap(Item::getId, Function.identity(), (a, b) -> a, LinkedHashMap::new));
120
        List<Integer> unknown = quantities.keySet().stream().filter(x -> !preview.items.containsKey(x)).collect(Collectors.toList());
121
        if (!unknown.isEmpty()) {
122
            preview.problems.add("unknown item ids " + unknown);
123
        }
124
 
125
        Map<Integer, InternalMovementAvailabilityModel> availability = internalMovementPricingService.describeAvailability(supplier, items);
126
        for (Map.Entry<Integer, Integer> line : quantities.entrySet()) {
127
            InternalMovementAvailabilityModel movable = availability.get(line.getKey());
128
            int canMove = movable == null ? 0 : movable.getMovableAltogether();
129
            if (line.getValue() > canMove) {
130
                preview.problems.add("item " + line.getKey() + " asks " + line.getValue() + ", only " + canMove
131
                        + " can move (in stock " + (movable == null ? 0 : movable.getInStock()) + ")");
132
            }
133
        }
134
        if (preview.problems.isEmpty()) {
135
            preview.purchaseOrders = purchaseOrderService.previewMovementPurchaseOrders(supplier, toPurchaseItems(quantities));
136
        }
137
        return preview;
138
    }
139
 
140
    /** Raises the PO and its siblings, all or nothing. Returns their PO numbers in the order raised. */
141
    @Transactional(rollbackFor = Throwable.class)
142
    public List<String> create(int warehouseId, int supplierId, Map<Integer, Integer> quantities, String operatorEmail)
143
            throws Exception {
144
        Preview preview = this.preview(warehouseId, supplierId, quantities);
145
        if (!preview.problems.isEmpty()) {
146
            throw new ProfitMandiBusinessException("file", supplierId, String.join("; ", preview.problems));
147
        }
148
        AuthUser operator = authRepository.selectByEmailOrMobile(operatorEmail);
149
        if (operator == null) {
150
            throw new ProfitMandiBusinessException("email", operatorEmail, "No auth user for " + operatorEmail);
151
        }
152
        CreateWarehousePurchaseOrderModel model = new CreateWarehousePurchaseOrderModel();
153
        model.setWarehouseId(warehouseId);
154
        model.setVendorId(supplierId);
155
        model.setPoDate(LocalDate.now());
156
        model.setCreatedBy(operator.getFullName());
157
        model.setRequesterAuthId(operator.getId());
158
        model.setItems(toPurchaseItems(quantities));
159
 
160
        int firstPoId = purchaseOrderService.createPurchaseOrder(model);
161
        List<Integer> window = IntStream.range(firstPoId, firstPoId + SPLIT_ID_WINDOW).boxed().collect(Collectors.toList());
162
        List<WarehousePurchaseOrder> raised = warehousePurchaseOrderRepository.selectAll(window).stream()
163
                .filter(x -> x.getSupplierId() == supplierId && x.getWarehouseId() == warehouseId
164
                        && operator.getFullName().equals(x.getCreatedBy())
165
                        && x.getCreatedAt() != null && LocalDate.now().equals(x.getCreatedAt().toLocalDate()))
166
                .sorted(Comparator.comparingInt(WarehousePurchaseOrder::getId))
167
                .collect(Collectors.toList());
168
        if (raised.size() != preview.purchaseOrders.size()) {
169
            throw new ProfitMandiBusinessException("po", firstPoId, "Expected " + preview.purchaseOrders.size()
170
                    + " PO(s) but found " + raised.size() + " near PO id " + firstPoId + " - rolled back, check by hand");
171
        }
172
        return raised.stream().map(WarehousePurchaseOrder::getPoNumber).collect(Collectors.toList());
173
    }
174
 
175
    private static List<ItemWisePurchaseModel> toPurchaseItems(Map<Integer, Integer> quantities) {
176
        List<ItemWisePurchaseModel> items = new ArrayList<>();
177
        for (Map.Entry<Integer, Integer> line : quantities.entrySet()) {
178
            ItemWisePurchaseModel item = new ItemWisePurchaseModel();
179
            item.setItemId(line.getKey());
180
            item.setAmendedQty(line.getValue());
181
            items.add(item);
182
        }
183
        return items;
184
    }
185
}