| 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 |
}
|