Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
37617 amit 1
package com.smartdukaan.cron.migrations;
2
 
3
import com.spice.profitmandi.common.exception.ProfitMandiBusinessException;
4
import com.spice.profitmandi.dao.entity.inventory.SaholicInventorySnapshot;
5
import com.spice.profitmandi.dao.entity.transaction.Order;
6
import com.spice.profitmandi.dao.entity.warehouse.WarehouseInventoryItem;
7
import com.spice.profitmandi.dao.entity.warehouse.WarehouseInvoiceItem;
8
import com.spice.profitmandi.dao.entity.warehouse.WarehouseLineItem;
9
import com.spice.profitmandi.dao.entity.warehouse.WarehousePurchase;
10
import com.spice.profitmandi.dao.entity.warehouse.WarehousePurchaseOrder;
11
import com.spice.profitmandi.dao.entity.warehouse.WarehouseScan;
12
import com.spice.profitmandi.dao.entity.warehouse.WarehouseSupplierInvoice;
13
import in.shop2020.warehouse.ScanType;
14
import com.spice.profitmandi.dao.repository.GenericRepository;
15
import com.spice.profitmandi.dao.repository.inventory.SaholicInventorySnapshotRepository;
16
import com.spice.profitmandi.dao.repository.transaction.OrderRepository;
17
import com.spice.profitmandi.dao.repository.warehouse.WarehouseInvoiceItemRepository;
18
import com.spice.profitmandi.dao.repository.warehouse.WarehouseLineItemRepository;
19
import com.spice.profitmandi.dao.repository.warehouse.WarehousePurchaseOrderRepository;
20
import com.spice.profitmandi.dao.repository.warehouse.WarehousePurchaseRepository;
21
import com.spice.profitmandi.dao.repository.warehouse.WarehouseScanRepository;
22
import com.spice.profitmandi.dao.repository.warehouse.WarehouseSupplierInvoiceRepository;
23
import in.shop2020.purchase.POStatus;
24
import org.apache.logging.log4j.LogManager;
25
import org.apache.logging.log4j.Logger;
26
import org.springframework.beans.factory.annotation.Autowired;
27
import org.springframework.stereotype.Component;
28
import org.springframework.transaction.annotation.Propagation;
29
import org.springframework.transaction.annotation.Transactional;
30
 
31
import java.util.ArrayList;
32
import java.util.HashSet;
33
import java.util.LinkedHashMap;
34
import java.util.List;
35
import java.util.Map;
36
import java.util.Set;
37
 
38
/**
39
 * Undoes one internal GRN so the invoice can be received again cleanly.
40
 *
41
 * It removes only what receiving created - the scans, the inventory units, the invoice items, the purchase
42
 * and the supplier invoice - and puts back the two running figures receiving moved: the warehouse
43
 * availability it added to, and the quantity it took off the purchase order line.
44
 *
45
 * It refuses an invoice whose stock has been touched since. A unit that has been scanned out, split or
46
 * partly consumed is no longer ours to delete, and reversing around it would leave the warehouse holding
47
 * stock no record explains.
48
 *
49
 * Kept in its own bean so InternalGrnTask reaches it through the Spring proxy and the transaction below is
50
 * actually applied.
51
 */
