Subversion Repositories SmartDukaan

Rev

Rev 37836 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 37836 Rev 37893
Line 84... Line 84...
84
    // AI leads are auto-assigned to a random active BGC L1 (the lead desk). Previously a hardcoded
84
    // AI leads are auto-assigned to a random active BGC L1 (the lead desk). Previously a hardcoded
85
    // round-robin between Khushbu (134) and Archana (126); before that a fixed 53 / Shankar Mushran.
85
    // round-robin between Khushbu (134) and Archana (126); before that a fixed 53 / Shankar Mushran.
86
    // 126/134 are themselves BGC L1, so they stay in the pool - it just widens to the whole desk.
86
    // 126/134 are themselves BGC L1, so they stay in the pool - it just widens to the whole desk.
87
    private static final int AI_ASSIGN_FALLBACK = 134;  // Khushbu Saxena - used only if no active BGC L1 resolves
87
    private static final int AI_ASSIGN_FALLBACK = 134;  // Khushbu Saxena - used only if no active BGC L1 resolves
88
 
88
 
-
 
89
    // Ira channels the AI side sends in AiLeadRequest.source, stored on lead.ai_channel.
-
 
90
    private static final String IRA_PHONE = "IRA_PHONE";
-
 
91
    private static final String IRA_WEB = "IRA_WEB";
-
 
92
 
-
 
93
    // AI leads from the Ira toll-free number all go to Meenakshi Mehta, and she is left out of the
-
 
94
    // random pool for every other AI lead.
-
 
95
    private static final int IRA_PHONE_LEAD_ASSIGNEE = 84;
-
 
96
 
89
    @Value("${ai.lead.intake.token:}")
97
    @Value("${ai.lead.intake.token:}")
90
    private String aiLeadIntakeToken;
98
    private String aiLeadIntakeToken;
91
 
99
 
