Subversion Repositories SmartDukaan

Rev

Rev 36000 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
35867 ranu 1
package com.spice.profitmandi.web.service;
2
 
36000 ranu 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;
35879 ranu 7
import org.hibernate.Session;
8
import org.hibernate.SessionFactory;
9
import org.springframework.beans.factory.annotation.Autowired;
35867 ranu 10
import org.springframework.stereotype.Service;
36000 ranu 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;
35867 ranu 15
 
35879 ranu 16
import javax.annotation.PostConstruct;
36000 ranu 17
import java.time.LocalDate;
35879 ranu 18
import java.time.LocalDateTime;
19
import java.util.ArrayList;
20
import java.util.List;
21
import java.util.Map;
22
import java.util.concurrent.ConcurrentHashMap;
23
 
35867 ranu 24
@Service
25
public class AgentLiveStatusService {
35879 ranu 26
 
36000 ranu 27
    private static final Logger LOGGER = LogManager.getLogger(AgentLiveStatusService.class);
28
 
35867 ranu 29
    // In-memory map to store agent status by authId
30
    private final Map<Integer, AgentStatus> agentStatusMap = new ConcurrentHashMap<>();
31
    // Caller ID (Knowlarity SR Number) to Auth ID mapping (loaded from sip_master)
32
    private final Map<String, Integer> callerIdToAuthIdMap = new ConcurrentHashMap<>();
33
    // SR Number to Auth ID mapping (loaded from sip_master)
34
    private final Map<String, Integer> srNumberToAuthIdMap = new ConcurrentHashMap<>();
35
    // Agent name to Auth ID mapping (fallback)
36
    private final Map<String, Integer> agentNameToAuthIdMap = new ConcurrentHashMap<>();
36000 ranu 37
 
35867 ranu 38
    @Autowired
39
    private SessionFactory sessionFactory;
36000 ranu 40
 
41
    @Autowired
42
    private RbmBreakLogRepository rbmBreakLogRepository;
43
 
44
    @Autowired
45
    private PlatformTransactionManager transactionManager;
46
 
47
    private TransactionTemplate transactionTemplate;
48
 
35867 ranu 49
    private String lastError = null;
50
    private int lastQueryCount = 0;
51
 
35879 ranu 52
    /**
35867 ranu 53
     * Load mapping from sip_master table on startup
54
     * caller_id in sip_master = SR Number on Knowlarity dashboard
55
     * sip_master has auth_id directly
35879 ranu 56
     */
35867 ranu 57
    @PostConstruct
58
    public void loadMappingFromDatabase() {
36000 ranu 59
        this.transactionTemplate = new TransactionTemplate(transactionManager);
35867 ranu 60
        try {
61
            Session session = sessionFactory.openSession();
62
 
63
            // Query to get caller_id, auth_user.id, first_name from sip_master
64
            // Join with auth_user to get correct auth_id used by rbm_call_target page
65
            String sql = "SELECT sm.caller_id, au.id, sm.first_name " +
66
                    "FROM auth.sip_master sm " +
67
                    "JOIN auth.auth_user au ON sm.email_id = au.email_id " +
68
                    "WHERE au.id IS NOT NULL";
69
 
70
            List<Object[]> results = session.createNativeQuery(sql).getResultList();
71
            lastQueryCount = results.size();
72
 
73
            for (Object[] row : results) {
74
                String callerId = row[0] != null ? row[0].toString().trim() : "";
75
                int authId = row[1] != null ? ((Number) row[1]).intValue() : 0;
76
                String firstName = row[2] != null ? row[2].toString().trim() : "";
77
 
78
                if (authId > 0) {
79
                    // Map by caller_id if available
80
                    if (!callerId.isEmpty()) {
81
                        callerIdToAuthIdMap.put(callerId, authId);
82
                    }
83
                    // Map by first_name for name-based matching
84
                    if (!firstName.isEmpty()) {
85
                        agentNameToAuthIdMap.put(firstName.toLowerCase(), authId);
86
                        System.out.println("Mapping: '" + firstName + "' -> authId=" + authId + " (caller_id=" + callerId + ")");
87
                    }
88
                }
89
            }
90
 
91
            session.close();
92
            System.out.println("Loaded " + callerIdToAuthIdMap.size() + " caller_id -> auth_id mappings from sip_master");
93
            System.out.println("Loaded " + agentNameToAuthIdMap.size() + " name -> auth_id mappings");
94
 
95
        } catch (Exception e) {
96
            lastError = e.getClass().getSimpleName() + ": " + e.getMessage();
97
            System.err.println("Error loading sip_master mapping: " + e.getMessage());
98
            e.printStackTrace();
99
        }
100
    }
101
 
102
    public String getLastError() {
103
        return lastError;
104
    }
105
 
106
    public int getLastQueryCount() {
107
        return lastQueryCount;
108
    }
109
 
35879 ranu 110
    /**
35867 ranu 111
     * Reload mapping from database (can be called via API)
35879 ranu 112
     */
35867 ranu 113
    public void reloadMapping() {
114
        callerIdToAuthIdMap.clear();
115
        srNumberToAuthIdMap.clear();
116
        agentNameToAuthIdMap.clear();
117
        lastError = null;
118
        lastQueryCount = 0;
119
        loadMappingFromDatabase();
120
    }
121
 
122
    public Integer getAuthIdByCallerId(String callerId) {
123
        return callerIdToAuthIdMap.get(callerId);
124
    }
125
 
126
    public void setSrNumberMapping(String srNumber, int authId) {
127
        srNumberToAuthIdMap.put(srNumber, authId);
128
    }
129
 
130
    public void setAgentNameMapping(String agentName, int authId) {
131
        agentNameToAuthIdMap.put(agentName.toLowerCase(), authId);
132
    }
133
 
134
    public Integer getAuthIdBySrNumber(String srNumber) {
135
        return srNumberToAuthIdMap.get(srNumber);
136
    }
137
 
138
    public Integer getAuthIdByAgentName(String agentName) {
139
        // Try exact match
140
        Integer authId = agentNameToAuthIdMap.get(agentName.toLowerCase());
141
        if (authId != null) return authId;
142
 
143
        // Try partial match
144
        for (Map.Entry<String, Integer> entry : agentNameToAuthIdMap.entrySet()) {
145
            if (agentName.toLowerCase().contains(entry.getKey()) || entry.getKey().contains(agentName.toLowerCase())) {
146
                return entry.getValue();
147
            }
148
        }
149
        return null;
150
    }
151
 
152
    public Map<String, Integer> getSrNumberMapping() {
153
        return srNumberToAuthIdMap;
154
    }
155
 
156
    public Map<String, Integer> getAgentNameMapping() {
157
        return agentNameToAuthIdMap;
158
    }
159
 
160
    public Map<String, Integer> getCallerIdMapping() {
161
        return callerIdToAuthIdMap;
162
    }
163
 
164
    public void clearMappings() {
165
        srNumberToAuthIdMap.clear();
166
        agentNameToAuthIdMap.clear();
167
    }
168
 
169
    public void updateStatus(int authId, String srNumber, String agentName, String agentStatus, String timeInStatus, String customerNumber) {
36000 ranu 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
35867 ranu 199
        AgentStatus status = new AgentStatus(authId, srNumber, agentName, agentStatus, timeInStatus, customerNumber);
200
        agentStatusMap.put(authId, status);
201
    }
202
 
36000 ranu 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
36017 ranu 261
     * Uses UPSERT logic: If record exists within time window, update duration; otherwise create new
36000 ranu 262
     *
263
     * @param statusStartTime - the time when agent ENTERED this status (start time)
264
     */
265
    private void saveStatusDuration(int authId, String agentName, String srNumber, String status,
266
                                    String durationDisplay, int durationSeconds, LocalDateTime statusStartTime) {
267
        try {
268
            transactionTemplate.execute(new TransactionCallbackWithoutResult() {
269
                @Override
270
                protected void doInTransactionWithoutResult(TransactionStatus txStatus) {
271
                    LocalDate logDate = statusStartTime.toLocalDate();
272
 
36017 ranu 273
                    // Check if existing record within 5 minute window
274
                    RbmBreakLog existing = rbmBreakLogRepository.findByAuthIdStatusAndLogTimeWindow(
275
                            authId, status, statusStartTime, 5);
36000 ranu 276
 
36017 ranu 277
                    if (existing != null) {
278
                        // Update existing record with latest duration
279
                        LOGGER.info("Updating existing break log id={}: agent={}, status={}, duration {}s -> {}s",
280
                                existing.getId(), agentName, status, existing.getDurationSeconds(), durationSeconds);
281
                        rbmBreakLogRepository.updateDuration(existing.getId(), durationSeconds, durationDisplay);
282
                    } else {
283
                        // Create new record
284
                        LOGGER.info("Creating new break log: agent={}, status={}, startTime={}, duration={}s ({})",
285
                                agentName, status, statusStartTime, durationSeconds, durationDisplay);
286
 
287
                        RbmBreakLog breakLog = new RbmBreakLog(
288
                                authId,
289
                                agentName,
290
                                srNumber,
291
                                status,
292
                                statusStartTime,
293
                                durationSeconds,
294
                                durationDisplay,
295
                                logDate
296
                        );
297
                        rbmBreakLogRepository.persist(breakLog);
298
                    }
36000 ranu 299
                }
300
            });
301
        } catch (Exception e) {
302
            LOGGER.error("Error saving status duration for {}: {}", agentName, e.getMessage());
303
        }
304
    }
305
 
35867 ranu 306
    public AgentStatus getStatusByAuthId(int authId) {
307
        return agentStatusMap.get(authId);
308
    }
309
 
310
    public Map<Integer, AgentStatus> getAllStatusMap() {
311
        return agentStatusMap;
312
    }
313
 
314
    public List<AgentStatus> getAllStatuses() {
315
        return new ArrayList<>(agentStatusMap.values());
316
    }
317
 
318
    public void clearAll() {
319
        agentStatusMap.clear();
320
    }
321
 
322
    // Inner class to hold agent status
323
    public static class AgentStatus {
324
        private int authId;
325
        private String srNumber;
326
        private String agentName;
327
        private String agentStatus;
328
        private String timeInStatus;
329
        private String customerNumber;
330
        private LocalDateTime lastUpdated;
331
 
332
        public AgentStatus() {
333
        }
334
 
335
        public AgentStatus(int authId, String srNumber, String agentName, String agentStatus, String timeInStatus, String customerNumber) {
336
            this.authId = authId;
337
            this.srNumber = srNumber;
338
            this.agentName = agentName;
339
            this.agentStatus = agentStatus;
340
            this.timeInStatus = timeInStatus;
341
            this.customerNumber = customerNumber;
342
            this.lastUpdated = LocalDateTime.now();
343
        }
344
 
345
        public int getAuthId() {
346
            return authId;
347
        }
348
 
349
        public void setAuthId(int authId) {
350
            this.authId = authId;
351
        }
352
 
353
        public String getSrNumber() {
354
            return srNumber;
355
        }
356
 
357
        public void setSrNumber(String srNumber) {
358
            this.srNumber = srNumber;
359
        }
360
 
361
        public String getAgentName() {
362
            return agentName;
363
        }
364
 
365
        public void setAgentName(String agentName) {
366
            this.agentName = agentName;
367
        }
368
 
369
        public String getAgentStatus() {
370
            return agentStatus;
371
        }
372
 
373
        public void setAgentStatus(String agentStatus) {
374
            this.agentStatus = agentStatus;
375
        }
376
 
377
        public String getTimeInStatus() {
378
            return timeInStatus;
379
        }
380
 
381
        public void setTimeInStatus(String timeInStatus) {
382
            this.timeInStatus = timeInStatus;
383
        }
384
 
385
        public String getCustomerNumber() {
386
            return customerNumber;
387
        }
388
 
389
        public void setCustomerNumber(String customerNumber) {
390
            this.customerNumber = customerNumber;
391
        }
392
 
393
        public LocalDateTime getLastUpdated() {
394
            return lastUpdated;
395
        }
396
 
397
        public void setLastUpdated(LocalDateTime lastUpdated) {
398
            this.lastUpdated = lastUpdated;
399
        }
35879 ranu 400
    }
35867 ranu 401
}