Subversion Repositories SmartDukaan

Rev

Rev 37615 | Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
37614 amit 1
package com.smartdukaan.cron.migrations;
2
 
3
import com.spice.profitmandi.common.enumuration.FofoType;
4
import com.spice.profitmandi.common.enumuration.ItemType;
5
import com.spice.profitmandi.common.exception.ProfitMandiBusinessException;
6
import com.spice.profitmandi.common.model.PORowModel;
7
import com.spice.profitmandi.dao.entity.catalog.Item;
8
import com.spice.profitmandi.dao.entity.fofo.FofoStore;
9
import com.spice.profitmandi.dao.entity.transaction.LineItem;
10
import com.spice.profitmandi.dao.entity.transaction.Order;
11
import com.spice.profitmandi.dao.entity.warehouse.WarehousePurchaseOrder;
12
import com.spice.profitmandi.dao.entity.warehouse.WarehouseSupplierInvoice;
13
import com.spice.profitmandi.dao.repository.catalog.ItemRepository;
14
import com.spice.profitmandi.dao.repository.dtr.FofoStoreRepository;
15
import com.spice.profitmandi.dao.repository.transaction.OrderRepository;
16
import com.spice.profitmandi.dao.repository.warehouse.WarehousePurchaseOrderRepository;
17
import com.spice.profitmandi.dao.repository.warehouse.WarehouseSupplierInvoiceRepository;
18
import com.spice.profitmandi.service.warehouse.PurchaseOrderService;
19
import org.apache.logging.log4j.LogManager;
20
import org.apache.logging.log4j.Logger;
21
import org.springframework.beans.factory.annotation.Autowired;
22
import org.springframework.stereotype.Component;
23
import org.springframework.transaction.annotation.Propagation;
24
import org.springframework.transaction.annotation.Transactional;
25
 
26
import java.math.BigDecimal;
27
import java.time.LocalDateTime;
28
import java.util.ArrayList;
29
import java.util.Collections;
30
import java.util.HashSet;
31
import java.util.List;
32
import java.util.Map;
33
import java.util.Set;
34
 
35
/**
36
 * Receives one internal transfer invoice into the destination warehouse.
37
 *
38
 * Kept in its own bean on purpose: InternalGrnTask calls this through the Spring proxy, so the transaction
39
 * below is actually applied. Calling it from inside the driver would be a self invocation, the proxy would
40
 * be bypassed and the work would run with no transaction at all.
41
 */
