feat(P19ERP2-1,P19ERP2-2): 实现采购ERP凭证与摘要出站服务
这个提交包含在:
@@ -1,10 +1,17 @@
|
||||
const { z } = require("zod");
|
||||
const { AppError } = require("../utils/AppError");
|
||||
const { buildAuditLogData } = require("../utils/audit");
|
||||
const { sendData } = require("../utils/reply");
|
||||
const { resolvePermissionsForRoles, hasPermission, ROLE_PERMISSIONS, resolveEntityPermission } = require("../constants/permissions");
|
||||
const { getWorkflowStepsSnapshot, resolveActionableStep, toMoneyString } = require("../utils/workflow");
|
||||
const { consumeReservedLeave, releaseReservedLeave, formatDateOnly } = require("../services/leave");
|
||||
const { FEATURE_FLAG_KEYS, evaluateFeatureFlag } = require("../services/featureFlags");
|
||||
const { confirmBudgetForEntity, releaseReservedBudgetIfAny } = require("../services/purchaseBudget");
|
||||
const {
|
||||
PROCUREMENT_ERP_TARGETS,
|
||||
enqueuePurchaseCommitmentJournalExport,
|
||||
loadPurchaseRequestErpContext,
|
||||
} = require("../services/integrations/erpProcurement");
|
||||
const {
|
||||
createNotifications,
|
||||
resolveRecipientsForAssignee,
|
||||
@@ -97,6 +104,89 @@ function parseTaskId(taskId) {
|
||||
return parsed.data;
|
||||
}
|
||||
|
||||
async function handlePurchaseApprovalErpSync({
|
||||
tx,
|
||||
config,
|
||||
request,
|
||||
actorId,
|
||||
purchaseRequestId,
|
||||
loadContext = loadPurchaseRequestErpContext,
|
||||
evaluateGate = evaluateFeatureFlag,
|
||||
enqueueCommitment = enqueuePurchaseCommitmentJournalExport,
|
||||
}) {
|
||||
const context = await loadContext(tx, purchaseRequestId);
|
||||
if (!context?.purchaseRequest) {
|
||||
return { skipped: true, reason: 'purchase_request_not_found' };
|
||||
}
|
||||
|
||||
const purchaseRequest = context.purchaseRequest;
|
||||
const departmentId = purchaseRequest.department_id ?? request.employee.department_id ?? null;
|
||||
const gate = await evaluateGate({
|
||||
prisma: tx,
|
||||
config,
|
||||
flagKey: FEATURE_FLAG_KEYS.erpSync,
|
||||
departmentId,
|
||||
strictDepartment: true,
|
||||
});
|
||||
|
||||
if (!gate.enabled) {
|
||||
await tx.auditLog.create({
|
||||
data: buildAuditLogData(request, {
|
||||
actorId,
|
||||
action: 'ERP_SYNC_SKIPPED_BY_FEATURE_FLAG',
|
||||
targetType: 'purchase_request',
|
||||
targetId: purchaseRequest.id,
|
||||
extra: {
|
||||
erpTarget: PROCUREMENT_ERP_TARGETS.commitmentJournal,
|
||||
eventType: 'erp.journal.export',
|
||||
purchaseRequestId: purchaseRequest.id,
|
||||
purchaseOrderArchiveId: null,
|
||||
gateReason: gate.reason,
|
||||
featureFlag: FEATURE_FLAG_KEYS.erpSync,
|
||||
departmentId,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
return { skipped: true, gate, purchaseRequest };
|
||||
}
|
||||
|
||||
const enqueued = await enqueueCommitment(tx, config, {
|
||||
purchaseRequest,
|
||||
occurredAt: context.approvalOccurredAt ?? new Date(),
|
||||
actorId,
|
||||
requeueOnFailure: false,
|
||||
});
|
||||
|
||||
await tx.auditLog.create({
|
||||
data: buildAuditLogData(request, {
|
||||
actorId,
|
||||
action: 'ERP_SYNC_ENQUEUED',
|
||||
targetType: 'purchase_request',
|
||||
targetId: purchaseRequest.id,
|
||||
extra: {
|
||||
erpTarget: PROCUREMENT_ERP_TARGETS.commitmentJournal,
|
||||
eventType: enqueued.eventType,
|
||||
purchaseRequestId: purchaseRequest.id,
|
||||
purchaseOrderArchiveId: null,
|
||||
outboxId: enqueued.id,
|
||||
duplicated: enqueued.duplicated,
|
||||
requeued: enqueued.requeued,
|
||||
gateReason: gate.reason,
|
||||
featureFlag: FEATURE_FLAG_KEYS.erpSync,
|
||||
departmentId,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
return {
|
||||
skipped: false,
|
||||
gate,
|
||||
purchaseRequest,
|
||||
enqueued,
|
||||
};
|
||||
}
|
||||
|
||||
function parseTaskListQuery(query) {
|
||||
const parsed = TASK_LIST_QUERY_SCHEMA.safeParse(query ?? {});
|
||||
if (!parsed.success) {
|
||||
@@ -1372,6 +1462,21 @@ async function workflowRoutes(fastify) {
|
||||
actorId: request.employee.id,
|
||||
note: "approved_confirm_purchase_budget",
|
||||
});
|
||||
|
||||
try {
|
||||
await handlePurchaseApprovalErpSync({
|
||||
tx,
|
||||
config: fastify.config,
|
||||
request,
|
||||
actorId: request.employee.id,
|
||||
purchaseRequestId: workflowInstance.entity_id,
|
||||
});
|
||||
} catch (error) {
|
||||
fastify.log.error(
|
||||
{ err: error, purchaseRequestId: workflowInstance.entity_id },
|
||||
"Failed to enqueue purchase approval ERP events",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await tx.workflowHistory.create({
|
||||
@@ -1905,5 +2010,6 @@ async function workflowRoutes(fastify) {
|
||||
module.exports = workflowRoutes;
|
||||
|
||||
module.exports.__testables = {
|
||||
handlePurchaseApprovalErpSync,
|
||||
listAllowedActionsForTask,
|
||||
};
|
||||
|
||||
在新工单中引用
屏蔽一个用户