92
    /**
100
    /**
93
     * AI-assistant lead intake, hosted in the fofo app so it deploys via {@code fofo_deploy}. Mirrors the
101
     * AI-assistant lead intake, hosted in the fofo app so it deploys via {@code fofo_deploy}. Mirrors the
Line 153... Line 161...
153
        String recordingUrl = aiLeadRequest.getRecordingUrl();
161
        String recordingUrl = aiLeadRequest.getRecordingUrl();
154
        if (recordingUrl != null && !recordingUrl.trim().isEmpty()) {
162
        if (recordingUrl != null && !recordingUrl.trim().isEmpty()) {
155
            lead.setRecordingUrl(recordingUrl.trim());
163
            lead.setRecordingUrl(recordingUrl.trim());
156
        }
164
        }
157
 
165
 
-
 
166
        // Ira channel: IRA_PHONE from the toll-free number, everything else (IRA_WEB, no source, or an
-
 
167
        // unknown value) is a web lead. A missing source is still accepted, as before.
-
 
168
        String channel = aiLeadRequest.getSource() == null ? "" : aiLeadRequest.getSource().trim().toUpperCase();
-
 
169
        if (!channel.isEmpty() && !IRA_PHONE.equals(channel) && !IRA_WEB.equals(channel)) {
-
 
170
            LOGGER.warn("AI lead intake: unknown source '{}' for mobile {}, treating as web", aiLeadRequest.getSource(), mobile);
-
 
171
        }
-
 
172
        lead.setAiChannel(IRA_PHONE.equals(channel) ? IRA_PHONE : IRA_WEB);
-
 
173
 
158
        // Random active BGC L1 - only on a real create, dedup hits return before this point.
174
        // Only on a real create, dedup hits return before this point.
159
        int assignTo = resolveAiLeadAssignee();
175
        int assignTo = resolveAiLeadAssignee(lead);
160
        lead.setAssignTo(assignTo);
176
        lead.setAssignTo(assignTo);
161
        lead.setAuthId(lead.getAssignTo());
177
        lead.setAuthId(lead.getAssignTo());
162
        lead.setCreatedTimestamp(LocalDateTime.now());
178
        lead.setCreatedTimestamp(LocalDateTime.now());
163
        lead.setUpdatedTimestamp(LocalDateTime.now());
179
        lead.setUpdatedTimestamp(LocalDateTime.now());
164
        // Stamp before the insert: region, owner, stage and an SLA clock. Without it the lead is
180
        // Stamp before the insert: region, owner, stage and an SLA clock. Without it the lead is
Line 195... Line 211...
195
                ProfitMandiConstants.TICKET_CATEGORY_BGC, EscalationType.L1);
211
                ProfitMandiConstants.TICKET_CATEGORY_BGC, EscalationType.L1);
196
 
212
 
197
        List<AuthUser> eligible = bgcL1 == null ? Collections.<AuthUser>emptyList()
213
        List<AuthUser> eligible = bgcL1 == null ? Collections.<AuthUser>emptyList()
198
                : bgcL1.stream()
214
                : bgcL1.stream()
199
                        .filter(x -> Boolean.TRUE.equals(x.isActive()))
215
                        .filter(x -> Boolean.TRUE.equals(x.isActive()))
-
 
216
                        .filter(x -> x.getId() != IRA_PHONE_LEAD_ASSIGNEE)
200
                        .collect(Collectors.toList());
217
                        .collect(Collectors.toList());
201
 
218
 
202
        if (eligible.isEmpty()) {
219
        if (eligible.isEmpty()) {
203
            LOGGER.warn("AI lead intake: no active BGC L1 in cs.position, falling back to auth id {}",
220
            LOGGER.warn("AI lead intake: no active BGC L1 in cs.position, falling back to auth id {}",
204
                    AI_ASSIGN_FALLBACK);
221
                    AI_ASSIGN_FALLBACK);
Line 210... Line 227...
210
        LOGGER.info("AI lead intake: assigned to BGC L1 {} ({}) picked from {} eligible",
227
        LOGGER.info("AI lead intake: assigned to BGC L1 {} ({}) picked from {} eligible",
211
                assignee.getId(), assignee.getFullName(), eligible.size());
228
                assignee.getId(), assignee.getFullName(), eligible.size());
212
        return assignee.getId();
229
        return assignee.getId();
213
    }
230
    }
214
 
231
 
-
 
232
    /** IRA_PHONE / IRA_WEB for AI Assistant leads (any AI lead not from the phone line is web), else null. */
-
 
233
    private String aiChannelOf(Lead lead) {
-
 
234
        if (!AI_LEAD_SOURCE.equals(lead.getSource())) {
-
 
235
            return null;
-
 
236
        }
-
 
237
        return IRA_PHONE.equals(lead.getAiChannel()) ? IRA_PHONE : IRA_WEB;
-
 
238
    }
-
 
239
 
-
 
240
    /** Source as the Lead Dashboard shows it: "AI Assistant (Phone)" / "AI Assistant (Web)" for AI leads. */
-
 
241
    private String displaySource(Lead lead) {
-
 
242
        String channel = aiChannelOf(lead);
-
 
243
        String source = lead.getSource() == null ? "" : lead.getSource();
-
 
244
        return IRA_PHONE.equals(channel) ? source + " (Phone)" : IRA_WEB.equals(channel) ? source + " (Web)" : source;
-
 
245
    }
-
 
246
 
-
 
247
    /** Keeps only AI leads from one channel (IRA_PHONE / IRA_WEB); a blank channel keeps every lead. */
-
 
248
    private List<Lead> filterByAiChannel(List<Lead> leads, String aiChannel) {
-
 
249
        if (aiChannel == null || aiChannel.trim().isEmpty()) {
-
 
250
            return leads;
-
 
251
        }
-
 
252
        return leads.stream().filter(l -> aiChannel.equals(aiChannelOf(l))).collect(Collectors.toList());
-
 
253
    }
-
 
254
 
-
 
