Subversion Repositories SmartDukaan

Rev

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

Rev 36897 Rev 36913
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