Rev 36334 | Blame | Compare with Previous | Last modification | View Log | RSS feed
package com.spice.profitmandi.service;import com.spice.profitmandi.common.enumuration.ActivationType;import com.spice.profitmandi.common.exception.ProfitMandiBusinessException;import com.spice.profitmandi.common.model.CustomRetailer;import com.spice.profitmandi.common.model.ProfitMandiConstants;import com.spice.profitmandi.dao.entity.auth.AuthUser;import com.spice.profitmandi.dao.entity.auth.PartnerCollectionRemark;import com.spice.profitmandi.dao.entity.auth.RbmCallSequenceLog;import com.spice.profitmandi.dao.entity.cs.AgentCallLog;import com.spice.profitmandi.dao.entity.cs.Position;import com.spice.profitmandi.dao.entity.cs.Ticket;import com.spice.profitmandi.dao.entity.fofo.FofoStore;import com.spice.profitmandi.dao.entity.fofo.MonthlyTarget;import com.spice.profitmandi.dao.entity.fofo.RetailerContact;import com.spice.profitmandi.dao.entity.inventory.RbmAchievements;import com.spice.profitmandi.dao.entity.inventory.RbmTargets;import com.spice.profitmandi.dao.entity.user.Address;import com.spice.profitmandi.dao.enumuration.auth.CollectionRemark;import com.spice.profitmandi.dao.enumuration.cs.EscalationType;import com.spice.profitmandi.dao.model.*;import com.spice.profitmandi.dao.repository.auth.AuthRepository;import com.spice.profitmandi.dao.repository.auth.PartnerCollectionRemarkRepository;import com.spice.profitmandi.dao.repository.auth.RbmCallSequenceLogRepository;import com.spice.profitmandi.dao.repository.catalog.RbmAchievementsRepository;import com.spice.profitmandi.dao.repository.catalog.RbmTargetsRepository;import com.spice.profitmandi.dao.repository.cs.CsService;import com.spice.profitmandi.dao.repository.cs.PositionRepository;import com.spice.profitmandi.dao.repository.dtr.FofoStoreRepository;import com.spice.profitmandi.dao.repository.dtr.RetailerContactRepository;import com.spice.profitmandi.dao.repository.fofo.MonthlyTargetRepository;import com.spice.profitmandi.dao.repository.logistics.PublicHolidaysRepository;import com.spice.profitmandi.dao.repository.transaction.LoanRepository;import com.spice.profitmandi.dao.repository.transaction.OrderRepository;import com.spice.profitmandi.dao.repository.user.AddressRepository;import com.spice.profitmandi.service.user.RetailerService;import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import org.hibernate.Session;import org.hibernate.SessionFactory;import org.hibernate.query.NativeQuery;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cache.annotation.Cacheable;import org.springframework.stereotype.Component;import javax.persistence.TypedQuery;import java.time.*;import java.time.format.DateTimeFormatter;import java.time.temporal.ChronoUnit;import java.util.*;import java.util.stream.Collectors;@Componentpublic class RbmTargetServiceImpl implements RbmTargetService {private static final Logger LOGGER = LogManager.getLogger(RbmTargetServiceImpl.class);@AutowiredSessionFactory sessionFactory;@AutowiredRbmTargetsRepository rbmTargetsRepository;@AutowiredRbmAchievementsRepository rbmAchievementsRepository;@AutowiredMonthlyTargetRepository monthlyTargetRepository;@AutowiredPublicHolidaysRepository publicHolidaysRepository;@AutowiredRetailerService retailerService;@Overridepublic List<WarehouseRbmTargetModel> getWarehouseWiseRbmMonthlyTarget() {Session session = sessionFactory.getCurrentSession();final TypedQuery<WarehouseRbmTargetModel> typedQuerySimilar = session.createNamedQuery("RbmTarget.getWarehouseWiseMonthlyTarget", WarehouseRbmTargetModel.class);return typedQuerySimilar.getResultList();}@Overridepublic List<MTDAchievedTargetModel> getDateWiseAchievedTargetOfRbm(LocalDate startDate, LocalDate endDate) {Session session = sessionFactory.getCurrentSession();final TypedQuery<MTDAchievedTargetModel> typedQuerySimilar = session.createNamedQuery("RbmTarget.getRbmAchievedMonthlyTarget", MTDAchievedTargetModel.class);typedQuerySimilar.setParameter("startDate", startDate);typedQuerySimilar.setParameter("endDate", endDate);return typedQuerySimilar.getResultList();}@Overridepublic List<TodayAchievedMovementModel> getMovementWiseAchievementByDate(LocalDate startDate, LocalDate endDate) {LOGGER.info("start date {}, end date {}", startDate, endDate);Session session = sessionFactory.getCurrentSession();final TypedQuery<TodayAchievedMovementModel> typedQuerySimilar = session.createNamedQuery("RBMTarget.TodayAchivementByMovement", TodayAchievedMovementModel.class);typedQuerySimilar.setParameter("startDate", startDate);typedQuerySimilar.setParameter("endDate", endDate);return typedQuerySimilar.getResultList();}@Overridepublic List<WarehouseMobileStockByMovementModel> getWarehouseMobileStockByMovement() {Session session = sessionFactory.getCurrentSession();final TypedQuery<WarehouseMobileStockByMovementModel> typedQuerySimilar = session.createNamedQuery("WarehouseStock.MovementWiseMobileStock", WarehouseMobileStockByMovementModel.class);return typedQuerySimilar.getResultList();}@Overridepublic List<SoldCatalogsReportModel> getCatalogSoldReport(LocalDate startDate, LocalDate endDate) {Session session = sessionFactory.getCurrentSession();final TypedQuery<SoldCatalogsReportModel> typedQuerySimilar = session.createNamedQuery("CatalogsReport.SoldCatalogsReport", SoldCatalogsReportModel.class);typedQuerySimilar.setParameter("startDate", startDate);typedQuerySimilar.setParameter("endDate", endDate);return typedQuerySimilar.getResultList();}public int getWorkingDaysCount(LocalDate startDate) {Session session = sessionFactory.getCurrentSession();// Convert the LocalDate to a format MySQL can interpretString startDateString = startDate.toString();final NativeQuery<?> nativeQuery = session.createNativeQuery("SELECT (DATEDIFF(LAST_DAY(:startDate), :startDate) + 1) " +" - (FLOOR((DATEDIFF(LAST_DAY(:startDate), :startDate) + (WEEKDAY(:startDate) + 1)) / 7)) " +" - (SELECT COUNT(*) " +" FROM logistics.publicholidays " +" WHERE date BETWEEN :startDate AND LAST_DAY(:startDate) " +" AND WEEKDAY(date) != 6) AS working_days");// Set the start date parameter for each placeholdernativeQuery.setParameter("startDate", startDateString);Object result = nativeQuery.getSingleResult();return result != null ? ((Number) result).intValue() : 0;}@Overridepublic List<RbmArrViewModel> getRbmTodayArr() throws Exception {LocalDate todayDate = LocalDate.now();return getRbmTodayArr(todayDate);}@Overridepublic List<RbmTargetAndAchievementsModel> getRbmTargetsAndAchievemnts(LocalDate startDate, LocalDate endDate) {List<RbmTargetsModel> rbmTargetsList = rbmTargetsRepository.selectTargetsModelListByDates(startDate.atStartOfDay(), endDate.atTime(LocalTime.MAX));LOGGER.info("rbmTargetsList {}", rbmTargetsList);// Group Targtes by RBM and WarehouseMap<String, RbmTargetsModel> targetsMap = rbmTargetsList.stream().collect(Collectors.toMap(a -> a.getRbmAuthId() + "-" + a.getWarehouseId(),a -> a,(a1, a2) -> mergeTargets(a1, a2) // Handle duplicates by merging));List<RbmAchievementsModel> rbmAchievements = rbmAchievementsRepository.selectAchievementsModelListByDates(startDate.atStartOfDay(), endDate.atTime(LocalTime.MAX));LOGGER.info("rbmTargetsList {}", rbmAchievements);// Group achievements by RBM and WarehouseMap<String, RbmAchievementsModel> achievementMap = rbmAchievements.stream().collect(Collectors.toMap(a -> a.getRbmAuthId() + "-" + a.getWarehouseId(),a -> a,(a1, a2) -> mergeAchievements(a1, a2) // Handle duplicates by merging));return targetsMap.keySet().stream().map(key -> {String[] parts = key.split("-");int rbmAuthId = Integer.parseInt(parts[0]);int warehouseId = Integer.parseInt(parts[1]);RbmTargetsModel target = targetsMap.get(key);RbmAchievementsModel achievement = achievementMap.getOrDefault(key, new RbmAchievementsModel());RbmTargetAndAchievementsModel model = new RbmTargetAndAchievementsModel();model.setAuthId(rbmAuthId);model.setRbmName(target.getRbmName());model.setWarehouseName(ProfitMandiConstants.WAREHOUSE_MAP.getOrDefault(warehouseId, "Unknown"));// Set target valuesmodel.setHidTarget((long) target.getHidTarget());model.setRunningTarget((long) target.getRunningTarget());model.setFastMovingTarget((long) target.getFastMovingTarget());model.setSlowMovingTarget((long) target.getSlowMovingTarget());model.setOtherMovingTarget((long) target.getOtherTarget());// Set achievement valuesmodel.setAchievedHid((long) achievement.getAchievedHidTarget());model.setAchievedRunning((long) achievement.getAchievedRunningTarget());model.setAchievedFastMoving((long) achievement.getAchievedFastMovingTarget());model.setAchievedSlowMoving((long) achievement.getAchievedSlowMovingTarget());model.setAchievedOtherMoving((long) achievement.getAchievedOtherTarget());model.setTotalTarget((long) target.getHidTarget() +(long) target.getRunningTarget() +(long) target.getFastMovingTarget() +(long) target.getSlowMovingTarget() +(long) target.getOtherTarget());model.setTotalAchievemnt((long) achievement.getAchievedHidTarget() +(long) achievement.getAchievedRunningTarget() +(long) achievement.getAchievedFastMovingTarget() +(long) achievement.getAchievedSlowMovingTarget() +(long) achievement.getAchievedOtherTarget());return model;}).collect(Collectors.toList());}private RbmTargetsModel mergeTargets(RbmTargetsModel a1, RbmTargetsModel a2) {// Merge logic for achievements (aggregate the target and achieved values)a1.setHidTarget((a1.getHidTarget()) +(a2.getHidTarget()));a1.setFastMovingTarget((a1.getFastMovingTarget()) +(a2.getFastMovingTarget()));a1.setSlowMovingTarget((a1.getSlowMovingTarget()) +(a2.getSlowMovingTarget()));a1.setRunningTarget((a1.getRunningTarget()) +(a2.getRunningTarget()));a1.setOtherTarget((a1.getOtherTarget()) +(a2.getOtherTarget()));return a1;}private RbmAchievementsModel mergeAchievements(RbmAchievementsModel a1, RbmAchievementsModel a2) {// Merge logic for achievements (aggregate the target and achieved values)a1.setAchievedHidTarget((a1.getAchievedHidTarget()) +(a2.getAchievedHidTarget()));a1.setAchievedRunningTarget((a1.getAchievedRunningTarget()) +(a2.getAchievedRunningTarget()));a1.setAchievedFastMovingTarget((a1.getAchievedFastMovingTarget()) +(a2.getAchievedFastMovingTarget()));a1.setAchievedSlowMovingTarget((a1.getAchievedSlowMovingTarget()) +(a2.getAchievedSlowMovingTarget()));a1.setAchievedOtherTarget((a1.getAchievedOtherTarget()) +(a2.getAchievedOtherTarget()));return a1;}@Overridepublic List<RbmArrViewModel> getRbmTodayArr(LocalDate todayDate) throws Exception {LocalDate startDateOfMonthDay1 = LocalDate.now().withDayOfMonth(1);List<WarehouseRbmTargetModel> warehouseRbmTargetModelList = this.getWarehouseWiseRbmMonthlyTarget();List<WarehouseRbmTargetModel> warehouseRbmTargetModels = warehouseRbmTargetModelList.stream().filter(x -> x.getMonthlyTarget() > 0).collect(Collectors.toList());LOGGER.info("warehouseRbmTargetModels {}", warehouseRbmTargetModels);List<TodayAchievedMovementModel> todayAchievedMovementModels = getMovementWiseAchievementByDate(todayDate, todayDate.plusDays(1));List<MTDAchievedTargetModel> mtdAchievedTargetModels = getDateWiseAchievedTargetOfRbm(startDateOfMonthDay1, todayDate);int remainingWorkingDaysCount = (int) getRemainingDaysInMonth(todayDate);List<RbmTargets> todayRbmTargetsList = rbmTargetsRepository.selectTargetsByDates(todayDate.atStartOfDay(), todayDate.atTime(LocalTime.MAX));LOGGER.info("todayRbmTargetsList {}", todayRbmTargetsList);List<RbmArrViewModel> rbmArrViewModels = new ArrayList<>();if (!todayRbmTargetsList.isEmpty()) {// OPTIMIZED: Pre-build maps for O(1) lookup instead of O(n) filter in each iteration// Map key: "authId-warehouseId"Map<String, Double> mtdAchievedMap = mtdAchievedTargetModels.stream().collect(Collectors.groupingBy(x -> x.getAuthId() + "-" + x.getWarehouseId(),Collectors.summingDouble(MTDAchievedTargetModel::getAcheivedMonthlyTarget)));Map<String, TodayAchievedMovementModel> todayAchievedMap = todayAchievedMovementModels.stream().collect(Collectors.toMap(x -> x.getAuthId() + "-" + x.getWarehouseId(),x -> x,(a, b) -> a));Map<String, RbmTargets> todayRbmTargetsMap = todayRbmTargetsList.stream().collect(Collectors.toMap(x -> x.getRbmAuthId() + "-" + x.getWarehouseId(),x -> x,(a, b) -> a));for (WarehouseRbmTargetModel rbmTarget : warehouseRbmTargetModels) {String lookupKey = rbmTarget.getAuthId() + "-" + rbmTarget.getWarehouseId();float monthlyTarget = rbmTarget.getMonthlyTarget();float achievedSoFar = mtdAchievedMap.getOrDefault(lookupKey, 0.0).floatValue();float remainingTarget = monthlyTarget - achievedSoFar;float todayTarget = (remainingWorkingDaysCount > 0 && remainingTarget > 0) ? remainingTarget / remainingWorkingDaysCount : 0;String warehouseName = ProfitMandiConstants.WAREHOUSE_MAP.getOrDefault(rbmTarget.getWarehouseId(), "Unknown");LOGGER.info("rbmTarget ==== {}", rbmTarget);TodayAchievedMovementModel todayAchievedMovementModel = todayAchievedMap.get(lookupKey);RbmTargets todayRbmTargets = todayRbmTargetsMap.get(lookupKey);if (todayRbmTargets != null) {LOGGER.info("todayRbmTargets {}", todayRbmTargets);RbmArrViewModel viewModel = new RbmArrViewModel();viewModel.setAuthId(rbmTarget.getAuthId());viewModel.setRbmName(rbmTarget.getRbmName());viewModel.setWarehouseName(warehouseName);viewModel.setTodayTarget(Math.round(todayTarget));viewModel.setMonthlyTarget(Math.round(monthlyTarget));viewModel.setMtdAchievedTarget(Math.round(achievedSoFar));viewModel.setTodayHidTarget(Math.round((todayRbmTargets.getHidTarget())));viewModel.setTodayFastMovingTarget(Math.round(todayRbmTargets.getFastMovingTarget()));viewModel.setTodaySlowMovingTarget(Math.round(todayRbmTargets.getSlowMovingtarget()));viewModel.setTodayRunningTarget(Math.round(todayRbmTargets.getRunningtarget()));viewModel.setTodayOtherMovingTarget(Math.round(todayRbmTargets.getOtherTarget()));if (todayAchievedMovementModel != null) {viewModel.setTodayAchievedHidTarget(Math.round(todayAchievedMovementModel.getHidBilled()));viewModel.setTodayAchievedFastMovingTarget(Math.round(todayAchievedMovementModel.getFastMovingBilled()));viewModel.setTodayAchievedSlowMovingTarget(Math.round(todayAchievedMovementModel.getSlowMovinBilled()));viewModel.setTodayAchievedRunningTarget(Math.round(todayAchievedMovementModel.getRunningBilled()));viewModel.setTodayAchievedOtherMovingTarget(Math.round(todayAchievedMovementModel.getOtherBilled()));viewModel.setTotalAchievedTarget(Math.round(todayAchievedMovementModel.getHidBilled() + todayAchievedMovementModel.getFastMovingBilled() + todayAchievedMovementModel.getSlowMovinBilled() + todayAchievedMovementModel.getRunningBilled() + todayAchievedMovementModel.getOtherBilled()));} else {viewModel.setTodayAchievedHidTarget(0);viewModel.setTodayAchievedFastMovingTarget(0);viewModel.setTodayAchievedSlowMovingTarget(0);viewModel.setTodayAchievedRunningTarget(0);viewModel.setTodayAchievedOtherMovingTarget(0);viewModel.setTotalAchievedTarget(0);}rbmArrViewModels.add(viewModel);} else {LOGGER.info("No matching RbmTargets found for AuthId: {} and rbmname {} and WarehouseId: {}", rbmTarget.getAuthId(), rbmTarget.getRbmName(), rbmTarget.getWarehouseId());}}}LOGGER.info("rbmArrViewModels {}", rbmArrViewModels);return rbmArrViewModels;}@Overridepublic void setMovementWiseRbmTargets() {LocalDate todayDate = LocalDate.now();LocalDate startDateOfMonthDay1 = LocalDate.now().withDayOfMonth(1);List<WarehouseRbmTargetModel> warehouseRbmTargetModels = this.getWarehouseWiseRbmMonthlyTarget();List<MTDAchievedTargetModel> mtdAchievedTargetModels = getDateWiseAchievedTargetOfRbm(startDateOfMonthDay1, todayDate);int remainingWorkingDaysCount = (int) getRemainingDaysInMonth(todayDate);List<WarehouseMobileStockByMovementModel> warehouseMobileStockByMovementModels = getWarehouseMobileStockByMovement();// OPTIMIZED: Pre-build maps for O(1) lookup instead of O(n) filter in each iterationMap<String, Double> mtdAchievedMap = mtdAchievedTargetModels.stream().collect(Collectors.groupingBy(x -> x.getAuthId() + "-" + x.getWarehouseId(),Collectors.summingDouble(MTDAchievedTargetModel::getAcheivedMonthlyTarget)));Map<Integer, WarehouseMobileStockByMovementModel> warehouseStockMap = warehouseMobileStockByMovementModels.stream().collect(Collectors.toMap(WarehouseMobileStockByMovementModel::getWarehouseId,x -> x,(a, b) -> a));for (WarehouseRbmTargetModel rbmTarget : warehouseRbmTargetModels) {String lookupKey = rbmTarget.getAuthId() + "-" + rbmTarget.getWarehouseId();float monthlyTarget = rbmTarget.getMonthlyTarget();float achievedSoFar = mtdAchievedMap.getOrDefault(lookupKey, 0.0).floatValue();float remainingTarget = monthlyTarget - achievedSoFar;LOGGER.info("remainingTarget {}", remainingTarget);float todayTarget = (remainingWorkingDaysCount > 0 && remainingTarget > 0) ? remainingTarget / remainingWorkingDaysCount : 0;LOGGER.info("todayTarget {}", todayTarget);// Get the warehouse stock dataWarehouseMobileStockByMovementModel warehouseMobileStockByMovementModel = warehouseStockMap.get(rbmTarget.getWarehouseId());if (warehouseMobileStockByMovementModel != null) {// Total stock value for this warehousefloat totalStockValue = warehouseMobileStockByMovementModel.getTotalAvailabilityPrice();// Calculate target allocation based on stock value proportionfloat hidTarget = (warehouseMobileStockByMovementModel.getTotalHidCatalogPrice() / totalStockValue) * todayTarget;float fastMovingTarget = (warehouseMobileStockByMovementModel.getTotalFastMovingCatalogPrice() / totalStockValue) * todayTarget;float slowMovingTarget = (warehouseMobileStockByMovementModel.getTotalSlowMovingCatalogPrice() / totalStockValue) * todayTarget;float runningTarget = (warehouseMobileStockByMovementModel.getTotalRunningCatalogPrice() / totalStockValue) * todayTarget;float otherTarget = (warehouseMobileStockByMovementModel.getTotalOtherCategoryCatalogPrice() / totalStockValue) * todayTarget;RbmTargets rbmTargets = new RbmTargets();rbmTargets.setWarehouseId(rbmTarget.getWarehouseId());rbmTargets.setRbmAuthId(rbmTarget.getAuthId());rbmTargets.setRbmName(rbmTarget.getRbmName());rbmTargets.setRunningtarget(runningTarget);rbmTargets.setHidTarget(hidTarget);rbmTargets.setFastMovingTarget(fastMovingTarget);rbmTargets.setSlowMovingtarget(slowMovingTarget);rbmTargets.setOtherTarget(otherTarget);rbmTargets.setCreateTimestamp(LocalDateTime.now());rbmTargetsRepository.persist(rbmTargets);}}}@Overridepublic void setMovementWiseRbmAchievement() {LocalDate todayDate = LocalDate.now();List<TodayAchievedMovementModel> todayAchievedMovementModels = getMovementWiseAchievementByDate(todayDate, todayDate.plusDays(1));for (TodayAchievedMovementModel achievement : todayAchievedMovementModels) {RbmAchievements rbmAchievements = new RbmAchievements();rbmAchievements.setRbmAuthId(achievement.getAuthId());rbmAchievements.setRbmName(achievement.getRbmName());rbmAchievements.setWarehouseId(achievement.getWarehouseId());rbmAchievements.setAchievedHidTarget(achievement.getHidBilled());rbmAchievements.setAchievedFastMovingTarget(achievement.getFastMovingBilled());rbmAchievements.setAchievedSlowMovingTarget(achievement.getSlowMovinBilled());rbmAchievements.setAchievedRunningTarget(achievement.getRunningBilled());rbmAchievements.setAchievedOtherTarget(achievement.getOtherBilled());rbmAchievements.setCreateTimestamp(LocalDateTime.now());rbmAchievementsRepository.persist(rbmAchievements);}}@Overridepublic List<Sold15daysOldAgingModel> getAgingSale(LocalDate startDate, LocalDate endDate) {Session session = sessionFactory.getCurrentSession();final TypedQuery<Sold15daysOldAgingModel> typedQuerySimilar = session.createNamedQuery("Aging.SoldAgingModel", Sold15daysOldAgingModel.class);typedQuerySimilar.setParameter("startDate", startDate);typedQuerySimilar.setParameter("endDate", endDate);return typedQuerySimilar.getResultList();}@Overridepublic List<RbmBilledFofoIdsModel> getDateWiseBilledFofoIdByRbm(LocalDate startDate, LocalDate endDate) {Session session = sessionFactory.getCurrentSession();final TypedQuery<RbmBilledFofoIdsModel> typedQuerySimilar = session.createNamedQuery("RBM.RbmBilledFofoId", RbmBilledFofoIdsModel.class);typedQuerySimilar.setParameter("startDate", startDate);typedQuerySimilar.setParameter("endDate", endDate);return typedQuerySimilar.getResultList();}@Override@Cacheable(value = "rbmWeeklyBilling",cacheManager = "fiveMintimeoutCacheManager",sync = true)public List<RbmWeeklyBillingModel> getWeeklyBillingDataForMonth(LocalDate monthStart, LocalDate monthEnd) {Session session = sessionFactory.getCurrentSession();final TypedQuery<RbmWeeklyBillingModel> typedQuery = session.createNamedQuery("RBM.WeeklyBilling", RbmWeeklyBillingModel.class);typedQuery.setParameter("startDate", monthStart);typedQuery.setParameter("endDate", monthEnd);return typedQuery.getResultList();}public List<Our15DaysOldAgingStock> our15DaysAgingStock() {Session session = sessionFactory.getCurrentSession();final TypedQuery<Our15DaysOldAgingStock> typedQuerySimilar = session.createNamedQuery("Aging.15DaysOurStock", Our15DaysOldAgingStock.class);return typedQuerySimilar.getResultList();}@Overridepublic List<WarehouseAgingStockModel> getWarehouseWiseAgingStock() {Session session = sessionFactory.getCurrentSession();final TypedQuery<WarehouseAgingStockModel> typedQuery = session.createNamedQuery("Aging.15DaysWarehouseWiseStock", WarehouseAgingStockModel.class);return typedQuery.getResultList();}@AutowiredOrderRepository orderRepository;@Overridepublic double calculateFofoIdTodayTarget(int fofoId, double secondryMtd,LocalDate date) {MonthlyTarget monthlyTarget = monthlyTargetRepository.selectByDateAndFofoId(YearMonth.now(), fofoId);if (monthlyTarget == null) {// Log or handle as neededreturn 0; // or -1 or some fallback}double remainingTarget = monthlyTarget.getPurchaseTarget() - secondryMtd;// double remainingWorkingDays = getWorkingDaysCount(date);double remainingWorkingDays = (double) getRemainingDaysInMonth(date);if (remainingWorkingDays == 0) return remainingTarget; // Last dayLOGGER.info("remainingWorkingDays {}", remainingWorkingDays);LOGGER.info("remainingTarget {}", remainingTarget);return (int) Math.ceil(remainingTarget / remainingWorkingDays);}@Overridepublic long getRemainingDaysInMonth(LocalDate date) {LocalDate lastDayOfMonth = YearMonth.from(date).atEndOfMonth();long totalDays = ChronoUnit.DAYS.between(date, lastDayOfMonth) + 1;// Count Sundays manuallylong sundayCount = 0;LocalDate current = date;while (!current.isAfter(lastDayOfMonth)) {if (current.getDayOfWeek() == DayOfWeek.SUNDAY) {sundayCount++;}current = current.plusDays(1);}// Public holidays in the rangelong publicHolidays = publicHolidaysRepository.selectAllBetweenDates(date, lastDayOfMonth).size();long remainingDays = totalDays - sundayCount - publicHolidays;LOGGER.info("remainingDays {}", remainingDays);LOGGER.info("totalDays {}", totalDays);LOGGER.info("sundays {}", sundayCount);LOGGER.info("publicHolidays {}", publicHolidays);return remainingDays;}@AutowiredPositionRepository positionRepository;@AutowiredCsService csService;@AutowiredFofoStoreRepository fofoStoreRepository;@AutowiredAuthRepository authRepository;@AutowiredLoanRepository loanRepository;@AutowiredPartnerCollectionService partnerCollectionService;@AutowiredPartnerCollectionRemarkRepository partnerCollectionRemarkRepository;@AutowiredRbmCallSequenceLogRepository rbmCallSequenceLogRepository;@Autowiredcom.spice.profitmandi.dao.repository.cs.TicketRepository ticketRepository;@Autowiredcom.spice.profitmandi.dao.repository.cs.AgentCallLogRepository agentCallLogRepository;@AutowiredRetailerContactRepository retailerContactRepository;@AutowiredAddressRepository addressRepository;@Autowiredcom.spice.profitmandi.dao.repository.cs.PartnerPositionRepository partnerPositionRepository;@Overridepublic List<RbmCallTargetModel> getRbmCallTargetModels() throws Exception {return getRbmCallTargetModels(LocalDate.now());}public List<RbmCallTargetModel> getRbmCallTargetModels(LocalDate queryDate) throws Exception {long methodStart = System.currentTimeMillis();List<RbmCallTargetModel> rbmCallTargetModels = new ArrayList<>();// Get all RBM positions (L1 and L2)long start = System.currentTimeMillis();List<Position> allRbmPositions = positionRepository.selectPositionByCategoryId(ProfitMandiConstants.TICKET_CATEGORY_RBM).stream().filter(x -> Arrays.asList(EscalationType.L1, EscalationType.L2, EscalationType.L3).contains(x.getEscalationType())).collect(Collectors.toList());// Separate L1, L2 and L3 auth IDsList<Integer> l1AuthIds = allRbmPositions.stream().filter(p -> EscalationType.L1.equals(p.getEscalationType())).map(Position::getAuthUserId).distinct().collect(Collectors.toList());List<Integer> l2AuthIds = allRbmPositions.stream().filter(p -> EscalationType.L2.equals(p.getEscalationType())).map(Position::getAuthUserId).distinct().collect(Collectors.toList());List<Integer> l3AuthIds = allRbmPositions.stream().filter(p -> EscalationType.L3.equals(p.getEscalationType())).map(Position::getAuthUserId).distinct().collect(Collectors.toList());// Union of all auth IDs for batch fetchingList<Integer> rbmPositionsAuthIds = allRbmPositions.stream().map(Position::getAuthUserId).distinct().collect(Collectors.toList());LOGGER.info("RBM Call Target - RBM positions fetch: {}ms, L1: {}, L2: {}, L3: {}", System.currentTimeMillis() - start, l1AuthIds.size(), l2AuthIds.size(), l3AuthIds.size());start = System.currentTimeMillis();Map<String, Set<Integer>> storeGuyMap = csService.getAuthUserPartnerIdMapping();LOGGER.info("RBM Call Target - StoreGuyMap fetch: {}ms", System.currentTimeMillis() - start);LocalDateTime startDate = queryDate.atStartOfDay();LocalDate firstOfMonth = queryDate.withDayOfMonth(1);LocalDate endOfMonth = queryDate.withDayOfMonth(queryDate.lengthOfMonth()).plusDays(1);// Get auth user mapstart = System.currentTimeMillis();Map<Integer, AuthUser> authUserMap = authRepository.selectByIds(rbmPositionsAuthIds).stream().collect(Collectors.toMap(AuthUser::getId, au -> au));LOGGER.info("RBM Call Target - AuthUser fetch: {}ms", System.currentTimeMillis() - start);// Batch fetch positions by auth IDs (to check if RBM is L1)start = System.currentTimeMillis();Map<Integer, List<Position>> positionsByAuthId = positionRepository.selectPositionByAuthIds(rbmPositionsAuthIds).stream().collect(Collectors.groupingBy(Position::getAuthUserId));LOGGER.info("RBM Call Target - Positions by AuthId fetch: {}ms", System.currentTimeMillis() - start);// Get all fofo IDs for all RBMsSet<Integer> allFofoIds = new HashSet<>();Map<Integer, List<Integer>> rbmToFofoIdsMap = new HashMap<>();for (int rbmAuthId : rbmPositionsAuthIds) {AuthUser au = authUserMap.get(rbmAuthId);if (au != null && storeGuyMap.containsKey(au.getEmailId())) {List<Integer> fofoIds = new ArrayList<>(storeGuyMap.get(au.getEmailId()));allFofoIds.addAll(fofoIds);rbmToFofoIdsMap.put(rbmAuthId, fofoIds);}}// Initialize L2 calling list map - will be populated after fetching remarksMap<Integer, List<Integer>> l2AuthIdToFofoIds = new HashMap<>();for (int l2AuthId : l2AuthIds) {l2AuthIdToFofoIds.put(l2AuthId, new ArrayList<>());}// Initialize L3 calling list map - will be populated after fetching remarksMap<Integer, List<Integer>> l3AuthIdToFofoIds = new HashMap<>();for (int l3AuthId : l3AuthIds) {l3AuthIdToFofoIds.put(l3AuthId, new ArrayList<>());}LOGGER.info("RBM Call Target - Total fofo IDs to process: {}", allFofoIds.size());// Get only needed fofo stores (OPTIMIZED - was fetching ALL stores before)start = System.currentTimeMillis();Map<Integer, FofoStore> fofoStoresMap = new HashMap<>();if (!allFofoIds.isEmpty()) {try {fofoStoresMap = fofoStoreRepository.selectByRetailerIds(new ArrayList<>(allFofoIds)).stream().collect(Collectors.toMap(FofoStore::getId, x -> x, (a, b) -> a));} catch (ProfitMandiBusinessException e) {LOGGER.error("Error fetching fofo stores", e);}}LOGGER.info("RBM Call Target - FofoStores fetch (only needed): {}ms, count: {}", System.currentTimeMillis() - start, fofoStoresMap.size());// Batch fetch max remark ids for all fofoIds (for escalation filtering)start = System.currentTimeMillis();Map<Integer, PartnerCollectionRemark> allPartnerCollectionRemarks = new HashMap<>();if (!allFofoIds.isEmpty()) {List<Integer> allRemarkIds = partnerCollectionRemarkRepository.selectMaxRemarkId(new ArrayList<>(allFofoIds));if (!allRemarkIds.isEmpty()) {allPartnerCollectionRemarks = partnerCollectionRemarkRepository.selectByIds(allRemarkIds).stream().collect(Collectors.toMap(PartnerCollectionRemark::getFofoId, x -> x, (a, b) -> a));}}LOGGER.info("RBM Call Target - PartnerCollectionRemarks fetch: {}ms", System.currentTimeMillis() - start);// Populate L2 calling list based on partners whose latest remark is RBM_L2_ESCALATION// Find the L1 who has the partner and add to that L1's manager (L2) calling listfor (Map.Entry<Integer, PartnerCollectionRemark> entry : allPartnerCollectionRemarks.entrySet()) {Integer fofoId = entry.getKey();PartnerCollectionRemark remark = entry.getValue();if (CollectionRemark.RBM_L2_ESCALATION.equals(remark.getRemark())) {// Find which L1 RBM has this partner assignedfor (int l1AuthId : l1AuthIds) {List<Integer> l1FofoIds = rbmToFofoIdsMap.getOrDefault(l1AuthId, Collections.emptyList());if (l1FofoIds.contains(fofoId)) {// Get L1's manager (L2)AuthUser l1User = authUserMap.get(l1AuthId);if (l1User != null && l2AuthIdToFofoIds.containsKey(l1User.getManagerId())) {int l2ManagerId = l1User.getManagerId();l2AuthIdToFofoIds.get(l2ManagerId).add(fofoId);}break; // Found the L1 for this fofoId}}}}LOGGER.info("RBM Call Target - L2 calling lists populated from RBM_L2_ESCALATION remarks");// Populate L3 calling list based on partners whose latest remark is RBM_L3_ESCALATION// Find the L1 who originally has the partner, then:// Case 1: L1 -> L3 directly (if L1's manager IS L3)// Case 2: L1 -> L2 -> L3 (if L1's manager is L2, then L2's manager is L3)for (Map.Entry<Integer, PartnerCollectionRemark> entry : allPartnerCollectionRemarks.entrySet()) {Integer fofoId = entry.getKey();PartnerCollectionRemark remark = entry.getValue();if (CollectionRemark.RBM_L3_ESCALATION.equals(remark.getRemark())) {// Find which L1 RBM originally has this partner assignedfor (int l1AuthId : l1AuthIds) {List<Integer> l1FofoIds = rbmToFofoIdsMap.getOrDefault(l1AuthId, Collections.emptyList());if (l1FofoIds.contains(fofoId)) {AuthUser l1User = authUserMap.get(l1AuthId);if (l1User != null) {int l1ManagerId = l1User.getManagerId();// Case 1: L1's manager IS L3 directly (L1 → L3, no L2 in between)if (l3AuthIdToFofoIds.containsKey(l1ManagerId)) {l3AuthIdToFofoIds.get(l1ManagerId).add(fofoId);LOGGER.debug("L3 Calling List (direct): fofoId={} -> L1={} -> L3={}",fofoId, l1AuthId, l1ManagerId);} else {// Case 2: L1 -> L2 -> L3AuthUser l2User = authUserMap.get(l1ManagerId);if (l2User != null && l3AuthIdToFofoIds.containsKey(l2User.getManagerId())) {int l3ManagerId = l2User.getManagerId();l3AuthIdToFofoIds.get(l3ManagerId).add(fofoId);LOGGER.debug("L3 Calling List: fofoId={} -> L1={} -> L2={} -> L3={}",fofoId, l1AuthId, l1ManagerId, l3ManagerId);}}}break; // Found the L1 for this fofoId}}}}LOGGER.info("RBM Call Target - L3 calling lists populated from RBM_L3_ESCALATION remarks");// Batch fetch collection RANK map for all fofoIds (OPTIMIZED - only fetches rank, not full model)start = System.currentTimeMillis();Map<Integer, Integer> allCollectionRankMap = new HashMap<>();if (!allFofoIds.isEmpty()) {try {allCollectionRankMap = partnerCollectionService.getCollectionRankMap(new ArrayList<>(allFofoIds), startDate);} catch (ProfitMandiBusinessException e) {LOGGER.error("Error fetching collection rank map for all fofoIds", e);}}LOGGER.info("RBM Call Target - CollectionRankMap fetch (OPTIMIZED): {}ms", System.currentTimeMillis() - start);// Get MTD billing data for zero billing calculation and partner countsstart = System.currentTimeMillis();List<RbmWeeklyBillingModel> mtdBillingData = getWeeklyBillingDataForMonth(firstOfMonth, endOfMonth);Set<Integer> allMtdBilledFofoIds = mtdBillingData.stream().filter(RbmWeeklyBillingModel::isMtdBilled).map(RbmWeeklyBillingModel::getFofoId).collect(Collectors.toSet());// Build partner count and fofoIds per RBM from mtdBillingData (same source as Today ARR page)Map<Integer, Set<Integer>> mtdFofoIdsByAuthId = mtdBillingData.stream().filter(RbmWeeklyBillingModel::isTargetedPartner).collect(Collectors.groupingBy(RbmWeeklyBillingModel::getAuthId,Collectors.mapping(RbmWeeklyBillingModel::getFofoId, Collectors.toSet())));LOGGER.info("RBM Call Target - MTD Billing fetch: {}ms", System.currentTimeMillis() - start);// Batch fetch today's remarks for all auth IDs (to calculate Value Achieved)start = System.currentTimeMillis();Map<Integer, List<PartnerCollectionRemark>> remarksByAuthId = partnerCollectionRemarkRepository.selectAllByAuthIdsOnDate(rbmPositionsAuthIds, LocalDate.now()).stream().collect(Collectors.groupingBy(PartnerCollectionRemark::getAuthId));LOGGER.info("RBM Call Target - Today Remarks fetch: {}ms", System.currentTimeMillis() - start);// Batch fetch today's out-of-sequence logs for all RBMsstart = System.currentTimeMillis();LocalDateTime todayStart = LocalDate.now().atStartOfDay();LocalDateTime todayEnd = LocalDate.now().plusDays(1).atStartOfDay();List<RbmCallSequenceLog> outOfSequenceLogs = rbmCallSequenceLogRepository.selectOutOfSequenceByDateRange(todayStart, todayEnd);Map<Integer, Long> outOfSequenceCountByAuthId = outOfSequenceLogs.stream().collect(Collectors.groupingBy(RbmCallSequenceLog::getAuthId,Collectors.mapping(RbmCallSequenceLog::getFofoId, Collectors.collectingAndThen(Collectors.toSet(), s -> (long) s.size()))));LOGGER.info("RBM Call Target - Out of Sequence fetch: {}ms", System.currentTimeMillis() - start);// BATCH FETCH: All call logs for all RBMs (L1 + L2 + L3) in a single query.// Replaces the previous N+1 pattern where getCallStats() was called per RBM.start = System.currentTimeMillis();List<AgentCallLog> allCallLogs = agentCallLogRepository.findByAuthIdsAndDate(rbmPositionsAuthIds, queryDate);Map<Long, List<AgentCallLog>> callLogsByAuthId = allCallLogs.stream().collect(Collectors.groupingBy(AgentCallLog::getAuthId));LOGGER.info("RBM Call Target - Call logs batch fetch: {}ms ({} logs across {} RBMs)",System.currentTimeMillis() - start, allCallLogs.size(), callLogsByAuthId.size());// BATCH FETCH: Build a single mobile -> fofoId map from all unique customer numbers across all call logs.// Replaces the previous N+1 pattern where findFofoIdByMobile() was called per call log entry.start = System.currentTimeMillis();Set<String> allNormalizedMobiles = new HashSet<>();for (AgentCallLog callLog : allCallLogs) {String customerNumber = callLog.getCustomerNumber();if (customerNumber != null) {String normalized = customerNumber.startsWith("+91") ? customerNumber.substring(3) : customerNumber;allNormalizedMobiles.add(normalized);}}Map<String, Integer> mobileToFofoIdMap = buildMobileToFofoIdMap(allNormalizedMobiles);LOGGER.info("RBM Call Target - Mobile→FofoId batch fetch: {}ms ({} unique mobiles, {} mapped)",System.currentTimeMillis() - start, allNormalizedMobiles.size(), mobileToFofoIdMap.size());// Identify users who are both L1 and L2 — they will be shown only as L2Set<Integer> l2AuthIdSet = new HashSet<>(l2AuthIds);// Process L1 RBMs (skip users who are also L2 — their data will be merged into L2 model)for (int rbmAuthId : l1AuthIds) {if (l2AuthIdSet.contains(rbmAuthId)) {continue; // Will be handled in L2 processing with merged L1 data}AuthUser authUser = authUserMap.get(rbmAuthId);if (authUser == null || !storeGuyMap.containsKey(authUser.getEmailId())) {continue;}List<Integer> fofoIdList = rbmToFofoIdsMap.getOrDefault(rbmAuthId, Collections.emptyList());// Check if RBM is L1 (same logic as getSummaryModel)List<Position> positions = positionsByAuthId.getOrDefault(authUser.getId(), Collections.emptyList());boolean isRBMAndL1 = positions.stream().anyMatch(position ->ProfitMandiConstants.TICKET_CATEGORY_RBM == position.getCategoryId()&& EscalationType.L1.equals(position.getEscalationType()));// Filter escalated partners for L1 RBMs (same logic as getSummaryModel)List<Integer> fofoIds = fofoIdList;if (isRBMAndL1) {Map<Integer, PartnerCollectionRemark> partnerCollectionRemarks = new HashMap<>();for (Integer fofoId : fofoIdList) {if (allPartnerCollectionRemarks.containsKey(fofoId)) {partnerCollectionRemarks.put(fofoId, allPartnerCollectionRemarks.get(fofoId));}}fofoIds = partnerCollectionRemarks.entrySet().stream().filter(entry -> {PartnerCollectionRemark pcrMap = entry.getValue();return !(CollectionRemark.RBM_L2_ESCALATION.equals(pcrMap.getRemark())|| CollectionRemark.SALES_ESCALATION.equals(pcrMap.getRemark()));}).map(Map.Entry::getKey).collect(Collectors.toList());}// Filter to only external, ACTIVE or REVIVAL stores (collection plan not required)Map<Integer, Integer> finalAllCollectionRankMap = allCollectionRankMap;Map<Integer, FofoStore> finalFofoStoresMap = fofoStoresMap;List<Integer> validFofoIds = fofoIds.stream().filter(fofoId -> {FofoStore store = finalFofoStoresMap.get(fofoId);if (store == null || store.isInternal()) {return false;}// Only include ACTIVE or REVIVAL partners (not Low Sale, not Disputed, not Billing Pending)return ActivationType.ACTIVE.equals(store.getActivationType())|| ActivationType.REVIVAL.equals(store.getActivationType());}).collect(Collectors.toList());if (validFofoIds.isEmpty()) {continue;}RbmCallTargetModel targetModel = new RbmCallTargetModel();targetModel.setAuthId(rbmAuthId);targetModel.setRbmName(authUser.getFullName());// Use partner count from mtdBillingData (same source as Today ARR page)Set<Integer> mtdFofoIds = mtdFofoIdsByAuthId.getOrDefault(rbmAuthId, Collections.emptySet());targetModel.setPartnerCount(mtdFofoIds.size());// Categorize each partner - each partner belongs to ONE category only// Priority: PlanToday > CarryForward > ZeroBilling > Untouched > FuturePlan > Normal// Revival is counted separately (just for display, doesn't affect categorization)Set<Integer> planTodayPartners = new HashSet<>();Set<Integer> carryForwardPartners = new HashSet<>();Set<Integer> untouchedPartners = new HashSet<>();Set<Integer> zeroBillingPartners = new HashSet<>();Set<Integer> futurePlanPartners = new HashSet<>();Set<Integer> normalPartners = new HashSet<>();Set<Integer> revivalPartners = new HashSet<>();for (Integer fofoId : validFofoIds) {// Get collection plan rank (from optimized rank map)int rank = allCollectionRankMap.getOrDefault(fofoId, 5); // default to Normal if no plan// Check if partner has zero billing in MTDboolean hasZeroBilling = !allMtdBilledFofoIds.contains(fofoId);// Count REVIVAL partners separately (just for display, doesn't affect categorization)FofoStore store = finalFofoStoresMap.get(fofoId);if (store != null && ActivationType.REVIVAL.equals(store.getActivationType())) {revivalPartners.add(fofoId);}// Assign to category based on priorityif (rank == 1) {planTodayPartners.add(fofoId);} else if (rank == 2) {carryForwardPartners.add(fofoId);} else if (hasZeroBilling) {zeroBillingPartners.add(fofoId);} else if (rank == 3) {untouchedPartners.add(fofoId);} else if (rank == 4) {futurePlanPartners.add(fofoId);} else {normalPartners.add(fofoId);}}// Set countstargetModel.setCreditCollection(0); // Credit collection is handled in separate listtargetModel.setPlanToday(planTodayPartners.size());targetModel.setCarryForward(carryForwardPartners.size());targetModel.setUntouched(untouchedPartners.size());targetModel.setZeroBilling(zeroBillingPartners.size());targetModel.setFuturePlan(futurePlanPartners.size());targetModel.setNormal(normalPartners.size());targetModel.setRevival(revivalPartners.size());// Today Target = PlanToday + CarryForward + ZeroBilling + Untouched// These are mutually exclusive now, so we can sum themlong todayTarget = planTodayPartners.size() +carryForwardPartners.size() + zeroBillingPartners.size() + untouchedPartners.size();targetModel.setTodayTargetOfCall(todayTarget);// Create set of partners in Today Target categoriesSet<Integer> todayTargetPartners = new HashSet<>();todayTargetPartners.addAll(planTodayPartners);todayTargetPartners.addAll(carryForwardPartners);todayTargetPartners.addAll(zeroBillingPartners);todayTargetPartners.addAll(untouchedPartners);// Value Achieved = All distinct partners called today (from pre-fetched call logs)long[] callStats = getCallStatsFromLogs(callLogsByAuthId.get((long) rbmAuthId), mobileToFofoIdMap);targetModel.setValueTargetAchieved(callStats[0]);targetModel.setTotalRecordingCalls(callStats[1]);targetModel.setUniqueRecordingCalls(callStats[2]);// Keep todayRemarks for movedToFuture calculationList<PartnerCollectionRemark> todayRemarks = remarksByAuthId.getOrDefault(rbmAuthId, Collections.emptyList());// Moved to Future = Partners in Future Plan category who have a remark today// These are partners who were contacted today but moved to a future dateSet<Integer> todayRemarkedFofoIds = todayRemarks.stream().map(PartnerCollectionRemark::getFofoId).collect(Collectors.toSet());long movedToFuture = futurePlanPartners.stream().filter(todayRemarkedFofoIds::contains).count();targetModel.setMovedToFuture(movedToFuture);// Set out of sequence count for this RBMtargetModel.setOutOfSequenceCount(outOfSequenceCountByAuthId.getOrDefault(rbmAuthId, 0L));rbmCallTargetModels.add(targetModel);}// Process L2 RBMs (escalated ticket logic with categorization)// For users who are both L1 and L2, merge their L1 calling target into L2 modelfor (int l2AuthId : l2AuthIds) {AuthUser authUser = authUserMap.get(l2AuthId);if (authUser == null) {continue;}List<Integer> l2FofoIdList = l2AuthIdToFofoIds.getOrDefault(l2AuthId, Collections.emptyList());// For L2, use unique fofoIds with RBM_L2_ESCALATION remark as targetSet<Integer> l2TargetFofoIds = new HashSet<>(l2FofoIdList);RbmCallTargetModel l2Model = new RbmCallTargetModel();l2Model.setAuthId(l2AuthId);l2Model.setRbmName(authUser.getFullName() + " (L2)");l2Model.setL2Position(true);l2Model.setL2CallingList(l2TargetFofoIds.size());// Partner count: if user is also L1, use L1 partner count (MTD targeted partners)if (l1AuthIds.contains(l2AuthId)) {Set<Integer> mtdFofoIds = mtdFofoIdsByAuthId.getOrDefault(l2AuthId, Collections.emptySet());l2Model.setPartnerCount(mtdFofoIds.size());} else {List<Integer> l2AssignedFofoIds = rbmToFofoIdsMap.getOrDefault(l2AuthId, Collections.emptyList());l2Model.setPartnerCount(l2AssignedFofoIds.size());}// If user is also L1, calculate full L1 breakdown and merge into L2 modelif (l1AuthIds.contains(l2AuthId) && storeGuyMap.containsKey(authUser.getEmailId())) {// Get only L1 RBM position partners (not L2 partners) from partner_positionList<Position> positions = positionsByAuthId.getOrDefault(l2AuthId, Collections.emptyList());Set<Integer> l1RbmPositionIds = positions.stream().filter(p -> ProfitMandiConstants.TICKET_CATEGORY_RBM == p.getCategoryId()&& EscalationType.L1.equals(p.getEscalationType())).map(Position::getId).collect(Collectors.toSet());// Fetch partner_position for only L1 RBM positions of this userList<Integer> fofoIdList = partnerPositionRepository.selectByPositionIds(new ArrayList<>(l1RbmPositionIds)).stream().map(pp -> pp.getFofoId()).distinct().collect(Collectors.toList());boolean isRBMAndL1 = !l1RbmPositionIds.isEmpty();List<Integer> l1FofoIds = new ArrayList<>(fofoIdList);if (isRBMAndL1) {Map<Integer, PartnerCollectionRemark> partnerCollectionRemarks = new HashMap<>();for (Integer fofoId : fofoIdList) {if (allPartnerCollectionRemarks.containsKey(fofoId)) {partnerCollectionRemarks.put(fofoId, allPartnerCollectionRemarks.get(fofoId));}}l1FofoIds = partnerCollectionRemarks.entrySet().stream().filter(entry -> {PartnerCollectionRemark pcrMap = entry.getValue();return !(CollectionRemark.RBM_L2_ESCALATION.equals(pcrMap.getRemark())|| CollectionRemark.SALES_ESCALATION.equals(pcrMap.getRemark()));}).map(Map.Entry::getKey).collect(Collectors.toList());}Map<Integer, FofoStore> finalFofoStoresMap2 = fofoStoresMap;List<Integer> validL1FofoIds = l1FofoIds.stream().filter(fofoId -> {FofoStore store = finalFofoStoresMap2.get(fofoId);if (store == null || store.isInternal()) return false;return ActivationType.ACTIVE.equals(store.getActivationType())|| ActivationType.REVIVAL.equals(store.getActivationType());}).collect(Collectors.toList());// Categorize L1 partners — same logic as L1 processingSet<Integer> planTodayPartners = new HashSet<>();Set<Integer> carryForwardPartners = new HashSet<>();Set<Integer> untouchedPartners = new HashSet<>();Set<Integer> zeroBillingPartners = new HashSet<>();Set<Integer> futurePlanPartners = new HashSet<>();Set<Integer> normalPartners = new HashSet<>();Set<Integer> revivalPartners = new HashSet<>();for (Integer fofoId : validL1FofoIds) {int rank = allCollectionRankMap.getOrDefault(fofoId, 5);boolean hasZeroBilling = !allMtdBilledFofoIds.contains(fofoId);FofoStore store = finalFofoStoresMap2.get(fofoId);if (store != null && ActivationType.REVIVAL.equals(store.getActivationType())) {revivalPartners.add(fofoId);}if (rank == 1) {planTodayPartners.add(fofoId);} else if (rank == 2) {carryForwardPartners.add(fofoId);} else if (hasZeroBilling) {zeroBillingPartners.add(fofoId);} else if (rank == 3) {untouchedPartners.add(fofoId);} else if (rank == 4) {futurePlanPartners.add(fofoId);} else {normalPartners.add(fofoId);}}// Set L1 breakdown fields on L2 modell2Model.setPlanToday(planTodayPartners.size());l2Model.setCarryForward(carryForwardPartners.size());l2Model.setZeroBilling(zeroBillingPartners.size());l2Model.setUntouched(untouchedPartners.size());l2Model.setFuturePlan(futurePlanPartners.size());l2Model.setNormal(normalPartners.size());l2Model.setRevival(revivalPartners.size());long l1OwnTarget = planTodayPartners.size() + carryForwardPartners.size()+ zeroBillingPartners.size() + untouchedPartners.size();// Today Target = own L1 target + L2 escalationl2Model.setTodayTargetOfCall(l2TargetFofoIds.size() + l1OwnTarget);// Moved to Future from L1 partnersList<PartnerCollectionRemark> todayRemarks = remarksByAuthId.getOrDefault(l2AuthId, Collections.emptyList());Set<Integer> todayRemarkedFofoIds = todayRemarks.stream().map(PartnerCollectionRemark::getFofoId).collect(Collectors.toSet());long movedToFuture = futurePlanPartners.stream().filter(todayRemarkedFofoIds::contains).count();l2Model.setMovedToFuture(movedToFuture);} else {// Pure L2 (not also L1) — target is only L2 escalationl2Model.setTodayTargetOfCall(l2TargetFofoIds.size());}// Value Achieved = All distinct partners called today (from pre-fetched call logs)long[] l2CallStats = getCallStatsFromLogs(callLogsByAuthId.get((long) l2AuthId), mobileToFofoIdMap);l2Model.setValueTargetAchieved(l2CallStats[0]);l2Model.setTotalRecordingCalls(l2CallStats[1]);l2Model.setUniqueRecordingCalls(l2CallStats[2]);l2Model.setOutOfSequenceCount(outOfSequenceCountByAuthId.getOrDefault(l2AuthId, 0L));rbmCallTargetModels.add(l2Model);}// Process L3 RBMs (escalated ticket logic with categorization)for (int l3AuthId : l3AuthIds) {AuthUser authUser = authUserMap.get(l3AuthId);if (authUser == null) {continue;}List<Integer> l3FofoIdList = l3AuthIdToFofoIds.getOrDefault(l3AuthId, Collections.emptyList());// For L3, use unique fofoIds with RBM_L3_ESCALATION remark as targetSet<Integer> l3TargetFofoIds = new HashSet<>(l3FofoIdList);RbmCallTargetModel l3Model = new RbmCallTargetModel();l3Model.setAuthId(l3AuthId);l3Model.setRbmName(authUser.getFullName() + " (L3)");l3Model.setL3Position(true);l3Model.setL3CallingList(l3TargetFofoIds.size());// Partner count = total assigned partners (excluding internal)Map<Integer, FofoStore> finalFofoStoresMapL3 = fofoStoresMap;List<Integer> l3AssignedFofoIds = rbmToFofoIdsMap.getOrDefault(l3AuthId, Collections.emptyList());long l3ExternalPartnerCount = l3AssignedFofoIds.stream().filter(fofoId -> {FofoStore store = finalFofoStoresMapL3.get(fofoId);return store != null && !store.isInternal();}).count();l3Model.setPartnerCount(l3ExternalPartnerCount);// L3 Target = partners with RBM_L3_ESCALATION as latest remarkl3Model.setTodayTargetOfCall(l3TargetFofoIds.size());// Value Achieved = All distinct partners called today (from pre-fetched call logs)long[] l3CallStats = getCallStatsFromLogs(callLogsByAuthId.get((long) l3AuthId), mobileToFofoIdMap);l3Model.setValueTargetAchieved(l3CallStats[0]);l3Model.setTotalRecordingCalls(l3CallStats[1]);l3Model.setUniqueRecordingCalls(l3CallStats[2]);l3Model.setOutOfSequenceCount(outOfSequenceCountByAuthId.getOrDefault(l3AuthId, 0L));rbmCallTargetModels.add(l3Model);}// Group models by escalation levelMap<Integer, RbmCallTargetModel> l3ModelsByAuthId = new HashMap<>();Map<Integer, RbmCallTargetModel> l2ModelsByAuthId = new HashMap<>();Map<Integer, RbmCallTargetModel> l1ModelsByAuthId = new HashMap<>();for (RbmCallTargetModel m : rbmCallTargetModels) {if (m.isL3Position()) {l3ModelsByAuthId.put(m.getAuthId(), m);} else if (m.isL2Position()) {l2ModelsByAuthId.put(m.getAuthId(), m);} else {l1ModelsByAuthId.put(m.getAuthId(), m);}}// Build partner-based hierarchy using partner_position table// Map positionId -> Position for quick lookupMap<Integer, Position> positionByIdMap = allRbmPositions.stream().collect(Collectors.toMap(Position::getId, p -> p, (a, b) -> a));// Get all RBM position IDs and fetch their partner assignmentsList<Integer> allRbmPositionIds = allRbmPositions.stream().map(Position::getId).collect(Collectors.toList());List<com.spice.profitmandi.dao.entity.cs.PartnerPosition> allPartnerPositions =partnerPositionRepository.selectByPositionIds(allRbmPositionIds);// Build partnerId -> map of escalationType -> Set<authUserIds>// This tells us for each partner, who is their L1, L2, L3Map<Integer, Map<EscalationType, Set<Integer>>> partnerToAuthByLevel = new HashMap<>();for (com.spice.profitmandi.dao.entity.cs.PartnerPosition pp : allPartnerPositions) {Position pos = positionByIdMap.get(pp.getPositionId());if (pos != null && pos.getEscalationType() != null) {partnerToAuthByLevel.computeIfAbsent(pp.getFofoId(), k -> new HashMap<>()).computeIfAbsent(pos.getEscalationType(), k -> new HashSet<>()).add(pos.getAuthUserId());}}// Build L2 -> L1 team map based on shared partners// If an L1 and L2 share partners (L1 at L1 level, L2 at L2 level), that L1 belongs under that L2Map<Integer, List<RbmCallTargetModel>> l2TeamMap = new LinkedHashMap<>();for (RbmCallTargetModel l2Model : l2ModelsByAuthId.values()) {l2TeamMap.put(l2Model.getAuthId(), new ArrayList<>());}// Build L3 -> L2 team map based on shared partnersMap<Integer, List<RbmCallTargetModel>> l3TeamMap = new LinkedHashMap<>();for (RbmCallTargetModel l3Model : l3ModelsByAuthId.values()) {l3TeamMap.put(l3Model.getAuthId(), new ArrayList<>());}// For each L1, find which L2 shares the most partners -> that's their L2Set<Integer> addedL1AuthIds = new HashSet<>();for (RbmCallTargetModel l1Model : l1ModelsByAuthId.values()) {Map<Integer, Integer> l2SharedCount = new HashMap<>(); // l2AuthId -> shared partner countfor (Map.Entry<Integer, Map<EscalationType, Set<Integer>>> entry : partnerToAuthByLevel.entrySet()) {Map<EscalationType, Set<Integer>> levelMap = entry.getValue();Set<Integer> l1IdsForPartner = levelMap.getOrDefault(EscalationType.L1, Collections.emptySet());Set<Integer> l2AuthIdsForPartner = levelMap.getOrDefault(EscalationType.L2, Collections.emptySet());if (l1IdsForPartner.contains(l1Model.getAuthId())) {for (int l2Id : l2AuthIdsForPartner) {if (l2ModelsByAuthId.containsKey(l2Id) && l2Id != l1Model.getAuthId()) {l2SharedCount.merge(l2Id, 1, Integer::sum);}}}}// Assign L1 to the L2 with most shared partnersif (!l2SharedCount.isEmpty()) {int bestL2 = l2SharedCount.entrySet().stream().max(Map.Entry.comparingByValue()).get().getKey();l2TeamMap.get(bestL2).add(l1Model);addedL1AuthIds.add(l1Model.getAuthId());}}// For each L2, find which L3 shares the most partners -> that's their L3Set<Integer> addedL2AuthIds = new HashSet<>();for (RbmCallTargetModel l2Model : l2ModelsByAuthId.values()) {Map<Integer, Integer> l3SharedCount = new HashMap<>();for (Map.Entry<Integer, Map<EscalationType, Set<Integer>>> entry : partnerToAuthByLevel.entrySet()) {Map<EscalationType, Set<Integer>> levelMap = entry.getValue();Set<Integer> l2AuthIdsForPartner = levelMap.getOrDefault(EscalationType.L2, Collections.emptySet());Set<Integer> l3AuthIdsForPartner = levelMap.getOrDefault(EscalationType.L3, Collections.emptySet());if (l2AuthIdsForPartner.contains(l2Model.getAuthId())) {for (int l3Id : l3AuthIdsForPartner) {if (l3ModelsByAuthId.containsKey(l3Id)) {l3SharedCount.merge(l3Id, 1, Integer::sum);}}}}if (!l3SharedCount.isEmpty()) {int bestL3 = l3SharedCount.entrySet().stream().max(Map.Entry.comparingByValue()).get().getKey();l3TeamMap.get(bestL3).add(l2Model);addedL2AuthIds.add(l2Model.getAuthId());}}// For L1s not mapped to any L2, check if they map directly to an L3 via shared partnersMap<Integer, List<RbmCallTargetModel>> l3DirectL1Map = new LinkedHashMap<>();for (RbmCallTargetModel l3Model : l3ModelsByAuthId.values()) {l3DirectL1Map.put(l3Model.getAuthId(), new ArrayList<>());}for (RbmCallTargetModel l1Model : l1ModelsByAuthId.values()) {if (addedL1AuthIds.contains(l1Model.getAuthId())) continue;Map<Integer, Integer> l3SharedCount = new HashMap<>();for (Map.Entry<Integer, Map<EscalationType, Set<Integer>>> entry : partnerToAuthByLevel.entrySet()) {Map<EscalationType, Set<Integer>> levelMap = entry.getValue();Set<Integer> l1IdsForPartner = levelMap.getOrDefault(EscalationType.L1, Collections.emptySet());Set<Integer> l3AuthIdsForPartner = levelMap.getOrDefault(EscalationType.L3, Collections.emptySet());if (l1IdsForPartner.contains(l1Model.getAuthId())) {for (int l3Id : l3AuthIdsForPartner) {if (l3ModelsByAuthId.containsKey(l3Id)) {l3SharedCount.merge(l3Id, 1, Integer::sum);}}}}if (!l3SharedCount.isEmpty()) {int bestL3 = l3SharedCount.entrySet().stream().max(Map.Entry.comparingByValue()).get().getKey();l3DirectL1Map.get(bestL3).add(l1Model);addedL1AuthIds.add(l1Model.getAuthId());}}// Build sorted result: L3 -> L2 -> L1 (with direct L1s under L3 if no L2)List<RbmCallTargetModel> sortedModels = new ArrayList<>();List<RbmCallTargetModel> l3Sorted = new ArrayList<>(l3ModelsByAuthId.values());l3Sorted.sort(Comparator.comparing(RbmCallTargetModel::getRbmName));for (RbmCallTargetModel l3Model : l3Sorted) {sortedModels.add(l3Model);List<RbmCallTargetModel> l2Team = l3TeamMap.getOrDefault(l3Model.getAuthId(), Collections.emptyList());l2Team.sort(Comparator.comparing(RbmCallTargetModel::getRbmName));for (RbmCallTargetModel l2Model : l2Team) {sortedModels.add(l2Model);List<RbmCallTargetModel> l1Team = l2TeamMap.getOrDefault(l2Model.getAuthId(), Collections.emptyList());l1Team.sort(Comparator.comparing(RbmCallTargetModel::getRbmName));sortedModels.addAll(l1Team);}// Add L1s that report directly to this L3 (no L2 in between)List<RbmCallTargetModel> directL1Team = l3DirectL1Map.getOrDefault(l3Model.getAuthId(), Collections.emptyList());directL1Team.sort(Comparator.comparing(RbmCallTargetModel::getRbmName));sortedModels.addAll(directL1Team);}// Add L2s not mapped to any L3List<RbmCallTargetModel> unmappedL2 = new ArrayList<>();for (RbmCallTargetModel l2Model : l2ModelsByAuthId.values()) {if (!addedL2AuthIds.contains(l2Model.getAuthId())) {unmappedL2.add(l2Model);}}unmappedL2.sort(Comparator.comparing(RbmCallTargetModel::getRbmName));for (RbmCallTargetModel l2Model : unmappedL2) {sortedModels.add(l2Model);List<RbmCallTargetModel> team = l2TeamMap.getOrDefault(l2Model.getAuthId(), Collections.emptyList());team.sort(Comparator.comparing(RbmCallTargetModel::getRbmName));sortedModels.addAll(team);}// Add any L1s not mapped to any L2 or L3List<RbmCallTargetModel> unmappedL1 = new ArrayList<>();for (RbmCallTargetModel m : l1ModelsByAuthId.values()) {if (!addedL1AuthIds.contains(m.getAuthId())) {unmappedL1.add(m);}}unmappedL1.sort(Comparator.comparing(RbmCallTargetModel::getRbmName));sortedModels.addAll(unmappedL1);LOGGER.info("RBM Call Target - TOTAL TIME: {}ms, RBM count: {}", System.currentTimeMillis() - methodStart, sortedModels.size());return sortedModels;}@Overridepublic List<OutOfSequenceDetailModel> getOutOfSequenceDetails(int authId) {LocalDate today = LocalDate.now();LocalDateTime start = today.atStartOfDay();LocalDateTime end = today.plusDays(1).atStartOfDay();List<RbmCallSequenceLog> logs =rbmCallSequenceLogRepository.selectByAuthIdAndDateRange(authId, start, end);Map<Integer, RbmCallSequenceLog> uniqueOosLogsByFofoId = new LinkedHashMap<>();for (RbmCallSequenceLog log : logs) {if (log.isOutOfSequence()) {// Keep only the first occurrence per fofoId (latest entry since ordered by id DESC)uniqueOosLogsByFofoId.putIfAbsent(log.getFofoId(), log);}}if (uniqueOosLogsByFofoId.isEmpty()) {return Collections.emptyList();}Set<Integer> fofoIds = uniqueOosLogsByFofoId.keySet();Map<Integer, CustomRetailer> retailerMap = Collections.emptyMap();try {retailerMap = retailerService.getFofoRetailers(new ArrayList<>(fofoIds));} catch (ProfitMandiBusinessException e) {LOGGER.error("Error fetching fofo stores", e);}DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("hh:mm a");List<OutOfSequenceDetailModel> result = new ArrayList<>();for (RbmCallSequenceLog log : uniqueOosLogsByFofoId.values()) {CustomRetailer retailer = retailerMap.get(log.getFofoId());String partyName = retailer != null? retailer.getBusinessName(): "Unknown (" + log.getFofoId() + ")";String code = retailer != null? retailer.getCode(): "-";String time = log.getCreateTimestamp() != null? log.getCreateTimestamp().format(timeFormatter): "-";result.add(new OutOfSequenceDetailModel(partyName, code, time));}return result;}@Overridepublic List<CalledPartnerDetailModel> getCalledPartnerDetails(int authId) throws ProfitMandiBusinessException {return getCalledPartnerDetails(authId, LocalDate.now());}@Overridepublic List<CalledPartnerDetailModel> getCalledPartnerDetails(int authId, LocalDate date) throws ProfitMandiBusinessException {// Get all call logs for this auth user on this dateLOGGER.info("getCalledPartnerDetails: authId={}, date={}", authId, date);List<AgentCallLog> callLogs = agentCallLogRepository.findByAuthIdAndDate(authId, date);LOGGER.info("Found {} call logs for authId={} on date={}", callLogs.size(), authId, date);if (callLogs.isEmpty()) {return Collections.emptyList();}// Build a map of normalized customer number -> fofoIdMap<String, Integer> customerToFofoIdMap = new HashMap<>();Set<String> normalizedNumbers = new HashSet<>();for (AgentCallLog callLog : callLogs) {String customerNumber = callLog.getCustomerNumber();if (customerNumber != null) {String normalized = customerNumber.startsWith("+91") ? customerNumber.substring(3) : customerNumber;normalizedNumbers.add(normalized);}}// For each normalized number, find fofoId from retailer_contact first, then addressfor (String mobile : normalizedNumbers) {Integer fofoId = findFofoIdByMobile(mobile);if (fofoId != null) {customerToFofoIdMap.put(mobile, fofoId);}}// Get unique fofoIds for retailer lookupSet<Integer> fofoIds = new HashSet<>(customerToFofoIdMap.values());Map<Integer, CustomRetailer> retailerMap = Collections.emptyMap();if (!fofoIds.isEmpty()) {try {retailerMap = retailerService.getFofoRetailers(new ArrayList<>(fofoIds));} catch (ProfitMandiBusinessException e) {LOGGER.error("Error fetching fofo stores", e);}}// Get today's remarks for these fofoIdsMap<Integer, List<PartnerCollectionRemark>> fofoRemarkMap = new HashMap<>();if (!fofoIds.isEmpty()) {List<PartnerCollectionRemark> todayRemarks = partnerCollectionRemarkRepository.selectAllByFofoIdsOnDate(new ArrayList<>(fofoIds), date);for (PartnerCollectionRemark remark : todayRemarks) {fofoRemarkMap.computeIfAbsent(remark.getFofoId(), k -> new ArrayList<>()).add(remark);}}DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("hh:mm a");DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy hh:mm a");List<CalledPartnerDetailModel> result = new ArrayList<>();for (com.spice.profitmandi.dao.entity.cs.AgentCallLog callLog : callLogs) {String customerNumber = callLog.getCustomerNumber();if (customerNumber == null) {continue;}String normalized = customerNumber.startsWith("+91") ? customerNumber.substring(3) : customerNumber;Integer fofoId = customerToFofoIdMap.get(normalized);String partyName = "Unknown";String code = "-";if (fofoId != null) {CustomRetailer retailer = retailerMap.get(fofoId);if (retailer != null) {partyName = retailer.getBusinessName();code = retailer.getCode();} else {partyName = "Unknown (" + fofoId + ")";}} else {partyName = "Unknown (" + normalized + ")";}// Get remark if availableString remarkValue = "-";String messageValue = "-";String remarkTime = "-";if (fofoId != null && fofoRemarkMap.containsKey(fofoId)) {List<PartnerCollectionRemark> remarks = fofoRemarkMap.get(fofoId);if (!remarks.isEmpty()) {PartnerCollectionRemark remark = remarks.get(0);remarkValue = remark.getRemark() != null ? remark.getRemark().getValue() : "-";messageValue = remark.getMessage() != null ? remark.getMessage() : "-";remarkTime = remark.getCreateTimestamp() != null ? remark.getCreateTimestamp().format(timeFormatter) : "-";}}// Build call log dataString recordingUrl = callLog.getRecordingUrl();String callStatus = callLog.getCallStatus();String callDuration = callLog.getCallDuration();String callDateTime = null;if (callLog.getCallDate() != null && callLog.getCallTime() != null) {LocalDateTime callDateTimeObj = LocalDateTime.of(callLog.getCallDate(), callLog.getCallTime());callDateTime = callDateTimeObj.format(dateTimeFormatter);}result.add(new CalledPartnerDetailModel(partyName, code, remarkValue, messageValue, remarkTime,recordingUrl, callStatus, callDuration, callDateTime));}return result;}private Integer findFofoIdByMobile(String mobile) {// First check retailer_contactList<RetailerContact> contacts = retailerContactRepository.selectByMobile(mobile);if (contacts != null && !contacts.isEmpty()) {return contacts.get(0).getFofoId();}// Fallback to user.addressList<Address> addresses = addressRepository.selectAllByPhoneNumber(mobile);if (addresses != null && !addresses.isEmpty()) {return addresses.get(0).getRetaierId();}return null;}private List<CalledPartnerDetailModel> buildCalledPartnerResult(List<PartnerCollectionRemark> allRemarks) {if (allRemarks.isEmpty()) {return Collections.emptyList();}// Get unique fofoIds for retailer lookupSet<Integer> fofoIds = allRemarks.stream().map(PartnerCollectionRemark::getFofoId).collect(Collectors.toSet());Map<Integer, CustomRetailer> retailerMap = Collections.emptyMap();try {retailerMap = retailerService.getFofoRetailers(new ArrayList<>(fofoIds));} catch (ProfitMandiBusinessException e) {LOGGER.error("Error fetching fofo stores", e);}// Fetch call logs for remarks that have agentCallLogIdMap<Long, com.spice.profitmandi.dao.entity.cs.AgentCallLog> callLogMap = new HashMap<>();try {List<Long> callLogIds = allRemarks.stream().filter(r -> r.getAgentCallLogId() > 0).map(PartnerCollectionRemark::getAgentCallLogId).collect(Collectors.toList());if (!callLogIds.isEmpty()) {List<com.spice.profitmandi.dao.entity.cs.AgentCallLog> callLogs = agentCallLogRepository.findByIds(callLogIds);if (callLogs != null) {callLogMap = callLogs.stream().collect(Collectors.toMap(com.spice.profitmandi.dao.entity.cs.AgentCallLog::getId, c -> c, (a, b) -> a));}}} catch (Exception e) {LOGGER.error("Error fetching call logs by ids", e);}DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("hh:mm a");DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy hh:mm a");List<CalledPartnerDetailModel> result = new ArrayList<>();for (PartnerCollectionRemark remark : allRemarks) {CustomRetailer retailer = retailerMap.get(remark.getFofoId());String partyName = retailer != null? retailer.getBusinessName(): "Unknown (" + remark.getFofoId() + ")";String code = retailer != null? retailer.getCode(): "-";String remarkValue = remark.getRemark() != null? remark.getRemark().getValue(): "-";String messageValue = remark.getMessage() != null? remark.getMessage(): "-";String time = remark.getCreateTimestamp() != null? remark.getCreateTimestamp().format(timeFormatter): "-";// Get call log data if availableString recordingUrl = null;String callStatus = null;String callDuration = null;String callDateTime = null;try {if (remark.getAgentCallLogId() > 0 && callLogMap.containsKey(remark.getAgentCallLogId())) {com.spice.profitmandi.dao.entity.cs.AgentCallLog callLog = callLogMap.get(remark.getAgentCallLogId());recordingUrl = callLog.getRecordingUrl();callStatus = callLog.getCallStatus();callDuration = callLog.getCallDuration();if (callLog.getCallDate() != null && callLog.getCallTime() != null) {LocalDateTime callDateTimeObj = LocalDateTime.of(callLog.getCallDate(), callLog.getCallTime());callDateTime = callDateTimeObj.format(dateTimeFormatter);}}} catch (Exception e) {LOGGER.error("Error processing call log for remark id: {}", remark.getId(), e);}result.add(new CalledPartnerDetailModel(partyName, code, remarkValue, messageValue, time,recordingUrl, callStatus, callDuration, callDateTime));}return result;}@Overridepublic List<List<String>> getRbmCallTargetRawDataByAuthId(int authId) throws Exception {List<List<String>> rows = new ArrayList<>();// Get auth userList<AuthUser> authUsers = authRepository.selectByIds(Collections.singletonList(authId));if (authUsers.isEmpty()) {return rows;}AuthUser authUser = authUsers.get(0);// Get positions to determine if L2List<Position> positions = positionRepository.selectPositionByAuthIds(Collections.singletonList(authId));boolean isL2 = positions.stream().anyMatch(p -> ProfitMandiConstants.TICKET_CATEGORY_RBM == p.getCategoryId()&& EscalationType.L2.equals(p.getEscalationType()));LocalDateTime startDate = LocalDate.now().atStartOfDay();LocalDate firstOfMonth = LocalDate.now().withDayOfMonth(1);LocalDate endOfMonth = LocalDate.now().withDayOfMonth(LocalDate.now().lengthOfMonth()).plusDays(1);// Get fofo IDs from mtdBillingData (same source as Partner Count in getRbmCallTargetModels)List<RbmWeeklyBillingModel> mtdBillingData = getWeeklyBillingDataForMonth(firstOfMonth, endOfMonth);List<Integer> fofoIdList;if (isL2) {// L2: get fofo IDs from escalated tickets (same as getRbmCallTargetModels)List<Ticket> escalatedTickets = ticketRepository.selectOpenEscalatedTicketsByAuthIds(Collections.singletonList(authId));fofoIdList = escalatedTickets.stream().filter(t -> t.getL2AuthUser() == authId|| t.getL3AuthUser() == authId|| t.getL4AuthUser() == authId|| t.getL5AuthUser() == authId).map(Ticket::getFofoId).distinct().collect(Collectors.toList());} else {// L1: get fofo IDs from mtdBillingData with isTargetedPartner (same as Partner Count)fofoIdList = mtdBillingData.stream().filter(RbmWeeklyBillingModel::isTargetedPartner).filter(m -> m.getAuthId() == authId).map(RbmWeeklyBillingModel::getFofoId).distinct().collect(Collectors.toList());}if (fofoIdList.isEmpty()) {return rows;}// MTD billed fofoIds for zero billing checkSet<Integer> mtdBilledFofoIds = mtdBillingData.stream().filter(RbmWeeklyBillingModel::isMtdBilled).map(RbmWeeklyBillingModel::getFofoId).collect(Collectors.toSet());// Collection rank map for status calculationMap<Integer, Integer> collectionRankMap = new HashMap<>();try {collectionRankMap = partnerCollectionService.getCollectionRankMap(fofoIdList, startDate);} catch (ProfitMandiBusinessException e) {LOGGER.error("Error fetching collection rank map", e);}// Resolve partner names/codesMap<Integer, CustomRetailer> retailerMap = Collections.emptyMap();if (!fofoIdList.isEmpty()) {try {retailerMap = retailerService.getFofoRetailers(fofoIdList);} catch (ProfitMandiBusinessException e) {LOGGER.error("Error fetching fofo retailers for raw data", e);}}String rbmName = authUser.getFullName() + (isL2 ? " (L2)" : "");// Build rows for ALL partners (same count as Partner Count)for (Integer fofoId : fofoIdList) {// Default to rank 5 (Normal) for partners without collection planint rank = collectionRankMap.getOrDefault(fofoId, 5);boolean hasZeroBilling = !mtdBilledFofoIds.contains(fofoId);// Status assignment with same priority as getRbmCallTargetModelsString status;if (rank == 1) {status = "Plan Today";} else if (rank == 2) {status = "Carry Forward";} else if (hasZeroBilling) {status = "Zero Billing";} else if (rank == 3) {status = "Untouched";} else if (rank == 4) {status = "Future Plan";} else {status = "Normal";}CustomRetailer retailer = retailerMap.get(fofoId);String partnerName = retailer != null ? retailer.getBusinessName() : "Unknown (" + fofoId + ")";String partnerCode = retailer != null ? retailer.getCode() : "-";rows.add(Arrays.asList(partnerName, partnerCode, status, rbmName));}return rows;}/*** Get count of distinct partners called today based on call logs.* Maps customerNumber from call log to fofoId using retailer_contact and address.* If same fofoId is called multiple times, counts only once.* Numbers without fofoId mapping are also counted (by distinct customer number).** @param authId the RBM auth ID* @return count of distinct partners/numbers called today*/public long getCalledCountFromCallLogs(long authId) {return getCalledCountFromCallLogs(authId, LocalDate.now());}public long getCalledCountFromCallLogs(long authId, LocalDate date) {return getCallStats(authId, date)[0];}/*** Batch-builds a mobile (normalized, +91 stripped) -> fofoId mapping for all the given mobiles* in just two queries (retailer_contact, then address fallback for unmapped numbers).* Replaces the previous per-call-log findFofoIdByMobile() N+1 lookups.*/private Map<String, Integer> buildMobileToFofoIdMap(Set<String> normalizedMobiles) {Map<String, Integer> result = new HashMap<>();if (normalizedMobiles == null || normalizedMobiles.isEmpty()) {return result;}List<String> mobilesList = new ArrayList<>(normalizedMobiles);// First pass: retailer_contactList<RetailerContact> contacts = retailerContactRepository.selectByMobiles(mobilesList);for (RetailerContact rc : contacts) {// Keep the first fofoId we see per mobile (matches old single-mobile behavior of get(0))result.putIfAbsent(rc.getMobile(), rc.getFofoId());}// Second pass: address fallback for mobiles not yet mappedList<String> unmapped = mobilesList.stream().filter(m -> !result.containsKey(m)).collect(Collectors.toList());if (!unmapped.isEmpty()) {List<Address> addresses = addressRepository.selectAllByPhoneNumbers(unmapped);for (Address addr : addresses) {result.putIfAbsent(addr.getPhoneNumber(), addr.getRetaierId());}}return result;}/*** In-memory variant of getCallStats() that uses pre-fetched call logs and mobile→fofoId mapping.* Used by the batch-fetched RBM Call Target loop to avoid N+1 query patterns.*/private long[] getCallStatsFromLogs(List<AgentCallLog> callLogs, Map<String, Integer> mobileToFofoIdMap) {if (callLogs == null || callLogs.isEmpty()) {return new long[]{0, 0, 0};}Set<Integer> calledFofoIds = new HashSet<>();Set<String> calledNumbersWithoutFofoId = new HashSet<>();long totalRecordingCalls = 0;Set<String> uniqueRecordingNumbers = new HashSet<>();for (AgentCallLog callLog : callLogs) {String customerNumber = callLog.getCustomerNumber();if (customerNumber != null) {String normalized = customerNumber.startsWith("+91") ? customerNumber.substring(3) : customerNumber;Integer fofoId = mobileToFofoIdMap.get(normalized);if (fofoId != null) {calledFofoIds.add(fofoId);} else {calledNumbersWithoutFofoId.add(normalized);}if (callLog.getRecordingUrl() != null && !callLog.getRecordingUrl().isEmpty()&& !"None".equalsIgnoreCase(callLog.getRecordingUrl())) {totalRecordingCalls++;uniqueRecordingNumbers.add(normalized);}}}long calledCount = calledFofoIds.size() + calledNumbersWithoutFofoId.size();return new long[]{calledCount, totalRecordingCalls, uniqueRecordingNumbers.size()};}/*** Returns call stats: [0] = called count, [1] = total recording calls, [2] = unique recording calls*/public long[] getCallStats(long authId, LocalDate date) {List<AgentCallLog> callLogs = agentCallLogRepository.findByAuthIdAndDate(authId, date);if (callLogs == null || callLogs.isEmpty()) {return new long[]{0, 0, 0};}Set<Integer> calledFofoIds = new HashSet<>();Set<String> calledNumbersWithoutFofoId = new HashSet<>();long totalRecordingCalls = 0;Set<String> uniqueRecordingNumbers = new HashSet<>();for (AgentCallLog callLog : callLogs) {String customerNumber = callLog.getCustomerNumber();if (customerNumber != null) {// Normalize the phone number (remove +91 prefix if present)String normalized = customerNumber.startsWith("+91") ? customerNumber.substring(3) : customerNumber;// Find fofoId from retailer_contact or addressInteger fofoId = findFofoIdByMobile(normalized);if (fofoId != null) {calledFofoIds.add(fofoId);} else {// Number not found in retailer_contact or address, count by distinct numbercalledNumbersWithoutFofoId.add(normalized);}// Count calls with recordingsif (callLog.getRecordingUrl() != null && !callLog.getRecordingUrl().isEmpty()&& !"None".equalsIgnoreCase(callLog.getRecordingUrl())) {totalRecordingCalls++;uniqueRecordingNumbers.add(normalized);}}}// Total called = distinct fofoIds + distinct numbers without fofoId mappinglong calledCount = calledFofoIds.size() + calledNumbersWithoutFofoId.size();return new long[]{calledCount, totalRecordingCalls, uniqueRecordingNumbers.size()};}@Overridepublic List<List<String>> getAllCallDataByDate(LocalDate date) throws Exception {List<List<String>> rows = new ArrayList<>();// Add header rowrows.add(Arrays.asList("RBM Name", "Partner Name", "Code", "Remark", "Call Status", "Call Duration", "Call Date Time", "Recording URL"));// Get all call logs for the dateList<AgentCallLog> allCallLogs = agentCallLogRepository.findAllByDate(date);LOGGER.info("getAllCallDataByDate: Found {} call logs for date {}", allCallLogs.size(), date);if (allCallLogs.isEmpty()) {return rows;}// Get unique authIds from call logsSet<Long> authIds = allCallLogs.stream().map(AgentCallLog::getAuthId).collect(Collectors.toSet());// Get auth users for RBM namesList<Integer> authIdInts = authIds.stream().map(Long::intValue).collect(Collectors.toList());Map<Integer, AuthUser> authUserMap = authRepository.selectByIds(authIdInts).stream().collect(Collectors.toMap(AuthUser::getId, au -> au, (a, b) -> a));// Build a map of normalized customer number -> fofoIdMap<String, Integer> customerToFofoIdMap = new HashMap<>();Set<String> normalizedNumbers = new HashSet<>();for (AgentCallLog callLog : allCallLogs) {String customerNumber = callLog.getCustomerNumber();if (customerNumber != null) {String normalized = customerNumber.startsWith("+91") ? customerNumber.substring(3) : customerNumber;normalizedNumbers.add(normalized);}}// For each normalized number, find fofoIdfor (String mobile : normalizedNumbers) {Integer fofoId = findFofoIdByMobile(mobile);if (fofoId != null) {customerToFofoIdMap.put(mobile, fofoId);}}// Get unique fofoIds for retailer lookupSet<Integer> fofoIds = new HashSet<>(customerToFofoIdMap.values());Map<Integer, CustomRetailer> retailerMap = Collections.emptyMap();if (!fofoIds.isEmpty()) {try {retailerMap = retailerService.getFofoRetailers(new ArrayList<>(fofoIds));} catch (ProfitMandiBusinessException e) {LOGGER.error("Error fetching fofo stores", e);}}// Get remarks for these fofoIds on this dateMap<Integer, List<PartnerCollectionRemark>> fofoRemarkMap = new HashMap<>();if (!fofoIds.isEmpty()) {List<PartnerCollectionRemark> dateRemarks = partnerCollectionRemarkRepository.selectAllByFofoIdsOnDate(new ArrayList<>(fofoIds), date);for (PartnerCollectionRemark remark : dateRemarks) {fofoRemarkMap.computeIfAbsent(remark.getFofoId(), k -> new ArrayList<>()).add(remark);}}DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy hh:mm a");// Build rowsfor (AgentCallLog callLog : allCallLogs) {String customerNumber = callLog.getCustomerNumber();if (customerNumber == null) {continue;}// Get RBM NameAuthUser authUser = authUserMap.get((int) callLog.getAuthId());String rbmName = authUser != null ? authUser.getFullName() : "Unknown";String normalized = customerNumber.startsWith("+91") ? customerNumber.substring(3) : customerNumber;Integer fofoId = customerToFofoIdMap.get(normalized);String partyName = "Unknown";String code = "-";if (fofoId != null) {CustomRetailer retailer = retailerMap.get(fofoId);if (retailer != null) {partyName = retailer.getBusinessName();code = retailer.getCode();} else {partyName = "Unknown (" + fofoId + ")";}} else {partyName = "Unknown (" + normalized + ")";}// Get remark if availableString remarkValue = "-";if (fofoId != null && fofoRemarkMap.containsKey(fofoId)) {List<PartnerCollectionRemark> remarks = fofoRemarkMap.get(fofoId);if (!remarks.isEmpty()) {PartnerCollectionRemark remark = remarks.get(0);remarkValue = remark.getRemark() != null ? remark.getRemark().getValue() : "-";if (remark.getMessage() != null && !remark.getMessage().isEmpty()) {remarkValue = remarkValue + " - " + remark.getMessage();}}}// Call status from customerStatus fieldString callStatus = callLog.getCustomerStatus() != null ? callLog.getCustomerStatus() : "-";String callDuration = callLog.getCallDuration() != null ? callLog.getCallDuration() : "-";String callDateTime = "-";if (callLog.getCallDate() != null && callLog.getCallTime() != null) {LocalDateTime callDateTimeObj = LocalDateTime.of(callLog.getCallDate(), callLog.getCallTime());callDateTime = callDateTimeObj.format(dateTimeFormatter);}String recordingUrl = callLog.getRecordingUrl() != null ? callLog.getRecordingUrl() : "-";rows.add(Arrays.asList(rbmName, partyName, code, remarkValue, callStatus, callDuration, callDateTime, recordingUrl));}return rows;}}