const assetsRoutes = require('../../../src/routes/assets'); const { resolveAssetLedgerSummaryChange } = require('../../../src/services/integrations/erpAsset'); describe('asset ERP helper', () => { const { handleAssetLedgerSummaryErpSync } = assetsRoutes.__testables; function createRequest() { return { id: 'req-1', ip: '127.0.0.1', headers: {}, employee: { id: 'actor-1', permissionCodes: ['asset.manage'], }, }; } function createTx() { return { auditLog: { create: vi.fn().mockResolvedValue({ id: 'audit-1' }), }, }; } function createAsset(overrides = {}) { return { id: 'asset-1', asset_no: 'ZC-001', legal_entity_id: 'le-1', cost_center_id: 'cc-1', status: 'in_use', ...overrides, }; } it('auto enqueues asset export when gate is enabled', async () => { const tx = createTx(); const evaluateGate = vi.fn().mockResolvedValue({ enabled: true, reason: 'department_deferred', }); const enqueueExport = vi.fn().mockResolvedValue({ id: 'outbox-1', status: 'pending', duplicated: false, requeued: false, eventType: 'erp.asset.export', }); const result = await handleAssetLedgerSummaryErpSync({ tx, config: {}, request: createRequest(), actorId: 'actor-1', asset: createAsset(), sourceEventName: 'asset.card.created', changeType: 'created', changedFields: [], evaluateGate, enqueueExport, }); expect(enqueueExport).toHaveBeenCalledWith( tx, {}, expect.objectContaining({ actorId: 'actor-1', requeueOnFailure: false, }), ); expect(tx.auditLog.create).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ action: 'ERP_SYNC_ENQUEUED', target_type: 'asset_card', target_id: 'asset-1', }), }), ); expect(result.skipped).toBe(false); }); it('writes skip audit when asset ERP gate is disabled', async () => { const tx = createTx(); const evaluateGate = vi.fn().mockResolvedValue({ enabled: false, reason: 'flag_force_off' }); const enqueueExport = vi.fn(); const result = await handleAssetLedgerSummaryErpSync({ tx, config: {}, request: createRequest(), actorId: 'actor-1', asset: createAsset(), sourceEventName: 'asset.card.created', changeType: 'created', changedFields: [], evaluateGate, enqueueExport, }); expect(enqueueExport).not.toHaveBeenCalled(); expect(tx.auditLog.create).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ action: 'ERP_SYNC_SKIPPED_BY_FEATURE_FLAG', target_type: 'asset_card', target_id: 'asset-1', }), }), ); expect(result.skipped).toBe(true); }); it('only marks financial field and retired status changes as ERP-relevant', () => { const current = createAsset(); expect( resolveAssetLedgerSummaryChange(current, { ...current, name: '新资产名称', }), ).toBeNull(); expect( resolveAssetLedgerSummaryChange(current, { ...current, legal_entity_id: 'le-2', }), ).toEqual({ sourceEventName: 'asset.ledger.summary.updated', changeType: 'financial_dimensions_changed', changedFields: ['legalEntityId'], }); expect( resolveAssetLedgerSummaryChange(current, { ...current, status: 'retired', }), ).toEqual({ sourceEventName: 'asset.ledger.summary.updated', changeType: 'retired', changedFields: ['status'], }); }); });