|
|
@@ -5,8 +5,6 @@ import com.sundot.airport.common.core.domain.entity.SysDept;
|
|
5
|
5
|
import com.sundot.airport.common.core.domain.entity.SysRole;
|
|
6
|
6
|
import com.sundot.airport.common.core.domain.model.LoginUser;
|
|
7
|
7
|
import com.sundot.airport.common.enums.RoleTypeEnum;
|
|
8
|
|
-import com.sundot.airport.common.core.domain.SysAnalysisReportParamDto;
|
|
9
|
|
-import com.sundot.airport.common.utils.DateRangeQueryUtils;
|
|
10
|
8
|
import com.sundot.airport.common.utils.SecurityUtils;
|
|
11
|
9
|
import com.sundot.airport.exam.adapter.DailyTaskToExamAdapter;
|
|
12
|
10
|
import com.sundot.airport.exam.common.ClientTypeConstant;
|
|
|
@@ -34,10 +32,11 @@ import org.springframework.validation.annotation.Validated;
|
|
34
|
32
|
import org.springframework.web.bind.annotation.*;
|
|
35
|
33
|
|
|
36
|
34
|
import javax.annotation.Resource;
|
|
|
35
|
+import java.math.BigDecimal;
|
|
|
36
|
+import java.math.RoundingMode;
|
|
37
|
37
|
import java.sql.Date;
|
|
38
|
38
|
import java.text.SimpleDateFormat;
|
|
39
|
39
|
import java.time.LocalDate;
|
|
40
|
|
-import java.time.format.DateTimeFormatter;
|
|
41
|
40
|
import java.util.*;
|
|
42
|
41
|
import java.util.stream.Collectors;
|
|
43
|
42
|
|
|
|
@@ -292,16 +291,27 @@ public class DailyExamController {
|
|
292
|
291
|
|
|
293
|
292
|
/**
|
|
294
|
293
|
* 抽问抽答完成趋势
|
|
295
|
|
- * 美兰4级架构:STATION > 大队(BRIGADE) > 主管(MANAGER) > 班组(TEAMS)
|
|
296
|
|
- * 站长/管理员/质检科:各大队完成趋势折线
|
|
297
|
|
- * 大队长(经理/行政):本大队各主管完成趋势折线
|
|
298
|
|
- * 主管:本主管室完成趋势(单条折线)
|
|
299
|
|
- * 班组长/安检员:show=false
|
|
|
294
|
+ * 美兰4级架构:STATION > 大队(BRIGADE) > 主管(MANAGER) > 班组
|
|
|
295
|
+ * 始终返回多期对比数据:
|
|
|
296
|
+ * YEAR:[去年, 今年] 含同比
|
|
|
297
|
+ * QUARTER:[去年同季度, 上季度, 本季度] 含同比+环比
|
|
|
298
|
+ * MONTH:[去年同月, 上月, 本月] 含同比+环比
|
|
300
|
299
|
*
|
|
301
|
|
- * @param param dateRangeQueryType=YEAR|QUARTER|MONTH,year,quarter,month
|
|
|
300
|
+ * @param dateRangeQueryType 时间类型:YEAR/QUARTER/MONTH,默认 MONTH
|
|
|
301
|
+ * @param year 年份,默认当前年
|
|
|
302
|
+ * @param quarter 季度 1-4,dateRangeQueryType=QUARTER 时有效
|
|
|
303
|
+ * @param month 月份 1-12,dateRangeQueryType=MONTH 时有效
|
|
|
304
|
+ * @param scopeType 统计范围:STATION/BRIGADE/MANAGER/USER,不传则按登录角色自动判断
|
|
|
305
|
+ * @param scopeId 统计范围ID(deptId 或 userId)
|
|
302
|
306
|
*/
|
|
303
|
307
|
@GetMapping("/completion-trend")
|
|
304
|
|
- public HttpResult<Map<String, Object>> getCompletionTrend(SysAnalysisReportParamDto param) {
|
|
|
308
|
+ public HttpResult<Map<String, Object>> getCompletionTrend(
|
|
|
309
|
+ @RequestParam(required = false, defaultValue = "MONTH") String dateRangeQueryType,
|
|
|
310
|
+ @RequestParam(required = false) Integer year,
|
|
|
311
|
+ @RequestParam(required = false) Integer quarter,
|
|
|
312
|
+ @RequestParam(required = false) Integer month,
|
|
|
313
|
+ @RequestParam(required = false) String scopeType,
|
|
|
314
|
+ @RequestParam(required = false) Long scopeId) {
|
|
305
|
315
|
|
|
306
|
316
|
try {
|
|
307
|
317
|
LoginUser loginUser = SecurityUtils.getLoginUser();
|
|
|
@@ -316,100 +326,29 @@ public class DailyExamController {
|
|
316
|
326
|
|| roleKeys.contains(RoleTypeEnum.zhijianke.getCode());
|
|
317
|
327
|
boolean isBrigade = !isStation && (roleKeys.contains(RoleTypeEnum.jingli.getCode())
|
|
318
|
328
|
|| roleKeys.contains(RoleTypeEnum.xingzheng.getCode()));
|
|
319
|
|
- boolean isKezhang = !isStation && !isBrigade && roleKeys.contains(RoleTypeEnum.kezhang.getCode());
|
|
|
329
|
+ boolean isManager = !isStation && !isBrigade && roleKeys.contains(RoleTypeEnum.kezhang.getCode());
|
|
320
|
330
|
|
|
321
|
|
- // 班组长及以下不显示此tab
|
|
322
|
|
- if (!isStation && !isBrigade && !isKezhang) {
|
|
|
331
|
+ // 班组长及以下不显示(未传 scopeType 且无管理权限)
|
|
|
332
|
+ if (scopeType == null && !isStation && !isBrigade && !isManager) {
|
|
323
|
333
|
Map<String, Object> noShow = new HashMap<>();
|
|
324
|
334
|
noShow.put("show", false);
|
|
325
|
335
|
return HttpResult.success(noShow);
|
|
326
|
336
|
}
|
|
327
|
337
|
|
|
328
|
|
- LocalDate[] range = resolveDateRange(param);
|
|
329
|
|
- LocalDate start = range[0];
|
|
330
|
|
- LocalDate end = range[1];
|
|
331
|
|
- boolean byMonth = "YEAR".equals(param != null ? param.getDateRangeQueryType() : null);
|
|
332
|
|
- List<String> xAxis = buildXAxis(start, end, byMonth);
|
|
333
|
|
- List<Map<String, Object>> series = new ArrayList<>();
|
|
334
|
|
-
|
|
335
|
|
- if (isStation) {
|
|
336
|
|
- // 站长:查该站所有子部门,按大队分组
|
|
337
|
|
- List<SysDept> subDepts = sysDeptMapper.selectChildrenDeptById(userDeptId);
|
|
338
|
|
- if (subDepts == null) subDepts = Collections.emptyList();
|
|
339
|
|
-
|
|
340
|
|
- // 大队(站的直接下级)
|
|
341
|
|
- List<SysDept> brigadeDepts = subDepts.stream()
|
|
342
|
|
- .filter(d -> userDeptId.equals(d.getParentId()) && "0".equals(d.getDelFlag()))
|
|
343
|
|
- .collect(Collectors.toList());
|
|
344
|
|
-
|
|
345
|
|
- // 构建 主管deptId -> 所属大队deptId 的映射
|
|
346
|
|
- Map<Long, Long> managerToBrigadeId = new HashMap<>();
|
|
347
|
|
- for (SysDept d : subDepts) {
|
|
348
|
|
- if ("0".equals(d.getDelFlag()) && d.getParentId() != null && !userDeptId.equals(d.getParentId())) {
|
|
349
|
|
- // 非直接下级,其parentId为某个大队,记录映射(主管级别)
|
|
350
|
|
- managerToBrigadeId.put(d.getDeptId(), d.getParentId());
|
|
351
|
|
- }
|
|
352
|
|
- }
|
|
353
|
|
-
|
|
354
|
|
- // 查全站所有任务
|
|
355
|
|
- Set<Long> allDeptIds = new HashSet<>();
|
|
356
|
|
- allDeptIds.add(userDeptId);
|
|
357
|
|
- subDepts.forEach(d -> allDeptIds.add(d.getDeptId()));
|
|
358
|
|
- List<DailyTask> allTasks = queryTasksByDeptIds(allDeptIds, start, end);
|
|
359
|
|
-
|
|
360
|
|
- // 按大队分组生成折线
|
|
361
|
|
- for (SysDept brigade : brigadeDepts) {
|
|
362
|
|
- Long brigadeId = brigade.getDeptId();
|
|
363
|
|
- List<DailyTask> brigadeTasks = allTasks.stream()
|
|
364
|
|
- .filter(t -> {
|
|
365
|
|
- Long managerId = t.getDtDepartmentId();
|
|
366
|
|
- if (managerId == null) return false;
|
|
367
|
|
- Long parentBrigadeId = managerToBrigadeId.get(managerId);
|
|
368
|
|
- return brigadeId.equals(parentBrigadeId);
|
|
369
|
|
- })
|
|
370
|
|
- .collect(Collectors.toList());
|
|
371
|
|
- Map<String, Object> item = new LinkedHashMap<>();
|
|
372
|
|
- item.put("name", brigade.getDeptName());
|
|
373
|
|
- item.put("deptId", brigadeId);
|
|
374
|
|
- item.put("data", buildSeriesData(xAxis, brigadeTasks, byMonth));
|
|
375
|
|
- series.add(item);
|
|
376
|
|
- }
|
|
377
|
|
-
|
|
378
|
|
- } else if (isBrigade) {
|
|
379
|
|
- // 大队长:查本大队下各主管的任务
|
|
380
|
|
- List<SysDept> subDepts = sysDeptMapper.selectChildrenDeptById(userDeptId);
|
|
381
|
|
- if (subDepts == null) subDepts = Collections.emptyList();
|
|
382
|
|
-
|
|
383
|
|
- // 主管(大队的直接下级)
|
|
384
|
|
- List<SysDept> managerDepts = subDepts.stream()
|
|
385
|
|
- .filter(d -> userDeptId.equals(d.getParentId()) && "0".equals(d.getDelFlag()))
|
|
386
|
|
- .collect(Collectors.toList());
|
|
387
|
|
-
|
|
388
|
|
- for (SysDept manager : managerDepts) {
|
|
389
|
|
- List<DailyTask> tasks = queryTasksByDepartmentId(manager.getDeptId(), start, end);
|
|
390
|
|
- Map<String, Object> item = new LinkedHashMap<>();
|
|
391
|
|
- item.put("name", manager.getDeptName());
|
|
392
|
|
- item.put("deptId", manager.getDeptId());
|
|
393
|
|
- item.put("data", buildSeriesData(xAxis, tasks, byMonth));
|
|
394
|
|
- series.add(item);
|
|
395
|
|
- }
|
|
|
338
|
+ int curYear = (year != null) ? year : LocalDate.now().getYear();
|
|
|
339
|
+ int curQuarter = (quarter != null) ? quarter : ((LocalDate.now().getMonthValue() - 1) / 3 + 1);
|
|
|
340
|
+ int curMonth = (month != null) ? month : LocalDate.now().getMonthValue();
|
|
396
|
341
|
|
|
397
|
|
- } else {
|
|
398
|
|
- // 主管:本主管室所有任务,一条折线
|
|
399
|
|
- List<DailyTask> tasks = queryTasksByDepartmentId(userDeptId, start, end);
|
|
400
|
|
- String deptName = loginUser.getUser().getDept() != null
|
|
401
|
|
- ? loginUser.getUser().getDept().getDeptName() : "本主管";
|
|
402
|
|
- Map<String, Object> item = new LinkedHashMap<>();
|
|
403
|
|
- item.put("name", deptName);
|
|
404
|
|
- item.put("deptId", userDeptId);
|
|
405
|
|
- item.put("data", buildSeriesData(xAxis, tasks, byMonth));
|
|
406
|
|
- series.add(item);
|
|
407
|
|
- }
|
|
|
342
|
+ List<LocalDate[]> periods = buildPeriods(dateRangeQueryType, curYear, curQuarter, curMonth);
|
|
|
343
|
+ List<String> xAxis = buildXAxisLabels(dateRangeQueryType, curYear, curQuarter, curMonth);
|
|
408
|
344
|
|
|
409
|
345
|
Map<String, Object> result = new LinkedHashMap<>();
|
|
410
|
346
|
result.put("show", true);
|
|
411
|
347
|
result.put("xAxis", xAxis);
|
|
412
|
|
- result.put("series", series);
|
|
|
348
|
+ result.put("series", buildComparisonSeries(
|
|
|
349
|
+ scopeType, scopeId, periods, dateRangeQueryType,
|
|
|
350
|
+ isStation, isBrigade, isManager, userDeptId, loginUser));
|
|
|
351
|
+
|
|
413
|
352
|
return HttpResult.success(result);
|
|
414
|
353
|
|
|
415
|
354
|
} catch (Exception e) {
|
|
|
@@ -419,75 +358,273 @@ public class DailyExamController {
|
|
419
|
358
|
}
|
|
420
|
359
|
|
|
421
|
360
|
/**
|
|
422
|
|
- * 根据 SysAnalysisReportParamDto 计算起止日期
|
|
423
|
|
- * YEAR → 全年;QUARTER → 指定季度;MONTH → 指定月份;默认 → 当月
|
|
|
361
|
+ * 构建各期日期范围列表
|
|
|
362
|
+ * YEAR:[去年, 今年]
|
|
|
363
|
+ * QUARTER:[去年同季度, 上季度, 本季度]
|
|
|
364
|
+ * MONTH:[去年同月, 上月, 本月]
|
|
424
|
365
|
*/
|
|
425
|
|
- private LocalDate[] resolveDateRange(SysAnalysisReportParamDto param) {
|
|
426
|
|
- LocalDate today = LocalDate.now();
|
|
427
|
|
- int year = (param != null && param.getYear() != null) ? param.getYear() : today.getYear();
|
|
428
|
|
- String type = (param != null && param.getDateRangeQueryType() != null) ? param.getDateRangeQueryType() : "MONTH";
|
|
429
|
|
- com.sundot.airport.common.core.domain.SysAnalysisReportDateRangeDto range;
|
|
430
|
|
- switch (type) {
|
|
|
366
|
+ private List<LocalDate[]> buildPeriods(String type, int year, int quarter, int month) {
|
|
|
367
|
+ List<LocalDate[]> periods = new ArrayList<>();
|
|
|
368
|
+ switch (type == null ? "MONTH" : type) {
|
|
|
369
|
+ case "YEAR":
|
|
|
370
|
+ periods.add(new LocalDate[]{LocalDate.of(year - 1, 1, 1), LocalDate.of(year - 1, 12, 31)});
|
|
|
371
|
+ periods.add(new LocalDate[]{LocalDate.of(year, 1, 1), LocalDate.of(year, 12, 31)});
|
|
|
372
|
+ break;
|
|
|
373
|
+ case "QUARTER":
|
|
|
374
|
+ periods.add(resolveDateRange("QUARTER", year - 1, quarter, null));
|
|
|
375
|
+ int prevQ = quarter - 1;
|
|
|
376
|
+ int prevQYear = year;
|
|
|
377
|
+ if (prevQ < 1) { prevQ = 4; prevQYear = year - 1; }
|
|
|
378
|
+ periods.add(resolveDateRange("QUARTER", prevQYear, prevQ, null));
|
|
|
379
|
+ periods.add(resolveDateRange("QUARTER", year, quarter, null));
|
|
|
380
|
+ break;
|
|
|
381
|
+ case "MONTH":
|
|
|
382
|
+ default:
|
|
|
383
|
+ periods.add(resolveDateRange("MONTH", year - 1, null, month));
|
|
|
384
|
+ int prevM = month - 1;
|
|
|
385
|
+ int prevMYear = year;
|
|
|
386
|
+ if (prevM < 1) { prevM = 12; prevMYear = year - 1; }
|
|
|
387
|
+ periods.add(resolveDateRange("MONTH", prevMYear, null, prevM));
|
|
|
388
|
+ periods.add(resolveDateRange("MONTH", year, null, month));
|
|
|
389
|
+ break;
|
|
|
390
|
+ }
|
|
|
391
|
+ return periods;
|
|
|
392
|
+ }
|
|
|
393
|
+
|
|
|
394
|
+ /**
|
|
|
395
|
+ * 构建 X 轴中文标签列表
|
|
|
396
|
+ */
|
|
|
397
|
+ private List<String> buildXAxisLabels(String type, int year, int quarter, int month) {
|
|
|
398
|
+ List<String> labels = new ArrayList<>();
|
|
|
399
|
+ switch (type == null ? "MONTH" : type) {
|
|
431
|
400
|
case "YEAR":
|
|
432
|
|
- range = DateRangeQueryUtils.getYearRange(year, null);
|
|
|
401
|
+ labels.add((year - 1) + "年");
|
|
|
402
|
+ labels.add(year + "年");
|
|
433
|
403
|
break;
|
|
434
|
404
|
case "QUARTER":
|
|
435
|
|
- int quarter = (param.getQuarter() != null) ? param.getQuarter() : 1;
|
|
436
|
|
- range = DateRangeQueryUtils.getQuarterRange(year, quarter, null);
|
|
|
405
|
+ labels.add((year - 1) + "年第" + quarter + "季度");
|
|
|
406
|
+ int prevQ = quarter - 1;
|
|
|
407
|
+ int prevQYear = year;
|
|
|
408
|
+ if (prevQ < 1) { prevQ = 4; prevQYear = year - 1; }
|
|
|
409
|
+ labels.add(prevQYear + "年第" + prevQ + "季度");
|
|
|
410
|
+ labels.add(year + "年第" + quarter + "季度");
|
|
437
|
411
|
break;
|
|
438
|
|
- default: // MONTH
|
|
439
|
|
- int month = (param != null && param.getMonth() != null) ? param.getMonth() : today.getMonthValue();
|
|
440
|
|
- range = DateRangeQueryUtils.getMonthRange(year, month, null);
|
|
|
412
|
+ case "MONTH":
|
|
|
413
|
+ default:
|
|
|
414
|
+ labels.add((year - 1) + "年" + month + "月");
|
|
|
415
|
+ int prevM = month - 1;
|
|
|
416
|
+ int prevMYear = year;
|
|
|
417
|
+ if (prevM < 1) { prevM = 12; prevMYear = year - 1; }
|
|
|
418
|
+ labels.add(prevMYear + "年" + prevM + "月");
|
|
|
419
|
+ labels.add(year + "年" + month + "月");
|
|
441
|
420
|
break;
|
|
442
|
421
|
}
|
|
443
|
|
- return new LocalDate[]{
|
|
444
|
|
- ((java.sql.Date) range.getStartDate()).toLocalDate(),
|
|
445
|
|
- ((java.sql.Date) range.getEndDate()).toLocalDate()
|
|
446
|
|
- };
|
|
|
422
|
+ return labels;
|
|
447
|
423
|
}
|
|
448
|
424
|
|
|
449
|
425
|
/**
|
|
450
|
|
- * 生成X轴标签列表
|
|
451
|
|
- * byMonth=true:每月一个标签(格式 yyyy-MM);false:每天一个标签(格式 yyyy-MM-dd)
|
|
|
426
|
+ * 构建多期对比 series 列表(适配美兰4级架构)
|
|
|
427
|
+ * STATION:各大队各一条 series + 全站汇总
|
|
|
428
|
+ * BRIGADE:该大队各主管各一条 series
|
|
|
429
|
+ * MANAGER/USER:单条 series
|
|
452
|
430
|
*/
|
|
453
|
|
- private List<String> buildXAxis(LocalDate start, LocalDate end, boolean byMonth) {
|
|
454
|
|
- List<String> xAxis = new ArrayList<>();
|
|
455
|
|
- if (byMonth) {
|
|
456
|
|
- LocalDate cursor = start.withDayOfMonth(1);
|
|
457
|
|
- LocalDate endMonth = end.withDayOfMonth(1);
|
|
458
|
|
- while (!cursor.isAfter(endMonth)) {
|
|
459
|
|
- xAxis.add(cursor.format(DateTimeFormatter.ofPattern("yyyy-MM")));
|
|
460
|
|
- cursor = cursor.plusMonths(1);
|
|
|
431
|
+ private List<Map<String, Object>> buildComparisonSeries(
|
|
|
432
|
+ String scopeType, Long scopeId, List<LocalDate[]> periods, String dateRangeQueryType,
|
|
|
433
|
+ boolean isStation, boolean isBrigade, boolean isManager, Long userDeptId, LoginUser loginUser) {
|
|
|
434
|
+
|
|
|
435
|
+ boolean isYearType = "YEAR".equals(dateRangeQueryType);
|
|
|
436
|
+
|
|
|
437
|
+ if ("MANAGER".equals(scopeType) && scopeId != null) {
|
|
|
438
|
+ String name = "主管" + scopeId;
|
|
|
439
|
+ List<Integer> data = new ArrayList<>();
|
|
|
440
|
+ for (LocalDate[] p : periods) {
|
|
|
441
|
+ List<DailyTask> tasks = queryTasksByDepartmentId(scopeId, p[0], p[1]);
|
|
|
442
|
+ if (name.startsWith("主管")) {
|
|
|
443
|
+ name = tasks.stream().map(DailyTask::getDtDepartmentName)
|
|
|
444
|
+ .filter(Objects::nonNull).findFirst().orElse(name);
|
|
|
445
|
+ }
|
|
|
446
|
+ data.add(countCompleted(tasks));
|
|
|
447
|
+ }
|
|
|
448
|
+ return Collections.singletonList(buildSeriesItem(name, scopeId, null, data, isYearType));
|
|
|
449
|
+
|
|
|
450
|
+ } else if ("USER".equals(scopeType) && scopeId != null) {
|
|
|
451
|
+ String name = "用户" + scopeId;
|
|
|
452
|
+ List<Integer> data = new ArrayList<>();
|
|
|
453
|
+ for (LocalDate[] p : periods) {
|
|
|
454
|
+ List<DailyTask> tasks = queryTasksByUserId(scopeId, p[0], p[1]);
|
|
|
455
|
+ if (name.startsWith("用户")) {
|
|
|
456
|
+ name = tasks.stream().map(DailyTask::getDtUserName)
|
|
|
457
|
+ .filter(Objects::nonNull).findFirst().orElse(name);
|
|
|
458
|
+ }
|
|
|
459
|
+ data.add(countCompleted(tasks));
|
|
461
|
460
|
}
|
|
|
461
|
+ return Collections.singletonList(buildSeriesItem(name, null, scopeId, data, isYearType));
|
|
|
462
|
+
|
|
|
463
|
+ } else if ("BRIGADE".equals(scopeType) && scopeId != null) {
|
|
|
464
|
+ return buildBrigadeSeries(scopeId, periods, isYearType);
|
|
|
465
|
+
|
|
462
|
466
|
} else {
|
|
463
|
|
- LocalDate cursor = start;
|
|
464
|
|
- while (!cursor.isAfter(end)) {
|
|
465
|
|
- xAxis.add(cursor.toString());
|
|
466
|
|
- cursor = cursor.plusDays(1);
|
|
|
467
|
+ Long stationDeptId = ("STATION".equals(scopeType) && scopeId != null) ? scopeId : userDeptId;
|
|
|
468
|
+ if (isStation || "STATION".equals(scopeType)) {
|
|
|
469
|
+ return buildStationSeries(stationDeptId, periods, isYearType);
|
|
|
470
|
+ } else if (isBrigade) {
|
|
|
471
|
+ return buildBrigadeSeries(userDeptId, periods, isYearType);
|
|
|
472
|
+ } else {
|
|
|
473
|
+ // isManager:单条 series
|
|
|
474
|
+ String deptName = loginUser.getUser().getDept() != null
|
|
|
475
|
+ ? loginUser.getUser().getDept().getDeptName() : "本主管";
|
|
|
476
|
+ List<Integer> data = new ArrayList<>();
|
|
|
477
|
+ for (LocalDate[] p : periods) {
|
|
|
478
|
+ data.add(countCompleted(queryTasksByDepartmentId(userDeptId, p[0], p[1])));
|
|
|
479
|
+ }
|
|
|
480
|
+ return Collections.singletonList(buildSeriesItem(deptName, userDeptId, null, data, isYearType));
|
|
467
|
481
|
}
|
|
468
|
482
|
}
|
|
469
|
|
- return xAxis;
|
|
470
|
483
|
}
|
|
471
|
484
|
|
|
472
|
485
|
/**
|
|
473
|
|
- * 构建某条折线的数据数组,与 xAxis 对齐
|
|
|
486
|
+ * 站长视角:按大队分组,各期聚合 + 全站汇总
|
|
474
|
487
|
*/
|
|
475
|
|
- private List<Integer> buildSeriesData(List<String> xAxis, List<DailyTask> tasks, boolean byMonth) {
|
|
476
|
|
- SimpleDateFormat sdf = new SimpleDateFormat(byMonth ? "yyyy-MM" : "yyyy-MM-dd");
|
|
477
|
|
- Map<String, Long> countMap = tasks.stream()
|
|
478
|
|
- .filter(t -> "COMPLETED".equals(t.getDtStatus()) && t.getDtBusinessDate() != null)
|
|
479
|
|
- .collect(Collectors.groupingBy(
|
|
480
|
|
- t -> sdf.format(t.getDtBusinessDate()),
|
|
481
|
|
- Collectors.counting()));
|
|
482
|
|
- List<Integer> data = new ArrayList<>();
|
|
483
|
|
- for (String label : xAxis) {
|
|
484
|
|
- data.add(countMap.getOrDefault(label, 0L).intValue());
|
|
|
488
|
+ private List<Map<String, Object>> buildStationSeries(Long stationDeptId, List<LocalDate[]> periods, boolean isYearType) {
|
|
|
489
|
+ List<SysDept> subDepts = sysDeptMapper.selectChildrenDeptById(stationDeptId);
|
|
|
490
|
+ if (subDepts == null) subDepts = Collections.emptyList();
|
|
|
491
|
+
|
|
|
492
|
+ List<SysDept> brigadeDepts = subDepts.stream()
|
|
|
493
|
+ .filter(d -> stationDeptId.equals(d.getParentId()) && "0".equals(d.getDelFlag()))
|
|
|
494
|
+ .collect(Collectors.toList());
|
|
|
495
|
+
|
|
|
496
|
+ // 主管deptId -> 所属大队deptId
|
|
|
497
|
+ Map<Long, Long> managerToBrigadeId = new HashMap<>();
|
|
|
498
|
+ for (SysDept d : subDepts) {
|
|
|
499
|
+ if ("0".equals(d.getDelFlag()) && d.getParentId() != null && !stationDeptId.equals(d.getParentId())) {
|
|
|
500
|
+ managerToBrigadeId.put(d.getDeptId(), d.getParentId());
|
|
|
501
|
+ }
|
|
|
502
|
+ }
|
|
|
503
|
+
|
|
|
504
|
+ Set<Long> allDeptIds = new HashSet<>();
|
|
|
505
|
+ allDeptIds.add(stationDeptId);
|
|
|
506
|
+ subDepts.forEach(d -> allDeptIds.add(d.getDeptId()));
|
|
|
507
|
+
|
|
|
508
|
+ List<List<DailyTask>> periodTasks = new ArrayList<>();
|
|
|
509
|
+ for (LocalDate[] p : periods) {
|
|
|
510
|
+ periodTasks.add(queryTasksByDeptIds(allDeptIds, p[0], p[1]));
|
|
485
|
511
|
}
|
|
486
|
|
- return data;
|
|
|
512
|
+
|
|
|
513
|
+ List<Map<String, Object>> series = new ArrayList<>();
|
|
|
514
|
+ List<Integer> totalData = new ArrayList<>();
|
|
|
515
|
+ for (int i = 0; i < periods.size(); i++) totalData.add(0);
|
|
|
516
|
+
|
|
|
517
|
+ for (SysDept brigade : brigadeDepts) {
|
|
|
518
|
+ Long brigadeId = brigade.getDeptId();
|
|
|
519
|
+ List<Integer> data = new ArrayList<>();
|
|
|
520
|
+ for (int i = 0; i < periodTasks.size(); i++) {
|
|
|
521
|
+ int count = countCompleted(periodTasks.get(i).stream()
|
|
|
522
|
+ .filter(t -> {
|
|
|
523
|
+ Long managerId = t.getDtDepartmentId();
|
|
|
524
|
+ if (managerId == null) return false;
|
|
|
525
|
+ return brigadeId.equals(managerToBrigadeId.get(managerId));
|
|
|
526
|
+ })
|
|
|
527
|
+ .collect(Collectors.toList()));
|
|
|
528
|
+ data.add(count);
|
|
|
529
|
+ totalData.set(i, totalData.get(i) + count);
|
|
|
530
|
+ }
|
|
|
531
|
+ series.add(buildSeriesItem(brigade.getDeptName(), brigadeId, null, data, isYearType));
|
|
|
532
|
+ }
|
|
|
533
|
+ series.add(buildSeriesItem("全站", null, null, totalData, isYearType));
|
|
|
534
|
+ return series;
|
|
487
|
535
|
}
|
|
488
|
536
|
|
|
489
|
537
|
/**
|
|
490
|
|
- * 按部门ID集合查询任务(用于站长视角:通过dtDeptId匹配所有子部门)
|
|
|
538
|
+ * 大队视角:按主管分组,各期聚合
|
|
|
539
|
+ */
|
|
|
540
|
+ private List<Map<String, Object>> buildBrigadeSeries(Long brigadeId, List<LocalDate[]> periods, boolean isYearType) {
|
|
|
541
|
+ List<SysDept> subDepts = sysDeptMapper.selectChildrenDeptById(brigadeId);
|
|
|
542
|
+ if (subDepts == null) subDepts = Collections.emptyList();
|
|
|
543
|
+
|
|
|
544
|
+ List<SysDept> managerDepts = subDepts.stream()
|
|
|
545
|
+ .filter(d -> brigadeId.equals(d.getParentId()) && "0".equals(d.getDelFlag()))
|
|
|
546
|
+ .collect(Collectors.toList());
|
|
|
547
|
+
|
|
|
548
|
+ List<Map<String, Object>> series = new ArrayList<>();
|
|
|
549
|
+ for (SysDept manager : managerDepts) {
|
|
|
550
|
+ List<Integer> data = new ArrayList<>();
|
|
|
551
|
+ for (LocalDate[] p : periods) {
|
|
|
552
|
+ data.add(countCompleted(queryTasksByDepartmentId(manager.getDeptId(), p[0], p[1])));
|
|
|
553
|
+ }
|
|
|
554
|
+ series.add(buildSeriesItem(manager.getDeptName(), manager.getDeptId(), null, data, isYearType));
|
|
|
555
|
+ }
|
|
|
556
|
+ return series;
|
|
|
557
|
+ }
|
|
|
558
|
+
|
|
|
559
|
+ /**
|
|
|
560
|
+ * 构建单条 series map,含同比/环比
|
|
|
561
|
+ * data[0]=去年同期,data[1]=上期(仅QUARTER/MONTH),data[last]=本期
|
|
|
562
|
+ */
|
|
|
563
|
+ private Map<String, Object> buildSeriesItem(String name, Long deptId, Long userId,
|
|
|
564
|
+ List<Integer> data, boolean isYearType) {
|
|
|
565
|
+ Map<String, Object> s = new LinkedHashMap<>();
|
|
|
566
|
+ s.put("name", name);
|
|
|
567
|
+ if (deptId != null) s.put("deptId", deptId);
|
|
|
568
|
+ if (userId != null) s.put("userId", userId);
|
|
|
569
|
+ s.put("data", data);
|
|
|
570
|
+ int current = data.get(data.size() - 1);
|
|
|
571
|
+ // 同比:本期 vs 去年同期(data[0])
|
|
|
572
|
+ s.put("yoyRate", calcRate(current, data.get(0)));
|
|
|
573
|
+ // 环比:本期 vs 上一期(data[last-1]),仅 QUARTER/MONTH 有效
|
|
|
574
|
+ if (!isYearType && data.size() >= 3) {
|
|
|
575
|
+ s.put("chainRatio", calcRate(current, data.get(data.size() - 2)));
|
|
|
576
|
+ } else {
|
|
|
577
|
+ s.put("chainRatio", null);
|
|
|
578
|
+ }
|
|
|
579
|
+ return s;
|
|
|
580
|
+ }
|
|
|
581
|
+
|
|
|
582
|
+ /**
|
|
|
583
|
+ * 计算变化率百分比,base=0 时返回 null
|
|
|
584
|
+ */
|
|
|
585
|
+ private BigDecimal calcRate(int current, int base) {
|
|
|
586
|
+ if (base == 0) return null;
|
|
|
587
|
+ return BigDecimal.valueOf(current - base)
|
|
|
588
|
+ .multiply(BigDecimal.valueOf(100))
|
|
|
589
|
+ .divide(BigDecimal.valueOf(base), 2, RoundingMode.HALF_UP);
|
|
|
590
|
+ }
|
|
|
591
|
+
|
|
|
592
|
+ /**
|
|
|
593
|
+ * 统计 COMPLETED 任务数
|
|
|
594
|
+ */
|
|
|
595
|
+ private int countCompleted(List<DailyTask> tasks) {
|
|
|
596
|
+ return (int) tasks.stream().filter(t -> "COMPLETED".equals(t.getDtStatus())).count();
|
|
|
597
|
+ }
|
|
|
598
|
+
|
|
|
599
|
+ /**
|
|
|
600
|
+ * 根据 dateRangeQueryType/year/quarter/month 计算起止日期
|
|
|
601
|
+ */
|
|
|
602
|
+ private LocalDate[] resolveDateRange(String type, Integer year, Integer quarter, Integer month) {
|
|
|
603
|
+ LocalDate today = LocalDate.now();
|
|
|
604
|
+ int y = (year != null) ? year : today.getYear();
|
|
|
605
|
+ LocalDate start, end;
|
|
|
606
|
+ switch (type == null ? "MONTH" : type) {
|
|
|
607
|
+ case "YEAR":
|
|
|
608
|
+ start = LocalDate.of(y, 1, 1);
|
|
|
609
|
+ end = LocalDate.of(y, 12, 31);
|
|
|
610
|
+ break;
|
|
|
611
|
+ case "QUARTER":
|
|
|
612
|
+ int q = (quarter != null) ? quarter : 1;
|
|
|
613
|
+ start = LocalDate.of(y, (q - 1) * 3 + 1, 1);
|
|
|
614
|
+ end = start.plusMonths(3).minusDays(1);
|
|
|
615
|
+ break;
|
|
|
616
|
+ case "MONTH":
|
|
|
617
|
+ default:
|
|
|
618
|
+ int m = (month != null) ? month : today.getMonthValue();
|
|
|
619
|
+ start = LocalDate.of(y, m, 1);
|
|
|
620
|
+ end = start.withDayOfMonth(start.lengthOfMonth());
|
|
|
621
|
+ break;
|
|
|
622
|
+ }
|
|
|
623
|
+ return new LocalDate[]{start, end};
|
|
|
624
|
+ }
|
|
|
625
|
+
|
|
|
626
|
+ /**
|
|
|
627
|
+ * 按部门ID集合查询任务
|
|
491
|
628
|
*/
|
|
492
|
629
|
private List<DailyTask> queryTasksByDeptIds(Set<Long> deptIds, LocalDate start, LocalDate end) {
|
|
493
|
630
|
LambdaQueryWrapper<DailyTask> wrapper = new LambdaQueryWrapper<>();
|
|
|
@@ -499,7 +636,7 @@ public class DailyExamController {
|
|
499
|
636
|
}
|
|
500
|
637
|
|
|
501
|
638
|
/**
|
|
502
|
|
- * 按主管ID查询任务(通过dtDepartmentId匹配)
|
|
|
639
|
+ * 按主管ID查询任务(dtDepartmentId)
|
|
503
|
640
|
*/
|
|
504
|
641
|
private List<DailyTask> queryTasksByDepartmentId(Long departmentId, LocalDate start, LocalDate end) {
|
|
505
|
642
|
LambdaQueryWrapper<DailyTask> wrapper = new LambdaQueryWrapper<>();
|
|
|
@@ -509,4 +646,16 @@ public class DailyExamController {
|
|
509
|
646
|
wrapper.eq(DailyTask::getDelStatus, 0);
|
|
510
|
647
|
return dailyTaskMapper.selectList(wrapper);
|
|
511
|
648
|
}
|
|
|
649
|
+
|
|
|
650
|
+ /**
|
|
|
651
|
+ * 按用户ID查询任务(dtUserId)
|
|
|
652
|
+ */
|
|
|
653
|
+ private List<DailyTask> queryTasksByUserId(Long userId, LocalDate start, LocalDate end) {
|
|
|
654
|
+ LambdaQueryWrapper<DailyTask> wrapper = new LambdaQueryWrapper<>();
|
|
|
655
|
+ wrapper.eq(DailyTask::getDtUserId, userId);
|
|
|
656
|
+ wrapper.ge(DailyTask::getDtBusinessDate, Date.valueOf(start));
|
|
|
657
|
+ wrapper.le(DailyTask::getDtBusinessDate, Date.valueOf(end));
|
|
|
658
|
+ wrapper.eq(DailyTask::getDelStatus, 0);
|
|
|
659
|
+ return dailyTaskMapper.selectList(wrapper);
|
|
|
660
|
+ }
|
|
512
|
661
|
}
|