Blame | Last modification | View Log | RSS feed
package com.smartdukaan.cron.migrations;import com.spice.profitmandi.common.exception.ProfitMandiBusinessException;import com.spice.profitmandi.dao.entity.inventory.SaholicInventorySnapshot;import com.spice.profitmandi.dao.entity.transaction.Order;import com.spice.profitmandi.dao.entity.warehouse.WarehouseInventoryItem;import com.spice.profitmandi.dao.entity.warehouse.WarehouseInvoiceItem;import com.spice.profitmandi.dao.entity.warehouse.WarehouseLineItem;import com.spice.profitmandi.dao.entity.warehouse.WarehousePurchase;import com.spice.profitmandi.dao.entity.warehouse.WarehousePurchaseOrder;import com.spice.profitmandi.dao.entity.warehouse.WarehouseScan;import com.spice.profitmandi.dao.entity.warehouse.WarehouseSupplierInvoice;import in.shop2020.warehouse.ScanType;import com.spice.profitmandi.dao.repository.GenericRepository;import com.spice.profitmandi.dao.repository.inventory.SaholicInventorySnapshotRepository;import com.spice.profitmandi.dao.repository.transaction.OrderRepository;import com.spice.profitmandi.dao.repository.warehouse.WarehouseInvoiceItemRepository;import com.spice.profitmandi.dao.repository.warehouse.WarehouseLineItemRepository;import com.spice.profitmandi.dao.repository.warehouse.WarehousePurchaseOrderRepository;import com.spice.profitmandi.dao.repository.warehouse.WarehousePurchaseRepository;import com.spice.profitmandi.dao.repository.warehouse.WarehouseScanRepository;import com.spice.profitmandi.dao.repository.warehouse.WarehouseSupplierInvoiceRepository;import in.shop2020.purchase.POStatus;import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional;import java.util.ArrayList;import java.util.HashSet;import java.util.LinkedHashMap;import java.util.List;import java.util.Map;import java.util.Set;/*** Undoes one internal GRN so the invoice can be received again cleanly.** It removes only what receiving created - the scans, the inventory units, the invoice items, the purchase* and the supplier invoice - and puts back the two running figures receiving moved: the warehouse* availability it added to, and the quantity it took off the purchase order line.** It refuses an invoice whose stock has been touched since. A unit that has been scanned out, split or* partly consumed is no longer ours to delete, and reversing around it would leave the warehouse holding* stock no record explains.** Kept in its own bean so InternalGrnTask reaches it through the Spring proxy and the transaction below is* actually applied.*/@Componentpublic class InternalGrnReverser {private static final Logger LOGGER = LogManager.getLogger(InternalGrnReverser.class);@Autowiredprivate OrderRepository orderRepository;@Autowiredprivate GenericRepository genericRepository;@Autowiredprivate WarehousePurchaseOrderRepository warehousePurchaseOrderRepository;@Autowiredprivate WarehouseSupplierInvoiceRepository warehouseSupplierInvoiceRepository;@Autowiredprivate WarehousePurchaseRepository warehousePurchaseRepository;@Autowiredprivate WarehouseInvoiceItemRepository warehouseInvoiceItemRepository;@Autowiredprivate WarehouseLineItemRepository warehouseLineItemRepository;@Autowiredprivate WarehouseScanRepository warehouseScanRepository;@Autowiredprivate SaholicInventorySnapshotRepository saholicInventorySnapshotRepository;/*** @return true when the invoice was reversed (or would be, on a dry run), false when it was left alone.*/@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Throwable.class)public boolean reverseInvoice(String invoiceNumber, boolean dryRun) throws ProfitMandiBusinessException {List<Order> orders = orderRepository.selectByInvoiceNumber(invoiceNumber);if (orders.isEmpty()) {LOGGER.info("SKIP {} - no orders for this invoice", invoiceNumber);return false;}// Resolved the same way receiving resolved it, so the reversal cannot land on a different PO.Set<Integer> transactionIds = new HashSet<>();for (Order order : orders) {transactionIds.add(order.getTransactionId());}if (transactionIds.size() > 1) {LOGGER.info("SKIP {} - spans {} transactions", invoiceNumber, transactionIds.size());return false;}WarehousePurchaseOrder purchaseOrder =warehousePurchaseOrderRepository.selectByTransactionId(transactionIds.iterator().next());if (purchaseOrder == null) {LOGGER.info("SKIP {} - no purchase order mapped to the transaction", invoiceNumber);return false;}WarehouseSupplierInvoice invoice = warehouseSupplierInvoiceRepository.selectAllBySupplierInvoice(purchaseOrder.getSupplierId(), invoiceNumber);if (invoice == null) {LOGGER.info("SKIP {} - not received, nothing to reverse", invoiceNumber);return false;}List<WarehousePurchase> purchases = warehousePurchaseRepository.selectByInvoiceId(invoice.getId());List<WarehouseInventoryItem> inventoryItems = new ArrayList<>();for (WarehousePurchase purchase : purchases) {inventoryItems.addAll(genericRepository.<WarehouseInventoryItem>selectAllByEqualOrderByDesc(WarehouseInventoryItem.class, "purchaseId", purchase.getId(), "id"));}// Anything that moved since receiving is not ours to undo.for (WarehouseInventoryItem inventoryItem : inventoryItems) {if (inventoryItem.getCurrentQuantity() != inventoryItem.getInitialQuantity()) {LOGGER.info("REFUSE {} - inventory item {} has moved ({} of {} left), reverse it by hand",invoiceNumber, inventoryItem.getId(), inventoryItem.getCurrentQuantity(),inventoryItem.getInitialQuantity());return false;}if (!ScanType.PURCHASE.equals(inventoryItem.getLastScanType())) {LOGGER.info("REFUSE {} - inventory item {} was last scanned {}, not PURCHASE",invoiceNumber, inventoryItem.getId(), inventoryItem.getLastScanType());return false;}}// What receiving added to availability, keyed the way it added it.Map<String, Integer> availabilityToRelease = new LinkedHashMap<>();// What receiving took off each PO line.Map<Integer, Integer> quantityToRestore = new LinkedHashMap<>();int units = 0;for (WarehouseInventoryItem inventoryItem : inventoryItems) {String key = inventoryItem.getCurrentWarehouseId() + ":" + inventoryItem.getItemId();Integer released = availabilityToRelease.get(key);availabilityToRelease.put(key, (released == null ? 0 : released) + inventoryItem.getInitialQuantity());Integer restored = quantityToRestore.get(inventoryItem.getItemId());quantityToRestore.put(inventoryItem.getItemId(),(restored == null ? 0 : restored) + inventoryItem.getInitialQuantity());units += inventoryItem.getInitialQuantity();}List<WarehouseInvoiceItem> invoiceItems = warehouseInvoiceItemRepository.selectByInvoiceId(invoice.getId());if (dryRun) {LOGGER.info("WOULD REVERSE {} - invoice id {}, po {}, {} purchase(s), {} inventory row(s), {} unit(s), {} invoice item(s)",invoiceNumber, invoice.getId(), purchaseOrder.getPoNumber(), purchases.size(),inventoryItems.size(), units, invoiceItems.size());for (WarehouseInventoryItem inventoryItem : inventoryItems) {LOGGER.info(" delete inventory {} item {} qty {} serial {}",inventoryItem.getId(), inventoryItem.getItemId(), inventoryItem.getInitialQuantity(),inventoryItem.getSerialNumber() == null ? "-" : inventoryItem.getSerialNumber());}for (Map.Entry<String, Integer> entry : availabilityToRelease.entrySet()) {String[] key = entry.getKey().split(":");SaholicInventorySnapshot snapshot = saholicInventorySnapshotRepository.selectByWarehouseIdandItemId(Integer.valueOf(key[0]), Integer.valueOf(key[1]));LOGGER.info(" availability warehouse {} item {} : {} -> {}",key[0], key[1], snapshot == null ? "absent" : snapshot.getAvailability(),snapshot == null ? "absent" : (snapshot.getAvailability() - entry.getValue()));}for (Map.Entry<Integer, Integer> entry : quantityToRestore.entrySet()) {WarehouseLineItem lineItem = warehouseLineItemRepository.selectByPurchaseOrderIdItemId(purchaseOrder.getId(), entry.getKey());LOGGER.info(" po line item {} unfulfilled : {} -> {}", entry.getKey(),lineItem == null ? "absent" : lineItem.getUnfulfilledQuantity(),lineItem == null ? "absent" : (lineItem.getUnfulfilledQuantity() + entry.getValue()));}if (POStatus.CLOSED.equals(purchaseOrder.getStatus())) {LOGGER.info(" po {} would be reopened to READY", purchaseOrder.getPoNumber());}return true;}// Give back the availability receiving added.for (Map.Entry<String, Integer> entry : availabilityToRelease.entrySet()) {String[] key = entry.getKey().split(":");SaholicInventorySnapshot snapshot = saholicInventorySnapshotRepository.selectByWarehouseIdandItemId(Integer.valueOf(key[0]), Integer.valueOf(key[1]));if (snapshot != null) {snapshot.setAvailability(snapshot.getAvailability() - entry.getValue());}}// Put back the quantity receiving took off the PO lines.for (Map.Entry<Integer, Integer> entry : quantityToRestore.entrySet()) {WarehouseLineItem lineItem = warehouseLineItemRepository.selectByPurchaseOrderIdItemId(purchaseOrder.getId(), entry.getKey());if (lineItem != null) {lineItem.setUnfulfilledQuantity(lineItem.getUnfulfilledQuantity() + entry.getValue());}}// A PO closed by this receipt is open again now that the quantity is back on it.if (POStatus.CLOSED.equals(purchaseOrder.getStatus())) {purchaseOrder.setStatus(POStatus.READY);}for (WarehouseInventoryItem inventoryItem : inventoryItems) {for (WarehouseScan scan : warehouseScanRepository.selectAll(inventoryItem.getId(), ScanType.PURCHASE)) {warehouseScanRepository.delete(scan);}genericRepository.delete(inventoryItem);}for (WarehouseInvoiceItem invoiceItem : invoiceItems) {warehouseInvoiceItemRepository.delete(invoiceItem);}for (WarehousePurchase purchase : purchases) {genericRepository.delete(purchase);}warehouseSupplierInvoiceRepository.delete(invoice);LOGGER.info("REVERSED {} - invoice id {}, po {}, {} inventory row(s), {} unit(s) released",invoiceNumber, invoice.getId(), purchaseOrder.getPoNumber(), inventoryItems.size(), units);return true;}}