| Line 11... |
Line 11... |
| 11 |
import com.spice.profitmandi.dao.entity.user.LeadLiveLocation;
|
11 |
import com.spice.profitmandi.dao.entity.user.LeadLiveLocation;
|
| 12 |
import com.spice.profitmandi.dao.repository.dtr.LeadActivityRepository;
|
12 |
import com.spice.profitmandi.dao.repository.dtr.LeadActivityRepository;
|
| 13 |
import com.spice.profitmandi.dao.entity.user.LeadCall;
|
13 |
import com.spice.profitmandi.dao.entity.user.LeadCall;
|
| 14 |
import com.spice.profitmandi.dao.repository.dtr.LeadCallRepository;
|
14 |
import com.spice.profitmandi.dao.repository.dtr.LeadCallRepository;
|
| 15 |
import com.spice.profitmandi.dao.repository.dtr.LeadDndRepository;
|
15 |
import com.spice.profitmandi.dao.repository.dtr.LeadDndRepository;
|
| - |
|
16 |
import com.spice.profitmandi.dao.entity.auth.VonageAgent;
|
| - |
|
17 |
import com.spice.profitmandi.dao.repository.auth.VonageAgentRepository;
|
| - |
|
18 |
import com.spice.profitmandi.dao.repository.dtr.LeadRecordingAccessRepository;
|
| 16 |
import com.spice.profitmandi.dao.repository.dtr.LeadLiveLocationRepository;
|
19 |
import com.spice.profitmandi.dao.repository.dtr.LeadLiveLocationRepository;
|
| 17 |
import com.spice.profitmandi.dao.repository.dtr.LeadRepository;
|
20 |
import com.spice.profitmandi.dao.repository.dtr.LeadRepository;
|
| 18 |
import org.apache.logging.log4j.LogManager;
|
21 |
import org.apache.logging.log4j.LogManager;
|
| 19 |
import org.apache.logging.log4j.Logger;
|
22 |
import org.apache.logging.log4j.Logger;
|
| 20 |
import org.springframework.beans.factory.annotation.Autowired;
|
23 |
import org.springframework.beans.factory.annotation.Autowired;
|
| Line 78... |
Line 81... |
| 78 |
|
81 |
|
| 79 |
@Autowired
|
82 |
@Autowired
|
| 80 |
private LeadDndRepository leadDndRepository;
|
83 |
private LeadDndRepository leadDndRepository;
|
| 81 |
|
84 |
|
| 82 |
@Autowired
|
85 |
@Autowired
|
| - |
|
86 |
private LeadRecordingAccessRepository leadRecordingAccessRepository;
|
| - |
|
87 |
|
| - |
|
88 |
@Autowired
|
| - |
|
89 |
private VonageAgentRepository vonageAgentRepository;
|
| - |
|
90 |
|
| - |
|
91 |
@Autowired
|
| 83 |
private LeadLiveLocationRepository leadLiveLocationRepository;
|
92 |
private LeadLiveLocationRepository leadLiveLocationRepository;
|
| 84 |
|
93 |
|
| 85 |
// ---- Command Center + shared summary ----------------------------------------------------
|
94 |
// ---- Command Center + shared summary ----------------------------------------------------
|
| 86 |
|
95 |
|
| 87 |
/** KPIs + lifecycle funnel + attention queue for the given region / created-date window. */
|
96 |
/** KPIs + lifecycle funnel + attention queue for the given region / created-date window. */
|
| Line 292... |
Line 301... |
| 292 |
out.put("total", total);
|
301 |
out.put("total", total);
|
| 293 |
out.put("rows", rows);
|
302 |
out.put("rows", rows);
|
| 294 |
return out;
|
303 |
return out;
|
| 295 |
}
|
304 |
}
|
| 296 |
|
305 |
|
| - |
|
306 |
// ---- Standard report suite (SOP §18.2) --------------------------------------------------
|
| - |
|
307 |
|
| - |
|
308 |
/**
|
| - |
|
309 |
* BGC Daily: calls made, connect rate, average talk time and dispositions — per agent, per day.
|
| - |
|
310 |
*
|
| - |
|
311 |
* <p>Real telephony, not the {@code contacted / days} proxy the tile used to show. That proxy
|
| - |
|
312 |
* was invented before {@code user.lead_call} existed and reported a plausible-looking number
|
| - |
|
313 |
* that no call had ever contributed to.
|
| - |
|
314 |
*/
|
| - |
|
315 |
public Map<String, Object> bgcDaily(LocalDateTime from, LocalDateTime to) {
|
| - |
|
316 |
List<Object[]> rows = leadCallRepository.countDailyByAgent(from, to);
|
| - |
|
317 |
java.util.Set<Integer> authIds = new java.util.HashSet<>();
|
| - |
|
318 |
for (Object[] r : rows) {
|
| - |
|
319 |
if (r[0] != null) authIds.add(((Number) r[0]).intValue());
|
| - |
|
320 |
}
|
| - |
|
321 |
Map<Integer, AuthUser> agents = new HashMap<>();
|
| - |
|
322 |
if (!authIds.isEmpty()) {
|
| - |
|
323 |
for (AuthUser u : authRepository.selectByIds(new ArrayList<>(authIds))) agents.put(u.getId(), u);
|
| - |
|
324 |
}
|
| - |
|
325 |
|
| - |
|
326 |
List<Map<String, Object>> out = new ArrayList<>();
|
| - |
|
327 |
long totCalls = 0, totConn = 0, totTalk = 0;
|
| - |
|
328 |
for (Object[] r : rows) {
|
| - |
|
329 |
int authId = r[0] == null ? 0 : ((Number) r[0]).intValue();
|
| - |
|
330 |
long calls = ((Number) r[2]).longValue();
|
| - |
|
331 |
long conn = ((Number) r[3]).longValue();
|
| - |
|
332 |
long talk = ((Number) r[4]).longValue();
|
| - |
|
333 |
totCalls += calls; totConn += conn; totTalk += talk;
|
| - |
|
334 |
AuthUser a = agents.get(authId);
|
| - |
|
335 |
Map<String, Object> m = new LinkedHashMap<>();
|
| - |
|
336 |
m.put("authId", authId);
|
| - |
|
337 |
m.put("agent", a != null ? a.getFullName() : ("#" + authId));
|
| - |
|
338 |
m.put("day", r[1] == null ? null : r[1].toString());
|
| - |
|
339 |
m.put("calls", calls);
|
| - |
|
340 |
m.put("connected", conn);
|
| - |
|
341 |
m.put("connectPct", calls > 0 ? Math.round(conn * 1000.0 / calls) / 10.0 : 0);
|
| - |
|
342 |
// Average over CONNECTED calls: dividing by every dial would make a day of unanswered
|
| - |
|
343 |
// ringing look like short conversations rather than no conversations.
|
| - |
|
344 |
m.put("avgTalkSeconds", conn > 0 ? Math.round(talk / (double) conn) : 0);
|
| - |
|
345 |
m.put("longestSeconds", ((Number) r[5]).longValue());
|
| - |
|
346 |
out.add(m);
|
| - |
|
347 |
}
|
| - |
|
348 |
|
| - |
|
349 |
Map<String, Object> res = new LinkedHashMap<>();
|
| - |
|
350 |
res.put("rows", out);
|
| - |
|
351 |
res.put("calls", totCalls);
|
| - |
|
352 |
res.put("connected", totConn);
|
| - |
|
353 |
res.put("connectPct", totCalls > 0 ? Math.round(totConn * 1000.0 / totCalls) / 10.0 : 0);
|
| - |
|
354 |
res.put("avgTalkSeconds", totConn > 0 ? Math.round(totTalk / (double) totConn) : 0);
|
| - |
|
355 |
res.put("talkMinutes", Math.round(totTalk / 60.0));
|
| - |
|
356 |
return res;
|
| - |
|
357 |
}
|
| - |
|
358 |
|
| - |
|
359 |
/**
|
| - |
|
360 |
* Audit Hygiene (SOP §17): the breaches an audit review actually looks for.
|
| - |
|
361 |
*
|
| - |
|
362 |
* <p>Each figure is either measured or reported as unavailable — never estimated. A hygiene
|
| - |
|
363 |
* report that guesses is worse than one that admits a gap, because the gap is the finding.
|
| - |
|
364 |
*/
|
| - |
|
365 |
public Map<String, Object> auditHygiene(LocalDateTime from, LocalDateTime to) {
|
| - |
|
366 |
LocalDateTime now = LocalDateTime.now();
|
| - |
|
367 |
Map<String, Object> m = new LinkedHashMap<>();
|
| - |
|
368 |
|
| - |
|
369 |
// Same source the Command Center's attention tiles use, so the two cannot disagree.
|
| - |
|
370 |
Object[] att = leadRepository.attentionCounts(null, now, now.plusHours(1));
|
| - |
|
371 |
m.put("slaBreached", num(att, 0));
|
| - |
|
372 |
m.put("hold", num(att, 1));
|
| - |
|
373 |
m.put("missingDisposition", num(att, 3));
|
| - |
|
374 |
|
| - |
|
375 |
m.put("dndRegister", leadDndRepository.count());
|
| - |
|
376 |
List<Object[]> breaches = leadCallRepository.selectDndBreaches(from, to, 50);
|
| - |
|
377 |
List<Map<String, Object>> bl = new ArrayList<>();
|
| - |
|
378 |
for (Object[] b : breaches) {
|
| - |
|
379 |
Map<String, Object> row = new LinkedHashMap<>();
|
| - |
|
380 |
row.put("callId", ((Number) b[0]).intValue());
|
| - |
|
381 |
row.put("leadId", ((Number) b[1]).intValue());
|
| - |
|
382 |
row.put("authId", b[2] == null ? 0 : ((Number) b[2]).intValue());
|
| - |
|
383 |
row.put("mobile", b[3] == null ? null : b[3].toString());
|
| - |
|
384 |
row.put("when", b[4] == null ? null : b[4].toString());
|
| - |
|
385 |
bl.add(row);
|
| - |
|
386 |
}
|
| - |
|
387 |
m.put("dndBreaches", bl);
|
| - |
|
388 |
m.put("dndBreachCount", bl.size());
|
| - |
|
389 |
|
| - |
|
390 |
m.put("recordingOpens", leadRecordingAccessRepository.countInWindow(from, to, true));
|
| - |
|
391 |
m.put("recordingDenied", leadRecordingAccessRepository.countInWindow(from, to, false));
|
| - |
|
392 |
return m;
|
| - |
|
393 |
}
|
| - |
|
394 |
|
| - |
|
395 |
// ---- Live call board ---------------------------------------------------------------------
|
| - |
|
396 |
|
| - |
|
397 |
/**
|
| - |
|
398 |
* A non-terminal call older than this is not believed. Only the browser's 3-second poll
|
| - |
|
399 |
* terminalises a VBC call, so an agent who closed the tab mid-call leaves a row that is
|
| - |
|
400 |
* forever RINGING/ANSWERED with a null ended_at. Without this the board would show them On
|
| - |
|
401 |
* Call indefinitely — wrong in its single most-read column.
|
| - |
|
402 |
*/
|
| - |
|
403 |
private static final int LIVE_CALL_STALE_MINUTES = 30;
|
| - |
|
404 |
/** Just off a call and no outcome logged yet. */
|
| - |
|
405 |
private static final int WRAP_UP_MINUTES = 2;
|
| - |
|
406 |
/** No call for this long and the agent reads as idle rather than merely available. */
|
| - |
|
407 |
private static final int IDLE_MINUTES = 15;
|
| - |
|
408 |
|
| - |
|
409 |
/**
|
| - |
|
410 |
* Per-agent live board (the LMS answer to the RBM Call Target Summary).
|
| - |
|
411 |
*
|
| - |
|
412 |
* <p>The roster is {@code auth.vonage_agent}, not every user with leads: an agent without a
|
| - |
|
413 |
* device mapping cannot dial at all, so they have no place on a call board.
|
| - |
|
414 |
*
|
| - |
|
415 |
* <p>Status is derived from call activity — Vonage exposes no presence of any kind, so there is
|
| - |
|
416 |
* deliberately no Login or Break here rather than a guess dressed up as one.
|
| - |
|
417 |
*/
|
| - |
|
418 |
public Map<String, Object> liveCallBoard() {
|
| - |
|
419 |
LocalDateTime now = LocalDateTime.now();
|
| - |
|
420 |
LocalDateTime dayStart = now.toLocalDate().atStartOfDay();
|
| - |
|
421 |
|
| - |
|
422 |
Map<Integer, Object[]> calls = byAuthId(leadCallRepository.selectLiveAgentBoard(dayStart, now));
|
| - |
|
423 |
Map<Integer, Object[]> leads = byAuthId(leadRepository.selectAgentLeadCounters(now));
|
| - |
|
424 |
Map<Integer, Object[]> acts = byAuthId(leadActivityRepository.countAgentActivity(dayStart, now));
|
| - |
|
425 |
|
| - |
|
426 |
List<VonageAgent> roster = vonageAgentRepository.selectAll();
|
| - |
|
427 |
java.util.Set<Integer> ids = new java.util.HashSet<>();
|
| - |
|
428 |
for (VonageAgent a : roster) {
|
| - |
|
429 |
ids.add(a.getAuthId());
|
| - |
|
430 |
}
|
| - |
|
431 |
Map<Integer, AuthUser> users = new HashMap<>();
|
| - |
|
432 |
if (!ids.isEmpty()) {
|
| - |
|
433 |
for (AuthUser u : authRepository.selectByIds(new ArrayList<>(ids))) {
|
| - |
|
434 |
users.put(u.getId(), u);
|
| - |
|
435 |
}
|
| - |
|
436 |
}
|
| - |
|
437 |
|
| - |
|
438 |
List<Map<String, Object>> rows = new ArrayList<>();
|
| - |
|
439 |
long totCalls = 0, totConn = 0, totTalk = 0, onCallNow = 0;
|
| - |
|
440 |
|
| - |
|
441 |
for (VonageAgent agent : roster) {
|
| - |
|
442 |
int authId = agent.getAuthId();
|
| - |
|
443 |
Object[] c = calls.get(authId);
|
| - |
|
444 |
Object[] l = leads.get(authId);
|
| - |
|
445 |
Object[] a = acts.get(authId);
|
| - |
|
446 |
AuthUser u = users.get(authId);
|
| - |
|
447 |
|
| - |
|
448 |
long cCalls = c == null ? 0 : num(c, 1);
|
| - |
|
449 |
long cConn = c == null ? 0 : num(c, 2);
|
| - |
|
450 |
long cTalk = c == null ? 0 : num(c, 3);
|
| - |
|
451 |
LocalDateTime lastStarted = c == null ? null : ts(c, 5);
|
| - |
|
452 |
LocalDateTime lastEnded = c == null ? null : ts(c, 6);
|
| - |
|
453 |
Integer liveCallId = c == null || c[7] == null ? null : ((Number) c[7]).intValue();
|
| - |
|
454 |
LocalDateTime liveStarted = c == null ? null : ts(c, 9);
|
| - |
|
455 |
|
| - |
|
456 |
// A live row past the staleness window is an abandoned tab, not a conversation.
|
| - |
|
457 |
boolean stale = liveStarted != null && liveStarted.isBefore(now.minusMinutes(LIVE_CALL_STALE_MINUTES));
|
| - |
|
458 |
boolean live = liveCallId != null && !stale;
|
| - |
|
459 |
|
| - |
|
460 |
String status;
|
| - |
|
461 |
LocalDateTime since;
|
| - |
|
462 |
if (!agent.isActive()) {
|
| - |
|
463 |
status = "—";
|
| - |
|
464 |
since = null;
|
| - |
|
465 |
} else if (live) {
|
| - |
|
466 |
status = "On Call";
|
| - |
|
467 |
since = liveStarted;
|
| - |
|
468 |
onCallNow++;
|
| - |
|
469 |
} else if (lastEnded != null && lastEnded.isAfter(now.minusMinutes(WRAP_UP_MINUTES))) {
|
| - |
|
470 |
status = "Wrap Up";
|
| - |
|
471 |
since = lastEnded;
|
| - |
|
472 |
} else if (lastEnded != null && lastEnded.isBefore(now.minusMinutes(IDLE_MINUTES))) {
|
| - |
|
473 |
status = "Idle";
|
| - |
|
474 |
since = lastEnded;
|
| - |
|
475 |
} else if (lastEnded != null) {
|
| - |
|
476 |
status = "Available";
|
| - |
|
477 |
since = lastEnded;
|
| - |
|
478 |
} else {
|
| - |
|
479 |
status = "Available";
|
| - |
|
480 |
since = null;
|
| - |
|
481 |
}
|
| - |
|
482 |
|
| - |
|
483 |
Map<String, Object> r = new LinkedHashMap<>();
|
| - |
|
484 |
r.put("authId", authId);
|
| - |
|
485 |
r.put("agent", u != null ? u.getFullName() : agent.getVonageUserName());
|
| - |
|
486 |
r.put("extension", agent.getVbcExtension());
|
| - |
|
487 |
r.put("status", status);
|
| - |
|
488 |
// Computed, never stored — the RBM board does the same rather than persisting a clock.
|
| - |
|
489 |
r.put("sinceSeconds", since == null ? null : java.time.Duration.between(since, now).getSeconds());
|
| - |
|
490 |
r.put("staleCall", stale);
|
| - |
|
491 |
r.put("liveCallId", live ? liveCallId : null);
|
| - |
|
492 |
r.put("liveLeadId", live && c[8] != null ? ((Number) c[8]).intValue() : null);
|
| - |
|
493 |
r.put("calls", cCalls);
|
| - |
|
494 |
r.put("connected", cConn);
|
| - |
|
495 |
r.put("connectPct", cCalls > 0 ? Math.round(cConn * 1000.0 / cCalls) / 10.0 : 0);
|
| - |
|
496 |
r.put("talkSeconds", cTalk);
|
| - |
|
497 |
r.put("longestSeconds", c == null ? 0 : num(c, 4));
|
| - |
|
498 |
r.put("lastCallAt", lastStarted == null ? null : lastStarted.toString());
|
| - |
|
499 |
r.put("assigned", l == null ? 0 : num(l, 1));
|
| - |
|
500 |
r.put("slaBreached", l == null ? 0 : num(l, 2));
|
| - |
|
501 |
r.put("touched", a == null ? 0 : num(a, 1));
|
| - |
|
502 |
r.put("dispositioned", a == null ? 0 : num(a, 2));
|
| - |
|
503 |
rows.add(r);
|
| - |
|
504 |
|
| - |
|
505 |
totCalls += cCalls; totConn += cConn; totTalk += cTalk;
|
| - |
|
506 |
}
|
| - |
|
507 |
|
| - |
|
508 |
Map<String, Object> out = new LinkedHashMap<>();
|
| - |
|
509 |
out.put("rows", rows);
|
| - |
|
510 |
out.put("agents", rows.size());
|
| - |
|
511 |
out.put("onCallNow", onCallNow);
|
| - |
|
512 |
out.put("calls", totCalls);
|
| - |
|
513 |
out.put("connected", totConn);
|
| - |
|
514 |
out.put("connectPct", totCalls > 0 ? Math.round(totConn * 1000.0 / totCalls) / 10.0 : 0);
|
| - |
|
515 |
out.put("talkMinutes", Math.round(totTalk / 60.0));
|
| - |
|
516 |
out.put("asOf", now.toString());
|
| - |
|
517 |
return out;
|
| - |
|
518 |
}
|
| - |
|
519 |
|
| - |
|
520 |
/**
|
| - |
|
521 |
* The full call log for a window — every call, with ring time, talk time, outcome and recording.
|
| - |
|
522 |
*
|
| - |
|
523 |
* <p>The agent's extension comes from {@code auth.vonage_agent}, not from the call's
|
| - |
|
524 |
* {@code from_number}: click2dial addresses the agent by DEVICE, so that column holds an opaque
|
| - |
|
525 |
* id like {@code VH7NdQbBHcmsR8wFLsiU}, which means nothing to anyone reading a report.
|
| - |
|
526 |
*/
|
| - |
|
527 |
public Map<String, Object> callLog(LocalDateTime from, LocalDateTime to, int page, int pageSize) {
|
| - |
|
528 |
int size = Math.min(Math.max(pageSize, 1), MAX_PAGE_SIZE);
|
| - |
|
529 |
int p = Math.max(page, 1);
|
| - |
|
530 |
long total = leadCallRepository.countCallLog(from, to);
|
| - |
|
531 |
List<Object[]> rows = leadCallRepository.selectCallLog(from, to, (p - 1) * size, size);
|
| - |
|
532 |
|
| - |
|
533 |
java.util.Set<Integer> authIds = new java.util.HashSet<>();
|
| - |
|
534 |
for (Object[] r : rows) {
|
| - |
|
535 |
if (r[7] != null) {
|
| - |
|
536 |
authIds.add(((Number) r[7]).intValue());
|
| - |
|
537 |
}
|
| - |
|
538 |
}
|
| - |
|
539 |
Map<Integer, AuthUser> users = new HashMap<>();
|
| - |
|
540 |
Map<Integer, String> extByAuth = new HashMap<>();
|
| - |
|
541 |
if (!authIds.isEmpty()) {
|
| - |
|
542 |
for (AuthUser u : authRepository.selectByIds(new ArrayList<>(authIds))) {
|
| - |
|
543 |
users.put(u.getId(), u);
|
| - |
|
544 |
}
|
| - |
|
545 |
for (VonageAgent a : vonageAgentRepository.selectAll()) {
|
| - |
|
546 |
extByAuth.put(a.getAuthId(), a.getVbcExtension());
|
| - |
|
547 |
}
|
| - |
|
548 |
}
|
| - |
|
549 |
|
| - |
|
550 |
List<Map<String, Object>> out = new ArrayList<>();
|
| - |
|
551 |
for (Object[] r : rows) {
|
| - |
|
552 |
int authId = r[7] == null ? 0 : ((Number) r[7]).intValue();
|
| - |
|
553 |
AuthUser u = users.get(authId);
|
| - |
|
554 |
Map<String, Object> m = new LinkedHashMap<>();
|
| - |
|
555 |
m.put("callId", ((Number) r[0]).intValue());
|
| - |
|
556 |
m.put("startedAt", str(ts(r, 1)));
|
| - |
|
557 |
m.put("answeredAt", str(ts(r, 2)));
|
| - |
|
558 |
m.put("endedAt", str(ts(r, 3)));
|
| - |
|
559 |
m.put("status", r[4] == null ? null : r[4].toString());
|
| - |
|
560 |
// Negative or null when never answered — normalise to null so the UI shows a dash.
|
| - |
|
561 |
Long ring = r[5] == null ? null : ((Number) r[5]).longValue();
|
| - |
|
562 |
m.put("ringSeconds", ring != null && ring >= 0 ? ring : null);
|
| - |
|
563 |
m.put("talkSeconds", num(r, 6));
|
| - |
|
564 |
m.put("authId", authId);
|
| - |
|
565 |
m.put("agent", u != null ? u.getFullName() : (authId > 0 ? "#" + authId : "—"));
|
| - |
|
566 |
m.put("extension", extByAuth.get(authId));
|
| - |
|
567 |
m.put("leadId", r[8] == null ? null : ((Number) r[8]).intValue());
|
| - |
|
568 |
m.put("toNumber", r[9] == null ? null : r[9].toString());
|
| - |
|
569 |
m.put("direction", r[10] == null ? null : r[10].toString());
|
| - |
|
570 |
m.put("lmsCode", r[11] == null ? null : r[11].toString());
|
| - |
|
571 |
m.put("retailer", r[12] == null ? null : r[12].toString().trim());
|
| - |
|
572 |
m.put("outlet", r[13] == null ? null : r[13].toString());
|
| - |
|
573 |
m.put("recording", r[14] == null ? "none" : r[14].toString());
|
| - |
|
574 |
m.put("disposition", r[15] == null ? null : r[15].toString());
|
| - |
|
575 |
out.add(m);
|
| - |
|
576 |
}
|
| - |
|
577 |
|
| - |
|
578 |
Map<String, Object> res = new LinkedHashMap<>();
|
| - |
|
579 |
res.put("rows", out);
|
| - |
|
580 |
res.put("total", total);
|
| - |
|
581 |
res.put("page", p);
|
| - |
|
582 |
res.put("pageSize", size);
|
| - |
|
583 |
res.put("pages", (int) Math.ceil(total / (double) size));
|
| - |
|
584 |
return res;
|
| - |
|
585 |
}
|
| - |
|
586 |
|
| - |
|
587 |
private String str(LocalDateTime t) {
|
| - |
|
588 |
return t == null ? null : t.toString();
|
| - |
|
589 |
}
|
| - |
|
590 |
|
| - |
|
591 |
/** Index an aggregate result by its first column (the auth id). */
|
| - |
|
592 |
private Map<Integer, Object[]> byAuthId(List<Object[]> rows) {
|
| - |
|
593 |
Map<Integer, Object[]> m = new HashMap<>();
|
| - |
|
594 |
if (rows != null) {
|
| - |
|
595 |
for (Object[] r : rows) {
|
| - |
|
596 |
if (r[0] != null) {
|
| - |
|
597 |
m.put(((Number) r[0]).intValue(), r);
|
| - |
|
598 |
}
|
| - |
|
599 |
}
|
| - |
|
600 |
}
|
| - |
|
601 |
return m;
|
| - |
|
602 |
}
|
| - |
|
603 |
|
| - |
|
604 |
/** Native queries hand back java.sql.Timestamp; the entity side works in LocalDateTime. */
|
| - |
|
605 |
private LocalDateTime ts(Object[] row, int i) {
|
| - |
|
606 |
Object v = row[i];
|
| - |
|
607 |
if (v == null) {
|
| - |
|
608 |
return null;
|
| - |
|
609 |
}
|
| - |
|
610 |
if (v instanceof java.sql.Timestamp) {
|
| - |
|
611 |
return ((java.sql.Timestamp) v).toLocalDateTime();
|
| - |
|
612 |
}
|
| - |
|
613 |
return v instanceof LocalDateTime ? (LocalDateTime) v : null;
|
| - |
|
614 |
}
|
| - |
|
615 |
|
| 297 |
// ---- Drill-down -------------------------------------------------------------------------
|
616 |
// ---- Drill-down -------------------------------------------------------------------------
|
| 298 |
|
617 |
|
| 299 |
/** Lead rows for a drill bucket, shaped for the drawer table. */
|
618 |
/** Lead rows for a drill bucket, shaped for the drawer table. */
|
| 300 |
public List<Map<String, Object>> drill(String bucketType, String bucketKey, Integer regionId,
|
619 |
public List<Map<String, Object>> drill(String bucketType, String bucketKey, Integer regionId,
|
| 301 |
LocalDateTime from, LocalDateTime to) {
|
620 |
LocalDateTime from, LocalDateTime to) {
|
| Line 397... |
Line 716... |
| 397 |
r.put("slaState", lmsAssignmentService.slaState(l));
|
716 |
r.put("slaState", lmsAssignmentService.slaState(l));
|
| 398 |
r.put("value", l.getPotential());
|
717 |
r.put("value", l.getPotential());
|
| 399 |
// Both are sortable columns on the pipeline table, so the row has to carry them.
|
718 |
// Both are sortable columns on the pipeline table, so the row has to carry them.
|
| 400 |
r.put("city", l.getCity());
|
719 |
r.put("city", l.getCity());
|
| 401 |
r.put("createdBy", l.getCreatedBy());
|
720 |
r.put("createdBy", l.getCreatedBy());
|
| - |
|
721 |
// Lets the assign picker surface whoever covers this lead's region first.
|
| - |
|
722 |
r.put("regionId", l.getRegionId());
|
| 402 |
out.add(r);
|
723 |
out.add(r);
|
| 403 |
}
|
724 |
}
|
| 404 |
return out;
|
725 |
return out;
|
| 405 |
}
|
726 |
}
|
| 406 |
|
727 |
|