Subversion Repositories SmartDukaan

Rev

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

Rev 36814 Rev 36821
Line 600... Line 600...
600
		try {
600
		try {
601
			toDate = LocalDate.parse(toDateStr);
601
			toDate = LocalDate.parse(toDateStr);
602
		} catch (Exception e) {
602
		} catch (Exception e) {
603
			return responseSender.badRequest("Invalid toDate (yyyy-MM-dd)");
603
			return responseSender.badRequest("Invalid toDate (yyyy-MM-dd)");
604
		}
604
		}
-
 
605
		// Reschedule must land on a STRICTLY future date — today is already in progress.
-
 
606
		if (!toDate.isAfter(LocalDate.now()))
605
		if (toDate.isBefore(LocalDate.now())) return responseSender.badRequest("Reschedule date cannot be in the past");
607
			return responseSender.badRequest("Reschedule date must be in the future (today is already in progress).");
606
 
608
 
607
		if ("reschedule_beat".equalsIgnoreCase(action)) {
609
		if ("reschedule_beat".equalsIgnoreCase(action)) {
608
			boolean hasBeat = beatRepository.selectActiveByAuthUserId(d.getAuthUserId()).stream()
610
			boolean hasBeat = beatRepository.selectActiveByAuthUserId(d.getAuthUserId()).stream()
609
					.flatMap(b -> beatScheduleRepository.selectByBeatId(b.getId()).stream())
611
					.flatMap(b -> beatScheduleRepository.selectByBeatId(b.getId()).stream())
610
					.anyMatch(s -> s.getStartDate() != null && s.getStartDate().equals(toDate));
612
					.anyMatch(s -> s.getStartDate() != null && s.getStartDate().equals(toDate));
Line 705... Line 707...
705
		try {
707
		try {
706
			date = LocalDate.parse(dateStr);
708
			date = LocalDate.parse(dateStr);
707
		} catch (Exception e) {
709
		} catch (Exception e) {
708
			return responseSender.badRequest("Invalid date (yyyy-MM-dd)");
710
			return responseSender.badRequest("Invalid date (yyyy-MM-dd)");
709
		}
711
		}
-
 
712
		// Drop-onto-beat must land on a STRICTLY future date — today is already in progress.
-
 
713
		if (!date.isAfter(LocalDate.now()))
710
		if (date.isBefore(LocalDate.now())) return responseSender.badRequest("Pick an upcoming date");
714
			return responseSender.badRequest("Pick a future date — today is already in progress.");
711
 
715
 
712
		BeatDeferredVisit d = beatDeferredVisitRepository.selectById(deferredId);
716
		BeatDeferredVisit d = beatDeferredVisitRepository.selectById(deferredId);
713
		if (d == null) return responseSender.badRequest("Deferred record not found");
717
		if (d == null) return responseSender.badRequest("Deferred record not found");
714
 
718
 
715
		// A deferral can only move FORWARD — never onto the day it was deferred or earlier.
719
		// A deferral can only move FORWARD — never onto the day it was deferred or earlier.
Line 1000... Line 1004...
1000
			@RequestParam String planData) throws Exception {
1004
			@RequestParam String planData) throws Exception {
1001
 
1005
 
1002
		Beat beat = beatRepository.selectById(beatId);
1006
		Beat beat = beatRepository.selectById(beatId);
1003
		if (beat == null) return responseSender.badRequest("Beat not found");
1007
		if (beat == null) return responseSender.badRequest("Beat not found");
1004
 
1008
 
-
 
1009
		// Refuse edits while the beat is "live" — i.e., a schedule row covers today.
-
 
1010
		// The salesperson is already on-route; mutating the plan mid-day would
-
 
1011
		// break their location_tracking timeline. Edits resume tomorrow.
-
 
1012
		LocalDate today = LocalDate.now();
-
 
1013
		boolean runningToday = beatScheduleRepository.selectByBeatId(beatId).stream()
-
 
1014
				.anyMatch(s -> s.getStartDate() != null && s.getStartDate().equals(today));
-
 
1015
		if (runningToday) {
-
 
1016
			return responseSender.badRequest(
-
 
1017
					"This beat is scheduled to run today — editing is locked until tomorrow. "
-
 
1018
							+ "Use the Deferred panel for today's adjustments.");
-
 
1019
		}
-
 
1020
 
1005
		Gson gson = new Gson();
1021
		Gson gson = new Gson();
1006
		Type type = new TypeToken<Map<String, Object>>() {
1022
		Type type = new TypeToken<Map<String, Object>>() {
1007
		}.getType();
1023
		}.getType();
1008
		Map<String, Object> plan = gson.fromJson(planData, type);
1024
		Map<String, Object> plan = gson.fromJson(planData, type);
1009
 
1025
 
Line 1259... Line 1275...
1259
		model.addAttribute("escalationTypes", visibleLevelsFor(request));
1275
		model.addAttribute("escalationTypes", visibleLevelsFor(request));
1260
		return "beat-plan-base-location";
1276
		return "beat-plan-base-location";
1261
	}
1277
	}
1262
 
1278
 
1263
	// Tabular JSON: one row per (beat, scheduled date) in [startDate, endDate].
1279
	// Tabular JSON: one row per (beat, scheduled date) in [startDate, endDate].
-
 
1280
	// Hierarchy-scoped: a manager sees only their downline + self; super-admins see all.
-
 
1281
	// Optional categoryId + escalationType further narrow the listing to users at
-
 
1282
	// a specific level (e.g. all Sales L1 in scope).
1264
	@GetMapping(value = "/beatPlan/scheduledList")