52
@Component
53
public class InternalGrnReverser {
54
 
55
    private static final Logger LOGGER = LogManager.getLogger(InternalGrnReverser.class);
56
 
57
    @Autowired
58
    private OrderRepository orderRepository;
59
 
60
    @Autowired
61
    private GenericRepository genericRepository;
62
 
63
    @Autowired
64
    private WarehousePurchaseOrderRepository warehousePurchaseOrderRepository;
65
 
66
    @Autowired
67
    private WarehouseSupplierInvoiceRepository warehouseSupplierInvoiceRepository;
68
 
69
    @Autowired
70
    private WarehousePurchaseRepository warehousePurchaseRepository;
71
 
72
    @Autowired
73
    private WarehouseInvoiceItemRepository warehouseInvoiceItemRepository;
74
 
75
    @Autowired
76
    private WarehouseLineItemRepository warehouseLineItemRepository;
77
 
78
    @Autowired
79
    private WarehouseScanRepository warehouseScanRepository;
80
 
81
    @Autowired
82
    private SaholicInventorySnapshotRepository saholicInventorySnapshotRepository;
83
 
84
    /**
85
     * @return true when the invoice was reversed (or would be, on a dry run), false when it was left alone.
86
     */
87
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Throwable.class)
88
    public boolean reverseInvoice(String invoiceNumber, boolean dryRun) throws ProfitMandiBusinessException {
89
        List<Order> orders = orderRepository.selectByInvoiceNumber(invoiceNumber);
90
        if (orders.isEmpty()) {
91
            LOGGER.info("SKIP     {} - no orders for this invoice", invoiceNumber);
92
            return false;
93
        }
94
 
95
        // Resolved the same way receiving resolved it, so the reversal cannot land on a different PO.
96
        Set<Integer> transactionIds = new HashSet<>();
97
        for (Order order : orders) {
98
            transactionIds.add(order.getTransactionId());
99
        }
100
        if (transactionIds.size() > 1) {
101
            LOGGER.info("SKIP     {} - spans {} transactions", invoiceNumber, transactionIds.size());
102
            return false;
103
        }
104
        WarehousePurchaseOrder purchaseOrder =
105
                warehousePurchaseOrderRepository.selectByTransactionId(transactionIds.iterator().next());
106
        if (purchaseOrder == null) {
107
            LOGGER.info("SKIP     {} - no purchase order mapped to the transaction", invoiceNumber);
108
            return false;
109
        }
110
 
111
        WarehouseSupplierInvoice invoice = warehouseSupplierInvoiceRepository
112
                .selectAllBySupplierInvoice(purchaseOrder.getSupplierId(), invoiceNumber);
113
        if (invoice == null) {
114
            LOGGER.info("SKIP     {} - not received, nothing to reverse", invoiceNumber);
115
            return false;
116
        }
117
 
118
        List<WarehousePurchase> purchases = warehousePurchaseRepository.selectByInvoiceId(invoice.getId());
119
        List<WarehouseInventoryItem> inventoryItems = new ArrayList<>();
120
        for (WarehousePurchase purchase : purchases) {
121
            inventoryItems.addAll(genericRepository.<WarehouseInventoryItem>selectAllByEqualOrderByDesc(
122
                    WarehouseInventoryItem.class, "purchaseId", purchase.getId(), "id"));
123
        }
124
 
125
        // Anything that moved since receiving is not ours to undo.
126
        for (WarehouseInventoryItem inventoryItem : inventoryItems) {
127
            if (inventoryItem.getCurrentQuantity() != inventoryItem.getInitialQuantity()) {
128
                LOGGER.info("REFUSE   {} - inventory item {} has moved ({} of {} left), reverse it by hand",
129
                        invoiceNumber, inventoryItem.getId(), inventoryItem.getCurrentQuantity(),
130
                        inventoryItem.getInitialQuantity());
131
                return false;
132
            }
133
            if (!ScanType.PURCHASE.equals(inventoryItem.getLastScanType())) {
134
                LOGGER.info("REFUSE   {} - inventory item {} was last scanned {}, not PURCHASE",
135
                        invoiceNumber, inventoryItem.getId(), inventoryItem.getLastScanType());
136
                return false;
137
            }
138
        }
139
 
140
        // What receiving added to availability, keyed the way it added it.
141
        Map<String, Integer> availabilityToRelease = new LinkedHashMap<>();
142
        // What receiving took off each PO line.
143
        Map<Integer, Integer> quantityToRestore = new LinkedHashMap<>();
144
        int units = 0;
145
        for (WarehouseInventoryItem inventoryItem : inventoryItems) {
146
            String key = inventoryItem.getCurrentWarehouseId() + ":" + inventoryItem.getItemId();
147
            Integer released = availabilityToRelease.get(key);
148
            availabilityToRelease.put(key, (released == null ? 0 : released) + inventoryItem.getInitialQuantity());
149
            Integer restored = quantityToRestore.get(inventoryItem.getItemId());
150
            quantityToRestore.put(inventoryItem.getItemId(),
151
                    (restored == null ? 0 : restored) + inventoryItem.getInitialQuantity());
152
            units += inventoryItem.getInitialQuantity();
153
        }
154
 
155
        List<WarehouseInvoiceItem> invoiceItems = warehouseInvoiceItemRepository.selectByInvoiceId(invoice.getId());
156
 
157
        if (dryRun) {
158
            LOGGER.info("WOULD REVERSE {} - invoice id {}, po {}, {} purchase(s), {} inventory row(s), {} unit(s), {} invoice item(s)",
159
                    invoiceNumber, invoice.getId(), purchaseOrder.getPoNumber(), purchases.size(),
160
                    inventoryItems.size(), units, invoiceItems.size());
161
            for (WarehouseInventoryItem inventoryItem : inventoryItems) {
162
                LOGGER.info("          delete inventory {} item {} qty {} serial {}",
163
                        inventoryItem.getId(), inventoryItem.getItemId(), inventoryItem.getInitialQuantity(),
164
                        inventoryItem.getSerialNumber() == null ? "-" : inventoryItem.getSerialNumber());
165
            }
166
            for (Map.Entry<String, Integer> entry : availabilityToRelease.entrySet()) {
167
                String[] key = entry.getKey().split(":");
168
                SaholicInventorySnapshot snapshot = saholicInventorySnapshotRepository
169
                        .selectByWarehouseIdandItemId(Integer.valueOf(key[0]), Integer.valueOf(key[1]));
170
                LOGGER.info("          availability warehouse {} item {} : {} -> {}",
171
                        key[0], key[1], snapshot == null ? "absent" : snapshot.getAvailability(),
172
                        snapshot == null ? "absent" : (snapshot.getAvailability() - entry.getValue()));
173
            }
174
            for (Map.Entry<Integer, Integer> entry : quantityToRestore.entrySet()) {
175
                WarehouseLineItem lineItem = warehouseLineItemRepository
176
                        .selectByPurchaseOrderIdItemId(purchaseOrder.getId(), entry.getKey());
177
                LOGGER.info("          po line item {} unfulfilled : {} -> {}", entry.getKey(),
178
                        lineItem == null ? "absent" : lineItem.getUnfulfilledQuantity(),
179
                        lineItem == null ? "absent" : (lineItem.getUnfulfilledQuantity() + entry.getValue()));
180
            }
181
            if (POStatus.CLOSED.equals(purchaseOrder.getStatus())) {
182
                LOGGER.info("          po {} would be reopened to READY", purchaseOrder.getPoNumber());
183
            }
184
            return true;
185
        }
186
 
187
        // Give back the availability receiving added.
188
        for (Map.Entry<String, Integer> entry : availabilityToRelease.entrySet()) {
189
            String[] key = entry.getKey().split(":");
190
            SaholicInventorySnapshot snapshot = saholicInventorySnapshotRepository
191
                    .selectByWarehouseIdandItemId(Integer.valueOf(key[0]), Integer.valueOf(key[1]));
192
            if (snapshot != null) {
193
                snapshot.setAvailability(snapshot.getAvailability() - entry.getValue());
194
            }
195
        }
196
 
197
        // Put back the quantity receiving took off the PO lines.
198
        for (Map.Entry<Integer, Integer> entry : quantityToRestore.entrySet()) {
199
            WarehouseLineItem lineItem = warehouseLineItemRepository
200
                    .selectByPurchaseOrderIdItemId(purchaseOrder.getId(), entry.getKey());
201
            if (lineItem != null) {
202
                lineItem.setUnfulfilledQuantity(lineItem.getUnfulfilledQuantity() + entry.getValue());
203
            }
204
        }
205
 
206
        // A PO closed by this receipt is open again now that the quantity is back on it.
207
        if (POStatus.CLOSED.equals(purchaseOrder.getStatus())) {
208
            purchaseOrder.setStatus(POStatus.READY);
209
        }
210
 
211
        for (WarehouseInventoryItem inventoryItem : inventoryItems) {
212
            for (WarehouseScan scan : warehouseScanRepository.selectAll(inventoryItem.getId(), ScanType.PURCHASE)) {
213
                warehouseScanRepository.delete(scan);
214
            }
215
            genericRepository.delete(inventoryItem);
216
        }
217
        for (WarehouseInvoiceItem invoiceItem : invoiceItems) {
218
            warehouseInvoiceItemRepository.delete(invoiceItem);
219
        }
220
        for (WarehousePurchase purchase : purchases) {
221
            genericRepository.delete(purchase);
222
        }
223
        warehouseSupplierInvoiceRepository.delete(invoice);
224
 
225
        LOGGER.info("REVERSED {} - invoice id {}, po {}, {} inventory row(s), {} unit(s) released",
226
                invoiceNumber, invoice.getId(), purchaseOrder.getPoNumber(), inventoryItems.size(), units);
227
        return true;
228
    }
229
}