Subversion Repositories SmartDukaan

Rev

Rev 37614 | Go to most recent revision | Details | Compare with Previous | 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;
37615 amit 6
import com.spice.profitmandi.common.model.GrnModel;
7
import com.spice.profitmandi.common.model.InvoiceItemModel;
8
import com.spice.profitmandi.common.model.WarehouseInvoiceModel;
9
import com.spice.profitmandi.dao.entity.auth.AuthUser;
37614 amit 10
import com.spice.profitmandi.dao.entity.catalog.Item;
11
import com.spice.profitmandi.dao.entity.fofo.FofoStore;
12
import com.spice.profitmandi.dao.entity.transaction.LineItem;
13
import com.spice.profitmandi.dao.entity.transaction.Order;
14
import com.spice.profitmandi.dao.entity.warehouse.WarehousePurchaseOrder;
15
import com.spice.profitmandi.dao.entity.warehouse.WarehouseSupplierInvoice;
37615 amit 16
import com.spice.profitmandi.dao.enumuration.warehouse.WarehouseInvoiceStatus;
17
import com.spice.profitmandi.dao.repository.auth.AuthRepository;
37614 amit 18
import com.spice.profitmandi.dao.repository.catalog.ItemRepository;
19
import com.spice.profitmandi.dao.repository.dtr.FofoStoreRepository;
20
import com.spice.profitmandi.dao.repository.transaction.OrderRepository;
21
import com.spice.profitmandi.dao.repository.warehouse.WarehousePurchaseOrderRepository;
22
import com.spice.profitmandi.dao.repository.warehouse.WarehouseSupplierInvoiceRepository;
37615 amit 23
import com.spice.profitmandi.service.warehouse.InvoiceService;
37614 amit 24
import com.spice.profitmandi.service.warehouse.PurchaseOrderService;
25
import org.apache.logging.log4j.LogManager;
26
import org.apache.logging.log4j.Logger;
27
import org.springframework.beans.factory.annotation.Autowired;
28
import org.springframework.stereotype.Component;
29
import org.springframework.transaction.annotation.Propagation;
30
import org.springframework.transaction.annotation.Transactional;
31
 
32
import java.math.BigDecimal;
33
import java.time.LocalDateTime;
34
import java.util.ArrayList;
35
import java.util.Collections;
36
import java.util.HashSet;
37615 amit 37
import java.util.LinkedHashMap;
37614 amit 38
import java.util.List;
39
import java.util.Map;
40
import java.util.Set;
41
 
42
/**
37615 amit 43
 * Receives one internal transfer invoice into the destination warehouse, following the same sequence the
44
 * portal performs: record the supplier invoice, record its items, then GRN it through
45
 * PurchaseOrderService.grnPoModels - the call behind the portal's Create GRN button.
37614 amit 46
 *
37615 amit 47
 * The Excel upload path (addPORowModels) is deliberately not used. It persists a supplier invoice without a
48
 * status, which the column does not allow, so that route cannot complete a receipt at all.
49
 *
37614 amit 50
 * Kept in its own bean on purpose: InternalGrnTask calls this through the Spring proxy, so the transaction
51
 * below is actually applied. Calling it from inside the driver would be a self invocation, the proxy would
52
 * be bypassed and the work would run with no transaction at all.
53
 */
