Subversion Repositories SmartDukaan

Rev

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

Rev Author Line No. Line
33917 ranu 1
package com.spice.profitmandi.service;
2
 
35631 ranu 3
import com.spice.profitmandi.common.enumuration.ActivationType;
4
import com.spice.profitmandi.common.exception.ProfitMandiBusinessException;
5
import com.spice.profitmandi.common.model.CustomRetailer;
33917 ranu 6
import com.spice.profitmandi.common.model.ProfitMandiConstants;
35631 ranu 7
import com.spice.profitmandi.dao.entity.auth.AuthUser;
8
import com.spice.profitmandi.dao.entity.auth.PartnerCollectionRemark;
9
import com.spice.profitmandi.dao.entity.auth.RbmCallSequenceLog;
35761 ranu 10
import com.spice.profitmandi.dao.entity.cs.AgentCallLog;
35631 ranu 11
import com.spice.profitmandi.dao.entity.cs.Position;
12
import com.spice.profitmandi.dao.entity.cs.Ticket;
13
import com.spice.profitmandi.dao.entity.fofo.FofoStore;
34397 ranu 14
import com.spice.profitmandi.dao.entity.fofo.MonthlyTarget;
35759 ranu 15
import com.spice.profitmandi.dao.entity.fofo.RetailerContact;
33985 ranu 16
import com.spice.profitmandi.dao.entity.inventory.RbmAchievements;
33926 ranu 17
import com.spice.profitmandi.dao.entity.inventory.RbmTargets;
35759 ranu 18
import com.spice.profitmandi.dao.entity.user.Address;
35631 ranu 19
import com.spice.profitmandi.dao.enumuration.auth.CollectionRemark;
20
import com.spice.profitmandi.dao.enumuration.cs.EscalationType;
33917 ranu 21
import com.spice.profitmandi.dao.model.*;
35631 ranu 22
import com.spice.profitmandi.dao.repository.auth.AuthRepository;
23
import com.spice.profitmandi.dao.repository.auth.PartnerCollectionRemarkRepository;
24
import com.spice.profitmandi.dao.repository.auth.RbmCallSequenceLogRepository;
33985 ranu 25
import com.spice.profitmandi.dao.repository.catalog.RbmAchievementsRepository;
33926 ranu 26
import com.spice.profitmandi.dao.repository.catalog.RbmTargetsRepository;
35631 ranu 27
import com.spice.profitmandi.dao.repository.cs.CsService;
28
import com.spice.profitmandi.dao.repository.cs.PositionRepository;
29
import com.spice.profitmandi.dao.repository.dtr.FofoStoreRepository;
35759 ranu 30
import com.spice.profitmandi.dao.repository.dtr.RetailerContactRepository;
34397 ranu 31
import com.spice.profitmandi.dao.repository.fofo.MonthlyTargetRepository;
34880 ranu 32
import com.spice.profitmandi.dao.repository.logistics.PublicHolidaysRepository;
35631 ranu 33
import com.spice.profitmandi.dao.repository.transaction.LoanRepository;
34397 ranu 34
import com.spice.profitmandi.dao.repository.transaction.OrderRepository;
35759 ranu 35
import com.spice.profitmandi.dao.repository.user.AddressRepository;
35631 ranu 36
import com.spice.profitmandi.service.user.RetailerService;
33917 ranu 37
import org.apache.logging.log4j.LogManager;
38
import org.apache.logging.log4j.Logger;
39
import org.hibernate.Session;
40
import org.hibernate.SessionFactory;
41
import org.hibernate.query.NativeQuery;
42
import org.springframework.beans.factory.annotation.Autowired;
43
import org.springframework.stereotype.Component;
44
 
45
import javax.persistence.TypedQuery;
34880 ranu 46
import java.time.*;
35631 ranu 47
import java.time.format.DateTimeFormatter;
34880 ranu 48
import java.time.temporal.ChronoUnit;
35631 ranu 49
import java.util.*;
33997 ranu 50
import java.util.stream.Collectors;
33917 ranu 51
 
