Subversion Repositories SmartDukaan

Rev

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

Rev 36998 Rev 37010
Line 622... Line 622...
622
    @Override
622
    @Override
623
    public List<RbmCallTargetModel> getRbmCallTargetModels() throws Exception {
623
    public List<RbmCallTargetModel> getRbmCallTargetModels() throws Exception {
624
        return getRbmCallTargetModels(LocalDate.now());
624
        return getRbmCallTargetModels(LocalDate.now());
625
    }
625
    }
626
 
626
 
-
 
627
    // Matches "No Answer", "NO_ANSWER", "no-answer", "noanswer", etc.
-
 
628
    private static boolean isNoAnswerStatus(String status) {
-
 
629
        if (status == null) return false;
-
 
630
        String s = status.trim().toLowerCase().replace('_', ' ').replace('-', ' ').replaceAll("\\s+", " ");
-
 
631
        return s.equals("no answer") || s.equals("noanswer");
-
 
632
    }
-
 
633
 
-
 
634
    @Override
-
 
635
    public List<OutOfSequenceDetailModel> getOutOfSequenceDetails(int authId) {
-
 
636
 
-
 
637
        LocalDate today = LocalDate.now();
-
 
638
        LocalDateTime start = today.atStartOfDay();
-
 
639
        LocalDateTime end = today.plusDays(1).atStartOfDay();
-
 
640
 
-
 
641
        List<RbmCallSequenceLog> logs =
-
 
642
                rbmCallSequenceLogRepository.selectByAuthIdAndDateRange(authId, start, end);
-
 
643
 
-
 
644
        Map<Integer, RbmCallSequenceLog> uniqueOosLogsByFofoId = new LinkedHashMap<>();
-
 
645
 
-
 
646
        for (RbmCallSequenceLog log : logs) {
-
 
647
            if (log.isOutOfSequence()) {
-
 
648
                // Keep only the first occurrence per fofoId (latest entry since ordered by id DESC)
-
 
649
                uniqueOosLogsByFofoId.putIfAbsent(log.getFofoId(), log);
-
 
650
            }
-
 
651
        }
-
 
652
 
-
 
653
        if (uniqueOosLogsByFofoId.isEmpty()) {
-
 
654
            return Collections.emptyList();
-
 
655
        }
-
 
656
 
-
 
657
        Set<Integer> fofoIds = uniqueOosLogsByFofoId.keySet();
-
 
658
        Map<Integer, CustomRetailer> retailerMap = Collections.emptyMap();
-
 
659
        try {
-
 
660
            retailerMap = retailerService.getFofoRetailers(new ArrayList<>(fofoIds));
-
 
661
        } catch (ProfitMandiBusinessException e) {
-
 
662
            LOGGER.error("Error fetching fofo stores", e);
-
 
663
        }
-
 
664
 
-
 
665
        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("hh:mm a");
-
 
666
        List<OutOfSequenceDetailModel> result = new ArrayList<>();
-
 
667
 
-
 
668
        for (RbmCallSequenceLog log : uniqueOosLogsByFofoId.values()) {
-
 
669
            CustomRetailer retailer = retailerMap.get(log.getFofoId());
-
 
670
            String partyName = retailer != null
-
 
671
                    ? retailer.getBusinessName()
-
 
672
                    : "Unknown (" + log.getFofoId() + ")";
-
 
673
            String code = retailer != null
-
 
674
                    ? retailer.getCode()
-
 
675
                    : "-";
-
 
676
 
-
 
677
            String time = log.getCreateTimestamp() != null
-
 
678
                    ? log.getCreateTimestamp().format(timeFormatter)
-
 
679
                    : "-";
-
 
680
 
-
 
681
            result.add(new OutOfSequenceDetailModel(partyName, code, time));
-
 
682
        }
-
 
683
 
-
 
684
        return result;
-
 
685
    }
-
 
686
 
-
 
687
    @Override
-
 
688
    public List<CalledPartnerDetailModel> getCalledPartnerDetails(int authId) throws ProfitMandiBusinessException {
-
 
689
        return getCalledPartnerDetails(authId, LocalDate.now());
-
 
690
    }
-
 
691
 
-
 
692
    @Override
-
 
693
    public List<CalledPartnerDetailModel> getCalledPartnerDetails(int authId, LocalDate date) throws ProfitMandiBusinessException {
-
 
694
        // Get all call logs for this auth user on this date
-
 
695
        LOGGER.info("getCalledPartnerDetails: authId={}, date={}", authId, date);
-
 
696
        List<AgentCallLog> callLogs = agentCallLogRepository.findByAuthIdAndDate(authId, date);
-
 
697
        LOGGER.info("Found {} call logs for authId={} on date={}", callLogs.size(), authId, date);
-
 
698
 
-
 
699
        if (callLogs.isEmpty()) {
-
 
700
            return Collections.emptyList();
-
 
701
        }
-
 
702
 
-
 
703
        // Build a map of normalized customer number -> fofoId
-
 
704
        Map<String, Integer> customerToFofoIdMap = new HashMap<>();
-
 
705
        Set<String> normalizedNumbers = new HashSet<>();
-
 
706
 
-
 
707
        for (AgentCallLog callLog : callLogs) {
-
 
708
            String customerNumber = callLog.getCustomerNumber();
-
 
709
            if (customerNumber != null) {
-
 
710
                String normalized = customerNumber.startsWith("+91") ? customerNumber.substring(3) : customerNumber;
-
 
711
                normalizedNumbers.add(normalized);
-
 
712
            }
-
 
713
        }
-
 
714
 
-
 
715
        // For each normalized number, find fofoId from retailer_contact first, then address
-
 
716
        for (String mobile : normalizedNumbers) {
-
 
717
            Integer fofoId = findFofoIdByMobile(mobile);
-
 
718
            if (fofoId != null) {
-
 
719
                customerToFofoIdMap.put(mobile, fofoId);
-
 
720
            }
-
 
721
        }
-
 
722
 
-
 
723
        // Get unique fofoIds for retailer lookup
-
 
724
        Set<Integer> fofoIds = new HashSet<>(customerToFofoIdMap.values());
-
 
725
        Map<Integer, CustomRetailer> retailerMap = Collections.emptyMap();
-
 
726
        if (!fofoIds.isEmpty()) {
-
 
727
            try {
-
 
728
                retailerMap = retailerService.getFofoRetailers(new ArrayList<>(fofoIds));
-
 
729
            } catch (ProfitMandiBusinessException e) {
-
 
730
                LOGGER.error("Error fetching fofo stores", e);
-
 
731
            }
-
 
732
        }
-
 
733
 
-
 
734
        // Get today's remarks for these fofoIds
-
 
735
        Map<Integer, List<PartnerCollectionRemark>> fofoRemarkMap = new HashMap<>();
-
 
736
        if (!fofoIds.isEmpty()) {
-
 
737
            List<PartnerCollectionRemark> todayRemarks = partnerCollectionRemarkRepository
-
 
738
                    .selectAllByFofoIdsOnDate(new ArrayList<>(fofoIds), date);
-
 
739
            for (PartnerCollectionRemark remark : todayRemarks) {
-
 
740
                fofoRemarkMap.computeIfAbsent(remark.getFofoId(), k -> new ArrayList<>()).add(remark);
-
 
741
            }
-
 
742
        }
-
 
743
 
-
 
744
        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("hh:mm a");
-
 
745
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy hh:mm a");
-
 
746
        List<CalledPartnerDetailModel> result = new ArrayList<>();
-
 
747
 
-
 
748
        for (com.spice.profitmandi.dao.entity.cs.AgentCallLog callLog : callLogs) {
-
 
749
            String customerNumber = callLog.getCustomerNumber();
-
 
750
            if (customerNumber == null) {
-
 
751
                continue;
-
 
752
            }
-
 
753
 
-
 
754
            String normalized = customerNumber.startsWith("+91") ? customerNumber.substring(3) : customerNumber;
-
 
755
            Integer fofoId = customerToFofoIdMap.get(normalized);
-
 
756
 
-
 
757
            String partyName = "Unknown";
-
 
758
            String code = "-";
-
 
759
 
-
 
760
            if (fofoId != null) {
-
 
761
                CustomRetailer retailer = retailerMap.get(fofoId);
-
 
762
                if (retailer != null) {
-
 
763
                    partyName = retailer.getBusinessName();
-
 
764
                    code = retailer.getCode();
-
 
765
                } else {
-
 
766
                    partyName = "Unknown (" + fofoId + ")";
-
 
767
                }
-
 
768
            } else {
-
 
769
                partyName = "Unknown (" + normalized + ")";
-
 
770
            }
-
 
771
 
-
 
772
            // Get remark if available
-
 
773
            String remarkValue = "-";
-
 
774
            String messageValue = "-";
-
 
775
            String remarkTime = "-";
-
 
776
 
-
 
777
            if (fofoId != null && fofoRemarkMap.containsKey(fofoId)) {
-
 
778
                List<PartnerCollectionRemark> remarks = fofoRemarkMap.get(fofoId);
-
 
779
                if (!remarks.isEmpty()) {
-
 
780
                    PartnerCollectionRemark remark = remarks.get(0);
-
 
781
                    remarkValue = remark.getRemark() != null ? remark.getRemark().getValue() : "-";
-
 
782
                    messageValue = remark.getMessage() != null ? remark.getMessage() : "-";
-
 
783
                    remarkTime = remark.getCreateTimestamp() != null ? remark.getCreateTimestamp().format(timeFormatter) : "-";
-
 
784
                }
-
 
785
            }
-
 
786
 
-
 
787
            // Build call log data
-
 
788
            String recordingUrl = callLog.getRecordingUrl();
-
 
789
            String callStatus = callLog.getCallStatus();
-
 
790
            String callDuration = callLog.getCallDuration();
-
 
791
            String callDateTime = null;
-
 
792
            if (callLog.getCallDate() != null && callLog.getCallTime() != null) {
-
 
793
                LocalDateTime callDateTimeObj = LocalDateTime.of(callLog.getCallDate(), callLog.getCallTime());
-
 
794
                callDateTime = callDateTimeObj.format(dateTimeFormatter);
-
 
795
            }
-
 
796
 
-
 
797
            result.add(new CalledPartnerDetailModel(partyName, code, remarkValue, messageValue, remarkTime,
-
 
798
                    recordingUrl, callStatus, callDuration, callDateTime));
-
 
799
        }
-
 
800
 
-
 
801
        return result;
-
 
802
    }
-
 
803
 
-
 
804
    private Integer findFofoIdByMobile(String mobile) {
-
 
805
        // First check retailer_contact
-
 
806
        List<RetailerContact> contacts = retailerContactRepository.selectByMobile(mobile);
-
 
807
        if (contacts != null && !contacts.isEmpty()) {
-
 
808
            return contacts.get(0).getFofoId();
-
 
809
        }
-
 
810
 
-
 
811
        // Fallback to user.address
-
 
812
        List<Address> addresses = addressRepository.selectAllByPhoneNumber(mobile);
-
 
813
        if (addresses != null && !addresses.isEmpty()) {
-
 
814
            return addresses.get(0).getRetaierId();
-
 
815
        }
-
 
816
 
-
 
817
        return null;
-
 
818
    }
-
 
819
 
-
 