1283
	@GetMapping(value = "/beatPlan/scheduledList")
1265
	public ResponseEntity<?> scheduledList(
1284
	public ResponseEntity<?> scheduledList(
-
 
1285
			HttpServletRequest request,
1266
			@RequestParam(required = false) String startDate,
1286
			@RequestParam(required = false) String startDate,
1267
			@RequestParam(required = false) String endDate) {
1287
			@RequestParam(required = false) String endDate,
-
 
1288
			@RequestParam(required = false) Integer categoryId,
-
 
1289
			@RequestParam(required = false) com.spice.profitmandi.dao.enumuration.cs.EscalationType escalationType) throws ProfitMandiBusinessException {
1268
 
1290
 
1269
		LocalDate start, end;
1291
		LocalDate start, end;
1270
		try {
1292
		try {
1271
			start = (startDate == null || startDate.isEmpty()) ? LocalDate.now() : LocalDate.parse(startDate);
1293
			start = (startDate == null || startDate.isEmpty()) ? LocalDate.now() : LocalDate.parse(startDate);
1272
			end = (endDate == null || endDate.isEmpty()) ? start.plusDays(7) : LocalDate.parse(endDate);
1294
			end = (endDate == null || endDate.isEmpty()) ? start.plusDays(7) : LocalDate.parse(endDate);
1273
		} catch (Exception e) {
1295
		} catch (Exception e) {
1274
			return responseSender.badRequest("Invalid date — expected yyyy-MM-dd");
1296
			return responseSender.badRequest("Invalid date — expected yyyy-MM-dd");
1275
		}
1297
		}
1276
 
1298
 
-
 
1299
		// ---- Scope: which auth users does the caller get to see ----
-
 
1300
		LoginDetails ld = cookiesProcessor.getCookiesObject(request);
-
 
1301
		AuthUser me = (ld != null) ? authRepository.selectByEmailOrMobile(ld.getEmailId()) : null;
-
 
1302
		Set<Integer> visible = null;
-
 
1303
		if (me != null && !isSuperAdmin(me)) {
-
 
1304
			visible = new HashSet<>(authService.getAllReportees(me.getId()));
-
 
1305
			visible.add(me.getId());
-
 
1306
		}
-
 
1307
		// If a (category, level) was picked, further restrict to that population.
-
 
1308
		Set<Integer> levelFilter = null;
-
 
1309
		if (categoryId != null && escalationType != null) {
-
 
1310
			levelFilter = csService.getAuthUserByCategoryId(categoryId, escalationType).stream()
-
 
1311
					.filter(AuthUser::getActive)
-
 
1312
					.map(AuthUser::getId)
-
 
1313
					.collect(java.util.stream.Collectors.toSet());
-
 
1314
		}
-
 
1315
 
1277
		List<com.spice.profitmandi.dao.model.BeatDayDetails> beats =
1316
		List<com.spice.profitmandi.dao.model.BeatDayDetails> beats =
1278
				beatPlanQueryService.getAllScheduledBeats(start, end);
1317
				beatPlanQueryService.getAllScheduledBeats(start, end);
1279
 
1318
 
-
 
1319
		final Set<Integer> visibleF = visible;
-
 
1320
		final Set<Integer> levelF = levelFilter;
-
 
1321
		beats = beats.stream()
-
 
1322
				.filter(b -> visibleF == null || visibleF.contains(b.getAuthUserId()))
-
 
1323
				.filter(b -> levelF == null || levelF.contains(b.getAuthUserId()))
-
 
1324
				.collect(java.util.stream.Collectors.toList());
-
 
1325
 
1280
		// Resolve user names in bulk
1326
		// Resolve user names in bulk
1281
		Set<Integer> userIds = beats.stream()
1327
		Set<Integer> userIds = beats.stream()
1282
				.map(com.spice.profitmandi.dao.model.BeatDayDetails::getAuthUserId)
1328
				.map(com.spice.profitmandi.dao.model.BeatDayDetails::getAuthUserId)
1283
				.collect(java.util.stream.Collectors.toSet());
1329
				.collect(java.util.stream.Collectors.toSet());
1284
		Map<Integer, AuthUser> userMap = new HashMap<>();
1330
		Map<Integer, AuthUser> userMap = new HashMap<>();
Line 1434... Line 1480...
1434
	// scheduled in a date range across all users. Each row has a View button that
1480
	// scheduled in a date range across all users. Each row has a View button that
1435
	// opens that user's calendar in a modal.
1481
	// opens that user's calendar in a modal.
1436
	@GetMapping(value = "/beatPlan/dayView")
1482
	@GetMapping(value = "/beatPlan/dayView")
1437
	public String beatPlanDayView(HttpServletRequest request, Model model) throws ProfitMandiBusinessException {
1483
	public String beatPlanDayView(HttpServletRequest request, Model model) throws ProfitMandiBusinessException {
1438
		model.addAttribute("escalationTypes", visibleLevelsFor(request));
1484
		model.addAttribute("escalationTypes", visibleLevelsFor(request));
-
 
1485
		// Matches /beatPlan/getAuthUsers and the Beat Report — default Sales category.
-
 
1486
		model.addAttribute("categoryId", com.spice.profitmandi.common.model.ProfitMandiConstants.TICKET_CATEGORY_SALES);
1439
		return "beat-plan-day-view";
1487
		return "beat-plan-day-view";
1440
	}
1488
	}
1441
 
1489
 
1442
	// Returns visits for a beat.
1490
	// Returns visits for a beat.
1443
	// - Partner stops (beat_route) belong to the beat template — always returned.
1491
	// - Partner stops (beat_route) belong to the beat template — always returned.