feat: 完成P6-P8后端能力与P9路由重构(V1-V2,D1,D3,B1-B2,B4,I1-I4,P9A2-P9A3)
这个提交包含在:
@@ -0,0 +1,106 @@
|
||||
const { z } = require("zod");
|
||||
|
||||
const { AppError } = require("../utils/AppError");
|
||||
const { sendData } = require("../utils/reply");
|
||||
const { buildAuditLogData } = require("../utils/audit");
|
||||
const { enqueueIntegrationEvent } = require("../services/integrations/outbox");
|
||||
const { buildErpPayload } = require("../services/integrations/erp");
|
||||
const { runIntegrationOutboxOnce } = require("../jobs/integrationOutboxJob");
|
||||
|
||||
async function integrationsRoutes(fastify) {
|
||||
fastify.post(
|
||||
"/integrations/erp/sync/:reimbursementId",
|
||||
{
|
||||
preHandler: fastify.requirePermission("integration.manage"),
|
||||
},
|
||||
async (request, reply) => {
|
||||
if (!fastify.config.erpSyncEnabled) {
|
||||
throw new AppError({
|
||||
code: "VALID_001",
|
||||
message: "参数校验失败",
|
||||
statusCode: 409,
|
||||
details: { integration: "erp_sync_disabled" },
|
||||
});
|
||||
}
|
||||
|
||||
const parsedId = z.string().uuid().safeParse(request.params?.reimbursementId);
|
||||
if (!parsedId.success) {
|
||||
throw new AppError({
|
||||
code: "VALID_001",
|
||||
message: "参数校验失败",
|
||||
statusCode: 400,
|
||||
details: { reimbursementId: "invalid_uuid" },
|
||||
});
|
||||
}
|
||||
|
||||
const reimbursementId = parsedId.data;
|
||||
|
||||
const reimbursement = await fastify.prisma.reimbursementForm.findUnique({
|
||||
where: { id: reimbursementId },
|
||||
select: {
|
||||
id: true,
|
||||
form_number: true,
|
||||
owner_id: true,
|
||||
submitter_id: true,
|
||||
status: true,
|
||||
total_amount: true,
|
||||
payment_voucher_no: true,
|
||||
payment_date: true,
|
||||
paid_at: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!reimbursement) {
|
||||
throw new AppError({
|
||||
code: "SYS_404",
|
||||
message: "Not Found",
|
||||
statusCode: 404,
|
||||
});
|
||||
}
|
||||
|
||||
if (reimbursement.status !== "paid") {
|
||||
throw new AppError({
|
||||
code: "VALID_001",
|
||||
message: "参数校验失败",
|
||||
statusCode: 409,
|
||||
details: { reimbursement: "erp_sync_requires_paid" },
|
||||
});
|
||||
}
|
||||
|
||||
const payload = buildErpPayload({ reimbursement });
|
||||
const enqueued = await enqueueIntegrationEvent(fastify.prisma, {
|
||||
eventType: "erp.reimbursement.paid",
|
||||
payload,
|
||||
dedupeKey: reimbursement.id,
|
||||
maxAttempts: fastify.config.integrationOutboxMaxAttempts,
|
||||
actorId: request.employee.id,
|
||||
});
|
||||
|
||||
await fastify.prisma.auditLog.create({
|
||||
data: buildAuditLogData(request, {
|
||||
actorId: request.employee.id,
|
||||
action: "ERP_SYNC_ENQUEUED",
|
||||
targetType: "reimbursement_form",
|
||||
targetId: reimbursementId,
|
||||
extra: {
|
||||
outboxId: enqueued.id,
|
||||
duplicated: enqueued.duplicated,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const runNow = request.query?.runNow === "1" || request.query?.runNow === 1 || request.query?.runNow === true || request.query?.runNow === "true";
|
||||
if (runNow) {
|
||||
await runIntegrationOutboxOnce(fastify.prisma, fastify.config, fastify.log);
|
||||
}
|
||||
|
||||
return sendData(reply, request, {
|
||||
outboxId: enqueued.id,
|
||||
status: enqueued.status,
|
||||
duplicated: enqueued.duplicated,
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = integrationsRoutes;
|
||||
在新工单中引用
屏蔽一个用户