config-generator.js 12.2 KB
/**
 * 计划书配置生成器
 *
 * @description 将提取的数据转换为 plan-templates.js 的配置格式
 * @file utils/parsers/config-generator
 * @author Claude Code
 * @created 2026-02-13
 */

import { generateFormSn, validateConfig } from './ai-extractor'

/**
 * 生成完整的配置代码
 *
 * @description 将提取的配置数据转换为可写入 plan-templates.js 的代码
 * @param {Object} extractedConfig - 从文档提取的配置
 * @param {Object} options - 生成选项
 * @param {string} options.formSn - 自定义 form_sn(可选)
 * @param {boolean} options.includeComment - 是否包含注释(默认 true)
 * @returns {Object} { formSn: string, configCode: string, insertPosition: string }
 *
 * @example
 * const result = generateConfigCode({
 *   product_name: '宏挚传承保障计划',
 *   product_type: 'savings',
 *   ...
 * })
 * // 返回: { formSn: 'savings-xxx', configCode: '...', insertPosition: '...' }
 */
export function generateConfigCode(extractedConfig, options = {}) {
  const { formSn: customFormSn, includeComment = true } = options

  // 验证配置
  const validation = validateConfig(extractedConfig)
  if (!validation.valid) {
    throw new Error(`配置验证失败:\n${validation.errors.join('\n')}`)
  }

  // 生成 form_sn
  const formSn = customFormSn || generateFormSn(
    extractedConfig.product_name,
    extractedConfig.product_type
  )

  // 根据产品类型生成配置
  let configCode = ''

  if (extractedConfig.is_savings) {
    configCode = generateSavingsConfig(extractedConfig, formSn, includeComment)
  } else if (extractedConfig.product_type === 'life-insurance') {
    configCode = generateLifeInsuranceConfig(extractedConfig, formSn, includeComment)
  } else if (extractedConfig.product_type === 'critical-illness') {
    configCode = generateCriticalIllnessConfig(extractedConfig, formSn, includeComment)
  } else {
    throw new Error(`不支持的产品类型: ${extractedConfig.product_type}`)
  }

  // 生成插入位置提示
  const insertPosition = generateInsertPositionHint(extractedConfig)

  return {
    formSn,
    configCode,
    insertPosition,
    validation
  }
}

/**
 * 生成储蓄型产品配置
 *
 * @private
 */
function generateSavingsConfig(config, formSn, includeComment) {
  const comment = includeComment ? `
  // ${config.product_name}
  // form_sn: ${formSn}
  // 提取方式: ${config.withdrawal_modes.join(', ')}
  // 提取周期: ${config.withdrawal_periods.join(', ')}` : ''

  const paymentPeriodsArray = JSON.stringify(config.payment_periods, null, 2)
  const withdrawalModesArray = JSON.stringify(config.withdrawal_modes, null, 2)
  const withdrawalPeriodsArray = JSON.stringify(config.withdrawal_periods, null, 2)
  const { form_schema_ref, submit_mapping_ref } = resolveSchemaRefs(config)
  const form_schema_code = buildSchemaCode(config.form_schema, form_schema_ref)
  const submit_mapping_code = buildSchemaCode(config.submit_mapping, submit_mapping_ref)

  return `  '${formSn}': {${comment}
    name: '${config.product_name}',
    component: 'SavingsTemplate',
    category: 'savings',
    config: {
      currency: '${config.currency}',
      payment_periods: ${paymentPeriodsArray},
      age_range: { min: ${config.age_range.min}, max: ${config.age_range.max} },
      insurance_period: '${config.insurance_period}',
      withdrawal_plan: {
        enabled: true,
        currencies: ['HKD', 'USD', 'CNY'],
        default_currency: '${config.currency}',
        withdrawal_modes: ${withdrawalModesArray},
        withdrawal_periods: ${withdrawalPeriodsArray}
      },
      form_schema: ${form_schema_code},
      submit_mapping: ${submit_mapping_code}
    }
  }`
}

/**
 * 生成人寿保险产品配置
 *
 * @private
 */
