Subversion Repositories SmartDukaan

Rev

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

Rev 36670 Rev 36681
Line 457... Line 457...
457
		Map<String, Object> firstDay = days.get(0);
457
		Map<String, Object> firstDay = days.get(0);
458
		if (firstDay.get("startLocationName") != null)
458
		if (firstDay.get("startLocationName") != null)
459
			beat.setStartLocationName((String) firstDay.get("startLocationName"));
459
			beat.setStartLocationName((String) firstDay.get("startLocationName"));
460
		if (firstDay.get("startLatitude") != null) beat.setStartLatitude((String) firstDay.get("startLatitude"));
460
		if (firstDay.get("startLatitude") != null) beat.setStartLatitude((String) firstDay.get("startLatitude"));
461
		if (firstDay.get("startLongitude") != null) beat.setStartLongitude((String) firstDay.get("startLongitude"));
461
		if (firstDay.get("startLongitude") != null) beat.setStartLongitude((String) firstDay.get("startLongitude"));
462
		beat.setTotalDays(days.size());
-
 
463
 
462
 
-
 
463
		int oldTotalDays = beat.getTotalDays();
-
 
464
		int newTotalDays = days.size();
-
 
465
 
-
 
466
		// Hard rule: you cannot grow the number of days on an existing beat.
-
 
467
		// If you need more days, create a new beat. (Shrinking is allowed and
-
 
468
		// the schedules for dropped day numbers are cleaned below.)
-
 
469
		if (newTotalDays > oldTotalDays) {
-
 
470
			return responseSender.badRequest(
-
 
471
					"Cannot increase the number of days on an existing beat. "
-
 
472
							+ "Original: " + oldTotalDays + " day(s), tried: " + newTotalDays + " day(s). "
-
 
473
							+ "Please create a new beat for additional days.");
-
 
474
		}
-
 
475
		beat.setTotalDays(newTotalDays);
-
 
476
 
464
		// Replace routes (partner stops). Schedules stay intact.
477
		// Replace routes (partner stops). Schedules stay intact (except for
-
 
478
		// dayNumber > newTotalDays cleanup below if the beat shrank).
465
		beatRouteRepository.deleteByBeatId(beatId);
479
		beatRouteRepository.deleteByBeatId(beatId);
466
		// Collect lead IDs the user kept on the plan (for date-aware edit below)
480
		// Collect lead IDs the user kept on the plan
467
		Set<Integer> keptLeadIds = new HashSet<>();
481
		Set<Integer> keptLeadIds = new HashSet<>();
468
		for (int d = 0; d < days.size(); d++) {
482
		for (int d = 0; d < days.size(); d++) {
469
			Map<String, Object> day = days.get(d);
483
			Map<String, Object> day = days.get(d);
470
			int dayNumber = d + 1;
484
			int dayNumber = d + 1;
471
			List<Map<String, Object>> visits = (List<Map<String, Object>>) day.get("visits");
485
			List<Map<String, Object>> visits = (List<Map<String, Object>>) day.get("visits");
Line 485... Line 499...
485
				route.setActive(true);
499
				route.setActive(true);
486
				beatRouteRepository.persist(route);
500
				beatRouteRepository.persist(route);
487
			}
501
			}
488
		}
502
		}
489
 
503
 
-
 
504
		// If the beat shrank, drop schedule rows for day numbers that no longer exist
-
 
505
		if (newTotalDays < oldTotalDays) {
-
 
506
			List<BeatSchedule> currentSchedules = beatScheduleRepository.selectByBeatId(beatId);
-
 
507
			for (BeatSchedule s : currentSchedules) {
-
 
508
				if (s.getDayNumber() > newTotalDays) beatScheduleRepository.delete(s);
-
 
509
			}
-
 
510
		}
-
 
511
 
-
 
512
		// Process per-lead actions sent from the editor's removed-leads popup.
-
 
513
		// Each entry: {leadId, action: "cancel"|"reschedule", toDate?: "yyyy-MM-dd"}.
-
 
514
		// - cancel: mark the lead's current APPROVED row for this beat as CANCELLED.