820
    private List<CalledPartnerDetailModel> buildCalledPartnerResult(List<PartnerCollectionRemark> allRemarks) {
-
 
821
        if (allRemarks.isEmpty()) {
-
 
822
            return Collections.emptyList();
-
 
823
        }
-
 
824
 
-
 
825
        // Get unique fofoIds for retailer lookup
-
 
826
        Set<Integer> fofoIds = allRemarks.stream()
-
 
827
                .map(PartnerCollectionRemark::getFofoId)
-
 
828
                .collect(Collectors.toSet());
-
 
829
        Map<Integer, CustomRetailer> retailerMap = Collections.emptyMap();
-
 
830
        try {
-
 
831
            retailerMap = retailerService.getFofoRetailers(new ArrayList<>(fofoIds));
-
 
832
        } catch (ProfitMandiBusinessException e) {
-
 
833
            LOGGER.error("Error fetching fofo stores", e);
-
 
834
        }
-
 
835
 
-
 
836
        // Fetch call logs for remarks that have agentCallLogId
-
 
837
        Map<Long, com.spice.profitmandi.dao.entity.cs.AgentCallLog> callLogMap = new HashMap<>();
-
 
838
        try {
-
 
839
            List<Long> callLogIds = allRemarks.stream()
-
 
840
                    .filter(r -> r.getAgentCallLogId() > 0)
-
 
841
                    .map(PartnerCollectionRemark::getAgentCallLogId)
-
 
842
                    .collect(Collectors.toList());
-
 
843
 
-
 
844
            if (!callLogIds.isEmpty()) {
-
 
845
                List<com.spice.profitmandi.dao.entity.cs.AgentCallLog> callLogs = agentCallLogRepository.findByIds(callLogIds);
-
 
846
                if (callLogs != null) {
-
 
847
                    callLogMap = callLogs.stream()
-
 
848
                            .collect(Collectors.toMap(com.spice.profitmandi.dao.entity.cs.AgentCallLog::getId, c -> c, (a, b) -> a));
-
 
849
                }
-
 
850
            }
-
 
851
        } catch (Exception e) {
-
 
852
            LOGGER.error("Error fetching call logs by ids", e);
-
 
853
        }
-
 
854
 
-
 
855
        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("hh:mm a");
-
 
856
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy hh:mm a");
-
 
857
        List<CalledPartnerDetailModel> result = new ArrayList<>();
-
 
858
 
-
 
859
        for (PartnerCollectionRemark remark : allRemarks) {
-
 
860
            CustomRetailer retailer = retailerMap.get(remark.getFofoId());
-
 
861
            String partyName = retailer != null
-
 
862
                    ? retailer.getBusinessName()
-
 
863
                    : "Unknown (" + remark.getFofoId() + ")";
-
 
864
            String code = retailer != null
-
 
865
                    ? retailer.getCode()
-
 
866
                    : "-";
-
 
867
 
-
 
868
            String remarkValue = remark.getRemark() != null
-
 
869
                    ? remark.getRemark().getValue()
-
 
870
                    : "-";
-
 
871
 
-
 
872
            String messageValue = remark.getMessage() != null
-
 
873
                    ? remark.getMessage()
-
 
874
                    : "-";
-
 
875
 
-
 
876
            String time = remark.getCreateTimestamp() != null
-
 
877
                    ? remark.getCreateTimestamp().format(timeFormatter)
-
 
878
                    : "-";
-
 
879
 
-
 
880
            // Get call log data if available
-
 
881
            String recordingUrl = null;
-
 
882
            String callStatus = null;
-
 
883
            String callDuration = null;
-
 
884
            String callDateTime = null;
-
 
885
 
-
 
886
            try {
-
 
887
                if (remark.getAgentCallLogId() > 0 && callLogMap.containsKey(remark.getAgentCallLogId())) {
-
 
888
                    com.spice.profitmandi.dao.entity.cs.AgentCallLog callLog = callLogMap.get(remark.getAgentCallLogId());
-
 
889
                    recordingUrl = callLog.getRecordingUrl();
-
 
890
                    callStatus = callLog.getCallStatus();
-
 
891
                    callDuration = callLog.getCallDuration();
-
 
892
                    if (callLog.getCallDate() != null && callLog.getCallTime() != null) {
-
 
893
                        LocalDateTime callDateTimeObj = LocalDateTime.of(callLog.getCallDate(), callLog.getCallTime());
-
 
894
                        callDateTime = callDateTimeObj.format(dateTimeFormatter);
-
 
895
                    }
-
 
896
                }
-
 
897
            } catch (Exception e) {
-
 
898
                LOGGER.error("Error processing call log for remark id: {}", remark.getId(), e);
-
 
899
            }
-
 
900
 
-
 
901
            result.add(new CalledPartnerDetailModel(partyName, code, remarkValue, messageValue, time,
-
 
902
                    recordingUrl, callStatus, callDuration, callDateTime));
-
 
903
        }
-
 
904
 
-
 
905
        return result;
-
 
906
    }
-
 
907
 
-
 
908
    @Override
-
 
909
    public List<List<String>> getRbmCallTargetRawDataByAuthId(int authId) throws Exception {
-
 
910
        List<List<String>> rows = new ArrayList<>();
-
 
911
 
-
 
912
        // Get auth user
-
 
913
        List<AuthUser> authUsers = authRepository.selectByIds(Collections.singletonList(authId));
-
 
914
        if (authUsers.isEmpty()) {
-
 
915
            return rows;
-
 
916
        }
-
 
917
        AuthUser authUser = authUsers.get(0);
-
 
918
 
-
 
919
        // Get positions to determine if L2
-
 
920
        List<Position> positions = positionRepository.selectPositionByAuthIds(Collections.singletonList(authId));
-
 
921
        boolean isL2 = positions.stream()
-
 
922
                .anyMatch(p -> ProfitMandiConstants.TICKET_CATEGORY_RBM == p.getCategoryId()
-
 
923
                        && EscalationType.L2.equals(p.getEscalationType()));
-
 
924
 
-
 
925
        LocalDateTime startDate = LocalDate.now().atStartOfDay();
-
 
926
        LocalDate firstOfMonth = LocalDate.now().withDayOfMonth(1);
-
 
927
        LocalDate endOfMonth = LocalDate.now().withDayOfMonth(LocalDate.now().lengthOfMonth()).plusDays(1);
-
 
928
 
-
 
929
        // Get fofo IDs from mtdBillingData (same source as Partner Count in getRbmCallTargetModels)
-
 
930
        List<RbmWeeklyBillingModel> mtdBillingData = getWeeklyBillingDataForMonth(firstOfMonth, endOfMonth);
-
 
931
 
-
 
932
        List<Integer> fofoIdList;
-
 
933
        if (isL2) {
-
 
934
            // L2: get fofo IDs from escalated tickets (same as getRbmCallTargetModels)
-
 
935
            List<Ticket> escalatedTickets = ticketRepository.selectOpenEscalatedTicketsByAuthIds(Collections.singletonList(authId));
-
 
936
            fofoIdList = escalatedTickets.stream()
-
 
937
                    .filter(t -> t.getL2AuthUser() == authId
-
 
938
                            || t.getL3AuthUser() == authId
-
 
939
                            || t.getL4AuthUser() == authId
-
 
940
                            || t.getL5AuthUser() == authId)
-
 
941
                    .map(Ticket::getFofoId)
-
 
942
                    .distinct()
-
 
943
                    .collect(Collectors.toList());
-
 
944
        } else {
-
 
945
            // L1: get fofo IDs from mtdBillingData with isTargetedPartner (same as Partner Count)
-
 
946
            fofoIdList = mtdBillingData.stream()
-
 
947
                    .filter(RbmWeeklyBillingModel::isTargetedPartner)
-
 
948
                    .filter(m -> m.getAuthId() == authId)
-
 
949
                    .map(RbmWeeklyBillingModel::getFofoId)
-
 
950
                    .distinct()
-
 
951
                    .collect(Collectors.toList());
-
 
952
        }
-
 
953
 
-
 
954
        if (fofoIdList.isEmpty()) {
-
 
955
            return rows;
-
 
956
        }
-
 
957
 
-
 
958
        // MTD billed fofoIds for zero billing check
-
 
959
        Set<Integer> mtdBilledFofoIds = mtdBillingData.stream()
-
 
960
                .filter(RbmWeeklyBillingModel::isMtdBilled)
-
 
961
                .map(RbmWeeklyBillingModel::getFofoId)
-
 
962
                .collect(Collectors.toSet());
-
 
963
 
-
 
964
        // Collection rank map for status calculation
-
 
965
        Map<Integer, Integer> collectionRankMap = new HashMap<>();
-
 
966
        try {
-
 
967
            collectionRankMap = partnerCollectionService.getCollectionRankMap(fofoIdList, startDate);
-
 
968
        } catch (ProfitMandiBusinessException e) {
-
 
969
            LOGGER.error("Error fetching collection rank map", e);
-
 
970
        }
-
 
971
 
-
 
972
        // Resolve partner names/codes
-
 
973
        Map<Integer, CustomRetailer> retailerMap = Collections.emptyMap();
-
 
974
        if (!fofoIdList.isEmpty()) {
-
 
975
            try {
-
 
976
                retailerMap = retailerService.getFofoRetailers(fofoIdList);
-
 
977
            } catch (ProfitMandiBusinessException e) {
-
 
978
                LOGGER.error("Error fetching fofo retailers for raw data", e);
-
 
979
            }
-
 
980
        }
-
 
981
 
-
 
982
        String rbmName = authUser.getFullName() + (isL2 ? " (L2)" : "");
-
 
983
 
-
 
984
        // Build rows for ALL partners (same count as Partner Count)
-
 
985
        for (Integer fofoId : fofoIdList) {
-
 
986
            // Default to rank 5 (Normal) for partners without collection plan
-
 
987
            int rank = collectionRankMap.getOrDefault(fofoId, 5);
-
 
988
            boolean hasZeroBilling = !mtdBilledFofoIds.contains(fofoId);
-
 
989
 
-
 
990
            // Status assignment with same priority as getRbmCallTargetModels
-
 
991
            String status;
-
 
992
            if (rank == 1) {
-
 
993
                status = "Plan Today";
-
 
994
            } else if (rank == 2) {
-
 
995
                status = "Carry Forward";
-
 
996
            } else if (hasZeroBilling) {
-
 
997
                status = "Zero Billing";
-
 
998
            } else if (rank == 3) {
-
 
999
                status = "Untouched";
-
 
1000
            } else if (rank == 4) {
-
 
1001
                status = "Future Plan";
-
 
1002
            } else {
-
 
1003
                status = "Normal";
-
 
1004
            }
-
 
1005
 
-
 
1006
            CustomRetailer retailer = retailerMap.get(fofoId);
-
 
1007
            String partnerName = retailer != null ? retailer.getBusinessName() : "Unknown (" + fofoId + ")";
-
 
1008
            String partnerCode = retailer != null ? retailer.getCode() : "-";
-
 
1009
 
-
 
1010
            rows.add(Arrays.asList(partnerName, partnerCode, status, rbmName));
-
 
1011
        }
-
 
1012
 
-
 
1013
        return rows;
-
 
1014
    }
-
 
1015
 
-
 
1016
    @Override
-
 
