360 lines
15 KiB
JavaScript
360 lines
15 KiB
JavaScript
import { normalizeOptionalSafeInteger } from './request-normalizers.js'
|
||
import { createRequestError } from './request-client.js'
|
||
import { normalizeOptionalNumericId } from './response-normalizers.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')
|
||
|
||
const lineageRankError = (message) =>
|
||
createRequestError(message, 'LINEAGE_RANK_RESPONSE_INVALID')
|
||
|
||
const normalizeLineageRankText = (value, label) => {
|
||
if (typeof value !== 'string' || !value.trim()) {
|
||
throw lineageRankError(`世系排行选项缺少${label}`)
|
||
}
|
||
return value.trim()
|
||
}
|
||
|
||
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, '关系显示名称')
|
||
const rankId = normalizeOptionalNumericId(
|
||
value.rankId,
|
||
'成员排行标识',
|
||
'LINEAGE_PERSON_RESPONSE_INVALID'
|
||
)
|
||
const rankName = normalizeLineagePersonText(value.rankName, '排行名称')
|
||
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,
|
||
rankId,
|
||
rankName,
|
||
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 normalizeLineageRankOptions = (value) => {
|
||
if (!Array.isArray(value)) throw lineageRankError('世系排行选项响应不是列表')
|
||
const rankIds = new Set()
|
||
return value.map((option) => {
|
||
if (!option || typeof option !== 'object' || Array.isArray(option)) {
|
||
throw lineageRankError('世系排行选项包含无效记录')
|
||
}
|
||
const rankId = normalizeOptionalNumericId(
|
||
option.rankId,
|
||
'世系排行标识',
|
||
'LINEAGE_RANK_RESPONSE_INVALID'
|
||
)
|
||
if (!rankId || rankIds.has(rankId)) {
|
||
throw lineageRankError(rankId ? '世系排行选项包含重复标识' : '世系排行选项缺少标识')
|
||
}
|
||
rankIds.add(rankId)
|
||
const rankCode = normalizeLineageRankText(option.rankCode, '排行编码')
|
||
const rankName = normalizeLineageRankText(option.rankName, '排行名称')
|
||
const rankType = normalizeLineageRankText(option.rankType, '排行类型')
|
||
const genderScope = normalizeLineageRankText(option.genderScope, '排行适用性别')
|
||
if (!['ANCESTOR', 'GENERATION'].includes(rankType)) {
|
||
throw lineageRankError('世系排行选项类型无效')
|
||
}
|
||
if (!['0', '1', '2'].includes(genderScope)) {
|
||
throw lineageRankError('世系排行选项适用性别无效')
|
||
}
|
||
const rankOrder = normalizeOptionalSafeInteger(option.rankOrder, '排行顺序')
|
||
if (rankOrder !== undefined && rankOrder < 0) {
|
||
throw lineageRankError('世系排行选项顺序无效')
|
||
}
|
||
return { rankId, rankCode, rankName, rankType, genderScope, rankOrder }
|
||
})
|
||
}
|
||
|
||
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
|
||
}
|