490
		// Date-aware lead handling: if planDate is provided, remove lead_route rows
515
		// - reschedule: cancel here, then create a fresh APPROVED LeadRoute on
491
		// for that (beat, date) that the user removed in the editor.
516
		//   whichever beat this user has scheduled on toDate. If no beat exists
-
 
517
		//   on toDate, the whole update fails (so the caller can prompt again).
-
 
518
		int leadsCancelled = 0, leadsRescheduled = 0;
-
 
519
		List<String> leadFailures = new ArrayList<>();
-
 
520
		String removedLeadActionsJson = (String) plan.get("removedLeadActions");
-
 
521
		if (removedLeadActionsJson != null && !removedLeadActionsJson.isEmpty()) {
-
 
522
			Type listType = new TypeToken<List<Map<String, Object>>>() {
-
 
523
			}.getType();
-
 
524
			List<Map<String, Object>> actions = gson.fromJson(removedLeadActionsJson, listType);
-
 
525
 
-
 
526
			List<LeadRoute> beatLeads = leadRouteRepository.selectByBeatId(beatId);
-
 
527
 
-
 
528
			for (Map<String, Object> act : actions) {
-
 
529
				int leadId = ((Number) act.get("leadId")).intValue();
-
 
530
				String mode = (String) act.get("action");
-
 
531
 
-
 
532
				// Find this lead's most-recent APPROVED row on this beat
-
 
533
				LeadRoute current = beatLeads.stream()
-
 
534
						.filter(r -> r.getLeadId() == leadId && "APPROVED".equals(r.getStatus()))
-
 
535
						.findFirst().orElse(null);
-
 
536
				if (current == null) continue; // already removed/cancelled; nothing to do
-
 
537
 
-
 
538
				if ("reschedule".equalsIgnoreCase(mode)) {
492
		String planDateStr = (String) plan.get("planDate");
539
					String toDateStr = (String) act.get("toDate");
493
		if (planDateStr != null && !planDateStr.isEmpty()) {
540
					if (toDateStr == null || toDateStr.isEmpty()) {
-
 
541
						leadFailures.add("Lead " + leadId + ": reschedule date missing");
-
 
542
						continue;
494
			try {
543
					}
495
				LocalDate planDate = LocalDate.parse(planDateStr);
544
					LocalDate toDate = LocalDate.parse(toDateStr);
-
 
545
 
-
 
546
					// Find ANY beat this user has scheduled on toDate
-
 
547
					Beat targetBeat = null;
-
 
548
					Integer targetDayNumber = null;
496
				List<LeadRoute> existing = leadRouteRepository.selectByBeatId(beatId);
549
					List<Beat> userBeats = beatRepository.selectActiveByAuthUserId(beat.getAuthUserId());
497
				for (LeadRoute lr : existing) {
550
					for (Beat b : userBeats) {
-
 
551
						List<BeatSchedule> sl = beatScheduleRepository.selectByBeatId(b.getId());
498
					if (!"APPROVED".equals(lr.getStatus())) continue;
552
						for (BeatSchedule s : sl) {
499
					if (lr.getScheduleDate() == null || !lr.getScheduleDate().equals(planDate)) continue;
553
							if (s.getStartDate() != null && s.getStartDate().equals(toDate)) {
-
 
554
								targetBeat = b;
-
 
555
								targetDayNumber = s.getDayNumber();
-
 
556
								break;
-
 
557
							}
-
 
558
						}
500
					if (keptLeadIds.contains(lr.getLeadId())) continue;
559
						if (targetBeat != null) break;
-
 
560
					}
-
 
561
					if (targetBeat == null) {
-
 
562
						return responseSender.badRequest(
501
					// User removed this lead from the date — mark cancelled
563
								"No beat is scheduled for this user on " + toDateStr
-
 
564
										+ ". Pick a different date for lead " + leadId
-
 
565
										+ ", or choose Cancel for it.");
-
 
566
					}
-
 
567
 
-
 
568
					// Cancel the current attachment to this beat
502
					lr.setStatus("CANCELLED");
569
					current.setStatus("CANCELLED");
-
 
570
					current.setUpdatedTimestamp(LocalDateTime.now());
-
 
571
 
-
 
572
					// Create the new attachment on the target beat/date
-
 
573
					LeadRoute fresh = new LeadRoute();
-
 
574
					fresh.setBeatId(targetBeat.getId());
-
 
575
					fresh.setLeadId(leadId);
-
 
576
					fresh.setNearestStoreId(current.getNearestStoreId());
-
 
577
					fresh.setScheduleDate(toDate);
-
 
578
					fresh.setSequenceOrder(9999); // append; the planner can reorder
-
 
579
					fresh.setStatus("APPROVED");
-
 
580
					fresh.setRequestedBy(current.getRequestedBy());
-
 
581
					fresh.setApprovedBy(current.getApprovedBy());
-
 
582
					fresh.setApprovedTimestamp(LocalDateTime.now());
-
 
583
					fresh.setCreatedTimestamp(LocalDateTime.now());
503
					lr.setUpdatedTimestamp(LocalDateTime.now());
584
					fresh.setUpdatedTimestamp(LocalDateTime.now());
-
 
585
					leadRouteRepository.persist(fresh);
-
 
586
 
-
 
587
					LeadActivity la = new LeadActivity();
-
 
588
					la.setLeadId(leadId);
-
 
589
					la.setRemark("Rescheduled from beat '" + beat.getName() + "' to '"
-
 
590
							+ targetBeat.getName() + "' on " + toDateStr + " (day " + targetDayNumber + ")");
-
 
591
					la.setAuthId(0);
-
 
592
					la.setCreatedTimestamp(LocalDateTime.now());
-
 
593
					leadActivityRepositoryAuto.persist(la);
-
 
594
					leadsRescheduled++;
-
 
595
				} else {
504
					// activity log
596
					// cancel (default)
-
 
597
					current.setStatus("CANCELLED");
-
 
598
					current.setUpdatedTimestamp(LocalDateTime.now());
-
 
599
 
505
					LeadActivity a = new LeadActivity();
600
					LeadActivity la = new LeadActivity();
506
					a.setLeadId(lr.getLeadId());
601
					la.setLeadId(leadId);
507
					a.setRemark("Removed from beat '" + beat.getName() + "' on " + planDate);
602
					la.setRemark("Cancelled from beat '" + beat.getName() + "' during edit");
508
					a.setAuthId(0);
603
					la.setAuthId(0);
509
					a.setCreatedTimestamp(LocalDateTime.now());
604
					la.setCreatedTimestamp(LocalDateTime.now());
510
					leadActivityRepositoryAuto.persist(a);
605
					leadActivityRepositoryAuto.persist(la);
-
 
606
					leadsCancelled++;
511
				}
607
				}
512
			} catch (Exception e) {
-
 
513
				LOGGER.warn("Could not parse planDate '{}' — leads not adjusted", planDateStr);
-
 
514
			}
608
			}
515
		}
609
		}
