Subversion Repositories SmartDukaan

Rev

Rev 36338 | 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
 
47
    @Autowired
48
    private CreditAccountRepository creditAccountRepository;
49
 
50
    @Autowired
51
    private SDCreditService sdCreditService;
52
 
53
    @Autowired
54
    private PartnerInvestmentService partnerInvestmentService;
55
 
56
    @Autowired
57
    private TransactionRepository transactionRepository;
58
 
59
    @Autowired
60
    private CronBatchService cronBatchService;
61
 
62
    private static final NavigableMap<Double, Double> discountMap = new TreeMap<>();
63
    private static final List<Integer> hundredPercentLimitPartnerIds = Arrays.asList();
64
 
65
    static {
66
        discountMap.put(4 * ProfitMandiConstants.ONE_LAC - 1, 0.2);
67
        discountMap.put(10 * ProfitMandiConstants.ONE_LAC - 1, 0.25);
68
        discountMap.put(20 * ProfitMandiConstants.ONE_LAC - 1, 0.3);
69
        discountMap.put(Double.MAX_VALUE, 0.4);
70
    }
71
 
72
    /**
73
     * Read-only: calculates limits for all partners, returns only those that changed.
74
     */
75
    @Transactional(readOnly = true)
76
    public List<PartnerLimitUpdateData> calculateChangedPartnerLimits() throws ProfitMandiBusinessException {
77
        List<PartnerLimitUpdateData> changedPartners = new ArrayList<>();
78
 
79
        Map<Integer, CustomRetailer> customRetailerMap = retailerService.getFofoRetailers(true);
80
        Map<Integer, BigDecimal> fofoSidbiLimitMap = sidbiService.getSuggestedLimitMap();
81
        Map<Integer, SDCreditRequirement> sdCreditRequirementMap = sdCreditRequirementRepository.selectAll()
82
                .stream().collect(Collectors.toMap(x -> x.getFofoId(), x -> x));
83
        Map<Integer, CreditAccount> creditAccountMap = creditAccountRepository
84
                .selectAllByGateways(Arrays.asList(Gateway.SIDBI, Gateway.SDDIRECT))
85
                .stream().filter(x -> x.isActive()).collect(Collectors.toMap(x -> x.getFofoId(), x -> x));
86
        Map<Integer, BulkCreditSummary> bulkSummaryMap = sdCreditService.getCreditSummaryBulk();
87
 
88
        List<Integer> sortedFofoIds = customRetailerMap.keySet().stream().sorted().collect(Collectors.toList());
89
 
90
        for (int fofoId : sortedFofoIds) {
37087 amit 91
            // Per-partner isolation: one partner's bad data (null limit/util in the
92
            // compareTo, a getFirstBillingDate/getCurrentRisk failure, etc.) must not
93
            // abort the whole read phase and silently update zero partners. Mirrors the
94
            // REQUIRES_NEW isolation the write phase (updateSinglePartnerLimit) already has.
36306 amit 95
            try {
37087 amit 96
                CreditAccount creditAccount = creditAccountMap.get(fofoId);
97
                BulkCreditSummary bulkSummary = bulkSummaryMap.get(fofoId);
98
                BigDecimal utilizationAmount = bulkSummary != null ? bulkSummary.getUtilization() : BigDecimal.ZERO;
36306 amit 99
 
37087 amit 100
                PartnerDailyInvestment partnerDailyInvestment = partnerInvestmentService.getInvestment(fofoId, 0);
36306 amit 101
 
37087 amit 102
                BigDecimal suggestedAmount = getSuggestedAmount(creditAccount, partnerDailyInvestment, utilizationAmount, fofoSidbiLimitMap.get(fofoId));
103
                SDCreditRequirement existing = sdCreditRequirementMap.get(fofoId);
36306 amit 104
 
37087 amit 105
                if (existing == null) {
106
                    // New partner — needs a record
107
                    changedPartners.add(new PartnerLimitUpdateData(
108
                            fofoId, suggestedAmount, utilizationAmount,
109
                            suggestedAmount.subtract(utilizationAmount),
110
                            CreditRisk.HIGH_RISK, true));
111
                    continue;
112
                }
36306 amit 113
 
37087 amit 114
                LocalDateTime firstBillingDate = transactionRepository.getFirstBillingDate(fofoId);
115
                CreditRisk newRisk = sdCreditService.getCurrentRisk(existing, firstBillingDate);
36306 amit 116
 
37087 amit 117
                BigDecimal currentLimit = existing.isHardLimit() ? existing.getLimit() : existing.getSuggestedLimit();
118
                BigDecimal newLimit = existing.isHardLimit() ? existing.getLimit() : suggestedAmount;
119
                BigDecimal newAvailable = newLimit.subtract(utilizationAmount);
36306 amit 120
 
37087 amit 121
                // Compare: only include if something actually changed
122
                boolean limitChanged = suggestedAmount.compareTo(existing.getSuggestedLimit()) != 0;
123
                boolean utilizationChanged = utilizationAmount.compareTo(existing.getUtilizedAmount()) != 0;
124
                boolean riskChanged = !newRisk.equals(existing.getRisk());
125
 
126
                if (limitChanged || utilizationChanged || riskChanged) {
127
                    LOGGER.info("fofoId={} changed: limit {}→{}, util {}→{}, risk {}→{}",
128
                            fofoId,
129
                            existing.getSuggestedLimit(), suggestedAmount,
130
                            existing.getUtilizedAmount(), utilizationAmount,
131
                            existing.getRisk(), newRisk);
132
                    changedPartners.add(new PartnerLimitUpdateData(
133
                            fofoId, suggestedAmount, utilizationAmount, newAvailable, newRisk, false));
134
                }
135
            } catch (Exception e) {
136
                LOGGER.error("Partner limit calc failed for fofoId={}, skipping: {}", fofoId, e.getMessage());
36306 amit 137
            }
138
        }
139
 
140
        LOGGER.info("Partner limit check: {} total, {} changed", sortedFofoIds.size(), changedPartners.size());
141
        return changedPartners;
142
    }