1017
    public List<List<String>> getAllRbmCallTargetRawData() throws Exception {
-
 
1018
        List<List<String>> rows = new ArrayList<>();
-
 
1019
 
-
 
1020
        // Get all L1 RBM positions (rows are emitted per L1 RBM).
-
 
1021
        // Call-log lookup happens against ALL callers via findAllByDate — so an L2/L3 or admin call
-
 
1022
        // still counts as "tried" for the partner.
-
 
1023
        List<Position> allRbmPositions = positionRepository
-
 
1024
                .selectPositionByCategoryId(ProfitMandiConstants.TICKET_CATEGORY_RBM).stream()
-
 
1025
                .filter(x -> EscalationType.L1.equals(x.getEscalationType()))
-
 
1026
                .collect(Collectors.toList());
-
 
1027
 
-
 
1028
        List<Integer> l1AuthIds = allRbmPositions.stream()
-
 
1029
                .map(Position::getAuthUserId).distinct().collect(Collectors.toList());
-
 
1030
 
-
 
1031
        if (l1AuthIds.isEmpty()) {
-
 
1032
            return rows;
-
 
1033
        }
-
 
1034
 
-
 
1035
        // Get auth user map
-
 
1036
        Map<Integer, AuthUser> authUserMap = authRepository.selectByIds(l1AuthIds).stream()
-
 
1037
                .collect(Collectors.toMap(AuthUser::getId, au -> au));
-
 
1038
 
-
 
1039
        // Positions per auth user (needed early to build L1 partner assignments below)
-
 
1040
        Map<Integer, List<Position>> positionsByAuthId = positionRepository.selectPositionByAuthIds(l1AuthIds).stream()
-
 
1041
                .collect(Collectors.groupingBy(Position::getAuthUserId));
-
 
1042
 
-
 
1043
        // Build L1 partner assignments from partner_position — SAME source the UI summary uses for
-
 
1044
        // Calling Target. storeGuyMap (email-based CS mapping) was returning smaller/different lists
-
 
1045
        // for users who are also L2, causing summary=43 but download=3 for Ebadullah.
-
 
1046
        Map<Integer, Integer> l1PositionIdToAuthId = new HashMap<>();
-
 
1047
        for (int rbmAuthId : l1AuthIds) {
-
 
1048
            List<Position> positions = positionsByAuthId.getOrDefault(rbmAuthId, Collections.emptyList());
-
 
1049
            for (Position p : positions) {
-
 
1050
                if (ProfitMandiConstants.TICKET_CATEGORY_RBM == p.getCategoryId()
-
 
1051
                        && EscalationType.L1.equals(p.getEscalationType())) {
-
 
1052
                    l1PositionIdToAuthId.put(p.getId(), rbmAuthId);
-
 
1053
                }
-
 
1054
            }
-
 
1055
        }
-
 
1056
 
-
 
1057
        Map<Integer, List<Integer>> rbmToFofoIdsMap = new HashMap<>();
-
 
1058
        Set<Integer> allFofoIds = new HashSet<>();
-
 
1059
        if (!l1PositionIdToAuthId.isEmpty()) {
-
 
1060
            List<com.spice.profitmandi.dao.entity.cs.PartnerPosition> allL1PPs =
-
 
1061
                    partnerPositionRepository.selectByPositionIds(new ArrayList<>(l1PositionIdToAuthId.keySet()));
-
 
1062
            // Group by owning RBM; keep partners distinct within each RBM.
-
 
1063
            Map<Integer, Set<Integer>> perRbmSets = new HashMap<>();
-
 
1064
            for (com.spice.profitmandi.dao.entity.cs.PartnerPosition pp : allL1PPs) {
-
 
1065
                Integer ownerAuthId = l1PositionIdToAuthId.get(pp.getPositionId());
-
 
1066
                if (ownerAuthId == null) continue;
-
 
1067
                perRbmSets.computeIfAbsent(ownerAuthId, k -> new HashSet<>()).add(pp.getFofoId());
-
 
1068
                allFofoIds.add(pp.getFofoId());
-
 
1069
            }
-
 
1070
            for (Map.Entry<Integer, Set<Integer>> e : perRbmSets.entrySet()) {
-
 
1071
                rbmToFofoIdsMap.put(e.getKey(), new ArrayList<>(e.getValue()));
-
 
1072
            }
-
 
1073
        }
-
 
1074
 
-
 
1075
        if (allFofoIds.isEmpty()) {
-
 
1076
            return rows;
-
 
1077
        }
-
 
1078
 
-
 
1079
        // Get fofo stores for filtering and name resolution
-
 
1080
        Map<Integer, FofoStore> fofoStoresMap = new HashMap<>();
-
 
1081
        try {
-
 
1082
            fofoStoresMap = fofoStoreRepository.selectByRetailerIds(new ArrayList<>(allFofoIds)).stream()
-
 
1083
                    .collect(Collectors.toMap(FofoStore::getId, x -> x, (a, b) -> a));
-
 
1084
        } catch (ProfitMandiBusinessException e) {
-
 
1085
            LOGGER.error("Error fetching fofo stores for all raw data", e);
-
 
1086
        }
-
 
1087
 
-
 
1088
        // Batch fetch collection rank map
-
 
1089
        LocalDateTime startDate = LocalDate.now().atStartOfDay();
-
 
1090
        Map<Integer, Integer> allCollectionRankMap = new HashMap<>();
-
 
1091
        try {
-
 
1092
            allCollectionRankMap = partnerCollectionService.getCollectionRankMap(new ArrayList<>(allFofoIds), startDate);
-
 
1093
        } catch (ProfitMandiBusinessException e) {
-
 
1094
            LOGGER.error("Error fetching collection rank map for all raw data", e);
-
 
1095
        }
-
 
1096
 
-
 
1097
        // MTD billing data
-
 
1098
        LocalDate firstOfMonth = LocalDate.now().withDayOfMonth(1);
-
 
1099
        LocalDate endOfMonth = LocalDate.now().withDayOfMonth(LocalDate.now().lengthOfMonth()).plusDays(1);
-
 
1100
        List<RbmWeeklyBillingModel> mtdBillingData = getWeeklyBillingDataForMonth(firstOfMonth, endOfMonth);
-
 
1101
        Set<Integer> allMtdBilledFofoIds = mtdBillingData.stream()
-
 
1102
                .filter(RbmWeeklyBillingModel::isMtdBilled)
-
 
1103
                .map(RbmWeeklyBillingModel::getFofoId)
-
 
1104
                .collect(Collectors.toSet());
-
 
1105
 
-
 
1106
        // Batch fetch partner collection remarks for escalation filtering
-
 
1107
        Map<Integer, PartnerCollectionRemark> allPartnerCollectionRemarks = new HashMap<>();
-
 
1108
        if (!allFofoIds.isEmpty()) {
-
 
1109
            List<Integer> allRemarkIds = partnerCollectionRemarkRepository.selectMaxRemarkId(new ArrayList<>(allFofoIds));
-
 
1110
            if (!allRemarkIds.isEmpty()) {
-
 
1111
                allPartnerCollectionRemarks = partnerCollectionRemarkRepository.selectByIds(allRemarkIds).stream()
-
 
1112
                        .collect(Collectors.toMap(PartnerCollectionRemark::getFofoId, x -> x, (a, b) -> a));
-
 
1113
            }
-
 
1114
        }
-
 
1115
 
-
 
1116
        // Resolve partner names/codes in batch
-
 
1117
        Map<Integer, CustomRetailer> retailerMap = Collections.emptyMap();
-
 
1118
        try {
-
 
1119
            retailerMap = retailerService.getFofoRetailers(new ArrayList<>(allFofoIds));
-
 
1120
        } catch (ProfitMandiBusinessException e) {
-
 
1121
            LOGGER.error("Error fetching fofo retailers for all raw data", e);
-
 
1122
        }
-
 
1123
 
-
 
1124
        // Batch-fetch EVERY call log for today (across all callers, not just RBMs) and build a
-
 
1125
        // fofoId -> latest AgentCallLog map. We use findAllByDate here so that calls placed by
-
 
1126
        // admins, escalation agents, or anyone whose authId isn't in the RBM position table are
-
 
1127
        // still surfaced. Downstream we only read this map for fofoIds we actually emit, so
-
 
1128
        // extra entries are harmless.
-
 
1129
        Map<Integer, AgentCallLog> latestCallByFofo = new HashMap<>();
-
 
1130
        try {
-
 
1131
            List<AgentCallLog> todayCallLogs = agentCallLogRepository.findAllByDate(LocalDate.now());
-
 
1132
            Set<String> normalizedMobiles = new HashSet<>();
-
 
1133
            for (AgentCallLog log : todayCallLogs) {
-
 
1134
                if (log.getCustomerNumber() != null) {
-
 
1135
                    String n = log.getCustomerNumber();
-
 
1136
                    normalizedMobiles.add(n.startsWith("+91") ? n.substring(3) : n);
-
 
1137
                }
-
 
1138
            }
-
 
1139
            Map<String, Integer> mobileToFofoIdMap = buildMobileToFofoIdMap(normalizedMobiles);
-
 
1140
            for (AgentCallLog log : todayCallLogs) {
-
 
1141
                if (log.getCustomerNumber() == null) continue;
-
 
1142
                String n = log.getCustomerNumber();
-
 
1143
                String normalized = n.startsWith("+91") ? n.substring(3) : n;
-
 
1144
                Integer fofoId = mobileToFofoIdMap.get(normalized);
-
 
1145
                if (fofoId == null) continue;
-
 
1146
                AgentCallLog existing = latestCallByFofo.get(fofoId);
-
 
1147
                if (existing == null || (log.getId() != null && existing.getId() != null && log.getId() > existing.getId())) {
-
 
1148
                    latestCallByFofo.put(fofoId, log);
-
 
1149
                }
-
 
1150
            }
-
 
1151
        } catch (Exception e) {
-
 
1152
            LOGGER.error("Error building latest call status map for all raw data", e);
-
 
1153
        }
-
 
1154
 
-
 
1155
        // Process each L1 RBM
-
 
1156
        // Note: no cross-RBM dedup — a partner assigned to multiple RBMs appears once per RBM,
-
 
1157
        // matching the UI summary counts so row totals per RBM equal that RBM's "Calling Target".
-
 
1158
        for (int rbmAuthId : l1AuthIds) {
-
 
1159
            AuthUser authUser = authUserMap.get(rbmAuthId);
-
 
1160
            if (authUser == null) {
-
 
1161
                continue;
-
 
1162
            }
-
 
1163
 
-
 
1164
            List<Integer> fofoIdList = rbmToFofoIdsMap.getOrDefault(rbmAuthId, Collections.emptyList());
-
 
1165
            if (fofoIdList.isEmpty()) {
-
 
1166
                continue; // No L1 partner assignments for this RBM
-
 
1167
            }
-
 
1168
 
-
 
1169
            // Filter escalated partners for L1 RBMs
-
 
1170
            List<Position> positions = positionsByAuthId.getOrDefault(rbmAuthId, Collections.emptyList());
-
 
1171
            boolean isRBMAndL1 = positions.stream()
-
 
1172
                    .anyMatch(position ->
-
 
1173
                            ProfitMandiConstants.TICKET_CATEGORY_RBM == position.getCategoryId()
-
 
1174
                                    && EscalationType.L1.equals(position.getEscalationType()));
-
 
1175
 
-
 
1176
            List<Integer> fofoIds = fofoIdList;
-
 
1177
            if (isRBMAndL1) {
-
 
1178
                Map<Integer, PartnerCollectionRemark> partnerRemarks = new HashMap<>();
-
 
1179
                for (Integer fofoId : fofoIdList) {
-
 
1180
                    if (allPartnerCollectionRemarks.containsKey(fofoId)) {
-
 
1181
                        partnerRemarks.put(fofoId, allPartnerCollectionRemarks.get(fofoId));
-
 
1182
                    }
-
 
1183
                }
-
 
1184
                Map<Integer, PartnerCollectionRemark> finalPartnerRemarks = partnerRemarks;
-
 
1185
                fofoIds = fofoIdList.stream()
-
 
1186
                        .filter(fofoId -> {
-
 
1187
                            if (!finalPartnerRemarks.containsKey(fofoId)) return true;
-
 
1188
                            PartnerCollectionRemark pcr = finalPartnerRemarks.get(fofoId);
-
 
1189
                            return !(CollectionRemark.RBM_L2_ESCALATION.equals(pcr.getRemark())
-
 
1190
                                    || CollectionRemark.SALES_ESCALATION.equals(pcr.getRemark()));
-
 
1191
                        })
-
 
1192
                        .collect(Collectors.toList());
-
 
1193
            }
-
 
1194
 
-
 
1195
            // Filter to only external, ACTIVE or REVIVAL stores
-
 
1196
            Map<Integer, FofoStore> finalFofoStoresMap = fofoStoresMap;
-
 
1197
            List<Integer> validFofoIds = fofoIds.stream()
-
 
1198
                    .filter(fofoId -> {
-
 
1199
                        FofoStore store = finalFofoStoresMap.get(fofoId);
-
 
1200
                        if (store == null || store.isInternal()) return false;
-
 
1201
                        return ActivationType.ACTIVE.equals(store.getActivationType())
-
 
1202
                                || ActivationType.REVIVAL.equals(store.getActivationType());
-
 
1203
                    })
-
 
1204
                    .collect(Collectors.toList());
-
 
1205
 
-
 
1206
            String rbmName = authUser.getFullName();
-
 
1207
 
-
 
1208
            // Categorize each partner and add a row per RBM assignment.
-
 
1209
            for (Integer fofoId : validFofoIds) {
-
 
1210
                int rank = allCollectionRankMap.getOrDefault(fofoId, 5);
-
 
1211
                boolean hasZeroBilling = !allMtdBilledFofoIds.contains(fofoId);
-
 
1212
 
-
 
1213
                String status;
-
 
1214
                if (rank == 1) {
-
 
1215
                    status = "Plan Today";
-
 
1216
                } else if (rank == 2) {
-
 
1217
                    status = "Carry Forward";
-
 
1218
                } else if (hasZeroBilling) {
-
 
1219
                    status = "Zero Billing";
-
 
1220
                } else if (rank == 3) {
-
 
1221
                    status = "Untouched";
-
 
1222
                } else {
-
 
1223
                    continue; // Skip Future Plan and Normal — only include calling target parties
-
 
1224
                }
-
 
1225
 
-
 
1226
                CustomRetailer retailer = retailerMap.get(fofoId);
-
 
1227
                String partnerName = retailer != null ? retailer.getBusinessName() : "Unknown (" + fofoId + ")";
-
 
1228
                String partnerCode = retailer != null ? retailer.getCode() : "-";
-
 
1229
 
-
 
1230
                // "Did Not Try" means the PARTNER has no call log today from anyone — not tied to this RBM.
-
 
1231
                // If any caller reached this partner, show the actual status.
-
 
1232
                String latestCallStatus = "Did Not Try";
-
 
1233
                AgentCallLog latestLog = latestCallByFofo.get(fofoId);
-
 
1234
                if (latestLog != null && latestLog.getCallStatus() != null && !latestLog.getCallStatus().isEmpty()) {
-
 
1235
                    latestCallStatus = latestLog.getCallStatus();
-
 
1236
                }
-
 
1237
 
-
 
1238
                rows.add(Arrays.asList(partnerName, partnerCode, status, rbmName, latestCallStatus));
-
 
1239
            }
-
 
1240
        }
-
 
1241
 
-
 
1242
        return rows;
-
 
1243
    }