516
 
610
 
517
		Map<String, Object> response = new HashMap<>();
611
		Map<String, Object> response = new HashMap<>();
518
		response.put("status", true);
612
		response.put("status", true);
519
		response.put("planGroupId", String.valueOf(beat.getId()));
613
		response.put("planGroupId", String.valueOf(beat.getId()));
-
 
614
		response.put("leadsCancelled", leadsCancelled);
-
 
615
		response.put("leadsRescheduled", leadsRescheduled);
-
 
616
		response.put("leadFailures", leadFailures);
520
		response.put("message", "Beat updated successfully");
617
		response.put("message", "Beat updated successfully"
-
 
618
				+ (leadsCancelled > 0 ? " (" + leadsCancelled + " lead(s) cancelled)" : "")
-
 
619
				+ (leadsRescheduled > 0 ? " (" + leadsRescheduled + " lead(s) rescheduled)" : ""));
521
		return responseSender.ok(response);
620
		return responseSender.ok(response);
522
	}
621
	}
523
 
622
 
-
 
623
	// Used by the edit-mode "removed leads" popup so the date picker can warn
-
 
624
	// upfront when the user picks a date that has no beat for them.
-
 
625
	@GetMapping(value = "/beatPlan/userBeatsOnDate")
-
 
626
	public ResponseEntity<?> userBeatsOnDate(
-
 
627
			@RequestParam int authUserId,
-
 
628
			@RequestParam String date) {
-
 
629
		LocalDate target;
-
 
630
		try {
-
 
631
			target = LocalDate.parse(date);
-
 
632
		} catch (Exception e) {
-
 
633
			return responseSender.badRequest("Invalid date");
-
 
634
		}
-
 
635
 
-
 
636
		List<Map<String, Object>> hits = new ArrayList<>();
-
 
637
		List<Beat> userBeats = beatRepository.selectActiveByAuthUserId(authUserId);
-
 
638
		for (Beat b : userBeats) {
-
 
639
			List<BeatSchedule> schedules = beatScheduleRepository.selectByBeatId(b.getId());
-
 
640
			for (BeatSchedule s : schedules) {
-
 
641
				if (s.getStartDate() != null && s.getStartDate().equals(target)) {
-
 
642
					Map<String, Object> m = new HashMap<>();
-
 
643
					m.put("beatId", b.getId());
-
 
644
					m.put("beatName", b.getName());
-
 
645
					m.put("dayNumber", s.getDayNumber());
-
 
646
					hits.add(m);
-
 
647
				}
-
 
648
			}
-
 
649
		}
-
 
650
		Map<String, Object> result = new HashMap<>();
-
 
651
		result.put("date", date);
-
 
652
		result.put("authUserId", authUserId);
-
 
653
		result.put("beats", hits);
-
 
654
		return responseSender.ok(result);
-
 
655
	}
