Subversion Repositories SmartDukaan

Rev

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

Rev 37465 Rev 37589
Line 24... Line 24...
24
import org.springframework.stereotype.Service;
24
import org.springframework.stereotype.Service;
25
import org.springframework.transaction.annotation.Propagation;
25
import org.springframework.transaction.annotation.Propagation;
26
import org.springframework.transaction.annotation.Transactional;
26
import org.springframework.transaction.annotation.Transactional;
27
 
27
 
28
import java.math.BigDecimal;
28
import java.math.BigDecimal;
-
 
29
import java.math.RoundingMode;
29
import java.time.LocalDateTime;
30
import java.time.LocalDateTime;
30
import java.util.*;
31
import java.util.*;
31
import java.util.stream.Collectors;
32
import java.util.stream.Collectors;
32
 
33
 
33
@Service
34
@Service
Line 74... Line 75...
74
    /**
75
    /**
75
     * Read-only: calculates limits for all partners, returns only those that changed.
76
     * Read-only: calculates limits for all partners, returns only those that changed.
76
     */
77
     */
77
    @Transactional(readOnly = true)
78
    @Transactional(readOnly = true)
78
    public List<PartnerLimitUpdateData> calculateChangedPartnerLimits() throws ProfitMandiBusinessException {
79
    public List<PartnerLimitUpdateData> calculateChangedPartnerLimits() throws ProfitMandiBusinessException {
-
 
80
        return calculateChangedPartnerLimits(null);
-
 
81
    }
-
 
82
 
-
 
83
    /**
-
 
84
     * Read-only: calculates limits and returns only those that changed.
-
 
85
     *
-
 
86
     * @param restrictTo when non-null, only these partners are considered. The 2-minute investment
-
 
87
     * sweep passes the partners whose {@code base_value} moved — measured at 3 on average, peak 18 —
-
 
88
     * so the limit stops rescanning all ~980 partners to find the handful that can possibly differ.
-
 
89
     */
-
 
90
    @Transactional(readOnly = true)
-
 
91
    public List<PartnerLimitUpdateData> calculateChangedPartnerLimits(Collection<Integer> restrictTo)
-
 
92
            throws ProfitMandiBusinessException {
79
        List<PartnerLimitUpdateData> changedPartners = new ArrayList<>();
93
        List<PartnerLimitUpdateData> changedPartners = new ArrayList<>();
80
 
94
 
81
        Map<Integer, CustomRetailer> customRetailerMap = retailerService.getFofoRetailers(true);
95
        Map<Integer, CustomRetailer> customRetailerMap = retailerService.getFofoRetailers(true);
82
        Map<Integer, BigDecimal> fofoSidbiLimitMap = sidbiService.getSuggestedLimitMap();
96
        Map<Integer, BigDecimal> fofoSidbiLimitMap = sidbiService.getSuggestedLimitMap();
83
        Map<Integer, SDCreditRequirement> sdCreditRequirementMap = sdCreditRequirementRepository.selectAll()
97
        Map<Integer, SDCreditRequirement> sdCreditRequirementMap = sdCreditRequirementRepository.selectAll()
Line 85... Line 99...
85
        Map<Integer, CreditAccount> creditAccountMap = creditAccountRepository
99
        Map<Integer, CreditAccount> creditAccountMap = creditAccountRepository
86
                .selectAllByGateways(Arrays.asList(Gateway.SIDBI, Gateway.SDDIRECT))
100
                .selectAllByGateways(Arrays.asList(Gateway.SIDBI, Gateway.SDDIRECT))
87
                .stream().filter(x -> x.isActive()).collect(Collectors.toMap(x -> x.getFofoId(), x -> x));
101
                .stream().filter(x -> x.isActive()).collect(Collectors.toMap(x -> x.getFofoId(), x -> x));
88
        Map<Integer, BulkCreditSummary> bulkSummaryMap = sdCreditService.getCreditSummaryBulk();
102
        Map<Integer, BulkCreditSummary> bulkSummaryMap = sdCreditService.getCreditSummaryBulk();
89
 
103
 
90
        List<Integer> sortedFofoIds = customRetailerMap.keySet().stream().sorted().collect(Collectors.toList());
104
        List<Integer> sortedFofoIds = customRetailerMap.keySet().stream()
-
 
105
                .filter(id -> restrictTo == null || restrictTo.contains(id))
-
 
106
                .sorted().collect(Collectors.toList());
-
 
107
 
-
 
108
        // One grouped query instead of one per partner. getCurrentRisk only consults this when the
-
 
109
        // partner has no risk_timestamp (418 of 980 have one, so those reads were discarded), and
-
 
110
        // the per-partner form was ~4.7ms x 980 -- most of this job's runtime.
-
 
111
        Map<Integer, LocalDateTime> firstBillingDateMap = transactionRepository.getFirstBillingDates(sortedFofoIds);
91
 
112
 
92
        for (int fofoId : sortedFofoIds) {
113
        for (int fofoId : sortedFofoIds) {
93
            // Per-partner isolation: one partner's bad data (null limit/util in the
114
            // Per-partner isolation: one partner's bad data (null limit/util in the
94
            // compareTo, a getFirstBillingDate/getCurrentRisk failure, etc.) must not
115
            // compareTo, a getFirstBillingDate/getCurrentRisk failure, etc.) must not
95
            // abort the whole read phase and silently update zero partners. Mirrors the
116
            // abort the whole read phase and silently update zero partners. Mirrors the
Line 111... Line 132...
111
                            suggestedAmount.subtract(utilizationAmount),
132
                            suggestedAmount.subtract(utilizationAmount),
112
                            CreditRisk.HIGH_RISK, true));
133
                            CreditRisk.HIGH_RISK, true));
