R64 A1-A6:新员工入门引导
这个提交包含在:
@@ -28,6 +28,22 @@ function parseNotificationPreferences(body) {
|
||||
return parsed.data;
|
||||
}
|
||||
|
||||
function buildProfileCompleteness(employee) {
|
||||
const hasEmergencyContact = Boolean(employee?.emergency_contact_name && employee?.emergency_contact_phone);
|
||||
const hasBankAccount = Boolean(employee?.bank_account_encrypted && employee?.bank_name);
|
||||
const hasAvatar = Boolean(employee?.avatar_path);
|
||||
const hasIdNumber = Boolean(employee?.id_number_encrypted);
|
||||
const completedCount = [hasEmergencyContact, hasBankAccount, hasAvatar, hasIdNumber].filter(Boolean).length;
|
||||
|
||||
return {
|
||||
hasEmergencyContact,
|
||||
hasBankAccount,
|
||||
hasAvatar,
|
||||
hasIdNumber,
|
||||
score: completedCount * 25,
|
||||
};
|
||||
}
|
||||
|
||||
async function meRoutes(fastify) {
|
||||
fastify.get(
|
||||
"/me",
|
||||
@@ -46,6 +62,12 @@ async function meRoutes(fastify) {
|
||||
department_id: true,
|
||||
direct_manager_id: true,
|
||||
status: true,
|
||||
emergency_contact_name: true,
|
||||
emergency_contact_phone: true,
|
||||
bank_account_encrypted: true,
|
||||
bank_name: true,
|
||||
avatar_path: true,
|
||||
id_number_encrypted: true,
|
||||
notification_push_enabled: true,
|
||||
roles: { select: { role_code: true } },
|
||||
wecom_identity_binding: { select: { external_user_id: true, created_at: true } },
|
||||
@@ -53,6 +75,7 @@ async function meRoutes(fastify) {
|
||||
});
|
||||
|
||||
const roleCodes = (employee?.roles ?? []).map((r) => r.role_code);
|
||||
const profileCompleteness = buildProfileCompleteness(employee);
|
||||
return sendData(reply, request, {
|
||||
id: employee?.id ?? request.employee.id,
|
||||
employeeNo: employee?.employee_no ?? "",
|
||||
@@ -64,6 +87,7 @@ async function meRoutes(fastify) {
|
||||
status: employee?.status ?? "active",
|
||||
notificationPushEnabled: employee?.notification_push_enabled ?? true,
|
||||
roleCodes,
|
||||
profileCompleteness,
|
||||
wecomBinding: employee?.wecom_identity_binding ? { externalUserId: employee.wecom_identity_binding.external_user_id, createdAt: employee.wecom_identity_binding.created_at?.toISOString?.() ?? null } : null,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -14,6 +14,12 @@ function createPrismaState(overrides = {}) {
|
||||
department_id: 'department-1',
|
||||
direct_manager_id: 'manager-1',
|
||||
status: 'active',
|
||||
emergency_contact_name: null,
|
||||
emergency_contact_phone: null,
|
||||
bank_account_encrypted: '',
|
||||
bank_name: '',
|
||||
avatar_path: null,
|
||||
id_number_encrypted: null,
|
||||
notification_push_enabled: true,
|
||||
roles: [{ role_code: 'employee' }],
|
||||
wecom_identity_binding: null,
|
||||
@@ -107,12 +113,48 @@ describe('me routes', () => {
|
||||
expect.objectContaining({
|
||||
id: 'employee-1',
|
||||
notificationPushEnabled: false,
|
||||
profileCompleteness: {
|
||||
hasEmergencyContact: false,
|
||||
hasBankAccount: false,
|
||||
hasAvatar: false,
|
||||
hasIdNumber: false,
|
||||
score: 0,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it('calculates profileCompleteness score from completed profile fields', async () => {
|
||||
const { prisma } = createPrismaState({
|
||||
emergency_contact_name: '联系人',
|
||||
emergency_contact_phone: '13900000000',
|
||||
bank_account_encrypted: 'ENC',
|
||||
bank_name: '招商银行',
|
||||
avatar_path: null,
|
||||
id_number_encrypted: null,
|
||||
});
|
||||
const app = await buildApp(prisma);
|
||||
|
||||
const response = await app.inject({
|
||||
method: 'GET',
|
||||
url: '/api/me',
|
||||
headers: { authorization: 'Bearer test-token' },
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.json().data.profileCompleteness).toEqual({
|
||||
hasEmergencyContact: true,
|
||||
hasBankAccount: true,
|
||||
hasAvatar: false,
|
||||
hasIdNumber: false,
|
||||
score: 50,
|
||||
});
|
||||
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it('updates notification preferences for current employee', async () => {
|
||||
const { prisma, employeeRecord, auditEntries } = createPrismaState();
|
||||
const app = await buildApp(prisma);
|
||||
|
||||
在新工单中引用
屏蔽一个用户