268 lines
10 KiB
JavaScript
268 lines
10 KiB
JavaScript
import assert from 'node:assert/strict';
|
||
import test from 'node:test';
|
||
|
||
import {
|
||
actionLabel,
|
||
allowedTaskActions,
|
||
buildBusinessOverview,
|
||
buildAcceptanceChecklist,
|
||
buildJobReferenceChain,
|
||
buildNodeCallbackRequestBody,
|
||
buildNodeDetails,
|
||
buildNodeCallbackTemplate,
|
||
jobStatusBucketLabel,
|
||
jobStatusBucketMatches,
|
||
nodeCallbackActionLabel,
|
||
reasonLabel,
|
||
roleLabel,
|
||
taskCandidates,
|
||
taskStatusLabel,
|
||
} from './stateMachine.ts';
|
||
|
||
test('待办相关编码全部显示为中文', () => {
|
||
assert.equal(roleLabel('PLANNER'), '计划员');
|
||
assert.equal(taskStatusLabel('OPEN'), '待处理');
|
||
assert.equal(actionLabel('SELECT_CANDIDATE'), '选择候选货品');
|
||
assert.equal(reasonLabel('MATCH_MULTI'), '存在多个候选货品');
|
||
assert.equal(reasonLabel('SOURCE_ADD'), '需要新增货源');
|
||
});
|
||
|
||
test('候选货品兼容数据库返回的数组和 JSON 文本', () => {
|
||
const task = {
|
||
task_id: 'TASK-1',
|
||
job_id: 'JOB-1',
|
||
node_code: 'PRODUCT_MATCH',
|
||
role_code: 'PLANNER',
|
||
status: 'OPEN',
|
||
title: '对码待确认',
|
||
reason_type: 'MATCH_MULTI',
|
||
};
|
||
assert.equal(taskCandidates({ ...task, candidates_json: '[{"goods_id":"G-1"}]' })[0]?.goods_id, 'G-1');
|
||
assert.equal(taskCandidates({ ...task, candidates_json: [{ goods_id: 'G-2' }] })[0]?.goods_id, 'G-2');
|
||
});
|
||
|
||
test('对码待办只提供后端支持的业务动作', () => {
|
||
assert.deepEqual(
|
||
allowedTaskActions({
|
||
task_id: 'TASK-1',
|
||
job_id: 'JOB-1',
|
||
node_code: 'PRODUCT_MATCH',
|
||
role_code: 'PLANNER',
|
||
status: 'OPEN',
|
||
title: '对码待确认',
|
||
reason_type: 'MATCH_MULTI',
|
||
}),
|
||
['SELECT_CANDIDATE', 'TRANSFER_MANUAL', 'TERMINATE_JOB'],
|
||
);
|
||
});
|
||
|
||
test('合同建单成功回调模板带必填合同编号', () => {
|
||
const template = buildNodeCallbackTemplate('CONTRACT_CREATE', 'complete');
|
||
assert.equal(template.attemptNo, 1);
|
||
assert.equal(template.executorPlatform, 'iS-RPA');
|
||
assert.equal(template.businessRefs.contractSystemNo, 'HT-联调-001');
|
||
assert.equal(template.artifacts[0].artifactType, 'CONTRACT_PDF');
|
||
});
|
||
|
||
test('节点失败回调模板带后端必填错误字段', () => {
|
||
const template = buildNodeCallbackTemplate('REBATE_IMPORT', 'fail');
|
||
assert.equal(nodeCallbackActionLabel('fail'), '失败');
|
||
assert.equal(template.errorCode, 'SYS_PAGE_TIMEOUT');
|
||
assert.equal(template.retryable, true);
|
||
assert.equal(template.routeRole, 'OPS');
|
||
});
|
||
|
||
test('成功回调请求自动补幂等键并保留手工覆盖字段', () => {
|
||
const body = buildNodeCallbackRequestBody(
|
||
'complete',
|
||
'CONTRACT_CREATE',
|
||
{
|
||
summary: { contractAmount: 1200 },
|
||
businessRefs: { contractSystemNo: 'HT-20260811-001' },
|
||
},
|
||
'idem-001',
|
||
);
|
||
|
||
assert.equal(body.idempotencyKey, 'idem-001');
|
||
assert.equal(body.businessRefs.contractSystemNo, 'HT-20260811-001');
|
||
assert.equal(body.summary.contractAmount, 1200);
|
||
});
|
||
|
||
test('单据状态筛选按验收口径归类', () => {
|
||
assert.equal(jobStatusBucketLabel('ACTIVE'), '进行中');
|
||
assert.equal(jobStatusBucketMatches('ALL', 'SUCCESS'), true);
|
||
assert.equal(jobStatusBucketMatches('ACTIVE', 'RUNNING'), true);
|
||
assert.equal(jobStatusBucketMatches('ACTIVE', 'WAITING_EXTERNAL'), true);
|
||
assert.equal(jobStatusBucketMatches('WAITING', 'WAITING_USER'), true);
|
||
assert.equal(jobStatusBucketMatches('WAITING', 'RUNNING'), false);
|
||
assert.equal(jobStatusBucketMatches('FAILED', 'FAILED_MANUAL'), true);
|
||
assert.equal(jobStatusBucketMatches('SUCCESS', 'SUCCESS'), true);
|
||
});
|
||
|
||
test('节点详情按节点关联执行记录、证据和待办', () => {
|
||
const details = buildNodeDetails(
|
||
[
|
||
{ node_code: 'ORDER_FETCH', display_status: 'SUCCESS', attempt_no: 1, status: 'SUCCESS' },
|
||
{ node_code: 'SOURCE_ADD', display_status: 'WAITING_USER', attempt_no: 1, status: 'WAITING_USER' },
|
||
],
|
||
[
|
||
{
|
||
node_code: 'ORDER_FETCH',
|
||
attempt_no: 1,
|
||
status: 'SUCCESS',
|
||
started_at: '2026-08-11T09:00:00',
|
||
ended_at: '2026-08-11T09:00:12',
|
||
output_json: '{"summary":{"orderCount":2}}',
|
||
},
|
||
],
|
||
[
|
||
{
|
||
node_code: 'ORDER_FETCH',
|
||
attempt_no: 1,
|
||
artifact_type: 'SOURCE_EXCEL',
|
||
storage_path: '/data/jobs/JOB-1/orders.xlsx',
|
||
},
|
||
],
|
||
[
|
||
{
|
||
task_id: 'TASK-1',
|
||
job_id: 'JOB-1',
|
||
node_code: 'SOURCE_ADD',
|
||
role_code: 'PURCHASER',
|
||
status: 'OPEN',
|
||
title: '新增货源',
|
||
reason_type: 'SOURCE_ADD',
|
||
},
|
||
],
|
||
);
|
||
|
||
assert.equal(details[0].summaryText, 'orderCount:2');
|
||
assert.equal(details[0].artifacts[0].storage_path, '/data/jobs/JOB-1/orders.xlsx');
|
||
assert.equal(details[1].openTask?.task_id, 'TASK-1');
|
||
});
|
||
|
||
test('编号链能标出已回填和待回填项', () => {
|
||
const chain = buildJobReferenceChain({
|
||
job_id: 'JOB-1',
|
||
supplier_name: '重庆亿昊药业',
|
||
order_date: '2026-08-04',
|
||
source_type: 'YYC',
|
||
status: 'RUNNING',
|
||
current_node_code: 'CONTRACT_CREATE',
|
||
order_no_list_json: '["ZQD-1","ZQD-2"]',
|
||
contract_system_no: 'HT-1',
|
||
rebate_order_no: null,
|
||
m5_order_no: '',
|
||
receipt_notice_no: 'SH-1',
|
||
});
|
||
|
||
assert.deepEqual(chain.map((item) => item.status), ['DONE', 'DONE', 'MISSING', 'MISSING', 'DONE']);
|
||
assert.equal(chain[0].value, 'ZQD-1、ZQD-2');
|
||
});
|
||
|
||
test('业务概览只保留首屏必须信息', () => {
|
||
const overview = buildBusinessOverview(
|
||
{
|
||
job_id: 'JOB-1',
|
||
supplier_name: '重庆亿昊药业',
|
||
order_date: '2026-08-04',
|
||
source_type: 'YYC',
|
||
status: 'SUCCESS',
|
||
current_node_code: 'ARCHIVE_JOB',
|
||
current_role: 'OPS',
|
||
contract_system_no: 'HT-1',
|
||
rebate_order_no: 'FL-1',
|
||
m5_order_no: 'M5-1',
|
||
receipt_notice_no: 'SH-1',
|
||
order_no_list_json: '["ZQD-1","ZQD-2"]',
|
||
},
|
||
[
|
||
{ node_code: 'ORDER_FETCH', display_status: 'SUCCESS', attempt_no: 1, status: 'SUCCESS' },
|
||
{ node_code: 'PRODUCT_MATCH', display_status: 'SUCCESS', attempt_no: 1, status: 'SUCCESS' },
|
||
{ node_code: 'SOURCE_ADD', display_status: 'WAITING_USER', attempt_no: 1, status: 'WAITING_USER' },
|
||
{ node_code: 'CONTRACT_CREATE', display_status: 'PENDING' },
|
||
{ node_code: 'ARCHIVE_JOB', display_status: 'SUCCESS', attempt_no: 1, status: 'SUCCESS' },
|
||
],
|
||
[
|
||
{ task_id: 'TASK-1', job_id: 'JOB-1', node_code: 'SOURCE_ADD', role_code: 'PURCHASER', status: 'OPEN', title: '新增货源:重庆亿昊药业', reason_type: 'SOURCE_ADD' },
|
||
],
|
||
[
|
||
{ node_code: 'ORDER_FETCH', attempt_no: 1, artifact_type: 'SOURCE_EXCEL', storage_path: '/data/jobs/JOB-1/orders.xlsx' },
|
||
],
|
||
{
|
||
node_order: ['ORDER_FETCH', 'PRODUCT_MATCH', 'SOURCE_ADD', 'CONTRACT_CREATE', 'ARCHIVE_JOB'],
|
||
transitions: {},
|
||
conditional_nodes: [],
|
||
human_node_roles: {},
|
||
node_executor: {},
|
||
node_labels: {
|
||
ORDER_FETCH: '订单获取',
|
||
PRODUCT_MATCH: '对码',
|
||
SOURCE_ADD: '新增货源',
|
||
CONTRACT_CREATE: '合同建单',
|
||
ARCHIVE_JOB: '归档',
|
||
},
|
||
executor_labels: {},
|
||
status_labels: {
|
||
SUCCESS: '已完成',
|
||
WAITING_USER: '等待人工',
|
||
PENDING: '待处理',
|
||
},
|
||
},
|
||
);
|
||
|
||
assert.equal(overview.header.currentStatusText, '已完成');
|
||
assert.equal(overview.header.currentNodeText, '归档');
|
||
assert.equal(overview.referenceChain.length, 5);
|
||
assert.equal(overview.primaryTask?.title, '新增货源:重庆亿昊药业');
|
||
assert.equal(overview.flow.visibleNodes.length, 5);
|
||
assert.equal(overview.summary.evidenceCount, 1);
|
||
});
|
||
|
||
test('验收收口能识别真库一单的完成和缺口', () => {
|
||
const checklist = buildAcceptanceChecklist(
|
||
{
|
||
job_id: 'JOB-1',
|
||
supplier_name: '重庆亿昊药业',
|
||
order_date: '2026-08-04',
|
||
source_type: 'YYC',
|
||
status: 'SUCCESS',
|
||
current_node_code: 'ARCHIVE_JOB',
|
||
contract_system_no: 'HT-1',
|
||
rebate_order_no: 'FL-1',
|
||
m5_order_no: 'M5-1',
|
||
receipt_notice_no: 'SH-1',
|
||
order_no_list_json: '["ZQD-1"]',
|
||
},
|
||
[
|
||
{ node_code: 'ORDER_FETCH', display_status: 'SUCCESS', attempt_no: 1, status: 'SUCCESS' },
|
||
{ node_code: 'OCR_EXTRACT', display_status: 'SKIPPED' },
|
||
{ node_code: 'PRODUCT_MATCH', display_status: 'SUCCESS', attempt_no: 1, status: 'SUCCESS' },
|
||
{ node_code: 'SOURCE_ADD', display_status: 'SUCCESS', attempt_no: 1, status: 'SUCCESS' },
|
||
{ node_code: 'CONTRACT_CREATE', display_status: 'SUCCESS', attempt_no: 1, status: 'SUCCESS' },
|
||
{ node_code: 'CONTRACT_APPROVE_WAIT', display_status: 'SUCCESS', attempt_no: 1, status: 'SUCCESS' },
|
||
{ node_code: 'REBATE_HEADER', display_status: 'SUCCESS', attempt_no: 1, status: 'SUCCESS' },
|
||
{ node_code: 'REBATE_IMPORT', display_status: 'SUCCESS', attempt_no: 1, status: 'SUCCESS' },
|
||
{ node_code: 'REBATE_SUBMIT', display_status: 'SUCCESS', attempt_no: 1, status: 'SUCCESS' },
|
||
{ node_code: 'M5_CREATE_ORDER', display_status: 'SUCCESS', attempt_no: 1, status: 'SUCCESS' },
|
||
{ node_code: 'M5_MANUAL_CONFIRM', display_status: 'SUCCESS', attempt_no: 1, status: 'SUCCESS' },
|
||
{ node_code: 'M5_RECEIPT_NOTICE', display_status: 'SUCCESS', attempt_no: 1, status: 'SUCCESS' },
|
||
{ node_code: 'ARCHIVE_JOB', display_status: 'SUCCESS', attempt_no: 1, status: 'SUCCESS' },
|
||
],
|
||
[
|
||
{ node_code: 'ORDER_FETCH', attempt_no: 1, status: 'SUCCESS', started_at: '2026-08-11T09:00:00', ended_at: '2026-08-11T09:00:12', output_json: '{"summary":{"orderCount":1}}' },
|
||
{ node_code: 'CONTRACT_CREATE', attempt_no: 1, status: 'SUCCESS', started_at: '2026-08-11T09:02:00', ended_at: '2026-08-11T09:02:12', output_json: '{"summary":{"saved":true}}' },
|
||
],
|
||
[
|
||
{ node_code: 'ORDER_FETCH', attempt_no: 1, artifact_type: 'SOURCE_EXCEL', storage_path: '/data/jobs/JOB-1/orders.xlsx' },
|
||
],
|
||
[
|
||
{ task_id: 'TASK-1', job_id: 'JOB-1', node_code: 'M5_MANUAL_CONFIRM', role_code: 'PURCHASER', status: 'SUBMITTED', title: 'M5人工确认', reason_type: 'SOURCE_ADD' },
|
||
],
|
||
);
|
||
|
||
assert.equal(checklist.find((item) => item.key === 'timeline_complete')?.status, 'DONE');
|
||
assert.equal(checklist.find((item) => item.key === 'node_detail_coverage')?.status, 'PARTIAL');
|
||
assert.equal(checklist.find((item) => item.key === 'reference_chain')?.status, 'DONE');
|
||
});
|