Subversion Repositories SmartDukaan

Rev

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

Rev 36802 Rev 36811
Line 83... Line 83...
83
	private CookiesProcessor cookiesProcessor;
83
	private CookiesProcessor cookiesProcessor;
84
	@Autowired
84
	@Autowired
85
	private ResponseSender responseSender;
85
	private ResponseSender responseSender;
86
	@Autowired
86
	@Autowired
87
	private FofoStoreRepository fofoStoreRepository;
87
	private FofoStoreRepository fofoStoreRepository;
-
 
88
	@Autowired
-
 
89
	private com.spice.profitmandi.dao.repository.logistics.CompanyOfficeRepository companyOfficeRepository;
88
 
90
 
89
	@GetMapping(value = "/beatPlan")
91
	@GetMapping(value = "/beatPlan")
90
	public String beatPlan(HttpServletRequest request, Model model) throws ProfitMandiBusinessException {
92
	public String beatPlan(HttpServletRequest request, Model model) throws ProfitMandiBusinessException {
91
		model.addAttribute("escalationTypes", visibleLevelsFor(request));
93
		model.addAttribute("escalationTypes", visibleLevelsFor(request));
92
		return "beat-plan";
94
		return "beat-plan";
Line 525... Line 527...
525
 
527
 
526
		List<Map<String, Object>> out = new ArrayList<>();
528
		List<Map<String, Object>> out = new ArrayList<>();
527
		for (BeatDeferredVisit r : rows) {
529
		for (BeatDeferredVisit r : rows) {
528
			AuthUser u = userMap.get(r.getAuthUserId());
530
			AuthUser u = userMap.get(r.getAuthUserId());
529
			boolean isLead = "lead".equalsIgnoreCase(r.getTaskType());
531
			boolean isLead = "lead".equalsIgnoreCase(r.getTaskType());
-
 
532
			boolean isOffice = "office-visit".equalsIgnoreCase(r.getTaskType());
530
			Map<String, Object> row = new HashMap<>();
533
			Map<String, Object> row = new HashMap<>();
531
			row.put("id", r.getId());
534
			row.put("id", r.getId());
532
			row.put("authUserId", r.getAuthUserId());
535
			row.put("authUserId", r.getAuthUserId());
533
			row.put("userName", u != null ? (u.getFirstName() + " " + u.getLastName()) : ("User #" + r.getAuthUserId()));
536
			row.put("userName", u != null ? (u.getFirstName() + " " + u.getLastName()) : ("User #" + r.getAuthUserId()));
534
			row.put("fofoStoreId", r.getFofoId());
537
			row.put("fofoStoreId", r.getFofoId());
535
			row.put("name", r.getDisplayName() != null ? r.getDisplayName() : ("#" + r.getFofoId()));
538
			row.put("name", r.getDisplayName() != null ? r.getDisplayName() : ("#" + r.getFofoId()));
536
			row.put("type", isLead ? "Lead" : "Visit");
539
			row.put("type", isLead ? "Lead" : (isOffice ? "Office" : "Visit"));
537
			row.put("deferredDate", r.getDeferredDate() != null ? r.getDeferredDate().toString() : null);
540
			row.put("deferredDate", r.getDeferredDate() != null ? r.getDeferredDate().toString() : null);
538
			row.put("reason", r.getReason());
541
			row.put("reason", r.getReason());
539
			row.put("status", r.getStatus());
542
			row.put("status", r.getStatus());
540
			LocalDate next = nextByRowId.get(r.getId());
543
			LocalDate next = nextByRowId.get(r.getId());
541
			row.put("nextScheduledDate", next != null ? next.toString() : null);
544
			row.put("nextScheduledDate", next != null ? next.toString() : null);
Line 614... Line 617...
614
		// leads, looking up fofo_store would be the wrong id space). For visits we
617
		// leads, looking up fofo_store would be the wrong id space). For visits we
615
		// still try to pull lat/lng for the visit location.
618
		// still try to pull lat/lng for the visit location.
616
		Integer dtrId = resolveDtrId(d.getAuthUserId(), new HashMap<>());
619
		Integer dtrId = resolveDtrId(d.getAuthUserId(), new HashMap<>());
617
		if (dtrId == null) return responseSender.badRequest("No dtr.users record for this sales person");
620
		if (dtrId == null) return responseSender.badRequest("No dtr.users record for this sales person");
618
		boolean isLead = "lead".equalsIgnoreCase(d.getTaskType());
621
		boolean isLead = "lead".equalsIgnoreCase(d.getTaskType());
-
 
622
		boolean isOffice = "office-visit".equalsIgnoreCase(d.getTaskType());
619
		String visitLocation = "0.0000,0.0000";
623
		String visitLocation = "0.0000,0.0000";
-
 
624
		if (isOffice) {
-
 
625
			// Office stops resolve lat/lng from logistics.company_office.
-
 
626
			try {
-
 
627
				com.spice.profitmandi.dao.entity.logistics.CompanyOffice o = companyOfficeRepository.selectById(d.getFofoId());
-
 
628
				if (o != null) visitLocation = o.getLat() + "," + o.getLng();
-
 
629
			} catch (Exception ignored) {
-
 
630
			}
620
		if (!isLead) {
631
		} else if (!isLead) {
621
			try {
632
			try {
622
				List<FofoStore> ss = fofoStoreRepository.selectByRetailerIds(java.util.Collections.singletonList(d.getFofoId()));
633
				List<FofoStore> ss = fofoStoreRepository.selectByRetailerIds(java.util.Collections.singletonList(d.getFofoId()));
623
				if (!ss.isEmpty()) {
634
				if (!ss.isEmpty()) {
624
					FofoStore fs = ss.get(0);
635
					FofoStore fs = ss.get(0);
625
					if (fs.getLatitude() != null && fs.getLongitude() != null
636
					if (fs.getLatitude() != null && fs.getLongitude() != null
Line 748... Line 759...
748
						.filter(r -> r.getDayNumber() == sched.getDayNumber())
759
						.filter(r -> r.getDayNumber() == sched.getDayNumber())
749
						.mapToInt(BeatRoute::getSequenceOrder).max().orElse(-1) + 1;
760
						.mapToInt(BeatRoute::getSequenceOrder).max().orElse(-1) + 1;
750
				BeatRoute br = new BeatRoute();
761
				BeatRoute br = new BeatRoute();
751
				br.setBeatId(beatId);
762
				br.setBeatId(beatId);
752
				br.setFofoId(d.getFofoId());
763
				br.setFofoId(d.getFofoId());
-
 
764
				br.setVisitType(com.spice.profitmandi.dao.enumuration.dtr.BeatVisitType.PARTNER);
753
				br.setDayNumber(sched.getDayNumber());
765
				br.setDayNumber(sched.getDayNumber());
754
				br.setSequenceOrder(nextSeq);
766
				br.setSequenceOrder(nextSeq);
755
				br.setActive(true);
767
				br.setActive(true);
756
				beatRouteRepository.persist(br);
768
				beatRouteRepository.persist(br);
757
			}
769
			}
Line 1051... Line 1063...
1051
					continue; // leads live in lead_route, handled below
1063
					continue; // leads live in lead_route, handled below
1052
				}
1064
				}
1053
				BeatRoute route = new BeatRoute();
1065
				BeatRoute route = new BeatRoute();
1054
				route.setBeatId(beatId);
1066
				route.setBeatId(beatId);
1055
				route.setFofoId(((Number) v.get("id")).intValue());
1067
				route.setFofoId(((Number) v.get("id")).intValue());
-
 
1068
				route.setVisitType("office".equals(v.get("type"))
-
 
1069
						? com.spice.profitmandi.dao.enumuration.dtr.BeatVisitType.OFFICE
-
 
1070
						: com.spice.profitmandi.dao.enumuration.dtr.BeatVisitType.PARTNER);
1056
				route.setSequenceOrder(partnerSeq++);
1071
				route.setSequenceOrder(partnerSeq++);
1057
				route.setDayNumber(dayNumber);
1072
				route.setDayNumber(dayNumber);
1058
				route.setActive(true);
1073
				route.setActive(true);
1059
                if (v.get("distanceFromPrevKm") != null)
1074
                if (v.get("distanceFromPrevKm") != null)
1060
                    route.setDistanceFromPrevKm(((Number) v.get("distanceFromPrevKm")).doubleValue());
1075
                    route.setDistanceFromPrevKm(((Number) v.get("distanceFromPrevKm")).doubleValue());
Line 1441... Line 1456...
1441
		}
1456
		}
1442
 
1457
 
1443
		List<BeatRoute> routes = beatRouteRepository.selectByBeatId(beatId);
1458
		List<BeatRoute> routes = beatRouteRepository.selectByBeatId(beatId);
1444
		List<Map<String, Object>> result = new ArrayList<>();
1459
		List<Map<String, Object>> result = new ArrayList<>();
1445
 
1460
 
-
 
1461
		// Stops — partner OR office, dispatched by visit_type. Partners are
1446
		// Partner stops — always (they belong to the beat template)
1462
		// enriched on the client from the partner map (already in scope);
-
 
1463
		// offices are enriched here because the client has no office map.
1447
		for (BeatRoute r : routes) {
1464
		for (BeatRoute r : routes) {
1448
			Map<String, Object> map = new HashMap<>();
1465
			Map<String, Object> map = new HashMap<>();
1449
			map.put("fofoId", r.getFofoId());
1466
			map.put("fofoId", r.getFofoId());
1450
			map.put("dayNumber", r.getDayNumber());
1467
			map.put("dayNumber", r.getDayNumber());
1451
			map.put("sequenceOrder", r.getSequenceOrder());
1468
			map.put("sequenceOrder", r.getSequenceOrder());
-
 
1469
			if (r.getVisitType() == com.spice.profitmandi.dao.enumuration.dtr.BeatVisitType.OFFICE) {
-
 
1470
				map.put("visitType", "office");
-
 
1471
				try {
-
 
1472
					com.spice.profitmandi.dao.entity.logistics.CompanyOffice o =
-
 
1473
							companyOfficeRepository.selectById(r.getFofoId());
-
 
1474
					if (o != null) {
-
 
1475
						map.put("code", o.getCode());
-
 
1476
						map.put("name", o.getName());
-
 
1477
						map.put("latitude", String.valueOf(o.getLat()));
-
 
1478
						map.put("longitude", String.valueOf(o.getLng()));
-
 
1479
					}
-
 
1480
				} catch (Exception ignored) {
-
 
1481
				}
-
 
1482
			} else {
1452
			map.put("visitType", "partner");
1483
				map.put("visitType", "partner");
-
 
1484
			}
1453
			result.add(map);
1485
			result.add(map);
1454
		}
1486
		}
1455
 
1487
 
1456
		// Lead stops — only for the requested run date
1488
		// Lead stops — only for the requested run date
1457
		if (planDate != null && !planDate.isEmpty()) {
1489
		if (planDate != null && !planDate.isEmpty()) {
Line 1880... Line 1912...
1880
				for (int i = 0; i < visits.size(); i++) {
1912
				for (int i = 0; i < visits.size(); i++) {
1881
					Map<String, Object> visit = visits.get(i);
1913
					Map<String, Object> visit = visits.get(i);
1882
					BeatRoute route = new BeatRoute();
1914
					BeatRoute route = new BeatRoute();
1883
					route.setBeatId(beat.getId());
1915
					route.setBeatId(beat.getId());
1884
					route.setFofoId(((Number) visit.get("id")).intValue());
1916
					route.setFofoId(((Number) visit.get("id")).intValue());
-
 
1917
					route.setVisitType("office".equals(visit.get("type"))
-
 
1918
							? com.spice.profitmandi.dao.enumuration.dtr.BeatVisitType.OFFICE
-
 
1919
							: com.spice.profitmandi.dao.enumuration.dtr.BeatVisitType.PARTNER);
1885
					route.setSequenceOrder(i);
1920
					route.setSequenceOrder(i);
1886
					route.setDayNumber(dayNumber);
1921
					route.setDayNumber(dayNumber);
1887
					route.setActive(true);
1922
					route.setActive(true);
1888
                    if (visit.get("distanceFromPrevKm") != null)
1923
                    if (visit.get("distanceFromPrevKm") != null)
1889
                        route.setDistanceFromPrevKm(((Number) visit.get("distanceFromPrevKm")).doubleValue());
1924
                        route.setDistanceFromPrevKm(((Number) visit.get("distanceFromPrevKm")).doubleValue());
Line 2095... Line 2130...
2095
			}
2130
			}
2096
			g.addPartner(day, seq, code.trim(), r + 1);
2131
			g.addPartner(day, seq, code.trim(), r + 1);
2097
		}
2132
		}
2098
		workbook.close();
2133
		workbook.close();
2099
 
2134
 
-
 
2135
		// Partner-code lookup (legacy).
2100
		List<FofoStore> allStores = fofoStoreRepository.selectAll();
2136
		List<FofoStore> allStores = fofoStoreRepository.selectAll();
2101
		Map<String, Integer> codeToId = new HashMap<>();
2137
		Map<String, Integer> codeToId = new HashMap<>();
2102
		for (FofoStore store : allStores) codeToId.put(store.getCode(), store.getId());
2138
		for (FofoStore store : allStores) codeToId.put(store.getCode(), store.getId());
2103
 
2139
 
-
 
2140
		// Office-code lookup — office stops share the same `partner_code` column in the bulk
-
 
2141
		// sheet; resolution dispatches by which catalogue the code belongs to. A code present
-
 
2142
		// in BOTH catalogues is treated as an error so the planner fixes the collision.
-
 
2143
		Map<String, Integer> officeCodeToId = new HashMap<>();
-
 
2144
		for (com.spice.profitmandi.dao.entity.logistics.CompanyOffice o : companyOfficeRepository.selectAll()) {
-
 
2145
			if (o.getCode() != null && !o.getCode().isEmpty()) officeCodeToId.put(o.getCode(), o.getId());
-
 
2146
		}
-
 
2147
 
2104
		LocalDate holidayStart = LocalDate.now();
2148
		LocalDate holidayStart = LocalDate.now();
2105
		List<PublicHolidays> holidays = publicHolidaysRepository.selectAllBetweenDates(holidayStart, holidayStart.plusMonths(6));
2149
		List<PublicHolidays> holidays = publicHolidaysRepository.selectAllBetweenDates(holidayStart, holidayStart.plusMonths(6));
2106
		Set<LocalDate> holidayDates = holidays.stream().map(PublicHolidays::getDate).collect(Collectors.toSet());
2150
		Set<LocalDate> holidayDates = holidays.stream().map(PublicHolidays::getDate).collect(Collectors.toSet());
2107
 
2151
 
2108
		// =====================================================================
2152
		// =====================================================================
Line 2168... Line 2212...
2168
			if (bulkConflict != null) {
2212
			if (bulkConflict != null) {
2169
				errorMessages.add("Beat '" + beatName + "': " + scheduleConflictMessage(bulkConflict));
2213
				errorMessages.add("Beat '" + beatName + "': " + scheduleConflictMessage(bulkConflict));
2170
				continue;
2214
				continue;
2171
			}
2215
			}
2172
 
2216
 
2173
			// Validate partner codes upfront so we don't half-persist.
2217
			// Validate codes upfront so we don't half-persist. A code may belong to
-
 
2218
			// fofo_store (PARTNER) or company_office (OFFICE) but not both.
2174
			List<String> badCodes = new ArrayList<>();
2219
			List<String> badCodes = new ArrayList<>();
-
 
2220
			List<String> ambiguousCodes = new ArrayList<>();
2175
			for (List<BulkPartner> ps : g.dayToPartners.values()) {
2221
			for (List<BulkPartner> ps : g.dayToPartners.values()) {
2176
				for (BulkPartner p : ps) {
2222
				for (BulkPartner p : ps) {
2177
					if (!codeToId.containsKey(p.code)) {
2223
					boolean inPartner = codeToId.containsKey(p.code);
-
 
2224
					boolean inOffice = officeCodeToId.containsKey(p.code);
-
 
2225
					if (inPartner && inOffice) {
-
 
2226
						ambiguousCodes.add(p.code + " (row " + p.rowNum + ")");
-
 
2227
					} else if (!inPartner && !inOffice) {
2178
						badCodes.add(p.code + " (row " + p.rowNum + ")");
2228
						badCodes.add(p.code + " (row " + p.rowNum + ")");
2179
					}
2229
					}
2180
				}
2230
				}
2181
			}
2231
			}
2182
			if (!badCodes.isEmpty()) {
2232
			if (!badCodes.isEmpty()) {
2183
				errorMessages.add("Beat '" + beatName + "': unknown partner code(s) — " + String.join(", ", badCodes) + ".");
2233
				errorMessages.add("Beat '" + beatName + "': unknown code(s) — " + String.join(", ", badCodes) + ".");
-
 
2234
				continue;
-
 
2235
			}
-
 
2236
			if (!ambiguousCodes.isEmpty()) {
-
 
2237
				errorMessages.add("Beat '" + beatName + "': code(s) exist in both partner and office catalogues — " + String.join(", ", ambiguousCodes) + ".");
2184
				continue;
2238
				continue;
2185
			}
2239
			}
2186
 
2240
 
2187
			ready.add(new ValidatedBulkBeat(g, authUserId, sortedDays, scheduleDates));
2241
			ready.add(new ValidatedBulkBeat(g, authUserId, sortedDays, scheduleDates));
2188
		}
2242
		}
Line 2259... Line 2313...
2259
					return Integer.compare(a.rowNum, b.rowNum);
2313
					return Integer.compare(a.rowNum, b.rowNum);
2260
				});
2314
				});