-
 
1244
 
-
 
1245
    /**
-
 
1246
     * Get count of distinct partners called today based on call logs.
-
 
1247
     * Maps customerNumber from call log to fofoId using retailer_contact and address.
-
 
1248
     * If same fofoId is called multiple times, counts only once.
-
 
1249
     * Numbers without fofoId mapping are also counted (by distinct customer number).
-
 
1250
     *
-
 
1251
     * @param authId the RBM auth ID
-
 
1252
     * @return count of distinct partners/numbers called today
-
 
1253
     */
-
 
1254
    public long getCalledCountFromCallLogs(long authId) {
-
 
1255
        return getCalledCountFromCallLogs(authId, LocalDate.now());
-
 
1256
    }
-
 
1257
 
-
 
1258
    public long getCalledCountFromCallLogs(long authId, LocalDate date) {
-
 
1259
        return getCallStats(authId, date)[0];
-
 
1260
    }
-
 
1261
 
-
 
1262
    /**
-
 
1263
     * Batch-builds a mobile (normalized, +91 stripped) -> fofoId mapping for all the given mobiles
-
 
1264
     * in just two queries (retailer_contact, then address fallback for unmapped numbers).
-
 
1265
     * Replaces the previous per-call-log findFofoIdByMobile() N+1 lookups.
-
 
1266
     */
-
 
1267
    private Map<String, Integer> buildMobileToFofoIdMap(Set<String> normalizedMobiles) {
-
 
1268
        Map<String, Integer> result = new HashMap<>();
-
 
1269
        if (normalizedMobiles == null || normalizedMobiles.isEmpty()) {
-
 
1270
            return result;
-
 
1271
        }
-
 
1272
        List<String> mobilesList = new ArrayList<>(normalizedMobiles);
-
 
1273
 
-
 
1274
        // First pass: retailer_contact
-
 
1275
        List<RetailerContact> contacts = retailerContactRepository.selectByMobiles(mobilesList);
-
 
1276
        for (RetailerContact rc : contacts) {
-
 
1277
            // Keep the first fofoId we see per mobile (matches old single-mobile behavior of get(0))
-
 
1278
            result.putIfAbsent(rc.getMobile(), rc.getFofoId());
-
 
1279
        }
-
 
1280
 
-
 
1281
        // Second pass: address fallback for mobiles not yet mapped
-
 
1282
        List<String> unmapped = mobilesList.stream()
-
 
1283
                .filter(m -> !result.containsKey(m))
-
 
1284
                .collect(Collectors.toList());
-
 
1285
        if (!unmapped.isEmpty()) {
-
 
1286
            List<Address> addresses = addressRepository.selectAllByPhoneNumbers(unmapped);
-
 
1287
            for (Address addr : addresses) {
-
 
1288
                result.putIfAbsent(addr.getPhoneNumber(), addr.getRetaierId());
-
 
1289
            }
-
 
1290
        }
-
 
1291
        return result;
-
 
1292
    }
-
 
1293
 
-
 
1294
    /**
-
 
1295
     * In-memory variant of getCallStats() that uses pre-fetched call logs and mobile→fofoId mapping.
-
 
1296
     * Used by the batch-fetched RBM Call Target loop to avoid N+1 query patterns.
-
 
1297
     */
-
 
1298
    private long[] getCallStatsFromLogs(List<AgentCallLog> callLogs, Map<String, Integer> mobileToFofoIdMap) {
-
 
1299
        if (callLogs == null || callLogs.isEmpty()) {
-
 
1300
            return new long[]{0, 0, 0};
-
 
1301
        }
-
 
1302
 
-
 
1303
        Set<Integer> calledFofoIds = new HashSet<>();
-
 
1304
        Set<String> calledNumbersWithoutFofoId = new HashSet<>();
-
 
1305
        long totalRecordingCalls = 0;
-
 
1306
        Set<String> uniqueRecordingNumbers = new HashSet<>();
-
 
1307
 
-
 
1308
        for (AgentCallLog callLog : callLogs) {
-
 
1309
            String customerNumber = callLog.getCustomerNumber();
-
 
1310
            if (customerNumber != null) {
-
 
1311
                String normalized = customerNumber.startsWith("+91") ? customerNumber.substring(3) : customerNumber;
-
 
1312
                Integer fofoId = mobileToFofoIdMap.get(normalized);
-
 
1313
                if (fofoId != null) {
-
 
1314
                    calledFofoIds.add(fofoId);
-
 
1315
                } else {
-
 
1316
                    calledNumbersWithoutFofoId.add(normalized);
-
 
1317
                }
-
 
1318
 
-
 
1319
                if (callLog.getRecordingUrl() != null && !callLog.getRecordingUrl().isEmpty()
-
 
1320
                        && !"None".equalsIgnoreCase(callLog.getRecordingUrl())) {
-
 
1321
                    totalRecordingCalls++;
-
 
1322
                    uniqueRecordingNumbers.add(normalized);
-
 
1323
                }
-
 
1324
            }
-
 
1325
        }
-
 
1326
 
-
 
1327
        long calledCount = calledFofoIds.size() + calledNumbersWithoutFofoId.size();
-
 
1328
        return new long[]{calledCount, totalRecordingCalls, uniqueRecordingNumbers.size()};
-
 
1329
    }
-
 
1330
 
627
    public List<RbmCallTargetModel> getRbmCallTargetModels(LocalDate queryDate) throws Exception {
1331
    public List<RbmCallTargetModel> getRbmCallTargetModels(LocalDate queryDate) throws Exception {
628
        long methodStart = System.currentTimeMillis();
1332
        long methodStart = System.currentTimeMillis();
629
        List<RbmCallTargetModel> rbmCallTargetModels = new ArrayList<>();
1333
        List<RbmCallTargetModel> rbmCallTargetModels = new ArrayList<>();
630
 
1334
 
631
        // Get all RBM positions (L1 and L2)
1335
        // Get all RBM positions (L1 and L2)
Line 978... Line 1682...
978
            todayTargetPartners.addAll(carryForwardPartners);
1682
            todayTargetPartners.addAll(carryForwardPartners);
979
            todayTargetPartners.addAll(zeroBillingPartners);
1683
            todayTargetPartners.addAll(zeroBillingPartners);
980
            todayTargetPartners.addAll(untouchedPartners);
1684
            todayTargetPartners.addAll(untouchedPartners);
981
 
1685
 
982
            // Value Achieved = All distinct partners called today (from pre-fetched call logs)
1686
            // Value Achieved = All distinct partners called today (from pre-fetched call logs)
-
 
1687
            List<AgentCallLog> l1Logs = callLogsByAuthId.get((long) rbmAuthId);
983
            long[] callStats = getCallStatsFromLogs(callLogsByAuthId.get((long) rbmAuthId), mobileToFofoIdMap);
1688
            long[] callStats = getCallStatsFromLogs(l1Logs, mobileToFofoIdMap);
984
            targetModel.setValueTargetAchieved(callStats[0]);
1689
            targetModel.setValueTargetAchieved(callStats[0]);
985
            targetModel.setTotalRecordingCalls(callStats[1]);
1690
            targetModel.setTotalRecordingCalls(callStats[1]);
986
            targetModel.setUniqueRecordingCalls(callStats[2]);
1691
            targetModel.setUniqueRecordingCalls(callStats[2]);
-
 
1692
            targetModel.setTriedUniqueCalls(computeUniqueTriedCount(l1Logs));
987
 
1693
 
988
            // Keep todayRemarks for movedToFuture calculation
1694
            // Keep todayRemarks for movedToFuture calculation
989
            List<PartnerCollectionRemark> todayRemarks = remarksByAuthId.getOrDefault(rbmAuthId, Collections.emptyList());
1695
            List<PartnerCollectionRemark> todayRemarks = remarksByAuthId.getOrDefault(rbmAuthId, Collections.emptyList());
990
 
1696
 
991
            // Moved to Future = Partners in Future Plan category who have a remark today
1697
            // Moved to Future = Partners in Future Plan category who have a remark today
Line 1133... Line 1839...
1133
                // Pure L2 (not also L1) — target is only L2 escalation
1839
                // Pure L2 (not also L1) — target is only L2 escalation
1134
                l2Model.setTodayTargetOfCall(l2TargetFofoIds.size());
1840
                l2Model.setTodayTargetOfCall(l2TargetFofoIds.size());
1135
            }
1841
            }
1136
 
1842
 
1137
            // Value Achieved = All distinct partners called today (from pre-fetched call logs)
1843
            // Value Achieved = All distinct partners called today (from pre-fetched call logs)
-
 
1844
            List<AgentCallLog> l2Logs = callLogsByAuthId.get((long) l2AuthId);
1138
            long[] l2CallStats = getCallStatsFromLogs(callLogsByAuthId.get((long) l2AuthId), mobileToFofoIdMap);
1845
            long[] l2CallStats = getCallStatsFromLogs(l2Logs, mobileToFofoIdMap);
1139
            l2Model.setValueTargetAchieved(l2CallStats[0]);
1846
            l2Model.setValueTargetAchieved(l2CallStats[0]);
1140
            l2Model.setTotalRecordingCalls(l2CallStats[1]);
1847
            l2Model.setTotalRecordingCalls(l2CallStats[1]);
1141
            l2Model.setUniqueRecordingCalls(l2CallStats[2]);
1848
            l2Model.setUniqueRecordingCalls(l2CallStats[2]);
-
 
1849
            l2Model.setTriedUniqueCalls(computeUniqueTriedCount(l2Logs));
1142
 
1850
 
1143
            l2Model.setOutOfSequenceCount(outOfSequenceCountByAuthId.getOrDefault(l2AuthId, 0L));
