| Line 99... |
Line 99... |
| 99 |
private com.spice.profitmandi.dao.repository.dtr.UserRepository userRepositoryAuto;
|
99 |
private com.spice.profitmandi.dao.repository.dtr.UserRepository userRepositoryAuto;
|
| 100 |
@Autowired
|
100 |
@Autowired
|
| 101 |
private com.spice.profitmandi.common.web.client.RestClient restClientAuto;
|
101 |
private com.spice.profitmandi.common.web.client.RestClient restClientAuto;
|
| 102 |
@Autowired
|
102 |
@Autowired
|
| 103 |
private com.spice.profitmandi.dao.repository.auth.LocationTrackingRepository locationTrackingRepositoryAuto;
|
103 |
private com.spice.profitmandi.dao.repository.auth.LocationTrackingRepository locationTrackingRepositoryAuto;
|
| - |
|
104 |
@Autowired
|
| - |
|
105 |
private com.spice.profitmandi.dao.repository.dtr.BeatDeferredVisitRepository beatDeferredVisitRepository;
|
| 104 |
|
106 |
|
| 105 |
private static Double parseDoubleOrNull(String s) {
|
107 |
private static Double parseDoubleOrNull(String s) {
|
| 106 |
if (s == null || s.trim().isEmpty()) return null;
|
108 |
if (s == null || s.trim().isEmpty()) return null;
|
| 107 |
try {
|
109 |
try {
|
| 108 |
return Double.parseDouble(s.trim());
|
110 |
return Double.parseDouble(s.trim());
|
| Line 453... |
Line 455... |
| 453 |
msg.append(" for ").append(au.getFirstName()).append(" ").append(au.getLastName());
|
455 |
msg.append(" for ").append(au.getFirstName()).append(" ").append(au.getLastName());
|
| 454 |
result.put("message", msg.toString());
|
456 |
result.put("message", msg.toString());
|
| 455 |
return responseSender.ok(result);
|
457 |
return responseSender.ok(result);
|
| 456 |
}
|
458 |
}
|
| 457 |
|
459 |
|
| - |
|
460 |
// ====================== DEFERRED PARTNERS ======================
|
| - |
|
461 |
// Heads review partners that weren't visited on their planned day and act on
|
| - |
|
462 |
// them. The deferral lifecycle lives in user.beat_deferred_visit (separate
|
| - |
|
463 |
// from the raw location_tracking event log). Detection = explicit
|
| - |
|
464 |
// (mark_type='DEFERRED') + derived (planned beat_route minus completed visits).
|
| - |
|
465 |
|
| - |
|
466 |
// Page
|
| - |
|
467 |
@GetMapping(value = "/beatPlan/deferredView")
|
| - |
|
468 |
public String deferredView(HttpServletRequest request, Model model) throws ProfitMandiBusinessException {
|
| - |
|
469 |
model.addAttribute("escalationTypes", visibleLevelsFor(request));
|
| - |
|
470 |
return "beat-plan-deferred";
|
| - |
|
471 |
}
|
| - |
|
472 |
|
| - |
|
473 |
// List (syncs the table first, then returns the head's downline deferrals).
|
| - |
|
474 |
@GetMapping(value = "/beatPlan/deferred")
|
| - |
|
475 |
public ResponseEntity<?> deferredList(
|
| - |
|
476 |
HttpServletRequest request,
|
| - |
|
477 |
@RequestParam(required = false) String startDate,
|
| - |
|
478 |
@RequestParam(required = false) String endDate) throws Exception {
|
| - |
|
479 |
|
| - |
|
480 |
LocalDate start, end;
|
| - |
|
481 |
try {
|
| - |
|
482 |
start = (startDate == null || startDate.isEmpty()) ? LocalDate.now().minusDays(7) : LocalDate.parse(startDate);
|
| - |
|
483 |
end = (endDate == null || endDate.isEmpty()) ? LocalDate.now() : LocalDate.parse(endDate);
|
| - |
|
484 |
} catch (Exception e) {
|
| - |
|
485 |
return responseSender.badRequest("Invalid date — expected yyyy-MM-dd");
|
| - |
|
486 |
}
|
| - |
|
487 |
|
| - |
|
488 |
LoginDetails ld = cookiesProcessor.getCookiesObject(request);
|
| - |
|
489 |
AuthUser me = (ld != null) ? authRepository.selectByEmailOrMobile(ld.getEmailId()) : null;
|
| - |
|
490 |
if (me == null) return responseSender.unauthorized("Not logged in");
|
| - |
|
491 |
|
| - |
|
492 |
// PURE READ. Deferrals are persisted at the write point (BeatTrackingController,
|
| - |
|
493 |
// when mark_type='DEFERRED' is recorded) — this endpoint never writes.
|
| - |
|
494 |
List<BeatDeferredVisit> source;
|
| - |
|
495 |
if (isSuperAdmin(me)) {
|
| - |
|
496 |
source = beatDeferredVisitRepository.selectByDateRange(start, end);
|
| - |
|
497 |
} else {
|
| - |
|
498 |
Set<Integer> downline = new HashSet<>(authService.getAllReportees(me.getId()));
|
| - |
|
499 |
downline.add(me.getId());
|
| - |
|
500 |
source = beatDeferredVisitRepository.selectByAuthUserIdsAndDateRange(new ArrayList<>(downline), start, end);
|
| - |
|
501 |
}
|
| - |
|
502 |
List<BeatDeferredVisit> rows = source.stream()
|
| - |
|
503 |
.filter(r -> "DEFERRED".equals(r.getStatus()))
|
| - |
|
504 |
.collect(Collectors.toList());
|
| - |
|
505 |
|
| - |
|
506 |
// ---- nextScheduledDate (info only: does a future run already cover it?) ----
|
| - |
|
507 |
// Only meaningful for partner visits (leads aren't on beat_route). Purely a
|
| - |
|
508 |
// hint — the row stays actionable even when auto-covered, since the next run
|
| - |
|
509 |
// could be far off.
|
| - |
|
510 |
Map<Integer, Map<Integer, LocalDate>> coverCache = new HashMap<>();
|
| - |
|
511 |
Map<Integer, LocalDate> nextByRowId = new HashMap<>();
|
| - |
|
512 |
for (BeatDeferredVisit r : rows) {
|
| - |
|
513 |
if (!"franchisee-visit".equals(r.getTaskType())) continue;
|
| - |
|
514 |
Map<Integer, LocalDate> cover = coverCache.computeIfAbsent(r.getAuthUserId(), this::computeFutureCover);
|
| - |
|
515 |
LocalDate next = cover.get(r.getFofoId());
|
| - |
|
516 |
if (next != null) nextByRowId.put(r.getId(), next);
|
| - |
|
517 |
}
|
| - |
|
518 |
|
| - |
|
519 |
// ---- resolve user names (display name + type already denormalized on the row) ----
|
| - |
|
520 |
Set<Integer> authIds = rows.stream().map(BeatDeferredVisit::getAuthUserId).collect(Collectors.toSet());
|
| - |
|
521 |
Map<Integer, AuthUser> userMap = new HashMap<>();
|
| - |
|
522 |
if (!authIds.isEmpty())
|
| - |
|
523 |
authRepository.selectByIds(new ArrayList<>(authIds)).forEach(u -> userMap.put(u.getId(), u));
|
| - |
|
524 |
|
| - |
|
525 |
List<Map<String, Object>> out = new ArrayList<>();
|
| - |
|
526 |
for (BeatDeferredVisit r : rows) {
|
| - |
|
527 |
AuthUser u = userMap.get(r.getAuthUserId());
|
| - |
|
528 |
boolean isLead = "lead".equalsIgnoreCase(r.getTaskType());
|
| - |
|
529 |
Map<String, Object> row = new HashMap<>();
|
| - |
|
530 |
row.put("id", r.getId());
|
| - |
|
531 |
row.put("authUserId", r.getAuthUserId());
|
| - |
|
532 |
row.put("userName", u != null ? (u.getFirstName() + " " + u.getLastName()) : ("User #" + r.getAuthUserId()));
|
| - |
|
533 |
row.put("fofoStoreId", r.getFofoId());
|
| - |
|
534 |
row.put("name", r.getDisplayName() != null ? r.getDisplayName() : ("#" + r.getFofoId()));
|
| - |
|
535 |
row.put("type", isLead ? "Lead" : "Visit");
|
| - |
|
536 |
row.put("deferredDate", r.getDeferredDate() != null ? r.getDeferredDate().toString() : null);
|
| - |
|
537 |
row.put("reason", r.getReason());
|
| - |
|
538 |
row.put("status", r.getStatus());
|
| - |
|
539 |
LocalDate next = nextByRowId.get(r.getId());
|
| - |
|
540 |
row.put("nextScheduledDate", next != null ? next.toString() : null);
|
| - |
|
541 |
out.add(row);
|
| - |
|
542 |
}
|
| - |
|
543 |
out.sort((a, c) -> String.valueOf(a.get("deferredDate")).compareTo(String.valueOf(c.get("deferredDate"))));
|
| - |
|
544 |
|
| - |
|
545 |
Map<String, Object> result = new HashMap<>();
|
| - |
|
546 |
result.put("rows", out);
|
| - |
|
547 |
result.put("startDate", start.toString());
|
| - |
|
548 |
result.put("endDate", end.toString());
|
| - |
|
549 |
return responseSender.ok(result);
|
| - |
|
550 |
}
|
| - |
|
551 |
|
| - |
|
552 |
// Head action on a deferral: reschedule (one-off visit, or into an existing
|
| - |
|
553 |
// beat-day) or cancel. Never edits the beat template.
|
| - |
|
554 |
@PostMapping(value = "/beatPlan/deferred/action")
|
| - |
|
555 |
public ResponseEntity<?> deferredAction(
|
| - |
|
556 |
HttpServletRequest request,
|
| - |
|
557 |
@org.springframework.web.bind.annotation.RequestBody Map<String, Object> body) throws Exception {
|
| - |
|
558 |
|
| - |
|
559 |
Integer deferredId = body.get("deferredId") != null ? ((Number) body.get("deferredId")).intValue() : null;
|
| - |
|
560 |
String action = (String) body.get("action");
|
| - |
|
561 |
String toDateStr = (String) body.get("toDate");
|
| - |
|
562 |
if (deferredId == null || action == null)
|
| - |
|
563 |
return responseSender.badRequest("deferredId and action are required");
|
| - |
|
564 |
|
| - |
|
565 |
LoginDetails ld = cookiesProcessor.getCookiesObject(request);
|
| - |
|
566 |
AuthUser me = (ld != null) ? authRepository.selectByEmailOrMobile(ld.getEmailId()) : null;
|
| - |
|
567 |
if (me == null) return responseSender.unauthorized("Not logged in");
|
| - |
|
568 |
|
| - |
|
569 |
BeatDeferredVisit d = beatDeferredVisitRepository.selectById(deferredId);
|
| - |
|
570 |
if (d == null) return responseSender.badRequest("Deferred record not found");
|
| - |
|
571 |
|
| - |
|
572 |
LocalDateTime now = LocalDateTime.now();
|
| - |
|
573 |
|
| - |
|
574 |
if ("cancel".equalsIgnoreCase(action)) {
|
| - |
|
575 |
d.setStatus("CANCELLED");
|
| - |
|
576 |
d.setActionBy(me.getId());
|
| - |
|
577 |
d.setUpdatedTimestamp(now);
|
| - |
|
578 |
beatDeferredVisitRepository.persist(d);
|
| - |
|
579 |
Map<String, Object> ok = new HashMap<>();
|
| - |
|
580 |
ok.put("status", true);
|
| - |
|
581 |
ok.put("message", "Deferred visit cancelled");
|
| - |
|
582 |
return responseSender.ok(ok);
|
| - |
|
583 |
}
|
| - |
|
584 |
|
| - |
|
585 |
// reschedule_oneoff | reschedule_beat
|
| - |
|
586 |
if (toDateStr == null || toDateStr.isEmpty())
|
| - |
|
587 |
return responseSender.badRequest("toDate is required to reschedule");
|
| - |
|
588 |
LocalDate toDate;
|
| - |
|
589 |
try {
|
| - |
|
590 |
toDate = LocalDate.parse(toDateStr);
|
| - |
|
591 |
} catch (Exception e) {
|
| - |
|
592 |
return responseSender.badRequest("Invalid toDate (yyyy-MM-dd)");
|
| - |
|
593 |
}
|
| - |
|
594 |
if (toDate.isBefore(LocalDate.now())) return responseSender.badRequest("Reschedule date cannot be in the past");
|
| - |
|
595 |
|
| - |
|
596 |
if ("reschedule_beat".equalsIgnoreCase(action)) {
|
| - |
|
597 |
boolean hasBeat = beatRepository.selectActiveByAuthUserId(d.getAuthUserId()).stream()
|
| - |
|
598 |
.flatMap(b -> beatScheduleRepository.selectByBeatId(b.getId()).stream())
|
| - |
|
599 |
.anyMatch(s -> s.getStartDate() != null && s.getStartDate().equals(toDate));
|
| - |
|
600 |
if (!hasBeat)
|
| - |
|
601 |
return responseSender.badRequest("No beat is scheduled for this user on " + toDateStr + ". Pick another date or use a one-off visit.");
|
| - |
|
602 |
}
|
| - |
|
603 |
|
| - |
|
604 |
// Resolve dtr user, then create a PENDING task on toDate. Reuse the
|
| - |
|
605 |
// denormalized name + type (works for both partner visits and leads — for
|
| - |
|
606 |
// leads, looking up fofo_store would be the wrong id space). For visits we
|
| - |
|
607 |
// still try to pull lat/lng for the visit location.
|
| - |
|
608 |
Integer dtrId = resolveDtrId(d.getAuthUserId(), new HashMap<>());
|
| - |
|
609 |
if (dtrId == null) return responseSender.badRequest("No dtr.users record for this sales person");
|
| - |
|
610 |
boolean isLead = "lead".equalsIgnoreCase(d.getTaskType());
|
| - |
|
611 |
String visitLocation = "0.0000,0.0000";
|
| - |
|
612 |
if (!isLead) {
|
| - |
|
613 |
try {
|
| - |
|
614 |
List<FofoStore> ss = fofoStoreRepository.selectByRetailerIds(java.util.Collections.singletonList(d.getFofoId()));
|
| - |
|
615 |
if (!ss.isEmpty()) {
|
| - |
|
616 |
FofoStore fs = ss.get(0);
|
| - |
|
617 |
if (fs.getLatitude() != null && fs.getLongitude() != null
|
| - |
|
618 |
&& !fs.getLatitude().isEmpty() && !fs.getLongitude().isEmpty()) {
|
| - |
|
619 |
visitLocation = fs.getLatitude() + "," + fs.getLongitude();
|
| - |
|
620 |
}
|
| - |
|
621 |
}
|
| - |
|
622 |
} catch (Exception ignored) {
|
| - |
|
623 |
}
|
| - |
|
624 |
}
|
| - |
|
625 |
String taskName = d.getDisplayName() != null ? d.getDisplayName()
|
| - |
|
626 |
: ((d.getReason() != null ? d.getReason() : "Rescheduled") + " | #" + d.getFofoId());
|
| - |
|
627 |
|
| - |
|
628 |
com.spice.profitmandi.dao.entity.auth.LocationTracking row = new com.spice.profitmandi.dao.entity.auth.LocationTracking();
|
| - |
|
629 |
row.setUserId(dtrId);
|
| - |
|
630 |
row.setDeviceId("0");
|
| - |
|
631 |
row.setTaskId(d.getFofoId());
|
| - |
|
632 |
row.setTaskDate(toDate);
|
| - |
|
633 |
row.setTaskName(taskName);
|
| - |
|
634 |
row.setTaskDescription("Rescheduled from " + d.getDeferredDate());
|
| - |
|
635 |
row.setTaskType(d.getTaskType() != null ? d.getTaskType() : "franchisee-visit");
|
| - |
|
636 |
row.setMarkType("PENDING");
|
| - |
|
637 |
row.setAddress("");
|
| - |
|
638 |
row.setVisitLocation(visitLocation);
|
| - |
|
639 |
row.setCheckInLatLng("0.0000,0.0000");
|
| - |
|
640 |
row.setCheckOutLatLng("0.0000,0.0000");
|
| - |
|
641 |
row.setCheckInTime(java.time.LocalTime.MIDNIGHT);
|
| - |
|
642 |
row.setCheckOutTime(java.time.LocalTime.MIDNIGHT);
|
| - |
|
643 |
row.setTransitTime(java.time.LocalTime.MIDNIGHT);
|
| - |
|
644 |
row.setTimeSpent(java.time.LocalTime.MIDNIGHT);
|
| - |
|
645 |
row.setEstimatedTime(java.time.LocalTime.MIDNIGHT);
|
| - |
|
646 |
row.setSessionStartTime(java.time.LocalTime.MIDNIGHT);
|
| - |
|
647 |
row.setSessionEndTime(java.time.LocalTime.MIDNIGHT);
|
| - |
|
648 |
row.setTotalDistance("0.0");
|
| - |
|
649 |
row.setStatus(false);
|
| - |
|
650 |
row.setCreatedTimestamp(now);
|
| - |
|
651 |
row.setUpdatedTimestamp(now);
|
| - |
|
652 |
locationTrackingRepositoryAuto.persist(row);
|
| - |
|
653 |
|
| - |
|
654 |
d.setStatus("RESCHEDULED");
|
| - |
|
655 |
d.setRescheduledToDate(toDate);
|
| - |
|
656 |
d.setActionBy(me.getId());
|
| - |
|
657 |
d.setUpdatedTimestamp(now);
|
| - |
|
658 |
beatDeferredVisitRepository.persist(d);
|
| - |
|
659 |
|
| - |
|
660 |
Map<String, Object> ok = new HashMap<>();
|
| - |
|
661 |
ok.put("status", true);
|
| - |
|
662 |
ok.put("message", "Visit rescheduled to " + toDateStr);
|
| - |
|
663 |
return responseSender.ok(ok);
|
| - |
|
664 |
}
|
| - |
|
665 |
|
| - |
|
666 |
// Drop a deferred item into a specific upcoming BEAT run (chosen from the beat
|
| - |
|
667 |
// calendar). Lead → a lead_route row on that beat/date (renders as a lead stop).
|
| - |
|
668 |
// Partner visit → appended to that beat's route for the date's day_number.
|
| - |
|
669 |
// The beat plan calendar then shows it. Marks the deferral RESCHEDULED.
|
| - |
|
670 |
@PostMapping(value = "/beatPlan/deferred/assignToBeat")
|
| - |
|
671 |
public ResponseEntity<?> deferredAssignToBeat(
|
| - |
|
672 |
HttpServletRequest request,
|
| - |
|
673 |
@org.springframework.web.bind.annotation.RequestBody Map<String, Object> body) throws Exception {
|
| - |
|
674 |
|
| - |
|
675 |
Integer deferredId = body.get("deferredId") != null ? ((Number) body.get("deferredId")).intValue() : null;
|
| - |
|
676 |
Integer beatId = body.get("beatId") != null ? ((Number) body.get("beatId")).intValue() : null;
|
| - |
|
677 |
String dateStr = (String) body.get("date");
|
| - |
|
678 |
if (deferredId == null || beatId == null || dateStr == null)
|
| - |
|
679 |
return responseSender.badRequest("deferredId, beatId and date are required");
|
| - |
|
680 |
|
| - |
|
681 |
LoginDetails ld = cookiesProcessor.getCookiesObject(request);
|
| - |
|
682 |
AuthUser me = (ld != null) ? authRepository.selectByEmailOrMobile(ld.getEmailId()) : null;
|
| - |
|
683 |
if (me == null) return responseSender.unauthorized("Not logged in");
|
| - |
|
684 |
|
| - |
|
685 |
LocalDate date;
|
| - |
|
686 |
try {
|
| - |
|
687 |
date = LocalDate.parse(dateStr);
|
| - |
|
688 |
} catch (Exception e) {
|
| - |
|
689 |
return responseSender.badRequest("Invalid date (yyyy-MM-dd)");
|
| - |
|
690 |
}
|
| - |
|
691 |
if (date.isBefore(LocalDate.now())) return responseSender.badRequest("Pick an upcoming date");
|
| - |
|
692 |
|
| - |
|
693 |
BeatDeferredVisit d = beatDeferredVisitRepository.selectById(deferredId);
|
| - |
|
694 |
if (d == null) return responseSender.badRequest("Deferred record not found");
|
| - |
|
695 |
|
| - |
|
696 |
// A deferral can only move FORWARD — never onto the day it was deferred or earlier.
|
| - |
|
697 |
if (d.getDeferredDate() != null && !date.isAfter(d.getDeferredDate())) {
|
| - |
|
698 |
return responseSender.badRequest("A deferred item can only be moved to a date after "
|
| - |
|
699 |
+ d.getDeferredDate() + " (it was deferred that day).");
|
| - |
|
700 |
}
|
| - |
|
701 |
|
| - |
|
702 |
Beat beat = beatRepository.selectById(beatId);
|
| - |
|
703 |
if (beat == null) return responseSender.badRequest("Beat not found");
|
| - |
|
704 |
|
| - |
|
705 |
// The beat must actually run on the chosen date — get that run's day number.
|
| - |
|
706 |
BeatSchedule sched = beatScheduleRepository.selectByBeatId(beatId).stream()
|
| - |
|
707 |
.filter(s -> s.getStartDate() != null && s.getStartDate().equals(date))
|
| - |
|
708 |
.findFirst().orElse(null);
|
| - |
|
709 |
if (sched == null) return responseSender.badRequest("That beat is not scheduled on " + dateStr);
|
| - |
|
710 |
|
| - |
|
711 |
LocalDateTime now = LocalDateTime.now();
|
| - |
|
712 |
boolean isLead = "lead".equalsIgnoreCase(d.getTaskType());
|
| - |
|
713 |
|
| - |
|
714 |
if (isLead) {
|
| - |
|
715 |
// Avoid duplicating the same lead on the same beat/date
|
| - |
|
716 |
boolean exists = leadRouteRepository.selectByBeatId(beatId).stream()
|
| - |
|
717 |
.anyMatch(lr -> lr.getLeadId() == d.getFofoId()
|
| - |
|
718 |
&& date.equals(lr.getScheduleDate())
|
| - |
|
719 |
&& !"CANCELLED".equals(lr.getStatus()));
|
| - |
|
720 |
if (!exists) {
|
| - |
|
721 |
LeadRoute lr = new LeadRoute();
|
| - |
|
722 |
lr.setBeatId(beatId);
|
| - |
|
723 |
lr.setLeadId(d.getFofoId());
|
| - |
|
724 |
lr.setScheduleDate(date);
|
| - |
|
725 |
lr.setSequenceOrder(9999); // append; planner can reorder
|
| - |
|
726 |
lr.setStatus("APPROVED");
|
| - |
|
727 |
lr.setApprovedBy(me.getId());
|
| - |
|
728 |
lr.setApprovedTimestamp(now);
|
| - |
|
729 |
lr.setCreatedTimestamp(now);
|
| - |
|
730 |
lr.setUpdatedTimestamp(now);
|
| - |
|
731 |
leadRouteRepository.persist(lr);
|
| - |
|
732 |
}
|
| - |
|
733 |
} else {
|
| - |
|
734 |
// Partner visit → append to that beat's route for the date's day number,
|
| - |
|
735 |
// if not already present on that day.
|
| - |
|
736 |
boolean exists = beatRouteRepository.selectByBeatId(beatId).stream()
|
| - |
|
737 |
.anyMatch(r -> r.getFofoId() == d.getFofoId() && r.getDayNumber() == sched.getDayNumber() && r.isActive());
|
| - |
|
738 |
if (!exists) {
|
| - |
|
739 |
int nextSeq = beatRouteRepository.selectByBeatId(beatId).stream()
|
| - |
|
740 |
.filter(r -> r.getDayNumber() == sched.getDayNumber())
|
| - |
|
741 |
.mapToInt(BeatRoute::getSequenceOrder).max().orElse(-1) + 1;
|
| - |
|
742 |
BeatRoute br = new BeatRoute();
|
| - |
|
743 |
br.setBeatId(beatId);
|
| - |
|
744 |
br.setFofoId(d.getFofoId());
|
| - |
|
745 |
br.setDayNumber(sched.getDayNumber());
|
| - |
|
746 |
br.setSequenceOrder(nextSeq);
|
| - |
|
747 |
br.setActive(true);
|
| - |
|
748 |
beatRouteRepository.persist(br);
|
| - |
|
749 |
}
|
| - |
|
750 |
}
|
| - |
|
751 |
|
| - |
|
752 |
d.setStatus("RESCHEDULED");
|
| - |
|
753 |
d.setRescheduledToDate(date);
|
| - |
|
754 |
d.setActionBy(me.getId());
|
| - |
|
755 |
d.setUpdatedTimestamp(now);
|
| - |
|
756 |
beatDeferredVisitRepository.persist(d);
|
| - |
|
757 |
|
| - |
|
758 |
Map<String, Object> ok = new HashMap<>();
|
| - |
|
759 |
ok.put("status", true);
|
| - |
|
760 |
ok.put("message", (isLead ? "Lead" : "Partner") + " added to beat '" + beat.getName() + "' on " + dateStr);
|
| - |
|
761 |
return responseSender.ok(ok);
|
| - |
|
762 |
}
|
| - |
|
763 |
|
| - |
|
764 |
// authUserId -> dtr.users id (via shared email), memoized in the passed cache.
|
| - |
|
765 |
// Used by the reschedule action to create the new PENDING location_tracking row.
|
| - |
|
766 |
private Integer resolveDtrId(int authUserId, Map<Integer, Integer> cache) {
|
| - |
|
767 |
if (cache.containsKey(authUserId)) return cache.get(authUserId);
|
| - |
|
768 |
Integer dtrId = null;
|
| - |
|
769 |
try {
|
| - |
|
770 |
AuthUser au = authRepository.selectById(authUserId);
|
| - |
|
771 |
if (au != null && au.getEmailId() != null) {
|
| - |
|
772 |
com.spice.profitmandi.dao.entity.dtr.User u = userRepositoryAuto.selectByEmailId(au.getEmailId());
|
| - |
|
773 |
if (u != null) dtrId = u.getId();
|
| - |
|
774 |
}
|
| - |
|
775 |
} catch (Exception ignored) {
|
| - |
|
776 |
}
|
| - |
|
777 |
cache.put(authUserId, dtrId);
|
| - |
|
778 |
return dtrId;
|
| - |
|
779 |
}
|
| - |
|
780 |
|
| - |
|
781 |
// For an auth user: fofoId -> earliest upcoming (>= today) scheduled date where
|
| - |
|
782 |
// an active beat's route still includes that partner (the "Next Scheduled" hint).
|
| - |
|
783 |
private Map<Integer, LocalDate> computeFutureCover(int authUserId) {
|
| - |
|
784 |
LocalDate today = LocalDate.now();
|
| - |
|
785 |
Map<Integer, LocalDate> cover = new HashMap<>();
|
| - |
|
786 |
for (Beat b : beatRepository.selectActiveByAuthUserId(authUserId)) {
|
| - |
|
787 |
LocalDate earliest = null;
|
| - |
|
788 |
for (BeatSchedule s : beatScheduleRepository.selectByBeatId(b.getId())) {
|
| - |
|
789 |
LocalDate dt = s.getStartDate();
|
| - |
|
790 |
if (dt != null && dt.getYear() != 9999 && !dt.isBefore(today)) {
|
| - |
|
791 |
if (earliest == null || dt.isBefore(earliest)) earliest = dt;
|
| - |
|
792 |
}
|
| - |
|
793 |
}
|
| - |
|
794 |
if (earliest == null) continue;
|
| - |
|
795 |
for (BeatRoute rt : beatRouteRepository.selectByBeatId(b.getId())) {
|
| - |
|
796 |
if (!rt.isActive()) continue;
|
| - |
|
797 |
LocalDate cur = cover.get(rt.getFofoId());
|
| - |
|
798 |
if (cur == null || earliest.isBefore(cur)) cover.put(rt.getFofoId(), earliest);
|
| - |
|
799 |
}
|
| - |
|
800 |
}
|
| - |
|
801 |
return cover;
|
| - |
|
802 |
}
|
| - |
|
803 |
|
| 458 |
@GetMapping(value = "/beatPlanWindow")
|
804 |
@GetMapping(value = "/beatPlanWindow")
|
| 459 |
public String beatPlanWindow(HttpServletRequest request, Model model) throws ProfitMandiBusinessException {
|
805 |
public String beatPlanWindow(HttpServletRequest request, Model model) throws ProfitMandiBusinessException {
|
| 460 |
model.addAttribute("escalationTypes", visibleLevelsFor(request));
|
806 |
model.addAttribute("escalationTypes", visibleLevelsFor(request));
|
| 461 |
return "beat-plan-window";
|
807 |
return "beat-plan-window";
|
| 462 |
}
|
808 |
}
|