R50 通知推送:dispatcher 投递服务、业务事件接入与员工推送开关

这个提交包含在:
2026-04-05 11:21:39 +08:00
父节点 ff0a677bf3
当前提交 47e10e6acc
共修改 24 个文件,包含 1701 行新增和 197 行删除
+19 -17
查看文件
@@ -26,14 +26,14 @@ const {
} = require("../services/integrations/erpProcurement");
const { reserveBudgetForEntity, releaseReservedBudgetIfAny } = require("../services/purchaseBudget");
const { sendNotificationEmailSafe } = require("../services/email");
const { createNotifications, resolveRecipientsForAssignee } = require("../services/notifications");
const { createNotificationsWithRecords, resolveRecipientsForAssignee } = require("../services/notifications");
const {
buildPendingTaskCopy,
buildWorkflowEntityMeta,
} = require("../services/notifications/workflowCopy");
const { enqueueImNotifications } = require("../services/notifications/imEnqueue");
const { NOTIFICATION_TYPES } = require("../constants/notificationTypes");
const { openReadStream, StorageNotFoundError, writeBuffer } = require("../services/storage");
const { dispatchNotificationBatch } = require("../services/notificationDispatcher");
const UUID_SCHEMA = z.string().uuid();
const DATE_ONLY_SCHEMA = z.string().regex(/^\d{4}-\d{2}-\d{2}$/);
@@ -896,8 +896,8 @@ function buildSkippedNotificationResult(reason) {
return { skipped: true, reason };
}
function buildSkippedImResult(reason) {
return { queued: 0, duplicated: 0, matchedChannels: [], skippedReason: reason };
function buildSkippedDispatchResult(reason) {
return { deliveredChannels: [], skippedReason: reason, notificationCount: 0, eligibleRecipientCount: 0, matchedChannelCount: 0 };
}
function buildSkippedEmailResult(reason, failed = 0) {
@@ -934,8 +934,8 @@ async function finalizePurchaseSubmitSideEffects({
actorId,
loadSummary = loadPurchaseSubmitSummary,
resolveRecipients = resolveRecipientsForAssignee,
createAppNotifications = createNotifications,
enqueueIm = enqueueImNotifications,
createAppNotifications = createNotificationsWithRecords,
dispatchNotifications = dispatchNotificationBatch,
sendEmailSafe = sendNotificationEmailSafe,
}) {
let copy = buildPendingTaskCopy(
@@ -986,7 +986,7 @@ async function finalizePurchaseSubmitSideEffects({
return {
recipientEmployeeIds: [],
notification: buildSkippedNotificationResult("recipient_resolution_failed"),
im: buildSkippedImResult("recipient_resolution_failed"),
dispatch: buildSkippedDispatchResult("recipient_resolution_failed"),
email: buildSkippedEmailResult("recipient_resolution_failed"),
};
}
@@ -995,14 +995,15 @@ async function finalizePurchaseSubmitSideEffects({
return {
recipientEmployeeIds: [],
notification: buildSkippedNotificationResult("no_recipients"),
im: buildSkippedImResult("no_recipients"),
dispatch: buildSkippedDispatchResult("no_recipients"),
email: buildSkippedEmailResult("no_recipients"),
};
}
let notification = buildSkippedNotificationResult("not_attempted");
let notificationRecords = [];
try {
notification = await createAppNotifications(prisma, {
notificationRecords = await createAppNotifications(prisma, {
recipientEmployeeIds,
type: NOTIFICATION_TYPES.workflowTaskAssigned,
title,
@@ -1010,6 +1011,7 @@ async function finalizePurchaseSubmitSideEffects({
targetType: "workflow_task",
targetId: workflowTaskId,
});
notification = { count: notificationRecords.length };
} catch (error) {
logger?.error?.(
{
@@ -1024,20 +1026,20 @@ async function finalizePurchaseSubmitSideEffects({
notification = buildSkippedNotificationResult("notification_failed");
}
let im = buildSkippedImResult("not_attempted");
let dispatch = buildSkippedDispatchResult("not_attempted");
try {
im = await enqueueIm(prisma, {
dispatch = await dispatchNotifications({
prisma,
config,
logger,
recipientEmployeeIds,
requestId,
notifications: notificationRecords,
type: NOTIFICATION_TYPES.workflowTaskAssigned,
title,
body,
targetType: "workflow_task",
targetId: workflowTaskId,
targetPath,
entityType: "purchase_request",
entityId: purchaseRequestId,
actorId,
});
} catch (error) {
@@ -1049,9 +1051,9 @@ async function finalizePurchaseSubmitSideEffects({
workflowTaskId,
recipients: recipientEmployeeIds.length,
},
"Failed to enqueue purchase submit IM notifications",
"Failed to dispatch purchase submit notifications",
);
im = buildSkippedImResult("enqueue_im_failed");
dispatch = buildSkippedDispatchResult("dispatch_failed");
}
let email = buildSkippedEmailResult("not_attempted");
@@ -1085,7 +1087,7 @@ async function finalizePurchaseSubmitSideEffects({
return {
recipientEmployeeIds,
notification,
im,
dispatch,
email,
};
}