| Line 512... |
Line 512... |
| 512 |
setSalesTargetsForDate(LocalDate.now());
|
512 |
setSalesTargetsForDate(LocalDate.now());
|
| 513 |
}
|
513 |
}
|
| 514 |
|
514 |
|
| 515 |
@Override
|
515 |
@Override
|
| 516 |
public void setSalesTargetsForDate(LocalDate date) {
|
516 |
public void setSalesTargetsForDate(LocalDate date) {
|
| 517 |
LocalDateTime dayStart = date.atStartOfDay();
|
517 |
// Sales target for a person = (own_monthly_target − MTD_own_achievement) /
|
| 518 |
LocalDateTime dayEnd = date.plusDays(1).atStartOfDay();
|
518 |
// remainingDays, then split by warehouse stock mix — exactly like RBM, but
|
| 519 |
|
- |
|
| 520 |
// Persisted RBM snapshots for the target date (one row per RBM × warehouse).
|
519 |
// scoped to the Sales person's OWN assigned partners (no RBM roll-up).
|
| 521 |
List<RbmTargets> todayRbmSnapshots = rbmTargetsRepository.selectTargetsByDates(dayStart, dayEnd);
|
- |
|
| 522 |
if (todayRbmSnapshots.isEmpty()) {
|
520 |
LocalDate monthStart = date.withDayOfMonth(1);
|
| 523 |
LOGGER.warn("setSalesTargetsForDate: no RBM snapshots for {}; run pushRbmTargets first", date);
|
521 |
int remainingWorkingDays = (int) getRemainingDaysInMonth(date);
|
| 524 |
return;
|
- |
|
| 525 |
}
|
- |
|
| 526 |
|
522 |
|
| 527 |
// Index snapshots by (rbmAuthId, warehouseId) for O(1) roll-up.
|
523 |
// Warehouse-level stock mix (same source used by setMovementWiseRbmTargets).
|
| 528 |
// rbm_auth_id → warehouseId → snapshot row.
|
- |
|
| 529 |
Map<Integer, Map<Integer, RbmTargets>> byRbmByWh = new HashMap<>();
|
524 |
Map<Integer, WarehouseMobileStockByMovementModel> whStockByWh = new HashMap<>();
|
| 530 |
for (RbmTargets s : todayRbmSnapshots) {
|
525 |
for (WarehouseMobileStockByMovementModel s : getWarehouseMobileStockByMovement()) {
|
| 531 |
byRbmByWh.computeIfAbsent(s.getRbmAuthId(), k -> new HashMap<>()).put(s.getWarehouseId(), s);
|
526 |
whStockByWh.put(s.getWarehouseId(), s);
|
| 532 |
}
|
527 |
}
|
| 533 |
Set<Integer> allSnapshotRbms = byRbmByWh.keySet();
|
- |
|
| 534 |
|
528 |
|
| 535 |
// Sales roles + their (categoryId, escalationType) mapping — mirrors
|
- |
|
| 536 |
// MonitorController /indent/today_po_table_fragment so both places agree.
|
- |
|
| 537 |
String[][] roles = {
|
529 |
String[][] roles = {
|
| 538 |
{"ASM", "4", "L1"}, {"CM", "4", "L2"}, {"DM", "4", "L3"},
|
530 |
{"ASM", "4", "L1"}, {"CM", "4", "L2"}, {"DM", "4", "L3"},
|
| 539 |
{"BM", "4", "L4"}, {"RSM", "4", "L5"}, {"NSM", "4", "L6"}
|
531 |
{"BM", "4", "L4"}, {"RSM", "4", "L5"}, {"NSM", "4", "L6"}
|
| 540 |
};
|
532 |
};
|
| 541 |
|
533 |
|
| 542 |
int written = 0;
|
- |
|
| 543 |
// Stamp rows within the target day so date-range queries pick them up
|
- |
|
| 544 |
// for the right day, even during backfill of a past date.
|
- |
|
| 545 |
LocalDate today = LocalDate.now();
|
534 |
LocalDate today = LocalDate.now();
|
| 546 |
LocalDateTime stamp = date.equals(today) ? LocalDateTime.now() : date.atTime(java.time.LocalTime.NOON);
|
535 |
LocalDateTime stamp = date.equals(today) ? LocalDateTime.now() : date.atTime(java.time.LocalTime.NOON);
|
| - |
|
536 |
int written = 0, zeroTargetSkipped = 0;
|
| - |
|
537 |
|
| 547 |
for (String[] r : roles) {
|
538 |
for (String[] r : roles) {
|
| 548 |
String role = r[0];
|
539 |
String role = r[0];
|
| 549 |
int catId = Integer.parseInt(r[1]);
|
540 |
int catId = Integer.parseInt(r[1]);
|
| 550 |
EscalationType level = EscalationType.valueOf(r[2]);
|
- |
|
| 551 |
for (AuthUser sales : csService.getAuthUserByCategoryId(catId, level)) {
|
- |
|
| 552 |
if (!sales.isActive()) continue;
|
541 |
String levelStr = r[2];
|
| 553 |
|
542 |
|
| 554 |
// Downline authIds (includes self; we skip self since Sales users
|
543 |
// Monthly target per (authId × warehouse) for this role.
|
| 555 |
// aren't RBMs and won't appear in snapshot).
|
544 |
// Use the target date's month so backfills for past months work correctly.
|
| 556 |
Set<Integer> downline = new HashSet<>();
|
- |
|
| 557 |
downline.add(sales.getId());
|
- |
|
| 558 |
List<Integer> reportees = authService.getAllReportees(sales.getId());
|
545 |
List<SalesHierarchyMonthlyTargetModel> monthlyTgts = getSalesMonthlyTargetByRole(catId, levelStr, date);
|
| 559 |
if (reportees != null) downline.addAll(reportees);
|
546 |
if (monthlyTgts.isEmpty()) continue;
|
| 560 |
downline.retainAll(allSnapshotRbms); // keep only RBMs with today's snapshot
|
- |
|
| 561 |
if (downline.isEmpty()) continue; // no RBMs under this Sales person today
|
- |
|
| 562 |
|
547 |
|
| 563 |
// Aggregate per warehouse across the downline RBMs.
|
548 |
// MTD achieved per (authId × warehouse) for this role. Same-day date range → MTD.
|
| 564 |
Map<Integer, float[]> perWh = new LinkedHashMap<>(); // wh → [hid, fast, run, slow, other]
|
- |
|
| 565 |
for (int rbmAuthId : downline) {
|
549 |
Map<String, Double> mtdByKey = new HashMap<>();
|
| 566 |
Map<Integer, RbmTargets> whMap = byRbmByWh.get(rbmAuthId);
|
- |
|
| 567 |
if (whMap == null) continue;
|
550 |
for (SalesHierarchyAchievedByMovementModel a :
|
| 568 |
for (Map.Entry<Integer, RbmTargets> e : whMap.entrySet()) {
|
551 |
getSalesAchievementByRole(catId, levelStr, monthStart, date.plusDays(1))) {
|
| 569 |
RbmTargets t = e.getValue();
|
552 |
double totBilled =
|
| 570 |
float[] acc = perWh.computeIfAbsent(e.getKey(), k -> new float[5]);
|
- |
|
| 571 |
acc[0] += t.getHidTarget();
|
553 |
a.getHidBilled()
|
| 572 |
acc[1] += t.getFastMovingTarget();
|
554 |
+ a.getFastMovingBilled()
|
| 573 |
acc[2] += t.getRunningtarget();
|
555 |
+ (a.getRunningBilled() != null ? a.getRunningBilled() : 0)
|
| 574 |
acc[3] += t.getSlowMovingtarget();
|
556 |
+ (a.getSlowMovingBilled() != null ? a.getSlowMovingBilled() : 0)
|
| 575 |
acc[4] += t.getOtherTarget();
|
557 |
+ (a.getOtherBilled() != null ? a.getOtherBilled() : 0);
|
| 576 |
}
|
558 |
mtdByKey.merge(a.getAuthId() + "-" + a.getWarehouseId(), totBilled, Double::sum);
|
| 577 |
}
|
559 |
}
|
| 578 |
|
560 |
|
| 579 |
for (Map.Entry<Integer, float[]> e : perWh.entrySet()) {
|
561 |
for (SalesHierarchyMonthlyTargetModel mt : monthlyTgts) {
|
| 580 |
float[] acc = e.getValue();
|
- |
|
| 581 |
SalesTargets st = new SalesTargets();
|
- |
|
| 582 |
st.setSalesAuthId(sales.getId());
|
- |
|
| 583 |
st.setSalesName(sales.getFullName());
|
- |
|
| 584 |
st.setRole(role);
|
- |
|
| 585 |
st.setWarehouseId(e.getKey());
|
- |
|
| 586 |
st.setHidTarget(acc[0]);
|
- |
|
| 587 |
st.setFastMovingTarget(acc[1]);
|
- |
|
| 588 |
st.setRunningTarget(acc[2]);
|
562 |
if (mt.getMonthlyTarget() <= 0) {
|
| 589 |
st.setSlowMovingTarget(acc[3]);
|
- |
|
| 590 |
st.setOtherTarget(acc[4]);
|
563 |
zeroTargetSkipped++;
|
| 591 |
st.setCreateTimestamp(stamp);
|
- |
|
| 592 |
salesTargetsRepository.persist(st);
|
- |
|
| 593 |
written++;
|
564 |
continue;
|
| 594 |
}
|
565 |
}
|
| - |
|
566 |
double achieved = mtdByKey.getOrDefault(mt.getAuthId() + "-" + mt.getWarehouseId(), 0.0);
|
| - |
|
567 |
double remaining = mt.getMonthlyTarget() - achieved;
|
| - |
|
568 |
double todayTarget = (remainingWorkingDays > 0 && remaining > 0)
|
| - |
|
569 |
? remaining / remainingWorkingDays : 0;
|
| - |
|
570 |
|
| - |
|
571 |
WarehouseMobileStockByMovementModel stock = whStockByWh.get(mt.getWarehouseId());
|
| - |
|
572 |
if (stock == null || stock.getTotalAvailabilityPrice() <= 0) continue;
|
| - |
|
573 |
float total = stock.getTotalAvailabilityPrice();
|
| - |
|
574 |
|
| - |
|
575 |
SalesTargets st = new SalesTargets();
|
| - |
|
576 |
st.setSalesAuthId(mt.getAuthId());
|
| - |
|
577 |
st.setSalesName(mt.getName());
|
| - |
|
578 |
st.setRole(role);
|
| - |
|
579 |
st.setWarehouseId(mt.getWarehouseId());
|
| - |
|
580 |
st.setHidTarget((float) ((stock.getTotalHidCatalogPrice() / total) * todayTarget));
|
| - |
|
581 |
st.setFastMovingTarget((float) ((stock.getTotalFastMovingCatalogPrice() / total) * todayTarget));
|
| - |
|
582 |
st.setSlowMovingTarget((float) ((stock.getTotalSlowMovingCatalogPrice() / total) * todayTarget));
|
| - |
|
583 |
st.setRunningTarget((float) ((stock.getTotalRunningCatalogPrice() / total) * todayTarget));
|
| - |
|
584 |
st.setOtherTarget((float) ((stock.getTotalOtherCategoryCatalogPrice() / total) * todayTarget));
|
| - |
|
585 |
st.setCreateTimestamp(stamp);
|
| - |
|
586 |
salesTargetsRepository.persist(st);
|
| - |
|
587 |
written++;
|
| 595 |
}
|
588 |
}
|
| 596 |
}
|
589 |
}
|
| 597 |
LOGGER.info("setSalesTargetsForDate: wrote {} sales_targets rows for {}", written, date);
|
590 |
LOGGER.info("setSalesTargetsForDate: wrote {} sales_targets rows for {} ({} rows skipped due to zero monthly target)",
|
| - |
|
591 |
written, date, zeroTargetSkipped);
|
| 598 |
}
|
592 |
}
|
| 599 |
|
593 |
|
| 600 |
@Override
|
594 |
@Override
|
| 601 |
public List<SalesTargets> getSalesTargetsByDates(LocalDate startDate, LocalDate endDate) {
|
595 |
public List<SalesTargets> getSalesTargetsByDates(LocalDate startDate, LocalDate endDate) {
|
| 602 |
return salesTargetsRepository.selectTargetsByDates(startDate.atStartOfDay(), endDate.atStartOfDay());
|
596 |
return salesTargetsRepository.selectTargetsByDates(startDate.atStartOfDay(), endDate.atStartOfDay());
|
| 603 |
}
|
597 |
}
|
| 604 |
|
598 |
|
| 605 |
@Override
|
599 |
@Override
|
| - |
|
600 |
public List<SalesHierarchyAchievedByMovementModel> getSalesAchievementByRole(
|
| - |
|
601 |
int categoryId, String escalationType, LocalDate startDate, LocalDate endDate) {
|
| - |
|
602 |
Session session = sessionFactory.getCurrentSession();
|
| - |
|
603 |
TypedQuery<SalesHierarchyAchievedByMovementModel> q = session.createNamedQuery(
|
| - |
|
604 |
"RbmDrr.SalesHierarchyAchievedByMovement", SalesHierarchyAchievedByMovementModel.class);
|
| - |
|
605 |
q.setParameter("categoryId", categoryId);
|
| - |
|
606 |
q.setParameter("escalationType", escalationType);
|
| - |
|
607 |
q.setParameter("startDate", startDate);
|
| - |
|
608 |
q.setParameter("endDate", endDate);
|
| - |
|
609 |
return q.getResultList();
|
| - |
|
610 |
}
|
| - |
|
611 |
|
| - |
|
612 |
@Override
|
| - |
|
613 |
public List<SalesHierarchyMonthlyTargetModel> getSalesMonthlyTargetByRole(
|
| - |
|
614 |
int categoryId, String escalationType, LocalDate refDate) {
|
| - |
|
615 |
Session session = sessionFactory.getCurrentSession();
|
| - |
|
616 |
TypedQuery<SalesHierarchyMonthlyTargetModel> q = session.createNamedQuery(
|
| - |
|
617 |
"RbmDrr.SalesHierarchyMonthlyTarget", SalesHierarchyMonthlyTargetModel.class);
|
| - |
|
618 |
q.setParameter("categoryId", categoryId);
|
| - |
|
619 |
q.setParameter("escalationType", escalationType);
|
| - |
|
620 |
q.setParameter("refDate", refDate);
|
| - |
|
621 |
return q.getResultList();
|
| - |
|
622 |
}
|
| - |
|
623 |
|
| - |
|
624 |
@Override
|
| 606 |
public void setSalesAchievements() {
|
625 |
public void setSalesAchievements() {
|
| 607 |
setSalesAchievementsForDate(LocalDate.now());
|
626 |
setSalesAchievementsForDate(LocalDate.now());
|
| 608 |
}
|
627 |
}
|
| 609 |
|
628 |
|
| 610 |
@Override
|
629 |
@Override
|
| 611 |
public void setSalesAchievementsForDate(LocalDate date) {
|
630 |
public void setSalesAchievementsForDate(LocalDate date) {
|
| 612 |
// ACH per (RBM authId × warehouse) for the target date, from the live
|
631 |
// Sales ACH per (auth × warehouse) — direct query scoped to each Sales
|
| 613 |
// order SQL. Same source used everywhere else.
|
632 |
// person's OWN partners (no RBM roll-up). Same source-of-truth orders SQL.
|
| 614 |
List<TodayAchievedMovementModel> achModels =
|
- |
|
| 615 |
getMovementWiseAchievementByDate(date, date.plusDays(1));
|
- |
|
| 616 |
if (achModels.isEmpty()) {
|
- |
|
| 617 |
LOGGER.warn("setSalesAchievementsForDate: no orders on {}; nothing to snapshot", date);
|
- |
|
| 618 |
return;
|
- |
|
| 619 |
}
|
- |
|
| 620 |
|
- |
|
| 621 |
// Sales roles + (categoryId, escalationType) — same mapping used elsewhere.
|
- |
|
| 622 |
String[][] roles = {
|
633 |
String[][] roles = {
|
| 623 |
{"ASM", "4", "L1"}, {"CM", "4", "L2"}, {"DM", "4", "L3"},
|
634 |
{"ASM", "4", "L1"}, {"CM", "4", "L2"}, {"DM", "4", "L3"},
|
| 624 |
{"BM", "4", "L4"}, {"RSM", "4", "L5"}, {"NSM", "4", "L6"}
|
635 |
{"BM", "4", "L4"}, {"RSM", "4", "L5"}, {"NSM", "4", "L6"}
|
| 625 |
};
|
636 |
};
|
| 626 |
|
637 |
|
| 627 |
// Stamp within the target date so date-range queries pick these rows
|
- |
|
| 628 |
// up for the correct day even during backfill.
|
- |
|
| 629 |
LocalDate today = LocalDate.now();
|
638 |
LocalDate today = LocalDate.now();
|
| 630 |
LocalDateTime stamp = date.equals(today) ? LocalDateTime.now() : date.atTime(java.time.LocalTime.NOON);
|
639 |
LocalDateTime stamp = date.equals(today) ? LocalDateTime.now() : date.atTime(java.time.LocalTime.NOON);
|
| 631 |
|
- |
|
| 632 |
int written = 0;
|
640 |
int written = 0;
|
| - |
|
641 |
|
| 633 |
for (String[] r : roles) {
|
642 |
for (String[] r : roles) {
|
| 634 |
String role = r[0];
|
643 |
String role = r[0];
|
| 635 |
int catId = Integer.parseInt(r[1]);
|
644 |
int catId = Integer.parseInt(r[1]);
|
| 636 |
EscalationType level = EscalationType.valueOf(r[2]);
|
- |
|
| 637 |
for (AuthUser sales : csService.getAuthUserByCategoryId(catId, level)) {
|
- |
|
| 638 |
if (!sales.isActive()) continue;
|
- |
|
| 639 |
|
- |
|
| 640 |
// Downline authIds (include self).
|
- |
|
| 641 |
Set<Integer> downline = new HashSet<>();
|
- |
|
| 642 |
downline.add(sales.getId());
|
645 |
String levelStr = r[2];
|
| 643 |
List<Integer> reportees = authService.getAllReportees(sales.getId());
|
- |
|
| 644 |
if (reportees != null) downline.addAll(reportees);
|
- |
|
| 645 |
|
- |
|
| 646 |
// Aggregate ACH per warehouse across downline RBMs.
|
- |
|
| 647 |
Map<Integer, float[]> perWh = new LinkedHashMap<>(); // wh → [hid, fast, run, slow, other]
|
- |
|
| 648 |
for (TodayAchievedMovementModel a : achModels) {
|
- |
|
| 649 |
if (!downline.contains(a.getAuthId())) continue;
|
- |
|
| 650 |
float[] acc = perWh.computeIfAbsent(a.getWarehouseId(), k -> new float[5]);
|
- |
|
| 651 |
acc[0] += a.getHidBilled();
|
- |
|
| 652 |
acc[1] += a.getFastMovingBilled();
|
- |
|
| 653 |
acc[2] += a.getRunningBilled() != null ? a.getRunningBilled() : 0f;
|
- |
|
| 654 |
acc[3] += a.getSlowMovinBilled() != null ? a.getSlowMovinBilled() : 0f;
|
- |
|
| 655 |
acc[4] += a.getOtherBilled() != null ? a.getOtherBilled() : 0f;
|
- |
|
| 656 |
}
|
- |
|
| 657 |
if (perWh.isEmpty()) continue;
|
- |
|
| 658 |
|
646 |
|
| 659 |
for (Map.Entry<Integer, float[]> e : perWh.entrySet()) {
|
647 |
for (SalesHierarchyAchievedByMovementModel a :
|
| 660 |
float[] acc = e.getValue();
|
648 |
getSalesAchievementByRole(catId, levelStr, date, date.plusDays(1))) {
|
| 661 |
SalesAchievements sa = new SalesAchievements();
|
649 |
SalesAchievements sa = new SalesAchievements();
|
| 662 |
sa.setSalesAuthId(sales.getId());
|
650 |
sa.setSalesAuthId(a.getAuthId());
|
| 663 |
sa.setSalesName(sales.getFullName());
|
651 |
sa.setSalesName(a.getName());
|
| 664 |
sa.setRole(role);
|
652 |
sa.setRole(role);
|
| 665 |
sa.setWarehouseId(e.getKey());
|
653 |
sa.setWarehouseId(a.getWarehouseId());
|
| 666 |
sa.setAchievedHidTarget(acc[0]);
|
654 |
sa.setAchievedHidTarget(a.getHidBilled());
|
| 667 |
sa.setAchievedFastMovingTarget(acc[1]);
|
655 |
sa.setAchievedFastMovingTarget(a.getFastMovingBilled());
|
| 668 |
sa.setAchievedRunningTarget(acc[2]);
|
656 |
sa.setAchievedRunningTarget(a.getRunningBilled() != null ? a.getRunningBilled() : 0f);
|
| 669 |
sa.setAchievedSlowMovingTarget(acc[3]);
|
657 |
sa.setAchievedSlowMovingTarget(a.getSlowMovingBilled() != null ? a.getSlowMovingBilled() : 0f);
|
| 670 |
sa.setAchievedOtherTarget(acc[4]);
|
658 |
sa.setAchievedOtherTarget(a.getOtherBilled() != null ? a.getOtherBilled() : 0f);
|
| 671 |
sa.setCreateTimestamp(stamp);
|
659 |
sa.setCreateTimestamp(stamp);
|
| 672 |
salesAchievementsRepository.persist(sa);
|
660 |
salesAchievementsRepository.persist(sa);
|
| 673 |
written++;
|
661 |
written++;
|
| 674 |
}
|
- |
|
| 675 |
}
|
662 |
}
|
| 676 |
}
|
663 |
}
|
| 677 |
LOGGER.info("setSalesAchievementsForDate: wrote {} sales_achievements rows for {}", written, date);
|
664 |
LOGGER.info("setSalesAchievementsForDate: wrote {} sales_achievements rows for {}", written, date);
|
| 678 |
}
|
665 |
}
|
| 679 |
|
666 |
|