143
 
144
    /**
145
     * Writes updated limit for a single partner in its own transaction.
146
     */
147
    @Transactional(propagation = Propagation.REQUIRES_NEW,
148
            rollbackFor = {Throwable.class, ProfitMandiBusinessException.class})
149
    public void updateSinglePartnerLimit(int batchId, PartnerLimitUpdateData data) throws ProfitMandiBusinessException {
150
        int fofoId = data.getFofoId();
151
 
152
        SDCreditRequirement sdCreditRequirement;
153
        if (data.isNewRecord()) {
154
            sdCreditRequirement = new SDCreditRequirement();
155
            sdCreditRequirement.setFofoId(fofoId);
156
            sdCreditRequirement.setCreditDays(15);
157
            sdCreditRequirement.setInterestRate(ProfitMandiConstants.NEW_INTEREST_RATE);
158
            sdCreditRequirement.setRisk(data.getCreditRisk());
159
            sdCreditRequirement.setUtilizedAmount(BigDecimal.ZERO);
160
            sdCreditRequirement.setCreateTimestamp(LocalDateTime.now());
161
            sdCreditRequirement.setUpdateTimestamp(LocalDateTime.now());
162
            sdCreditRequirement.setLimit(data.getSuggestedLimit());
163
            sdCreditRequirement.setSuggestedLimit(data.getSuggestedLimit());
164
            sdCreditRequirementRepository.persist(sdCreditRequirement);
165
        } else {
166
            sdCreditRequirement = sdCreditRequirementRepository.selectByFofoId(fofoId);
167
            sdCreditRequirement.setRisk(data.getCreditRisk());
168
            sdCreditRequirement.setSuggestedLimit(data.getSuggestedLimit());
169
            if (!sdCreditRequirement.isHardLimit()) {
170
                sdCreditRequirement.setLimit(data.getSuggestedLimit());
171
            }
172
            sdCreditRequirement.setUtilizedAmount(data.getUtilizationAmount());
173
            sdCreditRequirement.setUpdateTimestamp(LocalDateTime.now());
174
        }
175
 
176
        CreditAccount creditAccount = creditAccountRepository.selectByFofoIdAndGateway(fofoId, Gateway.SDDIRECT);
177
        if (creditAccount == null) {
178
            creditAccount = creditAccountRepository.selectByFofoIdAndGateway(fofoId, Gateway.SIDBI);
179
        }
180
        if (creditAccount != null) {
181
            creditAccount.setInterestRate(sdCreditRequirement.getInterestRate().floatValue());
182
            creditAccount.setSanctionedAmount(sdCreditRequirement.getLimit().floatValue());
183
            creditAccount.setAvailableAmount(data.getAvailableLimit().floatValue());
184
            creditAccount.setFreeDays(sdCreditRequirement.getFreeDays());
185
            creditAccount.setUpdatedOn(LocalDateTime.now());
186
        }
187
 
188
        cronBatchService.markItemSuccess(batchId, fofoId);
189
        LOGGER.info("fofoId={} updated: limit={}, util={}, risk={}", fofoId,
190
                sdCreditRequirement.getLimit(), data.getUtilizationAmount(), data.getCreditRisk());
191
    }
192
 
193
    private BigDecimal getSuggestedAmount(CreditAccount creditAccount, PartnerDailyInvestment partnerDailyInvestment,
194
                                          BigDecimal utilizationAmount, BigDecimal sidbiLimit) {
195
        BigDecimal suggestedAmount = BigDecimal.ZERO;
196
        double utilization = utilizationAmount != null ? utilizationAmount.doubleValue() : 0;
197
        if (creditAccount == null || creditAccount.getGateway().equals(Gateway.SDDIRECT)) {
198
            if (partnerDailyInvestment != null) {
199
                if (hundredPercentLimitPartnerIds.contains(partnerDailyInvestment.getFofoId())) {
200
                    suggestedAmount = BigDecimal.valueOf((partnerDailyInvestment.getTotalInvestment() - utilization) * 1);
201
                    suggestedAmount = suggestedAmount.min(BigDecimal.valueOf(1500000));
202
                } else {
203
                    suggestedAmount = getSuggestedLimit(partnerDailyInvestment.getTotalInvestment() - utilization);
204
                }
205
            }
206
            if (suggestedAmount.doubleValue() < 0) {
207
                suggestedAmount = BigDecimal.ZERO;
208
            }
209
        } else if (creditAccount.getGateway().equals(Gateway.SIDBI) && sidbiLimit != null) {
210
            suggestedAmount = getSuggestedLimit(partnerDailyInvestment.getTotalInvestment() - utilization);
211
            suggestedAmount = suggestedAmount.max(sidbiLimit);
212
        }
213
        return suggestedAmount;
214
    }
215
 
216
    private BigDecimal getSuggestedLimit(double investmentValue) {
217
        double percentageValue = discountMap.ceilingEntry(investmentValue).getValue();
218
        return BigDecimal.valueOf(investmentValue * percentageValue);
219
    }
220
}