文件
hyb-oa/server/tests/unit/services/erpTravel.test.js
T

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);
});
});