Rev 37614 | 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.enumuration.FofoType;import com.spice.profitmandi.common.enumuration.ItemType;import com.spice.profitmandi.common.exception.ProfitMandiBusinessException;import com.spice.profitmandi.common.model.GrnModel;import com.spice.profitmandi.common.model.InvoiceItemModel;import com.spice.profitmandi.common.model.WarehouseInvoiceModel;import com.spice.profitmandi.dao.entity.auth.AuthUser;import com.spice.profitmandi.dao.entity.catalog.Item;import com.spice.profitmandi.dao.entity.fofo.FofoStore;import com.spice.profitmandi.dao.entity.transaction.LineItem;import com.spice.profitmandi.dao.entity.transaction.Order;import com.spice.profitmandi.dao.entity.warehouse.WarehousePurchaseOrder;import com.spice.profitmandi.dao.entity.warehouse.WarehouseSupplierInvoice;import com.spice.profitmandi.dao.enumuration.warehouse.WarehouseInvoiceStatus;import com.spice.profitmandi.dao.repository.auth.AuthRepository;import com.spice.profitmandi.dao.repository.catalog.ItemRepository;import com.spice.profitmandi.dao.repository.dtr.FofoStoreRepository;import com.spice.profitmandi.dao.repository.transaction.OrderRepository;import com.spice.profitmandi.dao.repository.warehouse.WarehousePurchaseOrderRepository;import com.spice.profitmandi.dao.repository.warehouse.WarehouseSupplierInvoiceRepository;import com.spice.profitmandi.service.warehouse.InvoiceService;import com.spice.profitmandi.service.warehouse.PurchaseOrderService;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.math.BigDecimal;import java.time.LocalDateTime;import java.util.ArrayList;import java.util.Collections;import java.util.HashSet;import java.util.LinkedHashMap;import java.util.List;import java.util.Map;import java.util.Set;/*** Receives one internal transfer invoice into the destination warehouse, following the same sequence the* portal performs: record the supplier invoice, record its items, then GRN it through* PurchaseOrderService.grnPoModels - the call behind the portal's Create GRN button.** The Excel upload path (addPORowModels) is deliberately not used. It persists a supplier invoice without a* status, which the column does not allow, so that route cannot complete a receipt at all.** Kept in its own bean on purpose: InternalGrnTask calls this through the Spring proxy, so the transaction* below is actually applied. Calling it from inside the driver would be a self invocation, the proxy would* be bypassed and the work would run with no transaction at all.*/@Componentpublic class InternalGrnReceiver {private static final Logger LOGGER = LogManager.getLogger(InternalGrnReceiver.class);/** The portal stores the scanned supplier invoice against the receipt; an internal transfer has none. */private static final int NO_INVOICE_DOCUMENT = 0;@Autowiredprivate OrderRepository orderRepository;@Autowiredprivate ItemRepository itemRepository;@Autowiredprivate FofoStoreRepository fofoStoreRepository;@Autowiredprivate AuthRepository authRepository;@Autowiredprivate WarehousePurchaseOrderRepository warehousePurchaseOrderRepository;@Autowiredprivate WarehouseSupplierInvoiceRepository warehouseSupplierInvoiceRepository;@Autowiredprivate InvoiceService invoiceService;@Autowiredprivate PurchaseOrderService purchaseOrderService;/** One item of an invoice, gathered across the orders that share it. */private static class ReceiptLine {private int itemId;private boolean serialized;private float rate;private int qty;private final List<String> serialNumbers = new ArrayList<>();}/*** Each invoice commits or rolls back on its own so one bad invoice cannot undo those already received.** @return true when the invoice was received (or would be, on a dry run), false when it was skipped.*/@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Throwable.class)public boolean grnInvoice(String invoiceNumber, String operatorEmail, boolean dryRun) throws ProfitMandiBusinessException {List<Order> orders = orderRepository.selectByInvoiceNumber(invoiceNumber);if (orders.isEmpty()) {LOGGER.info("SKIP {} - no orders for this invoice", invoiceNumber);return false;}// Only our own stock moving between our own warehouses - never auto-receive a partner's goods.FofoStore buyer = fofoStoreRepository.selectByRetailerId(orders.get(0).getRetailerId());if (buyer == null || !FofoType.INTERNAL.equals(buyer.getFofoType())) {LOGGER.info("SKIP {} - buyer {} is not an INTERNAL store", invoiceNumber, orders.get(0).getRetailerId());return false;}WarehousePurchaseOrder purchaseOrder = resolvePurchaseOrder(invoiceNumber, orders);if (purchaseOrder == null) {return false;}WarehouseSupplierInvoice existing = warehouseSupplierInvoiceRepository.selectAllBySupplierInvoice(purchaseOrder.getSupplierId(), invoiceNumber);if (existing != null) {LOGGER.info("SKIP {} - already received (invoice id {}, status {})",invoiceNumber, existing.getId(), existing.getStatus());return false;}// grnPoModels takes one entry per item, carrying every serial of that item, so gather the orders by item.Map<Integer, ReceiptLine> linesByItem = new LinkedHashMap<>();for (Order order : orders) {LineItem lineItem = order.getLineItem();if (lineItem == null) {LOGGER.info("SKIP {} - order {} has no line item", invoiceNumber, order.getId());return false;}Item item = itemRepository.selectById(lineItem.getItemId());if (item == null) {LOGGER.info("SKIP {} - item {} not found", invoiceNumber, lineItem.getItemId());return false;}String serialNumber = lineItem.getSerialNumber();boolean serialized = ItemType.SERIALIZED.equals(item.getType());// Receiving a serialized unit without its serial would create stock no scan could ever match.if (serialized && (serialNumber == null || serialNumber.trim().isEmpty())) {LOGGER.info("SKIP {} - serialized item {} on order {} has no serial number",invoiceNumber, item.getId(), order.getId());return false;}ReceiptLine line = linesByItem.get(lineItem.getItemId());if (line == null) {line = new ReceiptLine();line.itemId = lineItem.getItemId();line.serialized = serialized;line.rate = lineItem.getUnitPrice();linesByItem.put(line.itemId, line);}line.qty += lineItem.getQuantity();if (serialized) {line.serialNumbers.add(serialNumber.trim());}}int numItems = 0;int serialisedUnits = 0;BigDecimal value = BigDecimal.ZERO;for (ReceiptLine line : linesByItem.values()) {numItems += line.qty;serialisedUnits += line.serialNumbers.size();value = value.add(BigDecimal.valueOf(line.rate).multiply(BigDecimal.valueOf(line.qty)));}if (dryRun) {LOGGER.info("WOULD GRN {} - po {} (id {}), warehouse {}, supplier {}, {} item(s), {} unit(s), {} serialized, value {}",invoiceNumber, purchaseOrder.getPoNumber(), purchaseOrder.getId(), purchaseOrder.getWarehouseId(),purchaseOrder.getSupplierId(), linesByItem.size(), numItems, serialisedUnits, value.toPlainString());for (ReceiptLine line : linesByItem.values()) {LOGGER.info(" item {} qty {} rate {} serials {}",line.itemId, line.qty, line.rate,line.serialNumbers.isEmpty() ? "-" : line.serialNumbers);}return true;}// 1. Record the supplier invoice, as the portal's Receive Invoice screen does.AuthUser operator = authRepository.selectByEmailOrMobile(operatorEmail);WarehouseSupplierInvoice invoice = new WarehouseSupplierInvoice();invoice.setInvoiceNumber(invoiceNumber);invoice.setInvoiceDate(orders.get(0).getBillingTimestamp());invoice.setSupplierId(purchaseOrder.getSupplierId());invoice.setWarehouseId(purchaseOrder.getWarehouseId());invoice.setCreateDate(LocalDateTime.now());invoice.setReceivedFrom(operator.getFullName());invoice.setNumItems(numItems);invoice.setTotalValue(value.floatValue());invoice.setStatus(WarehouseInvoiceStatus.init);invoice.setInvoiceDocId(NO_INVOICE_DOCUMENT);warehouseSupplierInvoiceRepository.persist(invoice);// 2. Record its items, as the Add Item screen does - the buying reports read these rows.WarehouseInvoiceModel invoiceModel = new WarehouseInvoiceModel();invoiceModel.setInvoiceId(invoice.getId());invoiceModel.setSupplierId(purchaseOrder.getSupplierId());invoiceModel.setWarehouseId(purchaseOrder.getWarehouseId());List<InvoiceItemModel> invoiceItems = new ArrayList<>();for (ReceiptLine line : linesByItem.values()) {InvoiceItemModel invoiceItem = new InvoiceItemModel();invoiceItem.setItemId(line.itemId);invoiceItem.setQty(line.qty);invoiceItem.setRate(line.rate);invoiceItems.add(invoiceItem);}invoiceModel.setInvoiceItems(invoiceItems);invoiceService.createInvoiceItem(invoiceModel);// 3. GRN it, exactly as the portal's Create GRN button does.List<GrnModel> grnModels = new ArrayList<>();for (ReceiptLine line : linesByItem.values()) {GrnModel grnModel = new GrnModel();grnModel.setPoId(purchaseOrder.getId());grnModel.setInvoiceId(invoice.getId());grnModel.setItemId(line.itemId);grnModel.setQty(line.qty);grnModel.setInvoicePrice(line.rate);grnModel.setSerialNumbers(line.serialized ? line.serialNumbers : new ArrayList<String>());grnModels.add(grnModel);}Map<Integer, Map<Integer, List<GrnModel>>> grnModelsMap = Collections.singletonMap(purchaseOrder.getId(), Collections.singletonMap(invoice.getId(), grnModels));purchaseOrderService.grnPoModels(operatorEmail, grnModelsMap);LOGGER.info("GRNED {} - po {}, warehouse {}, invoice id {}, {} item(s), {} unit(s), {} serialized, value {}",invoiceNumber, purchaseOrder.getPoNumber(), purchaseOrder.getWarehouseId(), invoice.getId(),linesByItem.size(), numItems, serialisedUnits, value.toPlainString());return true;}/*** Creating an internal PO raises one transaction and records it on the order* (PurchaseOrderServiceImpl: warehousePurchaseOrder.setTransactionId), so that is the mapping back from* an invoice to its PO. order.purchase_order_id is not it - nothing populates it on this route.** The invoice's orders all belong to that one transaction; if they ever did not, the PO is ambiguous and* the invoice is left alone rather than guessed at.*/private WarehousePurchaseOrder resolvePurchaseOrder(String invoiceNumber, List<Order> orders) {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(), transactionIds);return null;}int transactionId = transactionIds.iterator().next();WarehousePurchaseOrder purchaseOrder = warehousePurchaseOrderRepository.selectByTransactionId(transactionId);if (purchaseOrder == null) {LOGGER.info("SKIP {} - no purchase order mapped to transaction {}", invoiceNumber, transactionId);return null;}return purchaseOrder;}}