Subversion Repositories SmartDukaan

Rev

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

Rev 37182 Rev 37248
Line 388... Line 388...
388
		Map<String, Object> result = new HashMap<>();
388
		Map<String, Object> result = new HashMap<>();
389
		result.put("dtrUserId", dtrUserId);
389
		result.put("dtrUserId", dtrUserId);
390
		result.put("authUserId", authUserId);
390
		result.put("authUserId", authUserId);
391
		result.put("userName", au.getFirstName() + " " + au.getLastName());
391
		result.put("userName", au.getFirstName() + " " + au.getLastName());
392
		result.put("parties", parties);
392
		result.put("parties", parties);
-
 
393
		result.put("agendaOptions", com.spice.profitmandi.dao.enumuration.dtr.VisitAgenda.labels());
-
 
394
		// Managers can't assign a visit while a scheduled beat's agenda is unfilled; the
-
 
395
		// modal uses this flag to show the warning and disable the assign action. (A
-
 
396
		// user with no beat that day is allowed — nothing to fill.)
-
 
397
		boolean agendaFilled = agendaAllowsAssign(authUserId, parsedDate != null ? parsedDate : LocalDate.now());
-
 
398
		result.put("agendaFilled", agendaFilled);
-
 
399
		result.put("agendaNotFilledMessage", AGENDA_NOT_FILLED_MSG);
393
		// Only non-auto agendas are manually assignable; auto ones surface via
400
		// Only non-auto agendas are manually assignable; auto ones surface via
394
		// openAgendas (chips) and are woven into task_name server-side on submit.
401
		// openAgendas (chips) and are woven into task_name server-side on submit.
395
		result.put("agendaOptions", AgendaType.manualAssignableLabels());
402
		result.put("agendaOptions", AgendaType.manualAssignableLabels());
396
		return responseSender.ok(result);
403
		return responseSender.ok(result);
397
	}
404
	}
Line 465... Line 472...
465
		// button on the UI isn't enough — block at the API too.
472
		// button on the UI isn't enough — block at the API too.
466
		if (!taskDate.equals(LocalDate.now())) {
473
		if (!taskDate.equals(LocalDate.now())) {
467
			return responseSender.badRequest("Visits can only be assigned for today's date (" + LocalDate.now() + ")");
474
			return responseSender.badRequest("Visits can only be assigned for today's date (" + LocalDate.now() + ")");
468
		}
475
		}
469
 
476
 
-
 
477
		// Block when the executive has a beat scheduled today but hasn't filled its
-
 
478
		// agenda yet (enforced here so it applies to every caller, not just the UI).
-
 
479
		if (!agendaAllowsAssign(authUserId, taskDate)) {
-
 
480
			return responseSender.badRequest(AGENDA_NOT_FILLED_MSG);
-
 
481
		}
-
 
482
 
470
		LocalDateTime now = LocalDateTime.now();
483
		LocalDateTime now = LocalDateTime.now();
471
		int createdCount = 0, updatedCount = 0;
484
		int createdCount = 0, updatedCount = 0;
472
 
485
 
473
		for (Map<String, Object> p : selected) {
486
		for (Map<String, Object> p : selected) {
474
			Integer fofoStoreId = ((Number) p.get("fofoStoreId")).intValue();
487
			Integer fofoStoreId = ((Number) p.get("fofoStoreId")).intValue();
Line 606... Line 619...
606
		result.put("status", true);
619
		result.put("status", true);
607
		result.put("instanceId", instanceId);
620
		result.put("instanceId", instanceId);
608
		return responseSender.ok(result);
621
		return responseSender.ok(result);
609
	}
622
	}
610
 
623
 
-
 
624
	// Warning shown (and enforced) when a manager tries to assign a visit before the
-
 
625
	// executive has filled their agenda for the day.
-
 
626
	static final String AGENDA_NOT_FILLED_MSG = "Agenda not filled yet you cant assign visit";
-
 
627
 
-
 
628
	// True when the agenda rule permits a manager to assign a visit to `authUserId`
-
 
629
	// on `date`: either the executive has no beat scheduled that day (nothing to
-
 
630
	// fill), or at least one of their scheduled beats has its agenda_filled_timestamp
-
 
631
	// set. ONLY a scheduled-but-unfilled agenda blocks assignment.
-
 
632
	private boolean agendaAllowsAssign(int authUserId, LocalDate date) {
-
 
633
		if (date == null) return true;
-
 
634
		List<Integer> beatIds = beatPlanQueryService.getAllScheduledBeats(date, date).stream()
-
 
635
				.filter(b -> b.getAuthUserId() == authUserId)
-
 
636
				.map(com.spice.profitmandi.dao.model.BeatDayDetails::getBeatId)
-
 
637
				.distinct().collect(Collectors.toList());
-
 
638
		if (beatIds.isEmpty()) return true;   // no beat scheduled → agenda rule doesn't apply
-
 
639
		for (BeatSchedule bs : beatScheduleRepository.selectByBeatIds(beatIds)) {
-
 
640
			if (bs.getStartDate() != null && bs.getStartDate().equals(date)
-
 
641
					&& bs.getAgendaFilledTimestamp() != null) {
-
 
642
				return true;
-
 
643
			}
-
 
644
		}
-
 
645
		return false;
-
 
646
	}
-
 
647
 
611
	// ====================== DEFERRED PARTNERS ======================
648
	// ====================== DEFERRED PARTNERS ======================
612
	// Heads review partners that weren't visited on their planned day and act on
649
	// Heads review partners that weren't visited on their planned day and act on
