| Line 241... |
Line 241... |
| 241 |
+ " of " + qty + " free in the source's vendor warehouses");
|
241 |
+ " of " + qty + " free in the source's vendor warehouses");
|
| 242 |
continue;
|
242 |
continue;
|
| 243 |
}
|
243 |
}
|
| 244 |
}
|
244 |
}
|
| 245 |
|
245 |
|
| - |
|
246 |
// The order is priced from one cost layer; only units received at that price (or with no cost recorded,
|
| - |
|
247 |
// which is what a catalog-priced line moves) may fill it, so the layers left behind stay true.
|
| - |
|
248 |
Float price = order.getLineItem().getUnitPrice();
|
| 246 |
for (Map.Entry<Integer, Integer> chunk : chunks.entrySet()) {
|
249 |
for (Map.Entry<Integer, Integer> chunk : chunks.entrySet()) {
|
| 247 |
Line line = new Line();
|
250 |
Line line = new Line();
|
| 248 |
line.orderId = order.getId();
|
251 |
line.orderId = order.getId();
|
| 249 |
line.itemId = itemId;
|
252 |
line.itemId = itemId;
|
| 250 |
line.item = description;
|
253 |
line.item = description;
|
| 251 |
line.qty = chunk.getValue();
|
254 |
line.qty = chunk.getValue();
|
| 252 |
line.fromWarehouseId = chunk.getKey();
|
255 |
line.fromWarehouseId = chunk.getKey();
|
| 253 |
line.repoint = repoint;
|
256 |
line.repoint = repoint;
|
| 254 |
line.serialized = serialized;
|
257 |
line.serialized = serialized;
|
| 255 |
String poolKey = key(chunk.getKey(), itemId);
|
258 |
String poolKey = key(chunk.getKey(), itemId) + "@" + price;
|
| 256 |
if (serialized) {
|
259 |
if (serialized) {
|
| 257 |
List<String> pool = serialPools.computeIfAbsent(poolKey, x -> this.billableImeis(itemId, chunk.getKey()));
|
260 |
List<String> pool = serialPools.computeIfAbsent(poolKey, x -> this.billableImeis(itemId, chunk.getKey(), price));
|
| 258 |
if (pool.size() < line.qty) {
|
261 |
if (pool.size() < line.qty) {
|
| 259 |
plan.problems.add("order " + order.getId() + " " + description + ": " + pool.size()
|
262 |
plan.problems.add("order " + order.getId() + " " + description + ": " + pool.size()
|
| 260 |
+ " billable IMEI(s) in warehouse " + chunk.getKey() + ", needs " + line.qty);
|
263 |
+ " billable IMEI(s) at " + price + " in warehouse " + chunk.getKey() + ", needs " + line.qty);
|
| 261 |
continue;
|
264 |
continue;
|
| 262 |
}
|
265 |
}
|
| 263 |
line.imeis.addAll(pool.subList(0, line.qty));
|
266 |
line.imeis.addAll(pool.subList(0, line.qty));
|
| 264 |
pool.subList(0, line.qty).clear();
|
267 |
pool.subList(0, line.qty).clear();
|
| 265 |
} else {
|
268 |
} else {
|
| 266 |
int lots = lotPools.computeIfAbsent(poolKey, x -> this.billableLotQuantity(itemId, chunk.getKey()));
|
269 |
int lots = lotPools.computeIfAbsent(poolKey, x -> this.billableLotQuantity(itemId, chunk.getKey(), price));
|
| 267 |
if (lots < line.qty) {
|
270 |
if (lots < line.qty) {
|
| 268 |
plan.problems.add("order " + order.getId() + " " + description + ": " + lots
|
271 |
plan.problems.add("order " + order.getId() + " " + description + ": " + lots
|
| 269 |
+ " billable unit(s) in warehouse " + chunk.getKey() + ", needs " + line.qty);
|
272 |
+ " billable unit(s) at " + price + " in warehouse " + chunk.getKey() + ", needs " + line.qty);
|
| 270 |
continue;
|
273 |
continue;
|
| 271 |
}
|
274 |
}
|
| 272 |
lotPools.put(poolKey, lots - line.qty);
|
275 |
lotPools.put(poolKey, lots - line.qty);
|
| 273 |
}
|
276 |
}
|
| 274 |
plan.lines.add(line);
|
277 |
plan.lines.add(line);
|
| Line 280... |
Line 283... |
| 280 |
}
|
283 |
}
|
| 281 |
return plan;
|
284 |
return plan;
|
| 282 |
}
|
285 |
}
|
| 283 |
|
286 |
|
| 284 |
// Billing takes an IMEI only at quantity 1 with a last scan that leaves it in stock (WarehouseServiceImpl billing).
|
287 |
// Billing takes an IMEI only at quantity 1 with a last scan that leaves it in stock (WarehouseServiceImpl billing).
|
| - |
|
288 |
// Only units at the billed price, then those with no cost recorded - core billing ranks lots the same way.
|
| 285 |
private List<String> billableImeis(int itemId, int vendorWarehouseId) {
|
289 |
private List<String> billableImeis(int itemId, int vendorWarehouseId, Float price) {
|
| 286 |
return warehouseInventoryItemRepository.selectBillableStock(itemId, vendorWarehouseId).stream()
|
290 |
return warehouseInventoryItemRepository.selectBillableStock(itemId, vendorWarehouseId).stream()
|
| 287 |
.filter(x -> x.getCurrentQuantity() == 1 && x.getSerialNumber() != null && !x.getSerialNumber().trim().isEmpty())
|
291 |
.filter(x -> x.getCurrentQuantity() == 1 && x.getSerialNumber() != null && !x.getSerialNumber().trim().isEmpty())
|
| 288 |
.filter(x -> WarehouseServiceImpl.GOOD_SCAN_TYPES.contains(x.getLastScanType()))
|
292 |
.filter(x -> WarehouseServiceImpl.GOOD_SCAN_TYPES.contains(x.getLastScanType()))
|
| - |
|
293 |
.filter(x -> WarehouseServiceImpl.costRank(x.getReceiptUnitCost(), price) < 2)
|
| - |
|
294 |
.sorted(Comparator.comparingInt((WarehouseInventoryItem x) -> WarehouseServiceImpl.costRank(x.getReceiptUnitCost(), price))
|
| 289 |
.sorted(Comparator.comparing(WarehouseInventoryItem::getCreated, Comparator.nullsFirst(Comparator.naturalOrder())))
|
295 |
.thenComparing(WarehouseInventoryItem::getCreated, Comparator.nullsFirst(Comparator.naturalOrder())))
|
| 290 |
.map(WarehouseInventoryItem::getSerialNumber)
|
296 |
.map(WarehouseInventoryItem::getSerialNumber)
|
| 291 |
.collect(Collectors.toCollection(ArrayList::new));
|
297 |
.collect(Collectors.toCollection(ArrayList::new));
|
| 292 |
}
|
298 |
}
|
| 293 |
|
299 |
|
| 294 |
private int billableLotQuantity(int itemId, int vendorWarehouseId) {
|
300 |
private int billableLotQuantity(int itemId, int vendorWarehouseId, Float price) {
|
| 295 |
return warehouseInventoryItemRepository.selectBillableStock(itemId, vendorWarehouseId).stream()
|
301 |
return warehouseInventoryItemRepository.selectBillableStock(itemId, vendorWarehouseId).stream()
|
| 296 |
.filter(x -> x.getCurrentQuantity() > 0)
|
302 |
.filter(x -> x.getCurrentQuantity() > 0)
|
| - |
|
303 |
.filter(x -> WarehouseServiceImpl.costRank(x.getReceiptUnitCost(), price) < 2)
|
| 297 |
.mapToInt(WarehouseInventoryItem::getCurrentQuantity).sum();
|
304 |
.mapToInt(WarehouseInventoryItem::getCurrentQuantity).sum();
|
| 298 |
}
|
305 |
}
|
| 299 |
|
306 |
|
| 300 |
// A vendor warehouse the source bills from: its own OURS/GOOD stock, not the Dummy phantom target.
|
307 |
// A vendor warehouse the source bills from: its own OURS/GOOD stock, not the Dummy phantom target.
|
| 301 |
private boolean isRealVendorWarehouse(Integer warehouseId, int sourceWarehouseId, Map<Integer, InventoryWarehouse> cache) {
|
308 |
private boolean isRealVendorWarehouse(Integer warehouseId, int sourceWarehouseId, Map<Integer, InventoryWarehouse> cache) {
|