feat(P19ERP3-1,P19ERP3-2): 实现资产ERP摘要服务与建卡/流转出站联动
这个提交包含在:
@@ -0,0 +1,148 @@
|
||||
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'],
|
||||
});
|
||||
});
|
||||
});
|
||||
在新工单中引用
屏蔽一个用户