54
@Component
55
public class InternalGrnReceiver {
56
 
57
    private static final Logger LOGGER = LogManager.getLogger(InternalGrnReceiver.class);
58
 
37615 amit 59
    /** The portal stores the scanned supplier invoice against the receipt; an internal transfer has none. */
60
    private static final int NO_INVOICE_DOCUMENT = 0;
61
 
37614 amit 62
    @Autowired
63
    private OrderRepository orderRepository;
64
 
65
    @Autowired
66
    private ItemRepository itemRepository;
67
 
68
    @Autowired
69
    private FofoStoreRepository fofoStoreRepository;
70
 
71
    @Autowired
37615 amit 72
    private AuthRepository authRepository;
73
 
74
    @Autowired
37614 amit 75
    private WarehousePurchaseOrderRepository warehousePurchaseOrderRepository;
76
 
77
    @Autowired
78
    private WarehouseSupplierInvoiceRepository warehouseSupplierInvoiceRepository;
79
 
80
    @Autowired
37615 amit 81
    private InvoiceService invoiceService;
82
 
83
    @Autowired
37614 amit 84
    private PurchaseOrderService purchaseOrderService;
85
 
37615 amit 86
    /** One item of an invoice, gathered across the orders that share it. */
87
    private static class ReceiptLine {
88
        private int itemId;
89
        private boolean serialized;
90
        private float rate;
91
        private int qty;
92
        private final List<String> serialNumbers = new ArrayList<>();
93
    }
94
 
37614 amit 95
    /**
96
     * Each invoice commits or rolls back on its own so one bad invoice cannot undo those already received.
97
     *
98
     * @return true when the invoice was received (or would be, on a dry run), false when it was skipped.
99
     */
100
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Throwable.class)
101
    public boolean grnInvoice(String invoiceNumber, String operatorEmail, boolean dryRun) throws ProfitMandiBusinessException {
102
        List<Order> orders = orderRepository.selectByInvoiceNumber(invoiceNumber);
103
        if (orders.isEmpty()) {
104
            LOGGER.info("SKIP    {} - no orders for this invoice", invoiceNumber);
105
            return false;
106
        }
107
 
108
        // Only our own stock moving between our own warehouses - never auto-receive a partner's goods.
109
        FofoStore buyer = fofoStoreRepository.selectByRetailerId(orders.get(0).getRetailerId());
110
        if (buyer == null || !FofoType.INTERNAL.equals(buyer.getFofoType())) {
111
            LOGGER.info("SKIP    {} - buyer {} is not an INTERNAL store", invoiceNumber, orders.get(0).getRetailerId());
112
            return false;
113
        }
114
 
37615 amit 115
        WarehousePurchaseOrder purchaseOrder = resolvePurchaseOrder(invoiceNumber, orders);
37614 amit 116
        if (purchaseOrder == null) {
117
            return false;
118
        }
119
 
120
        WarehouseSupplierInvoice existing = warehouseSupplierInvoiceRepository
121
                .selectAllBySupplierInvoice(purchaseOrder.getSupplierId(), invoiceNumber);
122
        if (existing != null) {
123
            LOGGER.info("SKIP    {} - already received (invoice id {}, status {})",
124
                    invoiceNumber, existing.getId(), existing.getStatus());
125
            return false;
126
        }
127
 
37615 amit 128
        // grnPoModels takes one entry per item, carrying every serial of that item, so gather the orders by item.
129
        Map<Integer, ReceiptLine> linesByItem = new LinkedHashMap<>();
37614 amit 130
        for (Order order : orders) {
131
            LineItem lineItem = order.getLineItem();
132
            if (lineItem == null) {
133
                LOGGER.info("SKIP    {} - order {} has no line item", invoiceNumber, order.getId());
134
                return false;
135
            }
136
            Item item = itemRepository.selectById(lineItem.getItemId());
137
            if (item == null) {
138
                LOGGER.info("SKIP    {} - item {} not found", invoiceNumber, lineItem.getItemId());
139
                return false;
140
            }
141
 
142
            String serialNumber = lineItem.getSerialNumber();
143
            boolean serialized = ItemType.SERIALIZED.equals(item.getType());
144
            // Receiving a serialized unit without its serial would create stock no scan could ever match.
145
            if (serialized && (serialNumber == null || serialNumber.trim().isEmpty())) {
146
                LOGGER.info("SKIP    {} - serialized item {} on order {} has no serial number",
147
                        invoiceNumber, item.getId(), order.getId());
148
                return false;
149
            }
37615 amit 150
 
151
            ReceiptLine line = linesByItem.get(lineItem.getItemId());
152
            if (line == null) {
153
                line = new ReceiptLine();
154
                line.itemId = lineItem.getItemId();
155
                line.serialized = serialized;
156
                line.rate = lineItem.getUnitPrice();
157
                linesByItem.put(line.itemId, line);
158
            }
159
            line.qty += lineItem.getQuantity();
37614 amit 160
            if (serialized) {
37615 amit 161
                line.serialNumbers.add(serialNumber.trim());
37614 amit 162
            }
37615 amit 163
        }
37614 amit 164
 
37615 amit 165
        int numItems = 0;
166
        int serialisedUnits = 0;
167
        BigDecimal value = BigDecimal.ZERO;
168
        for (ReceiptLine line : linesByItem.values()) {
169
            numItems += line.qty;
170
            serialisedUnits += line.serialNumbers.size();
171
            value = value.add(BigDecimal.valueOf(line.rate).multiply(BigDecimal.valueOf(line.qty)));
37614 amit 172
        }
173
 
174
        if (dryRun) {
37615 amit 175
            LOGGER.info("WOULD GRN {} - po {} (id {}), warehouse {}, supplier {}, {} item(s), {} unit(s), {} serialized, value {}",
37614 amit 176
                    invoiceNumber, purchaseOrder.getPoNumber(), purchaseOrder.getId(), purchaseOrder.getWarehouseId(),
37615 amit 177
                    purchaseOrder.getSupplierId(), linesByItem.size(), numItems, serialisedUnits, value.toPlainString());
178
            for (ReceiptLine line : linesByItem.values()) {
179
                LOGGER.info("          item {} qty {} rate {} serials {}",
180
                        line.itemId, line.qty, line.rate,
181
                        line.serialNumbers.isEmpty() ? "-" : line.serialNumbers);
37614 amit 182
            }
183
            return true;
184
        }
185
 
37615 amit 186
        // 1. Record the supplier invoice, as the portal's Receive Invoice screen does.
187
        AuthUser operator = authRepository.selectByEmailOrMobile(operatorEmail);
188
        WarehouseSupplierInvoice invoice = new WarehouseSupplierInvoice();
189
        invoice.setInvoiceNumber(invoiceNumber);
190
        invoice.setInvoiceDate(orders.get(0).getBillingTimestamp());
191
        invoice.setSupplierId(purchaseOrder.getSupplierId());
192
        invoice.setWarehouseId(purchaseOrder.getWarehouseId());
193
        invoice.setCreateDate(LocalDateTime.now());
194
        invoice.setReceivedFrom(operator.getFullName());
195
        invoice.setNumItems(numItems);
196
        invoice.setTotalValue(value.floatValue());
197
        invoice.setStatus(WarehouseInvoiceStatus.init);
198
        invoice.setInvoiceDocId(NO_INVOICE_DOCUMENT);
199
        warehouseSupplierInvoiceRepository.persist(invoice);
37614 amit 200
 
37615 amit 201
        // 2. Record its items, as the Add Item screen does - the buying reports read these rows.
202
        WarehouseInvoiceModel invoiceModel = new WarehouseInvoiceModel();
203
        invoiceModel.setInvoiceId(invoice.getId());
204
        invoiceModel.setSupplierId(purchaseOrder.getSupplierId());
205
        invoiceModel.setWarehouseId(purchaseOrder.getWarehouseId());
206
        List<InvoiceItemModel> invoiceItems = new ArrayList<>();
207
        for (ReceiptLine line : linesByItem.values()) {
208
            InvoiceItemModel invoiceItem = new InvoiceItemModel();
209
            invoiceItem.setItemId(line.itemId);
210
            invoiceItem.setQty(line.qty);
211
            invoiceItem.setRate(line.rate);
212
            invoiceItems.add(invoiceItem);
213
        }
214
        invoiceModel.setInvoiceItems(invoiceItems);
215
        invoiceService.createInvoiceItem(invoiceModel);
216
 
217
        // 3. GRN it, exactly as the portal's Create GRN button does.
218
        List<GrnModel> grnModels = new ArrayList<>();
219
        for (ReceiptLine line : linesByItem.values()) {
220
            GrnModel grnModel = new GrnModel();
221
            grnModel.setPoId(purchaseOrder.getId());
222
            grnModel.setInvoiceId(invoice.getId());
223
            grnModel.setItemId(line.itemId);
224
            grnModel.setQty(line.qty);
225
            grnModel.setInvoicePrice(line.rate);
226
            grnModel.setSerialNumbers(line.serialized ? line.serialNumbers : new ArrayList<String>());
227
            grnModels.add(grnModel);
228
        }
229
        Map<Integer, Map<Integer, List<GrnModel>>> grnModelsMap = Collections.singletonMap(
230
                purchaseOrder.getId(), Collections.singletonMap(invoice.getId(), grnModels));
231
        purchaseOrderService.grnPoModels(operatorEmail, grnModelsMap);
232
 
233
        LOGGER.info("GRNED   {} - po {}, warehouse {}, invoice id {}, {} item(s), {} unit(s), {} serialized, value {}",
234
                invoiceNumber, purchaseOrder.getPoNumber(), purchaseOrder.getWarehouseId(), invoice.getId(),
235
                linesByItem.size(), numItems, serialisedUnits, value.toPlainString());
37614 amit 236
        return true;
237
    }
