Subversion Repositories SmartDukaan

Rev

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

Rev 36698 Rev 36711
Line 120... Line 120...
120
				* Math.sin(dLng / 2) * Math.sin(dLng / 2);
120
				* Math.sin(dLng / 2) * Math.sin(dLng / 2);
121
		double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
121
		double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
122
		return R * c;
122
		return R * c;
123
	}
123
	}
124
 
124
 
-
 
125
    // Mirrors the JS recalcDay() formula. Used by schedule/repeat endpoints
-
 
126
    // which create fresh BeatSchedule rows — they need to fill totals from the
-
 
127
    // existing beat_route table, not from anything the client posted.
-
 
128
    // Returns {totalKm, totalMins}.
-
 
129
    private double[] computeDayTotals(int beatId, int dayNumber, String endAction) {
-
 
130
        Beat beat = beatRepository.selectById(beatId);
-
 
131
        if (beat == null) return new double[]{0d, 0d};
-
 
132
 
-
 
133
        List<BeatRoute> dayRoutes = beatRouteRepository.selectByBeatId(beatId).stream()
-
 
134
                .filter(r -> r.getDayNumber() == dayNumber)
-
 
135
                .sorted(java.util.Comparator.comparingInt(BeatRoute::getSequenceOrder))
-
 
136
                .collect(Collectors.toList());
-
 
137
        if (dayRoutes.isEmpty()) return new double[]{0d, 0d};
-
 
138
 
-
 
139
        List<Integer> fofoIds = dayRoutes.stream().map(BeatRoute::getFofoId).distinct().collect(Collectors.toList());
-
 
140
        Map<Integer, FofoStore> storeMap = new HashMap<>();
-
 
141
        try {
-
 
142
            for (FofoStore fs : fofoStoreRepository.selectByRetailerIds(fofoIds)) {
-
 
143
                storeMap.put(fs.getId(), fs);
-
 
144
            }
-
 
145
        } catch (Exception ignored) { /* fall through with empty map */ }
-
 
146
 
-
 
147
        double ROAD_FACTOR = 1.3;
-
 
148
        double AVG_SPEED = 30.0; // km/h
-
 
149
        int VISIT_MINS = 30;
-
 
150
 
-
 
151
        Double prevLat = parseDoubleOrNull(beat.getStartLatitude());
-
 
152
        Double prevLng = parseDoubleOrNull(beat.getStartLongitude());
-
 
153
 
-
 
154
        double totalKm = 0d;
-
 
155
        for (BeatRoute r : dayRoutes) {
-
 
156
            FofoStore fs = storeMap.get(r.getFofoId());
-
 
157
            if (fs == null) continue;
-
 
158
            Double curLat = parseDoubleOrNull(fs.getLatitude());
-
 
159
            Double curLng = parseDoubleOrNull(fs.getLongitude());
-
 
160
            if (prevLat != null && prevLng != null && curLat != null && curLng != null) {
-
 
161
                totalKm += haversineKm(prevLat, prevLng, curLat, curLng) * ROAD_FACTOR;
-
 
162
            }
-
 
163
            if (curLat != null && curLng != null) {
-
 
164
                prevLat = curLat;
-
 
165
                prevLng = curLng;
-
 
166
            }
-
 
167
        }
-
 
168
 
-
 
169
        // Return-home leg only when end_action='HOME'
-
 
170
        if ("HOME".equalsIgnoreCase(endAction)) {
-
 
171
            Double homeLat = parseDoubleOrNull(beat.getStartLatitude());
-
 
172
            Double homeLng = parseDoubleOrNull(beat.getStartLongitude());
-
 
173
            if (prevLat != null && prevLng != null && homeLat != null && homeLng != null) {
-
 
174
                totalKm += haversineKm(prevLat, prevLng, homeLat, homeLng) * ROAD_FACTOR;
-
 
175
            }
-
 
176
        }
-
 
177
 
-
 
178
        double totalMins = (totalKm / AVG_SPEED) * 60.0 + dayRoutes.size() * VISIT_MINS;
-
 
179
        return new double[]{Math.round(totalKm * 1000d) / 1000d, Math.round(totalMins)};
-
 
180
    }
-
 
181
 
125
	// ====================== ASSIGN VISIT ======================
182
	// ====================== ASSIGN VISIT ======================
126
	// Day View "Assign Visit" — lets an admin pick parties (stores) for a specific
183
	// Day View "Assign Visit" — lets an admin pick parties (stores) for a specific
127
	// auth user on a specific date and pushes them as visit tasks to the v2
184
	// auth user on a specific date and pushes them as visit tasks to the v2
128
	// /profitmandi-web/v2/beat-tracking/batch endpoint.
185
	// /profitmandi-web/v2/beat-tracking/batch endpoint.
129
 
