| 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) {
|
|
|
91 |
CreditAccount creditAccount = creditAccountMap.get(fofoId);
|
|
|
92 |
BulkCreditSummary bulkSummary = bulkSummaryMap.get(fofoId);
|
|
|
93 |
BigDecimal utilizationAmount = bulkSummary != null ? bulkSummary.getUtilization() : BigDecimal.ZERO;
|
|
|
94 |
|
|
|
95 |
PartnerDailyInvestment partnerDailyInvestment;
|
|
|
96 |
try {
|
|
|
97 |
partnerDailyInvestment = partnerInvestmentService.getInvestment(fofoId, 0);
|
|
|
98 |
} catch (ProfitMandiBusinessException e) {
|
|
|
99 |
LOGGER.error("Failed to get investment for fofoId={}: {}", fofoId, e.getMessage());
|
|
|
100 |
continue;
|
|
|
101 |
}
|
|
|
102 |
|
|
|
103 |
BigDecimal suggestedAmount = getSuggestedAmount(creditAccount, partnerDailyInvestment, utilizationAmount, fofoSidbiLimitMap.get(fofoId));
|
|
|
104 |
SDCreditRequirement existing = sdCreditRequirementMap.get(fofoId);
|
|
|
105 |
|
|
|
106 |
if (existing == null) {
|
|
|
107 |
// New partner — needs a record
|
|
|
108 |
changedPartners.add(new PartnerLimitUpdateData(
|
|
|
109 |
fofoId, suggestedAmount, utilizationAmount,
|
|
|
110 |
suggestedAmount.subtract(utilizationAmount),
|
|
|
111 |
CreditRisk.HIGH_RISK, true));
|
|
|
112 |
continue;
|
|
|
113 |
}
|
|
|
114 |
|
|
|
115 |
LocalDateTime firstBillingDate = transactionRepository.getFirstBillingDate(fofoId);
|
|
|
116 |
CreditRisk newRisk = sdCreditService.getCurrentRisk(existing, firstBillingDate);
|
|
|
117 |
|
|
|
118 |
BigDecimal currentLimit = existing.isHardLimit() ? existing.getLimit() : existing.getSuggestedLimit();
|
|
|
119 |
BigDecimal newLimit = existing.isHardLimit() ? existing.getLimit() : suggestedAmount;
|
|
|
120 |
BigDecimal newAvailable = newLimit.subtract(utilizationAmount);
|
|
|
121 |
|
|
|
122 |
// Compare: only include if something actually changed
|
|
|
123 |
boolean limitChanged = suggestedAmount.compareTo(existing.getSuggestedLimit()) != 0;
|
|
|
124 |
boolean utilizationChanged = utilizationAmount.compareTo(existing.getUtilizedAmount()) != 0;
|
|
|
125 |
boolean riskChanged = !newRisk.equals(existing.getRisk());
|
|
|
126 |
|
|
|
127 |
if (limitChanged || utilizationChanged || riskChanged) {
|
|
|
128 |
LOGGER.info("fofoId={} changed: limit {}→{}, util {}→{}, risk {}→{}",
|
|
|
129 |
fofoId,
|
|
|
130 |
existing.getSuggestedLimit(), suggestedAmount,
|
|
|
131 |
existing.getUtilizedAmount(), utilizationAmount,
|
|
|
132 |
existing.getRisk(), newRisk);
|
|
|
133 |
changedPartners.add(new PartnerLimitUpdateData(
|
|
|
134 |
fofoId, suggestedAmount, utilizationAmount, newAvailable, newRisk, false));
|
|
|
135 |
}
|
|
|
136 |
}
|
|
|
137 |
|
|
|
138 |
LOGGER.info("Partner limit check: {} total, {} changed", sortedFofoIds.size(), changedPartners.size());
|
|
|
139 |
return changedPartners;
|
|
|
140 |
}
|
|
|
141 |
|
|
|
142 |
/**
|
|
|
143 |
* Writes updated limit for a single partner in its own transaction.
|
|
|
144 |
*/
|
|
|
145 |
@Transactional(propagation = Propagation.REQUIRES_NEW,
|
|
|
146 |
rollbackFor = {Throwable.class, ProfitMandiBusinessException.class})
|
|
|
147 |
public void updateSinglePartnerLimit(int batchId, PartnerLimitUpdateData data) throws ProfitMandiBusinessException {
|
|
|
148 |
int fofoId = data.getFofoId();
|
|
|
149 |
|
|
|
150 |
SDCreditRequirement sdCreditRequirement;
|
|
|
151 |
if (data.isNewRecord()) {
|
|
|
152 |
sdCreditRequirement = new SDCreditRequirement();
|
|
|
153 |
sdCreditRequirement.setFofoId(fofoId);
|
|
|
154 |
sdCreditRequirement.setCreditDays(15);
|
|
|
155 |
sdCreditRequirement.setInterestRate(ProfitMandiConstants.NEW_INTEREST_RATE);
|
|
|
156 |
sdCreditRequirement.setRisk(data.getCreditRisk());
|
|
|
157 |
sdCreditRequirement.setUtilizedAmount(BigDecimal.ZERO);
|
|
|
158 |
sdCreditRequirement.setCreateTimestamp(LocalDateTime.now());
|
|
|
159 |
sdCreditRequirement.setUpdateTimestamp(LocalDateTime.now());
|
|
|
160 |
sdCreditRequirement.setLimit(data.getSuggestedLimit());
|
|
|
161 |
sdCreditRequirement.setSuggestedLimit(data.getSuggestedLimit());
|
|
|
162 |
sdCreditRequirementRepository.persist(sdCreditRequirement);
|
|
|
163 |
} else {
|
|
|
164 |
sdCreditRequirement = sdCreditRequirementRepository.selectByFofoId(fofoId);
|
|
|
165 |
sdCreditRequirement.setRisk(data.getCreditRisk());
|
|
|
166 |
sdCreditRequirement.setSuggestedLimit(data.getSuggestedLimit());
|
|
|
167 |
if (!sdCreditRequirement.isHardLimit()) {
|
|
|
168 |
sdCreditRequirement.setLimit(data.getSuggestedLimit());
|
|
|
169 |
}
|
|
|
170 |
sdCreditRequirement.setUtilizedAmount(data.getUtilizationAmount());
|
|
|
171 |
sdCreditRequirement.setUpdateTimestamp(LocalDateTime.now());
|
|
|
172 |
}
|
|
|
173 |
|
|
|
174 |
CreditAccount creditAccount = creditAccountRepository.selectByFofoIdAndGateway(fofoId, Gateway.SDDIRECT);
|
|
|
175 |
if (creditAccount == null) {
|
|
|
176 |
creditAccount = creditAccountRepository.selectByFofoIdAndGateway(fofoId, Gateway.SIDBI);
|
|
|
177 |
}
|
|
|
178 |
if (creditAccount != null) {
|
|
|
179 |
creditAccount.setInterestRate(sdCreditRequirement.getInterestRate().floatValue());
|
|
|
180 |
creditAccount.setSanctionedAmount(sdCreditRequirement.getLimit().floatValue());
|
|
|
181 |
creditAccount.setAvailableAmount(data.getAvailableLimit().floatValue());
|
|
|
182 |
creditAccount.setFreeDays(sdCreditRequirement.getFreeDays());
|
|
|
183 |
creditAccount.setUpdatedOn(LocalDateTime.now());
|
|
|
184 |
}
|
|
|
185 |
|
|
|
186 |
cronBatchService.markItemSuccess(batchId, fofoId);
|
|
|
187 |
LOGGER.info("fofoId={} updated: limit={}, util={}, risk={}", fofoId,
|
|
|
188 |
sdCreditRequirement.getLimit(), data.getUtilizationAmount(), data.getCreditRisk());
|
|
|
189 |
}
|
|
|
190 |
|
|
|
191 |
private BigDecimal getSuggestedAmount(CreditAccount creditAccount, PartnerDailyInvestment partnerDailyInvestment,
|
|
|
192 |
BigDecimal utilizationAmount, BigDecimal sidbiLimit) {
|
|
|
193 |
BigDecimal suggestedAmount = BigDecimal.ZERO;
|
|
|
194 |
double utilization = utilizationAmount != null ? utilizationAmount.doubleValue() : 0;
|
|
|
195 |
if (creditAccount == null || creditAccount.getGateway().equals(Gateway.SDDIRECT)) {
|
|
|
196 |
if (partnerDailyInvestment != null) {
|
|
|
197 |
if (hundredPercentLimitPartnerIds.contains(partnerDailyInvestment.getFofoId())) {
|
|
|
198 |
suggestedAmount = BigDecimal.valueOf((partnerDailyInvestment.getTotalInvestment() - utilization) * 1);
|
|
|
199 |
suggestedAmount = suggestedAmount.min(BigDecimal.valueOf(1500000));
|
|
|
200 |
} else {
|
|
|
201 |
suggestedAmount = getSuggestedLimit(partnerDailyInvestment.getTotalInvestment() - utilization);
|
|
|
202 |
}
|
|
|
203 |
}
|
|
|
204 |
if (suggestedAmount.doubleValue() < 0) {
|
|
|
205 |
suggestedAmount = BigDecimal.ZERO;
|
|
|
206 |
}
|
|
|
207 |
} else if (creditAccount.getGateway().equals(Gateway.SIDBI) && sidbiLimit != null) {
|
|
|
208 |
suggestedAmount = getSuggestedLimit(partnerDailyInvestment.getTotalInvestment() - utilization);
|
|
|
209 |
suggestedAmount = suggestedAmount.max(sidbiLimit);
|
|
|
210 |
}
|
|
|
211 |
return suggestedAmount;
|
|
|
212 |
}
|
|
|
213 |
|
|
|
214 |
private BigDecimal getSuggestedLimit(double investmentValue) {
|
|
|
215 |
double percentageValue = discountMap.ceilingEntry(investmentValue).getValue();
|
|
|
216 |
return BigDecimal.valueOf(investmentValue * percentageValue);
|
|
|
217 |
}
|
|
|
218 |
}
|