fix(workbench): correct home overview aggregation

This commit is contained in:
yelan
2026-09-01 15:34:59 +08:00
parent b4184260da
commit dc86ee9149
6 changed files with 310 additions and 58 deletions
+123 -48
View File
@@ -8,7 +8,10 @@ import type {
WorkbenchDocumentRanking,
WorkbenchOverviewQuery,
WorkbenchOverviewResponse,
SigningApiOptions,
SigningTemplate,
SigningTaskRecord,
SigningTaskQuery,
WorkbenchTodoTask,
WorkbenchTrendPoint,
} from './types'
@@ -26,6 +29,7 @@ const mockCampusOverviews: Record<WorkbenchCampus, MockCampusOverview> = {
本部院区: {
summary: {
todaySigned: 36,
todaySignedChange: 2,
pendingPatientSigning: 18,
todayOverdue: 2,
availableTemplates: 128,
@@ -93,6 +97,7 @@ const mockCampusOverviews: Record<WorkbenchCampus, MockCampusOverview> = {
东院区: {
summary: {
todaySigned: 22,
todaySignedChange: 1,
pendingPatientSigning: 11,
todayOverdue: 1,
availableTemplates: 76,
@@ -160,6 +165,7 @@ const mockCampusOverviews: Record<WorkbenchCampus, MockCampusOverview> = {
西院区: {
summary: {
todaySigned: 17,
todaySignedChange: 2,
pendingPatientSigning: 9,
todayOverdue: 2,
availableTemplates: 54,
@@ -246,6 +252,47 @@ function createRanking(
.sort((left, right) => right.signedCount - left.signedCount)
}
function countAvailableTemplates(templates: SigningTemplate[]) {
return new Set(templates.map((template) => template.id)).size
}
function countCoveredDepartments(templates: SigningTemplate[]) {
return new Set(
templates
.map((template) => {
if (template.departmentId) {
return `id:${template.departmentId}`
}
return template.department && template.department !== '全院通用'
? `name:${template.department}`
: null
})
.filter((department): department is string => Boolean(department)),
).size
}
async function getAllSigningTasks(
query: SigningTaskQuery,
options: SigningApiOptions,
): Promise<SigningTaskRecord[]> {
const pageSize = 200
const firstPage = await getSigningTasks({ ...query, page: 1, pageSize }, options)
const totalPages = Math.ceil(firstPage.total / Math.max(firstPage.pageSize, 1))
if (totalPages <= 1) {
return firstPage.records
}
const remainingPages = await Promise.all(
Array.from({ length: totalPages - 1 }, (_, index) =>
getSigningTasks({ ...query, page: index + 2, pageSize }, options),
),
)
return [firstPage, ...remainingPages].flatMap((page) => page.records)
}
function isWorkbenchCampus(value: string): value is WorkbenchCampus {
return value === '本部院区' || value === '东院区' || value === '西院区'
}
@@ -268,8 +315,31 @@ function isSameDay(left: Date | null, right: Date) {
)
}
function getTaskDate(task: SigningTaskRecord) {
return parseDate(task.createdAt) ?? parseDate(task.updatedAt)
function getSignedDate(task: SigningTaskRecord) {
return parseDate(task.signedAt)
}
function getExpiredDate(task: SigningTaskRecord) {
return parseDate(task.expiresAt)
}
function getUpdatedDate(task: SigningTaskRecord) {
return parseDate(task.updatedAt) ?? parseDate(task.createdAt)
}
function isWithinPeriod(value: Date | null, today: Date, period: HomeRankingPeriod) {
if (!value) {
return false
}
const start = new Date(today)
start.setHours(0, 0, 0, 0)
start.setDate(start.getDate() - period + 1)
const end = new Date(today)
end.setHours(23, 59, 59, 999)
return value >= start && value <= end
}
function isPendingTask(task: SigningTaskRecord) {
@@ -284,10 +354,15 @@ function createLiveOverview(
): WorkbenchOverviewResponse {
const today = new Date()
const signedTasks = tasks.filter((task) => task.status === 'signed')
const todaySigned = signedTasks.filter((task) => isSameDay(getTaskDate(task), today)).length
const todaySigned = signedTasks.filter((task) => isSameDay(getSignedDate(task), today)).length
const yesterday = new Date(today)
yesterday.setDate(yesterday.getDate() - 1)
const yesterdaySigned = signedTasks.filter((task) =>
isSameDay(getSignedDate(task), yesterday),
).length
const pendingTasks = tasks.filter(isPendingTask)
const todayOverdue = tasks.filter(
(task) => task.status === 'expired' && isSameDay(getTaskDate(task), today),
(task) => task.status === 'expired' && isSameDay(getExpiredDate(task), today),
).length
const trend = Array.from({ length: rankingPeriod }, (_, index) => {
@@ -297,15 +372,15 @@ function createLiveOverview(
return {
label: `${date.getMonth() + 1}/${date.getDate()}`,
value: signedTasks.filter((task) => isSameDay(getTaskDate(task), date)).length,
value: signedTasks.filter((task) => isSameDay(getSignedDate(task), date)).length,
}
})
const todos = tasks
.filter((task) => isPendingTask(task) || task.status === 'expired')
.sort((left, right) => {
const leftDate = getTaskDate(left)?.getTime() ?? 0
const rightDate = getTaskDate(right)?.getTime() ?? 0
const leftDate = getUpdatedDate(left)?.getTime() ?? 0
const rightDate = getUpdatedDate(right)?.getTime() ?? 0
return rightDate - leftDate
})
.slice(0, 8)
@@ -321,19 +396,22 @@ function createLiveOverview(
}))
const rankingMap = new Map<string, WorkbenchDocumentRanking>()
signedTasks.forEach((task) => {
const current = rankingMap.get(task.documentName)
rankingMap.set(task.documentName, {
id: current?.id ?? task.documentId,
documentName: task.documentName,
department: task.department,
signedCount: (current?.signedCount ?? 0) + 1,
signedTasks
.filter((task) => isWithinPeriod(getSignedDate(task), today, rankingPeriod))
.forEach((task) => {
const current = rankingMap.get(task.documentId)
rankingMap.set(task.documentId, {
id: current?.id ?? task.documentId,
documentName: task.documentName,
department: task.department,
signedCount: (current?.signedCount ?? 0) + 1,
})
})
})
return {
summary: {
todaySigned,
todaySignedChange: todaySigned - yesterdaySigned,
pendingPatientSigning: pendingTasks.length,
todayOverdue,
availableTemplates: templateCount,
@@ -361,38 +439,35 @@ export function getWorkbenchOverview(
})
}
return Promise.allSettled([getSigningTemplates(), getCampuses()]).then(
async ([templatesResult, campusesResult]) => {
const templates = templatesResult.status === 'fulfilled' ? templatesResult.value : []
const campusNames: Record<string, WorkbenchCampus> =
campusesResult.status === 'fulfilled'
? (Object.fromEntries(
campusesResult.value
.filter((campus) => isWorkbenchCampus(campus.name))
.map((campus) => [campus.id, campus.name]),
) as Record<string, WorkbenchCampus>)
: {}
const tasksResponse = await getSigningTasks(
{
page: 1,
pageSize: 200,
status: 'all',
dateRange: 'all',
campus: 'all',
method: 'all',
},
{ templates, campusNames },
)
const scopedTasks = Object.keys(campusNames).length
? tasksResponse.records.filter((task) => task.campus === query.campus)
: tasksResponse.records
return getCampuses().then(async (campuses) => {
const campus = campuses.find((item) => item.name === query.campus)
return createLiveOverview(
scopedTasks,
templates.length,
new Set(templates.map((template) => template.department)).size,
query.rankingPeriod,
)
},
)
if (!campus) {
throw new Error(`未找到院区:${query.campus}`)
}
const campusIds = Object.fromEntries(campuses.map((item) => [item.name, item.id]))
const campusNames = Object.fromEntries(
campuses.filter((item) => isWorkbenchCampus(item.name)).map((item) => [item.id, item.name]),
) as Record<string, WorkbenchCampus>
const templates = await getSigningTemplates({ campusId: campus.id })
const tasks = await getAllSigningTasks(
{
page: 1,
pageSize: 200,
status: 'all',
dateRange: 'all',
campus: query.campus,
method: 'all',
},
{ templates, campusIds, campusNames },
)
return createLiveOverview(
tasks,
countAvailableTemplates(templates),
countCoveredDepartments(templates),
query.rankingPeriod,
)
})
}