function generateLifeInsuranceConfig(config, formSn, includeComment) {
  const comment = includeComment ? `
  // ${config.product_name}
  // form_sn: ${formSn}` : ''

  const paymentPeriodsArray = JSON.stringify(config.payment_periods, null, 2)
  const { form_schema_ref, submit_mapping_ref } = resolveSchemaRefs(config)
  const form_schema_code = buildSchemaCode(config.form_schema, form_schema_ref)
  const submit_mapping_code = buildSchemaCode(config.submit_mapping, submit_mapping_ref)

  return `  '${formSn}': {${comment}
    name: '${config.product_name}',
    component: 'LifeInsuranceTemplate',
    config: {
      currency: '${config.currency}',
      payment_periods: ${paymentPeriodsArray},
      age_range: { min: ${config.age_range.min}, max: ${config.age_range.max} },
      insurance_period: '${config.insurance_period}',
      form_schema: ${form_schema_code},
      submit_mapping: ${submit_mapping_code}
    }
  }`
}

/**
 * 生成重疾保险产品配置
 *
 * @private
 */
function generateCriticalIllnessConfig(config, formSn, includeComment) {
  const comment = includeComment ? `
  // ${config.product_name}
  // form_sn: ${formSn}` : ''

  const paymentPeriodsArray = JSON.stringify(config.payment_periods, null, 2)
  const { form_schema_ref, submit_mapping_ref } = resolveSchemaRefs(config)
  const form_schema_code = buildSchemaCode(config.form_schema, form_schema_ref)
  const submit_mapping_code = buildSchemaCode(config.submit_mapping, submit_mapping_ref)

  return `  '${formSn}': {${comment}
    name: '${config.product_name}',
    component: 'CriticalIllnessTemplate',
    config: {
      currency: '${config.currency}',
      payment_periods: ${paymentPeriodsArray},
      age_range: { min: ${config.age_range.min}, max: ${config.age_range.max} },
      insurance_period: '${config.insurance_period}',
      form_schema: ${form_schema_code},
      submit_mapping: ${submit_mapping_code}
    }
  }`
}

/**
 * 生成插入位置提示
 *
 * @private
 * @param {Object} config - 配置对象
 * @returns {string} 插入位置说明
 */
function generateInsertPositionHint(config) {
  if (config.is_savings) {
    return '// 插入到 PLAN_TEMPLATES 对象中的储蓄型产品部分(搜索 "// ====== 储蓄型产品")'
  } else if (config.product_type === 'life-insurance') {
    return '// 插入到 PLAN_TEMPLATES 对象中的人寿保险部分(搜索 "// 人寿保险产品")'
  } else if (config.product_type === 'critical-illness') {
    return '// 插入到 PLAN_TEMPLATES 对象中的重疾保险部分(搜索 "// 重疾保险产品")'
  }
  return '// 插入到 PLAN_TEMPLATES 对象中'
}

/**
 * 生成完整的导出代码(包含导入和导出)
 *
 * @param {Object} config - 提取的配置
 * @param {Object} options - 选项
 * @returns {string} 完整的模块代码
 */
export function generateFullModuleCode(config, options = {}) {
  const { configCode, formSn } = generateConfigCode(config, options)

  return `/**
 * 新增产品配置
 * @description ${config.product_name}
 * @created ${new Date().toISOString()}
 */

// 在 PLAN_TEMPLATES 中添加以下配置:
${configCode}

/**
 * 如果需要导出,在文件底部的 export 中添加:
 */
export const ${toCamelCase(formSn)} = PLAN_TEMPLATES['${formSn}']
`
}

/**
 * 转换为驼峰命名
 *
 * @private
 */
function toCamelCase(str) {
  return str
    .replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : '')
    .replace(/^(.)/, (c) => c.toLowerCase())
}

function resolveSchemaRefs(config) {
  const isSavings = config?.is_savings || config?.product_type === 'savings'
  if (isSavings) {
    return {
      form_schema_ref: 'savingsFormSchema',
      submit_mapping_ref: 'savingsSubmitMapping'
    }
  }
  return {
    form_schema_ref: 'protectionFormSchema',
    submit_mapping_ref: 'baseSubmitMapping'
  }
}

/**
 * 构建表单 Schema 代码
 *
 * @description 将 schema 对象转换为代码字符串
 * @param {Object|string} value - schema 值
 * @param {string} fallbackRef - 回退引用名
 * @returns {string} 代码字符串
 *
 * @updated 2026-02-15 - 支持新的条件格式 show_when: { field, op, value }
 */
