Subversion Repositories SmartDukaan

Rev

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

Rev 35454 Rev 35631
Line 1... Line 1...
1
package com.spice.profitmandi.service;
1
package com.spice.profitmandi.service;
2
 
2
 
-
 
3
import com.spice.profitmandi.common.enumuration.ActivationType;
-
 
4
import com.spice.profitmandi.common.exception.ProfitMandiBusinessException;
-
 
5
import com.spice.profitmandi.common.model.CustomRetailer;
3
import com.spice.profitmandi.common.model.ProfitMandiConstants;
6
import com.spice.profitmandi.common.model.ProfitMandiConstants;
-
 
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;
-
 
10
import com.spice.profitmandi.dao.entity.cs.Position;
-
 
11
import com.spice.profitmandi.dao.entity.cs.Ticket;
-
 
12
import com.spice.profitmandi.dao.entity.fofo.FofoStore;
4
import com.spice.profitmandi.dao.entity.fofo.MonthlyTarget;
13
import com.spice.profitmandi.dao.entity.fofo.MonthlyTarget;
5
import com.spice.profitmandi.dao.entity.inventory.RbmAchievements;
14
import com.spice.profitmandi.dao.entity.inventory.RbmAchievements;
6
import com.spice.profitmandi.dao.entity.inventory.RbmTargets;
15
import com.spice.profitmandi.dao.entity.inventory.RbmTargets;
-
 
16
import com.spice.profitmandi.dao.enumuration.auth.CollectionRemark;
-
 
17
import com.spice.profitmandi.dao.enumuration.cs.EscalationType;
7
import com.spice.profitmandi.dao.model.*;
18
import com.spice.profitmandi.dao.model.*;
-
 
19
import com.spice.profitmandi.dao.repository.auth.AuthRepository;
-
 
20
import com.spice.profitmandi.dao.repository.auth.PartnerCollectionRemarkRepository;
-
 
21
import com.spice.profitmandi.dao.repository.auth.RbmCallSequenceLogRepository;
8
import com.spice.profitmandi.dao.repository.catalog.RbmAchievementsRepository;
22
import com.spice.profitmandi.dao.repository.catalog.RbmAchievementsRepository;
9
import com.spice.profitmandi.dao.repository.catalog.RbmTargetsRepository;
23
import com.spice.profitmandi.dao.repository.catalog.RbmTargetsRepository;
-
 
24
import com.spice.profitmandi.dao.repository.cs.CsService;
-
 
25
import com.spice.profitmandi.dao.repository.cs.PositionRepository;
-
 
26
import com.spice.profitmandi.dao.repository.dtr.FofoStoreRepository;
10
import com.spice.profitmandi.dao.repository.fofo.MonthlyTargetRepository;
27
import com.spice.profitmandi.dao.repository.fofo.MonthlyTargetRepository;
11
import com.spice.profitmandi.dao.repository.logistics.PublicHolidaysRepository;
28
import com.spice.profitmandi.dao.repository.logistics.PublicHolidaysRepository;
-
 
29
import com.spice.profitmandi.dao.repository.transaction.LoanRepository;
12
import com.spice.profitmandi.dao.repository.transaction.OrderRepository;
30
import com.spice.profitmandi.dao.repository.transaction.OrderRepository;
-
 
31
import com.spice.profitmandi.service.user.RetailerService;
13
import org.apache.logging.log4j.LogManager;
32
import org.apache.logging.log4j.LogManager;
14
import org.apache.logging.log4j.Logger;
33
import org.apache.logging.log4j.Logger;
15
import org.hibernate.Session;
34
import org.hibernate.Session;
16
import org.hibernate.SessionFactory;
35
import org.hibernate.SessionFactory;
17
import org.hibernate.query.NativeQuery;
36
import org.hibernate.query.NativeQuery;
18
import org.springframework.beans.factory.annotation.Autowired;
37
import org.springframework.beans.factory.annotation.Autowired;
19
import org.springframework.stereotype.Component;
38
import org.springframework.stereotype.Component;
20
 
39
 
21
import javax.persistence.TypedQuery;
40
import javax.persistence.TypedQuery;
22
import java.time.*;
41
import java.time.*;
-
 
42
import java.time.format.DateTimeFormatter;
23
import java.time.temporal.ChronoUnit;
43
import java.time.temporal.ChronoUnit;
24
import java.util.ArrayList;
-
 
25
import java.util.List;
-
 
26
import java.util.Map;
44
import java.util.*;
27
import java.util.Optional;
-
 
28
import java.util.stream.Collectors;
45
import java.util.stream.Collectors;
29
 
46
 
