import { CallExpressionShape, Plugin, DurationShape } from '@servicenow/sdk-build-core' import { NowIdShape } from './now-id-plugin' import { toReference } from './utils' import { validateUserSpecifiedDuration, validateRelativeDuration, validateRetroactiveStart, validateRetroactiveFieldsWithoutStart, validateNoScheduleRestrictions, validateTaskFieldSchedule, validateSlaDefinitionSchedule, validateTimezoneSource, validateNoMatchResume, validateCancelCondition, DEFAULT_WHEN_TO_RESUME, DEFAULT_WHEN_TO_CANCEL, DEFAULT_TIMEZONE_SOURCE, DEFAULT_RETROACTIVE_PAUSE, DEFAULT_NO_SCHEDULE_ID, } from './sla/sla-validators' const DEFAULT_SLA_FLOW = '828f267973333300e289235f04f6a7a3' const DEFAULT_COLLECTION = 'incident' const DEFAULT_TYPE = 'SLA' const DEFAULT_ACTIVE = true const DEFAULT_ADV_CONDITION_TYPE = 'none' const DEFAULT_SCHEDULE_SOURCE = 'sla_definition' const DEFAULT_RELATIVE_DURATION_WORKS_ON = 'Task record' const DEFAULT_RESET_ACTION = 'cancel' const DEFAULT_RETROACTIVE = false const DEFAULT_ENABLE_LOGGING = false const DEFAULT_SYS_DOMAIN_PATH = '/' const DEFAULT_DURATION_ON_PLATFORM = '1970-01-01 00:00:00' /** * See ./sla/sla-validators.ts for validation behavior documentation. */ export const SLAPlugin = Plugin.create({ name: 'SLAPlugin', records: { contract_sla: { async toShape(record) { return { success: true, value: new CallExpressionShape({ source: record, callee: 'Sla', args: [ record.transform(({ $ }) => ({ $id: $.val(NowIdShape.from(record)), name: $.map((v) => v.ifString()?.ifNotEmpty()?.getValue()), table: $.from('collection'), duration: $.from('duration').map((v) => { const stringShape = v.ifString()?.ifNotEmpty() if (!stringShape) { return DurationShape.zero(record) } const value = stringShape.getValue() if (value === DEFAULT_DURATION_ON_PLATFORM) { return DurationShape.zero(record) } return DurationShape.from(record, stringShape) }), // Return empty string when scheduleSource is 'no_schedule' schedule: $.from('schedule', 'schedule_source').map((schedule, schedule_source) => { const scheduleSourceValue = schedule_source.ifString()?.getValue() if (scheduleSourceValue === 'no_schedule') { return '' } return schedule.ifString()?.ifNotEmpty()?.getValue() ?? '' }), type: $.def(DEFAULT_TYPE), active: $.toBoolean().def(DEFAULT_ACTIVE), advancedConditionType: $.from('adv_condition_type').def(DEFAULT_ADV_CONDITION_TYPE), // Duration & Schedule durationType: $.from('duration_type').def(''), scheduleSource: $.from('schedule_source').def(DEFAULT_SCHEDULE_SOURCE), scheduleSourceField: $.from('schedule_source_field').def(''), relativeDurationWorksOn: $.from('relative_duration_works_on').def( DEFAULT_RELATIVE_DURATION_WORKS_ON ), // Conditions conditions: $.from( 'start_condition', 'stop_condition', 'pause_condition', 'resume_condition', 'reset_condition', 'cancel_condition' ).map( ( start_condition, stop_condition, pause_condition, resume_condition, reset_condition, cancel_condition ) => { const conditions: Record = {} const startStr = start_condition.ifString()?.ifNotEmpty()?.getValue() if (startStr) { conditions['start'] = startStr } const stopStr = stop_condition.ifString()?.ifNotEmpty()?.getValue() if (stopStr) { conditions['stop'] = stopStr } const pauseStr = pause_condition.ifString()?.ifNotEmpty()?.getValue() if (pauseStr) { conditions['pause'] = pauseStr } const resumeStr = resume_condition.ifString()?.ifNotEmpty()?.getValue() if (resumeStr) { conditions['resume'] = resumeStr } const resetStr = reset_condition.ifString()?.ifNotEmpty()?.getValue() if (resetStr) { conditions['reset'] = resetStr } const cancelStr = cancel_condition.ifString()?.ifNotEmpty()?.getValue() if (cancelStr) { conditions['cancel'] = cancelStr } return Object.keys(conditions).length > 0 ? conditions : undefined } ), // Condition Behavior conditionType: $.from('condition_class').def(''), resetAction: $.from('reset_action').def(DEFAULT_RESET_ACTION), whenTo: $.from('when_to_resume', 'when_to_cancel').map( (when_to_resume, when_to_cancel) => { const resumeValue = when_to_resume.ifString()?.getValue() const cancelValue = when_to_cancel.ifString()?.getValue() return { resume: resumeValue || DEFAULT_WHEN_TO_RESUME, cancel: cancelValue || DEFAULT_WHEN_TO_CANCEL, } } ), // Retroactive retroactive: $.from('retroactive', 'retroactive_pause', 'set_start_to').map( (retroactive, retroactive_pause, set_start_to) => { const startValue = retroactive.ifBoolean()?.getValue() ?? DEFAULT_RETROACTIVE const pauseValue = retroactive_pause.ifBoolean()?.getValue() const setStartToValue = set_start_to.ifString()?.ifNotEmpty()?.getValue() const result: { start: boolean; pause?: boolean; setStartTo?: string } = { start: startValue, } // Only include pause when start is true (per Behavior 6/7) if (startValue) { result.pause = pauseValue ?? DEFAULT_RETROACTIVE_PAUSE } // Only include setStartTo if it has a valid value if (setStartToValue) { result.setStartTo = setStartToValue } return result } ), // References overrides: $.from('sys_overrides').def(''), workflow: $.def(''), flow: $.def(''), vendor: $.def(''), // Time Zones timezone: $.def(''), timezoneSource: $.from('timezone_source').def(DEFAULT_TIMEZONE_SOURCE), // Metadata enableLogging: $.from('enable_logging').toBoolean().def(DEFAULT_ENABLE_LOGGING), target: $.def(''), // Domain domainPath: $.from('sys_domain_path').def(DEFAULT_SYS_DOMAIN_PATH), })), ], }), } }, }, }, shapes: [ { shape: CallExpressionShape, fileTypes: ['fluent'], async toRecord(callExpression, { factory, diagnostics }) { if (callExpression.getCallee() !== 'Sla') { return { success: false } } const config = callExpression.getArgument(0).asObject() // Extract Control Fields const durationType = config.get('durationType') const durationTypeValue = durationType && !durationType.isUndefined() ? toReference(durationType) : '' const isUserSpecifiedOrEmptyDuration = !durationTypeValue const scheduleSource = config.get('scheduleSource') const scheduleSourceValue = scheduleSource?.ifString()?.getValue() || DEFAULT_SCHEDULE_SOURCE const retroactive = config.get('retroactive')?.ifDefined()?.asObject() const retroactiveStartValue = retroactive?.get('start')?.ifBoolean()?.getValue() || false const whenTo = config.get('whenTo')?.ifDefined()?.asObject() const whenToResumeValue = whenTo?.get('resume')?.ifString()?.getValue() || DEFAULT_WHEN_TO_RESUME const whenToCancelValue = whenTo?.get('cancel')?.ifString()?.getValue() || DEFAULT_WHEN_TO_CANCEL const timezoneSource = config.get('timezoneSource') const timezoneSourceValue = timezoneSource?.ifString()?.getValue() || DEFAULT_TIMEZONE_SOURCE // Extract conditions object const conditions = config.get('conditions').ifDefined()?.asObject() if (isUserSpecifiedOrEmptyDuration) { // Behavior 1: User specified duration // Validate duration field uses Duration() helper (only when duration is expected) const duration = config.get('duration') if (duration && duration.isDefined()) { if (!(duration instanceof CallExpressionShape) || duration.getCallee() !== 'Duration') { diagnostics.error(duration, "SLA 'duration' field must use the Duration() helper function") } } validateUserSpecifiedDuration(config, diagnostics) } else { // Behavior 2: Relative duration validateRelativeDuration(config, conditions, diagnostics) } // Schedule Source Validations if (scheduleSourceValue === 'no_schedule') { // Behavior 5: No schedule validateNoScheduleRestrictions(config, diagnostics) } else if (scheduleSourceValue === 'task_field') { // Behavior 6: Task field schedule validateTaskFieldSchedule(config, diagnostics) } else if (scheduleSourceValue === 'sla_definition') { // Behavior 7: SLA definition schedule (default) validateSlaDefinitionSchedule(config, diagnostics) } // Condition Field Validations // Behavior 8: Timezone source validateTimezoneSource(config, timezoneSourceValue, diagnostics) // Behavior 9: When to resume = "no_match" if (whenToResumeValue === 'no_match') { validateNoMatchResume(conditions, diagnostics) } // Behavior 10: When to cancel validation validateCancelCondition(whenToCancelValue, conditions, diagnostics) // Retroactive Validations if (retroactiveStartValue && retroactive) { // Behaviors 6 & 8: Retroactive start validations validateRetroactiveStart(retroactive, isUserSpecifiedOrEmptyDuration, diagnostics) } else { // Behavior 7: Validate that retroactive-dependent fields are not set when retroactive.start is false validateRetroactiveFieldsWithoutStart(retroactive, diagnostics) } return { success: true, value: await factory.createRecord({ source: callExpression, table: 'contract_sla', explicitId: config.get('$id'), properties: config.transform(({ $ }) => ({ // Core name: $.map((v) => v.ifString()?.ifNotEmpty()?.getValue()), collection: $.from('table').def(DEFAULT_COLLECTION), type: $.def(DEFAULT_TYPE), active: $.def(DEFAULT_ACTIVE), adv_condition_type: $.from('advancedConditionType').def(DEFAULT_ADV_CONDITION_TYPE), // Duration & Schedule duration: $.map((v) => v.toString()), duration_type: $.from('durationType').map((v) => toReference(v) || undefined), // Conditional schedule: force default when no_schedule schedule: $.map((v) => { if (scheduleSourceValue === 'no_schedule') { return DEFAULT_NO_SCHEDULE_ID } return toReference(v) || undefined }), schedule_source: $.from('scheduleSource').def(DEFAULT_SCHEDULE_SOURCE), schedule_source_field: $.from('scheduleSourceField'), relative_duration_works_on: $.from('relativeDurationWorksOn').def( DEFAULT_RELATIVE_DURATION_WORKS_ON ), // Conditions start_condition: $.val(conditions?.get('start')), reset_condition: $.val(conditions?.get('reset')), stop_condition: $.val(conditions?.get('stop')), cancel_condition: $.val(conditions?.get('cancel')), pause_condition: $.val(conditions?.get('pause')), resume_condition: $.val(conditions?.get('resume')), // Condition Behavior condition_class: $.from('conditionType').map((v) => toReference(v) || undefined), reset_action: $.from('resetAction').def(DEFAULT_RESET_ACTION), when_to_resume: $.val(whenTo?.get('resume')).def(DEFAULT_WHEN_TO_RESUME), when_to_cancel: $.val(whenTo?.get('cancel')).def(DEFAULT_WHEN_TO_CANCEL), // Retroactive retroactive: $.val(retroactive?.get('start')).def(DEFAULT_RETROACTIVE), retroactive_pause: $.map(() => { const value = retroactive?.get('pause')?.ifBoolean()?.getValue() if (value !== undefined) { return value } return isUserSpecifiedOrEmptyDuration ? DEFAULT_RETROACTIVE_PAUSE : undefined }), set_start_to: $.val(retroactive?.get('setStartTo')), // References sys_overrides: $.from('overrides'), workflow: $, flow: $.from('flow').def(DEFAULT_SLA_FLOW), vendor: $, // Time Zones timezone: $, timezone_source: $.from('timezoneSource').def(DEFAULT_TIMEZONE_SOURCE), // Metadata enable_logging: $.from('enableLogging').def(DEFAULT_ENABLE_LOGGING), target: $, // Domain sys_domain_path: $.from('domainPath').def(DEFAULT_SYS_DOMAIN_PATH), })), }), } }, }, ], })