feat(server): 站内通知基础设施与关键事件触发(F14,F30)
- 新增 notifications 表与 API(list/mark-read) - submit/approve/reject/return/transfer/pay/forgot-password 写入站内通知 - 通知失败降级,不阻塞主流程
这个提交包含在:
@@ -4,6 +4,10 @@ const { sendData } = require("../utils/reply");
|
||||
const { buildAuditLogData } = require("../utils/audit");
|
||||
const { compactDateString, currentDateString, parseDateOnlyString } = require("../utils/date");
|
||||
const { findActiveDelegationForDelegate } = require("../utils/delegation");
|
||||
const {
|
||||
createNotifications,
|
||||
resolveRecipientsForAssignee,
|
||||
} = require("../services/notifications");
|
||||
const {
|
||||
buildWorkflowStepsFromApprovalRule,
|
||||
resolveActionableStep,
|
||||
@@ -1347,6 +1351,27 @@ async function reimbursementRoutes(fastify) {
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const recipients = await resolveRecipientsForAssignee(tx, {
|
||||
assigneeId: workflowTask.assignee_id,
|
||||
assigneeRole: workflowTask.assignee_role,
|
||||
});
|
||||
|
||||
await createNotifications(tx, {
|
||||
recipientEmployeeIds: recipients,
|
||||
type: "workflow.task_assigned",
|
||||
title: `【待办】报销单 ${formNumber} 待审批`,
|
||||
body: `报销单 ${formNumber} 已提交,请及时处理。`,
|
||||
targetType: "workflow_task",
|
||||
targetId: workflowTask.id,
|
||||
});
|
||||
} catch (error) {
|
||||
fastify.log.error(
|
||||
{ err: error, reimbursementId, taskId: workflowTask.id },
|
||||
"Failed to create task assigned notifications",
|
||||
);
|
||||
}
|
||||
|
||||
if (reimbursement.submitter_id !== reimbursement.owner_id) {
|
||||
await tx.auditLog.create({
|
||||
data: buildAuditLogData(request, {
|
||||
@@ -1524,7 +1549,8 @@ async function reimbursementRoutes(fastify) {
|
||||
|
||||
await fastify.prisma.$transaction(async (tx) => {
|
||||
const reimbursementRows = await tx.$queryRaw`
|
||||
SELECT id, status, payment_voucher_no, payment_date, payment_channel, paid_at, paid_by
|
||||
SELECT id, status, form_number, owner_id, submitter_id,
|
||||
payment_voucher_no, payment_date, payment_channel, paid_at, paid_by
|
||||
FROM "reimbursement_forms"
|
||||
WHERE id = ${reimbursementId}::uuid
|
||||
FOR UPDATE
|
||||
@@ -1596,6 +1622,25 @@ async function reimbursementRoutes(fastify) {
|
||||
updated_by: request.employee.id,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const recipients = Array.from(
|
||||
new Set([reimbursement.owner_id, reimbursement.submitter_id].filter(Boolean)),
|
||||
);
|
||||
await createNotifications(tx, {
|
||||
recipientEmployeeIds: recipients,
|
||||
type: "reimbursement.paid",
|
||||
title: `【已付款】报销单 ${reimbursement.form_number ?? reimbursementId}`,
|
||||
body: `报销单 ${reimbursement.form_number ?? reimbursementId} 已完成付款。`,
|
||||
targetType: "reimbursement_form",
|
||||
targetId: reimbursementId,
|
||||
});
|
||||
} catch (error) {
|
||||
fastify.log.error(
|
||||
{ err: error, reimbursementId },
|
||||
"Failed to create reimbursement paid notifications",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
response = {
|
||||
|
||||
在新工单中引用
屏蔽一个用户