Subversion Repositories SmartDukaan

Rev

Rev 36999 | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 36999 Rev 37002
Line 9... Line 9...
9
import com.spice.profitmandi.dao.enumuration.transaction.PriceDropImeiStatus;
9
import com.spice.profitmandi.dao.enumuration.transaction.PriceDropImeiStatus;
10
import com.spice.profitmandi.dao.repository.fofo.InventoryItemRepository;
10
import com.spice.profitmandi.dao.repository.fofo.InventoryItemRepository;
11
import com.spice.profitmandi.dao.repository.transaction.OrderRepository;
11
import com.spice.profitmandi.dao.repository.transaction.OrderRepository;
12
import com.spice.profitmandi.dao.repository.transaction.PriceDropIMEIRepository;
12
import com.spice.profitmandi.dao.repository.transaction.PriceDropIMEIRepository;
13
import com.spice.profitmandi.dao.repository.transaction.PriceDropRepository;
13
import com.spice.profitmandi.dao.repository.transaction.PriceDropRepository;
-
 
14
import com.spice.profitmandi.service.inventory.InventoryService;
-
 
15
import com.spice.profitmandi.service.scheme.SchemeService;
14
import com.spice.profitmandi.service.wallet.WalletService;
16
import com.spice.profitmandi.service.wallet.WalletService;
15
import in.shop2020.model.v1.order.WalletReferenceType;
17
import in.shop2020.model.v1.order.WalletReferenceType;
16
import org.apache.logging.log4j.LogManager;
18
import org.apache.logging.log4j.LogManager;
17
import org.apache.logging.log4j.Logger;
19
import org.apache.logging.log4j.Logger;
18
import org.springframework.beans.factory.annotation.Autowired;
20
import org.springframework.beans.factory.annotation.Autowired;
Line 21... Line 23...
21
import org.springframework.transaction.annotation.Transactional;
23
import org.springframework.transaction.annotation.Transactional;
22
 
24
 
23
import java.text.MessageFormat;
25
import java.text.MessageFormat;
24
import java.time.LocalDateTime;
26
import java.time.LocalDateTime;
25
import java.util.Collections;
27
import java.util.Collections;
26
import java.util.HashMap;
-
 
27
import java.util.HashSet;
28
import java.util.HashSet;
28
import java.util.List;
29
import java.util.List;
29
import java.util.Map;
30
import java.util.Map;
30
import java.util.Set;
31
import java.util.Set;
31
import java.util.stream.Collectors;
32
import java.util.stream.Collectors;
32
 
33
 
33
/**
34
/**
34
 * Shared unit of work for price-hike deduction. The core {@link #deductPartnerUnits} (no transaction
35
 * Shared unit of work for price-hike deduction. {@link #deductPartnerUnits} (no transaction of its own —
35
 * of its own — runs in the caller's) is used by both the GRN hook (inside the GRN transaction) and
36
 * runs in the caller's) is used by both the GRN hook (inside the GRN transaction) and the on-demand
36
 * the on-demand endpoint. The endpoint path additionally uses the REQUIRES_NEW methods here so each
37
 * endpoint. The endpoint path additionally uses the REQUIRES_NEW methods here so each retailer is isolated.
37
 * retailer is isolated.
-
 
38
 */
38
 */