255
    /**
-
 
256
     * Ira phone leads go to Meenakshi; every other AI lead to a random active BGC L1. If she is ever
-
 
257
     * deactivated, phone leads fall back to the pool rather than stranding with an inactive owner.
-
 
258
     */
-
 
259
    private int resolveAiLeadAssignee(Lead lead) {
-
 
260
        if (IRA_PHONE.equals(lead.getAiChannel())) {
-
 
261
            AuthUser phoneOwner = authRepository.selectById(IRA_PHONE_LEAD_ASSIGNEE);
-
 
262
            if (phoneOwner != null && Boolean.TRUE.equals(phoneOwner.isActive())) {
-
 
263
                return IRA_PHONE_LEAD_ASSIGNEE;
-
 
264
            }
-
 
265
            LOGGER.warn("AI lead intake: Ira phone owner {} is inactive, using the BGC L1 pool", IRA_PHONE_LEAD_ASSIGNEE);
-
 
266
        }
-
 
267
        return resolveAiLeadAssignee();
-
 
268
    }
-
 
269
 
215
    private ResponseEntity<Map<String, Object>> aiLeadResponse(HttpStatus status, String statusCode,
270
    private ResponseEntity<Map<String, Object>> aiLeadResponse(HttpStatus status, String statusCode,
216
                                                               String statusMessage, String responseStatus, Object response) {
271
                                                               String statusMessage, String responseStatus, Object response) {
217
        Map<String, Object> body = new HashMap<>();
272
        Map<String, Object> body = new HashMap<>();
218
        body.put("statusCode", statusCode);
273
        body.put("statusCode", statusCode);
219
        body.put("statusMessage", statusMessage);
274
        body.put("statusMessage", statusMessage);
Line 341... Line 396...
341
                .anyMatch(p -> p.getEscalationType() == null
396
                .anyMatch(p -> p.getEscalationType() == null
342
                        || !p.getEscalationType().isGreaterThanEqualTo(EscalationType.L4));
397
                        || !p.getEscalationType().isGreaterThanEqualTo(EscalationType.L4));
343
    }
398
    }
344
 
399
 
345
    @RequestMapping(value = "/getOpenLead", method = RequestMethod.GET)
400
    @RequestMapping(value = "/getOpenLead", method = RequestMethod.GET)
346
    public String getOpenLead(HttpServletRequest request, @RequestParam(name = "leadStatus", required = false, defaultValue = "All") List<LeadStatus> leadStatus, @RequestParam(name = "color", required = false, defaultValue = "All") List<String> color, @RequestParam(name = "startDate", required = false) LocalDate startDate, @RequestParam(name = "endDate", required = false) LocalDate endDate, Model model) throws Exception {
401
    public String getOpenLead(HttpServletRequest request, @RequestParam(name = "leadStatus", required = false, defaultValue = "All") List<LeadStatus> leadStatus, @RequestParam(name = "color", required = false, defaultValue = "All") List<String> color, @RequestParam(name = "startDate", required = false) LocalDate startDate, @RequestParam(name = "endDate", required = false) LocalDate endDate, @RequestParam(name = "aiChannel", required = false, defaultValue = "") String aiChannel, Model model) throws Exception {
347
 
402
 
348
 
403
 
349
        LocalDateTime startDateTime = startDate != null ? startDate.atStartOfDay() : null;
404
        LocalDateTime startDateTime = startDate != null ? startDate.atStartOfDay() : null;
350
        LocalDateTime endDateTime = endDate != null ? endDate.atTime(23, 59, 59) : null;
405
        LocalDateTime endDateTime = endDate != null ? endDate.atTime(23, 59, 59) : null;
351
        LOGGER.info("startDateTime: " + startDateTime + ", endDateTime: " + endDateTime);
406
        LOGGER.info("startDateTime: " + startDateTime + ", endDateTime: " + endDateTime);
Line 454... Line 509...
454
            leadCreators.add("daily-sync");
509
            leadCreators.add("daily-sync");
455
            leadCreators.addAll(csService.getAuthUserIds(ProfitMandiConstants.TICKET_CATEGORY_SALES, Arrays.asList(EscalationType.L1, EscalationType.L2)).stream().map(x -> x.getFullName()).collect(Collectors.toList()));
510
            leadCreators.addAll(csService.getAuthUserIds(ProfitMandiConstants.TICKET_CATEGORY_SALES, Arrays.asList(EscalationType.L1, EscalationType.L2)).stream().map(x -> x.getFullName()).collect(Collectors.toList()));
456
            leadCreators.addAll(csService.getAuthUserIds(ProfitMandiConstants.TICKET_CATEGORY_BGC, Arrays.asList(EscalationType.L1, EscalationType.L2, EscalationType.L3)).stream().map(x -> x.getFullName()).collect(Collectors.toList()));
511
            leadCreators.addAll(csService.getAuthUserIds(ProfitMandiConstants.TICKET_CATEGORY_BGC, Arrays.asList(EscalationType.L1, EscalationType.L2, EscalationType.L3)).stream().map(x -> x.getFullName()).collect(Collectors.toList()));
457
            model.addAttribute("leadCreators", leadCreators);
512
            model.addAttribute("leadCreators", leadCreators);
458
        }
513
        }
