feat(G2-1,G2-2,G2-3): 扩展预算编号与工作流跨域桥接
这个提交包含在:
+127
-26
@@ -335,6 +335,77 @@ async function loadReimbursementNotifyContext(tx, reimbursementId) {
|
||||
});
|
||||
}
|
||||
|
||||
function getWorkflowEntityMeta(entityType, entityRecord, entityId) {
|
||||
const normalizedEntityType = entityType === "procurement" ? "purchase_request" : entityType;
|
||||
|
||||
if (normalizedEntityType === "reimbursement") {
|
||||
return {
|
||||
label: "报销单",
|
||||
reference: entityRecord?.form_number ?? entityId,
|
||||
targetType: "reimbursement_form",
|
||||
detailPath: `/reimbursements/${entityId}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (normalizedEntityType === "leave") {
|
||||
return {
|
||||
label: "请假单",
|
||||
reference: entityId,
|
||||
targetType: "leave_request",
|
||||
detailPath: "/leave-requests",
|
||||
};
|
||||
}
|
||||
|
||||
if (normalizedEntityType === "purchase_request") {
|
||||
return {
|
||||
label: "采购申请",
|
||||
reference: entityRecord?.request_no ?? entityId,
|
||||
targetType: "purchase_request",
|
||||
detailPath: "/purchase-requests",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
label: "审批单",
|
||||
reference: entityId,
|
||||
targetType: normalizedEntityType,
|
||||
detailPath: `/workflow/tasks/${entityId}`,
|
||||
};
|
||||
}
|
||||
|
||||
function buildPendingTaskCopy(entityMeta, { transfer = false } = {}) {
|
||||
return {
|
||||
title: `【待办】${entityMeta.label} ${entityMeta.reference} ${transfer ? "转审待处理" : "待审批"}`,
|
||||
body: transfer
|
||||
? `${entityMeta.label} ${entityMeta.reference} 已转审给你,请及时处理。`
|
||||
: `${entityMeta.label} ${entityMeta.reference} 已流转到你,请及时处理。`,
|
||||
};
|
||||
}
|
||||
|
||||
function buildApprovedCopy(entityMeta) {
|
||||
return {
|
||||
title: `【已批准】${entityMeta.label} ${entityMeta.reference}`,
|
||||
body:
|
||||
entityMeta.targetType === "reimbursement_form"
|
||||
? `${entityMeta.label} ${entityMeta.reference} 已审批通过,等待财务付款。`
|
||||
: `${entityMeta.label} ${entityMeta.reference} 已审批通过。`,
|
||||
};
|
||||
}
|
||||
|
||||
function buildRejectedCopy(entityMeta, comment) {
|
||||
return {
|
||||
title: `【已驳回】${entityMeta.label} ${entityMeta.reference}`,
|
||||
body: `${entityMeta.label} ${entityMeta.reference} 已被驳回:${comment}`,
|
||||
};
|
||||
}
|
||||
|
||||
function buildReturnedCopy(entityMeta, comment) {
|
||||
return {
|
||||
title: `【已退回】${entityMeta.label} ${entityMeta.reference}`,
|
||||
body: `${entityMeta.label} ${entityMeta.reference} 已退回:${comment}`,
|
||||
};
|
||||
}
|
||||
|
||||
async function createSkipHistoryRecords(tx, workflowInstanceId, actorId, ownerId, skippedSteps) {
|
||||
for (const skipped of skippedSteps) {
|
||||
await tx.workflowHistory.create({
|
||||
@@ -892,6 +963,12 @@ async function workflowRoutes(fastify) {
|
||||
tx,
|
||||
workflowInstance.entity_id,
|
||||
);
|
||||
const entityMeta = getWorkflowEntityMeta(
|
||||
workflowInstance.entity_type,
|
||||
reimbursement,
|
||||
workflowInstance.entity_id,
|
||||
);
|
||||
const pendingCopy = buildPendingTaskCopy(entityMeta);
|
||||
const recipients = await resolveRecipientsForAssignee(tx, {
|
||||
assigneeId: actionable.assigneeId,
|
||||
assigneeRole: actionable.assigneeRole,
|
||||
@@ -900,8 +977,8 @@ async function workflowRoutes(fastify) {
|
||||
await createNotifications(tx, {
|
||||
recipientEmployeeIds: recipients,
|
||||
type: "workflow.task_assigned",
|
||||
title: `【待办】报销单 ${reimbursement?.form_number ?? workflowInstance.entity_id} 待审批`,
|
||||
body: `报销单 ${reimbursement?.form_number ?? workflowInstance.entity_id} 已流转到你,请及时处理。`,
|
||||
title: pendingCopy.title,
|
||||
body: pendingCopy.body,
|
||||
targetType: "workflow_task",
|
||||
targetId: nextTask.id,
|
||||
});
|
||||
@@ -909,8 +986,8 @@ async function workflowRoutes(fastify) {
|
||||
emailTasks.push({
|
||||
type: "workflow.task_assigned",
|
||||
recipientEmployeeIds: recipients,
|
||||
subject: `【待办】报销单 ${reimbursement?.form_number ?? workflowInstance.entity_id} 待审批`,
|
||||
body: `报销单 ${reimbursement?.form_number ?? workflowInstance.entity_id} 已流转到你,请及时处理。`,
|
||||
subject: pendingCopy.title,
|
||||
body: pendingCopy.body,
|
||||
targetPath: `/workflow/tasks/${nextTask.id}`,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -1013,6 +1090,12 @@ async function workflowRoutes(fastify) {
|
||||
if (workflowInstance.entity_type === "reimbursement") {
|
||||
try {
|
||||
const reimbursement = await loadReimbursementNotifyContext(tx, workflowInstance.entity_id);
|
||||
const entityMeta = getWorkflowEntityMeta(
|
||||
workflowInstance.entity_type,
|
||||
reimbursement,
|
||||
workflowInstance.entity_id,
|
||||
);
|
||||
const approvedCopy = buildApprovedCopy(entityMeta);
|
||||
const recipients = Array.from(
|
||||
new Set([reimbursement?.owner_id, reimbursement?.submitter_id].filter(Boolean)),
|
||||
);
|
||||
@@ -1020,18 +1103,18 @@ async function workflowRoutes(fastify) {
|
||||
await createNotifications(tx, {
|
||||
recipientEmployeeIds: recipients,
|
||||
type: "reimbursement.approved",
|
||||
title: `【已批准】报销单 ${reimbursement?.form_number ?? workflowInstance.entity_id}`,
|
||||
body: `报销单 ${reimbursement?.form_number ?? workflowInstance.entity_id} 已审批通过,等待财务付款。`,
|
||||
targetType: "reimbursement_form",
|
||||
title: approvedCopy.title,
|
||||
body: approvedCopy.body,
|
||||
targetType: entityMeta.targetType,
|
||||
targetId: workflowInstance.entity_id,
|
||||
});
|
||||
|
||||
emailTasks.push({
|
||||
type: "reimbursement.approved",
|
||||
recipientEmployeeIds: recipients,
|
||||
subject: `【已批准】报销单 ${reimbursement?.form_number ?? workflowInstance.entity_id}`,
|
||||
body: `报销单 ${reimbursement?.form_number ?? workflowInstance.entity_id} 已审批通过,等待财务付款。`,
|
||||
targetPath: `/reimbursements/${workflowInstance.entity_id}`,
|
||||
subject: approvedCopy.title,
|
||||
body: approvedCopy.body,
|
||||
targetPath: entityMeta.detailPath,
|
||||
});
|
||||
} catch (error) {
|
||||
fastify.log.error(
|
||||
@@ -1179,6 +1262,12 @@ async function workflowRoutes(fastify) {
|
||||
if (workflowInstance.entity_type === "reimbursement") {
|
||||
try {
|
||||
const reimbursement = await loadReimbursementNotifyContext(tx, workflowInstance.entity_id);
|
||||
const entityMeta = getWorkflowEntityMeta(
|
||||
workflowInstance.entity_type,
|
||||
reimbursement,
|
||||
workflowInstance.entity_id,
|
||||
);
|
||||
const rejectedCopy = buildRejectedCopy(entityMeta, comment);
|
||||
const recipients = Array.from(
|
||||
new Set([reimbursement?.owner_id, reimbursement?.submitter_id].filter(Boolean)),
|
||||
);
|
||||
@@ -1186,18 +1275,18 @@ async function workflowRoutes(fastify) {
|
||||
await createNotifications(tx, {
|
||||
recipientEmployeeIds: recipients,
|
||||
type: "reimbursement.rejected",
|
||||
title: `【已驳回】报销单 ${reimbursement?.form_number ?? workflowInstance.entity_id}`,
|
||||
body: `报销单 ${reimbursement?.form_number ?? workflowInstance.entity_id} 已被驳回:${comment}`,
|
||||
targetType: "reimbursement_form",
|
||||
title: rejectedCopy.title,
|
||||
body: rejectedCopy.body,
|
||||
targetType: entityMeta.targetType,
|
||||
targetId: workflowInstance.entity_id,
|
||||
});
|
||||
|
||||
emailTasks.push({
|
||||
type: "reimbursement.rejected",
|
||||
recipientEmployeeIds: recipients,
|
||||
subject: `【已驳回】报销单 ${reimbursement?.form_number ?? workflowInstance.entity_id}`,
|
||||
body: `报销单 ${reimbursement?.form_number ?? workflowInstance.entity_id} 已被驳回:${comment}`,
|
||||
targetPath: `/reimbursements/${workflowInstance.entity_id}`,
|
||||
subject: rejectedCopy.title,
|
||||
body: rejectedCopy.body,
|
||||
targetPath: entityMeta.detailPath,
|
||||
});
|
||||
} catch (error) {
|
||||
fastify.log.error(
|
||||
@@ -1344,6 +1433,12 @@ async function workflowRoutes(fastify) {
|
||||
if (workflowInstance.entity_type === "reimbursement") {
|
||||
try {
|
||||
const reimbursement = await loadReimbursementNotifyContext(tx, workflowInstance.entity_id);
|
||||
const entityMeta = getWorkflowEntityMeta(
|
||||
workflowInstance.entity_type,
|
||||
reimbursement,
|
||||
workflowInstance.entity_id,
|
||||
);
|
||||
const returnedCopy = buildReturnedCopy(entityMeta, comment);
|
||||
const recipients = Array.from(
|
||||
new Set([reimbursement?.owner_id, reimbursement?.submitter_id].filter(Boolean)),
|
||||
);
|
||||
@@ -1351,18 +1446,18 @@ async function workflowRoutes(fastify) {
|
||||
await createNotifications(tx, {
|
||||
recipientEmployeeIds: recipients,
|
||||
type: "reimbursement.returned",
|
||||
title: `【已退回】报销单 ${reimbursement?.form_number ?? workflowInstance.entity_id}`,
|
||||
body: `报销单 ${reimbursement?.form_number ?? workflowInstance.entity_id} 已退回:${comment}`,
|
||||
targetType: "reimbursement_form",
|
||||
title: returnedCopy.title,
|
||||
body: returnedCopy.body,
|
||||
targetType: entityMeta.targetType,
|
||||
targetId: workflowInstance.entity_id,
|
||||
});
|
||||
|
||||
emailTasks.push({
|
||||
type: "reimbursement.returned",
|
||||
recipientEmployeeIds: recipients,
|
||||
subject: `【已退回】报销单 ${reimbursement?.form_number ?? workflowInstance.entity_id}`,
|
||||
body: `报销单 ${reimbursement?.form_number ?? workflowInstance.entity_id} 已退回:${comment}`,
|
||||
targetPath: `/reimbursements/${workflowInstance.entity_id}`,
|
||||
subject: returnedCopy.title,
|
||||
body: returnedCopy.body,
|
||||
targetPath: entityMeta.detailPath,
|
||||
});
|
||||
} catch (error) {
|
||||
fastify.log.error(
|
||||
@@ -1475,11 +1570,17 @@ async function workflowRoutes(fastify) {
|
||||
|
||||
try {
|
||||
const reimbursement = await loadReimbursementNotifyContext(tx, workflowInstance.entity_id);
|
||||
const entityMeta = getWorkflowEntityMeta(
|
||||
workflowInstance.entity_type,
|
||||
reimbursement,
|
||||
workflowInstance.entity_id,
|
||||
);
|
||||
const pendingCopy = buildPendingTaskCopy(entityMeta, { transfer: true });
|
||||
await createNotifications(tx, {
|
||||
recipientEmployeeIds: [toApproverId],
|
||||
type: "workflow.task_assigned",
|
||||
title: `【待办】报销单 ${reimbursement?.form_number ?? workflowInstance.entity_id} 转审待处理`,
|
||||
body: `报销单 ${reimbursement?.form_number ?? workflowInstance.entity_id} 已转审给你,请及时处理。`,
|
||||
title: pendingCopy.title,
|
||||
body: pendingCopy.body,
|
||||
targetType: "workflow_task",
|
||||
targetId: taskId,
|
||||
});
|
||||
@@ -1487,8 +1588,8 @@ async function workflowRoutes(fastify) {
|
||||
emailTasks.push({
|
||||
type: "workflow.task_assigned",
|
||||
recipientEmployeeIds: [toApproverId],
|
||||
subject: `【待办】报销单 ${reimbursement?.form_number ?? workflowInstance.entity_id} 转审待处理`,
|
||||
body: `报销单 ${reimbursement?.form_number ?? workflowInstance.entity_id} 已转审给你,请及时处理。`,
|
||||
subject: pendingCopy.title,
|
||||
body: pendingCopy.body,
|
||||
targetPath: `/workflow/tasks/${taskId}`,
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
在新工单中引用
屏蔽一个用户