| Line 93... |
Line 93... |
| 93 |
|
93 |
|
| 94 |
@Autowired
|
94 |
@Autowired
|
| 95 |
private com.spice.profitmandi.dao.repository.dtr.LeadLiveLocationRepository leadLiveLocationRepositoryAuto;
|
95 |
private com.spice.profitmandi.dao.repository.dtr.LeadLiveLocationRepository leadLiveLocationRepositoryAuto;
|
| 96 |
@Autowired
|
96 |
@Autowired
|
| 97 |
private com.spice.profitmandi.dao.repository.dtr.LeadActivityRepository leadActivityRepositoryAuto;
|
97 |
private com.spice.profitmandi.dao.repository.dtr.LeadActivityRepository leadActivityRepositoryAuto;
|
| - |
|
98 |
@Autowired
|
| - |
|
99 |
private com.spice.profitmandi.dao.repository.dtr.UserRepository userRepositoryAuto;
|
| - |
|
100 |
@Autowired
|
| - |
|
101 |
private com.spice.profitmandi.common.web.client.RestClient restClientAuto;
|
| - |
|
102 |
@Autowired
|
| - |
|
103 |
private com.spice.profitmandi.dao.repository.auth.LocationTrackingRepository locationTrackingRepositoryAuto;
|
| 98 |
|
104 |
|
| 99 |
private static Double parseDoubleOrNull(String s) {
|
105 |
private static Double parseDoubleOrNull(String s) {
|
| 100 |
if (s == null || s.trim().isEmpty()) return null;
|
106 |
if (s == null || s.trim().isEmpty()) return null;
|
| 101 |
try {
|
107 |
try {
|
| 102 |
return Double.parseDouble(s.trim());
|
108 |
return Double.parseDouble(s.trim());
|
| Line 114... |
Line 120... |
| 114 |
* Math.sin(dLng / 2) * Math.sin(dLng / 2);
|
120 |
* Math.sin(dLng / 2) * Math.sin(dLng / 2);
|
| 115 |
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));
|
| 116 |
return R * c;
|
122 |
return R * c;
|
| 117 |
}
|
123 |
}
|
| 118 |
|
124 |
|
| - |
|
125 |
// ====================== ASSIGN VISIT ======================
|
| - |
|
126 |
// 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
|
| - |
|
128 |
// /profitmandi-web/v2/beat-tracking/batch endpoint.
|
| - |
|
129 |
|
| - |
|
130 |
// List of parties (stores) assigned to this auth user + their dtr.users.id
|
| - |
|
131 |
@GetMapping(value = "/beatPlan/assignVisit/parties")
|
| - |
|
132 |
public ResponseEntity<?> assignVisitParties(@RequestParam int authUserId) throws Exception {
|
| - |
|
133 |
AuthUser au = authRepository.selectById(authUserId);
|
| - |
|
134 |
if (au == null) return responseSender.badRequest("Auth user not found");
|
| - |
|
135 |
|
| - |
|
136 |
// Map auth_user → dtr.users via email
|
| - |
|
137 |
Integer dtrUserId = null;
|
| - |
|
138 |
try {
|
| - |
|
139 |
com.spice.profitmandi.dao.entity.dtr.User dtrUser =
|
| - |
|
140 |
userRepositoryAuto.selectByEmailId(au.getEmailId());
|
| - |
|
141 |
if (dtrUser != null) dtrUserId = dtrUser.getId();
|
| - |
|
142 |
} catch (Exception ignored) {
|
| - |
|
143 |
}
|
| - |
|
144 |
|
| - |
|
145 |
Map<Integer, List<Integer>> mapping = csService.getAuthUserIdPartnerIdMapping();
|
| - |
|
146 |
List<Integer> fofoIds = mapping.get(authUserId);
|
| - |
|
147 |
|
| - |
|
148 |
List<Map<String, Object>> parties = new ArrayList<>();
|
| - |
|
149 |
if (fofoIds != null && !fofoIds.isEmpty()) {
|
| - |
|
150 |
List<FofoStore> stores = fofoStoreRepository.selectByRetailerIds(fofoIds);
|
| - |
|
151 |
Map<Integer, CustomRetailer> retailerMap = retailerService.getFofoRetailers(fofoIds);
|
| - |
|
152 |
for (FofoStore store : stores) {
|
| - |
|
153 |
if (!store.isActive() || store.isClosed()) continue;
|
| - |
|
154 |
CustomRetailer retailer = retailerMap.get(store.getId());
|
| - |
|
155 |
Map<String, Object> p = new HashMap<>();
|
| - |
|
156 |
p.put("fofoStoreId", store.getId());
|
| - |
|
157 |
p.put("code", store.getCode());
|
| - |
|
158 |
p.put("outletName", store.getOutletName() != null ? store.getOutletName()
|
| - |
|
159 |
: (retailer != null ? retailer.getBusinessName() : "Store #" + store.getId()));
|
| - |
|
160 |
p.put("latitude", store.getLatitude());
|
| - |
|
161 |
p.put("longitude", store.getLongitude());
|
| - |
|
162 |
p.put("city", retailer != null && retailer.getAddress() != null ? retailer.getAddress().getCity() : null);
|
| - |
|
163 |
parties.add(p);
|
| - |
|
164 |
}
|
| - |
|
165 |
// Sort by code for stable display
|
| - |
|
166 |
parties.sort((a, b) -> String.valueOf(a.get("code")).compareToIgnoreCase(String.valueOf(b.get("code"))));
|
| - |
|
167 |
}
|
| - |
|
168 |
|
| - |
|
169 |
Map<String, Object> result = new HashMap<>();
|
| - |
|
170 |
result.put("dtrUserId", dtrUserId);
|
| - |
|
171 |
result.put("authUserId", authUserId);
|
| - |
|
172 |
result.put("userName", au.getFirstName() + " " + au.getLastName());
|
| - |
|
173 |
result.put("parties", parties);
|
| - |
|
174 |
return responseSender.ok(result);
|
| - |
|
175 |
}
|
| - |
|
176 |
|
| - |
|
177 |
// Submit assignment — accepts a JSON body, builds the v2 payload, posts it
|
| - |
|
178 |
@PostMapping(value = "/beatPlan/assignVisit/submit")
|
| - |
|
179 |
public ResponseEntity<?> assignVisitSubmit(
|
| - |
|
180 |
HttpServletRequest request,
|
| - |
|
181 |
@org.springframework.web.bind.annotation.RequestBody Map<String, Object> body) throws Exception {
|
| - |
|
182 |
|
| - |
|
183 |
Integer authUserId = body.get("authUserId") != null ? ((Number) body.get("authUserId")).intValue() : null;
|
| - |
|
184 |
String planDate = (String) body.get("planDate");
|
| - |
|
185 |
List<Map<String, Object>> selected = (List<Map<String, Object>>) body.get("parties");
|
| - |
|
186 |
if (authUserId == null || planDate == null || selected == null || selected.isEmpty()) {
|
| - |
|
187 |
return responseSender.badRequest("authUserId, planDate and parties are required");
|
| - |
|
188 |
}
|
| - |
|
189 |
|
| - |
|
190 |
AuthUser au = authRepository.selectById(authUserId);
|
| - |
|
191 |
if (au == null) return responseSender.badRequest("Auth user not found");
|
| - |
|
192 |
|
| - |
|
193 |
// Map auth → dtr.users.id (this is the userId the v2 endpoint expects)
|
| - |
|
194 |
com.spice.profitmandi.dao.entity.dtr.User dtrUser;
|
| - |
|
195 |
try {
|
| - |
|
196 |
dtrUser = userRepositoryAuto.selectByEmailId(au.getEmailId());
|
| - |
|
197 |
} catch (Exception e) {
|
| - |
|
198 |
return responseSender.badRequest("Failed to look up dtr.users for this auth user");
|
| - |
|
199 |
}
|
| - |
|
200 |
if (dtrUser == null) {
|
| - |
|
201 |
return responseSender.badRequest("No dtr.users record found for auth user " + authUserId);
|
| - |
|
202 |
}
|
| - |
|
203 |
int dtrUserId = dtrUser.getId();
|
| - |
|
204 |
|
| - |
|
205 |
// Persist directly via the shared DAO — mirrors BeatTrackingController.createBatch
|
| - |
|
206 |
// in profitmandi-web. We can't autowire a controller across WARs, but
|
| - |
|
207 |
// LocationTrackingRepository lives in profitmandi-dao and is shared.
|
| - |
|
208 |
LocalDate taskDate;
|
| - |
|
209 |
try {
|
| - |
|
210 |
taskDate = LocalDate.parse(planDate);
|
| - |
|
211 |
} catch (Exception e) {
|
| - |
|
212 |
return responseSender.badRequest("Invalid planDate (expected yyyy-MM-dd): " + planDate);
|
| - |
|
213 |
}
|
| - |
|
214 |
|
| - |
|
215 |
LocalDateTime now = LocalDateTime.now();
|
| - |
|
216 |
List<com.spice.profitmandi.dao.entity.auth.LocationTracking> created = new ArrayList<>();
|
| - |
|
217 |
|
| - |
|
218 |
for (Map<String, Object> p : selected) {
|
| - |
|
219 |
Integer fofoStoreId = ((Number) p.get("fofoStoreId")).intValue();
|
| - |
|
220 |
String outletName = (String) p.get("outletName");
|
| - |
|
221 |
String lat = (String) p.get("latitude");
|
| - |
|
222 |
String lng = (String) p.get("longitude");
|
| - |
|
223 |
String agenda = (String) p.get("agenda");
|
| - |
|
224 |
if (agenda != null) agenda = agenda.trim();
|
| - |
|
225 |
if (agenda == null || agenda.isEmpty()) agenda = "Visit";
|
| - |
|
226 |
|
| - |
|
227 |
String visitLocation = (lat != null && lng != null && !lat.isEmpty() && !lng.isEmpty())
|
| - |
|
228 |
? (lat + "," + lng) : "0.0000,0.0000";
|
| - |
|
229 |
|
| - |
|
230 |
String displayName = (outletName != null && !outletName.isEmpty()) ? outletName : ("Store #" + fofoStoreId);
|
| - |
|
231 |
|
| - |
|
232 |
com.spice.profitmandi.dao.entity.auth.LocationTracking row =
|
| - |
|
233 |
new com.spice.profitmandi.dao.entity.auth.LocationTracking();
|
| - |
|
234 |
row.setUserId(dtrUserId);
|
| - |
|
235 |
row.setDeviceId("0");
|
| - |
|
236 |
row.setTaskId(fofoStoreId);
|
| - |
|
237 |
row.setTaskDate(taskDate);
|
| - |
|
238 |
row.setTaskName(agenda + " | " + displayName);
|
| - |
|
239 |
row.setTaskType("franchisee-visit");
|
| - |
|
240 |
row.setMarkType("PENDING");
|
| - |
|
241 |
row.setAddress("");
|
| - |
|
242 |
row.setVisitLocation(visitLocation);
|
| - |
|
243 |
row.setCheckInLatLng("0.0000,0.0000");
|
| - |
|
244 |
row.setCheckOutLatLng("0.0000,0.0000");
|
| - |
|
245 |
row.setCheckInTime(java.time.LocalTime.MIDNIGHT);
|
| - |
|
246 |
row.setCheckOutTime(java.time.LocalTime.MIDNIGHT);
|
| - |
|
247 |
row.setTransitTime(java.time.LocalTime.MIDNIGHT);
|
| - |
|
248 |
row.setTimeSpent(java.time.LocalTime.MIDNIGHT);
|
| - |
|
249 |
row.setEstimatedTime(java.time.LocalTime.MIDNIGHT);
|
| - |
|
250 |
row.setSessionStartTime(java.time.LocalTime.MIDNIGHT);
|
| - |
|
251 |
row.setSessionEndTime(java.time.LocalTime.MIDNIGHT);
|
| - |
|
252 |
row.setTotalDistance("0.0");
|
| - |
|
253 |
row.setStatus(false);
|
| - |
|
254 |
row.setCreatedTimestamp(now);
|
| - |
|
255 |
row.setUpdatedTimestamp(now);
|
| - |
|
256 |
|
| - |
|
257 |
// Do NOT try/catch this — if persist throws, let it propagate so
|
| - |
|
258 |
// @Transactional(rollbackFor = Throwable.class) rolls back cleanly.
|
| - |
|
259 |
// Catching here lets Hibernate try to commit a broken session, which
|
| - |
|
260 |
// then surfaces the misleading "null id in entry" flush error and hides
|
| - |
|
261 |
// the real root cause (constraint violation, bad value, etc.).
|
| - |
|
262 |
locationTrackingRepositoryAuto.persist(row);
|
| - |
|
263 |
created.add(row);
|
| - |
|
264 |
}
|
| - |
|
265 |
LOGGER.info("assignVisit persisted {} location_tracking rows for dtrUserId={}", created.size(), dtrUserId);
|
| - |
|
266 |
|
| - |
|
267 |
Map<String, Object> result = new HashMap<>();
|
| - |
|
268 |
result.put("status", true);
|
| - |
|
269 |
result.put("assignedCount", created.size());
|
| - |
|
270 |
result.put("dtrUserId", dtrUserId);
|
| - |
|
271 |
result.put("message", created.size() + " visit tasks assigned to " + au.getFirstName() + " " + au.getLastName());
|
| - |
|
272 |
return responseSender.ok(result);
|
| - |
|
273 |
}
|
| - |
|
274 |
|
| 119 |
// ====================== BASE LOCATION MANAGEMENT ======================
|
275 |
// ====================== BASE LOCATION MANAGEMENT ======================
|
| 120 |
// Inline page that lets Sales L3+ pick a user and set their base (home)
|
276 |
// Inline page that lets Sales L3+ pick a user and set their base (home)
|
| 121 |
// location via map. Reads use the existing /beatPlan/getBaseLocation, writes
|
277 |
// location via map. Reads use the existing /beatPlan/getBaseLocation, writes
|
| 122 |
// go through the L3+-guarded endpoint below.
|
278 |
// go through the L3+-guarded endpoint below.
|
| 123 |
@GetMapping(value = "/beatPlan/baseLocationPage")
|
279 |
@GetMapping(value = "/beatPlan/baseLocationPage")
|