Files
2026-07-13 19:57:46 +08:00

196 lines
5.5 KiB
JavaScript

const TASK_SCHEMA_VERSION = 'erp-task-v1';
const KNOWN_OPERATIONS = new Set([
'create_order',
'update_order',
'export_confirmation',
]);
const KNOWN_ROUTES = new Set([
'team_single',
'team_batch',
'split_parent',
'split_child',
]);
const EXPORT_TYPES = new Set([
'xingyou-confirm',
'liantai-confirm',
'job-order',
]);
const EXPORT_TYPE_ALIASES = new Map([
['confirmation', 'xingyou-confirm'],
['confirm', 'xingyou-confirm'],
['xingyou', 'xingyou-confirm'],
['xingyou-confirm', 'xingyou-confirm'],
['liantai', 'liantai-confirm'],
['liantai-confirm', 'liantai-confirm'],
['job', 'job-order'],
['beian', 'job-order'],
['job-order', 'job-order'],
]);
function isObject(value) {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
}
function nonEmptyString(value) {
return typeof value === 'string' && value.trim().length > 0;
}
function normalizeExportTypes(types) {
return [...new Set(types.map((type) => {
const value = String(type || '').trim().toLowerCase();
return EXPORT_TYPE_ALIASES.get(value) || value;
}))];
}
function normalizeTaskEnvelope(input) {
const envelope = isObject(input) ? { ...input } : {};
const inputTask = isObject(envelope.task) ? envelope.task : {};
const route = inputTask.route || envelope.route || '';
const operation = inputTask.operation
|| envelope.operation
|| (route ? 'create_order' : '');
const task = {
...inputTask,
schemaVersion: inputTask.schemaVersion || TASK_SCHEMA_VERSION,
operation,
};
if (route) task.route = route;
if (!Array.isArray(task.attachments)) task.attachments = [];
if (operation === 'export_confirmation') {
task.exportTypes = Array.isArray(task.exportTypes) && task.exportTypes.length
? normalizeExportTypes(task.exportTypes)
: ['xingyou-confirm'];
}
return {
...envelope,
operation,
route,
task,
};
}
function validateTaskShape(task, envelope = {}) {
const violations = [];
const operation = task.operation || envelope.operation || '';
const route = task.route || envelope.route || '';
if (!KNOWN_OPERATIONS.has(operation)) {
violations.push(`unsupported operation: ${operation || '(missing)'}`);
}
if (envelope.operation && task.operation && envelope.operation !== task.operation) {
violations.push('envelope.operation does not match task.operation');
}
if (envelope.route && task.route && envelope.route !== task.route) {
violations.push('envelope.route does not match task.route');
}
if (task.schemaVersion !== TASK_SCHEMA_VERSION) {
violations.push(`unsupported schemaVersion: ${task.schemaVersion || '(missing)'}`);
}
if (!Array.isArray(task.attachments)) {
violations.push('attachments must be an array');
} else if (task.attachments.some((item) => typeof item !== 'string')) {
violations.push('attachments must contain only paths');
}
if (operation === 'create_order') {
if (!KNOWN_ROUTES.has(route)) violations.push(`route is required for create_order: ${route || '(missing)'}`);
if (!isObject(task.fields)) violations.push('fields must be an object for create_order');
}
if (operation !== 'create_order' && route && !KNOWN_ROUTES.has(route)) {
violations.push(`unsupported route: ${route}`);
}
if (operation === 'update_order') {
if (!nonEmptyString(task.identifier)) violations.push('identifier is required for update_order');
if (!isObject(task.updatePlan)) violations.push('updatePlan must be an object for update_order');
if (isObject(task.updatePlan) && !Array.isArray(task.updatePlan.actions)) {
violations.push('updatePlan.actions must be an array for update_order');
}
}
if (operation === 'export_confirmation') {
if (!nonEmptyString(task.identifier)) violations.push('identifier is required for export_confirmation');
if (!Array.isArray(task.exportTypes) || !task.exportTypes.length) {
violations.push('exportTypes must be a non-empty array for export_confirmation');
} else if (task.exportTypes.some((type) => !EXPORT_TYPES.has(type))) {
violations.push('exportTypes contains an unsupported artifact type');
}
if (route === 'split_parent') violations.push('split_parent cannot export confirmation files');
}
return violations;
}
function validateTaskEnvelope(input) {
if (!isObject(input)) {
return {
valid: false,
executable: false,
status: 'invalid_task',
violations: ['task envelope must be an object'],
};
}
if (input.status && input.status !== 'ready') {
return {
valid: false,
executable: false,
status: input.status,
envelope: input,
task: input.task || null,
violations: [],
};
}
if (!isObject(input.task)) {
return {
valid: false,
executable: false,
status: 'invalid_task',
envelope: input,
task: null,
violations: ['ready task envelope must contain task object'],
};
}
const envelope = normalizeTaskEnvelope(input);
const violations = validateTaskShape(envelope.task, envelope);
if (violations.length) {
return {
valid: false,
executable: false,
status: 'invalid_task',
envelope,
task: envelope.task,
violations,
};
}
return {
valid: true,
executable: true,
status: 'ready',
envelope,
task: envelope.task,
violations: [],
};
}
module.exports = {
TASK_SCHEMA_VERSION,
KNOWN_OPERATIONS,
KNOWN_ROUTES,
EXPORT_TYPES,
normalizeExportTypes,
normalizeTaskEnvelope,
validateTaskEnvelope,
validateTaskShape,
};