-
 
656
 
524
	// ====================== DAY VIEW ======================
657
	// ====================== DAY VIEW ======================
525
	// Inline page (loaded into dashboard #main-content): tabular list of all beats
658
	// Inline page (loaded into dashboard #main-content): tabular list of all beats
526
	// scheduled in a date range across all users. Each row has a View button that
659
	// scheduled in a date range across all users. Each row has a View button that
527
	// opens that user's calendar in a modal.
660
	// opens that user's calendar in a modal.
528
	@GetMapping(value = "/beatPlan/dayView")
661
	@GetMapping(value = "/beatPlan/dayView")
Line 772... Line 905...
772
		});
905
		});
773
 
906
 
774
		return responseSender.ok(result);
907
		return responseSender.ok(result);
775
	}
908
	}
776
 
909
 
-
 
910
	// Returns the user's DEFAULT base location. Falls back to most-recent for
-
 
911
	// legacy users who pre-date the is_default column.
777
	@GetMapping(value = "/beatPlan/getBaseLocation")
912
	@GetMapping(value = "/beatPlan/getBaseLocation")
778
	public ResponseEntity<?> getBaseLocation(@RequestParam int authUserId) {
913
	public ResponseEntity<?> getBaseLocation(@RequestParam int authUserId) {
779
		AuthUserLocation baseLoc = authUserLocationRepository.selectLatestByAuthUserIdAndType(authUserId, "BASE");
914
		AuthUserLocation baseLoc = authUserLocationRepository.selectDefaultByAuthUserIdAndType(authUserId, "BASE");
780
		if (baseLoc == null) {
915
		if (baseLoc == null) {
781
			return responseSender.ok(new HashMap<>());
916
			return responseSender.ok(new HashMap<>());
782
		}
917
		}
783
		Map<String, Object> result = new HashMap<>();
918
		Map<String, Object> result = new HashMap<>();
784
		result.put("id", baseLoc.getId());
919
		result.put("id", baseLoc.getId());
785
		result.put("locationName", baseLoc.getLocationName());
920
		result.put("locationName", baseLoc.getLocationName());
786
		result.put("latitude", baseLoc.getLatitude());
921
		result.put("latitude", baseLoc.getLatitude());
787
		result.put("longitude", baseLoc.getLongitude());
922
		result.put("longitude", baseLoc.getLongitude());
788
		result.put("address", baseLoc.getAddress());
923
		result.put("address", baseLoc.getAddress());
-
 
924
		result.put("isDefault", baseLoc.isDefault());
-
 
925
		return responseSender.ok(result);
-
 
926
	}
-
 
927
 
-
 
928
	// Returns ALL BASE locations for a user, default first.
-
 
929
	@GetMapping(value = "/beatPlan/listBaseLocations")
-
 
930
	public ResponseEntity<?> listBaseLocations(@RequestParam int authUserId) {
-
 
931
		List<AuthUserLocation> all = authUserLocationRepository.selectAllByAuthUserIdAndType(authUserId, "BASE");
-
 
932
		// Default at the top, then by created desc (the repo already returns desc).
-
 
933
		all.sort((a, b) -> {
-
 
934
			if (a.isDefault() && !b.isDefault()) return -1;
-
 
935
			if (!a.isDefault() && b.isDefault()) return 1;
-
 
936
			return 0;
-
 
937
		});
-
 
938
		List<Map<String, Object>> rows = new ArrayList<>();
-
 
939
		for (AuthUserLocation l : all) {
-
 
940
			Map<String, Object> row = new HashMap<>();
-
 
941
			row.put("id", l.getId());
-
 
942
			row.put("locationName", l.getLocationName());
-
 
943
			row.put("latitude", l.getLatitude());
-
 
944
			row.put("longitude", l.getLongitude());
-
 
945
			row.put("address", l.getAddress());
-
 
946
			row.put("isDefault", l.isDefault());
-
 
947
			row.put("createdTimestamp", l.getCreatedTimestamp() != null ? l.getCreatedTimestamp().toString() : null);
-
 
948
			rows.add(row);
-
 
949
		}
-
 
950
		Map<String, Object> result = new HashMap<>();
-
 
951
		result.put("authUserId", authUserId);
-
 
952
		result.put("locations", rows);
-
 
953
		return responseSender.ok(result);
-
 
954
	}
-
 
955
 
-
 
956
	// Flip the default flag — set this id default, clear all others.
-
 
957
	@PostMapping(value = "/beatPlan/setDefaultBaseLocation")
-
 
958
	public ResponseEntity<?> setDefaultBaseLocation(
-
 
959
			HttpServletRequest request,
-
 
960
			@RequestParam int id) throws ProfitMandiBusinessException {
-
 
961
		LoginDetails ld = cookiesProcessor.getCookiesObject(request);
-
 
962
		AuthUser me = authRepository.selectByEmailOrMobile(ld.getEmailId());
-
 
963
		if (me == null) return responseSender.unauthorized("Not logged in");
-
 
964
		if (!isBaseLocationManager(me)) {
-
 
965
			return responseSender.forbidden("You are not authorized for this action. Only Sales L3 and above can manage base locations.");
-
 
966
		}
-
 
967
 
-
 
968
		AuthUserLocation target = authUserLocationRepository.selectById(id);
-
 
969
		if (target == null) return responseSender.badRequest("Location not found");
-
 
970
 
-
 
971
		List<AuthUserLocation> all = authUserLocationRepository.selectAllByAuthUserIdAndType(target.getAuthUserId(), "BASE");
-
 
972
		for (AuthUserLocation l : all) {
-
 
973
			boolean shouldBeDefault = (l.getId() == id);
-
 
974
			if (l.isDefault() != shouldBeDefault) {
-
 
975
				l.setDefault(shouldBeDefault);
-
 
976
				authUserLocationRepository.persist(l); // saveOrUpdate
-
 
977
			}
-
 
978
		}
-
 
979
 
-
 
980
		Map<String, Object> result = new HashMap<>();
-
 
981
		result.put("status", true);
-
 
982
		result.put("id", id);
-
 
983
		result.put("message", "Default base location updated");
789
		return responseSender.ok(result);
984
		return responseSender.ok(result);
790
	}
985
	}
