238 行
7.6 KiB
JavaScript
238 行
7.6 KiB
JavaScript
const erp = require('../../../../src/services/integrations/erp');
|
|
|
|
describe('ERP Sync Services', () => {
|
|
const originalFetch = global.fetch;
|
|
|
|
afterEach(() => {
|
|
global.fetch = originalFetch;
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it('builds reimbursement journal payload via compat helper', () => {
|
|
const payload = erp.buildErpPayload({
|
|
reimbursement: {
|
|
id: 'r1',
|
|
form_number: 'F001',
|
|
owner_id: 'owner-1',
|
|
submitter_id: 'submitter-1',
|
|
department_id: 'dept-1',
|
|
total_amount: { toString: () => '100.00' },
|
|
payment_voucher_no: 'PV001',
|
|
payment_date: '2026-03-01T12:00:00.000Z',
|
|
paid_at: '2026-03-02T08:00:00.000Z',
|
|
owner: { legal_entity: { code: 'HY' } },
|
|
},
|
|
});
|
|
|
|
expect(payload.journalType).toBe('reimbursement_paid');
|
|
expect(payload.documentNo).toBe('F001');
|
|
expect(payload.paymentDate).toBe('2026-03-01');
|
|
expect(payload.dimensions.legalEntityCode).toBe('HY');
|
|
});
|
|
|
|
it('returns mock synced when journal export provider is mock', async () => {
|
|
const result = await erp.exportJournalToErp(
|
|
{ erpSyncEnabled: true, erpSyncProvider: 'mock' },
|
|
{ journalType: 'purchase_request_commitment', documentNo: 'PR-001' },
|
|
);
|
|
expect(result.ok).toBe(true);
|
|
expect(result.code).toBe('mock_synced');
|
|
expect(result.response.externalId).toContain('PR-001');
|
|
});
|
|
|
|
it('returns mock procurement result when provider is mock', async () => {
|
|
const result = await erp.exportProcurementToErp(
|
|
{ erpSyncEnabled: true, erpSyncProvider: 'mock' },
|
|
{ poNo: 'PO-001', contractNo: 'CT-001' },
|
|
);
|
|
expect(result.ok).toBe(true);
|
|
expect(result.code).toBe('mock_synced');
|
|
expect(result.response.externalPoNo).toContain('PO-001');
|
|
expect(result.response.externalContractNo).toContain('CT-001');
|
|
});
|
|
|
|
it('returns mock master data result when provider is mock', async () => {
|
|
const result = await erp.pullMasterDataFromErp(
|
|
{ erpSyncEnabled: true, erpSyncProvider: 'mock' },
|
|
{ resources: ['legal_entity', 'supplier'] },
|
|
);
|
|
expect(result.ok).toBe(true);
|
|
expect(result.code).toBe('mock_synced');
|
|
expect(result.data.legalEntities).toEqual([]);
|
|
expect(result.data.suppliers).toEqual([]);
|
|
});
|
|
|
|
it('handles http master data 4xx errors', async () => {
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: false,
|
|
status: 422,
|
|
headers: { get: () => 'application/json' },
|
|
json: vi.fn().mockResolvedValue({ code: 'invalid_payload' }),
|
|
});
|
|
|
|
const result = await erp.pullMasterDataFromErp(
|
|
{
|
|
erpSyncEnabled: true,
|
|
erpSyncProvider: 'http',
|
|
erpMasterDataEndpoint: 'https://example.com/master-data',
|
|
erpMasterDataTimeoutMs: 500,
|
|
},
|
|
{ resources: ['legal_entity'] },
|
|
);
|
|
|
|
expect(result.ok).toBe(false);
|
|
expect(result.code).toBe('erp_master_data_http_4xx');
|
|
expect(result.retryable).toBe(false);
|
|
});
|
|
|
|
it('handles http journal export 5xx errors', async () => {
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: false,
|
|
status: 503,
|
|
headers: { get: () => 'application/json' },
|
|
json: vi.fn().mockResolvedValue({ code: 'service_unavailable' }),
|
|
});
|
|
|
|
const result = await erp.exportJournalToErp(
|
|
{
|
|
erpSyncEnabled: true,
|
|
erpSyncProvider: 'http',
|
|
erpJournalExportEndpoint: 'https://example.com/journals',
|
|
erpSyncTimeoutMs: 500,
|
|
},
|
|
{ journalType: 'purchase_order_accrual', documentNo: 'PO-001' },
|
|
);
|
|
|
|
expect(result.ok).toBe(false);
|
|
expect(result.code).toBe('erp_sync_http_5xx');
|
|
expect(result.retryable).toBe(true);
|
|
});
|
|
|
|
it('handles http procurement export 4xx errors', async () => {
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: false,
|
|
status: 400,
|
|
headers: { get: () => 'application/json' },
|
|
json: vi.fn().mockResolvedValue({ code: 'bad_request' }),
|
|
});
|
|
|
|
const result = await erp.exportProcurementToErp(
|
|
{
|
|
erpSyncEnabled: true,
|
|
erpSyncProvider: 'http',
|
|
erpProcurementExportEndpoint: 'https://example.com/procurement',
|
|
erpProcurementTimeoutMs: 500,
|
|
},
|
|
{ exportType: 'purchase_order_archive', poNo: 'PO-001' },
|
|
);
|
|
|
|
expect(result.ok).toBe(false);
|
|
expect(result.code).toBe('erp_procurement_http_4xx');
|
|
expect(result.retryable).toBe(false);
|
|
});
|
|
|
|
it('handles http procurement export timeout', async () => {
|
|
global.fetch = vi.fn().mockRejectedValue({ name: 'AbortError' });
|
|
|
|
const result = await erp.exportProcurementToErp(
|
|
{
|
|
erpSyncEnabled: true,
|
|
erpSyncProvider: 'http',
|
|
erpProcurementExportEndpoint: 'https://example.com/procurement',
|
|
erpProcurementTimeoutMs: 500,
|
|
},
|
|
{ exportType: 'purchase_order_archive', poNo: 'PO-001' },
|
|
);
|
|
|
|
expect(result.ok).toBe(false);
|
|
expect(result.code).toBe('erp_procurement_timeout');
|
|
expect(result.retryable).toBe(true);
|
|
});
|
|
|
|
it('handles http journal export timeout', async () => {
|
|
global.fetch = vi.fn().mockRejectedValue({ name: 'AbortError' });
|
|
|
|
const result = await erp.exportJournalToErp(
|
|
{
|
|
erpSyncEnabled: true,
|
|
erpSyncProvider: 'http',
|
|
erpJournalExportEndpoint: 'https://example.com/journals',
|
|
erpSyncTimeoutMs: 500,
|
|
},
|
|
{ documentNo: 'BX-001' },
|
|
);
|
|
|
|
expect(result.ok).toBe(false);
|
|
expect(result.code).toBe('erp_sync_timeout');
|
|
expect(result.retryable).toBe(true);
|
|
});
|
|
|
|
it('returns mock asset export result when provider is mock', async () => {
|
|
const result = await erp.exportAssetToErp(
|
|
{ erpSyncEnabled: true, erpSyncProvider: 'mock' },
|
|
{ assetNo: 'ZC-001', entityId: 'asset-1' },
|
|
);
|
|
|
|
expect(result.ok).toBe(true);
|
|
expect(result.code).toBe('mock_synced');
|
|
expect(result.response.externalAssetId).toContain('ZC-001');
|
|
expect(result.response.externalAssetNo).toContain('ZC-001');
|
|
});
|
|
|
|
it('handles http asset export 4xx errors', async () => {
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: false,
|
|
status: 400,
|
|
headers: { get: () => 'application/json' },
|
|
json: vi.fn().mockResolvedValue({ code: 'bad_request' }),
|
|
});
|
|
|
|
const result = await erp.exportAssetToErp(
|
|
{
|
|
erpSyncEnabled: true,
|
|
erpSyncProvider: 'http',
|
|
erpAssetExportEndpoint: 'https://example.com/assets',
|
|
erpAssetTimeoutMs: 500,
|
|
},
|
|
{ exportType: 'asset_ledger_summary', assetNo: 'ZC-001' },
|
|
);
|
|
|
|
expect(result.ok).toBe(false);
|
|
expect(result.code).toBe('erp_asset_http_4xx');
|
|
expect(result.retryable).toBe(false);
|
|
});
|
|
|
|
it('handles http asset export timeout', async () => {
|
|
global.fetch = vi.fn().mockRejectedValue({ name: 'AbortError' });
|
|
|
|
const result = await erp.exportAssetToErp(
|
|
{
|
|
erpSyncEnabled: true,
|
|
erpSyncProvider: 'http',
|
|
erpAssetExportEndpoint: 'https://example.com/assets',
|
|
erpAssetTimeoutMs: 500,
|
|
},
|
|
{ exportType: 'asset_ledger_summary', assetNo: 'ZC-001' },
|
|
);
|
|
|
|
expect(result.ok).toBe(false);
|
|
expect(result.code).toBe('erp_asset_timeout');
|
|
expect(result.retryable).toBe(true);
|
|
});
|
|
|
|
it('returns invalid provider if unsupported', async () => {
|
|
const result = await erp.exportJournalToErp(
|
|
{ erpSyncEnabled: true, erpSyncProvider: 'invalid_xyz' },
|
|
{},
|
|
);
|
|
expect(result.ok).toBe(false);
|
|
expect(result.code).toBe('erp_sync_provider_invalid');
|
|
});
|
|
|
|
it('returns disabled if ERP sync is disabled', async () => {
|
|
const result = await erp.exportJournalToErp({ erpSyncEnabled: false }, {});
|
|
expect(result.ok).toBe(false);
|
|
expect(result.code).toBe('erp_sync_disabled');
|
|
});
|
|
});
|