Subversion Repositories SmartDukaan

Rev

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

Rev Author Line No. Line
36999 amit 1
package com.spice.profitmandi.service.pricing;
2
 
3
import com.spice.profitmandi.common.exception.ProfitMandiBusinessException;
4
import com.spice.profitmandi.common.model.GrnPendingDataModel;
5
import com.spice.profitmandi.common.util.FormattingUtils;
6
import com.spice.profitmandi.dao.entity.fofo.InventoryItem;
7
import com.spice.profitmandi.dao.entity.transaction.PriceDrop;
8
import com.spice.profitmandi.dao.entity.transaction.PriceDropIMEI;
9
import com.spice.profitmandi.dao.enumuration.transaction.PriceDropImeiStatus;
10
import com.spice.profitmandi.dao.repository.fofo.InventoryItemRepository;
11
import com.spice.profitmandi.dao.repository.transaction.OrderRepository;
12
import com.spice.profitmandi.dao.repository.transaction.PriceDropIMEIRepository;
13
import com.spice.profitmandi.dao.repository.transaction.PriceDropRepository;
14
import com.spice.profitmandi.service.wallet.WalletService;
15
import in.shop2020.model.v1.order.WalletReferenceType;
16
import org.apache.logging.log4j.LogManager;
17
import org.apache.logging.log4j.Logger;
18
import org.springframework.beans.factory.annotation.Autowired;
19
import org.springframework.stereotype.Component;
20
import org.springframework.transaction.annotation.Propagation;
21
import org.springframework.transaction.annotation.Transactional;
22
 
23
import java.text.MessageFormat;
24
import java.time.LocalDateTime;
25
import java.util.Collections;
26
import java.util.HashMap;
27
import java.util.HashSet;
28
import java.util.List;
29
import java.util.Map;
30
import java.util.Set;
31
import java.util.stream.Collectors;
32
 
33
/**
34
 * Shared unit of work for price-hike deduction. The core {@link #deductPartnerUnits} (no transaction
35
 * of its own — runs in the caller's) is used by both the GRN hook (inside the GRN transaction) and
36
 * the on-demand endpoint. The endpoint path additionally uses the REQUIRES_NEW methods here so each
37
 * retailer is isolated.
38
 */
39
@Component
40
public class PriceHikeWorker {
41
 
42
    private static final Logger LOGGER = LogManager.getLogger(PriceHikeWorker.class);
43
 
44
    @Autowired
45
    private PriceDropRepository priceDropRepository;
46
    @Autowired
47
    private PriceDropIMEIRepository priceDropIMEIRepository;
48
    @Autowired
49
    private InventoryItemRepository inventoryItemRepository;
50
    @Autowired
51
    private OrderRepository orderRepository;
52
    @Autowired
53
    private WalletService walletService;
54
 
55
    /**
56
     * Idempotent create-APPROVED-rows + wallet debit for one retailer's serials against one hike.
57
     * Runs in the caller's transaction. Serials already recorded for this hike are skipped.
58
     */
59
    public void deductPartnerUnits(PriceDrop hike, int retailerId, Map<String, Integer> serialToInventoryItemId)
60
            throws ProfitMandiBusinessException {
61
        if (serialToInventoryItemId.isEmpty()) {
62
            return;
63
        }
64
        Set<String> existing = priceDropIMEIRepository.selectByPriceDropId(hike.getId()).stream()
65
                .filter(x -> x.getPartnerId() == retailerId)
66
                .map(PriceDropIMEI::getImei).collect(Collectors.toSet());
67
        Map<String, Integer> fresh = serialToInventoryItemId.entrySet().stream()
68
                .filter(e -> !existing.contains(e.getKey()))
69
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a));
70
        if (fresh.isEmpty()) {
71
            return;
72
        }
73
        LocalDateTime now = LocalDateTime.now();
74
        for (Map.Entry<String, Integer> e : fresh.entrySet()) {
75
            PriceDropIMEI row = new PriceDropIMEI();
76
            row.setPriceDropId(hike.getId());
77
            row.setPartnerId(retailerId);
78
            row.setImei(e.getKey());
79
            row.setStatus(PriceDropImeiStatus.APPROVED);
80
            row.setInventoryItemId(e.getValue() == null ? 0 : e.getValue());
81
            row.setCreditTimestamp(now);
82
            row.setUpdateTimestamp(now);
83
            priceDropIMEIRepository.persist(row);
84
        }
85
        // amount is negative for a hike, so amount * count reduces the wallet balance (a debit).
86
        String reason = MessageFormat.format("Price Hike recovery of Rs.{0} per unit. Total {1} item(s)",
87
                FormattingUtils.formatDecimal(Math.abs(hike.getDropAmount())), fresh.size());
88
        walletService.addAmountToWallet(retailerId, hike.getId(), WalletReferenceType.PRICE_DROP, reason,
89
                hike.getAmount() * fresh.size(), hike.getAffectedOn());
90
        LOGGER.info("Price hike {} — debited retailer {} for {} unit(s)", hike.getId(), retailerId, fresh.size());
91
    }
92
 
93
    /** Endpoint path: eligible (gap-billed) serials for a hike, grouped by retailer. Own transaction. */
94
    @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
95
    public Map<Integer, List<String>> loadEligibleUnits(int priceDropId) throws ProfitMandiBusinessException {
96
        PriceDrop pd = priceDropRepository.selectById(priceDropId);
97
        if (pd == null || pd.getAmount() >= 0) {
98
            return Collections.emptyMap();
99
        }
100
        List<GrnPendingDataModel> billed = orderRepository.selectAllOrdersByBilledDateCatalogId(
101
                pd.getCatalogItemId(), pd.getAffectedOn(), pd.getCreatedOn());
102
        return billed.stream()
103
                .filter(x -> x.getSerialNumber() != null && x.getRetailerId() > 0)
104
                .collect(Collectors.groupingBy(GrnPendingDataModel::getRetailerId,
105
                        Collectors.mapping(GrnPendingDataModel::getSerialNumber, Collectors.toList())));
106
    }
107
 
108
    /** Endpoint path: one retailer, its own transaction, so a single retailer's failure is isolated. */
109
    @Transactional(propagation = Propagation.REQUIRES_NEW)
110
    public void deductForRetailer(int priceDropId, int retailerId, List<String> serials)
111
            throws ProfitMandiBusinessException {
112
        PriceDrop hike = priceDropRepository.selectById(priceDropId);
113
        if (hike == null || hike.getAmount() >= 0) {
114
            return;
115
        }
116
        Set<String> serialSet = new HashSet<>(serials);
117
        Map<String, Integer> serialToInventoryItemId = new HashMap<>();
118
        for (String serial : serialSet) {
119
            serialToInventoryItemId.put(serial, 0); // 0 until GRN'd; the DN backfill fills it in later
120
        }
121
        // Set the real inventory_item_id for any serial already GRN'd (present in inventory).
122
        inventoryItemRepository.selectByFofoIdSerialNumbers(retailerId, serialSet, false)
123
                .forEach(ii -> serialToInventoryItemId.put(ii.getSerialNumber(), ii.getId()));
124
        deductPartnerUnits(hike, retailerId, serialToInventoryItemId);
125
    }
126
}