791
 
986
 
-
 
987
	// Delete a base location. The DEFAULT one cannot be deleted — user must
-
 
988
	// first pick another row as default.
-
 
989
	@PostMapping(value = "/beatPlan/deleteBaseLocation")
-
 
990
	public ResponseEntity<?> deleteBaseLocation(
-
 
991
			HttpServletRequest request,
-
 
992
			@RequestParam int id) throws ProfitMandiBusinessException {
-
 
993
		LoginDetails ld = cookiesProcessor.getCookiesObject(request);
-
 
994
		AuthUser me = authRepository.selectByEmailOrMobile(ld.getEmailId());
-
 
995
		if (me == null) return responseSender.unauthorized("Not logged in");
-
 
996
		if (!isBaseLocationManager(me)) {
-
 
997
			return responseSender.forbidden("You are not authorized for this action. Only Sales L3 and above can manage base locations.");
-
 
998
		}
-
 
999
 
-
 
1000
		AuthUserLocation target = authUserLocationRepository.selectById(id);
-
 
1001
		if (target == null) return responseSender.badRequest("Location not found");
-
 
1002
		if (target.isDefault()) {
-
 
1003
			return responseSender.badRequest("Default base location cannot be removed. Set another location as default first.");
-
 
1004
		}
-
 
1005
 
-
 
1006
		authUserLocationRepository.delete(target);
-
 
1007
 
-
 
1008
		Map<String, Object> result = new HashMap<>();
-
 
1009
		result.put("status", true);
-
 
1010
		result.put("message", "Base location removed");
-
 
1011
		return responseSender.ok(result);
-
 
1012
	}