1851
            l2Model.setOutOfSequenceCount(outOfSequenceCountByAuthId.getOrDefault(l2AuthId, 0L));
1144
            rbmCallTargetModels.add(l2Model);
1852
            rbmCallTargetModels.add(l2Model);
1145
        }
1853
        }
1146
 
1854
 
Line 1174... Line 1882...
1174
 
1882
 
1175
            // L3 Target = partners with RBM_L3_ESCALATION as latest remark
1883
            // L3 Target = partners with RBM_L3_ESCALATION as latest remark
1176
            l3Model.setTodayTargetOfCall(l3TargetFofoIds.size());
1884
            l3Model.setTodayTargetOfCall(l3TargetFofoIds.size());
1177
 
1885
 
1178
            // Value Achieved = All distinct partners called today (from pre-fetched call logs)
1886
            // Value Achieved = All distinct partners called today (from pre-fetched call logs)
-
 
1887
            List<AgentCallLog> l3Logs = callLogsByAuthId.get((long) l3AuthId);
1179
            long[] l3CallStats = getCallStatsFromLogs(callLogsByAuthId.get((long) l3AuthId), mobileToFofoIdMap);
1888
            long[] l3CallStats = getCallStatsFromLogs(l3Logs, mobileToFofoIdMap);
1180
            l3Model.setValueTargetAchieved(l3CallStats[0]);
1889
            l3Model.setValueTargetAchieved(l3CallStats[0]);
1181
            l3Model.setTotalRecordingCalls(l3CallStats[1]);
1890
            l3Model.setTotalRecordingCalls(l3CallStats[1]);
1182
            l3Model.setUniqueRecordingCalls(l3CallStats[2]);
1891
            l3Model.setUniqueRecordingCalls(l3CallStats[2]);
-
 
1892
            l3Model.setTriedUniqueCalls(computeUniqueTriedCount(l3Logs));
1183
 
1893
 
1184
            l3Model.setOutOfSequenceCount(outOfSequenceCountByAuthId.getOrDefault(l3AuthId, 0L));
1894
            l3Model.setOutOfSequenceCount(outOfSequenceCountByAuthId.getOrDefault(l3AuthId, 0L));
1185
            rbmCallTargetModels.add(l3Model);
1895
            rbmCallTargetModels.add(l3Model);
1186
        }
1896
        }
1187
 
1897
 
Line 1362... Line 2072...
1362
 
2072
 
1363
        LOGGER.info("RBM Call Target - TOTAL TIME: {}ms, RBM count: {}", System.currentTimeMillis() - methodStart, sortedModels.size());
2073
        LOGGER.info("RBM Call Target - TOTAL TIME: {}ms, RBM count: {}", System.currentTimeMillis() - methodStart, sortedModels.size());
1364
        return sortedModels;
2074
        return sortedModels;
1365
    }
2075
    }
1366
 
2076
 
1367
    @Override
-
 
1368
    public List<OutOfSequenceDetailModel> getOutOfSequenceDetails(int authId) {
-
 
1369
 
-
 
1370
        LocalDate today = LocalDate.now();
-
 
1371
        LocalDateTime start = today.atStartOfDay();
-
 
1372
        LocalDateTime end = today.plusDays(1).atStartOfDay();
-
 
1373
 
-
 
1374
        List<RbmCallSequenceLog> logs =
-
 
1375
                rbmCallSequenceLogRepository.selectByAuthIdAndDateRange(authId, start, end);
-
 
1376
 
-
 
1377
        Map<Integer, RbmCallSequenceLog> uniqueOosLogsByFofoId = new LinkedHashMap<>();
-
 
1378
 
-
 
1379
        for (RbmCallSequenceLog log : logs) {
-
 
1380
            if (log.isOutOfSequence()) {
-
 
1381
                // Keep only the first occurrence per fofoId (latest entry since ordered by id DESC)
-
 
1382
                uniqueOosLogsByFofoId.putIfAbsent(log.getFofoId(), log);
-
 
1383
            }
-
 
1384
        }
-
 
1385
 
-
 
1386
        if (uniqueOosLogsByFofoId.isEmpty()) {
-
 
1387
            return Collections.emptyList();
-
 
1388
        }
-
 
1389
 
-
 
1390
        Set<Integer> fofoIds = uniqueOosLogsByFofoId.keySet();
-
 
1391
        Map<Integer, CustomRetailer> retailerMap = Collections.emptyMap();
-
 
1392
        try {
-
 
1393
            retailerMap = retailerService.getFofoRetailers(new ArrayList<>(fofoIds));
-
 
1394
        } catch (ProfitMandiBusinessException e) {
-
 
1395
            LOGGER.error("Error fetching fofo stores", e);
-
 
1396
        }
-
 
1397
 
-
 
1398
        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("hh:mm a");
-
 
1399
        List<OutOfSequenceDetailModel> result = new ArrayList<>();
-
 
1400
 
-
 
1401
        for (RbmCallSequenceLog log : uniqueOosLogsByFofoId.values()) {
-
 
1402
            CustomRetailer retailer = retailerMap.get(log.getFofoId());
-
 
1403
            String partyName = retailer != null
-
 
1404
                    ? retailer.getBusinessName()
-
 
1405
                    : "Unknown (" + log.getFofoId() + ")";
-
 
1406
            String code = retailer != null
-
 
1407
                    ? retailer.getCode()
-
 
1408
                    : "-";
-
 
1409
 
-
 
1410
            String time = log.getCreateTimestamp() != null
-
 
1411
                    ? log.getCreateTimestamp().format(timeFormatter)
-
 
1412
                    : "-";
-
 
1413
 
-
 
1414
            result.add(new OutOfSequenceDetailModel(partyName, code, time));
-
 
1415
        }
-
 
1416
 
-
 
1417
        return result;
-
 
1418
    }
-
 
1419
 
-
 
1420
    @Override
-
 
1421
    public List<CalledPartnerDetailModel> getCalledPartnerDetails(int authId) throws ProfitMandiBusinessException {
-
 
1422
        return getCalledPartnerDetails(authId, LocalDate.now());
-
 
1423
    }
-
 
1424
 
-
 
1425
    @Override
-
 
1426
    public List<CalledPartnerDetailModel> getCalledPartnerDetails(int authId, LocalDate date) throws ProfitMandiBusinessException {
-
 
1427
        // Get all call logs for this auth user on this date
-
 
1428
        LOGGER.info("getCalledPartnerDetails: authId={}, date={}", authId, date);
-
 
1429
        List<AgentCallLog> callLogs = agentCallLogRepository.findByAuthIdAndDate(authId, date);
-
 
1430
        LOGGER.info("Found {} call logs for authId={} on date={}", callLogs.size(), authId, date);
-
 
1431
 
-
 
1432
        if (callLogs.isEmpty()) {
-
 
1433
            return Collections.emptyList();
-
 
1434
        }
-
 
1435
 
-
 
1436
        // Build a map of normalized customer number -> fofoId
-
 
1437
        Map<String, Integer> customerToFofoIdMap = new HashMap<>();
-
 
1438
        Set<String> normalizedNumbers = new HashSet<>();
-
 
1439
 
-
 
1440
        for (AgentCallLog callLog : callLogs) {
-
 
1441
            String customerNumber = callLog.getCustomerNumber();
-
 
1442
            if (customerNumber != null) {
-
 
1443
                String normalized = customerNumber.startsWith("+91") ? customerNumber.substring(3) : customerNumber;
-
 
1444
                normalizedNumbers.add(normalized);
-
 
1445
            }
-
 
1446
        }
-
 
1447
 
-
 
1448
        // For each normalized number, find fofoId from retailer_contact first, then address
-
 
1449
        for (String mobile : normalizedNumbers) {
-
 
1450
            Integer fofoId = findFofoIdByMobile(mobile);
-
 
1451
            if (fofoId != null) {
-
 
1452
                customerToFofoIdMap.put(mobile, fofoId);
-
 
1453
            }
-
 
1454
        }
-
 
1455
 
-
 
1456
        // Get unique fofoIds for retailer lookup
-
 
1457
        Set<Integer> fofoIds = new HashSet<>(customerToFofoIdMap.values());
-
 
1458
        Map<Integer, CustomRetailer> retailerMap = Collections.emptyMap();
-
 
1459
        if (!fofoIds.isEmpty()) {
-
 
1460
            try {
-
 
1461
                retailerMap = retailerService.getFofoRetailers(new ArrayList<>(fofoIds));
-
 
1462
            } catch (ProfitMandiBusinessException e) {
-
 
1463
                LOGGER.error("Error fetching fofo stores", e);
-
 
1464
            }
-
 
1465
        }
-
 
1466
 
-
 
1467
        // Get today's remarks for these fofoIds
-
 
1468
        Map<Integer, List<PartnerCollectionRemark>> fofoRemarkMap = new HashMap<>();
-
 
1469
        if (!fofoIds.isEmpty()) {
-
 
1470
            List<PartnerCollectionRemark> todayRemarks = partnerCollectionRemarkRepository
-
 
1471
                    .selectAllByFofoIdsOnDate(new ArrayList<>(fofoIds), date);
-
 
1472
            for (PartnerCollectionRemark remark : todayRemarks) {
-
 
1473
                fofoRemarkMap.computeIfAbsent(remark.getFofoId(), k -> new ArrayList<>()).add(remark);
-
 
1474
            }
-
 
1475
        }
-
 
1476
 
-
 
1477
        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("hh:mm a");
-
 
1478
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy hh:mm a");
-
 
1479
        List<CalledPartnerDetailModel> result = new ArrayList<>();
-
 
1480
 
-
 
1481
        for (com.spice.profitmandi.dao.entity.cs.AgentCallLog callLog : callLogs) {
-
 
1482
            String customerNumber = callLog.getCustomerNumber();
-
 
1483
            if (customerNumber == null) {
-
 
1484
                continue;
-
 
1485
            }
-
 
1486
 
-
 
1487
            String normalized = customerNumber.startsWith("+91") ? customerNumber.substring(3) : customerNumber;
-
 
1488
            Integer fofoId = customerToFofoIdMap.get(normalized);
-
 
1489
 
-
 
1490
            String partyName = "Unknown";
-
 
1491
            String code = "-";
-
 
1492
 
-
 
1493
            if (fofoId != null) {
-
 
1494
                CustomRetailer retailer = retailerMap.get(fofoId);
-
 
1495
                if (retailer != null) {
-
 
1496
                    partyName = retailer.getBusinessName();
-
 
1497
                    code = retailer.getCode();
-
 
1498
                } else {
-
 
1499
                    partyName = "Unknown (" + fofoId + ")";
-
 
1500
                }
-
 
1501
            } else {
-
 
1502
                partyName = "Unknown (" + normalized + ")";
-
 
1503
            }
-
 
1504
 
-
 
1505
            // Get remark if available
-
 
1506
            String remarkValue = "-";
-
 
1507
            String messageValue = "-";
-
 
1508
            String remarkTime = "-";
-
 
1509
 
-
 
1510
            if (fofoId != null && fofoRemarkMap.containsKey(fofoId)) {
-
 
1511
                List<PartnerCollectionRemark> remarks = fofoRemarkMap.get(fofoId);
-
 
1512
                if (!remarks.isEmpty()) {
-
 
1513
                    PartnerCollectionRemark remark = remarks.get(0);
-
 
1514
                    remarkValue = remark.getRemark() != null ? remark.getRemark().getValue() : "-";
-
 
1515
                    messageValue = remark.getMessage() != null ? remark.getMessage() : "-";
-
 
1516
                    remarkTime = remark.getCreateTimestamp() != null ? remark.getCreateTimestamp().format(timeFormatter) : "-";
-
 
1517
                }
-
 
1518
            }
-
 
1519
 
-
 
1520
            // Build call log data
-
 
1521
            String recordingUrl = callLog.getRecordingUrl();
-
 
1522
            String callStatus = callLog.getCallStatus();
-
 
1523
            String callDuration = callLog.getCallDuration();
-
 
1524
            String callDateTime = null;
-
 
1525
            if (callLog.getCallDate() != null && callLog.getCallTime() != null) {
-
 
1526
                LocalDateTime callDateTimeObj = LocalDateTime.of(callLog.getCallDate(), callLog.getCallTime());
-
 
1527
                callDateTime = callDateTimeObj.format(dateTimeFormatter);
-
 
1528
            }
-
 
1529
 
-
 
1530
            result.add(new CalledPartnerDetailModel(partyName, code, remarkValue, messageValue, remarkTime,
-
 
1531
                    recordingUrl, callStatus, callDuration, callDateTime));
-
 
1532
        }
-
 
1533
 
-
 
1534
        return result;
-
 
1535
    }
