import { normalizeOptionalSafeInteger } from './request-normalizers.js' import { createRequestError } from './request-client.js' import { LINEAGE_PERSON_OPTIONS } from './lineage-person-options.js' import { normalizeBusinessOptionProjection } from './business-dictionary-contract.js' const optionLabels = (options) => Object.freeze( Object.fromEntries(options.map(({ value, label }) => [value, label])) ) const lineagePersonError = (message) => createRequestError(message, 'LINEAGE_PERSON_RESPONSE_INVALID') export const normalizeLineagePersonIdentity = (value, label) => { if (typeof value === 'string' && /^[1-9]\d*$/.test(value)) return value if (typeof value === 'number' && Number.isSafeInteger(value) && value > 0) return String(value) throw lineagePersonError(`成员详情${label}无效`) } const normalizeOptionalLineagePersonIdentity = (value, label) => { if (value === undefined || value === null || value === '') return '' return normalizeLineagePersonIdentity(value, label) } export const normalizeLineagePersonText = (value, label, { required = false } = {}) => { if (value === undefined || value === null) { if (required) throw lineagePersonError(`成员详情缺少${label}`) return '' } if (typeof value !== 'string') throw lineagePersonError(`成员详情${label}无效`) const normalized = value.trim() if (required && !normalized) throw lineagePersonError(`成员详情缺少${label}`) return normalized } const normalizeLineageSpouseNames = (value) => normalizeLineagePersonText(value, '配偶姓名') .replace(/\s*[((]P\d{15,}[))]/g, '') .trim() const LINEAGE_PERSON_SEX_LABELS = optionLabels(LINEAGE_PERSON_OPTIONS.sex) const LINEAGE_PERSON_STATUS_LABELS = optionLabels( LINEAGE_PERSON_OPTIONS.personStatus ) const LINEAGE_BOOLEAN_LABELS = optionLabels(LINEAGE_PERSON_OPTIONS.lunar) const lineageBindingModes = new Set( LINEAGE_PERSON_OPTIONS.bindingMode.map(({ value }) => value) ) const normalizeLineagePersonDictionaryLabel = (value, label, labels) => { const normalized = normalizeLineagePersonText(value, label) if (!normalized) return '' const display = labels[normalized] if (!display) throw lineagePersonError(`成员详情${label}字典值无效`) return display } export const normalizeLineagePersonDate = (value, label) => { const normalized = normalizeLineagePersonText(value, label) if (!normalized) return '' if (!/^\d{4}-\d{2}-\d{2}(?:[T\s].*)?$/.test(normalized)) { throw lineagePersonError(`成员详情${label}无效`) } const datePart = normalized.slice(0, 10) const parsed = new Date(`${datePart}T00:00:00Z`) if (Number.isNaN(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== datePart) { throw lineagePersonError(`成员详情${label}无效`) } return datePart } export const normalizeLineagePersonDetail = (value, expectedGenealogyId, expectedPersonId) => { if (!value || typeof value !== 'object' || Array.isArray(value)) { throw lineagePersonError('成员详情响应不是对象') } const genealogyId = normalizeLineagePersonIdentity(value.genealogyId, '家谱标识') const id = normalizeLineagePersonIdentity(value.personId, '人物标识') if (genealogyId !== expectedGenealogyId || id !== expectedPersonId) { throw lineagePersonError('成员详情响应标识与请求不匹配') } if (!Number.isSafeInteger(value.generation) || value.generation < 1) { throw lineagePersonError('成员详情世代无效') } const name = normalizeLineagePersonText(value.name, '姓名', { required: true }) const appUserId = normalizeOptionalLineagePersonIdentity(value.appUserId, '绑定用户标识') const bindingMode = value.bindingMode === undefined || value.bindingMode === null || value.bindingMode === '' ? (appUserId ? 'SPECIFIED' : 'NONE') : normalizeLineagePersonText(value.bindingMode, '身份认领方式') if (!lineageBindingModes.has(bindingMode)) { throw lineagePersonError('成员详情身份认领方式无效') } const personNo = normalizeLineagePersonText(value.personNo, '人物编号') const generationName = normalizeLineagePersonText(value.generationName, '字辈') const courtesyName = normalizeLineagePersonText(value.courtesyName, '表字') const aliasName = normalizeLineagePersonText(value.aliasName, '别号或曾用名') const avatarOssId = normalizeOptionalLineagePersonIdentity(value.avatarOssId, '头像文件标识') const birthDate = normalizeLineagePersonDate(value.birthDate, '出生日期') const deathDate = normalizeLineagePersonDate(value.deathDate, '逝世日期') const birthLunar = normalizeLineagePersonText(value.birthLunar, '出生农历') const deathLunar = normalizeLineagePersonText(value.deathLunar, '逝世农历') const birthPlace = normalizeLineagePersonText(value.birthPlace, '出生地') const zodiacOption = normalizeBusinessOptionProjection( value.zodiacCode, value.zodiacLabel, value.zodiacOptionState, '成员生肖', 'LINEAGE_PERSON_RESPONSE_INVALID' ) const currentAddress = normalizeLineagePersonText(value.currentAddress, '现居地') const mobile = normalizeLineagePersonText(value.mobile, '联系电话') const email = normalizeLineagePersonText(value.email, '电子邮箱') const educationOption = normalizeBusinessOptionProjection( value.educationCode, value.educationLabel, value.educationOptionState, '成员学历分类', 'LINEAGE_PERSON_RESPONSE_INVALID' ) const occupation = normalizeLineagePersonText(value.occupation, '职业') const deathAge = value.deathAge === undefined || value.deathAge === null ? null : normalizeOptionalSafeInteger(value.deathAge, '人物享年') if (deathAge !== null && (deathAge < 0 || deathAge > 200)) { throw lineagePersonError('成员详情享年无效') } const deathPlace = normalizeLineagePersonText(value.deathPlace, '逝世地') const deathExpressionOption = normalizeBusinessOptionProjection( value.deathExpressionCode, value.deathExpressionLabel, value.deathExpressionOptionState, '成员逝世表述', 'LINEAGE_PERSON_RESPONSE_INVALID' ) const burialDate = normalizeLineagePersonDate(value.burialDate, '安葬日期') const burialPlace = normalizeLineagePersonText(value.burialPlace, '安葬地') const spouseNames = normalizeLineageSpouseNames(value.spouseNames) const personStatus = normalizeLineagePersonText(value.personStatus, '人物状态') const sex = normalizeLineagePersonText(value.sex, '性别') const sexLabel = normalizeLineagePersonDictionaryLabel(sex, '性别', LINEAGE_PERSON_SEX_LABELS) const personStatusLabel = normalizeLineagePersonDictionaryLabel(personStatus, '人物状态', LINEAGE_PERSON_STATUS_LABELS) const birthLunarLabel = normalizeLineagePersonDictionaryLabel(birthLunar, '出生农历', LINEAGE_BOOLEAN_LABELS) const deathLunarLabel = normalizeLineagePersonDictionaryLabel(deathLunar, '逝世农历', LINEAGE_BOOLEAN_LABELS) const biography = normalizeLineagePersonText(value.biography, '生平') const remark = normalizeLineagePersonText(value.remark, '备注') const canManageSensitiveMedicalHistory = value.canManageSensitiveMedicalHistory === true if (value.canManageSensitiveMedicalHistory !== undefined && typeof value.canManageSensitiveMedicalHistory !== 'boolean') { throw lineagePersonError('成员详情敏感病史权限无效') } const relationName = normalizeLineagePersonText(value.relationName, '关系显示名称') for (const field of ['canDisable', 'canCreateDocument', 'canManageDocuments']) { if (value[field] !== undefined && typeof value[field] !== 'boolean') { throw lineagePersonError(`成员详情权限字段 ${field} 无效`) } } const sortOrder = value.sortOrder === undefined || value.sortOrder === null ? null : normalizeOptionalSafeInteger(value.sortOrder, '人物排序值') const relatives = [] for (const relation of [ { id: value.fatherId, name: value.fatherName, label: '父亲' }, { id: value.motherId, name: value.motherName, label: '母亲' } ]) { if (relation.id === undefined || relation.id === null) continue const relativeId = normalizeLineagePersonIdentity(relation.id, `${relation.label}标识`) if (relativeId === id || relatives.some((relative) => relative.id === relativeId)) { throw lineagePersonError('成员详情亲属标识冲突') } relatives.push({ id: relativeId, name: normalizeLineagePersonText(relation.name, `${relation.label}姓名`) || relation.label, relation: relation.label }) } return { id, genealogyId, genealogyName: normalizeLineagePersonText(value.genealogyName, '家谱名称') || '当前家谱', name, appUserId, bindingMode, personNo, courtesyName, aliasName, generation: value.generation, generationName, avatarOssId, relation: value.generation === 1 ? '始祖' : '家谱成员', branch: generationName ? (generationName.endsWith('字辈') ? generationName : `${generationName}字辈`) : '字辈待补', sex, sexLabel, birthDate, birthLunar, birthLunarLabel, deathDate, deathLunar, deathLunarLabel, years: birthDate || deathDate ? `${birthDate}—${deathDate}` : '生卒待补', birthplace: birthPlace, zodiac: zodiacOption.label, zodiacCode: zodiacOption.value, zodiacOptionState: zodiacOption.state, currentAddress, mobile, email, education: educationOption.label, educationCode: educationOption.value, educationOptionState: educationOption.state, occupation, deathAge, deathPlace, deathType: deathExpressionOption.label, deathExpressionCode: deathExpressionOption.value, deathExpressionOptionState: deathExpressionOption.state, burialDate, burialPlace, spouseNames, personStatus, personStatusLabel, biography, remark, canManageSensitiveMedicalHistory, relationName, sortOrder, status: personStatus === '1' ? 'deceased' : 'normal', canDisable: value.canDisable === true, disabledReason: normalizeLineagePersonText(value.disabledReason, '不可停用原因'), canCreateDocument: value.canCreateDocument === true, canManageDocuments: value.canManageDocuments === true, relatives } } export const normalizeLineageSensitiveProfile = (value, expectedPersonId) => { if (!value || typeof value !== 'object' || Array.isArray(value)) { throw lineagePersonError('成员敏感健康资料响应不是对象') } const personId = normalizeLineagePersonIdentity(value.personId, '敏感健康资料人物标识') if (personId !== expectedPersonId) { throw lineagePersonError('成员敏感健康资料响应标识与请求不匹配') } if (typeof value.present !== 'boolean' || typeof value.canManage !== 'boolean') { throw lineagePersonError('成员敏感健康资料状态无效') } const hereditaryMedicalHistory = normalizeLineagePersonText( value.hereditaryMedicalHistory, '遗传病史' ) if (!value.present && hereditaryMedicalHistory) { throw lineagePersonError('成员敏感健康资料状态与正文不一致') } return { personId, hereditaryMedicalHistory, present: value.present, canManage: value.canManage, updatedAt: normalizeLineagePersonText(value.updatedAt, '敏感健康资料更新时间') } } export const normalizeLineagePersonPage = (value, expectedGenealogyId) => { if (!value || typeof value !== 'object' || Array.isArray(value)) { throw lineagePersonError('成员分页响应不是对象') } if (!Array.isArray(value.rows)) throw lineagePersonError('成员分页缺少 rows') if (!Number.isSafeInteger(value.total) || value.total < 0) { throw lineagePersonError('成员分页 total 无效') } const rows = value.rows.map((item) => normalizeLineagePersonDetail( item, expectedGenealogyId, normalizeLineagePersonIdentity(item?.personId, '人物标识') )) if (new Set(rows.map((person) => person.id)).size !== rows.length) { throw lineagePersonError('成员分页包含重复人物标识') } return { rows, total: value.total } } export const normalizeLineagePersonOptions = (value, expectedGenealogyId) => { if (!Array.isArray(value)) throw lineagePersonError('世系人物选项响应不是列表') const options = value.map((item) => { if (!item || typeof item !== 'object' || Array.isArray(item)) { throw lineagePersonError('世系人物选项包含无效条目') } const id = normalizeLineagePersonIdentity(item.personId, '人物标识') const genealogyId = normalizeLineagePersonIdentity(item.genealogyId, '家谱标识') if (genealogyId !== expectedGenealogyId) { throw lineagePersonError('世系人物选项归属与请求不匹配') } return { id, name: normalizeLineagePersonText(item.name, '姓名', { required: true }), personNo: normalizeLineagePersonText(item.personNo, '人物编号'), generation: Number.isSafeInteger(item.generation) && item.generation > 0 ? item.generation : null, generationName: normalizeLineagePersonText(item.generationName, '字辈') } }) if (new Set(options.map((person) => person.id)).size !== options.length) { throw lineagePersonError('世系人物选项包含重复人物标识') } return options }