186
 
Line 470... Line 527...
470
							+ "Please create a new beat for additional days.");
527
							+ "Please create a new beat for additional days.");
471
		}
528
		}
472
		beat.setTotalDays(newTotalDays);
529
		beat.setTotalDays(newTotalDays);
473
 
530
 
474
		// Replace routes (partner stops). Schedules stay intact (except for
531
		// Replace routes (partner stops). Schedules stay intact (except for
475
		// dayNumber > newTotalDays cleanup below if the beat shrank).
532
        // dayNumber > newTotalDays cleanup + total km/min refresh below).
476
		beatRouteRepository.deleteByBeatId(beatId);
533
		beatRouteRepository.deleteByBeatId(beatId);
477
		// Collect lead IDs the user kept on the plan
534
		// Collect lead IDs the user kept on the plan
478
		Set<Integer> keptLeadIds = new HashSet<>();
535
		Set<Integer> keptLeadIds = new HashSet<>();
479
		for (int d = 0; d < days.size(); d++) {
536
		for (int d = 0; d < days.size(); d++) {
480
			Map<String, Object> day = days.get(d);
537
			Map<String, Object> day = days.get(d);
Line 492... Line 549...
492
				route.setBeatId(beatId);
549
				route.setBeatId(beatId);
493
				route.setFofoId(((Number) v.get("id")).intValue());
550
				route.setFofoId(((Number) v.get("id")).intValue());
494
				route.setSequenceOrder(partnerSeq++);
551
				route.setSequenceOrder(partnerSeq++);
495
				route.setDayNumber(dayNumber);
552
				route.setDayNumber(dayNumber);
496
				route.setActive(true);
553
				route.setActive(true);
-
 
554
                if (v.get("distanceFromPrevKm") != null)
-
 
555
                    route.setDistanceFromPrevKm(((Number) v.get("distanceFromPrevKm")).doubleValue());
-
 
556
                if (v.get("timeFromPrevMins") != null)
-
 
557
                    route.setTimeFromPrevMins(((Number) v.get("timeFromPrevMins")).intValue());
497
				beatRouteRepository.persist(route);
558
				beatRouteRepository.persist(route);
498
			}
559
			}
499
		}
560
		}
500
 
561
 
501
		// If the beat shrank, drop schedule rows for day numbers that no longer exist
562
		// If the beat shrank, drop schedule rows for day numbers that no longer exist
Line 504... Line 565...
504
			for (BeatSchedule s : currentSchedules) {
565
			for (BeatSchedule s : currentSchedules) {
505
				if (s.getDayNumber() > newTotalDays) beatScheduleRepository.delete(s);
566
				if (s.getDayNumber() > newTotalDays) beatScheduleRepository.delete(s);
506
			}
567
			}
507
		}
568
		}
508
 
569
 
-
 
570
        // Refresh the day-level totals on every remaining schedule row so they
-
 
571
        // reflect the post-edit route. Previously updateBeat left these stale
-
 
572
        // (or NULL, for beats created before this fix), which is what the user
-
 
573
        // reported. Keyed by dayNumber so multi-instance beats all get updated.
-
 
574
        Map<Integer, Map<String, Object>> dayByNumber = new HashMap<>();
-
 
575
        for (int d = 0; d < days.size(); d++) {
-
 
576
            dayByNumber.put(d + 1, days.get(d));
-
 
577
        }
-
 
578
        List<BeatSchedule> allSchedules = beatScheduleRepository.selectByBeatId(beatId);
-
 
579
        for (BeatSchedule s : allSchedules) {
-
 
580
            Map<String, Object> day = dayByNumber.get(s.getDayNumber());
-
 
581
            if (day == null) continue;
-
 
582
            if (day.get("totalDistanceKm") != null)
-
 
583
                s.setTotalDistanceKm(((Number) day.get("totalDistanceKm")).doubleValue());
-
 
584
            if (day.get("totalTimeMins") != null)
-
 
585
                s.setTotalTimeMins(((Number) day.get("totalTimeMins")).intValue());
-
 
586
        }
-
 
587
 
509
		// Process per-lead actions sent from the editor's removed-leads popup.
588
		// Process per-lead actions sent from the editor's removed-leads popup.
510
		// Each entry: {leadId, action: "cancel"|"reschedule", toDate?: "yyyy-MM-dd"}.
589
		// Each entry: {leadId, action: "cancel"|"reschedule", toDate?: "yyyy-MM-dd"}.
511
		// - cancel: mark the lead's current APPROVED row for this beat as CANCELLED.
590
		// - cancel: mark the lead's current APPROVED row for this beat as CANCELLED.
512
		// - reschedule: cancel here, then create a fresh APPROVED LeadRoute on
591
		// - reschedule: cancel here, then create a fresh APPROVED LeadRoute on
