Subversion Repositories SmartDukaan

Rev

Rev 36338 | Blame | Compare with Previous | Last modification | View Log | RSS feed

package com.smartdukaan.cron.scheduled;

import com.spice.profitmandi.common.exception.ProfitMandiBusinessException;
import com.spice.profitmandi.common.model.CustomRetailer;
import com.spice.profitmandi.common.model.ProfitMandiConstants;
import com.spice.profitmandi.dao.entity.dtr.CreditAccount;
import com.spice.profitmandi.dao.entity.fofo.PartnerDailyInvestment;
import com.spice.profitmandi.dao.entity.transaction.SDCreditRequirement;
import com.spice.profitmandi.dao.enumuration.fofo.Gateway;
import com.spice.profitmandi.dao.enumuration.transaction.CreditRisk;
import com.spice.profitmandi.dao.model.BulkCreditSummary;
import com.spice.profitmandi.dao.repository.dtr.CreditAccountRepository;
import com.spice.profitmandi.dao.repository.transaction.SDCreditRequirementRepository;
import com.spice.profitmandi.dao.repository.transaction.TransactionRepository;
import com.spice.profitmandi.service.PartnerInvestmentService;
import com.spice.profitmandi.service.cron.CronBatchService;
import com.spice.profitmandi.service.transaction.PartnerLimitUpdateData;
import com.spice.profitmandi.service.transaction.SDCreditService;
import com.spice.profitmandi.service.user.RetailerService;
import com.spice.profitmandi.dao.service.SidbiService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class PartnerLimitHelper {

    private static final Logger LOGGER = LogManager.getLogger(PartnerLimitHelper.class);

    @Autowired
    private RetailerService retailerService;

    @Autowired
    private SidbiService sidbiService;

    @Autowired
    private SDCreditRequirementRepository sdCreditRequirementRepository;

    @Autowired
    private CreditAccountRepository creditAccountRepository;

    @Autowired
    private SDCreditService sdCreditService;

    @Autowired
    private PartnerInvestmentService partnerInvestmentService;

    @Autowired
    private TransactionRepository transactionRepository;

    @Autowired
    private CronBatchService cronBatchService;

    private static final NavigableMap<Double, Double> discountMap = new TreeMap<>();
    private static final List<Integer> hundredPercentLimitPartnerIds = Arrays.asList();

    static {
        discountMap.put(4 * ProfitMandiConstants.ONE_LAC - 1, 0.2);
        discountMap.put(10 * ProfitMandiConstants.ONE_LAC - 1, 0.25);
        discountMap.put(20 * ProfitMandiConstants.ONE_LAC - 1, 0.3);
        discountMap.put(Double.MAX_VALUE, 0.4);
    }

    /**
     * Read-only: calculates limits for all partners, returns only those that changed.
     */
    @Transactional(readOnly = true)
    public List<PartnerLimitUpdateData> calculateChangedPartnerLimits() throws ProfitMandiBusinessException {
        List<PartnerLimitUpdateData> changedPartners = new ArrayList<>();

        Map<Integer, CustomRetailer> customRetailerMap = retailerService.getFofoRetailers(true);
        Map<Integer, BigDecimal> fofoSidbiLimitMap = sidbiService.getSuggestedLimitMap();
        Map<Integer, SDCreditRequirement> sdCreditRequirementMap = sdCreditRequirementRepository.selectAll()
                .stream().collect(Collectors.toMap(x -> x.getFofoId(), x -> x));
        Map<Integer, CreditAccount> creditAccountMap = creditAccountRepository
                .selectAllByGateways(Arrays.asList(Gateway.SIDBI, Gateway.SDDIRECT))
                .stream().filter(x -> x.isActive()).collect(Collectors.toMap(x -> x.getFofoId(), x -> x));
        Map<Integer, BulkCreditSummary> bulkSummaryMap = sdCreditService.getCreditSummaryBulk();

        List<Integer> sortedFofoIds = customRetailerMap.keySet().stream().sorted().collect(Collectors.toList());

        for (int fofoId : sortedFofoIds) {
            // Per-partner isolation: one partner's bad data (null limit/util in the
            // compareTo, a getFirstBillingDate/getCurrentRisk failure, etc.) must not
            // abort the whole read phase and silently update zero partners. Mirrors the
            // REQUIRES_NEW isolation the write phase (updateSinglePartnerLimit) already has.
            try {
                CreditAccount creditAccount = creditAccountMap.get(fofoId);
                BulkCreditSummary bulkSummary = bulkSummaryMap.get(fofoId);
                BigDecimal utilizationAmount = bulkSummary != null ? bulkSummary.getUtilization() : BigDecimal.ZERO;

                PartnerDailyInvestment partnerDailyInvestment = partnerInvestmentService.getInvestment(fofoId, 0);

                BigDecimal suggestedAmount = getSuggestedAmount(creditAccount, partnerDailyInvestment, utilizationAmount, fofoSidbiLimitMap.get(fofoId));
                SDCreditRequirement existing = sdCreditRequirementMap.get(fofoId);

                if (existing == null) {
                    // New partner — needs a record
                    changedPartners.add(new PartnerLimitUpdateData(
                            fofoId, suggestedAmount, utilizationAmount,
                            suggestedAmount.subtract(utilizationAmount),
                            CreditRisk.HIGH_RISK, true));
                    continue;
                }

                LocalDateTime firstBillingDate = transactionRepository.getFirstBillingDate(fofoId);
                CreditRisk newRisk = sdCreditService.getCurrentRisk(existing, firstBillingDate);

                BigDecimal currentLimit = existing.isHardLimit() ? existing.getLimit() : existing.getSuggestedLimit();
                BigDecimal newLimit = existing.isHardLimit() ? existing.getLimit() : suggestedAmount;
                BigDecimal newAvailable = newLimit.subtract(utilizationAmount);

                // Compare: only include if something actually changed
                boolean limitChanged = suggestedAmount.compareTo(existing.getSuggestedLimit()) != 0;
                boolean utilizationChanged = utilizationAmount.compareTo(existing.getUtilizedAmount()) != 0;
                boolean riskChanged = !newRisk.equals(existing.getRisk());

                if (limitChanged || utilizationChanged || riskChanged) {
                    LOGGER.info("fofoId={} changed: limit {}→{}, util {}→{}, risk {}→{}",
                            fofoId,
                            existing.getSuggestedLimit(), suggestedAmount,
                            existing.getUtilizedAmount(), utilizationAmount,
                            existing.getRisk(), newRisk);
                    changedPartners.add(new PartnerLimitUpdateData(
                            fofoId, suggestedAmount, utilizationAmount, newAvailable, newRisk, false));
                }
            } catch (Exception e) {
                LOGGER.error("Partner limit calc failed for fofoId={}, skipping: {}", fofoId, e.getMessage());
            }
        }

        LOGGER.info("Partner limit check: {} total, {} changed", sortedFofoIds.size(), changedPartners.size());
        return changedPartners;
    }

    /**
     * Writes updated limit for a single partner in its own transaction.
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW,
            rollbackFor = {Throwable.class, ProfitMandiBusinessException.class})
    public void updateSinglePartnerLimit(int batchId, PartnerLimitUpdateData data) throws ProfitMandiBusinessException {
        int fofoId = data.getFofoId();

        SDCreditRequirement sdCreditRequirement;
        if (data.isNewRecord()) {
            sdCreditRequirement = new SDCreditRequirement();
            sdCreditRequirement.setFofoId(fofoId);
            sdCreditRequirement.setCreditDays(15);
            sdCreditRequirement.setInterestRate(ProfitMandiConstants.NEW_INTEREST_RATE);
            sdCreditRequirement.setRisk(data.getCreditRisk());
            sdCreditRequirement.setUtilizedAmount(BigDecimal.ZERO);
            sdCreditRequirement.setCreateTimestamp(LocalDateTime.now());
            sdCreditRequirement.setUpdateTimestamp(LocalDateTime.now());
            sdCreditRequirement.setLimit(data.getSuggestedLimit());
            sdCreditRequirement.setSuggestedLimit(data.getSuggestedLimit());
            sdCreditRequirementRepository.persist(sdCreditRequirement);
        } else {
            sdCreditRequirement = sdCreditRequirementRepository.selectByFofoId(fofoId);
            sdCreditRequirement.setRisk(data.getCreditRisk());
            sdCreditRequirement.setSuggestedLimit(data.getSuggestedLimit());
            if (!sdCreditRequirement.isHardLimit()) {
                sdCreditRequirement.setLimit(data.getSuggestedLimit());
            }
            sdCreditRequirement.setUtilizedAmount(data.getUtilizationAmount());
            sdCreditRequirement.setUpdateTimestamp(LocalDateTime.now());
        }

        CreditAccount creditAccount = creditAccountRepository.selectByFofoIdAndGateway(fofoId, Gateway.SDDIRECT);
        if (creditAccount == null) {
            creditAccount = creditAccountRepository.selectByFofoIdAndGateway(fofoId, Gateway.SIDBI);
        }
        if (creditAccount != null) {
            creditAccount.setInterestRate(sdCreditRequirement.getInterestRate().floatValue());
            creditAccount.setSanctionedAmount(sdCreditRequirement.getLimit().floatValue());
            creditAccount.setAvailableAmount(data.getAvailableLimit().floatValue());
            creditAccount.setFreeDays(sdCreditRequirement.getFreeDays());
            creditAccount.setUpdatedOn(LocalDateTime.now());
        }

        cronBatchService.markItemSuccess(batchId, fofoId);
        LOGGER.info("fofoId={} updated: limit={}, util={}, risk={}", fofoId,
                sdCreditRequirement.getLimit(), data.getUtilizationAmount(), data.getCreditRisk());
    }

    private BigDecimal getSuggestedAmount(CreditAccount creditAccount, PartnerDailyInvestment partnerDailyInvestment,
                                          BigDecimal utilizationAmount, BigDecimal sidbiLimit) {
        BigDecimal suggestedAmount = BigDecimal.ZERO;
        double utilization = utilizationAmount != null ? utilizationAmount.doubleValue() : 0;
        if (creditAccount == null || creditAccount.getGateway().equals(Gateway.SDDIRECT)) {
            if (partnerDailyInvestment != null) {
                if (hundredPercentLimitPartnerIds.contains(partnerDailyInvestment.getFofoId())) {
                    suggestedAmount = BigDecimal.valueOf((partnerDailyInvestment.getTotalInvestment() - utilization) * 1);
                    suggestedAmount = suggestedAmount.min(BigDecimal.valueOf(1500000));
                } else {
                    suggestedAmount = getSuggestedLimit(partnerDailyInvestment.getTotalInvestment() - utilization);
                }
            }
            if (suggestedAmount.doubleValue() < 0) {
                suggestedAmount = BigDecimal.ZERO;
            }
        } else if (creditAccount.getGateway().equals(Gateway.SIDBI) && sidbiLimit != null) {
            suggestedAmount = getSuggestedLimit(partnerDailyInvestment.getTotalInvestment() - utilization);
            suggestedAmount = suggestedAmount.max(sidbiLimit);
        }
        return suggestedAmount;
    }

    private BigDecimal getSuggestedLimit(double investmentValue) {
        double percentageValue = discountMap.ceilingEntry(investmentValue).getValue();
        return BigDecimal.valueOf(investmentValue * percentageValue);
    }
}