-
 
514
        leads = filterByAiChannel(leads, aiChannel);
-
 
515
        model.addAttribute("selectedAiChannel", aiChannel);
459
        List<Lead> weekLast = new ArrayList<>();
516
        List<Lead> weekLast = new ArrayList<>();
460
        List<Lead> weekThird = new ArrayList<>();
517
        List<Lead> weekThird = new ArrayList<>();
461
        List<Lead> weekSecond = new ArrayList<>();
518
        List<Lead> weekSecond = new ArrayList<>();
462
        List<Lead> weekFirst = new ArrayList<>();
519
        List<Lead> weekFirst = new ArrayList<>();
463
        LocalDateTime curDate = LocalDate.now().atStartOfDay();
520
        LocalDateTime curDate = LocalDate.now().atStartOfDay();
Line 707... Line 764...
707
    public ResponseEntity<?> getdownloadLeadDataByDate(HttpServletRequest request,
764
    public ResponseEntity<?> getdownloadLeadDataByDate(HttpServletRequest request,
708
                                                       @RequestParam(name = "leadStatus", required = false, defaultValue = "All") List<LeadStatus> leadStatus,
765
                                                       @RequestParam(name = "leadStatus", required = false, defaultValue = "All") List<LeadStatus> leadStatus,
709
                                                       @RequestParam(name = "color", required = false, defaultValue = "All") List<String> color,
766
                                                       @RequestParam(name = "color", required = false, defaultValue = "All") List<String> color,
710
                                                       @RequestParam(name = "startDate", required = false) LocalDate startDate,
767
                                                       @RequestParam(name = "startDate", required = false) LocalDate startDate,
711
                                                       @RequestParam(name = "endDate", required = false) LocalDate endDate,
768
                                                       @RequestParam(name = "endDate", required = false) LocalDate endDate,
-
 
769
                                                       @RequestParam(name = "searchTerm", required = false, defaultValue = "") String searchTerm,
712
                                                       @RequestParam(name = "searchTerm", required = false, defaultValue = "") String searchTerm) throws Exception {
770
                                                       @RequestParam(name = "aiChannel", required = false, defaultValue = "") String aiChannel) throws Exception {
713
 
771
 
714
        LoginDetails fofoDetails = cookiesProcessor.getCookiesObject(request);
772
        LoginDetails fofoDetails = cookiesProcessor.getCookiesObject(request);
715
        String emailId = fofoDetails.getEmailId();
773
        String emailId = fofoDetails.getEmailId();
716
        LocalDateTime startDateTime = startDate != null ? startDate.atStartOfDay() : null;
774
        LocalDateTime startDateTime = startDate != null ? startDate.atStartOfDay() : null;
717
        LocalDateTime endDateTime = endDate != null ? endDate.atTime(23, 59, 59) : null;
775
        LocalDateTime endDateTime = endDate != null ? endDate.atTime(23, 59, 59) : null;
Line 778... Line 836...
778
                    leads.addAll(withinCreatedRange(leadFollowUp, startDateTime, endDateTime));
836
                    leads.addAll(withinCreatedRange(leadFollowUp, startDateTime, endDateTime));
779
                }
837
                }