42
@Component
43
public class InternalGrnReceiver {
44
 
45
    private static final Logger LOGGER = LogManager.getLogger(InternalGrnReceiver.class);
46
 
47
    @Autowired
48
    private OrderRepository orderRepository;
49
 
50
    @Autowired
51
    private ItemRepository itemRepository;
52
 
53
    @Autowired
54
    private FofoStoreRepository fofoStoreRepository;
55
 
56
    @Autowired
57
    private WarehousePurchaseOrderRepository warehousePurchaseOrderRepository;
58
 
59
    @Autowired
60
    private WarehouseSupplierInvoiceRepository warehouseSupplierInvoiceRepository;
61
 
62
    @Autowired
63
    private PurchaseOrderService purchaseOrderService;
64
 
65
    /**
66
     * Each invoice commits or rolls back on its own so one bad invoice cannot undo those already received.
67
     *
68
     * @return true when the invoice was received (or would be, on a dry run), false when it was skipped.
69
     */
70
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Throwable.class)
71
    public boolean grnInvoice(String invoiceNumber, String operatorEmail, boolean dryRun) throws ProfitMandiBusinessException {
72
        List<Order> orders = orderRepository.selectByInvoiceNumber(invoiceNumber);
73
        if (orders.isEmpty()) {
74
            LOGGER.info("SKIP    {} - no orders for this invoice", invoiceNumber);
75
            return false;
76
        }
77
 
78
        // Only our own stock moving between our own warehouses - never auto-receive a partner's goods.
79
        FofoStore buyer = fofoStoreRepository.selectByRetailerId(orders.get(0).getRetailerId());
80
        if (buyer == null || !FofoType.INTERNAL.equals(buyer.getFofoType())) {
81
            LOGGER.info("SKIP    {} - buyer {} is not an INTERNAL store", invoiceNumber, orders.get(0).getRetailerId());
82
            return false;
83
        }
84
 
85
        // An internal PO reaches its orders through the transaction it raised, not through order.purchase_order_id.
86
        Set<Integer> transactionIds = new HashSet<>();
87
        for (Order order : orders) {
88
            transactionIds.add(order.getTransactionId());
89
        }
90
        Set<Integer> poIds = new HashSet<>();
91
        WarehousePurchaseOrder purchaseOrder = null;
92
        for (Integer transactionId : transactionIds) {
93
            WarehousePurchaseOrder po = warehousePurchaseOrderRepository.selectByTransactionId(transactionId);
94
            if (po != null && poIds.add(po.getId())) {
95
                purchaseOrder = po;
96
            }
97
        }
98
        if (purchaseOrder == null) {
99
            LOGGER.info("SKIP    {} - no purchase order found for transaction(s) {}", invoiceNumber, transactionIds);
100
            return false;
101
        }
102
        // addPORowModels resolves a single PO for the whole map, so an invoice spanning POs cannot be trusted to it.
103
        if (poIds.size() > 1) {
104
            LOGGER.info("SKIP    {} - spans {} purchase orders {}", invoiceNumber, poIds.size(), poIds);
105
            return false;
106
        }
107
 
108
        WarehouseSupplierInvoice existing = warehouseSupplierInvoiceRepository
109
                .selectAllBySupplierInvoice(purchaseOrder.getSupplierId(), invoiceNumber);
110
        if (existing != null) {
111
            LOGGER.info("SKIP    {} - already received (invoice id {}, status {})",
112
                    invoiceNumber, existing.getId(), existing.getStatus());
113
            return false;
114
        }
115
 
116
        LocalDateTime receivedDate = LocalDateTime.now();
117
        List<PORowModel> rows = new ArrayList<>();
118
        BigDecimal value = BigDecimal.ZERO;
119
        int serials = 0;
120
        for (Order order : orders) {
121
            LineItem lineItem = order.getLineItem();
122
            if (lineItem == null) {
123
                LOGGER.info("SKIP    {} - order {} has no line item", invoiceNumber, order.getId());
124
                return false;
125
            }
126
            Item item = itemRepository.selectById(lineItem.getItemId());
127
            if (item == null) {
128
                LOGGER.info("SKIP    {} - item {} not found", invoiceNumber, lineItem.getItemId());
129
                return false;
130
            }
131
 
132
            String serialNumber = lineItem.getSerialNumber();
133
            boolean serialized = ItemType.SERIALIZED.equals(item.getType());
134
            // Receiving a serialized unit without its serial would create stock no scan could ever match.
135
            if (serialized && (serialNumber == null || serialNumber.trim().isEmpty())) {
136
                LOGGER.info("SKIP    {} - serialized item {} on order {} has no serial number",
137
                        invoiceNumber, item.getId(), order.getId());
138
                return false;
139
            }
140
            if (serialized) {
141
                serials++;
142
            }
143
 
144
            PORowModel row = new PORowModel();
145
            row.setPoNumber(purchaseOrder.getPoNumber());
146
            row.setInvoiceNumber(invoiceNumber);
147
            row.setInvoiceDate(order.getBillingTimestamp());
148
            row.setReceivedDate(receivedDate);
149
            row.setItemId(lineItem.getItemId());
150
            row.setQuantity(lineItem.getQuantity());
151
            row.setUnitPrice(lineItem.getUnitPrice());
152
            row.setImeiNumber(serialized ? serialNumber.trim() : null);
153
            rows.add(row);
154
 
155
            value = value.add(BigDecimal.valueOf(lineItem.getUnitPrice())
156
                    .multiply(BigDecimal.valueOf(lineItem.getQuantity())));
157
        }
158
 
159
        if (dryRun) {
160
            LOGGER.info("WOULD GRN {} - po {} (id {}), warehouse {}, supplier {}, {} line(s), {} serialized, value {}, receivedDate {}",
161
                    invoiceNumber, purchaseOrder.getPoNumber(), purchaseOrder.getId(), purchaseOrder.getWarehouseId(),
162
                    purchaseOrder.getSupplierId(), rows.size(), serials, value.toPlainString(), receivedDate);
163
            for (PORowModel row : rows) {
164
                LOGGER.info("          item {} qty {} price {} serial {}",
165
                        row.getItemId(), row.getQuantity(), row.getUnitPrice(),
166
                        row.getImeiNumber() == null ? "-" : row.getImeiNumber());
167
            }
168
            return true;
169
        }
170
 
171
        // One PO and one invoice per call - addPORowModels applies the first PO it resolves to every entry.
172
        Map<String, Map<String, List<PORowModel>>> poRowModelsMap = Collections.singletonMap(
173
                purchaseOrder.getPoNumber(), Collections.singletonMap(invoiceNumber, rows));
174
        purchaseOrderService.addPORowModels(operatorEmail, poRowModelsMap);
175
 
176
        LOGGER.info("GRNED   {} - po {}, warehouse {}, {} line(s), {} serialized, value {}",
177
                invoiceNumber, purchaseOrder.getPoNumber(), purchaseOrder.getWarehouseId(),
178
                rows.size(), serials, value.toPlainString());
179
        return true;
180
    }
181
}