| Line 61... |
Line 61... |
| 61 |
@Autowired
|
61 |
@Autowired
|
| 62 |
private BeatScheduleRepository beatScheduleRepository;
|
62 |
private BeatScheduleRepository beatScheduleRepository;
|
| 63 |
@Autowired
|
63 |
@Autowired
|
| 64 |
private LeadRouteRepository leadRouteRepository;
|
64 |
private LeadRouteRepository leadRouteRepository;
|
| 65 |
@Autowired
|
65 |
@Autowired
|
| - |
|
66 |
private com.spice.profitmandi.dao.service.BeatPlanQueryService beatPlanQueryService;
|
| - |
|
67 |
@Autowired
|
| 66 |
private AuthUserLocationRepository authUserLocationRepository;
|
68 |
private AuthUserLocationRepository authUserLocationRepository;
|
| 67 |
@Autowired
|
69 |
@Autowired
|
| 68 |
private LeadRepository leadRepository;
|
70 |
private LeadRepository leadRepository;
|
| 69 |
@Autowired
|
71 |
@Autowired
|
| 70 |
private PublicHolidaysRepository publicHolidaysRepository;
|
72 |
private PublicHolidaysRepository publicHolidaysRepository;
|
| Line 87... |
Line 89... |
| 87 |
EscalationType[] escalationTypes = EscalationType.values();
|
89 |
EscalationType[] escalationTypes = EscalationType.values();
|
| 88 |
model.addAttribute("escalationTypes", escalationTypes);
|
90 |
model.addAttribute("escalationTypes", escalationTypes);
|
| 89 |
return "beat-plan-window";
|
91 |
return "beat-plan-window";
|
| 90 |
}
|
92 |
}
|
| 91 |
|
93 |
|
| - |
|
94 |
@Autowired
|
| - |
|
95 |
private com.spice.profitmandi.dao.repository.dtr.LeadLiveLocationRepository leadLiveLocationRepositoryAuto;
|
| - |
|
96 |
|
| - |
|
97 |
// ====================== DAY VIEW ======================
|
| - |
|
98 |
// Inline page (loaded into dashboard #main-content): tabular list of all beats
|
| - |
|
99 |
// scheduled in a date range across all users. Each row has a View button that
|
| - |
|
100 |
// opens that user's calendar in a modal.
|
| - |
|
101 |
@GetMapping(value = "/beatPlan/dayView")
|
| - |
|
102 |
public String beatPlanDayView(HttpServletRequest request, Model model) {
|
| - |
|
103 |
EscalationType[] escalationTypes = EscalationType.values();
|
| - |
|
104 |
model.addAttribute("escalationTypes", escalationTypes);
|
| - |
|
105 |
return "beat-plan-day-view";
|
| - |
|
106 |
}
|
| - |
|
107 |
|
| - |
|
108 |
// Tabular JSON: one row per (beat, scheduled date) in [startDate, endDate].
|
| - |
|
109 |
@GetMapping(value = "/beatPlan/scheduledList")
|
| - |
|
110 |
public ResponseEntity<?> scheduledList(
|
| - |
|
111 |
@RequestParam(required = false) String startDate,
|
| - |
|
112 |
@RequestParam(required = false) String endDate) {
|
| - |
|
113 |
|
| - |
|
114 |
LocalDate start, end;
|
| - |
|
115 |
try {
|
| - |
|
116 |
start = (startDate == null || startDate.isEmpty()) ? LocalDate.now() : LocalDate.parse(startDate);
|
| - |
|
117 |
end = (endDate == null || endDate.isEmpty()) ? start.plusDays(7) : LocalDate.parse(endDate);
|
| - |
|
118 |
} catch (Exception e) {
|
| - |
|
119 |
return responseSender.badRequest("Invalid date — expected yyyy-MM-dd");
|
| - |
|
120 |
}
|
| - |
|
121 |
|
| - |
|
122 |
List<com.spice.profitmandi.dao.model.BeatDayDetails> beats =
|
| - |
|
123 |
beatPlanQueryService.getAllScheduledBeats(start, end);
|
| - |
|
124 |
|
| - |
|
125 |
// Resolve user names in bulk
|
| - |
|
126 |
Set<Integer> userIds = beats.stream()
|
| - |
|
127 |
.map(com.spice.profitmandi.dao.model.BeatDayDetails::getAuthUserId)
|
| - |
|
128 |
.collect(java.util.stream.Collectors.toSet());
|
| - |
|
129 |
Map<Integer, AuthUser> userMap = new HashMap<>();
|
| - |
|
130 |
if (!userIds.isEmpty()) {
|
| - |
|
131 |
authRepository.selectByIds(new ArrayList<>(userIds))
|
| - |
|
132 |
.forEach(u -> userMap.put(u.getId(), u));
|
| - |
|
133 |
}
|
| - |
|
134 |
|
| - |
|
135 |
List<Map<String, Object>> rows = new ArrayList<>();
|
| - |
|
136 |
for (com.spice.profitmandi.dao.model.BeatDayDetails b : beats) {
|
| - |
|
137 |
AuthUser u = userMap.get(b.getAuthUserId());
|
| - |
|
138 |
Map<String, Object> row = new HashMap<>();
|
| - |
|
139 |
row.put("authUserId", b.getAuthUserId());
|
| - |
|
140 |
row.put("userName", u != null ? (u.getFirstName() + " " + u.getLastName()) : "User #" + b.getAuthUserId());
|
| - |
|
141 |
row.put("scheduleDate", b.getScheduleDate().toString());
|
| - |
|
142 |
row.put("dayNumber", b.getDayNumber());
|
| - |
|
143 |
row.put("beatId", b.getBeatId());
|
| - |
|
144 |
row.put("beatName", b.getBeatName());
|
| - |
|
145 |
row.put("beatColor", b.getBeatColor());
|
| - |
|
146 |
row.put("partnerCount", b.getPartnerStops().size());
|
| - |
|
147 |
row.put("leadCount", b.getLeadStops().size());
|
| - |
|
148 |
row.put("visitCount", b.getPartnerStops().size() + b.getLeadStops().size());
|
| - |
|
149 |
rows.add(row);
|
| - |
|
150 |
}
|
| - |
|
151 |
|
| - |
|
152 |
Map<String, Object> result = new HashMap<>();
|
| - |
|
153 |
result.put("rows", rows);
|
| - |
|
154 |
result.put("startDate", start.toString());
|
| - |
|
155 |
result.put("endDate", end.toString());
|
| - |
|
156 |
return responseSender.ok(result);
|
| - |
|
157 |
}
|
| - |
|
158 |
|
| - |
|
159 |
// JSON: beats running for (authUserId, date) — enriched with partner/lead names & coords
|
| - |
|
160 |
@GetMapping(value = "/beatPlan/dayViewData")
|
| - |
|
161 |
public ResponseEntity<?> beatPlanDayViewData(
|
| - |
|
162 |
@RequestParam int authUserId,
|
| - |
|
163 |
@RequestParam String date) throws ProfitMandiBusinessException {
|
| - |
|
164 |
|
| - |
|
165 |
LocalDate localDate;
|
| - |
|
166 |
try {
|
| - |
|
167 |
localDate = LocalDate.parse(date);
|
| - |
|
168 |
} catch (Exception e) {
|
| - |
|
169 |
return responseSender.badRequest("Invalid date — expected yyyy-MM-dd");
|
| - |
|
170 |
}
|
| - |
|
171 |
|
| - |
|
172 |
List<com.spice.profitmandi.dao.model.BeatDayDetails> beats =
|
| - |
|
173 |
beatPlanQueryService.getBeatsForUserOnDate(authUserId, localDate);
|
| - |
|
174 |
|
| - |
|
175 |
// Collect all partner & lead IDs to fetch metadata in bulk
|
| - |
|
176 |
Set<Integer> partnerIds = new HashSet<>();
|
| - |
|
177 |
Set<Integer> leadIds = new HashSet<>();
|
| - |
|
178 |
for (com.spice.profitmandi.dao.model.BeatDayDetails b : beats) {
|
| - |
|
179 |
b.getPartnerStops().forEach(s -> partnerIds.add((Integer) s.get("fofoId")));
|
| - |
|
180 |
b.getLeadStops().forEach(s -> leadIds.add((Integer) s.get("leadId")));
|
| - |
|
181 |
}
|
| - |
|
182 |
|
| - |
|
183 |
// Partners: name + geocoded lat/lng (geocoder is cached in Redis)
|
| - |
|
184 |
Map<Integer, CustomRetailer> retailerMap = partnerIds.isEmpty()
|
| - |
|
185 |
? new HashMap<>()
|
| - |
|
186 |
: retailerService.getFofoRetailers(new ArrayList<>(partnerIds));
|
| - |
|
187 |
Map<Integer, FofoStore> storeMap = new HashMap<>();
|
| - |
|
188 |
if (!partnerIds.isEmpty()) {
|
| - |
|
189 |
fofoStoreRepository.selectByRetailerIds(new ArrayList<>(partnerIds))
|
| - |
|
190 |
.forEach(fs -> storeMap.put(fs.getId(), fs));
|
| - |
|
191 |
}
|
| - |
|
192 |
|
| - |
|
193 |
// Leads: name + geo
|
| - |
|
194 |
Map<Integer, com.spice.profitmandi.dao.entity.user.Lead> leadMap = new HashMap<>();
|
| - |
|
195 |
Map<Integer, com.spice.profitmandi.dao.entity.user.LeadLiveLocation> leadGeoMap = new HashMap<>();
|
| - |
|
196 |
for (int leadId : leadIds) {
|
| - |
|
197 |
com.spice.profitmandi.dao.entity.user.Lead l = leadRepository.selectById(leadId);
|
| - |
|
198 |
if (l != null) leadMap.put(leadId, l);
|
| - |
|
199 |
com.spice.profitmandi.dao.entity.user.LeadLiveLocation lg =
|
| - |
|
200 |
leadLiveLocationRepositoryAuto.selectApprovedByLeadId(leadId);
|
| - |
|
201 |
if (lg != null) leadGeoMap.put(leadId, lg);
|
| - |
|
202 |
}
|
| - |
|
203 |
|
| - |
|
204 |
// Enrich each stop
|
| - |
|
205 |
List<Map<String, Object>> out = new ArrayList<>();
|
| - |
|
206 |
for (com.spice.profitmandi.dao.model.BeatDayDetails b : beats) {
|
| - |
|
207 |
Map<String, Object> beatJson = new HashMap<>();
|
| - |
|
208 |
beatJson.put("beatId", b.getBeatId());
|
| - |
|
209 |
beatJson.put("beatName", b.getBeatName());
|
| - |
|
210 |
beatJson.put("beatColor", b.getBeatColor());
|
| - |
|
211 |
beatJson.put("dayNumber", b.getDayNumber());
|
| - |
|
212 |
beatJson.put("scheduleDate", b.getScheduleDate().toString());
|
| - |
|
213 |
beatJson.put("endAction", b.getEndAction());
|
| - |
|
214 |
beatJson.put("totalDistanceKm", b.getTotalDistanceKm());
|
| - |
|
215 |
beatJson.put("totalTimeMins", b.getTotalTimeMins());
|
| - |
|
216 |
beatJson.put("startLocationName", b.getStartLocationName());
|
| - |
|
217 |
beatJson.put("startLatitude", b.getStartLatitude());
|
| - |
|
218 |
beatJson.put("startLongitude", b.getStartLongitude());
|
| - |
|
219 |
|
| - |
|
220 |
List<Map<String, Object>> stops = new ArrayList<>();
|
| - |
|
221 |
// Partners
|
| - |
|
222 |
for (Map<String, Object> ps : b.getPartnerStops()) {
|
| - |
|
223 |
int fofoId = (Integer) ps.get("fofoId");
|
| - |
|
224 |
Map<String, Object> stop = new HashMap<>();
|
| - |
|
225 |
stop.put("type", "partner");
|
| - |
|
226 |
stop.put("id", fofoId);
|
| - |
|
227 |
stop.put("sequenceOrder", ps.get("sequenceOrder"));
|
| - |
|
228 |
FofoStore fs = storeMap.get(fofoId);
|
| - |
|
229 |
CustomRetailer cr = retailerMap.get(fofoId);
|
| - |
|
230 |
stop.put("code", fs != null ? fs.getCode() : null);
|
| - |
|
231 |
stop.put("name", fs != null && fs.getOutletName() != null ? fs.getOutletName()
|
| - |
|
232 |
: (cr != null ? cr.getBusinessName() : "Store #" + fofoId));
|
| - |
|
233 |
if (cr != null && cr.getAddress() != null) {
|
| - |
|
234 |
stop.put("address", cr.getAddress().getAddressString());
|
| - |
|
235 |
try {
|
| - |
|
236 |
String geoAddr = com.spice.profitmandi.service.GeocodingService.buildGeoAddress(
|
| - |
|
237 |
cr.getAddress().getLine1(), cr.getAddress().getCity(),
|
| - |
|
238 |
cr.getAddress().getState(), cr.getAddress().getPinCode());
|
| - |
|
239 |
double[] coords = geocodingService.geocodeAddress(geoAddr);
|
| - |
|
240 |
if (coords != null) {
|
| - |
|
241 |
stop.put("lat", coords[0]);
|
| - |
|
242 |
stop.put("lng", coords[1]);
|
| - |
|
243 |
}
|
| - |
|
244 |
} catch (Exception ignored) {
|
| - |
|
245 |
}
|
| - |
|
246 |
}
|
| - |
|
247 |
stops.add(stop);
|
| - |
|
248 |
}
|
| - |
|
249 |
// Leads
|
| - |
|
250 |
for (Map<String, Object> ls : b.getLeadStops()) {
|
| - |
|
251 |
int leadId = (Integer) ls.get("leadId");
|
| - |
|
252 |
Map<String, Object> stop = new HashMap<>();
|
| - |
|
253 |
stop.put("type", "lead");
|
| - |
|
254 |
stop.put("id", leadId);
|
| - |
|
255 |
stop.put("sequenceOrder", ls.get("sequenceOrder"));
|
| - |
|
256 |
stop.put("nearestStoreId", ls.get("nearestStoreId"));
|
| - |
|
257 |
com.spice.profitmandi.dao.entity.user.Lead l = leadMap.get(leadId);
|
| - |
|
258 |
stop.put("name", l != null ? l.getFirstName() + " " + l.getLastName() : "Lead #" + leadId);
|
| - |
|
259 |
stop.put("mobile", l != null ? l.getLeadMobile() : null);
|
| - |
|
260 |
stop.put("city", l != null ? l.getCity() : null);
|
| - |
|
261 |
com.spice.profitmandi.dao.entity.user.LeadLiveLocation lg = leadGeoMap.get(leadId);
|
| - |
|
262 |
if (lg != null) {
|
| - |
|
263 |
stop.put("lat", lg.getLatitude());
|
| - |
|
264 |
stop.put("lng", lg.getLongitude());
|
| - |
|
265 |
}
|
| - |
|
266 |
stops.add(stop);
|
| - |
|
267 |
}
|
| - |
|
268 |
beatJson.put("stops", stops);
|
| - |
|
269 |
beatJson.put("partnerCount", b.getPartnerStops().size());
|
| - |
|
270 |
beatJson.put("leadCount", b.getLeadStops().size());
|
| - |
|
271 |
out.add(beatJson);
|
| - |
|
272 |
}
|
| - |
|
273 |
|
| - |
|
274 |
Map<String, Object> result = new HashMap<>();
|
| - |
|
275 |
result.put("beats", out);
|
| - |
|
276 |
return responseSender.ok(result);
|
| - |
|
277 |
}
|
| - |
|
278 |
|
| 92 |
@GetMapping(value = "/beatPlan/getAuthUsers")
|
279 |
@GetMapping(value = "/beatPlan/getAuthUsers")
|
| 93 |
public ResponseEntity<?> getAuthUsers(
|
280 |
public ResponseEntity<?> getAuthUsers(
|
| 94 |
@RequestParam int categoryId,
|
281 |
@RequestParam int categoryId,
|
| 95 |
@RequestParam EscalationType escalationType) {
|
282 |
@RequestParam EscalationType escalationType) {
|
| 96 |
List<AuthUser> authUsers = csService.getAuthUserByCategoryId(categoryId, escalationType);
|
283 |
List<AuthUser> authUsers = csService.getAuthUserByCategoryId(categoryId, escalationType);
|