780
            }
838
            }
781
        }
839
        }
782
 
840
 
-
 
841
        leads = filterByAiChannel(leads, aiChannel);
-
 
842
 
783
        // Build activity map and auth map (SAME AS VIEW ENDPOINT)
843
        // Build activity map and auth map (SAME AS VIEW ENDPOINT)
784
        // Guard both lookups on a non-empty result: an empty list would generate an invalid
844
        // Guard both lookups on a non-empty result: an empty list would generate an invalid
785
        // "IN ()" and 500 (e.g. a date range with no leads), so an empty range yields an empty CSV.
845
        // "IN ()" and 500 (e.g. a date range with no leads), so an empty range yields an empty CSV.
786
        Map<Integer, AuthUser> authIdAndAuthUserMap = new HashMap<>();
846
        Map<Integer, AuthUser> authIdAndAuthUserMap = new HashMap<>();
787
        if (!leads.isEmpty()) {
847
        if (!leads.isEmpty()) {
Line 797... Line 857...
797
        List<List<?>> rows = new ArrayList<>();
857
        List<List<?>> rows = new ArrayList<>();
798
        // Mirror the on-screen table's search box: keep only leads whose exported fields contain the term,
858
        // Mirror the on-screen table's search box: keep only leads whose exported fields contain the term,
799
        // so the CSV matches what the user is looking at.
859
        // so the CSV matches what the user is looking at.
800
        if (searchTerm != null && !searchTerm.trim().isEmpty()) {
860
        if (searchTerm != null && !searchTerm.trim().isEmpty()) {
801
            String term = searchTerm.trim().toLowerCase();
861
            String term = searchTerm.trim().toLowerCase();
802
            leads = leads.stream().filter(l -> Stream.of(l.getId(), l.getSource(), l.getFullName(), l.getOutLetName(),
862
            leads = leads.stream().filter(l -> Stream.of(l.getId(), displaySource(l), l.getFullName(), l.getOutLetName(),
803
                            l.getLeadMobile(), l.getAddress(), l.getCity(), l.getState(), l.getCreatedBy(), l.getStatus(), l.getColor())
863
                            l.getLeadMobile(), l.getAddress(), l.getCity(), l.getState(), l.getCreatedBy(), l.getStatus(), l.getColor())
804
                    .anyMatch(v -> v != null && String.valueOf(v).toLowerCase().contains(term)))
864
                    .anyMatch(v -> v != null && String.valueOf(v).toLowerCase().contains(term)))
805
                    .collect(Collectors.toList());
865
                    .collect(Collectors.toList());
806
        }
866
        }
807
 
867
 
Line 824... Line 884...
824
                assignTo = (user.getFirstName() + " " + user.getLastName()).trim();
884
                assignTo = (user.getFirstName() + " " + user.getLastName()).trim();
825
            }
885
            }
826
 
886
 
827
            rows.add(Arrays.asList(
887
            rows.add(Arrays.asList(
828
                    lead.getId(),
888
                    lead.getId(),
829
                    lead.getSource(),
889
                    displaySource(lead),
830
                    lead.getFullName(),
890
                    lead.getFullName(),
831
                    outLetName,
891
                    outLetName,
832
                    potential,
892
                    potential,
833
                    lead.getLeadMobile(),
893
                    lead.getLeadMobile(),
834
                    lead.getAddress() != null ? lead.getAddress() : "",
894
                    lead.getAddress() != null ? lead.getAddress() : "",