301 lines
14 KiB
JavaScript
301 lines
14 KiB
JavaScript
import { createRequestError } from './request-client.js'
|
|
import {
|
|
normalizeNormalDisableResponseStatus,
|
|
normalizeOptionalCurrencyAmount,
|
|
normalizeOptionalNonnegativeInteger,
|
|
normalizeOptionalNumericId,
|
|
normalizeResponseText
|
|
} from './response-normalizers.js'
|
|
import {
|
|
assertPlainPayload,
|
|
normalizeOptionalCurrencyNumber,
|
|
normalizeOptionalNormalDisableStatus,
|
|
normalizeOptionalSafeInteger,
|
|
normalizeOptionalText,
|
|
normalizeOssIdString,
|
|
normalizeResourcePathId
|
|
} from './request-normalizers.js'
|
|
import {
|
|
normalizeBusinessFileAccess,
|
|
normalizeResourceCapabilities
|
|
} from './business-file-contract.js'
|
|
import { normalizeGenealogyPathId } from './genealogy-contract.js'
|
|
import { normalizeBusinessOptionProjection } from './business-dictionary-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
|
|
}
|
|
|
|
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')
|
|
}
|
|
const ceremonyType = normalizeBusinessOptionProjection(
|
|
value.ceremonyType,
|
|
value.ceremonyTypeLabel,
|
|
value.ceremonyTypeOptionState,
|
|
'礼仪活动类型',
|
|
'CEREMONY_RESPONSE_INVALID'
|
|
)
|
|
return {
|
|
id,
|
|
genealogyId,
|
|
title: normalizeResponseText(value.ceremonyTitle, 'ceremonyTitle') || '未命名活动',
|
|
type: ceremonyType.value,
|
|
typeLabel: ceremonyType.label,
|
|
typeOptionState: ceremonyType.state,
|
|
time: normalizeResponseText(value.ceremonyTime, 'ceremonyTime'),
|
|
createTime: normalizeResponseText(value.createTime, 'createTime'),
|
|
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 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
|
|
}
|
|
|
|
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 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('请填写活动类型和活动标题')
|
|
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 (Object.prototype.hasOwnProperty.call(payload, 'coverOssId')) {
|
|
if (payload.coverOssId === null) normalizedPayload.coverOssId = null
|
|
else if (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 = normalizeOptionalCurrencyNumber(payload.giftAmount, '献礼金额')
|
|
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')
|
|
}
|
|
})
|
|
}
|