30
@Component
47
@Component
31
public class RbmTargetServiceImpl implements RbmTargetService {
48
public class RbmTargetServiceImpl implements RbmTargetService {
32
    private static final Logger LOGGER = LogManager.getLogger(RbmTargetServiceImpl.class);
49
    private static final Logger LOGGER = LogManager.getLogger(RbmTargetServiceImpl.class);
Line 44... Line 61...
44
    MonthlyTargetRepository monthlyTargetRepository;
61
    MonthlyTargetRepository monthlyTargetRepository;
45
 
62
 
46
    @Autowired
63
    @Autowired
47
    PublicHolidaysRepository publicHolidaysRepository;
64
    PublicHolidaysRepository publicHolidaysRepository;
48
 
65
 
-
 
66
    @Autowired
-
 
67
    RetailerService retailerService;
-
 
68
 
49
    @Override
69
    @Override
50
    public List<WarehouseRbmTargetModel> getWarehouseWiseRbmMonthlyTarget() {
70
    public List<WarehouseRbmTargetModel> getWarehouseWiseRbmMonthlyTarget() {
51
        Session session = sessionFactory.getCurrentSession();
71
        Session session = sessionFactory.getCurrentSession();
52
        final TypedQuery<WarehouseRbmTargetModel> typedQuerySimilar = session.createNamedQuery("RbmTarget.getWarehouseWiseMonthlyTarget", WarehouseRbmTargetModel.class);
72
        final TypedQuery<WarehouseRbmTargetModel> typedQuerySimilar = session.createNamedQuery("RbmTarget.getWarehouseWiseMonthlyTarget", WarehouseRbmTargetModel.class);
53
 
73
 
Line 543... Line 563...
543
        LOGGER.info("publicHolidays {}", publicHolidays);
563
        LOGGER.info("publicHolidays {}", publicHolidays);
544
 
564
 
545
        return remainingDays;
565
        return remainingDays;
546
    }
566
    }
547
 
567
 
-
 
568
    @Autowired
-
 
569
    PositionRepository positionRepository;
-
 
570
 
-
 
571
    @Autowired
-
 
572
    CsService csService;
-
 
573
 
-
 
574
    @Autowired
-
 
575
    FofoStoreRepository fofoStoreRepository;
-
 
576
 
-
 
577
    @Autowired
-
 
578
    AuthRepository authRepository;
-
 
579
 
-
 
580
    @Autowired
-
 
581
    LoanRepository loanRepository;
-
 
582
 
-
 
583
    @Autowired
-
 
584
    PartnerCollectionService partnerCollectionService;
-
 
585
 
-
 
586
    @Autowired
-
 
587
    PartnerCollectionRemarkRepository partnerCollectionRemarkRepository;
-
 
588
 
-
 
589
    @Autowired
-
 
590
    RbmCallSequenceLogRepository rbmCallSequenceLogRepository;
-
 
591
 
-
 
592
    @Autowired
-
 
593
    com.spice.profitmandi.dao.repository.cs.TicketRepository ticketRepository;
-
 
594
 
-
 
595
    @Override
-
 
596
    public List<RbmCallTargetModel> getRbmCallTargetModels() throws Exception {
-
 
597
        long methodStart = System.currentTimeMillis();
-
 
598
        List<RbmCallTargetModel> rbmCallTargetModels = new ArrayList<>();
-
 
599
 
-
 
600
        // Get all RBM positions (L1 and L2)
-
 
601
        long start = System.currentTimeMillis();
-
 
602
        List<Position> allRbmPositions = positionRepository
-
 
603
                .selectPositionByCategoryId(ProfitMandiConstants.TICKET_CATEGORY_RBM).stream()
-
 
604
                .filter(x -> Arrays.asList(EscalationType.L1, EscalationType.L2).contains(x.getEscalationType()))
-
 
605
                .collect(Collectors.toList());
-
 
606
 
-
 
607
        // Separate L1 and L2 auth IDs
-
 
608
        List<Integer> l1AuthIds = allRbmPositions.stream()
-
 
609
                .filter(p -> EscalationType.L1.equals(p.getEscalationType()))
-
 
610
                .map(Position::getAuthUserId).distinct().collect(Collectors.toList());
-
 
611
        List<Integer> l2AuthIds = allRbmPositions.stream()
-
 
612
                .filter(p -> EscalationType.L2.equals(p.getEscalationType()))
-
 
613
                .map(Position::getAuthUserId).distinct().collect(Collectors.toList());
-
 
614
 
-
 
615
        // Union of all auth IDs for batch fetching
-
 
616
        List<Integer> rbmPositionsAuthIds = allRbmPositions.stream()
-
 
617
                .map(Position::getAuthUserId).distinct().collect(Collectors.toList());
-
 
618
        LOGGER.info("RBM Call Target - RBM positions fetch: {}ms, L1: {}, L2: {}", System.currentTimeMillis() - start, l1AuthIds.size(), l2AuthIds.size());
-
 
619
 
-
 
620
        start = System.currentTimeMillis();
-
 
621
        Map<String, Set<Integer>> storeGuyMap = csService.getAuthUserPartnerIdMapping();
-
 
622
        LOGGER.info("RBM Call Target - StoreGuyMap fetch: {}ms", System.currentTimeMillis() - start);
-
 
623
 
-
 
624
        LocalDateTime startDate = LocalDate.now().atStartOfDay();
-
 
625
        LocalDate firstOfMonth = LocalDate.now().withDayOfMonth(1);
-
 
626
        LocalDate endOfMonth = LocalDate.now().withDayOfMonth(LocalDate.now().lengthOfMonth()).plusDays(1);
-
 
627
 
-
 
628
        // Get auth user map
-
 
629
        start = System.currentTimeMillis();
-
 
630
        Map<Integer, AuthUser> authUserMap = authRepository.selectByIds(rbmPositionsAuthIds).stream()
-
 
631
                .collect(Collectors.toMap(AuthUser::getId, au -> au));
-
 
632
        LOGGER.info("RBM Call Target - AuthUser fetch: {}ms", System.currentTimeMillis() - start);
-
 
633
 
-
 
634
        // Batch fetch positions by auth IDs (to check if RBM is L1)
-
 
635
        start = System.currentTimeMillis();
-
 
636
        Map<Integer, List<Position>> positionsByAuthId = positionRepository.selectPositionByAuthIds(rbmPositionsAuthIds).stream()
-
 
637
                .collect(Collectors.groupingBy(Position::getAuthUserId));
-
 
638
        LOGGER.info("RBM Call Target - Positions by AuthId fetch: {}ms", System.currentTimeMillis() - start);
-
 
639
 
-
 
640
        // Get all fofo IDs for all RBMs
-
 
641
        Set<Integer> allFofoIds = new HashSet<>();
-
 
642
        Map<Integer, List<Integer>> rbmToFofoIdsMap = new HashMap<>();
-
 
643
        for (int rbmAuthId : rbmPositionsAuthIds) {
-
 
644
            AuthUser au = authUserMap.get(rbmAuthId);
-
 
645
            if (au != null && storeGuyMap.containsKey(au.getEmailId())) {
-
 
646
                List<Integer> fofoIds = new ArrayList<>(storeGuyMap.get(au.getEmailId()));
-
 
647
                allFofoIds.addAll(fofoIds);
-
 
648
                rbmToFofoIdsMap.put(rbmAuthId, fofoIds);
-
 
649
            }
-
 
650
        }
-
 
651
        // Fetch escalated tickets for L2 early so their fofoIds are included in batch fetches
-
 
652
        List<Ticket> escalatedTickets = Collections.emptyList();
-
 
653
        Map<Integer, List<Integer>> l2AuthIdToFofoIds = new HashMap<>();
-
 
654
        if (!l2AuthIds.isEmpty()) {
-
 
655
            start = System.currentTimeMillis();
-
 
656
            escalatedTickets = ticketRepository.selectOpenEscalatedTicketsByAuthIds(l2AuthIds);
-
 
657
            LOGGER.info("RBM Call Target - Escalated tickets fetch: {}ms, count: {}", System.currentTimeMillis() - start, escalatedTickets.size());
-
 
658
 
-
 
659
            for (int l2AuthId : l2AuthIds) {
-
 
660
                List<Integer> l2FofoIds = escalatedTickets.stream()
-
 
661
                        .filter(t -> t.getL2AuthUser() == l2AuthId
-
 
662
                                || t.getL3AuthUser() == l2AuthId
-
 
663
                                || t.getL4AuthUser() == l2AuthId
-
 
664
                                || t.getL5AuthUser() == l2AuthId)
-
 
665
                        .map(Ticket::getFofoId)
-
 
666
                        .distinct()
-
 
667
                        .collect(Collectors.toList());
-
 
668
                l2AuthIdToFofoIds.put(l2AuthId, l2FofoIds);
-
 
669
                allFofoIds.addAll(l2FofoIds);
-
 
670
            }
-
 
671
        }
-
 
672
        LOGGER.info("RBM Call Target - Total fofo IDs to process: {}", allFofoIds.size());
-
 
673
 
-
 
674
        // Get only needed fofo stores (OPTIMIZED - was fetching ALL stores before)
-
 
675
        start = System.currentTimeMillis();
-
 
676
        Map<Integer, FofoStore> fofoStoresMap = new HashMap<>();
-
 
677
        if (!allFofoIds.isEmpty()) {
-
 
678
            try {
-
 
679
                fofoStoresMap = fofoStoreRepository.selectByRetailerIds(new ArrayList<>(allFofoIds)).stream()
-
 
680
                        .collect(Collectors.toMap(FofoStore::getId, x -> x, (a, b) -> a));
-
 
681
            } catch (ProfitMandiBusinessException e) {
-
 
682
                LOGGER.error("Error fetching fofo stores", e);
-
 
683
            }
-
 
684
        }
-
 
685
        LOGGER.info("RBM Call Target - FofoStores fetch (only needed): {}ms, count: {}", System.currentTimeMillis() - start, fofoStoresMap.size());
-
 
686
 
-
 
687
        // Batch fetch max remark ids for all fofoIds (for escalation filtering)
-
 
688
        start = System.currentTimeMillis();
-
 
689
        Map<Integer, PartnerCollectionRemark> allPartnerCollectionRemarks = new HashMap<>();
-
 
690
        if (!allFofoIds.isEmpty()) {
-
 
691
            List<Integer> allRemarkIds = partnerCollectionRemarkRepository.selectMaxRemarkId(new ArrayList<>(allFofoIds));
-
 
692
            if (!allRemarkIds.isEmpty()) {
-
 
693
                allPartnerCollectionRemarks = partnerCollectionRemarkRepository.selectByIds(allRemarkIds).stream()
-
 
694
                        .collect(Collectors.toMap(PartnerCollectionRemark::getFofoId, x -> x, (a, b) -> a));
-
 
695
            }
-
 
696
        }
-
 
697
        LOGGER.info("RBM Call Target - PartnerCollectionRemarks fetch: {}ms", System.currentTimeMillis() - start);
-
 
698
 
-
 
699
        // Batch fetch collection RANK map for all fofoIds (OPTIMIZED - only fetches rank, not full model)
-
 
700
        start = System.currentTimeMillis();
-
 
701
        Map<Integer, Integer> allCollectionRankMap = new HashMap<>();
-
 
702
        if (!allFofoIds.isEmpty()) {
-
 
703
            try {
-
 
704
                allCollectionRankMap = partnerCollectionService.getCollectionRankMap(new ArrayList<>(allFofoIds), startDate);
-
 
705
            } catch (ProfitMandiBusinessException e) {
-
 
706
                LOGGER.error("Error fetching collection rank map for all fofoIds", e);
-
 
707
            }
-
 
708
        }
-
 
709
        LOGGER.info("RBM Call Target - CollectionRankMap fetch (OPTIMIZED): {}ms", System.currentTimeMillis() - start);
-
 
710
 
-
 
711
        // Get MTD billing data for zero billing calculation
-
 
712
        start = System.currentTimeMillis();
-
 
713
        List<RbmWeeklyBillingModel> mtdBillingData = getWeeklyBillingDataForMonth(firstOfMonth, endOfMonth);
-
 
714
        Set<Integer> allMtdBilledFofoIds = mtdBillingData.stream()
-
 
715
                .filter(RbmWeeklyBillingModel::isMtdBilled)
-
 
716
                .map(RbmWeeklyBillingModel::getFofoId)
-
 
717
                .collect(Collectors.toSet());
-
 
718
        LOGGER.info("RBM Call Target - MTD Billing fetch: {}ms", System.currentTimeMillis() - start);
-
 
719
 
-
 
720
        // Batch fetch today's remarks for all auth IDs (to calculate Value Achieved)
-
 
721
        start = System.currentTimeMillis();
-
 
722
        Map<Integer, List<PartnerCollectionRemark>> remarksByAuthId = partnerCollectionRemarkRepository
-
 
723
                .selectAllByAuthIdsOnDate(rbmPositionsAuthIds, LocalDate.now()).stream()
-
 
724
                .collect(Collectors.groupingBy(PartnerCollectionRemark::getAuthId));
-
 
725
        LOGGER.info("RBM Call Target - Today Remarks fetch: {}ms", System.currentTimeMillis() - start);
-
 
726
 
-
 
727
        // Batch fetch today's out-of-sequence logs for all RBMs
-
 
728
        start = System.currentTimeMillis();
-
 
729
        LocalDateTime todayStart = LocalDate.now().atStartOfDay();
-
 
730
        LocalDateTime todayEnd = LocalDate.now().plusDays(1).atStartOfDay();
-
 
731
        List<RbmCallSequenceLog> outOfSequenceLogs = rbmCallSequenceLogRepository.selectOutOfSequenceByDateRange(todayStart, todayEnd);
-
 
732
        Map<Integer, Long> outOfSequenceCountByAuthId = outOfSequenceLogs.stream()
-
 
733
                .collect(Collectors.groupingBy(RbmCallSequenceLog::getAuthId, Collectors.counting()));
-
 
734
        LOGGER.info("RBM Call Target - Out of Sequence fetch: {}ms", System.currentTimeMillis() - start);
-
 
735
 
-
 
736
        // Process L1 RBMs (existing logic)
-
 
737
        for (int rbmAuthId : l1AuthIds) {
-
 
738
            AuthUser authUser = authUserMap.get(rbmAuthId);
-
 
739
            if (authUser == null || !storeGuyMap.containsKey(authUser.getEmailId())) {
-
 
740
                continue;
-
 
741
            }
-
 
742
 
-
 
743
            List<Integer> fofoIdList = rbmToFofoIdsMap.getOrDefault(rbmAuthId, Collections.emptyList());
-
 
744
 
-
 
745
            // Check if RBM is L1 (same logic as getSummaryModel)
-
 
746
            List<Position> positions = positionsByAuthId.getOrDefault(authUser.getId(), Collections.emptyList());
-
 
747
            boolean isRBMAndL1 = positions.stream()
-
 
748
                    .anyMatch(position ->
-
 
749
                            ProfitMandiConstants.TICKET_CATEGORY_RBM == position.getCategoryId()
-
 
750
                                    && EscalationType.L1.equals(position.getEscalationType()));
-
 
751
 
-
 
752
            // Filter escalated partners for L1 RBMs (same logic as getSummaryModel)
-
 
753
            List<Integer> fofoIds = fofoIdList;
-
 
754
            if (isRBMAndL1) {
-
 
755
                Map<Integer, PartnerCollectionRemark> partnerCollectionRemarks = new HashMap<>();
-
 
756
                for (Integer fofoId : fofoIdList) {
-
 
757
                    if (allPartnerCollectionRemarks.containsKey(fofoId)) {
-
 
758
                        partnerCollectionRemarks.put(fofoId, allPartnerCollectionRemarks.get(fofoId));
-
 
759
                    }
-
 
760
                }
-
 
761
                fofoIds = partnerCollectionRemarks.entrySet().stream()
-
 
762
                        .filter(entry -> {
-
 
763
                            PartnerCollectionRemark pcrMap = entry.getValue();
-
 
764
                            return !(CollectionRemark.RBM_L2_ESCALATION.equals(pcrMap.getRemark())
-
 
765
                                    || CollectionRemark.SALES_ESCALATION.equals(pcrMap.getRemark()));
-
 
766
                        })
-
 
767
                        .map(Map.Entry::getKey)
-
 
768
                        .collect(Collectors.toList());
-
 
769
            }
-
 
770
 
-
 
771
            // Filter to only external, ACTIVE stores with collection plan (like today_target Active section)
-
 
772
            Map<Integer, Integer> finalAllCollectionRankMap = allCollectionRankMap;
-
 
773
            Map<Integer, FofoStore> finalFofoStoresMap = fofoStoresMap;
-
 
774
            List<Integer> validFofoIds = fofoIds.stream()
-
 
775
                    .filter(fofoId -> {
-
 
776
                        FofoStore store = finalFofoStoresMap.get(fofoId);
-
 
777
                        if (store == null || store.isInternal()) {
-
 
778
                            return false;
-
 
779
                        }
-
 
780
                        // Only include ACTIVE partners (not Low Sale, not Disputed, not Billing Pending)
-
 
781
                        if (!ActivationType.ACTIVE.equals(store.getActivationType())) {
-
 
782
                            return false;
-
 
783
                        }
-
 
784
                        // Only include partners who have a collection plan (to match today_target Active section)
-
 
785
                        return finalAllCollectionRankMap.containsKey(fofoId);
-
 
786
                    })
-
 
787
                    .collect(Collectors.toList());
-
 
788
 
-
 
789
            if (validFofoIds.isEmpty()) {
-
 
790
                continue;
-
 
791
            }
-
 
792
 
-
 
793
            RbmCallTargetModel targetModel = new RbmCallTargetModel();
-
 
794
            targetModel.setAuthId(rbmAuthId);
-
 
795
            targetModel.setRbmName(authUser.getFullName());
-
 
796
            targetModel.setPartnerCount(validFofoIds.size());
-
 
797
 
-
 
798
            // Categorize each partner - each partner belongs to ONE category only
-
 
799
            // Priority: PlanToday > CarryForward > Untouched > ZeroBilling > FuturePlan > Normal
-
 
800
            Set<Integer> planTodayPartners = new HashSet<>();
-
 
801
            Set<Integer> carryForwardPartners = new HashSet<>();
-
 
802
            Set<Integer> untouchedPartners = new HashSet<>();
-
 
803
            Set<Integer> zeroBillingPartners = new HashSet<>();
-
 
804
            Set<Integer> futurePlanPartners = new HashSet<>();
-
 
805
            Set<Integer> normalPartners = new HashSet<>();
-
 
806
 
-
 
807
            for (Integer fofoId : validFofoIds) {
-
 
808
                // Get collection plan rank (from optimized rank map)
-
 
809
                int rank = allCollectionRankMap.getOrDefault(fofoId, 5); // default to Normal if no plan
-
 
810
 
-
 
811
                // Check if partner has zero billing in MTD
-
 
812
                boolean hasZeroBilling = !allMtdBilledFofoIds.contains(fofoId);
-
 
813
 
-
 
814
                // Assign to category based on priority
-
 
815
                if (rank == 1) {
-
 
816
                    planTodayPartners.add(fofoId);
-
 
817
                } else if (rank == 2) {
-
 
818
                    carryForwardPartners.add(fofoId);
-
 
819
                } else if (rank == 3) {
-
 
820
                    untouchedPartners.add(fofoId);
-
 
821
                } else if (hasZeroBilling) {
-
 
822
                    zeroBillingPartners.add(fofoId);
-
 
823
                } else if (rank == 4) {
-
 
824
                    futurePlanPartners.add(fofoId);
-
 
825
                } else {
-
 
826
                    normalPartners.add(fofoId);
-
 
827
                }
-
 
828
            }
-
 
829
 
-
 
830
            // Set counts
-
 
831
            targetModel.setCreditCollection(0); // Credit collection is handled in separate list
-
 
832
            targetModel.setPlanToday(planTodayPartners.size());
-
 
833
            targetModel.setCarryForward(carryForwardPartners.size());
-
 
834
            targetModel.setUntouched(untouchedPartners.size());
-
 
835
            targetModel.setZeroBilling(zeroBillingPartners.size());
-
 
836
            targetModel.setFuturePlan(futurePlanPartners.size());
-
 
837
            targetModel.setNormal(normalPartners.size());
-
 
838
 
-
 
839
            // Today Target = PlanToday + CarryForward + ZeroBilling + Untouched
-
 
840
            // These are mutually exclusive now, so we can sum them
-
 
841
            long todayTarget = planTodayPartners.size() +
-
 
842
                    carryForwardPartners.size() + zeroBillingPartners.size() + untouchedPartners.size();
-
 
843
            targetModel.setTodayTargetOfCall(todayTarget);
-
 
844
 
-
 
845
            // Create set of partners in Today Target categories
-
 
846
            Set<Integer> todayTargetPartners = new HashSet<>();
-
 
847
            todayTargetPartners.addAll(planTodayPartners);
-
 
848
            todayTargetPartners.addAll(carryForwardPartners);
-
 
849
            todayTargetPartners.addAll(zeroBillingPartners);
-
 
850
            todayTargetPartners.addAll(untouchedPartners);
-
 
851
 
-
 
852
            // Value Achieved = Partners from Today Target that have been contacted today (have remark today)
-
 
853
            List<PartnerCollectionRemark> todayRemarks = remarksByAuthId.getOrDefault(rbmAuthId, Collections.emptyList());
-
 
854
            long valueAchieved = todayRemarks.stream()
-
 
855
                    .map(PartnerCollectionRemark::getFofoId)
-
 
856
                    .filter(todayTargetPartners::contains)
-
 
857
                    .distinct()
-
 
858
                    .count();
-
 
859
            targetModel.setValueTargetAchieved(valueAchieved);
-
 
860
 
-
 
861
            // Moved to Future = Partners in Future Plan category who have a remark today
-
 
862
            // These are partners who were contacted today but moved to a future date
-
 
863
            Set<Integer> todayRemarkedFofoIds = todayRemarks.stream()
-
 
864
                    .map(PartnerCollectionRemark::getFofoId)
-
 
865
                    .collect(Collectors.toSet());
-
 
866
            long movedToFuture = futurePlanPartners.stream()
-
 
867
                    .filter(todayRemarkedFofoIds::contains)
-
 
868
                    .count();
-
 
869
            targetModel.setMovedToFuture(movedToFuture);
-
 
870
 
-
 
871
            // Set out of sequence count for this RBM
-
 
872
            targetModel.setOutOfSequenceCount(outOfSequenceCountByAuthId.getOrDefault(rbmAuthId, 0L));
-
 
873
 
-
 
874
            rbmCallTargetModels.add(targetModel);
-
 
875
        }
-
 
876
 
-
 
877
        // Process L2 RBMs (escalated ticket logic with categorization)
-
 
878
        for (int l2AuthId : l2AuthIds) {
-
 
879
            AuthUser authUser = authUserMap.get(l2AuthId);
-
 
880
            if (authUser == null) {
-
 
881
                continue;
-
 
882
            }
-
 
883
 
-
 
884
            List<Integer> l2FofoIdList = l2AuthIdToFofoIds.getOrDefault(l2AuthId, Collections.emptyList());
-
 
885
            if (l2FofoIdList.isEmpty()) {
-
 
886
                continue;
-
 
887
            }
-
 
888
 
-
 
889
            // Filter to only external, ACTIVE stores with collection plan
-
 
890
            Map<Integer, Integer> finalAllCollectionRankMap2 = allCollectionRankMap;
-
 
891
            Map<Integer, FofoStore> finalFofoStoresMap2 = fofoStoresMap;
-
 
892
            List<Integer> validL2FofoIds = l2FofoIdList.stream()
-
 
893
                    .filter(fofoId -> {
-
 
894
                        FofoStore store = finalFofoStoresMap2.get(fofoId);
-
 
895
                        if (store == null || store.isInternal()) {
-
 
896
                            return false;
-
 
897
                        }
-
 
898
                        if (!ActivationType.ACTIVE.equals(store.getActivationType())) {
-
 
899
                            return false;
-
 
900
                        }
-
 
901
                        return finalAllCollectionRankMap2.containsKey(fofoId);
-
 
902
                    })
-
 
903
                    .collect(Collectors.toList());
-
 
904
 
-
 
905
            RbmCallTargetModel l2Model = new RbmCallTargetModel();
-
 
906
            l2Model.setAuthId(l2AuthId);
-
 
907
            l2Model.setRbmName(authUser.getFullName() + " (L2)");
-
 
908
            l2Model.setL2Position(true);
-
 
909
            l2Model.setL2CallingList(l2FofoIdList.size());
-
 
910
            // Partner count = total assigned partners (same as L1 source)
-
 
911
            List<Integer> l2AssignedFofoIds = rbmToFofoIdsMap.getOrDefault(l2AuthId, Collections.emptyList());
-
 
912
            l2Model.setPartnerCount(l2AssignedFofoIds.size());
-
 
913
 
-
 
914
            if (!validL2FofoIds.isEmpty()) {
-
 
915
                // Categorize using same logic as L1
-
 
916
                Set<Integer> l2PlanToday = new HashSet<>();
-
 
917
                Set<Integer> l2CarryForward = new HashSet<>();
-
 
918
                Set<Integer> l2Untouched = new HashSet<>();
-
 
919
                Set<Integer> l2ZeroBilling = new HashSet<>();
-
 
920
                Set<Integer> l2FuturePlan = new HashSet<>();
-
 
921
                Set<Integer> l2Normal = new HashSet<>();
-
 
922
 
-
 
923
                for (Integer fofoId : validL2FofoIds) {
-
 
924
                    int rank = allCollectionRankMap.getOrDefault(fofoId, 5);
-
 
925
                    boolean hasZeroBilling = !allMtdBilledFofoIds.contains(fofoId);
-
 
926
 
-
 
927
                    if (rank == 1) {
-
 
928
                        l2PlanToday.add(fofoId);
-
 
929
                    } else if (rank == 2) {
-
 
930
                        l2CarryForward.add(fofoId);
-
 
931
                    } else if (rank == 3) {
-
 
932
                        l2Untouched.add(fofoId);
-
 
933
                    } else if (hasZeroBilling) {
-
 
934
                        l2ZeroBilling.add(fofoId);
-
 
935
                    } else if (rank == 4) {
-
 
936
                        l2FuturePlan.add(fofoId);
-
 
937
                    } else {
-
 
938
                        l2Normal.add(fofoId);
-
 
939
                    }
-
 
940
                }
-
 
941
 
-
 
942
                l2Model.setPlanToday(l2PlanToday.size());
-
 
943
                l2Model.setCarryForward(l2CarryForward.size());
-
 
944
                l2Model.setUntouched(l2Untouched.size());
-
 
945
                l2Model.setZeroBilling(l2ZeroBilling.size());
-
 
946
                l2Model.setFuturePlan(l2FuturePlan.size());
-
 
947
                l2Model.setNormal(l2Normal.size());
-
 
948
 
-
 
949
                long l2TodayTarget = l2PlanToday.size() + l2CarryForward.size()
-
 
950
                        + l2ZeroBilling.size() + l2Untouched.size();
-
 
951
                l2Model.setTodayTargetOfCall(l2TodayTarget);
-
 
952
 
-
 
953
                // Value Achieved
-
 
954
                Set<Integer> l2TodayTargetPartners = new HashSet<>();
-
 
955
                l2TodayTargetPartners.addAll(l2PlanToday);
-
 
956
                l2TodayTargetPartners.addAll(l2CarryForward);
-
 
957
                l2TodayTargetPartners.addAll(l2ZeroBilling);
-
 
958
                l2TodayTargetPartners.addAll(l2Untouched);
-
 
959
 
-
 
960
                List<PartnerCollectionRemark> l2TodayRemarks = remarksByAuthId.getOrDefault(l2AuthId, Collections.emptyList());
-
 
961
                long l2ValueAchieved = l2TodayRemarks.stream()
-
 
962
                        .map(PartnerCollectionRemark::getFofoId)
-
 
963
                        .filter(l2TodayTargetPartners::contains)
-
 
964
                        .distinct()
-
 
965
                        .count();
-
 
966
                l2Model.setValueTargetAchieved(l2ValueAchieved);
-
 
967
 
-
 
968
                // Moved to Future
-
 
969
                Set<Integer> l2TodayRemarkedFofoIds = l2TodayRemarks.stream()
-
 
970
                        .map(PartnerCollectionRemark::getFofoId)
-
 
971
                        .collect(Collectors.toSet());
-
 
972
                long l2MovedToFuture = l2FuturePlan.stream()
-
 
973
                        .filter(l2TodayRemarkedFofoIds::contains)
-
 
974
                        .count();
-
 
975
                l2Model.setMovedToFuture(l2MovedToFuture);
-
 
976
            }
-
 
977
 
-
 
978
            l2Model.setOutOfSequenceCount(outOfSequenceCountByAuthId.getOrDefault(l2AuthId, 0L));
-
 
979
            rbmCallTargetModels.add(l2Model);
-
 
980
        }
-
 
981
 
-
 
982
        // Group L1 under their L2 manager using authUser.managerId
-
 
983
        Map<Integer, RbmCallTargetModel> l2ModelsByAuthId = new HashMap<>();
-
 
984
        Map<Integer, RbmCallTargetModel> l1ModelsByAuthId = new HashMap<>();
-
 
985
        for (RbmCallTargetModel m : rbmCallTargetModels) {
-
 
986
            if (m.isL2Position()) {
-
 
987
                l2ModelsByAuthId.put(m.getAuthId(), m);
-
 
988
            } else {
-
 
989
                l1ModelsByAuthId.put(m.getAuthId(), m);
-
 
990
            }
-
 
991
        }
-
 
992
 
-
 
993
        // Build L2 -> L1 team map using managerId from AuthUser
-
 
994
        Map<Integer, List<RbmCallTargetModel>> l2TeamMap = new LinkedHashMap<>();
-
 
995
        for (RbmCallTargetModel l2Model : l2ModelsByAuthId.values()) {
-
 
996
            l2TeamMap.put(l2Model.getAuthId(), new ArrayList<>());
-
 
997
        }
-
 
998
 
-
 
999
        Set<Integer> addedL1AuthIds = new HashSet<>();
-
 
1000
        for (RbmCallTargetModel l1Model : l1ModelsByAuthId.values()) {
-
 
1001
            AuthUser l1User = authUserMap.get(l1Model.getAuthId());
-
 
1002
            if (l1User != null && l2TeamMap.containsKey(l1User.getManagerId())) {
-
 
1003
                l2TeamMap.get(l1User.getManagerId()).add(l1Model);
-
 
1004
                addedL1AuthIds.add(l1Model.getAuthId());
-
 
1005
            }
-
 
1006
        }
-
 
1007
 
-
 
1008
        // Build sorted result: L2 row, then its L1 team (sorted by name)
-
 
1009
        List<RbmCallTargetModel> sortedModels = new ArrayList<>();
-
 
1010
 
-
 
1011
        List<RbmCallTargetModel> l2Sorted = new ArrayList<>(l2ModelsByAuthId.values());
-
 
1012
        l2Sorted.sort(Comparator.comparing(RbmCallTargetModel::getRbmName));
-
 
1013
 
-
 
1014
        for (RbmCallTargetModel l2Model : l2Sorted) {
-
 
1015
            sortedModels.add(l2Model);
-
 
1016
            List<RbmCallTargetModel> team = l2TeamMap.getOrDefault(l2Model.getAuthId(), Collections.emptyList());
-
 
1017
            team.sort(Comparator.comparing(RbmCallTargetModel::getRbmName));
-
 
1018
            sortedModels.addAll(team);
-
 
1019
        }
-
 
1020
 
-
 
1021
        // Add any L1 RBMs not mapped to any L2 (sorted by name)
-
 
1022
        List<RbmCallTargetModel> unmappedL1 = new ArrayList<>();
-
 
1023
        for (RbmCallTargetModel m : l1ModelsByAuthId.values()) {
-
 
1024
            if (!addedL1AuthIds.contains(m.getAuthId())) {
-
 
1025
                unmappedL1.add(m);
-
 
1026
            }
-
 
1027
        }
-
 
1028
        unmappedL1.sort(Comparator.comparing(RbmCallTargetModel::getRbmName));
-
 
1029
        sortedModels.addAll(unmappedL1);
-
 
1030
 
-
 
1031
        LOGGER.info("RBM Call Target - TOTAL TIME: {}ms, RBM count: {}", System.currentTimeMillis() - methodStart, sortedModels.size());
-
 
1032
        return sortedModels;
-
 
1033
    }
-
 
1034
 
-
 
1035
    @Override
-
 
1036
    public List<OutOfSequenceDetailModel> getOutOfSequenceDetails(int authId) {
-
 
1037
 
-
 
1038
        LocalDate today = LocalDate.now();
-
 
1039
        LocalDateTime start = today.atStartOfDay();
-
 
1040
        LocalDateTime end = today.plusDays(1).atStartOfDay();
-
 
1041
 
-
 
1042
        List<RbmCallSequenceLog> logs =
-
 
1043
                rbmCallSequenceLogRepository.selectByAuthIdAndDateRange(authId, start, end);
-
 
1044
 
-
 
1045
        Set<Integer> fofoIds = new HashSet<>();
-
 
1046
        List<RbmCallSequenceLog> oosLogs = new ArrayList<>();
-
 
1047
 
-
 
1048
        for (RbmCallSequenceLog log : logs) {
-
 
1049
            if (log.isOutOfSequence()) {
-
 
1050
                oosLogs.add(log);
-
 
1051
                fofoIds.add(log.getFofoId());
-
 
1052
            }
-
 
1053
        }
-
 
1054
 
-
 
1055
        if (oosLogs.isEmpty()) {
-
 
1056
            return Collections.emptyList();
-
 
1057
        }
-
 
1058
 
-
 
1059
        Map<Integer, CustomRetailer> retailerMap = Collections.emptyMap();
-
 
1060
        try {
-
 
1061
            retailerMap = retailerService.getFofoRetailers(new ArrayList<>(fofoIds));
-
 
1062
        } catch (ProfitMandiBusinessException e) {
-
 
1063
            LOGGER.error("Error fetching fofo stores", e);
-
 
1064
        }
-
 
1065
 
-
 
1066
        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("hh:mm a");
-
 
1067
        List<OutOfSequenceDetailModel> result = new ArrayList<>();
-
 
1068
 
-
 
1069
        for (RbmCallSequenceLog log : oosLogs) {
-
 
1070
            CustomRetailer retailer = retailerMap.get(log.getFofoId());
-
 
1071
            String partyName = retailer != null
-
 
1072
                    ? retailer.getBusinessName()
-
 
1073
                    : "Unknown (" + log.getFofoId() + ")";
-
 
1074
            String code = retailer != null
-
 
1075
                    ? retailer.getCode()
-
 
1076
                    : "-";
-
 
1077
 
-
 
1078
            String time = log.getCreateTimestamp() != null
-
 
1079
                    ? log.getCreateTimestamp().format(timeFormatter)
-
 
1080
                    : "-";
-
 
1081
 
-
 
1082
            result.add(new OutOfSequenceDetailModel(partyName, code, time));
-
 
1083
        }
-
 
1084
 
-
 
1085
        return result;
-
 
1086
    }
548
 
1087
 
549
 
1088
 
550
}
1089
}