| Line 72... |
Line 72... |
| 72 |
import java.time.format.DateTimeFormatter;
|
72 |
import java.time.format.DateTimeFormatter;
|
| 73 |
import java.time.temporal.TemporalAdjusters;
|
73 |
import java.time.temporal.TemporalAdjusters;
|
| 74 |
import java.util.*;
|
74 |
import java.util.*;
|
| 75 |
import java.util.HashSet;
|
75 |
import java.util.HashSet;
|
| 76 |
import java.util.Map.Entry;
|
76 |
import java.util.Map.Entry;
|
| - |
|
77 |
import java.util.concurrent.ThreadLocalRandom;
|
| 77 |
import java.util.stream.Collectors;
|
78 |
import java.util.stream.Collectors;
|
| 78 |
|
79 |
|
| 79 |
@Controller
|
80 |
@Controller
|
| 80 |
@Transactional(rollbackFor = Throwable.class)
|
81 |
@Transactional(rollbackFor = Throwable.class)
|
| 81 |
public class LeadController {
|
82 |
public class LeadController {
|
| 82 |
private static final Logger LOGGER = LogManager.getLogger(LeadController.class);
|
83 |
private static final Logger LOGGER = LogManager.getLogger(LeadController.class);
|
| 83 |
|
84 |
|
| 84 |
/** Source/createdBy label for leads captured by the AI assistant. */
|
85 |
/** Source/createdBy label for leads captured by the AI assistant. */
|
| 85 |
private static final String AI_LEAD_SOURCE = "AI Assistant";
|
86 |
private static final String AI_LEAD_SOURCE = "AI Assistant";
|
| 86 |
|
87 |
|
| - |
|
88 |
/**
|
| - |
|
89 |
* Assignee used for AI leads only when no active Sales L1 can be resolved from cs.position.
|
| - |
|
90 |
* Same auth id the web capture has always routed to, so intake never fails on an empty pool.
|
| - |
|
91 |
*/
|
| - |
|
92 |
private static final int AI_LEAD_FALLBACK_ASSIGNEE = 53;
|
| - |
|
93 |
|
| 87 |
@Autowired
|
94 |
@Autowired
|
| 88 |
private ResponseSender<?> responseSender;
|
95 |
private ResponseSender<?> responseSender;
|
| 89 |
|
96 |
|
| 90 |
@Value("${ai.lead.intake.token}")
|
97 |
@Value("${ai.lead.intake.token}")
|
| 91 |
private String aiLeadIntakeToken;
|
98 |
private String aiLeadIntakeToken;
|
| Line 798... |
Line 805... |
| 798 |
String recordingUrl = aiLeadRequest.getRecordingUrl();
|
805 |
String recordingUrl = aiLeadRequest.getRecordingUrl();
|
| 799 |
if (recordingUrl != null && !recordingUrl.trim().isEmpty()) {
|
806 |
if (recordingUrl != null && !recordingUrl.trim().isEmpty()) {
|
| 800 |
lead.setRecordingUrl(recordingUrl.trim());
|
807 |
lead.setRecordingUrl(recordingUrl.trim());
|
| 801 |
}
|
808 |
}
|
| 802 |
|
809 |
|
| 803 |
// Auto-assign using the same routing the public web capture uses today
|
810 |
// Auto-assign to a randomly picked active Sales L1 so AI leads spread across the field team
|
| 804 |
lead.setAssignTo(53);
|
811 |
lead.setAssignTo(resolveAiLeadAssignee());
|
| 805 |
lead.setAuthId(lead.getAssignTo());
|
812 |
lead.setAuthId(lead.getAssignTo());
|
| 806 |
|
813 |
|
| 807 |
lead.setCreatedTimestamp(LocalDateTime.now());
|
814 |
lead.setCreatedTimestamp(LocalDateTime.now());
|
| 808 |
lead.setUpdatedTimestamp(LocalDateTime.now());
|
815 |
lead.setUpdatedTimestamp(LocalDateTime.now());
|
| 809 |
leadRepository.persist(lead);
|
816 |
leadRepository.persist(lead);
|
| Line 811... |
Line 818... |
| 811 |
LOGGER.info("AI lead intake: created lead id={} for mobile {}", lead.getId(), mobile);
|
818 |
LOGGER.info("AI lead intake: created lead id={} for mobile {}", lead.getId(), mobile);
|
| 812 |
return responseSender.ok(lead.getId());
|
819 |
return responseSender.ok(lead.getId());
|
| 813 |
|
820 |
|
| 814 |
}
|
821 |
}
|
| 815 |
|
822 |
|
| - |
|
823 |
/**
|
| - |
|
824 |
* Picks the owner for an incoming AI lead: a uniformly random <b>active</b> Sales L1 from
|
| - |
|
825 |
* cs.position (category {@link ProfitMandiConstants#TICKET_CATEGORY_SALES}, escalation L1).
|
| - |
|
826 |
* <p>
|
| - |
|
827 |
* Sales L1 is the same position that drives Lead Management visibility, so the chosen user sees
|
| - |
|
828 |
* the lead in their own list and their L2/L3 managers see it in their downline. Inactive users
|
| - |
|
829 |
* are filtered out here because {@code getAuthUserByCategoryId(int, EscalationType)} does not
|
| - |
|
830 |
* filter on active, and an inactive owner would strand the lead. Ids are already distinct per
|
| - |
|
831 |
* user, so holding several regional positions does not raise a person's odds of being picked.
|
| - |
|
832 |
*/
|
| - |
|
833 |
private int resolveAiLeadAssignee() {
|
| - |
|
834 |
List<AuthUser> salesL1 = csService.getAuthUserByCategoryId(
|
| - |
|
835 |
ProfitMandiConstants.TICKET_CATEGORY_SALES, EscalationType.L1);
|
| - |
|
836 |
|
| - |
|
837 |
List<AuthUser> eligible = salesL1 == null ? Collections.<AuthUser>emptyList()
|
| - |
|
838 |
: salesL1.stream()
|
| - |
|
839 |
.filter(x -> Boolean.TRUE.equals(x.isActive()))
|
| - |
|
840 |
.collect(Collectors.toList());
|
| - |
|
841 |
|
| - |
|
842 |
if (eligible.isEmpty()) {
|
| - |
|
843 |
LOGGER.warn("AI lead intake: no active Sales L1 in cs.position, falling back to auth id {}",
|
| - |
|
844 |
AI_LEAD_FALLBACK_ASSIGNEE);
|
| - |
|
845 |
return AI_LEAD_FALLBACK_ASSIGNEE;
|
| - |
|
846 |
}
|
| - |
|
847 |
|
| - |
|
848 |
AuthUser assignee = eligible.get(ThreadLocalRandom.current().nextInt(eligible.size()));
|
| - |
|
849 |
LOGGER.info("AI lead intake: assigned to Sales L1 {} ({}) picked from {} eligible",
|
| - |
|
850 |
assignee.getId(), assignee.getFullName(), eligible.size());
|
| - |
|
851 |
return assignee.getId();
|
| - |
|
852 |
}
|
| - |
|
853 |
|
| 816 |
@RequestMapping(value = "/getPartnersList", method = RequestMethod.GET)
|
854 |
@RequestMapping(value = "/getPartnersList", method = RequestMethod.GET)
|
| 817 |
@ApiImplicitParams({@ApiImplicitParam(name = "Auth-Token", value = "Auth-Token", required = true, dataType = "string", paramType = "header")})
|
855 |
@ApiImplicitParams({@ApiImplicitParam(name = "Auth-Token", value = "Auth-Token", required = true, dataType = "string", paramType = "header")})
|
| 818 |
public ResponseEntity<?> getPartners(HttpServletRequest request, @RequestParam(name = "gmailId") String gmailId)
|
856 |
public ResponseEntity<?> getPartners(HttpServletRequest request, @RequestParam(name = "gmailId") String gmailId)
|
| 819 |
throws ProfitMandiBusinessException {
|
857 |
throws ProfitMandiBusinessException {
|
| 820 |
|
858 |
|