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, };