39
@Component
39
@Component
40
public class PriceHikeWorker {
40
public class PriceHikeWorker {
41
 
41
 
42
    private static final Logger LOGGER = LogManager.getLogger(PriceHikeWorker.class);
42
    private static final Logger LOGGER = LogManager.getLogger(PriceHikeWorker.class);
Line 49... Line 49...
49
    private InventoryItemRepository inventoryItemRepository;
49
    private InventoryItemRepository inventoryItemRepository;
50
    @Autowired
50
    @Autowired
51
    private OrderRepository orderRepository;
51
    private OrderRepository orderRepository;
52
    @Autowired
52
    @Autowired
53
    private WalletService walletService;
53
    private WalletService walletService;
-
 
54
    @Autowired
-
 
55
    private InventoryService inventoryService;
-
 
56
    @Autowired
-
 
57
    private SchemeService schemeService;
54
 
58
 
55
    /**
59
    /**
-
 
60
     * Idempotent, for one retailer's in-hand items against one hike. Runs in the caller's transaction.
56
     * Idempotent create-APPROVED-rows + wallet debit for one retailer's serials against one hike.
61
     * Creates APPROVED rows, raises the item's price-drop accumulator so scheme margin follows the new
-
 
62
     * (hiked) price, debits the wallet, and — when {@code recomputeSchemes} is true — reverses and
57
     * Runs in the caller's transaction. Serials already recorded for this hike are skipped.
63
     * reprocesses schemes as the price-drop flow does. Serials already recorded for this hike are skipped.
-
 
64
     *
-
 
65
     * @param recomputeSchemes false on the GRN path (the item has no SchemeInOut yet and the GRN flow's
-
 
66
     *                         own processSchemeIn runs right after, on the raised basis); true on the
-
 
67
     *                         endpoint path (the item is already GRN'd and has schemes to reprocess).
58
     */
68
     */
59
    public void deductPartnerUnits(PriceDrop hike, int retailerId, Map<String, Integer> serialToInventoryItemId)
69
    public void deductPartnerUnits(PriceDrop hike, int retailerId, List<InventoryItem> items, boolean recomputeSchemes)
60
            throws ProfitMandiBusinessException {
70
            throws ProfitMandiBusinessException {
61
        if (serialToInventoryItemId.isEmpty()) {
71
        if (items == null || items.isEmpty()) {
62
            return;
72
            return;
63
        }
73
        }
64
        Set<String> existing = priceDropIMEIRepository.selectByPriceDropId(hike.getId()).stream()
74
        Set<String> existing = priceDropIMEIRepository.selectByPriceDropId(hike.getId()).stream()
65
                .filter(x -> x.getPartnerId() == retailerId)
75
                .filter(x -> x.getPartnerId() == retailerId)
66
                .map(PriceDropIMEI::getImei).collect(Collectors.toSet());
76
                .map(PriceDropIMEI::getImei).collect(Collectors.toSet());
67
        Map<String, Integer> fresh = serialToInventoryItemId.entrySet().stream()
77
        List<InventoryItem> fresh = items.stream()
68
                .filter(e -> !existing.contains(e.getKey()))
78
                .filter(ii -> ii.getSerialNumber() != null && !existing.contains(ii.getSerialNumber()))
69
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a));
79
                .collect(Collectors.toList());
70
        if (fresh.isEmpty()) {
80
        if (fresh.isEmpty()) {
71
            return;
81
            return;
72
        }
82
        }
73
        LocalDateTime now = LocalDateTime.now();
83
        LocalDateTime now = LocalDateTime.now();
74
        for (Map.Entry<String, Integer> e : fresh.entrySet()) {
84
        for (InventoryItem ii : fresh) {
75
            PriceDropIMEI row = new PriceDropIMEI();
85
            PriceDropIMEI row = new PriceDropIMEI();
76
            row.setPriceDropId(hike.getId());
86
            row.setPriceDropId(hike.getId());
77
            row.setPartnerId(retailerId);
87
            row.setPartnerId(retailerId);
78
            row.setImei(e.getKey());
88
            row.setImei(ii.getSerialNumber());
79
            row.setStatus(PriceDropImeiStatus.APPROVED);
89
            row.setStatus(PriceDropImeiStatus.APPROVED);
80
            row.setInventoryItemId(e.getValue() == null ? 0 : e.getValue());
90
            row.setInventoryItemId(ii.getId());
81
            row.setCreditTimestamp(now);
91
            row.setCreditTimestamp(now);
82
            row.setUpdateTimestamp(now);
92
            row.setUpdateTimestamp(now);
83
            priceDropIMEIRepository.persist(row);
93
            priceDropIMEIRepository.persist(row);
84
        }
94
        }
-
 
95
        // Raise the item's cost basis (amount is negative), so scheme/offer margins track the new price.
-
 
96
        inventoryService.updatePriceDrop(fresh, hike.getAmount());
-
 
97
        if (recomputeSchemes) {
-
 
98
            String reversalReason = MessageFormat.format(
-
 
99
                    "Scheme reversal for Price Hike of Rs.{0} on {1}. Total {2} item(s)",
-
 
100
                    hike.getAmount(), FormattingUtils.formatDate(hike.getAffectedOn()), fresh.size());
-
 
101
            schemeService.reverseSchemes(fresh, hike.getId(), reversalReason);
-
 
102
            schemeService.processSchemeIn(fresh);
-
 
103
        }
85
        // amount is negative for a hike, so amount * count reduces the wallet balance (a debit).
104
        // 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)",
105
        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());
106
                FormattingUtils.formatDecimal(Math.abs(hike.getDropAmount())), fresh.size());
