|
|
@@ -21,17 +21,21 @@ import org.springframework.web.bind.annotation.RestController;
|
|
21
|
21
|
import java.math.BigDecimal;
|
|
22
|
22
|
import java.math.RoundingMode;
|
|
23
|
23
|
import java.sql.Date;
|
|
|
24
|
+import java.text.SimpleDateFormat;
|
|
24
|
25
|
import java.time.LocalDate;
|
|
|
26
|
+import java.time.format.DateTimeFormatter;
|
|
25
|
27
|
import java.util.*;
|
|
26
|
28
|
import java.util.stream.Collectors;
|
|
27
|
29
|
|
|
28
|
30
|
/**
|
|
29
|
|
- * 抽问抽答完成趋势对比接口(美兰4级架构)
|
|
30
|
|
- * STATION > 大队(BRIGADE) > 主管(MANAGER) > 班组
|
|
31
|
|
- * 始终返回多期聚合对比数据:
|
|
32
|
|
- * YEAR:[去年, 今年] 含同比(yoyRate)
|
|
33
|
|
- * QUARTER:[去年同季度, 上季度, 本季度] 含同比+环比(chainRatio)
|
|
34
|
|
- * MONTH:[去年同月, 上月, 本月] 含同比+环比
|
|
|
31
|
+ * 抽问抽答完成趋势对比接口(PC管理端,美兰4级架构)
|
|
|
32
|
+ * STATION(安检站)> BRIGADE(大队)> MANAGER(主管)> 班组
|
|
|
33
|
+ *
|
|
|
34
|
+ * 返回当前周期的时间序列趋势数据(同 /completion-trend),
|
|
|
35
|
+ * 同时在每条 series 上附加 yoyRate(同比)和 chainRatio(环比)。
|
|
|
36
|
+ *
|
|
|
37
|
+ * yoyRate = (本期总数 - 去年同期总数) / 去年同期总数 × 100
|
|
|
38
|
+ * chainRatio = (本期总数 - 上期总数) / 上期总数 × 100(仅 QUARTER/MONTH)
|
|
35
|
39
|
*/
|
|
36
|
40
|
@RestController
|
|
37
|
41
|
@RequestMapping("/v1/cs/app/daily-exam")
|
|
|
@@ -79,7 +83,6 @@ public class DailyExamComparisonController {
|
|
79
|
83
|
|| roleKeys.contains(RoleTypeEnum.xingzheng.getCode()));
|
|
80
|
84
|
boolean isManager = !isStation && !isBrigade && roleKeys.contains(RoleTypeEnum.kezhang.getCode());
|
|
81
|
85
|
|
|
82
|
|
- // 班组长及以下不显示(未传 scopeType 且无管理权限)
|
|
83
|
86
|
if (scopeType == null && !isStation && !isBrigade && !isManager) {
|
|
84
|
87
|
Map<String, Object> noShow = new HashMap<>();
|
|
85
|
88
|
noShow.put("show", false);
|
|
|
@@ -90,16 +93,24 @@ public class DailyExamComparisonController {
|
|
90
|
93
|
int curQuarter = (quarter != null) ? quarter : ((LocalDate.now().getMonthValue() - 1) / 3 + 1);
|
|
91
|
94
|
int curMonth = (month != null) ? month : LocalDate.now().getMonthValue();
|
|
92
|
95
|
|
|
93
|
|
- List<LocalDate[]> periods = buildPeriods(dateRangeQueryType, curYear, curQuarter, curMonth);
|
|
94
|
|
- List<String> xAxis = buildXAxisLabels(dateRangeQueryType, curYear, curQuarter, curMonth);
|
|
|
96
|
+ // 当前周期
|
|
|
97
|
+ LocalDate[] currentRange = resolveDateRange(dateRangeQueryType, curYear, curQuarter, curMonth);
|
|
|
98
|
+ // 去年同期(用于计算同比)
|
|
|
99
|
+ LocalDate[] yoyRange = resolveDateRange(dateRangeQueryType, curYear - 1, curQuarter, curMonth);
|
|
|
100
|
+ // 上一期(用于计算环比,仅 QUARTER/MONTH)
|
|
|
101
|
+ LocalDate[] prevRange = buildPrevRange(dateRangeQueryType, curYear, curQuarter, curMonth);
|
|
|
102
|
+
|
|
|
103
|
+ boolean byMonth = "YEAR".equals(dateRangeQueryType);
|
|
|
104
|
+ List<String> xAxis = buildXAxis(currentRange[0], currentRange[1], byMonth);
|
|
|
105
|
+
|
|
|
106
|
+ List<Map<String, Object>> series = buildComparisonSeries(
|
|
|
107
|
+ scopeType, scopeId, xAxis, currentRange, yoyRange, prevRange,
|
|
|
108
|
+ byMonth, isStation, isBrigade, isManager, userDeptId, loginUser);
|
|
95
|
109
|
|
|
96
|
110
|
Map<String, Object> result = new LinkedHashMap<>();
|
|
97
|
111
|
result.put("show", true);
|
|
98
|
112
|
result.put("xAxis", xAxis);
|
|
99
|
|
- result.put("series", buildComparisonSeries(
|
|
100
|
|
- scopeType, scopeId, periods, dateRangeQueryType,
|
|
101
|
|
- isStation, isBrigade, isManager, userDeptId, loginUser));
|
|
102
|
|
-
|
|
|
113
|
+ result.put("series", series);
|
|
103
|
114
|
return HttpResult.success(result);
|
|
104
|
115
|
|
|
105
|
116
|
} catch (Exception e) {
|
|
|
@@ -109,134 +120,84 @@ public class DailyExamComparisonController {
|
|
109
|
120
|
}
|
|
110
|
121
|
|
|
111
|
122
|
/**
|
|
112
|
|
- * 构建各期日期范围列表
|
|
113
|
|
- * YEAR:[去年, 今年]
|
|
114
|
|
- * QUARTER:[去年同季度, 上季度, 本季度]
|
|
115
|
|
- * MONTH:[去年同月, 上月, 本月]
|
|
|
123
|
+ * 计算上一期日期范围(QUARTER/MONTH);YEAR 返回 null(无环比)
|
|
116
|
124
|
*/
|
|
117
|
|
- private List<LocalDate[]> buildPeriods(String type, int year, int quarter, int month) {
|
|
118
|
|
- List<LocalDate[]> periods = new ArrayList<>();
|
|
119
|
|
- switch (type == null ? "MONTH" : type) {
|
|
120
|
|
- case "YEAR":
|
|
121
|
|
- periods.add(new LocalDate[]{LocalDate.of(year - 1, 1, 1), LocalDate.of(year - 1, 12, 31)});
|
|
122
|
|
- periods.add(new LocalDate[]{LocalDate.of(year, 1, 1), LocalDate.of(year, 12, 31)});
|
|
123
|
|
- break;
|
|
124
|
|
- case "QUARTER":
|
|
125
|
|
- periods.add(resolveDateRange("QUARTER", year - 1, quarter, null));
|
|
126
|
|
- int prevQ = quarter - 1;
|
|
127
|
|
- int prevQYear = year;
|
|
128
|
|
- if (prevQ < 1) { prevQ = 4; prevQYear = year - 1; }
|
|
129
|
|
- periods.add(resolveDateRange("QUARTER", prevQYear, prevQ, null));
|
|
130
|
|
- periods.add(resolveDateRange("QUARTER", year, quarter, null));
|
|
131
|
|
- break;
|
|
132
|
|
- case "MONTH":
|
|
133
|
|
- default:
|
|
134
|
|
- periods.add(resolveDateRange("MONTH", year - 1, null, month));
|
|
135
|
|
- int prevM = month - 1;
|
|
136
|
|
- int prevMYear = year;
|
|
137
|
|
- if (prevM < 1) { prevM = 12; prevMYear = year - 1; }
|
|
138
|
|
- periods.add(resolveDateRange("MONTH", prevMYear, null, prevM));
|
|
139
|
|
- periods.add(resolveDateRange("MONTH", year, null, month));
|
|
140
|
|
- break;
|
|
|
125
|
+ private LocalDate[] buildPrevRange(String type, int year, int quarter, int month) {
|
|
|
126
|
+ if ("YEAR".equals(type)) return null;
|
|
|
127
|
+ if ("QUARTER".equals(type)) {
|
|
|
128
|
+ int prevQ = quarter - 1;
|
|
|
129
|
+ int prevYear = year;
|
|
|
130
|
+ if (prevQ < 1) { prevQ = 4; prevYear = year - 1; }
|
|
|
131
|
+ return resolveDateRange("QUARTER", prevYear, prevQ, month);
|
|
141
|
132
|
}
|
|
142
|
|
- return periods;
|
|
|
133
|
+ // MONTH
|
|
|
134
|
+ int prevM = month - 1;
|
|
|
135
|
+ int prevYear = year;
|
|
|
136
|
+ if (prevM < 1) { prevM = 12; prevYear = year - 1; }
|
|
|
137
|
+ return resolveDateRange("MONTH", prevYear, quarter, prevM);
|
|
143
|
138
|
}
|
|
144
|
139
|
|
|
145
|
140
|
/**
|
|
146
|
|
- * 构建 X 轴中文标签列表
|
|
147
|
|
- */
|
|
148
|
|
- private List<String> buildXAxisLabels(String type, int year, int quarter, int month) {
|
|
149
|
|
- List<String> labels = new ArrayList<>();
|
|
150
|
|
- switch (type == null ? "MONTH" : type) {
|
|
151
|
|
- case "YEAR":
|
|
152
|
|
- labels.add((year - 1) + "年");
|
|
153
|
|
- labels.add(year + "年");
|
|
154
|
|
- break;
|
|
155
|
|
- case "QUARTER":
|
|
156
|
|
- labels.add((year - 1) + "年第" + quarter + "季度");
|
|
157
|
|
- int prevQ = quarter - 1;
|
|
158
|
|
- int prevQYear = year;
|
|
159
|
|
- if (prevQ < 1) { prevQ = 4; prevQYear = year - 1; }
|
|
160
|
|
- labels.add(prevQYear + "年第" + prevQ + "季度");
|
|
161
|
|
- labels.add(year + "年第" + quarter + "季度");
|
|
162
|
|
- break;
|
|
163
|
|
- case "MONTH":
|
|
164
|
|
- default:
|
|
165
|
|
- labels.add((year - 1) + "年" + month + "月");
|
|
166
|
|
- int prevM = month - 1;
|
|
167
|
|
- int prevMYear = year;
|
|
168
|
|
- if (prevM < 1) { prevM = 12; prevMYear = year - 1; }
|
|
169
|
|
- labels.add(prevMYear + "年" + prevM + "月");
|
|
170
|
|
- labels.add(year + "年" + month + "月");
|
|
171
|
|
- break;
|
|
172
|
|
- }
|
|
173
|
|
- return labels;
|
|
174
|
|
- }
|
|
175
|
|
-
|
|
176
|
|
- /**
|
|
177
|
|
- * 构建多期对比 series 列表(适配美兰4级架构)
|
|
178
|
|
- * STATION:各大队各一条 series + 全站汇总
|
|
179
|
|
- * BRIGADE:该大队各主管各一条 series
|
|
180
|
|
- * MANAGER/USER:单条 series
|
|
|
141
|
+ * 构建 series 列表:每条 series 含当前周期趋势数据 + yoyRate + chainRatio
|
|
181
|
142
|
*/
|
|
182
|
143
|
private List<Map<String, Object>> buildComparisonSeries(
|
|
183
|
|
- String scopeType, Long scopeId, List<LocalDate[]> periods, String dateRangeQueryType,
|
|
184
|
|
- boolean isStation, boolean isBrigade, boolean isManager, Long userDeptId, LoginUser loginUser) {
|
|
185
|
|
-
|
|
186
|
|
- boolean isYearType = "YEAR".equals(dateRangeQueryType);
|
|
|
144
|
+ String scopeType, Long scopeId,
|
|
|
145
|
+ List<String> xAxis, LocalDate[] currentRange, LocalDate[] yoyRange, LocalDate[] prevRange,
|
|
|
146
|
+ boolean byMonth, boolean isStation, boolean isBrigade, boolean isManager,
|
|
|
147
|
+ Long userDeptId, LoginUser loginUser) {
|
|
187
|
148
|
|
|
188
|
149
|
if ("MANAGER".equals(scopeType) && scopeId != null) {
|
|
189
|
|
- String name = "主管" + scopeId;
|
|
190
|
|
- List<Integer> data = new ArrayList<>();
|
|
191
|
|
- for (LocalDate[] p : periods) {
|
|
192
|
|
- List<DailyTask> tasks = queryTasksByDepartmentId(scopeId, p[0], p[1]);
|
|
193
|
|
- if (name.startsWith("主管")) {
|
|
194
|
|
- name = tasks.stream().map(DailyTask::getDtDepartmentName)
|
|
195
|
|
- .filter(Objects::nonNull).findFirst().orElse(name);
|
|
196
|
|
- }
|
|
197
|
|
- data.add(countCompleted(tasks));
|
|
198
|
|
- }
|
|
199
|
|
- return Collections.singletonList(buildSeriesItem(name, scopeId, null, data, isYearType));
|
|
|
150
|
+ List<DailyTask> current = queryTasksByDepartmentId(scopeId, currentRange[0], currentRange[1]);
|
|
|
151
|
+ String name = current.stream().map(DailyTask::getDtDepartmentName)
|
|
|
152
|
+ .filter(Objects::nonNull).findFirst().orElse("主管" + scopeId);
|
|
|
153
|
+ int yoyCount = countCompleted(queryTasksByDepartmentId(scopeId, yoyRange[0], yoyRange[1]));
|
|
|
154
|
+ Integer prevCount = prevRange != null
|
|
|
155
|
+ ? countCompleted(queryTasksByDepartmentId(scopeId, prevRange[0], prevRange[1])) : null;
|
|
|
156
|
+ return Collections.singletonList(
|
|
|
157
|
+ buildSeriesItem(name, scopeId, null, xAxis, current, byMonth, yoyCount, prevCount));
|
|
200
|
158
|
|
|
201
|
159
|
} else if ("USER".equals(scopeType) && scopeId != null) {
|
|
202
|
|
- String name = "用户" + scopeId;
|
|
203
|
|
- List<Integer> data = new ArrayList<>();
|
|
204
|
|
- for (LocalDate[] p : periods) {
|
|
205
|
|
- List<DailyTask> tasks = queryTasksByUserId(scopeId, p[0], p[1]);
|
|
206
|
|
- if (name.startsWith("用户")) {
|
|
207
|
|
- name = tasks.stream().map(DailyTask::getDtUserName)
|
|
208
|
|
- .filter(Objects::nonNull).findFirst().orElse(name);
|
|
209
|
|
- }
|
|
210
|
|
- data.add(countCompleted(tasks));
|
|
211
|
|
- }
|
|
212
|
|
- return Collections.singletonList(buildSeriesItem(name, null, scopeId, data, isYearType));
|
|
|
160
|
+ List<DailyTask> current = queryTasksByUserId(scopeId, currentRange[0], currentRange[1]);
|
|
|
161
|
+ String name = current.stream().map(DailyTask::getDtUserName)
|
|
|
162
|
+ .filter(Objects::nonNull).findFirst().orElse("用户" + scopeId);
|
|
|
163
|
+ int yoyCount = countCompleted(queryTasksByUserId(scopeId, yoyRange[0], yoyRange[1]));
|
|
|
164
|
+ Integer prevCount = prevRange != null
|
|
|
165
|
+ ? countCompleted(queryTasksByUserId(scopeId, prevRange[0], prevRange[1])) : null;
|
|
|
166
|
+ return Collections.singletonList(
|
|
|
167
|
+ buildSeriesItem(name, null, scopeId, xAxis, current, byMonth, yoyCount, prevCount));
|
|
213
|
168
|
|
|
214
|
169
|
} else if ("BRIGADE".equals(scopeType) && scopeId != null) {
|
|
215
|
|
- return buildBrigadeSeries(scopeId, periods, isYearType);
|
|
|
170
|
+ return buildBrigadeSeries(scopeId, xAxis, currentRange, yoyRange, prevRange, byMonth);
|
|
216
|
171
|
|
|
217
|
172
|
} else {
|
|
218
|
173
|
Long stationDeptId = ("STATION".equals(scopeType) && scopeId != null) ? scopeId : userDeptId;
|
|
219
|
174
|
if (isStation || "STATION".equals(scopeType)) {
|
|
220
|
|
- return buildStationSeries(stationDeptId, periods, isYearType);
|
|
|
175
|
+ return buildStationSeries(stationDeptId, xAxis, currentRange, yoyRange, prevRange, byMonth);
|
|
221
|
176
|
} else if (isBrigade) {
|
|
222
|
|
- return buildBrigadeSeries(userDeptId, periods, isYearType);
|
|
|
177
|
+ return buildBrigadeSeries(userDeptId, xAxis, currentRange, yoyRange, prevRange, byMonth);
|
|
223
|
178
|
} else {
|
|
224
|
179
|
// 主管:单条 series
|
|
225
|
180
|
String deptName = loginUser.getUser().getDept() != null
|
|
226
|
181
|
? loginUser.getUser().getDept().getDeptName() : "本主管";
|
|
227
|
|
- List<Integer> data = new ArrayList<>();
|
|
228
|
|
- for (LocalDate[] p : periods) {
|
|
229
|
|
- data.add(countCompleted(queryTasksByDepartmentId(userDeptId, p[0], p[1])));
|
|
230
|
|
- }
|
|
231
|
|
- return Collections.singletonList(buildSeriesItem(deptName, userDeptId, null, data, isYearType));
|
|
|
182
|
+ List<DailyTask> current = queryTasksByDepartmentId(userDeptId, currentRange[0], currentRange[1]);
|
|
|
183
|
+ int yoyCount = countCompleted(queryTasksByDepartmentId(userDeptId, yoyRange[0], yoyRange[1]));
|
|
|
184
|
+ Integer prevCount = prevRange != null
|
|
|
185
|
+ ? countCompleted(queryTasksByDepartmentId(userDeptId, prevRange[0], prevRange[1])) : null;
|
|
|
186
|
+ return Collections.singletonList(
|
|
|
187
|
+ buildSeriesItem(deptName, userDeptId, null, xAxis, current, byMonth, yoyCount, prevCount));
|
|
232
|
188
|
}
|
|
233
|
189
|
}
|
|
234
|
190
|
}
|
|
235
|
191
|
|
|
236
|
192
|
/**
|
|
237
|
|
- * 站长视角:按大队分组,各期聚合 + 全站汇总
|
|
|
193
|
+ * 站长视角:按大队分组,各大队各一条 series + 全站汇总
|
|
238
|
194
|
*/
|
|
239
|
|
- private List<Map<String, Object>> buildStationSeries(Long stationDeptId, List<LocalDate[]> periods, boolean isYearType) {
|
|
|
195
|
+ private List<Map<String, Object>> buildStationSeries(Long stationDeptId,
|
|
|
196
|
+ List<String> xAxis,
|
|
|
197
|
+ LocalDate[] currentRange,
|
|
|
198
|
+ LocalDate[] yoyRange,
|
|
|
199
|
+ LocalDate[] prevRange,
|
|
|
200
|
+ boolean byMonth) {
|
|
240
|
201
|
List<SysDept> subDepts = sysDeptMapper.selectChildrenDeptById(stationDeptId);
|
|
241
|
202
|
if (subDepts == null) subDepts = Collections.emptyList();
|
|
242
|
203
|
|
|
|
@@ -245,51 +206,42 @@ public class DailyExamComparisonController {
|
|
245
|
206
|
.filter(d -> stationDeptId.equals(d.getParentId()) && "0".equals(d.getDelFlag()))
|
|
246
|
207
|
.collect(Collectors.toList());
|
|
247
|
208
|
|
|
248
|
|
- // 主管deptId -> 所属大队deptId
|
|
249
|
|
- Map<Long, Long> managerToBrigadeId = new HashMap<>();
|
|
250
|
|
- for (SysDept d : subDepts) {
|
|
251
|
|
- if ("0".equals(d.getDelFlag()) && d.getParentId() != null && !stationDeptId.equals(d.getParentId())) {
|
|
252
|
|
- managerToBrigadeId.put(d.getDeptId(), d.getParentId());
|
|
253
|
|
- }
|
|
254
|
|
- }
|
|
255
|
|
-
|
|
256
|
|
- Set<Long> allDeptIds = new HashSet<>();
|
|
257
|
|
- allDeptIds.add(stationDeptId);
|
|
258
|
|
- subDepts.forEach(d -> allDeptIds.add(d.getDeptId()));
|
|
259
|
|
-
|
|
260
|
|
- List<List<DailyTask>> periodTasks = new ArrayList<>();
|
|
261
|
|
- for (LocalDate[] p : periods) {
|
|
262
|
|
- periodTasks.add(queryTasksByDeptIds(allDeptIds, p[0], p[1]));
|
|
263
|
|
- }
|
|
264
|
|
-
|
|
265
|
209
|
List<Map<String, Object>> series = new ArrayList<>();
|
|
266
|
|
- List<Integer> totalData = new ArrayList<>();
|
|
267
|
|
- for (int i = 0; i < periods.size(); i++) totalData.add(0);
|
|
|
210
|
+ List<DailyTask> stationCurrentAll = new ArrayList<>();
|
|
|
211
|
+ int stationYoyTotal = 0;
|
|
|
212
|
+ int stationPrevTotal = 0;
|
|
268
|
213
|
|
|
269
|
214
|
for (SysDept brigade : brigadeDepts) {
|
|
270
|
|
- Long brigadeId = brigade.getDeptId();
|
|
271
|
|
- List<Integer> data = new ArrayList<>();
|
|
272
|
|
- for (int i = 0; i < periodTasks.size(); i++) {
|
|
273
|
|
- int count = countCompleted(periodTasks.get(i).stream()
|
|
274
|
|
- .filter(t -> {
|
|
275
|
|
- Long managerId = t.getDtDepartmentId();
|
|
276
|
|
- if (managerId == null) return false;
|
|
277
|
|
- return brigadeId.equals(managerToBrigadeId.get(managerId));
|
|
278
|
|
- })
|
|
279
|
|
- .collect(Collectors.toList()));
|
|
280
|
|
- data.add(count);
|
|
281
|
|
- totalData.set(i, totalData.get(i) + count);
|
|
282
|
|
- }
|
|
283
|
|
- series.add(buildSeriesItem(brigade.getDeptName(), brigadeId, null, data, isYearType));
|
|
|
215
|
+ List<DailyTask> current = queryTasksByBrigadeId(brigade.getDeptId(), currentRange[0], currentRange[1]);
|
|
|
216
|
+ int yoyCount = countCompletedByBrigadeId(brigade.getDeptId(), yoyRange[0], yoyRange[1]);
|
|
|
217
|
+ int prevCount = prevRange != null
|
|
|
218
|
+ ? countCompletedByBrigadeId(brigade.getDeptId(), prevRange[0], prevRange[1]) : 0;
|
|
|
219
|
+
|
|
|
220
|
+ stationCurrentAll.addAll(current);
|
|
|
221
|
+ stationYoyTotal += yoyCount;
|
|
|
222
|
+ stationPrevTotal += prevCount;
|
|
|
223
|
+
|
|
|
224
|
+ series.add(buildSeriesItem(brigade.getDeptName(), brigade.getDeptId(), null,
|
|
|
225
|
+ xAxis, current, byMonth, yoyCount, prevRange != null ? prevCount : null));
|
|
284
|
226
|
}
|
|
285
|
|
- series.add(buildSeriesItem("全站", null, null, totalData, isYearType));
|
|
|
227
|
+
|
|
|
228
|
+ // 全站汇总 series
|
|
|
229
|
+ int finalYoyTotal = stationYoyTotal;
|
|
|
230
|
+ Integer finalPrevTotal = prevRange != null ? stationPrevTotal : null;
|
|
|
231
|
+ series.add(buildSeriesItem("全站", null, null, xAxis, stationCurrentAll, byMonth,
|
|
|
232
|
+ finalYoyTotal, finalPrevTotal));
|
|
286
|
233
|
return series;
|
|
287
|
234
|
}
|
|
288
|
235
|
|
|
289
|
236
|
/**
|
|
290
|
|
- * 大队视角:按主管分组,各期聚合
|
|
|
237
|
+ * 大队视角:按主管分组,各主管各一条 series
|
|
291
|
238
|
*/
|
|
292
|
|
- private List<Map<String, Object>> buildBrigadeSeries(Long brigadeId, List<LocalDate[]> periods, boolean isYearType) {
|
|
|
239
|
+ private List<Map<String, Object>> buildBrigadeSeries(Long brigadeId,
|
|
|
240
|
+ List<String> xAxis,
|
|
|
241
|
+ LocalDate[] currentRange,
|
|
|
242
|
+ LocalDate[] yoyRange,
|
|
|
243
|
+ LocalDate[] prevRange,
|
|
|
244
|
+ boolean byMonth) {
|
|
293
|
245
|
List<SysDept> subDepts = sysDeptMapper.selectChildrenDeptById(brigadeId);
|
|
294
|
246
|
if (subDepts == null) subDepts = Collections.emptyList();
|
|
295
|
247
|
|
|
|
@@ -299,40 +251,76 @@ public class DailyExamComparisonController {
|
|
299
|
251
|
|
|
300
|
252
|
List<Map<String, Object>> series = new ArrayList<>();
|
|
301
|
253
|
for (SysDept manager : managerDepts) {
|
|
302
|
|
- List<Integer> data = new ArrayList<>();
|
|
303
|
|
- for (LocalDate[] p : periods) {
|
|
304
|
|
- data.add(countCompleted(queryTasksByDepartmentId(manager.getDeptId(), p[0], p[1])));
|
|
305
|
|
- }
|
|
306
|
|
- series.add(buildSeriesItem(manager.getDeptName(), manager.getDeptId(), null, data, isYearType));
|
|
|
254
|
+ List<DailyTask> current = queryTasksByDepartmentId(manager.getDeptId(), currentRange[0], currentRange[1]);
|
|
|
255
|
+ int yoyCount = countCompleted(queryTasksByDepartmentId(manager.getDeptId(), yoyRange[0], yoyRange[1]));
|
|
|
256
|
+ Integer prevCount = prevRange != null
|
|
|
257
|
+ ? countCompleted(queryTasksByDepartmentId(manager.getDeptId(), prevRange[0], prevRange[1])) : null;
|
|
|
258
|
+ series.add(buildSeriesItem(manager.getDeptName(), manager.getDeptId(), null,
|
|
|
259
|
+ xAxis, current, byMonth, yoyCount, prevCount));
|
|
307
|
260
|
}
|
|
308
|
261
|
return series;
|
|
309
|
262
|
}
|
|
310
|
263
|
|
|
311
|
264
|
/**
|
|
312
|
|
- * 构建单条 series map,含同比/环比
|
|
313
|
|
- * data[0]=去年同期,data[1]=上期(仅QUARTER/MONTH),data[last]=本期
|
|
|
265
|
+ * 构建单条 series:趋势数据来自当前周期,yoyRate/chainRatio 由聚合总数计算
|
|
314
|
266
|
*/
|
|
315
|
267
|
private Map<String, Object> buildSeriesItem(String name, Long deptId, Long userId,
|
|
316
|
|
- List<Integer> data, boolean isYearType) {
|
|
|
268
|
+ List<String> xAxis, List<DailyTask> currentTasks,
|
|
|
269
|
+ boolean byMonth, int yoyBaseCount, Integer prevCount) {
|
|
|
270
|
+ List<Integer> data = buildSeriesData(xAxis, currentTasks, byMonth);
|
|
|
271
|
+ int currentTotal = data.stream().mapToInt(Integer::intValue).sum();
|
|
|
272
|
+
|
|
317
|
273
|
Map<String, Object> s = new LinkedHashMap<>();
|
|
318
|
274
|
s.put("name", name);
|
|
319
|
275
|
if (deptId != null) s.put("deptId", deptId);
|
|
320
|
276
|
if (userId != null) s.put("userId", userId);
|
|
321
|
277
|
s.put("data", data);
|
|
322
|
|
- int current = data.get(data.size() - 1);
|
|
323
|
|
- // 同比:本期 vs 去年同期(data[0])
|
|
324
|
|
- s.put("yoyRate", calcRate(current, data.get(0)));
|
|
325
|
|
- // 环比:本期 vs 上一期(data[last-1]),仅 QUARTER/MONTH 有效
|
|
326
|
|
- if (!isYearType && data.size() >= 3) {
|
|
327
|
|
- s.put("chainRatio", calcRate(current, data.get(data.size() - 2)));
|
|
|
278
|
+ s.put("yoyRate", calcRate(currentTotal, yoyBaseCount));
|
|
|
279
|
+ s.put("chainRatio", prevCount != null ? calcRate(currentTotal, prevCount) : null);
|
|
|
280
|
+ return s;
|
|
|
281
|
+ }
|
|
|
282
|
+
|
|
|
283
|
+ /**
|
|
|
284
|
+ * 将任务列表按时间维度汇聚,对齐到 xAxis
|
|
|
285
|
+ */
|
|
|
286
|
+ private List<Integer> buildSeriesData(List<String> xAxis, List<DailyTask> tasks, boolean byMonth) {
|
|
|
287
|
+ SimpleDateFormat sdf = new SimpleDateFormat(byMonth ? "yyyy-MM" : "yyyy-MM-dd");
|
|
|
288
|
+ Map<String, Long> countMap = tasks.stream()
|
|
|
289
|
+ .filter(t -> "COMPLETED".equals(t.getDtStatus()) && t.getDtBusinessDate() != null)
|
|
|
290
|
+ .collect(Collectors.groupingBy(
|
|
|
291
|
+ t -> sdf.format(t.getDtBusinessDate()),
|
|
|
292
|
+ Collectors.counting()));
|
|
|
293
|
+ List<Integer> result = new ArrayList<>();
|
|
|
294
|
+ for (String label : xAxis) {
|
|
|
295
|
+ result.add(countMap.getOrDefault(label, 0L).intValue());
|
|
|
296
|
+ }
|
|
|
297
|
+ return result;
|
|
|
298
|
+ }
|
|
|
299
|
+
|
|
|
300
|
+ /**
|
|
|
301
|
+ * 生成 X 轴标签:YEAR→每月(yyyy-MM);QUARTER/MONTH→每天(yyyy-MM-dd)
|
|
|
302
|
+ */
|
|
|
303
|
+ private List<String> buildXAxis(LocalDate start, LocalDate end, boolean byMonth) {
|
|
|
304
|
+ List<String> xAxis = new ArrayList<>();
|
|
|
305
|
+ if (byMonth) {
|
|
|
306
|
+ LocalDate cursor = start.withDayOfMonth(1);
|
|
|
307
|
+ LocalDate endMonth = end.withDayOfMonth(1);
|
|
|
308
|
+ while (!cursor.isAfter(endMonth)) {
|
|
|
309
|
+ xAxis.add(cursor.format(DateTimeFormatter.ofPattern("yyyy-MM")));
|
|
|
310
|
+ cursor = cursor.plusMonths(1);
|
|
|
311
|
+ }
|
|
328
|
312
|
} else {
|
|
329
|
|
- s.put("chainRatio", null);
|
|
|
313
|
+ LocalDate cursor = start;
|
|
|
314
|
+ while (!cursor.isAfter(end)) {
|
|
|
315
|
+ xAxis.add(cursor.toString());
|
|
|
316
|
+ cursor = cursor.plusDays(1);
|
|
|
317
|
+ }
|
|
330
|
318
|
}
|
|
331
|
|
- return s;
|
|
|
319
|
+ return xAxis;
|
|
332
|
320
|
}
|
|
333
|
321
|
|
|
334
|
322
|
/**
|
|
335
|
|
- * 计算变化率百分比,base=0 时返回 null
|
|
|
323
|
+ * 计算变化率百分比;base=0 时返回 null
|
|
336
|
324
|
*/
|
|
337
|
325
|
private BigDecimal calcRate(int current, int base) {
|
|
338
|
326
|
if (base == 0) return null;
|
|
|
@@ -341,16 +329,33 @@ public class DailyExamComparisonController {
|
|
341
|
329
|
.divide(BigDecimal.valueOf(base), 2, RoundingMode.HALF_UP);
|
|
342
|
330
|
}
|
|
343
|
331
|
|
|
344
|
|
- /**
|
|
345
|
|
- * 统计 COMPLETED 任务数
|
|
346
|
|
- */
|
|
347
|
332
|
private int countCompleted(List<DailyTask> tasks) {
|
|
348
|
333
|
return (int) tasks.stream().filter(t -> "COMPLETED".equals(t.getDtStatus())).count();
|
|
349
|
334
|
}
|
|
350
|
335
|
|
|
351
|
336
|
/**
|
|
352
|
|
- * 根据 type/year/quarter/month 计算起止日期
|
|
|
337
|
+ * 查询大队下所有主管的任务(通过大队子部门的 dtDepartmentId)
|
|
353
|
338
|
*/
|
|
|
339
|
+ private List<DailyTask> queryTasksByBrigadeId(Long brigadeId, LocalDate start, LocalDate end) {
|
|
|
340
|
+ List<SysDept> subDepts = sysDeptMapper.selectChildrenDeptById(brigadeId);
|
|
|
341
|
+ if (subDepts == null) subDepts = Collections.emptyList();
|
|
|
342
|
+ Set<Long> managerIds = subDepts.stream()
|
|
|
343
|
+ .filter(d -> brigadeId.equals(d.getParentId()) && "0".equals(d.getDelFlag()))
|
|
|
344
|
+ .map(SysDept::getDeptId)
|
|
|
345
|
+ .collect(Collectors.toSet());
|
|
|
346
|
+ if (managerIds.isEmpty()) return Collections.emptyList();
|
|
|
347
|
+ LambdaQueryWrapper<DailyTask> wrapper = new LambdaQueryWrapper<>();
|
|
|
348
|
+ wrapper.in(DailyTask::getDtDepartmentId, managerIds);
|
|
|
349
|
+ wrapper.ge(DailyTask::getDtBusinessDate, Date.valueOf(start));
|
|
|
350
|
+ wrapper.le(DailyTask::getDtBusinessDate, Date.valueOf(end));
|
|
|
351
|
+ wrapper.eq(DailyTask::getDelStatus, 0);
|
|
|
352
|
+ return dailyTaskMapper.selectList(wrapper);
|
|
|
353
|
+ }
|
|
|
354
|
+
|
|
|
355
|
+ private int countCompletedByBrigadeId(Long brigadeId, LocalDate start, LocalDate end) {
|
|
|
356
|
+ return countCompleted(queryTasksByBrigadeId(brigadeId, start, end));
|
|
|
357
|
+ }
|
|
|
358
|
+
|
|
354
|
359
|
private LocalDate[] resolveDateRange(String type, Integer year, Integer quarter, Integer month) {
|
|
355
|
360
|
LocalDate today = LocalDate.now();
|
|
356
|
361
|
int y = (year != null) ? year : today.getYear();
|