| Line 54... |
Line 54... |
| 54 |
out.put("utilization", utilization);
|
54 |
out.put("utilization", utilization);
|
| 55 |
out.put("deferral", deferral);
|
55 |
out.put("deferral", deferral);
|
| 56 |
out.put("travel", travel);
|
56 |
out.put("travel", travel);
|
| 57 |
out.put("outcomes", outcomes);
|
57 |
out.put("outcomes", outcomes);
|
| 58 |
out.put("findings", findings(kpis, funnel, deferral, utilization, travel, outcomes, coverage, scorecard));
|
58 |
out.put("findings", findings(kpis, funnel, deferral, utilization, travel, outcomes, coverage, scorecard));
|
| - |
|
59 |
// drill-down lists
|
| - |
|
60 |
out.put("coverageDetail", coverageDetail(s, start, end, auth));
|
| - |
|
61 |
out.put("deferralsList", deferralsList(s, start, end, auth));
|
| - |
|
62 |
out.put("leadsList", leadsList(s, start, end, auth));
|
| 59 |
return out;
|
63 |
return out;
|
| 60 |
}
|
64 |
}
|
| 61 |
|
65 |
|
| 62 |
// ---- KPIs ---------------------------------------------------------------
|
66 |
// ---- KPIs ---------------------------------------------------------------
|
| 63 |
|
67 |
|
| Line 82... |
Line 86... |
| 82 |
m.put("coverageL1With", l1With);
|
86 |
m.put("coverageL1With", l1With);
|
| 83 |
m.put("coverageL1Total", l1Total);
|
87 |
m.put("coverageL1Total", l1Total);
|
| 84 |
m.put("coverageL1Pct", l1Total == 0 ? 0 : Math.round(100.0 * l1With / l1Total));
|
88 |
m.put("coverageL1Pct", l1Total == 0 ? 0 : Math.round(100.0 * l1With / l1Total));
|
| 85 |
|
89 |
|
| 86 |
// avg visits / active (punched-in) day, and total punch-in days (journeys)
|
90 |
// avg visits / active (punched-in) day, and total punch-in days (journeys)
|
| - |
|
91 |
// A "punch-in day" is the attendance row (task_id=0) with a real check-in
|
| - |
|
92 |
// time, matching the Today lens. (Literal mark_type='PUNCHIN' rows barely
|
| - |
|
93 |
// exist in the data, so relying on them under-counts drastically.)
|
| 87 |
NativeQuery<?> q = s.createNativeQuery(
|
94 |
NativeQuery<?> q = s.createNativeQuery(
|
| 88 |
"SELECT COUNT(DISTINCT CONCAT(user_id,'-',task_date)) FROM auth.location_tracking" +
|
95 |
"SELECT COUNT(DISTINCT CONCAT(user_id,'-',task_date)) FROM auth.location_tracking" +
|
| 89 |
" WHERE task_date>=:ms AND task_date<=:me AND mark_type='PUNCHIN' AND user_id IN (:dtr)");
|
96 |
" WHERE task_date>=:ms AND task_date<=:me AND user_id IN (:dtr)" +
|
| - |
|
97 |
" AND (mark_type='PUNCHIN' OR (task_id=0 AND check_in_time IS NOT NULL AND check_in_time<>'00:00:00'))");
|
| 90 |
q.setParameter("ms", ms).setParameter("me", me).setParameterList("dtr", dtr);
|
98 |
q.setParameter("ms", ms).setParameter("me", me).setParameterList("dtr", dtr);
|
| 91 |
long journeys = asLong(q.uniqueResult());
|
99 |
long journeys = asLong(q.uniqueResult());
|
| 92 |
m.put("journeys", journeys);
|
100 |
m.put("journeys", journeys);
|
| 93 |
m.put("avgVisitsPerActiveDay", journeys == 0 ? 0 : round1((double) asLong(funnel.get("checkedIn")) / journeys));
|
101 |
m.put("avgVisitsPerActiveDay", journeys == 0 ? 0 : round1((double) asLong(funnel.get("checkedIn")) / journeys));
|
| 94 |
|
102 |
|
| 95 |
// avg in-store discussion minutes
|
103 |
// avg in-store discussion minutes
|
| 96 |
NativeQuery<?> dq = s.createNativeQuery(
|
104 |
NativeQuery<?> dq = s.createNativeQuery(
|
| 97 |
"SELECT ROUND(AVG(NULLIF(TIME_TO_SEC(time_spent),0))/60,0) FROM auth.location_tracking" +
|
105 |
"SELECT ROUND(AVG(NULLIF(TIME_TO_SEC(time_spent),0))/60,0) FROM auth.location_tracking" +
|
| 98 |
" WHERE task_date>=:ms AND task_date<=:me AND task_type='franchisee-visit' AND mark_type LIKE '%CHECKOUT%' AND user_id IN (:dtr)");
|
106 |
" WHERE task_date>=:ms AND task_date<=:me AND task_id<>0 AND mark_type LIKE '%CHECKOUT%' AND user_id IN (:dtr)");
|
| 99 |
dq.setParameter("ms", ms).setParameter("me", me).setParameterList("dtr", dtr);
|
107 |
dq.setParameter("ms", ms).setParameter("me", me).setParameterList("dtr", dtr);
|
| 100 |
m.put("avgDiscussionMin", asLong(dq.uniqueResult()));
|
108 |
m.put("avgDiscussionMin", asLong(dq.uniqueResult()));
|
| 101 |
|
109 |
|
| 102 |
m.put("autoMissed", asLong(deferral.get("total")));
|
110 |
m.put("autoMissed", asLong(deferral.get("total")));
|
| 103 |
m.put("autoMissedSystemPct", asLong(deferral.get("systemPct")));
|
111 |
m.put("autoMissedSystemPct", asLong(deferral.get("systemPct")));
|
| Line 113... |
Line 121... |
| 113 |
// ---- Funnel -------------------------------------------------------------
|
121 |
// ---- Funnel -------------------------------------------------------------
|
| 114 |
|
122 |
|
| 115 |
private Map<String, Object> funnel(Session s, String ms, String me, List<Integer> auth, List<Integer> dtr) {
|
123 |
private Map<String, Object> funnel(Session s, String ms, String me, List<Integer> auth, List<Integer> dtr) {
|
| 116 |
Map<String, Object> m = new LinkedHashMap<>();
|
124 |
Map<String, Object> m = new LinkedHashMap<>();
|
| 117 |
|
125 |
|
| 118 |
// planned stops = scheduled beat-days × their routed stops
|
126 |
// Planned stops = partner stops (beat_route) + lead stops (lead_route, APPROVED)
|
| 119 |
NativeQuery<?> pq = s.createNativeQuery(
|
- |
|
| 120 |
"SELECT COUNT(*) FROM `user`.beat_schedule sc" +
|
- |
|
| 121 |
" JOIN `user`.beat b ON b.id=sc.beat_id AND b.auth_user_id IN (:auth)" +
|
127 |
// on each scheduled beat-day, over active beats — exactly how the Today lens
|
| 122 |
" JOIN `user`.beat_route r ON r.beat_id=sc.beat_id AND r.day_number=sc.day_number AND r.active=1" +
|
128 |
// (getAllScheduledBeats) counts, so Period reconciles with the day views.
|
| 123 |
" WHERE sc.start_date>=:ms AND sc.start_date<=:me");
|
129 |
long planned = plannedCount(s, ms, me, auth, dtr, false, false)
|
| 124 |
pq.setParameter("ms", ms).setParameter("me", me).setParameterList("auth", auth);
|
130 |
+ plannedCount(s, ms, me, auth, dtr, true, false);
|
| 125 |
long planned = asLong(pq.uniqueResult());
|
- |
|
| 126 |
|
- |
|
| 127 |
// planned stops that fall on a day the owner actually punched in
|
131 |
// ...of those, the planned stops that fall on a day the owner punched in
|
| 128 |
NativeQuery<?> piq = s.createNativeQuery(
|
- |
|
| 129 |
"SELECT COUNT(*) FROM `user`.beat_schedule sc" +
|
- |
|
| 130 |
" JOIN `user`.beat b ON b.id=sc.beat_id AND b.auth_user_id IN (:auth)" +
|
- |
|
| 131 |
" JOIN `user`.beat_route r ON r.beat_id=sc.beat_id AND r.day_number=sc.day_number AND r.active=1" +
|
- |
|
| 132 |
" JOIN auth.auth_user au ON au.id=b.auth_user_id" +
|
132 |
long punchedIn = plannedCount(s, ms, me, auth, dtr, false, true)
|
| 133 |
" JOIN dtr.users du ON du.email=au.email_id" +
|
- |
|
| 134 |
" JOIN (SELECT DISTINCT user_id, task_date FROM auth.location_tracking" +
|
- |
|
| 135 |
" WHERE task_date>=:ms AND task_date<=:me AND mark_type='PUNCHIN' AND user_id IN (:dtr)) p" +
|
- |
|
| 136 |
" ON p.user_id=du.id AND p.task_date=sc.start_date" +
|
- |
|
| 137 |
" WHERE sc.start_date>=:ms AND sc.start_date<=:me");
|
133 |
+ plannedCount(s, ms, me, auth, dtr, true, true);
|
| 138 |
piq.setParameter("ms", ms).setParameter("me", me).setParameterList("auth", auth).setParameterList("dtr", dtr);
|
- |
|
| 139 |
long punchedIn = asLong(piq.uniqueResult());
|
- |
|
| 140 |
|
134 |
|
| 141 |
long checkedIn = countVisits(s, ms, me, dtr, "mark_type LIKE 'CHECKIN%'");
|
135 |
long checkedIn = countVisits(s, ms, me, dtr, "mark_type LIKE 'CHECKIN%'");
|
| 142 |
long checkedOut = countVisits(s, ms, me, dtr, "(mark_type LIKE '%CHECKOUT%')");
|
136 |
long checkedOut = countVisits(s, ms, me, dtr, "(mark_type LIKE '%CHECKOUT%')");
|
| 143 |
|
137 |
|
| 144 |
m.put("plannedStops", planned);
|
138 |
m.put("plannedStops", planned);
|
| Line 149... |
Line 143... |
| 149 |
m.put("dropCheckin", punchedIn == 0 ? 0 : Math.round(100.0 * (punchedIn - checkedIn) / punchedIn));
|
143 |
m.put("dropCheckin", punchedIn == 0 ? 0 : Math.round(100.0 * (punchedIn - checkedIn) / punchedIn));
|
| 150 |
m.put("dropCheckout", checkedIn == 0 ? 0 : Math.round(100.0 * (checkedIn - checkedOut) / checkedIn));
|
144 |
m.put("dropCheckout", checkedIn == 0 ? 0 : Math.round(100.0 * (checkedIn - checkedOut) / checkedIn));
|
| 151 |
return m;
|
145 |
return m;
|
| 152 |
}
|
146 |
}
|
| 153 |
|
147 |
|
| - |
|
148 |
// Planned stop count over scheduled beat-days of active beats. lead=false counts
|
| - |
|
149 |
// partner stops (beat_route by day_number); lead=true counts approved lead stops
|
| - |
|
150 |
// (lead_route by schedule_date). onlyPunchInDays restricts to days the owner
|
| - |
|
151 |
// actually punched in (the funnel's "planned on punch-in days" stage).
|
| - |
|
152 |
private long plannedCount(Session s, String ms, String me, List<Integer> auth,
|
| - |
|
153 |
List<Integer> dtr, boolean lead, boolean onlyPunchInDays) {
|
| - |
|
154 |
String stopJoin = lead
|
| - |
|
155 |
? " JOIN `user`.lead_route x ON x.beat_id=sc.beat_id AND x.schedule_date=sc.start_date AND x.status='APPROVED'"
|
| - |
|
156 |
: " JOIN `user`.beat_route x ON x.beat_id=sc.beat_id AND x.day_number=sc.day_number";
|
| - |
|
157 |
String punchJoin = onlyPunchInDays
|
| - |
|
158 |
? " JOIN auth.auth_user au ON au.id=b.auth_user_id" +
|
| - |
|
159 |
" JOIN dtr.users du ON du.email=au.email_id" +
|
| - |
|
160 |
" JOIN (SELECT DISTINCT user_id, task_date FROM auth.location_tracking" +
|
| - |
|
161 |
" WHERE task_date>=:ms AND task_date<=:me AND user_id IN (:dtr)" +
|
| - |
|
162 |
" AND (mark_type='PUNCHIN' OR (task_id=0 AND check_in_time IS NOT NULL AND check_in_time<>'00:00:00'))) p" +
|
| - |
|
163 |
" ON p.user_id=du.id AND p.task_date=sc.start_date"
|
| - |
|
164 |
: "";
|
| - |
|
165 |
NativeQuery<?> q = s.createNativeQuery(
|
| - |
|
166 |
"SELECT COUNT(*) FROM `user`.beat_schedule sc" +
|
| - |
|
167 |
" JOIN `user`.beat b ON b.id=sc.beat_id AND b.active=1 AND b.auth_user_id IN (:auth)" +
|
| - |
|
168 |
stopJoin + punchJoin +
|
| - |
|
169 |
" WHERE sc.start_date>=:ms AND sc.start_date<=:me");
|
| - |
|
170 |
q.setParameter("ms", ms).setParameter("me", me).setParameterList("auth", auth);
|
| - |
|
171 |
if (onlyPunchInDays) q.setParameterList("dtr", dtr);
|
| - |
|
172 |
return asLong(q.uniqueResult());
|
| - |
|
173 |
}
|
| - |
|
174 |
|
| - |
|
175 |
// Counts visits across ALL task types (franchisee + lead + office) — task_id<>0
|
| - |
|
176 |
// excludes the attendance/punch row — matching the Today lens, which tallies
|
| - |
|
177 |
// every check-in/out regardless of type.
|
| 154 |
private long countVisits(Session s, String ms, String me, List<Integer> dtr, String markPredicate) {
|
178 |
private long countVisits(Session s, String ms, String me, List<Integer> dtr, String markPredicate) {
|
| 155 |
NativeQuery<?> q = s.createNativeQuery(
|
179 |
NativeQuery<?> q = s.createNativeQuery(
|
| 156 |
"SELECT COUNT(*) FROM auth.location_tracking" +
|
180 |
"SELECT COUNT(*) FROM auth.location_tracking" +
|
| 157 |
" WHERE task_date>=:ms AND task_date<=:me AND task_type='franchisee-visit' AND " + markPredicate +
|
181 |
" WHERE task_date>=:ms AND task_date<=:me AND task_id<>0 AND " + markPredicate +
|
| 158 |
" AND user_id IN (:dtr)");
|
182 |
" AND user_id IN (:dtr)");
|
| 159 |
q.setParameter("ms", ms).setParameter("me", me).setParameterList("dtr", dtr);
|
183 |
q.setParameter("ms", ms).setParameter("me", me).setParameterList("dtr", dtr);
|
| 160 |
return asLong(q.uniqueResult());
|
184 |
return asLong(q.uniqueResult());
|
| 161 |
}
|
185 |
}
|
| 162 |
|
186 |
|
| Line 188... |
Line 212... |
| 188 |
// ---- Per-executive scorecard -------------------------------------------
|
212 |
// ---- Per-executive scorecard -------------------------------------------
|
| 189 |
|
213 |
|
| 190 |
private List<Map<String, Object>> scorecard(Session s, String ms, String me, List<Integer> auth, List<Integer> dtr) {
|
214 |
private List<Map<String, Object>> scorecard(Session s, String ms, String me, List<Integer> auth, List<Integer> dtr) {
|
| 191 |
NativeQuery<?> q = s.createNativeQuery(
|
215 |
NativeQuery<?> q = s.createNativeQuery(
|
| 192 |
"SELECT au.id uid, TRIM(CONCAT(au.first_name,' ',COALESCE(au.last_name,''))) nm, lvl.lvl," +
|
216 |
"SELECT au.id uid, TRIM(CONCAT(au.first_name,' ',COALESCE(au.last_name,''))) nm, lvl.lvl," +
|
| 193 |
" COALESCE(pl.planned,0) planned, COALESCE(ac.done,0) done," +
|
217 |
" COALESCE(pl.planned,0)+COALESCE(plL.planned,0) planned, COALESCE(ac.done,0) done," +
|
| 194 |
" COALESCE(ds.disc_min,0) disc_min, COALESCE(wk.work_sec,0) work_sec," +
|
218 |
" COALESCE(ds.disc_min,0) disc_min, COALESCE(wk.work_sec,0) work_sec," +
|
| 195 |
" COALESCE(ld.leads,0) leads, du.id duid" +
|
219 |
" COALESCE(ld.leads,0) leads, du.id duid" +
|
| 196 |
" FROM (SELECT DISTINCT auth_user_id FROM `user`.beat WHERE active=1 AND auth_user_id IN (:auth)) o" +
|
220 |
" FROM (SELECT DISTINCT auth_user_id FROM `user`.beat WHERE active=1 AND auth_user_id IN (:auth)) o" +
|
| 197 |
" JOIN auth.auth_user au ON au.id=o.auth_user_id" +
|
221 |
" JOIN auth.auth_user au ON au.id=o.auth_user_id" +
|
| 198 |
" LEFT JOIN dtr.users du ON du.email=au.email_id" +
|
222 |
" LEFT JOIN dtr.users du ON du.email=au.email_id" +
|
| 199 |
" LEFT JOIN (SELECT auth_user_id, MIN(escalation_type) lvl FROM cs.position WHERE category_id=4 GROUP BY auth_user_id) lvl ON lvl.auth_user_id=au.id" +
|
223 |
" LEFT JOIN (SELECT auth_user_id, MIN(escalation_type) lvl FROM cs.position WHERE category_id=4 GROUP BY auth_user_id) lvl ON lvl.auth_user_id=au.id" +
|
| 200 |
" LEFT JOIN (SELECT b.auth_user_id uid, COUNT(*) planned FROM `user`.beat_schedule sc" +
|
224 |
" LEFT JOIN (SELECT b.auth_user_id uid, COUNT(*) planned FROM `user`.beat_schedule sc" +
|
| 201 |
" JOIN `user`.beat b ON b.id=sc.beat_id JOIN `user`.beat_route r ON r.beat_id=sc.beat_id AND r.day_number=sc.day_number AND r.active=1" +
|
225 |
" JOIN `user`.beat b ON b.id=sc.beat_id AND b.active=1 JOIN `user`.beat_route r ON r.beat_id=sc.beat_id AND r.day_number=sc.day_number" +
|
| 202 |
" WHERE sc.start_date>=:ms AND sc.start_date<=:me GROUP BY b.auth_user_id) pl ON pl.uid=au.id" +
|
226 |
" WHERE sc.start_date>=:ms AND sc.start_date<=:me GROUP BY b.auth_user_id) pl ON pl.uid=au.id" +
|
| - |
|
227 |
" LEFT JOIN (SELECT b.auth_user_id uid, COUNT(*) planned FROM `user`.beat_schedule sc" +
|
| - |
|
228 |
" JOIN `user`.beat b ON b.id=sc.beat_id AND b.active=1 JOIN `user`.lead_route lr ON lr.beat_id=sc.beat_id AND lr.schedule_date=sc.start_date AND lr.status='APPROVED'" +
|
| - |
|
229 |
" WHERE sc.start_date>=:ms AND sc.start_date<=:me GROUP BY b.auth_user_id) plL ON plL.uid=au.id" +
|
| 203 |
" LEFT JOIN (SELECT user_id uid, COUNT(*) done FROM auth.location_tracking" +
|
230 |
" LEFT JOIN (SELECT user_id uid, COUNT(*) done FROM auth.location_tracking" +
|
| 204 |
" WHERE task_date>=:ms AND task_date<=:me AND task_type='franchisee-visit' AND mark_type LIKE 'CHECKIN%' GROUP BY user_id) ac ON ac.uid=du.id" +
|
231 |
" WHERE task_date>=:ms AND task_date<=:me AND task_id<>0 AND mark_type LIKE 'CHECKIN%' GROUP BY user_id) ac ON ac.uid=du.id" +
|
| 205 |
" LEFT JOIN (SELECT user_id uid, ROUND(AVG(NULLIF(TIME_TO_SEC(time_spent),0))/60,0) disc_min FROM auth.location_tracking" +
|
232 |
" LEFT JOIN (SELECT user_id uid, ROUND(AVG(NULLIF(TIME_TO_SEC(time_spent),0))/60,0) disc_min FROM auth.location_tracking" +
|
| 206 |
" WHERE task_date>=:ms AND task_date<=:me AND task_type='franchisee-visit' AND mark_type LIKE '%CHECKOUT%' GROUP BY user_id) ds ON ds.uid=du.id" +
|
233 |
" WHERE task_date>=:ms AND task_date<=:me AND task_id<>0 AND mark_type LIKE '%CHECKOUT%' GROUP BY user_id) ds ON ds.uid=du.id" +
|
| 207 |
" LEFT JOIN (SELECT user_id uid, SUM(GREATEST(TIME_TO_SEC(check_out_time)-TIME_TO_SEC(check_in_time),0)) work_sec FROM auth.location_tracking" +
|
234 |
" LEFT JOIN (SELECT user_id uid, SUM(GREATEST(TIME_TO_SEC(check_out_time)-TIME_TO_SEC(check_in_time),0)) work_sec FROM auth.location_tracking" +
|
| 208 |
" WHERE task_date>=:ms AND task_date<=:me AND task_id=0 AND check_out_time IS NOT NULL AND check_out_time<>'00:00:00' AND check_in_time IS NOT NULL AND check_in_time<>'00:00:00' GROUP BY user_id) wk ON wk.uid=du.id" +
|
235 |
" WHERE task_date>=:ms AND task_date<=:me AND task_id=0 AND check_out_time IS NOT NULL AND check_out_time<>'00:00:00' AND check_in_time IS NOT NULL AND check_in_time<>'00:00:00' GROUP BY user_id) wk ON wk.uid=du.id" +
|
| 209 |
" LEFT JOIN (SELECT auth_id uid, COUNT(*) leads FROM `user`.lead WHERE created_timestamp>=:ms AND created_timestamp<=CONCAT(:me,' 23:59:59') GROUP BY auth_id) ld ON ld.uid=au.id" +
|
236 |
" LEFT JOIN (SELECT auth_id uid, COUNT(*) leads FROM `user`.lead WHERE created_timestamp>=:ms AND created_timestamp<=CONCAT(:me,' 23:59:59') GROUP BY auth_id) ld ON ld.uid=au.id" +
|
| 210 |
" ORDER BY done DESC, planned DESC");
|
237 |
" ORDER BY done DESC, planned DESC");
|
| 211 |
q.setParameter("ms", ms).setParameter("me", me).setParameterList("auth", auth);
|
238 |
q.setParameter("ms", ms).setParameter("me", me).setParameterList("auth", auth);
|
| Line 217... |
Line 244... |
| 217 |
|
244 |
|
| 218 |
List<Map<String, Object>> list = new ArrayList<>();
|
245 |
List<Map<String, Object>> list = new ArrayList<>();
|
| 219 |
for (Object o : q.getResultList()) {
|
246 |
for (Object o : q.getResultList()) {
|
| 220 |
Object[] r = (Object[]) o;
|
247 |
Object[] r = (Object[]) o;
|
| 221 |
long planned = asLong(r[3]), done = asLong(r[4]);
|
248 |
long planned = asLong(r[3]), done = asLong(r[4]);
|
| - |
|
249 |
Integer duid = (r[8] == null) ? null : ((Number) r[8]).intValue();
|
| 222 |
Map<String, Object> m = new LinkedHashMap<>();
|
250 |
Map<String, Object> m = new LinkedHashMap<>();
|
| - |
|
251 |
m.put("authUserId", asLong(r[0]));
|
| - |
|
252 |
m.put("userId", duid == null ? 0 : duid);
|
| 223 |
m.put("name", asStr(r[1]));
|
253 |
m.put("name", asStr(r[1]));
|
| 224 |
m.put("level", r[2] == null ? "-" : asStr(r[2]));
|
254 |
m.put("level", r[2] == null ? "-" : asStr(r[2]));
|
| 225 |
m.put("planned", planned);
|
255 |
m.put("planned", planned);
|
| 226 |
m.put("done", done);
|
256 |
m.put("done", done);
|
| 227 |
m.put("adherencePct", planned == 0 ? 0 : Math.round(100.0 * done / planned));
|
257 |
m.put("adherencePct", planned == 0 ? 0 : Math.round(100.0 * done / planned));
|
| 228 |
m.put("discussionMin", asLong(r[5]));
|
258 |
m.put("discussionMin", asLong(r[5]));
|
| 229 |
m.put("workingHrs", round1(asDouble(r[6]) / 3600.0));
|
259 |
m.put("workingHrs", round1(asDouble(r[6]) / 3600.0));
|
| 230 |
m.put("leads", asLong(r[7]));
|
260 |
m.put("leads", asLong(r[7]));
|
| 231 |
Integer duid = (r[8] == null) ? null : ((Number) r[8]).intValue();
|
- |
|
| 232 |
m.put("geoFlags", duid == null ? 0 : geoByUser.getOrDefault(duid, 0));
|
261 |
m.put("geoFlags", duid == null ? 0 : geoByUser.getOrDefault(duid, 0));
|
| 233 |
list.add(m);
|
262 |
list.add(m);
|
| 234 |
}
|
263 |
}
|
| 235 |
return list;
|
264 |
return list;
|
| 236 |
}
|
265 |
}
|
| Line 285... |
Line 314... |
| 285 |
// ---- Working-hour utilization ------------------------------------------
|
314 |
// ---- Working-hour utilization ------------------------------------------
|
| 286 |
|
315 |
|
| 287 |
private Map<String, Object> utilization(Session s, String ms, String me, List<Integer> dtr) {
|
316 |
private Map<String, Object> utilization(Session s, String ms, String me, List<Integer> dtr) {
|
| 288 |
NativeQuery<?> q = s.createNativeQuery(
|
317 |
NativeQuery<?> q = s.createNativeQuery(
|
| 289 |
"SELECT" +
|
318 |
"SELECT" +
|
| 290 |
" (SELECT COALESCE(SUM(TIME_TO_SEC(time_spent)),0) FROM auth.location_tracking WHERE task_date>=:ms AND task_date<=:me AND task_type='franchisee-visit' AND user_id IN (:dtr)) instore," +
|
319 |
" (SELECT COALESCE(SUM(TIME_TO_SEC(time_spent)),0) FROM auth.location_tracking WHERE task_date>=:ms AND task_date<=:me AND task_id<>0 AND user_id IN (:dtr)) instore," +
|
| 291 |
" (SELECT COALESCE(SUM(TIME_TO_SEC(transit_time)),0) FROM auth.location_tracking WHERE task_date>=:ms AND task_date<=:me AND task_type='franchisee-visit' AND user_id IN (:dtr)) travel," +
|
320 |
" (SELECT COALESCE(SUM(TIME_TO_SEC(transit_time)),0) FROM auth.location_tracking WHERE task_date>=:ms AND task_date<=:me AND task_id<>0 AND user_id IN (:dtr)) travel," +
|
| 292 |
" (SELECT COALESCE(SUM(GREATEST(TIME_TO_SEC(check_out_time)-TIME_TO_SEC(check_in_time),0)),0) FROM auth.location_tracking WHERE task_date>=:ms AND task_date<=:me AND task_id=0 AND check_out_time IS NOT NULL AND check_out_time<>'00:00:00' AND check_in_time IS NOT NULL AND check_in_time<>'00:00:00' AND user_id IN (:dtr)) working");
|
321 |
" (SELECT COALESCE(SUM(GREATEST(TIME_TO_SEC(check_out_time)-TIME_TO_SEC(check_in_time),0)),0) FROM auth.location_tracking WHERE task_date>=:ms AND task_date<=:me AND task_id=0 AND check_out_time IS NOT NULL AND check_out_time<>'00:00:00' AND check_in_time IS NOT NULL AND check_in_time<>'00:00:00' AND user_id IN (:dtr)) working");
|
| 293 |
q.setParameter("ms", ms).setParameter("me", me).setParameterList("dtr", dtr);
|
322 |
q.setParameter("ms", ms).setParameter("me", me).setParameterList("dtr", dtr);
|
| 294 |
Object[] r = (Object[]) q.uniqueResult();
|
323 |
Object[] r = (Object[]) q.uniqueResult();
|
| 295 |
double instore = asDouble(r[0]), travel = asDouble(r[1]), working = asDouble(r[2]);
|
324 |
double instore = asDouble(r[0]), travel = asDouble(r[1]), working = asDouble(r[2]);
|
| 296 |
if (working < instore + travel) working = instore + travel; // guard: working must cover its parts
|
325 |
if (working < instore + travel) working = instore + travel; // guard: working must cover its parts
|
| Line 331... |
Line 360... |
| 331 |
m.put("systemPct", total == 0 ? 0 : Math.round(100.0 * system / total));
|
360 |
m.put("systemPct", total == 0 ? 0 : Math.round(100.0 * system / total));
|
| 332 |
m.put("humanPct", total == 0 ? 0 : Math.round(100.0 * human / total));
|
361 |
m.put("humanPct", total == 0 ? 0 : Math.round(100.0 * human / total));
|
| 333 |
return m;
|
362 |
return m;
|
| 334 |
}
|
363 |
}
|
| 335 |
|
364 |
|
| - |
|
365 |
// ---- Drill-down lists ---------------------------------------------------
|
| - |
|
366 |
|
| - |
|
367 |
// Per-executive PJP coverage (with/without a plan in range) — powers the
|
| - |
|
368 |
// PJP coverage KPI and the coverage-by-level bar drill-downs.
|
| - |
|
369 |
private List<Map<String, Object>> coverageDetail(Session s, String ms, String me, List<Integer> auth) {
|
| - |
|
370 |
NativeQuery<?> q = s.createNativeQuery(
|
| - |
|
371 |
"SELECT au.id uid, TRIM(CONCAT(au.first_name,' ',COALESCE(au.last_name,''))) nm, p.lvl," +
|
| - |
|
372 |
" MAX(CASE WHEN sc.id IS NOT NULL THEN 1 ELSE 0 END) has_plan" +
|
| - |
|
373 |
" FROM (SELECT auth_user_id, MIN(escalation_type) lvl FROM cs.position WHERE category_id=4 AND auth_user_id IN (:auth) GROUP BY auth_user_id) p" +
|
| - |
|
374 |
" JOIN auth.auth_user au ON au.id=p.auth_user_id" +
|
| - |
|
375 |
" LEFT JOIN `user`.beat b ON b.auth_user_id=p.auth_user_id AND b.active=1" +
|
| - |
|
376 |
" LEFT JOIN `user`.beat_schedule sc ON sc.beat_id=b.id AND sc.start_date>=:ms AND sc.start_date<=:me" +
|
| - |
|
377 |
" GROUP BY au.id, nm, p.lvl ORDER BY p.lvl, nm");
|
| - |
|
378 |
q.setParameter("ms", ms).setParameter("me", me).setParameterList("auth", auth);
|
| - |
|
379 |
List<Map<String, Object>> list = new ArrayList<>();
|
| - |
|
380 |
for (Object o : q.getResultList()) {
|
| - |
|
381 |
Object[] r = (Object[]) o;
|
| - |
|
382 |
Map<String, Object> m = new LinkedHashMap<>();
|
| - |
|
383 |
m.put("authUserId", asLong(r[0]));
|
| - |
|
384 |
m.put("name", asStr(r[1]));
|
| - |
|
385 |
m.put("level", asStr(r[2]));
|
| - |
|
386 |
m.put("hasPlan", asLong(r[3]) > 0);
|
| - |
|
387 |
list.add(m);
|
| - |
|
388 |
}
|
| - |
|
389 |
return list;
|
| - |
|
390 |
}
|
| - |
|
391 |
|
| - |
|
392 |
// Every deferral in range (exec, partner, reason, date, status, system flag).
|
| - |
|
393 |
private List<Map<String, Object>> deferralsList(Session s, String ms, String me, List<Integer> auth) {
|
| - |
|
394 |
NativeQuery<?> q = s.createNativeQuery(
|
| - |
|
395 |
"SELECT TRIM(CONCAT(au.first_name,' ',COALESCE(au.last_name,''))) nm, d.display_name, d.reason," +
|
| - |
|
396 |
" d.deferred_date, d.status, d.task_type," +
|
| - |
|
397 |
" (d.reason LIKE 'Beat missed%' OR d.reason LIKE 'Not marked%') sys" +
|
| - |
|
398 |
" FROM `user`.beat_deferred_visit d JOIN auth.auth_user au ON au.id=d.auth_user_id" +
|
| - |
|
399 |
" WHERE d.deferred_date>=:ms AND d.deferred_date<=:me AND d.auth_user_id IN (:auth)" +
|
| - |
|
400 |
" ORDER BY d.deferred_date, nm");
|
| - |
|
401 |
q.setParameter("ms", ms).setParameter("me", me).setParameterList("auth", auth);
|
| - |
|
402 |
List<Map<String, Object>> list = new ArrayList<>();
|
| - |
|
403 |
for (Object o : q.getResultList()) {
|
| - |
|
404 |
Object[] r = (Object[]) o;
|
| - |
|
405 |
Map<String, Object> m = new LinkedHashMap<>();
|
| - |
|
406 |
m.put("exec", asStr(r[0]));
|
| - |
|
407 |
m.put("partner", asStr(r[1]));
|
| - |
|
408 |
m.put("reason", asStr(r[2]));
|
| - |
|
409 |
m.put("date", asStr(r[3]));
|
| - |
|
410 |
m.put("status", asStr(r[4]));
|
| - |
|
411 |
m.put("taskType", asStr(r[5]));
|
| - |
|
412 |
m.put("system", asLong(r[6]) > 0);
|
| - |
|
413 |
list.add(m);
|
| - |
|
414 |
}
|
| - |
|
415 |
return list;
|
| - |
|
416 |
}
|
| - |
|
417 |
|
| - |
|
418 |
// Every lead created in range (exec, outlet, city, date, status).
|
| - |
|
419 |
private List<Map<String, Object>> leadsList(Session s, String ms, String me, List<Integer> auth) {
|
| - |
|
420 |
NativeQuery<?> q = s.createNativeQuery(
|
| - |
|
421 |
"SELECT TRIM(CONCAT(au.first_name,' ',COALESCE(au.last_name,''))) nm," +
|
| - |
|
422 |
" COALESCE(NULLIF(l.outlet_name,''), TRIM(CONCAT(COALESCE(l.first_name,''),' ',COALESCE(l.last_name,'')))) lead," +
|
| - |
|
423 |
" l.city, DATE(l.created_timestamp) dt, l.status" +
|
| - |
|
424 |
" FROM `user`.lead l JOIN auth.auth_user au ON au.id=l.auth_id" +
|
| - |
|
425 |
" WHERE l.created_timestamp>=:ms AND l.created_timestamp<=CONCAT(:me,' 23:59:59') AND l.auth_id IN (:auth)" +
|
| - |
|
426 |
" ORDER BY dt, nm");
|
| - |
|
427 |
q.setParameter("ms", ms).setParameter("me", me).setParameterList("auth", auth);
|
| - |
|
428 |
List<Map<String, Object>> list = new ArrayList<>();
|
| - |
|
429 |
for (Object o : q.getResultList()) {
|
| - |
|
430 |
Object[] r = (Object[]) o;
|
| - |
|
431 |
Map<String, Object> m = new LinkedHashMap<>();
|
| - |
|
432 |
m.put("exec", asStr(r[0]));
|
| - |
|
433 |
m.put("lead", asStr(r[1]));
|
| - |
|
434 |
m.put("city", asStr(r[2]));
|
| - |
|
435 |
m.put("date", asStr(r[3]));
|
| - |
|
436 |
m.put("status", asStr(r[4]));
|
| - |
|
437 |
list.add(m);
|
| - |
|
438 |
}
|
| - |
|
439 |
return list;
|
| - |
|
440 |
}
|
| - |
|
441 |
|
| 336 |
// ---- Travel & route quality --------------------------------------------
|
442 |
// ---- Travel & route quality --------------------------------------------
|
| 337 |
|
443 |
|
| 338 |
private Map<String, Object> travel(Session s, String ms, String me, List<Integer> auth, List<Integer> dtr) {
|
444 |
private Map<String, Object> travel(Session s, String ms, String me, List<Integer> auth, List<Integer> dtr) {
|
| 339 |
Map<String, Object> m = new LinkedHashMap<>();
|
445 |
Map<String, Object> m = new LinkedHashMap<>();
|
| 340 |
|
446 |
|