Subversion Repositories SmartDukaan

Rev

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

Rev 36618 Rev 36632
Line 104... Line 104...
104
				.collect(Collectors.toList());
104
				.collect(Collectors.toList());
105
 
105
 
106
		return responseSender.ok(result);
106
		return responseSender.ok(result);
107
	}
107
	}
108
 
108
 
-
 
109
	@GetMapping(value = "/beatPlan/getBeatVisits")
-
 
110
	public ResponseEntity<?> getBeatVisits(@RequestParam String planGroupId) {
-
 
111
		List<BeatPlan> visits = beatPlanRepository.selectByPlanGroupId(planGroupId);
-
 
112
		List<Map<String, Object>> result = visits.stream().map(v -> {
-
 
113
			Map<String, Object> map = new HashMap<>();
-
 
114
			map.put("fofoId", v.getFofoId());
-
 
115
			map.put("dayNumber", v.getDayNumber());
-
 
116
			map.put("sequenceOrder", v.getSequenceOrder());
-
 
117
			map.put("visitType", v.getVisitType());
-
 
118
			return map;
-
 
119
		}).collect(Collectors.toList());
-
 
120
		return responseSender.ok(result);
-
 
121
	}
-
 
122
 
109
	@GetMapping(value = "/beatPlan/getBaseLocation")
123
	@GetMapping(value = "/beatPlan/getBaseLocation")
