Subversion Repositories SmartDukaan

Rev

Rev 37613 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 37613 Rev 37614
Line 1... Line 1...
1
package com.smartdukaan.cron.migrations;
1
package com.smartdukaan.cron.migrations;
2
 
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;
3
import org.apache.logging.log4j.LogManager;
20
import org.apache.logging.log4j.Logger;
4
import org.apache.logging.log4j.Logger;
21
import org.springframework.beans.factory.annotation.Autowired;
5
import org.springframework.beans.factory.annotation.Autowired;
22
import org.springframework.stereotype.Component;
6
import org.springframework.stereotype.Component;
23
import org.springframework.transaction.annotation.Propagation;
-
 
24
import org.springframework.transaction.annotation.Transactional;
-
 
25
 
7
 
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;
8
import java.util.List;
32
import java.util.Map;
-
 
33
import java.util.Set;
-
 
34
 
9
 
35
/**
10
/**
36
 * Receives stock into the destination warehouse for internal transfer invoices that were billed but never
11
 * Receives stock into the destination warehouse for internal transfer invoices that were billed but never
37
 * GRNed. Per invoice it builds the rows the Excel GRN upload would have carried and hands them to
12
 * GRNed. The receiving itself is InternalGrnReceiver, one invoice at a time.
38
 * PurchaseOrderService.addPORowModels, which creates the supplier invoice, the purchase and the inventory
-
 
39
 * items in one call - the same path the portal uses, so nothing here reimplements receiving.
-
 
40
 *
13
 *
41
 * Deliberately narrow: only INTERNAL buyers, only invoices not already received, and a serialized line with
14
 * This driver carries no transaction of its own. It must not, and it must not hold the receiving logic
42
 * no serial number aborts its invoice rather than creating stock that cannot be traced to a unit.
15
 * either: a transactional method called from within its own bean is a self invocation, which bypasses the
-
 
16
 * Spring proxy and runs with no transaction at all.
43
 */
17
 */
