Subversion Repositories SmartDukaan

Rev

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

Rev 37324 Rev 37355
Line 78... Line 78...
78
    private static final Logger LOGGER = LogManager.getLogger(LeadController.class);
78
    private static final Logger LOGGER = LogManager.getLogger(LeadController.class);
79
 
79
 
80
    /** Source / createdBy label for leads captured by the AI assistant (chat + voice). */
80
    /** Source / createdBy label for leads captured by the AI assistant (chat + voice). */
81
    private static final String AI_LEAD_SOURCE = "AI Assistant";
81
    private static final String AI_LEAD_SOURCE = "AI Assistant";
82
 
82
 
83
    // AI leads are auto-assigned round-robin (one by one) between these two auth users (was hardcoded 53 / Shankar Mushran).
83
    // AI leads are auto-assigned to a random active BGC L1 (the lead desk). Previously a hardcoded
84
    private static final int AI_ASSIGN_KHUSHBU = 134;  // Khushbu Saxena (khushbu@smartdukaan.com)
84
    // round-robin between Khushbu (134) and Archana (126); before that a fixed 53 / Shankar Mushran.
85
    private static final int AI_ASSIGN_ARCHANA = 126;  // Archana Ramola (archana@smartdukaan.com)
85
    // 126/134 are themselves BGC L1, so they stay in the pool - it just widens to the whole desk.
86
    private final java.util.concurrent.atomic.AtomicInteger aiAssignToggle = new java.util.concurrent.atomic.AtomicInteger(0);
86
    private static final int AI_ASSIGN_FALLBACK = 134;  // Khushbu Saxena - used only if no active BGC L1 resolves
87
 
87
 
88
    @Value("${ai.lead.intake.token:}")
88
    @Value("${ai.lead.intake.token:}")
89
    private String aiLeadIntakeToken;
89
    private String aiLeadIntakeToken;
90
 
90
 
91
    /**
91
    /**
Line 152... Line 152...
152
        String recordingUrl = aiLeadRequest.getRecordingUrl();
152
        String recordingUrl = aiLeadRequest.getRecordingUrl();
153
        if (recordingUrl != null && !recordingUrl.trim().isEmpty()) {
153
        if (recordingUrl != null && !recordingUrl.trim().isEmpty()) {
154
            lead.setRecordingUrl(recordingUrl.trim());
154
            lead.setRecordingUrl(recordingUrl.trim());
155
        }
155
        }
156
 
156
 
157
        // Round-robin between Khushbu and Archana, one lead at a time (toggle only advances on a real create, not dedup hits).
157
        // Random active BGC L1 - only on a real create, dedup hits return before this point.
158
        int assignTo = (aiAssignToggle.getAndIncrement() % 2 == 0) ? AI_ASSIGN_KHUSHBU : AI_ASSIGN_ARCHANA;
158
        int assignTo = resolveAiLeadAssignee();
159
        lead.setAssignTo(assignTo);
159
        lead.setAssignTo(assignTo);
160
        lead.setAuthId(lead.getAssignTo());
160
        lead.setAuthId(lead.getAssignTo());
161
        lead.setCreatedTimestamp(LocalDateTime.now());
161
        lead.setCreatedTimestamp(LocalDateTime.now());
162
        lead.setUpdatedTimestamp(LocalDateTime.now());
162
        lead.setUpdatedTimestamp(LocalDateTime.now());
163
        leadRepository.persist(lead);
163
        leadRepository.persist(lead);
164
 
164
 
165
        LOGGER.info("AI lead intake: created lead id={} for mobile {}, assigned to authId={}", lead.getId(), mobile, assignTo);
165
        LOGGER.info("AI lead intake: created lead id={} for mobile {}, assigned to authId={}", lead.getId(), mobile, assignTo);
166
        return aiLeadResponse(HttpStatus.OK, "200", "OK", "SUCCESS", lead.getId());
166
        return aiLeadResponse(HttpStatus.OK, "200", "OK", "SUCCESS", lead.getId());
167
    }
167
    }
168
 
168
 
-
 
169
    /**
-
 
170
     * Picks the owner for an incoming AI lead: a uniformly random <b>active</b> BGC L1 from
-
 
171
     * cs.position (category {@link ProfitMandiConstants#TICKET_CATEGORY_BGC}, escalation L1).
-
 
172
     * <p>
-
 
173
     * BGC is the desk that works AI / inbound leads before they reach field sales, and BGC L1-L3 is
-
 
174
     * already one of the groups offered in the Lead Management "Assign To" list, so a BGC L1 owner is
-
 
175
     * a valid assignee the team can see and reassign. Field Sales (category
-
 
176
     * {@link ProfitMandiConstants#TICKET_CATEGORY_SALES}) is deliberately NOT the pool.
-
 
177
     * <p>
-
 
178
     * Inactive users are filtered out because {@code getAuthUserByCategoryId(int, EscalationType)}
-
 
179
     * does not filter on active, and an inactive owner would strand the lead. Ids are already
-
 
180
     * distinct per user, so holding several regional positions does not raise a person's odds.
-
 
181
     */
-
 
182
    private int resolveAiLeadAssignee() {
-
 
183
        List<AuthUser> bgcL1 = csService.getAuthUserByCategoryId(
-
 
184
                ProfitMandiConstants.TICKET_CATEGORY_BGC, EscalationType.L1);
-
 
185
 
-
 
186
        List<AuthUser> eligible = bgcL1 == null ? Collections.<AuthUser>emptyList()
-
 
187
                : bgcL1.stream()
-
 
188
                        .filter(x -> Boolean.TRUE.equals(x.isActive()))
-
 
189
                        .collect(Collectors.toList());
-
 
190
 
-
 
191
        if (eligible.isEmpty()) {
-
 
192
            LOGGER.warn("AI lead intake: no active BGC L1 in cs.position, falling back to auth id {}",
-
 
193
                    AI_ASSIGN_FALLBACK);
-
 
194
            return AI_ASSIGN_FALLBACK;
-
 
195
        }
-
 
196
 
-
 
197
        AuthUser assignee = eligible.get(
-
 
198
                java.util.concurrent.ThreadLocalRandom.current().nextInt(eligible.size()));
-
 
199
        LOGGER.info("AI lead intake: assigned to BGC L1 {} ({}) picked from {} eligible",
-
 
200
                assignee.getId(), assignee.getFullName(), eligible.size());
-
 
201
        return assignee.getId();
-
 
202
    }
-
 
203
 
169
    private ResponseEntity<Map<String, Object>> aiLeadResponse(HttpStatus status, String statusCode,
204
    private ResponseEntity<Map<String, Object>> aiLeadResponse(HttpStatus status, String statusCode,
170
                                                               String statusMessage, String responseStatus, Object response) {
205
                                                               String statusMessage, String responseStatus, Object response) {
171
        Map<String, Object> body = new HashMap<>();
206
        Map<String, Object> body = new HashMap<>();
172
        body.put("statusCode", statusCode);
207
        body.put("statusCode", statusCode);
173
        body.put("statusMessage", statusMessage);
208
        body.put("statusMessage", statusMessage);