Files
jiapuapp/services/api/lineage-person-contract.js
T

225 lines
9.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { normalizeOptionalSafeInteger } from './request-normalizers.js'
import { createRequestError } from './request-client.js'
import { LINEAGE_PERSON_OPTIONS } from './lineage-person-options.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 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 deathPlace = normalizeLineagePersonText(value.deathPlace, '逝世地')
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 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,
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,
deathPlace,
burialPlace,
spouseNames,
personStatus,
personStatusLabel,
biography,
remark,
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 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
}