Subversion Repositories SmartDukaan

Rev

Rev 37352 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
36306 amit 1
package com.smartdukaan.cron.scheduled;
2
 
3
import com.spice.profitmandi.common.exception.ProfitMandiBusinessException;
4
import com.spice.profitmandi.common.model.CustomRetailer;
5
import com.spice.profitmandi.common.model.ProfitMandiConstants;
6
import com.spice.profitmandi.dao.entity.dtr.CreditAccount;
7
import com.spice.profitmandi.dao.entity.fofo.PartnerDailyInvestment;
8
import com.spice.profitmandi.dao.entity.transaction.SDCreditRequirement;
9
import com.spice.profitmandi.dao.enumuration.fofo.Gateway;
10
import com.spice.profitmandi.dao.enumuration.transaction.CreditRisk;
11
import com.spice.profitmandi.dao.model.BulkCreditSummary;
12
import com.spice.profitmandi.dao.repository.dtr.CreditAccountRepository;
13
import com.spice.profitmandi.dao.repository.transaction.SDCreditRequirementRepository;
14
import com.spice.profitmandi.dao.repository.transaction.TransactionRepository;
15
import com.spice.profitmandi.service.PartnerInvestmentService;
36338 amit 16
import com.spice.profitmandi.service.cron.CronBatchService;
36306 amit 17
import com.spice.profitmandi.service.transaction.PartnerLimitUpdateData;
18
import com.spice.profitmandi.service.transaction.SDCreditService;
19
import com.spice.profitmandi.service.user.RetailerService;
20
import com.spice.profitmandi.dao.service.SidbiService;
21
import org.apache.logging.log4j.LogManager;
22
import org.apache.logging.log4j.Logger;
23
import org.springframework.beans.factory.annotation.Autowired;
24
import org.springframework.stereotype.Service;
25
import org.springframework.transaction.annotation.Propagation;
26
import org.springframework.transaction.annotation.Transactional;
27
 
28
import java.math.BigDecimal;
29
import java.time.LocalDateTime;
30
import java.util.*;
31
import java.util.stream.Collectors;
32
 