-
 
1013
 
-
 
1014
	// Shared permission check for base-location admin actions.
-
 
1015
	private boolean isBaseLocationManager(AuthUser me) {
-
 
1016
		Set<String> baseLocationAllowList = new HashSet<>(Arrays.asList(
-
 
1017
				"tarun.verma@smartdukaan.com"
-
 
1018
		));
-
 
1019
		String myEmail = me.getEmailId() != null ? me.getEmailId().toLowerCase() : "";
-
 
1020
		if (baseLocationAllowList.contains(myEmail)) return true;
-
 
1021
 
-
 
1022
		return csService.getAuthUserIds(
-
 
1023
						com.spice.profitmandi.common.model.ProfitMandiConstants.TICKET_CATEGORY_SALES,
-
 
1024
						Arrays.asList(EscalationType.L3, EscalationType.L4))
-
 
1025
				.stream().anyMatch(u -> u.getId() == me.getId());
-
 
1026
	}
-
 
1027
 
792
	@PostMapping(value = "/beatPlan/saveBaseLocation")
1028
	@PostMapping(value = "/beatPlan/saveBaseLocation")
793
	public ResponseEntity<?> saveBaseLocation(
1029
	public ResponseEntity<?> saveBaseLocation(
794
			@RequestParam int authUserId,
1030
			@RequestParam int authUserId,
795
			@RequestParam String locationName,
1031
			@RequestParam String locationName,
796
			@RequestParam String latitude,
1032
			@RequestParam String latitude,
Line 802... Line 1038...
802
		loc.setLocationName(locationName);
1038
		loc.setLocationName(locationName);
803
		loc.setLatitude(latitude);
1039
		loc.setLatitude(latitude);
804
		loc.setLongitude(longitude);
1040
		loc.setLongitude(longitude);
805
		loc.setAddress(address);
1041
		loc.setAddress(address);
806
		loc.setCreatedTimestamp(LocalDateTime.now());
1042
		loc.setCreatedTimestamp(LocalDateTime.now());
-
 
1043
 
-
 
1044
		// First BASE for this user → auto-default so every user always has one.
-
 
1045
		List<AuthUserLocation> existing = authUserLocationRepository.selectAllByAuthUserIdAndType(authUserId, "BASE");
-
 
1046
		boolean noExistingDefault = existing.stream().noneMatch(AuthUserLocation::isDefault);
-
 
1047
		loc.setDefault(existing.isEmpty() || noExistingDefault);
807
		authUserLocationRepository.persist(loc);
1048
		authUserLocationRepository.persist(loc);
808
 
1049
 
809
		Map<String, Object> result = new HashMap<>();
1050
		Map<String, Object> result = new HashMap<>();
810
		result.put("status", true);
1051
		result.put("status", true);
811
		result.put("id", loc.getId());
1052
		result.put("id", loc.getId());
-
 
1053
		result.put("isDefault", loc.isDefault());
812
		return responseSender.ok(result);
1054
		return responseSender.ok(result);
813
	}
1055
	}
