|
|
@@ -16543,6 +16543,198 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
|
|
|
return msg;
|
|
|
}
|
|
|
|
|
|
+ private static class FlagDepartmentScope {
|
|
|
+ private final List<Integer> scopedDeptIds;
|
|
|
+ private final Map<Integer, Integer> deptToFlagRoot;
|
|
|
+
|
|
|
+ private FlagDepartmentScope(List<Integer> scopedDeptIds, Map<Integer, Integer> deptToFlagRoot) {
|
|
|
+ this.scopedDeptIds = scopedDeptIds;
|
|
|
+ this.deptToFlagRoot = deptToFlagRoot;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private FlagDepartmentScope buildFlagDepartmentScope(Integer companyId) {
|
|
|
+ List<Department> allDeptList = departmentMapper.selectList(new QueryWrapper<Department>().eq("company_id", companyId));
|
|
|
+ List<Department> flagDepartments = allDeptList.stream()
|
|
|
+ .filter(department -> Boolean.TRUE.equals(department.getFlag()))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ Map<Integer, Integer> deptToFlagRoot = new HashMap<>();
|
|
|
+ List<Integer> scopedDeptIds = new ArrayList<>();
|
|
|
+ for (Department flagDept : flagDepartments) {
|
|
|
+ Integer flagDeptId = flagDept.getDepartmentId();
|
|
|
+ deptToFlagRoot.put(flagDeptId, flagDeptId);
|
|
|
+ scopedDeptIds.add(flagDeptId);
|
|
|
+ for (Department subDept : getSubDepts(flagDept, allDeptList)) {
|
|
|
+ deptToFlagRoot.put(subDept.getDepartmentId(), flagDeptId);
|
|
|
+ scopedDeptIds.add(subDept.getDepartmentId());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return new FlagDepartmentScope(scopedDeptIds, deptToFlagRoot);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public HttpRespMsg getDashboardExtraOvertimeByDept(Integer companyId, String ymonth, String startDate, String endDate) {
|
|
|
+ return getDashboardExtraOvertimeByDept(companyId, ymonth, startDate, endDate, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public HttpRespMsg getDashboardExtraOvertimeByDept(Integer companyId, String ymonth, String startDate, String endDate, String deptMode) {
|
|
|
+ HttpRespMsg msg = new HttpRespMsg();
|
|
|
+ DashboardDateFilter dateFilter = resolveDashboardDateFilter(ymonth, startDate, endDate, msg);
|
|
|
+ if (dateFilter == null) {
|
|
|
+ return msg;
|
|
|
+ }
|
|
|
+ boolean useTopLevel = "top".equals(deptMode);
|
|
|
+ FlagDepartmentScope scope = buildFlagDepartmentScope(companyId);
|
|
|
+ if (scope.scopedDeptIds.isEmpty()) {
|
|
|
+ msg.data = new ArrayList<>();
|
|
|
+ return msg;
|
|
|
+ }
|
|
|
+
|
|
|
+ List<HashMap> userList = userCorpwxTimeMapper.getExtraWorkHoursList(null, companyId, scope.scopedDeptIds, null);
|
|
|
+ if (userList.isEmpty()) {
|
|
|
+ msg.data = new ArrayList<>();
|
|
|
+ return msg;
|
|
|
+ }
|
|
|
+
|
|
|
+ List<String> corpwxUserIds = userList.stream()
|
|
|
+ .map(row -> row.get("corpwxUserid"))
|
|
|
+ .filter(Objects::nonNull)
|
|
|
+ .map(String::valueOf)
|
|
|
+ .filter(id -> !id.isEmpty() && !"null".equals(id))
|
|
|
+ .distinct()
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ Map<String, Double> overtimeByCorpwxId = new HashMap<>();
|
|
|
+ if (!corpwxUserIds.isEmpty()) {
|
|
|
+ userCorpwxTimeMapper.getOvertimeByCorpwxUserIdsInRange(
|
|
|
+ companyId,
|
|
|
+ dateFilter.startDate.toString(),
|
|
|
+ dateFilter.endDate.toString(),
|
|
|
+ corpwxUserIds)
|
|
|
+ .forEach(row -> overtimeByCorpwxId.put(
|
|
|
+ String.valueOf(row.get("corpwxUserid")),
|
|
|
+ toDouble(row.get("overtime"))));
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Map<String, Object>> userRows = new ArrayList<>();
|
|
|
+ userList.forEach(row -> {
|
|
|
+ Integer deptId = toInteger(row.get("departmentId"));
|
|
|
+ if (deptId == null || !scope.deptToFlagRoot.containsKey(deptId)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ String userId = row.get("id") == null ? null : String.valueOf(row.get("id"));
|
|
|
+ String corpwxUserid = row.get("corpwxUserid") == null ? null : String.valueOf(row.get("corpwxUserid"));
|
|
|
+ double overtimeHours = corpwxUserid == null || "null".equals(corpwxUserid)
|
|
|
+ ? 0D
|
|
|
+ : overtimeByCorpwxId.getOrDefault(corpwxUserid, 0D);
|
|
|
+ Integer isActive = toInteger(row.get("isActive"));
|
|
|
+ HashMap<String, Object> userRow = new HashMap<>();
|
|
|
+ userRow.put("deptId", deptId);
|
|
|
+ userRow.put("userId", userId);
|
|
|
+ userRow.put("overtimeHours", overtimeHours);
|
|
|
+ userRow.put("countForAvg", shouldCountForExtraOvertimeAvg(
|
|
|
+ isActive,
|
|
|
+ toLocalDate(row.get("inactiveDate")),
|
|
|
+ dateFilter.startDate));
|
|
|
+ userRows.add(userRow);
|
|
|
+ });
|
|
|
+
|
|
|
+ Map<Integer, Department> departmentMap = getDepartmentMap(companyId);
|
|
|
+ List<Map<String, Object>> aggregated;
|
|
|
+ if (useTopLevel) {
|
|
|
+ aggregated = rollupExtraOvertimeByFlagDept(userRows, scope.deptToFlagRoot);
|
|
|
+ } else {
|
|
|
+ aggregated = rollupExtraOvertimeByDirectDept(userRows);
|
|
|
+ }
|
|
|
+ msg.data = buildExtraOvertimeDeptResult(aggregated, departmentMap);
|
|
|
+ return msg;
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<Map<String, Object>> rollupExtraOvertimeByFlagDept(List<Map<String, Object>> userRows, Map<Integer, Integer> deptToFlagRoot) {
|
|
|
+ Map<Integer, HashMap<String, Object>> grouped = new LinkedHashMap<>();
|
|
|
+ userRows.forEach(row -> {
|
|
|
+ Integer deptId = toInteger(row.get("deptId"));
|
|
|
+ Integer flagRootId = deptToFlagRoot.get(deptId);
|
|
|
+ if (flagRootId == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ HashMap<String, Object> target = grouped.computeIfAbsent(flagRootId, key -> {
|
|
|
+ HashMap<String, Object> map = new HashMap<>();
|
|
|
+ map.put("deptId", flagRootId);
|
|
|
+ map.put("overtimeHours", 0D);
|
|
|
+ map.put("memberIds", new HashSet<String>());
|
|
|
+ return map;
|
|
|
+ });
|
|
|
+ target.put("overtimeHours", toDouble(target.get("overtimeHours")) + toDouble(row.get("overtimeHours")));
|
|
|
+ String userId = row.get("userId") == null ? null : String.valueOf(row.get("userId"));
|
|
|
+ if (!StringUtils.isEmpty(userId) && Boolean.TRUE.equals(row.get("countForAvg"))) {
|
|
|
+ ((Set<String>) target.get("memberIds")).add(userId);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ grouped.values().forEach(row -> {
|
|
|
+ Set<String> memberIds = (Set<String>) row.remove("memberIds");
|
|
|
+ row.put("memberCount", memberIds != null ? memberIds.size() : 0);
|
|
|
+ });
|
|
|
+ return new ArrayList<>(grouped.values());
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<Map<String, Object>> rollupExtraOvertimeByDirectDept(List<Map<String, Object>> userRows) {
|
|
|
+ Map<Integer, HashMap<String, Object>> grouped = new LinkedHashMap<>();
|
|
|
+ userRows.forEach(row -> {
|
|
|
+ Integer deptId = toInteger(row.get("deptId"));
|
|
|
+ HashMap<String, Object> target = grouped.computeIfAbsent(deptId, key -> {
|
|
|
+ HashMap<String, Object> map = new HashMap<>();
|
|
|
+ map.put("deptId", deptId);
|
|
|
+ map.put("overtimeHours", 0D);
|
|
|
+ map.put("memberIds", new HashSet<String>());
|
|
|
+ return map;
|
|
|
+ });
|
|
|
+ target.put("overtimeHours", toDouble(target.get("overtimeHours")) + toDouble(row.get("overtimeHours")));
|
|
|
+ String userId = row.get("userId") == null ? null : String.valueOf(row.get("userId"));
|
|
|
+ if (!StringUtils.isEmpty(userId) && Boolean.TRUE.equals(row.get("countForAvg"))) {
|
|
|
+ ((Set<String>) target.get("memberIds")).add(userId);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ grouped.values().forEach(row -> {
|
|
|
+ Set<String> memberIds = (Set<String>) row.remove("memberIds");
|
|
|
+ row.put("memberCount", memberIds != null ? memberIds.size() : 0);
|
|
|
+ });
|
|
|
+ return new ArrayList<>(grouped.values());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 额外加班人均统计口径:查询区间内曾在职的员工计入。
|
|
|
+ * 在职员工全部计入;离职员工仅当离职日晚于等于区间开始日时计入。
|
|
|
+ */
|
|
|
+ private boolean shouldCountForExtraOvertimeAvg(Integer isActive, LocalDate inactiveDate, LocalDate rangeStart) {
|
|
|
+ if (isActive == null || isActive == 1) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ if (inactiveDate == null || rangeStart == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return !inactiveDate.isBefore(rangeStart);
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<HashMap> buildExtraOvertimeDeptResult(List<Map<String, Object>> deptReportList, Map<Integer, Department> departmentMap) {
|
|
|
+ List<HashMap> resultList = new ArrayList<>();
|
|
|
+ deptReportList.stream()
|
|
|
+ .sorted((left, right) -> Double.compare(toDouble(right.get("overtimeHours")), toDouble(left.get("overtimeHours"))))
|
|
|
+ .forEach(row -> {
|
|
|
+ Integer deptId = toInteger(row.get("deptId"));
|
|
|
+ Department department = departmentMap.get(deptId);
|
|
|
+ double overtimeHours = roundHours(row.get("overtimeHours"));
|
|
|
+ double memberCount = toDouble(row.get("memberCount"));
|
|
|
+ HashMap<String, Object> map = new HashMap<>();
|
|
|
+ fillDepartmentInfo(map, deptId, departmentMap);
|
|
|
+ map.put("overtimeHours", overtimeHours);
|
|
|
+ map.put("avgOvertimeHours", memberCount > 0D ? roundHours(overtimeHours / memberCount) : 0D);
|
|
|
+ map.put("memberCount", memberCount);
|
|
|
+ resultList.add(map);
|
|
|
+ });
|
|
|
+ return resultList;
|
|
|
+ }
|
|
|
+
|
|
|
private String normalizeYearMonth(String ymonth) {
|
|
|
if (StringUtils.isEmpty(ymonth)) {
|
|
|
return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM"));
|
|
|
@@ -16963,6 +17155,26 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
|
|
|
return BigDecimal.valueOf(toDouble(value)).setScale(2, RoundingMode.HALF_UP).doubleValue();
|
|
|
}
|
|
|
|
|
|
+ private LocalDate toLocalDate(Object value) {
|
|
|
+ if (value == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ if (value instanceof LocalDate) {
|
|
|
+ return (LocalDate) value;
|
|
|
+ }
|
|
|
+ if (value instanceof java.sql.Date) {
|
|
|
+ return ((java.sql.Date) value).toLocalDate();
|
|
|
+ }
|
|
|
+ if (value instanceof java.util.Date) {
|
|
|
+ return new java.sql.Date(((java.util.Date) value).getTime()).toLocalDate();
|
|
|
+ }
|
|
|
+ String str = String.valueOf(value);
|
|
|
+ if (StringUtils.isEmpty(str) || "null".equals(str)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return LocalDate.parse(str.length() > 10 ? str.substring(0, 10) : str);
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public HttpRespMsg getAllReportListByToken(String json) {
|
|
|
HttpRespMsg msg=new HttpRespMsg();
|