-
 
1536
 
-
 
1537
    private Integer findFofoIdByMobile(String mobile) {
-
 
1538
        // First check retailer_contact
-
 
1539
        List<RetailerContact> contacts = retailerContactRepository.selectByMobile(mobile);
-
 
1540
        if (contacts != null && !contacts.isEmpty()) {
-
 
1541
            return contacts.get(0).getFofoId();
-
 
1542
        }
-
 
1543
 
-
 
1544
        // Fallback to user.address
-
 
1545
        List<Address> addresses = addressRepository.selectAllByPhoneNumber(mobile);
-
 
1546
        if (addresses != null && !addresses.isEmpty()) {
-
 
1547
            return addresses.get(0).getRetaierId();
-
 
1548
        }
-
 
1549
 
-
 
1550
        return null;
-
 
1551
    }
-
 
1552
 
-
 
1553
    private List<CalledPartnerDetailModel> buildCalledPartnerResult(List<PartnerCollectionRemark> allRemarks) {
-
 
1554
        if (allRemarks.isEmpty()) {
-
 
1555
            return Collections.emptyList();
-
 
1556
        }
-
 
1557
 
-
 
1558
        // Get unique fofoIds for retailer lookup
-
 
1559
        Set<Integer> fofoIds = allRemarks.stream()
-
 
1560
                .map(PartnerCollectionRemark::getFofoId)
-
 
1561
                .collect(Collectors.toSet());
-
 
1562
        Map<Integer, CustomRetailer> retailerMap = Collections.emptyMap();
-
 
1563
        try {
-
 
1564
            retailerMap = retailerService.getFofoRetailers(new ArrayList<>(fofoIds));
-
 
1565
        } catch (ProfitMandiBusinessException e) {
-
 
1566
            LOGGER.error("Error fetching fofo stores", e);
-
 
1567
        }
-
 
1568
 
-
 
1569
        // Fetch call logs for remarks that have agentCallLogId
-
 
1570
        Map<Long, com.spice.profitmandi.dao.entity.cs.AgentCallLog> callLogMap = new HashMap<>();
-
 
1571
        try {
-
 
1572
            List<Long> callLogIds = allRemarks.stream()
-
 
1573
                    .filter(r -> r.getAgentCallLogId() > 0)
-
 
1574
                    .map(PartnerCollectionRemark::getAgentCallLogId)
-
 
1575
                    .collect(Collectors.toList());
-
 
1576
 
-
 
1577
            if (!callLogIds.isEmpty()) {
-
 
1578
                List<com.spice.profitmandi.dao.entity.cs.AgentCallLog> callLogs = agentCallLogRepository.findByIds(callLogIds);
-
 
1579
                if (callLogs != null) {
-
 
1580
                    callLogMap = callLogs.stream()
-
 
1581
                            .collect(Collectors.toMap(com.spice.profitmandi.dao.entity.cs.AgentCallLog::getId, c -> c, (a, b) -> a));
-
 
1582
                }
-
 
1583
            }
-
 
1584
        } catch (Exception e) {
-
 
1585
            LOGGER.error("Error fetching call logs by ids", e);
-
 
1586
        }
-
 
1587
 
-
 
1588
        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("hh:mm a");
-
 
1589
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy hh:mm a");
-
 
1590
        List<CalledPartnerDetailModel> result = new ArrayList<>();
-
 
1591
 
-
 
1592
        for (PartnerCollectionRemark remark : allRemarks) {
-
 
1593
            CustomRetailer retailer = retailerMap.get(remark.getFofoId());
-
 
1594
            String partyName = retailer != null
-
 
1595
                    ? retailer.getBusinessName()
-
 
1596
                    : "Unknown (" + remark.getFofoId() + ")";
-
 
1597
            String code = retailer != null
-
 
1598
                    ? retailer.getCode()
-
 
1599
                    : "-";
-
 
1600
 
-
 
1601
            String remarkValue = remark.getRemark() != null
-
 
1602
                    ? remark.getRemark().getValue()
-
 
1603
                    : "-";
-
 
1604
 
-
 
1605
            String messageValue = remark.getMessage() != null
-
 
1606
                    ? remark.getMessage()
-
 
1607
                    : "-";
-
 
1608
 
-
 
1609
            String time = remark.getCreateTimestamp() != null
-
 
1610
                    ? remark.getCreateTimestamp().format(timeFormatter)
-
 
1611
                    : "-";
-
 
1612
 
-
 
1613
            // Get call log data if available
-
 
1614
            String recordingUrl = null;
-
 
1615
            String callStatus = null;
-
 
1616
            String callDuration = null;
-
 
1617
            String callDateTime = null;
-
 
1618
 
-
 
1619
            try {
-
 
1620
                if (remark.getAgentCallLogId() > 0 && callLogMap.containsKey(remark.getAgentCallLogId())) {
-
 
1621
                    com.spice.profitmandi.dao.entity.cs.AgentCallLog callLog = callLogMap.get(remark.getAgentCallLogId());
-
 
1622
                    recordingUrl = callLog.getRecordingUrl();
-
 
1623
                    callStatus = callLog.getCallStatus();
-
 
1624
                    callDuration = callLog.getCallDuration();
-
 
1625
                    if (callLog.getCallDate() != null && callLog.getCallTime() != null) {
-
 
1626
                        LocalDateTime callDateTimeObj = LocalDateTime.of(callLog.getCallDate(), callLog.getCallTime());
-
 
1627
                        callDateTime = callDateTimeObj.format(dateTimeFormatter);
-
 
1628
                    }
-
 
1629
                }
-
 
1630
            } catch (Exception e) {
-
 
1631
                LOGGER.error("Error processing call log for remark id: {}", remark.getId(), e);
-
 
1632
            }
-
 
1633
 
-
 
1634
            result.add(new CalledPartnerDetailModel(partyName, code, remarkValue, messageValue, time,
-
 
1635
                    recordingUrl, callStatus, callDuration, callDateTime));
-
 
1636
        }
-
 
1637
 
-
 
1638
        return result;
-
 
1639
    }
-
 
1640
 
-
 
1641
    @Override
-
 
1642
    public List<List<String>> getRbmCallTargetRawDataByAuthId(int authId) throws Exception {
-
 
1643
        List<List<String>> rows = new ArrayList<>();
-
 
1644
 
-
 
1645
        // Get auth user
-
 
1646
        List<AuthUser> authUsers = authRepository.selectByIds(Collections.singletonList(authId));
-
 
1647
        if (authUsers.isEmpty()) {
-
 
1648
            return rows;
-
 
1649
        }
-
 
1650
        AuthUser authUser = authUsers.get(0);
-
 
1651
 
-
 
1652
        // Get positions to determine if L2
-
 
1653
        List<Position> positions = positionRepository.selectPositionByAuthIds(Collections.singletonList(authId));
-
 
1654
        boolean isL2 = positions.stream()
-
 
1655
                .anyMatch(p -> ProfitMandiConstants.TICKET_CATEGORY_RBM == p.getCategoryId()
-
 
1656
                        && EscalationType.L2.equals(p.getEscalationType()));
-
 
1657
 
-
 
1658
        LocalDateTime startDate = LocalDate.now().atStartOfDay();
-
 
1659
        LocalDate firstOfMonth = LocalDate.now().withDayOfMonth(1);
-
 
1660
        LocalDate endOfMonth = LocalDate.now().withDayOfMonth(LocalDate.now().lengthOfMonth()).plusDays(1);
-
 
1661
 
-
 
1662
        // Get fofo IDs from mtdBillingData (same source as Partner Count in getRbmCallTargetModels)
-
 
1663
        List<RbmWeeklyBillingModel> mtdBillingData = getWeeklyBillingDataForMonth(firstOfMonth, endOfMonth);
-
 
1664
 
-
 
1665
        List<Integer> fofoIdList;
-
 
1666
        if (isL2) {
-
 
1667
            // L2: get fofo IDs from escalated tickets (same as getRbmCallTargetModels)
-
 
1668
            List<Ticket> escalatedTickets = ticketRepository.selectOpenEscalatedTicketsByAuthIds(Collections.singletonList(authId));
-
 
1669
            fofoIdList = escalatedTickets.stream()
-
 
1670
                    .filter(t -> t.getL2AuthUser() == authId
-
 
1671
                            || t.getL3AuthUser() == authId
-
 
1672
                            || t.getL4AuthUser() == authId
-
 
1673
                            || t.getL5AuthUser() == authId)
-
 
1674
                    .map(Ticket::getFofoId)
-
 
1675
                    .distinct()
-
 
1676
                    .collect(Collectors.toList());
-
 
1677
        } else {
-
 
1678
            // L1: get fofo IDs from mtdBillingData with isTargetedPartner (same as Partner Count)
-
 
1679
            fofoIdList = mtdBillingData.stream()
-
 
1680
                    .filter(RbmWeeklyBillingModel::isTargetedPartner)
-
 
1681
                    .filter(m -> m.getAuthId() == authId)
-
 
1682
                    .map(RbmWeeklyBillingModel::getFofoId)
-
 
1683
                    .distinct()
-
 
1684
                    .collect(Collectors.toList());
-
 
1685
        }
-
 
1686
 
-
 
1687
        if (fofoIdList.isEmpty()) {
-
 
1688
            return rows;
-
 
1689
        }
-
 
1690
 
-
 
1691
        // MTD billed fofoIds for zero billing check
-
 
1692
        Set<Integer> mtdBilledFofoIds = mtdBillingData.stream()
-
 
1693
                .filter(RbmWeeklyBillingModel::isMtdBilled)
-
 
1694
                .map(RbmWeeklyBillingModel::getFofoId)
-
 
1695
                .collect(Collectors.toSet());
-
 
1696
 
-
 
1697
        // Collection rank map for status calculation
-
 
1698
        Map<Integer, Integer> collectionRankMap = new HashMap<>();
-
 
1699
        try {
-
 
1700
            collectionRankMap = partnerCollectionService.getCollectionRankMap(fofoIdList, startDate);
-
 
1701
        } catch (ProfitMandiBusinessException e) {
-
 
1702
            LOGGER.error("Error fetching collection rank map", e);
-
 
1703
        }
-
 
1704
 
-
 
1705
        // Resolve partner names/codes
-
 
1706
        Map<Integer, CustomRetailer> retailerMap = Collections.emptyMap();
-
 
1707
        if (!fofoIdList.isEmpty()) {
-
 
1708
            try {
-
 
1709
                retailerMap = retailerService.getFofoRetailers(fofoIdList);
-
 
1710
            } catch (ProfitMandiBusinessException e) {
-
 
1711
                LOGGER.error("Error fetching fofo retailers for raw data", e);
-
 
1712
            }
-
 
1713
        }
-
 
1714
 
-
 
1715
        String rbmName = authUser.getFullName() + (isL2 ? " (L2)" : "");
-
 
1716
 
-
 
1717
        // Build rows for ALL partners (same count as Partner Count)
-
 
1718
        for (Integer fofoId : fofoIdList) {
-
 
1719
            // Default to rank 5 (Normal) for partners without collection plan
-
 
1720
            int rank = collectionRankMap.getOrDefault(fofoId, 5);
-
 
1721
            boolean hasZeroBilling = !mtdBilledFofoIds.contains(fofoId);
-
 
1722
 
-
 
1723
            // Status assignment with same priority as getRbmCallTargetModels
-
 
1724
            String status;
-
 
1725
            if (rank == 1) {
-
 
1726
                status = "Plan Today";
-
 
1727
            } else if (rank == 2) {
-
 
1728
                status = "Carry Forward";
-
 
1729
            } else if (hasZeroBilling) {
-
 
1730
                status = "Zero Billing";
-
 
1731
            } else if (rank == 3) {
-
 
1732
                status = "Untouched";
-
 
1733
            } else if (rank == 4) {
-
 
1734
                status = "Future Plan";
-
 
1735
            } else {
-
 
1736
                status = "Normal";
-
 
1737
            }
-
 
1738
 
-
 
1739
            CustomRetailer retailer = retailerMap.get(fofoId);
-
 
1740
            String partnerName = retailer != null ? retailer.getBusinessName() : "Unknown (" + fofoId + ")";
-
 
1741
            String partnerCode = retailer != null ? retailer.getCode() : "-";
-
 
1742
 
-
 
1743
            rows.add(Arrays.asList(partnerName, partnerCode, status, rbmName));
-
 
1744
        }
-
 
1745
 
-
 
1746
        return rows;
-
 
1747
    }