110
	public ResponseEntity<?> getBaseLocation(@RequestParam int authUserId) {
124
	public ResponseEntity<?> getBaseLocation(@RequestParam int authUserId) {
111
		AuthUserLocation baseLoc = authUserLocationRepository.selectLatestByAuthUserIdAndType(authUserId, "BASE");
125
		AuthUserLocation baseLoc = authUserLocationRepository.selectLatestByAuthUserIdAndType(authUserId, "BASE");
112
		if (baseLoc == null) {
126
		if (baseLoc == null) {
113
			return responseSender.ok(new HashMap<>());
127
			return responseSender.ok(new HashMap<>());
Line 180... Line 194...
180
			partnerData.put("fofoId", store.getId());
194
			partnerData.put("fofoId", store.getId());
181
			partnerData.put("code", store.getCode());
195
			partnerData.put("code", store.getCode());
182
			partnerData.put("outletName", store.getOutletName());
196
			partnerData.put("outletName", store.getOutletName());
183
			partnerData.put("type", "partner");
197
			partnerData.put("type", "partner");
184
 
198
 
-
 
199
			String displayAddress = null;
185
			String addressStr = null;
200
			String geoAddress = null;
186
			if (retailer != null) {
201
			if (retailer != null) {
187
				partnerData.put("businessName", retailer.getBusinessName());
202
				partnerData.put("businessName", retailer.getBusinessName());
188
				if (retailer.getAddress() != null) {
203
				if (retailer.getAddress() != null) {
189
					addressStr = retailer.getAddress().getAddressString();
204
					displayAddress = retailer.getAddress().getAddressString();
190
					partnerData.put("address", addressStr);
205
					partnerData.put("address", displayAddress);
-
 
206
					// Build clean address for geocoding: city, state, pincode, India
-
 
207
					geoAddress = com.spice.profitmandi.service.GeocodingService.buildGeoAddress(
-
 
208
							retailer.getAddress().getLine1(),
-
 
209
							retailer.getAddress().getCity(),
-
 
210
							retailer.getAddress().getState(),
-
 
211
							retailer.getAddress().getPinCode());
191
				}
212
				}
192
			}
213
			}
193
 
214
 
194
			addressesToGeocode.add(addressStr);
215
			addressesToGeocode.add(geoAddress);
195
			partners.add(partnerData);
216
			partners.add(partnerData);
196
		}
217
		}
197
 
218
 
198
		// Geocode all addresses in parallel (cached eternally in Redis, so only first call hits Google)
219
		// Geocode all addresses in parallel (cached eternally in Redis, so only first call hits Google)
199
		java.util.concurrent.ExecutorService executor = java.util.concurrent.Executors.newFixedThreadPool(
220
		java.util.concurrent.ExecutorService executor = java.util.concurrent.Executors.newFixedThreadPool(
Line 360... Line 381...
360
		response.put("planGroupId", planGroupId);
381
		response.put("planGroupId", planGroupId);
361
		response.put("message", "Beat plan submitted successfully");
382
		response.put("message", "Beat plan submitted successfully");
362
		return responseSender.ok(response);
383
		return responseSender.ok(response);
363
	}
384
	}
364
 
385
 
-
 
386
	// ============ BULK UPLOAD ============
-
 
387
 
-
 
388
	@GetMapping(value = "/beatPlan/bulkUpload")
-
 
389
	public String bulkUploadPage(HttpServletRequest request, Model model) {
-
 
390
		return "beat-plan-bulk";
-
 
391
	}
-
 
392
 
-
 
393
	@GetMapping(value = "/beatPlan/downloadTemplate")
-
 
394
	public ResponseEntity<?> downloadTemplate() {
-
 
395
		String csv = "beat_name,auth_user_id,start_date,day_number,partner_codes\n";
-
 
396
		csv += "Jaipur East Route,280,2026-06-02,1,\"RJKAI1478,RJBUN1449,RJDEG1443\"\n";
-
 
397
		csv += ",280,,2,\"RJALR1362,RJBTR1388\"\n";
-
 
398
		csv += ",280,,3,\"RJRSD1518,RJSML356\"\n";
-
 
399
		csv += "Agra Circuit,145,2026-06-05,1,\"UPAGR101,UPAGR102\"\n";
-
 
400
 
-
 
401
		org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders();
-
 
402
		headers.add("Content-Disposition", "attachment; filename=beat_plan_template.csv");
-
 
403
		headers.add("Content-Type", "text/csv");
-
 
404
 
-
 
405
		return new ResponseEntity<>(csv, headers, org.springframework.http.HttpStatus.OK);
-
 
406
	}
-
 
407
 
-
 
408
	@PostMapping(value = "/beatPlan/bulkUploadProcess")
-
 
409
	public ResponseEntity<?> bulkUploadProcess(
-
 
410
			HttpServletRequest request,
-
 
411
			@RequestParam("file") org.springframework.web.multipart.MultipartFile file,
-
 
412
			@RequestParam(value = "includeSundays", defaultValue = "false") boolean includeSundays) throws Exception {
-
 
413
 
-
 
414
		LoginDetails loginDetails = cookiesProcessor.getCookiesObject(request);
-
 
415
		AuthUser currentUser = authRepository.selectByEmailOrMobile(loginDetails.getEmailId());
-
 
416
 
-
 
417
		java.io.Reader reader = new java.io.InputStreamReader(file.getInputStream());
-
 
418
		org.apache.commons.csv.CSVParser parser = new org.apache.commons.csv.CSVParser(reader,
-
 
419
				org.apache.commons.csv.CSVFormat.DEFAULT.withFirstRecordAsHeader().withTrim());
-
 
420
 
-
 
421
		// Each row = one day: beat_name, auth_user_id, start_date (only on day 1), day_number, partner_codes
-
 
422
		// First pass: collect all records, normalize beat_name
-
 
423
		List<org.apache.commons.csv.CSVRecord> allRecords = parser.getRecords();
-
 
424
		parser.close();
-
 
425
 
-
 
426
		// Track last beat_name per auth_user_id for blank name rows
-
 
427
		Map<String, String> lastBeatNameByUser = new HashMap<>();
-
 
428
 
-
 
429
		Map<String, List<org.apache.commons.csv.CSVRecord>> beatGroups = new LinkedHashMap<>();
-
 
430
		// Also store resolved beat names since CSVRecord is immutable
-
 
431
		Map<Long, String> resolvedBeatNames = new HashMap<>();
-
 
432
 
-
 
433
		for (org.apache.commons.csv.CSVRecord record : allRecords) {
-
 
434
			String authId = record.get("auth_user_id").trim();
-
 
435
			String rawName = record.get("beat_name").trim();
-
 
436
			// Normalize: collapse multiple spaces, trim
-
 
437
			String beatName = rawName.replaceAll("\\s+", " ").trim();
-
 
438
 
-
 
439
			// If blank, use last beat_name for this auth_user_id
-
 
440
			if (beatName.isEmpty()) {
-
 
441
				beatName = lastBeatNameByUser.getOrDefault(authId, "Beat");
-
 
442
			} else {
-
 
443
				lastBeatNameByUser.put(authId, beatName);
-
 
444
			}
-
 
445
 
-
 
446
			resolvedBeatNames.put(record.getRecordNumber(), beatName);
-
 
447
			String key = beatName + "|" + authId;
-
 
448
			beatGroups.computeIfAbsent(key, k -> new ArrayList<>()).add(record);
-
 
449
		}
-
 
450
 
-
 
451
		// Build FofoStore code → id lookup
-
 
452
		List<FofoStore> allStores = fofoStoreRepository.selectAll();
-
 
453
		Map<String, Integer> codeToId = new HashMap<>();
-
 
454
		for (FofoStore store : allStores) {
-
 
455
			codeToId.put(store.getCode(), store.getId());
-
 
456
		}
-
 
457
 
-
 
458
		// Load holidays for validation
-
 
459
		LocalDate holidayStart = LocalDate.now();
-
 
460
		LocalDate holidayEnd = holidayStart.plusMonths(6);
-
 
461
		List<PublicHolidays> holidays = publicHolidaysRepository.selectAllBetweenDates(holidayStart, holidayEnd);
-
 
462
		Set<LocalDate> holidayDates = holidays.stream().map(PublicHolidays::getDate).collect(Collectors.toSet());
-
 
463
 
-
 
464
		int beatsCreated = 0;
-
 
465
		int errors = 0;
-
 
466
		List<String> errorMessages = new ArrayList<>();
-
 
467
 
-
 
468
		for (Map.Entry<String, List<org.apache.commons.csv.CSVRecord>> entry : beatGroups.entrySet()) {
-
 
469
			try {
-
 
470
				String[] keyParts = entry.getKey().split("\\|");
-
 
471
				String beatName = keyParts[0];
-
 
472
				int authUserId = Integer.parseInt(keyParts[1]);
-
 
473
				List<org.apache.commons.csv.CSVRecord> rows = entry.getValue();
-
 
474
 
-
 
475
				// Sort rows by day_number
-
 
476
				rows.sort((a, b) -> Integer.parseInt(a.get("day_number").trim()) - Integer.parseInt(b.get("day_number").trim()));
-
 
477
 
-
 
478
				// Get start_date from first row
-
 
479
				String startDateStr = rows.get(0).get("start_date").trim();
-
 
480
				LocalDate startDate = null;
-
 
481
				if (!startDateStr.isEmpty()) {
-
 
482
					startDate = LocalDate.parse(startDateStr, DateTimeFormatter.ISO_DATE);
-
 
483
				}
-
 
484
 
-
 
485
				// Validate start date
-
 
486
				if (startDate != null && startDate.isBefore(LocalDate.now())) {
-
 
487
					errorMessages.add("Beat '" + beatName + "': start_date " + startDate + " is in the past. Skipped.");
-
 
488
					errors++;
-
 
489
					continue;
-
 
490
				}
-
 
491
 
-
 
492
				// Auto-calculate dates for each day, skipping Sundays and holidays
-
 
493
				int totalDays = rows.size();
-
 
494
				List<LocalDate> scheduleDates = new ArrayList<>();
-
 
495
				if (startDate != null) {
-
 
496
					LocalDate d = startDate;
-
 
497
					while (scheduleDates.size() < totalDays) {
-
 
498
						boolean isSunday = d.getDayOfWeek() == DayOfWeek.SUNDAY;
-
 
499
						boolean isHoliday = holidayDates.contains(d);
-
 
500
 
-
 
501
						if (isHoliday) {
-
 
502
							// Always skip holidays
-
 
503
							errorMessages.add("Beat '" + beatName + "': skipping " + d + " (Holiday)");
-
 
504
						} else if (isSunday && !includeSundays) {
-
 
505
							// Skip Sunday only if not including Sundays
-
 
506
							errorMessages.add("Beat '" + beatName + "': skipping " + d + " (Sunday)");
-
 
507
						} else {
-
 
508
							scheduleDates.add(d);
-
 
509
							if (isSunday) {
-
 
510
								errorMessages.add("Beat '" + beatName + "': including Sunday " + d);
-
 
511
							}
-
 
512
						}
-
 
513
						d = d.plusDays(1);
-
 
514
					}
-
 
515
				}
-
 
516
 
-
 
517
				String planGroupId = UUID.randomUUID().toString();
-
 
518
				String beatColor = BEAT_COLORS[Math.abs(planGroupId.hashCode()) % BEAT_COLORS.length];
-
 
519
 
-
 
520
				// Get home location for this auth user
-
 
521
				AuthUserLocation homeLoc = authUserLocationRepository.selectLatestByAuthUserIdAndType(authUserId, "BASE");
-
 
522
				String homeName = homeLoc != null ? homeLoc.getLocationName() : "Home";
-
 
523
				String homeLat = homeLoc != null ? homeLoc.getLatitude() : null;
-
 
524
				String homeLng = homeLoc != null ? homeLoc.getLongitude() : null;
-
 
525
 
-
 
526
				int totalRows = rows.size();
-
 
527
 
-
 
528
				for (int rowIdx = 0; rowIdx < totalRows; rowIdx++) {
-
 
529
					org.apache.commons.csv.CSVRecord row = rows.get(rowIdx);
-
 
530
					int dayNumber = Integer.parseInt(row.get("day_number").trim());
-
 
531
					boolean isFirstDay = (rowIdx == 0);
-
 
532
					boolean isLastDay = (rowIdx == totalRows - 1);
-
 
533
 
-
 
534
					LocalDate planDate = (rowIdx < scheduleDates.size()) ? scheduleDates.get(rowIdx) : null;
-
 
535
 
-
 
536
					// Save BeatPlanDay
-
 
537
					BeatPlanDay bpDay = new BeatPlanDay();
-
 
538
					bpDay.setPlanGroupId(planGroupId);
-
 
539
					bpDay.setAuthUserId(authUserId);
-
 
540
					bpDay.setDayNumber(dayNumber);
-
 
541
					bpDay.setPlanDate(planDate);
-
 
542
					bpDay.setBeatName(beatName);
-
 
543
					bpDay.setBeatColor(beatColor);
-
 
544
 
-
 
545
					// Start location: Day 1 starts from home, other days start from home too (each day starts fresh)
-
 
546
					bpDay.setStartLocationName(homeName);
-
 
547
					bpDay.setStartLatitude(homeLat);
-
 
548
					bpDay.setStartLongitude(homeLng);
-
 
549
 
-
 
550
					// End action: last day = HOME (return home), other days = DAYBREAK
-
 
551
					if (isLastDay) {
-
 
552
						bpDay.setEndAction("HOME");
-
 
553
					} else {
-
 
554
						bpDay.setEndAction("DAYBREAK");
-
 
555
					}
-
 
556
 
-
 
557
					bpDay.setCreatedBy(currentUser.getId());
-
 
558
					bpDay.setCreatedTimestamp(LocalDateTime.now());
-
 
559
					bpDay.setActive(true);
-
 
560
					beatPlanDayRepository.persist(bpDay);
-
 
561
 
-
 
562
					// Parse comma-separated partner codes
-
 
563
					String partnerCodesStr = row.get("partner_codes").trim();
-
 
564
					String[] partnerCodes = partnerCodesStr.split(",");
-
 
565
 
-
 
566
					for (int i = 0; i < partnerCodes.length; i++) {
-
 
567
						String partnerCode = partnerCodes[i].trim();
-
 
568
						if (partnerCode.isEmpty()) continue;
-
 
569
 
-
 
570
						Integer fofoId = codeToId.get(partnerCode);
-
 
571
						if (fofoId == null) {
-
 
572
							errorMessages.add("Partner code not found: " + partnerCode + " in beat '" + beatName + "'");
-
 
573
							errors++;
-
 
574
							continue;
-
 
575
						}
-
 
576
 
-
 
577
						BeatPlan bp = new BeatPlan();
-
 
578
						bp.setAuthUserId(authUserId);
-
 
579
						bp.setFofoId(fofoId);
-
 
580
						bp.setVisitType("partner");
-
 
581
						bp.setDayNumber(dayNumber);
-
 
582
						bp.setPlanGroupId(planGroupId);
-
 
583
						bp.setPlanDate(planDate);
-
 
584
						bp.setSequenceOrder(i);
-
 
585
						bp.setCreatedBy(currentUser.getId());
-
 
586
						bp.setCreatedTimestamp(LocalDateTime.now());
-
 
587
						bp.setUpdatedTimestamp(LocalDateTime.now());
-
 
588
						bp.setActive(true);
-
 
589
						beatPlanRepository.persist(bp);
-
 
590
					}
-
 
591
				}
-
 
592
				beatsCreated++;
-
 
593
 
-
 
594
				// Log scheduled dates
-
 
595
				if (!scheduleDates.isEmpty()) {
-
 
596
					errorMessages.add("Beat '" + beatName + "' for user " + authUserId + " scheduled on: " +
-
 
597
							scheduleDates.stream().map(LocalDate::toString).collect(Collectors.joining(", ")));
-
 
598
				}
-
 
599
 
-
 
600
			} catch (Exception e) {
-
 
601
				errors++;
-
 
602
				errorMessages.add("Error processing beat: " + entry.getKey() + " - " + e.getMessage());
-
 
603
				LOGGER.error("Bulk upload error for {}: {}", entry.getKey(), e.getMessage());
-
 
604
			}
-
 
605
		}
-
 
606
 
-
 
607
		Map<String, Object> response = new HashMap<>();
-
 
608
		response.put("status", true);
-
 
609
		response.put("beatsCreated", beatsCreated);
-
 
610
		response.put("errors", errors);
-
 
611
		response.put("errorMessages", errorMessages);
-
 
612
		return responseSender.ok(response);
-
 
613
	}
-
 
614
 
365
	// ============ CALENDAR ENDPOINTS ============
615
	// ============ CALENDAR ENDPOINTS ============
366
 
616
 
367
	@PostMapping(value = "/beatPlan/delete")
617
	@PostMapping(value = "/beatPlan/delete")
368
	public ResponseEntity<?> deleteBeat(@RequestParam String planGroupId) {
618
	public ResponseEntity<?> deleteBeat(@RequestParam String planGroupId) {
369
		beatPlanDayRepository.deleteByPlanGroupId(planGroupId);
619
		beatPlanDayRepository.deleteByPlanGroupId(planGroupId);
Line 391... Line 641...
391
			m.put("date", h.getDate().toString());
641
			m.put("date", h.getDate().toString());
392
			m.put("occasion", h.getOccasion());
642
			m.put("occasion", h.getOccasion());
393
			return m;
643
			return m;
394
		}).collect(Collectors.toList());
644
		}).collect(Collectors.toList());