613
	// them. The deferral lifecycle lives in user.beat_deferred_visit (separate
650
	// them. The deferral lifecycle lives in user.beat_deferred_visit (separate
614
	// from the raw location_tracking event log). Detection = explicit
651
	// from the raw location_tracking event log). Detection = explicit
615
	// (mark_type='DEFERRED') + derived (planned beat_route minus completed visits).
652
	// (mark_type='DEFERRED') + derived (planned beat_route minus completed visits).
Line 1036... Line 1073...
1036
	// Shared, idempotent lead_route write. Returns true when a new row is created,
1073
	// Shared, idempotent lead_route write. Returns true when a new row is created,
1037
	// false when the lead is already on that (beat, date). Best-effort nudges the
1074
	// false when the lead is already on that (beat, date). Best-effort nudges the
1038
	// lead forward to BEAT_PLANNED. Callers are responsible for the beat-runs-on-date
1075
	// lead forward to BEAT_PLANNED. Callers are responsible for the beat-runs-on-date
1039
	// and future-date guards. Mirrors deferredAssignToBeat's lead branch.
1076
	// and future-date guards. Mirrors deferredAssignToBeat's lead branch.
1040
	private boolean writeLeadRoute(int beatId, int leadId, LocalDate date, int approverAuthId) {
1077
	private boolean writeLeadRoute(int beatId, int leadId, LocalDate date, int approverAuthId) {
-
 
1078
		return writeLeadRoute(beatId, leadId, date, approverAuthId, 9999);
-
 
1079
	}
-
 
1080
 
-
 
1081
	// As above, but with an explicit route position — used when building a Lead beat
-
 
1082
	// where the leads carry a real ordering (vs 9999 "append" for one-off attaches).
-
 
1083
	private boolean writeLeadRoute(int beatId, int leadId, LocalDate date, int approverAuthId, int sequenceOrder) {
1041
		boolean exists = leadRouteRepository.selectByBeatId(beatId).stream()
1084
		boolean exists = leadRouteRepository.selectByBeatId(beatId).stream()
1042
				.anyMatch(lr -> lr.getLeadId() == leadId
1085
				.anyMatch(lr -> lr.getLeadId() == leadId
1043
						&& date.equals(lr.getScheduleDate())
1086
						&& date.equals(lr.getScheduleDate())
1044
						&& !"CANCELLED".equals(lr.getStatus()));
1087
						&& !"CANCELLED".equals(lr.getStatus()));
1045
		if (exists) return false;
1088
		if (exists) return false;
Line 1047... Line 1090...
1047
		LocalDateTime now = LocalDateTime.now();
1090
		LocalDateTime now = LocalDateTime.now();
1048
		LeadRoute lr = new LeadRoute();
1091
		LeadRoute lr = new LeadRoute();
1049
		lr.setBeatId(beatId);
1092
		lr.setBeatId(beatId);
1050
		lr.setLeadId(leadId);
1093
		lr.setLeadId(leadId);
1051
		lr.setScheduleDate(date);
1094
		lr.setScheduleDate(date);
1052
		lr.setSequenceOrder(9999); // append; planner can reorder
1095
		lr.setSequenceOrder(sequenceOrder);
1053
		lr.setStatus("APPROVED");
1096
		lr.setStatus("APPROVED");
1054
		lr.setApprovedBy(approverAuthId);
1097
		lr.setApprovedBy(approverAuthId);
1055
		lr.setApprovedTimestamp(now);
1098
		lr.setApprovedTimestamp(now);
1056
		lr.setCreatedTimestamp(now);
1099
		lr.setCreatedTimestamp(now);
1057
		lr.setUpdatedTimestamp(now);
1100
		lr.setUpdatedTimestamp(now);
Line 2465... Line 2508...
2465
            // client so reports/dashboards don't have to recompute from lat/lng.
2508
            // client so reports/dashboards don't have to recompute from lat/lng.
2466
			List<Map<String, Object>> visits = (List<Map<String, Object>>) day.get("visits");
2509
			List<Map<String, Object>> visits = (List<Map<String, Object>>) day.get("visits");
2467
			if (visits != null) {
2510
			if (visits != null) {
2468
				for (int i = 0; i < visits.size(); i++) {
2511
				for (int i = 0; i < visits.size(); i++) {
2469
					Map<String, Object> visit = visits.get(i);
2512
					Map<String, Object> visit = visits.get(i);
-
 
2513
					// Lead stop → lead_route (separate table, keyed by scheduleDate).
-
 
2514
					// planDate is guaranteed non-null for lead visits (validated above).
-
 
2515
					if ("lead".equals(visit.get("type"))) {
-
 
2516
						int leadId = ((Number) visit.get("id")).intValue();
-
 
2517
						writeLeadRoute(beat.getId(), leadId, planDate, currentUser.getId(), i);
-
 
2518
						continue;
-
 
2519
					}
2470
					BeatRoute route = new BeatRoute();
2520
					BeatRoute route = new BeatRoute();
2471
					route.setBeatId(beat.getId());
2521
					route.setBeatId(beat.getId());
2472
					route.setFofoId(((Number) visit.get("id")).intValue());
2522
					route.setFofoId(((Number) visit.get("id")).intValue());
2473
					route.setVisitType("office".equals(visit.get("type"))
2523
					route.setVisitType("office".equals(visit.get("type"))
2474
							? com.spice.profitmandi.dao.enumuration.dtr.BeatVisitType.OFFICE
2524
							? com.spice.profitmandi.dao.enumuration.dtr.BeatVisitType.OFFICE