R21/R22: 补齐公网安全文档 试点观测与最小导入脚本
这个提交包含在:
@@ -125,6 +125,9 @@ function renderMarkdown(report) {
|
||||
lines.push(`- 本周周报提交:${report.weeklySubmittedCount}/${report.activeEmployeeCount}(${report.weeklySubmissionRate}%)`);
|
||||
lines.push(`- 今日状态更新:${report.presenceUpdatedCount}/${report.activeEmployeeCount}(${report.presenceCoverageRate}%)`);
|
||||
lines.push(`- 当前公告数:${report.announcementCount}`);
|
||||
lines.push(`- 活跃项目数:${report.activeProjectCount}`);
|
||||
lines.push(`- 近 7 天有任务更新的项目:${report.projectTaskUpdated7dCount}/${report.activeProjectCount}(${report.projectTaskUpdateRate}%)`);
|
||||
lines.push(`- 里程碑按期完成:${report.milestoneOnTimeCount}/${report.milestoneDueCount}(${report.milestoneOnTimeRate}%)`);
|
||||
lines.push(`- 入职中:${report.onboardingCount}`);
|
||||
lines.push(`- 离职中:${report.offboardingCount}`);
|
||||
lines.push(`- 资产待归还:${report.assetReturnPendingCount}`);
|
||||
@@ -135,6 +138,7 @@ function renderMarkdown(report) {
|
||||
lines.push("## 建议动作");
|
||||
lines.push("- 将本快照内容填入 `deploy/trialrun/observation-template.md` 对应周次。");
|
||||
lines.push("- 若指标未达标,优先通过 Portal、公告与站内通知补提醒,不新增外部表格。");
|
||||
lines.push("- 多平台重复录入次数和表格替代率仍建议在 observation template 中人工补记。");
|
||||
return `${lines.join("\n")}\n`;
|
||||
}
|
||||
|
||||
@@ -162,6 +166,9 @@ async function main() {
|
||||
onboardingCount,
|
||||
offboardingCount,
|
||||
assetReturnPendingCount,
|
||||
activeProjects,
|
||||
updatedProjectTasks,
|
||||
dueMilestones,
|
||||
] = await Promise.all([
|
||||
prisma.authSession.findMany({
|
||||
where: {
|
||||
@@ -185,6 +192,25 @@ async function main() {
|
||||
prisma.employee.count({ where: { employment_status: "onboarding", status: "active" } }),
|
||||
prisma.employee.count({ where: { employment_status: "offboarding" } }),
|
||||
prisma.assetCard.count({ where: { assignee: { is: { employment_status: "offboarding" } } } }),
|
||||
prisma.project.findMany({
|
||||
where: { status: { in: ["draft", "active", "paused"] } },
|
||||
select: { id: true },
|
||||
}),
|
||||
prisma.projectTask.findMany({
|
||||
where: {
|
||||
updated_at: { gte: since },
|
||||
project: { status: { in: ["draft", "active", "paused"] } },
|
||||
},
|
||||
distinct: ["project_id"],
|
||||
select: { project_id: true },
|
||||
}),
|
||||
prisma.projectMilestone.findMany({
|
||||
where: {
|
||||
due_date: { not: null },
|
||||
project: { status: { in: ["draft", "active", "paused"] } },
|
||||
},
|
||||
select: { due_date: true, completed_at: true, status: true },
|
||||
}),
|
||||
]);
|
||||
|
||||
let presenceUpdatedCount = 0;
|
||||
@@ -195,6 +221,15 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
const milestoneOnTimeCount = dueMilestones.filter((item) => {
|
||||
if (item.status !== "done" || !item.completed_at || !item.due_date) {
|
||||
return false;
|
||||
}
|
||||
const dueEnd = new Date(item.due_date);
|
||||
dueEnd.setUTCHours(23, 59, 59, 999);
|
||||
return item.completed_at.getTime() <= dueEnd.getTime();
|
||||
}).length;
|
||||
|
||||
const report = {
|
||||
generatedAt: new Date().toISOString(),
|
||||
date: dateString,
|
||||
@@ -210,6 +245,14 @@ async function main() {
|
||||
presenceMissingCount: Math.max(0, activeEmployeeCount - presenceUpdatedCount),
|
||||
presenceCoverageRate: toPercent(presenceUpdatedCount, activeEmployeeCount),
|
||||
announcementCount,
|
||||
activeProjectCount: activeProjects.length,
|
||||
projectTaskUpdated7dCount: updatedProjectTasks.length,
|
||||
projectTaskUpdateRate: toPercent(updatedProjectTasks.length, activeProjects.length),
|
||||
milestoneDueCount: dueMilestones.length,
|
||||
milestoneOnTimeCount,
|
||||
milestoneOnTimeRate: toPercent(milestoneOnTimeCount, dueMilestones.length),
|
||||
duplicateEntryCount: null,
|
||||
spreadsheetReplacementRate: null,
|
||||
onboardingCount,
|
||||
offboardingCount,
|
||||
assetReturnPendingCount,
|
||||
|
||||
在新工单中引用
屏蔽一个用户