Subversion Repositories SmartDukaan

Rev

Blame | Last modification | View Log | RSS feed

package com.spice.profitmandi.service;

import com.spice.profitmandi.common.enumuration.ActivationType;
import com.spice.profitmandi.dao.entity.fofo.FofoStore;
import com.spice.profitmandi.dao.entity.fofo.PartnerDailyInvestment;
import com.spice.profitmandi.dao.enumuration.dtr.AgendaType;
import com.spice.profitmandi.dao.repository.dtr.FofoStoreRepository;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.hibernate.SessionFactory;
import org.hibernate.query.NativeQuery;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class AgendaAutoRuleServiceImpl implements AgendaAutoRuleService {

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

    @Autowired
    FofoStoreRepository fofoStoreRepository;

    @Autowired
    PartnerInvestmentService partnerInvestmentService;

    @Autowired
    AgendaConfigService agendaConfigService;

    @Autowired
    AgendaAutoRuleApplier agendaAutoRuleApplier;

    @Autowired
    SessionFactory sessionFactory;

    @Override
    @Transactional
    public AgendaSyncResult syncAllPartners() {
        AgendaSyncResult result = new AgendaSyncResult();
        try {
            // Global-default params. Sales window params (baselineDays/windowDays)
            // are honored globally only — the batched SQL below cannot vary the
            // time window per partner. flagPct/clearPct etc. resolve per partner.
            Map<String, Double> salesDefaults = agendaConfigService.getParams(AgendaType.LOW_SALES.name(), 0);
            int windowDays = (int) doubleOr(salesDefaults.get("windowDays"), 30);
            int baselineDays = (int) doubleOr(salesDefaults.get("baselineDays"), 90);
            int lookbackDays = windowDays + baselineDays;

            List<FofoStore> candidates = fofoStoreRepository.selectAllFranchiseStores();
            candidates.removeIf(s -> !s.isActive() || s.isInternal() || s.isClosed());

            Map<Integer, double[]> salesByFofo = gatherSales(windowDays, lookbackDays);
            Map<Integer, Integer> billGapByFofo = gatherPurchaseGaps();
            Map<Integer, double[]> loanByFofo = gatherLoanAges();

            LocalDateTime activationCutoff = LocalDateTime.now().minusDays(lookbackDays);

            for (FofoStore store : candidates) {
                int fofoId = store.getId();
                result.setScanned(result.getScanned() + 1);
                try {
                    AgendaRuleDecider.Inputs in = new AgendaRuleDecider.Inputs();
                    in.fofoId = fofoId;
                    in.revival = store.getActivationType() == ActivationType.REVIVAL;
                    in.storeActivatedRecently = store.getActiveTimeStamp() != null
                            && store.getActiveTimeStamp().isAfter(activationCutoff);
                    in.minInvestment = store.getMinimumInvestment();

                    if (in.minInvestment > 0) {
                        try {
                            PartnerDailyInvestment inv = partnerInvestmentService.getInvestment(fofoId, 0);
                            in.shortPercentage = (double) inv.getShortPercentage();
                        } catch (Exception e) {
                            in.shortPercentage = null; // decider LEAVEs LOW_INVESTMENT
                            LOGGER.warn("Investment eval failed for fofoId={}: {}", fofoId, e.getMessage());
                        }
                    }

                    double[] sales = salesByFofo.get(fofoId);
                    in.baselineAmt = sales != null ? sales[0] : 0;
                    in.currentAmt = sales != null ? sales[1] : 0;
                    in.lastBillDaysAgo = billGapByFofo.get(fofoId);
                    double[] loan = loanByFofo.get(fofoId);
                    in.oldestLoanAgeDays = loan != null ? (int) loan[0] : null;
                    in.pendingLoanAmt = loan != null ? loan[1] : 0;

                    // Per-partner params, EXCEPT the sales time windows: those were
                    // baked into the batched SQL above, so the global values must
                    // win here even if a partner row overrides them.
                    Map<String, Double> salesParams = new HashMap<>(
                            agendaConfigService.getParams(AgendaType.LOW_SALES.name(), fofoId));
                    salesParams.put("windowDays", (double) windowDays);
                    salesParams.put("baselineDays", (double) baselineDays);

                    Map<AgendaType, AgendaRuleDecider.Decision> decisions = AgendaRuleDecider.decide(in,
                            agendaConfigService.getParams(AgendaType.LOW_INVESTMENT.name(), fofoId),
                            salesParams,
                            agendaConfigService.getParams(AgendaType.LOW_PURCHASE.name(), fofoId),
                            agendaConfigService.getParams(AgendaType.CREDIT_DUES.name(), fofoId));

                    int[] counts = agendaAutoRuleApplier.applyForStore(fofoId, decisions);
                    result.addOpened(counts[0]);
                    result.addClosed(counts[1]);
                } catch (Exception e) {
                    result.addError("fofoId " + fofoId + ": " + e.getMessage());
                    LOGGER.error("Agenda sync failed for fofoId={}", fofoId, e);
                }
            }
        } catch (Exception e) {
            result.addError("sync aborted: " + e.getMessage());
            LOGGER.error("Agenda sync aborted", e);
        }
        LOGGER.info("PJP agenda sync done: {}", result);
        return result;
    }

    // fofo_id -> [baselineAmt, currentAmt]
    private Map<Integer, double[]> gatherSales(int windowDays, int lookbackDays) {
        String sql = "SELECT fofo_id, " +
                " SUM(CASE WHEN create_timestamp <  DATE_SUB(NOW(), INTERVAL :windowDays DAY) THEN total_amount ELSE 0 END), " +
                " SUM(CASE WHEN create_timestamp >= DATE_SUB(NOW(), INTERVAL :windowDays DAY) THEN total_amount ELSE 0 END) " +
                "FROM fofo.fofo_order " +
                "WHERE cancelled_timestamp IS NULL " +
                "  AND create_timestamp >= DATE_SUB(NOW(), INTERVAL :lookbackDays DAY) " +
                "GROUP BY fofo_id";
        NativeQuery<?> q = sessionFactory.getCurrentSession().createNativeQuery(sql);
        q.setParameter("windowDays", windowDays);
        q.setParameter("lookbackDays", lookbackDays);
        Map<Integer, double[]> out = new HashMap<>();
        for (Object rowObj : q.getResultList()) {
            Object[] r = (Object[]) rowObj;
            out.put(asInt(r[0]), new double[]{asDouble(r[1]), asDouble(r[2])});
        }
        return out;
    }

    // fofo_id -> days since last billed purchase (absent = never billed)
    private Map<Integer, Integer> gatherPurchaseGaps() {
        String sql = "SELECT t.customer_id, DATEDIFF(NOW(), MAX(o.billing_timestamp)) " +
                "FROM transaction.`order` o " +
                "JOIN transaction.transaction t ON t.id = o.transaction_id " +
                "WHERE o.billing_timestamp IS NOT NULL " +
                "GROUP BY t.customer_id";
        NativeQuery<?> q = sessionFactory.getCurrentSession().createNativeQuery(sql);
        Map<Integer, Integer> out = new HashMap<>();
        for (Object rowObj : q.getResultList()) {
            Object[] r = (Object[]) rowObj;
            out.put(asInt(r[0]), asInt(r[1]));
        }
        return out;
    }

    // fofo_id -> [oldest open-loan age in days from created_on, total pending]
    private Map<Integer, double[]> gatherLoanAges() {
        String sql = "SELECT l.fofo_id, MAX(DATEDIFF(NOW(), l.created_on)), SUM(l.pending_amount) " +
                "FROM transaction.loan l " +
                "WHERE l.settled_on IS NULL AND l.pending_amount > 1 " +
                "GROUP BY l.fofo_id";
        NativeQuery<?> q = sessionFactory.getCurrentSession().createNativeQuery(sql);
        Map<Integer, double[]> out = new HashMap<>();
        for (Object rowObj : q.getResultList()) {
            Object[] r = (Object[]) rowObj;
            out.put(asInt(r[0]), new double[]{asDouble(r[1]), asDouble(r[2])});
        }
        return out;
    }

    private static int asInt(Object o) {
        return o == null ? 0 : ((Number) o).intValue();
    }

    private static double asDouble(Object o) {
        return o == null ? 0 : ((Number) o).doubleValue();
    }

    private static double doubleOr(Double v, double fallback) {
        return v != null ? v : fallback;
    }
}