R50 通知推送:dispatcher 投递服务、业务事件接入与员工推送开关
这个提交包含在:
@@ -31,11 +31,15 @@ describe('purchase submit side effects helper', () => {
|
||||
},
|
||||
}),
|
||||
resolveRecipients: vi.fn().mockResolvedValue(['manager-1']),
|
||||
createAppNotifications: vi.fn().mockResolvedValue({ count: 1 }),
|
||||
enqueueIm: vi.fn().mockResolvedValue({
|
||||
queued: 1,
|
||||
duplicated: 0,
|
||||
matchedChannels: [{ id: 'channel-1' }],
|
||||
createAppNotifications: vi.fn().mockResolvedValue([
|
||||
{
|
||||
id: 'notification-1',
|
||||
recipient_employee_id: 'manager-1',
|
||||
},
|
||||
]),
|
||||
dispatchNotifications: vi.fn().mockResolvedValue({
|
||||
deliveredChannels: [{ channelId: 'channel-1', status: 'success' }],
|
||||
skippedReason: null,
|
||||
}),
|
||||
sendEmailSafe: vi.fn().mockResolvedValue({
|
||||
email: { skipped: true, sent: 0, failed: 0 },
|
||||
@@ -50,7 +54,7 @@ describe('purchase submit side effects helper', () => {
|
||||
warn: vi.fn(),
|
||||
};
|
||||
const createAppNotifications = vi.fn();
|
||||
const enqueueIm = vi.fn();
|
||||
const dispatchNotifications = vi.fn();
|
||||
const sendEmailSafe = vi.fn();
|
||||
|
||||
const result = await finalizePurchaseSubmitSideEffects(
|
||||
@@ -58,20 +62,20 @@ describe('purchase submit side effects helper', () => {
|
||||
logger,
|
||||
resolveRecipients: vi.fn().mockRejectedValue(new Error('resolve failed')),
|
||||
createAppNotifications,
|
||||
enqueueIm,
|
||||
dispatchNotifications,
|
||||
sendEmailSafe,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(logger.error).toHaveBeenCalledTimes(1);
|
||||
expect(createAppNotifications).not.toHaveBeenCalled();
|
||||
expect(enqueueIm).not.toHaveBeenCalled();
|
||||
expect(dispatchNotifications).not.toHaveBeenCalled();
|
||||
expect(sendEmailSafe).not.toHaveBeenCalled();
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
recipientEmployeeIds: [],
|
||||
notification: expect.objectContaining({ skipped: true, reason: 'recipient_resolution_failed' }),
|
||||
im: expect.objectContaining({ queued: 0, skippedReason: 'recipient_resolution_failed' }),
|
||||
dispatch: expect.objectContaining({ skippedReason: 'recipient_resolution_failed' }),
|
||||
email: expect.objectContaining({ skipped: true, reason: 'recipient_resolution_failed' }),
|
||||
}),
|
||||
);
|
||||
@@ -82,7 +86,7 @@ describe('purchase submit side effects helper', () => {
|
||||
error: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
};
|
||||
const enqueueIm = vi.fn().mockRejectedValue(new Error('im failed'));
|
||||
const dispatchNotifications = vi.fn().mockRejectedValue(new Error('dispatch failed'));
|
||||
const sendEmailSafe = vi.fn().mockResolvedValue({
|
||||
email: { skipped: true, sent: 0, failed: 0 },
|
||||
});
|
||||
@@ -91,30 +95,34 @@ describe('purchase submit side effects helper', () => {
|
||||
createParams({
|
||||
logger,
|
||||
createAppNotifications: vi.fn().mockRejectedValue(new Error('notifications failed')),
|
||||
enqueueIm,
|
||||
dispatchNotifications,
|
||||
sendEmailSafe,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(logger.error).toHaveBeenCalledTimes(2);
|
||||
expect(enqueueIm).toHaveBeenCalledTimes(1);
|
||||
expect(dispatchNotifications).toHaveBeenCalledTimes(1);
|
||||
expect(sendEmailSafe).toHaveBeenCalledTimes(1);
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
recipientEmployeeIds: ['manager-1'],
|
||||
notification: expect.objectContaining({ skipped: true, reason: 'notification_failed' }),
|
||||
im: expect.objectContaining({ queued: 0, skippedReason: 'enqueue_im_failed' }),
|
||||
dispatch: expect.objectContaining({ skippedReason: 'dispatch_failed' }),
|
||||
email: expect.objectContaining({ skipped: true, sent: 0, failed: 0 }),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('builds structured purchase copy for app notification, IM and email', async () => {
|
||||
const createAppNotifications = vi.fn().mockResolvedValue({ count: 1 });
|
||||
const enqueueIm = vi.fn().mockResolvedValue({
|
||||
queued: 1,
|
||||
duplicated: 0,
|
||||
matchedChannels: [{ id: 'channel-1' }],
|
||||
const createAppNotifications = vi.fn().mockResolvedValue([
|
||||
{
|
||||
id: 'notification-1',
|
||||
recipient_employee_id: 'manager-1',
|
||||
},
|
||||
]);
|
||||
const dispatchNotifications = vi.fn().mockResolvedValue({
|
||||
deliveredChannels: [{ channelId: 'channel-1', status: 'success' }],
|
||||
skippedReason: null,
|
||||
});
|
||||
const sendEmailSafe = vi.fn().mockResolvedValue({
|
||||
email: { skipped: true, sent: 0, failed: 0 },
|
||||
@@ -123,7 +131,7 @@ describe('purchase submit side effects helper', () => {
|
||||
await finalizePurchaseSubmitSideEffects(
|
||||
createParams({
|
||||
createAppNotifications,
|
||||
enqueueIm,
|
||||
dispatchNotifications,
|
||||
sendEmailSafe,
|
||||
}),
|
||||
);
|
||||
@@ -135,11 +143,16 @@ describe('purchase submit side effects helper', () => {
|
||||
body: '申请人:李经理\n金额:5600.00\n单号:CG-20260324-001\n动作:请及时处理审批任务。',
|
||||
}),
|
||||
);
|
||||
expect(enqueueIm).toHaveBeenCalledWith(
|
||||
{},
|
||||
expect(dispatchNotifications).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
prisma: {},
|
||||
title: '【待办】采购申请 CG-20260324-001 待审批',
|
||||
body: '申请人:李经理\n金额:5600.00\n单号:CG-20260324-001\n动作:请及时处理审批任务。',
|
||||
notifications: [
|
||||
expect.objectContaining({
|
||||
id: 'notification-1',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
expect(sendEmailSafe).toHaveBeenCalledWith(
|
||||
|
||||
在新工单中引用
屏蔽一个用户