feat(P19ERP3-3,P19ERP3-4): 新增资产ERP轨道接口与前端卡片挂件
这个提交包含在:
@@ -0,0 +1,139 @@
|
||||
const integrationsRoutes = require('../../../src/routes/integrations');
|
||||
|
||||
describe('Integrations asset ERP route helpers', () => {
|
||||
const { canReadAsset, enqueueAssetExportAndMaybeRun, parseRunNow } = integrationsRoutes.__testables;
|
||||
|
||||
function createAsset(overrides = {}) {
|
||||
return {
|
||||
id: 'asset-1',
|
||||
asset_no: 'ZC-001',
|
||||
assignee_employee_id: 'employee-1',
|
||||
legal_entity_id: 'le-1',
|
||||
cost_center_id: 'cc-1',
|
||||
status: 'in_use',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
it('respects asset read permission rules', () => {
|
||||
const asset = createAsset();
|
||||
|
||||
expect(
|
||||
canReadAsset(
|
||||
{ employee: { id: 'employee-1', permissionCodes: ['asset.view_own'] } },
|
||||
asset,
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
canReadAsset(
|
||||
{ employee: { id: 'other', permissionCodes: ['asset.view_own'] } },
|
||||
asset,
|
||||
),
|
||||
).toBe(false);
|
||||
expect(
|
||||
canReadAsset(
|
||||
{ employee: { id: 'other', permissionCodes: ['asset.view_all'] } },
|
||||
asset,
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('retries same asset payload without triggering runNow when duplicated', async () => {
|
||||
const enqueueExport = vi.fn().mockResolvedValue({
|
||||
id: 'outbox-1',
|
||||
status: 'succeeded',
|
||||
duplicated: true,
|
||||
requeued: false,
|
||||
eventType: 'erp.asset.export',
|
||||
});
|
||||
const runOutboxOnce = vi.fn();
|
||||
|
||||
const result = await enqueueAssetExportAndMaybeRun({
|
||||
prisma: {},
|
||||
config: {},
|
||||
logger: {},
|
||||
context: { asset: createAsset() },
|
||||
actorId: 'actor-1',
|
||||
runNow: true,
|
||||
findExistingOutbox: vi.fn().mockResolvedValue({
|
||||
id: 'outbox-1',
|
||||
status: 'succeeded',
|
||||
payload: {
|
||||
exportType: 'asset_ledger_summary',
|
||||
entityId: 'asset-1',
|
||||
assetNo: 'ZC-001',
|
||||
legalEntity: { id: 'le-1' },
|
||||
costCenter: { id: 'cc-1' },
|
||||
status: 'in_use',
|
||||
},
|
||||
}),
|
||||
enqueueExport,
|
||||
runOutboxOnce,
|
||||
});
|
||||
|
||||
expect(enqueueExport).toHaveBeenCalledWith(
|
||||
{},
|
||||
{},
|
||||
expect.objectContaining({
|
||||
payload: expect.objectContaining({
|
||||
assetNo: 'ZC-001',
|
||||
status: 'in_use',
|
||||
}),
|
||||
requeueOnFailure: true,
|
||||
}),
|
||||
);
|
||||
expect(runOutboxOnce).not.toHaveBeenCalled();
|
||||
expect(result.duplicated).toBe(true);
|
||||
});
|
||||
|
||||
it('resyncs current asset snapshot when financial drift exists', async () => {
|
||||
const enqueueExport = vi.fn().mockResolvedValue({
|
||||
id: 'outbox-1',
|
||||
status: 'pending',
|
||||
duplicated: false,
|
||||
requeued: true,
|
||||
eventType: 'erp.asset.export',
|
||||
});
|
||||
const runOutboxOnce = vi.fn().mockResolvedValue({ total: 1, succeeded: 1, failed: 0 });
|
||||
|
||||
const result = await enqueueAssetExportAndMaybeRun({
|
||||
prisma: {},
|
||||
config: {},
|
||||
logger: {},
|
||||
context: { asset: createAsset({ cost_center_id: 'cc-2', status: 'retired' }) },
|
||||
actorId: 'actor-1',
|
||||
runNow: true,
|
||||
findExistingOutbox: vi.fn().mockResolvedValue({
|
||||
id: 'outbox-1',
|
||||
status: 'succeeded',
|
||||
payload: {
|
||||
exportType: 'asset_ledger_summary',
|
||||
entityId: 'asset-1',
|
||||
assetNo: 'ZC-001',
|
||||
legalEntity: { id: 'le-1' },
|
||||
costCenter: { id: 'cc-1' },
|
||||
status: 'in_use',
|
||||
},
|
||||
}),
|
||||
enqueueExport,
|
||||
runOutboxOnce,
|
||||
});
|
||||
|
||||
expect(enqueueExport).toHaveBeenCalledWith(
|
||||
{},
|
||||
{},
|
||||
expect.objectContaining({
|
||||
payload: undefined,
|
||||
changeType: 'retired',
|
||||
changedFields: expect.arrayContaining(['costCenterId', 'status']),
|
||||
}),
|
||||
);
|
||||
expect(runOutboxOnce).toHaveBeenCalled();
|
||||
expect(result.requeued).toBe(true);
|
||||
});
|
||||
|
||||
it('parses runNow flags for asset requeue route', () => {
|
||||
expect(parseRunNow({ runNow: '1' })).toBe(true);
|
||||
expect(parseRunNow({ runNow: '0' })).toBe(false);
|
||||
});
|
||||
});
|
||||
在新工单中引用
屏蔽一个用户