113
                    continue;
134
                    continue;
114
                }
135
                }
115
 
136
 
116
                LocalDateTime firstBillingDate = transactionRepository.getFirstBillingDate(fofoId);
137
                LocalDateTime firstBillingDate = firstBillingDateMap.get(fofoId);
117
                CreditRisk newRisk = sdCreditService.getCurrentRisk(existing, firstBillingDate);
138
                CreditRisk newRisk = sdCreditService.getCurrentRisk(existing, firstBillingDate);
118
 
139
 
119
                BigDecimal currentLimit = existing.isHardLimit() ? existing.getLimit() : existing.getSuggestedLimit();
140
                BigDecimal currentLimit = existing.isHardLimit() ? existing.getLimit() : existing.getSuggestedLimit();
120
                BigDecimal newLimit = existing.isHardLimit() ? existing.getLimit() : suggestedAmount;
141
                BigDecimal newLimit = existing.isHardLimit() ? existing.getLimit() : suggestedAmount;
121
                BigDecimal newAvailable = newLimit.subtract(utilizationAmount);
142
                BigDecimal newAvailable = newLimit.subtract(utilizationAmount);
122
 
143
 
123
                // Compare: only include if something actually changed
144
                // Compare: only include if something actually changed. Both sides are rounded to
-
 
145
                // paise first — without that, double noise below half a paisa made ~90% of these
-
 
146
                // "changes" phantom, rewriting sd_credit_requirement and dtr.credit_account (and
-
 
147
                // churning the SIDBI mirror) ~14,000 times a day for identical values, and leaving
-
 
148
                // update_timestamp useless as a signal of when a limit really moved.
124
                boolean limitChanged = suggestedAmount.compareTo(existing.getSuggestedLimit()) != 0;
149
                boolean limitChanged = scaleMoney(suggestedAmount).compareTo(scaleMoney(existing.getSuggestedLimit())) != 0;
125
                boolean utilizationChanged = utilizationAmount.compareTo(existing.getUtilizedAmount()) != 0;
150
                boolean utilizationChanged = scaleMoney(utilizationAmount).compareTo(scaleMoney(existing.getUtilizedAmount())) != 0;
126
                boolean riskChanged = !newRisk.equals(existing.getRisk());
151
                boolean riskChanged = !newRisk.equals(existing.getRisk());
127
 
152
 
128
                if (limitChanged || utilizationChanged || riskChanged) {
153
                if (limitChanged || utilizationChanged || riskChanged) {
129
                    LOGGER.info("fofoId={} changed: limit {}→{}, util {}→{}, risk {}→{}, agedAppleStock={}",
154
                    LOGGER.info("fofoId={} changed: limit {}→{}, util {}→{}, risk {}→{}, agedAppleStock={}",
130
                            fofoId,
155
                            fofoId,
Line 212... Line 237...
212
            }
237
            }
213
        } else if (creditAccount.getGateway().equals(Gateway.SIDBI) && sidbiLimit != null) {
238
        } else if (creditAccount.getGateway().equals(Gateway.SIDBI) && sidbiLimit != null) {
214
            suggestedAmount = getSuggestedLimit(getCreditableInvestment(partnerDailyInvestment) - utilization);
239
            suggestedAmount = getSuggestedLimit(getCreditableInvestment(partnerDailyInvestment) - utilization);
215
            suggestedAmount = suggestedAmount.max(sidbiLimit);
240
            suggestedAmount = suggestedAmount.max(sidbiLimit);
216
        }
241
        }
217
        return suggestedAmount;
242
        return scaleMoney(suggestedAmount);
-
 
243
    }
-
 
244
 
-
 
245
    /**
-
 
246
     * Rounds to paise before the value is compared or stored.
-
 
247
     *
-
 
248
     * <p>The limit comes out of a double multiply, so it carries binary noise the stored
-
 
249
     * DECIMAL(12,4) cannot — 65123.7400 round-trips and comes back as 65123.740000000005, which
-
 
250
     * {@code BigDecimal.compareTo} reports as a change. Measured on a production run, 208 of 230
-
 
251
     * "changed" partners differed by less than half a paisa; real changes were never smaller than a
-
 
252
     * rupee, so rounding here sits in a clean gap and cannot suppress one.
-
 
253
     */
-
 
254
    private static BigDecimal scaleMoney(BigDecimal value) {
-
 
255
        return (value == null ? BigDecimal.ZERO : value).setScale(2, RoundingMode.HALF_UP);
218
    }
256
    }
219
 
257
 
220
    /**
258
    /**
221
     * Investment that counts toward a suggested limit: total investment less the value of Apple
259
     * 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
260
     * handsets the partner has held beyond {@link ProfitMandiConstants#AGED_STOCK_APPLE_DAYS} days