44
@Component
18
@Component
45
public class InternalGrnTask {
19
public class InternalGrnTask {
46
 
20
 
47
    private static final Logger LOGGER = LogManager.getLogger(InternalGrnTask.class);
21
    private static final Logger LOGGER = LogManager.getLogger(InternalGrnTask.class);
48
 
22
 
49
    @Autowired
23
    @Autowired
50
    private OrderRepository orderRepository;
24
    private InternalGrnReceiver internalGrnReceiver;
51
 
25
 
52
    @Autowired
-
 
53
    private ItemRepository itemRepository;
-
 
54
 
-
 
55
    @Autowired
-
 
56
    private FofoStoreRepository fofoStoreRepository;
-
 
57
 
-
 
58
    @Autowired
-
 
59
    private WarehousePurchaseOrderRepository warehousePurchaseOrderRepository;
-
 
60
 
-
 
61
    @Autowired
-
 
62
    private WarehouseSupplierInvoiceRepository warehouseSupplierInvoiceRepository;
-
 
63
 
-
 
64
    @Autowired
-
 
65
    private PurchaseOrderService purchaseOrderService;
-
 
66
 
-
 
67
    /**
-
 
68
     * Drives the run without holding a transaction of its own, so one invoice failing cannot roll back the
-
 
69
     * invoices already received - each invoice commits or rolls back on its own inside grnInvoice.
-
 
70
     */
-
 
71
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
-
 
72
    public void grnInternalInvoices(List<String> invoiceNumbers, String operatorEmail, boolean dryRun) {
26
    public void grnInternalInvoices(List<String> invoiceNumbers, String operatorEmail, boolean dryRun) {
73
        LOGGER.info("=== Internal GRN {} : {} invoice(s), operator {} ===",
27
        LOGGER.info("=== Internal GRN {} : {} invoice(s), operator {} ===",
74
                dryRun ? "DRY RUN (no writes)" : "LIVE RUN", invoiceNumbers.size(), operatorEmail);
28
                dryRun ? "DRY RUN (no writes)" : "LIVE RUN", invoiceNumbers.size(), operatorEmail);
75
        int received = 0;
29
        int received = 0;
76
        int skipped = 0;
30
        int skipped = 0;
77
        int failed = 0;
31
        int failed = 0;
78
        for (String invoiceNumber : invoiceNumbers) {
32
        for (String invoiceNumber : invoiceNumbers) {
79
            try {
33
            try {
80
                if (grnInvoice(invoiceNumber.trim(), operatorEmail, dryRun)) {
34
                if (internalGrnReceiver.grnInvoice(invoiceNumber.trim(), operatorEmail, dryRun)) {
81
                    received++;
35
                    received++;
82
                } else {
36
                } else {
83
                    skipped++;
37
                    skipped++;
84
                }
38
                }
85
            } catch (Exception e) {
39
            } catch (Exception e) {
Line 89... Line 43...
89
        }
43
        }
90
        LOGGER.info("=== Internal GRN {} complete : {} {}, {} skipped, {} failed ===",
44
        LOGGER.info("=== Internal GRN {} complete : {} {}, {} skipped, {} failed ===",
91
                dryRun ? "DRY RUN" : "LIVE RUN", received, dryRun ? "would be received" : "received",
45
                dryRun ? "DRY RUN" : "LIVE RUN", received, dryRun ? "would be received" : "received",
92
                skipped, failed);
46
                skipped, failed);
93
    }
47
    }
94
 
-
 
95
    /**
-
 
96
     * @return true when the invoice was received (or would be, on a dry run), false when it was skipped.
-
 
97
     */
-
 
98
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Throwable.class)
-
 
99
    public boolean grnInvoice(String invoiceNumber, String operatorEmail, boolean dryRun) throws ProfitMandiBusinessException {
-
 
100
        List<Order> orders = orderRepository.selectByInvoiceNumber(invoiceNumber);
-
 
101
        if (orders.isEmpty()) {
-
 
102
            LOGGER.info("SKIP    {} - no orders for this invoice", invoiceNumber);
-
 
103
            return false;
-
 
104
        }
-
 
105
 
-
 
106
        // Only our own stock moving between our own warehouses - never auto-receive a partner's goods.
-
 
107
        FofoStore buyer = fofoStoreRepository.selectByRetailerId(orders.get(0).getRetailerId());
-
 
108
        if (buyer == null || !FofoType.INTERNAL.equals(buyer.getFofoType())) {
-
 
109
            LOGGER.info("SKIP    {} - buyer {} is not an INTERNAL store", invoiceNumber, orders.get(0).getRetailerId());
-
 
110
            return false;
-
 
111
        }
-
 
112
 
-
 
113
        // An internal PO reaches its orders through the transaction it raised, not through order.purchase_order_id.
-
 
114
        Set<Integer> transactionIds = new HashSet<>();
-
 
115
        for (Order order : orders) {
-
 
116
            transactionIds.add(order.getTransactionId());
-
 
117
        }
-
 
118
        Set<Integer> poIds = new HashSet<>();
-
 
119
        WarehousePurchaseOrder purchaseOrder = null;
-
 
120
        for (Integer transactionId : transactionIds) {
-
 
121
            WarehousePurchaseOrder po = warehousePurchaseOrderRepository.selectByTransactionId(transactionId);
-
 
122
            if (po != null && poIds.add(po.getId())) {
-
 
123
                purchaseOrder = po;
-
 
124
            }
-
 
125
        }
-
 
126
        if (purchaseOrder == null) {
-
 
127
            LOGGER.info("SKIP    {} - no purchase order found for transaction(s) {}", invoiceNumber, transactionIds);
-
 
128
            return false;
-
 
129
        }
-
 
130
        // addPORowModels resolves a single PO for the whole map, so an invoice spanning POs cannot be trusted to it.
-
 
131
        if (poIds.size() > 1) {
-
 
132
            LOGGER.info("SKIP    {} - spans {} purchase orders {}", invoiceNumber, poIds.size(), poIds);
-
 
133
            return false;
-
 
134
        }
-
 
135
 
-
 
136
        WarehouseSupplierInvoice existing = warehouseSupplierInvoiceRepository
-
 
137
                .selectAllBySupplierInvoice(purchaseOrder.getSupplierId(), invoiceNumber);
-
 
138
        if (existing != null) {
-
 
139
            LOGGER.info("SKIP    {} - already received (invoice id {}, status {})",
-
 
140
                    invoiceNumber, existing.getId(), existing.getStatus());
-
 
141
            return false;
-
 
142
        }
-
 
143
 
-
 
144
        LocalDateTime receivedDate = LocalDateTime.now();
-
 
145
        List<PORowModel> rows = new ArrayList<>();
-
 
146
        BigDecimal value = BigDecimal.ZERO;
-
 
147
        int serials = 0;
-
 
148
        for (Order order : orders) {
-
 
149
            LineItem lineItem = order.getLineItem();
-
 
150
            if (lineItem == null) {
-
 
151
                LOGGER.info("SKIP    {} - order {} has no line item", invoiceNumber, order.getId());
-
 
152
                return false;
-
 
153
            }
-
 
154
            Item item = itemRepository.selectById(lineItem.getItemId());
-
 
155
            if (item == null) {
-
 
156
                LOGGER.info("SKIP    {} - item {} not found", invoiceNumber, lineItem.getItemId());
-
 
157
                return false;
-
 
158
            }
-
 
159
 
-
 
160
            String serialNumber = lineItem.getSerialNumber();
-
 
161
            boolean serialized = ItemType.SERIALIZED.equals(item.getType());
-
 
162
            // Receiving a serialized unit without its serial would create stock no scan could ever match.
-
 
163
            if (serialized && (serialNumber == null || serialNumber.trim().isEmpty())) {
-
 
164
                LOGGER.info("SKIP    {} - serialized item {} on order {} has no serial number",
-
 
165
                        invoiceNumber, item.getId(), order.getId());
-
 
166
                return false;
-
 
167
            }
-
 
168
            if (serialized) {
-
 
169
                serials++;
-
 
170
            }
-
 
171
 
-
 
172
            PORowModel row = new PORowModel();
-
 
173
            row.setPoNumber(purchaseOrder.getPoNumber());
-
 
174
            row.setInvoiceNumber(invoiceNumber);
-
 
175
            row.setInvoiceDate(order.getBillingTimestamp());
-
 
176
            row.setReceivedDate(receivedDate);
-
 
177
            row.setItemId(lineItem.getItemId());
-
 
178
            row.setQuantity(lineItem.getQuantity());
-
 
179
            row.setUnitPrice(lineItem.getUnitPrice());
-
 
180
            row.setImeiNumber(serialized ? serialNumber.trim() : null);
-
 
181
            rows.add(row);
-
 
182
 
-
 
183
            value = value.add(BigDecimal.valueOf(lineItem.getUnitPrice())
-
 
184
                    .multiply(BigDecimal.valueOf(lineItem.getQuantity())));
-
 
185
        }
-
 
186
 
-
 
187
        if (dryRun) {
-
 
188
            LOGGER.info("WOULD GRN {} - po {} (id {}), warehouse {}, supplier {}, {} line(s), {} serialized, value {}, receivedDate {}",
-
 
189
                    invoiceNumber, purchaseOrder.getPoNumber(), purchaseOrder.getId(), purchaseOrder.getWarehouseId(),
-
 
190
                    purchaseOrder.getSupplierId(), rows.size(), serials, value.toPlainString(), receivedDate);
-
 
191
            for (PORowModel row : rows) {
-
 
192
                LOGGER.info("          item {} qty {} price {} serial {}",
-
 
193
                        row.getItemId(), row.getQuantity(), row.getUnitPrice(),
-
 
194
                        row.getImeiNumber() == null ? "-" : row.getImeiNumber());
-
 
195
            }
-
 
196
            return true;
-
 
197
        }
-
 
198
 
-
 
199
        // One PO and one invoice per call - addPORowModels applies the first PO it resolves to every entry.
-
 
200
        Map<String, Map<String, List<PORowModel>>> poRowModelsMap = Collections.singletonMap(
-
 
201
                purchaseOrder.getPoNumber(), Collections.singletonMap(invoiceNumber, rows));
-
 
202
        purchaseOrderService.addPORowModels(operatorEmail, poRowModelsMap);
-
 
203
 
-
 
204
        LOGGER.info("GRNED   {} - po {}, warehouse {}, {} line(s), {} serialized, value {}",
-
 
205
                invoiceNumber, purchaseOrder.getPoNumber(), purchaseOrder.getWarehouseId(),
-
 
206
                rows.size(), serials, value.toPlainString());
-
 
207
        return true;
-
 
208
    }
-
 
209
}
48
}