import { CallExpressionShape, Plugin, type Record, VariableStatementShape, IdentifierShape, } from '@servicenow/sdk-build-core' import { buildVariableRecords } from './shape-to-record' import { toValidIdentifier, shouldWriteAsCallExpression, validateRequestedForVariableConflict, validateVariableNameConflicts, createScript, } from './utils' import { validateFulfillmentProcessExclusivity, validateCategoriesRequireCatalogs } from './service-catalog-diagnostics' import { buildVariablesSchema, CatalogItemBaseRelationships, CatalogItemBaseCoalesce, transformCatalogBaseFieldsToShape, transformCatalogItemBaseFieldsToRecord, transformCatalogItemSpecificFieldsToShape, transformCatalogItemSpecificFieldsToRecord, createSharedM2MRecords, } from './service-catalog-base' export const CatalogItemPlugin = Plugin.create({ name: 'CatalogItemPlugin', records: { sc_cat_item: { relationships: CatalogItemBaseRelationships, async toShape(record, { descendants, transform, logger }) { const variablesSchema = buildVariablesSchema(descendants, logger) const deliveryPlanScript = await createScript( record, record.get('delivery_plan_script'), transform, 'delivery-plan-script' ) const entitlementScript = await createScript( record, record.get('entitlement_script'), transform, 'entitlement-script' ) const callExpression = new CallExpressionShape({ source: record, callee: 'CatalogItem', args: [ record.transform(({ $ }) => ({ ...transformCatalogBaseFieldsToShape(record, $, descendants), ...transformCatalogItemSpecificFieldsToShape(record, $, descendants), entitlementScript: $.val(entitlementScript).def(''), deliveryPlanScript: $.val(deliveryPlanScript).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').toString().getValue()), }), initializer: callExpression, }), } }, }, ...CatalogItemBaseCoalesce, }, shapes: [ { shape: CallExpressionShape, fileTypes: ['fluent'], async toRecord(callExpression, { diagnostics, factory }) { if (callExpression.getCallee() !== 'CatalogItem') { 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) // Validate all RequestedForVariable constraints (direct variables + variable sets) in a single optimized pass if (!validateRequestedForVariableConflict(arg, diagnostics, 'CatalogItem')) { return { success: false } } // Validate that variable names don't conflict between direct variables and variable sets if (!validateVariableNameConflicts(arg, diagnostics, 'CatalogItem')) { return { success: false } } const catItem = await factory.createRecord({ source: callExpression, table: 'sc_cat_item', explicitId: arg.get('$id'), properties: arg.transform(({ $ }) => ({ ...transformCatalogItemBaseFieldsToRecord($), ...transformCatalogItemSpecificFieldsToRecord(arg, $), })), }) const m2mRecords = await createSharedM2MRecords(callExpression, arg, catItem, factory) let variableRecords: Record[] = [] if (arg.get('variables').isDefined()) { const variablesConfig = arg.get('variables').asObject() variableRecords = await buildVariableRecords({ variablesConfig, factory, parent: catItem, diagnostics, parentArg: arg, }) } const pricingDetails = arg.get('pricingDetails').ifArray()?.getElements() ?? [] const pricingDetailsRecords: Record[] = [] for (const pricingDetail of pricingDetails) { const pricingDetailObj = pricingDetail.asObject() pricingDetailsRecords.push( await factory.createRecord({ source: callExpression, table: 'fx_price', properties: { id: catItem, amount: pricingDetailObj.get('amount')?.getValue(), currency: pricingDetailObj.get('currencyType')?.getValue(), type: 'calculated', field: pricingDetailObj.get('field')?.getValue(), table: 'sc_cat_item', }, }) ) } return { success: true, value: catItem.with( ...m2mRecords.catalogsRecords, ...m2mRecords.categoriesRecords, ...m2mRecords.variableSetRecords, ...m2mRecords.availableForRecords, ...m2mRecords.notAvailableForRecords, ...m2mRecords.assignedTopicsRecords, ...pricingDetailsRecords, ...variableRecords ), } }, }, ], })