R59 A1-A6:极简考勤打卡

- 新增 AttendanceRecord 与 AttendancePolicy 模型
- 上下班打卡、今日状态、月度统计端点
- 考勤管理页面(我的/团队/设置三 Tab)
- 移动端快速打卡入口
- 管理员补卡与审计日志
- 单测覆盖打卡幂等、迟到计算、工时计算、权限边界
这个提交包含在:
2026-04-11 21:57:27 +08:00
父节点 ba48e7d1d4
当前提交 677aa6cac3
共修改 25 个文件,包含 3081 行新增和 2 行删除
+49
查看文件
@@ -289,6 +289,13 @@ const DEFAULT_FEATURE_FLAGS = [
department_ids: [],
note: "bootstrap default: force_off",
},
{
flag_key: "attendance_v1",
override_mode: "force_off",
rollout_type: "global",
department_ids: [],
note: "bootstrap default: force_off",
},
{
flag_key: "weekly_report_v1",
override_mode: "force_off",
@@ -552,6 +559,45 @@ async function seedFeatureFlags(prisma) {
}
}
async function seedAttendancePolicy(prisma) {
const now = new Date();
const current = await prisma.attendancePolicy.findFirst({
orderBy: { created_at: "asc" },
select: { id: true },
});
if (current) {
await prisma.attendancePolicy.update({
where: { id: current.id },
data: {
work_start_time: "09:00",
work_end_time: "18:00",
lunch_start_time: "12:00",
lunch_end_time: "13:00",
late_tolerance_minutes: 0,
enabled: true,
updated_at: now,
updated_by: null,
},
});
return;
}
await prisma.attendancePolicy.create({
data: {
work_start_time: "09:00",
work_end_time: "18:00",
lunch_start_time: "12:00",
lunch_end_time: "13:00",
late_tolerance_minutes: 0,
enabled: true,
created_at: now,
updated_at: now,
updated_by: null,
},
});
}
async function seedBudgetControlRules(prisma) {
const now = new Date();
@@ -794,6 +840,7 @@ async function main() {
await seedWorkflowDefinition(prisma);
await seedLeaveTypes(prisma);
await seedFeatureFlags(prisma);
await seedAttendancePolicy(prisma);
await seedBudgetControlRules(prisma);
// eslint-disable-next-line no-console
@@ -818,6 +865,8 @@ async function main() {
console.log(`- leave types: ${DEFAULT_LEAVE_TYPES.length} seeded with default policies`);
// eslint-disable-next-line no-console
console.log(`- feature flags: ${DEFAULT_FEATURE_FLAGS.length} seeded`);
// eslint-disable-next-line no-console
console.log("- attendance policy: default 09:00-18:00 seeded");
} finally {
await prisma.$disconnect();
await pool.end();
+1
查看文件
@@ -458,6 +458,7 @@ async function main() {
"announcements_v1",
"global_search_v1",
"presence_board_v1",
"attendance_v1",
"weekly_report_v1",
"employee_lifecycle_v1",
"todo_board_v1",
+1
查看文件
@@ -260,6 +260,7 @@ async function main() {
"announcements_v1",
"global_search_v1",
"presence_board_v1",
"attendance_v1",
"weekly_report_v1",
"employee_lifecycle_v1",
"todo_board_v1",