import { CallExpressionShape, Plugin, type Record, VariableStatementShape, IdentifierShape, } from '@servicenow/sdk-build-core' import { buildVariableRecords } from './shape-to-record' import { toValidIdentifier, shouldWriteAsCallExpression, defaultScript, defaultpostInsertScript, defaultSaveScript, validateRequestedForVariableConflict, validateVariableNameConflicts, getRedirectUrlFromDb, getRedirectUrlToDb, createScript, } from './utils' import { buildVariablesSchema, CatalogItemBaseRelationships, CatalogItemBaseCoalesce, transformCatalogBaseFieldsToShape, transformCatalogItemBaseFieldsToRecord, createSharedM2MRecords, } from './service-catalog-base' import { validateFulfillmentProcessExclusivity, validateCategoriesRequireCatalogs } from './service-catalog-diagnostics' import { ModuleFunctionShape } from '../server-module-plugin' export const CatalogItemRecordProducerPlugin = Plugin.create({ name: 'CatalogItemRecordProducerPlugin', records: { sc_cat_item_producer: { relationships: CatalogItemBaseRelationships, async toShape(record, { descendants, transform, logger }) { const variablesSchema = buildVariablesSchema(descendants, logger) const script = await createScript(record, record.get('script'), transform, 'script') const postInsertScript = await createScript( record, record.get('post_insert_script'), transform, 'post-insert-script' ) const saveScript = await createScript(record, record.get('save_script'), transform, 'save-script') const callExpression = new CallExpressionShape({ source: record, callee: 'CatalogItemRecordProducer', args: [ record.transform(({ $ }) => ({ ...transformCatalogBaseFieldsToShape(record, $, descendants), table: $.from('table_name'), script: $.val(script).def(defaultScript), postInsertScript: $.val(postInsertScript).def(defaultpostInsertScript), saveScript: $.val(saveScript).def(defaultSaveScript), redirectUrl: $.from('redirect_url') .map((v) => getRedirectUrlFromDb(v.toString().getValue())) .def('generatedRecord'), view: $.def(''), allowEdit: $.from('allow_edit').toBoolean().def(false), canCancel: $.from('can_cancel').toBoolean().def(false), saveOptions: $.from('save_options').def(''), variables: $.val(variablesSchema).def({}), })), ], }) return { success: true, value: shouldWriteAsCallExpression(record) ? callExpression : new VariableStatementShape({ source: record, isExported: true, variableName: new IdentifierShape({ source: record, name: toValidIdentifier(record.get('name').asString().getValue()), }), initializer: callExpression, }), } }, }, ...CatalogItemBaseCoalesce, }, shapes: [ { shape: CallExpressionShape, fileTypes: ['fluent'], async toRecord(callExpression, { diagnostics, factory }) { if (callExpression.getCallee() !== 'CatalogItemRecordProducer') { return { success: false } } const arg = callExpression.getArgument(0).asObject() // Validate mutually exclusive fulfillment process fields (executionPlan/flow/workflow) validateFulfillmentProcessExclusivity(arg, diagnostics) // Validate categories require catalogs validateCategoriesRequireCatalogs(arg, diagnostics) const recordProducer = await factory.createRecord({ source: callExpression, table: 'sc_cat_item_producer', explicitId: arg.get('$id'), properties: arg.transform(({ $ }) => ({ ...transformCatalogItemBaseFieldsToRecord($), table_name: $.from('table'), script: $.from('script') .map( (v) => v .if(ModuleFunctionShape) ?.toString((n: string) => `${n}({{PARAMS}})`, ['producer', 'current']) ?? v ) .toCdata() .def(defaultScript), post_insert_script: $.from('postInsertScript') .map( (v) => v .if(ModuleFunctionShape) ?.toString( (n: string) => `${n}({{PARAMS}})`, ['producer', 'current', 'cat_item'] ) ?? v ) .toCdata() .def(defaultpostInsertScript), save_script: $.from('saveScript') .map( (v) => v .if(ModuleFunctionShape) ?.toString( (n: string) => `${n}({{PARAMS}})`, ['producer', 'current', 'cat_item'] ) ?? v ) .toCdata() .def(defaultSaveScript), redirect_url: $.from('redirectUrl') .map((v) => getRedirectUrlToDb(v.toString().getValue())) .def('generated_record'), view: $.def(''), allow_edit: $.from('allowEdit').def(false), can_cancel: $.from('canCancel').def(false), save_options: $.from('saveOptions').def(''), })), }) let variableRecords: Record[] = [] // Validate all RequestedForVariable constraints (direct variables + variable sets) in a single optimized pass if (!validateRequestedForVariableConflict(arg, diagnostics, 'RecordProducer')) { return { success: false } } // Validate that variable names don't conflict between direct variables and variable sets if (!validateVariableNameConflicts(arg, diagnostics, 'RecordProducer')) { return { success: false } } const m2mRecords = await createSharedM2MRecords(callExpression, arg, recordProducer, factory) if (arg.get('variables').isDefined()) { const variablesConfig = arg.get('variables').asObject() variableRecords = await buildVariableRecords({ variablesConfig, factory, parent: recordProducer, parentArg: arg, diagnostics, }) const recordProducerTable = arg.get('table').getValue() variableRecords = variableRecords.map((variableRecord) => { return variableRecord.merge({ record_producer_table: recordProducerTable, }) }) } return { success: true, value: recordProducer.with( ...m2mRecords.catalogsRecords, ...m2mRecords.categoriesRecords, ...m2mRecords.variableSetRecords, ...m2mRecords.availableForRecords, ...m2mRecords.notAvailableForRecords, ...m2mRecords.assignedTopicsRecords, ...variableRecords ), } }, }, ], })