-
 
1748
 
-
 
1749
    @Override
-
 
1750
    public List<List<String>> getAllRbmCallTargetRawData() throws Exception {
-
 
1751
        List<List<String>> rows = new ArrayList<>();
-
 
1752
 
-
 
1753
        // Get all L1 RBM positions (rows are emitted per L1 RBM).
-
 
1754
        // Call-log lookup happens against ALL callers via findAllByDate — so an L2/L3 or admin call
-
 
1755
        // still counts as "tried" for the partner.
-
 
1756
        List<Position> allRbmPositions = positionRepository
-
 
1757
                .selectPositionByCategoryId(ProfitMandiConstants.TICKET_CATEGORY_RBM).stream()
-
 
1758
                .filter(x -> EscalationType.L1.equals(x.getEscalationType()))
-
 
1759
                .collect(Collectors.toList());
-
 
1760
 
-
 
1761
        List<Integer> l1AuthIds = allRbmPositions.stream()
-
 
1762
                .map(Position::getAuthUserId).distinct().collect(Collectors.toList());
-
 
1763
 
-
 
1764
        if (l1AuthIds.isEmpty()) {
-
 
1765
            return rows;
-
 
1766
        }
-
 
1767
 
-
 
1768
        // Get auth user map
-
 
1769
        Map<Integer, AuthUser> authUserMap = authRepository.selectByIds(l1AuthIds).stream()
-
 
1770
                .collect(Collectors.toMap(AuthUser::getId, au -> au));
-
 
1771
 
-
 
1772
        // Positions per auth user (needed early to build L1 partner assignments below)
-
 
1773
        Map<Integer, List<Position>> positionsByAuthId = positionRepository.selectPositionByAuthIds(l1AuthIds).stream()
-
 
1774
                .collect(Collectors.groupingBy(Position::getAuthUserId));
-
 
1775
 
-
 
1776
        // Build L1 partner assignments from partner_position — SAME source the UI summary uses for
-
 
1777
        // Calling Target. storeGuyMap (email-based CS mapping) was returning smaller/different lists
-
 
1778
        // for users who are also L2, causing summary=43 but download=3 for Ebadullah.
-
 
1779
        Map<Integer, Integer> l1PositionIdToAuthId = new HashMap<>();
-
 
1780
        for (int rbmAuthId : l1AuthIds) {
-
 
1781
            List<Position> positions = positionsByAuthId.getOrDefault(rbmAuthId, Collections.emptyList());
-
 
1782
            for (Position p : positions) {
-
 
1783
                if (ProfitMandiConstants.TICKET_CATEGORY_RBM == p.getCategoryId()
-
 
1784
                        && EscalationType.L1.equals(p.getEscalationType())) {
-
 
1785
                    l1PositionIdToAuthId.put(p.getId(), rbmAuthId);
-
 
1786
                }
-
 
1787
            }
-
 
1788
        }
-
 
1789
 
-
 
1790
        Map<Integer, List<Integer>> rbmToFofoIdsMap = new HashMap<>();
-
 
1791
        Set<Integer> allFofoIds = new HashSet<>();
-
 
1792
        if (!l1PositionIdToAuthId.isEmpty()) {
-
 
1793
            List<com.spice.profitmandi.dao.entity.cs.PartnerPosition> allL1PPs =
-
 
1794
                    partnerPositionRepository.selectByPositionIds(new ArrayList<>(l1PositionIdToAuthId.keySet()));
-
 
1795
            // Group by owning RBM; keep partners distinct within each RBM.
-
 
1796
            Map<Integer, Set<Integer>> perRbmSets = new HashMap<>();
-
 
1797
            for (com.spice.profitmandi.dao.entity.cs.PartnerPosition pp : allL1PPs) {
-
 
1798
                Integer ownerAuthId = l1PositionIdToAuthId.get(pp.getPositionId());
-
 
1799
                if (ownerAuthId == null) continue;
-
 
1800
                perRbmSets.computeIfAbsent(ownerAuthId, k -> new HashSet<>()).add(pp.getFofoId());
-
 
1801
                allFofoIds.add(pp.getFofoId());
-
 
1802
            }
-
 
1803
            for (Map.Entry<Integer, Set<Integer>> e : perRbmSets.entrySet()) {
-
 
1804
                rbmToFofoIdsMap.put(e.getKey(), new ArrayList<>(e.getValue()));
-
 
1805
            }
-
 
1806
        }
-
 
1807
 
-
 
1808
        if (allFofoIds.isEmpty()) {
-
 
1809
            return rows;
-
 
1810
        }
-
 
1811
 
-
 
1812
        // Get fofo stores for filtering and name resolution
-
 
1813
        Map<Integer, FofoStore> fofoStoresMap = new HashMap<>();
-
 
1814
        try {
-
 
1815
            fofoStoresMap = fofoStoreRepository.selectByRetailerIds(new ArrayList<>(allFofoIds)).stream()
-
 
1816
                    .collect(Collectors.toMap(FofoStore::getId, x -> x, (a, b) -> a));
-
 
1817
        } catch (ProfitMandiBusinessException e) {
-
 
1818
            LOGGER.error("Error fetching fofo stores for all raw data", e);
-
 
1819
        }
-
 
1820
 
-
 
1821
        // Batch fetch collection rank map
-
 
1822
        LocalDateTime startDate = LocalDate.now().atStartOfDay();
-
 
1823
        Map<Integer, Integer> allCollectionRankMap = new HashMap<>();
-
 
1824
        try {
-
 
1825
            allCollectionRankMap = partnerCollectionService.getCollectionRankMap(new ArrayList<>(allFofoIds), startDate);
-
 
1826
        } catch (ProfitMandiBusinessException e) {
-
 
1827
            LOGGER.error("Error fetching collection rank map for all raw data", e);
-
 
1828
        }
-
 
1829
 
-
 
1830
        // MTD billing data
-
 
1831
        LocalDate firstOfMonth = LocalDate.now().withDayOfMonth(1);
-
 
1832
        LocalDate endOfMonth = LocalDate.now().withDayOfMonth(LocalDate.now().lengthOfMonth()).plusDays(1);
-
 
1833
        List<RbmWeeklyBillingModel> mtdBillingData = getWeeklyBillingDataForMonth(firstOfMonth, endOfMonth);
-
 
1834
        Set<Integer> allMtdBilledFofoIds = mtdBillingData.stream()
-
 
1835
                .filter(RbmWeeklyBillingModel::isMtdBilled)
-
 
1836
                .map(RbmWeeklyBillingModel::getFofoId)
-
 
1837
                .collect(Collectors.toSet());
-
 
1838
 
-
 
1839
        // Batch fetch partner collection remarks for escalation filtering
-
 
1840
        Map<Integer, PartnerCollectionRemark> allPartnerCollectionRemarks = new HashMap<>();
-
 
1841
        if (!allFofoIds.isEmpty()) {
-
 
1842
            List<Integer> allRemarkIds = partnerCollectionRemarkRepository.selectMaxRemarkId(new ArrayList<>(allFofoIds));
-
 
1843
            if (!allRemarkIds.isEmpty()) {
-
 
1844
                allPartnerCollectionRemarks = partnerCollectionRemarkRepository.selectByIds(allRemarkIds).stream()
-
 
1845
                        .collect(Collectors.toMap(PartnerCollectionRemark::getFofoId, x -> x, (a, b) -> a));
-
 
1846
            }
-
 
1847
        }
-
 
1848
 
-
 
1849
        // Resolve partner names/codes in batch
-
 
1850
        Map<Integer, CustomRetailer> retailerMap = Collections.emptyMap();
-
 
1851
        try {
-
 
1852
            retailerMap = retailerService.getFofoRetailers(new ArrayList<>(allFofoIds));
-
 
1853
        } catch (ProfitMandiBusinessException e) {
-
 
1854
            LOGGER.error("Error fetching fofo retailers for all raw data", e);
-
 
1855
        }
-
 
1856
 
-
 
1857
        // Batch-fetch EVERY call log for today (across all callers, not just RBMs) and build a
-
 
1858
        // fofoId -> latest AgentCallLog map. We use findAllByDate here so that calls placed by
-
 
1859
        // admins, escalation agents, or anyone whose authId isn't in the RBM position table are
-
 
1860
        // still surfaced. Downstream we only read this map for fofoIds we actually emit, so
-
 
1861
        // extra entries are harmless.
-
 
1862
        Map<Integer, AgentCallLog> latestCallByFofo = new HashMap<>();
-
 
1863
        try {
-
 
1864
            List<AgentCallLog> todayCallLogs = agentCallLogRepository.findAllByDate(LocalDate.now());
-
 
1865
            Set<String> normalizedMobiles = new HashSet<>();
-
 
1866
            for (AgentCallLog log : todayCallLogs) {
-
 
1867
                if (log.getCustomerNumber() != null) {
-
 
1868
                    String n = log.getCustomerNumber();
-
 
1869
                    normalizedMobiles.add(n.startsWith("+91") ? n.substring(3) : n);
-
 
1870
                }
-
 
1871
            }
-
 
1872
            Map<String, Integer> mobileToFofoIdMap = buildMobileToFofoIdMap(normalizedMobiles);
-
 
1873
            for (AgentCallLog log : todayCallLogs) {
-
 
1874
                if (log.getCustomerNumber() == null) continue;
-
 
1875
                String n = log.getCustomerNumber();
-
 
1876
                String normalized = n.startsWith("+91") ? n.substring(3) : n;
-
 
1877
                Integer fofoId = mobileToFofoIdMap.get(normalized);
-
 
1878
                if (fofoId == null) continue;
-
 
1879
                AgentCallLog existing = latestCallByFofo.get(fofoId);
-
 
1880
                if (existing == null || (log.getId() != null && existing.getId() != null && log.getId() > existing.getId())) {
-
 
1881
                    latestCallByFofo.put(fofoId, log);
-
 
1882
                }
-
 
1883
            }
-
 
1884
        } catch (Exception e) {
-
 
1885
            LOGGER.error("Error building latest call status map for all raw data", e);
-
 
1886
        }
-
 
1887
 
-
 
1888
        // Process each L1 RBM
-
 
1889
        // Note: no cross-RBM dedup — a partner assigned to multiple RBMs appears once per RBM,
-
 
1890
        // matching the UI summary counts so row totals per RBM equal that RBM's "Calling Target".
-
 
1891
        for (int rbmAuthId : l1AuthIds) {
-
 
1892
            AuthUser authUser = authUserMap.get(rbmAuthId);
-
 
1893
            if (authUser == null) {
-
 
1894
                continue;
-
 
1895
            }
-
 
1896
 
-
 
1897
            List<Integer> fofoIdList = rbmToFofoIdsMap.getOrDefault(rbmAuthId, Collections.emptyList());
-
 
1898
            if (fofoIdList.isEmpty()) {
-
 
1899
                continue; // No L1 partner assignments for this RBM
-
 
1900
            }
-
 
1901
 
-
 
1902
            // Filter escalated partners for L1 RBMs
-
 
1903
            List<Position> positions = positionsByAuthId.getOrDefault(rbmAuthId, Collections.emptyList());
-
 
1904
            boolean isRBMAndL1 = positions.stream()
-
 
1905
                    .anyMatch(position ->
-
 
1906
                            ProfitMandiConstants.TICKET_CATEGORY_RBM == position.getCategoryId()
-
 
1907
                                    && EscalationType.L1.equals(position.getEscalationType()));
-
 
1908
 
-
 
1909
            List<Integer> fofoIds = fofoIdList;
-
 
1910
            if (isRBMAndL1) {
-
 
1911
                Map<Integer, PartnerCollectionRemark> partnerRemarks = new HashMap<>();
-
 
1912
                for (Integer fofoId : fofoIdList) {
-
 
1913
                    if (allPartnerCollectionRemarks.containsKey(fofoId)) {
-
 
1914
                        partnerRemarks.put(fofoId, allPartnerCollectionRemarks.get(fofoId));
-
 
1915
                    }
-
 
1916
                }
-
 
1917
                Map<Integer, PartnerCollectionRemark> finalPartnerRemarks = partnerRemarks;
-
 
1918
                fofoIds = fofoIdList.stream()
-
 
1919
                        .filter(fofoId -> {
-
 
1920
                            if (!finalPartnerRemarks.containsKey(fofoId)) return true;
-
 
1921
                            PartnerCollectionRemark pcr = finalPartnerRemarks.get(fofoId);
-
 
1922
                            return !(CollectionRemark.RBM_L2_ESCALATION.equals(pcr.getRemark())
-
 
1923
                                    || CollectionRemark.SALES_ESCALATION.equals(pcr.getRemark()));
-
 
1924
                        })
-
 
1925
                        .collect(Collectors.toList());
-
 
1926
            }
-
 
1927
 
-
 
1928
            // Filter to only external, ACTIVE or REVIVAL stores
-
 
1929
            Map<Integer, FofoStore> finalFofoStoresMap = fofoStoresMap;
-
 
1930
            List<Integer> validFofoIds = fofoIds.stream()
-
 
1931
                    .filter(fofoId -> {
-
 
1932
                        FofoStore store = finalFofoStoresMap.get(fofoId);
-
 
1933
                        if (store == null || store.isInternal()) return false;
-
 
1934
                        return ActivationType.ACTIVE.equals(store.getActivationType())
-
 
1935
                                || ActivationType.REVIVAL.equals(store.getActivationType());
-
 
1936
                    })
-
 
1937
                    .collect(Collectors.toList());
-
 
1938
 
-
 
1939
            String rbmName = authUser.getFullName();
-
 
1940
 
-
 
1941
            // Categorize each partner and add a row per RBM assignment.
-
 
1942
            for (Integer fofoId : validFofoIds) {
-
 
1943
                int rank = allCollectionRankMap.getOrDefault(fofoId, 5);
-
 
1944
                boolean hasZeroBilling = !allMtdBilledFofoIds.contains(fofoId);
-
 
1945
 
-
 
1946
                String status;
-
 
1947
                if (rank == 1) {
-
 
1948
                    status = "Plan Today";
-
 
1949
                } else if (rank == 2) {
-
 
1950
                    status = "Carry Forward";
-
 
1951
                } else if (hasZeroBilling) {
-
 
1952
                    status = "Zero Billing";
-
 
1953
                } else if (rank == 3) {
-
 
1954
                    status = "Untouched";
-
 
1955
                } else {
-
 
1956
                    continue; // Skip Future Plan and Normal — only include calling target parties
-
 
1957
                }
-
 
1958
 
-
 
1959
                CustomRetailer retailer = retailerMap.get(fofoId);
-
 
1960
                String partnerName = retailer != null ? retailer.getBusinessName() : "Unknown (" + fofoId + ")";
-
 
1961
                String partnerCode = retailer != null ? retailer.getCode() : "-";
-
 
1962
 
-
 
1963
                // "Did Not Try" means the PARTNER has no call log today from anyone — not tied to this RBM.
-
 
1964
                // If any caller reached this partner, show the actual status.
-
 
1965
                String latestCallStatus = "Did Not Try";
-
 
1966
                AgentCallLog latestLog = latestCallByFofo.get(fofoId);
-
 
1967
                if (latestLog != null && latestLog.getCallStatus() != null && !latestLog.getCallStatus().isEmpty()) {
-
 
1968
                    latestCallStatus = latestLog.getCallStatus();
-
 
1969
                }
-
 
1970
 
-
 
1971
                rows.add(Arrays.asList(partnerName, partnerCode, status, rbmName, latestCallStatus));
-
 
1972
            }
-
 
1973
        }
-
 
1974
 
-
 
1975
        return rows;
-
 
1976
    }
