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

139 行
4.6 KiB
JavaScript

const {
buildAssetLedgerSummaryDedupeKey,
buildAssetLedgerSummaryPayload,
enqueueAssetLedgerSummaryExport,
hashAssetPayload,
resolveAssetLedgerSummaryChange,
} = require('../../../src/services/integrations/erpAsset');
describe('ERP Asset Service', () => {
function createAsset(overrides = {}) {
return {
id: 'asset-1',
asset_no: 'ZC-001',
name: 'ThinkPad X1',
status: 'in_use',
location: '上海办公室-A区',
activated_at: '2026-03-09T00:00:00.000Z',
retired_at: null,
created_at: '2026-03-09T08:00:00.000Z',
updated_at: '2026-03-09T08:00:00.000Z',
legal_entity_id: 'le-1',
cost_center_id: 'cc-1',
purchase_request_id: 'pr-1',
assignee_employee_id: 'emp-1',
category: {
id: 'cat-1',
code: 'LAPTOP',
name: '笔记本电脑',
document_category: { code: 'ASSET_STD' },
},
legal_entity: { id: 'le-1', code: 'HY', name: '海音达' },
cost_center: { id: 'cc-1', code: 'IT001', name: 'IT中心' },
purchase_request: { id: 'pr-1', request_no: 'PR-001', title: '办公电脑采购' },
assignee: { id: 'emp-1', employee_no: 'E0001', name: '张三' },
...overrides,
};
}
it('builds asset ledger summary payload for create event', () => {
const payload = buildAssetLedgerSummaryPayload({
asset: createAsset(),
sourceEventName: 'asset.card.created',
changeType: 'created',
changedFields: [],
occurredAt: '2026-03-09T08:00:00.000Z',
});
expect(payload.exportType).toBe('asset_ledger_summary');
expect(payload.sourceEventName).toBe('asset.card.created');
expect(payload.changeType).toBe('created');
expect(payload.assetNo).toBe('ZC-001');
expect(payload.category.code).toBe('LAPTOP');
expect(payload.legalEntity.code).toBe('HY');
expect(payload.costCenter.code).toBe('IT001');
expect(payload.purchaseRequest.requestNo).toBe('PR-001');
expect(payload.assignee.employeeNo).toBe('E0001');
});
it('detects financial-dimension changes only', () => {
const previousAsset = createAsset();
expect(
resolveAssetLedgerSummaryChange(previousAsset, {
...previousAsset,
name: 'ThinkPad X1 Carbon Gen 12',
location: '上海办公室-B区',
}),
).toBeNull();
expect(
resolveAssetLedgerSummaryChange(previousAsset, {
...previousAsset,
cost_center_id: 'cc-2',
}),
).toEqual({
sourceEventName: 'asset.ledger.summary.updated',
changeType: 'financial_dimensions_changed',
changedFields: ['costCenterId'],
});
expect(
resolveAssetLedgerSummaryChange(previousAsset, {
...previousAsset,
status: 'retired',
}),
).toEqual({
sourceEventName: 'asset.ledger.summary.updated',
changeType: 'retired',
changedFields: ['status'],
});
});
it('builds deterministic dedupe key and payload hash', () => {
const asset = createAsset();
const payload = buildAssetLedgerSummaryPayload({ asset, changedFields: [] });
const changedPayload = buildAssetLedgerSummaryPayload({
asset: createAsset({ cost_center: { id: 'cc-2', code: 'IT002', name: 'IT二部' }, cost_center_id: 'cc-2' }),
sourceEventName: 'asset.ledger.summary.updated',
changeType: 'financial_dimensions_changed',
changedFields: ['costCenterId'],
});
expect(buildAssetLedgerSummaryDedupeKey('asset-1')).toBe('asset_card:asset-1:ledger_summary');
expect(hashAssetPayload(payload)).not.toBe(hashAssetPayload(changedPayload));
});
it('resets same outbox when asset payload changes', async () => {
const prisma = {
integrationOutbox: {
findUnique: vi.fn().mockResolvedValue({
id: 'outbox-1',
status: 'succeeded',
payload: { assetNo: 'ZC-001', costCenter: { id: 'cc-1', code: 'IT001' } },
}),
update: vi.fn().mockResolvedValue({ id: 'outbox-1', status: 'pending' }),
},
};
const result = await enqueueAssetLedgerSummaryExport(
prisma,
{ integrationOutboxMaxAttempts: 5 },
{
actorId: 'actor-1',
asset: createAsset({
cost_center_id: 'cc-2',
cost_center: { id: 'cc-2', code: 'IT002', name: 'IT二部' },
}),
sourceEventName: 'asset.ledger.summary.updated',
changeType: 'financial_dimensions_changed',
changedFields: ['costCenterId'],
},
);
expect(prisma.integrationOutbox.update).toHaveBeenCalled();
expect(result.eventType).toBe('erp.asset.export');
expect(result.requeued).toBe(true);
});
});