| Line 1... |
Line 1... |
| 1 |
package com.spice.profitmandi.web.service;
|
1 |
package com.spice.profitmandi.web.service;
|
| 2 |
|
2 |
|
| - |
|
3 |
import com.spice.profitmandi.dao.entity.cs.RbmBreakLog;
|
| - |
|
4 |
import com.spice.profitmandi.dao.repository.cs.RbmBreakLogRepository;
|
| - |
|
5 |
import org.apache.logging.log4j.LogManager;
|
| - |
|
6 |
import org.apache.logging.log4j.Logger;
|
| 3 |
import org.hibernate.Session;
|
7 |
import org.hibernate.Session;
|
| 4 |
import org.hibernate.SessionFactory;
|
8 |
import org.hibernate.SessionFactory;
|
| 5 |
import org.springframework.beans.factory.annotation.Autowired;
|
9 |
import org.springframework.beans.factory.annotation.Autowired;
|
| 6 |
import org.springframework.stereotype.Service;
|
10 |
import org.springframework.stereotype.Service;
|
| - |
|
11 |
import org.springframework.transaction.PlatformTransactionManager;
|
| - |
|
12 |
import org.springframework.transaction.TransactionStatus;
|
| - |
|
13 |
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
|
| - |
|
14 |
import org.springframework.transaction.support.TransactionTemplate;
|
| 7 |
|
15 |
|
| 8 |
import javax.annotation.PostConstruct;
|
16 |
import javax.annotation.PostConstruct;
|
| - |
|
17 |
import java.time.LocalDate;
|
| 9 |
import java.time.LocalDateTime;
|
18 |
import java.time.LocalDateTime;
|
| 10 |
import java.util.ArrayList;
|
19 |
import java.util.ArrayList;
|
| 11 |
import java.util.List;
|
20 |
import java.util.List;
|
| 12 |
import java.util.Map;
|
21 |
import java.util.Map;
|
| 13 |
import java.util.concurrent.ConcurrentHashMap;
|
22 |
import java.util.concurrent.ConcurrentHashMap;
|
| 14 |
|
23 |
|
| 15 |
@Service
|
24 |
@Service
|
| 16 |
public class AgentLiveStatusService {
|
25 |
public class AgentLiveStatusService {
|
| 17 |
|
26 |
|
| - |
|
27 |
private static final Logger LOGGER = LogManager.getLogger(AgentLiveStatusService.class);
|
| - |
|
28 |
|
| 18 |
// In-memory map to store agent status by authId
|
29 |
// In-memory map to store agent status by authId
|
| 19 |
private final Map<Integer, AgentStatus> agentStatusMap = new ConcurrentHashMap<>();
|
30 |
private final Map<Integer, AgentStatus> agentStatusMap = new ConcurrentHashMap<>();
|
| 20 |
// Caller ID (Knowlarity SR Number) to Auth ID mapping (loaded from sip_master)
|
31 |
// Caller ID (Knowlarity SR Number) to Auth ID mapping (loaded from sip_master)
|
| 21 |
private final Map<String, Integer> callerIdToAuthIdMap = new ConcurrentHashMap<>();
|
32 |
private final Map<String, Integer> callerIdToAuthIdMap = new ConcurrentHashMap<>();
|
| 22 |
// SR Number to Auth ID mapping (loaded from sip_master)
|
33 |
// SR Number to Auth ID mapping (loaded from sip_master)
|
| 23 |
private final Map<String, Integer> srNumberToAuthIdMap = new ConcurrentHashMap<>();
|
34 |
private final Map<String, Integer> srNumberToAuthIdMap = new ConcurrentHashMap<>();
|
| 24 |
// Agent name to Auth ID mapping (fallback)
|
35 |
// Agent name to Auth ID mapping (fallback)
|
| 25 |
private final Map<String, Integer> agentNameToAuthIdMap = new ConcurrentHashMap<>();
|
36 |
private final Map<String, Integer> agentNameToAuthIdMap = new ConcurrentHashMap<>();
|
| - |
|
37 |
|
| 26 |
@Autowired
|
38 |
@Autowired
|
| 27 |
private SessionFactory sessionFactory;
|
39 |
private SessionFactory sessionFactory;
|
| - |
|
40 |
|
| - |
|
41 |
@Autowired
|
| - |
|
42 |
private RbmBreakLogRepository rbmBreakLogRepository;
|
| - |
|
43 |
|
| - |
|
44 |
@Autowired
|
| - |
|
45 |
private PlatformTransactionManager transactionManager;
|
| - |
|
46 |
|
| - |
|
47 |
private TransactionTemplate transactionTemplate;
|
| - |
|
48 |
|
| 28 |
private String lastError = null;
|
49 |
private String lastError = null;
|
| 29 |
private int lastQueryCount = 0;
|
50 |
private int lastQueryCount = 0;
|
| 30 |
|
51 |
|
| 31 |
/**
|
52 |
/**
|
| 32 |
* Load mapping from sip_master table on startup
|
53 |
* Load mapping from sip_master table on startup
|
| 33 |
* caller_id in sip_master = SR Number on Knowlarity dashboard
|
54 |
* caller_id in sip_master = SR Number on Knowlarity dashboard
|
| 34 |
* sip_master has auth_id directly
|
55 |
* sip_master has auth_id directly
|
| 35 |
*/
|
56 |
*/
|
| 36 |
@PostConstruct
|
57 |
@PostConstruct
|
| 37 |
public void loadMappingFromDatabase() {
|
58 |
public void loadMappingFromDatabase() {
|
| - |
|
59 |
this.transactionTemplate = new TransactionTemplate(transactionManager);
|
| 38 |
try {
|
60 |
try {
|
| 39 |
Session session = sessionFactory.openSession();
|
61 |
Session session = sessionFactory.openSession();
|
| 40 |
|
62 |
|
| 41 |
// Query to get caller_id, auth_user.id, first_name from sip_master
|
63 |
// Query to get caller_id, auth_user.id, first_name from sip_master
|
| 42 |
// Join with auth_user to get correct auth_id used by rbm_call_target page
|
64 |
// Join with auth_user to get correct auth_id used by rbm_call_target page
|
| Line 143... |
Line 165... |
| 143 |
srNumberToAuthIdMap.clear();
|
165 |
srNumberToAuthIdMap.clear();
|
| 144 |
agentNameToAuthIdMap.clear();
|
166 |
agentNameToAuthIdMap.clear();
|
| 145 |
}
|
167 |
}
|
| 146 |
|
168 |
|
| 147 |
public void updateStatus(int authId, String srNumber, String agentName, String agentStatus, String timeInStatus, String customerNumber) {
|
169 |
public void updateStatus(int authId, String srNumber, String agentName, String agentStatus, String timeInStatus, String customerNumber) {
|
| - |
|
170 |
// Get previous status to detect change
|
| - |
|
171 |
AgentStatus previousStatus = agentStatusMap.get(authId);
|
| - |
|
172 |
|
| - |
|
173 |
// Check if status has changed
|
| - |
|
174 |
if (previousStatus != null && previousStatus.getAgentStatus() != null) {
|
| - |
|
175 |
String prevStatusStr = previousStatus.getAgentStatus();
|
| - |
|
176 |
String newStatusStr = agentStatus != null ? agentStatus : "";
|
| - |
|
177 |
|
| - |
|
178 |
// If status changed, save the previous status duration to RbmBreakLog
|
| - |
|
179 |
if (!prevStatusStr.equalsIgnoreCase(newStatusStr)) {
|
| - |
|
180 |
// Only track Available and Break statuses
|
| - |
|
181 |
if (isTrackableStatus(prevStatusStr)) {
|
| - |
|
182 |
String prevTimeInStatus = previousStatus.getTimeInStatus();
|
| - |
|
183 |
int durationSeconds = parseTimeInStatusToSeconds(prevTimeInStatus);
|
| - |
|
184 |
|
| - |
|
185 |
// Calculate the START TIME when agent entered this status
|
| - |
|
186 |
// startTime = now - duration
|
| - |
|
187 |
LocalDateTime statusStartTime = LocalDateTime.now().minusSeconds(durationSeconds);
|
| - |
|
188 |
|
| - |
|
189 |
LOGGER.info("Status changed for {} (authId={}): '{}' -> '{}', started at: {}, duration: {} ({}s)",
|
| - |
|
190 |
agentName, authId, prevStatusStr, newStatusStr, statusStartTime, prevTimeInStatus, durationSeconds);
|
| - |
|
191 |
|
| - |
|
192 |
// Save to RbmBreakLog with START TIME
|
| - |
|
193 |
saveStatusDuration(authId, agentName, srNumber, prevStatusStr, prevTimeInStatus, durationSeconds, statusStartTime);
|
| - |
|
194 |
}
|
| - |
|
195 |
}
|
| - |
|
196 |
}
|
| - |
|
197 |
|
| - |
|
198 |
// Update current status
|
| 148 |
AgentStatus status = new AgentStatus(authId, srNumber, agentName, agentStatus, timeInStatus, customerNumber);
|
199 |
AgentStatus status = new AgentStatus(authId, srNumber, agentName, agentStatus, timeInStatus, customerNumber);
|
| 149 |
agentStatusMap.put(authId, status);
|
200 |
agentStatusMap.put(authId, status);
|
| 150 |
}
|
201 |
}
|
| 151 |
|
202 |
|
| - |
|
203 |
/**
|
| - |
|
204 |
* Check if status should be tracked (Only Available and Break)
|
| - |
|
205 |
*/
|
| - |
|
206 |
private boolean isTrackableStatus(String status) {
|
| - |
|
207 |
if (status == null || status.isEmpty()) return false;
|
| - |
|
208 |
String lower = status.toLowerCase();
|
| - |
|
209 |
return lower.contains("available") || lower.contains("break");
|
| - |
|
210 |
}
|
| - |
|
211 |
|
| - |
|
212 |
/**
|
| - |
|
213 |
* Parse timeInStatus string (e.g., "00:05:30" or "5m 30s") to seconds
|
| - |
|
214 |
*/
|
| - |
|
215 |
private int parseTimeInStatusToSeconds(String timeInStatus) {
|
| - |
|
216 |
if (timeInStatus == null || timeInStatus.isEmpty()) return 0;
|
| - |
|
217 |
|
| - |
|
218 |
try {
|
| - |
|
219 |
// Format: HH:mm:ss or H:mm:ss
|
| - |
|
220 |
if (timeInStatus.contains(":")) {
|
| - |
|
221 |
String[] parts = timeInStatus.split(":");
|
| - |
|
222 |
if (parts.length == 3) {
|
| - |
|
223 |
int hours = Integer.parseInt(parts[0].trim());
|
| - |
|
224 |
int minutes = Integer.parseInt(parts[1].trim());
|
| - |
|
225 |
int seconds = Integer.parseInt(parts[2].trim());
|
| - |
|
226 |
return hours * 3600 + minutes * 60 + seconds;
|
| - |
|
227 |
} else if (parts.length == 2) {
|
| - |
|
228 |
int minutes = Integer.parseInt(parts[0].trim());
|
| - |
|
229 |
int seconds = Integer.parseInt(parts[1].trim());
|
| - |
|
230 |
return minutes * 60 + seconds;
|
| - |
|
231 |
}
|
| - |
|
232 |
}
|
| - |
|
233 |
|
| - |
|
234 |
// Format: "5m 30s" or "1h 5m 30s"
|
| - |
|
235 |
int totalSeconds = 0;
|
| - |
|
236 |
String lower = timeInStatus.toLowerCase();
|
| - |
|
237 |
if (lower.contains("h")) {
|
| - |
|
238 |
String hourPart = lower.split("h")[0].trim();
|
| - |
|
239 |
totalSeconds += Integer.parseInt(hourPart.replaceAll("[^0-9]", "")) * 3600;
|
| - |
|
240 |
}
|
| - |
|
241 |
if (lower.contains("m")) {
|
| - |
|
242 |
String minPart = lower.contains("h") ? lower.split("h")[1].split("m")[0] : lower.split("m")[0];
|
| - |
|
243 |
totalSeconds += Integer.parseInt(minPart.trim().replaceAll("[^0-9]", "")) * 60;
|
| - |
|
244 |
}
|
| - |
|
245 |
if (lower.contains("s") && !lower.contains("h") && !lower.contains("m")) {
|
| - |
|
246 |
totalSeconds += Integer.parseInt(lower.replaceAll("[^0-9]", ""));
|
| - |
|
247 |
} else if (lower.contains("s")) {
|
| - |
|
248 |
String secPart = lower.split("m")[1].split("s")[0];
|
| - |
|
249 |
totalSeconds += Integer.parseInt(secPart.trim().replaceAll("[^0-9]", ""));
|
| - |
|
250 |
}
|
| - |
|
251 |
return totalSeconds;
|
| - |
|
252 |
|
| - |
|
253 |
} catch (Exception e) {
|
| - |
|
254 |
LOGGER.warn("Could not parse timeInStatus: {}", timeInStatus);
|
| - |
|
255 |
return 0;
|
| - |
|
256 |
}
|
| - |
|
257 |
}
|
| - |
|
258 |
|
| - |
|
259 |
/**
|
| - |
|
260 |
* Save status duration to RbmBreakLog
|
| - |
|
261 |
*
|
| - |
|
262 |
* @param statusStartTime - the time when agent ENTERED this status (start time)
|
| - |
|
263 |
*/
|
| - |
|
264 |
private void saveStatusDuration(int authId, String agentName, String srNumber, String status,
|
| - |
|
265 |
String durationDisplay, int durationSeconds, LocalDateTime statusStartTime) {
|
| - |
|
266 |
try {
|
| - |
|
267 |
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
|
| - |
|
268 |
@Override
|
| - |
|
269 |
protected void doInTransactionWithoutResult(TransactionStatus txStatus) {
|
| - |
|
270 |
LocalDate logDate = statusStartTime.toLocalDate();
|
| - |
|
271 |
|
| - |
|
272 |
LOGGER.info("Saving status duration: agent={}, status={}, startTime={}, duration={}s ({})",
|
| - |
|
273 |
agentName, status, statusStartTime, durationSeconds, durationDisplay);
|
| - |
|
274 |
|
| - |
|
275 |
RbmBreakLog breakLog = new RbmBreakLog(
|
| - |
|
276 |
authId,
|
| - |
|
277 |
agentName,
|
| - |
|
278 |
srNumber,
|
| - |
|
279 |
status,
|
| - |
|
280 |
statusStartTime, // Use START TIME when agent entered this status
|
| - |
|
281 |
durationSeconds,
|
| - |
|
282 |
durationDisplay,
|
| - |
|
283 |
logDate
|
| - |
|
284 |
);
|
| - |
|
285 |
rbmBreakLogRepository.persist(breakLog);
|
| - |
|
286 |
}
|
| - |
|
287 |
});
|
| - |
|
288 |
} catch (Exception e) {
|
| - |
|
289 |
LOGGER.error("Error saving status duration for {}: {}", agentName, e.getMessage());
|
| - |
|
290 |
}
|
| - |
|
291 |
}
|
| - |
|
292 |
|
| 152 |
public AgentStatus getStatusByAuthId(int authId) {
|
293 |
public AgentStatus getStatusByAuthId(int authId) {
|
| 153 |
return agentStatusMap.get(authId);
|
294 |
return agentStatusMap.get(authId);
|
| 154 |
}
|
295 |
}
|
| 155 |
|
296 |
|
| 156 |
public Map<Integer, AgentStatus> getAllStatusMap() {
|
297 |
public Map<Integer, AgentStatus> getAllStatusMap() {
|