function buildSchemaCode(value, fallbackRef) {
  if (!value || isEmptyObject(value)) {
    return fallbackRef
  }
  if (value && typeof value === 'object' && !Array.isArray(value)) {
    const baseFields = value.base_fields
    const withdrawalFields = value.withdrawal_fields
    const baseFieldsEmpty = Array.isArray(baseFields) && baseFields.length === 0
    const withdrawalFieldsEmpty = !Array.isArray(withdrawalFields) || withdrawalFields.length === 0
    if (baseFieldsEmpty && withdrawalFieldsEmpty) {
      return fallbackRef
    }
    // 处理字段中的 show_when 格式转换
    const processedValue = processSchemaFields(value)
    return JSON.stringify(processedValue, null, 2).replace(/\n/g, '\n      ')
  }
  if (typeof value === 'string') {
    return value
  }
  return JSON.stringify(value, null, 2).replace(/\n/g, '\n      ')
}

/**
 * 处理 Schema 字段,转换条件格式
 *
 * @private
 */
function processSchemaFields(schema) {
  const result = {}
  for (const [key, value] of Object.entries(schema)) {
    if (key === 'base_fields' || key === 'withdrawal_fields') {
      result[key] = value.map(field => processFieldCondition(field))
    } else if (key !== 'reset_map') {
      // 忽略 reset_map,不再生成
      result[key] = value
    }
  }
  return result
}

/**
 * 处理字段条件,转换为新格式
 *
 * @private
 */
function processFieldCondition(field) {
  const result = { ...field }
  // 转换 show_when: { field, equals } -> { field, op: 'eq', value }
  if (result.show_when && result.show_when.equals !== undefined) {
    result.show_when = {
      field: result.show_when.field,
      op: 'eq',
      value: result.show_when.equals
    }
  }
  return result
}

function isEmptyObject(value) {
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
    return false
  }
  return Object.keys(value).length === 0
}

/**
 * 批量生成配置代码
 *
 * @param {Array} configs - 配置数组
 * @returns {string} 批量配置代码
 */
export function generateBatchConfigCode(configs) {
  const result = configs.map((config, index) => {
    const { configCode } = generateConfigCode(config, {
      formSn: `batch-product-${Date.now()}-${index}`,
      includeComment: true
    })
    return configCode
  })

  return result.join(',\n\n')
}

/**
 * 预览配置数据(用于确认)
 *
 * @param {Object} config - 配置对象
 * @returns {Object} 格式化的预览数据
 */
export function previewConfig(config) {
  return {
    '产品名称': config.product_name,
    '产品类型': getProductTypeLabel(config.product_type),
    '币种': config.currency,
    '缴费年期': config.payment_periods?.join('、') || '-',
    '年龄范围': `${config.age_range?.min || 0} - ${config.age_range?.max || 75} `,
    '保险期间': config.insurance_period,
    '是否储蓄型': config.is_savings ? '是' : '否',
    '提取方式': config.withdrawal_modes?.join('、') || '-',
    '提取周期': config.withdrawal_periods?.join('、') || '-'
  }
}

/**
 * 获取产品类型标签
 *
 * @private
 */
function getProductTypeLabel(type) {
  const labels = {
    'life-insurance': '人寿保险',
    'critical-illness': '重疾保险',
    'savings': '储蓄型'
  }
  return labels[type] || type
}

/**
 * 生成差异对比(新配置 vs 现有配置)
 *
 * @param {Object} newConfig - 新配置
 * @param {Object} existingConfig - 现有配置
 * @returns {Object} 差异对象
 */
export function compareConfigs(newConfig, existingConfig) {
  const differences = {
    added: [],
    removed: [],
    changed: []
  }

  // 比较缴费年期
  if (newConfig.payment_periods && existingConfig.payment_periods) {
    const newPeriods = newConfig.payment_periods.filter(p => !existingConfig.payment_periods.includes(p))
    const removedPeriods = existingConfig.payment_periods.filter(p => !newConfig.payment_periods.includes(p))

    if (newPeriods.length > 0) differences.changed.push(`新增缴费年期: ${newPeriods.join(', ')}`)
    if (removedPeriods.length > 0) differences.changed.push(`移除缴费年期: ${removedPeriods.join(', ')}`)
  }

  // 比较年龄范围
  if (newConfig.age_range && existingConfig.age_range) {
    if (newConfig.age_range.min !== existingConfig.age_range.min ||
        newConfig.age_range.max !== existingConfig.age_range.max) {
      differences.changed.push(`年龄范围: ${existingConfig.age_range.min}-${existingConfig.age_range.max}${newConfig.age_range.min}-${newConfig.age_range.max}`)
    }
  }

  return differences
}