33
@Service
34
public class PartnerLimitHelper {
35
 
36
    private static final Logger LOGGER = LogManager.getLogger(PartnerLimitHelper.class);
37
 
38
    @Autowired
39
    private RetailerService retailerService;
40
 
41
    @Autowired
42
    private SidbiService sidbiService;
43
 
44
    @Autowired
45
    private SDCreditRequirementRepository sdCreditRequirementRepository;
46
 
37339 amit 47
    private static final BigDecimal FIFTEEN_LAC = BigDecimal.valueOf(1500000);
48
 
49
    @Autowired
36306 amit 50
    private CreditAccountRepository creditAccountRepository;
51
 
52
    @Autowired
53
    private SDCreditService sdCreditService;
54
 
55
    @Autowired
56
    private PartnerInvestmentService partnerInvestmentService;
57
 
58
    @Autowired
59
    private TransactionRepository transactionRepository;
60
 
61
    @Autowired
62
    private CronBatchService cronBatchService;
63
 
64
    private static final NavigableMap<Double, Double> discountMap = new TreeMap<>();
65
    private static final List<Integer> hundredPercentLimitPartnerIds = Arrays.asList();
66
 
67
    static {
68
        discountMap.put(4 * ProfitMandiConstants.ONE_LAC - 1, 0.2);
69
        discountMap.put(10 * ProfitMandiConstants.ONE_LAC - 1, 0.25);
70
        discountMap.put(20 * ProfitMandiConstants.ONE_LAC - 1, 0.3);
71
        discountMap.put(Double.MAX_VALUE, 0.4);
72
    }
73
 
74
    /**
75
     * Read-only: calculates limits for all partners, returns only those that changed.
76
     */
77
    @Transactional(readOnly = true)
78
    public List<PartnerLimitUpdateData> calculateChangedPartnerLimits() throws ProfitMandiBusinessException {
79
        List<PartnerLimitUpdateData> changedPartners = new ArrayList<>();
80
 
81
        Map<Integer, CustomRetailer> customRetailerMap = retailerService.getFofoRetailers(true);
82
        Map<Integer, BigDecimal> fofoSidbiLimitMap = sidbiService.getSuggestedLimitMap();
83
        Map<Integer, SDCreditRequirement> sdCreditRequirementMap = sdCreditRequirementRepository.selectAll()
84
                .stream().collect(Collectors.toMap(x -> x.getFofoId(), x -> x));
85
        Map<Integer, CreditAccount> creditAccountMap = creditAccountRepository
86
                .selectAllByGateways(Arrays.asList(Gateway.SIDBI, Gateway.SDDIRECT))
87
                .stream().filter(x -> x.isActive()).collect(Collectors.toMap(x -> x.getFofoId(), x -> x));
88
        Map<Integer, BulkCreditSummary> bulkSummaryMap = sdCreditService.getCreditSummaryBulk();
89
 
90
        List<Integer> sortedFofoIds = customRetailerMap.keySet().stream().sorted().collect(Collectors.toList());
91
 
92
        for (int fofoId : sortedFofoIds) {
37087 amit 93
            // Per-partner isolation: one partner's bad data (null limit/util in the
94
            // compareTo, a getFirstBillingDate/getCurrentRisk failure, etc.) must not
95
            // abort the whole read phase and silently update zero partners. Mirrors the
96
            // REQUIRES_NEW isolation the write phase (updateSinglePartnerLimit) already has.
36306 amit 97
            try {
37087 amit 98
                CreditAccount creditAccount = creditAccountMap.get(fofoId);
99
                BulkCreditSummary bulkSummary = bulkSummaryMap.get(fofoId);
100
                BigDecimal utilizationAmount = bulkSummary != null ? bulkSummary.getUtilization() : BigDecimal.ZERO;
36306 amit 101
 
37087 amit 102
                PartnerDailyInvestment partnerDailyInvestment = partnerInvestmentService.getInvestment(fofoId, 0);
36306 amit 103
 
37087 amit 104
                BigDecimal suggestedAmount = getSuggestedAmount(creditAccount, partnerDailyInvestment, utilizationAmount, fofoSidbiLimitMap.get(fofoId));
105
                SDCreditRequirement existing = sdCreditRequirementMap.get(fofoId);
36306 amit 106
 
37087 amit 107
                if (existing == null) {
108
                    // New partner — needs a record
109
                    changedPartners.add(new PartnerLimitUpdateData(
110
                            fofoId, suggestedAmount, utilizationAmount,
111
                            suggestedAmount.subtract(utilizationAmount),
112
                            CreditRisk.HIGH_RISK, true));
113
                    continue;
114
                }
36306 amit 115
 
37087 amit 116
                LocalDateTime firstBillingDate = transactionRepository.getFirstBillingDate(fofoId);
117
                CreditRisk newRisk = sdCreditService.getCurrentRisk(existing, firstBillingDate);
36306 amit 118
 
37087 amit 119
                BigDecimal currentLimit = existing.isHardLimit() ? existing.getLimit() : existing.getSuggestedLimit();
120
                BigDecimal newLimit = existing.isHardLimit() ? existing.getLimit() : suggestedAmount;
121
                BigDecimal newAvailable = newLimit.subtract(utilizationAmount);
36306 amit 122
 
37087 amit 123
                // Compare: only include if something actually changed
124
                boolean limitChanged = suggestedAmount.compareTo(existing.getSuggestedLimit()) != 0;
125
                boolean utilizationChanged = utilizationAmount.compareTo(existing.getUtilizedAmount()) != 0;
126
                boolean riskChanged = !newRisk.equals(existing.getRisk());
127
 
128
                if (limitChanged || utilizationChanged || riskChanged) {
37465 amit 129
                    LOGGER.info("fofoId={} changed: limit {}→{}, util {}→{}, risk {}→{}, agedAppleStock={}",
37087 amit 130
                            fofoId,
131
                            existing.getSuggestedLimit(), suggestedAmount,
132
                            existing.getUtilizedAmount(), utilizationAmount,
37465 amit 133
                            existing.getRisk(), newRisk,
134
                            partnerDailyInvestment != null ? partnerDailyInvestment.getAgedAppleStockAmount() : 0);
37087 amit 135
                    changedPartners.add(new PartnerLimitUpdateData(
136
                            fofoId, suggestedAmount, utilizationAmount, newAvailable, newRisk, false));
137
                }
138
            } catch (Exception e) {
139
                LOGGER.error("Partner limit calc failed for fofoId={}, skipping: {}", fofoId, e.getMessage());
36306 amit 140
            }
141
        }
142
 
143
        LOGGER.info("Partner limit check: {} total, {} changed", sortedFofoIds.size(), changedPartners.size());
144
        return changedPartners;
145
    }
146
 
147
    /**
148
     * Writes updated limit for a single partner in its own transaction.
149
     */
150
    @Transactional(propagation = Propagation.REQUIRES_NEW,
151
            rollbackFor = {Throwable.class, ProfitMandiBusinessException.class})
152
    public void updateSinglePartnerLimit(int batchId, PartnerLimitUpdateData data) throws ProfitMandiBusinessException {
153
        int fofoId = data.getFofoId();
154
 
155
        SDCreditRequirement sdCreditRequirement;
156
        if (data.isNewRecord()) {
157
            sdCreditRequirement = new SDCreditRequirement();
158
            sdCreditRequirement.setFofoId(fofoId);
159
            sdCreditRequirement.setCreditDays(15);
160
            sdCreditRequirement.setInterestRate(ProfitMandiConstants.NEW_INTEREST_RATE);
161
            sdCreditRequirement.setRisk(data.getCreditRisk());
162
            sdCreditRequirement.setUtilizedAmount(BigDecimal.ZERO);
163
            sdCreditRequirement.setCreateTimestamp(LocalDateTime.now());
164
            sdCreditRequirement.setUpdateTimestamp(LocalDateTime.now());
165
            sdCreditRequirement.setLimit(data.getSuggestedLimit());
166
            sdCreditRequirement.setSuggestedLimit(data.getSuggestedLimit());
167
            sdCreditRequirementRepository.persist(sdCreditRequirement);
168
        } else {
169
            sdCreditRequirement = sdCreditRequirementRepository.selectByFofoId(fofoId);
170
            sdCreditRequirement.setRisk(data.getCreditRisk());
171
            sdCreditRequirement.setSuggestedLimit(data.getSuggestedLimit());
172
            if (!sdCreditRequirement.isHardLimit()) {
173
                sdCreditRequirement.setLimit(data.getSuggestedLimit());
174
            }
175
            sdCreditRequirement.setUtilizedAmount(data.getUtilizationAmount());
176
            sdCreditRequirement.setUpdateTimestamp(LocalDateTime.now());
177
        }
178
 
179
        CreditAccount creditAccount = creditAccountRepository.selectByFofoIdAndGateway(fofoId, Gateway.SDDIRECT);
180
        if (creditAccount == null) {
181
            creditAccount = creditAccountRepository.selectByFofoIdAndGateway(fofoId, Gateway.SIDBI);
182
        }
183
        if (creditAccount != null) {
184
            creditAccount.setInterestRate(sdCreditRequirement.getInterestRate().floatValue());
185
            creditAccount.setSanctionedAmount(sdCreditRequirement.getLimit().floatValue());
186
            creditAccount.setAvailableAmount(data.getAvailableLimit().floatValue());
187
            creditAccount.setFreeDays(sdCreditRequirement.getFreeDays());
188
            creditAccount.setUpdatedOn(LocalDateTime.now());
189
        }
190
 
191
        cronBatchService.markItemSuccess(batchId, fofoId);
192
        LOGGER.info("fofoId={} updated: limit={}, util={}, risk={}", fofoId,
193
                sdCreditRequirement.getLimit(), data.getUtilizationAmount(), data.getCreditRisk());
194
    }
195
 
196
    private BigDecimal getSuggestedAmount(CreditAccount creditAccount, PartnerDailyInvestment partnerDailyInvestment,
197
                                          BigDecimal utilizationAmount, BigDecimal sidbiLimit) {
198
        BigDecimal suggestedAmount = BigDecimal.ZERO;
199
        double utilization = utilizationAmount != null ? utilizationAmount.doubleValue() : 0;
200
        if (creditAccount == null || creditAccount.getGateway().equals(Gateway.SDDIRECT)) {
201
            if (partnerDailyInvestment != null) {
37465 amit 202
                double creditableInvestment = getCreditableInvestment(partnerDailyInvestment);
36306 amit 203
                if (hundredPercentLimitPartnerIds.contains(partnerDailyInvestment.getFofoId())) {
37465 amit 204
                    suggestedAmount = BigDecimal.valueOf((creditableInvestment - utilization) * 1);
37339 amit 205
                    suggestedAmount = suggestedAmount.min(FIFTEEN_LAC);
36306 amit 206
                } else {
37465 amit 207
                    suggestedAmount = getSuggestedLimit(creditableInvestment - utilization);
36306 amit 208
                }
209
            }
210
            if (suggestedAmount.doubleValue() < 0) {
211
                suggestedAmount = BigDecimal.ZERO;
212
            }
213
        } else if (creditAccount.getGateway().equals(Gateway.SIDBI) && sidbiLimit != null) {
37465 amit 214
            suggestedAmount = getSuggestedLimit(getCreditableInvestment(partnerDailyInvestment) - utilization);
36306 amit 215
            suggestedAmount = suggestedAmount.max(sidbiLimit);
216
        }
217
        return suggestedAmount;
218
    }
219
 
37465 amit 220
    /**
221
     * Investment that counts toward a suggested limit: total investment less the value of Apple
222
     * handsets the partner has held beyond {@link ProfitMandiConstants#AGED_STOCK_APPLE_DAYS} days
223
     * from GRN.
224
     *
225
     * The haircut is applied here only. PartnerDailyInvestment.getTotalInvestment() is deliberately
226
     * left alone, so stock value is unchanged for checkout payment options, the investment-OK gates
227
     * and every partner-facing screen — this is a credit-limit policy, not a change to what the
228
     * stock is worth.
229
     */
230
    private double getCreditableInvestment(PartnerDailyInvestment partnerDailyInvestment) {
231
        return partnerDailyInvestment.getTotalInvestment() - partnerDailyInvestment.getAgedAppleStockAmount();
232
    }
233
 
36306 amit 234
    private BigDecimal getSuggestedLimit(double investmentValue) {
235
        double percentageValue = discountMap.ceilingEntry(investmentValue).getValue();
37339 amit 236
        return BigDecimal.valueOf(investmentValue * percentageValue).min(FIFTEEN_LAC);
36306 amit 237
    }
238
}