Files
jiapuapp/services/api/ceremony-contract.js

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')
}
})
}