| Line 48... |
Line 48... |
| 48 |
};
|
48 |
};
|
| 49 |
@Autowired
|
49 |
@Autowired
|
| 50 |
private CsService csService;
|
50 |
private CsService csService;
|
| 51 |
@Autowired
|
51 |
@Autowired
|
| 52 |
private AuthRepository authRepository;
|
52 |
private AuthRepository authRepository;
|
| - |
|
53 |
// Emails that bypass hierarchy and role gates — single source of truth.
|
| - |
|
54 |
private static final Set<String> SUPER_ADMIN_EMAILS = new HashSet<>(Arrays.asList(
|
| - |
|
55 |
"tarun.verma@smartdukaan.com"
|
| - |
|
56 |
));
|
| 53 |
@Autowired
|
57 |
@Autowired
|
| - |
|
58 |
private com.spice.profitmandi.service.AuthService authService;
|
| - |
|
59 |
@Autowired
|
| 54 |
private FofoStoreRepository fofoStoreRepository;
|
60 |
private com.spice.profitmandi.dao.repository.cs.PositionRepository positionRepository;
|
| 55 |
@Autowired
|
61 |
@Autowired
|
| 56 |
private RetailerService retailerService;
|
62 |
private RetailerService retailerService;
|
| 57 |
@Autowired
|
63 |
@Autowired
|
| 58 |
private BeatRepository beatRepository;
|
64 |
private BeatRepository beatRepository;
|
| 59 |
@Autowired
|
65 |
@Autowired
|
| Line 74... |
Line 80... |
| 74 |
private com.spice.profitmandi.service.GeocodingService geocodingService;
|
80 |
private com.spice.profitmandi.service.GeocodingService geocodingService;
|
| 75 |
@Autowired
|
81 |
@Autowired
|
| 76 |
private CookiesProcessor cookiesProcessor;
|
82 |
private CookiesProcessor cookiesProcessor;
|
| 77 |
@Autowired
|
83 |
@Autowired
|
| 78 |
private ResponseSender responseSender;
|
84 |
private ResponseSender responseSender;
|
| - |
|
85 |
@Autowired
|
| - |
|
86 |
private FofoStoreRepository fofoStoreRepository;
|
| 79 |
|
87 |
|
| 80 |
@GetMapping(value = "/beatPlan")
|
88 |
@GetMapping(value = "/beatPlan")
|
| 81 |
public String beatPlan(HttpServletRequest request, Model model) {
|
89 |
public String beatPlan(HttpServletRequest request, Model model) throws ProfitMandiBusinessException {
|
| 82 |
EscalationType[] escalationTypes = EscalationType.values();
|
- |
|
| 83 |
model.addAttribute("escalationTypes", escalationTypes);
|
90 |
model.addAttribute("escalationTypes", visibleLevelsFor(request));
|
| 84 |
return "beat-plan";
|
91 |
return "beat-plan";
|
| 85 |
}
|
92 |
}
|
| 86 |
|
93 |
|
| 87 |
@GetMapping(value = "/beatPlanWindow")
|
- |
|
| 88 |
public String beatPlanWindow(HttpServletRequest request, Model model) {
|
- |
|
| 89 |
EscalationType[] escalationTypes = EscalationType.values();
|
- |
|
| 90 |
model.addAttribute("escalationTypes", escalationTypes);
|
- |
|
| 91 |
return "beat-plan-window";
|
- |
|
| 92 |
}
|
- |
|
| 93 |
|
- |
|
| 94 |
@Autowired
|
94 |
@Autowired
|
| 95 |
private com.spice.profitmandi.dao.repository.dtr.LeadLiveLocationRepository leadLiveLocationRepositoryAuto;
|
95 |
private com.spice.profitmandi.dao.repository.dtr.LeadLiveLocationRepository leadLiveLocationRepositoryAuto;
|
| 96 |
@Autowired
|
96 |
@Autowired
|
| 97 |
private com.spice.profitmandi.dao.repository.dtr.LeadActivityRepository leadActivityRepositoryAuto;
|
97 |
private com.spice.profitmandi.dao.repository.dtr.LeadActivityRepository leadActivityRepositoryAuto;
|
| 98 |
@Autowired
|
98 |
@Autowired
|
| Line 270... |
Line 270... |
| 270 |
result.put("dtrUserId", dtrUserId);
|
270 |
result.put("dtrUserId", dtrUserId);
|
| 271 |
result.put("message", created.size() + " visit tasks assigned to " + au.getFirstName() + " " + au.getLastName());
|
271 |
result.put("message", created.size() + " visit tasks assigned to " + au.getFirstName() + " " + au.getLastName());
|
| 272 |
return responseSender.ok(result);
|
272 |
return responseSender.ok(result);
|
| 273 |
}
|
273 |
}
|
| 274 |
|
274 |
|
| 275 |
// ====================== BASE LOCATION MANAGEMENT ======================
|
- |
|
| 276 |
// Inline page that lets Sales L3+ pick a user and set their base (home)
|
- |
|
| 277 |
// location via map. Reads use the existing /beatPlan/getBaseLocation, writes
|
- |
|
| 278 |
// go through the L3+-guarded endpoint below.
|
- |
|
| 279 |
@GetMapping(value = "/beatPlan/baseLocationPage")
|
275 |
@GetMapping(value = "/beatPlanWindow")
|
| 280 |
public String baseLocationPage(HttpServletRequest request, Model model) {
|
276 |
public String beatPlanWindow(HttpServletRequest request, Model model) throws ProfitMandiBusinessException {
|
| 281 |
EscalationType[] escalationTypes = EscalationType.values();
|
- |
|
| 282 |
model.addAttribute("escalationTypes", escalationTypes);
|
277 |
model.addAttribute("escalationTypes", visibleLevelsFor(request));
|
| 283 |
return "beat-plan-base-location";
|
278 |
return "beat-plan-window";
|
| 284 |
}
|
279 |
}
|
| 285 |
|
280 |
|
| 286 |
// Helpers for XLSX bulk upload
|
281 |
// Helpers for XLSX bulk upload
|
| 287 |
private static String readCell(org.apache.poi.ss.usermodel.Cell cell) {
|
282 |
private static String readCell(org.apache.poi.ss.usermodel.Cell cell) {
|
| 288 |
if (cell == null) return null;
|
283 |
if (cell == null) return null;
|
| Line 652... |
Line 647... |
| 652 |
result.put("authUserId", authUserId);
|
647 |
result.put("authUserId", authUserId);
|
| 653 |
result.put("beats", hits);
|
648 |
result.put("beats", hits);
|
| 654 |
return responseSender.ok(result);
|
649 |
return responseSender.ok(result);
|
| 655 |
}
|
650 |
}
|
| 656 |
|
651 |
|
| 657 |
// ====================== DAY VIEW ======================
|
652 |
// ====================== BASE LOCATION MANAGEMENT ======================
|
| 658 |
// Inline page (loaded into dashboard #main-content): tabular list of all beats
|
653 |
// Inline page that lets Sales L3+ pick a user and set their base (home)
|
| 659 |
// scheduled in a date range across all users. Each row has a View button that
|
654 |
// location via map. Reads use the existing /beatPlan/getBaseLocation, writes
|
| 660 |
// opens that user's calendar in a modal.
|
655 |
// go through the L3+-guarded endpoint below.
|
| 661 |
@GetMapping(value = "/beatPlan/dayView")
|
656 |
@GetMapping(value = "/beatPlan/baseLocationPage")
|
| 662 |
public String beatPlanDayView(HttpServletRequest request, Model model) {
|
657 |
public String baseLocationPage(HttpServletRequest request, Model model) throws ProfitMandiBusinessException {
|
| 663 |
EscalationType[] escalationTypes = EscalationType.values();
|
- |
|
| 664 |
model.addAttribute("escalationTypes", escalationTypes);
|
658 |
model.addAttribute("escalationTypes", visibleLevelsFor(request));
|
| 665 |
return "beat-plan-day-view";
|
659 |
return "beat-plan-base-location";
|
| 666 |
}
|
660 |
}
|
| 667 |
|
661 |
|
| 668 |
// Tabular JSON: one row per (beat, scheduled date) in [startDate, endDate].
|
662 |
// Tabular JSON: one row per (beat, scheduled date) in [startDate, endDate].
|
| 669 |
@GetMapping(value = "/beatPlan/scheduledList")
|
663 |
@GetMapping(value = "/beatPlan/scheduledList")
|
| 670 |
public ResponseEntity<?> scheduledList(
|
664 |
public ResponseEntity<?> scheduledList(
|
| Line 832... |
Line 826... |
| 832 |
Map<String, Object> result = new HashMap<>();
|
826 |
Map<String, Object> result = new HashMap<>();
|
| 833 |
result.put("beats", out);
|
827 |
result.put("beats", out);
|
| 834 |
return responseSender.ok(result);
|
828 |
return responseSender.ok(result);
|
| 835 |
}
|
829 |
}
|
| 836 |
|
830 |
|
| 837 |
@GetMapping(value = "/beatPlan/getAuthUsers")
|
831 |
// ====================== DAY VIEW ======================
|
| 838 |
public ResponseEntity<?> getAuthUsers(
|
- |
|
| 839 |
@RequestParam int categoryId,
|
- |
|
| 840 |
@RequestParam EscalationType escalationType) {
|
832 |
// Inline page (loaded into dashboard #main-content): tabular list of all beats
|
| 841 |
List<AuthUser> authUsers = csService.getAuthUserByCategoryId(categoryId, escalationType);
|
833 |
// scheduled in a date range across all users. Each row has a View button that
|
| 842 |
List<Map<String, Object>> result = authUsers.stream()
|
834 |
// opens that user's calendar in a modal.
|
| 843 |
.filter(au -> au.getActive())
|
- |
|
| 844 |
.map(au -> {
|
- |
|
| 845 |
Map<String, Object> map = new HashMap<>();
|
835 |
@GetMapping(value = "/beatPlan/dayView")
|
| 846 |
map.put("id", au.getId());
|
- |
|
| 847 |
map.put("name", au.getFirstName() + " " + au.getLastName());
|
836 |
public String beatPlanDayView(HttpServletRequest request, Model model) throws ProfitMandiBusinessException {
|
| 848 |
return map;
|
- |
|
| 849 |
})
|
- |
|
| 850 |
.collect(Collectors.toList());
|
837 |
model.addAttribute("escalationTypes", visibleLevelsFor(request));
|
| 851 |
return responseSender.ok(result);
|
838 |
return "beat-plan-day-view";
|
| 852 |
}
|
839 |
}
|
| 853 |
|
840 |
|
| 854 |
// Returns visits for a beat.
|
841 |
// Returns visits for a beat.
|
| 855 |
// - Partner stops (beat_route) belong to the beat template — always returned.
|
842 |
// - Partner stops (beat_route) belong to the beat template — always returned.
|
| 856 |
// - Lead stops (lead_route) belong to a specific run — returned ONLY when planDate
|
843 |
// - Lead stops (lead_route) belong to a specific run — returned ONLY when planDate
|
| Line 1009... |
Line 996... |
| 1009 |
result.put("status", true);
|
996 |
result.put("status", true);
|
| 1010 |
result.put("message", "Base location removed");
|
997 |
result.put("message", "Base location removed");
|
| 1011 |
return responseSender.ok(result);
|
998 |
return responseSender.ok(result);
|
| 1012 |
}
|
999 |
}
|
| 1013 |
|
1000 |
|
| - |
|
1001 |
@GetMapping(value = "/beatPlan/getAuthUsers")
|
| - |
|
1002 |
public ResponseEntity<?> getAuthUsers(
|
| - |
|
1003 |
HttpServletRequest request,
|
| - |
|
1004 |
@RequestParam int categoryId,
|
| - |
|
1005 |
@RequestParam EscalationType escalationType) throws ProfitMandiBusinessException {
|
| - |
|
1006 |
|
| 1014 |
// Shared permission check for base-location admin actions.
|
1007 |
// Hierarchy filter: a manager only sees users in their downline
|
| - |
|
1008 |
// (themselves + every reportee under them, recursively). Super-admin
|
| - |
|
1009 |
// emails bypass the filter and see everyone. Downline is computed by
|
| - |
|
1010 |
// AuthService.getAllReportees (existing recursive walker).
|
| - |
|
1011 |
LoginDetails ld = cookiesProcessor.getCookiesObject(request);
|
| - |
|
1012 |
AuthUser me = (ld != null) ? authRepository.selectByEmailOrMobile(ld.getEmailId()) : null;
|
| - |
|
1013 |
|
| - |
|
1014 |
final Set<Integer> visible;
|
| - |
|
1015 |
if (me == null || isSuperAdmin(me)) {
|
| - |
|
1016 |
visible = null; // null = no filter
|
| - |
|
1017 |
} else {
|
| - |
|
1018 |
visible = new HashSet<>(authService.getAllReportees(me.getId()));
|
| - |
|
1019 |
visible.add(me.getId()); // include self
|
| - |
|
1020 |
}
|
| - |
|
1021 |
|
| - |
|
1022 |
List<AuthUser> authUsers = csService.getAuthUserByCategoryId(categoryId, escalationType);
|
| 1015 |
private boolean isBaseLocationManager(AuthUser me) {
|
1023 |
List<Map<String, Object>> result = authUsers.stream()
|
| - |
|
1024 |
.filter(au -> au.getActive())
|
| - |
|
1025 |
.filter(au -> visible == null || visible.contains(au.getId()))
|
| - |
|
1026 |
.map(au -> {
|
| 1016 |
Set<String> baseLocationAllowList = new HashSet<>(Arrays.asList(
|
1027 |
Map<String, Object> map = new HashMap<>();
|
| 1017 |
"tarun.verma@smartdukaan.com"
|
1028 |
map.put("id", au.getId());
|
| - |
|
1029 |
map.put("name", au.getFirstName() + " " + au.getLastName());
|
| - |
|
1030 |
return map;
|
| 1018 |
));
|
1031 |
})
|
| - |
|
1032 |
.collect(Collectors.toList());
|
| - |
|
1033 |
return responseSender.ok(result);
|
| - |
|
1034 |
}
|
| - |
|
1035 |
|
| - |
|
1036 |
private boolean isSuperAdmin(AuthUser me) {
|
| 1019 |
String myEmail = me.getEmailId() != null ? me.getEmailId().toLowerCase() : "";
|
1037 |
String myEmail = me.getEmailId() != null ? me.getEmailId().toLowerCase() : "";
|
| 1020 |
if (baseLocationAllowList.contains(myEmail)) return true;
|
1038 |
return SUPER_ADMIN_EMAILS.contains(myEmail);
|
| - |
|
1039 |
}
|
| - |
|
1040 |
|
| - |
|
1041 |
// Returns the user's highest escalation level across all positions.
|
| - |
|
1042 |
// Mirrors OrderController.getSalesEscalationLevel but category-agnostic.
|
| - |
|
1043 |
private EscalationType getHighestEscalation(int authUserId) {
|
| - |
|
1044 |
EscalationType highest = null;
|
| - |
|
1045 |
List<com.spice.profitmandi.dao.entity.cs.Position> positions = positionRepository.selectPositionByAuthId(authUserId);
|
| - |
|
1046 |
for (com.spice.profitmandi.dao.entity.cs.Position p : positions) {
|
| - |
|
1047 |
if (highest == null || p.getEscalationType().isGreaterThanEqualTo(highest)) {
|
| - |
|
1048 |
highest = p.getEscalationType();
|
| - |
|
1049 |
}
|
| - |
|
1050 |
}
|
| - |
|
1051 |
return highest;
|
| - |
|
1052 |
}
|
| - |
|
1053 |
|
| - |
|
1054 |
// Returns the escalation levels a user can manage — strictly below their own.
|
| - |
|
1055 |
// L3 → [L1, L2]; L4 → [L1, L2, L3]; Final → all levels. Super-admin → all levels.
|
| - |
|
1056 |
private List<EscalationType> getVisibleEscalationLevels(AuthUser me) {
|
| - |
|
1057 |
if (isSuperAdmin(me)) return EscalationType.escalations;
|
| - |
|
1058 |
EscalationType mine = getHighestEscalation(me.getId());
|
| - |
|
1059 |
if (mine == null) return java.util.Collections.emptyList();
|
| - |
|
1060 |
List<EscalationType> below = new ArrayList<>();
|
| - |
|
1061 |
for (EscalationType e : EscalationType.escalations) {
|
| - |
|
1062 |
if (mine.isGreaterThanEqualTo(e) && !e.equals(mine)) below.add(e);
|
| - |
|
1063 |
}
|
| - |
|
1064 |
return below;
|
| - |
|
1065 |
}
|
| 1021 |
|
1066 |
|
| - |
|
1067 |
private List<EscalationType> visibleLevelsFor(HttpServletRequest request) throws ProfitMandiBusinessException {
|
| - |
|
1068 |
LoginDetails ld = cookiesProcessor.getCookiesObject(request);
|
| - |
|
1069 |
AuthUser me = (ld != null) ? authRepository.selectByEmailOrMobile(ld.getEmailId()) : null;
|
| - |
|
1070 |
return me == null ? java.util.Collections.emptyList() : getVisibleEscalationLevels(me);
|
| - |
|
1071 |
}
|
| - |
|
1072 |
|
| - |
|
1073 |
// Shared permission check for base-location admin actions: Sales L3+ OR super-admin.
|
| - |
|
1074 |
private boolean isBaseLocationManager(AuthUser me) {
|
| - |
|
1075 |
if (isSuperAdmin(me)) return true;
|
| 1022 |
return csService.getAuthUserIds(
|
1076 |
return csService.getAuthUserIds(
|
| 1023 |
com.spice.profitmandi.common.model.ProfitMandiConstants.TICKET_CATEGORY_SALES,
|
1077 |
com.spice.profitmandi.common.model.ProfitMandiConstants.TICKET_CATEGORY_SALES,
|
| 1024 |
Arrays.asList(EscalationType.L3, EscalationType.L4))
|
1078 |
Arrays.asList(EscalationType.L3, EscalationType.L4))
|
| 1025 |
.stream().anyMatch(u -> u.getId() == me.getId());
|
1079 |
.stream().anyMatch(u -> u.getId() == me.getId());
|
| 1026 |
}
|
1080 |
}
|