196 lines
5.5 KiB
JavaScript
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,
|
|
};
|