Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
37167 amit 1
package com.spice.profitmandi.service;
2
 
3
import com.spice.profitmandi.common.enumuration.ActivationType;
4
import com.spice.profitmandi.dao.entity.fofo.FofoStore;
5
import com.spice.profitmandi.dao.entity.fofo.PartnerDailyInvestment;
6
import com.spice.profitmandi.dao.enumuration.dtr.AgendaType;
7
import com.spice.profitmandi.dao.repository.dtr.FofoStoreRepository;
8
import org.apache.logging.log4j.LogManager;
9
import org.apache.logging.log4j.Logger;
10
import org.hibernate.SessionFactory;
11
import org.hibernate.query.NativeQuery;
12
import org.springframework.beans.factory.annotation.Autowired;
13
import org.springframework.stereotype.Service;
14
import org.springframework.transaction.annotation.Transactional;
15
 
16
import java.time.LocalDateTime;
17
import java.util.HashMap;
18
import java.util.List;
19
import java.util.Map;
20
 
21
@Service
22
public class AgendaAutoRuleServiceImpl implements AgendaAutoRuleService {
23
 
24
    private static final Logger LOGGER = LogManager.getLogger(AgendaAutoRuleServiceImpl.class);
25
 
26
    @Autowired
27
    FofoStoreRepository fofoStoreRepository;
28
 
29
    @Autowired
30
    PartnerInvestmentService partnerInvestmentService;
31
 
32
    @Autowired
33
    AgendaConfigService agendaConfigService;
34
 
35
    @Autowired
36
    AgendaAutoRuleApplier agendaAutoRuleApplier;
37
 
38
    @Autowired
39
    SessionFactory sessionFactory;
40
 
41
    @Override
42
    @Transactional
43
    public AgendaSyncResult syncAllPartners() {
44
        AgendaSyncResult result = new AgendaSyncResult();
45
        try {
46
            // Global-default params. Sales window params (baselineDays/windowDays)
47
            // are honored globally only — the batched SQL below cannot vary the
48
            // time window per partner. flagPct/clearPct etc. resolve per partner.
49
            Map<String, Double> salesDefaults = agendaConfigService.getParams(AgendaType.LOW_SALES.name(), 0);
50
            int windowDays = (int) doubleOr(salesDefaults.get("windowDays"), 30);
51
            int baselineDays = (int) doubleOr(salesDefaults.get("baselineDays"), 90);
52
            int lookbackDays = windowDays + baselineDays;
53
 
54
            List<FofoStore> candidates = fofoStoreRepository.selectAllFranchiseStores();
55
            candidates.removeIf(s -> !s.isActive() || s.isInternal() || s.isClosed());
56
 
57
            Map<Integer, double[]> salesByFofo = gatherSales(windowDays, lookbackDays);
58
            Map<Integer, Integer> billGapByFofo = gatherPurchaseGaps();
59
            Map<Integer, double[]> loanByFofo = gatherLoanAges();
60
 
61
            LocalDateTime activationCutoff = LocalDateTime.now().minusDays(lookbackDays);
62
 
63
            for (FofoStore store : candidates) {
64
                int fofoId = store.getId();
65
                result.setScanned(result.getScanned() + 1);
66
                try {
67
                    AgendaRuleDecider.Inputs in = new AgendaRuleDecider.Inputs();
68
                    in.fofoId = fofoId;
69
                    in.revival = store.getActivationType() == ActivationType.REVIVAL;
70
                    in.storeActivatedRecently = store.getActiveTimeStamp() != null
71
                            && store.getActiveTimeStamp().isAfter(activationCutoff);
72
                    in.minInvestment = store.getMinimumInvestment();
73
 
74
                    if (in.minInvestment > 0) {
75
                        try {
76
                            PartnerDailyInvestment inv = partnerInvestmentService.getInvestment(fofoId, 0);
77
                            in.shortPercentage = (double) inv.getShortPercentage();
78
                        } catch (Exception e) {
79
                            in.shortPercentage = null; // decider LEAVEs LOW_INVESTMENT
80
                            LOGGER.warn("Investment eval failed for fofoId={}: {}", fofoId, e.getMessage());
81
                        }
82
                    }
83
 
84
                    double[] sales = salesByFofo.get(fofoId);
85
                    in.baselineAmt = sales != null ? sales[0] : 0;
86
                    in.currentAmt = sales != null ? sales[1] : 0;
87
                    in.lastBillDaysAgo = billGapByFofo.get(fofoId);
88
                    double[] loan = loanByFofo.get(fofoId);
89
                    in.oldestLoanAgeDays = loan != null ? (int) loan[0] : null;
90
                    in.pendingLoanAmt = loan != null ? loan[1] : 0;
91
 
92
                    // Per-partner params, EXCEPT the sales time windows: those were
93
                    // baked into the batched SQL above, so the global values must
94
                    // win here even if a partner row overrides them.
95
                    Map<String, Double> salesParams = new HashMap<>(
96
                            agendaConfigService.getParams(AgendaType.LOW_SALES.name(), fofoId));
97
                    salesParams.put("windowDays", (double) windowDays);
98
                    salesParams.put("baselineDays", (double) baselineDays);
99
 
100
                    Map<AgendaType, AgendaRuleDecider.Decision> decisions = AgendaRuleDecider.decide(in,
101
                            agendaConfigService.getParams(AgendaType.LOW_INVESTMENT.name(), fofoId),
102
                            salesParams,
103
                            agendaConfigService.getParams(AgendaType.LOW_PURCHASE.name(), fofoId),
104
                            agendaConfigService.getParams(AgendaType.CREDIT_DUES.name(), fofoId));
105
 
106
                    int[] counts = agendaAutoRuleApplier.applyForStore(fofoId, decisions);
107
                    result.addOpened(counts[0]);
108
                    result.addClosed(counts[1]);
109
                } catch (Exception e) {
110
                    result.addError("fofoId " + fofoId + ": " + e.getMessage());
111
                    LOGGER.error("Agenda sync failed for fofoId={}", fofoId, e);
112
                }
113
            }
114
        } catch (Exception e) {
115
            result.addError("sync aborted: " + e.getMessage());
116
            LOGGER.error("Agenda sync aborted", e);
117
        }
118
        LOGGER.info("PJP agenda sync done: {}", result);
119
        return result;
120
    }
121
 
122
    // fofo_id -> [baselineAmt, currentAmt]
123
    private Map<Integer, double[]> gatherSales(int windowDays, int lookbackDays) {
124
        String sql = "SELECT fofo_id, " +
125
                " SUM(CASE WHEN create_timestamp <  DATE_SUB(NOW(), INTERVAL :windowDays DAY) THEN total_amount ELSE 0 END), " +
126
                " SUM(CASE WHEN create_timestamp >= DATE_SUB(NOW(), INTERVAL :windowDays DAY) THEN total_amount ELSE 0 END) " +
127
                "FROM fofo.fofo_order " +
128
                "WHERE cancelled_timestamp IS NULL " +
129
                "  AND create_timestamp >= DATE_SUB(NOW(), INTERVAL :lookbackDays DAY) " +
130
                "GROUP BY fofo_id";
131
        NativeQuery<?> q = sessionFactory.getCurrentSession().createNativeQuery(sql);
132
        q.setParameter("windowDays", windowDays);
133
        q.setParameter("lookbackDays", lookbackDays);
134
        Map<Integer, double[]> out = new HashMap<>();
135
        for (Object rowObj : q.getResultList()) {
136
            Object[] r = (Object[]) rowObj;
137
            out.put(asInt(r[0]), new double[]{asDouble(r[1]), asDouble(r[2])});
138
        }
139
        return out;
140
    }
141
 
142
    // fofo_id -> days since last billed purchase (absent = never billed)
143
    private Map<Integer, Integer> gatherPurchaseGaps() {
144
        String sql = "SELECT t.customer_id, DATEDIFF(NOW(), MAX(o.billing_timestamp)) " +
145
                "FROM transaction.`order` o " +
146
                "JOIN transaction.transaction t ON t.id = o.transaction_id " +
147
                "WHERE o.billing_timestamp IS NOT NULL " +
148
                "GROUP BY t.customer_id";
149
        NativeQuery<?> q = sessionFactory.getCurrentSession().createNativeQuery(sql);
150
        Map<Integer, Integer> out = new HashMap<>();
151
        for (Object rowObj : q.getResultList()) {
152
            Object[] r = (Object[]) rowObj;
153
            out.put(asInt(r[0]), asInt(r[1]));
154
        }
155
        return out;
156
    }
157
 
158
    // fofo_id -> [oldest open-loan age in days from created_on, total pending]
159
    private Map<Integer, double[]> gatherLoanAges() {
160
        String sql = "SELECT l.fofo_id, MAX(DATEDIFF(NOW(), l.created_on)), SUM(l.pending_amount) " +
161
                "FROM transaction.loan l " +
162
                "WHERE l.settled_on IS NULL AND l.pending_amount > 1 " +
163
                "GROUP BY l.fofo_id";
164
        NativeQuery<?> q = sessionFactory.getCurrentSession().createNativeQuery(sql);
165
        Map<Integer, double[]> out = new HashMap<>();
166
        for (Object rowObj : q.getResultList()) {
167
            Object[] r = (Object[]) rowObj;
168
            out.put(asInt(r[0]), new double[]{asDouble(r[1]), asDouble(r[2])});
169
        }
170
        return out;
171
    }
172
 
173
    private static int asInt(Object o) {
174
        return o == null ? 0 : ((Number) o).intValue();
175
    }
176
 
177
    private static double asDouble(Object o) {
178
        return o == null ? 0 : ((Number) o).doubleValue();
179
    }
180
 
181
    private static double doubleOr(Double v, double fallback) {
182
        return v != null ? v : fallback;
183
    }
184
}