513
		//   whichever beat this user has scheduled on toDate. If no beat exists
592
		//   whichever beat this user has scheduled on toDate. If no beat exists
Line 1264... Line 1343...
1264
			if (day.get("totalTimeMins") != null)
1343
			if (day.get("totalTimeMins") != null)
1265
				schedule.setTotalTimeMins(((Number) day.get("totalTimeMins")).intValue());
1344
				schedule.setTotalTimeMins(((Number) day.get("totalTimeMins")).intValue());
1266
			schedule.setCreatedTimestamp(LocalDateTime.now());
1345
			schedule.setCreatedTimestamp(LocalDateTime.now());
1267
			beatScheduleRepository.persist(schedule);
1346
			beatScheduleRepository.persist(schedule);
1268
 
1347
 
1269
			// Routes (stops)
1348
            // Routes (stops) — also persist per-leg distance/time supplied by the
-
 
1349
            // client so reports/dashboards don't have to recompute from lat/lng.
1270
			List<Map<String, Object>> visits = (List<Map<String, Object>>) day.get("visits");
1350
			List<Map<String, Object>> visits = (List<Map<String, Object>>) day.get("visits");
1271
			if (visits != null) {
1351
			if (visits != null) {
1272
				for (int i = 0; i < visits.size(); i++) {
1352
				for (int i = 0; i < visits.size(); i++) {
1273
					Map<String, Object> visit = visits.get(i);
1353
					Map<String, Object> visit = visits.get(i);
1274
					BeatRoute route = new BeatRoute();
1354
					BeatRoute route = new BeatRoute();
1275
					route.setBeatId(beat.getId());
1355
					route.setBeatId(beat.getId());
1276
					route.setFofoId(((Number) visit.get("id")).intValue());
1356
					route.setFofoId(((Number) visit.get("id")).intValue());
1277
					route.setSequenceOrder(i);
1357
					route.setSequenceOrder(i);
1278
					route.setDayNumber(dayNumber);
1358
					route.setDayNumber(dayNumber);
1279
					route.setActive(true);
1359
					route.setActive(true);
-
 
1360
                    if (visit.get("distanceFromPrevKm") != null)
-
 
1361
                        route.setDistanceFromPrevKm(((Number) visit.get("distanceFromPrevKm")).doubleValue());
-
 
1362
                    if (visit.get("timeFromPrevMins") != null)
-
 
1363
                        route.setTimeFromPrevMins(((Number) visit.get("timeFromPrevMins")).intValue());
1280
					beatRouteRepository.persist(route);
1364
					beatRouteRepository.persist(route);
1281
				}
1365
				}
1282
			}
1366
			}
1283
		}
1367
		}
1284
 
1368
 
Line 1812... Line 1896...
1812
				dayInfo.put("dayNumber", s.getDayNumber());
1896
				dayInfo.put("dayNumber", s.getDayNumber());
1813
				boolean isUnscheduled = s.getStartDate().getYear() == 9999;
1897
				boolean isUnscheduled = s.getStartDate().getYear() == 9999;
1814
				dayInfo.put("planDate", isUnscheduled ? null : s.getStartDate().toString());
1898
				dayInfo.put("planDate", isUnscheduled ? null : s.getStartDate().toString());
1815
				dayInfo.put("totalKm", s.getTotalDistanceKm());
1899
				dayInfo.put("totalKm", s.getTotalDistanceKm());
1816
				dayInfo.put("totalMins", s.getTotalTimeMins());
1900
				dayInfo.put("totalMins", s.getTotalTimeMins());
-
 
1901
                // endAction tells the planner whether to draw the return-to-home line
-
 
1902
                // for this day (HOME) or end at the last stop (DAYBREAK).
-
 
1903
                dayInfo.put("endAction", s.getEndAction());
1817
				long visitCount = routes.stream().filter(r -> r.getDayNumber() == s.getDayNumber()).count();
1904
				long visitCount = routes.stream().filter(r -> r.getDayNumber() == s.getDayNumber()).count();
1818
				dayInfo.put("visitCount", (int) visitCount);
1905
				dayInfo.put("visitCount", (int) visitCount);
1819
				dayInfoList.add(dayInfo);
1906
				dayInfoList.add(dayInfo);
1820
			}
1907
			}