395
 
645
 
396
		// Get beats for this month (includes null-date/unscheduled ones)
646
		// Get ALL active beats for this auth user (not just this month)
397
		List<BeatPlanDay> beatDays = beatPlanDayRepository.selectByAuthUserIdAndDateRange(authUserId, startDate, endDate);
647
		List<BeatPlanDay> allBeatDays = beatPlanDayRepository.selectByAuthUserIdAndDateRange(
-
 
648
				authUserId, LocalDate.of(2000, 1, 1), LocalDate.of(2099, 12, 31));
398
 
649
 
399
		// Group by planGroupId
650
		// Group by planGroupId
400
		Map<String, List<BeatPlanDay>> grouped = beatDays.stream()
651
		Map<String, List<BeatPlanDay>> grouped = allBeatDays.stream()
401
				.filter(d -> d.getPlanGroupId() != null)
652
				.filter(d -> d.getPlanGroupId() != null)
402
				.collect(Collectors.groupingBy(BeatPlanDay::getPlanGroupId));
653
				.collect(Collectors.groupingBy(BeatPlanDay::getPlanGroupId));
403
 
654
 
404
		LocalDate today = LocalDate.now();
655
		LocalDate today = LocalDate.now();
405
		List<Map<String, Object>> scheduledBeats = new ArrayList<>();
656
		List<Map<String, Object>> scheduledBeats = new ArrayList<>();