| Line 9... |
Line 9... |
| 9 |
import com.spice.profitmandi.dao.entity.auth.AuthUser;
|
9 |
import com.spice.profitmandi.dao.entity.auth.AuthUser;
|
| 10 |
import com.spice.profitmandi.dao.entity.fofo.FofoStore;
|
10 |
import com.spice.profitmandi.dao.entity.fofo.FofoStore;
|
| 11 |
import com.spice.profitmandi.dao.entity.logistics.PublicHolidays;
|
11 |
import com.spice.profitmandi.dao.entity.logistics.PublicHolidays;
|
| 12 |
import com.spice.profitmandi.dao.entity.user.*;
|
12 |
import com.spice.profitmandi.dao.entity.user.*;
|
| 13 |
import com.spice.profitmandi.dao.enumuration.cs.EscalationType;
|
13 |
import com.spice.profitmandi.dao.enumuration.cs.EscalationType;
|
| - |
|
14 |
import com.spice.profitmandi.dao.enumuration.dtr.AgendaSource;
|
| - |
|
15 |
import com.spice.profitmandi.dao.enumuration.dtr.AgendaType;
|
| 14 |
import com.spice.profitmandi.dao.repository.auth.AuthRepository;
|
16 |
import com.spice.profitmandi.dao.repository.auth.AuthRepository;
|
| 15 |
import com.spice.profitmandi.dao.repository.cs.CsService;
|
17 |
import com.spice.profitmandi.dao.repository.cs.CsService;
|
| 16 |
import com.spice.profitmandi.dao.repository.dtr.*;
|
18 |
import com.spice.profitmandi.dao.repository.dtr.*;
|
| 17 |
import com.spice.profitmandi.dao.repository.logistics.PublicHolidaysRepository;
|
19 |
import com.spice.profitmandi.dao.repository.logistics.PublicHolidaysRepository;
|
| 18 |
import com.spice.profitmandi.service.user.RetailerService;
|
20 |
import com.spice.profitmandi.service.user.RetailerService;
|
| Line 85... |
Line 87... |
| 85 |
private ResponseSender responseSender;
|
87 |
private ResponseSender responseSender;
|
| 86 |
@Autowired
|
88 |
@Autowired
|
| 87 |
private FofoStoreRepository fofoStoreRepository;
|
89 |
private FofoStoreRepository fofoStoreRepository;
|
| 88 |
@Autowired
|
90 |
@Autowired
|
| 89 |
private com.spice.profitmandi.dao.repository.logistics.CompanyOfficeRepository companyOfficeRepository;
|
91 |
private com.spice.profitmandi.dao.repository.logistics.CompanyOfficeRepository companyOfficeRepository;
|
| - |
|
92 |
@Autowired
|
| - |
|
93 |
private AgendaInstanceRepository agendaInstanceRepository;
|
| - |
|
94 |
@Autowired
|
| - |
|
95 |
private com.spice.profitmandi.service.AgendaInstanceService agendaInstanceService;
|
| 90 |
|
96 |
|
| 91 |
@GetMapping(value = "/beatPlan")
|
97 |
@GetMapping(value = "/beatPlan")
|
| 92 |
public String beatPlan(HttpServletRequest request, Model model) throws ProfitMandiBusinessException {
|
98 |
public String beatPlan(HttpServletRequest request, Model model) throws ProfitMandiBusinessException {
|
| 93 |
model.addAttribute("escalationTypes", visibleLevelsFor(request));
|
99 |
model.addAttribute("escalationTypes", visibleLevelsFor(request));
|
| 94 |
return "beat-plan";
|
100 |
return "beat-plan";
|
| Line 349... |
Line 355... |
| 349 |
if (ai != bi) return ai ? -1 : 1;
|
355 |
if (ai != bi) return ai ? -1 : 1;
|
| 350 |
return String.valueOf(a.get("code")).compareToIgnoreCase(String.valueOf(b.get("code")));
|
356 |
return String.valueOf(a.get("code")).compareToIgnoreCase(String.valueOf(b.get("code")));
|
| 351 |
});
|
357 |
});
|
| 352 |
}
|
358 |
}
|
| 353 |
|
359 |
|
| - |
|
360 |
// Open agenda instances (AUTO + MANUAL) per party — the modal pre-selects
|
| - |
|
361 |
// these; AUTO ones are badged and close only when their metric normalizes.
|
| - |
|
362 |
List<Integer> partyIds = new ArrayList<>();
|
| - |
|
363 |
for (Map<String, Object> p : parties) partyIds.add((Integer) p.get("fofoStoreId"));
|
| - |
|
364 |
Map<Integer, List<Map<String, Object>>> openAgendasByFofo = new HashMap<>();
|
| - |
|
365 |
for (AgendaInstance ai : agendaInstanceRepository.selectOpenByFofoIds(partyIds)) {
|
| - |
|
366 |
Map<String, Object> m = new HashMap<>();
|
| - |
|
367 |
m.put("id", ai.getId());
|
| - |
|
368 |
m.put("agendaType", ai.getAgendaType());
|
| - |
|
369 |
m.put("label", labelFor(ai.getAgendaType()));
|
| - |
|
370 |
m.put("source", ai.getSource());
|
| - |
|
371 |
m.put("openedOn", ai.getOpenedOn() != null ? ai.getOpenedOn().toLocalDate().toString() : null);
|
| - |
|
372 |
openAgendasByFofo.computeIfAbsent(ai.getFofoId(), k -> new ArrayList<>()).add(m);
|
| - |
|
373 |
}
|
| - |
|
374 |
for (Map<String, Object> p : parties) {
|
| - |
|
375 |
p.put("openAgendas", openAgendasByFofo.getOrDefault((Integer) p.get("fofoStoreId"), new ArrayList<>()));
|
| - |
|
376 |
}
|
| - |
|
377 |
|
| 354 |
Map<String, Object> result = new HashMap<>();
|
378 |
Map<String, Object> result = new HashMap<>();
|
| 355 |
result.put("dtrUserId", dtrUserId);
|
379 |
result.put("dtrUserId", dtrUserId);
|
| 356 |
result.put("authUserId", authUserId);
|
380 |
result.put("authUserId", authUserId);
|
| 357 |
result.put("userName", au.getFirstName() + " " + au.getLastName());
|
381 |
result.put("userName", au.getFirstName() + " " + au.getLastName());
|
| 358 |
result.put("parties", parties);
|
382 |
result.put("parties", parties);
|
| 359 |
result.put("agendaOptions", com.spice.profitmandi.dao.enumuration.dtr.VisitAgenda.labels());
|
383 |
result.put("agendaOptions", com.spice.profitmandi.dao.enumuration.dtr.VisitAgenda.labels());
|
| 360 |
return responseSender.ok(result);
|
384 |
return responseSender.ok(result);
|
| 361 |
}
|
385 |
}
|
| 362 |
|
386 |
|
| - |
|
387 |
// agenda_type holds the enum NAME (e.g. LOW_SALES); tolerate unknown values.
|
| - |
|
388 |
private String labelFor(String agendaTypeName) {
|
| - |
|
389 |
try {
|
| - |
|
390 |
return AgendaType.valueOf(agendaTypeName).getLabel();
|
| - |
|
391 |
} catch (Exception e) {
|
| - |
|
392 |
return agendaTypeName;
|
| - |
|
393 |
}
|
| - |
|
394 |
}
|
| - |
|
395 |
|
| 363 |
// Submit assignment — accepts a JSON body, builds the v2 payload, posts it
|
396 |
// Submit assignment — accepts a JSON body, builds the v2 payload, posts it
|
| 364 |
@PostMapping(value = "/beatPlan/assignVisit/submit")
|
397 |
@PostMapping(value = "/beatPlan/assignVisit/submit")
|
| 365 |
public ResponseEntity<?> assignVisitSubmit(
|
398 |
public ResponseEntity<?> assignVisitSubmit(
|
| 366 |
HttpServletRequest request,
|
399 |
HttpServletRequest request,
|
| 367 |
@org.springframework.web.bind.annotation.RequestBody Map<String, Object> body) throws Exception {
|
400 |
@org.springframework.web.bind.annotation.RequestBody Map<String, Object> body) throws Exception {
|
| Line 372... |
Line 405... |
| 372 |
if (authUserId == null || planDate == null || selected == null || selected.isEmpty()) {
|
405 |
if (authUserId == null || planDate == null || selected == null || selected.isEmpty()) {
|
| 373 |
return responseSender.badRequest("authUserId, planDate and parties are required");
|
406 |
return responseSender.badRequest("authUserId, planDate and parties are required");
|
| 374 |
}
|
407 |
}
|
| 375 |
|
408 |
|
| 376 |
// Defence-in-depth: Assign Visit is L2-and-above only (mirrors the hidden UI).
|
409 |
// Defence-in-depth: Assign Visit is L2-and-above only (mirrors the hidden UI).
|
| - |
|
410 |
AuthUser operator = currentUser(request);
|
| 377 |
if (!canEditBeat(currentUser(request))) {
|
411 |
if (!canEditBeat(operator)) {
|
| 378 |
return responseSender.badRequest("Assigning a visit is restricted to L2 and above.");
|
412 |
return responseSender.badRequest("Assigning a visit is restricted to L2 and above.");
|
| 379 |
}
|
413 |
}
|
| - |
|
414 |
Integer operatorAuthId = operator != null ? operator.getId() : null;
|
| 380 |
|
415 |
|
| 381 |
AuthUser au = authRepository.selectById(authUserId);
|
416 |
AuthUser au = authRepository.selectById(authUserId);
|
| 382 |
if (au == null) return responseSender.badRequest("Auth user not found");
|
417 |
if (au == null) return responseSender.badRequest("Auth user not found");
|
| 383 |
|
418 |
|
| 384 |
// Map auth → dtr.users.id (this is the userId the v2 endpoint expects)
|
419 |
// Map auth → dtr.users.id (this is the userId the v2 endpoint expects)
|
| Line 448... |
Line 483... |
| 448 |
if (single != null && !single.trim().isEmpty()) agendas.add(single.trim());
|
483 |
if (single != null && !single.trim().isEmpty()) agendas.add(single.trim());
|
| 449 |
}
|
484 |
}
|
| 450 |
if (agendas.isEmpty()) agendas.add("Visit");
|
485 |
if (agendas.isEmpty()) agendas.add("Visit");
|
| 451 |
String agendaJoined = String.join(", ", agendas);
|
486 |
String agendaJoined = String.join(", ", agendas);
|
| 452 |
|
487 |
|
| - |
|
488 |
// Lifecycle: any picked agenda without an open instance becomes a
|
| - |
|
489 |
// MANUAL instance. open() no-ops when one is already OPEN (AUTO or
|
| - |
|
490 |
// MANUAL), so pre-existing auto agendas are left untouched.
|
| - |
|
491 |
for (String label : agendas) {
|
| - |
|
492 |
AgendaType type = AgendaType.fromLabel(label);
|
| - |
|
493 |
if (type == null) continue; // "Visit" default — not lifecycle-tracked
|
| - |
|
494 |
agendaInstanceService.open(fofoStoreId, type.name(), AgendaSource.MANUAL, operatorAuthId, null);
|
| - |
|
495 |
}
|
| - |
|
496 |
|
| 453 |
String visitLocation = (lat != null && lng != null && !lat.isEmpty() && !lng.isEmpty())
|
497 |
String visitLocation = (lat != null && lng != null && !lat.isEmpty() && !lng.isEmpty())
|
| 454 |
? (lat + "," + lng) : "0.0000,0.0000";
|
498 |
? (lat + "," + lng) : "0.0000,0.0000";
|
| 455 |
|
499 |
|
| 456 |
String displayName = (outletName != null && !outletName.isEmpty()) ? outletName : ("Store #" + fofoStoreId);
|
500 |
String displayName = (outletName != null && !outletName.isEmpty()) ? outletName : ("Store #" + fofoStoreId);
|
| 457 |
String newTaskName = agendaJoined + " | " + displayName;
|
501 |
String newTaskName = agendaJoined + " | " + displayName;
|