88
        walletService.addAmountToWallet(retailerId, hike.getId(), WalletReferenceType.PRICE_DROP, reason,
107
        walletService.addAmountToWallet(retailerId, hike.getId(), WalletReferenceType.PRICE_DROP, reason,
89
                hike.getAmount() * fresh.size(), hike.getAffectedOn());
108
                hike.getAmount() * fresh.size(), hike.getAffectedOn());
90
        LOGGER.info("Price hike {} — debited retailer {} for {} unit(s)", hike.getId(), retailerId, fresh.size());
109
        LOGGER.info("Price hike {} — debited retailer {} for {} unit(s), recomputeSchemes={}",
-
 
110
                hike.getId(), retailerId, fresh.size(), recomputeSchemes);
91
    }
111
    }
92
 
112
 
93
    /** Endpoint path: eligible (gap-billed) serials for a hike, grouped by retailer. Own transaction. */
113
    /** Endpoint path: eligible (gap-billed) serials for a hike, grouped by retailer. Own transaction. */
94
    @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
114
    @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
95
    public Map<Integer, List<String>> loadEligibleUnits(int priceDropId) throws ProfitMandiBusinessException {
115
    public Map<Integer, List<String>> loadEligibleUnits(int priceDropId) throws ProfitMandiBusinessException {
Line 103... Line 123...
103
                .filter(x -> x.getSerialNumber() != null && x.getRetailerId() > 0)
123
                .filter(x -> x.getSerialNumber() != null && x.getRetailerId() > 0)
104
                .collect(Collectors.groupingBy(GrnPendingDataModel::getRetailerId,
124
                .collect(Collectors.groupingBy(GrnPendingDataModel::getRetailerId,
105
                        Collectors.mapping(GrnPendingDataModel::getSerialNumber, Collectors.toList())));
125
                        Collectors.mapping(GrnPendingDataModel::getSerialNumber, Collectors.toList())));
106
    }
126
    }
107
 
127
 
-
 
128
    /**
108
    /** Endpoint path: one retailer, its own transaction, so a single retailer's failure is isolated. */
129
     * Endpoint path: one retailer, its own transaction. Processes only serials already GRN'd (present in
-
 
130
     * inventory) — full scheme reprocess applies to them; not-yet-GRN'd serials are left for the GRN hook.
-
 
131
     */
109
    @Transactional(propagation = Propagation.REQUIRES_NEW)
132
    @Transactional(propagation = Propagation.REQUIRES_NEW)
110
    public void deductForRetailer(int priceDropId, int retailerId, List<String> serials)
133
    public void deductForRetailer(int priceDropId, int retailerId, List<String> serials)
111
            throws ProfitMandiBusinessException {
134
            throws ProfitMandiBusinessException {
112
        PriceDrop hike = priceDropRepository.selectById(priceDropId);
135
        PriceDrop hike = priceDropRepository.selectById(priceDropId);
113
        if (hike == null || hike.getAmount() >= 0) {
136
        if (hike == null || hike.getAmount() >= 0) {
114
            return;
137
            return;
115
        }
138
        }
116
        Set<String> serialSet = new HashSet<>(serials);
-
 
117
        Map<String, Integer> serialToInventoryItemId = new HashMap<>();
139
        List<InventoryItem> items = inventoryItemRepository
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)
140
                .selectByFofoIdSerialNumbers(retailerId, new HashSet<>(serials), false);
123
                .forEach(ii -> serialToInventoryItemId.put(ii.getSerialNumber(), ii.getId()));
-
 
124
        deductPartnerUnits(hike, retailerId, serialToInventoryItemId);
141
        deductPartnerUnits(hike, retailerId, items, true);
125
    }
142
    }
126
}
143
}