52
@Component
53
public class RbmTargetServiceImpl implements RbmTargetService {
54
    private static final Logger LOGGER = LogManager.getLogger(RbmTargetServiceImpl.class);
55
 
56
    @Autowired
57
    SessionFactory sessionFactory;
58
 
33926 ranu 59
    @Autowired
60
    RbmTargetsRepository rbmTargetsRepository;
33917 ranu 61
 
33985 ranu 62
    @Autowired
63
    RbmAchievementsRepository rbmAchievementsRepository;
64
 
34397 ranu 65
    @Autowired
66
    MonthlyTargetRepository monthlyTargetRepository;
67
 
34880 ranu 68
    @Autowired
69
    PublicHolidaysRepository publicHolidaysRepository;
70
 
35631 ranu 71
    @Autowired
72
    RetailerService retailerService;
73
 
33917 ranu 74
    @Override
75
    public List<WarehouseRbmTargetModel> getWarehouseWiseRbmMonthlyTarget() {
76
        Session session = sessionFactory.getCurrentSession();
77
        final TypedQuery<WarehouseRbmTargetModel> typedQuerySimilar = session.createNamedQuery("RbmTarget.getWarehouseWiseMonthlyTarget", WarehouseRbmTargetModel.class);
78
 
79
        return typedQuerySimilar.getResultList();
80
 
81
    }
82
 
83
    @Override
84
    public List<MTDAchievedTargetModel> getDateWiseAchievedTargetOfRbm(LocalDate startDate, LocalDate endDate) {
85
        Session session = sessionFactory.getCurrentSession();
86
        final TypedQuery<MTDAchievedTargetModel> typedQuerySimilar = session.createNamedQuery("RbmTarget.getRbmAchievedMonthlyTarget", MTDAchievedTargetModel.class);
87
        typedQuerySimilar.setParameter("startDate", startDate);
88
        typedQuerySimilar.setParameter("endDate", endDate);
89
        return typedQuerySimilar.getResultList();
90
 
91
    }
92
 
93
    @Override
34055 ranu 94
    public List<TodayAchievedMovementModel> getMovementWiseAchievementByDate(LocalDate startDate, LocalDate endDate) {
33917 ranu 95
        LOGGER.info("start date {}, end date {}", startDate, endDate);
96
        Session session = sessionFactory.getCurrentSession();
34055 ranu 97
        final TypedQuery<TodayAchievedMovementModel> typedQuerySimilar = session.createNamedQuery("RBMTarget.TodayAchivementByMovement", TodayAchievedMovementModel.class);
33917 ranu 98
        typedQuerySimilar.setParameter("startDate", startDate);
99
        typedQuerySimilar.setParameter("endDate", endDate);
100
        return typedQuerySimilar.getResultList();
101
 
102
    }
103
 
104
    @Override
105
    public List<WarehouseMobileStockByMovementModel> getWarehouseMobileStockByMovement() {
106
        Session session = sessionFactory.getCurrentSession();
107
        final TypedQuery<WarehouseMobileStockByMovementModel> typedQuerySimilar = session.createNamedQuery("WarehouseStock.MovementWiseMobileStock", WarehouseMobileStockByMovementModel.class);
108
 
109
        return typedQuerySimilar.getResultList();
110
 
111
    }
112
 
33991 ranu 113
    @Override
114
    public List<SoldCatalogsReportModel> getCatalogSoldReport(LocalDate startDate, LocalDate endDate) {
115
        Session session = sessionFactory.getCurrentSession();
116
        final TypedQuery<SoldCatalogsReportModel> typedQuerySimilar = session.createNamedQuery("CatalogsReport.SoldCatalogsReport", SoldCatalogsReportModel.class);
117
        typedQuerySimilar.setParameter("startDate", startDate);
118
        typedQuerySimilar.setParameter("endDate", endDate);
119
        return typedQuerySimilar.getResultList();
33917 ranu 120
 
33991 ranu 121
    }
122
 
123
 
33917 ranu 124
    public int getWorkingDaysCount(LocalDate startDate) {
125
        Session session = sessionFactory.getCurrentSession();
126
 
127
        // Convert the LocalDate to a format MySQL can interpret
128
        String startDateString = startDate.toString();
129
 
130
        final NativeQuery<?> nativeQuery = session.createNativeQuery(
131
                "SELECT (DATEDIFF(LAST_DAY(:startDate), :startDate) + 1) " +
132
                        " - (FLOOR((DATEDIFF(LAST_DAY(:startDate), :startDate) + (WEEKDAY(:startDate) + 1)) / 7)) " +
133
                        " - (SELECT COUNT(*) " +
134
                        " FROM logistics.publicholidays " +
135
                        " WHERE date BETWEEN :startDate AND LAST_DAY(:startDate) " +
136
                        " AND WEEKDAY(date) != 6) AS working_days"
137
        );
138
 
139
        // Set the start date parameter for each placeholder
140
        nativeQuery.setParameter("startDate", startDateString);
141
 
142
        Object result = nativeQuery.getSingleResult();
143
        return result != null ? ((Number) result).intValue() : 0;
144
    }
145
 
146
 
35044 ranu 147
 
33917 ranu 148
    @Override
149
    public List<RbmArrViewModel> getRbmTodayArr() throws Exception {
150
        LocalDate todayDate = LocalDate.now();
151
        return getRbmTodayArr(todayDate);
152
    }
153
 
154
    @Override
33997 ranu 155
    public List<RbmTargetAndAchievementsModel> getRbmTargetsAndAchievemnts(LocalDate startDate, LocalDate endDate) {
156
 
34002 ranu 157
        List<RbmTargetsModel> rbmTargetsList = rbmTargetsRepository.selectTargetsModelListByDates(startDate.atStartOfDay(), endDate.atTime(LocalTime.MAX));
33997 ranu 158
 
159
        LOGGER.info("rbmTargetsList {}", rbmTargetsList);
160
        // Group Targtes by RBM and Warehouse
34002 ranu 161
        Map<String, RbmTargetsModel> targetsMap = rbmTargetsList.stream()
33997 ranu 162
                .collect(Collectors.toMap(
163
                        a -> a.getRbmAuthId() + "-" + a.getWarehouseId(),
164
                        a -> a,
165
                        (a1, a2) -> mergeTargets(a1, a2) // Handle duplicates by merging
166
                ));
167
 
168
 
34002 ranu 169
        List<RbmAchievementsModel> rbmAchievements = rbmAchievementsRepository.selectAchievementsModelListByDates(startDate.atStartOfDay(), endDate.atTime(LocalTime.MAX));
33997 ranu 170
        LOGGER.info("rbmTargetsList {}", rbmAchievements);
171
        // Group achievements by RBM and Warehouse
34002 ranu 172
        Map<String, RbmAchievementsModel> achievementMap = rbmAchievements.stream()
33997 ranu 173
                .collect(Collectors.toMap(
174
                        a -> a.getRbmAuthId() + "-" + a.getWarehouseId(),
175
                        a -> a,
176
                        (a1, a2) -> mergeAchievements(a1, a2) // Handle duplicates by merging
177
                ));
178
 
179
        return targetsMap.keySet().stream()
180
                .map(key -> {
181
                    String[] parts = key.split("-");
182
                    int rbmAuthId = Integer.parseInt(parts[0]);
183
                    int warehouseId = Integer.parseInt(parts[1]);
184
 
34002 ranu 185
                    RbmTargetsModel target = targetsMap.get(key);
186
                    RbmAchievementsModel achievement = achievementMap.getOrDefault(key, new RbmAchievementsModel());
33997 ranu 187
 
188
                    RbmTargetAndAchievementsModel model = new RbmTargetAndAchievementsModel();
189
                    model.setAuthId(rbmAuthId);
190
                    model.setRbmName(target.getRbmName());
191
                    model.setWarehouseName(ProfitMandiConstants.WAREHOUSE_MAP.getOrDefault(warehouseId, "Unknown"));
192
 
193
                    // Set target values
34006 ranu 194
                    model.setHidTarget((long) target.getHidTarget());
34098 ranu 195
                    model.setRunningTarget((long) target.getRunningTarget());
34006 ranu 196
                    model.setFastMovingTarget((long) target.getFastMovingTarget());
197
                    model.setSlowMovingTarget((long) target.getSlowMovingTarget());
198
                    model.setOtherMovingTarget((long) target.getOtherTarget());
33997 ranu 199
 
200
                    // Set achievement values
34006 ranu 201
                    model.setAchievedHid((long) achievement.getAchievedHidTarget());
34098 ranu 202
                    model.setAchievedRunning((long) achievement.getAchievedRunningTarget());
34006 ranu 203
                    model.setAchievedFastMoving((long) achievement.getAchievedFastMovingTarget());
204
                    model.setAchievedSlowMoving((long) achievement.getAchievedSlowMovingTarget());
205
                    model.setAchievedOtherMoving((long) achievement.getAchievedOtherTarget());
33997 ranu 206
 
207
                    model.setTotalTarget(
34006 ranu 208
                            (long) target.getHidTarget() +
34098 ranu 209
                                    (long) target.getRunningTarget() +
34006 ranu 210
                                    (long) target.getFastMovingTarget() +
211
                                    (long) target.getSlowMovingTarget() +
212
                                    (long) target.getOtherTarget()
33997 ranu 213
                    );
214
                    model.setTotalAchievemnt(
34006 ranu 215
                            (long) achievement.getAchievedHidTarget() +
34098 ranu 216
                                    (long) achievement.getAchievedRunningTarget() +
34006 ranu 217
                                    (long) achievement.getAchievedFastMovingTarget() +
218
                                    (long) achievement.getAchievedSlowMovingTarget() +
219
                                    (long) achievement.getAchievedOtherTarget()
33997 ranu 220
                    );
221
 
222
                    return model;
223
                })
224
                .collect(Collectors.toList());
225
 
226
    }
227
 
34002 ranu 228
    private RbmTargetsModel mergeTargets(RbmTargetsModel a1, RbmTargetsModel a2) {
33997 ranu 229
 
230
        // Merge logic for achievements (aggregate the target and achieved values)
34006 ranu 231
        a1.setHidTarget((a1.getHidTarget()) +
232
                (a2.getHidTarget()));
33997 ranu 233
 
34006 ranu 234
        a1.setFastMovingTarget((a1.getFastMovingTarget()) +
235
                (a2.getFastMovingTarget()));
33997 ranu 236
 
34006 ranu 237
        a1.setSlowMovingTarget((a1.getSlowMovingTarget()) +
238
                (a2.getSlowMovingTarget()));
33997 ranu 239
 
34098 ranu 240
        a1.setRunningTarget((a1.getRunningTarget()) +
241
                (a2.getRunningTarget()));
33997 ranu 242
 
34006 ranu 243
        a1.setOtherTarget((a1.getOtherTarget()) +
244
                (a2.getOtherTarget()));
33997 ranu 245
        return a1;
246
    }
247
 
34002 ranu 248
    private RbmAchievementsModel mergeAchievements(RbmAchievementsModel a1, RbmAchievementsModel a2) {
33997 ranu 249
        // Merge logic for achievements (aggregate the target and achieved values)
34006 ranu 250
        a1.setAchievedHidTarget((a1.getAchievedHidTarget()) +
251
                (a2.getAchievedHidTarget()));
33997 ranu 252
 
34098 ranu 253
        a1.setAchievedRunningTarget((a1.getAchievedRunningTarget()) +
254
                (a2.getAchievedRunningTarget()));
33997 ranu 255
 
34006 ranu 256
        a1.setAchievedFastMovingTarget((a1.getAchievedFastMovingTarget()) +
257
                (a2.getAchievedFastMovingTarget()));
33997 ranu 258
 
34006 ranu 259
        a1.setAchievedSlowMovingTarget((a1.getAchievedSlowMovingTarget()) +
260
                (a2.getAchievedSlowMovingTarget()));
33997 ranu 261
 
34006 ranu 262
        a1.setAchievedOtherTarget((a1.getAchievedOtherTarget()) +
263
                (a2.getAchievedOtherTarget()));
33997 ranu 264
 
265
        return a1;
266
    }
34002 ranu 267
 
33997 ranu 268
    @Override
33917 ranu 269
    public List<RbmArrViewModel> getRbmTodayArr(LocalDate todayDate) throws Exception {
270
 
271
        LocalDate startDateOfMonthDay1 = LocalDate.now().withDayOfMonth(1);
272
 
34289 ranu 273
        List<WarehouseRbmTargetModel> warehouseRbmTargetModelList = this.getWarehouseWiseRbmMonthlyTarget();
274
        List<WarehouseRbmTargetModel> warehouseRbmTargetModels = warehouseRbmTargetModelList.stream().filter(x -> x.getMonthlyTarget() > 0).collect(Collectors.toList());
275
        LOGGER.info("warehouseRbmTargetModels {}", warehouseRbmTargetModels);
276
        List<TodayAchievedMovementModel> todayAchievedMovementModels = getMovementWiseAchievementByDate(todayDate, todayDate.plusDays(1));
33917 ranu 277
 
278
        List<MTDAchievedTargetModel> mtdAchievedTargetModels = getDateWiseAchievedTargetOfRbm(startDateOfMonthDay1, todayDate);
279
 
35044 ranu 280
        int remainingWorkingDaysCount = (int) getRemainingDaysInMonth(todayDate);
33917 ranu 281
 
33926 ranu 282
        List<RbmTargets> todayRbmTargetsList = rbmTargetsRepository.selectTargetsByDates(todayDate.atStartOfDay(), todayDate.atTime(LocalTime.MAX));
33917 ranu 283
 
33926 ranu 284
        LOGGER.info("todayRbmTargetsList {}", todayRbmTargetsList);
33917 ranu 285
 
286
        List<RbmArrViewModel> rbmArrViewModels = new ArrayList<>();
287
 
34028 ranu 288
        if (!todayRbmTargetsList.isEmpty()) {
33917 ranu 289
 
35454 amit 290
            // OPTIMIZED: Pre-build maps for O(1) lookup instead of O(n) filter in each iteration
291
            // Map key: "authId-warehouseId"
292
            Map<String, Double> mtdAchievedMap = mtdAchievedTargetModels.stream()
293
                    .collect(Collectors.groupingBy(
294
                            x -> x.getAuthId() + "-" + x.getWarehouseId(),
295
                            Collectors.summingDouble(MTDAchievedTargetModel::getAcheivedMonthlyTarget)
296
                    ));
297
 
298
            Map<String, TodayAchievedMovementModel> todayAchievedMap = todayAchievedMovementModels.stream()
299
                    .collect(Collectors.toMap(
300
                            x -> x.getAuthId() + "-" + x.getWarehouseId(),
301
                            x -> x,
302
                            (a, b) -> a
303
                    ));
304
 
305
            Map<String, RbmTargets> todayRbmTargetsMap = todayRbmTargetsList.stream()
306
                    .collect(Collectors.toMap(
307
                            x -> x.getRbmAuthId() + "-" + x.getWarehouseId(),
308
                            x -> x,
309
                            (a, b) -> a
310
                    ));
311
 
34028 ranu 312
            for (WarehouseRbmTargetModel rbmTarget : warehouseRbmTargetModels) {
33917 ranu 313
 
35454 amit 314
                String lookupKey = rbmTarget.getAuthId() + "-" + rbmTarget.getWarehouseId();
315
 
34028 ranu 316
                float monthlyTarget = rbmTarget.getMonthlyTarget();
35454 amit 317
                float achievedSoFar = mtdAchievedMap.getOrDefault(lookupKey, 0.0).floatValue();
33917 ranu 318
 
34028 ranu 319
                float remainingTarget = monthlyTarget - achievedSoFar;
33953 ranu 320
 
34028 ranu 321
                float todayTarget = (remainingWorkingDaysCount > 0 && remainingTarget > 0) ? remainingTarget / remainingWorkingDaysCount : 0;
322
 
33926 ranu 323
                String warehouseName = ProfitMandiConstants.WAREHOUSE_MAP.getOrDefault(rbmTarget.getWarehouseId(), "Unknown");
33917 ranu 324
 
34288 ranu 325
                LOGGER.info("rbmTarget ==== {}", rbmTarget);
34281 ranu 326
 
35454 amit 327
                TodayAchievedMovementModel todayAchievedMovementModel = todayAchievedMap.get(lookupKey);
34283 ranu 328
 
35454 amit 329
                RbmTargets todayRbmTargets = todayRbmTargetsMap.get(lookupKey);
34285 ranu 330
 
35454 amit 331
                if (todayRbmTargets != null) {
34034 ranu 332
                    LOGGER.info("todayRbmTargets {}", todayRbmTargets);
333
                    RbmArrViewModel viewModel = new RbmArrViewModel();
33917 ranu 334
 
34034 ranu 335
                    viewModel.setAuthId(rbmTarget.getAuthId());
336
                    viewModel.setRbmName(rbmTarget.getRbmName());
337
                    viewModel.setWarehouseName(warehouseName);
338
                    viewModel.setTodayTarget(Math.round(todayTarget));
339
                    viewModel.setMonthlyTarget(Math.round(monthlyTarget));
340
                    viewModel.setMtdAchievedTarget(Math.round(achievedSoFar));
33926 ranu 341
 
34034 ranu 342
                    viewModel.setTodayHidTarget(Math.round((todayRbmTargets.getHidTarget())));
343
                    viewModel.setTodayFastMovingTarget(Math.round(todayRbmTargets.getFastMovingTarget()));
34098 ranu 344
                    viewModel.setTodaySlowMovingTarget(Math.round(todayRbmTargets.getSlowMovingtarget()));
345
                    viewModel.setTodayRunningTarget(Math.round(todayRbmTargets.getRunningtarget()));
34034 ranu 346
                    viewModel.setTodayOtherMovingTarget(Math.round(todayRbmTargets.getOtherTarget()));
33926 ranu 347
 
34283 ranu 348
                    if (todayAchievedMovementModel != null) {
349
                        viewModel.setTodayAchievedHidTarget(Math.round(todayAchievedMovementModel.getHidBilled()));
350
                        viewModel.setTodayAchievedFastMovingTarget(Math.round(todayAchievedMovementModel.getFastMovingBilled()));
351
                        viewModel.setTodayAchievedSlowMovingTarget(Math.round(todayAchievedMovementModel.getSlowMovinBilled()));
352
                        viewModel.setTodayAchievedRunningTarget(Math.round(todayAchievedMovementModel.getRunningBilled()));
353
                        viewModel.setTodayAchievedOtherMovingTarget(Math.round(todayAchievedMovementModel.getOtherBilled()));
354
                        viewModel.setTotalAchievedTarget(Math.round(todayAchievedMovementModel.getHidBilled() + todayAchievedMovementModel.getFastMovingBilled() + todayAchievedMovementModel.getSlowMovinBilled() + todayAchievedMovementModel.getRunningBilled() + todayAchievedMovementModel.getOtherBilled()));
355
                    } else {
356
                        viewModel.setTodayAchievedHidTarget(0);
357
                        viewModel.setTodayAchievedFastMovingTarget(0);
358
                        viewModel.setTodayAchievedSlowMovingTarget(0);
359
                        viewModel.setTodayAchievedRunningTarget(0);
360
                        viewModel.setTodayAchievedOtherMovingTarget(0);
361
                        viewModel.setTotalAchievedTarget(0);
362
                    }
34034 ranu 363
                    rbmArrViewModels.add(viewModel);
364
                } else {
365
                    LOGGER.info("No matching RbmTargets found for AuthId: {} and rbmname {} and WarehouseId: {}", rbmTarget.getAuthId(), rbmTarget.getRbmName(), rbmTarget.getWarehouseId());
366
                }
33917 ranu 367
 
34034 ranu 368
 
369
 
34028 ranu 370
            }
33917 ranu 371
        }
372
 
373
        LOGGER.info("rbmArrViewModels {}", rbmArrViewModels);
374
        return rbmArrViewModels;
375
    }
376
 
33926 ranu 377
    @Override
378
    public void setMovementWiseRbmTargets() {
379
        LocalDate todayDate = LocalDate.now();
33917 ranu 380
 
33926 ranu 381
        LocalDate startDateOfMonthDay1 = LocalDate.now().withDayOfMonth(1);
382
 
383
        List<WarehouseRbmTargetModel> warehouseRbmTargetModels = this.getWarehouseWiseRbmMonthlyTarget();
384
 
385
        List<MTDAchievedTargetModel> mtdAchievedTargetModels = getDateWiseAchievedTargetOfRbm(startDateOfMonthDay1, todayDate);
386
 
387
 
35044 ranu 388
        int remainingWorkingDaysCount = (int) getRemainingDaysInMonth(todayDate);
33926 ranu 389
 
390
        List<WarehouseMobileStockByMovementModel> warehouseMobileStockByMovementModels = getWarehouseMobileStockByMovement();
391
 
35454 amit 392
        // OPTIMIZED: Pre-build maps for O(1) lookup instead of O(n) filter in each iteration
393
        Map<String, Double> mtdAchievedMap = mtdAchievedTargetModels.stream()
394
                .collect(Collectors.groupingBy(
395
                        x -> x.getAuthId() + "-" + x.getWarehouseId(),
396
                        Collectors.summingDouble(MTDAchievedTargetModel::getAcheivedMonthlyTarget)
397
                ));
398
 
399
        Map<Integer, WarehouseMobileStockByMovementModel> warehouseStockMap = warehouseMobileStockByMovementModels.stream()
400
                .collect(Collectors.toMap(
401
                        WarehouseMobileStockByMovementModel::getWarehouseId,
402
                        x -> x,
403
                        (a, b) -> a
404
                ));
405
 
33926 ranu 406
        for (WarehouseRbmTargetModel rbmTarget : warehouseRbmTargetModels) {
407
 
35454 amit 408
            String lookupKey = rbmTarget.getAuthId() + "-" + rbmTarget.getWarehouseId();
409
 
33926 ranu 410
            float monthlyTarget = rbmTarget.getMonthlyTarget();
35454 amit 411
            float achievedSoFar = mtdAchievedMap.getOrDefault(lookupKey, 0.0).floatValue();
33926 ranu 412
 
413
 
414
            float remainingTarget = monthlyTarget - achievedSoFar;
33953 ranu 415
            LOGGER.info("remainingTarget {}", remainingTarget);
33926 ranu 416
 
33953 ranu 417
            float todayTarget = (remainingWorkingDaysCount > 0 && remainingTarget > 0) ? remainingTarget / remainingWorkingDaysCount : 0;
418
            LOGGER.info("todayTarget {}", todayTarget);
419
 
33926 ranu 420
            // Get the warehouse stock data
35454 amit 421
            WarehouseMobileStockByMovementModel warehouseMobileStockByMovementModel = warehouseStockMap.get(rbmTarget.getWarehouseId());
33926 ranu 422
 
423
 
424
            if (warehouseMobileStockByMovementModel != null) {
425
 
426
                // Total stock value for this warehouse
427
                float totalStockValue = warehouseMobileStockByMovementModel.getTotalAvailabilityPrice();
428
 
429
                // Calculate target allocation based on stock value proportion
430
                float hidTarget = (warehouseMobileStockByMovementModel.getTotalHidCatalogPrice() / totalStockValue) * todayTarget;
431
                float fastMovingTarget = (warehouseMobileStockByMovementModel.getTotalFastMovingCatalogPrice() / totalStockValue) * todayTarget;
432
                float slowMovingTarget = (warehouseMobileStockByMovementModel.getTotalSlowMovingCatalogPrice() / totalStockValue) * todayTarget;
34098 ranu 433
                float runningTarget = (warehouseMobileStockByMovementModel.getTotalRunningCatalogPrice() / totalStockValue) * todayTarget;
33926 ranu 434
                float otherTarget = (warehouseMobileStockByMovementModel.getTotalOtherCategoryCatalogPrice() / totalStockValue) * todayTarget;
435
 
436
                RbmTargets rbmTargets = new RbmTargets();
437
                rbmTargets.setWarehouseId(rbmTarget.getWarehouseId());
438
                rbmTargets.setRbmAuthId(rbmTarget.getAuthId());
439
                rbmTargets.setRbmName(rbmTarget.getRbmName());
34098 ranu 440
                rbmTargets.setRunningtarget(runningTarget);
33926 ranu 441
                rbmTargets.setHidTarget(hidTarget);
442
                rbmTargets.setFastMovingTarget(fastMovingTarget);
34098 ranu 443
                rbmTargets.setSlowMovingtarget(slowMovingTarget);
33926 ranu 444
                rbmTargets.setOtherTarget(otherTarget);
445
                rbmTargets.setCreateTimestamp(LocalDateTime.now());
446
 
447
                rbmTargetsRepository.persist(rbmTargets);
448
 
449
            }
450
        }
451
 
452
    }
453
 
454
 
33985 ranu 455
    @Override
456
    public void setMovementWiseRbmAchievement() {
457
        LocalDate todayDate = LocalDate.now();
458
 
34055 ranu 459
        List<TodayAchievedMovementModel> todayAchievedMovementModels = getMovementWiseAchievementByDate(todayDate, todayDate.plusDays(1));
33985 ranu 460
 
461
 
462
        for (TodayAchievedMovementModel achievement : todayAchievedMovementModels) {
463
 
464
            RbmAchievements rbmAchievements = new RbmAchievements();
465
 
466
            rbmAchievements.setRbmAuthId(achievement.getAuthId());
467
            rbmAchievements.setRbmName(achievement.getRbmName());
468
            rbmAchievements.setWarehouseId(achievement.getWarehouseId());
469
            rbmAchievements.setAchievedHidTarget(achievement.getHidBilled());
470
            rbmAchievements.setAchievedFastMovingTarget(achievement.getFastMovingBilled());
34098 ranu 471
            rbmAchievements.setAchievedSlowMovingTarget(achievement.getSlowMovinBilled());
472
            rbmAchievements.setAchievedRunningTarget(achievement.getRunningBilled());
34012 ranu 473
            rbmAchievements.setAchievedOtherTarget(achievement.getOtherBilled());
33985 ranu 474
            rbmAchievements.setCreateTimestamp(LocalDateTime.now());
475
 
476
            rbmAchievementsRepository.persist(rbmAchievements);
477
 
478
        }
479
 
480
    }
481
 
34055 ranu 482
    @Override
483
    public List<Sold15daysOldAgingModel> getAgingSale(LocalDate startDate, LocalDate endDate) {
484
        Session session = sessionFactory.getCurrentSession();
485
        final TypedQuery<Sold15daysOldAgingModel> typedQuerySimilar = session.createNamedQuery("Aging.SoldAgingModel", Sold15daysOldAgingModel.class);
34056 ranu 486
        typedQuerySimilar.setParameter("startDate", startDate);
487
        typedQuerySimilar.setParameter("endDate", endDate);
34055 ranu 488
        return typedQuerySimilar.getResultList();
33985 ranu 489
 
34055 ranu 490
    }
491
 
34103 ranu 492
    @Override
493
    public List<RbmBilledFofoIdsModel> getDateWiseBilledFofoIdByRbm(LocalDate startDate, LocalDate endDate) {
494
        Session session = sessionFactory.getCurrentSession();
495
        final TypedQuery<RbmBilledFofoIdsModel> typedQuerySimilar = session.createNamedQuery("RBM.RbmBilledFofoId", RbmBilledFofoIdsModel.class);
496
        typedQuerySimilar.setParameter("startDate", startDate);
497
        typedQuerySimilar.setParameter("endDate", endDate);
498
        return typedQuerySimilar.getResultList();
35453 amit 499
    }
34103 ranu 500
 
35453 amit 501
    @Override
502
    public List<RbmWeeklyBillingModel> getWeeklyBillingDataForMonth(LocalDate monthStart, LocalDate monthEnd) {
503
        Session session = sessionFactory.getCurrentSession();
504
        final TypedQuery<RbmWeeklyBillingModel> typedQuery = session.createNamedQuery("RBM.WeeklyBilling", RbmWeeklyBillingModel.class);
505
        typedQuery.setParameter("startDate", monthStart);
506
        typedQuery.setParameter("endDate", monthEnd);
507
        return typedQuery.getResultList();
34103 ranu 508
    }
509
 
34055 ranu 510
    public List<Our15DaysOldAgingStock> our15DaysAgingStock() {
511
        Session session = sessionFactory.getCurrentSession();
512
        final TypedQuery<Our15DaysOldAgingStock> typedQuerySimilar = session.createNamedQuery("Aging.15DaysOurStock", Our15DaysOldAgingStock.class);
513
        return typedQuerySimilar.getResultList();
514
 
515
    }
516
 
34397 ranu 517
    @Autowired
518
    OrderRepository orderRepository;
34055 ranu 519
 
34397 ranu 520
    @Override
34641 ranu 521
    public double calculateFofoIdTodayTarget(int fofoId, double secondryMtd,LocalDate date) {
34397 ranu 522
 
523
        MonthlyTarget monthlyTarget = monthlyTargetRepository.selectByDateAndFofoId(YearMonth.now(), fofoId);
34404 ranu 524
        if (monthlyTarget == null) {
525
            // Log or handle as needed
526
            return 0; // or -1 or some fallback
527
        }
34397 ranu 528
 
529
        double remainingTarget = monthlyTarget.getPurchaseTarget() - secondryMtd;
34880 ranu 530
//        double remainingWorkingDays = getWorkingDaysCount(date);
531
        double remainingWorkingDays = (double) getRemainingDaysInMonth(date);
34397 ranu 532
 
533
 
34716 ranu 534
 
34397 ranu 535
        if (remainingWorkingDays == 0) return remainingTarget; // Last day
34716 ranu 536
        LOGGER.info("remainingWorkingDays {}", remainingWorkingDays);
537
        LOGGER.info("remainingTarget {}", remainingTarget);
34397 ranu 538
 
539
        return (int) Math.ceil(remainingTarget / remainingWorkingDays);
540
    }
541
 
34880 ranu 542
    @Override
543
    public long getRemainingDaysInMonth(LocalDate date) {
544
        LocalDate lastDayOfMonth = YearMonth.from(date).atEndOfMonth();
34397 ranu 545
 
34880 ranu 546
        long totalDays = ChronoUnit.DAYS.between(date, lastDayOfMonth) + 1;
34397 ranu 547
 
34880 ranu 548
        // Count Sundays manually
549
        long sundayCount = 0;
550
        LocalDate current = date;
551
        while (!current.isAfter(lastDayOfMonth)) {
552
            if (current.getDayOfWeek() == DayOfWeek.SUNDAY) {
553
                sundayCount++;
554
            }
555
            current = current.plusDays(1);
556
        }
557
 
558
        // Public holidays in the range
559
        long publicHolidays = publicHolidaysRepository
560
                .selectAllBetweenDates(date, lastDayOfMonth)
561
                .size();
562
 
563
        long remainingDays = totalDays - sundayCount - publicHolidays;
564
 
565
        LOGGER.info("remainingDays {}", remainingDays);
566
        LOGGER.info("totalDays {}", totalDays);
567
        LOGGER.info("sundays {}", sundayCount);
568
        LOGGER.info("publicHolidays {}", publicHolidays);
569
 
570
        return remainingDays;
571
    }
572
 
35631 ranu 573
    @Autowired
574
    PositionRepository positionRepository;
34880 ranu 575
 
35631 ranu 576
    @Autowired
577
    CsService csService;
34880 ranu 578
 
35631 ranu 579
    @Autowired
580
    FofoStoreRepository fofoStoreRepository;
581
 
582
    @Autowired
583
    AuthRepository authRepository;
584
 
585
    @Autowired
586
    LoanRepository loanRepository;
587
 
588
    @Autowired
589
    PartnerCollectionService partnerCollectionService;
590
 
591
    @Autowired
592
    PartnerCollectionRemarkRepository partnerCollectionRemarkRepository;
593
 
594
    @Autowired
595
    RbmCallSequenceLogRepository rbmCallSequenceLogRepository;
596
 
597
    @Autowired
598
    com.spice.profitmandi.dao.repository.cs.TicketRepository ticketRepository;
599
 
35702 ranu 600
    @Autowired
601
    com.spice.profitmandi.dao.repository.cs.AgentCallLogRepository agentCallLogRepository;
602
 
35759 ranu 603
    @Autowired
604
    RetailerContactRepository retailerContactRepository;
605
 
606
    @Autowired
607
    AddressRepository addressRepository;
608
 
36225 ranu 609
    @Autowired
610
    com.spice.profitmandi.dao.repository.cs.PartnerPositionRepository partnerPositionRepository;
611
 
35631 ranu 612
    @Override
613
    public List<RbmCallTargetModel> getRbmCallTargetModels() throws Exception {
36234 ranu 614
        return getRbmCallTargetModels(LocalDate.now());
615
    }
616
 
617
    public List<RbmCallTargetModel> getRbmCallTargetModels(LocalDate queryDate) throws Exception {
35631 ranu 618
        long methodStart = System.currentTimeMillis();
619
        List<RbmCallTargetModel> rbmCallTargetModels = new ArrayList<>();
620
 
621
        // Get all RBM positions (L1 and L2)
622
        long start = System.currentTimeMillis();
623
        List<Position> allRbmPositions = positionRepository
624
                .selectPositionByCategoryId(ProfitMandiConstants.TICKET_CATEGORY_RBM).stream()
36210 ranu 625
                .filter(x -> Arrays.asList(EscalationType.L1, EscalationType.L2, EscalationType.L3).contains(x.getEscalationType()))
35631 ranu 626
                .collect(Collectors.toList());
627
 
36210 ranu 628
        // Separate L1, L2 and L3 auth IDs
35631 ranu 629
        List<Integer> l1AuthIds = allRbmPositions.stream()
630
                .filter(p -> EscalationType.L1.equals(p.getEscalationType()))
631
                .map(Position::getAuthUserId).distinct().collect(Collectors.toList());
632
        List<Integer> l2AuthIds = allRbmPositions.stream()
633
                .filter(p -> EscalationType.L2.equals(p.getEscalationType()))
634
                .map(Position::getAuthUserId).distinct().collect(Collectors.toList());
36210 ranu 635
        List<Integer> l3AuthIds = allRbmPositions.stream()
636
                .filter(p -> EscalationType.L3.equals(p.getEscalationType()))
637
                .map(Position::getAuthUserId).distinct().collect(Collectors.toList());
35631 ranu 638
 
639
        // Union of all auth IDs for batch fetching
640
        List<Integer> rbmPositionsAuthIds = allRbmPositions.stream()
641
                .map(Position::getAuthUserId).distinct().collect(Collectors.toList());
36210 ranu 642
        LOGGER.info("RBM Call Target - RBM positions fetch: {}ms, L1: {}, L2: {}, L3: {}", System.currentTimeMillis() - start, l1AuthIds.size(), l2AuthIds.size(), l3AuthIds.size());
35631 ranu 643
 
644
        start = System.currentTimeMillis();
645
        Map<String, Set<Integer>> storeGuyMap = csService.getAuthUserPartnerIdMapping();
646
        LOGGER.info("RBM Call Target - StoreGuyMap fetch: {}ms", System.currentTimeMillis() - start);
647
 
36234 ranu 648
        LocalDateTime startDate = queryDate.atStartOfDay();
649
        LocalDate firstOfMonth = queryDate.withDayOfMonth(1);
650
        LocalDate endOfMonth = queryDate.withDayOfMonth(queryDate.lengthOfMonth()).plusDays(1);
35631 ranu 651
 
652
        // Get auth user map
653
        start = System.currentTimeMillis();
654
        Map<Integer, AuthUser> authUserMap = authRepository.selectByIds(rbmPositionsAuthIds).stream()
655
                .collect(Collectors.toMap(AuthUser::getId, au -> au));
656
        LOGGER.info("RBM Call Target - AuthUser fetch: {}ms", System.currentTimeMillis() - start);
657
 
658
        // Batch fetch positions by auth IDs (to check if RBM is L1)
659
        start = System.currentTimeMillis();
660
        Map<Integer, List<Position>> positionsByAuthId = positionRepository.selectPositionByAuthIds(rbmPositionsAuthIds).stream()
661
                .collect(Collectors.groupingBy(Position::getAuthUserId));
662
        LOGGER.info("RBM Call Target - Positions by AuthId fetch: {}ms", System.currentTimeMillis() - start);
663
 
664
        // Get all fofo IDs for all RBMs
665
        Set<Integer> allFofoIds = new HashSet<>();
666
        Map<Integer, List<Integer>> rbmToFofoIdsMap = new HashMap<>();
667
        for (int rbmAuthId : rbmPositionsAuthIds) {
668
            AuthUser au = authUserMap.get(rbmAuthId);
669
            if (au != null && storeGuyMap.containsKey(au.getEmailId())) {
670
                List<Integer> fofoIds = new ArrayList<>(storeGuyMap.get(au.getEmailId()));
671
                allFofoIds.addAll(fofoIds);
672
                rbmToFofoIdsMap.put(rbmAuthId, fofoIds);
673
            }
674
        }
35816 ranu 675
        // Initialize L2 calling list map - will be populated after fetching remarks
35631 ranu 676
        Map<Integer, List<Integer>> l2AuthIdToFofoIds = new HashMap<>();
35816 ranu 677
        for (int l2AuthId : l2AuthIds) {
678
            l2AuthIdToFofoIds.put(l2AuthId, new ArrayList<>());
35631 ranu 679
        }
36210 ranu 680
        // Initialize L3 calling list map - will be populated after fetching remarks
681
        Map<Integer, List<Integer>> l3AuthIdToFofoIds = new HashMap<>();
682
        for (int l3AuthId : l3AuthIds) {
683
            l3AuthIdToFofoIds.put(l3AuthId, new ArrayList<>());
684
        }
35631 ranu 685
        LOGGER.info("RBM Call Target - Total fofo IDs to process: {}", allFofoIds.size());
686
 
687
        // Get only needed fofo stores (OPTIMIZED - was fetching ALL stores before)
688
        start = System.currentTimeMillis();
689
        Map<Integer, FofoStore> fofoStoresMap = new HashMap<>();
690
        if (!allFofoIds.isEmpty()) {
691
            try {
692
                fofoStoresMap = fofoStoreRepository.selectByRetailerIds(new ArrayList<>(allFofoIds)).stream()
693
                        .collect(Collectors.toMap(FofoStore::getId, x -> x, (a, b) -> a));
694
            } catch (ProfitMandiBusinessException e) {
695
                LOGGER.error("Error fetching fofo stores", e);
696
            }
697
        }
698
        LOGGER.info("RBM Call Target - FofoStores fetch (only needed): {}ms, count: {}", System.currentTimeMillis() - start, fofoStoresMap.size());
699
 
700
        // Batch fetch max remark ids for all fofoIds (for escalation filtering)
701
        start = System.currentTimeMillis();
702
        Map<Integer, PartnerCollectionRemark> allPartnerCollectionRemarks = new HashMap<>();
703
        if (!allFofoIds.isEmpty()) {
704
            List<Integer> allRemarkIds = partnerCollectionRemarkRepository.selectMaxRemarkId(new ArrayList<>(allFofoIds));
705
            if (!allRemarkIds.isEmpty()) {
706
                allPartnerCollectionRemarks = partnerCollectionRemarkRepository.selectByIds(allRemarkIds).stream()
707
                        .collect(Collectors.toMap(PartnerCollectionRemark::getFofoId, x -> x, (a, b) -> a));
708
            }
709
        }
710
        LOGGER.info("RBM Call Target - PartnerCollectionRemarks fetch: {}ms", System.currentTimeMillis() - start);
711
 
35816 ranu 712
        // Populate L2 calling list based on partners whose latest remark is RBM_L2_ESCALATION
713
        // Find the L1 who has the partner and add to that L1's manager (L2) calling list
714
        for (Map.Entry<Integer, PartnerCollectionRemark> entry : allPartnerCollectionRemarks.entrySet()) {
715
            Integer fofoId = entry.getKey();
716
            PartnerCollectionRemark remark = entry.getValue();
717
 
718
            if (CollectionRemark.RBM_L2_ESCALATION.equals(remark.getRemark())) {
719
                // Find which L1 RBM has this partner assigned
720
                for (int l1AuthId : l1AuthIds) {
721
                    List<Integer> l1FofoIds = rbmToFofoIdsMap.getOrDefault(l1AuthId, Collections.emptyList());
722
                    if (l1FofoIds.contains(fofoId)) {
723
                        // Get L1's manager (L2)
724
                        AuthUser l1User = authUserMap.get(l1AuthId);
725
                        if (l1User != null && l2AuthIdToFofoIds.containsKey(l1User.getManagerId())) {
726
                            int l2ManagerId = l1User.getManagerId();
727
                            l2AuthIdToFofoIds.get(l2ManagerId).add(fofoId);
728
                        }
729
                        break; // Found the L1 for this fofoId
730
                    }
731
                }
732
            }
733
        }
734
        LOGGER.info("RBM Call Target - L2 calling lists populated from RBM_L2_ESCALATION remarks");
735
 
36210 ranu 736
        // Populate L3 calling list based on partners whose latest remark is RBM_L3_ESCALATION
36212 ranu 737
        // Find the L1 who originally has the partner, then:
738
        // Case 1: L1 -> L3 directly (if L1's manager IS L3)
739
        // Case 2: L1 -> L2 -> L3 (if L1's manager is L2, then L2's manager is L3)
36210 ranu 740
        for (Map.Entry<Integer, PartnerCollectionRemark> entry : allPartnerCollectionRemarks.entrySet()) {
741
            Integer fofoId = entry.getKey();
742
            PartnerCollectionRemark remark = entry.getValue();
743
 
744
            if (CollectionRemark.RBM_L3_ESCALATION.equals(remark.getRemark())) {
745
                // Find which L1 RBM originally has this partner assigned
746
                for (int l1AuthId : l1AuthIds) {
747
                    List<Integer> l1FofoIds = rbmToFofoIdsMap.getOrDefault(l1AuthId, Collections.emptyList());
748
                    if (l1FofoIds.contains(fofoId)) {
749
                        AuthUser l1User = authUserMap.get(l1AuthId);
750
                        if (l1User != null) {
36212 ranu 751
                            int l1ManagerId = l1User.getManagerId();
752
                            // Case 1: L1's manager IS L3 directly (L1 → L3, no L2 in between)
753
                            if (l3AuthIdToFofoIds.containsKey(l1ManagerId)) {
754
                                l3AuthIdToFofoIds.get(l1ManagerId).add(fofoId);
755
                                LOGGER.debug("L3 Calling List (direct): fofoId={} -> L1={} -> L3={}",
756
                                        fofoId, l1AuthId, l1ManagerId);
757
                            } else {
758
                                // Case 2: L1 -> L2 -> L3
759
                                AuthUser l2User = authUserMap.get(l1ManagerId);
760
                                if (l2User != null && l3AuthIdToFofoIds.containsKey(l2User.getManagerId())) {
761
                                    int l3ManagerId = l2User.getManagerId();
762
                                    l3AuthIdToFofoIds.get(l3ManagerId).add(fofoId);
763
                                    LOGGER.debug("L3 Calling List: fofoId={} -> L1={} -> L2={} -> L3={}",
764
                                            fofoId, l1AuthId, l1ManagerId, l3ManagerId);
765
                                }
36210 ranu 766
                            }
767
                        }
768
                        break; // Found the L1 for this fofoId
769
                    }
770
                }
771
            }
772
        }
773
        LOGGER.info("RBM Call Target - L3 calling lists populated from RBM_L3_ESCALATION remarks");
774
 
35631 ranu 775
        // Batch fetch collection RANK map for all fofoIds (OPTIMIZED - only fetches rank, not full model)
776
        start = System.currentTimeMillis();
777
        Map<Integer, Integer> allCollectionRankMap = new HashMap<>();
778
        if (!allFofoIds.isEmpty()) {
779
            try {
780
                allCollectionRankMap = partnerCollectionService.getCollectionRankMap(new ArrayList<>(allFofoIds), startDate);
781
            } catch (ProfitMandiBusinessException e) {
782
                LOGGER.error("Error fetching collection rank map for all fofoIds", e);
783
            }
784
        }
785
        LOGGER.info("RBM Call Target - CollectionRankMap fetch (OPTIMIZED): {}ms", System.currentTimeMillis() - start);
786
 
35669 ranu 787
        // Get MTD billing data for zero billing calculation and partner counts
35631 ranu 788
        start = System.currentTimeMillis();
789
        List<RbmWeeklyBillingModel> mtdBillingData = getWeeklyBillingDataForMonth(firstOfMonth, endOfMonth);
790
        Set<Integer> allMtdBilledFofoIds = mtdBillingData.stream()
791
                .filter(RbmWeeklyBillingModel::isMtdBilled)
792
                .map(RbmWeeklyBillingModel::getFofoId)
793
                .collect(Collectors.toSet());
35669 ranu 794
        // Build partner count and fofoIds per RBM from mtdBillingData (same source as Today ARR page)
795
        Map<Integer, Set<Integer>> mtdFofoIdsByAuthId = mtdBillingData.stream()
796
                .filter(RbmWeeklyBillingModel::isTargetedPartner)
797
                .collect(Collectors.groupingBy(RbmWeeklyBillingModel::getAuthId,
798
                        Collectors.mapping(RbmWeeklyBillingModel::getFofoId, Collectors.toSet())));
35631 ranu 799
        LOGGER.info("RBM Call Target - MTD Billing fetch: {}ms", System.currentTimeMillis() - start);
800
 
801
        // Batch fetch today's remarks for all auth IDs (to calculate Value Achieved)
802
        start = System.currentTimeMillis();
803
        Map<Integer, List<PartnerCollectionRemark>> remarksByAuthId = partnerCollectionRemarkRepository
804
                .selectAllByAuthIdsOnDate(rbmPositionsAuthIds, LocalDate.now()).stream()
805
                .collect(Collectors.groupingBy(PartnerCollectionRemark::getAuthId));
806
        LOGGER.info("RBM Call Target - Today Remarks fetch: {}ms", System.currentTimeMillis() - start);
807
 
808
        // Batch fetch today's out-of-sequence logs for all RBMs
809
        start = System.currentTimeMillis();
810
        LocalDateTime todayStart = LocalDate.now().atStartOfDay();
811
        LocalDateTime todayEnd = LocalDate.now().plusDays(1).atStartOfDay();
812
        List<RbmCallSequenceLog> outOfSequenceLogs = rbmCallSequenceLogRepository.selectOutOfSequenceByDateRange(todayStart, todayEnd);
813
        Map<Integer, Long> outOfSequenceCountByAuthId = outOfSequenceLogs.stream()
35654 ranu 814
                .collect(Collectors.groupingBy(RbmCallSequenceLog::getAuthId,
815
                        Collectors.mapping(RbmCallSequenceLog::getFofoId, Collectors.collectingAndThen(Collectors.toSet(), s -> (long) s.size()))));
35631 ranu 816
        LOGGER.info("RBM Call Target - Out of Sequence fetch: {}ms", System.currentTimeMillis() - start);
817
 
36284 ranu 818
        // BATCH FETCH: All call logs for all RBMs (L1 + L2 + L3) in a single query.
819
        // Replaces the previous N+1 pattern where getCallStats() was called per RBM.
820
        start = System.currentTimeMillis();
821
        List<AgentCallLog> allCallLogs = agentCallLogRepository.findByAuthIdsAndDate(rbmPositionsAuthIds, queryDate);
822
        Map<Long, List<AgentCallLog>> callLogsByAuthId = allCallLogs.stream()
823
                .collect(Collectors.groupingBy(AgentCallLog::getAuthId));
824
        LOGGER.info("RBM Call Target - Call logs batch fetch: {}ms ({} logs across {} RBMs)",
825
                System.currentTimeMillis() - start, allCallLogs.size(), callLogsByAuthId.size());
826
 
827
        // BATCH FETCH: Build a single mobile -> fofoId map from all unique customer numbers across all call logs.
828
        // Replaces the previous N+1 pattern where findFofoIdByMobile() was called per call log entry.
829
        start = System.currentTimeMillis();
830
        Set<String> allNormalizedMobiles = new HashSet<>();
831
        for (AgentCallLog callLog : allCallLogs) {
832
            String customerNumber = callLog.getCustomerNumber();
833
            if (customerNumber != null) {
834
                String normalized = customerNumber.startsWith("+91") ? customerNumber.substring(3) : customerNumber;
835
                allNormalizedMobiles.add(normalized);
836
            }
837
        }
838
        Map<String, Integer> mobileToFofoIdMap = buildMobileToFofoIdMap(allNormalizedMobiles);
839
        LOGGER.info("RBM Call Target - Mobile→FofoId batch fetch: {}ms ({} unique mobiles, {} mapped)",
840
                System.currentTimeMillis() - start, allNormalizedMobiles.size(), mobileToFofoIdMap.size());
841
 
36225 ranu 842
        // Identify users who are both L1 and L2 — they will be shown only as L2
843
        Set<Integer> l2AuthIdSet = new HashSet<>(l2AuthIds);
844
 
845
        // Process L1 RBMs (skip users who are also L2 — their data will be merged into L2 model)
35631 ranu 846
        for (int rbmAuthId : l1AuthIds) {
36225 ranu 847
            if (l2AuthIdSet.contains(rbmAuthId)) {
848
                continue; // Will be handled in L2 processing with merged L1 data
849
            }
35631 ranu 850
            AuthUser authUser = authUserMap.get(rbmAuthId);
851
            if (authUser == null || !storeGuyMap.containsKey(authUser.getEmailId())) {
852
                continue;
853
            }
854
 
855
            List<Integer> fofoIdList = rbmToFofoIdsMap.getOrDefault(rbmAuthId, Collections.emptyList());
856
 
857
            // Check if RBM is L1 (same logic as getSummaryModel)
858
            List<Position> positions = positionsByAuthId.getOrDefault(authUser.getId(), Collections.emptyList());
859
            boolean isRBMAndL1 = positions.stream()
860
                    .anyMatch(position ->
861
                            ProfitMandiConstants.TICKET_CATEGORY_RBM == position.getCategoryId()
862
                                    && EscalationType.L1.equals(position.getEscalationType()));
863
 
864
            // Filter escalated partners for L1 RBMs (same logic as getSummaryModel)
865
            List<Integer> fofoIds = fofoIdList;
866
            if (isRBMAndL1) {
867
                Map<Integer, PartnerCollectionRemark> partnerCollectionRemarks = new HashMap<>();
868
                for (Integer fofoId : fofoIdList) {
869
                    if (allPartnerCollectionRemarks.containsKey(fofoId)) {
870
                        partnerCollectionRemarks.put(fofoId, allPartnerCollectionRemarks.get(fofoId));
871
                    }
872
                }
873
                fofoIds = partnerCollectionRemarks.entrySet().stream()
874
                        .filter(entry -> {
875
                            PartnerCollectionRemark pcrMap = entry.getValue();
876
                            return !(CollectionRemark.RBM_L2_ESCALATION.equals(pcrMap.getRemark())
877
                                    || CollectionRemark.SALES_ESCALATION.equals(pcrMap.getRemark()));
878
                        })
879
                        .map(Map.Entry::getKey)
880
                        .collect(Collectors.toList());
881
            }
882
 
36181 ranu 883
            // Filter to only external, ACTIVE or REVIVAL stores (collection plan not required)
35631 ranu 884
            Map<Integer, Integer> finalAllCollectionRankMap = allCollectionRankMap;
885
            Map<Integer, FofoStore> finalFofoStoresMap = fofoStoresMap;
886
            List<Integer> validFofoIds = fofoIds.stream()
887
                    .filter(fofoId -> {
888
                        FofoStore store = finalFofoStoresMap.get(fofoId);
889
                        if (store == null || store.isInternal()) {
890
                            return false;
891
                        }
36181 ranu 892
                        // Only include ACTIVE or REVIVAL partners (not Low Sale, not Disputed, not Billing Pending)
893
                        return ActivationType.ACTIVE.equals(store.getActivationType())
894
                                || ActivationType.REVIVAL.equals(store.getActivationType());
35631 ranu 895
                    })
896
                    .collect(Collectors.toList());
897
 
898
            if (validFofoIds.isEmpty()) {
899
                continue;
900
            }
901
 
902
            RbmCallTargetModel targetModel = new RbmCallTargetModel();
903
            targetModel.setAuthId(rbmAuthId);
904
            targetModel.setRbmName(authUser.getFullName());
35669 ranu 905
            // Use partner count from mtdBillingData (same source as Today ARR page)
906
            Set<Integer> mtdFofoIds = mtdFofoIdsByAuthId.getOrDefault(rbmAuthId, Collections.emptySet());
907
            targetModel.setPartnerCount(mtdFofoIds.size());
35631 ranu 908
 
909
            // Categorize each partner - each partner belongs to ONE category only
35665 ranu 910
            // Priority: PlanToday > CarryForward > ZeroBilling > Untouched > FuturePlan > Normal
36181 ranu 911
            // Revival is counted separately (just for display, doesn't affect categorization)
35631 ranu 912
            Set<Integer> planTodayPartners = new HashSet<>();
913
            Set<Integer> carryForwardPartners = new HashSet<>();
914
            Set<Integer> untouchedPartners = new HashSet<>();
915
            Set<Integer> zeroBillingPartners = new HashSet<>();
916
            Set<Integer> futurePlanPartners = new HashSet<>();
917
            Set<Integer> normalPartners = new HashSet<>();
36181 ranu 918
            Set<Integer> revivalPartners = new HashSet<>();
35631 ranu 919
 
920
            for (Integer fofoId : validFofoIds) {
921
                // Get collection plan rank (from optimized rank map)
922
                int rank = allCollectionRankMap.getOrDefault(fofoId, 5); // default to Normal if no plan
923
 
924
                // Check if partner has zero billing in MTD
925
                boolean hasZeroBilling = !allMtdBilledFofoIds.contains(fofoId);
926
 
36181 ranu 927
                // Count REVIVAL partners separately (just for display, doesn't affect categorization)
928
                FofoStore store = finalFofoStoresMap.get(fofoId);
929
                if (store != null && ActivationType.REVIVAL.equals(store.getActivationType())) {
930
                    revivalPartners.add(fofoId);
931
                }
932
 
35631 ranu 933
                // Assign to category based on priority
934
                if (rank == 1) {
935
                    planTodayPartners.add(fofoId);
936
                } else if (rank == 2) {
937
                    carryForwardPartners.add(fofoId);
35665 ranu 938
                } else if (hasZeroBilling) {
939
                    zeroBillingPartners.add(fofoId);
35631 ranu 940
                } else if (rank == 3) {
941
                    untouchedPartners.add(fofoId);
942
                } else if (rank == 4) {
943
                    futurePlanPartners.add(fofoId);
944
                } else {
945
                    normalPartners.add(fofoId);
946
                }
947
            }
948
 
949
            // Set counts
950
            targetModel.setCreditCollection(0); // Credit collection is handled in separate list
951
            targetModel.setPlanToday(planTodayPartners.size());
952
            targetModel.setCarryForward(carryForwardPartners.size());
953
            targetModel.setUntouched(untouchedPartners.size());
954
            targetModel.setZeroBilling(zeroBillingPartners.size());
955
            targetModel.setFuturePlan(futurePlanPartners.size());
956
            targetModel.setNormal(normalPartners.size());
36181 ranu 957
            targetModel.setRevival(revivalPartners.size());
35631 ranu 958
 
959
            // Today Target = PlanToday + CarryForward + ZeroBilling + Untouched
960
            // These are mutually exclusive now, so we can sum them
961
            long todayTarget = planTodayPartners.size() +
962
                    carryForwardPartners.size() + zeroBillingPartners.size() + untouchedPartners.size();
963
            targetModel.setTodayTargetOfCall(todayTarget);
964
 
965
            // Create set of partners in Today Target categories
966
            Set<Integer> todayTargetPartners = new HashSet<>();
967
            todayTargetPartners.addAll(planTodayPartners);
968
            todayTargetPartners.addAll(carryForwardPartners);
969
            todayTargetPartners.addAll(zeroBillingPartners);
970
            todayTargetPartners.addAll(untouchedPartners);
971
 
36284 ranu 972
            // Value Achieved = All distinct partners called today (from pre-fetched call logs)
973
            long[] callStats = getCallStatsFromLogs(callLogsByAuthId.get((long) rbmAuthId), mobileToFofoIdMap);
36276 ranu 974
            targetModel.setValueTargetAchieved(callStats[0]);
975
            targetModel.setTotalRecordingCalls(callStats[1]);
976
            targetModel.setUniqueRecordingCalls(callStats[2]);
35631 ranu 977
 
35843 ranu 978
            // Keep todayRemarks for movedToFuture calculation
979
            List<PartnerCollectionRemark> todayRemarks = remarksByAuthId.getOrDefault(rbmAuthId, Collections.emptyList());
980
 
35631 ranu 981
            // Moved to Future = Partners in Future Plan category who have a remark today
982
            // These are partners who were contacted today but moved to a future date
983
            Set<Integer> todayRemarkedFofoIds = todayRemarks.stream()
984
                    .map(PartnerCollectionRemark::getFofoId)
985
                    .collect(Collectors.toSet());
986
            long movedToFuture = futurePlanPartners.stream()
987
                    .filter(todayRemarkedFofoIds::contains)
988
                    .count();
989
            targetModel.setMovedToFuture(movedToFuture);
990
 
991
            // Set out of sequence count for this RBM
992
            targetModel.setOutOfSequenceCount(outOfSequenceCountByAuthId.getOrDefault(rbmAuthId, 0L));
993
 
994
            rbmCallTargetModels.add(targetModel);
995
        }
996
 
997
        // Process L2 RBMs (escalated ticket logic with categorization)
36225 ranu 998
        // For users who are both L1 and L2, merge their L1 calling target into L2 model
35631 ranu 999
        for (int l2AuthId : l2AuthIds) {
1000
            AuthUser authUser = authUserMap.get(l2AuthId);
1001
            if (authUser == null) {
1002
                continue;
1003
            }
1004
 
1005
            List<Integer> l2FofoIdList = l2AuthIdToFofoIds.getOrDefault(l2AuthId, Collections.emptyList());
1006
 
35816 ranu 1007
            // For L2, use unique fofoIds with RBM_L2_ESCALATION remark as target
35662 ranu 1008
            Set<Integer> l2TargetFofoIds = new HashSet<>(l2FofoIdList);
35631 ranu 1009
 
1010
            RbmCallTargetModel l2Model = new RbmCallTargetModel();
1011
            l2Model.setAuthId(l2AuthId);
1012
            l2Model.setRbmName(authUser.getFullName() + " (L2)");
1013
            l2Model.setL2Position(true);
35816 ranu 1014
            l2Model.setL2CallingList(l2TargetFofoIds.size());
36228 ranu 1015
            // Partner count: if user is also L1, use L1 partner count (MTD targeted partners)
1016
            if (l1AuthIds.contains(l2AuthId)) {
1017
                Set<Integer> mtdFofoIds = mtdFofoIdsByAuthId.getOrDefault(l2AuthId, Collections.emptySet());
1018
                l2Model.setPartnerCount(mtdFofoIds.size());
1019
            } else {
1020
                List<Integer> l2AssignedFofoIds = rbmToFofoIdsMap.getOrDefault(l2AuthId, Collections.emptyList());
1021
                l2Model.setPartnerCount(l2AssignedFofoIds.size());
1022
            }
35631 ranu 1023
 
36229 ranu 1024
            // If user is also L1, calculate full L1 breakdown and merge into L2 model
36225 ranu 1025
            if (l1AuthIds.contains(l2AuthId) && storeGuyMap.containsKey(authUser.getEmailId())) {
36229 ranu 1026
                // Get only L1 RBM position partners (not L2 partners) from partner_position
36225 ranu 1027
                List<Position> positions = positionsByAuthId.getOrDefault(l2AuthId, Collections.emptyList());
36229 ranu 1028
                Set<Integer> l1RbmPositionIds = positions.stream()
1029
                        .filter(p -> ProfitMandiConstants.TICKET_CATEGORY_RBM == p.getCategoryId()
1030
                                && EscalationType.L1.equals(p.getEscalationType()))
1031
                        .map(Position::getId)
1032
                        .collect(Collectors.toSet());
1033
                // Fetch partner_position for only L1 RBM positions of this user
1034
                List<Integer> fofoIdList = partnerPositionRepository
1035
                        .selectByPositionIds(new ArrayList<>(l1RbmPositionIds)).stream()
1036
                        .map(pp -> pp.getFofoId())
1037
                        .distinct()
1038
                        .collect(Collectors.toList());
1039
                boolean isRBMAndL1 = !l1RbmPositionIds.isEmpty();
36225 ranu 1040
                List<Integer> l1FofoIds = new ArrayList<>(fofoIdList);
1041
                if (isRBMAndL1) {
1042
                    Map<Integer, PartnerCollectionRemark> partnerCollectionRemarks = new HashMap<>();
1043
                    for (Integer fofoId : fofoIdList) {
1044
                        if (allPartnerCollectionRemarks.containsKey(fofoId)) {
1045
                            partnerCollectionRemarks.put(fofoId, allPartnerCollectionRemarks.get(fofoId));
1046
                        }
1047
                    }
1048
                    l1FofoIds = partnerCollectionRemarks.entrySet().stream()
1049
                            .filter(entry -> {
1050
                                PartnerCollectionRemark pcrMap = entry.getValue();
1051
                                return !(CollectionRemark.RBM_L2_ESCALATION.equals(pcrMap.getRemark())
1052
                                        || CollectionRemark.SALES_ESCALATION.equals(pcrMap.getRemark()));
1053
                            })
1054
                            .map(Map.Entry::getKey)
1055
                            .collect(Collectors.toList());
1056
                }
1057
                Map<Integer, FofoStore> finalFofoStoresMap2 = fofoStoresMap;
1058
                List<Integer> validL1FofoIds = l1FofoIds.stream()
1059
                        .filter(fofoId -> {
1060
                            FofoStore store = finalFofoStoresMap2.get(fofoId);
1061
                            if (store == null || store.isInternal()) return false;
1062
                            return ActivationType.ACTIVE.equals(store.getActivationType())
1063
                                    || ActivationType.REVIVAL.equals(store.getActivationType());
1064
                        })
1065
                        .collect(Collectors.toList());
35631 ranu 1066
 
36229 ranu 1067
                // Categorize L1 partners — same logic as L1 processing
1068
                Set<Integer> planTodayPartners = new HashSet<>();
1069
                Set<Integer> carryForwardPartners = new HashSet<>();
1070
                Set<Integer> untouchedPartners = new HashSet<>();
1071
                Set<Integer> zeroBillingPartners = new HashSet<>();
1072
                Set<Integer> futurePlanPartners = new HashSet<>();
1073
                Set<Integer> normalPartners = new HashSet<>();
1074
                Set<Integer> revivalPartners = new HashSet<>();
1075
 
36225 ranu 1076
                for (Integer fofoId : validL1FofoIds) {
1077
                    int rank = allCollectionRankMap.getOrDefault(fofoId, 5);
1078
                    boolean hasZeroBilling = !allMtdBilledFofoIds.contains(fofoId);
36229 ranu 1079
                    FofoStore store = finalFofoStoresMap2.get(fofoId);
1080
                    if (store != null && ActivationType.REVIVAL.equals(store.getActivationType())) {
1081
                        revivalPartners.add(fofoId);
36225 ranu 1082
                    }
36229 ranu 1083
                    if (rank == 1) {
1084
                        planTodayPartners.add(fofoId);
1085
                    } else if (rank == 2) {
1086
                        carryForwardPartners.add(fofoId);
1087
                    } else if (hasZeroBilling) {
1088
                        zeroBillingPartners.add(fofoId);
1089
                    } else if (rank == 3) {
1090
                        untouchedPartners.add(fofoId);
1091
                    } else if (rank == 4) {
1092
                        futurePlanPartners.add(fofoId);
1093
                    } else {
1094
                        normalPartners.add(fofoId);
1095
                    }
36225 ranu 1096
                }
36229 ranu 1097
 
1098
                // Set L1 breakdown fields on L2 model
1099
                l2Model.setPlanToday(planTodayPartners.size());
1100
                l2Model.setCarryForward(carryForwardPartners.size());
1101
                l2Model.setZeroBilling(zeroBillingPartners.size());
1102
                l2Model.setUntouched(untouchedPartners.size());
1103
                l2Model.setFuturePlan(futurePlanPartners.size());
1104
                l2Model.setNormal(normalPartners.size());
1105
                l2Model.setRevival(revivalPartners.size());
1106
 
1107
                long l1OwnTarget = planTodayPartners.size() + carryForwardPartners.size()
1108
                        + zeroBillingPartners.size() + untouchedPartners.size();
1109
 
1110
                // Today Target = own L1 target + L2 escalation
1111
                l2Model.setTodayTargetOfCall(l2TargetFofoIds.size() + l1OwnTarget);
1112
 
1113
                // Moved to Future from L1 partners
1114
                List<PartnerCollectionRemark> todayRemarks = remarksByAuthId.getOrDefault(l2AuthId, Collections.emptyList());
1115
                Set<Integer> todayRemarkedFofoIds = todayRemarks.stream()
1116
                        .map(PartnerCollectionRemark::getFofoId)
1117
                        .collect(Collectors.toSet());
1118
                long movedToFuture = futurePlanPartners.stream()
1119
                        .filter(todayRemarkedFofoIds::contains)
1120
                        .count();
1121
                l2Model.setMovedToFuture(movedToFuture);
1122
            } else {
1123
                // Pure L2 (not also L1) — target is only L2 escalation
1124
                l2Model.setTodayTargetOfCall(l2TargetFofoIds.size());
36225 ranu 1125
            }
1126
 
36284 ranu 1127
            // Value Achieved = All distinct partners called today (from pre-fetched call logs)
1128
            long[] l2CallStats = getCallStatsFromLogs(callLogsByAuthId.get((long) l2AuthId), mobileToFofoIdMap);
36276 ranu 1129
            l2Model.setValueTargetAchieved(l2CallStats[0]);
1130
            l2Model.setTotalRecordingCalls(l2CallStats[1]);
1131
            l2Model.setUniqueRecordingCalls(l2CallStats[2]);
35631 ranu 1132
 
1133
            l2Model.setOutOfSequenceCount(outOfSequenceCountByAuthId.getOrDefault(l2AuthId, 0L));
1134
            rbmCallTargetModels.add(l2Model);
1135
        }
1136
 
36210 ranu 1137
        // Process L3 RBMs (escalated ticket logic with categorization)
1138
        for (int l3AuthId : l3AuthIds) {
1139
            AuthUser authUser = authUserMap.get(l3AuthId);
1140
            if (authUser == null) {
1141
                continue;
1142
            }
1143
 
1144
            List<Integer> l3FofoIdList = l3AuthIdToFofoIds.getOrDefault(l3AuthId, Collections.emptyList());
1145
 
1146
            // For L3, use unique fofoIds with RBM_L3_ESCALATION remark as target
1147
            Set<Integer> l3TargetFofoIds = new HashSet<>(l3FofoIdList);
1148
 
1149
            RbmCallTargetModel l3Model = new RbmCallTargetModel();
1150
            l3Model.setAuthId(l3AuthId);
1151
            l3Model.setRbmName(authUser.getFullName() + " (L3)");
1152
            l3Model.setL3Position(true);
1153
            l3Model.setL3CallingList(l3TargetFofoIds.size());
1154
            // Partner count = total assigned partners (same as L2 source)
1155
            List<Integer> l3AssignedFofoIds = rbmToFofoIdsMap.getOrDefault(l3AuthId, Collections.emptyList());
1156
            l3Model.setPartnerCount(l3AssignedFofoIds.size());
1157
 
1158
            // L3 Target = partners with RBM_L3_ESCALATION as latest remark
1159
            l3Model.setTodayTargetOfCall(l3TargetFofoIds.size());
1160
 
36284 ranu 1161
            // Value Achieved = All distinct partners called today (from pre-fetched call logs)
1162
            long[] l3CallStats = getCallStatsFromLogs(callLogsByAuthId.get((long) l3AuthId), mobileToFofoIdMap);
36276 ranu 1163
            l3Model.setValueTargetAchieved(l3CallStats[0]);
1164
            l3Model.setTotalRecordingCalls(l3CallStats[1]);
1165
            l3Model.setUniqueRecordingCalls(l3CallStats[2]);
36210 ranu 1166
 
1167
            l3Model.setOutOfSequenceCount(outOfSequenceCountByAuthId.getOrDefault(l3AuthId, 0L));
1168
            rbmCallTargetModels.add(l3Model);
1169
        }
1170
 
36225 ranu 1171
        // Group models by escalation level
36210 ranu 1172
        Map<Integer, RbmCallTargetModel> l3ModelsByAuthId = new HashMap<>();
35631 ranu 1173
        Map<Integer, RbmCallTargetModel> l2ModelsByAuthId = new HashMap<>();
1174
        Map<Integer, RbmCallTargetModel> l1ModelsByAuthId = new HashMap<>();
1175
        for (RbmCallTargetModel m : rbmCallTargetModels) {
36210 ranu 1176
            if (m.isL3Position()) {
1177
                l3ModelsByAuthId.put(m.getAuthId(), m);
1178
            } else if (m.isL2Position()) {
35631 ranu 1179
                l2ModelsByAuthId.put(m.getAuthId(), m);
1180
            } else {
1181
                l1ModelsByAuthId.put(m.getAuthId(), m);
1182
            }
1183
        }
1184
 
36225 ranu 1185
        // Build partner-based hierarchy using partner_position table
1186
        // Map positionId -> Position for quick lookup
1187
        Map<Integer, Position> positionByIdMap = allRbmPositions.stream()
1188
                .collect(Collectors.toMap(Position::getId, p -> p, (a, b) -> a));
1189
 
1190
        // Get all RBM position IDs and fetch their partner assignments
1191
        List<Integer> allRbmPositionIds = allRbmPositions.stream()
1192
                .map(Position::getId).collect(Collectors.toList());
1193
        List<com.spice.profitmandi.dao.entity.cs.PartnerPosition> allPartnerPositions =
1194
                partnerPositionRepository.selectByPositionIds(allRbmPositionIds);
1195
 
1196
        // Build partnerId -> map of escalationType -> Set<authUserIds>
1197
        // This tells us for each partner, who is their L1, L2, L3
1198
        Map<Integer, Map<EscalationType, Set<Integer>>> partnerToAuthByLevel = new HashMap<>();
1199
        for (com.spice.profitmandi.dao.entity.cs.PartnerPosition pp : allPartnerPositions) {
1200
            Position pos = positionByIdMap.get(pp.getPositionId());
1201
            if (pos != null && pos.getEscalationType() != null) {
1202
                partnerToAuthByLevel
1203
                        .computeIfAbsent(pp.getFofoId(), k -> new HashMap<>())
1204
                        .computeIfAbsent(pos.getEscalationType(), k -> new HashSet<>())
1205
                        .add(pos.getAuthUserId());
1206
            }
1207
        }
1208
 
1209
        // Build L2 -> L1 team map based on shared partners
1210
        // If an L1 and L2 share partners (L1 at L1 level, L2 at L2 level), that L1 belongs under that L2
1211
        Map<Integer, List<RbmCallTargetModel>> l2TeamMap = new LinkedHashMap<>();
1212
        for (RbmCallTargetModel l2Model : l2ModelsByAuthId.values()) {
1213
            l2TeamMap.put(l2Model.getAuthId(), new ArrayList<>());
1214
        }
1215
 
1216
        // Build L3 -> L2 team map based on shared partners
36210 ranu 1217
        Map<Integer, List<RbmCallTargetModel>> l3TeamMap = new LinkedHashMap<>();
1218
        for (RbmCallTargetModel l3Model : l3ModelsByAuthId.values()) {
1219
            l3TeamMap.put(l3Model.getAuthId(), new ArrayList<>());
1220
        }
1221
 
36225 ranu 1222
        // For each L1, find which L2 shares the most partners -> that's their L2
1223
        Set<Integer> addedL1AuthIds = new HashSet<>();
1224
        for (RbmCallTargetModel l1Model : l1ModelsByAuthId.values()) {
1225
            Map<Integer, Integer> l2SharedCount = new HashMap<>(); // l2AuthId -> shared partner count
1226
            for (Map.Entry<Integer, Map<EscalationType, Set<Integer>>> entry : partnerToAuthByLevel.entrySet()) {
1227
                Map<EscalationType, Set<Integer>> levelMap = entry.getValue();
1228
                Set<Integer> l1IdsForPartner = levelMap.getOrDefault(EscalationType.L1, Collections.emptySet());
1229
                Set<Integer> l2AuthIdsForPartner = levelMap.getOrDefault(EscalationType.L2, Collections.emptySet());
1230
                if (l1IdsForPartner.contains(l1Model.getAuthId())) {
1231
                    for (int l2Id : l2AuthIdsForPartner) {
1232
                        if (l2ModelsByAuthId.containsKey(l2Id) && l2Id != l1Model.getAuthId()) {
1233
                            l2SharedCount.merge(l2Id, 1, Integer::sum);
1234
                        }
1235
                    }
1236
                }
1237
            }
1238
            // Assign L1 to the L2 with most shared partners
1239
            if (!l2SharedCount.isEmpty()) {
1240
                int bestL2 = l2SharedCount.entrySet().stream()
1241
                        .max(Map.Entry.comparingByValue()).get().getKey();
1242
                l2TeamMap.get(bestL2).add(l1Model);
1243
                addedL1AuthIds.add(l1Model.getAuthId());
1244
            }
1245
        }
1246
 
1247
        // For each L2, find which L3 shares the most partners -> that's their L3
36210 ranu 1248
        Set<Integer> addedL2AuthIds = new HashSet<>();
1249
        for (RbmCallTargetModel l2Model : l2ModelsByAuthId.values()) {
36225 ranu 1250
            Map<Integer, Integer> l3SharedCount = new HashMap<>();
1251
            for (Map.Entry<Integer, Map<EscalationType, Set<Integer>>> entry : partnerToAuthByLevel.entrySet()) {
1252
                Map<EscalationType, Set<Integer>> levelMap = entry.getValue();
1253
                Set<Integer> l2AuthIdsForPartner = levelMap.getOrDefault(EscalationType.L2, Collections.emptySet());
1254
                Set<Integer> l3AuthIdsForPartner = levelMap.getOrDefault(EscalationType.L3, Collections.emptySet());
1255
                if (l2AuthIdsForPartner.contains(l2Model.getAuthId())) {
1256
                    for (int l3Id : l3AuthIdsForPartner) {
1257
                        if (l3ModelsByAuthId.containsKey(l3Id)) {
1258
                            l3SharedCount.merge(l3Id, 1, Integer::sum);
1259
                        }
1260
                    }
1261
                }
1262
            }
1263
            if (!l3SharedCount.isEmpty()) {
1264
                int bestL3 = l3SharedCount.entrySet().stream()
1265
                        .max(Map.Entry.comparingByValue()).get().getKey();
1266
                l3TeamMap.get(bestL3).add(l2Model);
36210 ranu 1267
                addedL2AuthIds.add(l2Model.getAuthId());
1268
            }
1269
        }
1270
 
36225 ranu 1271
        // For L1s not mapped to any L2, check if they map directly to an L3 via shared partners
1272
        Map<Integer, List<RbmCallTargetModel>> l3DirectL1Map = new LinkedHashMap<>();
1273
        for (RbmCallTargetModel l3Model : l3ModelsByAuthId.values()) {
1274
            l3DirectL1Map.put(l3Model.getAuthId(), new ArrayList<>());
35631 ranu 1275
        }
1276
        for (RbmCallTargetModel l1Model : l1ModelsByAuthId.values()) {
36225 ranu 1277
            if (addedL1AuthIds.contains(l1Model.getAuthId())) continue;
1278
            Map<Integer, Integer> l3SharedCount = new HashMap<>();
1279
            for (Map.Entry<Integer, Map<EscalationType, Set<Integer>>> entry : partnerToAuthByLevel.entrySet()) {
1280
                Map<EscalationType, Set<Integer>> levelMap = entry.getValue();
1281
                Set<Integer> l1IdsForPartner = levelMap.getOrDefault(EscalationType.L1, Collections.emptySet());
1282
                Set<Integer> l3AuthIdsForPartner = levelMap.getOrDefault(EscalationType.L3, Collections.emptySet());
1283
                if (l1IdsForPartner.contains(l1Model.getAuthId())) {
1284
                    for (int l3Id : l3AuthIdsForPartner) {
1285
                        if (l3ModelsByAuthId.containsKey(l3Id)) {
1286
                            l3SharedCount.merge(l3Id, 1, Integer::sum);
1287
                        }
1288
                    }
1289
                }
1290
            }
1291
            if (!l3SharedCount.isEmpty()) {
1292
                int bestL3 = l3SharedCount.entrySet().stream()
1293
                        .max(Map.Entry.comparingByValue()).get().getKey();
1294
                l3DirectL1Map.get(bestL3).add(l1Model);
35631 ranu 1295
                addedL1AuthIds.add(l1Model.getAuthId());
1296
            }
1297
        }
1298
 
36225 ranu 1299
        // Build sorted result: L3 -> L2 -> L1 (with direct L1s under L3 if no L2)
35631 ranu 1300
        List<RbmCallTargetModel> sortedModels = new ArrayList<>();
1301
 
36210 ranu 1302
        List<RbmCallTargetModel> l3Sorted = new ArrayList<>(l3ModelsByAuthId.values());
1303
        l3Sorted.sort(Comparator.comparing(RbmCallTargetModel::getRbmName));
35631 ranu 1304
 
36210 ranu 1305
        for (RbmCallTargetModel l3Model : l3Sorted) {
1306
            sortedModels.add(l3Model);
1307
            List<RbmCallTargetModel> l2Team = l3TeamMap.getOrDefault(l3Model.getAuthId(), Collections.emptyList());
1308
            l2Team.sort(Comparator.comparing(RbmCallTargetModel::getRbmName));
1309
            for (RbmCallTargetModel l2Model : l2Team) {
1310
                sortedModels.add(l2Model);
1311
                List<RbmCallTargetModel> l1Team = l2TeamMap.getOrDefault(l2Model.getAuthId(), Collections.emptyList());
1312
                l1Team.sort(Comparator.comparing(RbmCallTargetModel::getRbmName));
1313
                sortedModels.addAll(l1Team);
1314
            }
36225 ranu 1315
            // Add L1s that report directly to this L3 (no L2 in between)
1316
            List<RbmCallTargetModel> directL1Team = l3DirectL1Map.getOrDefault(l3Model.getAuthId(), Collections.emptyList());
1317
            directL1Team.sort(Comparator.comparing(RbmCallTargetModel::getRbmName));
1318
            sortedModels.addAll(directL1Team);
36210 ranu 1319
        }
1320
 
36225 ranu 1321
        // Add L2s not mapped to any L3
36210 ranu 1322
        List<RbmCallTargetModel> unmappedL2 = new ArrayList<>();
1323
        for (RbmCallTargetModel l2Model : l2ModelsByAuthId.values()) {
1324
            if (!addedL2AuthIds.contains(l2Model.getAuthId())) {
1325
                unmappedL2.add(l2Model);
1326
            }
1327
        }
1328
        unmappedL2.sort(Comparator.comparing(RbmCallTargetModel::getRbmName));
1329
        for (RbmCallTargetModel l2Model : unmappedL2) {
35631 ranu 1330
            sortedModels.add(l2Model);
1331
            List<RbmCallTargetModel> team = l2TeamMap.getOrDefault(l2Model.getAuthId(), Collections.emptyList());
1332
            team.sort(Comparator.comparing(RbmCallTargetModel::getRbmName));
1333
            sortedModels.addAll(team);
1334
        }
1335
 
36225 ranu 1336
        // Add any L1s not mapped to any L2 or L3
35631 ranu 1337
        List<RbmCallTargetModel> unmappedL1 = new ArrayList<>();
1338
        for (RbmCallTargetModel m : l1ModelsByAuthId.values()) {
1339
            if (!addedL1AuthIds.contains(m.getAuthId())) {
1340
                unmappedL1.add(m);
1341
            }
1342
        }
1343
        unmappedL1.sort(Comparator.comparing(RbmCallTargetModel::getRbmName));
1344
        sortedModels.addAll(unmappedL1);
1345
 
1346
        LOGGER.info("RBM Call Target - TOTAL TIME: {}ms, RBM count: {}", System.currentTimeMillis() - methodStart, sortedModels.size());
1347
        return sortedModels;
1348
    }
1349
 
1350
    @Override
1351
    public List<OutOfSequenceDetailModel> getOutOfSequenceDetails(int authId) {
1352
 
1353
        LocalDate today = LocalDate.now();
1354
        LocalDateTime start = today.atStartOfDay();
1355
        LocalDateTime end = today.plusDays(1).atStartOfDay();
1356
 
1357
        List<RbmCallSequenceLog> logs =
1358
                rbmCallSequenceLogRepository.selectByAuthIdAndDateRange(authId, start, end);
1359
 
35654 ranu 1360
        Map<Integer, RbmCallSequenceLog> uniqueOosLogsByFofoId = new LinkedHashMap<>();
35631 ranu 1361
 
1362
        for (RbmCallSequenceLog log : logs) {
1363
            if (log.isOutOfSequence()) {
35654 ranu 1364
                // Keep only the first occurrence per fofoId (latest entry since ordered by id DESC)
1365
                uniqueOosLogsByFofoId.putIfAbsent(log.getFofoId(), log);
35631 ranu 1366
            }
1367
        }
1368
 
35654 ranu 1369
        if (uniqueOosLogsByFofoId.isEmpty()) {
35631 ranu 1370
            return Collections.emptyList();
1371
        }
1372
 
35654 ranu 1373
        Set<Integer> fofoIds = uniqueOosLogsByFofoId.keySet();
35631 ranu 1374
        Map<Integer, CustomRetailer> retailerMap = Collections.emptyMap();
1375
        try {
1376
            retailerMap = retailerService.getFofoRetailers(new ArrayList<>(fofoIds));
1377
        } catch (ProfitMandiBusinessException e) {
1378
            LOGGER.error("Error fetching fofo stores", e);
1379
        }
1380
 
1381
        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("hh:mm a");
1382
        List<OutOfSequenceDetailModel> result = new ArrayList<>();
1383
 
35654 ranu 1384
        for (RbmCallSequenceLog log : uniqueOosLogsByFofoId.values()) {
35631 ranu 1385
            CustomRetailer retailer = retailerMap.get(log.getFofoId());
1386
            String partyName = retailer != null
1387
                    ? retailer.getBusinessName()
1388
                    : "Unknown (" + log.getFofoId() + ")";
1389
            String code = retailer != null
1390
                    ? retailer.getCode()
1391
                    : "-";
1392
 
1393
            String time = log.getCreateTimestamp() != null
1394
                    ? log.getCreateTimestamp().format(timeFormatter)
1395
                    : "-";
1396
 
1397
            result.add(new OutOfSequenceDetailModel(partyName, code, time));
1398
        }
1399
 
1400
        return result;
1401
    }
1402
 
35645 ranu 1403
    @Override
35672 ranu 1404
    public List<CalledPartnerDetailModel> getCalledPartnerDetails(int authId) throws ProfitMandiBusinessException {
35730 ranu 1405
        return getCalledPartnerDetails(authId, LocalDate.now());
1406
    }
1407
 
1408
    @Override
1409
    public List<CalledPartnerDetailModel> getCalledPartnerDetails(int authId, LocalDate date) throws ProfitMandiBusinessException {
35759 ranu 1410
        // Get all call logs for this auth user on this date
35760 ranu 1411
        LOGGER.info("getCalledPartnerDetails: authId={}, date={}", authId, date);
35761 ranu 1412
        List<AgentCallLog> callLogs = agentCallLogRepository.findByAuthIdAndDate(authId, date);
35760 ranu 1413
        LOGGER.info("Found {} call logs for authId={} on date={}", callLogs.size(), authId, date);
35670 ranu 1414
 
35759 ranu 1415
        if (callLogs.isEmpty()) {
35672 ranu 1416
            return Collections.emptyList();
1417
        }
35670 ranu 1418
 
35759 ranu 1419
        // Build a map of normalized customer number -> fofoId
1420
        Map<String, Integer> customerToFofoIdMap = new HashMap<>();
1421
        Set<String> normalizedNumbers = new HashSet<>();
35672 ranu 1422
 
35763 ranu 1423
        for (AgentCallLog callLog : callLogs) {
35759 ranu 1424
            String customerNumber = callLog.getCustomerNumber();
1425
            if (customerNumber != null) {
1426
                String normalized = customerNumber.startsWith("+91") ? customerNumber.substring(3) : customerNumber;
1427
                normalizedNumbers.add(normalized);
1428
            }
35672 ranu 1429
        }
1430
 
35759 ranu 1431
        // For each normalized number, find fofoId from retailer_contact first, then address
1432
        for (String mobile : normalizedNumbers) {
1433
            Integer fofoId = findFofoIdByMobile(mobile);
1434
            if (fofoId != null) {
1435
                customerToFofoIdMap.put(mobile, fofoId);
1436
            }
35670 ranu 1437
        }
1438
 
35759 ranu 1439
        // Get unique fofoIds for retailer lookup
1440
        Set<Integer> fofoIds = new HashSet<>(customerToFofoIdMap.values());
1441
        Map<Integer, CustomRetailer> retailerMap = Collections.emptyMap();
1442
        if (!fofoIds.isEmpty()) {
1443
            try {
1444
                retailerMap = retailerService.getFofoRetailers(new ArrayList<>(fofoIds));
1445
            } catch (ProfitMandiBusinessException e) {
1446
                LOGGER.error("Error fetching fofo stores", e);
1447
            }
35672 ranu 1448
        }
1449
 
35759 ranu 1450
        // Get today's remarks for these fofoIds
1451
        Map<Integer, List<PartnerCollectionRemark>> fofoRemarkMap = new HashMap<>();
1452
        if (!fofoIds.isEmpty()) {
1453
            List<PartnerCollectionRemark> todayRemarks = partnerCollectionRemarkRepository
1454
                    .selectAllByFofoIdsOnDate(new ArrayList<>(fofoIds), date);
1455
            for (PartnerCollectionRemark remark : todayRemarks) {
1456
                fofoRemarkMap.computeIfAbsent(remark.getFofoId(), k -> new ArrayList<>()).add(remark);
1457
            }
35672 ranu 1458
        }
1459
 
35759 ranu 1460
        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("hh:mm a");
1461
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy hh:mm a");
1462
        List<CalledPartnerDetailModel> result = new ArrayList<>();
35672 ranu 1463
 
35759 ranu 1464
        for (com.spice.profitmandi.dao.entity.cs.AgentCallLog callLog : callLogs) {
1465
            String customerNumber = callLog.getCustomerNumber();
1466
            if (customerNumber == null) {
1467
                continue;
1468
            }
35672 ranu 1469
 
35759 ranu 1470
            String normalized = customerNumber.startsWith("+91") ? customerNumber.substring(3) : customerNumber;
1471
            Integer fofoId = customerToFofoIdMap.get(normalized);
35672 ranu 1472
 
35759 ranu 1473
            String partyName = "Unknown";
1474
            String code = "-";
35672 ranu 1475
 
35759 ranu 1476
            if (fofoId != null) {
1477
                CustomRetailer retailer = retailerMap.get(fofoId);
1478
                if (retailer != null) {
1479
                    partyName = retailer.getBusinessName();
1480
                    code = retailer.getCode();
1481
                } else {
1482
                    partyName = "Unknown (" + fofoId + ")";
1483
                }
1484
            } else {
1485
                partyName = "Unknown (" + normalized + ")";
1486
            }
35672 ranu 1487
 
35759 ranu 1488
            // Get remark if available
1489
            String remarkValue = "-";
1490
            String messageValue = "-";
1491
            String remarkTime = "-";
35672 ranu 1492
 
35759 ranu 1493
            if (fofoId != null && fofoRemarkMap.containsKey(fofoId)) {
1494
                List<PartnerCollectionRemark> remarks = fofoRemarkMap.get(fofoId);
1495
                if (!remarks.isEmpty()) {
1496
                    PartnerCollectionRemark remark = remarks.get(0);
1497
                    remarkValue = remark.getRemark() != null ? remark.getRemark().getValue() : "-";
1498
                    messageValue = remark.getMessage() != null ? remark.getMessage() : "-";
1499
                    remarkTime = remark.getCreateTimestamp() != null ? remark.getCreateTimestamp().format(timeFormatter) : "-";
1500
                }
1501
            }
35672 ranu 1502
 
35759 ranu 1503
            // Build call log data
1504
            String recordingUrl = callLog.getRecordingUrl();
1505
            String callStatus = callLog.getCallStatus();
1506
            String callDuration = callLog.getCallDuration();
1507
            String callDateTime = null;
1508
            if (callLog.getCallDate() != null && callLog.getCallTime() != null) {
1509
                LocalDateTime callDateTimeObj = LocalDateTime.of(callLog.getCallDate(), callLog.getCallTime());
1510
                callDateTime = callDateTimeObj.format(dateTimeFormatter);
35672 ranu 1511
            }
35759 ranu 1512
 
1513
            result.add(new CalledPartnerDetailModel(partyName, code, remarkValue, messageValue, remarkTime,
1514
                    recordingUrl, callStatus, callDuration, callDateTime));
35672 ranu 1515
        }
1516
 
35759 ranu 1517
        return result;
1518
    }
35672 ranu 1519
 
35759 ranu 1520
    private Integer findFofoIdByMobile(String mobile) {
1521
        // First check retailer_contact
1522
        List<RetailerContact> contacts = retailerContactRepository.selectByMobile(mobile);
1523
        if (contacts != null && !contacts.isEmpty()) {
1524
            return contacts.get(0).getFofoId();
1525
        }
1526
 
1527
        // Fallback to user.address
35762 ranu 1528
        List<Address> addresses = addressRepository.selectAllByPhoneNumber(mobile);
1529
        if (addresses != null && !addresses.isEmpty()) {
1530
            return addresses.get(0).getRetaierId();
35759 ranu 1531
        }
1532
 
1533
        return null;
35672 ranu 1534
    }
1535
 
35757 ranu 1536
    private List<CalledPartnerDetailModel> buildCalledPartnerResult(List<PartnerCollectionRemark> allRemarks) {
1537
        if (allRemarks.isEmpty()) {
35672 ranu 1538
            return Collections.emptyList();
1539
        }
1540
 
35757 ranu 1541
        // Get unique fofoIds for retailer lookup
1542
        Set<Integer> fofoIds = allRemarks.stream()
1543
                .map(PartnerCollectionRemark::getFofoId)
1544
                .collect(Collectors.toSet());
35670 ranu 1545
        Map<Integer, CustomRetailer> retailerMap = Collections.emptyMap();
1546
        try {
1547
            retailerMap = retailerService.getFofoRetailers(new ArrayList<>(fofoIds));
1548
        } catch (ProfitMandiBusinessException e) {
1549
            LOGGER.error("Error fetching fofo stores", e);
1550
        }
1551
 
35702 ranu 1552
        // Fetch call logs for remarks that have agentCallLogId
35721 ranu 1553
        Map<Long, com.spice.profitmandi.dao.entity.cs.AgentCallLog> callLogMap = new HashMap<>();
1554
        try {
35757 ranu 1555
            List<Long> callLogIds = allRemarks.stream()
35721 ranu 1556
                    .filter(r -> r.getAgentCallLogId() > 0)
1557
                    .map(PartnerCollectionRemark::getAgentCallLogId)
1558
                    .collect(Collectors.toList());
35702 ranu 1559
 
35721 ranu 1560
            if (!callLogIds.isEmpty()) {
35720 ranu 1561
                List<com.spice.profitmandi.dao.entity.cs.AgentCallLog> callLogs = agentCallLogRepository.findByIds(callLogIds);
35721 ranu 1562
                if (callLogs != null) {
1563
                    callLogMap = callLogs.stream()
1564
                            .collect(Collectors.toMap(com.spice.profitmandi.dao.entity.cs.AgentCallLog::getId, c -> c, (a, b) -> a));
1565
                }
35720 ranu 1566
            }
35721 ranu 1567
        } catch (Exception e) {
1568
            LOGGER.error("Error fetching call logs by ids", e);
35702 ranu 1569
        }
1570
 
35670 ranu 1571
        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("hh:mm a");
35702 ranu 1572
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy hh:mm a");
35670 ranu 1573
        List<CalledPartnerDetailModel> result = new ArrayList<>();
1574
 
35757 ranu 1575
        for (PartnerCollectionRemark remark : allRemarks) {
35670 ranu 1576
            CustomRetailer retailer = retailerMap.get(remark.getFofoId());
1577
            String partyName = retailer != null
1578
                    ? retailer.getBusinessName()
1579
                    : "Unknown (" + remark.getFofoId() + ")";
1580
            String code = retailer != null
1581
                    ? retailer.getCode()
1582
                    : "-";
1583
 
1584
            String remarkValue = remark.getRemark() != null
1585
                    ? remark.getRemark().getValue()
1586
                    : "-";
1587
 
35677 ranu 1588
            String messageValue = remark.getMessage() != null
1589
                    ? remark.getMessage()
1590
                    : "-";
1591
 
35670 ranu 1592
            String time = remark.getCreateTimestamp() != null
1593
                    ? remark.getCreateTimestamp().format(timeFormatter)
1594
                    : "-";
1595
 
35702 ranu 1596
            // Get call log data if available
1597
            String recordingUrl = null;
1598
            String callStatus = null;
1599
            String callDuration = null;
1600
            String callDateTime = null;
1601
 
35721 ranu 1602
            try {
1603
                if (remark.getAgentCallLogId() > 0 && callLogMap.containsKey(remark.getAgentCallLogId())) {
1604
                    com.spice.profitmandi.dao.entity.cs.AgentCallLog callLog = callLogMap.get(remark.getAgentCallLogId());
1605
                    recordingUrl = callLog.getRecordingUrl();
1606
                    callStatus = callLog.getCallStatus();
1607
                    callDuration = callLog.getCallDuration();
1608
                    if (callLog.getCallDate() != null && callLog.getCallTime() != null) {
1609
                        LocalDateTime callDateTimeObj = LocalDateTime.of(callLog.getCallDate(), callLog.getCallTime());
1610
                        callDateTime = callDateTimeObj.format(dateTimeFormatter);
1611
                    }
35702 ranu 1612
                }
35721 ranu 1613
            } catch (Exception e) {
1614
                LOGGER.error("Error processing call log for remark id: {}", remark.getId(), e);
35702 ranu 1615
            }
1616
 
1617
            result.add(new CalledPartnerDetailModel(partyName, code, remarkValue, messageValue, time,
1618
                    recordingUrl, callStatus, callDuration, callDateTime));
35670 ranu 1619
        }
1620
 
1621
        return result;
1622
    }
1623
 
1624
    @Override
35645 ranu 1625
    public List<List<String>> getRbmCallTargetRawDataByAuthId(int authId) throws Exception {
1626
        List<List<String>> rows = new ArrayList<>();
35631 ranu 1627
 
35645 ranu 1628
        // Get auth user
1629
        List<AuthUser> authUsers = authRepository.selectByIds(Collections.singletonList(authId));
1630
        if (authUsers.isEmpty()) {
1631
            return rows;
1632
        }
1633
        AuthUser authUser = authUsers.get(0);
1634
 
35757 ranu 1635
        // Get positions to determine if L2
35645 ranu 1636
        List<Position> positions = positionRepository.selectPositionByAuthIds(Collections.singletonList(authId));
1637
        boolean isL2 = positions.stream()
1638
                .anyMatch(p -> ProfitMandiConstants.TICKET_CATEGORY_RBM == p.getCategoryId()
1639
                        && EscalationType.L2.equals(p.getEscalationType()));
1640
 
35757 ranu 1641
        LocalDateTime startDate = LocalDate.now().atStartOfDay();
1642
        LocalDate firstOfMonth = LocalDate.now().withDayOfMonth(1);
1643
        LocalDate endOfMonth = LocalDate.now().withDayOfMonth(LocalDate.now().lengthOfMonth()).plusDays(1);
35645 ranu 1644
 
35757 ranu 1645
        // Get fofo IDs from mtdBillingData (same source as Partner Count in getRbmCallTargetModels)
1646
        List<RbmWeeklyBillingModel> mtdBillingData = getWeeklyBillingDataForMonth(firstOfMonth, endOfMonth);
1647
 
1648
        List<Integer> fofoIdList;
35645 ranu 1649
        if (isL2) {
35757 ranu 1650
            // L2: get fofo IDs from escalated tickets (same as getRbmCallTargetModels)
35645 ranu 1651
            List<Ticket> escalatedTickets = ticketRepository.selectOpenEscalatedTicketsByAuthIds(Collections.singletonList(authId));
1652
            fofoIdList = escalatedTickets.stream()
1653
                    .filter(t -> t.getL2AuthUser() == authId
1654
                            || t.getL3AuthUser() == authId
1655
                            || t.getL4AuthUser() == authId
1656
                            || t.getL5AuthUser() == authId)
1657
                    .map(Ticket::getFofoId)
1658
                    .distinct()
1659
                    .collect(Collectors.toList());
35757 ranu 1660
        } else {
1661
            // L1: get fofo IDs from mtdBillingData with isTargetedPartner (same as Partner Count)
1662
            fofoIdList = mtdBillingData.stream()
1663
                    .filter(RbmWeeklyBillingModel::isTargetedPartner)
1664
                    .filter(m -> m.getAuthId() == authId)
1665
                    .map(RbmWeeklyBillingModel::getFofoId)
1666
                    .distinct()
1667
                    .collect(Collectors.toList());
35645 ranu 1668
        }
1669
 
1670
        if (fofoIdList.isEmpty()) {
1671
            return rows;
1672
        }
1673
 
35757 ranu 1674
        // MTD billed fofoIds for zero billing check
1675
        Set<Integer> mtdBilledFofoIds = mtdBillingData.stream()
1676
                .filter(RbmWeeklyBillingModel::isMtdBilled)
1677
                .map(RbmWeeklyBillingModel::getFofoId)
1678
                .collect(Collectors.toSet());
35645 ranu 1679
 
35757 ranu 1680
        // Collection rank map for status calculation
35645 ranu 1681
        Map<Integer, Integer> collectionRankMap = new HashMap<>();
1682
        try {
1683
            collectionRankMap = partnerCollectionService.getCollectionRankMap(fofoIdList, startDate);
1684
        } catch (ProfitMandiBusinessException e) {
1685
            LOGGER.error("Error fetching collection rank map", e);
1686
        }
1687
 
1688
        // Resolve partner names/codes
1689
        Map<Integer, CustomRetailer> retailerMap = Collections.emptyMap();
35757 ranu 1690
        if (!fofoIdList.isEmpty()) {
35645 ranu 1691
            try {
35757 ranu 1692
                retailerMap = retailerService.getFofoRetailers(fofoIdList);
35645 ranu 1693
            } catch (ProfitMandiBusinessException e) {
1694
                LOGGER.error("Error fetching fofo retailers for raw data", e);
1695
            }
1696
        }
1697
 
1698
        String rbmName = authUser.getFullName() + (isL2 ? " (L2)" : "");
1699
 
35757 ranu 1700
        // Build rows for ALL partners (same count as Partner Count)
1701
        for (Integer fofoId : fofoIdList) {
1702
            // Default to rank 5 (Normal) for partners without collection plan
35645 ranu 1703
            int rank = collectionRankMap.getOrDefault(fofoId, 5);
1704
            boolean hasZeroBilling = !mtdBilledFofoIds.contains(fofoId);
1705
 
35757 ranu 1706
            // Status assignment with same priority as getRbmCallTargetModels
35645 ranu 1707
            String status;
1708
            if (rank == 1) {
1709
                status = "Plan Today";
1710
            } else if (rank == 2) {
1711
                status = "Carry Forward";
35757 ranu 1712
            } else if (hasZeroBilling) {
1713
                status = "Zero Billing";
35645 ranu 1714
            } else if (rank == 3) {
1715
                status = "Untouched";
1716
            } else if (rank == 4) {
1717
                status = "Future Plan";
1718
            } else {
1719
                status = "Normal";
1720
            }
1721
 
1722
            CustomRetailer retailer = retailerMap.get(fofoId);
1723
            String partnerName = retailer != null ? retailer.getBusinessName() : "Unknown (" + fofoId + ")";
1724
            String partnerCode = retailer != null ? retailer.getCode() : "-";
1725
 
1726
            rows.add(Arrays.asList(partnerName, partnerCode, status, rbmName));
1727
        }
1728
 
1729
        return rows;
1730
    }
1731
 
35843 ranu 1732
    /**
1733
     * Get count of distinct partners called today based on call logs.
1734
     * Maps customerNumber from call log to fofoId using retailer_contact and address.
1735
     * If same fofoId is called multiple times, counts only once.
1736
     * Numbers without fofoId mapping are also counted (by distinct customer number).
1737
     *
1738
     * @param authId the RBM auth ID
1739
     * @return count of distinct partners/numbers called today
1740
     */
1741
    public long getCalledCountFromCallLogs(long authId) {
36234 ranu 1742
        return getCalledCountFromCallLogs(authId, LocalDate.now());
1743
    }
35843 ranu 1744
 
36234 ranu 1745
    public long getCalledCountFromCallLogs(long authId, LocalDate date) {
36276 ranu 1746
        return getCallStats(authId, date)[0];
1747
    }
1748
 
1749
    /**
36284 ranu 1750
     * Batch-builds a mobile (normalized, +91 stripped) -> fofoId mapping for all the given mobiles
1751
     * in just two queries (retailer_contact, then address fallback for unmapped numbers).
1752
     * Replaces the previous per-call-log findFofoIdByMobile() N+1 lookups.
1753
     */
1754
    private Map<String, Integer> buildMobileToFofoIdMap(Set<String> normalizedMobiles) {
1755
        Map<String, Integer> result = new HashMap<>();
1756
        if (normalizedMobiles == null || normalizedMobiles.isEmpty()) {
1757
            return result;
1758
        }
1759
        List<String> mobilesList = new ArrayList<>(normalizedMobiles);
1760
 
1761
        // First pass: retailer_contact
1762
        List<RetailerContact> contacts = retailerContactRepository.selectByMobiles(mobilesList);
1763
        for (RetailerContact rc : contacts) {
1764
            // Keep the first fofoId we see per mobile (matches old single-mobile behavior of get(0))
1765
            result.putIfAbsent(rc.getMobile(), rc.getFofoId());
1766
        }
1767
 
1768
        // Second pass: address fallback for mobiles not yet mapped
1769
        List<String> unmapped = mobilesList.stream()
1770
                .filter(m -> !result.containsKey(m))
1771
                .collect(Collectors.toList());
1772
        if (!unmapped.isEmpty()) {
1773
            List<Address> addresses = addressRepository.selectAllByPhoneNumbers(unmapped);
1774
            for (Address addr : addresses) {
1775
                result.putIfAbsent(addr.getPhoneNumber(), addr.getRetaierId());
1776
            }
1777
        }
1778
        return result;
1779
    }
1780
 
1781
    /**
1782
     * In-memory variant of getCallStats() that uses pre-fetched call logs and mobile→fofoId mapping.
1783
     * Used by the batch-fetched RBM Call Target loop to avoid N+1 query patterns.
1784
     */
1785
    private long[] getCallStatsFromLogs(List<AgentCallLog> callLogs, Map<String, Integer> mobileToFofoIdMap) {
1786
        if (callLogs == null || callLogs.isEmpty()) {
1787
            return new long[]{0, 0, 0};
1788
        }
1789
 
1790
        Set<Integer> calledFofoIds = new HashSet<>();
1791
        Set<String> calledNumbersWithoutFofoId = new HashSet<>();
1792
        long totalRecordingCalls = 0;
1793
        Set<String> uniqueRecordingNumbers = new HashSet<>();
1794
 
1795
        for (AgentCallLog callLog : callLogs) {
1796
            String customerNumber = callLog.getCustomerNumber();
1797
            if (customerNumber != null) {
1798
                String normalized = customerNumber.startsWith("+91") ? customerNumber.substring(3) : customerNumber;
1799
                Integer fofoId = mobileToFofoIdMap.get(normalized);
1800
                if (fofoId != null) {
1801
                    calledFofoIds.add(fofoId);
1802
                } else {
1803
                    calledNumbersWithoutFofoId.add(normalized);
1804
                }
1805
 
1806
                if (callLog.getRecordingUrl() != null && !callLog.getRecordingUrl().isEmpty()
1807
                        && !"None".equalsIgnoreCase(callLog.getRecordingUrl())) {
1808
                    totalRecordingCalls++;
1809
                    uniqueRecordingNumbers.add(normalized);
1810
                }
1811
            }
1812
        }
1813
 
1814
        long calledCount = calledFofoIds.size() + calledNumbersWithoutFofoId.size();
1815
        return new long[]{calledCount, totalRecordingCalls, uniqueRecordingNumbers.size()};
1816
    }
1817
 
1818
    /**
36276 ranu 1819
     * Returns call stats: [0] = called count, [1] = total recording calls, [2] = unique recording calls
1820
     */
1821
    public long[] getCallStats(long authId, LocalDate date) {
36234 ranu 1822
        List<AgentCallLog> callLogs = agentCallLogRepository.findByAuthIdAndDate(authId, date);
1823
 
35843 ranu 1824
        if (callLogs == null || callLogs.isEmpty()) {
36276 ranu 1825
            return new long[]{0, 0, 0};
35843 ranu 1826
        }
1827
 
1828
        Set<Integer> calledFofoIds = new HashSet<>();
1829
        Set<String> calledNumbersWithoutFofoId = new HashSet<>();
36276 ranu 1830
        long totalRecordingCalls = 0;
1831
        Set<String> uniqueRecordingNumbers = new HashSet<>();
35843 ranu 1832
 
1833
        for (AgentCallLog callLog : callLogs) {
1834
            String customerNumber = callLog.getCustomerNumber();
1835
            if (customerNumber != null) {
1836
                // Normalize the phone number (remove +91 prefix if present)
1837
                String normalized = customerNumber.startsWith("+91") ? customerNumber.substring(3) : customerNumber;
1838
 
1839
                // Find fofoId from retailer_contact or address
1840
                Integer fofoId = findFofoIdByMobile(normalized);
1841
                if (fofoId != null) {
1842
                    calledFofoIds.add(fofoId);
1843
                } else {
1844
                    // Number not found in retailer_contact or address, count by distinct number
1845
                    calledNumbersWithoutFofoId.add(normalized);
1846
                }
36276 ranu 1847
 
1848
                // Count calls with recordings
1849
                if (callLog.getRecordingUrl() != null && !callLog.getRecordingUrl().isEmpty()
1850
                        && !"None".equalsIgnoreCase(callLog.getRecordingUrl())) {
1851
                    totalRecordingCalls++;
1852
                    uniqueRecordingNumbers.add(normalized);
1853
                }
35843 ranu 1854
            }
1855
        }
1856
 
1857
        // Total called = distinct fofoIds + distinct numbers without fofoId mapping
36276 ranu 1858
        long calledCount = calledFofoIds.size() + calledNumbersWithoutFofoId.size();
1859
        return new long[]{calledCount, totalRecordingCalls, uniqueRecordingNumbers.size()};
35843 ranu 1860
    }
1861
 
35852 ranu 1862
    @Override
1863
    public List<List<String>> getAllCallDataByDate(LocalDate date) throws Exception {
1864
        List<List<String>> rows = new ArrayList<>();
1865
 
1866
        // Add header row
1867
        rows.add(Arrays.asList("RBM Name", "Partner Name", "Code", "Remark", "Call Status", "Call Duration", "Call Date Time", "Recording URL"));
1868
 
1869
        // Get all call logs for the date
1870
        List<AgentCallLog> allCallLogs = agentCallLogRepository.findAllByDate(date);
1871
        LOGGER.info("getAllCallDataByDate: Found {} call logs for date {}", allCallLogs.size(), date);
1872
 
1873
        if (allCallLogs.isEmpty()) {
1874
            return rows;
1875
        }
1876
 
1877
        // Get unique authIds from call logs
1878
        Set<Long> authIds = allCallLogs.stream()
1879
                .map(AgentCallLog::getAuthId)
1880
                .collect(Collectors.toSet());
1881
 
1882
        // Get auth users for RBM names
1883
        List<Integer> authIdInts = authIds.stream().map(Long::intValue).collect(Collectors.toList());
1884
        Map<Integer, AuthUser> authUserMap = authRepository.selectByIds(authIdInts).stream()
1885
                .collect(Collectors.toMap(AuthUser::getId, au -> au, (a, b) -> a));
1886
 
1887
        // Build a map of normalized customer number -> fofoId
1888
        Map<String, Integer> customerToFofoIdMap = new HashMap<>();
1889
        Set<String> normalizedNumbers = new HashSet<>();
1890
 
1891
        for (AgentCallLog callLog : allCallLogs) {
1892
            String customerNumber = callLog.getCustomerNumber();
1893
            if (customerNumber != null) {
1894
                String normalized = customerNumber.startsWith("+91") ? customerNumber.substring(3) : customerNumber;
1895
                normalizedNumbers.add(normalized);
1896
            }
1897
        }
1898
 
1899
        // For each normalized number, find fofoId
1900
        for (String mobile : normalizedNumbers) {
1901
            Integer fofoId = findFofoIdByMobile(mobile);
1902
            if (fofoId != null) {
1903
                customerToFofoIdMap.put(mobile, fofoId);
1904
            }
1905
        }
1906
 
1907
        // Get unique fofoIds for retailer lookup
1908
        Set<Integer> fofoIds = new HashSet<>(customerToFofoIdMap.values());
1909
        Map<Integer, CustomRetailer> retailerMap = Collections.emptyMap();
1910
        if (!fofoIds.isEmpty()) {
1911
            try {
1912
                retailerMap = retailerService.getFofoRetailers(new ArrayList<>(fofoIds));
1913
            } catch (ProfitMandiBusinessException e) {
1914
                LOGGER.error("Error fetching fofo stores", e);
1915
            }
1916
        }
1917
 
1918
        // Get remarks for these fofoIds on this date
1919
        Map<Integer, List<PartnerCollectionRemark>> fofoRemarkMap = new HashMap<>();
1920
        if (!fofoIds.isEmpty()) {
1921
            List<PartnerCollectionRemark> dateRemarks = partnerCollectionRemarkRepository
1922
                    .selectAllByFofoIdsOnDate(new ArrayList<>(fofoIds), date);
1923
            for (PartnerCollectionRemark remark : dateRemarks) {
1924
                fofoRemarkMap.computeIfAbsent(remark.getFofoId(), k -> new ArrayList<>()).add(remark);
1925
            }
1926
        }
1927
 
1928
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy hh:mm a");
1929
 
1930
        // Build rows
1931
        for (AgentCallLog callLog : allCallLogs) {
1932
            String customerNumber = callLog.getCustomerNumber();
1933
            if (customerNumber == null) {
1934
                continue;
1935
            }
1936
 
1937
            // Get RBM Name
1938
            AuthUser authUser = authUserMap.get((int) callLog.getAuthId());
1939
            String rbmName = authUser != null ? authUser.getFullName() : "Unknown";
1940
 
1941
            String normalized = customerNumber.startsWith("+91") ? customerNumber.substring(3) : customerNumber;
1942
            Integer fofoId = customerToFofoIdMap.get(normalized);
1943
 
1944
            String partyName = "Unknown";
1945
            String code = "-";
1946
 
1947
            if (fofoId != null) {
1948
                CustomRetailer retailer = retailerMap.get(fofoId);
1949
                if (retailer != null) {
1950
                    partyName = retailer.getBusinessName();
1951
                    code = retailer.getCode();
1952
                } else {
1953
                    partyName = "Unknown (" + fofoId + ")";
1954
                }
1955
            } else {
1956
                partyName = "Unknown (" + normalized + ")";
1957
            }
1958
 
1959
            // Get remark if available
1960
            String remarkValue = "-";
1961
            if (fofoId != null && fofoRemarkMap.containsKey(fofoId)) {
1962
                List<PartnerCollectionRemark> remarks = fofoRemarkMap.get(fofoId);
1963
                if (!remarks.isEmpty()) {
1964
                    PartnerCollectionRemark remark = remarks.get(0);
1965
                    remarkValue = remark.getRemark() != null ? remark.getRemark().getValue() : "-";
1966
                    if (remark.getMessage() != null && !remark.getMessage().isEmpty()) {
1967
                        remarkValue = remarkValue + " - " + remark.getMessage();
1968
                    }
1969
                }
1970
            }
1971
 
1972
            // Call status from customerStatus field
1973
            String callStatus = callLog.getCustomerStatus() != null ? callLog.getCustomerStatus() : "-";
1974
            String callDuration = callLog.getCallDuration() != null ? callLog.getCallDuration() : "-";
1975
 
1976
            String callDateTime = "-";
1977
            if (callLog.getCallDate() != null && callLog.getCallTime() != null) {
1978
                LocalDateTime callDateTimeObj = LocalDateTime.of(callLog.getCallDate(), callLog.getCallTime());
1979
                callDateTime = callDateTimeObj.format(dateTimeFormatter);
1980
            }
1981
 
1982
            String recordingUrl = callLog.getRecordingUrl() != null ? callLog.getRecordingUrl() : "-";
1983
 
1984
            rows.add(Arrays.asList(rbmName, partyName, code, remarkValue, callStatus, callDuration, callDateTime, recordingUrl));
1985
        }
1986
 
1987
        return rows;
1988
    }
1989
 
33917 ranu 1990
}