814
 
1056
 
815
	@GetMapping(value = "/beatPlan/getPartners")
1057
	@GetMapping(value = "/beatPlan/getPartners")
816
	public ResponseEntity<?> getPartners(
1058
	public ResponseEntity<?> getPartners(
Line 994... Line 1236...
994
	@GetMapping(value = "/beatPlan/bulkUpload")
1236
	@GetMapping(value = "/beatPlan/bulkUpload")
995
	public String bulkUploadPage(HttpServletRequest request, Model model) {
1237
	public String bulkUploadPage(HttpServletRequest request, Model model) {
996
		return "beat-plan-bulk";
1238
		return "beat-plan-bulk";
997
	}
1239
	}
998
 
1240
 
-
 
1241
	// Adds a new base location for the user. Caller can request this new row
-
 
1242
	// becomes the default. If the user has NO base locations yet, the new row
-
 
1243
	// is auto-defaulted (so every user always has exactly one default).
999
	@PostMapping(value = "/beatPlan/updateBaseLocation")
1244
	@PostMapping(value = "/beatPlan/updateBaseLocation")
1000
	public ResponseEntity<?> updateBaseLocation(
1245
	public ResponseEntity<?> updateBaseLocation(
1001
			HttpServletRequest request,
1246
			HttpServletRequest request,
1002
			@RequestParam int authUserId,
1247
			@RequestParam int authUserId,
1003
			@RequestParam String locationName,
1248
			@RequestParam String locationName,
1004
			@RequestParam String latitude,
1249
			@RequestParam String latitude,
1005
			@RequestParam String longitude,
1250
			@RequestParam String longitude,
1006
			@RequestParam(required = false) String address) throws Exception {
1251
			@RequestParam(required = false) String address,
-
 
1252
			@RequestParam(required = false, defaultValue = "false") boolean isDefault) throws Exception {
1007
 
1253
 
1008
		LoginDetails ld = cookiesProcessor.getCookiesObject(request);
1254
		LoginDetails ld = cookiesProcessor.getCookiesObject(request);
1009
		AuthUser me = authRepository.selectByEmailOrMobile(ld.getEmailId());
1255
		AuthUser me = authRepository.selectByEmailOrMobile(ld.getEmailId());
1010
		if (me == null) return responseSender.unauthorized("Not logged in");
1256
		if (me == null) return responseSender.unauthorized("Not logged in");
1011
 
-
 
1012
		// Permission gate: Sales L3+ OR explicit allow-list
-
 
1013
		// Allow-list (lowercase emails) — people outside Sales L3/L4 who still need
-
 
1014
		// to manage base locations (e.g., Tarun Verma).
-
 
1015
		Set<String> baseLocationAllowList = new HashSet<>(Arrays.asList(
-
 
1016
				"tarun.verma@smartdukaan.com"
-
 
1017
		));
-
 
1018
 
-
 
1019
		String myEmail = me.getEmailId() != null ? me.getEmailId().toLowerCase() : "";
-
 
1020
		boolean isWhitelisted = baseLocationAllowList.contains(myEmail);
-
 
1021
 
-
 
1022
		boolean isSalesL3Plus = csService.getAuthUserIds(
-
 
1023
						com.spice.profitmandi.common.model.ProfitMandiConstants.TICKET_CATEGORY_SALES,
-
 
1024
						Arrays.asList(EscalationType.L3, EscalationType.L4))
-
 
1025
				.stream().anyMatch(u -> u.getId() == me.getId());
-
 
1026
 
-
 
1027
		if (!isSalesL3Plus && !isWhitelisted) {
1257
		if (!isBaseLocationManager(me)) {
1028
			return responseSender.forbidden("You are not authorized for this action. Only Sales L3 and above can update base location.");
1258
			return responseSender.forbidden("You are not authorized for this action. Only Sales L3 and above can update base location.");
1029
		}
1259
		}
1030
 
1260
 
-
 
1261
		List<AuthUserLocation> existing = authUserLocationRepository.selectAllByAuthUserIdAndType(authUserId, "BASE");
-
 
1262
		boolean noExistingDefault = existing.stream().noneMatch(AuthUserLocation::isDefault);
-
 
1263
		boolean makeDefault = isDefault || existing.isEmpty() || noExistingDefault;
-
 
1264
 
-
 
1265
		// If this new row becomes the default, clear any existing default.
-
 
1266
		if (makeDefault) {
-
 
1267
			for (AuthUserLocation e : existing) {
-
 
1268
				if (e.isDefault()) {
-
 
1269
					e.setDefault(false);
-
 
1270
					authUserLocationRepository.persist(e);
-
 
1271
				}
-
 
1272
			}
-
 
1273
		}
-
 
1274
 
1031
		AuthUserLocation loc = new AuthUserLocation();
1275
		AuthUserLocation loc = new AuthUserLocation();
1032
		loc.setAuthUserId(authUserId);
1276
		loc.setAuthUserId(authUserId);
1033
		loc.setLocationType("BASE");
1277
		loc.setLocationType("BASE");
1034
		loc.setLocationName(locationName);
1278
		loc.setLocationName(locationName);
1035
		loc.setLatitude(latitude);
1279
		loc.setLatitude(latitude);
1036
		loc.setLongitude(longitude);
1280
		loc.setLongitude(longitude);
1037
		loc.setAddress(address);
1281
		loc.setAddress(address);
-
 
1282
		loc.setDefault(makeDefault);
1038
		loc.setCreatedTimestamp(LocalDateTime.now());
1283
		loc.setCreatedTimestamp(LocalDateTime.now());
1039
		authUserLocationRepository.persist(loc);
1284
		authUserLocationRepository.persist(loc);
1040
 
1285
 
1041
		Map<String, Object> result = new HashMap<>();
1286
		Map<String, Object> result = new HashMap<>();
1042
		result.put("status", true);
1287
		result.put("status", true);
1043
		result.put("id", loc.getId());
1288
		result.put("id", loc.getId());
1044
		result.put("message", "Base location updated");
1289
		result.put("isDefault", loc.isDefault());
-
 
1290
		result.put("message", makeDefault ? "Base location added and set as default" : "Base location added");
1045
		return responseSender.ok(result);
1291
		return responseSender.ok(result);
1046
	}
1292
	}
1047
 
1293
 
1048
	@GetMapping(value = "/beatPlan/downloadTemplate")
1294
	@GetMapping(value = "/beatPlan/downloadTemplate")
1049
	public ResponseEntity<?> downloadTemplate() throws java.io.IOException {
1295
	public ResponseEntity<?> downloadTemplate() throws java.io.IOException {
Line 1235... Line 1481...
1235
					errors++;
1481
					errors++;
1236
					continue;
1482
					continue;
1237
				}
1483
				}
1238
 
1484
 
1239
				String beatColor = BEAT_COLORS[Math.abs(beatName.hashCode()) % BEAT_COLORS.length];
1485
				String beatColor = BEAT_COLORS[Math.abs(beatName.hashCode()) % BEAT_COLORS.length];
1240
				AuthUserLocation homeLoc = authUserLocationRepository.selectLatestByAuthUserIdAndType(authUserId, "BASE");
1486
				AuthUserLocation homeLoc = authUserLocationRepository.selectDefaultByAuthUserIdAndType(authUserId, "BASE");
1241
 
1487
 
1242
				Beat beat = new Beat();
1488
				Beat beat = new Beat();
1243
				beat.setName(beatName);
1489
				beat.setName(beatName);
1244
				beat.setAuthUserId(authUserId);
1490
				beat.setAuthUserId(authUserId);
1245
				beat.setBeatColor(beatColor);
1491
				beat.setBeatColor(beatColor);