feat(P19ERP1-3,P19ERP1-4): 实现ERP凭证导出与管理入口
这个提交包含在:
@@ -0,0 +1,80 @@
|
||||
const {
|
||||
buildReimbursementJournalDedupeKey,
|
||||
buildReimbursementJournalPayload,
|
||||
enqueueReimbursementJournalExport,
|
||||
} = require('../../../src/services/integrations/erpJournal');
|
||||
|
||||
describe('ERP Journal Service', () => {
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('prefers owner legal entity code and keeps cost center null', () => {
|
||||
const payload = buildReimbursementJournalPayload({
|
||||
reimbursement: {
|
||||
id: 'rb-1',
|
||||
form_number: 'BX-001',
|
||||
owner_id: 'owner-1',
|
||||
submitter_id: 'submitter-1',
|
||||
department_id: 'dept-1',
|
||||
total_amount: { toString: () => '88.80' },
|
||||
payment_date: '2026-03-08T00:00:00.000Z',
|
||||
paid_at: '2026-03-08T12:00:00.000Z',
|
||||
owner: { legal_entity: { code: 'OWNER_LE' } },
|
||||
submitter: { legal_entity: { code: 'SUBMITTER_LE' } },
|
||||
},
|
||||
});
|
||||
|
||||
expect(payload.dimensions.legalEntityCode).toBe('OWNER_LE');
|
||||
expect(payload.dimensions.costCenterCode).toBeNull();
|
||||
expect(payload.currencyCode).toBe('CNY');
|
||||
});
|
||||
|
||||
it('falls back to submitter legal entity code', () => {
|
||||
const payload = buildReimbursementJournalPayload({
|
||||
reimbursement: {
|
||||
id: 'rb-1',
|
||||
form_number: 'BX-001',
|
||||
owner: { legal_entity: null },
|
||||
submitter: { legal_entity: { code: 'SUBMITTER_LE' } },
|
||||
},
|
||||
});
|
||||
|
||||
expect(payload.dimensions.legalEntityCode).toBe('SUBMITTER_LE');
|
||||
});
|
||||
|
||||
it('builds dedupe key and enqueues standard export event', async () => {
|
||||
const prisma = {
|
||||
integrationOutbox: {
|
||||
findUnique: vi.fn().mockResolvedValue(null),
|
||||
create: vi.fn().mockResolvedValue({ id: 'outbox-1', status: 'pending' }),
|
||||
},
|
||||
};
|
||||
const config = { integrationOutboxMaxAttempts: 5 };
|
||||
const reimbursement = {
|
||||
id: 'rb-1',
|
||||
form_number: 'BX-001',
|
||||
owner_id: 'owner-1',
|
||||
submitter_id: 'submitter-1',
|
||||
department_id: 'dept-1',
|
||||
};
|
||||
|
||||
const result = await enqueueReimbursementJournalExport(prisma, config, {
|
||||
reimbursement,
|
||||
actorId: 'actor-1',
|
||||
});
|
||||
|
||||
expect(buildReimbursementJournalDedupeKey('rb-1')).toBe('reimbursement:rb-1:paid');
|
||||
expect(prisma.integrationOutbox.findUnique).toHaveBeenCalledWith({
|
||||
where: {
|
||||
event_type_dedupe_key: {
|
||||
event_type: 'erp.journal.export',
|
||||
dedupe_key: 'reimbursement:rb-1:paid',
|
||||
},
|
||||
},
|
||||
select: { id: true, status: true },
|
||||
});
|
||||
expect(prisma.integrationOutbox.create).toHaveBeenCalled();
|
||||
expect(result.eventType).toBe('erp.journal.export');
|
||||
});
|
||||
});
|
||||
在新工单中引用
屏蔽一个用户