1821
			if (schedules.isEmpty()) {
1908
			if (schedules.isEmpty()) {
Line 1870... Line 1957...
1870
 
1957
 
1871
		// Delete old schedules and create new
1958
		// Delete old schedules and create new
1872
		beatScheduleRepository.deleteByBeatId(beatId);
1959
		beatScheduleRepository.deleteByBeatId(beatId);
1873
		LocalDate schEndDate = dateList.isEmpty() ? null : LocalDate.parse(dateList.get(dateList.size() - 1));
1960
		LocalDate schEndDate = dateList.isEmpty() ? null : LocalDate.parse(dateList.get(dateList.size() - 1));
1874
		for (int i = 0; i < dateList.size() && i < beat.getTotalDays(); i++) {
1961
		for (int i = 0; i < dateList.size() && i < beat.getTotalDays(); i++) {
-
 
1962
            int dayNumber = i + 1;
-
 
1963
            String endAction = (i == dateList.size() - 1) ? "HOME" : "DAYBREAK";
1875
			BeatSchedule schedule = new BeatSchedule();
1964
			BeatSchedule schedule = new BeatSchedule();
1876
			schedule.setBeatId(beatId);
1965
			schedule.setBeatId(beatId);
1877
			schedule.setStartDate(LocalDate.parse(dateList.get(i)));
1966
			schedule.setStartDate(LocalDate.parse(dateList.get(i)));
1878
			schedule.setEndDate(schEndDate);
1967
			schedule.setEndDate(schEndDate);
1879
			schedule.setDayNumber(i + 1);
1968
            schedule.setDayNumber(dayNumber);
-
 
1969
            schedule.setEndAction(endAction);
-
 
1970
            // Fill total_distance_km / total_time_mins from beat_route so the new
1880
			schedule.setEndAction(i == dateList.size() - 1 ? "HOME" : "DAYBREAK");
1971
            // schedule row isn't NULL (this was the bug — these were left unset).
-
 
1972
            double[] totals = computeDayTotals(beatId, dayNumber, endAction);
-
 
1973
            schedule.setTotalDistanceKm(totals[0]);
-
 
1974
            schedule.setTotalTimeMins((int) totals[1]);
1881
			schedule.setCreatedTimestamp(LocalDateTime.now());
1975
			schedule.setCreatedTimestamp(LocalDateTime.now());
1882
			beatScheduleRepository.persist(schedule);
1976
			beatScheduleRepository.persist(schedule);
1883
		}
1977
		}
1884
 
1978
 
1885
		Map<String, Object> response = new HashMap<>();
1979
		Map<String, Object> response = new HashMap<>();
Line 1910... Line 2004...
1910
			if (s.getStartDate() != null && s.getStartDate().getYear() == 9999) {
2004
			if (s.getStartDate() != null && s.getStartDate().getYear() == 9999) {
1911
				beatScheduleRepository.delete(s);
2005
				beatScheduleRepository.delete(s);
1912
			}
2006
			}
1913
		}
2007
		}
1914
 
2008
 
1915
		// Add new real-date schedule rows for the existing beat
2009
        // Add new real-date schedule rows for the existing beat — fill totals
-
 
2010
        // from beat_route so total_distance_km / total_time_mins aren't NULL.
1916
		LocalDate repeatEndDate = dateList.isEmpty() ? null : LocalDate.parse(dateList.get(dateList.size() - 1));
2011
		LocalDate repeatEndDate = dateList.isEmpty() ? null : LocalDate.parse(dateList.get(dateList.size() - 1));
1917
		for (int i = 0; i < dateList.size(); i++) {
2012
		for (int i = 0; i < dateList.size(); i++) {
-
 
2013
            int dayNumber = i + 1;
-
 
2014
            String endAction = (i == dateList.size() - 1) ? "HOME" : "DAYBREAK";
1918
			BeatSchedule schedule = new BeatSchedule();
2015
			BeatSchedule schedule = new BeatSchedule();
1919
			schedule.setBeatId(beatId);
2016
			schedule.setBeatId(beatId);
1920
			schedule.setStartDate(LocalDate.parse(dateList.get(i)));
2017
			schedule.setStartDate(LocalDate.parse(dateList.get(i)));
1921
			schedule.setEndDate(repeatEndDate);
2018
			schedule.setEndDate(repeatEndDate);
1922
			schedule.setDayNumber(i + 1);
2019
            schedule.setDayNumber(dayNumber);
-
 
2020
            schedule.setEndAction(endAction);
1923
			schedule.setEndAction(i == dateList.size() - 1 ? "HOME" : "DAYBREAK");
2021
            double[] totals = computeDayTotals(beatId, dayNumber, endAction);
-
 
2022
            schedule.setTotalDistanceKm(totals[0]);
-
 
2023
            schedule.setTotalTimeMins((int) totals[1]);
1924
			schedule.setCreatedTimestamp(LocalDateTime.now());
2024
			schedule.setCreatedTimestamp(LocalDateTime.now());
1925
			beatScheduleRepository.persist(schedule);
2025
			beatScheduleRepository.persist(schedule);
1926
		}
2026
		}
1927
 
2027
 
1928
		Map<String, Object> response = new HashMap<>();
2028
		Map<String, Object> response = new HashMap<>();