171 行
5.2 KiB
JavaScript
171 行
5.2 KiB
JavaScript
const {
|
|
TRAVEL_ERP_BINDING_TYPES,
|
|
TRAVEL_ERP_EVENT_TYPE,
|
|
buildTravelReconciliationErpTrack,
|
|
buildTravelReconciliationSummaryDedupeKey,
|
|
buildTravelReconciliationSummaryPayload,
|
|
} = require('../../../src/services/integrations/erpTravel');
|
|
|
|
describe('erpTravel service', () => {
|
|
function createBatch(overrides = {}) {
|
|
return {
|
|
id: 'batch-1',
|
|
batch_no: 'TRB-20260311-0001',
|
|
status: 'reconciled',
|
|
period_start: new Date('2026-03-01T00:00:00.000Z'),
|
|
period_end: new Date('2026-03-31T00:00:00.000Z'),
|
|
confirmed_at: new Date('2026-03-31T10:00:00.000Z'),
|
|
created_at: new Date('2026-03-31T09:00:00.000Z'),
|
|
updated_at: new Date('2026-03-31T10:00:00.000Z'),
|
|
summary_json: {
|
|
totalOrders: 2,
|
|
totalTransactions: 2,
|
|
anomalyItems: 1,
|
|
matchRate: '50.00',
|
|
totalOrderAmount: '200.00',
|
|
totalSettledAmount: '180.00',
|
|
totalReversedAmount: '20.00',
|
|
},
|
|
legal_entity: {
|
|
id: 'legal-1',
|
|
code: 'HY',
|
|
name: '海音达',
|
|
},
|
|
cost_center: {
|
|
id: 'cc-1',
|
|
code: 'CC001',
|
|
name: '销售中心',
|
|
},
|
|
items: [
|
|
{
|
|
id: 'item-1',
|
|
travel_request_id: 'travel-1',
|
|
travel_order_id: 'order-1',
|
|
card_transaction_id: 'txn-1',
|
|
match_status: 'matched',
|
|
resolution_status: 'confirmed',
|
|
order_amount: '100.00',
|
|
transaction_amount: '100.00',
|
|
currency: 'CNY',
|
|
snapshot_json: {
|
|
requestNo: 'CL-20260310-0001',
|
|
externalOrderNo: 'EXT-001',
|
|
externalTxnId: 'TXN-001',
|
|
linkedReimbursementId: null,
|
|
boundInvoiceCount: 1,
|
|
},
|
|
},
|
|
],
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
it('builds travel reconciliation summary payload with batch snapshot', () => {
|
|
const payload = buildTravelReconciliationSummaryPayload({ batch: createBatch() });
|
|
|
|
expect(payload).toEqual(
|
|
expect.objectContaining({
|
|
exportType: 'travel_reconciliation_summary',
|
|
entityType: 'travel_reconciliation_batch',
|
|
entityId: 'batch-1',
|
|
batchNo: 'TRB-20260311-0001',
|
|
periodStart: '2026-03-01',
|
|
periodEnd: '2026-03-31',
|
|
dimensions: {
|
|
legalEntityCode: 'HY',
|
|
costCenterCode: 'CC001',
|
|
},
|
|
summary: {
|
|
totalOrders: 2,
|
|
totalTransactions: 2,
|
|
anomalyItems: 1,
|
|
matchRate: '50.00',
|
|
totalOrderAmount: '200.00',
|
|
totalSettledAmount: '180.00',
|
|
totalReversedAmount: '20.00',
|
|
},
|
|
}),
|
|
);
|
|
expect(payload.items[0]).toEqual(
|
|
expect.objectContaining({
|
|
travelRequestId: 'travel-1',
|
|
requestNo: 'CL-20260310-0001',
|
|
travelOrderId: 'order-1',
|
|
externalOrderNo: 'EXT-001',
|
|
cardTransactionId: 'txn-1',
|
|
externalTxnId: 'TXN-001',
|
|
linkedReimbursementId: null,
|
|
boundInvoiceCount: 1,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('builds stable dedupe key for travel reconciliation batch summary export', () => {
|
|
expect(buildTravelReconciliationSummaryDedupeKey('batch-1')).toBe(
|
|
'travel_reconciliation_batch:batch-1:summary',
|
|
);
|
|
});
|
|
|
|
it('builds ERP track with redacted bindings for non integration managers', async () => {
|
|
const batch = createBatch();
|
|
const prisma = {
|
|
integrationOutbox: {
|
|
findUnique: vi.fn().mockResolvedValue({
|
|
id: 'outbox-1',
|
|
event_type: TRAVEL_ERP_EVENT_TYPE,
|
|
status: 'succeeded',
|
|
attempts: 1,
|
|
last_error: null,
|
|
processed_at: new Date('2026-03-31T10:01:00.000Z'),
|
|
updated_at: new Date('2026-03-31T10:01:00.000Z'),
|
|
}),
|
|
},
|
|
integrationBinding: {
|
|
findUnique: vi
|
|
.fn()
|
|
.mockResolvedValueOnce({
|
|
id: 'binding-summary',
|
|
provider: 'erp',
|
|
entity_type: TRAVEL_ERP_BINDING_TYPES.payableSummary,
|
|
local_id: 'batch-1',
|
|
external_id: 'SUM-001',
|
|
external_code: 'TRB-SUM-001',
|
|
last_synced_at: new Date('2026-03-31T10:02:00.000Z'),
|
|
})
|
|
.mockResolvedValueOnce({
|
|
id: 'binding-batch',
|
|
provider: 'erp',
|
|
entity_type: TRAVEL_ERP_BINDING_TYPES.settlementBatch,
|
|
local_id: 'batch-1',
|
|
external_id: 'BAT-001',
|
|
external_code: 'TRB-BAT-001',
|
|
last_synced_at: new Date('2026-03-31T10:02:00.000Z'),
|
|
}),
|
|
},
|
|
};
|
|
|
|
const track = await buildTravelReconciliationErpTrack(prisma, {}, 'batch-1', {
|
|
context: batch,
|
|
canManage: false,
|
|
gate: { effectiveEnabled: true },
|
|
});
|
|
|
|
expect(track.stage.summaryExport.status).toBe('succeeded');
|
|
expect(track.stage.summaryExport.bindings.payableSummary).toEqual(
|
|
expect.objectContaining({
|
|
entityType: TRAVEL_ERP_BINDING_TYPES.payableSummary,
|
|
externalId: null,
|
|
externalCode: null,
|
|
}),
|
|
);
|
|
expect(track.stage.summaryExport.bindings.settlementBatch).toEqual(
|
|
expect.objectContaining({
|
|
entityType: TRAVEL_ERP_BINDING_TYPES.settlementBatch,
|
|
externalId: null,
|
|
externalCode: null,
|
|
}),
|
|
);
|
|
expect(track.permissions.canRetry).toBe(false);
|
|
});
|
|
});
|