吸收数字员工通知订阅策略
This commit is contained in:
@@ -823,6 +823,62 @@ describe("digital employee state service", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("persists notification preferences and keeps notification overlays in UI state", async () => {
|
||||
const { readDigitalEmployeeUiState, saveDigitalEmployeeUiState } = await import("./stateService");
|
||||
|
||||
saveDigitalEmployeeUiState({
|
||||
action: "update_notification_preferences",
|
||||
metadata: { source: "notification-popover" },
|
||||
state: {
|
||||
selectedDutyIdsByDate: {},
|
||||
confirmedDates: {},
|
||||
reportScheduleTime: "18:00",
|
||||
reportGeneratedAtByDate: {},
|
||||
decisionResultsByDate: {},
|
||||
notificationStateById: {
|
||||
"task:task-1": { status: "read", read_at: "2026-06-07T07:00:00.000Z" },
|
||||
},
|
||||
notificationPreferences: {
|
||||
enabled: true,
|
||||
muted_kinds: ["task_finished", "need_input"],
|
||||
muted_levels: ["info"],
|
||||
updated_at: "2026-06-07T08:00:00.000Z",
|
||||
},
|
||||
consoleRole: "sales",
|
||||
rolePreferences: {},
|
||||
profile: {
|
||||
name: "飞天数字员工",
|
||||
company: "qimingclaw 客户端",
|
||||
position: "客户端任务执行员",
|
||||
currentStatus: "working",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(readDigitalEmployeeUiState()).toMatchObject({
|
||||
notificationStateById: {
|
||||
"task:task-1": { status: "read", read_at: "2026-06-07T07:00:00.000Z" },
|
||||
},
|
||||
notificationPreferences: {
|
||||
enabled: true,
|
||||
muted_kinds: ["task_finished", "need_input"],
|
||||
muted_levels: ["info"],
|
||||
updated_at: "2026-06-07T08:00:00.000Z",
|
||||
},
|
||||
});
|
||||
const preferenceEvent = Array.from(mockState.db?.events.values() ?? [])
|
||||
.find((event) => event.kind === "digital_workday_update_notification_preferences");
|
||||
expect(preferenceEvent).toMatchObject({
|
||||
kind: "digital_workday_update_notification_preferences",
|
||||
message: "数字员工通知订阅已更新",
|
||||
});
|
||||
expect(mockState.db?.outbox.get(`event:insert:${preferenceEvent?.id}`)).toMatchObject({
|
||||
entity_type: "event",
|
||||
operation: "insert",
|
||||
status: "pending",
|
||||
});
|
||||
});
|
||||
|
||||
it("records governance commands as formal runtime records", async () => {
|
||||
const { recordDigitalEmployeeGovernanceCommand } = await import("./stateService");
|
||||
|
||||
|
||||
@@ -466,6 +466,13 @@ export interface DigitalEmployeeUiState {
|
||||
reportScheduleTime: string;
|
||||
reportGeneratedAtByDate: Record<string, string>;
|
||||
decisionResultsByDate: Record<string, Record<string, string>>;
|
||||
notificationStateById?: Record<string, unknown>;
|
||||
notificationPreferences?: {
|
||||
enabled: boolean;
|
||||
muted_kinds: string[];
|
||||
muted_levels: string[];
|
||||
updated_at: string | null;
|
||||
};
|
||||
consoleRole?: string;
|
||||
rolePreferences?: Record<string, unknown>;
|
||||
profile: {
|
||||
@@ -640,6 +647,8 @@ function defaultUiState(): DigitalEmployeeUiState {
|
||||
reportScheduleTime: "18:00",
|
||||
reportGeneratedAtByDate: {},
|
||||
decisionResultsByDate: {},
|
||||
notificationStateById: {},
|
||||
notificationPreferences: defaultNotificationPreferences(),
|
||||
consoleRole: "sales",
|
||||
rolePreferences: {},
|
||||
profile: {
|
||||
@@ -693,6 +702,34 @@ function normalizeRecord(value: unknown): Record<string, unknown> {
|
||||
return { ...value as Record<string, unknown> };
|
||||
}
|
||||
|
||||
function defaultNotificationPreferences(): NonNullable<DigitalEmployeeUiState["notificationPreferences"]> {
|
||||
return {
|
||||
enabled: true,
|
||||
muted_kinds: [],
|
||||
muted_levels: [],
|
||||
updated_at: null,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeStringArray(value: unknown): string[] {
|
||||
if (!Array.isArray(value)) return [];
|
||||
return [...new Set(value.filter((item): item is string => typeof item === "string" && Boolean(item.trim())))];
|
||||
}
|
||||
|
||||
function normalizeNotificationPreferences(
|
||||
value: unknown,
|
||||
): NonNullable<DigitalEmployeeUiState["notificationPreferences"]> {
|
||||
const record = value && typeof value === "object" && !Array.isArray(value)
|
||||
? value as Record<string, unknown>
|
||||
: {};
|
||||
return {
|
||||
enabled: record.enabled !== false,
|
||||
muted_kinds: normalizeStringArray(record.muted_kinds),
|
||||
muted_levels: normalizeStringArray(record.muted_levels),
|
||||
updated_at: typeof record.updated_at === "string" ? record.updated_at : null,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeUiState(
|
||||
value: Partial<DigitalEmployeeUiState>,
|
||||
): DigitalEmployeeUiState {
|
||||
@@ -706,6 +743,8 @@ function normalizeUiState(
|
||||
: fallback.reportScheduleTime,
|
||||
reportGeneratedAtByDate: normalizeStringMap(value.reportGeneratedAtByDate),
|
||||
decisionResultsByDate: normalizeNestedStringMap(value.decisionResultsByDate),
|
||||
notificationStateById: normalizeRecord(value.notificationStateById),
|
||||
notificationPreferences: normalizeNotificationPreferences(value.notificationPreferences),
|
||||
consoleRole: typeof value.consoleRole === "string" && value.consoleRole.trim()
|
||||
? value.consoleRole
|
||||
: fallback.consoleRole,
|
||||
@@ -744,6 +783,8 @@ function digitalEmployeeUiActionMessage(action: string, date?: string): string {
|
||||
return "数字员工角色已更新";
|
||||
case "update_role_preferences":
|
||||
return "数字员工运营偏好已更新";
|
||||
case "update_notification_preferences":
|
||||
return "数字员工通知订阅已更新";
|
||||
default:
|
||||
return `数字员工页面状态已更新${suffix}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user