Subversion Repositories SmartDukaan

Rev

Rev 37533 | Rev 37845 | 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;
37497 ranu 18
import com.spice.profitmandi.dao.entity.inventory.SalesAchievements;
19
import com.spice.profitmandi.dao.entity.inventory.SalesTargets;
35759 ranu 20
import com.spice.profitmandi.dao.entity.user.Address;
35631 ranu 21
import com.spice.profitmandi.dao.enumuration.auth.CollectionRemark;
22
import com.spice.profitmandi.dao.enumuration.cs.EscalationType;
33917 ranu 23
import com.spice.profitmandi.dao.model.*;
35631 ranu 24
import com.spice.profitmandi.dao.repository.auth.AuthRepository;
25
import com.spice.profitmandi.dao.repository.auth.PartnerCollectionRemarkRepository;
26
import com.spice.profitmandi.dao.repository.auth.RbmCallSequenceLogRepository;
33985 ranu 27
import com.spice.profitmandi.dao.repository.catalog.RbmAchievementsRepository;
33926 ranu 28
import com.spice.profitmandi.dao.repository.catalog.RbmTargetsRepository;
37497 ranu 29
import com.spice.profitmandi.dao.repository.catalog.SalesAchievementsRepository;
30
import com.spice.profitmandi.dao.repository.catalog.SalesTargetsRepository;
35631 ranu 31
import com.spice.profitmandi.dao.repository.cs.CsService;
32
import com.spice.profitmandi.dao.repository.cs.PositionRepository;
33
import com.spice.profitmandi.dao.repository.dtr.FofoStoreRepository;
35759 ranu 34
import com.spice.profitmandi.dao.repository.dtr.RetailerContactRepository;
34397 ranu 35
import com.spice.profitmandi.dao.repository.fofo.MonthlyTargetRepository;
34880 ranu 36
import com.spice.profitmandi.dao.repository.logistics.PublicHolidaysRepository;
35631 ranu 37
import com.spice.profitmandi.dao.repository.transaction.LoanRepository;
34397 ranu 38
import com.spice.profitmandi.dao.repository.transaction.OrderRepository;
35759 ranu 39
import com.spice.profitmandi.dao.repository.user.AddressRepository;
35631 ranu 40
import com.spice.profitmandi.service.user.RetailerService;
33917 ranu 41
import org.apache.logging.log4j.LogManager;
42
import org.apache.logging.log4j.Logger;
43
import org.hibernate.Session;
44
import org.hibernate.SessionFactory;
45
import org.hibernate.query.NativeQuery;
46
import org.springframework.beans.factory.annotation.Autowired;
36334 ranu 47
import org.springframework.cache.annotation.Cacheable;
33917 ranu 48
import org.springframework.stereotype.Component;
49
 
50
import javax.persistence.TypedQuery;
34880 ranu 51
import java.time.*;
35631 ranu 52
import java.time.format.DateTimeFormatter;
34880 ranu 53
import java.time.temporal.ChronoUnit;
35631 ranu 54
import java.util.*;
33997 ranu 55
import java.util.stream.Collectors;
33917 ranu 56
 
