| 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 |
}
|