2261
 
2315
 
2262
				int autoSeq = 0;
2316
				int autoSeq = 0;
2263
				for (BulkPartner p : partners) {
2317
				for (BulkPartner p : partners) {
2264
					Integer fofoId = codeToId.get(p.code);
2318
					Integer partnerId = codeToId.get(p.code);
-
 
2319
					Integer officeId = officeCodeToId.get(p.code);
2265
					// Codes were validated in Phase 1, this is just a safety net.
2320
					// Codes were validated in Phase 1, this is just a safety net.
2266
					if (fofoId == null) continue;
2321
					if (partnerId == null && officeId == null) continue;
2267
					BeatRoute route = new BeatRoute();
2322
					BeatRoute route = new BeatRoute();
2268
					route.setBeatId(beat.getId());
2323
					route.setBeatId(beat.getId());
-
 
2324
					if (partnerId != null) {
-
 
2325
						route.setFofoId(partnerId);
-
 
2326
						route.setVisitType(com.spice.profitmandi.dao.enumuration.dtr.BeatVisitType.PARTNER);
-
 
2327
					} else {
2269
					route.setFofoId(fofoId);
2328
						route.setFofoId(officeId);
-
 
2329
						route.setVisitType(com.spice.profitmandi.dao.enumuration.dtr.BeatVisitType.OFFICE);
-
 
2330
					}
2270
					route.setSequenceOrder(p.seq >= 0 ? p.seq : autoSeq);
2331
					route.setSequenceOrder(p.seq >= 0 ? p.seq : autoSeq);
2271
					route.setDayNumber(dayNumber);
2332
					route.setDayNumber(dayNumber);
2272
					route.setActive(true);
2333
					route.setActive(true);
2273
					beatRouteRepository.persist(route);
2334
					beatRouteRepository.persist(route);
2274
					autoSeq++;
2335
					autoSeq++;