57
@Component
58
public class RbmTargetServiceImpl implements RbmTargetService {
59
    private static final Logger LOGGER = LogManager.getLogger(RbmTargetServiceImpl.class);
60
 
61
    @Autowired
62
    SessionFactory sessionFactory;
63
 
33926 ranu 64
    @Autowired
65
    RbmTargetsRepository rbmTargetsRepository;
33917 ranu 66
 
33985 ranu 67
    @Autowired
37497 ranu 68
    SalesTargetsRepository salesTargetsRepository;
69
 
70
    @Autowired
71
    SalesAchievementsRepository salesAchievementsRepository;
72
 
73
    @Autowired
74
    AuthService authService;
75
 
76
    @Autowired
33985 ranu 77
    RbmAchievementsRepository rbmAchievementsRepository;
78
 
34397 ranu 79
    @Autowired
80
    MonthlyTargetRepository monthlyTargetRepository;
81
 
34880 ranu 82
    @Autowired
83
    PublicHolidaysRepository publicHolidaysRepository;
84
 
35631 ranu 85
    @Autowired
86
    RetailerService retailerService;
87
 
33917 ranu 88
    @Override
89
    public List<WarehouseRbmTargetModel> getWarehouseWiseRbmMonthlyTarget() {
90
        Session session = sessionFactory.getCurrentSession();
91
        final TypedQuery<WarehouseRbmTargetModel> typedQuerySimilar = session.createNamedQuery("RbmTarget.getWarehouseWiseMonthlyTarget", WarehouseRbmTargetModel.class);
92
 
93
        return typedQuerySimilar.getResultList();
94
 
95
    }
96
 
97
    @Override
98
    public List<MTDAchievedTargetModel> getDateWiseAchievedTargetOfRbm(LocalDate startDate, LocalDate endDate) {
99
        Session session = sessionFactory.getCurrentSession();
100
        final TypedQuery<MTDAchievedTargetModel> typedQuerySimilar = session.createNamedQuery("RbmTarget.getRbmAchievedMonthlyTarget", MTDAchievedTargetModel.class);
101
        typedQuerySimilar.setParameter("startDate", startDate);
102
        typedQuerySimilar.setParameter("endDate", endDate);
103
        return typedQuerySimilar.getResultList();
104
 
105
    }
106
 
107
    @Override
34055 ranu 108
    public List<TodayAchievedMovementModel> getMovementWiseAchievementByDate(LocalDate startDate, LocalDate endDate) {
33917 ranu 109
        LOGGER.info("start date {}, end date {}", startDate, endDate);
110
        Session session = sessionFactory.getCurrentSession();
34055 ranu 111
        final TypedQuery<TodayAchievedMovementModel> typedQuerySimilar = session.createNamedQuery("RBMTarget.TodayAchivementByMovement", TodayAchievedMovementModel.class);
33917 ranu 112
        typedQuerySimilar.setParameter("startDate", startDate);
113
        typedQuerySimilar.setParameter("endDate", endDate);
114
        return typedQuerySimilar.getResultList();
115
 
116
    }
117
 
118
    @Override
37442 ranu 119
    public List<RbmBrandSoldModel> getBrandsSoldByRbm(int authId, int warehouseId, LocalDate startDate, LocalDate endDate) {
120
        Session session = sessionFactory.getCurrentSession();
121
        final TypedQuery<RbmBrandSoldModel> q = session.createNamedQuery("RbmDrr.BrandSoldByRbm", RbmBrandSoldModel.class);
122
        q.setParameter("authId", authId);
123
        q.setParameter("warehouseId", warehouseId);
124
        q.setParameter("startDate", startDate);
125
        q.setParameter("endDate", endDate);
126
        return q.getResultList();
127
    }
128
 
129
    @Override
37460 ranu 130
    public List<RbmBrandPartnerCountModel> getBrandPartnerCountByRbm(int authId, int warehouseId, LocalDate startDate, LocalDate endDate) {
131
        Session session = sessionFactory.getCurrentSession();
132
        final TypedQuery<RbmBrandPartnerCountModel> q = session.createNamedQuery("RbmDrr.BrandPartnerCountByRbm", RbmBrandPartnerCountModel.class);
133
        q.setParameter("authId", authId);
134
        q.setParameter("warehouseId", warehouseId);
135
        q.setParameter("startDate", startDate);
136
        q.setParameter("endDate", endDate);
137
        return q.getResultList();
138
    }
139
 
140
    @Override
37487 ranu 141
    public List<WarehouseBuyingByMovementModel> getWarehouseBuyingByMovement(int warehouseId, LocalDate startDate, LocalDate endDate) {
142
        Session session = sessionFactory.getCurrentSession();
143
        final TypedQuery<WarehouseBuyingByMovementModel> q = session.createNamedQuery("RbmDrr.WarehouseBuyingByMovement", WarehouseBuyingByMovementModel.class);
144
        q.setParameter("warehouseId", warehouseId);
145
        q.setParameter("startDate", startDate);
146
        q.setParameter("endDate", endDate);
147
        return q.getResultList();
148
    }
149
 
150
    @Override
37586 ranu 151
    public List<WarehouseOpeningByMovementModel> getWarehouseOpeningByMovement(int warehouseId, LocalDate asOfDate) {
152
        Session session = sessionFactory.getCurrentSession();
153
        final TypedQuery<WarehouseOpeningByMovementModel> q = session.createNamedQuery(
154
                "RbmDrr.WarehouseOpeningByMovement", WarehouseOpeningByMovementModel.class);
155
        q.setParameter("warehouseId", warehouseId);
156
        q.setParameter("asOfDate", asOfDate);
157
        return q.getResultList();
158
    }
159
 
160
    @Override
33917 ranu 161
    public List<WarehouseMobileStockByMovementModel> getWarehouseMobileStockByMovement() {
162
        Session session = sessionFactory.getCurrentSession();
163
        final TypedQuery<WarehouseMobileStockByMovementModel> typedQuerySimilar = session.createNamedQuery("WarehouseStock.MovementWiseMobileStock", WarehouseMobileStockByMovementModel.class);
164
 
165
        return typedQuerySimilar.getResultList();
166
 
167
    }
168
 
33991 ranu 169
    @Override
170
    public List<SoldCatalogsReportModel> getCatalogSoldReport(LocalDate startDate, LocalDate endDate) {
171
        Session session = sessionFactory.getCurrentSession();
172
        final TypedQuery<SoldCatalogsReportModel> typedQuerySimilar = session.createNamedQuery("CatalogsReport.SoldCatalogsReport", SoldCatalogsReportModel.class);
173
        typedQuerySimilar.setParameter("startDate", startDate);
174
        typedQuerySimilar.setParameter("endDate", endDate);
175
        return typedQuerySimilar.getResultList();
33917 ranu 176
 
33991 ranu 177
    }
178
 
179
 
33917 ranu 180
    public int getWorkingDaysCount(LocalDate startDate) {
181
        Session session = sessionFactory.getCurrentSession();
182
 
183
        // Convert the LocalDate to a format MySQL can interpret
184
        String startDateString = startDate.toString();
185
 
186
        final NativeQuery<?> nativeQuery = session.createNativeQuery(
187
                "SELECT (DATEDIFF(LAST_DAY(:startDate), :startDate) + 1) " +
188
                        " - (FLOOR((DATEDIFF(LAST_DAY(:startDate), :startDate) + (WEEKDAY(:startDate) + 1)) / 7)) " +
189
                        " - (SELECT COUNT(*) " +
190
                        " FROM logistics.publicholidays " +
191
                        " WHERE date BETWEEN :startDate AND LAST_DAY(:startDate) " +
192
                        " AND WEEKDAY(date) != 6) AS working_days"
193
        );
194
 
195
        // Set the start date parameter for each placeholder
196
        nativeQuery.setParameter("startDate", startDateString);
197
 
198
        Object result = nativeQuery.getSingleResult();
199
        return result != null ? ((Number) result).intValue() : 0;
200
    }
201
 
202
 
35044 ranu 203
 
33917 ranu 204
    @Override
205
    public List<RbmArrViewModel> getRbmTodayArr() throws Exception {
206
        LocalDate todayDate = LocalDate.now();
207
        return getRbmTodayArr(todayDate);
208
    }
209
 
210
    @Override
33997 ranu 211
    public List<RbmTargetAndAchievementsModel> getRbmTargetsAndAchievemnts(LocalDate startDate, LocalDate endDate) {
212
 
34002 ranu 213
        List<RbmTargetsModel> rbmTargetsList = rbmTargetsRepository.selectTargetsModelListByDates(startDate.atStartOfDay(), endDate.atTime(LocalTime.MAX));
33997 ranu 214
 
215
        LOGGER.info("rbmTargetsList {}", rbmTargetsList);
216
        // Group Targtes by RBM and Warehouse
34002 ranu 217
        Map<String, RbmTargetsModel> targetsMap = rbmTargetsList.stream()
33997 ranu 218
                .collect(Collectors.toMap(
219
                        a -> a.getRbmAuthId() + "-" + a.getWarehouseId(),
220
                        a -> a,
221
                        (a1, a2) -> mergeTargets(a1, a2) // Handle duplicates by merging
222
                ));
223
 
224
 
34002 ranu 225
        List<RbmAchievementsModel> rbmAchievements = rbmAchievementsRepository.selectAchievementsModelListByDates(startDate.atStartOfDay(), endDate.atTime(LocalTime.MAX));
33997 ranu 226
        LOGGER.info("rbmTargetsList {}", rbmAchievements);
227
        // Group achievements by RBM and Warehouse
34002 ranu 228
        Map<String, RbmAchievementsModel> achievementMap = rbmAchievements.stream()
33997 ranu 229
                .collect(Collectors.toMap(
230
                        a -> a.getRbmAuthId() + "-" + a.getWarehouseId(),
231
                        a -> a,
232
                        (a1, a2) -> mergeAchievements(a1, a2) // Handle duplicates by merging
233
                ));
234
 
235
        return targetsMap.keySet().stream()
236
                .map(key -> {
237
                    String[] parts = key.split("-");
238
                    int rbmAuthId = Integer.parseInt(parts[0]);
239
                    int warehouseId = Integer.parseInt(parts[1]);
240
 
34002 ranu 241
                    RbmTargetsModel target = targetsMap.get(key);
242
                    RbmAchievementsModel achievement = achievementMap.getOrDefault(key, new RbmAchievementsModel());
33997 ranu 243
 
244
                    RbmTargetAndAchievementsModel model = new RbmTargetAndAchievementsModel();
245
                    model.setAuthId(rbmAuthId);
246
                    model.setRbmName(target.getRbmName());
247
                    model.setWarehouseName(ProfitMandiConstants.WAREHOUSE_MAP.getOrDefault(warehouseId, "Unknown"));
248
 
249
                    // Set target values
34006 ranu 250
                    model.setHidTarget((long) target.getHidTarget());
34098 ranu 251
                    model.setRunningTarget((long) target.getRunningTarget());
34006 ranu 252
                    model.setFastMovingTarget((long) target.getFastMovingTarget());
253
                    model.setSlowMovingTarget((long) target.getSlowMovingTarget());
254
                    model.setOtherMovingTarget((long) target.getOtherTarget());
33997 ranu 255
 
256
                    // Set achievement values
34006 ranu 257
                    model.setAchievedHid((long) achievement.getAchievedHidTarget());
34098 ranu 258
                    model.setAchievedRunning((long) achievement.getAchievedRunningTarget());
34006 ranu 259
                    model.setAchievedFastMoving((long) achievement.getAchievedFastMovingTarget());
260
                    model.setAchievedSlowMoving((long) achievement.getAchievedSlowMovingTarget());
261
                    model.setAchievedOtherMoving((long) achievement.getAchievedOtherTarget());
33997 ranu 262
 
263
                    model.setTotalTarget(
34006 ranu 264
                            (long) target.getHidTarget() +
34098 ranu 265
                                    (long) target.getRunningTarget() +
34006 ranu 266
                                    (long) target.getFastMovingTarget() +
267
                                    (long) target.getSlowMovingTarget() +
268
                                    (long) target.getOtherTarget()
33997 ranu 269
                    );
270
                    model.setTotalAchievemnt(
34006 ranu 271
                            (long) achievement.getAchievedHidTarget() +
34098 ranu 272
                                    (long) achievement.getAchievedRunningTarget() +
34006 ranu 273
                                    (long) achievement.getAchievedFastMovingTarget() +
274
                                    (long) achievement.getAchievedSlowMovingTarget() +
275
                                    (long) achievement.getAchievedOtherTarget()
33997 ranu 276
                    );
277
 
278
                    return model;
279
                })
280
                .collect(Collectors.toList());
281
 
282
    }
283
 
34002 ranu 284
    private RbmTargetsModel mergeTargets(RbmTargetsModel a1, RbmTargetsModel a2) {
33997 ranu 285
 
286
        // Merge logic for achievements (aggregate the target and achieved values)
34006 ranu 287
        a1.setHidTarget((a1.getHidTarget()) +
288
                (a2.getHidTarget()));
33997 ranu 289
 
34006 ranu 290
        a1.setFastMovingTarget((a1.getFastMovingTarget()) +
291
                (a2.getFastMovingTarget()));
33997 ranu 292
 
34006 ranu 293
        a1.setSlowMovingTarget((a1.getSlowMovingTarget()) +
294
                (a2.getSlowMovingTarget()));
33997 ranu 295
 
34098 ranu 296
        a1.setRunningTarget((a1.getRunningTarget()) +
297
                (a2.getRunningTarget()));
33997 ranu 298
 
34006 ranu 299
        a1.setOtherTarget((a1.getOtherTarget()) +
300
                (a2.getOtherTarget()));
33997 ranu 301
        return a1;
302
    }
303
 
34002 ranu 304
    private RbmAchievementsModel mergeAchievements(RbmAchievementsModel a1, RbmAchievementsModel a2) {
33997 ranu 305
        // Merge logic for achievements (aggregate the target and achieved values)
34006 ranu 306
        a1.setAchievedHidTarget((a1.getAchievedHidTarget()) +
307
                (a2.getAchievedHidTarget()));
33997 ranu 308
 
34098 ranu 309
        a1.setAchievedRunningTarget((a1.getAchievedRunningTarget()) +
310
                (a2.getAchievedRunningTarget()));
33997 ranu 311
 
34006 ranu 312
        a1.setAchievedFastMovingTarget((a1.getAchievedFastMovingTarget()) +
313
                (a2.getAchievedFastMovingTarget()));
33997 ranu 314
 
34006 ranu 315
        a1.setAchievedSlowMovingTarget((a1.getAchievedSlowMovingTarget()) +
316
                (a2.getAchievedSlowMovingTarget()));
33997 ranu 317
 
34006 ranu 318
        a1.setAchievedOtherTarget((a1.getAchievedOtherTarget()) +
319
                (a2.getAchievedOtherTarget()));
33997 ranu 320
 
321
        return a1;
322
    }
34002 ranu 323
 
33997 ranu 324
    @Override
33917 ranu 325
    public List<RbmArrViewModel> getRbmTodayArr(LocalDate todayDate) throws Exception {
326
 
327
        LocalDate startDateOfMonthDay1 = LocalDate.now().withDayOfMonth(1);
328
 
34289 ranu 329
        List<WarehouseRbmTargetModel> warehouseRbmTargetModelList = this.getWarehouseWiseRbmMonthlyTarget();
330
        List<WarehouseRbmTargetModel> warehouseRbmTargetModels = warehouseRbmTargetModelList.stream().filter(x -> x.getMonthlyTarget() > 0).collect(Collectors.toList());
331
        LOGGER.info("warehouseRbmTargetModels {}", warehouseRbmTargetModels);
332
        List<TodayAchievedMovementModel> todayAchievedMovementModels = getMovementWiseAchievementByDate(todayDate, todayDate.plusDays(1));
33917 ranu 333
 
334
        List<MTDAchievedTargetModel> mtdAchievedTargetModels = getDateWiseAchievedTargetOfRbm(startDateOfMonthDay1, todayDate);
335
 
35044 ranu 336
        int remainingWorkingDaysCount = (int) getRemainingDaysInMonth(todayDate);
33917 ranu 337
 
33926 ranu 338
        List<RbmTargets> todayRbmTargetsList = rbmTargetsRepository.selectTargetsByDates(todayDate.atStartOfDay(), todayDate.atTime(LocalTime.MAX));
33917 ranu 339
 
33926 ranu 340
        LOGGER.info("todayRbmTargetsList {}", todayRbmTargetsList);
33917 ranu 341
 
342
        List<RbmArrViewModel> rbmArrViewModels = new ArrayList<>();
343
 
34028 ranu 344
        if (!todayRbmTargetsList.isEmpty()) {
33917 ranu 345
 
35454 amit 346
            // OPTIMIZED: Pre-build maps for O(1) lookup instead of O(n) filter in each iteration
347
            // Map key: "authId-warehouseId"
348
            Map<String, Double> mtdAchievedMap = mtdAchievedTargetModels.stream()
349
                    .collect(Collectors.groupingBy(
350
                            x -> x.getAuthId() + "-" + x.getWarehouseId(),
351
                            Collectors.summingDouble(MTDAchievedTargetModel::getAcheivedMonthlyTarget)
352
                    ));
353
 
354
            Map<String, TodayAchievedMovementModel> todayAchievedMap = todayAchievedMovementModels.stream()
355
                    .collect(Collectors.toMap(
356
                            x -> x.getAuthId() + "-" + x.getWarehouseId(),
357
                            x -> x,
358
                            (a, b) -> a
359
                    ));
360
 
361
            Map<String, RbmTargets> todayRbmTargetsMap = todayRbmTargetsList.stream()
362
                    .collect(Collectors.toMap(
363
                            x -> x.getRbmAuthId() + "-" + x.getWarehouseId(),
364
                            x -> x,
365
                            (a, b) -> a
366
                    ));
367
 
34028 ranu 368
            for (WarehouseRbmTargetModel rbmTarget : warehouseRbmTargetModels) {
33917 ranu 369
 
35454 amit 370
                String lookupKey = rbmTarget.getAuthId() + "-" + rbmTarget.getWarehouseId();
371
 
34028 ranu 372
                float monthlyTarget = rbmTarget.getMonthlyTarget();
35454 amit 373
                float achievedSoFar = mtdAchievedMap.getOrDefault(lookupKey, 0.0).floatValue();
33917 ranu 374
 
34028 ranu 375
                float remainingTarget = monthlyTarget - achievedSoFar;
33953 ranu 376
 
34028 ranu 377
                float todayTarget = (remainingWorkingDaysCount > 0 && remainingTarget > 0) ? remainingTarget / remainingWorkingDaysCount : 0;
378
 
33926 ranu 379
                String warehouseName = ProfitMandiConstants.WAREHOUSE_MAP.getOrDefault(rbmTarget.getWarehouseId(), "Unknown");
33917 ranu 380
 
34288 ranu 381
                LOGGER.info("rbmTarget ==== {}", rbmTarget);
34281 ranu 382
 
35454 amit 383
                TodayAchievedMovementModel todayAchievedMovementModel = todayAchievedMap.get(lookupKey);
34283 ranu 384
 
35454 amit 385
                RbmTargets todayRbmTargets = todayRbmTargetsMap.get(lookupKey);
34285 ranu 386
 
35454 amit 387
                if (todayRbmTargets != null) {
34034 ranu 388
                    LOGGER.info("todayRbmTargets {}", todayRbmTargets);
389
                    RbmArrViewModel viewModel = new RbmArrViewModel();
33917 ranu 390
 
34034 ranu 391
                    viewModel.setAuthId(rbmTarget.getAuthId());
392
                    viewModel.setRbmName(rbmTarget.getRbmName());
393
                    viewModel.setWarehouseName(warehouseName);
394
                    viewModel.setTodayTarget(Math.round(todayTarget));
395
                    viewModel.setMonthlyTarget(Math.round(monthlyTarget));
396
                    viewModel.setMtdAchievedTarget(Math.round(achievedSoFar));
33926 ranu 397
 
34034 ranu 398
                    viewModel.setTodayHidTarget(Math.round((todayRbmTargets.getHidTarget())));
399
                    viewModel.setTodayFastMovingTarget(Math.round(todayRbmTargets.getFastMovingTarget()));
34098 ranu 400
                    viewModel.setTodaySlowMovingTarget(Math.round(todayRbmTargets.getSlowMovingtarget()));
401
                    viewModel.setTodayRunningTarget(Math.round(todayRbmTargets.getRunningtarget()));
34034 ranu 402
                    viewModel.setTodayOtherMovingTarget(Math.round(todayRbmTargets.getOtherTarget()));
33926 ranu 403
 
34283 ranu 404
                    if (todayAchievedMovementModel != null) {
405
                        viewModel.setTodayAchievedHidTarget(Math.round(todayAchievedMovementModel.getHidBilled()));
406
                        viewModel.setTodayAchievedFastMovingTarget(Math.round(todayAchievedMovementModel.getFastMovingBilled()));
407
                        viewModel.setTodayAchievedSlowMovingTarget(Math.round(todayAchievedMovementModel.getSlowMovinBilled()));
408
                        viewModel.setTodayAchievedRunningTarget(Math.round(todayAchievedMovementModel.getRunningBilled()));
409
                        viewModel.setTodayAchievedOtherMovingTarget(Math.round(todayAchievedMovementModel.getOtherBilled()));
410
                        viewModel.setTotalAchievedTarget(Math.round(todayAchievedMovementModel.getHidBilled() + todayAchievedMovementModel.getFastMovingBilled() + todayAchievedMovementModel.getSlowMovinBilled() + todayAchievedMovementModel.getRunningBilled() + todayAchievedMovementModel.getOtherBilled()));
411
                    } else {
412
                        viewModel.setTodayAchievedHidTarget(0);
413
                        viewModel.setTodayAchievedFastMovingTarget(0);
414
                        viewModel.setTodayAchievedSlowMovingTarget(0);
415
                        viewModel.setTodayAchievedRunningTarget(0);
416
                        viewModel.setTodayAchievedOtherMovingTarget(0);
417
                        viewModel.setTotalAchievedTarget(0);
418
                    }
34034 ranu 419
                    rbmArrViewModels.add(viewModel);
420
                } else {
421
                    LOGGER.info("No matching RbmTargets found for AuthId: {} and rbmname {} and WarehouseId: {}", rbmTarget.getAuthId(), rbmTarget.getRbmName(), rbmTarget.getWarehouseId());
422
                }
33917 ranu 423
 
34034 ranu 424
 
425
 
34028 ranu 426
            }
33917 ranu 427
        }
428
 
429
        LOGGER.info("rbmArrViewModels {}", rbmArrViewModels);
430
        return rbmArrViewModels;
431
    }
432
 
33926 ranu 433
    @Override
434
    public void setMovementWiseRbmTargets() {
435
        LocalDate todayDate = LocalDate.now();
33917 ranu 436
 
33926 ranu 437
        LocalDate startDateOfMonthDay1 = LocalDate.now().withDayOfMonth(1);
438
 
439
        List<WarehouseRbmTargetModel> warehouseRbmTargetModels = this.getWarehouseWiseRbmMonthlyTarget();
440
 
441
        List<MTDAchievedTargetModel> mtdAchievedTargetModels = getDateWiseAchievedTargetOfRbm(startDateOfMonthDay1, todayDate);
442
 
443
 
35044 ranu 444
        int remainingWorkingDaysCount = (int) getRemainingDaysInMonth(todayDate);
33926 ranu 445
 
446
        List<WarehouseMobileStockByMovementModel> warehouseMobileStockByMovementModels = getWarehouseMobileStockByMovement();
447
 
35454 amit 448
        // OPTIMIZED: Pre-build maps for O(1) lookup instead of O(n) filter in each iteration
449
        Map<String, Double> mtdAchievedMap = mtdAchievedTargetModels.stream()
450
                .collect(Collectors.groupingBy(
451
                        x -> x.getAuthId() + "-" + x.getWarehouseId(),
452
                        Collectors.summingDouble(MTDAchievedTargetModel::getAcheivedMonthlyTarget)
453
                ));
454
 
455
        Map<Integer, WarehouseMobileStockByMovementModel> warehouseStockMap = warehouseMobileStockByMovementModels.stream()
456
                .collect(Collectors.toMap(
457
                        WarehouseMobileStockByMovementModel::getWarehouseId,
458
                        x -> x,
459
                        (a, b) -> a
460
                ));
461
 
33926 ranu 462
        for (WarehouseRbmTargetModel rbmTarget : warehouseRbmTargetModels) {
463
 
35454 amit 464
            String lookupKey = rbmTarget.getAuthId() + "-" + rbmTarget.getWarehouseId();
465
 
33926 ranu 466
            float monthlyTarget = rbmTarget.getMonthlyTarget();
35454 amit 467
            float achievedSoFar = mtdAchievedMap.getOrDefault(lookupKey, 0.0).floatValue();
33926 ranu 468
 
469
 
470
            float remainingTarget = monthlyTarget - achievedSoFar;
33953 ranu 471
            LOGGER.info("remainingTarget {}", remainingTarget);
33926 ranu 472
 
33953 ranu 473
            float todayTarget = (remainingWorkingDaysCount > 0 && remainingTarget > 0) ? remainingTarget / remainingWorkingDaysCount : 0;
474
            LOGGER.info("todayTarget {}", todayTarget);
475
 
33926 ranu 476
            // Get the warehouse stock data
35454 amit 477
            WarehouseMobileStockByMovementModel warehouseMobileStockByMovementModel = warehouseStockMap.get(rbmTarget.getWarehouseId());
33926 ranu 478
 
479
 
480
            if (warehouseMobileStockByMovementModel != null) {
481
 
482
                // Total stock value for this warehouse
483
                float totalStockValue = warehouseMobileStockByMovementModel.getTotalAvailabilityPrice();
484
 
485
                // Calculate target allocation based on stock value proportion
486
                float hidTarget = (warehouseMobileStockByMovementModel.getTotalHidCatalogPrice() / totalStockValue) * todayTarget;
487
                float fastMovingTarget = (warehouseMobileStockByMovementModel.getTotalFastMovingCatalogPrice() / totalStockValue) * todayTarget;
488
                float slowMovingTarget = (warehouseMobileStockByMovementModel.getTotalSlowMovingCatalogPrice() / totalStockValue) * todayTarget;
34098 ranu 489
                float runningTarget = (warehouseMobileStockByMovementModel.getTotalRunningCatalogPrice() / totalStockValue) * todayTarget;
33926 ranu 490
                float otherTarget = (warehouseMobileStockByMovementModel.getTotalOtherCategoryCatalogPrice() / totalStockValue) * todayTarget;
491
 
492
                RbmTargets rbmTargets = new RbmTargets();
493
                rbmTargets.setWarehouseId(rbmTarget.getWarehouseId());
494
                rbmTargets.setRbmAuthId(rbmTarget.getAuthId());
495
                rbmTargets.setRbmName(rbmTarget.getRbmName());
34098 ranu 496
                rbmTargets.setRunningtarget(runningTarget);
33926 ranu 497
                rbmTargets.setHidTarget(hidTarget);
498
                rbmTargets.setFastMovingTarget(fastMovingTarget);
34098 ranu 499
                rbmTargets.setSlowMovingtarget(slowMovingTarget);
33926 ranu 500
                rbmTargets.setOtherTarget(otherTarget);
501
                rbmTargets.setCreateTimestamp(LocalDateTime.now());
502
 
503
                rbmTargetsRepository.persist(rbmTargets);
504
 
505
            }
506
        }
507
 
508
    }
509
 
510
 
37497 ranu 511
    /**
512
     * Daily writer for Sales-hierarchy targets. Rolls up today's persisted
513
     * inventory.rbm_targets snapshots (produced by {@link #setMovementWiseRbmTargets})
514
     * to every active Sales person whose downline includes those RBMs, and
515
     * inserts one row per (sales, warehouse) into inventory.sales_targets.
516
     *
517
     * <p>Chain order matters: run this AFTER pushRbmTargets so today's RBM
518
     * snapshot rows exist when we roll up.
519
     */
33985 ranu 520
    @Override
37497 ranu 521
    public void setSalesTargets() {
522
        setSalesTargetsForDate(LocalDate.now());
523
    }
524
 
525
    @Override
526
    public void setSalesTargetsForDate(LocalDate date) {
37505 ranu 527
        // Sales target for a person = (own_monthly_target − MTD_own_achievement) /
528
        // remainingDays, then split by warehouse stock mix — exactly like RBM, but
529
        // scoped to the Sales person's OWN assigned partners (no RBM roll-up).
530
        LocalDate monthStart = date.withDayOfMonth(1);
531
        int remainingWorkingDays = (int) getRemainingDaysInMonth(date);
37497 ranu 532
 
37505 ranu 533
        // Warehouse-level stock mix (same source used by setMovementWiseRbmTargets).
534
        Map<Integer, WarehouseMobileStockByMovementModel> whStockByWh = new HashMap<>();
535
        for (WarehouseMobileStockByMovementModel s : getWarehouseMobileStockByMovement()) {
536
            whStockByWh.put(s.getWarehouseId(), s);
37497 ranu 537
        }
538
 
539
        String[][] roles = {
540
                {"ASM", "4", "L1"}, {"CM", "4", "L2"}, {"DM", "4", "L3"},
541
                {"BM", "4", "L4"}, {"RSM", "4", "L5"}, {"NSM", "4", "L6"}
542
        };
543
 
544
        LocalDate today = LocalDate.now();
545
        LocalDateTime stamp = date.equals(today) ? LocalDateTime.now() : date.atTime(java.time.LocalTime.NOON);
37505 ranu 546
        int written = 0, zeroTargetSkipped = 0;
547
 
37519 ranu 548
        // Cache monthly targets + MTD ACH per level so the RSM/NSM roll-up
549
        // pass can reuse them without repeating queries.
550
        Map<String, List<SalesHierarchyMonthlyTargetModel>> mtByLevel = new HashMap<>();
551
        Map<String, Map<String, Double>> mtdByLevelKey = new HashMap<>();
552
 
37497 ranu 553
        for (String[] r : roles) {
554
            String role = r[0];
555
            int catId = Integer.parseInt(r[1]);
37505 ranu 556
            String levelStr = r[2];
37497 ranu 557
 
37505 ranu 558
            List<SalesHierarchyMonthlyTargetModel> monthlyTgts = getSalesMonthlyTargetByRole(catId, levelStr, date);
37519 ranu 559
            mtByLevel.put(role, monthlyTgts);
37497 ranu 560
 
37505 ranu 561
            Map<String, Double> mtdByKey = new HashMap<>();
562
            for (SalesHierarchyAchievedByMovementModel a :
563
                    getSalesAchievementByRole(catId, levelStr, monthStart, date.plusDays(1))) {
564
                double totBilled =
565
                        a.getHidBilled()
566
                                + a.getFastMovingBilled()
567
                                + (a.getRunningBilled() != null ? a.getRunningBilled() : 0)
568
                                + (a.getSlowMovingBilled() != null ? a.getSlowMovingBilled() : 0)
569
                                + (a.getOtherBilled() != null ? a.getOtherBilled() : 0);
570
                mtdByKey.merge(a.getAuthId() + "-" + a.getWarehouseId(), totBilled, Double::sum);
571
            }
37519 ranu 572
            mtdByLevelKey.put(role, mtdByKey);
37505 ranu 573
 
37519 ranu 574
            // Direct-role writer — one row per (authId × warehouse) at this role.
575
            if (monthlyTgts.isEmpty()) continue;
37505 ranu 576
            for (SalesHierarchyMonthlyTargetModel mt : monthlyTgts) {
577
                if (mt.getMonthlyTarget() <= 0) {
578
                    zeroTargetSkipped++;
579
                    continue;
37497 ranu 580
                }
37505 ranu 581
                double achieved = mtdByKey.getOrDefault(mt.getAuthId() + "-" + mt.getWarehouseId(), 0.0);
582
                double remaining = mt.getMonthlyTarget() - achieved;
583
                double todayTarget = (remainingWorkingDays > 0 && remaining > 0)
584
                        ? remaining / remainingWorkingDays : 0;
37497 ranu 585
 
37505 ranu 586
                WarehouseMobileStockByMovementModel stock = whStockByWh.get(mt.getWarehouseId());
587
                if (stock == null || stock.getTotalAvailabilityPrice() <= 0) continue;
588
                float total = stock.getTotalAvailabilityPrice();
589
 
590
                SalesTargets st = new SalesTargets();
591
                st.setSalesAuthId(mt.getAuthId());
592
                st.setSalesName(mt.getName());
593
                st.setRole(role);
594
                st.setWarehouseId(mt.getWarehouseId());
595
                st.setHidTarget((float) ((stock.getTotalHidCatalogPrice() / total) * todayTarget));
596
                st.setFastMovingTarget((float) ((stock.getTotalFastMovingCatalogPrice() / total) * todayTarget));
597
                st.setSlowMovingTarget((float) ((stock.getTotalSlowMovingCatalogPrice() / total) * todayTarget));
598
                st.setRunningTarget((float) ((stock.getTotalRunningCatalogPrice() / total) * todayTarget));
599
                st.setOtherTarget((float) ((stock.getTotalOtherCategoryCatalogPrice() / total) * todayTarget));
600
                st.setCreateTimestamp(stamp);
601
                salesTargetsRepository.persist(st);
602
                written++;
37497 ranu 603
            }
604
        }
37519 ranu 605
 
606
        // Roll-up pass for RSM (L5) and NSM (L6) — uses distinct-partner dedup
607
        // queries so partners assigned to multiple positions in the downline
608
        // aren't counted more than once.
609
        String[] rollUpRoles = {"RSM", "NSM"};
610
        for (String rollUpRole : rollUpRoles) {
611
            String levelStr = "RSM".equals(rollUpRole) ? "L5" : "L6";
612
            for (AuthUser top : csService.getAuthUserByCategoryId(4, EscalationType.valueOf(levelStr))) {
613
                if (!top.isActive()) continue;
614
                Set<Integer> tree = new HashSet<>();
615
                tree.add(top.getId());
616
                List<Integer> reportees = authService.getAllReportees(top.getId());
617
                if (reportees != null) tree.addAll(reportees);
618
                if (tree.isEmpty()) continue;
619
 
620
                // Distinct-partner monthly TGT per warehouse for the downline.
621
                Map<Integer, Double> monthlyByWh = new LinkedHashMap<>();
622
                for (com.spice.profitmandi.dao.model.SalesDownlineMonthlyTargetModel d :
623
                        getSalesDownlineMonthlyTarget(tree, date)) {
624
                    monthlyByWh.put(d.getWarehouseId(), (double) d.getMonthlyTarget());
625
                }
626
                // Distinct-partner MTD ACH per warehouse for the downline.
627
                Map<Integer, Double> mtdByWh = new HashMap<>();
628
                for (com.spice.profitmandi.dao.model.SalesDownlineAchievedByMovementModel d :
629
                        getSalesDownlineAchievement(tree, monthStart, date.plusDays(1))) {
630
                    double tot = d.getHidBilled() + d.getFastMovingBilled()
631
                            + (d.getRunningBilled() != null ? d.getRunningBilled() : 0)
632
                            + (d.getSlowMovingBilled() != null ? d.getSlowMovingBilled() : 0)
633
                            + (d.getOtherBilled() != null ? d.getOtherBilled() : 0);
634
                    mtdByWh.put(d.getWarehouseId(), tot);
635
                }
636
 
637
                for (Map.Entry<Integer, Double> e : monthlyByWh.entrySet()) {
638
                    int whId = e.getKey();
639
                    double monthlyTgt = e.getValue();
640
                    if (monthlyTgt <= 0) {
641
                        zeroTargetSkipped++;
642
                        continue;
643
                    }
644
                    double achieved = mtdByWh.getOrDefault(whId, 0.0);
645
                    double remaining = monthlyTgt - achieved;
646
                    double todayTarget = (remainingWorkingDays > 0 && remaining > 0)
647
                            ? remaining / remainingWorkingDays : 0;
648
 
649
                    WarehouseMobileStockByMovementModel stock = whStockByWh.get(whId);
650
                    if (stock == null || stock.getTotalAvailabilityPrice() <= 0) continue;
651
                    float total = stock.getTotalAvailabilityPrice();
652
 
653
                    SalesTargets st = new SalesTargets();
654
                    st.setSalesAuthId(top.getId());
655
                    st.setSalesName(top.getFullName());
656
                    st.setRole(rollUpRole);
657
                    st.setWarehouseId(whId);
658
                    st.setHidTarget((float) ((stock.getTotalHidCatalogPrice() / total) * todayTarget));
659
                    st.setFastMovingTarget((float) ((stock.getTotalFastMovingCatalogPrice() / total) * todayTarget));
660
                    st.setSlowMovingTarget((float) ((stock.getTotalSlowMovingCatalogPrice() / total) * todayTarget));
661
                    st.setRunningTarget((float) ((stock.getTotalRunningCatalogPrice() / total) * todayTarget));
662
                    st.setOtherTarget((float) ((stock.getTotalOtherCategoryCatalogPrice() / total) * todayTarget));
663
                    st.setCreateTimestamp(stamp);
664
                    salesTargetsRepository.persist(st);
665
                    written++;
666
                }
667
            }
668
        }
37505 ranu 669
        LOGGER.info("setSalesTargetsForDate: wrote {} sales_targets rows for {} ({} rows skipped due to zero monthly target)",
670
                written, date, zeroTargetSkipped);
37497 ranu 671
    }
672
 
673
    @Override
674
    public List<SalesTargets> getSalesTargetsByDates(LocalDate startDate, LocalDate endDate) {
675
        return salesTargetsRepository.selectTargetsByDates(startDate.atStartOfDay(), endDate.atStartOfDay());
676
    }
677
 
678
    @Override
37505 ranu 679
    public List<SalesHierarchyAchievedByMovementModel> getSalesAchievementByRole(
680
            int categoryId, String escalationType, LocalDate startDate, LocalDate endDate) {
681
        Session session = sessionFactory.getCurrentSession();
682
        TypedQuery<SalesHierarchyAchievedByMovementModel> q = session.createNamedQuery(
683
                "RbmDrr.SalesHierarchyAchievedByMovement", SalesHierarchyAchievedByMovementModel.class);
684
        q.setParameter("categoryId", categoryId);
685
        q.setParameter("escalationType", escalationType);
686
        q.setParameter("startDate", startDate);
687
        q.setParameter("endDate", endDate);
688
        return q.getResultList();
689
    }
690
 
691
    @Override
692
    public List<SalesHierarchyMonthlyTargetModel> getSalesMonthlyTargetByRole(
693
            int categoryId, String escalationType, LocalDate refDate) {
694
        Session session = sessionFactory.getCurrentSession();
695
        TypedQuery<SalesHierarchyMonthlyTargetModel> q = session.createNamedQuery(
696
                "RbmDrr.SalesHierarchyMonthlyTarget", SalesHierarchyMonthlyTargetModel.class);
697
        q.setParameter("categoryId", categoryId);
698
        q.setParameter("escalationType", escalationType);
699
        q.setParameter("refDate", refDate);
700
        return q.getResultList();
701
    }
702
 
703
    @Override
37519 ranu 704
    public List<com.spice.profitmandi.dao.model.SalesDownlineAchievedByMovementModel>
705
    getSalesDownlineAchievement(Collection<Integer> authIds, LocalDate startDate, LocalDate endDate) {
706
        if (authIds == null || authIds.isEmpty()) return new ArrayList<>();
707
        Session session = sessionFactory.getCurrentSession();
708
        TypedQuery<com.spice.profitmandi.dao.model.SalesDownlineAchievedByMovementModel> q =
709
                session.createNamedQuery("RbmDrr.SalesDownlineAchievedByMovement",
710
                        com.spice.profitmandi.dao.model.SalesDownlineAchievedByMovementModel.class);
711
        q.setParameter("authIds", authIds);
712
        q.setParameter("startDate", startDate);
713
        q.setParameter("endDate", endDate);
714
        return q.getResultList();
715
    }
716
 
717
    @Override
718
    public List<com.spice.profitmandi.dao.model.SalesDownlineMonthlyTargetModel>
719
    getSalesDownlineMonthlyTarget(Collection<Integer> authIds, LocalDate refDate) {
720
        if (authIds == null || authIds.isEmpty()) return new ArrayList<>();
721
        Session session = sessionFactory.getCurrentSession();
722
        TypedQuery<com.spice.profitmandi.dao.model.SalesDownlineMonthlyTargetModel> q =
723
                session.createNamedQuery("RbmDrr.SalesDownlineMonthlyTarget",
724
                        com.spice.profitmandi.dao.model.SalesDownlineMonthlyTargetModel.class);
725
        q.setParameter("authIds", authIds);
726
        q.setParameter("refDate", refDate);
727
        return q.getResultList();
728
    }
729
 
730
    @Override
37533 ranu 731
    public List<com.spice.profitmandi.dao.model.SalesHierarchyBrandSoldModel>
732
    getSalesBrandsSold(Collection<Integer> authIds, int warehouseId, LocalDate startDate, LocalDate endDate) {
733
        if (authIds == null || authIds.isEmpty()) return new ArrayList<>();
734
        Session session = sessionFactory.getCurrentSession();
735
        TypedQuery<com.spice.profitmandi.dao.model.SalesHierarchyBrandSoldModel> q =
736
                session.createNamedQuery("RbmDrr.SalesBrandSold",
737
                        com.spice.profitmandi.dao.model.SalesHierarchyBrandSoldModel.class);
738
        q.setParameter("authIds", authIds);
739
        q.setParameter("warehouseId", warehouseId);
740
        q.setParameter("startDate", startDate);
741
        q.setParameter("endDate", endDate);
742
        return q.getResultList();
743
    }
744
 
745
    @Override
746
    public List<com.spice.profitmandi.dao.model.SalesHierarchyBrandPartnerCountModel>
747
    getSalesBrandPartnerCount(Collection<Integer> authIds, int warehouseId, LocalDate startDate, LocalDate endDate) {
748
        if (authIds == null || authIds.isEmpty()) return new ArrayList<>();
749
        Session session = sessionFactory.getCurrentSession();
750
        TypedQuery<com.spice.profitmandi.dao.model.SalesHierarchyBrandPartnerCountModel> q =
751
                session.createNamedQuery("RbmDrr.SalesBrandPartnerCount",
752
                        com.spice.profitmandi.dao.model.SalesHierarchyBrandPartnerCountModel.class);
753
        q.setParameter("authIds", authIds);
754
        q.setParameter("warehouseId", warehouseId);
755
        q.setParameter("startDate", startDate);
756
        q.setParameter("endDate", endDate);
757
        return q.getResultList();
758
    }
759
 
760
    @Override
37497 ranu 761
    public void setSalesAchievements() {
762
        setSalesAchievementsForDate(LocalDate.now());
763
    }
764
 
765
    @Override
766
    public void setSalesAchievementsForDate(LocalDate date) {
37505 ranu 767
        // Sales ACH per (auth × warehouse) — direct query scoped to each Sales
768
        // person's OWN partners (no RBM roll-up). Same source-of-truth orders SQL.
37497 ranu 769
        String[][] roles = {
770
                {"ASM", "4", "L1"}, {"CM", "4", "L2"}, {"DM", "4", "L3"},
771
                {"BM", "4", "L4"}, {"RSM", "4", "L5"}, {"NSM", "4", "L6"}
772
        };
773
 
774
        LocalDate today = LocalDate.now();
775
        LocalDateTime stamp = date.equals(today) ? LocalDateTime.now() : date.atTime(java.time.LocalTime.NOON);
37505 ranu 776
        int written = 0;
37497 ranu 777
 
37519 ranu 778
        // Cache per-level results so the RSM/NSM roll-up pass reuses them.
779
        Map<String, List<SalesHierarchyAchievedByMovementModel>> byLevel = new HashMap<>();
37497 ranu 780
        for (String[] r : roles) {
781
            String role = r[0];
782
            int catId = Integer.parseInt(r[1]);
37505 ranu 783
            String levelStr = r[2];
37497 ranu 784
 
37519 ranu 785
            List<SalesHierarchyAchievedByMovementModel> list =
786
                    getSalesAchievementByRole(catId, levelStr, date, date.plusDays(1));
787
            byLevel.put(role, list);
788
            for (SalesHierarchyAchievedByMovementModel a : list) {
37505 ranu 789
                SalesAchievements sa = new SalesAchievements();
790
                sa.setSalesAuthId(a.getAuthId());
791
                sa.setSalesName(a.getName());
792
                sa.setRole(role);
793
                sa.setWarehouseId(a.getWarehouseId());
794
                sa.setAchievedHidTarget(a.getHidBilled());
795
                sa.setAchievedFastMovingTarget(a.getFastMovingBilled());
796
                sa.setAchievedRunningTarget(a.getRunningBilled() != null ? a.getRunningBilled() : 0f);
797
                sa.setAchievedSlowMovingTarget(a.getSlowMovingBilled() != null ? a.getSlowMovingBilled() : 0f);
798
                sa.setAchievedOtherTarget(a.getOtherBilled() != null ? a.getOtherBilled() : 0f);
799
                sa.setCreateTimestamp(stamp);
800
                salesAchievementsRepository.persist(sa);
801
                written++;
37497 ranu 802
            }
803
        }
37519 ranu 804
 
805
        // Roll-up pass for RSM (L5) and NSM (L6) — uses distinct-partner dedup
806
        // query so partners assigned to multiple positions in the downline
807
        // don't have their orders counted more than once.
808
        String[] rollUpRoles = {"RSM", "NSM"};
809
        for (String rollUpRole : rollUpRoles) {
810
            String levelStr = "RSM".equals(rollUpRole) ? "L5" : "L6";
811
            for (AuthUser top : csService.getAuthUserByCategoryId(4, EscalationType.valueOf(levelStr))) {
812
                if (!top.isActive()) continue;
813
                Set<Integer> tree = new HashSet<>();
814
                tree.add(top.getId());
815
                List<Integer> reportees = authService.getAllReportees(top.getId());
816
                if (reportees != null) tree.addAll(reportees);
817
                if (tree.isEmpty()) continue;
818
 
819
                for (com.spice.profitmandi.dao.model.SalesDownlineAchievedByMovementModel d :
820
                        getSalesDownlineAchievement(tree, date, date.plusDays(1))) {
821
                    SalesAchievements sa = new SalesAchievements();
822
                    sa.setSalesAuthId(top.getId());
823
                    sa.setSalesName(top.getFullName());
824
                    sa.setRole(rollUpRole);
825
                    sa.setWarehouseId(d.getWarehouseId());
826
                    sa.setAchievedHidTarget(d.getHidBilled());
827
                    sa.setAchievedFastMovingTarget(d.getFastMovingBilled());
828
                    sa.setAchievedRunningTarget(d.getRunningBilled() != null ? d.getRunningBilled() : 0f);
829
                    sa.setAchievedSlowMovingTarget(d.getSlowMovingBilled() != null ? d.getSlowMovingBilled() : 0f);
830
                    sa.setAchievedOtherTarget(d.getOtherBilled() != null ? d.getOtherBilled() : 0f);
831
                    sa.setCreateTimestamp(stamp);
832
                    salesAchievementsRepository.persist(sa);
833
                    written++;
834
                }
835
            }
836
        }
37497 ranu 837
        LOGGER.info("setSalesAchievementsForDate: wrote {} sales_achievements rows for {}", written, date);
838
    }
839
 
840
    @Override
33985 ranu 841
    public void setMovementWiseRbmAchievement() {
842
        LocalDate todayDate = LocalDate.now();
843
 
34055 ranu 844
        List<TodayAchievedMovementModel> todayAchievedMovementModels = getMovementWiseAchievementByDate(todayDate, todayDate.plusDays(1));
33985 ranu 845
 
846
 
847
        for (TodayAchievedMovementModel achievement : todayAchievedMovementModels) {
848
 
849
            RbmAchievements rbmAchievements = new RbmAchievements();
850
 
851
            rbmAchievements.setRbmAuthId(achievement.getAuthId());
852
            rbmAchievements.setRbmName(achievement.getRbmName());
853
            rbmAchievements.setWarehouseId(achievement.getWarehouseId());
854
            rbmAchievements.setAchievedHidTarget(achievement.getHidBilled());
855
            rbmAchievements.setAchievedFastMovingTarget(achievement.getFastMovingBilled());
34098 ranu 856
            rbmAchievements.setAchievedSlowMovingTarget(achievement.getSlowMovinBilled());
857
            rbmAchievements.setAchievedRunningTarget(achievement.getRunningBilled());
34012 ranu 858
            rbmAchievements.setAchievedOtherTarget(achievement.getOtherBilled());
33985 ranu 859
            rbmAchievements.setCreateTimestamp(LocalDateTime.now());
860
 
861
            rbmAchievementsRepository.persist(rbmAchievements);
862
 
863
        }
864
 
865
    }
866
 
34055 ranu 867
    @Override
868
    public List<Sold15daysOldAgingModel> getAgingSale(LocalDate startDate, LocalDate endDate) {
869
        Session session = sessionFactory.getCurrentSession();
870
        final TypedQuery<Sold15daysOldAgingModel> typedQuerySimilar = session.createNamedQuery("Aging.SoldAgingModel", Sold15daysOldAgingModel.class);
34056 ranu 871
        typedQuerySimilar.setParameter("startDate", startDate);
872
        typedQuerySimilar.setParameter("endDate", endDate);
34055 ranu 873
        return typedQuerySimilar.getResultList();
33985 ranu 874
 
34055 ranu 875
    }
876
 
34103 ranu 877
    @Override
878
    public List<RbmBilledFofoIdsModel> getDateWiseBilledFofoIdByRbm(LocalDate startDate, LocalDate endDate) {
879
        Session session = sessionFactory.getCurrentSession();
880
        final TypedQuery<RbmBilledFofoIdsModel> typedQuerySimilar = session.createNamedQuery("RBM.RbmBilledFofoId", RbmBilledFofoIdsModel.class);
881
        typedQuerySimilar.setParameter("startDate", startDate);
882
        typedQuerySimilar.setParameter("endDate", endDate);
883
        return typedQuerySimilar.getResultList();
35453 amit 884
    }
34103 ranu 885
 
35453 amit 886
    @Override
36296 amit 887
    @Cacheable(value = "rbmWeeklyBilling",
36329 amit 888
            cacheManager = "fiveMintimeoutCacheManager",
889
            sync = true)
35453 amit 890
    public List<RbmWeeklyBillingModel> getWeeklyBillingDataForMonth(LocalDate monthStart, LocalDate monthEnd) {
891
        Session session = sessionFactory.getCurrentSession();
892
        final TypedQuery<RbmWeeklyBillingModel> typedQuery = session.createNamedQuery("RBM.WeeklyBilling", RbmWeeklyBillingModel.class);
893
        typedQuery.setParameter("startDate", monthStart);
894
        typedQuery.setParameter("endDate", monthEnd);
895
        return typedQuery.getResultList();
34103 ranu 896
    }
897
 
34055 ranu 898
    public List<Our15DaysOldAgingStock> our15DaysAgingStock() {
899
        Session session = sessionFactory.getCurrentSession();
900
        final TypedQuery<Our15DaysOldAgingStock> typedQuerySimilar = session.createNamedQuery("Aging.15DaysOurStock", Our15DaysOldAgingStock.class);
901
        return typedQuerySimilar.getResultList();
36334 ranu 902
    }
34055 ranu 903
 
36334 ranu 904
    @Override
905
    public List<WarehouseAgingStockModel> getWarehouseWiseAgingStock() {
906
        Session session = sessionFactory.getCurrentSession();
907
        final TypedQuery<WarehouseAgingStockModel> typedQuery = session.createNamedQuery("Aging.15DaysWarehouseWiseStock", WarehouseAgingStockModel.class);
908
        return typedQuery.getResultList();
34055 ranu 909
    }
910
 
34397 ranu 911
    @Autowired
912
    OrderRepository orderRepository;
34055 ranu 913
 
34397 ranu 914
    @Override
34641 ranu 915
    public double calculateFofoIdTodayTarget(int fofoId, double secondryMtd,LocalDate date) {
34397 ranu 916
 
917
        MonthlyTarget monthlyTarget = monthlyTargetRepository.selectByDateAndFofoId(YearMonth.now(), fofoId);
34404 ranu 918
        if (monthlyTarget == null) {
919
            // Log or handle as needed
920
            return 0; // or -1 or some fallback
921
        }
34397 ranu 922
 
923
        double remainingTarget = monthlyTarget.getPurchaseTarget() - secondryMtd;
34880 ranu 924
//        double remainingWorkingDays = getWorkingDaysCount(date);
925
        double remainingWorkingDays = (double) getRemainingDaysInMonth(date);
34397 ranu 926
 
927
 
34716 ranu 928
 
34397 ranu 929
        if (remainingWorkingDays == 0) return remainingTarget; // Last day
34716 ranu 930
        LOGGER.info("remainingWorkingDays {}", remainingWorkingDays);
931
        LOGGER.info("remainingTarget {}", remainingTarget);
34397 ranu 932
 
933
        return (int) Math.ceil(remainingTarget / remainingWorkingDays);
934
    }
935
 
34880 ranu 936
    @Override
937
    public long getRemainingDaysInMonth(LocalDate date) {
938
        LocalDate lastDayOfMonth = YearMonth.from(date).atEndOfMonth();
34397 ranu 939
 
34880 ranu 940
        long totalDays = ChronoUnit.DAYS.between(date, lastDayOfMonth) + 1;
34397 ranu 941
 
34880 ranu 942
        // Count Sundays manually
943
        long sundayCount = 0;
944
        LocalDate current = date;
945
        while (!current.isAfter(lastDayOfMonth)) {
946
            if (current.getDayOfWeek() == DayOfWeek.SUNDAY) {
947
                sundayCount++;
948
            }
949
            current = current.plusDays(1);
950
        }
951
 
952
        // Public holidays in the range
953
        long publicHolidays = publicHolidaysRepository
954
                .selectAllBetweenDates(date, lastDayOfMonth)
955
                .size();
956
 
957
        long remainingDays = totalDays - sundayCount - publicHolidays;
958
 
959
        LOGGER.info("remainingDays {}", remainingDays);
960
        LOGGER.info("totalDays {}", totalDays);
961
        LOGGER.info("sundays {}", sundayCount);
962
        LOGGER.info("publicHolidays {}", publicHolidays);
963
 
964
        return remainingDays;
965
    }
966
 
35631 ranu 967
    @Autowired
968
    PositionRepository positionRepository;
34880 ranu 969
 
35631 ranu 970
    @Autowired
971
    CsService csService;
34880 ranu 972
 
35631 ranu 973
    @Autowired
974
    FofoStoreRepository fofoStoreRepository;
975
 
976
    @Autowired
977
    AuthRepository authRepository;
978
 
979
    @Autowired
980
    LoanRepository loanRepository;
981
 
982
    @Autowired
983
    PartnerCollectionService partnerCollectionService;
984
 
985
    @Autowired
986
    PartnerCollectionRemarkRepository partnerCollectionRemarkRepository;
987
 
988
    @Autowired
989
    RbmCallSequenceLogRepository rbmCallSequenceLogRepository;
990
 
991
    @Autowired
992
    com.spice.profitmandi.dao.repository.cs.TicketRepository ticketRepository;
993
 
35702 ranu 994
    @Autowired
995
    com.spice.profitmandi.dao.repository.cs.AgentCallLogRepository agentCallLogRepository;
996
 
35759 ranu 997
    @Autowired
998
    RetailerContactRepository retailerContactRepository;
999
 
1000
    @Autowired
1001
    AddressRepository addressRepository;
1002
 
36225 ranu 1003
    @Autowired
1004
    com.spice.profitmandi.dao.repository.cs.PartnerPositionRepository partnerPositionRepository;
1005
 
35631 ranu 1006
    @Override
1007
    public List<RbmCallTargetModel> getRbmCallTargetModels() throws Exception {
36234 ranu 1008
        return getRbmCallTargetModels(LocalDate.now());
1009
    }
1010
 
37010 ranu 1011
    // Matches "No Answer", "NO_ANSWER", "no-answer", "noanswer", etc.
1012
    private static boolean isNoAnswerStatus(String status) {
1013
        if (status == null) return false;
1014
        String s = status.trim().toLowerCase().replace('_', ' ').replace('-', ' ').replaceAll("\\s+", " ");
1015
        return s.equals("no answer") || s.equals("noanswer");
1016
    }
1017
 
1018
    @Override
1019
    public List<OutOfSequenceDetailModel> getOutOfSequenceDetails(int authId) {
1020
 
1021
        LocalDate today = LocalDate.now();
1022
        LocalDateTime start = today.atStartOfDay();
1023
        LocalDateTime end = today.plusDays(1).atStartOfDay();
1024
 
1025
        List<RbmCallSequenceLog> logs =
1026
                rbmCallSequenceLogRepository.selectByAuthIdAndDateRange(authId, start, end);
1027
 
1028
        Map<Integer, RbmCallSequenceLog> uniqueOosLogsByFofoId = new LinkedHashMap<>();
1029
 
1030
        for (RbmCallSequenceLog log : logs) {
1031
            if (log.isOutOfSequence()) {
1032
                // Keep only the first occurrence per fofoId (latest entry since ordered by id DESC)
1033
                uniqueOosLogsByFofoId.putIfAbsent(log.getFofoId(), log);
1034
            }
1035
        }
1036
 
1037
        if (uniqueOosLogsByFofoId.isEmpty()) {
1038
            return Collections.emptyList();
1039
        }
1040
 
1041
        Set<Integer> fofoIds = uniqueOosLogsByFofoId.keySet();
1042
        Map<Integer, CustomRetailer> retailerMap = Collections.emptyMap();
1043
        try {
1044
            retailerMap = retailerService.getFofoRetailers(new ArrayList<>(fofoIds));
1045
        } catch (ProfitMandiBusinessException e) {
1046
            LOGGER.error("Error fetching fofo stores", e);
1047
        }
1048
 
1049
        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("hh:mm a");
1050
        List<OutOfSequenceDetailModel> result = new ArrayList<>();
1051
 
1052
        for (RbmCallSequenceLog log : uniqueOosLogsByFofoId.values()) {
1053
            CustomRetailer retailer = retailerMap.get(log.getFofoId());
1054
            String partyName = retailer != null
1055
                    ? retailer.getBusinessName()
1056
                    : "Unknown (" + log.getFofoId() + ")";
1057
            String code = retailer != null
1058
                    ? retailer.getCode()
1059
                    : "-";
1060
 
1061
            String time = log.getCreateTimestamp() != null
1062
                    ? log.getCreateTimestamp().format(timeFormatter)
1063
                    : "-";
1064
 
1065
            result.add(new OutOfSequenceDetailModel(partyName, code, time));
1066
        }
1067
 
1068
        return result;
1069
    }
1070
 
1071
    @Override
1072
    public List<CalledPartnerDetailModel> getCalledPartnerDetails(int authId) throws ProfitMandiBusinessException {
1073
        return getCalledPartnerDetails(authId, LocalDate.now());
1074
    }
1075
 
1076
    @Override
1077
    public List<CalledPartnerDetailModel> getCalledPartnerDetails(int authId, LocalDate date) throws ProfitMandiBusinessException {
1078
        // Get all call logs for this auth user on this date
1079
        LOGGER.info("getCalledPartnerDetails: authId={}, date={}", authId, date);
1080
        List<AgentCallLog> callLogs = agentCallLogRepository.findByAuthIdAndDate(authId, date);
1081
        LOGGER.info("Found {} call logs for authId={} on date={}", callLogs.size(), authId, date);
1082
 
1083
        if (callLogs.isEmpty()) {
1084
            return Collections.emptyList();
1085
        }
1086
 
1087
        // Build a map of normalized customer number -> fofoId
1088
        Map<String, Integer> customerToFofoIdMap = new HashMap<>();
1089
        Set<String> normalizedNumbers = new HashSet<>();
1090
 
1091
        for (AgentCallLog callLog : callLogs) {
1092
            String customerNumber = callLog.getCustomerNumber();
1093
            if (customerNumber != null) {
1094
                String normalized = customerNumber.startsWith("+91") ? customerNumber.substring(3) : customerNumber;
1095
                normalizedNumbers.add(normalized);
1096
            }
1097
        }
1098
 
1099
        // For each normalized number, find fofoId from retailer_contact first, then address
1100
        for (String mobile : normalizedNumbers) {
1101
            Integer fofoId = findFofoIdByMobile(mobile);
1102
            if (fofoId != null) {
1103
                customerToFofoIdMap.put(mobile, fofoId);
1104
            }
1105
        }
1106
 
1107
        // Get unique fofoIds for retailer lookup
1108
        Set<Integer> fofoIds = new HashSet<>(customerToFofoIdMap.values());
1109
        Map<Integer, CustomRetailer> retailerMap = Collections.emptyMap();
1110
        if (!fofoIds.isEmpty()) {
1111
            try {
1112
                retailerMap = retailerService.getFofoRetailers(new ArrayList<>(fofoIds));
1113
            } catch (ProfitMandiBusinessException e) {
1114
                LOGGER.error("Error fetching fofo stores", e);
1115
            }
1116
        }
1117
 
1118
        // Get today's remarks for these fofoIds
1119
        Map<Integer, List<PartnerCollectionRemark>> fofoRemarkMap = new HashMap<>();
1120
        if (!fofoIds.isEmpty()) {
1121
            List<PartnerCollectionRemark> todayRemarks = partnerCollectionRemarkRepository
1122
                    .selectAllByFofoIdsOnDate(new ArrayList<>(fofoIds), date);
1123
            for (PartnerCollectionRemark remark : todayRemarks) {
1124
                fofoRemarkMap.computeIfAbsent(remark.getFofoId(), k -> new ArrayList<>()).add(remark);
1125
            }
1126
        }
1127
 
1128
        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("hh:mm a");
1129
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy hh:mm a");
1130
        List<CalledPartnerDetailModel> result = new ArrayList<>();
1131
 
1132
        for (com.spice.profitmandi.dao.entity.cs.AgentCallLog callLog : callLogs) {
1133
            String customerNumber = callLog.getCustomerNumber();
1134
            if (customerNumber == null) {
1135
                continue;
1136
            }
1137
 
1138
            String normalized = customerNumber.startsWith("+91") ? customerNumber.substring(3) : customerNumber;
1139
            Integer fofoId = customerToFofoIdMap.get(normalized);
1140
 
1141
            String partyName = "Unknown";
1142
            String code = "-";
1143
 
1144
            if (fofoId != null) {
1145
                CustomRetailer retailer = retailerMap.get(fofoId);
1146
                if (retailer != null) {
1147
                    partyName = retailer.getBusinessName();
1148
                    code = retailer.getCode();
1149
                } else {
1150
                    partyName = "Unknown (" + fofoId + ")";
1151
                }
1152
            } else {
1153
                partyName = "Unknown (" + normalized + ")";
1154
            }
1155
 
1156
            // Get remark if available
1157
            String remarkValue = "-";
1158
            String messageValue = "-";
1159
            String remarkTime = "-";
1160
 
1161
            if (fofoId != null && fofoRemarkMap.containsKey(fofoId)) {
1162
                List<PartnerCollectionRemark> remarks = fofoRemarkMap.get(fofoId);
1163
                if (!remarks.isEmpty()) {
1164
                    PartnerCollectionRemark remark = remarks.get(0);
1165
                    remarkValue = remark.getRemark() != null ? remark.getRemark().getValue() : "-";
1166
                    messageValue = remark.getMessage() != null ? remark.getMessage() : "-";
1167
                    remarkTime = remark.getCreateTimestamp() != null ? remark.getCreateTimestamp().format(timeFormatter) : "-";
1168
                }
1169
            }
1170
 
1171
            // Build call log data
1172
            String recordingUrl = callLog.getRecordingUrl();
1173
            String callStatus = callLog.getCallStatus();
1174
            String callDuration = callLog.getCallDuration();
1175
            String callDateTime = null;
1176
            if (callLog.getCallDate() != null && callLog.getCallTime() != null) {
1177
                LocalDateTime callDateTimeObj = LocalDateTime.of(callLog.getCallDate(), callLog.getCallTime());
1178
                callDateTime = callDateTimeObj.format(dateTimeFormatter);
1179
            }
1180
 
1181
            result.add(new CalledPartnerDetailModel(partyName, code, remarkValue, messageValue, remarkTime,
1182
                    recordingUrl, callStatus, callDuration, callDateTime));
1183
        }
1184
 
1185
        return result;
1186
    }
1187
 
1188
    private Integer findFofoIdByMobile(String mobile) {
1189
        // First check retailer_contact
1190
        List<RetailerContact> contacts = retailerContactRepository.selectByMobile(mobile);
1191
        if (contacts != null && !contacts.isEmpty()) {
1192
            return contacts.get(0).getFofoId();
1193
        }
1194
 
1195
        // Fallback to user.address
1196
        List<Address> addresses = addressRepository.selectAllByPhoneNumber(mobile);
1197
        if (addresses != null && !addresses.isEmpty()) {
1198
            return addresses.get(0).getRetaierId();
1199
        }
1200
 
1201
        return null;
1202
    }
1203
 
1204
    private List<CalledPartnerDetailModel> buildCalledPartnerResult(List<PartnerCollectionRemark> allRemarks) {
1205
        if (allRemarks.isEmpty()) {
1206
            return Collections.emptyList();
1207
        }
1208
 
1209
        // Get unique fofoIds for retailer lookup
1210
        Set<Integer> fofoIds = allRemarks.stream()
1211
                .map(PartnerCollectionRemark::getFofoId)
1212
                .collect(Collectors.toSet());
1213
        Map<Integer, CustomRetailer> retailerMap = Collections.emptyMap();
1214
        try {
1215
            retailerMap = retailerService.getFofoRetailers(new ArrayList<>(fofoIds));
1216
        } catch (ProfitMandiBusinessException e) {
1217
            LOGGER.error("Error fetching fofo stores", e);
1218
        }
1219
 
1220
        // Fetch call logs for remarks that have agentCallLogId
1221
        Map<Long, com.spice.profitmandi.dao.entity.cs.AgentCallLog> callLogMap = new HashMap<>();
1222
        try {
1223
            List<Long> callLogIds = allRemarks.stream()
1224
                    .filter(r -> r.getAgentCallLogId() > 0)
1225
                    .map(PartnerCollectionRemark::getAgentCallLogId)
1226
                    .collect(Collectors.toList());
1227
 
1228
            if (!callLogIds.isEmpty()) {
1229
                List<com.spice.profitmandi.dao.entity.cs.AgentCallLog> callLogs = agentCallLogRepository.findByIds(callLogIds);
1230
                if (callLogs != null) {
1231
                    callLogMap = callLogs.stream()
1232
                            .collect(Collectors.toMap(com.spice.profitmandi.dao.entity.cs.AgentCallLog::getId, c -> c, (a, b) -> a));
1233
                }
1234
            }
1235
        } catch (Exception e) {
1236
            LOGGER.error("Error fetching call logs by ids", e);
1237
        }
1238
 
1239
        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("hh:mm a");
1240
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy hh:mm a");
1241
        List<CalledPartnerDetailModel> result = new ArrayList<>();
1242
 
1243
        for (PartnerCollectionRemark remark : allRemarks) {
1244
            CustomRetailer retailer = retailerMap.get(remark.getFofoId());
1245
            String partyName = retailer != null
1246
                    ? retailer.getBusinessName()
1247
                    : "Unknown (" + remark.getFofoId() + ")";
1248
            String code = retailer != null
1249
                    ? retailer.getCode()
1250
                    : "-";
1251
 
1252
            String remarkValue = remark.getRemark() != null
1253
                    ? remark.getRemark().getValue()
1254
                    : "-";
1255
 
1256
            String messageValue = remark.getMessage() != null
1257
                    ? remark.getMessage()
1258
                    : "-";
1259
 
1260
            String time = remark.getCreateTimestamp() != null
1261
                    ? remark.getCreateTimestamp().format(timeFormatter)
1262
                    : "-";
1263
 
1264
            // Get call log data if available
1265
            String recordingUrl = null;
1266
            String callStatus = null;
1267
            String callDuration = null;
1268
            String callDateTime = null;
1269
 
1270
            try {
1271
                if (remark.getAgentCallLogId() > 0 && callLogMap.containsKey(remark.getAgentCallLogId())) {
1272
                    com.spice.profitmandi.dao.entity.cs.AgentCallLog callLog = callLogMap.get(remark.getAgentCallLogId());
1273
                    recordingUrl = callLog.getRecordingUrl();
1274
                    callStatus = callLog.getCallStatus();
1275
                    callDuration = callLog.getCallDuration();
1276
                    if (callLog.getCallDate() != null && callLog.getCallTime() != null) {
1277
                        LocalDateTime callDateTimeObj = LocalDateTime.of(callLog.getCallDate(), callLog.getCallTime());
1278
                        callDateTime = callDateTimeObj.format(dateTimeFormatter);
1279
                    }
1280
                }
1281
            } catch (Exception e) {
1282
                LOGGER.error("Error processing call log for remark id: {}", remark.getId(), e);
1283
            }
1284
 
1285
            result.add(new CalledPartnerDetailModel(partyName, code, remarkValue, messageValue, time,
1286
                    recordingUrl, callStatus, callDuration, callDateTime));
1287
        }
1288
 
1289
        return result;
1290
    }
1291
 
1292
    @Override
1293
    public List<List<String>> getRbmCallTargetRawDataByAuthId(int authId) throws Exception {
1294
        List<List<String>> rows = new ArrayList<>();
1295
 
1296
        // Get auth user
1297
        List<AuthUser> authUsers = authRepository.selectByIds(Collections.singletonList(authId));
1298
        if (authUsers.isEmpty()) {
1299
            return rows;
1300
        }
1301
        AuthUser authUser = authUsers.get(0);
1302
 
1303
        // Get positions to determine if L2
1304
        List<Position> positions = positionRepository.selectPositionByAuthIds(Collections.singletonList(authId));
1305
        boolean isL2 = positions.stream()
1306
                .anyMatch(p -> ProfitMandiConstants.TICKET_CATEGORY_RBM == p.getCategoryId()
1307
                        && EscalationType.L2.equals(p.getEscalationType()));
1308
 
1309
        LocalDateTime startDate = LocalDate.now().atStartOfDay();
1310
        LocalDate firstOfMonth = LocalDate.now().withDayOfMonth(1);
1311
        LocalDate endOfMonth = LocalDate.now().withDayOfMonth(LocalDate.now().lengthOfMonth()).plusDays(1);
1312
 
1313
        // Get fofo IDs from mtdBillingData (same source as Partner Count in getRbmCallTargetModels)
1314
        List<RbmWeeklyBillingModel> mtdBillingData = getWeeklyBillingDataForMonth(firstOfMonth, endOfMonth);
1315
 
1316
        List<Integer> fofoIdList;
1317
        if (isL2) {
37145 ranu 1318
            // L2: get fofo IDs from escalated tickets (same as getRbmCallTargetModels).
1319
            // selectOpenEscalatedTicketsByAuthIds already filters to tickets where authId is the
1320
            // current assignee AND the ticket has moved past L1 — no post-filter needed.
37010 ranu 1321
            List<Ticket> escalatedTickets = ticketRepository.selectOpenEscalatedTicketsByAuthIds(Collections.singletonList(authId));
1322
            fofoIdList = escalatedTickets.stream()
1323
                    .map(Ticket::getFofoId)
1324
                    .distinct()
1325
                    .collect(Collectors.toList());
1326
        } else {
1327
            // L1: get fofo IDs from mtdBillingData with isTargetedPartner (same as Partner Count)
1328
            fofoIdList = mtdBillingData.stream()
1329
                    .filter(RbmWeeklyBillingModel::isTargetedPartner)
1330
                    .filter(m -> m.getAuthId() == authId)
1331
                    .map(RbmWeeklyBillingModel::getFofoId)
1332
                    .distinct()
1333
                    .collect(Collectors.toList());
1334
        }
1335
 
1336
        if (fofoIdList.isEmpty()) {
1337
            return rows;
1338
        }
1339
 
1340
        // MTD billed fofoIds for zero billing check
1341
        Set<Integer> mtdBilledFofoIds = mtdBillingData.stream()
1342
                .filter(RbmWeeklyBillingModel::isMtdBilled)
1343
                .map(RbmWeeklyBillingModel::getFofoId)
1344
                .collect(Collectors.toSet());
1345
 
1346
        // Collection rank map for status calculation
1347
        Map<Integer, Integer> collectionRankMap = new HashMap<>();
1348
        try {
1349
            collectionRankMap = partnerCollectionService.getCollectionRankMap(fofoIdList, startDate);
1350
        } catch (ProfitMandiBusinessException e) {
1351
            LOGGER.error("Error fetching collection rank map", e);
1352
        }
1353
 
1354
        // Resolve partner names/codes
1355
        Map<Integer, CustomRetailer> retailerMap = Collections.emptyMap();
1356
        if (!fofoIdList.isEmpty()) {
1357
            try {
1358
                retailerMap = retailerService.getFofoRetailers(fofoIdList);
1359
            } catch (ProfitMandiBusinessException e) {
1360
                LOGGER.error("Error fetching fofo retailers for raw data", e);
1361
            }
1362
        }
1363
 
1364
        String rbmName = authUser.getFullName() + (isL2 ? " (L2)" : "");
1365
 
1366
        // Build rows for ALL partners (same count as Partner Count)
1367
        for (Integer fofoId : fofoIdList) {
1368
            // Default to rank 5 (Normal) for partners without collection plan
1369
            int rank = collectionRankMap.getOrDefault(fofoId, 5);
1370
            boolean hasZeroBilling = !mtdBilledFofoIds.contains(fofoId);
1371
 
1372
            // Status assignment with same priority as getRbmCallTargetModels
1373
            String status;
1374
            if (rank == 1) {
1375
                status = "Plan Today";
1376
            } else if (rank == 2) {
1377
                status = "Carry Forward";
1378
            } else if (hasZeroBilling) {
1379
                status = "Zero Billing";
1380
            } else if (rank == 3) {
1381
                status = "Untouched";
1382
            } else if (rank == 4) {
1383
                status = "Future Plan";
1384
            } else {
1385
                status = "Normal";
1386
            }
1387
 
1388
            CustomRetailer retailer = retailerMap.get(fofoId);
1389
            String partnerName = retailer != null ? retailer.getBusinessName() : "Unknown (" + fofoId + ")";
1390
            String partnerCode = retailer != null ? retailer.getCode() : "-";
1391
 
1392
            rows.add(Arrays.asList(partnerName, partnerCode, status, rbmName));
1393
        }
1394
 
1395
        return rows;
1396
    }
1397
 
1398
    @Override
1399
    public List<List<String>> getAllRbmCallTargetRawData() throws Exception {
1400
        List<List<String>> rows = new ArrayList<>();
1401
 
1402
        // Get all L1 RBM positions (rows are emitted per L1 RBM).
1403
        // Call-log lookup happens against ALL callers via findAllByDate — so an L2/L3 or admin call
1404
        // still counts as "tried" for the partner.
1405
        List<Position> allRbmPositions = positionRepository
1406
                .selectPositionByCategoryId(ProfitMandiConstants.TICKET_CATEGORY_RBM).stream()
1407
                .filter(x -> EscalationType.L1.equals(x.getEscalationType()))
1408
                .collect(Collectors.toList());
1409
 
1410
        List<Integer> l1AuthIds = allRbmPositions.stream()
1411
                .map(Position::getAuthUserId).distinct().collect(Collectors.toList());
1412
 
1413
        if (l1AuthIds.isEmpty()) {
1414
            return rows;
1415
        }
1416
 
1417
        // Get auth user map
1418
        Map<Integer, AuthUser> authUserMap = authRepository.selectByIds(l1AuthIds).stream()
1419
                .collect(Collectors.toMap(AuthUser::getId, au -> au));
1420
 
1421
        // Positions per auth user (needed early to build L1 partner assignments below)
1422
        Map<Integer, List<Position>> positionsByAuthId = positionRepository.selectPositionByAuthIds(l1AuthIds).stream()
1423
                .collect(Collectors.groupingBy(Position::getAuthUserId));
1424
 
1425
        // Build L1 partner assignments from partner_position — SAME source the UI summary uses for
1426
        // Calling Target. storeGuyMap (email-based CS mapping) was returning smaller/different lists
1427
        // for users who are also L2, causing summary=43 but download=3 for Ebadullah.
1428
        Map<Integer, Integer> l1PositionIdToAuthId = new HashMap<>();
1429
        for (int rbmAuthId : l1AuthIds) {
1430
            List<Position> positions = positionsByAuthId.getOrDefault(rbmAuthId, Collections.emptyList());
1431
            for (Position p : positions) {
1432
                if (ProfitMandiConstants.TICKET_CATEGORY_RBM == p.getCategoryId()
1433
                        && EscalationType.L1.equals(p.getEscalationType())) {
1434
                    l1PositionIdToAuthId.put(p.getId(), rbmAuthId);
1435
                }
1436
            }
1437
        }
1438
 
1439
        Map<Integer, List<Integer>> rbmToFofoIdsMap = new HashMap<>();
1440
        Set<Integer> allFofoIds = new HashSet<>();
1441
        if (!l1PositionIdToAuthId.isEmpty()) {
1442
            List<com.spice.profitmandi.dao.entity.cs.PartnerPosition> allL1PPs =
1443
                    partnerPositionRepository.selectByPositionIds(new ArrayList<>(l1PositionIdToAuthId.keySet()));
1444
            // Group by owning RBM; keep partners distinct within each RBM.
1445
            Map<Integer, Set<Integer>> perRbmSets = new HashMap<>();
1446
            for (com.spice.profitmandi.dao.entity.cs.PartnerPosition pp : allL1PPs) {
1447
                Integer ownerAuthId = l1PositionIdToAuthId.get(pp.getPositionId());
1448
                if (ownerAuthId == null) continue;
1449
                perRbmSets.computeIfAbsent(ownerAuthId, k -> new HashSet<>()).add(pp.getFofoId());
1450
                allFofoIds.add(pp.getFofoId());
1451
            }
1452
            for (Map.Entry<Integer, Set<Integer>> e : perRbmSets.entrySet()) {
1453
                rbmToFofoIdsMap.put(e.getKey(), new ArrayList<>(e.getValue()));
1454
            }
1455
        }
1456
 
1457
        if (allFofoIds.isEmpty()) {
1458
            return rows;
1459
        }
1460
 
1461
        // Get fofo stores for filtering and name resolution
1462
        Map<Integer, FofoStore> fofoStoresMap = new HashMap<>();
1463
        try {
1464
            fofoStoresMap = fofoStoreRepository.selectByRetailerIds(new ArrayList<>(allFofoIds)).stream()
1465
                    .collect(Collectors.toMap(FofoStore::getId, x -> x, (a, b) -> a));
1466
        } catch (ProfitMandiBusinessException e) {
1467
            LOGGER.error("Error fetching fofo stores for all raw data", e);
1468
        }
1469
 
1470
        // Batch fetch collection rank map
1471
        LocalDateTime startDate = LocalDate.now().atStartOfDay();
1472
        Map<Integer, Integer> allCollectionRankMap = new HashMap<>();
1473
        try {
1474
            allCollectionRankMap = partnerCollectionService.getCollectionRankMap(new ArrayList<>(allFofoIds), startDate);
1475
        } catch (ProfitMandiBusinessException e) {
1476
            LOGGER.error("Error fetching collection rank map for all raw data", e);
1477
        }
1478
 
1479
        // MTD billing data
1480
        LocalDate firstOfMonth = LocalDate.now().withDayOfMonth(1);
1481
        LocalDate endOfMonth = LocalDate.now().withDayOfMonth(LocalDate.now().lengthOfMonth()).plusDays(1);
1482
        List<RbmWeeklyBillingModel> mtdBillingData = getWeeklyBillingDataForMonth(firstOfMonth, endOfMonth);
1483
        Set<Integer> allMtdBilledFofoIds = mtdBillingData.stream()
1484
                .filter(RbmWeeklyBillingModel::isMtdBilled)
1485
                .map(RbmWeeklyBillingModel::getFofoId)
1486
                .collect(Collectors.toSet());
1487
 
1488
        // Batch fetch partner collection remarks for escalation filtering
1489
        Map<Integer, PartnerCollectionRemark> allPartnerCollectionRemarks = new HashMap<>();
1490
        if (!allFofoIds.isEmpty()) {
1491
            List<Integer> allRemarkIds = partnerCollectionRemarkRepository.selectMaxRemarkId(new ArrayList<>(allFofoIds));
1492
            if (!allRemarkIds.isEmpty()) {
1493
                allPartnerCollectionRemarks = partnerCollectionRemarkRepository.selectByIds(allRemarkIds).stream()
1494
                        .collect(Collectors.toMap(PartnerCollectionRemark::getFofoId, x -> x, (a, b) -> a));
1495
            }
1496
        }
1497
 
1498
        // Resolve partner names/codes in batch
1499
        Map<Integer, CustomRetailer> retailerMap = Collections.emptyMap();
1500
        try {
1501
            retailerMap = retailerService.getFofoRetailers(new ArrayList<>(allFofoIds));
1502
        } catch (ProfitMandiBusinessException e) {
1503
            LOGGER.error("Error fetching fofo retailers for all raw data", e);
1504
        }
1505
 
1506
        // Batch-fetch EVERY call log for today (across all callers, not just RBMs) and build a
1507
        // fofoId -> latest AgentCallLog map. We use findAllByDate here so that calls placed by
1508
        // admins, escalation agents, or anyone whose authId isn't in the RBM position table are
1509
        // still surfaced. Downstream we only read this map for fofoIds we actually emit, so
1510
        // extra entries are harmless.
37013 ranu 1511
        // Group ALL today's call logs by fofoId so we can apply the Tried rule per partner
1512
        // (uses customerStatus + persistence, same as the summary's Tried metric).
1513
        Map<Integer, List<AgentCallLog>> callsByFofoId = new HashMap<>();
37010 ranu 1514
        try {
1515
            List<AgentCallLog> todayCallLogs = agentCallLogRepository.findAllByDate(LocalDate.now());
1516
            Set<String> normalizedMobiles = new HashSet<>();
1517
            for (AgentCallLog log : todayCallLogs) {
1518
                if (log.getCustomerNumber() != null) {
1519
                    String n = log.getCustomerNumber();
1520
                    normalizedMobiles.add(n.startsWith("+91") ? n.substring(3) : n);
1521
                }
1522
            }
1523
            Map<String, Integer> mobileToFofoIdMap = buildMobileToFofoIdMap(normalizedMobiles);
1524
            for (AgentCallLog log : todayCallLogs) {
1525
                if (log.getCustomerNumber() == null) continue;
1526
                String n = log.getCustomerNumber();
1527
                String normalized = n.startsWith("+91") ? n.substring(3) : n;
1528
                Integer fofoId = mobileToFofoIdMap.get(normalized);
1529
                if (fofoId == null) continue;
37013 ranu 1530
                callsByFofoId.computeIfAbsent(fofoId, k -> new ArrayList<>()).add(log);
37010 ranu 1531
            }
1532
        } catch (Exception e) {
37013 ranu 1533
            LOGGER.error("Error building call logs map for all raw data", e);
37010 ranu 1534
        }
1535
 
1536
        // Process each L1 RBM
1537
        // Note: no cross-RBM dedup — a partner assigned to multiple RBMs appears once per RBM,
1538
        // matching the UI summary counts so row totals per RBM equal that RBM's "Calling Target".
1539
        for (int rbmAuthId : l1AuthIds) {
1540
            AuthUser authUser = authUserMap.get(rbmAuthId);
1541
            if (authUser == null) {
1542
                continue;
1543
            }
1544
 
1545
            List<Integer> fofoIdList = rbmToFofoIdsMap.getOrDefault(rbmAuthId, Collections.emptyList());
1546
            if (fofoIdList.isEmpty()) {
1547
                continue; // No L1 partner assignments for this RBM
1548
            }
1549
 
1550
            // Filter escalated partners for L1 RBMs
1551
            List<Position> positions = positionsByAuthId.getOrDefault(rbmAuthId, Collections.emptyList());
1552
            boolean isRBMAndL1 = positions.stream()
1553
                    .anyMatch(position ->
1554
                            ProfitMandiConstants.TICKET_CATEGORY_RBM == position.getCategoryId()
1555
                                    && EscalationType.L1.equals(position.getEscalationType()));
1556
 
1557
            List<Integer> fofoIds = fofoIdList;
1558
            if (isRBMAndL1) {
1559
                Map<Integer, PartnerCollectionRemark> partnerRemarks = new HashMap<>();
1560
                for (Integer fofoId : fofoIdList) {
1561
                    if (allPartnerCollectionRemarks.containsKey(fofoId)) {
1562
                        partnerRemarks.put(fofoId, allPartnerCollectionRemarks.get(fofoId));
1563
                    }
1564
                }
1565
                Map<Integer, PartnerCollectionRemark> finalPartnerRemarks = partnerRemarks;
1566
                fofoIds = fofoIdList.stream()
1567
                        .filter(fofoId -> {
1568
                            if (!finalPartnerRemarks.containsKey(fofoId)) return true;
1569
                            PartnerCollectionRemark pcr = finalPartnerRemarks.get(fofoId);
1570
                            return !(CollectionRemark.RBM_L2_ESCALATION.equals(pcr.getRemark())
1571
                                    || CollectionRemark.SALES_ESCALATION.equals(pcr.getRemark()));
1572
                        })
1573
                        .collect(Collectors.toList());
1574
            }
1575
 
1576
            // Filter to only external, ACTIVE or REVIVAL stores
1577
            Map<Integer, FofoStore> finalFofoStoresMap = fofoStoresMap;
1578
            List<Integer> validFofoIds = fofoIds.stream()
1579
                    .filter(fofoId -> {
1580
                        FofoStore store = finalFofoStoresMap.get(fofoId);
1581
                        if (store == null || store.isInternal()) return false;
1582
                        return ActivationType.ACTIVE.equals(store.getActivationType())
1583
                                || ActivationType.REVIVAL.equals(store.getActivationType());
1584
                    })
1585
                    .collect(Collectors.toList());
1586
 
1587
            String rbmName = authUser.getFullName();
1588
 
1589
            // Categorize each partner and add a row per RBM assignment.
1590
            for (Integer fofoId : validFofoIds) {
1591
                int rank = allCollectionRankMap.getOrDefault(fofoId, 5);
1592
                boolean hasZeroBilling = !allMtdBilledFofoIds.contains(fofoId);
1593
 
1594
                String status;
1595
                if (rank == 1) {
1596
                    status = "Plan Today";
1597
                } else if (rank == 2) {
1598
                    status = "Carry Forward";
1599
                } else if (hasZeroBilling) {
1600
                    status = "Zero Billing";
1601
                } else if (rank == 3) {
1602
                    status = "Untouched";
1603
                } else {
1604
                    continue; // Skip Future Plan and Normal — only include calling target parties
1605
                }
1606
 
1607
                CustomRetailer retailer = retailerMap.get(fofoId);
1608
                String partnerName = retailer != null ? retailer.getBusinessName() : "Unknown (" + fofoId + ")";
1609
                String partnerCode = retailer != null ? retailer.getCode() : "-";
1610
 
37013 ranu 1611
                // Show the latest customerStatus from today's call logs for this partner.
1612
                // "Did Not Try" only when there is no call log at all today.
1613
                List<AgentCallLog> partnerCalls = callsByFofoId.getOrDefault(fofoId, Collections.emptyList());
37010 ranu 1614
                String latestCallStatus = "Did Not Try";
37013 ranu 1615
                if (!partnerCalls.isEmpty()) {
1616
                    AgentCallLog latest = null;
1617
                    for (AgentCallLog l : partnerCalls) {
1618
                        if (latest == null || (l.getId() != null && latest.getId() != null && l.getId() > latest.getId())) {
1619
                            latest = l;
1620
                        }
1621
                    }
1622
                    if (latest != null && latest.getCustomerStatus() != null && !latest.getCustomerStatus().isEmpty()) {
1623
                        latestCallStatus = latest.getCustomerStatus();
1624
                    }
37010 ranu 1625
                }
1626
 
1627
                rows.add(Arrays.asList(partnerName, partnerCode, status, rbmName, latestCallStatus));
1628
            }
1629
        }
1630
 
1631
        return rows;
1632
    }
1633
 
1634
    /**
1635
     * Get count of distinct partners called today based on call logs.
1636
     * Maps customerNumber from call log to fofoId using retailer_contact and address.
1637
     * If same fofoId is called multiple times, counts only once.
1638
     * Numbers without fofoId mapping are also counted (by distinct customer number).
1639
     *
1640
     * @param authId the RBM auth ID
1641
     * @return count of distinct partners/numbers called today
1642
     */
1643
    public long getCalledCountFromCallLogs(long authId) {
1644
        return getCalledCountFromCallLogs(authId, LocalDate.now());
1645
    }
1646
 
1647
    public long getCalledCountFromCallLogs(long authId, LocalDate date) {
1648
        return getCallStats(authId, date)[0];
1649
    }
1650
 
1651
    /**
1652
     * Batch-builds a mobile (normalized, +91 stripped) -> fofoId mapping for all the given mobiles
1653
     * in just two queries (retailer_contact, then address fallback for unmapped numbers).
1654
     * Replaces the previous per-call-log findFofoIdByMobile() N+1 lookups.
1655
     */
1656
    private Map<String, Integer> buildMobileToFofoIdMap(Set<String> normalizedMobiles) {
1657
        Map<String, Integer> result = new HashMap<>();
1658
        if (normalizedMobiles == null || normalizedMobiles.isEmpty()) {
1659
            return result;
1660
        }
1661
        List<String> mobilesList = new ArrayList<>(normalizedMobiles);
1662
 
1663
        // First pass: retailer_contact
1664
        List<RetailerContact> contacts = retailerContactRepository.selectByMobiles(mobilesList);
1665
        for (RetailerContact rc : contacts) {
1666
            // Keep the first fofoId we see per mobile (matches old single-mobile behavior of get(0))
1667
            result.putIfAbsent(rc.getMobile(), rc.getFofoId());
1668
        }
1669
 
1670
        // Second pass: address fallback for mobiles not yet mapped
1671
        List<String> unmapped = mobilesList.stream()
1672
                .filter(m -> !result.containsKey(m))
1673
                .collect(Collectors.toList());
1674
        if (!unmapped.isEmpty()) {
1675
            List<Address> addresses = addressRepository.selectAllByPhoneNumbers(unmapped);
1676
            for (Address addr : addresses) {
1677
                result.putIfAbsent(addr.getPhoneNumber(), addr.getRetaierId());
1678
            }
1679
        }
1680
        return result;
1681
    }
1682
 
1683
    /**
1684
     * In-memory variant of getCallStats() that uses pre-fetched call logs and mobile→fofoId mapping.
1685
     * Used by the batch-fetched RBM Call Target loop to avoid N+1 query patterns.
1686
     */
1687
    private long[] getCallStatsFromLogs(List<AgentCallLog> callLogs, Map<String, Integer> mobileToFofoIdMap) {
1688
        if (callLogs == null || callLogs.isEmpty()) {
1689
            return new long[]{0, 0, 0};
1690
        }
1691
 
1692
        Set<Integer> calledFofoIds = new HashSet<>();
1693
        Set<String> calledNumbersWithoutFofoId = new HashSet<>();
1694
        long totalRecordingCalls = 0;
1695
        Set<String> uniqueRecordingNumbers = new HashSet<>();
1696
 
1697
        for (AgentCallLog callLog : callLogs) {
1698
            String customerNumber = callLog.getCustomerNumber();
1699
            if (customerNumber != null) {
1700
                String normalized = customerNumber.startsWith("+91") ? customerNumber.substring(3) : customerNumber;
1701
                Integer fofoId = mobileToFofoIdMap.get(normalized);
1702
                if (fofoId != null) {
1703
                    calledFofoIds.add(fofoId);
1704
                } else {
1705
                    calledNumbersWithoutFofoId.add(normalized);
1706
                }
1707
 
1708
                if (callLog.getRecordingUrl() != null && !callLog.getRecordingUrl().isEmpty()
1709
                        && !"None".equalsIgnoreCase(callLog.getRecordingUrl())) {
1710
                    totalRecordingCalls++;
1711
                    uniqueRecordingNumbers.add(normalized);
1712
                }
1713
            }
1714
        }
1715
 
1716
        long calledCount = calledFofoIds.size() + calledNumbersWithoutFofoId.size();
1717
        return new long[]{calledCount, totalRecordingCalls, uniqueRecordingNumbers.size()};
1718
    }
1719
 
37022 ranu 1720
    // Same rule as computeUniqueTriedCount but restricted to fofoIds in the target set.
1721
    // Used to compute "TGT Calls" — how many of THIS RBM's Calling Target partners were
1722
    // actually engaged today. Off-target activity is silently excluded.
1723
    private long computeUniqueTriedCountForTargets(List<AgentCallLog> callLogs,
1724
                                                   Map<String, Integer> mobileToFofoIdMap,
1725
                                                   Set<Integer> targetFofoIds) {
1726
        if (callLogs == null || callLogs.isEmpty()) return 0L;
1727
        if (targetFofoIds == null || targetFofoIds.isEmpty()) return 0L;
1728
        Map<Integer, List<AgentCallLog>> byFofoId = new HashMap<>();
1729
        for (AgentCallLog log : callLogs) {
1730
            String num = log.getCustomerNumber();
1731
            if (num == null) continue;
1732
            String normalized = num.startsWith("+91") ? num.substring(3) : num;
1733
            Integer fofoId = mobileToFofoIdMap != null ? mobileToFofoIdMap.get(normalized) : null;
1734
            if (fofoId == null) continue;
1735
            if (!targetFofoIds.contains(fofoId)) continue; // off-target, skip
1736
            byFofoId.computeIfAbsent(fofoId, k -> new ArrayList<>()).add(log);
1737
        }
1738
        long count = 0L;
1739
        for (List<AgentCallLog> logs : byFofoId.values()) {
1740
            boolean hasNonMissed = false;
1741
            for (AgentCallLog l : logs) {
1742
                if (!isMissedCustomerStatus(l.getCustomerStatus())) {
1743
                    hasNonMissed = true;
1744
                    break;
1745
                }
1746
            }
1747
            if (hasNonMissed) count++;
1748
            else if (logs.size() >= 3) count++;
1749
        }
1750
        return count;
1751
    }
1752
 
37012 ranu 1753
    // Matches "Missed", "MISSED", "No Answer", "NO_ANSWER", "no-answer" etc.
1754
    // Kommuno currently emits "Missed" — kept liberal so we're covered if the tag changes.
1755
    private static boolean isMissedCustomerStatus(String status) {
1756
        if (status == null) return false;
1757
        String s = status.trim().toLowerCase().replace('_', ' ').replace('-', ' ').replaceAll("\\s+", " ");
1758
        return s.equals("missed") || s.equals("no answer") || s.equals("noanswer");
1759
    }
1760
 
37013 ranu 1761
 
36234 ranu 1762
    public List<RbmCallTargetModel> getRbmCallTargetModels(LocalDate queryDate) throws Exception {
35631 ranu 1763
        long methodStart = System.currentTimeMillis();
1764
        List<RbmCallTargetModel> rbmCallTargetModels = new ArrayList<>();
1765
 
1766
        // Get all RBM positions (L1 and L2)
1767
        long start = System.currentTimeMillis();
1768
        List<Position> allRbmPositions = positionRepository
1769
                .selectPositionByCategoryId(ProfitMandiConstants.TICKET_CATEGORY_RBM).stream()
36210 ranu 1770
                .filter(x -> Arrays.asList(EscalationType.L1, EscalationType.L2, EscalationType.L3).contains(x.getEscalationType()))
35631 ranu 1771
                .collect(Collectors.toList());
1772
 
36210 ranu 1773
        // Separate L1, L2 and L3 auth IDs
35631 ranu 1774
        List<Integer> l1AuthIds = allRbmPositions.stream()
1775
                .filter(p -> EscalationType.L1.equals(p.getEscalationType()))
1776
                .map(Position::getAuthUserId).distinct().collect(Collectors.toList());
1777
        List<Integer> l2AuthIds = allRbmPositions.stream()
1778
                .filter(p -> EscalationType.L2.equals(p.getEscalationType()))
1779
                .map(Position::getAuthUserId).distinct().collect(Collectors.toList());
36210 ranu 1780
        List<Integer> l3AuthIds = allRbmPositions.stream()
1781
                .filter(p -> EscalationType.L3.equals(p.getEscalationType()))
1782
                .map(Position::getAuthUserId).distinct().collect(Collectors.toList());
35631 ranu 1783
 
1784
        // Union of all auth IDs for batch fetching
1785
        List<Integer> rbmPositionsAuthIds = allRbmPositions.stream()
1786
                .map(Position::getAuthUserId).distinct().collect(Collectors.toList());
36210 ranu 1787
        LOGGER.info("RBM Call Target - RBM positions fetch: {}ms, L1: {}, L2: {}, L3: {}", System.currentTimeMillis() - start, l1AuthIds.size(), l2AuthIds.size(), l3AuthIds.size());
35631 ranu 1788
 
1789
        start = System.currentTimeMillis();
1790
        Map<String, Set<Integer>> storeGuyMap = csService.getAuthUserPartnerIdMapping();
1791
        LOGGER.info("RBM Call Target - StoreGuyMap fetch: {}ms", System.currentTimeMillis() - start);
1792
 
36234 ranu 1793
        LocalDateTime startDate = queryDate.atStartOfDay();
1794
        LocalDate firstOfMonth = queryDate.withDayOfMonth(1);
1795
        LocalDate endOfMonth = queryDate.withDayOfMonth(queryDate.lengthOfMonth()).plusDays(1);
35631 ranu 1796
 
1797
        // Get auth user map
1798
        start = System.currentTimeMillis();
1799
        Map<Integer, AuthUser> authUserMap = authRepository.selectByIds(rbmPositionsAuthIds).stream()
1800
                .collect(Collectors.toMap(AuthUser::getId, au -> au));
1801
        LOGGER.info("RBM Call Target - AuthUser fetch: {}ms", System.currentTimeMillis() - start);
1802
 
1803
        // Batch fetch positions by auth IDs (to check if RBM is L1)
1804
        start = System.currentTimeMillis();
1805
        Map<Integer, List<Position>> positionsByAuthId = positionRepository.selectPositionByAuthIds(rbmPositionsAuthIds).stream()
1806
                .collect(Collectors.groupingBy(Position::getAuthUserId));
1807
        LOGGER.info("RBM Call Target - Positions by AuthId fetch: {}ms", System.currentTimeMillis() - start);
1808
 
1809
        // Get all fofo IDs for all RBMs
1810
        Set<Integer> allFofoIds = new HashSet<>();
1811
        Map<Integer, List<Integer>> rbmToFofoIdsMap = new HashMap<>();
1812
        for (int rbmAuthId : rbmPositionsAuthIds) {
1813
            AuthUser au = authUserMap.get(rbmAuthId);
1814
            if (au != null && storeGuyMap.containsKey(au.getEmailId())) {
1815
                List<Integer> fofoIds = new ArrayList<>(storeGuyMap.get(au.getEmailId()));
1816
                allFofoIds.addAll(fofoIds);
1817
                rbmToFofoIdsMap.put(rbmAuthId, fofoIds);
1818
            }
1819
        }
35816 ranu 1820
        // Initialize L2 calling list map - will be populated after fetching remarks
35631 ranu 1821
        Map<Integer, List<Integer>> l2AuthIdToFofoIds = new HashMap<>();
35816 ranu 1822
        for (int l2AuthId : l2AuthIds) {
1823
            l2AuthIdToFofoIds.put(l2AuthId, new ArrayList<>());
35631 ranu 1824
        }
36210 ranu 1825
        // Initialize L3 calling list map - will be populated after fetching remarks
1826
        Map<Integer, List<Integer>> l3AuthIdToFofoIds = new HashMap<>();
1827
        for (int l3AuthId : l3AuthIds) {
1828
            l3AuthIdToFofoIds.put(l3AuthId, new ArrayList<>());
1829
        }
35631 ranu 1830
        LOGGER.info("RBM Call Target - Total fofo IDs to process: {}", allFofoIds.size());
1831
 
1832
        // Get only needed fofo stores (OPTIMIZED - was fetching ALL stores before)
1833
        start = System.currentTimeMillis();
1834
        Map<Integer, FofoStore> fofoStoresMap = new HashMap<>();
1835
        if (!allFofoIds.isEmpty()) {
1836
            try {
1837
                fofoStoresMap = fofoStoreRepository.selectByRetailerIds(new ArrayList<>(allFofoIds)).stream()
1838
                        .collect(Collectors.toMap(FofoStore::getId, x -> x, (a, b) -> a));
1839
            } catch (ProfitMandiBusinessException e) {
1840
                LOGGER.error("Error fetching fofo stores", e);
1841
            }
1842
        }
1843
        LOGGER.info("RBM Call Target - FofoStores fetch (only needed): {}ms, count: {}", System.currentTimeMillis() - start, fofoStoresMap.size());
1844
 
1845
        // Batch fetch max remark ids for all fofoIds (for escalation filtering)
1846
        start = System.currentTimeMillis();
1847
        Map<Integer, PartnerCollectionRemark> allPartnerCollectionRemarks = new HashMap<>();
1848
        if (!allFofoIds.isEmpty()) {
1849
            List<Integer> allRemarkIds = partnerCollectionRemarkRepository.selectMaxRemarkId(new ArrayList<>(allFofoIds));
1850
            if (!allRemarkIds.isEmpty()) {
1851
                allPartnerCollectionRemarks = partnerCollectionRemarkRepository.selectByIds(allRemarkIds).stream()
1852
                        .collect(Collectors.toMap(PartnerCollectionRemark::getFofoId, x -> x, (a, b) -> a));
1853
            }
1854
        }
1855
        LOGGER.info("RBM Call Target - PartnerCollectionRemarks fetch: {}ms", System.currentTimeMillis() - start);
1856
 
35816 ranu 1857
        // Populate L2 calling list based on partners whose latest remark is RBM_L2_ESCALATION
1858
        // Find the L1 who has the partner and add to that L1's manager (L2) calling list
1859
        for (Map.Entry<Integer, PartnerCollectionRemark> entry : allPartnerCollectionRemarks.entrySet()) {
1860
            Integer fofoId = entry.getKey();
1861
            PartnerCollectionRemark remark = entry.getValue();
1862
 
1863
            if (CollectionRemark.RBM_L2_ESCALATION.equals(remark.getRemark())) {
1864
                // Find which L1 RBM has this partner assigned
1865
                for (int l1AuthId : l1AuthIds) {
1866
                    List<Integer> l1FofoIds = rbmToFofoIdsMap.getOrDefault(l1AuthId, Collections.emptyList());
1867
                    if (l1FofoIds.contains(fofoId)) {
1868
                        // Get L1's manager (L2)
1869
                        AuthUser l1User = authUserMap.get(l1AuthId);
1870
                        if (l1User != null && l2AuthIdToFofoIds.containsKey(l1User.getManagerId())) {
1871
                            int l2ManagerId = l1User.getManagerId();
1872
                            l2AuthIdToFofoIds.get(l2ManagerId).add(fofoId);
1873
                        }
1874
                        break; // Found the L1 for this fofoId
1875
                    }
1876
                }
1877
            }
1878
        }
1879
        LOGGER.info("RBM Call Target - L2 calling lists populated from RBM_L2_ESCALATION remarks");
1880
 
36210 ranu 1881
        // Populate L3 calling list based on partners whose latest remark is RBM_L3_ESCALATION
36212 ranu 1882
        // Find the L1 who originally has the partner, then:
1883
        // Case 1: L1 -> L3 directly (if L1's manager IS L3)
1884
        // Case 2: L1 -> L2 -> L3 (if L1's manager is L2, then L2's manager is L3)
36210 ranu 1885
        for (Map.Entry<Integer, PartnerCollectionRemark> entry : allPartnerCollectionRemarks.entrySet()) {
1886
            Integer fofoId = entry.getKey();
1887
            PartnerCollectionRemark remark = entry.getValue();
1888
 
1889
            if (CollectionRemark.RBM_L3_ESCALATION.equals(remark.getRemark())) {
1890
                // Find which L1 RBM originally has this partner assigned
1891
                for (int l1AuthId : l1AuthIds) {
1892
                    List<Integer> l1FofoIds = rbmToFofoIdsMap.getOrDefault(l1AuthId, Collections.emptyList());
1893
                    if (l1FofoIds.contains(fofoId)) {
1894
                        AuthUser l1User = authUserMap.get(l1AuthId);
1895
                        if (l1User != null) {
36212 ranu 1896
                            int l1ManagerId = l1User.getManagerId();
1897
                            // Case 1: L1's manager IS L3 directly (L1 → L3, no L2 in between)
1898
                            if (l3AuthIdToFofoIds.containsKey(l1ManagerId)) {
1899
                                l3AuthIdToFofoIds.get(l1ManagerId).add(fofoId);
1900
                                LOGGER.debug("L3 Calling List (direct): fofoId={} -> L1={} -> L3={}",
1901
                                        fofoId, l1AuthId, l1ManagerId);
1902
                            } else {
1903
                                // Case 2: L1 -> L2 -> L3
1904
                                AuthUser l2User = authUserMap.get(l1ManagerId);
1905
                                if (l2User != null && l3AuthIdToFofoIds.containsKey(l2User.getManagerId())) {
1906
                                    int l3ManagerId = l2User.getManagerId();
1907
                                    l3AuthIdToFofoIds.get(l3ManagerId).add(fofoId);
1908
                                    LOGGER.debug("L3 Calling List: fofoId={} -> L1={} -> L2={} -> L3={}",
1909
                                            fofoId, l1AuthId, l1ManagerId, l3ManagerId);
1910
                                }
36210 ranu 1911
                            }
1912
                        }
1913
                        break; // Found the L1 for this fofoId
1914
                    }
1915
                }
1916
            }
1917
        }
1918
        LOGGER.info("RBM Call Target - L3 calling lists populated from RBM_L3_ESCALATION remarks");
1919
 
35631 ranu 1920
        // Batch fetch collection RANK map for all fofoIds (OPTIMIZED - only fetches rank, not full model)
1921
        start = System.currentTimeMillis();
1922
        Map<Integer, Integer> allCollectionRankMap = new HashMap<>();
1923
        if (!allFofoIds.isEmpty()) {
1924
            try {
1925
                allCollectionRankMap = partnerCollectionService.getCollectionRankMap(new ArrayList<>(allFofoIds), startDate);
1926
            } catch (ProfitMandiBusinessException e) {
1927
                LOGGER.error("Error fetching collection rank map for all fofoIds", e);
1928
            }
1929
        }
1930
        LOGGER.info("RBM Call Target - CollectionRankMap fetch (OPTIMIZED): {}ms", System.currentTimeMillis() - start);
1931
 
35669 ranu 1932
        // Get MTD billing data for zero billing calculation and partner counts
35631 ranu 1933
        start = System.currentTimeMillis();
1934
        List<RbmWeeklyBillingModel> mtdBillingData = getWeeklyBillingDataForMonth(firstOfMonth, endOfMonth);
1935
        Set<Integer> allMtdBilledFofoIds = mtdBillingData.stream()
1936
                .filter(RbmWeeklyBillingModel::isMtdBilled)
1937
                .map(RbmWeeklyBillingModel::getFofoId)
1938
                .collect(Collectors.toSet());
35669 ranu 1939
        // Build partner count and fofoIds per RBM from mtdBillingData (same source as Today ARR page)
1940
        Map<Integer, Set<Integer>> mtdFofoIdsByAuthId = mtdBillingData.stream()
1941
                .filter(RbmWeeklyBillingModel::isTargetedPartner)
1942
                .collect(Collectors.groupingBy(RbmWeeklyBillingModel::getAuthId,
1943
                        Collectors.mapping(RbmWeeklyBillingModel::getFofoId, Collectors.toSet())));
35631 ranu 1944
        LOGGER.info("RBM Call Target - MTD Billing fetch: {}ms", System.currentTimeMillis() - start);
1945
 
1946
        // Batch fetch today's remarks for all auth IDs (to calculate Value Achieved)
1947
        start = System.currentTimeMillis();
1948
        Map<Integer, List<PartnerCollectionRemark>> remarksByAuthId = partnerCollectionRemarkRepository
1949
                .selectAllByAuthIdsOnDate(rbmPositionsAuthIds, LocalDate.now()).stream()
1950
                .collect(Collectors.groupingBy(PartnerCollectionRemark::getAuthId));
1951
        LOGGER.info("RBM Call Target - Today Remarks fetch: {}ms", System.currentTimeMillis() - start);
1952
 
1953
        // Batch fetch today's out-of-sequence logs for all RBMs
1954
        start = System.currentTimeMillis();
1955
        LocalDateTime todayStart = LocalDate.now().atStartOfDay();
1956
        LocalDateTime todayEnd = LocalDate.now().plusDays(1).atStartOfDay();
1957
        List<RbmCallSequenceLog> outOfSequenceLogs = rbmCallSequenceLogRepository.selectOutOfSequenceByDateRange(todayStart, todayEnd);
1958
        Map<Integer, Long> outOfSequenceCountByAuthId = outOfSequenceLogs.stream()
35654 ranu 1959
                .collect(Collectors.groupingBy(RbmCallSequenceLog::getAuthId,
1960
                        Collectors.mapping(RbmCallSequenceLog::getFofoId, Collectors.collectingAndThen(Collectors.toSet(), s -> (long) s.size()))));
35631 ranu 1961
        LOGGER.info("RBM Call Target - Out of Sequence fetch: {}ms", System.currentTimeMillis() - start);
1962
 
36284 ranu 1963
        // BATCH FETCH: All call logs for all RBMs (L1 + L2 + L3) in a single query.
1964
        // Replaces the previous N+1 pattern where getCallStats() was called per RBM.
1965
        start = System.currentTimeMillis();
1966
        List<AgentCallLog> allCallLogs = agentCallLogRepository.findByAuthIdsAndDate(rbmPositionsAuthIds, queryDate);
1967
        Map<Long, List<AgentCallLog>> callLogsByAuthId = allCallLogs.stream()
1968
                .collect(Collectors.groupingBy(AgentCallLog::getAuthId));
1969
        LOGGER.info("RBM Call Target - Call logs batch fetch: {}ms ({} logs across {} RBMs)",
1970
                System.currentTimeMillis() - start, allCallLogs.size(), callLogsByAuthId.size());
1971
 
1972
        // BATCH FETCH: Build a single mobile -> fofoId map from all unique customer numbers across all call logs.
1973
        // Replaces the previous N+1 pattern where findFofoIdByMobile() was called per call log entry.
1974
        start = System.currentTimeMillis();
1975
        Set<String> allNormalizedMobiles = new HashSet<>();
1976
        for (AgentCallLog callLog : allCallLogs) {
1977
            String customerNumber = callLog.getCustomerNumber();
1978
            if (customerNumber != null) {
1979
                String normalized = customerNumber.startsWith("+91") ? customerNumber.substring(3) : customerNumber;
1980
                allNormalizedMobiles.add(normalized);
1981
            }
1982
        }
1983
        Map<String, Integer> mobileToFofoIdMap = buildMobileToFofoIdMap(allNormalizedMobiles);
1984
        LOGGER.info("RBM Call Target - Mobile→FofoId batch fetch: {}ms ({} unique mobiles, {} mapped)",
1985
                System.currentTimeMillis() - start, allNormalizedMobiles.size(), mobileToFofoIdMap.size());
1986
 
36225 ranu 1987
        // Identify users who are both L1 and L2 — they will be shown only as L2
1988
        Set<Integer> l2AuthIdSet = new HashSet<>(l2AuthIds);
1989
 
1990
        // Process L1 RBMs (skip users who are also L2 — their data will be merged into L2 model)
35631 ranu 1991
        for (int rbmAuthId : l1AuthIds) {
36225 ranu 1992
            if (l2AuthIdSet.contains(rbmAuthId)) {
1993
                continue; // Will be handled in L2 processing with merged L1 data
1994
            }
35631 ranu 1995
            AuthUser authUser = authUserMap.get(rbmAuthId);
1996
            if (authUser == null || !storeGuyMap.containsKey(authUser.getEmailId())) {
1997
                continue;
1998
            }
1999
 
2000
            List<Integer> fofoIdList = rbmToFofoIdsMap.getOrDefault(rbmAuthId, Collections.emptyList());
2001
 
2002
            // Check if RBM is L1 (same logic as getSummaryModel)
2003
            List<Position> positions = positionsByAuthId.getOrDefault(authUser.getId(), Collections.emptyList());
2004
            boolean isRBMAndL1 = positions.stream()
2005
                    .anyMatch(position ->
2006
                            ProfitMandiConstants.TICKET_CATEGORY_RBM == position.getCategoryId()
2007
                                    && EscalationType.L1.equals(position.getEscalationType()));
2008
 
2009
            // Filter escalated partners for L1 RBMs (same logic as getSummaryModel)
2010
            List<Integer> fofoIds = fofoIdList;
2011
            if (isRBMAndL1) {
2012
                Map<Integer, PartnerCollectionRemark> partnerCollectionRemarks = new HashMap<>();
2013
                for (Integer fofoId : fofoIdList) {
2014
                    if (allPartnerCollectionRemarks.containsKey(fofoId)) {
2015
                        partnerCollectionRemarks.put(fofoId, allPartnerCollectionRemarks.get(fofoId));
2016
                    }
2017
                }
2018
                fofoIds = partnerCollectionRemarks.entrySet().stream()
2019
                        .filter(entry -> {
2020
                            PartnerCollectionRemark pcrMap = entry.getValue();
2021
                            return !(CollectionRemark.RBM_L2_ESCALATION.equals(pcrMap.getRemark())
2022
                                    || CollectionRemark.SALES_ESCALATION.equals(pcrMap.getRemark()));
2023
                        })
2024
                        .map(Map.Entry::getKey)
2025
                        .collect(Collectors.toList());
2026
            }
2027
 
36181 ranu 2028
            // Filter to only external, ACTIVE or REVIVAL stores (collection plan not required)
35631 ranu 2029
            Map<Integer, Integer> finalAllCollectionRankMap = allCollectionRankMap;
2030
            Map<Integer, FofoStore> finalFofoStoresMap = fofoStoresMap;
2031
            List<Integer> validFofoIds = fofoIds.stream()
2032
                    .filter(fofoId -> {
2033
                        FofoStore store = finalFofoStoresMap.get(fofoId);
2034
                        if (store == null || store.isInternal()) {
2035
                            return false;
2036
                        }
36181 ranu 2037
                        // Only include ACTIVE or REVIVAL partners (not Low Sale, not Disputed, not Billing Pending)
2038
                        return ActivationType.ACTIVE.equals(store.getActivationType())
2039
                                || ActivationType.REVIVAL.equals(store.getActivationType());
35631 ranu 2040
                    })
2041
                    .collect(Collectors.toList());
2042
 
2043
            if (validFofoIds.isEmpty()) {
2044
                continue;
2045
            }
2046
 
2047
            RbmCallTargetModel targetModel = new RbmCallTargetModel();
2048
            targetModel.setAuthId(rbmAuthId);
2049
            targetModel.setRbmName(authUser.getFullName());
35669 ranu 2050
            // Use partner count from mtdBillingData (same source as Today ARR page)
2051
            Set<Integer> mtdFofoIds = mtdFofoIdsByAuthId.getOrDefault(rbmAuthId, Collections.emptySet());
2052
            targetModel.setPartnerCount(mtdFofoIds.size());
35631 ranu 2053
 
2054
            // Categorize each partner - each partner belongs to ONE category only
35665 ranu 2055
            // Priority: PlanToday > CarryForward > ZeroBilling > Untouched > FuturePlan > Normal
36181 ranu 2056
            // Revival is counted separately (just for display, doesn't affect categorization)
35631 ranu 2057
            Set<Integer> planTodayPartners = new HashSet<>();
2058
            Set<Integer> carryForwardPartners = new HashSet<>();
2059
            Set<Integer> untouchedPartners = new HashSet<>();
2060
            Set<Integer> zeroBillingPartners = new HashSet<>();
2061
            Set<Integer> futurePlanPartners = new HashSet<>();
2062
            Set<Integer> normalPartners = new HashSet<>();
36181 ranu 2063
            Set<Integer> revivalPartners = new HashSet<>();
35631 ranu 2064
 
2065
            for (Integer fofoId : validFofoIds) {
2066
                // Get collection plan rank (from optimized rank map)
2067
                int rank = allCollectionRankMap.getOrDefault(fofoId, 5); // default to Normal if no plan
2068
 
2069
                // Check if partner has zero billing in MTD
2070
                boolean hasZeroBilling = !allMtdBilledFofoIds.contains(fofoId);
2071
 
36181 ranu 2072
                // Count REVIVAL partners separately (just for display, doesn't affect categorization)
2073
                FofoStore store = finalFofoStoresMap.get(fofoId);
2074
                if (store != null && ActivationType.REVIVAL.equals(store.getActivationType())) {
2075
                    revivalPartners.add(fofoId);
2076
                }
2077
 
35631 ranu 2078
                // Assign to category based on priority
2079
                if (rank == 1) {
2080
                    planTodayPartners.add(fofoId);
2081
                } else if (rank == 2) {
2082
                    carryForwardPartners.add(fofoId);
35665 ranu 2083
                } else if (hasZeroBilling) {
2084
                    zeroBillingPartners.add(fofoId);
35631 ranu 2085
                } else if (rank == 3) {
2086
                    untouchedPartners.add(fofoId);
2087
                } else if (rank == 4) {
2088
                    futurePlanPartners.add(fofoId);
2089
                } else {
2090
                    normalPartners.add(fofoId);
2091
                }
2092
            }
2093
 
2094
            // Set counts
2095
            targetModel.setCreditCollection(0); // Credit collection is handled in separate list
2096
            targetModel.setPlanToday(planTodayPartners.size());
2097
            targetModel.setCarryForward(carryForwardPartners.size());
2098
            targetModel.setUntouched(untouchedPartners.size());
2099
            targetModel.setZeroBilling(zeroBillingPartners.size());
2100
            targetModel.setFuturePlan(futurePlanPartners.size());
2101
            targetModel.setNormal(normalPartners.size());
36181 ranu 2102
            targetModel.setRevival(revivalPartners.size());
35631 ranu 2103
 
2104
            // Today Target = PlanToday + CarryForward + ZeroBilling + Untouched
2105
            // These are mutually exclusive now, so we can sum them
2106
            long todayTarget = planTodayPartners.size() +
2107
                    carryForwardPartners.size() + zeroBillingPartners.size() + untouchedPartners.size();
2108
            targetModel.setTodayTargetOfCall(todayTarget);
2109
 
2110
            // Create set of partners in Today Target categories
2111
            Set<Integer> todayTargetPartners = new HashSet<>();
2112
            todayTargetPartners.addAll(planTodayPartners);
2113
            todayTargetPartners.addAll(carryForwardPartners);
2114
            todayTargetPartners.addAll(zeroBillingPartners);
2115
            todayTargetPartners.addAll(untouchedPartners);
2116
 
36284 ranu 2117
            // Value Achieved = All distinct partners called today (from pre-fetched call logs)
37010 ranu 2118
            List<AgentCallLog> l1Logs = callLogsByAuthId.get((long) rbmAuthId);
2119
            long[] callStats = getCallStatsFromLogs(l1Logs, mobileToFofoIdMap);
36276 ranu 2120
            targetModel.setValueTargetAchieved(callStats[0]);
2121
            targetModel.setTotalRecordingCalls(callStats[1]);
2122
            targetModel.setUniqueRecordingCalls(callStats[2]);
37022 ranu 2123
            targetModel.setTriedUniqueCalls(computeUniqueTriedCount(l1Logs, mobileToFofoIdMap));
2124
            targetModel.setTgtCalls(computeUniqueTriedCountForTargets(l1Logs, mobileToFofoIdMap, todayTargetPartners));
35631 ranu 2125
 
37012 ranu 2126
            // Keep todayRemarks for movedToFuture calculation
35843 ranu 2127
            List<PartnerCollectionRemark> todayRemarks = remarksByAuthId.getOrDefault(rbmAuthId, Collections.emptyList());
2128
 
35631 ranu 2129
            // Moved to Future = Partners in Future Plan category who have a remark today
2130
            // These are partners who were contacted today but moved to a future date
2131
            Set<Integer> todayRemarkedFofoIds = todayRemarks.stream()
2132
                    .map(PartnerCollectionRemark::getFofoId)
2133
                    .collect(Collectors.toSet());
2134
            long movedToFuture = futurePlanPartners.stream()
2135
                    .filter(todayRemarkedFofoIds::contains)
2136
                    .count();
2137
            targetModel.setMovedToFuture(movedToFuture);
2138
 
2139
            // Set out of sequence count for this RBM
2140
            targetModel.setOutOfSequenceCount(outOfSequenceCountByAuthId.getOrDefault(rbmAuthId, 0L));
2141
 
2142
            rbmCallTargetModels.add(targetModel);
2143
        }
2144
 
2145
        // Process L2 RBMs (escalated ticket logic with categorization)
36225 ranu 2146
        // For users who are both L1 and L2, merge their L1 calling target into L2 model
35631 ranu 2147
        for (int l2AuthId : l2AuthIds) {
2148
            AuthUser authUser = authUserMap.get(l2AuthId);
2149
            if (authUser == null) {
2150
                continue;
2151
            }
2152
 
2153
            List<Integer> l2FofoIdList = l2AuthIdToFofoIds.getOrDefault(l2AuthId, Collections.emptyList());
2154
 
35816 ranu 2155
            // For L2, use unique fofoIds with RBM_L2_ESCALATION remark as target
35662 ranu 2156
            Set<Integer> l2TargetFofoIds = new HashSet<>(l2FofoIdList);
35631 ranu 2157
 
37022 ranu 2158
            // Collected from the L1-merge branch below when the user is also L1. Used later
2159
            // for the TGT Calls scope: L2 escalation + L1 target buckets.
2160
            Set<Integer> l1MergedTargetPartners = new HashSet<>();
2161
 
35631 ranu 2162
            RbmCallTargetModel l2Model = new RbmCallTargetModel();
2163
            l2Model.setAuthId(l2AuthId);
2164
            l2Model.setRbmName(authUser.getFullName() + " (L2)");
2165
            l2Model.setL2Position(true);
35816 ranu 2166
            l2Model.setL2CallingList(l2TargetFofoIds.size());
36228 ranu 2167
            // Partner count: if user is also L1, use L1 partner count (MTD targeted partners)
2168
            if (l1AuthIds.contains(l2AuthId)) {
2169
                Set<Integer> mtdFofoIds = mtdFofoIdsByAuthId.getOrDefault(l2AuthId, Collections.emptySet());
2170
                l2Model.setPartnerCount(mtdFofoIds.size());
2171
            } else {
2172
                List<Integer> l2AssignedFofoIds = rbmToFofoIdsMap.getOrDefault(l2AuthId, Collections.emptyList());
2173
                l2Model.setPartnerCount(l2AssignedFofoIds.size());
2174
            }
35631 ranu 2175
 
36229 ranu 2176
            // If user is also L1, calculate full L1 breakdown and merge into L2 model
36225 ranu 2177
            if (l1AuthIds.contains(l2AuthId) && storeGuyMap.containsKey(authUser.getEmailId())) {
36229 ranu 2178
                // Get only L1 RBM position partners (not L2 partners) from partner_position
36225 ranu 2179
                List<Position> positions = positionsByAuthId.getOrDefault(l2AuthId, Collections.emptyList());
36229 ranu 2180
                Set<Integer> l1RbmPositionIds = positions.stream()
2181
                        .filter(p -> ProfitMandiConstants.TICKET_CATEGORY_RBM == p.getCategoryId()
2182
                                && EscalationType.L1.equals(p.getEscalationType()))
2183
                        .map(Position::getId)
2184
                        .collect(Collectors.toSet());
2185
                // Fetch partner_position for only L1 RBM positions of this user
2186
                List<Integer> fofoIdList = partnerPositionRepository
2187
                        .selectByPositionIds(new ArrayList<>(l1RbmPositionIds)).stream()
2188
                        .map(pp -> pp.getFofoId())
2189
                        .distinct()
2190
                        .collect(Collectors.toList());
2191
                boolean isRBMAndL1 = !l1RbmPositionIds.isEmpty();
36225 ranu 2192
                List<Integer> l1FofoIds = new ArrayList<>(fofoIdList);
2193
                if (isRBMAndL1) {
2194
                    Map<Integer, PartnerCollectionRemark> partnerCollectionRemarks = new HashMap<>();
2195
                    for (Integer fofoId : fofoIdList) {
2196
                        if (allPartnerCollectionRemarks.containsKey(fofoId)) {
2197
                            partnerCollectionRemarks.put(fofoId, allPartnerCollectionRemarks.get(fofoId));
2198
                        }
2199
                    }
2200
                    l1FofoIds = partnerCollectionRemarks.entrySet().stream()
2201
                            .filter(entry -> {
2202
                                PartnerCollectionRemark pcrMap = entry.getValue();
2203
                                return !(CollectionRemark.RBM_L2_ESCALATION.equals(pcrMap.getRemark())
2204
                                        || CollectionRemark.SALES_ESCALATION.equals(pcrMap.getRemark()));
2205
                            })
2206
                            .map(Map.Entry::getKey)
2207
                            .collect(Collectors.toList());
2208
                }
2209
                Map<Integer, FofoStore> finalFofoStoresMap2 = fofoStoresMap;
2210
                List<Integer> validL1FofoIds = l1FofoIds.stream()
2211
                        .filter(fofoId -> {
2212
                            FofoStore store = finalFofoStoresMap2.get(fofoId);
2213
                            if (store == null || store.isInternal()) return false;
2214
                            return ActivationType.ACTIVE.equals(store.getActivationType())
2215
                                    || ActivationType.REVIVAL.equals(store.getActivationType());
2216
                        })
2217
                        .collect(Collectors.toList());
35631 ranu 2218
 
36229 ranu 2219
                // Categorize L1 partners — same logic as L1 processing
2220
                Set<Integer> planTodayPartners = new HashSet<>();
2221
                Set<Integer> carryForwardPartners = new HashSet<>();
2222
                Set<Integer> untouchedPartners = new HashSet<>();
2223
                Set<Integer> zeroBillingPartners = new HashSet<>();
2224
                Set<Integer> futurePlanPartners = new HashSet<>();
2225
                Set<Integer> normalPartners = new HashSet<>();
2226
                Set<Integer> revivalPartners = new HashSet<>();
2227
 
36225 ranu 2228
                for (Integer fofoId : validL1FofoIds) {
2229
                    int rank = allCollectionRankMap.getOrDefault(fofoId, 5);
2230
                    boolean hasZeroBilling = !allMtdBilledFofoIds.contains(fofoId);
36229 ranu 2231
                    FofoStore store = finalFofoStoresMap2.get(fofoId);
2232
                    if (store != null && ActivationType.REVIVAL.equals(store.getActivationType())) {
2233
                        revivalPartners.add(fofoId);
36225 ranu 2234
                    }
36229 ranu 2235
                    if (rank == 1) {
2236
                        planTodayPartners.add(fofoId);
2237
                    } else if (rank == 2) {
2238
                        carryForwardPartners.add(fofoId);
2239
                    } else if (hasZeroBilling) {
2240
                        zeroBillingPartners.add(fofoId);
2241
                    } else if (rank == 3) {
2242
                        untouchedPartners.add(fofoId);
2243
                    } else if (rank == 4) {
2244
                        futurePlanPartners.add(fofoId);
2245
                    } else {
2246
                        normalPartners.add(fofoId);
2247
                    }
36225 ranu 2248
                }
36229 ranu 2249
 
2250
                // Set L1 breakdown fields on L2 model
2251
                l2Model.setPlanToday(planTodayPartners.size());
2252
                l2Model.setCarryForward(carryForwardPartners.size());
2253
                l2Model.setZeroBilling(zeroBillingPartners.size());
2254
                l2Model.setUntouched(untouchedPartners.size());
2255
                l2Model.setFuturePlan(futurePlanPartners.size());
2256
                l2Model.setNormal(normalPartners.size());
2257
                l2Model.setRevival(revivalPartners.size());
2258
 
2259
                long l1OwnTarget = planTodayPartners.size() + carryForwardPartners.size()
2260
                        + zeroBillingPartners.size() + untouchedPartners.size();
2261
 
37022 ranu 2262
                // Feed the L1 target sets up to the outer scope so TGT Calls can include them.
2263
                l1MergedTargetPartners.addAll(planTodayPartners);
2264
                l1MergedTargetPartners.addAll(carryForwardPartners);
2265
                l1MergedTargetPartners.addAll(zeroBillingPartners);
2266
                l1MergedTargetPartners.addAll(untouchedPartners);
2267
 
36229 ranu 2268
                // Today Target = own L1 target + L2 escalation
2269
                l2Model.setTodayTargetOfCall(l2TargetFofoIds.size() + l1OwnTarget);
2270
 
2271
                // Moved to Future from L1 partners
2272
                List<PartnerCollectionRemark> todayRemarks = remarksByAuthId.getOrDefault(l2AuthId, Collections.emptyList());
2273
                Set<Integer> todayRemarkedFofoIds = todayRemarks.stream()
2274
                        .map(PartnerCollectionRemark::getFofoId)
2275
                        .collect(Collectors.toSet());
2276
                long movedToFuture = futurePlanPartners.stream()
2277
                        .filter(todayRemarkedFofoIds::contains)
2278
                        .count();
2279
                l2Model.setMovedToFuture(movedToFuture);
2280
            } else {
2281
                // Pure L2 (not also L1) — target is only L2 escalation
2282
                l2Model.setTodayTargetOfCall(l2TargetFofoIds.size());
36225 ranu 2283
            }
2284
 
36284 ranu 2285
            // Value Achieved = All distinct partners called today (from pre-fetched call logs)
37010 ranu 2286
            List<AgentCallLog> l2Logs = callLogsByAuthId.get((long) l2AuthId);
2287
            long[] l2CallStats = getCallStatsFromLogs(l2Logs, mobileToFofoIdMap);
36276 ranu 2288
            l2Model.setValueTargetAchieved(l2CallStats[0]);
2289
            l2Model.setTotalRecordingCalls(l2CallStats[1]);
2290
            l2Model.setUniqueRecordingCalls(l2CallStats[2]);
37022 ranu 2291
            l2Model.setTriedUniqueCalls(computeUniqueTriedCount(l2Logs, mobileToFofoIdMap));
35631 ranu 2292
 
37022 ranu 2293
            // TGT Calls scope: L2 escalation + (if user is also L1) the L1 target buckets.
2294
            Set<Integer> l2TgtSet = new HashSet<>(l2TargetFofoIds);
2295
            l2TgtSet.addAll(l1MergedTargetPartners);
2296
            l2Model.setTgtCalls(computeUniqueTriedCountForTargets(l2Logs, mobileToFofoIdMap, l2TgtSet));
2297
 
35631 ranu 2298
            l2Model.setOutOfSequenceCount(outOfSequenceCountByAuthId.getOrDefault(l2AuthId, 0L));
2299
            rbmCallTargetModels.add(l2Model);
2300
        }
2301
 
36210 ranu 2302
        // Process L3 RBMs (escalated ticket logic with categorization)
2303
        for (int l3AuthId : l3AuthIds) {
2304
            AuthUser authUser = authUserMap.get(l3AuthId);
2305
            if (authUser == null) {
2306
                continue;
2307
            }
2308
 
2309
            List<Integer> l3FofoIdList = l3AuthIdToFofoIds.getOrDefault(l3AuthId, Collections.emptyList());
2310
 
2311
            // For L3, use unique fofoIds with RBM_L3_ESCALATION remark as target
2312
            Set<Integer> l3TargetFofoIds = new HashSet<>(l3FofoIdList);
2313
 
2314
            RbmCallTargetModel l3Model = new RbmCallTargetModel();
2315
            l3Model.setAuthId(l3AuthId);
2316
            l3Model.setRbmName(authUser.getFullName() + " (L3)");
2317
            l3Model.setL3Position(true);
2318
            l3Model.setL3CallingList(l3TargetFofoIds.size());
36359 ranu 2319
            // Partner count = total assigned partners (excluding internal)
2320
            Map<Integer, FofoStore> finalFofoStoresMapL3 = fofoStoresMap;
36210 ranu 2321
            List<Integer> l3AssignedFofoIds = rbmToFofoIdsMap.getOrDefault(l3AuthId, Collections.emptyList());
36359 ranu 2322
            long l3ExternalPartnerCount = l3AssignedFofoIds.stream()
2323
                    .filter(fofoId -> {
2324
                        FofoStore store = finalFofoStoresMapL3.get(fofoId);
2325
                        return store != null && !store.isInternal();
2326
                    })
2327
                    .count();
2328
            l3Model.setPartnerCount(l3ExternalPartnerCount);
36210 ranu 2329
 
2330
            // L3 Target = partners with RBM_L3_ESCALATION as latest remark
2331
            l3Model.setTodayTargetOfCall(l3TargetFofoIds.size());
2332
 
36284 ranu 2333
            // Value Achieved = All distinct partners called today (from pre-fetched call logs)
37010 ranu 2334
            List<AgentCallLog> l3Logs = callLogsByAuthId.get((long) l3AuthId);
2335
            long[] l3CallStats = getCallStatsFromLogs(l3Logs, mobileToFofoIdMap);
36276 ranu 2336
            l3Model.setValueTargetAchieved(l3CallStats[0]);
2337
            l3Model.setTotalRecordingCalls(l3CallStats[1]);
2338
            l3Model.setUniqueRecordingCalls(l3CallStats[2]);
37022 ranu 2339
            l3Model.setTriedUniqueCalls(computeUniqueTriedCount(l3Logs, mobileToFofoIdMap));
2340
            // L3 TGT Calls scope: L3 escalation partners.
2341
            l3Model.setTgtCalls(computeUniqueTriedCountForTargets(l3Logs, mobileToFofoIdMap, l3TargetFofoIds));
36210 ranu 2342
 
2343
            l3Model.setOutOfSequenceCount(outOfSequenceCountByAuthId.getOrDefault(l3AuthId, 0L));
2344
            rbmCallTargetModels.add(l3Model);
2345
        }
2346
 
36225 ranu 2347
        // Group models by escalation level
36210 ranu 2348
        Map<Integer, RbmCallTargetModel> l3ModelsByAuthId = new HashMap<>();
35631 ranu 2349
        Map<Integer, RbmCallTargetModel> l2ModelsByAuthId = new HashMap<>();
2350
        Map<Integer, RbmCallTargetModel> l1ModelsByAuthId = new HashMap<>();
2351
        for (RbmCallTargetModel m : rbmCallTargetModels) {
36210 ranu 2352
            if (m.isL3Position()) {
2353
                l3ModelsByAuthId.put(m.getAuthId(), m);
2354
            } else if (m.isL2Position()) {
35631 ranu 2355
                l2ModelsByAuthId.put(m.getAuthId(), m);
2356
            } else {
2357
                l1ModelsByAuthId.put(m.getAuthId(), m);
2358
            }
2359
        }
2360
 
36225 ranu 2361
        // Build partner-based hierarchy using partner_position table
2362
        // Map positionId -> Position for quick lookup
2363
        Map<Integer, Position> positionByIdMap = allRbmPositions.stream()
2364
                .collect(Collectors.toMap(Position::getId, p -> p, (a, b) -> a));
2365
 
2366
        // Get all RBM position IDs and fetch their partner assignments
2367
        List<Integer> allRbmPositionIds = allRbmPositions.stream()
2368
                .map(Position::getId).collect(Collectors.toList());
2369
        List<com.spice.profitmandi.dao.entity.cs.PartnerPosition> allPartnerPositions =
2370
                partnerPositionRepository.selectByPositionIds(allRbmPositionIds);
2371
 
2372
        // Build partnerId -> map of escalationType -> Set<authUserIds>
2373
        // This tells us for each partner, who is their L1, L2, L3
2374
        Map<Integer, Map<EscalationType, Set<Integer>>> partnerToAuthByLevel = new HashMap<>();
2375
        for (com.spice.profitmandi.dao.entity.cs.PartnerPosition pp : allPartnerPositions) {
2376
            Position pos = positionByIdMap.get(pp.getPositionId());
2377
            if (pos != null && pos.getEscalationType() != null) {
2378
                partnerToAuthByLevel
2379
                        .computeIfAbsent(pp.getFofoId(), k -> new HashMap<>())
2380
                        .computeIfAbsent(pos.getEscalationType(), k -> new HashSet<>())
2381
                        .add(pos.getAuthUserId());
2382
            }
2383
        }
2384
 
2385
        // Build L2 -> L1 team map based on shared partners
2386
        // If an L1 and L2 share partners (L1 at L1 level, L2 at L2 level), that L1 belongs under that L2
2387
        Map<Integer, List<RbmCallTargetModel>> l2TeamMap = new LinkedHashMap<>();
2388
        for (RbmCallTargetModel l2Model : l2ModelsByAuthId.values()) {
2389
            l2TeamMap.put(l2Model.getAuthId(), new ArrayList<>());
2390
        }
2391
 
2392
        // Build L3 -> L2 team map based on shared partners
36210 ranu 2393
        Map<Integer, List<RbmCallTargetModel>> l3TeamMap = new LinkedHashMap<>();
2394
        for (RbmCallTargetModel l3Model : l3ModelsByAuthId.values()) {
2395
            l3TeamMap.put(l3Model.getAuthId(), new ArrayList<>());
2396
        }
2397
 
36225 ranu 2398
        // For each L1, find which L2 shares the most partners -> that's their L2
2399
        Set<Integer> addedL1AuthIds = new HashSet<>();
2400
        for (RbmCallTargetModel l1Model : l1ModelsByAuthId.values()) {
2401
            Map<Integer, Integer> l2SharedCount = new HashMap<>(); // l2AuthId -> shared partner count
2402
            for (Map.Entry<Integer, Map<EscalationType, Set<Integer>>> entry : partnerToAuthByLevel.entrySet()) {
2403
                Map<EscalationType, Set<Integer>> levelMap = entry.getValue();
2404
                Set<Integer> l1IdsForPartner = levelMap.getOrDefault(EscalationType.L1, Collections.emptySet());
2405
                Set<Integer> l2AuthIdsForPartner = levelMap.getOrDefault(EscalationType.L2, Collections.emptySet());
2406
                if (l1IdsForPartner.contains(l1Model.getAuthId())) {
2407
                    for (int l2Id : l2AuthIdsForPartner) {
2408
                        if (l2ModelsByAuthId.containsKey(l2Id) && l2Id != l1Model.getAuthId()) {
2409
                            l2SharedCount.merge(l2Id, 1, Integer::sum);
2410
                        }
2411
                    }
2412
                }
2413
            }
2414
            // Assign L1 to the L2 with most shared partners
2415
            if (!l2SharedCount.isEmpty()) {
2416
                int bestL2 = l2SharedCount.entrySet().stream()
2417
                        .max(Map.Entry.comparingByValue()).get().getKey();
2418
                l2TeamMap.get(bestL2).add(l1Model);
2419
                addedL1AuthIds.add(l1Model.getAuthId());
2420
            }
2421
        }
2422
 
2423
        // For each L2, find which L3 shares the most partners -> that's their L3
36210 ranu 2424
        Set<Integer> addedL2AuthIds = new HashSet<>();
2425
        for (RbmCallTargetModel l2Model : l2ModelsByAuthId.values()) {
36225 ranu 2426
            Map<Integer, Integer> l3SharedCount = new HashMap<>();
2427
            for (Map.Entry<Integer, Map<EscalationType, Set<Integer>>> entry : partnerToAuthByLevel.entrySet()) {
2428
                Map<EscalationType, Set<Integer>> levelMap = entry.getValue();
2429
                Set<Integer> l2AuthIdsForPartner = levelMap.getOrDefault(EscalationType.L2, Collections.emptySet());
2430
                Set<Integer> l3AuthIdsForPartner = levelMap.getOrDefault(EscalationType.L3, Collections.emptySet());
2431
                if (l2AuthIdsForPartner.contains(l2Model.getAuthId())) {
2432
                    for (int l3Id : l3AuthIdsForPartner) {
2433
                        if (l3ModelsByAuthId.containsKey(l3Id)) {
2434
                            l3SharedCount.merge(l3Id, 1, Integer::sum);
2435
                        }
2436
                    }
2437
                }
2438
            }
2439
            if (!l3SharedCount.isEmpty()) {
2440
                int bestL3 = l3SharedCount.entrySet().stream()
2441
                        .max(Map.Entry.comparingByValue()).get().getKey();
2442
                l3TeamMap.get(bestL3).add(l2Model);
36210 ranu 2443
                addedL2AuthIds.add(l2Model.getAuthId());
2444
            }
2445
        }
2446
 
36225 ranu 2447
        // For L1s not mapped to any L2, check if they map directly to an L3 via shared partners
2448
        Map<Integer, List<RbmCallTargetModel>> l3DirectL1Map = new LinkedHashMap<>();
2449
        for (RbmCallTargetModel l3Model : l3ModelsByAuthId.values()) {
2450
            l3DirectL1Map.put(l3Model.getAuthId(), new ArrayList<>());
35631 ranu 2451
        }
2452
        for (RbmCallTargetModel l1Model : l1ModelsByAuthId.values()) {
36225 ranu 2453
            if (addedL1AuthIds.contains(l1Model.getAuthId())) continue;
2454
            Map<Integer, Integer> l3SharedCount = new HashMap<>();
2455
            for (Map.Entry<Integer, Map<EscalationType, Set<Integer>>> entry : partnerToAuthByLevel.entrySet()) {
2456
                Map<EscalationType, Set<Integer>> levelMap = entry.getValue();
2457
                Set<Integer> l1IdsForPartner = levelMap.getOrDefault(EscalationType.L1, Collections.emptySet());
2458
                Set<Integer> l3AuthIdsForPartner = levelMap.getOrDefault(EscalationType.L3, Collections.emptySet());
2459
                if (l1IdsForPartner.contains(l1Model.getAuthId())) {
2460
                    for (int l3Id : l3AuthIdsForPartner) {
2461
                        if (l3ModelsByAuthId.containsKey(l3Id)) {
2462
                            l3SharedCount.merge(l3Id, 1, Integer::sum);
2463
                        }
2464
                    }
2465
                }
2466
            }
2467
            if (!l3SharedCount.isEmpty()) {
2468
                int bestL3 = l3SharedCount.entrySet().stream()
2469
                        .max(Map.Entry.comparingByValue()).get().getKey();
2470
                l3DirectL1Map.get(bestL3).add(l1Model);
35631 ranu 2471
                addedL1AuthIds.add(l1Model.getAuthId());
2472
            }
2473
        }
2474
 
36225 ranu 2475
        // Build sorted result: L3 -> L2 -> L1 (with direct L1s under L3 if no L2)
35631 ranu 2476
        List<RbmCallTargetModel> sortedModels = new ArrayList<>();
2477
 
36210 ranu 2478
        List<RbmCallTargetModel> l3Sorted = new ArrayList<>(l3ModelsByAuthId.values());
2479
        l3Sorted.sort(Comparator.comparing(RbmCallTargetModel::getRbmName));
35631 ranu 2480
 
36210 ranu 2481
        for (RbmCallTargetModel l3Model : l3Sorted) {
2482
            sortedModels.add(l3Model);
2483
            List<RbmCallTargetModel> l2Team = l3TeamMap.getOrDefault(l3Model.getAuthId(), Collections.emptyList());
2484
            l2Team.sort(Comparator.comparing(RbmCallTargetModel::getRbmName));
2485
            for (RbmCallTargetModel l2Model : l2Team) {
2486
                sortedModels.add(l2Model);
2487
                List<RbmCallTargetModel> l1Team = l2TeamMap.getOrDefault(l2Model.getAuthId(), Collections.emptyList());
2488
                l1Team.sort(Comparator.comparing(RbmCallTargetModel::getRbmName));
2489
                sortedModels.addAll(l1Team);
2490
            }
36225 ranu 2491
            // Add L1s that report directly to this L3 (no L2 in between)
2492
            List<RbmCallTargetModel> directL1Team = l3DirectL1Map.getOrDefault(l3Model.getAuthId(), Collections.emptyList());
2493
            directL1Team.sort(Comparator.comparing(RbmCallTargetModel::getRbmName));
2494
            sortedModels.addAll(directL1Team);
36210 ranu 2495
        }
2496
 
36225 ranu 2497
        // Add L2s not mapped to any L3
36210 ranu 2498
        List<RbmCallTargetModel> unmappedL2 = new ArrayList<>();
2499
        for (RbmCallTargetModel l2Model : l2ModelsByAuthId.values()) {
2500
            if (!addedL2AuthIds.contains(l2Model.getAuthId())) {
2501
                unmappedL2.add(l2Model);
2502
            }
2503
        }
2504
        unmappedL2.sort(Comparator.comparing(RbmCallTargetModel::getRbmName));
2505
        for (RbmCallTargetModel l2Model : unmappedL2) {
35631 ranu 2506
            sortedModels.add(l2Model);
2507
            List<RbmCallTargetModel> team = l2TeamMap.getOrDefault(l2Model.getAuthId(), Collections.emptyList());
2508
            team.sort(Comparator.comparing(RbmCallTargetModel::getRbmName));
2509
            sortedModels.addAll(team);
2510
        }
2511
 
36225 ranu 2512
        // Add any L1s not mapped to any L2 or L3
35631 ranu 2513
        List<RbmCallTargetModel> unmappedL1 = new ArrayList<>();
2514
        for (RbmCallTargetModel m : l1ModelsByAuthId.values()) {
2515
            if (!addedL1AuthIds.contains(m.getAuthId())) {
2516
                unmappedL1.add(m);
2517
            }
2518
        }
2519
        unmappedL1.sort(Comparator.comparing(RbmCallTargetModel::getRbmName));
2520
        sortedModels.addAll(unmappedL1);
2521
 
2522
        LOGGER.info("RBM Call Target - TOTAL TIME: {}ms, RBM count: {}", System.currentTimeMillis() - methodStart, sortedModels.size());
2523
        return sortedModels;
2524
    }
2525
 
35843 ranu 2526
    /**
37011 ranu 2527
     * Rule: count each distinct customer number this RBM attempted today, based on
37012 ranu 2528
     * the vendor-reported customer-side outcome (AgentCallLog.customerStatus):
2529
     *   - If ANY call to this number has customerStatus that is not "Missed"
2530
     *     → count as 1 (customer was reached at some point).
2531
     *   - Else (every call to this number was Missed) → count only if attempts >= 3
37011 ranu 2532
     *     (persistence credit).
37012 ranu 2533
     * null/empty customerStatus is treated as "not Missed" (favours counting when
2534
     * the vendor didn't classify the outcome).
35843 ranu 2535
     */
37022 ranu 2536
    private long computeUniqueTriedCount(List<AgentCallLog> callLogs,
2537
                                         Map<String, Integer> mobileToFofoIdMap) {
37010 ranu 2538
        if (callLogs == null || callLogs.isEmpty()) return 0L;
37022 ranu 2539
        // Group by fofoId (not raw number) — numbers that don't resolve to a partner via
2540
        // retailer_contact/address are skipped. Once a number gets added to retailer_contact
2541
        // it'll automatically resolve and start counting on the next page load.
2542
        Map<Integer, List<AgentCallLog>> byFofoId = new HashMap<>();
37010 ranu 2543
        for (AgentCallLog log : callLogs) {
2544
            String num = log.getCustomerNumber();
2545
            if (num == null) continue;
2546
            String normalized = num.startsWith("+91") ? num.substring(3) : num;
37022 ranu 2547
            Integer fofoId = mobileToFofoIdMap != null ? mobileToFofoIdMap.get(normalized) : null;
2548
            if (fofoId == null) continue; // unknown number — excluded from Tried
2549
            byFofoId.computeIfAbsent(fofoId, k -> new ArrayList<>()).add(log);
36284 ranu 2550
        }
37010 ranu 2551
        long count = 0L;
37022 ranu 2552
        for (List<AgentCallLog> logs : byFofoId.values()) {
37012 ranu 2553
            boolean hasNonMissed = false;
2554
            for (AgentCallLog l : logs) {
2555
                if (!isMissedCustomerStatus(l.getCustomerStatus())) {
2556
                    hasNonMissed = true;
37010 ranu 2557
                    break;
36284 ranu 2558
                }
2559
            }
37012 ranu 2560
            if (hasNonMissed) count++;
2561
            else if (logs.size() >= 3) count++;
36284 ranu 2562
        }
37010 ranu 2563
        return count;
36284 ranu 2564
    }
2565
 
2566
    /**
36276 ranu 2567
     * Returns call stats: [0] = called count, [1] = total recording calls, [2] = unique recording calls
2568
     */
2569
    public long[] getCallStats(long authId, LocalDate date) {
36234 ranu 2570
        List<AgentCallLog> callLogs = agentCallLogRepository.findByAuthIdAndDate(authId, date);
2571
 
35843 ranu 2572
        if (callLogs == null || callLogs.isEmpty()) {
36276 ranu 2573
            return new long[]{0, 0, 0};
35843 ranu 2574
        }
2575
 
2576
        Set<Integer> calledFofoIds = new HashSet<>();
2577
        Set<String> calledNumbersWithoutFofoId = new HashSet<>();
36276 ranu 2578
        long totalRecordingCalls = 0;
2579
        Set<String> uniqueRecordingNumbers = new HashSet<>();
35843 ranu 2580
 
2581
        for (AgentCallLog callLog : callLogs) {
2582
            String customerNumber = callLog.getCustomerNumber();
2583
            if (customerNumber != null) {
2584
                // Normalize the phone number (remove +91 prefix if present)
2585
                String normalized = customerNumber.startsWith("+91") ? customerNumber.substring(3) : customerNumber;
2586
 
2587
                // Find fofoId from retailer_contact or address
2588
                Integer fofoId = findFofoIdByMobile(normalized);
2589
                if (fofoId != null) {
2590
                    calledFofoIds.add(fofoId);
2591
                } else {
2592
                    // Number not found in retailer_contact or address, count by distinct number
2593
                    calledNumbersWithoutFofoId.add(normalized);
2594
                }
36276 ranu 2595
 
2596
                // Count calls with recordings
2597
                if (callLog.getRecordingUrl() != null && !callLog.getRecordingUrl().isEmpty()
2598
                        && !"None".equalsIgnoreCase(callLog.getRecordingUrl())) {
2599
                    totalRecordingCalls++;
2600
                    uniqueRecordingNumbers.add(normalized);
2601
                }
35843 ranu 2602
            }
2603
        }
2604
 
2605
        // Total called = distinct fofoIds + distinct numbers without fofoId mapping
36276 ranu 2606
        long calledCount = calledFofoIds.size() + calledNumbersWithoutFofoId.size();
2607
        return new long[]{calledCount, totalRecordingCalls, uniqueRecordingNumbers.size()};
35843 ranu 2608
    }
2609
 
35852 ranu 2610
    @Override
2611
    public List<List<String>> getAllCallDataByDate(LocalDate date) throws Exception {
2612
        List<List<String>> rows = new ArrayList<>();
2613
 
2614
        // Add header row
2615
        rows.add(Arrays.asList("RBM Name", "Partner Name", "Code", "Remark", "Call Status", "Call Duration", "Call Date Time", "Recording URL"));
2616
 
2617
        // Get all call logs for the date
2618
        List<AgentCallLog> allCallLogs = agentCallLogRepository.findAllByDate(date);
2619
        LOGGER.info("getAllCallDataByDate: Found {} call logs for date {}", allCallLogs.size(), date);
2620
 
2621
        if (allCallLogs.isEmpty()) {
2622
            return rows;
2623
        }
2624
 
2625
        // Get unique authIds from call logs
2626
        Set<Long> authIds = allCallLogs.stream()
2627
                .map(AgentCallLog::getAuthId)
2628
                .collect(Collectors.toSet());
2629
 
2630
        // Get auth users for RBM names
2631
        List<Integer> authIdInts = authIds.stream().map(Long::intValue).collect(Collectors.toList());
2632
        Map<Integer, AuthUser> authUserMap = authRepository.selectByIds(authIdInts).stream()
2633
                .collect(Collectors.toMap(AuthUser::getId, au -> au, (a, b) -> a));
2634
 
2635
        // Build a map of normalized customer number -> fofoId
2636
        Map<String, Integer> customerToFofoIdMap = new HashMap<>();
2637
        Set<String> normalizedNumbers = new HashSet<>();
2638
 
2639
        for (AgentCallLog callLog : allCallLogs) {
2640
            String customerNumber = callLog.getCustomerNumber();
2641
            if (customerNumber != null) {
2642
                String normalized = customerNumber.startsWith("+91") ? customerNumber.substring(3) : customerNumber;
2643
                normalizedNumbers.add(normalized);
2644
            }
2645
        }
2646
 
2647
        // For each normalized number, find fofoId
2648
        for (String mobile : normalizedNumbers) {
2649
            Integer fofoId = findFofoIdByMobile(mobile);
2650
            if (fofoId != null) {
2651
                customerToFofoIdMap.put(mobile, fofoId);
2652
            }
2653
        }
2654
 
2655
        // Get unique fofoIds for retailer lookup
2656
        Set<Integer> fofoIds = new HashSet<>(customerToFofoIdMap.values());
2657
        Map<Integer, CustomRetailer> retailerMap = Collections.emptyMap();
2658
        if (!fofoIds.isEmpty()) {
2659
            try {
2660
                retailerMap = retailerService.getFofoRetailers(new ArrayList<>(fofoIds));
2661
            } catch (ProfitMandiBusinessException e) {
2662
                LOGGER.error("Error fetching fofo stores", e);
2663
            }
2664
        }
2665
 
2666
        // Get remarks for these fofoIds on this date
2667
        Map<Integer, List<PartnerCollectionRemark>> fofoRemarkMap = new HashMap<>();
2668
        if (!fofoIds.isEmpty()) {
2669
            List<PartnerCollectionRemark> dateRemarks = partnerCollectionRemarkRepository
2670
                    .selectAllByFofoIdsOnDate(new ArrayList<>(fofoIds), date);
2671
            for (PartnerCollectionRemark remark : dateRemarks) {
2672
                fofoRemarkMap.computeIfAbsent(remark.getFofoId(), k -> new ArrayList<>()).add(remark);
2673
            }
2674
        }
2675
 
2676
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy hh:mm a");
2677
 
2678
        // Build rows
2679
        for (AgentCallLog callLog : allCallLogs) {
2680
            String customerNumber = callLog.getCustomerNumber();
2681
            if (customerNumber == null) {
2682
                continue;
2683
            }
2684
 
2685
            // Get RBM Name
2686
            AuthUser authUser = authUserMap.get((int) callLog.getAuthId());
2687
            String rbmName = authUser != null ? authUser.getFullName() : "Unknown";
2688
 
2689
            String normalized = customerNumber.startsWith("+91") ? customerNumber.substring(3) : customerNumber;
2690
            Integer fofoId = customerToFofoIdMap.get(normalized);
2691
 
2692
            String partyName = "Unknown";
2693
            String code = "-";
2694
 
2695
            if (fofoId != null) {
2696
                CustomRetailer retailer = retailerMap.get(fofoId);
2697
                if (retailer != null) {
2698
                    partyName = retailer.getBusinessName();
2699
                    code = retailer.getCode();
2700
                } else {
2701
                    partyName = "Unknown (" + fofoId + ")";
2702
                }
2703
            } else {
2704
                partyName = "Unknown (" + normalized + ")";
2705
            }
2706
 
2707
            // Get remark if available
2708
            String remarkValue = "-";
2709
            if (fofoId != null && fofoRemarkMap.containsKey(fofoId)) {
2710
                List<PartnerCollectionRemark> remarks = fofoRemarkMap.get(fofoId);
2711
                if (!remarks.isEmpty()) {
2712
                    PartnerCollectionRemark remark = remarks.get(0);
2713
                    remarkValue = remark.getRemark() != null ? remark.getRemark().getValue() : "-";
2714
                    if (remark.getMessage() != null && !remark.getMessage().isEmpty()) {
2715
                        remarkValue = remarkValue + " - " + remark.getMessage();
2716
                    }
2717
                }
2718
            }
2719
 
2720
            // Call status from customerStatus field
2721
            String callStatus = callLog.getCustomerStatus() != null ? callLog.getCustomerStatus() : "-";
2722
            String callDuration = callLog.getCallDuration() != null ? callLog.getCallDuration() : "-";
2723
 
2724
            String callDateTime = "-";
2725
            if (callLog.getCallDate() != null && callLog.getCallTime() != null) {
2726
                LocalDateTime callDateTimeObj = LocalDateTime.of(callLog.getCallDate(), callLog.getCallTime());
2727
                callDateTime = callDateTimeObj.format(dateTimeFormatter);
2728
            }
2729
 
2730
            String recordingUrl = callLog.getRecordingUrl() != null ? callLog.getRecordingUrl() : "-";
2731
 
2732
            rows.add(Arrays.asList(rbmName, partyName, code, remarkValue, callStatus, callDuration, callDateTime, recordingUrl));
2733
        }
2734
 
2735
        return rows;
2736
    }
2737
 
33917 ranu 2738
}