feat(P19ERP1-3,P19ERP1-4): 实现ERP凭证导出与管理入口

这个提交包含在:
2026-03-08 16:29:05 +08:00
父节点 a1b1d7cfc9
当前提交 855bcdffeb
共修改 14 个文件,包含 1361 行新增和 145 行删除
+76 -12
查看文件
@@ -1,4 +1,4 @@
const { pushPaidReimbursementToErp } = require('./erp');
const { pushPaidReimbursementToErp, exportJournalToErp } = require('./erp');
const { sendInstantMessageByChannel } = require('../notifications/channels');
class IntegrationDispatchError extends Error {
@@ -22,18 +22,82 @@ function toDispatchError(result) {
});
}
async function dispatchOutboxEvent({ prisma, config, eventType, payload }) {
if (eventType === 'erp.reimbursement.paid') {
const result = await pushPaidReimbursementToErp(config, payload);
if (!result.ok) {
throw toDispatchError(result);
}
function extractExternalId(response) {
if (!response) {
return null;
}
return {
channel: 'erp',
code: result.code,
response: result.response ?? null,
};
if (typeof response.externalId === 'string' && response.externalId.trim()) {
return response.externalId.trim();
}
if (
typeof response.body?.externalId === 'string' &&
response.body.externalId.trim()
) {
return response.body.externalId.trim();
}
return null;
}
async function upsertErpBindingForPayload(prisma, payload, response) {
const entityType = String(payload?.entityType ?? '').trim();
const entityId = String(payload?.entityId ?? '').trim();
if (!prisma || !entityType || !entityId) {
return;
}
if (entityType !== 'reimbursement_form') {
return;
}
await prisma.integrationBinding.upsert({
where: {
provider_entity_type_local_id: {
provider: 'erp',
entity_type: 'reimbursement_journal',
local_id: entityId,
},
},
create: {
provider: 'erp',
entity_type: 'reimbursement_journal',
local_id: entityId,
external_id: extractExternalId(response),
external_code: payload?.documentNo ?? payload?.formNumber ?? null,
last_synced_at: new Date(),
},
update: {
external_id: extractExternalId(response),
external_code: payload?.documentNo ?? payload?.formNumber ?? null,
last_synced_at: new Date(),
},
});
}
async function dispatchErpEvent({ prisma, config, eventType, payload }) {
const result =
eventType === 'erp.journal.export'
? await exportJournalToErp(config, payload)
: await pushPaidReimbursementToErp(config, payload);
if (!result.ok) {
throw toDispatchError(result);
}
await upsertErpBindingForPayload(prisma, payload, result.response ?? null);
return {
channel: 'erp',
code: result.code,
response: result.response ?? null,
};
}
async function dispatchOutboxEvent({ prisma, config, eventType, payload }) {
if (eventType === 'erp.reimbursement.paid' || eventType === 'erp.journal.export') {
return dispatchErpEvent({ prisma, config, eventType, payload });
}
if (eventType === 'notification.im') {