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.PORowModel;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.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.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.List;import java.util.Map;import java.util.Set;/*** Receives stock into the destination warehouse for internal transfer invoices that were billed but never* GRNed. Per invoice it builds the rows the Excel GRN upload would have carried and hands them to* PurchaseOrderService.addPORowModels, which creates the supplier invoice, the purchase and the inventory* items in one call - the same path the portal uses, so nothing here reimplements receiving.** Deliberately narrow: only INTERNAL buyers, only invoices not already received, and a serialized line with* no serial number aborts its invoice rather than creating stock that cannot be traced to a unit.*/@Componentpublic class InternalGrnTask {private static final Logger LOGGER = LogManager.getLogger(InternalGrnTask.class);@Autowiredprivate OrderRepository orderRepository;@Autowiredprivate ItemRepository itemRepository;@Autowiredprivate FofoStoreRepository fofoStoreRepository;@Autowiredprivate WarehousePurchaseOrderRepository warehousePurchaseOrderRepository;@Autowiredprivate WarehouseSupplierInvoiceRepository warehouseSupplierInvoiceRepository;@Autowiredprivate PurchaseOrderService purchaseOrderService;/*** Drives the run without holding a transaction of its own, so one invoice failing cannot roll back the* invoices already received - each invoice commits or rolls back on its own inside grnInvoice.*/@Transactional(propagation = Propagation.NOT_SUPPORTED)public void grnInternalInvoices(List<String> invoiceNumbers, String operatorEmail, boolean dryRun) {LOGGER.info("=== Internal GRN {} : {} invoice(s), operator {} ===",dryRun ? "DRY RUN (no writes)" : "LIVE RUN", invoiceNumbers.size(), operatorEmail);int received = 0;int skipped = 0;int failed = 0;for (String invoiceNumber : invoiceNumbers) {try {if (grnInvoice(invoiceNumber.trim(), operatorEmail, dryRun)) {received++;} else {skipped++;}} catch (Exception e) {failed++;LOGGER.error("FAILED {} - {}", invoiceNumber, e.getMessage(), e);}}LOGGER.info("=== Internal GRN {} complete : {} {}, {} skipped, {} failed ===",dryRun ? "DRY RUN" : "LIVE RUN", received, dryRun ? "would be received" : "received",skipped, failed);}/*** @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;}// An internal PO reaches its orders through the transaction it raised, not through order.purchase_order_id.Set<Integer> transactionIds = new HashSet<>();for (Order order : orders) {transactionIds.add(order.getTransactionId());}Set<Integer> poIds = new HashSet<>();WarehousePurchaseOrder purchaseOrder = null;for (Integer transactionId : transactionIds) {WarehousePurchaseOrder po = warehousePurchaseOrderRepository.selectByTransactionId(transactionId);if (po != null && poIds.add(po.getId())) {purchaseOrder = po;}}if (purchaseOrder == null) {LOGGER.info("SKIP {} - no purchase order found for transaction(s) {}", invoiceNumber, transactionIds);return false;}// addPORowModels resolves a single PO for the whole map, so an invoice spanning POs cannot be trusted to it.if (poIds.size() > 1) {LOGGER.info("SKIP {} - spans {} purchase orders {}", invoiceNumber, poIds.size(), poIds);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;}LocalDateTime receivedDate = LocalDateTime.now();List<PORowModel> rows = new ArrayList<>();BigDecimal value = BigDecimal.ZERO;int serials = 0;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;}if (serialized) {serials++;}PORowModel row = new PORowModel();row.setPoNumber(purchaseOrder.getPoNumber());row.setInvoiceNumber(invoiceNumber);row.setInvoiceDate(order.getBillingTimestamp());row.setReceivedDate(receivedDate);row.setItemId(lineItem.getItemId());row.setQuantity(lineItem.getQuantity());row.setUnitPrice(lineItem.getUnitPrice());row.setImeiNumber(serialized ? serialNumber.trim() : null);rows.add(row);value = value.add(BigDecimal.valueOf(lineItem.getUnitPrice()).multiply(BigDecimal.valueOf(lineItem.getQuantity())));}if (dryRun) {LOGGER.info("WOULD GRN {} - po {} (id {}), warehouse {}, supplier {}, {} line(s), {} serialized, value {}, receivedDate {}",invoiceNumber, purchaseOrder.getPoNumber(), purchaseOrder.getId(), purchaseOrder.getWarehouseId(),purchaseOrder.getSupplierId(), rows.size(), serials, value.toPlainString(), receivedDate);for (PORowModel row : rows) {LOGGER.info(" item {} qty {} price {} serial {}",row.getItemId(), row.getQuantity(), row.getUnitPrice(),row.getImeiNumber() == null ? "-" : row.getImeiNumber());}return true;}// One PO and one invoice per call - addPORowModels applies the first PO it resolves to every entry.Map<String, Map<String, List<PORowModel>>> poRowModelsMap = Collections.singletonMap(purchaseOrder.getPoNumber(), Collections.singletonMap(invoiceNumber, rows));purchaseOrderService.addPORowModels(operatorEmail, poRowModelsMap);LOGGER.info("GRNED {} - po {}, warehouse {}, {} line(s), {} serialized, value {}",invoiceNumber, purchaseOrder.getPoNumber(), purchaseOrder.getWarehouseId(),rows.size(), serials, value.toPlainString());return true;}}