package com.sinata.appointment.service;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.sinata.appointment.constants.ApprovalInstanceStatus;
|
import com.sinata.appointment.constants.MiniUserType;
|
import com.sinata.appointment.dto.DashboardAppointmentStatsVo;
|
import com.sinata.appointment.dto.DashboardDailyTotalVo;
|
import com.sinata.appointment.dto.DashboardOverviewVo;
|
import com.sinata.appointment.dto.DashboardTrendPointVo;
|
import com.sinata.appointment.entity.AppointmentOrder;
|
import com.sinata.appointment.entity.ApprovalInstance;
|
import com.sinata.appointment.entity.MpUser;
|
import com.sinata.appointment.entity.SysDept;
|
import com.sinata.appointment.mapper.AppointmentOrderMapper;
|
import com.sinata.appointment.mapper.ApprovalInstanceMapper;
|
import com.sinata.appointment.mapper.MpUserMapper;
|
import com.sinata.appointment.mapper.SysDeptMapper;
|
import java.time.DayOfWeek;
|
import java.time.LocalDate;
|
import java.time.LocalDateTime;
|
import java.time.LocalTime;
|
import java.time.format.DateTimeFormatter;
|
import java.time.temporal.TemporalAdjusters;
|
import java.util.ArrayList;
|
import java.util.HashMap;
|
import java.util.HashSet;
|
import java.util.LinkedHashMap;
|
import java.util.LinkedHashSet;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.Set;
|
import lombok.RequiredArgsConstructor;
|
import org.springframework.stereotype.Service;
|
|
@Service
|
@RequiredArgsConstructor
|
public class DashboardApplicationService {
|
|
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE;
|
private static final DateTimeFormatter LABEL_FORMATTER = DateTimeFormatter.ofPattern("M-d");
|
|
private final AppointmentOrderMapper appointmentOrderMapper;
|
private final ApprovalInstanceMapper approvalInstanceMapper;
|
private final MpUserMapper mpUserMapper;
|
private final SysDeptMapper sysDeptMapper;
|
|
public DashboardOverviewVo getOverview(LocalDate startDate, LocalDate endDate) {
|
DateRange range = resolveRange(startDate, endDate);
|
LocalDate dailyDate = resolveDailyDate(range);
|
|
DashboardOverviewVo vo = new DashboardOverviewVo();
|
vo.setStartDate(format(range.startDate()));
|
vo.setEndDate(format(range.endDate()));
|
vo.setDailyDate(format(dailyDate));
|
vo.setDailyAppointmentCount(countAppointments(dailyDate, dailyDate, null));
|
vo.setTotalAppointmentCount(countAppointments(range.startDate(), range.endDate(), null));
|
vo.setRegisteredUserCount(countRegisteredUsers(range.startDate(), range.endDate()));
|
vo.setEmployeeCount(countEmployees(range.startDate(), range.endDate()));
|
return vo;
|
}
|
|
public DashboardAppointmentStatsVo getAppointmentStats(
|
LocalDate startDate, LocalDate endDate, Long visitedDeptId) {
|
DateRange range = resolveRange(startDate, endDate);
|
DashboardAppointmentStatsVo vo = new DashboardAppointmentStatsVo();
|
vo.setStartDate(format(range.startDate()));
|
vo.setEndDate(format(range.endDate()));
|
vo.setVisitedDeptId(visitedDeptId);
|
vo.setAppointmentCount(countAppointments(range.startDate(), range.endDate(), visitedDeptId));
|
vo.setInProgressCount(
|
countApprovalInstancesByCreateTime(
|
range.startDate(),
|
range.endDate(),
|
visitedDeptId,
|
ApprovalInstanceStatus.IN_PROGRESS));
|
vo.setRejectedCount(
|
countApprovalInstancesByCreateTime(
|
range.startDate(),
|
range.endDate(),
|
visitedDeptId,
|
ApprovalInstanceStatus.REJECTED));
|
vo.setCancelledCount(
|
countApprovalInstancesByCreateTime(
|
range.startDate(),
|
range.endDate(),
|
visitedDeptId,
|
ApprovalInstanceStatus.CANCELLED));
|
vo.setTrend(buildTrend(range, visitedDeptId));
|
return vo;
|
}
|
|
private List<DashboardTrendPointVo> buildTrend(DateRange range, Long visitedDeptId) {
|
List<Long> deptIds = resolveDeptIds(visitedDeptId);
|
List<DashboardTrendPointVo> orderRows =
|
appointmentOrderMapper.listDashboardTrend(range.startDate(), range.endDate(), deptIds);
|
Map<LocalDate, DashboardTrendPointVo> orderRowMap = new LinkedHashMap<>();
|
if (orderRows != null) {
|
for (DashboardTrendPointVo row : orderRows) {
|
if (row == null || row.getStatDate() == null) {
|
continue;
|
}
|
orderRowMap.put(row.getStatDate(), row);
|
}
|
}
|
Map<LocalDate, Long> inProgressMap = buildInProgressTrendMap(range, deptIds);
|
Map<LocalDate, Long> rejectedMap =
|
buildFinishedTrendMap(range, deptIds, ApprovalInstanceStatus.REJECTED);
|
Map<LocalDate, Long> cancelledMap =
|
buildFinishedTrendMap(range, deptIds, ApprovalInstanceStatus.CANCELLED);
|
|
List<DashboardTrendPointVo> result = new ArrayList<>();
|
LocalDate cursor = range.startDate();
|
while (!cursor.isAfter(range.endDate())) {
|
DashboardTrendPointVo row = orderRowMap.get(cursor);
|
DashboardTrendPointVo point = new DashboardTrendPointVo();
|
point.setStatDate(cursor);
|
point.setLabel(cursor.format(LABEL_FORMATTER));
|
point.setAppointmentCount(row == null ? 0L : defaultLong(row.getAppointmentCount()));
|
point.setInProgressCount(inProgressMap.getOrDefault(cursor, 0L));
|
point.setRejectedCount(rejectedMap.getOrDefault(cursor, 0L));
|
point.setCancelledCount(cancelledMap.getOrDefault(cursor, 0L));
|
result.add(point);
|
cursor = cursor.plusDays(1);
|
}
|
return result;
|
}
|
|
private long countAppointments(LocalDate startDate, LocalDate endDate, Long visitedDeptId) {
|
return appointmentOrderMapper.selectCount(
|
appointmentRangeWrapper(startDate, endDate, resolveDeptIds(visitedDeptId), null));
|
}
|
|
private LambdaQueryWrapper<AppointmentOrder> appointmentRangeWrapper(
|
LocalDate startDate, LocalDate endDate, List<Long> deptIds, String status) {
|
LambdaQueryWrapper<AppointmentOrder> wrapper =
|
new LambdaQueryWrapper<AppointmentOrder>()
|
.eq(AppointmentOrder::getDelFlag, 0)
|
.ge(AppointmentOrder::getCreateTime, atStartOfDay(startDate))
|
.lt(AppointmentOrder::getCreateTime, atStartOfNextDay(endDate));
|
if (deptIds != null && !deptIds.isEmpty()) {
|
wrapper.in(AppointmentOrder::getVisitedDeptId, deptIds);
|
}
|
if (status != null && !status.isBlank()) {
|
wrapper.eq(AppointmentOrder::getStatus, status);
|
}
|
return wrapper;
|
}
|
|
private long countApprovalInstancesByCreateTime(
|
LocalDate startDate, LocalDate endDate, Long visitedDeptId, String status) {
|
Long count =
|
approvalInstanceMapper.countDashboardByCreateTime(
|
startDate, endDate, resolveDeptIds(visitedDeptId), status);
|
return defaultLong(count);
|
}
|
|
private Map<LocalDate, Long> buildFinishedTrendMap(
|
DateRange range, List<Long> deptIds, String status) {
|
List<DashboardDailyTotalVo> rows =
|
approvalInstanceMapper.listDashboardFinishedTrend(
|
range.startDate(), range.endDate(), deptIds, status);
|
Map<LocalDate, Long> result = new HashMap<>();
|
if (rows == null) {
|
return result;
|
}
|
for (DashboardDailyTotalVo row : rows) {
|
if (row == null || row.getStatDate() == null) {
|
continue;
|
}
|
result.put(row.getStatDate(), defaultLong(row.getTotalCount()));
|
}
|
return result;
|
}
|
|
private Map<LocalDate, Long> buildInProgressTrendMap(DateRange range, List<Long> deptIds) {
|
List<ApprovalInstance> rows =
|
approvalInstanceMapper.listDashboardInProgressInstances(
|
range.startDate(), range.endDate(), deptIds);
|
Map<LocalDate, Long> result = new HashMap<>();
|
if (rows == null || rows.isEmpty()) {
|
return result;
|
}
|
for (ApprovalInstance row : rows) {
|
if (row.getCreateTime() == null) {
|
continue;
|
}
|
LocalDate start = row.getCreateTime().toLocalDate();
|
LocalDate end =
|
row.getFinishedAt() == null
|
? range.endDate()
|
: row.getFinishedAt().toLocalDate();
|
if (end.isBefore(range.startDate()) || start.isAfter(range.endDate())) {
|
continue;
|
}
|
LocalDate cursor = start.isBefore(range.startDate()) ? range.startDate() : start;
|
LocalDate limit = end.isAfter(range.endDate()) ? range.endDate() : end;
|
while (!cursor.isAfter(limit)) {
|
result.merge(cursor, 1L, Long::sum);
|
cursor = cursor.plusDays(1);
|
}
|
}
|
return result;
|
}
|
|
private long countRegisteredUsers(LocalDate startDate, LocalDate endDate) {
|
return mpUserMapper.selectCount(
|
new LambdaQueryWrapper<MpUser>()
|
.eq(MpUser::getDelFlag, 0)
|
.eq(MpUser::getUserType, MiniUserType.VISITOR)
|
.ge(MpUser::getCreateTime, atStartOfDay(startDate))
|
.lt(MpUser::getCreateTime, atStartOfNextDay(endDate)));
|
}
|
|
private long countEmployees(LocalDate startDate, LocalDate endDate) {
|
return mpUserMapper.selectCount(
|
new LambdaQueryWrapper<MpUser>()
|
.eq(MpUser::getDelFlag, 0)
|
.eq(MpUser::getUserType, MiniUserType.EMPLOYEE)
|
.ge(MpUser::getCreateTime, atStartOfDay(startDate))
|
.lt(MpUser::getCreateTime, atStartOfNextDay(endDate)));
|
}
|
|
private DateRange resolveRange(LocalDate startDate, LocalDate endDate) {
|
LocalDate today = LocalDate.now();
|
LocalDate defaultStart = today.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
|
LocalDate defaultEnd = today.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY));
|
|
LocalDate resolvedStart = startDate == null ? defaultStart : startDate;
|
LocalDate resolvedEnd = endDate == null ? defaultEnd : endDate;
|
if (resolvedStart.isAfter(resolvedEnd)) {
|
return new DateRange(resolvedEnd, resolvedStart);
|
}
|
return new DateRange(resolvedStart, resolvedEnd);
|
}
|
|
private LocalDate resolveDailyDate(DateRange range) {
|
LocalDate today = LocalDate.now();
|
if (!today.isBefore(range.startDate()) && !today.isAfter(range.endDate())) {
|
return today;
|
}
|
return range.endDate();
|
}
|
|
private LocalDateTime atStartOfDay(LocalDate date) {
|
return date.atStartOfDay();
|
}
|
|
private LocalDateTime atStartOfNextDay(LocalDate date) {
|
return LocalDateTime.of(date.plusDays(1), LocalTime.MIN);
|
}
|
|
private String format(LocalDate date) {
|
return date == null ? null : date.format(DATE_FORMATTER);
|
}
|
|
private List<Long> resolveDeptIds(Long visitedDeptId) {
|
if (visitedDeptId == null) {
|
return List.of();
|
}
|
List<SysDept> depts =
|
sysDeptMapper.selectList(
|
new LambdaQueryWrapper<SysDept>()
|
.eq(SysDept::getDelFlag, 0)
|
.orderByAsc(SysDept::getId));
|
if (depts == null || depts.isEmpty()) {
|
return List.of(visitedDeptId);
|
}
|
Map<Long, List<Long>> childrenMap = new HashMap<>();
|
Set<Long> existingIds = new HashSet<>();
|
for (SysDept dept : depts) {
|
if (dept.getId() == null) {
|
continue;
|
}
|
existingIds.add(dept.getId());
|
Long parentId = dept.getParentId() == null ? 0L : dept.getParentId();
|
childrenMap.computeIfAbsent(parentId, key -> new ArrayList<>()).add(dept.getId());
|
}
|
if (!existingIds.contains(visitedDeptId)) {
|
return List.of(visitedDeptId);
|
}
|
LinkedHashSet<Long> result = new LinkedHashSet<>();
|
ArrayList<Long> stack = new ArrayList<>();
|
stack.add(visitedDeptId);
|
while (!stack.isEmpty()) {
|
Long current = stack.remove(stack.size() - 1);
|
if (current == null || !result.add(current)) {
|
continue;
|
}
|
for (Long childId : childrenMap.getOrDefault(current, List.of())) {
|
if (Objects.nonNull(childId)) {
|
stack.add(childId);
|
}
|
}
|
}
|
return new ArrayList<>(result);
|
}
|
|
private long defaultLong(Long value) {
|
return value == null ? 0L : value;
|
}
|
|
private record DateRange(LocalDate startDate, LocalDate endDate) {}
|
}
|