326 lines
15 KiB
JavaScript
326 lines
15 KiB
JavaScript
import { createRequestError } from './request-client.js'
|
|
import {
|
|
normalizeNormalDisableResponseStatus,
|
|
normalizeOptionalCurrencyAmount,
|
|
normalizeOptionalNonnegativeInteger,
|
|
normalizeOptionalNumericId,
|
|
normalizeResponseText
|
|
} from './response-normalizers.js'
|
|
import {
|
|
assertPlainPayload,
|
|
normalizeOptionalNormalDisableStatus,
|
|
normalizeOptionalSafeInteger,
|
|
normalizeOptionalText,
|
|
normalizeOssIdString,
|
|
normalizeResourcePathId
|
|
} from './request-normalizers.js'
|
|
import {
|
|
normalizeBusinessFileAccess,
|
|
normalizeResourceCapabilities
|
|
} from './business-file-contract.js'
|
|
import { normalizeGenealogyPathId } from './genealogy-contract.js'
|
|
|
|
const normalizeAppCeremonyCoordinate = (value, field) => {
|
|
if (value === undefined || value === null || value === '') return null
|
|
const coordinate = typeof value === 'number' ? value : Number(value)
|
|
if (!Number.isFinite(coordinate)) {
|
|
throw createRequestError(`礼仪活动响应字段 ${field} 无效`, 'CEREMONY_RESPONSE_INVALID')
|
|
}
|
|
return coordinate
|
|
}
|
|
|
|
const CEREMONY_TYPE_LABELS = Object.freeze({
|
|
ancestor: '祭祖',
|
|
memorial: '纪念',
|
|
funeral: '丧礼',
|
|
wedding: '婚礼',
|
|
birth: '出生宴',
|
|
birthday: '生日宴',
|
|
banquet: '宴席',
|
|
other: '其他'
|
|
})
|
|
const ceremonyTypes = new Set(Object.keys(CEREMONY_TYPE_LABELS))
|
|
|
|
export const CEREMONY_INVITATION_STATUS = Object.freeze({
|
|
PENDING: 'PENDING',
|
|
ACCEPTED: 'ACCEPTED',
|
|
DECLINED: 'DECLINED',
|
|
CANCELED: 'CANCELED'
|
|
})
|
|
|
|
export const CEREMONY_INVITATION_STATUS_LABELS = Object.freeze({
|
|
[CEREMONY_INVITATION_STATUS.PENDING]: '等待回应',
|
|
[CEREMONY_INVITATION_STATUS.ACCEPTED]: '已接受',
|
|
[CEREMONY_INVITATION_STATUS.DECLINED]: '已拒绝',
|
|
[CEREMONY_INVITATION_STATUS.CANCELED]: '已取消'
|
|
})
|
|
|
|
const ceremonyInvitationStatuses = new Set(
|
|
Object.values(CEREMONY_INVITATION_STATUS)
|
|
)
|
|
const ceremonyInvitationResponseStatuses = new Set([
|
|
CEREMONY_INVITATION_STATUS.ACCEPTED,
|
|
CEREMONY_INVITATION_STATUS.DECLINED
|
|
])
|
|
|
|
export const normalizeAppCeremony = (value, expectedGenealogyId, expectedCeremonyId = '') => {
|
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
throw createRequestError('礼仪活动响应无效', 'CEREMONY_RESPONSE_INVALID')
|
|
}
|
|
const id = normalizeOptionalNumericId(value.ceremonyId, '礼仪活动标识', 'CEREMONY_RESPONSE_INVALID')
|
|
const genealogyId = normalizeOptionalNumericId(value.genealogyId, '礼仪活动家谱标识', 'CEREMONY_RESPONSE_INVALID')
|
|
if (!id || genealogyId !== expectedGenealogyId || (expectedCeremonyId && id !== expectedCeremonyId)) {
|
|
throw createRequestError('礼仪活动响应缺少稳定归属字段', 'CEREMONY_RESPONSE_INVALID')
|
|
}
|
|
const location = normalizeResponseText(value.location, 'location')
|
|
const locationAddress = normalizeResponseText(value.locationAddress, 'locationAddress')
|
|
const longitude = normalizeAppCeremonyCoordinate(value.longitude, 'longitude')
|
|
const latitude = normalizeAppCeremonyCoordinate(value.latitude, 'latitude')
|
|
if ((longitude === null) !== (latitude === null)) {
|
|
throw createRequestError('礼仪活动响应的经纬度必须同时存在或同时为空', 'CEREMONY_RESPONSE_INVALID')
|
|
}
|
|
return {
|
|
id,
|
|
genealogyId,
|
|
title: normalizeResponseText(value.ceremonyTitle, 'ceremonyTitle') || '未命名活动',
|
|
type: normalizeResponseText(value.ceremonyType, 'ceremonyType'),
|
|
time: normalizeResponseText(value.ceremonyTime, 'ceremonyTime'),
|
|
location,
|
|
locationAddress,
|
|
longitude,
|
|
latitude,
|
|
description: normalizeResponseText(value.ceremonyDesc, 'ceremonyDesc'),
|
|
coverFile: normalizeBusinessFileAccess(value.coverFile, '礼仪活动封面', 'CEREMONY_RESPONSE_INVALID'),
|
|
giftCount: normalizeOptionalNonnegativeInteger(value.giftCount, '礼仪献礼数量', 'CEREMONY_RESPONSE_INVALID'),
|
|
sortOrder: normalizeOptionalNonnegativeInteger(value.sortOrder, '礼仪活动排序值', 'CEREMONY_RESPONSE_INVALID'),
|
|
status: normalizeNormalDisableResponseStatus(value.status, '礼仪活动状态', 'CEREMONY_RESPONSE_INVALID'),
|
|
...normalizeResourceCapabilities(value, '礼仪活动', 'CEREMONY_RESPONSE_INVALID')
|
|
}
|
|
}
|
|
|
|
export const normalizeAppCeremonyWithTypeLabel = (ceremony) => {
|
|
const typeLabel = CEREMONY_TYPE_LABELS[ceremony.type]
|
|
if (!ceremony.type || !typeLabel) {
|
|
throw createRequestError('礼仪活动响应包含未公开的活动类型', 'CEREMONY_RESPONSE_INVALID')
|
|
}
|
|
return { ...ceremony, typeLabel }
|
|
}
|
|
|
|
export const normalizeAppCeremonies = (value, expectedGenealogyId) => {
|
|
if (!Array.isArray(value)) throw createRequestError('礼仪活动响应不是列表', 'CEREMONY_RESPONSE_INVALID')
|
|
const ceremonies = value.map((item) => normalizeAppCeremony(item, expectedGenealogyId))
|
|
if (new Set(ceremonies.map((item) => item.id)).size !== ceremonies.length) {
|
|
throw createRequestError('礼仪活动响应包含重复标识', 'CEREMONY_RESPONSE_INVALID')
|
|
}
|
|
return ceremonies.map((item) => normalizeAppCeremonyWithTypeLabel(item))
|
|
}
|
|
|
|
export const normalizeAppCeremonyGift = (value, expectedGenealogyId, expectedCeremonyId) => {
|
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
throw createRequestError('礼仪献礼响应无效', 'CEREMONY_GIFT_RESPONSE_INVALID')
|
|
}
|
|
const id = normalizeOptionalNumericId(value.giftId, '献礼标识', 'CEREMONY_GIFT_RESPONSE_INVALID')
|
|
const genealogyId = normalizeOptionalNumericId(value.genealogyId, '献礼家谱标识', 'CEREMONY_GIFT_RESPONSE_INVALID')
|
|
const ceremonyId = normalizeOptionalNumericId(value.ceremonyId, '献礼活动标识', 'CEREMONY_GIFT_RESPONSE_INVALID')
|
|
if (!id || genealogyId !== expectedGenealogyId || ceremonyId !== expectedCeremonyId) {
|
|
throw createRequestError('礼仪献礼响应缺少稳定归属字段', 'CEREMONY_GIFT_RESPONSE_INVALID')
|
|
}
|
|
return {
|
|
id,
|
|
genealogyId,
|
|
ceremonyId,
|
|
giverName: normalizeResponseText(value.giverName, 'giverName'),
|
|
giverNickName: normalizeResponseText(value.giverNickName, 'giverNickName'),
|
|
amount: normalizeOptionalCurrencyAmount(value.giftAmount, '献礼金额', 'CEREMONY_GIFT_RESPONSE_INVALID', { required: true }),
|
|
message: normalizeResponseText(value.giftMessage, 'giftMessage'),
|
|
time: normalizeResponseText(value.giftTime, 'giftTime'),
|
|
...normalizeResourceCapabilities(value, '礼仪献礼', 'CEREMONY_GIFT_RESPONSE_INVALID')
|
|
}
|
|
}
|
|
|
|
export const normalizeAppCeremonyGifts = (value, expectedGenealogyId, expectedCeremonyId) => {
|
|
if (!Array.isArray(value)) throw createRequestError('礼仪献礼响应不是列表', 'CEREMONY_GIFT_RESPONSE_INVALID')
|
|
const gifts = value.map((item) => normalizeAppCeremonyGift(item, expectedGenealogyId, expectedCeremonyId))
|
|
if (new Set(gifts.map((item) => item.id)).size !== gifts.length) {
|
|
throw createRequestError('礼仪献礼响应包含重复标识', 'CEREMONY_GIFT_RESPONSE_INVALID')
|
|
}
|
|
return gifts
|
|
}
|
|
|
|
export const projectPreviewCeremonies = (value, expectedGenealogyId) =>
|
|
value.map((item) => ({
|
|
id: String(item.ceremonyId),
|
|
genealogyId: expectedGenealogyId,
|
|
title: String(item.ceremonyTitle || '未命名活动'),
|
|
type: String(item.ceremonyType || ''),
|
|
typeLabel: CEREMONY_TYPE_LABELS[String(item.ceremonyType || '')] || '',
|
|
time: String(item.ceremonyTime || ''),
|
|
location: String(item.location || ''),
|
|
description: String(item.ceremonyDesc || ''),
|
|
coverFile: null,
|
|
giftCount: Number.isSafeInteger(item.giftCount) ? item.giftCount : 0,
|
|
canEdit: false,
|
|
canDelete: false
|
|
}))
|
|
|
|
export const normalizeCeremonyPayload = (payload) => {
|
|
const allowedFields = new Set(['ceremonyType', 'ceremonyTitle', 'ceremonyDesc', 'ceremonyTime', 'location', 'locationAddress', 'longitude', 'latitude', 'coverOssId', 'sortOrder', 'status'])
|
|
assertPlainPayload(payload, allowedFields, '礼仪活动请求')
|
|
const ceremonyType = normalizeOptionalText(payload.ceremonyType, 'ceremonyType')
|
|
const ceremonyTitle = normalizeOptionalText(payload.ceremonyTitle, 'ceremonyTitle')
|
|
if (!ceremonyType || !ceremonyTitle) throw new TypeError('请填写活动类型和活动标题')
|
|
if (!ceremonyTypes.has(ceremonyType)) throw new TypeError('请选择有效的活动类型')
|
|
const normalizedPayload = { ceremonyType, ceremonyTitle }
|
|
for (const field of ['ceremonyDesc', 'ceremonyTime', 'location', 'locationAddress']) {
|
|
const normalizedText = normalizeOptionalText(payload[field], field)
|
|
if (normalizedText) normalizedPayload[field] = normalizedText
|
|
}
|
|
const status = normalizeOptionalNormalDisableStatus(payload.status, '礼仪活动状态')
|
|
if (status) normalizedPayload.status = status
|
|
const hasLongitude = payload.longitude !== undefined && payload.longitude !== null && payload.longitude !== ''
|
|
const hasLatitude = payload.latitude !== undefined && payload.latitude !== null && payload.latitude !== ''
|
|
if (hasLongitude !== hasLatitude) throw new TypeError('longitude 和 latitude 必须同时提供')
|
|
if (hasLongitude) {
|
|
const longitude = typeof payload.longitude === 'number' ? payload.longitude : Number(payload.longitude)
|
|
const latitude = typeof payload.latitude === 'number' ? payload.latitude : Number(payload.latitude)
|
|
if (!Number.isFinite(longitude) || !Number.isFinite(latitude)) {
|
|
throw new TypeError('longitude 和 latitude 必须是有限数字')
|
|
}
|
|
normalizedPayload.longitude = longitude
|
|
normalizedPayload.latitude = latitude
|
|
}
|
|
if (payload.coverOssId !== undefined && payload.coverOssId !== null && payload.coverOssId !== '') {
|
|
normalizedPayload.coverOssId = normalizeOssIdString(payload.coverOssId, 'coverOssId')
|
|
}
|
|
const sortOrder = normalizeOptionalSafeInteger(payload.sortOrder, 'sortOrder')
|
|
if (sortOrder !== undefined) {
|
|
normalizedPayload.sortOrder = sortOrder
|
|
}
|
|
return normalizedPayload
|
|
}
|
|
|
|
export const normalizeCeremonyGiftPayload = (payload) => {
|
|
const allowedFields = new Set(['giverName', 'giftAmount', 'giftMessage'])
|
|
assertPlainPayload(payload, allowedFields, '礼仪献礼请求')
|
|
if (payload.giftAmount === undefined || payload.giftAmount === null || payload.giftAmount === '') {
|
|
throw new TypeError('请填写献礼金额')
|
|
}
|
|
const giftAmount = typeof payload.giftAmount === 'number' ? payload.giftAmount : Number(payload.giftAmount)
|
|
if (!Number.isFinite(giftAmount)) throw new TypeError('献礼金额必须是有限数字')
|
|
const normalizedPayload = { giftAmount }
|
|
for (const field of ['giverName', 'giftMessage']) {
|
|
const normalizedText = normalizeOptionalText(payload[field], field)
|
|
if (normalizedText) normalizedPayload[field] = normalizedText
|
|
}
|
|
return normalizedPayload
|
|
}
|
|
|
|
export const normalizeCeremonyInviteePayload = (payload) => {
|
|
assertPlainPayload(payload, new Set(['inviteeUserIds']), '活动受邀人请求')
|
|
if (!Array.isArray(payload.inviteeUserIds)) {
|
|
throw new TypeError('inviteeUserIds必须是数组')
|
|
}
|
|
const inviteeUserIds = payload.inviteeUserIds.map((value) =>
|
|
normalizeResourcePathId(value, '受邀业务用户标识')
|
|
)
|
|
if (new Set(inviteeUserIds).size !== inviteeUserIds.length) {
|
|
throw new TypeError('inviteeUserIds不能包含重复标识')
|
|
}
|
|
return { inviteeUserIds }
|
|
}
|
|
|
|
export const normalizeCeremonyInvitationResponsePayload = (payload) => {
|
|
assertPlainPayload(payload, new Set(['inviteStatus']), '活动邀请响应请求')
|
|
if (!ceremonyInvitationResponseStatuses.has(payload.inviteStatus)) {
|
|
throw new TypeError('inviteStatus仅允许ACCEPTED或DECLINED')
|
|
}
|
|
return { inviteStatus: payload.inviteStatus }
|
|
}
|
|
|
|
const normalizeCeremonyInvitationText = (value, field) => {
|
|
if (value === undefined || value === null) return ''
|
|
if (typeof value !== 'string') {
|
|
throw createRequestError(`活动邀请字段 ${field} 无效`, 'CEREMONY_INVITATION_RESPONSE_INVALID')
|
|
}
|
|
return value.trim()
|
|
}
|
|
|
|
export const normalizeCeremonyInvitationRows = (value, expectedGenealogyId = '') => {
|
|
if (!Array.isArray(value)) {
|
|
throw createRequestError('活动邀请响应不是数组', 'CEREMONY_INVITATION_RESPONSE_INVALID')
|
|
}
|
|
const seenIds = new Set()
|
|
return value.map((item) => {
|
|
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
throw createRequestError('活动邀请包含无效条目', 'CEREMONY_INVITATION_RESPONSE_INVALID')
|
|
}
|
|
const id = normalizeResourcePathId(item.invitationId, '活动邀请标识')
|
|
if (seenIds.has(id)) {
|
|
throw createRequestError('活动邀请包含重复标识', 'CEREMONY_INVITATION_RESPONSE_INVALID')
|
|
}
|
|
seenIds.add(id)
|
|
const genealogyId = normalizeGenealogyPathId(item.genealogyId)
|
|
if (expectedGenealogyId && genealogyId !== expectedGenealogyId) {
|
|
throw createRequestError('活动邀请家谱归属与请求不匹配', 'CEREMONY_INVITATION_RESPONSE_INVALID')
|
|
}
|
|
const ceremonyId = normalizeResourcePathId(item.ceremonyId, '礼仪活动标识')
|
|
const userKey = normalizeResourcePathId(item.inviteeUserId, '受邀业务用户标识')
|
|
const inviteStatus = normalizeCeremonyInvitationText(item.inviteStatus, 'inviteStatus')
|
|
if (!ceremonyInvitationStatuses.has(inviteStatus)) {
|
|
throw createRequestError('活动邀请状态无效', 'CEREMONY_INVITATION_RESPONSE_INVALID')
|
|
}
|
|
const inviteVersion = item.inviteVersion
|
|
if (inviteVersion !== undefined && (!Number.isSafeInteger(inviteVersion) || inviteVersion < 1)) {
|
|
throw createRequestError('活动邀请版本无效', 'CEREMONY_INVITATION_RESPONSE_INVALID')
|
|
}
|
|
return {
|
|
id,
|
|
genealogyId,
|
|
ceremonyId,
|
|
userKey,
|
|
inviteStatus,
|
|
inviteVersion: inviteVersion === undefined ? null : inviteVersion,
|
|
deliveredTime: normalizeCeremonyInvitationText(item.deliveredTime, 'deliveredTime'),
|
|
readTime: normalizeCeremonyInvitationText(item.readTime, 'readTime'),
|
|
responseTime: normalizeCeremonyInvitationText(item.responseTime, 'responseTime'),
|
|
ceremonyTitle: normalizeCeremonyInvitationText(item.ceremonyTitle, 'ceremonyTitle'),
|
|
ceremonyTime: normalizeCeremonyInvitationText(item.ceremonyTime, 'ceremonyTime'),
|
|
location: normalizeCeremonyInvitationText(item.location, 'location'),
|
|
locationAddress: normalizeCeremonyInvitationText(item.locationAddress, 'locationAddress')
|
|
}
|
|
})
|
|
}
|
|
|
|
export const normalizeCeremonyInviteeOptions = (value) => {
|
|
if (!Array.isArray(value)) {
|
|
throw createRequestError('活动受邀候选响应不是数组', 'CEREMONY_INVITEE_OPTIONS_RESPONSE_INVALID')
|
|
}
|
|
const seenUserIds = new Set()
|
|
return value.map((item) => {
|
|
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
throw createRequestError('活动受邀候选包含无效条目', 'CEREMONY_INVITEE_OPTIONS_RESPONSE_INVALID')
|
|
}
|
|
const memberId = normalizeResourcePathId(item.memberId, '受邀成员标识')
|
|
const appUserId = normalizeResourcePathId(item.appUserId, '受邀业务用户标识')
|
|
if (seenUserIds.has(appUserId)) {
|
|
throw createRequestError('活动受邀候选包含重复业务用户', 'CEREMONY_INVITEE_OPTIONS_RESPONSE_INVALID')
|
|
}
|
|
seenUserIds.add(appUserId)
|
|
const displayName = normalizeCeremonyInvitationText(item.displayName, 'displayName')
|
|
if (!displayName) {
|
|
throw createRequestError('活动受邀候选缺少显示名称', 'CEREMONY_INVITEE_OPTIONS_RESPONSE_INVALID')
|
|
}
|
|
if (typeof item.eligible !== 'boolean') {
|
|
throw createRequestError('活动受邀候选 eligible 无效', 'CEREMONY_INVITEE_OPTIONS_RESPONSE_INVALID')
|
|
}
|
|
return {
|
|
memberId,
|
|
appUserId,
|
|
displayName,
|
|
memberRole: normalizeCeremonyInvitationText(item.memberRole, 'memberRole'),
|
|
eligible: item.eligible,
|
|
disabledReason: normalizeCeremonyInvitationText(item.disabledReason, 'disabledReason')
|
|
}
|
|
})
|
|
}
|