37615 amit 238
 
239
    /**
240
     * Creating an internal PO raises one transaction and records it on the order
241
     * (PurchaseOrderServiceImpl: warehousePurchaseOrder.setTransactionId), so that is the mapping back from
242
     * an invoice to its PO. order.purchase_order_id is not it - nothing populates it on this route.
243
     *
244
     * The invoice's orders all belong to that one transaction; if they ever did not, the PO is ambiguous and
245
     * the invoice is left alone rather than guessed at.
246
     */
247
    private WarehousePurchaseOrder resolvePurchaseOrder(String invoiceNumber, List<Order> orders) {
248
        Set<Integer> transactionIds = new HashSet<>();
249
        for (Order order : orders) {
250
            transactionIds.add(order.getTransactionId());
251
        }
252
        if (transactionIds.size() > 1) {
253
            LOGGER.info("SKIP    {} - spans {} transactions {}", invoiceNumber, transactionIds.size(), transactionIds);
254
            return null;
255
        }
256
        int transactionId = transactionIds.iterator().next();
257
        WarehousePurchaseOrder purchaseOrder = warehousePurchaseOrderRepository.selectByTransactionId(transactionId);
258
        if (purchaseOrder == null) {
259
            LOGGER.info("SKIP    {} - no purchase order mapped to transaction {}", invoiceNumber, transactionId);
260
            return null;
261
        }
262
        return purchaseOrder;
263
    }
37614 amit 264
}