| Line 497... |
Line 497... |
| 497 |
@GetMapping(value = "/beatPlan/migrateStoreLatLng")
|
497 |
@GetMapping(value = "/beatPlan/migrateStoreLatLng")
|
| 498 |
public ResponseEntity<?> migrateStoreLatLng(
|
498 |
public ResponseEntity<?> migrateStoreLatLng(
|
| 499 |
@RequestParam(required = false, defaultValue = "false") boolean apply,
|
499 |
@RequestParam(required = false, defaultValue = "false") boolean apply,
|
| 500 |
@RequestParam(required = false, defaultValue = "5") double thresholdKm,
|
500 |
@RequestParam(required = false, defaultValue = "5") double thresholdKm,
|
| 501 |
@RequestParam(required = false, defaultValue = "0") int limit,
|
501 |
@RequestParam(required = false, defaultValue = "0") int limit,
|
| - |
|
502 |
@RequestParam(required = false, defaultValue = "0") int offset,
|
| 502 |
@RequestParam(required = false, defaultValue = "0") int offset) throws ProfitMandiBusinessException {
|
503 |
@RequestParam(required = false, defaultValue = "40") int maxSeconds) throws ProfitMandiBusinessException {
|
| 503 |
|
504 |
|
| 504 |
List<FofoStore> all = fofoStoreRepository.selectActiveStores();
|
505 |
List<FofoStore> all = fofoStoreRepository.selectActiveStores();
|
| 505 |
int totalAvailable = all.size();
|
506 |
int totalAvailable = all.size();
|
| 506 |
// offset + limit so this can be run in batches against large datasets
|
- |
|
| 507 |
int from = Math.max(0, Math.min(offset, totalAvailable));
|
507 |
int from = Math.max(0, Math.min(offset, totalAvailable));
|
| 508 |
int to = limit > 0 ? Math.min(from + limit, totalAvailable) : totalAvailable;
|
- |
|
| 509 |
List<FofoStore> stores = all.subList(from, to);
|
- |
|
| 510 |
|
508 |
|
| - |
|
509 |
// Hard cap (if limit given), else go to the end of the list.
|
| - |
|
510 |
int hardTo = limit > 0 ? Math.min(from + limit, totalAvailable) : totalAvailable;
|
| - |
|
511 |
|
| - |
|
512 |
// Time budget: stop processing once we approach the gateway timeout and
|
| - |
|
513 |
// return nextOffset so the caller can resume. Geocoding is the slow part
|
| - |
|
514 |
// (network/cache), so a fixed batch size could still time out on a cache-miss
|
| - |
|
515 |
// run — a wall-clock budget is safer. maxSeconds defaults to 40 (< typical 60s gateway).
|
| - |
|
516 |
long deadlineMs = System.currentTimeMillis() + Math.max(5, maxSeconds) * 1000L;
|
| - |
|
517 |
|
| - |
|
518 |
List<FofoStore> stores = all.subList(from, hardTo);
|
| 511 |
List<Integer> ids = stores.stream().map(FofoStore::getId).collect(Collectors.toList());
|
519 |
List<Integer> ids = stores.stream().map(FofoStore::getId).collect(Collectors.toList());
|
| 512 |
Map<Integer, CustomRetailer> retailerMap = retailerService.getFofoRetailers(ids);
|
520 |
Map<Integer, CustomRetailer> retailerMap = retailerService.getFofoRetailers(ids);
|
| 513 |
|
521 |
|
| 514 |
int total = stores.size();
|
522 |
int total = 0; // stores actually processed this call
|
| 515 |
int updated = 0, kept = 0, noAddress = 0, noGeocode = 0, errored = 0;
|
523 |
int updated = 0, kept = 0, noAddress = 0, noGeocode = 0, errored = 0;
|
| - |
|
524 |
boolean stoppedOnTime = false;
|
| - |
|
525 |
int nextIndex = from; // absolute index of next unprocessed store
|
| 516 |
List<Map<String, Object>> changes = new ArrayList<>();
|
526 |
List<Map<String, Object>> changes = new ArrayList<>();
|
| 517 |
|
527 |
|
| 518 |
for (FofoStore store : stores) {
|
528 |
for (FofoStore store : stores) {
|
| - |
|
529 |
// Stop before doing more slow geocoding work if we've spent our budget.
|
| - |
|
530 |
if (System.currentTimeMillis() >= deadlineMs) {
|
| - |
|
531 |
stoppedOnTime = true;
|
| - |
|
532 |
break;
|
| - |
|
533 |
}
|
| - |
|
534 |
total++;
|
| - |
|
535 |
nextIndex++;
|
| 519 |
try {
|
536 |
try {
|
| 520 |
CustomRetailer retailer = retailerMap.get(store.getId());
|
537 |
CustomRetailer retailer = retailerMap.get(store.getId());
|
| 521 |
if (retailer == null || retailer.getAddress() == null) {
|
538 |
if (retailer == null || retailer.getAddress() == null) {
|
| 522 |
noAddress++;
|
539 |
noAddress++;
|
| 523 |
continue;
|
540 |
continue;
|
| Line 556... |
Line 573... |
| 556 |
|
573 |
|
| 557 |
if (shouldUpdate) {
|
574 |
if (shouldUpdate) {
|
| 558 |
if (apply) {
|
575 |
if (apply) {
|
| 559 |
store.setLatitude(String.valueOf(coords[0]));
|
576 |
store.setLatitude(String.valueOf(coords[0]));
|
| 560 |
store.setLongitude(String.valueOf(coords[1]));
|
577 |
store.setLongitude(String.valueOf(coords[1]));
|
| - |
|
578 |
store.setLatLngUpdatedTimestamp(LocalDateTime.now());
|
| 561 |
fofoStoreRepository.persist(store);
|
579 |
fofoStoreRepository.persist(store);
|
| 562 |
}
|
580 |
}
|
| 563 |
updated++;
|
581 |
updated++;
|
| 564 |
Map<String, Object> ch = new HashMap<>();
|
582 |
Map<String, Object> ch = new HashMap<>();
|
| 565 |
ch.put("storeId", store.getId());
|
583 |
ch.put("storeId", store.getId());
|
| Line 570... |
Line 588... |
| 570 |
ch.put("newLng", coords[1]);
|
588 |
ch.put("newLng", coords[1]);
|
| 571 |
ch.put("distKm", distKm >= 0 ? Math.round(distKm * 10.0) / 10.0 : null);
|
589 |
ch.put("distKm", distKm >= 0 ? Math.round(distKm * 10.0) / 10.0 : null);
|
| 572 |
ch.put("reason", reason);
|
590 |
ch.put("reason", reason);
|
| 573 |
changes.add(ch);
|
591 |
changes.add(ch);
|
| 574 |
} else {
|
592 |
} else {
|
| - |
|
593 |
// Verified-kept: lat/lng was already within threshold. Still stamp it
|
| - |
|
594 |
// so "processed vs pending" can be told from lat_lng_updated_timestamp.
|
| - |
|
595 |
if (apply) {
|
| - |
|
596 |
store.setLatLngUpdatedTimestamp(LocalDateTime.now());
|
| - |
|
597 |
fofoStoreRepository.persist(store);
|
| - |
|
598 |
}
|
| 575 |
kept++;
|
599 |
kept++;
|
| 576 |
}
|
600 |
}
|
| 577 |
} catch (Exception e) {
|
601 |
} catch (Exception e) {
|
| 578 |
errored++;
|
602 |
errored++;
|
| 579 |
LOGGER.warn("Geocode/migrate failed for fofoId={}: {}", store.getId(), e.getMessage());
|
603 |
LOGGER.warn("Geocode/migrate failed for fofoId={}: {}", store.getId(), e.getMessage());
|
| Line 581... |
Line 605... |
| 581 |
}
|
605 |
}
|
| 582 |
|
606 |
|
| 583 |
Map<String, Object> result = new HashMap<>();
|
607 |
Map<String, Object> result = new HashMap<>();
|
| 584 |
result.put("mode", apply ? "APPLIED" : "DRY RUN — pass &apply=true to actually update");
|
608 |
result.put("mode", apply ? "APPLIED" : "DRY RUN — pass &apply=true to actually update");
|
| 585 |
result.put("thresholdKm", thresholdKm);
|
609 |
result.put("thresholdKm", thresholdKm);
|
| 586 |
result.put("totalAvailable", totalAvailable); // total active stores in DB
|
610 |
result.put("totalAvailable", totalAvailable); // total active stores in DB
|
| 587 |
result.put("offset", from);
|
611 |
result.put("offset", from);
|
| 588 |
result.put("processed", total); // stores processed this run
|
612 |
result.put("processed", total); // stores processed this call
|
| 589 |
result.put("nextOffset", to); // next offset to resume (or = totalAvailable when done)
|
613 |
result.put("nextOffset", nextIndex); // resume here next call
|
| 590 |
result.put("done", to >= totalAvailable);
|
614 |
result.put("done", nextIndex >= totalAvailable); // true when nothing left
|
| - |
|
615 |
result.put("stoppedOnTimeBudget", stoppedOnTime);// true if we paused for time, not because we finished
|
| 591 |
result.put("updated", updated);
|
616 |
result.put("updated", updated);
|
| 592 |
result.put("kept", kept);
|
617 |
result.put("kept", kept);
|
| 593 |
result.put("noAddress", noAddress);
|
618 |
result.put("noAddress", noAddress);
|
| 594 |
result.put("noGeocode", noGeocode);
|
619 |
result.put("noGeocode", noGeocode);
|
| 595 |
result.put("errored", errored);
|
620 |
result.put("errored", errored);
|