-
 
1977
 
-
 
1978
    /**
-
 
1979
     * Get count of distinct partners called today based on call logs.
-
 
1980
     * Maps customerNumber from call log to fofoId using retailer_contact and address.
-
 
1981
     * If same fofoId is called multiple times, counts only once.
-
 
1982
     * Numbers without fofoId mapping are also counted (by distinct customer number).
-
 
1983
     *
-
 
1984
     * @param authId the RBM auth ID
-
 
1985
     * @return count of distinct partners/numbers called today
-
 
1986
     */
-
 
1987
    public long getCalledCountFromCallLogs(long authId) {
-
 
1988
        return getCalledCountFromCallLogs(authId, LocalDate.now());
-
 
1989
    }
-
 
1990
 
-
 
1991
    public long getCalledCountFromCallLogs(long authId, LocalDate date) {
-
 
1992
        return getCallStats(authId, date)[0];
-
 
1993
    }
-
 
1994
 
-
 
1995
    /**
-
 
1996
     * Batch-builds a mobile (normalized, +91 stripped) -> fofoId mapping for all the given mobiles
-
 
1997
     * in just two queries (retailer_contact, then address fallback for unmapped numbers).
-
 
1998
     * Replaces the previous per-call-log findFofoIdByMobile() N+1 lookups.
-
 
1999
     */
-
 
2000
    private Map<String, Integer> buildMobileToFofoIdMap(Set<String> normalizedMobiles) {
-
 
2001
        Map<String, Integer> result = new HashMap<>();
-
 
2002
        if (normalizedMobiles == null || normalizedMobiles.isEmpty()) {
-
 
2003
            return result;
-
 
2004
        }
-
 
2005
        List<String> mobilesList = new ArrayList<>(normalizedMobiles);
-
 
2006
 
-
 
2007
        // First pass: retailer_contact
-
 
2008
        List<RetailerContact> contacts = retailerContactRepository.selectByMobiles(mobilesList);
-
 
2009
        for (RetailerContact rc : contacts) {
-
 
2010
            // Keep the first fofoId we see per mobile (matches old single-mobile behavior of get(0))
-
 
2011
            result.putIfAbsent(rc.getMobile(), rc.getFofoId());
-
 
2012
        }
-
 
2013
 
-
 
2014
        // Second pass: address fallback for mobiles not yet mapped
-
 
2015
        List<String> unmapped = mobilesList.stream()
-
 
2016
                .filter(m -> !result.containsKey(m))
-
 
2017
                .collect(Collectors.toList());
-
 
2018
        if (!unmapped.isEmpty()) {
-
 
2019
            List<Address> addresses = addressRepository.selectAllByPhoneNumbers(unmapped);
-
 
2020
            for (Address addr : addresses) {
-
 
2021
                result.putIfAbsent(addr.getPhoneNumber(), addr.getRetaierId());
-
 
2022
            }
-
 
2023
        }
-
 
2024
        return result;
-
 
2025
    }
-
 
2026
 
-
 
2027
    /**
2077
    /**
2028
     * In-memory variant of getCallStats() that uses pre-fetched call logs and mobile→fofoId mapping.
2078
     * Rule: count each distinct customer number this RBM attempted today, EXCEPT
2029
     * Used by the batch-fetched RBM Call Target loop to avoid N+1 query patterns.
2079
     * numbers whose every log entry has callStatus "No Answer" — those count only
-
 
2080
     * if there were 3 or more attempts (persistence credit). Recording presence is
-
 
2081
     * not required. null/empty callStatus is treated as "not no-answer" (favours
-
 
2082
     * counting when the vendor didn't classify the outcome).
2030
     */
2083
     */
2031
    private long[] getCallStatsFromLogs(List<AgentCallLog> callLogs, Map<String, Integer> mobileToFofoIdMap) {
2084
    private long computeUniqueTriedCount(List<AgentCallLog> callLogs) {
2032
        if (callLogs == null || callLogs.isEmpty()) {
2085
        if (callLogs == null || callLogs.isEmpty()) return 0L;
2033
            return new long[]{0, 0, 0};
-
 
2034
        }
-
 
2035
 
-
 
2036
        Set<Integer> calledFofoIds = new HashSet<>();
-
 
2037
        Set<String> calledNumbersWithoutFofoId = new HashSet<>();
-
 
2038
        long totalRecordingCalls = 0;
-
 
2039
        Set<String> uniqueRecordingNumbers = new HashSet<>();
2086
        Map<String, List<AgentCallLog>> byNumber = new HashMap<>();
2040
 
-
 
2041
        for (AgentCallLog callLog : callLogs) {
2087
        for (AgentCallLog log : callLogs) {
2042
            String customerNumber = callLog.getCustomerNumber();
2088
            String num = log.getCustomerNumber();
2043
            if (customerNumber != null) {
2089
            if (num == null) continue;
2044
                String normalized = customerNumber.startsWith("+91") ? customerNumber.substring(3) : customerNumber;
2090
            String normalized = num.startsWith("+91") ? num.substring(3) : num;
2045
                Integer fofoId = mobileToFofoIdMap.get(normalized);
2091
            byNumber.computeIfAbsent(normalized, k -> new ArrayList<>()).add(log);
2046
                if (fofoId != null) {
2092
        }
2047
                    calledFofoIds.add(fofoId);
-
 
2048
                } else {
2093
        long count = 0L;
2049
                    calledNumbersWithoutFofoId.add(normalized);
2094
        for (List<AgentCallLog> logs : byNumber.values()) {
2050
                }
2095
            boolean hasNonNoAnswer = false;
2051
 
-
 
2052
                if (callLog.getRecordingUrl() != null && !callLog.getRecordingUrl().isEmpty()
2096
            for (AgentCallLog l : logs) {
2053
                        && !"None".equalsIgnoreCase(callLog.getRecordingUrl())) {
2097
                if (!isNoAnswerStatus(l.getCallStatus())) {
2054
                    totalRecordingCalls++;
2098
                    hasNonNoAnswer = true;
2055
                    uniqueRecordingNumbers.add(normalized);
2099
                    break;
2056
                }
2100
                }
2057
            }
2101
            }
-
 
2102
            if (hasNonNoAnswer) count++;
-
 
2103
            else if (logs.size() >= 3) count++;
2058
        }
2104
        }
2059
 
-
 
2060
        long calledCount = calledFofoIds.size() + calledNumbersWithoutFofoId.size();
-
 
2061
        return new long[]{calledCount, totalRecordingCalls, uniqueRecordingNumbers.size()};
2105
        return count;
2062
    }
2106
    }
2063
 
2107
 
2064
    /**
2108
    /**
2065
     * Returns call stats: [0] = called count, [1] = total recording calls, [2] = unique recording calls
2109
     * Returns call stats: [0] = called count, [1] = total recording calls, [2] = unique recording calls
2066
     */
2110
     */